bashly 0.7.9 → 0.8.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 2ef923a576e9bb9e16ec9f78c96c6008772c77d3bfc9520299fc2dd3cd7f3dd3
4
- data.tar.gz: 593393d64ca6e63216e0e974c580db50db754fe430d319115aef8610c47d39aa
3
+ metadata.gz: 4be359964fc41fae59f83eae85c594a4e0edec41e0cc5752fec25850b7b7ca43
4
+ data.tar.gz: 62023932e59ae5b9856a9617f9148a7e28bfab5bca5cebca98d3fefbf401ae21
5
5
  SHA512:
6
- metadata.gz: 2b0ffb33c0c11b423cb59d594362a0c2c071a685a2ad7d4837cbdc3bb1d6a494bdcce8ff1cb3d19cfb0078ac5f167912a9d2f93c44f173d2821b415fe9cbd92e
7
- data.tar.gz: c6b6989f3c75226641847feee4e4e8a73326da2a006bbbd52ce7544c7c4ffb3bf1f4a659a15f0b70f14590b1fe0392062099f6ced43134b2a3d81000d5a350f9
6
+ metadata.gz: a0e222c6ead53cfef4597653d9ce51b6e9fffd1a3556acd72eff5447b9a203371a0097cd42f4058405cd473e7f0c55f4f9a448d45d3b1af73898b74a79706df4
7
+ data.tar.gz: 8362eff89fccaad8c9a1f370270e33426d0089bbe131eec4fc55f586413afaa556caa81e62389daefb400941122db96fa1f8b4e04d3780a4d45ea88060dfdc1e
@@ -3,14 +3,15 @@ module Bashly
3
3
  class Add < Base
4
4
  help "Add extra features and customization to your script"
5
5
 
6
- usage "bashly add strings [--force]"
7
- usage "bashly add lib [--force]"
8
- usage "bashly add config [--force]"
9
6
  usage "bashly add colors [--force]"
10
- usage "bashly add yaml [--force]"
11
- usage "bashly add validations [--force]"
12
- usage "bashly add test [--force]"
13
7
  usage "bashly add comp FORMAT [OUTPUT --force]"
8
+ usage "bashly add config [--force]"
9
+ usage "bashly add lib [--force]"
10
+ usage "bashly add settings [--force]"
11
+ usage "bashly add strings [--force]"
12
+ usage "bashly add test [--force]"
13
+ usage "bashly add validations [--force]"
14
+ usage "bashly add yaml [--force]"
14
15
  usage "bashly add (-h|--help)"
15
16
 
16
17
  option "-f --force", "Overwrite existing files"
@@ -18,14 +19,15 @@ module Bashly
18
19
  param "FORMAT", "Output format, can be one of:\n function : generate a function file to be included in your script.\n script : generate a standalone bash completions script.\n yaml : generate a yaml compatible with completely."
19
20
  param "OUTPUT", "For the 'comp function' command: Name of the generated function.\nFor the 'comp script' or 'comp yaml' commands: path to output file.\nIn all cases, this is optional and will have sensible defaults."
20
21
 
21
- command "strings", "Copy an additional configuration file to your project, allowing you to customize all the tips and error strings."
22
- command "lib", "Create the additional lib directory for additional user scripts. All *.sh scripts in this folder will be included in the final bash script."
23
- command "config", "Add standard functions for handling INI files to the lib directory."
24
22
  command "colors", "Add standard functions for printing colorful and formatted text to the lib directory."
25
- command "yaml", "Add standard functions for reading YAML files to the lib directory."
26
- command "validations", "Add argument validation functions to the lib directory."
27
- command "test", "Add approval testing."
28
23
  command "comp", "Generate a bash completions script or function."
24
+ command "config", "Add standard functions for handling INI files to the lib directory."
25
+ command "lib", "Create the additional lib directory for additional user scripts. All *.sh scripts in this folder will be included in the final bash script."
26
+ command "settings", "Copy a sample settings.yml file to your project, allowing you to customize some bashly options."
27
+ command "strings", "Copy an additional configuration file to your project, allowing you to customize all the tips and error strings."
28
+ command "test", "Add approval testing."
29
+ command "validations", "Add argument validation functions to the lib directory."
30
+ command "yaml", "Add standard functions for reading YAML files to the lib directory."
29
31
 
30
32
  example "bashly add strings --force"
31
33
  example "bashly add comp function"
@@ -34,45 +36,51 @@ module Bashly
34
36
  environment "BASHLY_SOURCE_DIR", "The path containing the bashly configuration and source files [default: src]"
35
37
  environment "BASHLY_LIB_DIR", "The path to use for creating the library files, relative to the source dir [default: lib]"
36
38
 
37
- def strings_command
38
- add_lib 'strings'
39
+ attr_reader :skip_src_check
40
+
41
+ def colors_command
42
+ add_lib 'colors'
39
43
  end
40
44
 
41
- def lib_command
42
- add_lib 'lib'
45
+ def comp_command
46
+ format = args['FORMAT']
47
+ output = args['OUTPUT']
48
+
49
+ case format
50
+ when "script" then add_lib 'completions_script', output
51
+ when "function" then add_lib 'completions', output
52
+ when "yaml" then add_lib 'completions_yaml', output
53
+ else raise Error, "Unrecognized format: #{format}"
54
+ end
43
55
  end
44
56
 
45
57
  def config_command
46
58
  add_lib 'config'
47
59
  end
48
60
 
49
- def colors_command
50
- add_lib 'colors'
61
+ def settings_command
62
+ @skip_src_check = true
63
+ add_lib 'settings'
51
64
  end
52
65
 
53
- def yaml_command
54
- add_lib 'yaml'
66
+ def strings_command
67
+ add_lib 'strings'
55
68
  end
56
69
 
57
- def validations_command
58
- add_lib 'validations'
70
+ def lib_command
71
+ add_lib 'lib'
59
72
  end
60
73
 
61
74
  def test_command
62
75
  add_lib 'test'
63
76
  end
64
77
 
65
- def comp_command
66
- format = args['FORMAT']
67
- output = args['OUTPUT']
68
-
69
- case format
70
- when "script" then add_lib 'completions_script', output
71
- when "function" then add_lib 'completions', output
72
- when "yaml" then add_lib 'completions_yaml', output
73
- else raise Error, "Unrecognized format: #{format}"
74
- end
78
+ def yaml_command
79
+ add_lib 'yaml'
80
+ end
75
81
 
82
+ def validations_command
83
+ add_lib 'validations'
76
84
  end
77
85
 
78
86
  private
@@ -89,7 +97,7 @@ module Bashly
89
97
  end
90
98
 
91
99
  def safe_write(path, content)
92
- if !Dir.exist? Settings.source_dir
100
+ if !skip_src_check and !Dir.exist? Settings.source_dir
93
101
  raise InitError, "Directory !txtgrn!#{Settings.source_dir}!txtrst! does not exist\nRun !txtpur!bashly init!txtrst! first"
94
102
  end
95
103
 
@@ -6,10 +6,28 @@ module Bashly
6
6
  class Base < MisterBin::Command
7
7
  include AssetHelper
8
8
 
9
+ def config
10
+ @config ||= Config.new "#{Settings.source_dir}/bashly.yml"
11
+ end
12
+
13
+ def config_validator
14
+ @config_validator ||= ConfigValidator.new config
15
+ end
16
+
9
17
  def validate_config
10
- config = Config.new "#{Settings.source_dir}/bashly.yml"
11
- validator = ConfigValidator.new config
12
- validator.validate
18
+ config_validator.validate
19
+ end
20
+
21
+ def with_valid_config
22
+ validate_config
23
+ yield
24
+ show_deprecations
25
+ end
26
+
27
+ def show_deprecations
28
+ return if config_validator.deprecations.empty? or ENV['BASHLY_HIDE_DEPRECATIONS']
29
+ messages = "\n" + config_validator.deprecations.map(&:message).join("\n\n") + "\n\n"
30
+ say! messages
13
31
  end
14
32
  end
15
33
  end
@@ -16,6 +16,7 @@ module Bashly
16
16
  environment "BASHLY_TARGET_DIR", "The path to use for creating the bash script [default: .]"
17
17
  environment "BASHLY_LIB_DIR", "The path to use for upgrading library files, relative to the source dir [default: lib]"
18
18
  environment "BASHLY_STRICT", "When not empty, enable bash strict mode (set -euo pipefail)"
19
+ environment "BASHLY_TAB_INDENT", "When not empty, the generated script will use tab indentation instead of spaces (every 2 leading spaces will be converted to a tab character)"
19
20
  environment "BASHLY_ENV", <<~EOF
20
21
  Set to 'production' or 'development':
21
22
  - production generate a smaller script, without file markers
@@ -28,13 +29,12 @@ module Bashly
28
29
  example "bashly generate --wrap my_function"
29
30
 
30
31
  def run
31
- validate_config
32
- ENV['BASHLY_ENV'] = args['--env'] if args['--env']
33
- quiet_say "creating !txtgrn!production!txtrst! version" if Bashly.production?
34
- create_user_files
35
- upgrade_libs if args['--upgrade']
36
- create_master_script
37
- quiet_say "run !txtpur!#{master_script_path} --help!txtrst! to test your bash script"
32
+ with_valid_config do
33
+ Settings.env = args['--env'] if args['--env']
34
+ quiet_say "creating !txtgrn!production!txtrst! version" if Settings.production?
35
+ generate_all_files
36
+ quiet_say "run !txtpur!#{master_script_path} --help!txtrst! to test your bash script"
37
+ end
38
38
  end
39
39
 
40
40
  private
@@ -43,6 +43,12 @@ module Bashly
43
43
  say message unless args['--quiet']
44
44
  end
45
45
 
46
+ def generate_all_files
47
+ create_user_files
48
+ upgrade_libs if args['--upgrade']
49
+ create_master_script
50
+ end
51
+
46
52
  def upgrade_libs
47
53
  generated_files.each do |file|
48
54
  content = File.read file
@@ -114,7 +120,7 @@ module Bashly
114
120
  end
115
121
 
116
122
  def create_master_script
117
- File.write master_script_path, script.code
123
+ File.write master_script_path, script.code(tab_indent: Settings.tab_indent)
118
124
  FileUtils.chmod "+x", master_script_path
119
125
  quiet_say "!txtgrn!created!txtrst! #{master_script_path}"
120
126
  end
@@ -127,10 +133,6 @@ module Bashly
127
133
  "#{Settings.target_dir}/#{command.name}"
128
134
  end
129
135
 
130
- def config
131
- @config ||= Config.new "#{Settings.source_dir}/bashly.yml"
132
- end
133
-
134
136
  def command
135
137
  @command ||= Script::Command.new config
136
138
  end
@@ -9,10 +9,11 @@ module Bashly
9
9
  environment "BASHLY_SOURCE_DIR", "The path containing the bashly configuration and source files [default: src]"
10
10
 
11
11
  def run
12
- config = Config.new "#{Settings.source_dir}/bashly.yml"
13
- command = Script::Command.new(config)
14
- script = Script::Wrapper.new command
15
- puts script.code
12
+ with_valid_config do
13
+ command = Script::Command.new config
14
+ script = Script::Wrapper.new command
15
+ puts script.code
16
+ end
16
17
  end
17
18
  end
18
19
  end
@@ -10,7 +10,13 @@ module Bashly
10
10
 
11
11
  def run
12
12
  validate_config
13
- say "!txtgrn!OK"
13
+ show_deprecations
14
+ deprecations = config_validator.deprecations
15
+ if deprecations.empty?
16
+ say "!txtgrn!OK"
17
+ else
18
+ say "!txtred!WARNING!txtrst! Found #{deprecations.count} deprecations"
19
+ end
14
20
  end
15
21
  end
16
22
  end
@@ -1,6 +1,21 @@
1
1
  module Bashly
2
2
  # This is a `Command` concern responsible for providing additional scopes.
3
3
  module CommandScopes
4
+ # Returns an array of all full names (including aliases and aliases of
5
+ # parents)
6
+ def all_full_names
7
+ if parent_command
8
+ parent_command.all_full_names.product(aliases).map { |a| a.join ' ' }
9
+ else
10
+ aliases
11
+ end
12
+ end
13
+
14
+ # Returns a full list of the Command names and aliases combined
15
+ def command_aliases
16
+ commands.map(&:aliases).flatten
17
+ end
18
+
4
19
  # Returns only the names of the Commands
5
20
  def command_names
6
21
  commands.map &:name
@@ -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
@@ -15,7 +15,7 @@ module Bashly
15
15
 
16
16
  def view_marker(id = nil)
17
17
  id ||= ":#{caller_locations.first.path}"
18
- "# #{id}" unless Bashly.production?
18
+ "# #{id}" unless Settings.production?
19
19
  end
20
20
 
21
21
  private
@@ -0,0 +1,75 @@
1
+ module Bashly
2
+ # This is a `ConfigValidator` concern responsible for providing basic
3
+ # assertion methods.
4
+ module ValidationHelpers
5
+
6
+ def deprecations
7
+ @deprecations ||= []
8
+ end
9
+
10
+ protected
11
+
12
+ def assert(valid, message)
13
+ raise ConfigurationError, message unless valid
14
+ end
15
+
16
+ def refute(invalid, message)
17
+ assert !invalid, message
18
+ end
19
+
20
+ def deprecate(key, **options)
21
+ deprecations.push Deprecation.new(key, **options)
22
+ end
23
+
24
+ def assert_string(key, value)
25
+ assert value.is_a?(String), "#{key} must be a string"
26
+ end
27
+
28
+ def assert_optional_string(key, value)
29
+ assert_string key, value if value
30
+ end
31
+
32
+ def assert_boolean(key, value)
33
+ assert [true, false, nil].include?(value), "#{key} must be a boolean"
34
+ end
35
+
36
+ def assert_array(key, value, of: nil)
37
+ return unless value
38
+ assert value.is_a?(Array), "#{key} must be an array"
39
+ if of
40
+ value.each_with_index do |val, i|
41
+ send "assert_#{of}".to_sym, "#{key}[#{i}]", val
42
+ end
43
+ end
44
+ end
45
+
46
+ def assert_hash(key, value, whitelist = nil)
47
+ assert value.is_a?(Hash), "#{key} must be a hash"
48
+
49
+ if whitelist
50
+ invalid_keys = value.keys.map(&:to_sym) - whitelist
51
+ assert invalid_keys.empty?, "#{key} contains invalid options: #{invalid_keys.join(', ')}"
52
+ end
53
+ end
54
+
55
+ def assert_uniq(key, value, array_keys)
56
+ return unless value
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 '}"
65
+ end
66
+
67
+ def assert_string_or_array(key, value)
68
+ return unless value
69
+ assert [Array, String].include?(value.class),
70
+ "#{key} must be a string or an array"
71
+
72
+ assert_array key, value, of: :string if value.is_a? Array
73
+ end
74
+ end
75
+ end
@@ -1,5 +1,7 @@
1
1
  module Bashly
2
2
  class ConfigValidator
3
+ include ValidationHelpers
4
+
3
5
  attr_reader :data
4
6
 
5
7
  def initialize(data)
@@ -12,45 +14,6 @@ module Bashly
12
14
 
13
15
  private
14
16
 
15
- def assert(valid, message)
16
- raise ConfigurationError, message unless valid
17
- end
18
-
19
- def refute(invalid, message)
20
- assert !invalid, message
21
- end
22
-
23
- def assert_string(key, value)
24
- assert value.is_a?(String), "#{key} must be a string"
25
- end
26
-
27
- def assert_optional_string(key, value)
28
- assert_string key, value if value
29
- end
30
-
31
- def assert_boolean(key, value)
32
- assert [true, false, nil].include?(value), "#{key} must be a boolean"
33
- end
34
-
35
- def assert_array(key, value, of: nil)
36
- return unless value
37
- assert value.is_a?(Array), "#{key} must be an array"
38
- if of
39
- value.each_with_index do |val, i|
40
- send "assert_#{of}".to_sym, "#{key}[#{i}]", val
41
- end
42
- end
43
- end
44
-
45
- def assert_hash(key, value, whitelist = nil)
46
- assert value.is_a?(Hash), "#{key} must be a hash"
47
-
48
- if whitelist
49
- invalid_keys = value.keys.map(&:to_sym) - whitelist
50
- assert invalid_keys.empty?, "#{key} contains invalid options: #{invalid_keys.join(', ')}"
51
- end
52
- end
53
-
54
17
  def assert_version(key, value)
55
18
  return unless value
56
19
  assert [String, Integer, Float].include?(value.class),
@@ -98,6 +61,8 @@ module Bashly
98
61
  assert_hash key, value, Script::Flag.option_keys
99
62
  assert value['short'] || value['long'], "#{key} must have at least one of long or short name"
100
63
 
64
+ refute value['allowed'] && value['completions'], "#{key} cannot have both allowed and completions"
65
+
101
66
  assert_optional_string "#{key}.long", value['long']
102
67
  assert_optional_string "#{key}.short", value['short']
103
68
  assert_optional_string "#{key}.help", value['help']
@@ -109,6 +74,7 @@ module Bashly
109
74
  assert_boolean "#{key}.required", value['required']
110
75
  assert_array "#{key}.allowed", value['allowed'], of: :string
111
76
  assert_array "#{key}.conflicts", value['conflicts'], of: :string
77
+ assert_array "#{key}.completions", value['completions'], of: :string
112
78
 
113
79
  assert value['long'].match(/^--[a-zA-Z0-9_\-]+$/), "#{key}.long must be in the form of '--name'" if value['long']
114
80
  assert value['short'].match(/^-[a-zA-Z0-9]$/), "#{key}.short must be in the form of '-n'" if value['short']
@@ -123,6 +89,10 @@ module Bashly
123
89
  if value['allowed']
124
90
  assert value['arg'], "#{key}.allowed does not make sense without arg"
125
91
  end
92
+
93
+ if value['completions']
94
+ assert value['arg'], "#{key}.completions does not make sense without arg"
95
+ end
126
96
  end
127
97
 
128
98
  def assert_env_var(key, value)
@@ -138,9 +108,8 @@ module Bashly
138
108
 
139
109
  refute value['commands'] && value['args'], "#{key} cannot have both commands and args"
140
110
  refute value['commands'] && value['flags'], "#{key} cannot have both commands and flags"
141
-
111
+
142
112
  assert_string "#{key}.name", value['name']
143
- assert_optional_string "#{key}.short", value['short']
144
113
  assert_optional_string "#{key}.help", value['help']
145
114
  assert_optional_string "#{key}.footer", value['footer']
146
115
  assert_optional_string "#{key}.group", value['group']
@@ -150,6 +119,7 @@ module Bashly
150
119
  assert_boolean "#{key}.default", value['default']
151
120
  assert_version "#{key}.version", value['version']
152
121
  assert_catch_all "#{key}.catch_all", value['catch_all']
122
+ assert_string_or_array "#{key}.alias", value['alias']
153
123
  assert_extensible "#{key}.extensible", value['extensible']
154
124
 
155
125
  assert_array "#{key}.args", value['args'], of: :arg
@@ -161,8 +131,18 @@ module Bashly
161
131
  assert_array "#{key}.environment_variables", value['environment_variables'], of: :env_var
162
132
  assert_array "#{key}.examples", value['examples'], of: :string
163
133
 
134
+ assert_uniq "#{key}.commands", value['commands'], ['name', 'alias']
135
+ assert_uniq "#{key}.flags", value['flags'], 'long'
136
+ assert_uniq "#{key}.flags", value['flags'], 'short'
137
+ assert_uniq "#{key}.args", value['args'], 'name'
138
+
139
+ if value['catch_all'] and value['args']
140
+ repeatable_arg = value['args'].select { |a| a['repeatable'] }.first&.dig 'name'
141
+ refute repeatable_arg, "#{key}.catch_all makes no sense with repeatable arg (#{repeatable_arg})"
142
+ end
143
+
164
144
  if key == "root"
165
- refute value['short'], "#{key}.short makes no sense"
145
+ refute value['alias'], "#{key}.alias makes no sense"
166
146
  refute value['group'], "#{key}.group makes no sense"
167
147
  refute value['default'], "#{key}.default makes no sense"
168
148
  refute value['private'], "#{key}.private makes no sense"
@@ -170,6 +150,11 @@ module Bashly
170
150
  refute value['version'], "#{key}.version makes no sense"
171
151
  refute value['extensible'], "#{key}.extensible makes no sense"
172
152
  end
153
+
154
+ # DEPRECATION 0.8.0
155
+ if value['short']
156
+ deprecate "#{key}.short", replacement: "alias", reference: "https://github.com/DannyBen/bashly/pull/220"
157
+ end
173
158
  end
174
159
  end
175
160
  end
@@ -0,0 +1,25 @@
1
+ module Bashly
2
+ class Deprecation
3
+ attr_reader :old, :replacement, :reference
4
+
5
+ def initialize(old, replacement: nil, reference: nil)
6
+ @old, @replacement, @reference = old, replacement, reference
7
+ end
8
+
9
+ def message
10
+ result = ["Deprecation Warning:", "!txtred!#{old}!txtrst! is deprecated"]
11
+ result.push "use !txtgrn!#{replacement}!txtrst! instead" if replacement
12
+ result.push "see !undblu!#{reference}!txtrst!" if reference
13
+
14
+ result.map { |line| "!txtred!▐!txtrst! #{line}"}.join("\n")
15
+ end
16
+
17
+ def to_h
18
+ {
19
+ old: old,
20
+ replacement: replacement,
21
+ reference: reference
22
+ }
23
+ end
24
+ end
25
+ end
@@ -4,4 +4,9 @@ class Array
4
4
  indentation = " " * offset
5
5
  map { |line| "#{indentation}#{line}" }
6
6
  end
7
+
8
+ def nonuniq
9
+ tally.select { |key, count| count > 1 }.keys
10
+ end
11
+
7
12
  end
@@ -31,4 +31,10 @@ class String
31
31
  split(/^---\s*/).last
32
32
  end
33
33
 
34
+ def expand_tabs(tabstop = 2)
35
+ gsub(/^( {#{tabstop}}+)/) do
36
+ "\t" * ($1.size / tabstop)
37
+ end
38
+ end
39
+
34
40
  end
@@ -3,21 +3,25 @@ colors:
3
3
  - source: "templates/lib/colors.sh"
4
4
  target: "%{user_lib_dir}/colors.sh"
5
5
 
6
+ completions: :CompletionsFunction
7
+ completions_script: :CompletionsScript
8
+ completions_yaml: :CompletionsYAML
9
+
6
10
  config:
7
11
  files:
8
12
  - source: "templates/lib/config.sh"
9
13
  target: "%{user_lib_dir}/config.sh"
10
14
 
11
- yaml:
12
- files:
13
- - source: "templates/lib/yaml.sh"
14
- target: "%{user_lib_dir}/yaml.sh"
15
-
16
15
  lib:
17
16
  files:
18
17
  - source: "templates/lib/sample_function.sh"
19
18
  target: "%{user_lib_dir}/sample_function.sh"
20
19
 
20
+ settings:
21
+ files:
22
+ - source: "templates/settings.yml"
23
+ target: "settings.yml"
24
+
21
25
  strings:
22
26
  files:
23
27
  - source: "templates/strings.yml"
@@ -37,7 +41,6 @@ test:
37
41
 
38
42
  Docs: !undblu!https://github.com/DannyBen/approvals.bash
39
43
 
40
-
41
44
  validations:
42
45
  files:
43
46
  - source: "templates/lib/validations/validate_dir_exists.sh"
@@ -49,6 +52,7 @@ validations:
49
52
  - source: "templates/lib/validations/validate_not_empty.sh"
50
53
  target: "%{user_lib_dir}/validations/validate_not_empty.sh"
51
54
 
52
- completions: :CompletionsFunction
53
- completions_script: :CompletionsScript
54
- completions_yaml: :CompletionsYAML
55
+ yaml:
56
+ files:
57
+ - source: "templates/lib/yaml.sh"
58
+ target: "%{user_lib_dir}/yaml.sh"
@@ -1,18 +1,20 @@
1
1
  module Bashly
2
2
  module Script
3
3
  class Command < Base
4
- include Completions
4
+ include Completions::Command
5
5
  include CommandScopes
6
6
 
7
7
  class << self
8
8
  def option_keys
9
9
  @option_keys ||= %i[
10
- args catch_all commands completions
10
+ alias args catch_all commands completions
11
11
  default dependencies environment_variables examples
12
12
  extensible filename filters flags
13
13
  footer group help name
14
- private short version
14
+ private version
15
+ short
15
16
  ]
17
+ # DEPRECATION 0.8.0
16
18
  end
17
19
  end
18
20
 
@@ -27,7 +29,15 @@ module Bashly
27
29
 
28
30
  # Returns all the possible aliases for this command
29
31
  def aliases
30
- short ? [name, short] : [name]
32
+ [name] + alt
33
+ end
34
+
35
+ # Returns an array of alternative aliases if any
36
+ def alt
37
+ # DEPRECATION 0.8.0
38
+ options['alias'] ||= options['short']
39
+ return [] unless options["alias"]
40
+ options['alias'].is_a?(String) ? [options['alias']] : options['alias']
31
41
  end
32
42
 
33
43
  # Returns an array of Arguments
@@ -40,7 +50,7 @@ module Bashly
40
50
 
41
51
  # Returns a string suitable to be a headline
42
52
  def caption_string
43
- help ? "#{full_name} - #{summary}" : full_name
53
+ help.empty? ? full_name : "#{full_name} - #{summary}"
44
54
  end
45
55
 
46
56
  def catch_all
@@ -52,6 +62,7 @@ module Bashly
52
62
  return [] unless options["commands"]
53
63
  options["commands"].map do |options|
54
64
  options['parents'] = parents + [name]
65
+ options['parent_command'] = self
55
66
  Command.new options
56
67
  end
57
68
  end
@@ -94,15 +105,21 @@ module Bashly
94
105
  # If the file is not found, returns a string with a hint.
95
106
  def load_user_file(file, placeholder: true)
96
107
  path = "#{Settings.source_dir}/#{file}"
97
- default_content = placeholder ? "echo \"error: cannot load file\"" : ''
98
108
 
99
109
  content = if File.exist? path
100
110
  File.read(path).remove_front_matter
101
- else
102
- default_content
111
+ elsif placeholder
112
+ %q[echo "error: cannot load file"]
113
+ else
114
+ ''
103
115
  end
104
116
 
105
- Bashly.production? ? content : "#{view_marker path}\n#{content}"
117
+ Settings.production? ? content : "#{view_marker path}\n#{content}"
118
+ end
119
+
120
+ # Returns the Command instance of the direct parent
121
+ def parent_command
122
+ options['parent_command']
106
123
  end
107
124
 
108
125
  # Returns an array of all parents. For example, the command
@@ -111,7 +128,12 @@ module Bashly
111
128
  options['parents'] || []
112
129
  end
113
130
 
114
- # Returns trus if this is the root command (no parents)
131
+ # Returns true if one of the args is repeatable
132
+ def repeatable_arg_exist?
133
+ args.select(&:repeatable).any?
134
+ end
135
+
136
+ # Returns true if this is the root command (no parents)
115
137
  def root_command?
116
138
  parents.empty?
117
139
  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
@@ -9,18 +9,22 @@ module Bashly
9
9
  @command, @function_name = command, function_name
10
10
  end
11
11
 
12
- def code
13
- if function_name
14
- result = [header, render('wrapper')].join "\n"
12
+ def code(tab_indent: false)
13
+ tab_indent ? base_code.expand_tabs : base_code
14
+ end
15
+
16
+ private
17
+
18
+ def base_code
19
+ result = if function_name
20
+ [header, render('wrapper')]
15
21
  else
16
- result = [header, body].join "\n"
22
+ [header, body]
17
23
  end
18
24
 
19
- result.lint
25
+ result.join("\n").lint
20
26
  end
21
27
 
22
- private
23
-
24
28
  def header
25
29
  @header ||= header!
26
30
  end
@@ -1,27 +1,56 @@
1
1
  module Bashly
2
2
  class Settings
3
3
  class << self
4
- attr_writer :source_dir, :target_dir, :lib_dir, :strict
4
+ attr_writer :source_dir, :target_dir, :lib_dir, :strict, :tab_indent
5
5
 
6
6
  def source_dir
7
- @source_dir ||= ENV['BASHLY_SOURCE_DIR'] || 'src'
7
+ @source_dir ||= get :source_dir, 'src'
8
8
  end
9
9
 
10
10
  def target_dir
11
- @target_dir ||= ENV['BASHLY_TARGET_DIR'] || '.'
11
+ @target_dir ||= get :target_dir, '.'
12
12
  end
13
13
 
14
14
  def lib_dir
15
- @lib_dir ||= ENV['BASHLY_LIB_DIR'] || 'lib'
15
+ @lib_dir ||= get :lib_dir, 'lib'
16
16
  end
17
17
 
18
18
  def strict
19
- @strict ||= ENV['BASHLY_STRICT']
19
+ @strict ||= get :strict
20
+ end
21
+
22
+ def tab_indent
23
+ @tab_indent ||= get :tab_indent
24
+ end
25
+
26
+ def env
27
+ @env ||= get(:env, :development)&.to_sym
28
+ end
29
+
30
+ def env=(value)
31
+ @env = value&.to_sym
32
+ end
33
+
34
+ def production?
35
+ env == :production
20
36
  end
21
37
 
22
38
  def full_lib_dir
23
39
  "#{source_dir}/#{lib_dir}"
24
40
  end
41
+
42
+ private
43
+
44
+ def get(key, default = nil)
45
+ ENV["BASHLY_#{key.upcase}"] || user_settings[key.to_s] || default
46
+ end
47
+
48
+ def user_settings
49
+ @user_settings ||= begin
50
+ File.exist?('settings.yml') ? Config.new('settings.yml') : {}
51
+ end
52
+ end
53
+
25
54
  end
26
55
  end
27
56
  end
@@ -8,7 +8,7 @@ environment_variables:
8
8
 
9
9
  commands:
10
10
  - name: download
11
- short: d
11
+ alias: d
12
12
  help: Download a file
13
13
 
14
14
  args:
@@ -32,7 +32,7 @@ commands:
32
32
  help: Set the default location to download to
33
33
 
34
34
  - name: upload
35
- short: u
35
+ alias: u
36
36
  help: Upload a file
37
37
  args:
38
38
  - name: source
@@ -0,0 +1,24 @@
1
+ # All settings are optional (with their default values provided below), and
2
+ # can also be set with an environment variable with the same name, capitalized
3
+ # and prefixed by `BASHLY_` - for example: BASHLY_SOURCE_DIR
4
+
5
+ # The path containing the bashly configuration and source files
6
+ source_dir: src
7
+
8
+ # The path to use for creating the bash script
9
+ target_dir: .
10
+
11
+ # The path to use for upgrading library files, relative to the source dir
12
+ lib_dir: lib
13
+
14
+ # When true, enable bash strict mode (set -euo pipefail)
15
+ strict: false
16
+
17
+ # When true, the generated script will use tab indentation instead of spaces
18
+ # (every 2 leading spaces will be converted to a tab character)
19
+ tab_indent: false
20
+
21
+ # Set to 'production' or 'development':
22
+ # - production generate a smaller script, without file markers
23
+ # - development generate with file markers
24
+ env: development
@@ -11,7 +11,7 @@ environment_variables: "Environment Variables:"
11
11
  group: "%{group} Commands:"
12
12
 
13
13
  # Usage helpers
14
- command_shortcut: "Shortcut: %{short}"
14
+ command_alias: "Alias: %{alias}"
15
15
  default_command_summary: "%{summary} (default)"
16
16
  required: "(required)"
17
17
  repeatable: "(repeatable)"
@@ -1,3 +1,3 @@
1
1
  module Bashly
2
- VERSION = "0.7.9"
2
+ VERSION = "0.8.1"
3
3
  end
@@ -1,38 +1,8 @@
1
1
  <%= view_marker %>
2
- % repeatable_arg = false
3
- % if args.any?
4
- % condition = "if"
5
- % args.each do |arg|
6
- <%= condition %> [[ -z ${args[<%= arg.name %>]+x} ]]; then
7
- <%= arg.render(:validations).indent 2 %>
8
- % if arg.repeatable
9
- % repeatable_arg = true
10
- args[<%= arg.name %>]="\"$1\""
11
- shift
12
- else
13
- args[<%= arg.name %>]="${args[<%= arg.name %>]} \"$1\""
14
- shift
15
- % else
16
- args[<%= arg.name %>]=$1
17
- shift
18
- % end
19
- % condition = "elif"
20
- % end
21
- % if !repeatable_arg
22
- else
23
- % end
24
2
  % if catch_all.enabled?
25
- other_args+=("$1")
26
- shift
27
- % elsif !repeatable_arg
28
- printf "<%= strings[:invalid_argument] %>\n" "$key"
29
- exit 1
30
- % end
31
- fi
32
- % elsif catch_all.enabled?
33
- other_args+=("$1")
34
- shift
35
- % elsif !repeatable_arg
36
- printf "<%= strings[:invalid_argument] %>\n" "$key"
37
- exit 1
3
+ <%= render(:parse_requirements_case_catch_all) %>
4
+ % elsif repeatable_arg_exist?
5
+ <%= render(:parse_requirements_case_repeatable) %>
6
+ % else
7
+ <%= render(:parse_requirements_case_simple) %>
38
8
  % end
@@ -0,0 +1,18 @@
1
+ <%= view_marker %>
2
+ % if args.any?
3
+ % condition = "if"
4
+ % args.each do |arg|
5
+ <%= condition %> [[ -z ${args[<%= arg.name %>]+x} ]]; then
6
+ <%= arg.render(:validations).indent 2 %>
7
+ args[<%= arg.name %>]=$1
8
+ shift
9
+ % condition = "elif"
10
+ % end
11
+ else
12
+ other_args+=("$1")
13
+ shift
14
+ fi
15
+ % else
16
+ other_args+=("$1")
17
+ shift
18
+ % end
@@ -0,0 +1,18 @@
1
+ <%= view_marker %>
2
+ % condition = "if"
3
+ % args.each do |arg|
4
+ <%= condition %> [[ -z ${args[<%= arg.name %>]+x} ]]; then
5
+ <%= arg.render(:validations).indent 2 %>
6
+ % if arg.repeatable
7
+ args[<%= arg.name %>]="\"$1\""
8
+ shift
9
+ else
10
+ args[<%= arg.name %>]="${args[<%= arg.name %>]} \"$1\""
11
+ shift
12
+ % else
13
+ args[<%= arg.name %>]=$1
14
+ shift
15
+ % end
16
+ % condition = "elif"
17
+ % end
18
+ fi
@@ -0,0 +1,18 @@
1
+ <%= view_marker %>
2
+ % if args.any?
3
+ % condition = "if"
4
+ % args.each do |arg|
5
+ <%= condition %> [[ -z ${args[<%= arg.name %>]+x} ]]; then
6
+ <%= arg.render(:validations).indent 2 %>
7
+ args[<%= arg.name %>]=$1
8
+ shift
9
+ % condition = "elif"
10
+ % end
11
+ else
12
+ printf "<%= strings[:invalid_argument] %>\n" "$key"
13
+ exit 1
14
+ fi
15
+ % else
16
+ printf "<%= strings[:invalid_argument] %>\n" "$key"
17
+ exit 1
18
+ % end
@@ -15,8 +15,8 @@
15
15
  echo
16
16
  fi
17
17
 
18
- <%- if short -%>
19
- printf "<%= strings[:command_shortcut] % { short: short } %>\n"
18
+ <%- if alt&.any? -%>
19
+ printf "<%= strings[:command_alias] % { alias: alt.join(', ') } %>\n"
20
20
  echo
21
21
  <%- end -%>
22
22
 
@@ -1,30 +1,4 @@
1
1
  <%= view_marker %>
2
2
  <%= aliases.join " | " %> )
3
3
  <%= render(:conflicts).indent 2 %>
4
- % if arg
5
- if [[ -n ${2+x} ]]; then
6
- <%= render(:validations).indent 4 %>
7
- % if repeatable
8
- if [[ -z ${args[<%= name %>]+x} ]]; then
9
- args[<%= name %>]="\"$2\""
10
- else
11
- args[<%= name %>]="${args[<%= name %>]} \"$2\""
12
- fi
13
- % else
14
- args[<%= name %>]="$2"
15
- % end
16
- shift
17
- shift
18
- else
19
- printf "%s\n" "<%= strings[:flag_requires_an_argument] % { name: name, usage: usage_string } %>"
20
- exit 1
21
- fi
22
- % else
23
- % if repeatable
24
- (( args[<%= name %>]+=1 ))
25
- % else
26
- args[<%= name %>]=1
27
- % end
28
- shift
29
- % end
30
- ;;
4
+ <%= render(arg ? :case_arg : :case_no_arg).indent 2 %>
@@ -0,0 +1,19 @@
1
+ <%= view_marker %>
2
+ if [[ -n ${2+x} ]]; then
3
+ <%= render(:validations).indent 2 %>
4
+ % if repeatable
5
+ if [[ -z ${args[<%= name %>]+x} ]]; then
6
+ args[<%= name %>]="\"$2\""
7
+ else
8
+ args[<%= name %>]="${args[<%= name %>]} \"$2\""
9
+ fi
10
+ % else
11
+ args[<%= name %>]="$2"
12
+ % end
13
+ shift
14
+ shift
15
+ else
16
+ printf "%s\n" "<%= strings[:flag_requires_an_argument] % { name: name, usage: usage_string } %>"
17
+ exit 1
18
+ fi
19
+ ;;
@@ -0,0 +1,8 @@
1
+ <%= view_marker %>
2
+ % if repeatable
3
+ (( args[<%= name %>]+=1 ))
4
+ % else
5
+ args[<%= name %>]=1
6
+ % end
7
+ shift
8
+ ;;
data/lib/bashly.rb CHANGED
@@ -15,15 +15,3 @@ requires 'bashly/script/base'
15
15
  requires 'bashly/commands/base'
16
16
  requires 'bashly/libraries/base'
17
17
  requires 'bashly'
18
-
19
- module Bashly
20
- class << self
21
- def env
22
- ENV['BASHLY_ENV']&.to_sym
23
- end
24
-
25
- def production?
26
- env == :production
27
- end
28
- end
29
- end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: bashly
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.7.9
4
+ version: 0.8.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Danny Ben Shitrit
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-04-02 00:00:00.000000000 Z
11
+ date: 2022-05-22 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: colsole
@@ -16,28 +16,28 @@ dependencies:
16
16
  requirements:
17
17
  - - "~>"
18
18
  - !ruby/object:Gem::Version
19
- version: '0.6'
19
+ version: '0.7'
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - "~>"
25
25
  - !ruby/object:Gem::Version
26
- version: '0.6'
26
+ version: '0.7'
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: completely
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
31
  - - "~>"
32
32
  - !ruby/object:Gem::Version
33
- version: '0.3'
33
+ version: 0.4.1
34
34
  type: :runtime
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
38
  - - "~>"
39
39
  - !ruby/object:Gem::Version
40
- version: '0.3'
40
+ version: 0.4.1
41
41
  - !ruby/object:Gem::Dependency
42
42
  name: mister_bin
43
43
  requirement: !ruby/object:Gem::Requirement
@@ -58,14 +58,14 @@ dependencies:
58
58
  requirements:
59
59
  - - "~>"
60
60
  - !ruby/object:Gem::Version
61
- version: '0.1'
61
+ version: '0.2'
62
62
  type: :runtime
63
63
  prerelease: false
64
64
  version_requirements: !ruby/object:Gem::Requirement
65
65
  requirements:
66
66
  - - "~>"
67
67
  - !ruby/object:Gem::Version
68
- version: '0.1'
68
+ version: '0.2'
69
69
  description: Generate bash command line tools using YAML configuration
70
70
  email: db@dannyben.com
71
71
  executables:
@@ -87,8 +87,10 @@ files:
87
87
  - lib/bashly/concerns/command_scopes.rb
88
88
  - lib/bashly/concerns/completions.rb
89
89
  - lib/bashly/concerns/renderable.rb
90
+ - lib/bashly/concerns/validation_helpers.rb
90
91
  - lib/bashly/config.rb
91
92
  - lib/bashly/config_validator.rb
93
+ - lib/bashly/deprecation.rb
92
94
  - lib/bashly/exceptions.rb
93
95
  - lib/bashly/extensions/array.rb
94
96
  - lib/bashly/extensions/file.rb
@@ -121,6 +123,7 @@ files:
121
123
  - lib/bashly/templates/lib/validations/validate_not_empty.sh
122
124
  - lib/bashly/templates/lib/yaml.sh
123
125
  - lib/bashly/templates/minimal.yml
126
+ - lib/bashly/templates/settings.yml
124
127
  - lib/bashly/templates/strings.yml
125
128
  - lib/bashly/templates/test/approvals.bash
126
129
  - lib/bashly/version.rb
@@ -145,6 +148,9 @@ files:
145
148
  - lib/bashly/views/command/normalize_input.erb
146
149
  - lib/bashly/views/command/parse_requirements.erb
147
150
  - lib/bashly/views/command/parse_requirements_case.erb
151
+ - lib/bashly/views/command/parse_requirements_case_catch_all.erb
152
+ - lib/bashly/views/command/parse_requirements_case_repeatable.erb
153
+ - lib/bashly/views/command/parse_requirements_case_simple.erb
148
154
  - lib/bashly/views/command/parse_requirements_while.erb
149
155
  - lib/bashly/views/command/required_args_filter.erb
150
156
  - lib/bashly/views/command/required_flags_filter.erb
@@ -163,6 +169,8 @@ files:
163
169
  - lib/bashly/views/command/whitelist_filter.erb
164
170
  - lib/bashly/views/environment_variable/usage.erb
165
171
  - lib/bashly/views/flag/case.erb
172
+ - lib/bashly/views/flag/case_arg.erb
173
+ - lib/bashly/views/flag/case_no_arg.erb
166
174
  - lib/bashly/views/flag/conflicts.erb
167
175
  - lib/bashly/views/flag/usage.erb
168
176
  - lib/bashly/views/flag/validations.erb