-
Notifications
You must be signed in to change notification settings - Fork 264
/
Copy pathcli.rb
206 lines (179 loc) · 5.01 KB
/
cli.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
# frozen_string_literal: true
require 'i18n/tasks'
require 'i18n/tasks/commands'
require 'optparse'
class I18n::Tasks::CLI
include ::I18n::Tasks::Logging
def self.start(argv)
new.start(argv)
end
def initialize; end
def start(argv)
I18n.with_locale(base_task.internal_locale) do
auto_output_coloring do
begin
exit 1 if run(argv) == :exit_1
rescue OptionParser::ParseError => e
error e.message, 64
rescue I18n::Tasks::CommandError => e
begin
error e.message, 78
ensure
log_verbose e.backtrace * "\n"
end
rescue Errno::EPIPE
# ignore Errno::EPIPE which is throw when pipe breaks, e.g.:
# i18n-tasks missing | head
exit 1
end
end
end
rescue ExecutionError => e
exit e.exit_code
end
def run(argv)
name, *options = parse!(argv.dup)
context.run(name, *options)
end
def context
@context ||= ::I18n::Tasks::Commands.new(base_task)
end
def commands
# load base task to initialize plugins
base_task
@commands ||= ::I18n::Tasks::Commands.cmds.transform_keys { |k| k.to_s.tr('_', '-') }
end
private
def base_task
@base_task ||= I18n::Tasks::BaseTask.new
end
def parse!(argv)
command = parse_command! argv
options = optparse! command, argv
parse_options! options, command, argv
[command.tr('-', '_'), options.update(arguments: argv)]
end
def optparse!(command, argv)
if command
optparse_command!(command, argv)
else
optparse_no_command!(argv)
end
end
def optparse_command!(command, argv)
cmd_conf = commands[command]
flags = cmd_conf[:args].dup
options = {}
OptionParser.new("Usage: #{program_name} #{command} [options] #{cmd_conf[:pos]}".strip) do |op|
flags.each do |flag|
op.on(*optparse_args(flag)) { |v| options[option_name(flag)] = v }
end
verbose_option op
help_option op
end.parse!(argv)
options
end
def optparse_no_command!(argv)
argv << '--help' if argv.empty?
OptionParser.new("Usage: #{program_name} [command] [options]") do |op|
op.on('-v', '--version', 'Print the version') do
puts I18n::Tasks::VERSION
exit
end
help_option op
commands_summary op
end.parse!(argv)
end
def allow_help_arg_first!(argv)
# allow `i18n-tasks --help command` in addition to `i18n-tasks command --help`
if %w(-h --help).include?(argv[0]) && argv[1] && !argv[1].start_with?('-')
argv[0], argv[1] = argv[1], argv[0]
end
end
def parse_command!(argv)
allow_help_arg_first! argv
if argv[0] && !argv[0].start_with?('-')
if commands.keys.include?(argv[0])
argv.shift
else
error "unknown command: #{argv[0]}", 64
end
end
end
def verbose_option(op)
op.on('--verbose', 'Verbose output') do
::I18n::Tasks.verbose = true
end
end
def help_option(op)
op.on('-h', '--help', 'Show this message') do
$stderr.puts op
exit
end
end
# @param [OptionParser] op
def commands_summary(op)
op.separator ''
op.separator 'Available commands:'
op.separator ''
commands.each do |cmd, cmd_conf|
op.separator " #{cmd.ljust(op.summary_width + 1, ' ')}#{try_call cmd_conf[:desc]}"
end
op.separator ''
op.separator 'See `i18n-tasks <command> --help` for more information on a specific command.'
end
def optparse_args(flag)
args = flag.dup
args.map! { |v| try_call v }
conf = args.extract_options!
if conf.key?(:default)
args[-1] = "#{args[-1]}. #{I18n.t('i18n_tasks.cmd.args.default_text', value: conf[:default])}"
end
args
end
def parse_options!(options, command, argv)
commands[command][:args].each do |flag|
name = option_name flag
options[name] = parse_option flag, options[name], argv, context
end
end
def parse_option(flag, val, argv, context)
conf = flag.last.is_a?(Hash) ? flag.last : {}
if conf[:consume_positional]
val = Array(val) + Array(flag.include?(Array) ? argv.flat_map { |x| x.split(',') } : argv)
end
val = conf[:default] if val.nil? && conf.key?(:default)
val = conf[:parser].call(val, context) if conf.key?(:parser)
val
end
def option_name(flag)
flag.detect do |f|
f.start_with?('--')
end.sub(/\A--(\[no-\])?/, '').sub(/[^\-\w].*\z/, '').to_sym
end
def try_call(v)
if v.respond_to? :call
v.call
else
v
end
end
def error(message, exit_code)
log_error message
fail ExecutionError.new(message, exit_code)
end
class ExecutionError < RuntimeError
attr_reader :exit_code
def initialize(message, exit_code)
super(message)
@exit_code = exit_code
end
end
def auto_output_coloring(coloring = ENV['I18N_TASKS_COLOR'] || STDOUT.isatty)
coloring_was = Term::ANSIColor.coloring?
Term::ANSIColor.coloring = coloring
yield
ensure
Term::ANSIColor.coloring = coloring_was
end
end