cli-mastermind 0.7.0 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +5 -5
- data/lib/cli/mastermind.rb +50 -15
- data/lib/cli/mastermind/configuration.rb +7 -26
- data/lib/cli/mastermind/errors.rb +6 -0
- data/lib/cli/mastermind/plan.rb +1 -0
- data/lib/cli/mastermind/version.rb +2 -2
- metadata +3 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: cd49841f87237f2a7223cd748c61772244232e84cdf46112241e291e73f1a311
|
4
|
+
data.tar.gz: '0197b2452f75c1c1c7ac50d84621a9dfef1635d04781c500fb4ca8516f2b65e5'
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
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
|
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
|
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
|
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
|
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
|
|
data/lib/cli/mastermind.rb
CHANGED
@@ -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
|
-
|
78
|
-
|
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
|
-
|
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
|
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
|
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
|
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!(
|
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 =
|
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 ||
|
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
|
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 :
|
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
|
data/lib/cli/mastermind/plan.rb
CHANGED
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.
|
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-
|
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.
|
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.
|