bashly 0.8.0 → 0.8.3

Sign up to get free protection for your applications and to get access to all the features.
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