# frozen_string_literal: true
#
# Copyright 2013 whiteleaf. All rights reserved.
#
require "optparse"
require "termcolorlight"
# help をログに記録しないために STDOUT に直接出力する
OptionParser::Officious["help"] = proc do |parser|
OptionParser::Switch::NoArgument.new do |_arg|
STDOUT.puts parser.help
exit
end
end
module Command
class CommandBase
attr_accessor :stream_io
# postfixies は改行で区切ることで2パターン以上記述できる
def initialize(postfixies = " ")
self.stream_io = $stdout
@opt = OptionParser.new(nil, 20)
command_name = self.class.to_s.scan(/::(.+)$/)[0][0].downcase
banner = postfixies.split("\n").map.with_index { |postfix, i|
(i == 0 ? "Usage: " : " or: ") + "narou #{command_name} #{postfix}"
}.join("\n")
@opt.banner = "#{TermColorLight.escape(banner)}".termcolor
@options = {}
# ヘルプを見やすく色付け
def @opt.help
msg = super
# 見出し部分
msg.gsub!(/((?:Examples|Options|Configuration|[^\s]+? Variable List):)/) do
"#{$1}".termcolor
end
# Examples のコメント部分
msg.gsub!(/(#.+)$/) do
"#{TermColorLight.escape($1)}".termcolor
end
# 文字列部分
msg.gsub!(/(".+?")/) do
"#{TermColorLight.escape($1)}".termcolor
end
msg
end
end
def display_help!
STDOUT.puts @opt.help
exit
end
def execute(argv)
@options.clear
load_local_settings
@opt.parse!(argv)
rescue OptionParser::InvalidOption => e
error "不明なオプションです(#{e})"
exit Narou::EXIT_ERROR_CODE
rescue OptionParser::InvalidArgument => e
error "オプションの引数が正しくありません(#{e})"
exit Narou::EXIT_ERROR_CODE
rescue OptionParser::MissingArgument => e
error "オプションの引数が指定されていないか正しくありません(#{e})"
exit Narou::EXIT_ERROR_CODE
rescue OptionParser::AmbiguousOption => e
error "曖昧な省略オプションです(#{e})"
exit Narou::EXIT_ERROR_CODE
end
def load_local_settings
command_prefix = self.class.to_s.scan(/[^:]+$/)[0].downcase
local_settings = Inventory.load("local_setting")
local_settings.each do |name, value|
if name =~ /^#{command_prefix}\.(.+)$/
@options[$1] = value
end
end
end
#
# タグ情報をID情報に展開する
#
def tagname_to_ids(array)
database = Database.instance
tag_index = database.tag_indexies
all_ids = database.ids
expanded_array = array.map { |arg|
if arg.to_s =~ /\A\d+\z/
# 優先度はID>タグのため、数字のみ指定されたら
# そのIDが存在した場合はIDとみなす
id = arg.to_i
next id if database[id]
end
ids =
case arg
when /\Atag:(.+)\z/
# tag:タグ名 は直接タグと指定できる形式
# (数字タグとIDがかぶった場合にタグを指定出来るようにするもの)
arg = $1
tag_index[$1]
when /\A\^tag:(.+)\z/
# ^tag:タグ名 は除外タグ指定
arg = $1
indexies = tag_index[$1]
indexies.empty? ? [] : all_ids - indexies
else
tag_index[arg]
end
ids.empty? ? arg : ids
}.flatten.uniq
array.replace(expanded_array)
end
#
# コマンドを実行するが、アプリケーションは終了させない
# (SystemExit を補足し、終了コードを返り値とする)
#
def execute!(*argv, io: $stdout)
self.stream_io = io
argv.flatten!
execute(argv)
rescue SystemExit => e
e.status
else
0
end
def self.execute!(*argv, io: $stdout)
cmd = new
cmd.execute!(*argv, io: io)
end
def self.oneline_help
raise "implement #{self}.oneline_help"
end
#
# 指定したメソッドを呼び出す際に、フック関数があればそれ経由で呼ぶ
#
# 指定したメソッドは存在しなくてもいい。存在しなければ空のProcが作られる
#
def hook_call(target_method, *argv)
hook = "hook_#{target_method}"
target_method_proc = self.method(target_method) rescue ->{}
if respond_to?(hook)
self.__send__(hook, *argv, &target_method_proc)
else
target_method_proc.call(*argv)
end
end
#
# 設定の強制設定
#
def force_change_settings_function(pairs)
settings = Inventory.load("local_setting")
modified = false
pairs.each do |name, value|
if settings[name].nil? || settings[name] != value
settings[name] = value
puts "#{name} を #{value} に強制変更しました".termcolor
modified = true
end
end
settings.save if modified
end
#
# コマンド出力のログ保存を抑制する
#
# コマンドの中で、stream_io に対して出力している必要がある
#
def disable_logging
self.stream_io = stream_io.dup_with_disabled_logging
end
end
end