cli-mastermind 0.7.0 → 1.0.0

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: c548ef96d5f0b89113a1110cae2deae76be3c15fd4380727fe74c84bcda29766
4
- data.tar.gz: f189bc224bc60e7ee496b5704df921b3cfd0d37ecb831990889c2666305628b8
3
+ metadata.gz: cd49841f87237f2a7223cd748c61772244232e84cdf46112241e291e73f1a311
4
+ data.tar.gz: '0197b2452f75c1c1c7ac50d84621a9dfef1635d04781c500fb4ca8516f2b65e5'
5
5
  SHA512:
6
- metadata.gz: cc898bb8b3ade138634ebd11fbcd6ab830c60e874528759acbbf9c95ba42b2db7a7d66551d34de8f379a74842bdf2850c2e81860cd66310cddcab86a49ce591c
7
- data.tar.gz: b2f01d7795e3b1ec527d1d3e74ab53b8f9fe250c4e647c7af004b2569b92a463ba11254d136eceb14fe06ea6c8963dd857ffbfc58075bec00a3d5e7963d0eb58
6
+ metadata.gz: b3f195e375e769c105e335c29ab33102db1b2ad76b963f6f463f9c77e0d25a6b79bced05eb44cc86eccaa8d32701236d10b73854310955f8f50742cbeb76a7ac
7
+ data.tar.gz: a5e649e5427b3bea7e52e6cb1a12fa3e563d5e54fbf405623c2d2cdb58147c89dab6043d8fabb3351bf70c13174b0c4c5827383cf75fea0270665847c4a2a3a0
data/README.md CHANGED
@@ -26,11 +26,11 @@ See [Writing Masterplans][writing-masterplans] for more on their structure and
26
26
  semantics.
27
27
 
28
28
  Mastermind makes up for the lack of flexibility in its configuration with full
29
- flexibility in its planfiles. Which brings us to...
29
+ flexibility in its plan files. Which brings us to...
30
30
 
31
31
  ## Extensibility
32
32
 
33
- Mastermind is designed from the outset to provide a means of extending its planfile
33
+ Mastermind is designed from the outset to provide a means of extending its plan file
34
34
  formats through custom `Loader`s. In fact, Mastermind's own `PlanfileLoader` is
35
35
  the first of such loaders. You can specify your own file extensions and provide
36
36
  your own loaders as needed.
@@ -41,14 +41,14 @@ about in the details!
41
41
 
42
42
  Obviously, it'd be a bit difficult to write your plan files in an entirely separate
43
43
  language, but there's nothing stopping you from delegating actions to another
44
- executable or even writing some C code to call into something else alltogether.
44
+ executable or even writing some C code to call into something else altogether.
45
45
 
46
- If you are writing your plans in Ruby, Mastermind provides `CLI::Mastermind::Plan::Interface`
46
+ If you are writing your plans in Ruby, Mastermind provides `CLI::Mastermind::Plan`
47
47
  which you can include in your plans to provide the basic `Plan` interface.
48
48
 
49
49
  ## Minimal Dependencies
50
50
 
51
- Mastermind only has one dependency, Shopify's excelent [cli-ui project][cli-ui].
51
+ Mastermind only has one dependency, Shopify's excellent [cli-ui project][cli-ui].
52
52
  Mastermind doesn't require that you load it in your Gemfile or add anything to
53
53
  your project's configuration files.
54
54
 
@@ -18,7 +18,7 @@ module CLI
18
18
  class << self
19
19
  # Expose the configuration loaded during +execute+.
20
20
  def configuration
21
- @config
21
+ @config ||= spinner('Loading configuration') { Configuration.new @base_path }
22
22
  end
23
23
 
24
24
  # Allows utilities wrapping Mastermind to specify that only plans under a
@@ -41,15 +41,11 @@ module CLI
41
41
  enable_ui if @arguments.display_ui?
42
42
 
43
43
  frame('Mastermind') do
44
- @config = spinner('Loading configuration') { Configuration.new @base_path }
45
-
46
44
  if @arguments.dump_config?
47
45
  do_print_configuration
48
46
  exit 0
49
47
  end
50
48
 
51
- @plans = spinner('Loading plans') { @config.load_plans }
52
-
53
49
  if @arguments.display_plans?
54
50
  do_filtered_plan_display
55
51
  exit 0
@@ -67,22 +63,61 @@ module CLI
67
63
  end
68
64
  end
69
65
 
66
+ # Look up a specific plan by its name
67
+ #
68
+ # Because plans also implement this method in a compatible way, there are
69
+ # three ways this method could be used:
70
+ #
71
+ # 1. List of arguments
72
+ # * Mastermind['name', 'of', 'plans']
73
+ #
74
+ # 2. Space separated string
75
+ # * Mastermind['name of plans']
76
+ #
77
+ # 3. Hash-like access
78
+ # * Mastermind['name']['of']['plans']
79
+ #
80
+ # All will provide the same plan.
81
+ #
82
+ # ---
83
+ #
84
+ # GOTCHA: Be careful if your plan name includes a space!
85
+ #
86
+ # While it's entirely valid to have a plan name that inlcudes a space, you
87
+ # should avoid them if you plan to look up your plan using this method.
88
+ #
89
+ def [](*plan_stack)
90
+ # Allow for a single space-separated string
91
+ if plan_stack.size == 1 and plan_stack.first.is_a?(String)
92
+ plan_stack = plan_stack.first.split(' ')
93
+ end
94
+
95
+ plan_stack.compact.reduce(plans) do |plan, plan_name|
96
+ plan[plan_name]
97
+ end
98
+ end
99
+
70
100
  private
71
101
 
102
+ def plans
103
+ @plans ||= spinner('Loading plans') { Loader.load_all configuration.plan_files }
104
+ end
105
+
72
106
  def do_print_configuration
73
107
  frame('Configuration') do
74
108
  fade_code = CLI::UI::Color.new(90, '').code
75
109
  puts stylize("{{?}} #{fade_code}Values starting with {{*}} #{fade_code}were lazy loaded.#{CLI::UI::Color::RESET.code}")
76
110
  print "\n"
77
- @config.instance_variables.each do |attribute|
78
- value = @config.instance_variable_get(attribute)
111
+
112
+ configuration.instance_variables.each do |attribute|
113
+ value = configuration.instance_variable_get(attribute)
79
114
 
80
115
  name = attribute.to_s.sub(/^@/, '')
81
116
 
82
117
  if value.respond_to? :call
83
118
  if @arguments.resolve_callable_attributes?
84
119
  value = begin
85
- @config.send(name)
120
+ configuration.send(name)
86
121
  rescue => e
87
122
  "UNABLE TO LOAD: #{e.message}"
88
123
  end
@@ -105,7 +140,7 @@ module CLI
105
140
  def do_filtered_plan_display
106
141
  filter_plans @arguments.pattern
107
142
 
108
- unless @plans.empty?
143
+ unless plans.empty?
109
144
  frame('Plans') do
110
145
  puts build_display_string
111
146
  end
@@ -114,7 +149,7 @@ module CLI
114
149
  end
115
150
  end
116
151
 
117
- def build_display_string(plans=@plans, prefix='')
152
+ def build_display_string(plans=self.plans, prefix='')
118
153
  fade_code = CLI::UI::Color.new(90, '').code
119
154
  reset = CLI::UI::Color::RESET.code
120
155
 
@@ -147,7 +182,7 @@ module CLI
147
182
  display_string.gsub(/\n{3,}/, "\n\n")
148
183
  end
149
184
 
150
- def filter_plans(pattern, plans=@plans)
185
+ def filter_plans(pattern, plans=self.plans)
151
186
  plans.keep_if do |name, plan|
152
187
  # Don't display plans without a description or children
153
188
  next false unless plan.has_children? or plan.description
@@ -161,13 +196,13 @@ module CLI
161
196
  end
162
197
 
163
198
  def process_plan_names
164
- @arguments.do_command_expansion!(@config)
199
+ @arguments.do_command_expansion!(configuration)
165
200
 
166
201
  @arguments.insert_base_plan!(@base_plan) unless @base_plan.nil?
167
202
 
168
203
  @plan_stack = []
169
204
 
170
- @selected_plan = @plans if @arguments.has_additional_plan_names?
205
+ @selected_plan = plans if @arguments.has_additional_plan_names?
171
206
 
172
207
  while @arguments.has_additional_plan_names?
173
208
  plan_name = @arguments.get_next_plan_name
@@ -184,7 +219,7 @@ module CLI
184
219
  end
185
220
 
186
221
  def do_interactive_plan_selection
187
- options = (@selected_plan&.children || @plans).map { |k,v| [titleize(k.to_s), v] }.to_h
222
+ options = (@selected_plan&.children || plans).map { |k,v| [titleize(k.to_s), v] }.to_h
188
223
 
189
224
  @selected_plan = select("Select a plan under #{@plan_stack.join('/')}", options: options)
190
225
  @plan_stack << titleize(@selected_plan.name)
@@ -195,7 +230,7 @@ module CLI
195
230
  end
196
231
 
197
232
  def user_is_sure?
198
- !@arguments.ask? or !@config.ask? or confirm("Execute plan #{@plan_stack.join('/')}?")
233
+ !@arguments.ask? or !configuration.ask? or confirm("Execute plan #{@plan_stack.join('/')}?")
199
234
  end
200
235
 
201
236
  def execute_plan!
@@ -24,7 +24,7 @@ module CLI
24
24
  # Path to the top-level masterplan
25
25
  MASTER_PLAN = File.join(Dir.home, PLANFILE)
26
26
 
27
- attr_reader :plans
27
+ attr_reader :plan_files
28
28
 
29
29
  # Adds an arbitrary attribute given by +attribute+ to the configuration class
30
30
  def self.add_attribute(attribute)
@@ -73,31 +73,6 @@ module CLI
73
73
  @plan_files.merge(allowed_plans)
74
74
  end
75
75
 
76
- # Loads all plan files added using +add_plans+
77
- # @see Plan.load
78
- def load_plans
79
- @plans = Loader.load_all(@plan_files)
80
- end
81
-
82
- def execute_plan(*plan_stack, arguments: nil)
83
- if plan_stack.size == 1
84
- case plan_stack.first
85
- when Array
86
- plan_stack = plan_stack.first
87
- when String
88
- plan_stack = plan_stack.first.split(' ')
89
- end
90
- end
91
-
92
- plan = @plans
93
-
94
- plan_stack.each do |plan_name|
95
- plan = plan[plan_name]
96
- end
97
-
98
- plan.call(arguments)
99
- end
100
-
101
76
  # Loads a masterplan using the DSL, if it exists and hasn't been loaded already
102
77
  def load_masterplan filename
103
78
  if File.exists? filename and !@loaded_masterplans.include? filename
@@ -126,6 +101,12 @@ module CLI
126
101
 
127
102
  private
128
103
 
104
+ def method_missing(symbol, *args)
105
+ super
106
+ rescue NoMethodError
107
+ raise MissingConfigurationError, symbol
108
+ end
109
+
129
110
  # Walks up the file tree looking for masterplans.
130
111
  def lookup_and_load_masterplans
131
112
  load_masterplan File.join(Dir.pwd, PLANFILE)
@@ -17,5 +17,11 @@ module CLI
17
17
  super "#{message}: #{directory} does not exist or is not a directory"
18
18
  end
19
19
  end
20
+
21
+ class MissingConfigurationError < Error
22
+ def initialize(attribute)
23
+ super "#{attribute} has not been defined. Call `configure :#{attribute}[, value]` in a `#{Configuration::PLANFILE}` to set it."
24
+ end
25
+ end
20
26
  end
21
27
  end
@@ -48,6 +48,7 @@ module CLI
48
48
  def call(options=nil)
49
49
  raise NotImplementedError
50
50
  end
51
+ alias_method :execute, :call
51
52
 
52
53
  def add_alias(alias_to)
53
54
  config.define_alias(alias_to.to_s, name)
@@ -6,8 +6,8 @@ module CLI
6
6
  end
7
7
 
8
8
  module VERSION
9
- RELEASE = 0
10
- MAJOR = 7
9
+ RELEASE = 1
10
+ MAJOR = 0
11
11
  MINOR = 0
12
12
  PATCH = nil
13
13
 
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.7.0
4
+ version: 1.0.0
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-03-08 00:00:00.000000000 Z
11
+ date: 2019-10-08 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: cli-ui
@@ -103,7 +103,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
103
103
  - !ruby/object:Gem::Version
104
104
  version: '0'
105
105
  requirements: []
106
- rubygems_version: 3.0.2
106
+ rubygems_version: 3.0.4
107
107
  signing_key:
108
108
  specification_version: 4
109
109
  summary: Mastermind is a framework for constructing command line toolboxes.