#! /usr/bin/ruby -E:UTF-8
# -*- mode:Ruby; tab-width:4; coding:UTF-8; -*-
# vi:set ft=ruby ts=4 fenc=UTF-8 :
#----------------------------------------------------------------
# ショートカットを作成する
#
# 2009/06/17 opa
#----------------------------------------------------------------

require 'optparse'
require 'win32ole'

ProgName = 'MakeShortcut'
Version = '1.03'

#=====dpk===== File.delete_if_exist
# ファイルがもし存在したら削除する
class << File
	def unlink_if_exist(filename)
		begin
			File.delete(filename)
		rescue Errno::ENOENT
			# NOP(ignore)
		end
	end

	alias :delete_if_exist :unlink_if_exist 
end

#=====dpk=====

#=====dpk===== File.unify_alt_separator
# ALT_SEPARATORがある環境では、SEPARATOR を ALT_SEPARATOR に統一する
if !File::ALT_SEPARATOR.nil?
	def File.unify_alt_separator(pathname)
		return pathname.gsub(File::SEPARATOR, File::ALT_SEPARATOR)
	end
else
	def File.unify_alt_separator(pathname)
		return pathname
	end
end

#=====dpk=====

#=====dpk===== LocaleVersion:File.dirname
# Windows環境下かどうか判定する
def os_is_windows
	return (RUBY_PLATFORM =~ /mswin(?!ce)|mingw|cygwin|bccwin/i) ? true : false
end

# Encoding用便利メソッド等
class Encoding
	ZWNBSP = "\ufeff"		# ZWNBSP(BOM)

	# あるオブジェクトのエンコードを変更する(Array,Hashの中も変更する)
	def self.change(obj, encoding=default_internal)
		case
		 when obj.respond_to?(:encode)
			if obj.method(:encode).arity == 1
				return obj.encode(encoding)
			else
				return obj.encode(encoding, {:invalid => :replace, :undef => :replace})
			end
		 when obj.is_a?(Array)
			return obj.clone.map! do |v|
				change(v, encoding)
			end
		 when obj.is_a?(Hash)
			tmp = obj.clone.clear
			obj.each do |k, v|
				tmp[change(k, encoding)] = change(v, encoding)
			end
			return tmp
		end
		return obj # case else
	end
end

# ロケール対応変換用メソッド
class Encoding
	# 内部エンコーディングに変換する (※任意のオブジェクト)
	# UTF-8等
	def self.to_in(obj)
		return change(obj, default_internal)
	end

	# 外部エンコーディングに変換する (※任意のオブジェクト)
	# Windowsでは、Windows-31J
	def self.to_ex(obj)
		return change(obj, default_external)
	end

	# ファイルシステムエンコーディングに変換する (※任意のオブジェクト)
	# Windowsでは、UTF-16LEだがASCII comaptibleでないといけないためUTF-8
	if os_is_windows
		def self.to_fs(obj)
			return change(obj, 'UTF-8')
		end
	else
		def self.to_fs(obj)
			return change(obj, default_external)		# 外部エンコーディングと同じと仮定
		end
	end

	# 内部エンコーディングから外部エンコーディングに変換する (※String専用)
	def self.in_to_ex(s)
		return s.encode(default_external, default_internal, {:invalid => :replace, :undef => :replace})
	end

	# 外部エンコーディングから内部エンコーディングに変換する (※String専用)
	def self.ex_to_in(s)
		return s.encode(default_internal, default_external, {:invalid => :replace, :undef => :replace})
	end
end

# エンコーディングを考慮した File.dirname
# (org: IN:OK[U],NG[W] OUT:NG[U,W])
class << File
	alias :dirname_verbatim :dirname  if !respond_to?(:dirname_verbatim)

	if os_is_windows
		def dirname(filename)
			ss = Regexp.escape(File::SEPARATOR + File::ALT_SEPARATOR.to_s)
			filename = Encoding.to_in(filename)

			# ドライブ名とそれ以降に分ける
			if filename =~ /^([a-z]:)(.*)$/i
				drive, filename = $1, $2
			else
				drive = ''
			end

			# 連続するパス区切りは1つにまとめる
			filename.gsub!(/([#{ss}])[#{ss}]+/, '\1')

			# パス区切りで終わっている場合は取り去る
			if filename =~ /^.+[#{ss}]$/
				filename.chop!
			end

			# 最後のコンポーネントまでを取り出す
			if filename =~ /^(.*[#{ss}])/
				# パス区切りで終わっている場合は取り去る ('/'1文字の場合除く)
				if $1.size > 1
					return drive + $1.chop
				end

				return drive + $1
			end

			return drive + '.'
		end
	else
		def dirname(filename)
			ival = Encoding.to_fs(filename)
			oval = dirname_verbatim(ival)
			return Encoding.to_in(oval)
		end
	end
end

#=====dpk=====

#=====dpk===== LocaleVersion:File.extname
# エンコーディングを考慮した File.extname
# (org: IN:OK[U],NG[W] OUT:NG[U,W])
class << File
	alias :extname_verbatim :extname  if !respond_to?(:extname_verbatim)

	if os_is_windows
		def extname(filename)
			ss = Regexp.escape(File::SEPARATOR + File::ALT_SEPARATOR.to_s)
			filename = Encoding.to_in(filename)

			# ドライブ名を取り除く
			if filename =~ /^([a-z]:)(.*)$/i
				filename = $2
			end

			# 連続するパス区切りは1つにまとめる
			filename.gsub!(/([#{ss}])[#{ss}]+/, '\1')

			# パス区切りで終わっている場合は取り去る
			if filename =~ /^.+[#{ss}]$/
				filename.chop!
			end

			# 最後のコンポーネントを取り出す
			filename =~ /([^#{ss}]*)$/
			filename = $1

			# 先頭の連続するピリオドは取り除く(dot fileは拡張子ではない)
			filename.gsub!(/^\.+/, '')

			# 最後のピリオド以降を取り出す
			if filename =~ /(\.[^.]+)$/
				return $1
			end

			return ''
		end
	else
		def extname(filename)
			ival = Encoding.to_fs(filename)
			oval = extname_verbatim(ival)
			return Encoding.to_in(oval)
		end
	end
end

#=====dpk=====

#=====dpk===== LocaleVersion:File.expand_path
# エンコーディングを考慮した File.expand_path
# (org: IN:? OUT:?)
class << File
	alias :expand_path_verbatim :expand_path  if !respond_to?(:expand_path_verbatim)

	def expand_path(path, default_dir='.')
		oval = expand_path_verbatim(path, default_dir)
		oval = oval.force_encoding(oval.encoding)		# for Issue #5533
		return Encoding.to_in(oval)
	end
end

#=====dpk=====

#=====dpk===== LocaleVersion:File.exist?
# エンコーディングを考慮した File.exist?
# (org: IN:OK[U],NG[W])
class << File
	alias :exist_verbatim? :exist?  if !respond_to?(:exist_verbatim?)

	if os_is_windows
		def exist?(file)
			ival = Encoding.to_fs(file)
			return exist_verbatim?(ival)
		end
	end
	alias :exists? :exist?
end

#=====dpk=====

#=====dpk===== LocaleVersion:File.directory?
# エンコーディングを考慮した File.directory?
# (org: IN:OK[U],NG[W])
class << File
	alias :directory_verbatim? :directory?  if !respond_to?(:directory_verbatim?)

	if os_is_windows
		def directory?(path)
			ival = Encoding.to_fs(path)
			return directory_verbatim?(ival)
		end
	end
end

#=====dpk=====

def var_init()
	$shell = WIN32OLE.new('WScript.Shell')

	$Arguments = nil
	$Description = nil
	$HotKey = nil
	$IconLocation = nil
	$TargetPath = nil
	$Update = false
	$WindowStyle = nil
	$WorkingDirectory = nil
	$Type = nil
end

def make_shortcut(filename)
	if $TargetPath == nil
		$stderr.print("#{ProgName}: error: TargetPath not presented\n")
		return
	end

	case $Type
	  when nil
		if !(File.extname(filename) == '.lnk' || File.extname(filename) == '.url')
			$stderr.printf("#{ProgName}: error: filename must be .lnk or .url: %s\n", filename)
			return
		end
	  else
		if File.extname(filename) != $Type
			filename += $Type
		end
	end

	if !$Update
		File.delete_if_exist(filename)
	end

	shortcut = $shell.CreateShortcut(filename)
	modified = false
	error = false

	if $Arguments != nil
		if shortcut.Arguments != $Arguments
			begin
				shortcut.Arguments = $Arguments
				modified = true
			rescue
				$stderr.printf("#{ProgName}: error: cannot set property: Argument=%s\n", $Arguments)
				error = true
			end
		end
	end

	if $Description != nil
		if shortcut.Description != $Description
			begin
				shortcut.Description = $Description
				modified = true
			rescue
				$stderr.printf("#{ProgName}: error: cannot set property: Description=%s\n", $Description)
				error = true
			end
		end
	end

	if $HotKey != nil
		if $HotKey.casecmp(shortcut.HotKey) != 0
			begin
				shortcut.HotKey = $HotKey
				modified = true
			rescue
				$stderr.printf("#{ProgName}: error: cannot set property: HotKey=%s\n", $HotKey)
				error = true
			end
		end
	end

	if $IconLocation != nil
		iconLocation = File.expand_path($IconLocation)
		iconLocation = File.unify_alt_separator(iconLocation)

		if !File.exist?(iconLocation.gsub(/,\d+$/,''))
			$stderr.printf("#{ProgName}: warning: IconLocation not exist: %s\n", iconLocation)
		end

		if iconLocation.casecmp(shortcut.IconLocation) != 0
			begin
				shortcut.IconLocation = iconLocation
				modified = true
			rescue
				$stderr.printf("#{ProgName}: error: cannot set property: IconLocation=%s\n", iconLocation)
				error = true
			end
		end
	end

	if $TargetPath != nil
		targetPath = File.expand_path($TargetPath)
		targetPath = File.unify_alt_separator(targetPath)

		if !File.exist?(targetPath) && !File.exist?(targetPath + '.exe')
			$stderr.printf("#{ProgName}: warning: TargetPath not exist: %s\n", targetPath)
		end

		if targetPath.casecmp(shortcut.TargetPath) != 0
			begin
				shortcut.TargetPath = targetPath
				modified = true
			rescue
				$stderr.printf("#{ProgName}: error: cannot set property: TargetPath=%s\n", targetPath)
				error = true
			end
		end
	end

	if $WindowStyle != nil
		if shortcut.WindowStyle != $WindowStyle
			begin
				shortcut.WindowStyle = $WindowStyle
				modified = true
			rescue
				$stderr.printf("#{ProgName}: error: cannot set property: WindowStyle=%s\n", $WindowStyle)
				error = true
			end
		end
	end

	if $WorkingDirectory != nil
		if $WorkingDirectory == '@'
			if File.directory?($TargetPath)
				workingDirectory = ""
			else
				workingDirectory = File.dirname(File.expand_path($TargetPath))
				workingDirectory = File.unify_alt_separator(workingDirectory)
			end
		else
			workingDirectory = $WorkingDirectory
		end

		if workingDirectory.length > 0 && !File.exist?(workingDirectory)
			$stderr.printf("#{ProgName}: warning: WorkingDirectory not exist: %s\n", workingDirectory)
		end

		if workingDirectory.casecmp(shortcut.WorkingDirectory) != 0
			begin
				shortcut.WorkingDirectory = workingDirectory
				modified = true
			rescue
				$stderr.printf("#{ProgName}: error: cannot set property: WorkingDirectory=%s\n", workingDirectory)
				error = true
			end
		end
	end

	if error && !$Update
		File.delete_if_exist(filename)
	elsif modified
		shortcut.save
	end
end

def main(argv)
	var_init()

	argv.options do |opt|
		opt.banner = "Usage: #{ProgName} [options] file..."

		opt.on('-a', '--Arguments=arg', String,
			'パラメータ') do |v|
			$Arguments = v
		end
		opt.on('-d', '--Description=text', String,
			'コメント') do |v|
			$Description = v
		end
		opt.on('-h', '--HotKey=key', String, /(\w|\+)+/,
			'ショートカットキー') do |v|
			$HotKey = v[0]
		end
		opt.on('-i', '--IconLocation=filename', String,
			'アイコン') do |v|
			$IconLocation = v
		end
		opt.on('-t', '--TargetPath=filename', String,
			'リンク先') do |v|
			$TargetPath = v
		end
		opt.on('-u', '--Update', TrueClass,
			'新規作成ではなく更新を行う') do |v|
			$Update = v
		end
		opt.on('-s', '--WindowStyle=style', String, /Normal|Maximize|Minimize/i,
			'実行時の大きさ(Normal|Maximize|Minimize)') do |v|
			case v
			  when /Normal/i
				$WindowStyle = 1
			  when /Maximize/i
				$WindowStyle = 3
			  when /Minimize/i
				$WindowStyle = 7
			end
		end
		opt.on('-w', '--WorkingDirectory=dir', String,
			'作業フォルダ') do |v|
			$WorkingDirectory = v
		end
		opt.on('-y', '--Type=type', String, /\.?(lnk|url)/i,
			'ショートカットの種類(.lnk|.url)') do |v|
			case v[0]
			  when /\.?lnk/i
				$Type = '.lnk'
			  when /\.?url/i
				$Type = '.url'
			end
		end

		opt.parse!
		print(opt.help)  if argv.size == 0
	end

	argv.each do |a|
		make_shortcut(a)
	end

	return 0
end

begin
	exit main(ARGV)
rescue RuntimeError => e
	$stderr.printf("#{ProgName}: Error: %s\n", e.message)
end
