bashly 0.8.0 → 0.8.3

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 (116) hide show
  1. checksums.yaml +4 -4
  2. data/lib/bashly/commands/generate.rb +40 -4
  3. data/lib/bashly/concerns/completions.rb +60 -35
  4. data/lib/bashly/concerns/renderable.rb +19 -6
  5. data/lib/bashly/concerns/validation_helpers.rb +9 -3
  6. data/lib/bashly/config_validator.rb +19 -2
  7. data/lib/bashly/extensions/array.rb +2 -2
  8. data/lib/bashly/extensions/string.rb +2 -2
  9. data/lib/bashly/script/command.rb +127 -19
  10. data/lib/bashly/script/flag.rb +4 -2
  11. data/lib/bashly/templates/strings.yml +1 -0
  12. data/lib/bashly/version.rb +1 -1
  13. data/lib/bashly/views/README.md +6 -0
  14. data/lib/bashly/views/argument/usage.gtx +15 -0
  15. data/lib/bashly/views/argument/validations.gtx +11 -0
  16. data/lib/bashly/views/command/catch_all_filter.gtx +10 -0
  17. data/lib/bashly/views/command/command_fallback.gtx +42 -0
  18. data/lib/bashly/views/command/command_filter.gtx +29 -0
  19. data/lib/bashly/views/command/command_functions.gtx +5 -0
  20. data/lib/bashly/views/command/default_assignments.gtx +13 -0
  21. data/lib/bashly/views/command/default_initialize_script.gtx +7 -0
  22. data/lib/bashly/views/command/default_root_script.gtx +4 -0
  23. data/lib/bashly/views/command/default_script.gtx +5 -0
  24. data/lib/bashly/views/command/dependencies_filter.gtx +10 -0
  25. data/lib/bashly/views/command/environment_variables_filter.gtx +18 -0
  26. data/lib/bashly/views/command/fixed_flags_filter.gtx +20 -0
  27. data/lib/bashly/views/command/footer.gtx +5 -0
  28. data/lib/bashly/views/command/function.gtx +6 -0
  29. data/lib/bashly/views/command/initialize.gtx +12 -0
  30. data/lib/bashly/views/command/inspect_args.gtx +21 -0
  31. data/lib/bashly/views/command/long_usage.gtx +13 -0
  32. data/lib/bashly/views/command/master_script.gtx +17 -0
  33. data/lib/bashly/views/command/normalize_input.gtx +25 -0
  34. data/lib/bashly/views/command/parse_requirements.gtx +26 -0
  35. data/lib/bashly/views/command/parse_requirements_case.gtx +11 -0
  36. data/lib/bashly/views/command/parse_requirements_case_catch_all.gtx +23 -0
  37. data/lib/bashly/views/command/parse_requirements_case_repeatable.gtx +24 -0
  38. data/lib/bashly/views/command/parse_requirements_case_simple.gtx +25 -0
  39. data/lib/bashly/views/command/parse_requirements_while.gtx +33 -0
  40. data/lib/bashly/views/command/required_args_filter.gtx +13 -0
  41. data/lib/bashly/views/command/required_flags_filter.gtx +12 -0
  42. data/lib/bashly/views/command/root_command.gtx +6 -0
  43. data/lib/bashly/views/command/run.gtx +28 -0
  44. data/lib/bashly/views/command/usage.gtx +50 -0
  45. data/lib/bashly/views/command/usage_args.gtx +20 -0
  46. data/lib/bashly/views/command/usage_commands.gtx +18 -0
  47. data/lib/bashly/views/command/usage_environment_variables.gtx +10 -0
  48. data/lib/bashly/views/command/usage_examples.gtx +10 -0
  49. data/lib/bashly/views/command/usage_fixed_flags.gtx +13 -0
  50. data/lib/bashly/views/command/usage_flags.gtx +7 -0
  51. data/lib/bashly/views/command/user_filter.gtx +13 -0
  52. data/lib/bashly/views/command/user_lib.gtx +11 -0
  53. data/lib/bashly/views/command/version_command.gtx +6 -0
  54. data/lib/bashly/views/command/whitelist_filter.gtx +41 -0
  55. data/lib/bashly/views/environment_variable/usage.gtx +11 -0
  56. data/lib/bashly/views/flag/case.gtx +6 -0
  57. data/lib/bashly/views/flag/case_arg.gtx +25 -0
  58. data/lib/bashly/views/flag/case_no_arg.gtx +11 -0
  59. data/lib/bashly/views/flag/conflicts.gtx +22 -0
  60. data/lib/bashly/views/flag/usage.gtx +15 -0
  61. data/lib/bashly/views/flag/validations.gtx +11 -0
  62. data/lib/bashly/views/wrapper/bash3_bouncer.gtx +7 -0
  63. data/lib/bashly/views/wrapper/header.gtx +5 -0
  64. data/lib/bashly/views/wrapper/wrapper.gtx +8 -0
  65. metadata +91 -62
  66. data/lib/bashly/concerns/command_scopes.rb +0 -68
  67. data/lib/bashly/views/argument/usage.erb +0 -10
  68. data/lib/bashly/views/argument/validations.erb +0 -8
  69. data/lib/bashly/views/command/catch_all_filter.erb +0 -7
  70. data/lib/bashly/views/command/command_fallback.erb +0 -45
  71. data/lib/bashly/views/command/command_filter.erb +0 -22
  72. data/lib/bashly/views/command/command_functions.erb +0 -4
  73. data/lib/bashly/views/command/default_assignments.erb +0 -7
  74. data/lib/bashly/views/command/default_initialize_script.erb +0 -6
  75. data/lib/bashly/views/command/default_root_script.erb +0 -3
  76. data/lib/bashly/views/command/default_script.erb +0 -4
  77. data/lib/bashly/views/command/dependencies_filter.erb +0 -9
  78. data/lib/bashly/views/command/environment_variables_filter.erb +0 -14
  79. data/lib/bashly/views/command/fixed_flags_filter.erb +0 -24
  80. data/lib/bashly/views/command/footer.erb +0 -3
  81. data/lib/bashly/views/command/function.erb +0 -4
  82. data/lib/bashly/views/command/initialize.erb +0 -8
  83. data/lib/bashly/views/command/inspect_args.erb +0 -19
  84. data/lib/bashly/views/command/master_script.erb +0 -14
  85. data/lib/bashly/views/command/normalize_input.erb +0 -24
  86. data/lib/bashly/views/command/parse_requirements.erb +0 -22
  87. data/lib/bashly/views/command/parse_requirements_case.erb +0 -8
  88. data/lib/bashly/views/command/parse_requirements_case_catch_all.erb +0 -18
  89. data/lib/bashly/views/command/parse_requirements_case_repeatable.erb +0 -18
  90. data/lib/bashly/views/command/parse_requirements_case_simple.erb +0 -18
  91. data/lib/bashly/views/command/parse_requirements_while.erb +0 -26
  92. data/lib/bashly/views/command/required_args_filter.erb +0 -7
  93. data/lib/bashly/views/command/required_flags_filter.erb +0 -7
  94. data/lib/bashly/views/command/root_command.erb +0 -4
  95. data/lib/bashly/views/command/run.erb +0 -23
  96. data/lib/bashly/views/command/usage.erb +0 -50
  97. data/lib/bashly/views/command/usage_args.erb +0 -14
  98. data/lib/bashly/views/command/usage_commands.erb +0 -18
  99. data/lib/bashly/views/command/usage_environment_variables.erb +0 -6
  100. data/lib/bashly/views/command/usage_examples.erb +0 -7
  101. data/lib/bashly/views/command/usage_fixed_flags.erb +0 -17
  102. data/lib/bashly/views/command/usage_flags.erb +0 -4
  103. data/lib/bashly/views/command/user_filter.erb +0 -11
  104. data/lib/bashly/views/command/user_lib.erb +0 -6
  105. data/lib/bashly/views/command/version_command.erb +0 -4
  106. data/lib/bashly/views/command/whitelist_filter.erb +0 -33
  107. data/lib/bashly/views/environment_variable/usage.erb +0 -7
  108. data/lib/bashly/views/flag/case.erb +0 -4
  109. data/lib/bashly/views/flag/case_arg.erb +0 -19
  110. data/lib/bashly/views/flag/case_no_arg.erb +0 -8
  111. data/lib/bashly/views/flag/conflicts.erb +0 -18
  112. data/lib/bashly/views/flag/usage.erb +0 -10
  113. data/lib/bashly/views/flag/validations.erb +0 -8
  114. data/lib/bashly/views/wrapper/bash3_bouncer.erb +0 -5
  115. data/lib/bashly/views/wrapper/header.erb +0 -4
  116. data/lib/bashly/views/wrapper/wrapper.erb +0 -6
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d3d0126229189987753e653a635054113961b1e9e0244d4cdb956beb1d5aff94
4
- data.tar.gz: 886987a65edc65d16d2134589cbb4ee9f3fcb9e0b1819c8a87211bcd2d164d0c
3
+ metadata.gz: f3cd1ba341eabd9f1c3acead91ff8c56878db7eb07f3ef6e8f29ceb057cbce04
4
+ data.tar.gz: 9c8fdd62e401cf1d693d76163e61d93779fcbda67406e301f06d9be554ccbe96
5
5
  SHA512:
6
- metadata.gz: 880579e8bb650c8835cc7992054f3f4dd3e765ddc1b7072f83ed6fc9049d4b56b38c4a64220c3dd154f8fdbf199462cca1ef955d36d465a0d57ecc00715bd85e
7
- data.tar.gz: efa2f73f9eed6d5ba1ebb432e37ef639e618bc1556ba83059ccad4804b6568b407d86f91fe11faa3335b69103e368fdb9bf1339f90af1fd15cb742b91163b911
6
+ metadata.gz: e690eda52685f2b34ae5773b79b48ede2972d8e328dcc193c90d4a60b22a768ccaa1444e52652f0b11513cced19a02eff295ac011ca414d254d4d9e3b8aacf4a
7
+ data.tar.gz: 5b7bf250ea4cdc8d194dee285a6a61748e5d649b455d1b3836a35df1c511b6781d84ac966903a445bd84bcb471604e15c016bbd88f643afe7f030c8df65e6563
@@ -1,3 +1,5 @@
1
+ require 'filewatcher'
2
+
1
3
  module Bashly
2
4
  module Commands
3
5
  class Generate < Base
@@ -9,7 +11,8 @@ module Bashly
9
11
  option "-f --force", "Overwrite existing files"
10
12
  option "-q --quiet", "Disable on-screen progress report"
11
13
  option "-u --upgrade", "Upgrade all added library functions"
12
- option "-w --wrap FUNCTION", "Wrap the entire script in a function so it can also be sourced"
14
+ option "-w --watch", "Watch the source directory for changes and regenerate on change"
15
+ option "-r --wrap FUNCTION", "Wrap the entire script in a function so it can also be sourced"
13
16
  option "-e --env ENV", "Force the generation environment (see BASHLY_ENV)"
14
17
 
15
18
  environment "BASHLY_SOURCE_DIR", "The path containing the bashly configuration and source files [default: src]"
@@ -27,17 +30,50 @@ module Bashly
27
30
 
28
31
  example "bashly generate --force"
29
32
  example "bashly generate --wrap my_function"
33
+ example "bashly g -uw"
34
+
35
+ attr_reader :watching
30
36
 
31
37
  def run
38
+ Settings.env = args['--env'] if args['--env']
39
+ @watching = args['--watch']
40
+
41
+ generate
42
+ watch if watching
43
+ end
44
+
45
+ private
46
+
47
+ def watch
48
+ quiet_say "!txtgrn!watching!txtrst! #{Settings.source_dir}\n"
49
+
50
+ Filewatcher.new([Settings.source_dir]).watch do
51
+ reset
52
+ generate
53
+
54
+ rescue Bashly::ConfigurationError => e
55
+ say! "!undred!#{e.class}!txtrst!\n#{e.message}"
56
+
57
+ ensure
58
+ quiet_say "!txtgrn!waiting\n"
59
+
60
+ end
61
+ end
62
+
63
+ def generate
32
64
  with_valid_config do
33
- Settings.env = args['--env'] if args['--env']
34
65
  quiet_say "creating !txtgrn!production!txtrst! version" if Settings.production?
35
66
  generate_all_files
36
- quiet_say "run !txtpur!#{master_script_path} --help!txtrst! to test your bash script"
67
+ quiet_say "run !txtpur!#{master_script_path} --help!txtrst! to test your bash script" unless watching
37
68
  end
38
69
  end
39
70
 
40
- private
71
+ def reset
72
+ @config = nil
73
+ @config_validator = nil
74
+ @command = nil
75
+ @script = nil
76
+ end
41
77
 
42
78
  def quiet_say(message)
43
79
  say message unless args['--quiet']
@@ -1,51 +1,76 @@
1
1
  require 'completely'
2
2
 
3
3
  module Bashly
4
- # This is a `Command` concern responsible for providing bash completion data
4
+ # This is a `Command` and `Flag` concern responsible for providing bash
5
+ # completion data
5
6
  module Completions
6
- def completion_data(with_version: true)
7
- result = { full_name => completion_words(with_version: with_version) }
8
-
9
- commands.each do |command|
10
- result.merge! command.completion_data(with_version: false)
11
- end
7
+ module Flag
8
+ def completion_data(command_full_name)
9
+ result = {}
10
+ comps = allowed || completions
12
11
 
13
- result
14
- end
12
+ if comps
13
+ aliases.each do |name|
14
+ result["#{command_full_name}*#{name}"] = comps
15
+ end
16
+ end
15
17
 
16
- def completion_script
17
- completion_generator.script
18
+ result
19
+ end
18
20
  end
19
21
 
20
- def completion_function(name = nil)
21
- completion_generator.wrapper_function(name)
22
- end
22
+ module Command
23
+ def completion_data(with_version: true)
24
+ result = {}
23
25
 
24
- private
26
+ all_full_names.each do |name|
27
+ result[name] = completion_words(with_version: with_version)
28
+ flags.each do |flag|
29
+ result.merge! flag.completion_data(name)
30
+ end
31
+ end
32
+
33
+ commands.each do |command|
34
+ result.merge! command.completion_data(with_version: false)
35
+ end
25
36
 
26
- def completion_generator
27
- Completely::Completions.new(completion_data)
28
- end
37
+ result
38
+ end
29
39
 
30
- def completion_flag_names
31
- flags.map(&:name) + flags.map(&:short)
32
- end
40
+ def completion_script
41
+ completion_generator.script
42
+ end
33
43
 
34
- def completion_allowed_args
35
- flags.map(&:allowed).flatten + args.map(&:allowed).flatten
36
- end
44
+ def completion_function(name = nil)
45
+ completion_generator.wrapper_function(name)
46
+ end
37
47
 
38
- def completion_words(with_version: false)
39
- trivial_flags = %w[--help -h]
40
- trivial_flags += %w[--version -v] if with_version
41
- all = (
42
- command_names + trivial_flags +
43
- completion_flag_names + completion_allowed_args
44
- )
48
+ private
45
49
 
46
- all += completions if completions
47
- all.compact.uniq.sort
48
- end
50
+ def completion_generator
51
+ Completely::Completions.new(completion_data)
52
+ end
53
+
54
+ def completion_flag_names
55
+ flags.map(&:name) + flags.map(&:short)
56
+ end
57
+
58
+ def completion_allowed_args
59
+ args.map(&:allowed).flatten
60
+ end
61
+
62
+ def completion_words(with_version: false)
63
+ trivial_flags = %w[--help -h]
64
+ trivial_flags += %w[--version -v] if with_version
65
+ all = (
66
+ command_aliases + trivial_flags +
67
+ completion_flag_names + completion_allowed_args
68
+ )
49
69
 
70
+ all += completions if completions
71
+ all.compact.uniq.sort
72
+ end
73
+
74
+ end
50
75
  end
51
- end
76
+ end
@@ -1,12 +1,9 @@
1
- require 'erb'
1
+ require 'gtx'
2
2
 
3
3
  module Bashly
4
4
  module Renderable
5
5
  def render(view)
6
- template = File.read view_path(view)
7
- erb = ERB.new(template, trim_mode: '%-')
8
- erb.filename = "#{views_subfolder}.#{view}"
9
- erb.result binding
6
+ GTX.render_file view_path(view), context: binding, filename: "#{views_subfolder}.#{view}"
10
7
  end
11
8
 
12
9
  def strings
@@ -18,10 +15,26 @@ module Bashly
18
15
  "# #{id}" unless Settings.production?
19
16
  end
20
17
 
18
+ # Reads a file from the userspace (Settings.source_dir) and returns
19
+ # its contents. If the file is not found, returns a string with a hint.
20
+ def load_user_file(file, placeholder: true)
21
+ path = "#{Settings.source_dir}/#{file}"
22
+
23
+ content = if File.exist? path
24
+ File.read(path).remove_front_matter
25
+ elsif placeholder
26
+ %q[echo "error: cannot load file"]
27
+ else
28
+ ''
29
+ end
30
+
31
+ Settings.production? ? content : "#{view_marker path}\n#{content}"
32
+ end
33
+
21
34
  private
22
35
 
23
36
  def view_path(view)
24
- "#{self_views_path}/#{view}.erb"
37
+ "#{self_views_path}/#{view}.gtx"
25
38
  end
26
39
 
27
40
  def self_views_path
@@ -52,10 +52,16 @@ module Bashly
52
52
  end
53
53
  end
54
54
 
55
- def assert_uniq(key, value, array_key)
55
+ def assert_uniq(key, value, array_keys)
56
56
  return unless value
57
- list = value.map { |c| c[array_key] }.compact.flatten
58
- assert list.uniq?, "#{key} cannot have elements with similar #{array_key} values"
57
+ array_keys = [array_keys] unless array_keys.is_a? Array
58
+ list = []
59
+ array_keys.each do |array_key|
60
+ list += value.map { |c| c[array_key] }.compact.flatten
61
+ end
62
+
63
+ nonuniqs = list.nonuniq
64
+ assert nonuniqs.empty?, "#{key} contains non-unique elements (#{nonuniqs.join ', '}) in #{array_keys.join ' or '}"
59
65
  end
60
66
 
61
67
  def assert_string_or_array(key, value)
@@ -41,6 +41,11 @@ module Bashly
41
41
  "#{key} must be a boolean or a string"
42
42
  end
43
43
 
44
+ def assert_expose(key, value)
45
+ return unless value
46
+ assert [true, false, nil, 'always'].include?(value), "#{key} must be a boolean, or the string 'always'"
47
+ end
48
+
44
49
  def assert_arg(key, value)
45
50
  assert_hash key, value, Script::Argument.option_keys
46
51
  assert_string "#{key}.name", value['name']
@@ -61,6 +66,8 @@ module Bashly
61
66
  assert_hash key, value, Script::Flag.option_keys
62
67
  assert value['short'] || value['long'], "#{key} must have at least one of long or short name"
63
68
 
69
+ refute value['allowed'] && value['completions'], "#{key} cannot have both allowed and completions"
70
+
64
71
  assert_optional_string "#{key}.long", value['long']
65
72
  assert_optional_string "#{key}.short", value['short']
66
73
  assert_optional_string "#{key}.help", value['help']
@@ -72,6 +79,7 @@ module Bashly
72
79
  assert_boolean "#{key}.required", value['required']
73
80
  assert_array "#{key}.allowed", value['allowed'], of: :string
74
81
  assert_array "#{key}.conflicts", value['conflicts'], of: :string
82
+ assert_array "#{key}.completions", value['completions'], of: :string
75
83
 
76
84
  assert value['long'].match(/^--[a-zA-Z0-9_\-]+$/), "#{key}.long must be in the form of '--name'" if value['long']
77
85
  assert value['short'].match(/^-[a-zA-Z0-9]$/), "#{key}.short must be in the form of '-n'" if value['short']
@@ -86,6 +94,10 @@ module Bashly
86
94
  if value['allowed']
87
95
  assert value['arg'], "#{key}.allowed does not make sense without arg"
88
96
  end
97
+
98
+ if value['completions']
99
+ assert value['arg'], "#{key}.completions does not make sense without arg"
100
+ end
89
101
  end
90
102
 
91
103
  def assert_env_var(key, value)
@@ -110,6 +122,7 @@ module Bashly
110
122
 
111
123
  assert_boolean "#{key}.private", value['private']
112
124
  assert_boolean "#{key}.default", value['default']
125
+ assert_expose "#{key}.expose", value['expose']
113
126
  assert_version "#{key}.version", value['version']
114
127
  assert_catch_all "#{key}.catch_all", value['catch_all']
115
128
  assert_string_or_array "#{key}.alias", value['alias']
@@ -124,8 +137,7 @@ module Bashly
124
137
  assert_array "#{key}.environment_variables", value['environment_variables'], of: :env_var
125
138
  assert_array "#{key}.examples", value['examples'], of: :string
126
139
 
127
- assert_uniq "#{key}.commands", value['commands'], 'name'
128
- assert_uniq "#{key}.commands", value['commands'], 'alias'
140
+ assert_uniq "#{key}.commands", value['commands'], ['name', 'alias']
129
141
  assert_uniq "#{key}.flags", value['flags'], 'long'
130
142
  assert_uniq "#{key}.flags", value['flags'], 'short'
131
143
  assert_uniq "#{key}.args", value['args'], 'name'
@@ -135,11 +147,16 @@ module Bashly
135
147
  refute repeatable_arg, "#{key}.catch_all makes no sense with repeatable arg (#{repeatable_arg})"
136
148
  end
137
149
 
150
+ if value['expose']
151
+ assert value['commands'], "#{key}.expose makes no sense without commands"
152
+ end
153
+
138
154
  if key == "root"
139
155
  refute value['alias'], "#{key}.alias makes no sense"
140
156
  refute value['group'], "#{key}.group makes no sense"
141
157
  refute value['default'], "#{key}.default makes no sense"
142
158
  refute value['private'], "#{key}.private makes no sense"
159
+ refute value['expose'], "#{key}.expose makes no sense"
143
160
  else
144
161
  refute value['version'], "#{key}.version makes no sense"
145
162
  refute value['extensible'], "#{key}.extensible makes no sense"
@@ -5,8 +5,8 @@ class Array
5
5
  map { |line| "#{indentation}#{line}" }
6
6
  end
7
7
 
8
- def uniq?
9
- self == uniq
8
+ def nonuniq
9
+ tally.select { |key, count| count > 1 }.keys
10
10
  end
11
11
 
12
12
  end
@@ -5,7 +5,7 @@ class String
5
5
 
6
6
  def indent(offset)
7
7
  return self unless offset > 0
8
- split("\n").indent(offset).join("\n")
8
+ lines.indent(offset).join
9
9
  end
10
10
 
11
11
  def to_underscore
@@ -24,7 +24,7 @@ class String
24
24
  end
25
25
 
26
26
  def lint
27
- gsub(/\s+\n/m, "\n\n").lines.reject { |l| l =~ /^\s*##/ }.join ""
27
+ gsub(/\s+\n/m, "\n\n").lines.reject { |l| l =~ /^\s*##/ }.join
28
28
  end
29
29
 
30
30
  def remove_front_matter
@@ -1,15 +1,14 @@
1
1
  module Bashly
2
2
  module Script
3
3
  class Command < Base
4
- include Completions
5
- include CommandScopes
4
+ include Completions::Command
6
5
 
7
6
  class << self
8
7
  def option_keys
9
8
  @option_keys ||= %i[
10
9
  alias args catch_all commands completions
11
10
  default dependencies environment_variables examples
12
- extensible filename filters flags
11
+ extensible expose filename filters flags
13
12
  footer group help name
14
13
  private version
15
14
  short
@@ -18,6 +17,9 @@ module Bashly
18
17
  end
19
18
  end
20
19
 
20
+ attr_accessor :parent_command
21
+ attr_writer :parents
22
+
21
23
  # Returns the name to be used as an action.
22
24
  # - If it is the root command, the action is "root"
23
25
  # - Else, it is all the parents, except the first one (root) joined
@@ -32,6 +34,16 @@ module Bashly
32
34
  [name] + alt
33
35
  end
34
36
 
37
+ # Returns an array of all full names (including aliases and aliases of
38
+ # parents)
39
+ def all_full_names
40
+ if parent_command
41
+ parent_command.all_full_names.product(aliases).map { |a| a.join ' ' }
42
+ else
43
+ aliases
44
+ end
45
+ end
46
+
35
47
  # Returns an array of alternative aliases if any
36
48
  def alt
37
49
  # DEPRECATION 0.8.0
@@ -57,15 +69,81 @@ module Bashly
57
69
  @catch_all ||= CatchAll.from_config options['catch_all']
58
70
  end
59
71
 
72
+ # Returns a full list of the Command names and aliases combined
73
+ def command_aliases
74
+ commands.map(&:aliases).flatten
75
+ end
76
+
77
+ # Returns a data structure for displaying subcommands help
78
+ def command_help_data
79
+ result = {}
80
+
81
+ public_commands.each do |command|
82
+ result[command.group_string] ||= {}
83
+ result[command.group_string][command.name] = { summary: command.summary_string }
84
+ next unless command.expose
85
+
86
+ command.public_commands.each do |subcommand|
87
+ result[command.group_string]["#{command.name} #{subcommand.name}"] = {
88
+ summary: subcommand.summary_string,
89
+ help_only: command.expose != 'always'
90
+ }
91
+ end
92
+ end
93
+
94
+ result
95
+ end
96
+
97
+ # Returns only the names of the Commands
98
+ def command_names
99
+ commands.map &:name
100
+ end
101
+
60
102
  # Returns an array of the Commands
61
103
  def commands
62
104
  return [] unless options["commands"]
63
105
  options["commands"].map do |options|
64
- options['parents'] = parents + [name]
65
- Command.new options
106
+ result = Command.new options
107
+ result.parents = parents + [name]
108
+ result.parent_command = self
109
+ result
66
110
  end
67
111
  end
68
112
 
113
+ # Returns a flat array containing all the commands in this tree.
114
+ # This includes self + children + grandchildres + ...
115
+ def deep_commands
116
+ result = []
117
+ commands.each do |command|
118
+ result << command
119
+ if command.commands.any?
120
+ result += command.deep_commands
121
+ end
122
+ end
123
+ result
124
+ end
125
+
126
+ # If any of this command's subcommands has the default option set to
127
+ # true, this default command will be returned, nil otherwise.
128
+ def default_command
129
+ commands.find { |c| c.default }
130
+ end
131
+
132
+ # Returns an array of all the default Args
133
+ def default_args
134
+ args.select &:default
135
+ end
136
+
137
+ # Returns an array of all the default Environment Variables
138
+ def default_environment_variables
139
+ environment_variables.select &:default
140
+ end
141
+
142
+ # Returns an array of all the default Flags
143
+ def default_flags
144
+ flags.select &:default
145
+ end
146
+
69
147
  # Returns an array of EnvironmentVariables
70
148
  def environment_variables
71
149
  return [] unless options["environment_variables"]
@@ -99,27 +177,24 @@ module Bashly
99
177
  parents.any? ? (parents + [name]).join(' ') : name
100
178
  end
101
179
 
102
- # Reads a file from the userspace (Settings.source_dir) and returns
103
- # its contents.
104
- # If the file is not found, returns a string with a hint.
105
- def load_user_file(file, placeholder: true)
106
- path = "#{Settings.source_dir}/#{file}"
107
-
108
- content = if File.exist? path
109
- File.read(path).remove_front_matter
110
- elsif placeholder
111
- %q[echo "error: cannot load file"]
180
+ # Returns the string for the group caption
181
+ def group_string
182
+ if group
183
+ strings[:group] % { group: group }
112
184
  else
113
- ''
185
+ strings[:commands]
114
186
  end
115
-
116
- Settings.production? ? content : "#{view_marker path}\n#{content}"
117
187
  end
118
188
 
119
189
  # Returns an array of all parents. For example, the command
120
190
  # "docker container run" will have [docker, container] as its parents
121
191
  def parents
122
- options['parents'] || []
192
+ @parents ||= []
193
+ end
194
+
195
+ # Returns only commands that are not private
196
+ def public_commands
197
+ commands.reject &:private
123
198
  end
124
199
 
125
200
  # Returns true if one of the args is repeatable
@@ -127,6 +202,21 @@ module Bashly
127
202
  args.select(&:repeatable).any?
128
203
  end
129
204
 
205
+ # Returns an array of all the required Arguments
206
+ def required_args
207
+ args.select &:required
208
+ end
209
+
210
+ # Returns an array of all the required EnvironmentVariables
211
+ def required_environment_variables
212
+ environment_variables.select &:required
213
+ end
214
+
215
+ # Returns an array of all the required Flags
216
+ def required_flags
217
+ flags.select &:required
218
+ end
219
+
130
220
  # Returns true if this is the root command (no parents)
131
221
  def root_command?
132
222
  parents.empty?
@@ -137,6 +227,15 @@ module Bashly
137
227
  flags.select { |f| f.short == flag }.any?
138
228
  end
139
229
 
230
+ # Returns the summary string
231
+ def summary_string
232
+ if default
233
+ strings[:default_command_summary] % { summary: summary }
234
+ else
235
+ summary
236
+ end
237
+ end
238
+
140
239
  # Returns a constructed string suitable for Usage pattern
141
240
  def usage_string
142
241
  result = [full_name]
@@ -156,6 +255,15 @@ module Bashly
156
255
  @user_lib ||= Dir["#{Settings.full_lib_dir}/**/*.sh"].sort
157
256
  end
158
257
 
258
+ # Returns an array of all the args with a whitelist
259
+ def whitelisted_args
260
+ args.select &:allowed
261
+ end
262
+
263
+ # Returns an array of all the flags with a whitelist arg
264
+ def whitelisted_flags
265
+ flags.select &:allowed
266
+ end
159
267
  end
160
268
  end
161
269
  end
@@ -1,11 +1,13 @@
1
1
  module Bashly
2
2
  module Script
3
3
  class Flag < Base
4
+ include Completions::Flag
5
+
4
6
  class << self
5
7
  def option_keys
6
8
  @option_keys ||= %i[
7
- allowed arg conflicts default help long repeatable required
8
- short validate
9
+ allowed arg completions conflicts default help long repeatable
10
+ required short validate
9
11
  ]
10
12
  end
11
13
  end
@@ -26,6 +26,7 @@ version_flag_text: Show version number
26
26
  flag_requires_an_argument: "%{name} requires an argument: %{usage}"
27
27
  invalid_argument: "invalid argument: %s"
28
28
  invalid_flag: "invalid option: %s"
29
+ invalid_command: "invalid command: %s"
29
30
  conflicting_flags: "conflicting options: %s cannot be used with %s"
30
31
  missing_required_argument: "missing required argument: %{arg}\\nusage: %{usage}"
31
32
  missing_required_flag: "missing required flag: %{usage}"
@@ -1,3 +1,3 @@
1
1
  module Bashly
2
- VERSION = "0.8.0"
2
+ VERSION = "0.8.3"
3
3
  end
@@ -0,0 +1,6 @@
1
+ # View Tempaltes
2
+
3
+ These are [GTX](https://github.com/dannyben/gtx) templates.
4
+
5
+ For syntax highlighting, set up your editor to treat `*.gtx` files as Ruby
6
+ source code.
@@ -0,0 +1,15 @@
1
+ = view_marker
2
+
3
+ > echo " {{ label }}"
4
+ > printf "{{ help.wrap(76).indent(4).sanitize_for_print }}\n"
5
+
6
+ if allowed
7
+ > printf " {{ strings[:allowed] % { values: allowed.join(', ') } }}\n"
8
+ end
9
+
10
+ if default
11
+ > printf " {{ strings[:default] % { value: default } }}\n"
12
+ end
13
+
14
+ > echo
15
+ >
@@ -0,0 +1,11 @@
1
+ if validate
2
+
3
+ = view_marker
4
+
5
+ > if [[ -n $(validate_{{ validate }} "$1") ]]; then
6
+ > printf "{{ strings[:validation_error] }}\n" "{{ name.upcase }}" "$(validate_{{ validate }} "$1")"
7
+ > exit 1
8
+ > fi
9
+ >
10
+
11
+ end
@@ -0,0 +1,10 @@
1
+ if catch_all.required?
2
+ = view_marker
3
+
4
+ > if [[ ${#other_args[@]} -eq 0 ]]; then
5
+ > printf "{{ strings[:missing_required_argument] % { arg: catch_all.label, usage: usage_string } }}\n"
6
+ > exit 1
7
+ > fi
8
+ >
9
+
10
+ end