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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a20b74d9d0e99c2d10a5f9c834be1ece3ffbecf8b5f978882c5fa165b7289316
4
- data.tar.gz: af0406ca768e182e8243348a958adebac42f7640b6d497d2e990c0871d7aaee7
3
+ metadata.gz: 8db2ac9239f2def0778c23ee6a02f8e9955d4de5acfb1247e024a108ab4ac260
4
+ data.tar.gz: 3a532c3c20cb827a781852ab02062928bb5edb0408ffd3c02348c0b6175e2856
5
5
  SHA512:
6
- metadata.gz: 7bba8a6c1e62ebf4adcd69075982396f4121422866f20f387396442c9e6f3607d67b09bab11bad462a093e137416359ff8ab14b39ff32bcf3d95be26262241fc
7
- data.tar.gz: 0e713288d9930e8d7b7a1a872d120264d9b2a6e51792a5d37ac5d1cc7d32a619085d9cc9ca235ecbae85fd18c68a617eeb1e08659ece7273beabef6abc285141
6
+ metadata.gz: 9cb69d8818573049fca65ef43d2b83c6d4931ca69a2d98ff87ec1d36c6002f1c15c7f7bb45daa687e796bf3ff117a348883ff9203f055576f59dcb9a70dbb834
7
+ data.tar.gz: 2c24a20bedc2e188825393cc853da61e29668b78e6760cd378d10ff41a7a0716dbb857e08373d14ac5b53c59c4d29bd11c37c66027cac7a51f2b20ec3d83364b
@@ -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
- attr_reader :plans
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
- @selected_plan = nil
31
+ if @arguments.dump_config?
32
+ do_print_configuration
33
+ exit 0
34
+ end
32
35
 
33
- while @arguments.has_additional_plan_names?
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
- if @selected_plan.nil?
39
- puts "No plan found at #{@plan_stack.join('/')}"
40
- puts @arguments.parser
41
- exit 1
42
- end
38
+ if @arguments.display_plans?
39
+ do_filtered_plan_display
40
+ exit 0
43
41
  end
44
42
 
45
- # Prevent the prompt from exploading
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
- while @selected_plan.nil? or @selected_plan.has_children?
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 !@arguments.ask? or confirm("Execute plan #{@plan_stack.join('/')}?")
57
- @selected_plan.call(@arguments.plan_arguments)
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
- # TODO: Finish plan display
60
- # opt.on('--plans [PATTERN]', '--tasks [PATTERN]', '-P [PATTERN]', '-T [PATTERN]',
61
- # [:text],
62
- # 'Display plans. Optional pattern is used to filter the returned plans.') do |pattern|
63
- # @pattern = RegExp.new(pattern || '*')
64
- # end
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
@@ -8,7 +8,7 @@ module CLI
8
8
  module VERSION
9
9
  RELEASE = 0
10
10
  MAJOR = 0
11
- MINOR = 1
11
+ MINOR = 2
12
12
  PATCH = nil
13
13
 
14
14
  STRING = [RELEASE, MAJOR, MINOR, PATCH].compact.join('.').freeze
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.1
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-15 00:00:00.000000000 Z
11
+ date: 2019-02-16 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: cli-ui