cli-mastermind 0.0.1 → 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/cli/mastermind.rb +126 -25
- data/lib/cli/mastermind/arg_parse.rb +18 -6
- data/lib/cli/mastermind/interface.rb +37 -0
- data/lib/cli/mastermind/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 8db2ac9239f2def0778c23ee6a02f8e9955d4de5acfb1247e024a108ab4ac260
|
4
|
+
data.tar.gz: 3a532c3c20cb827a781852ab02062928bb5edb0408ffd3c02348c0b6175e2856
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 9cb69d8818573049fca65ef43d2b83c6d4931ca69a2d98ff87ec1d36c6002f1c15c7f7bb45daa687e796bf3ff117a348883ff9203f055576f59dcb9a70dbb834
|
7
|
+
data.tar.gz: 2c24a20bedc2e188825393cc853da61e29668b78e6760cd378d10ff41a7a0716dbb857e08373d14ac5b53c59c4d29bd11c37c66027cac7a51f2b20ec3d83364b
|
data/lib/cli/mastermind.rb
CHANGED
@@ -1,3 +1,4 @@
|
|
1
|
+
# coding: utf-8
|
1
2
|
require 'cli/ui'
|
2
3
|
require 'cli/mastermind/arg_parse'
|
3
4
|
require 'cli/mastermind/configuration'
|
@@ -12,12 +13,13 @@ module CLI
|
|
12
13
|
extend Interface
|
13
14
|
|
14
15
|
class << self
|
15
|
-
|
16
|
-
|
16
|
+
# Expose the configuration loaded during +execute+.
|
17
17
|
def configuration
|
18
18
|
@config
|
19
19
|
end
|
20
20
|
|
21
|
+
# Process incoming options and take an appropriate action.
|
22
|
+
# This is normally called by the mastermind executable.
|
21
23
|
def execute(cli_args=ARGV)
|
22
24
|
@arguments = ArgParse.new(cli_args)
|
23
25
|
|
@@ -25,36 +27,25 @@ module CLI
|
|
25
27
|
|
26
28
|
frame('Mastermind') do
|
27
29
|
@config = spinner('Loading configuration') { Configuration.new }
|
28
|
-
@plans = spinner('Loading plans') { @config.load_plans }
|
29
|
-
@plan_stack = []
|
30
30
|
|
31
|
-
@
|
31
|
+
if @arguments.dump_config?
|
32
|
+
do_print_configuration
|
33
|
+
exit 0
|
34
|
+
end
|
32
35
|
|
33
|
-
|
34
|
-
plan_name = @arguments.get_next_plan_name
|
35
|
-
@selected_plan = (@selected_plan || @plans)[plan_name]
|
36
|
-
@plan_stack << titleize(plan_name)
|
36
|
+
@plans = spinner('Loading plans') { @config.load_plans }
|
37
37
|
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
exit 1
|
42
|
-
end
|
38
|
+
if @arguments.display_plans?
|
39
|
+
do_filtered_plan_display
|
40
|
+
exit 0
|
43
41
|
end
|
44
42
|
|
45
|
-
|
46
|
-
if @selected_plan.nil? and @plans.count == 1
|
47
|
-
@selected_plan = @plans.values.first
|
48
|
-
@plan_stack << titleize(@selected_plan.name)
|
49
|
-
end
|
43
|
+
process_plan_names
|
50
44
|
|
51
|
-
|
52
|
-
do_interactive_plan_selection
|
53
|
-
@plan_stack << titleize(@selected_plan.name)
|
54
|
-
end
|
45
|
+
do_interactive_plan_selection until executable_plan_selected?
|
55
46
|
|
56
|
-
if
|
57
|
-
|
47
|
+
if user_is_sure?
|
48
|
+
execute_plan!
|
58
49
|
else
|
59
50
|
puts 'aborted!'
|
60
51
|
end
|
@@ -63,12 +54,122 @@ module CLI
|
|
63
54
|
|
64
55
|
private
|
65
56
|
|
57
|
+
def do_print_configuration
|
58
|
+
frame('Configuration') do
|
59
|
+
fade_code = CLI::UI::Color.new(90, '').code
|
60
|
+
puts stylize("{{?}} #{fade_code}Values starting with {{*}} #{fade_code}were lazy loaded.#{CLI::UI::Color::RESET.code}")
|
61
|
+
print "\n"
|
62
|
+
@config.instance_variables.each do |attribute|
|
63
|
+
value = @config.instance_variable_get(attribute)
|
64
|
+
|
65
|
+
if value.respond_to? :call
|
66
|
+
if @arguments.resolve_callable_attributes?
|
67
|
+
value = begin
|
68
|
+
value.call
|
69
|
+
rescue => e
|
70
|
+
"UNABLE TO LOAD: #{e.messae}"
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
was_callable = true
|
75
|
+
else
|
76
|
+
was_callable = false
|
77
|
+
end
|
78
|
+
|
79
|
+
name = attribute.to_s.delete_prefix('@')
|
80
|
+
|
81
|
+
suffix = was_callable ? '{{*}}' : ' '
|
82
|
+
|
83
|
+
puts stylize("{{yellow:#{name}}}")
|
84
|
+
puts stylize("\t #{suffix} {{blue:#{value}}}")
|
85
|
+
print "\n"
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
def do_filtered_plan_display
|
91
|
+
filter_plans @arguments.pattern
|
92
|
+
|
93
|
+
unless @plans.empty?
|
94
|
+
frame('Plans') do
|
95
|
+
display_plans
|
96
|
+
end
|
97
|
+
else
|
98
|
+
puts stylize("{{x}} No plans match #{@arguments.pattern.source}")
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
def display_plans(plans=@plans, prefix='')
|
103
|
+
plans.each do |(name, plan)|
|
104
|
+
next unless plan.has_children? or plan.description
|
105
|
+
|
106
|
+
print prefix + '• '
|
107
|
+
puts stylize("{{yellow:#{titleize(name)}")
|
108
|
+
if plan.description
|
109
|
+
print prefix + ' - '
|
110
|
+
puts stylize("{{blue:#{plan.description}}}")
|
111
|
+
end
|
112
|
+
|
113
|
+
display_plans(plan.children, " " + prefix) if plan.has_children?
|
114
|
+
print "\n"
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
def filter_plans(pattern, plans=@plans)
|
119
|
+
plans.keep_if do |name, plan|
|
120
|
+
# Don't display plans without a description or children
|
121
|
+
next false unless plan.has_children? or plan.description
|
122
|
+
next true if name =~ pattern
|
123
|
+
next false unless plan.has_children?
|
124
|
+
|
125
|
+
filter_plans(pattern, plan.children)
|
126
|
+
|
127
|
+
plan.has_children?
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
def process_plan_names
|
132
|
+
@plan_stack = []
|
133
|
+
|
134
|
+
@selected_plan = nil
|
135
|
+
|
136
|
+
while @arguments.has_additional_plan_names?
|
137
|
+
plan_name = @arguments.get_next_plan_name
|
138
|
+
@selected_plan = (@selected_plan || @plans)[plan_name]
|
139
|
+
@plan_stack << titleize(plan_name)
|
140
|
+
|
141
|
+
if @selected_plan.nil?
|
142
|
+
puts "No plan found at #{@plan_stack.join('/')}"
|
143
|
+
puts @arguments.parser
|
144
|
+
exit 1
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
# Prevent the prompt from exploading
|
149
|
+
if @selected_plan.nil? and @plans.count == 1
|
150
|
+
@selected_plan = @plans.values.first
|
151
|
+
@plan_stack << titleize(@selected_plan.name)
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
66
155
|
def do_interactive_plan_selection
|
67
156
|
options = @selected_plan&.children || @plans
|
68
157
|
|
69
158
|
@selected_plan = select("Select a plan under #{@plan_stack.join('/')}", options: options)
|
159
|
+
@plan_stack << titleize(@selected_plan.name)
|
70
160
|
end
|
71
161
|
|
162
|
+
def executable_plan_selected?
|
163
|
+
not (@selected_plan.nil? or @selected_plan.has_children?)
|
164
|
+
end
|
165
|
+
|
166
|
+
def user_is_sure?
|
167
|
+
!@arguments.ask? or confirm("Execute plan #{@plan_stack.join('/')}?")
|
168
|
+
end
|
169
|
+
|
170
|
+
def execute_plan!
|
171
|
+
@selected_plan.call(@arguments.plan_arguments)
|
172
|
+
end
|
72
173
|
end
|
73
174
|
end
|
74
175
|
end
|
@@ -15,6 +15,8 @@ module CLI::Mastermind
|
|
15
15
|
@initial_arguments = arguments
|
16
16
|
@ask = true
|
17
17
|
@display_ui = true
|
18
|
+
@show_config = false
|
19
|
+
@call_blocks = false
|
18
20
|
|
19
21
|
parse_arguments
|
20
22
|
end
|
@@ -39,6 +41,14 @@ module CLI::Mastermind
|
|
39
41
|
@ask
|
40
42
|
end
|
41
43
|
|
44
|
+
def dump_config?
|
45
|
+
@show_config
|
46
|
+
end
|
47
|
+
|
48
|
+
def resolve_callable_attributes?
|
49
|
+
@call_blocks
|
50
|
+
end
|
51
|
+
|
42
52
|
def parser
|
43
53
|
@parser ||= OptionParser.new do |opt|
|
44
54
|
opt.banner = 'Usage: mastermind [--help, -h] [--plans[ PATTERN], --tasks[ PATTERN], -T [PATTERN], -P [PATTERN] [PLAN[, PLAN[, ...]]] -- [PLAN ARGUMENTS]'
|
@@ -56,12 +66,14 @@ module CLI::Mastermind
|
|
56
66
|
@display_ui = false
|
57
67
|
end
|
58
68
|
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
69
|
+
opt.on('--plans [PATTERN]', '--tasks [PATTERN]', '-P', '-T', 'Display plans. Optional pattern is used to filter the returned plans.') do |pattern|
|
70
|
+
@pattern = Regexp.new(pattern || '.')
|
71
|
+
end
|
72
|
+
|
73
|
+
opt.on('-C', '--show-configuration', 'Load configuration and print final values. Give multiple times to resolve lazy attributes as well.') do
|
74
|
+
@call_blocks = @show_config
|
75
|
+
@show_config = true
|
76
|
+
end
|
65
77
|
end
|
66
78
|
end
|
67
79
|
|
@@ -1,12 +1,19 @@
|
|
1
|
+
# Wraps methods from CLI::UI in a slightly nicer DSL
|
2
|
+
# @see https://github.com/Shopify/cli-ui
|
1
3
|
module CLI::Mastermind::Interface
|
4
|
+
# Enables cli-ui's STDOUT Router for fancy UIs
|
2
5
|
def enable_ui
|
3
6
|
CLI::UI::StdoutRouter.enable
|
4
7
|
end
|
5
8
|
|
9
|
+
# :private:
|
6
10
|
def ui_enabled?
|
7
11
|
CLI::UI::StdoutRouter.enabled?
|
8
12
|
end
|
9
13
|
|
14
|
+
# Display a spinner with a +title+ while data is being loaded
|
15
|
+
# @returns the value of the given block
|
16
|
+
# @see https://github.com/Shopify/cli-ui#spinner-groups
|
10
17
|
def spinner(title)
|
11
18
|
return yield unless ui_enabled?
|
12
19
|
|
@@ -30,19 +37,40 @@ module CLI::Mastermind::Interface
|
|
30
37
|
yield_value
|
31
38
|
end
|
32
39
|
|
40
|
+
# Uses +CLI::UI.fmt+ to format a string
|
41
|
+
# @see https://github.com/Shopify/cli-ui#symbolglyph-formatting
|
42
|
+
def stylize(string)
|
43
|
+
CLI::UI.fmt string
|
44
|
+
end
|
45
|
+
|
46
|
+
# Opens a CLI::UI frame with the given +title+
|
47
|
+
# @see https://github.com/Shopify/cli-ui#nested-framing
|
33
48
|
def frame(title)
|
34
49
|
return yield unless ui_enabled?
|
35
50
|
CLI::UI::Frame.open(title) { yield }
|
36
51
|
end
|
37
52
|
|
53
|
+
# Ask the user for some text.
|
54
|
+
# @see https://github.com/Shopify/cli-ui#free-form-text-prompts
|
38
55
|
def ask(question, default: nil)
|
39
56
|
CLI::UI.ask(question, default: default)
|
40
57
|
end
|
41
58
|
|
59
|
+
# Ask the user a yes/no +question+
|
60
|
+
# @see https://github.com/Shopify/cli-ui#interactive-prompts
|
42
61
|
def confirm(question)
|
43
62
|
CLI::UI.confirm(question)
|
44
63
|
end
|
45
64
|
|
65
|
+
# Display an interactive list of options for the user to select.
|
66
|
+
#
|
67
|
+
# @param +question+ The question to ask the user
|
68
|
+
# @param +options:+ Array|Hash the options to display
|
69
|
+
# @param +default:+ The value or key to display first. Defaults to the first option.
|
70
|
+
#
|
71
|
+
# Any other keyword arguments given are passed down into +CLI::UI::Prompt.ask+.
|
72
|
+
#
|
73
|
+
# @see https://github.com/Shopify/cli-ui#interactive-prompts
|
46
74
|
def select(question, options:, default: options.first, **opts)
|
47
75
|
default_value = nil
|
48
76
|
options = case options
|
@@ -68,6 +96,15 @@ module CLI::Mastermind::Interface
|
|
68
96
|
end
|
69
97
|
end
|
70
98
|
|
99
|
+
# Titleize the given +string+.
|
100
|
+
# Replaces any dashes (-) or underscores (_) in the +string+ with spaces and
|
101
|
+
# then capitalizes each word.
|
102
|
+
#
|
103
|
+
# Examples:
|
104
|
+
# titleize('foo') => 'Foo'
|
105
|
+
# titleize('foo bar') => 'Foo Bar'
|
106
|
+
# titleize('foo-bar') => 'Foo Bar'
|
107
|
+
# titleize('foo_bar') => 'Foo Bar'
|
71
108
|
def titleize(string)
|
72
109
|
string.gsub(/[-_-]/, ' ').split(' ').map(&:capitalize).join(' ')
|
73
110
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: cli-mastermind
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Chris Hall
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2019-02-
|
11
|
+
date: 2019-02-16 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: cli-ui
|