cli-mastermind 0.0.1 → 0.0.2
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/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
|