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 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