bashly 1.2.0 → 1.2.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.
Files changed (42) hide show
  1. checksums.yaml +4 -4
  2. data/lib/bashly/commands/shell.rb +1 -1
  3. data/lib/bashly/concerns/completions.rb +3 -3
  4. data/lib/bashly/config_validator.rb +4 -0
  5. data/lib/bashly/docs/env.yml +10 -0
  6. data/lib/bashly/docs/flag.yml +23 -0
  7. data/lib/bashly/extensions/array.rb +6 -0
  8. data/lib/bashly/libraries/render/mandoc/mandoc.gtx +7 -4
  9. data/lib/bashly/libraries/render/markdown/markdown.gtx +7 -3
  10. data/lib/bashly/libraries/settings/settings.yml +5 -0
  11. data/lib/bashly/libraries/strings/strings.yml +4 -0
  12. data/lib/bashly/script/command.rb +9 -208
  13. data/lib/bashly/script/environment_variable.rb +5 -1
  14. data/lib/bashly/script/flag.rb +5 -2
  15. data/lib/bashly/script/introspection/arguments.rb +41 -0
  16. data/lib/bashly/script/introspection/commands.rb +111 -0
  17. data/lib/bashly/script/introspection/dependencies.rb +16 -0
  18. data/lib/bashly/script/introspection/environment_variables.rb +47 -0
  19. data/lib/bashly/script/introspection/examples.rb +14 -0
  20. data/lib/bashly/script/introspection/flags.rb +58 -0
  21. data/lib/bashly/script/introspection/visibility.rb +19 -0
  22. data/lib/bashly/settings.rb +5 -0
  23. data/lib/bashly/version.rb +1 -1
  24. data/lib/bashly/views/argument/usage.gtx +3 -3
  25. data/lib/bashly/views/argument/validations.gtx +1 -0
  26. data/lib/bashly/views/command/command_filter.gtx +3 -1
  27. data/lib/bashly/views/command/environment_variables_filter.gtx +6 -0
  28. data/lib/bashly/views/command/long_usage.gtx +8 -4
  29. data/lib/bashly/views/command/needy_flags_filter.gtx +9 -0
  30. data/lib/bashly/views/command/normalize_input_function.gtx +5 -1
  31. data/lib/bashly/views/command/parse_requirements.gtx +1 -0
  32. data/lib/bashly/views/command/usage_commands.gtx +10 -3
  33. data/lib/bashly/views/command/usage_environment_variables.gtx +20 -4
  34. data/lib/bashly/views/command/usage_flags.gtx +10 -2
  35. data/lib/bashly/views/command/whitelist_filter.gtx +2 -0
  36. data/lib/bashly/views/environment_variable/usage.gtx +2 -2
  37. data/lib/bashly/views/environment_variable/validations.gtx +9 -0
  38. data/lib/bashly/views/flag/needs.gtx +22 -0
  39. data/lib/bashly/views/flag/usage.gtx +11 -3
  40. data/lib/bashly/views/flag/validations.gtx +1 -0
  41. data/lib/bashly.rb +27 -40
  42. metadata +27 -3
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 5000e81ecf047c31547832097355e0d1b5de630041ef2533a80ce70f146e8282
4
- data.tar.gz: 9c03b8ef9215a317ebfd75333304065c271e77296a55753de65e612f9d1a8432
3
+ metadata.gz: cde8d4a8294ffd7dee63567440b351ffbd67b69f1295306b20931b8f4328a77c
4
+ data.tar.gz: e99ca77abffd677b1469e145383ef74a90a28da29af88625d7bf378563909031
5
5
  SHA512:
6
- metadata.gz: c6dc6b49075f6e250aec3204f6b9ff2bd6e4236da1bcc5229691565dcc8a4ff4c1d1c06a139c092eae33c61123753cdb542583a69596caf299f87c6ccf3d4f07
7
- data.tar.gz: ab9d9fc13a779bfd4d71adbae49e66961c697a46e5cb386875dac1d9e1b021828a2821332f2687d16b87b3fb820cdc006b61d86e7425dd6fdb37ac7aceb12b33
6
+ metadata.gz: 3f33a67b5da7958d3dccc0e8ada2f5fb097d786fd35045e8a8148ac543ec1326e0d1da51c2b264b674ba1f8ffb1c5dc868dc2244afccc5b8d93c7429b68797c1
7
+ data.tar.gz: 9972faa37b1f14b4dd38be6c0d15a743640f065357b7a347299f3255a8f50377a580e64b284631ae99cf8eb1207045ce08eb4c9e65860d67827a55be435219e3
@@ -19,7 +19,7 @@ module Bashly
19
19
  terminal = MisterBin::Terminal.new runner, {
20
20
  autocomplete: autocomplete,
21
21
  show_usage: true,
22
- prompt: "\n$> bashly ",
22
+ prompt: '$ bashly ',
23
23
  }
24
24
 
25
25
  terminal.on('help') { runner.run %w[--help] }
@@ -29,7 +29,7 @@ module Bashly
29
29
  end
30
30
  end
31
31
 
32
- public_commands.each do |command|
32
+ visible_commands.each do |command|
33
33
  result.merge! command.completion_data(with_version: false)
34
34
  end
35
35
 
@@ -62,7 +62,7 @@ module Bashly
62
62
  end
63
63
 
64
64
  def completion_flag_names
65
- public_flags.map(&:name) + public_flags.map(&:short)
65
+ visible_flags.map(&:name) + visible_flags.map(&:short)
66
66
  end
67
67
 
68
68
  def completion_allowed_args
@@ -73,7 +73,7 @@ module Bashly
73
73
  trivial_flags = %w[--help -h]
74
74
  trivial_flags += %w[--version -v] if with_version
75
75
  all = (
76
- public_command_aliases + trivial_flags +
76
+ visible_command_aliases + trivial_flags +
77
77
  completion_flag_names + completion_allowed_args
78
78
  )
79
79
 
@@ -231,6 +231,10 @@ module Bashly
231
231
  assert value['args'].last['repeatable'],
232
232
  "#{key}.args cannot contain a repeatable arg unless it is the last one"
233
233
  end
234
+
235
+ required_order = value['args'].map { |x| x['required'] ? true : false }
236
+ refute required_order.include_sequence?(false, true),
237
+ "#{key}.args cannot contain required arg after optional arg"
234
238
  end
235
239
 
236
240
  if value['expose']
@@ -49,6 +49,16 @@ environment_variable.required:
49
49
  help: Specify that this variable is required.
50
50
  url: https://bashly.dannyb.co/configuration/environment-variable/#required
51
51
  example: |-
52
+ environment_variables:
52
53
  - name: api_key
53
54
  help: Your API key
54
55
  required: true
56
+
57
+ environment_variable.validate:
58
+ help: Apply custom validation functions.
59
+
60
+ url: https://bashly.dannyb.co/configuration/environment-variable/#validate
61
+ example: |-
62
+ environment_variables:
63
+ - name: build_dir
64
+ validate: dir_exists
@@ -113,6 +113,29 @@ flag.long:
113
113
  short: -s
114
114
  help: Clone using SSH
115
115
 
116
+ flag.needs:
117
+ help: Specify that this flag needs one or more additional flags. Use the long name of these needed flags.
118
+ url: https://bashly.dannyb.co/configuration/flag/#needs
119
+ example: |-
120
+ flags:
121
+ - long: --add
122
+ arg: alias
123
+
124
+ # When using --add, --command and --target must also be provided
125
+ needs: [--command, --target]
126
+
127
+ - long: --command
128
+ arg: command
129
+ help: Command for the alias
130
+
131
+ # Note that this relationship is marked on both sides
132
+ needs: [--add]
133
+
134
+ - long: --target
135
+ arg: target
136
+ help: Where to add the alias
137
+ needs: [--add]
138
+
116
139
  flag.private:
117
140
  help: Specify that this flag should not be displayed in the help text.
118
141
  url: https://bashly.dannyb.co/configuration/flag/#private
@@ -13,4 +13,10 @@ class Array
13
13
  def nonuniq
14
14
  tally.select { |_key, count| count > 1 }.keys
15
15
  end
16
+
17
+ def include_sequence?(*elements)
18
+ return false if elements.empty?
19
+
20
+ each_cons(elements.size).any?(elements)
21
+ end
16
22
  end
@@ -43,7 +43,7 @@ if extensible
43
43
  end
44
44
  >
45
45
 
46
- if public_commands.any?
46
+ if visible_commands.any?
47
47
  grouped_commands.each do |group, commands|
48
48
  > {{ group.gsub(/:$/, '').upcase }}
49
49
  > ==================================================
@@ -127,6 +127,9 @@ if flags.any?
127
127
  if flag.conflicts
128
128
  > - Conflicts With: **{{ flag.conflicts.join(', ') }}**
129
129
  end
130
+ if flag.needs
131
+ > - Needs: **{{ flag.needs.join(', ') }}**
132
+ end
130
133
  >
131
134
  end
132
135
  end
@@ -147,11 +150,11 @@ if dependencies.any?
147
150
  end
148
151
  end
149
152
 
150
- if public_environment_variables.any?
153
+ if visible_environment_variables.any?
151
154
  > ENVIRONMENT VARIABLES
152
155
  > ==================================================
153
156
  >
154
- public_environment_variables.each do |environment_variable|
157
+ visible_environment_variables.each do |environment_variable|
155
158
  > {{ environment_variable.name.upcase }}
156
159
  > --------------------------------------------------
157
160
  >
@@ -183,7 +186,7 @@ end
183
186
 
184
187
  see_also = []
185
188
  see_also << parents.first if parents.any?
186
- see_also += public_commands.map { |x| x.full_name.to_hyphen } if public_commands.any?
189
+ see_also += visible_commands.map { |x| x.full_name.to_hyphen } if visible_commands.any?
187
190
  see_also += x_mandoc_see_also if x_mandoc_see_also && x_mandoc_see_also.is_a?(Array)
188
191
  see_also.map! do |item|
189
192
  item.match(/(.+)(\(\d\))/) ? "**#{$1}**#{$2}" : "**#{item}**(1)"
@@ -62,10 +62,10 @@ end
62
62
 
63
63
  # === Environment Variables
64
64
 
65
- if public_environment_variables.any?
65
+ if visible_environment_variables.any?
66
66
  > ## Environment Variables
67
67
  >
68
- public_environment_variables.each do |environment_variable|
68
+ visible_environment_variables.each do |environment_variable|
69
69
  attributes = environment_variable.required || environment_variable.default
70
70
 
71
71
  > #### *{{ environment_variable.name.upcase }}*
@@ -152,7 +152,8 @@ if flags.any?
152
152
  > ## Options
153
153
  >
154
154
  flags.each do |flag|
155
- attributes = flag.required || flag.repeatable || flag.default || flag.allowed || flag.conflicts
155
+ attributes = flag.required || flag.repeatable || flag.default ||
156
+ flag.allowed || flag.conflicts || flag.needs
156
157
 
157
158
  > #### *{{ flag.usage_string }}*
158
159
  >
@@ -177,6 +178,9 @@ if flags.any?
177
178
  if flag.conflicts
178
179
  > | Conflicts With: | *{{ flag.conflicts.join(', ') }}*
179
180
  end
181
+ if flag.needs
182
+ > | Needs: | *{{ flag.needs.join(', ') }}*
183
+ end
180
184
  >
181
185
  end
182
186
  end
@@ -59,6 +59,11 @@ partials_extension: sh
59
59
  # required arguments
60
60
  show_examples_on_error: false
61
61
 
62
+ # When using private commands, flags, or environment variables, you may set
63
+ # this option to a name of an environment variable that, if set, will reveal
64
+ # all the private elements in the usage texts, as if they were public.
65
+ private_reveal_key: ~
66
+
62
67
  # Display various usage elements in color by providing the name of the color
63
68
  # function. The value for each property is a name of a function that is
64
69
  # available in your script, for example: `green` or `bold`.
@@ -18,6 +18,8 @@ required: "(required)"
18
18
  repeatable: "(repeatable)"
19
19
  default: "Default: %{value}"
20
20
  allowed: "Allowed: %{values}"
21
+ needs: "Needs: %{values}"
22
+ conflicts: "Conflicts: %{values}"
21
23
 
22
24
  # Fixed flags help text
23
25
  help_flag_text: Show this help
@@ -25,6 +27,7 @@ version_flag_text: Show version number
25
27
 
26
28
  # Error messages
27
29
  flag_requires_an_argument: "%{name} requires an argument: %{usage}"
30
+ flag_needs_another: "%{name} needs %{need}"
28
31
  invalid_argument: "invalid argument: %s"
29
32
  invalid_flag: "invalid option: %s"
30
33
  invalid_command: "invalid command: %s"
@@ -39,3 +42,4 @@ disallowed_argument: "%{name} must be one of: %{allowed}"
39
42
  disallowed_environment_variable: "%{name} environment variable must be one of: %{allowed}"
40
43
  unsupported_bash_version: "bash version 4 or higher is required"
41
44
  validation_error: "validation error in %s:\\n%s"
45
+ environment_variable_validation_error: "validation error in environment variable %s:\\n%s"
@@ -2,6 +2,13 @@ module Bashly
2
2
  module Script
3
3
  class Command < Base
4
4
  include Completions::Command
5
+ include Introspection::Arguments
6
+ include Introspection::Commands
7
+ include Introspection::Dependencies
8
+ include Introspection::EnvironmentVariables
9
+ include Introspection::Examples
10
+ include Introspection::Flags
11
+ include Introspection::Visibility
5
12
 
6
13
  class << self
7
14
  def option_keys
@@ -39,15 +46,6 @@ module Bashly
39
46
  options['alias'].is_a?(String) ? [options['alias']] : options['alias']
40
47
  end
41
48
 
42
- # Returns an array of Arguments
43
- def args
44
- return [] unless options['args']
45
-
46
- options['args'].map do |options|
47
- Argument.new options
48
- end
49
- end
50
-
51
49
  # Returns a string suitable to be a headline
52
50
  def caption_string
53
51
  help.empty? ? full_name : "#{full_name} - #{summary}"
@@ -58,123 +56,12 @@ module Bashly
58
56
  @catch_all ||= CatchAll.from_config options['catch_all']
59
57
  end
60
58
 
61
- # Returns a full list of the Command names and aliases combined
62
- def command_aliases
63
- commands.map(&:aliases).flatten
64
- end
65
-
66
- # Returns a data structure for displaying subcommands help
67
- def command_help_data
68
- result = {}
69
-
70
- public_commands.each do |command|
71
- result[command.group_string] ||= {}
72
- result[command.group_string][command.name] = { summary: command.summary_string }
73
- next unless command.expose
74
-
75
- command.public_commands.each do |subcommand|
76
- result[command.group_string]["#{command.name} #{subcommand.name}"] = {
77
- summary: subcommand.summary_string,
78
- help_only: command.expose != 'always',
79
- }
80
- end
81
- end
82
-
83
- result
84
- end
85
-
86
- # Returns only the names of the Commands
87
- def command_names
88
- commands.map(&:name)
89
- end
90
-
91
- # Returns an array of the Commands
92
- def commands
93
- return [] unless options['commands']
94
-
95
- options['commands'].map do |options|
96
- result = Command.new options
97
- result.parents = parents + [name]
98
- result.parent_command = self
99
- result
100
- end
101
- end
102
-
103
- # Returns a flat array containing all the commands in this tree.
104
- # This includes children + grandchildren (recursive), and may include self
105
- def deep_commands(include_self: false)
106
- result = []
107
- result << self if include_self
108
- commands.each do |command|
109
- result << command
110
- if command.commands.any?
111
- result += command.deep_commands
112
- end
113
- end
114
- result
115
- end
116
-
117
- # If any of this command's subcommands has the default option set to
118
- # true, this default command will be returned, nil otherwise.
119
- def default_command
120
- commands.find(&:default)
121
- end
122
-
123
- # Returns an array of all the default Args
124
- def default_args
125
- args.select(&:default)
126
- end
127
-
128
- # Returns an array of all the default Environment Variables
129
- def default_environment_variables
130
- environment_variables.select(&:default)
131
- end
132
-
133
- # Returns an array of all the default Flags
134
- def default_flags
135
- flags.select(&:default)
136
- end
137
-
138
- # Returns an array of Dependency objects
139
- def dependencies
140
- return [] unless options['dependencies']
141
-
142
- @dependencies ||= options['dependencies'].map do |key, value|
143
- Dependency.from_config key, value
144
- end
145
- end
146
-
147
- # Returns an array of EnvironmentVariable objects
148
- def environment_variables
149
- return [] unless options['environment_variables']
150
-
151
- options['environment_variables'].map do |options|
152
- EnvironmentVariable.new options
153
- end
154
- end
155
-
156
- # Returns an array of examples
157
- def examples
158
- return nil unless options['examples']
159
-
160
- options['examples'].is_a?(Array) ? options['examples'] : [options['examples']]
161
- end
162
-
163
59
  # Returns the filename that is expected to hold the user code for this
164
60
  # command
165
61
  def filename
166
62
  options['filename'] || implicit_filename
167
63
  end
168
64
 
169
- # Returns an array of Flags
170
- def flags
171
- return [] unless options['flags']
172
-
173
- options['flags'].map do |options|
174
- Flag.new options
175
- end
176
- end
177
-
178
65
  # Returns a unique name, suitable to be used in a bash function
179
66
  def function_name
180
67
  options['function'] || full_name.to_underscore
@@ -186,12 +73,6 @@ module Bashly
186
73
  parents.any? ? (parents + [name]).join(' ') : name
187
74
  end
188
75
 
189
- # Returns true if this command's flags should be considered as gloal
190
- # flags, and cascade to subcommands
191
- def global_flags?
192
- flags.any? and commands.any?
193
- end
194
-
195
76
  # Returns the string for the group caption
196
77
  def group_string
197
78
  if group
@@ -201,23 +82,6 @@ module Bashly
201
82
  end
202
83
  end
203
84
 
204
- # Returns subcommands by group
205
- def grouped_commands
206
- result = {}
207
-
208
- public_commands.each do |command|
209
- result[command.group_string] ||= []
210
- result[command.group_string] << command
211
- next unless command.expose
212
-
213
- command.public_commands.each do |subcommand|
214
- result[command.group_string] << subcommand
215
- end
216
- end
217
-
218
- result
219
- end
220
-
221
85
  # Returns true if this command, or any subcommand (deep) as any arg or
222
86
  # flag with arg that is defined as unique
223
87
  def has_unique_args_or_flags?
@@ -246,56 +110,11 @@ module Bashly
246
110
  @parents ||= []
247
111
  end
248
112
 
249
- # Returns only commands that are not private
250
- def public_commands
251
- commands.reject(&:private)
252
- end
253
-
254
- # Returns a full list of the public Command names and aliases combined
255
- def public_command_aliases
256
- public_commands.map(&:aliases).flatten
257
- end
258
-
259
- # Returns only environment variables that are not private
260
- def public_environment_variables
261
- environment_variables.reject(&:private)
262
- end
263
-
264
- # Returns only flags that are not private
265
- def public_flags
266
- flags.reject(&:private)
267
- end
268
-
269
- # Returns true if one of the args is repeatable
270
- def repeatable_arg_exist?
271
- args.any?(&:repeatable)
272
- end
273
-
274
- # Returns an array of all the required Arguments
275
- def required_args
276
- args.select(&:required)
277
- end
278
-
279
- # Returns an array of all the required EnvironmentVariables
280
- def required_environment_variables
281
- environment_variables.select(&:required)
282
- end
283
-
284
- # Returns an array of all the required Flags
285
- def required_flags
286
- flags.select(&:required)
287
- end
288
-
289
113
  # Returns true if this is the root command (no parents)
290
114
  def root_command?
291
115
  parents.empty?
292
116
  end
293
117
 
294
- # Returns true if one of the flags matches the provided short code
295
- def short_flag_exist?(flag)
296
- flags.any? { |f| f.short == flag }
297
- end
298
-
299
118
  # Returns the summary string
300
119
  def summary_string
301
120
  if default
@@ -322,11 +141,8 @@ module Bashly
322
141
  result.compact.join ' '
323
142
  end
324
143
 
325
- # Returns an array of args usage_string for the command's usage_string
326
- def usage_string_args
327
- args.map(&:usage_string)
328
- end
329
-
144
+ # Returns a base usage string that considers whether this command is the
145
+ # default, and if it has any parents. Used internally by `usage_string`.
330
146
  def base_usage_pattern
331
147
  usage_pattern = default ? "[#{name}]" : name
332
148
  parents.any? ? (parents + [usage_pattern]).join(' ') : usage_pattern
@@ -344,21 +160,6 @@ module Bashly
344
160
  @validatables ||= args.select(&:validate) + flags.select(&:validate)
345
161
  end
346
162
 
347
- # Returns an array of all the args with a whitelist
348
- def whitelisted_args
349
- args.select(&:allowed)
350
- end
351
-
352
- # Returns an array of all the environment_variables with a whitelist arg
353
- def whitelisted_environment_variables
354
- environment_variables.select(&:allowed)
355
- end
356
-
357
- # Returns an array of all the flags with a whitelist arg
358
- def whitelisted_flags
359
- flags.select(&:allowed)
360
- end
361
-
362
163
  private
363
164
 
364
165
  # Returns either a flat filename (docker_status_command.sh) or a nested
@@ -1,9 +1,13 @@
1
1
  module Bashly
2
2
  module Script
3
3
  class EnvironmentVariable < Base
4
+ include Introspection::Visibility
5
+
4
6
  class << self
5
7
  def option_keys
6
- @option_keys ||= %i[allowed default help name required private]
8
+ @option_keys ||= %i[
9
+ allowed default help name required private validate
10
+ ]
7
11
  end
8
12
  end
9
13
 
@@ -1,13 +1,16 @@
1
+ require 'shellwords'
2
+
1
3
  module Bashly
2
4
  module Script
3
5
  class Flag < Base
4
6
  include Completions::Flag
7
+ include Introspection::Visibility
5
8
 
6
9
  class << self
7
10
  def option_keys
8
11
  @option_keys ||= %i[
9
- allowed arg completions conflicts default help long repeatable
10
- required short unique validate private
12
+ allowed arg completions conflicts default help long needs
13
+ repeatable required short unique validate private
11
14
  ]
12
15
  end
13
16
  end
@@ -0,0 +1,41 @@
1
+ module Bashly
2
+ module Script
3
+ module Introspection
4
+ module Arguments
5
+ # Returns an array of Arguments
6
+ def args
7
+ return [] unless options['args']
8
+
9
+ options['args'].map do |options|
10
+ Argument.new options
11
+ end
12
+ end
13
+
14
+ # Returns an array of all the default Args
15
+ def default_args
16
+ args.select(&:default)
17
+ end
18
+
19
+ # Returns true if one of the args is repeatable
20
+ def repeatable_arg_exist?
21
+ args.any?(&:repeatable)
22
+ end
23
+
24
+ # Returns an array of all the required Arguments
25
+ def required_args
26
+ args.select(&:required)
27
+ end
28
+
29
+ # Returns an array of args usage_string for the command's usage_string
30
+ def usage_string_args
31
+ args.map(&:usage_string)
32
+ end
33
+
34
+ # Returns an array of all the args with a whitelist
35
+ def whitelisted_args
36
+ args.select(&:allowed)
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end