yuyi 1.0.8 → 1.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.new +1 -1
- data/.ruby-version +1 -0
- data/Gemfile +7 -2
- data/Gemfile.lock +16 -21
- data/Guardfile +1 -1
- data/README.md +41 -24
- data/Rakefile +38 -0
- data/bin/yuyi +9 -1
- data/lib/yuyi/cli.rb +32 -363
- data/lib/yuyi/core.rb +4 -0
- data/lib/yuyi/dsl.rb +131 -0
- data/lib/yuyi/menu.rb +174 -166
- data/lib/yuyi/roll.rb +95 -140
- data/lib/yuyi/source.rb +4 -3
- data/lib/yuyi/ui.rb +103 -0
- data/lib/yuyi.rb +17 -2
- data/spec/fixtures/menu.yaml +3 -2
- data/spec/fixtures/menu2.yaml +5 -2
- data/spec/fixtures/roll_dir/{nested/foo_roll.rb → foo_roll.rb} +0 -0
- data/spec/fixtures/roll_dir/foo_roll_model.rb +2 -0
- data/spec/fixtures/roll_dir/nested/bar_roll.rb +1 -1
- data/spec/fixtures/roll_zip.zip +0 -0
- data/spec/lib/yuyi/cli_spec.rb +11 -310
- data/spec/lib/yuyi/core_spec.rb +1 -1
- data/spec/lib/yuyi/dsl_spec.rb +135 -0
- data/spec/lib/yuyi/menu_spec.rb +177 -117
- data/spec/lib/yuyi/roll_spec.rb +38 -227
- data/spec/lib/yuyi/source_spec.rb +6 -2
- data/spec/lib/yuyi/ui_spec.rb +36 -0
- data/spec/roll_validator.rb +51 -0
- data/spec/spec_helper.rb +2 -0
- data/yuyi_menu +8 -0
- metadata +36 -10
- data/bin/install +0 -42
data/lib/yuyi/cli.rb
CHANGED
@@ -1,377 +1,46 @@
|
|
1
|
-
require '
|
2
|
-
require 'readline'
|
3
|
-
require 'yaml'
|
1
|
+
require 'thor'
|
4
2
|
|
5
|
-
|
6
|
-
|
7
|
-
|
3
|
+
class Yuyi::Cli < Thor
|
4
|
+
desc 'list', 'Show all rolls available based on the sources defined in your menu.'
|
5
|
+
option :menu, :aliases => '-m', :desc => 'Path to your menu file'
|
6
|
+
def list
|
7
|
+
Yuyi::Menu.new options[:menu]
|
8
8
|
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
:help => 'Shows these options.',
|
15
|
-
:list => 'List all rolls available to be included in your menu.',
|
16
|
-
:version => 'Shows the current version of Yuyi.',
|
17
|
-
:VERBOSE => 'Shows the output of all commands being run'
|
18
|
-
}
|
19
|
-
|
20
|
-
# Called by the install script
|
21
|
-
#
|
22
|
-
def init args
|
23
|
-
# get the first argument as the command
|
24
|
-
command, *rest = *args
|
25
|
-
|
26
|
-
# if not an option, assume it is a custom path
|
27
|
-
if command && command[0,1] != '-'
|
28
|
-
@path = command
|
29
|
-
command, *rest = *rest
|
30
|
-
end
|
31
|
-
|
32
|
-
# Call options method if valid argument is passed
|
33
|
-
# This checks for the full name or the first letter, proceeded by '--' or '-' respectively
|
34
|
-
CLI_OPTIONS.keys.each do |option|
|
35
|
-
if command == "--#{option}" || command == "-#{option.to_s.chars.first}"
|
36
|
-
begin
|
37
|
-
send option.to_s.downcase, rest
|
38
|
-
rescue
|
39
|
-
send option.to_s.downcase
|
40
|
-
end
|
41
|
-
return
|
42
|
-
end
|
43
|
-
end
|
44
|
-
|
45
|
-
# Show a warning if an invalid argument is passed and then show the help menu
|
46
|
-
if command
|
47
|
-
say 'INVALID ARGUMENT', :type => :fail
|
48
|
-
send :help
|
49
|
-
else
|
50
|
-
start
|
51
|
-
end
|
52
|
-
end
|
53
|
-
|
54
|
-
# Replacement for `puts` that accepts various stylistic arguments
|
55
|
-
# type: => [symbol] Preset colors for [:fail, :success, :warn]
|
56
|
-
# color: => [integer] See docs for #colorize for color codes
|
57
|
-
# justify: => [center|ljust|rjust] The type of justification to use
|
58
|
-
# padding: => [integer] The maximum string size to justify text in
|
59
|
-
# indent: => [integer] The maximum string size to justify text in
|
60
|
-
# newline: => [boolean] True if you want a newline after the output
|
61
|
-
#
|
62
|
-
def say text = '', args = {}
|
63
|
-
# defaults
|
64
|
-
args = {
|
65
|
-
:newline => true
|
66
|
-
}.merge args
|
67
|
-
|
68
|
-
# Justify options
|
69
|
-
if args[:justify] && args[:padding]
|
70
|
-
text = text.send args[:justify], args[:padding]
|
71
|
-
end
|
72
|
-
|
73
|
-
# Type options
|
74
|
-
# process last due to the addition of special color codes
|
75
|
-
text = case args[:type]
|
76
|
-
when :fail
|
77
|
-
colorize text, 31
|
78
|
-
when :success
|
79
|
-
colorize text, 32
|
80
|
-
when :warn
|
81
|
-
colorize text, 33
|
82
|
-
else
|
83
|
-
colorize text, args[:color]
|
84
|
-
end
|
85
|
-
|
86
|
-
if args[:indent]
|
87
|
-
text = (' ' * args[:indent]) + text
|
88
|
-
end
|
89
|
-
|
90
|
-
if args[:newline]
|
91
|
-
STDOUT.puts text
|
92
|
-
else
|
93
|
-
STDOUT.print text
|
9
|
+
# Collect all rolls from all sources
|
10
|
+
#
|
11
|
+
rolls = []
|
12
|
+
Yuyi::Menu.sources.each do |source|
|
13
|
+
rolls |= source.rolls.keys
|
94
14
|
end
|
95
|
-
end
|
96
|
-
|
97
|
-
# Accepts the same arguments as #say
|
98
|
-
#
|
99
|
-
def ask question, options = {}, &block
|
100
|
-
prompt = '>>> '
|
101
|
-
options = {
|
102
|
-
:readline => false,
|
103
|
-
:color => 1
|
104
|
-
}.merge(options)
|
105
|
-
|
106
|
-
say question, options
|
107
|
-
|
108
|
-
output = if options[:readline]
|
109
|
-
Readline.readline(prompt).chomp('/')
|
110
|
-
else
|
111
|
-
say prompt, :color => 1, :newline => false
|
112
|
-
STDIN.gets
|
113
|
-
end.rstrip
|
114
|
-
|
115
|
-
say
|
116
|
-
yield output if block
|
117
|
-
end
|
118
15
|
|
119
|
-
|
120
|
-
|
121
|
-
def run command, args = {}
|
122
|
-
# check if in verbose mode
|
123
|
-
verbose = args[:verbose] || @verbose
|
124
|
-
output = `echo | #{command} 2>&1`
|
125
|
-
success = $?.success?
|
16
|
+
# alphabatize rolls
|
17
|
+
rolls = rolls.map(&:to_s).sort
|
126
18
|
|
127
|
-
|
128
|
-
|
129
|
-
|
19
|
+
Yuyi.say 'Available Rolls', :type => :success
|
20
|
+
Yuyi.say '---------------', :type => :success
|
21
|
+
rolls.each do |roll|
|
22
|
+
Yuyi.say roll
|
130
23
|
end
|
131
|
-
|
132
|
-
args[:boolean] ? success : output
|
24
|
+
Yuyi.say
|
133
25
|
end
|
134
26
|
|
135
|
-
|
136
|
-
|
27
|
+
desc 'version', 'Show the currently running version of yuyi'
|
28
|
+
def version
|
29
|
+
say "#{Yuyi::NAME} #{Yuyi::VERSION}"
|
137
30
|
end
|
138
31
|
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
file.write full_text
|
149
|
-
end
|
150
|
-
end
|
151
|
-
end
|
152
|
-
|
153
|
-
# Delete specific lines from an existing file
|
154
|
-
#
|
155
|
-
def delete_from_file file, *text
|
156
|
-
text.flatten!
|
157
|
-
|
158
|
-
# get file text
|
159
|
-
new_text = File.read(File.expand_path(file))
|
160
|
-
|
161
|
-
# iterate through text and remove it
|
162
|
-
text.each do |t|
|
163
|
-
regex = /^.*#{Regexp.escape(t)}.*\n/
|
164
|
-
new_text.gsub!(regex, '')
|
165
|
-
end
|
166
|
-
|
167
|
-
# write new text back to file
|
168
|
-
File.open(File.expand_path(file), 'w') { |f| f.write(new_text) }
|
169
|
-
end
|
32
|
+
desc 'start', 'Run Yuyi'
|
33
|
+
option :verbose, :aliases => '-v', :type => :boolean, :desc => 'Run in verbose mode'
|
34
|
+
option :upgrade, :aliases => '-u', :type => :boolean, :desc => 'Check for upgrades for rolls on the menu that are already installed'
|
35
|
+
option :menu, :aliases => '-m', :desc => 'Path to your menu file'
|
36
|
+
def start
|
37
|
+
# enable verbose mode if flag is passed
|
38
|
+
Yuyi.verbose = true if options[:verbose]
|
39
|
+
Yuyi.upgrade = true if options[:upgrade]
|
40
|
+
Yuyi.menu_path = options[:menu]
|
170
41
|
|
171
|
-
|
172
|
-
run('/usr/bin/sw_vers -productVersion').chomp[/10\.\d+/].to_f
|
42
|
+
Yuyi.start
|
173
43
|
end
|
174
44
|
|
175
|
-
|
176
|
-
|
177
|
-
private
|
178
|
-
|
179
|
-
def start
|
180
|
-
header
|
181
|
-
get_menu
|
182
|
-
confirm_upgrade
|
183
|
-
confirm_options
|
184
|
-
authenticate
|
185
|
-
Yuyi::Menu.instance.order_rolls
|
186
|
-
end
|
187
|
-
|
188
|
-
def header
|
189
|
-
line_length = 50
|
190
|
-
say
|
191
|
-
say '-' * line_length, :color => 4
|
192
|
-
say
|
193
|
-
say '____ ____ __ __ ____ ____ __ ', :color => 31, :justify => :center, :padding => line_length
|
194
|
-
say '\ \ / / | | | | \ \ / / | | ', :color => 32, :justify => :center, :padding => line_length
|
195
|
-
say ' \ \/ / | | | | \ \/ / | | ', :color => 33, :justify => :center, :padding => line_length
|
196
|
-
say ' \_ _/ | | | | \_ _/ | | ', :color => 34, :justify => :center, :padding => line_length
|
197
|
-
say ' | | | `--\' | | | | | ', :color => 35, :justify => :center, :padding => line_length
|
198
|
-
say ' |__| \______/ |__| |__| ', :color => 36, :justify => :center, :padding => line_length
|
199
|
-
say
|
200
|
-
say "VERSION #{Yuyi::VERSION}", :justify => :center, :padding => line_length
|
201
|
-
say
|
202
|
-
say '-' * line_length, :color => 4
|
203
|
-
say
|
204
|
-
end
|
205
|
-
|
206
|
-
# Ask the user for a menu file to load
|
207
|
-
#
|
208
|
-
def get_menu
|
209
|
-
until @path
|
210
|
-
say 'Navigate to a menu file...', :type => :success
|
211
|
-
@path = ask "...or just press enter to load `#{Yuyi::DEFAULT_MENU}`", :readline => true, :color => 36 do |path|
|
212
|
-
path = path.empty? ? Yuyi::DEFAULT_MENU : path
|
213
|
-
|
214
|
-
if Yuyi::Menu.load_from_file path
|
215
|
-
say 'Downloading Sources... Please Wait', :type => :warn
|
216
|
-
say
|
217
|
-
|
218
|
-
path
|
219
|
-
else
|
220
|
-
say 'Invalid Path... Please check the location of your menu file', :type => :fail
|
221
|
-
say
|
222
|
-
|
223
|
-
nil
|
224
|
-
end
|
225
|
-
end
|
226
|
-
end
|
227
|
-
|
228
|
-
Yuyi::Menu.new @path
|
229
|
-
end
|
230
|
-
|
231
|
-
# Ask to check for upgrades
|
232
|
-
#
|
233
|
-
def confirm_upgrade
|
234
|
-
ask 'Do you want to check for upgrades for already installed rolls? (Yn)', :type => :warn do |upgrade|
|
235
|
-
Yuyi::Menu.upgrade? upgrade == 'Y'
|
236
|
-
end
|
237
|
-
end
|
238
|
-
|
239
|
-
# If any rolls on the menu have options, confirm the options before continuing
|
240
|
-
#
|
241
|
-
def confirm_options
|
242
|
-
confirm = false
|
243
|
-
Yuyi::Menu.rolls.each do |name, roll|
|
244
|
-
|
245
|
-
unless roll.class.options.empty?
|
246
|
-
present_options roll
|
247
|
-
confirm = true
|
248
|
-
end
|
249
|
-
end
|
250
|
-
|
251
|
-
if confirm
|
252
|
-
ask 'Hit any key when you have your options set correctly in your menu file', :type => :warn do
|
253
|
-
Yuyi::Menu.load_from_file
|
254
|
-
end
|
255
|
-
end
|
256
|
-
end
|
257
|
-
|
258
|
-
def authenticate
|
259
|
-
say 'Yuyi does not need your admin password, but some installations force a prompt.', :type => :warn
|
260
|
-
say 'You may be asked to enter your password several times. ', :type => :warn
|
261
|
-
|
262
|
-
# keep the sudo timestamp fresh
|
263
|
-
Thread::new do
|
264
|
-
loop do
|
265
|
-
sleep 1.minute
|
266
|
-
`sudo -v`
|
267
|
-
end
|
268
|
-
end
|
269
|
-
|
270
|
-
say
|
271
|
-
end
|
272
|
-
|
273
|
-
# Show formatted options
|
274
|
-
#
|
275
|
-
def present_options roll, examples = true
|
276
|
-
indent = 2
|
277
|
-
longest_option = roll.options.keys.map(&:to_s).max_by(&:length).length + indent
|
278
|
-
|
279
|
-
say "Available options for #{roll.title}...", :color => 32
|
280
|
-
|
281
|
-
roll.option_defs.each do |k, v|
|
282
|
-
option_color = v[:required] ? 31 : 36
|
283
|
-
|
284
|
-
say "#{k.to_s.rjust(longest_option)}: ", :color => option_color, :newline => false
|
285
|
-
say v[:description]
|
286
|
-
say (' ' * (longest_option + indent)), :newline => false
|
287
|
-
if v[:default]
|
288
|
-
say 'default: ', :color => 36, :newline => false
|
289
|
-
say v[:default]
|
290
|
-
end
|
291
|
-
end
|
292
|
-
|
293
|
-
if examples
|
294
|
-
examples_hash = {}
|
295
|
-
example_indent = longest_option + indent
|
296
|
-
options = roll.options.dup
|
297
|
-
|
298
|
-
# merge examples from roll source in
|
299
|
-
options.each do |option, value|
|
300
|
-
if example = roll.option_defs[option][:example]
|
301
|
-
options[option] = example
|
302
|
-
end
|
303
|
-
end
|
304
|
-
|
305
|
-
examples_hash[roll.file_name.to_s] = options
|
306
|
-
|
307
|
-
|
308
|
-
say
|
309
|
-
say 'Example', :color => 33, :indent => example_indent, :newline => false
|
310
|
-
say examples_hash.deep_stringify_keys!.to_yaml.sub('---', '').gsub(/\n(\s*)/, "\n\\1#{' ' * example_indent}")
|
311
|
-
end
|
312
|
-
end
|
313
|
-
|
314
|
-
# Output text with a certain color (or style)
|
315
|
-
# Reference for color codes
|
316
|
-
# https://github.com/flori/term-ansicolor/blob/master/lib/term/ansicolor.rb
|
317
|
-
#
|
318
|
-
def colorize text, color_code
|
319
|
-
return text unless color_code
|
320
|
-
"\e[#{color_code}m#{text}\e[0m"
|
321
|
-
end
|
322
|
-
|
323
|
-
# METHODS FOR FLAGS
|
324
|
-
#
|
325
|
-
def help
|
326
|
-
longest_option = CLI_OPTIONS.keys.map(&:to_s).max.length
|
327
|
-
|
328
|
-
say
|
329
|
-
CLI_OPTIONS.each do |option, description|
|
330
|
-
string = ''
|
331
|
-
string << "-#{option.to_s.chars.first}"
|
332
|
-
string << ', '
|
333
|
-
string << "--#{option.to_s.ljust(longest_option)}"
|
334
|
-
string << ' '
|
335
|
-
string << description
|
336
|
-
say string
|
337
|
-
end
|
338
|
-
say
|
339
|
-
end
|
340
|
-
|
341
|
-
# List all available rolls
|
342
|
-
#
|
343
|
-
def list
|
344
|
-
get_menu
|
345
|
-
Yuyi::Menu.set_sources
|
346
|
-
|
347
|
-
# Collect all rolls from all sources
|
348
|
-
#
|
349
|
-
rolls = []
|
350
|
-
Yuyi::Menu.sources.each do |source|
|
351
|
-
rolls |= source.available_rolls.keys
|
352
|
-
end
|
353
|
-
|
354
|
-
# alphabatize rolls
|
355
|
-
rolls = rolls.map(&:to_s).sort.map(&:to_sym)
|
356
|
-
|
357
|
-
say 'Available Rolls', :type => :success
|
358
|
-
say '---------------', :type => :success
|
359
|
-
rolls.each do |roll|
|
360
|
-
say roll.to_s
|
361
|
-
end
|
362
|
-
say
|
363
|
-
end
|
364
|
-
|
365
|
-
# Return current version
|
366
|
-
#
|
367
|
-
def version
|
368
|
-
say "#{Yuyi::NAME} #{Yuyi::VERSION}"
|
369
|
-
end
|
370
|
-
|
371
|
-
# Return current version
|
372
|
-
#
|
373
|
-
def verbose
|
374
|
-
@verbose = true
|
375
|
-
start
|
376
|
-
end
|
45
|
+
default_task :start
|
377
46
|
end
|
data/lib/yuyi/core.rb
CHANGED
@@ -19,6 +19,10 @@ class Hash
|
|
19
19
|
# to +to_sym+. This includes the keys from the root hash and from all
|
20
20
|
# nested hashes.
|
21
21
|
#
|
22
|
+
# DEPRECATION
|
23
|
+
# required for: ruby 1.8.7
|
24
|
+
# can be replaced with activesupport ~4.0
|
25
|
+
#
|
22
26
|
def deep_symbolize_keys!
|
23
27
|
deep_transform_keys!{ |key| key.to_sym rescue key }
|
24
28
|
end
|
data/lib/yuyi/dsl.rb
ADDED
@@ -0,0 +1,131 @@
|
|
1
|
+
require 'open3'
|
2
|
+
require 'readline'
|
3
|
+
require 'yaml'
|
4
|
+
|
5
|
+
module Yuyi::Dsl
|
6
|
+
# Configure readline
|
7
|
+
Readline.completion_append_character = '/'
|
8
|
+
|
9
|
+
attr_accessor :verbose, :upgrade, :menu_path
|
10
|
+
|
11
|
+
# Replacement for `puts` that accepts various stylistic arguments
|
12
|
+
# type: => [symbol] Preset colors for [:fail, :success, :warn]
|
13
|
+
# color: => [integer] See docs for #colorize for color codes
|
14
|
+
# justify: => [center|ljust|rjust] The type of justification to use
|
15
|
+
# padding: => [integer] The maximum string size to justify text in
|
16
|
+
# indent: => [integer] The maximum string size to justify text in
|
17
|
+
# newline: => [boolean] True if you want a newline after the output
|
18
|
+
#
|
19
|
+
def say text = '', args = {}
|
20
|
+
# defaults
|
21
|
+
args = {
|
22
|
+
:newline => true
|
23
|
+
}.merge args
|
24
|
+
|
25
|
+
# Justify options
|
26
|
+
if args[:justify] && args[:padding]
|
27
|
+
text = text.send args[:justify], args[:padding]
|
28
|
+
end
|
29
|
+
|
30
|
+
# Type options
|
31
|
+
# process last due to the addition of special color codes
|
32
|
+
text = case args[:type]
|
33
|
+
when :fail
|
34
|
+
Yuyi.colorize text, 31
|
35
|
+
when :success
|
36
|
+
Yuyi.colorize text, 32
|
37
|
+
when :warn
|
38
|
+
Yuyi.colorize text, 33
|
39
|
+
else
|
40
|
+
Yuyi.colorize text, args[:color]
|
41
|
+
end
|
42
|
+
|
43
|
+
if args[:indent]
|
44
|
+
text = (' ' * args[:indent]) + text
|
45
|
+
end
|
46
|
+
|
47
|
+
if args[:newline]
|
48
|
+
STDOUT.puts text
|
49
|
+
else
|
50
|
+
STDOUT.print text
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
# Accepts the same arguments as #say
|
55
|
+
#
|
56
|
+
def ask question, options = {}, &block
|
57
|
+
prompt = '>>> '
|
58
|
+
options = {
|
59
|
+
:readline => false,
|
60
|
+
:color => 1
|
61
|
+
}.merge(options)
|
62
|
+
|
63
|
+
say question, options
|
64
|
+
|
65
|
+
output = if options[:readline]
|
66
|
+
Readline.readline(prompt).chomp('/')
|
67
|
+
else
|
68
|
+
say prompt, :color => 1, :newline => false
|
69
|
+
STDIN.gets
|
70
|
+
end.rstrip
|
71
|
+
|
72
|
+
say
|
73
|
+
yield output if block
|
74
|
+
end
|
75
|
+
|
76
|
+
# Run a command and output formatting success/errors
|
77
|
+
#
|
78
|
+
def run command, args = {}
|
79
|
+
# check if in verbose mode
|
80
|
+
verbose = args[:verbose] || @verbose
|
81
|
+
output = `echo | #{command} 2>&1`
|
82
|
+
success = $?.success?
|
83
|
+
|
84
|
+
if verbose
|
85
|
+
say "RUNNING: #{command}", :type => (success ? :success : :fail)
|
86
|
+
say output
|
87
|
+
end
|
88
|
+
|
89
|
+
args[:boolean] ? success : output
|
90
|
+
end
|
91
|
+
|
92
|
+
def command? command
|
93
|
+
run command, :verbose => false, :boolean => true
|
94
|
+
end
|
95
|
+
|
96
|
+
# Write several lines to to an existing file
|
97
|
+
#
|
98
|
+
def write_to_file file, *text
|
99
|
+
text.flatten!
|
100
|
+
|
101
|
+
File.open(File.expand_path(file), 'a+') do |file|
|
102
|
+
full_text = (text * "\n") + "\n"
|
103
|
+
|
104
|
+
unless file.read.include? full_text
|
105
|
+
file.write full_text
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
# Delete specific lines from an existing file
|
111
|
+
#
|
112
|
+
def delete_from_file file, *text
|
113
|
+
text.flatten!
|
114
|
+
|
115
|
+
# get file text
|
116
|
+
new_text = File.read(File.expand_path(file))
|
117
|
+
|
118
|
+
# iterate through text and remove it
|
119
|
+
text.each do |t|
|
120
|
+
regex = /^.*#{Regexp.escape(t)}.*\n/
|
121
|
+
new_text.gsub!(regex, '')
|
122
|
+
end
|
123
|
+
|
124
|
+
# write new text back to file
|
125
|
+
File.open(File.expand_path(file), 'w') { |f| f.write(new_text) }
|
126
|
+
end
|
127
|
+
|
128
|
+
def osx_version
|
129
|
+
run('/usr/bin/sw_vers -productVersion').chomp[/10\.\d+/].to_f
|
130
|
+
end
|
131
|
+
end
|