bashly 0.6.6 → 0.7.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (50) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +4 -2
  3. data/lib/bashly/commands/add.rb +42 -78
  4. data/lib/bashly/commands/generate.rb +53 -8
  5. data/lib/bashly/commands/init.rb +1 -1
  6. data/lib/bashly/commands/preview.rb +2 -2
  7. data/lib/bashly/concerns/asset_helper.rb +4 -0
  8. data/lib/bashly/concerns/completions.rb +5 -1
  9. data/lib/bashly/extensions/file.rb +13 -0
  10. data/lib/bashly/extensions/string.rb +1 -1
  11. data/lib/bashly/library/base.rb +57 -0
  12. data/lib/bashly/library/colors.rb +9 -0
  13. data/lib/bashly/library/completions.rb +28 -0
  14. data/lib/bashly/library/completions_function.rb +26 -0
  15. data/lib/bashly/library/completions_script.rb +17 -0
  16. data/lib/bashly/library/completions_yaml.rb +15 -0
  17. data/lib/bashly/library/config.rb +9 -0
  18. data/lib/bashly/library/sample.rb +9 -0
  19. data/lib/bashly/library/strings.rb +12 -0
  20. data/lib/bashly/library/validations.rb +9 -0
  21. data/lib/bashly/library/yaml.rb +9 -0
  22. data/lib/bashly/{models → script}/argument.rb +1 -1
  23. data/lib/bashly/{models → script}/base.rb +2 -1
  24. data/lib/bashly/{models → script}/command.rb +1 -1
  25. data/lib/bashly/{models → script}/environment_variable.rb +1 -1
  26. data/lib/bashly/{models → script}/flag.rb +1 -1
  27. data/lib/bashly/{models/script.rb → script/wrapper.rb} +21 -3
  28. data/lib/bashly/templates/lib/colors.sh +41 -31
  29. data/lib/bashly/templates/lib/config.sh +50 -48
  30. data/lib/bashly/templates/lib/sample_function.sh +10 -10
  31. data/lib/bashly/templates/lib/validations/validate_dir_exists.sh +4 -0
  32. data/lib/bashly/templates/lib/validations/validate_file_exists.sh +4 -0
  33. data/lib/bashly/templates/lib/validations/validate_integer.sh +4 -0
  34. data/lib/bashly/templates/lib/validations/validate_not_empty.sh +4 -0
  35. data/lib/bashly/templates/lib/yaml.sh +12 -15
  36. data/lib/bashly/templates/strings.yml +1 -0
  37. data/lib/bashly/version.rb +1 -1
  38. data/lib/bashly/views/argument/validations.erb +8 -0
  39. data/lib/bashly/views/command/initialize.erb +0 -5
  40. data/lib/bashly/views/command/parse_requirements.erb +1 -1
  41. data/lib/bashly/views/command/parse_requirements_case.erb +2 -1
  42. data/lib/bashly/views/command/required_args_filter.erb +1 -5
  43. data/lib/bashly/views/command/required_flags_filter.erb +1 -4
  44. data/lib/bashly/views/flag/case.erb +2 -1
  45. data/lib/bashly/views/flag/validations.erb +8 -0
  46. data/lib/bashly/views/wrapper/bash3_bouncer.erb +5 -0
  47. data/lib/bashly/views/{script → wrapper}/header.erb +1 -0
  48. data/lib/bashly/views/{script → wrapper}/wrapper.erb +0 -0
  49. data/lib/bashly.rb +2 -1
  50. metadata +29 -10
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e61f83f8d8f8b4992a5f7d66731de37d640dd1da71c8bc243558ba173f56a76c
4
- data.tar.gz: c85eb4ea1c11c97c494b969075553f3a341bc29ac44ba94a4763d5efe1b03af0
3
+ metadata.gz: 13bbac52a39c2f6afb768d043911861a31f8e6abc8ff1472340a13c2834d5944
4
+ data.tar.gz: 54c4fea2c664dcd0c6aace3360004d715d87b5d4ed98ffd7d3ccdc6c0a120406
5
5
  SHA512:
6
- metadata.gz: 1685fa4d73c0bc652e3043e8686e39b8ce7571f7baa41e5c84fa5dcce01a49331fd65af7a817bdbd0b985200c9a0502565bfece80414d58b3eed2b1729be101d
7
- data.tar.gz: d20da15898e3792bb6efeb7257d0b4a21dc6c0adc5dca00b72411dd37acfd61a926427ec70264058fd276c509c24d7a5d7b7235f66b74b075d86ff1266b2163f
6
+ metadata.gz: f1c273771d9048c074fb9551ba4521e9cb38c98a819f53647186822155898f767b7dd95093642e01464788378833d4dd9e408efecf304a9feb1ba09ebf2edef3
7
+ data.tar.gz: efbdad6c22cfe1b7fbb46533db42f4c14b828259efeb5ede1099e20980ad505773af36897e27f8c6c7b25538a523427e63c1a5dac7ff958d753ae3810caf5eb9
data/README.md CHANGED
@@ -40,7 +40,7 @@ a [docker image](https://hub.docker.com/r/dannyben/bashly).
40
40
  file for you ([example](https://github.com/DannyBen/bashly/tree/master/examples/minimal#bashlyyml)).
41
41
  2. Bashly then automatically generates a bash script (when you run
42
42
  `bashly generate`) that can parse and validate user input, provide help
43
- messages, and run your code for each command ([example](https://github.com/DannyBen/bashly/blob/master/examples/minimal/download)).
43
+ messages, and run your code for each command.
44
44
  3. Your code for each command is kept in a separate file, and can be merged
45
45
  again if you change it ([example](https://github.com/DannyBen/bashly/blob/master/examples/minimal/src/root_command.sh)).
46
46
 
@@ -67,11 +67,13 @@ Bashly is responsible for:
67
67
  ## Contributing / Support
68
68
 
69
69
  If you experience any issue, have a question or a suggestion, or if you wish
70
- to contribute, feel free to [open an issue][issues].
70
+ to contribute, feel free to [open an issue][issues] or
71
+ [start a discussion][discussions].
71
72
 
72
73
 
73
74
 
74
75
  [issues]: https://github.com/DannyBen/bashly/issues
76
+ [discussions]: https://github.com/DannyBen/bashly/discussions
75
77
  [docs]: https://bashly.dannyb.co/
76
78
  [examples]: https://github.com/DannyBen/bashly/tree/master/examples#bashly-examples
77
79
 
@@ -8,7 +8,8 @@ module Bashly
8
8
  usage "bashly add config [--force]"
9
9
  usage "bashly add colors [--force]"
10
10
  usage "bashly add yaml [--force]"
11
- usage "bashly add comp FORMAT [OUTPUT]"
11
+ usage "bashly add validations [--force]"
12
+ usage "bashly add comp FORMAT [OUTPUT --force]"
12
13
  usage "bashly add (-h|--help)"
13
14
 
14
15
  option "-f --force", "Overwrite existing files"
@@ -21,6 +22,7 @@ module Bashly
21
22
  command "config", "Add standard functions for handling INI files to the lib directory."
22
23
  command "colors", "Add standard functions for printing colorful and formatted text to the lib directory."
23
24
  command "yaml", "Add standard functions for reading YAML files to the lib directory."
25
+ command "validations", "Add argument validation functions to the lib directory."
24
26
  command "comp", "Generate a bash completions script or function."
25
27
 
26
28
  example "bashly add strings --force"
@@ -30,119 +32,81 @@ module Bashly
30
32
  environment "BASHLY_SOURCE_DIR", "The path containing the bashly configuration and source files [default: src]"
31
33
 
32
34
  def strings_command
33
- safe_copy asset("templates/strings.yml"), "#{Settings.source_dir}/bashly-strings.yml"
35
+ add_lib Library::Strings.new
34
36
  end
35
37
 
36
38
  def lib_command
37
- safe_copy_lib "sample_function.sh"
39
+ add_lib Library::Sample.new
38
40
  end
39
41
 
40
42
  def config_command
41
- safe_copy_lib "config.sh"
43
+ add_lib Library::Config.new
42
44
  end
43
45
 
44
46
  def colors_command
45
- safe_copy_lib "colors.sh"
47
+ add_lib Library::Colors.new
46
48
  end
47
49
 
48
50
  def yaml_command
49
- safe_copy_lib "yaml.sh"
51
+ add_lib Library::YAML.new
52
+ end
53
+
54
+ def validations_command
55
+ add_lib Library::Validations.new
50
56
  end
51
57
 
52
58
  def comp_command
53
59
  format = args['FORMAT']
54
60
  output = args['OUTPUT']
55
-
61
+
56
62
  case format
63
+ when "script"
64
+ path = output || "#{Settings.target_dir}/completions.bash"
65
+ add_lib Library::CompletionsScript.new(path)
66
+
57
67
  when "function"
58
- save_comp_function output
68
+ function = output || "send_completions"
69
+ path = "#{Settings.source_dir}/lib/#{function}.sh"
70
+ add_lib Library::CompletionsFunction.new(path, function: function)
71
+
59
72
  when "yaml"
60
- save_comp_yaml output
61
- when "script"
62
- save_comp_script output
73
+ path = output || "#{Settings.target_dir}/completions.yml"
74
+ add_lib Library::CompletionsYAML.new(path)
75
+
63
76
  else
64
77
  raise Error, "Unrecognized format: #{format}"
78
+
65
79
  end
66
80
 
67
81
  end
68
82
 
69
83
  private
70
84
 
71
- def safe_copy_lib(libfile)
72
- safe_copy asset("templates/lib/#{libfile}"), "#{Settings.source_dir}/lib/#{libfile}"
85
+ def add_lib(handler)
86
+ files_created = 0
87
+ handler.files.each do |file|
88
+ created = safe_write file[:path], file[:content]
89
+ files_created += 1 if created
90
+ end
91
+ message = handler.post_install_message
92
+ say "\n#{message}" if message and files_created > 0
73
93
  end
74
94
 
75
- def safe_copy(source, target)
95
+ def safe_write(path, content)
76
96
  if !Dir.exist? Settings.source_dir
77
97
  raise InitError, "Directory !txtgrn!#{Settings.source_dir}!txtrst! does not exist\nRun !txtpur!bashly init!txtrst! first"
78
98
  end
79
99
 
80
- if File.exist? target and !args['--force']
81
- say "skipped !txtgrn!#{target}!txtrst! (exists)"
100
+ if File.exist? path and !args['--force']
101
+ say "!txtblu!skipped!txtrst! #{path} (exists)"
102
+ false
103
+
82
104
  else
83
- deep_copy source, target
84
- say "created !txtgrn!#{target}"
85
- end
86
- end
87
-
88
- def deep_copy(source, target)
89
- target_dir = File.dirname target
90
- FileUtils.mkdir_p target_dir unless Dir.exist? target_dir
91
- FileUtils.cp source, target
92
- end
93
-
94
- def config
95
- @config ||= Config.new "#{Settings.source_dir}/bashly.yml"
96
- end
97
-
98
- def command
99
- @command ||= Models::Command.new config
100
- end
101
-
102
- def completions
103
- @completions ||= command.completion_data
104
- end
105
-
106
- def completions_script
107
- @completions_script ||= command.completion_script
108
- end
109
-
110
- def completions_function
111
- @completions_function ||= command.completion_function
112
- end
113
-
114
- def save_comp_yaml(filename = nil)
115
- filename ||= "#{Settings.target_dir}/completions.yml"
116
- File.write filename, completions.to_yaml
117
- say "created !txtgrn!#{filename}"
118
- say ""
119
- say "This file can be converted to a completions script using the !txtgrn!completely!txtrst! gem."
120
- end
121
-
122
- def save_comp_script(filename = nil)
123
- filename ||= "#{Settings.target_dir}/completions.bash"
124
- File.write filename, completions_script
125
- say "created !txtgrn!#{filename}"
126
- say ""
127
- say "In order to enable completions, run:"
128
- say ""
129
- say " !txtpur!$ source #{filename}"
130
- end
131
-
132
- def save_comp_function(name = nil)
133
- name ||= "send_completions"
134
- target_dir = "#{Settings.source_dir}/lib"
135
- filename = "#{target_dir}/#{name}.sh"
105
+ File.deep_write path, content
106
+ say "!txtgrn!created!txtrst! #{path}"
107
+ true
136
108
 
137
- FileUtils.mkdir_p target_dir unless Dir.exist? target_dir
138
- File.write filename, completions_function
139
-
140
- say "created !txtgrn!#{filename}"
141
- say ""
142
- say "In order to use it in your script, create a command or a flag (for example: !txtgrn!#{command.name} completions!txtrst! or !txtgrn!#{command.name} --completions!txtrst!) that calls the !txtgrn!#{name}!txtrst! function."
143
- say "Your users can then run something like this to enable completions:"
144
- say ""
145
- say " !txtpur!$ eval \"$(#{command.name} completions)\""
109
+ end
146
110
  end
147
111
 
148
112
  end
@@ -3,10 +3,12 @@ module Bashly
3
3
  class Generate < Base
4
4
  help "Generate the bash script and required files"
5
5
 
6
- usage "bashly generate [--force --wrap FUNCTION]"
6
+ usage "bashly generate [--force --quiet --upgrade --wrap FUNCTION]"
7
7
  usage "bashly generate (-h|--help)"
8
8
 
9
9
  option "-f --force", "Overwrite existing files"
10
+ option "-q --quiet", "Disable on-screen progress report"
11
+ option "-u --upgrade", "Upgrade all added library functions"
10
12
  option "-w --wrap FUNCTION", "Wrap the entire script in a function so it can also be sourced"
11
13
 
12
14
  environment "BASHLY_SOURCE_DIR", "The path containing the bashly configuration and source files [default: src]"
@@ -17,14 +19,57 @@ module Bashly
17
19
 
18
20
  def run
19
21
  create_user_files
22
+ upgrade_libs if args['--upgrade']
20
23
  create_master_script
21
- say "run !txtpur!#{master_script_path} --help!txtrst! to test your bash script"
24
+ quiet_say "run !txtpur!#{master_script_path} --help!txtrst! to test your bash script"
22
25
  end
23
26
 
24
27
  private
25
28
 
29
+ def quiet_say(message)
30
+ say message unless args['--quiet']
31
+ end
32
+
33
+ def upgrade_libs
34
+ generated_files.each do |file|
35
+ content = File.read file
36
+
37
+ if content =~ /\[@bashly-upgrade (.+)\]/
38
+ lib = $1
39
+
40
+ case lib
41
+ when "colors"
42
+ upgrade file, Library::Colors.new
43
+ when "config"
44
+ upgrade file, Library::Config.new
45
+ when "yaml"
46
+ upgrade file, Library::YAML.new
47
+ when "validations"
48
+ upgrade file, Library::Validations.new
49
+ when /completions (.+)/
50
+ upgrade file, Library::CompletionsFunction.new(file, function: $1)
51
+ end
52
+ end
53
+ end
54
+ end
55
+
56
+ def generated_files
57
+ Dir["#{Settings.source_dir}/**/*.*"].sort
58
+ end
59
+
60
+ def upgrade(existing_file, handler)
61
+ file = handler.files.select { |f| f[:path] == existing_file }.first
62
+
63
+ if file
64
+ File.deep_write file[:path], file[:content]
65
+ quiet_say "!txtcyn!updated!txtrst! #{file[:path]}"
66
+ else
67
+ quiet_say "!txtred!warning!txtrst! not upgrading !txtcyn!#{existing_file}!txtrst!, path mismatch"
68
+ end
69
+ end
70
+
26
71
  def create_user_files
27
- say "creating user files in !txtgrn!#{Settings.source_dir}"
72
+ quiet_say "creating user files in !txtgrn!#{Settings.source_dir}"
28
73
 
29
74
  create_file "#{Settings.source_dir}/initialize.sh", command.render(:default_initialize_script)
30
75
 
@@ -50,21 +95,21 @@ module Bashly
50
95
 
51
96
  def create_file(file, content)
52
97
  if File.exist? file and !args['--force']
53
- say "skipped !txtgrn!#{file}!txtrst! (exists)"
98
+ quiet_say "!txtblu!skipped!txtrst! #{file} (exists)"
54
99
  else
55
100
  File.write file, content
56
- say "created !txtgrn!#{file}"
101
+ quiet_say "!txtgrn!created!txtrst! #{file}"
57
102
  end
58
103
  end
59
104
 
60
105
  def create_master_script
61
106
  File.write master_script_path, script.code
62
107
  FileUtils.chmod "+x", master_script_path
63
- say "created !txtgrn!#{master_script_path}"
108
+ quiet_say "!txtgrn!created!txtrst! #{master_script_path}"
64
109
  end
65
110
 
66
111
  def script
67
- @script ||= Models::Script.new(command, args['--wrap'])
112
+ @script ||= Script::Wrapper.new(command, args['--wrap'])
68
113
  end
69
114
 
70
115
  def master_script_path
@@ -76,7 +121,7 @@ module Bashly
76
121
  end
77
122
 
78
123
  def command
79
- @command ||= Models::Command.new config
124
+ @command ||= Script::Command.new config
80
125
  end
81
126
 
82
127
  end
@@ -17,7 +17,7 @@ module Bashly
17
17
  end
18
18
  Dir.mkdir target_dir unless Dir.exist? target_dir
19
19
  File.write "#{target_dir}/bashly.yml", yaml_content
20
- say "created !txtgrn!#{target_dir}/bashly.yml"
20
+ say "!txtgrn!created!txtrst! #{target_dir}/bashly.yml"
21
21
  say "run !txtpur!bashly generate!txtrst! to create the bash script"
22
22
  end
23
23
 
@@ -10,8 +10,8 @@ module Bashly
10
10
 
11
11
  def run
12
12
  config = Config.new "#{Settings.source_dir}/bashly.yml"
13
- command = Models::Command.new(config)
14
- script = Models::Script.new command
13
+ command = Script::Command.new(config)
14
+ script = Script::Wrapper.new command
15
15
  puts script.code
16
16
  end
17
17
  end
@@ -3,5 +3,9 @@ module Bashly
3
3
  def asset(path)
4
4
  File.expand_path "../#{path}", __dir__
5
5
  end
6
+
7
+ def asset_content(path)
8
+ File.read asset(path)
9
+ end
6
10
  end
7
11
  end
@@ -31,12 +31,16 @@ module Bashly
31
31
  flags.map(&:name) + flags.map(&:short)
32
32
  end
33
33
 
34
+ def completion_allowed_args
35
+ flags.map(&:allowed).flatten + args.map(&:allowed).flatten
36
+ end
37
+
34
38
  def completion_words(with_version: false)
35
39
  trivial_flags = %w[--help -h]
36
40
  trivial_flags += %w[--version -v] if with_version
37
41
  all = (
38
42
  command_names + trivial_flags +
39
- completion_flag_names
43
+ completion_flag_names + completion_allowed_args
40
44
  )
41
45
 
42
46
  all += completions if completions
@@ -0,0 +1,13 @@
1
+ require "fileutils"
2
+
3
+ class File
4
+ def self.deep_write(file, content)
5
+ dir = File.dirname file
6
+ FileUtils.mkdir_p dir unless Dir.exist? dir
7
+ File.write file, content
8
+ end
9
+
10
+ def self.append(path, content)
11
+ File.open(path, "a") { |f| f << content }
12
+ end
13
+ end
@@ -24,7 +24,7 @@ class String
24
24
  end
25
25
 
26
26
  def lint
27
- gsub(/\n{2,}/, "\n\n")
27
+ gsub(/\s+\n/m, "\n\n").lines.reject { |l| l =~ /^\s*##/ }.join ""
28
28
  end
29
29
 
30
30
  end
@@ -0,0 +1,57 @@
1
+ module Bashly
2
+ module Library
3
+ class Base
4
+ include AssetHelper
5
+
6
+ attr_reader :target_path, :options
7
+
8
+ def initialize(target_path = nil, options = nil)
9
+ @target_path = target_path || Settings.source_dir
10
+ @options = options || {}
11
+ end
12
+
13
+ def files
14
+ case content
15
+ when String then content_from_string content
16
+ when Hash then [content]
17
+ else content
18
+ end
19
+ end
20
+
21
+ def post_install_message
22
+ nil
23
+ end
24
+
25
+ def content
26
+ raise NotImplementedError, "Please implement either #content"
27
+ end
28
+
29
+ private
30
+
31
+ def content_from_string(string)
32
+ if File.directory? asset("templates/lib/#{string}")
33
+ content_for_dir string
34
+ else
35
+ [content_for_file(string)]
36
+ end
37
+ end
38
+
39
+ def content_for_file(file)
40
+ {
41
+ path: "#{target_path}/lib/#{file}",
42
+ content: asset_content("templates/lib/#{file}")
43
+ }
44
+ end
45
+
46
+ def content_for_dir(dir)
47
+ Dir[asset("templates/lib/#{dir}/*.sh")].sort.map do |file|
48
+ {
49
+ path: "#{target_path}/lib/#{dir}/#{File.basename file}",
50
+ content: File.read(file)
51
+ }
52
+ end
53
+ end
54
+
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,9 @@
1
+ module Bashly
2
+ module Library
3
+ class Colors < Base
4
+ def content
5
+ "colors.sh"
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,28 @@
1
+ module Bashly
2
+ module Library
3
+ class Completions < Base
4
+ def content
5
+ { path: target_path, content: file_content }
6
+ end
7
+
8
+ def file_content
9
+ raise NotImplementedError, "Please implement #file_content"
10
+ end
11
+
12
+ protected
13
+
14
+ def completions
15
+ @completions ||= command.completion_data
16
+ end
17
+
18
+ def config
19
+ @config ||= Bashly::Config.new "#{Settings.source_dir}/bashly.yml"
20
+ end
21
+
22
+ def command
23
+ @command ||= Script::Command.new config
24
+ end
25
+
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,26 @@
1
+ module Bashly
2
+ module Library
3
+ class CompletionsFunction < Completions
4
+ def file_content
5
+ [
6
+ "# [@bashly-upgrade completions #{function_name}]",
7
+ command.completion_function(function_name)
8
+ ].join "\n"
9
+ end
10
+
11
+ def post_install_message
12
+ <<~EOF
13
+ In order to enable completions in your script, create a command or a flag (for example: !txtgrn!#{command.name} completions!txtrst! or !txtgrn!#{command.name} --completions!txtrst!) that calls the !txtgrn!#{function_name}!txtrst! function.
14
+
15
+ Your users can then run something like this to enable completions:
16
+
17
+ !txtpur!$ eval \"$(#{command.name} completions)\"
18
+ EOF
19
+ end
20
+
21
+ def function_name
22
+ options[:function]
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,17 @@
1
+ module Bashly
2
+ module Library
3
+ class CompletionsScript < Completions
4
+ def file_content
5
+ command.completion_script
6
+ end
7
+
8
+ def post_install_message
9
+ <<~EOF
10
+ In order to enable completions, run:
11
+
12
+ !txtpur!$ source #{target_path}
13
+ EOF
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,15 @@
1
+ module Bashly
2
+ module Library
3
+ class CompletionsYAML < Completions
4
+ def file_content
5
+ completions.to_yaml
6
+ end
7
+
8
+ def post_install_message
9
+ <<~EOF
10
+ This file can be converted to a completions script using the !txtgrn!completely!txtrst! gem.
11
+ EOF
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,9 @@
1
+ module Bashly
2
+ module Library
3
+ class Config < Base
4
+ def content
5
+ "config.sh"
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,9 @@
1
+ module Bashly
2
+ module Library
3
+ class Sample < Base
4
+ def content
5
+ "sample_function.sh"
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,12 @@
1
+ module Bashly
2
+ module Library
3
+ class Strings < Base
4
+ def content
5
+ {
6
+ path: "#{target_path}/bashly-strings.yml",
7
+ content: asset_content("templates/strings.yml")
8
+ }
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,9 @@
1
+ module Bashly
2
+ module Library
3
+ class Validations < Base
4
+ def content
5
+ "validations"
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,9 @@
1
+ module Bashly
2
+ module Library
3
+ class YAML < Base
4
+ def content
5
+ "yaml.sh"
6
+ end
7
+ end
8
+ end
9
+ end
@@ -1,5 +1,5 @@
1
1
  module Bashly
2
- module Models
2
+ module Script
3
3
  class Argument < Base
4
4
  def usage_string
5
5
  required ? name.upcase : "[#{name.upcase}]"
@@ -1,5 +1,5 @@
1
1
  module Bashly
2
- module Models
2
+ module Script
3
3
  class Base
4
4
  include Renderable
5
5
 
@@ -25,6 +25,7 @@ module Bashly
25
25
  parent_name
26
26
  required
27
27
  short
28
+ validate
28
29
  version
29
30
  ]
30
31
 
@@ -1,5 +1,5 @@
1
1
  module Bashly
2
- module Models
2
+ module Script
3
3
  class Command < Base
4
4
  include Completions
5
5
 
@@ -1,5 +1,5 @@
1
1
  module Bashly
2
- module Models
2
+ module Script
3
3
  class EnvironmentVariable < Base
4
4
  def usage_string(extended: false)
5
5
  result = [name.upcase]
@@ -1,5 +1,5 @@
1
1
  module Bashly
2
- module Models
2
+ module Script
3
3
  class Flag < Base
4
4
  def aliases
5
5
  if long and short
@@ -1,6 +1,6 @@
1
1
  module Bashly
2
- module Models
3
- class Script
2
+ module Script
3
+ class Wrapper
4
4
  include Renderable
5
5
 
6
6
  attr_reader :command, :function_name
@@ -22,12 +22,30 @@ module Bashly
22
22
  private
23
23
 
24
24
  def header
25
- @header ||= render('header')
25
+ @header ||= header!
26
+ end
27
+
28
+ def header!
29
+ if File.exist? custom_header_path
30
+ File.read custom_header_path
31
+ else
32
+ default_header
33
+ end
34
+ end
35
+
36
+ def default_header
37
+ result = render('header')
38
+ result += render('bash3_bouncer') unless function_name
39
+ result
26
40
  end
27
41
 
28
42
  def body
29
43
  @body ||= command.render('master_script')
30
44
  end
45
+
46
+ def custom_header_path
47
+ @custom_header_path ||= "#{Settings.source_dir}/header.sh"
48
+ end
31
49
  end
32
50
  end
33
51
  end
@@ -1,32 +1,42 @@
1
- # ---
2
- # Color functions
3
- # This file is a part of Bashly standard library
4
- #
5
- # Usage:
6
- # Use any of the functions below to color or format a portion of a string.
7
- #
8
- # echo "before $(red this is red) after"
9
- # echo "before $(green_bold this is green_bold) after"
10
- #
11
- # ---
1
+ ## Color functions [@bashly-upgrade colors]
2
+ ## This file is a part of Bashly standard library
3
+ ##
4
+ ## Usage:
5
+ ## Use any of the functions below to color or format a portion of a string.
6
+ ##
7
+ ## echo "before $(red this is red) after"
8
+ ## echo "before $(green_bold this is green_bold) after"
9
+ ##
10
+ ## Color output will be disabled if `NO_COLOR` environment variable is set
11
+ ## in compliance with https://no-color.org/
12
+ ##
13
+ print_in_color() {
14
+ local color="$1"
15
+ shift
16
+ if [[ -z ${NO_COLOR+x} ]]; then
17
+ printf "$color%b\e[0m\n" "$*";
18
+ else
19
+ printf "%b\n" "$*";
20
+ fi
21
+ }
12
22
 
13
- red() { printf "\e[31m%b\e[0m\n" "$*"; }
14
- green() { printf "\e[32m%b\e[0m\n" "$*"; }
15
- yellow() { printf "\e[33m%b\e[0m\n" "$*"; }
16
- blue() { printf "\e[34m%b\e[0m\n" "$*"; }
17
- magenta() { printf "\e[35m%b\e[0m\n" "$*"; }
18
- cyan() { printf "\e[36m%b\e[0m\n" "$*"; }
19
- bold() { printf "\e[1m%b\e[0m\n" "$*"; }
20
- underlined() { printf "\e[4m%b\e[0m\n" "$*"; }
21
- red_bold() { printf "\e[1;31m%b\e[0m\n" "$*"; }
22
- green_bold() { printf "\e[1;32m%b\e[0m\n" "$*"; }
23
- yellow_bold() { printf "\e[1;33m%b\e[0m\n" "$*"; }
24
- blue_bold() { printf "\e[1;34m%b\e[0m\n" "$*"; }
25
- magenta_bold() { printf "\e[1;35m%b\e[0m\n" "$*"; }
26
- cyan_bold() { printf "\e[1;36m%b\e[0m\n" "$*"; }
27
- red_underlined() { printf "\e[4;31m%b\e[0m\n" "$*"; }
28
- green_underlined() { printf "\e[4;32m%b\e[0m\n" "$*"; }
29
- yellow_underlined() { printf "\e[4;33m%b\e[0m\n" "$*"; }
30
- blue_underlined() { printf "\e[4;34m%b\e[0m\n" "$*"; }
31
- magenta_underlined() { printf "\e[4;35m%b\e[0m\n" "$*"; }
32
- cyan_underlined() { printf "\e[4;36m%b\e[0m\n" "$*"; }
23
+ red() { print_in_color "\e[31m" "$*"; }
24
+ green() { print_in_color "\e[32m" "$*"; }
25
+ yellow() { print_in_color "\e[33m" "$*"; }
26
+ blue() { print_in_color "\e[34m" "$*"; }
27
+ magenta() { print_in_color "\e[35m" "$*"; }
28
+ cyan() { print_in_color "\e[36m" "$*"; }
29
+ bold() { print_in_color "\e[1m" "$*"; }
30
+ underlined() { print_in_color "\e[4m" "$*"; }
31
+ red_bold() { print_in_color "\e[1;31m" "$*"; }
32
+ green_bold() { print_in_color "\e[1;32m" "$*"; }
33
+ yellow_bold() { print_in_color "\e[1;33m" "$*"; }
34
+ blue_bold() { print_in_color "\e[1;34m" "$*"; }
35
+ magenta_bold() { print_in_color "\e[1;35m" "$*"; }
36
+ cyan_bold() { print_in_color "\e[1;36m" "$*"; }
37
+ red_underlined() { print_in_color "\e[4;31m" "$*"; }
38
+ green_underlined() { print_in_color "\e[4;32m" "$*"; }
39
+ yellow_underlined() { print_in_color "\e[4;33m" "$*"; }
40
+ blue_underlined() { print_in_color "\e[4;34m" "$*"; }
41
+ magenta_underlined() { print_in_color "\e[4;35m" "$*"; }
42
+ cyan_underlined() { print_in_color "\e[4;36m" "$*"; }
@@ -1,27 +1,27 @@
1
- # ---
2
- # Config functions
3
- # This file is a part of Bashly standard library
4
- #
5
- # Usage:
6
- # - In your script, set the CONFIG_FILE variable. For rxample:
7
- # CONFIG_FILE=settings.ini.
8
- # If it is unset, it will default to 'config.ini'.
9
- # - Use any of the functions below to access the config file.
10
- # ---
11
-
12
- # Create a new config file.
13
- # There is normally no need to use this function, it is used by other
14
- # functions as needed.
1
+ ## Config functions [@bashly-upgrade config]
2
+ ## This file is a part of Bashly standard library
3
+ ##
4
+ ## Usage:
5
+ ## - In your script, set the CONFIG_FILE variable. For rxample:
6
+ ## CONFIG_FILE=settings.ini.
7
+ ## If it is unset, it will default to 'config.ini'.
8
+ ## - Use any of the functions below to access the config file.
9
+ ##
10
+ ## Create a new config file.
11
+ ## There is normally no need to use this function, it is used by other
12
+ ## functions as needed.
13
+ ##
15
14
  config_init() {
16
15
  CONFIG_FILE=${CONFIG_FILE:=config.ini}
17
16
  [[ -f "$CONFIG_FILE" ]] || touch "$CONFIG_FILE"
18
17
  }
19
18
 
20
- # Get a value from the config.
21
- # Usage: result=$(config_get hello)
19
+ ## Get a value from the config.
20
+ ## Usage: result=$(config_get hello)
22
21
  config_get() {
23
- key=$1
24
- regex="^$key *= *(.+)$"
22
+ local key=$1
23
+ local regex="^$key *= *(.+)$"
24
+ local value=""
25
25
 
26
26
  config_init
27
27
 
@@ -35,18 +35,19 @@ config_get() {
35
35
  echo "$value"
36
36
  }
37
37
 
38
- # Add or update a key=value pair in the config.
39
- # Usage: config_set key value
38
+ ## Add or update a key=value pair in the config.
39
+ ## Usage: config_set key value
40
40
  config_set() {
41
- key=$1
41
+ local key=$1
42
42
  shift
43
- value="$*"
43
+ local value="$*"
44
44
 
45
45
  config_init
46
46
 
47
- regex="^($key) *= *.+$"
48
- output=""
49
- found_key=""
47
+ local regex="^($key) *= *.+$"
48
+ local output=""
49
+ local found_key=""
50
+ local newline
50
51
 
51
52
  while IFS= read -r line || [ -n "$line" ]; do
52
53
  newline=$line
@@ -66,18 +67,17 @@ config_set() {
66
67
  printf "%b\n" "$output" > "$CONFIG_FILE"
67
68
  }
68
69
 
69
- # Delete a key from the config.
70
- # Usage: config_del key
70
+ ## Delete a key from the config.
71
+ ## Usage: config_del key
71
72
  config_del() {
72
- key=$1
73
+ local key=$1
73
74
 
74
- regex="^($key) *="
75
- output=""
75
+ local regex="^($key) *="
76
+ local output=""
76
77
 
77
78
  config_init
78
79
 
79
80
  while IFS= read -r line || [ -n "$line" ]; do
80
- newline=$line
81
81
  if [[ $line ]] && [[ ! $line =~ $regex ]]; then
82
82
  output="$output$line\n"
83
83
  fi
@@ -86,25 +86,27 @@ config_del() {
86
86
  printf "%b\n" "$output" > "$CONFIG_FILE"
87
87
  }
88
88
 
89
- # Show the config file
89
+ ## Show the config file
90
90
  config_show() {
91
91
  config_init
92
92
  cat "$CONFIG_FILE"
93
93
  }
94
94
 
95
- # Return an array of the keys in the config file.
96
- # Usage:
97
- #
98
- # for k in $(config_keys); do
99
- # echo "- $k = $(config_get "$k")";
100
- # done
101
- #
95
+ ## Return an array of the keys in the config file.
96
+ ## Usage:
97
+ ##
98
+ ## for k in $(config_keys); do
99
+ ## echo "- $k = $(config_get "$k")";
100
+ ## done
101
+ ##
102
102
  config_keys() {
103
- regex="^([a-zA-Z0-9_\-\/\.]+) *="
103
+ local regex="^([a-zA-Z0-9_\-\/\.]+) *="
104
104
 
105
105
  config_init
106
106
 
107
- keys=()
107
+ local keys=()
108
+ local key
109
+
108
110
  while IFS= read -r line || [ -n "$line" ]; do
109
111
  if [[ $line =~ $regex ]]; then
110
112
  key="${BASH_REMATCH[1]}"
@@ -114,13 +116,13 @@ config_keys() {
114
116
  echo "${keys[@]}"
115
117
  }
116
118
 
117
- # Returns true if the specified key exists in the config file.
118
- # Usage:
119
- #
120
- # if config_has_key "key" ; then
121
- # echo "key exists"
122
- # fi
123
- #
119
+ ## Returns true if the specified key exists in the config file.
120
+ ## Usage:
121
+ ##
122
+ ## if config_has_key "key" ; then
123
+ ## echo "key exists"
124
+ ## fi
125
+ ##
124
126
  config_has_key() {
125
127
  [[ $(config_get "$1") ]]
126
128
  }
@@ -1,13 +1,13 @@
1
- # Add any function here that is needed in more than one parts of your
2
- # application, or that you otherwise wish to extract from the main function
3
- # scripts.
4
- #
5
- # Note that code here should be wrapped inside bash functions, and it is
6
- # recommended to have a separate file for each function.
7
- #
8
- # Subdirectories will also be scanned for *.sh, so you have no reason not
9
- # to organize your code neatly.
10
- #
1
+ ## Add any function here that is needed in more than one parts of your
2
+ ## application, or that you otherwise wish to extract from the main function
3
+ ## scripts.
4
+ ##
5
+ ## Note that code here should be wrapped inside bash functions, and it is
6
+ ## recommended to have a separate file for each function.
7
+ ##
8
+ ## Subdirectories will also be scanned for *.sh, so you have no reason not
9
+ ## to organize your code neatly.
10
+ ##
11
11
  sample_function() {
12
12
  echo "it works"
13
13
  }
@@ -0,0 +1,4 @@
1
+ ## [@bashly-upgrade validations]
2
+ validate_dir_exists() {
3
+ [[ -d "$1" ]] || echo "must be an existing directory"
4
+ }
@@ -0,0 +1,4 @@
1
+ ## [@bashly-upgrade validations]
2
+ validate_file_exists() {
3
+ [[ -f "$1" ]] || echo "must be an existing file"
4
+ }
@@ -0,0 +1,4 @@
1
+ ## [@bashly-upgrade validations]
2
+ validate_integer() {
3
+ [[ "$1" =~ ^[0-9]+$ ]] || echo "must be an integer"
4
+ }
@@ -0,0 +1,4 @@
1
+ ## [@bashly-upgrade validations]
2
+ validate_not_empty() {
3
+ [[ -z "$1" ]] && echo "must not be empty"
4
+ }
@@ -1,18 +1,15 @@
1
- # ---
2
- # YAML parser
3
- # This file is a part of Bashly standard library
4
- # Does not support arrays, only hashes
5
- #
6
- # Source: https://stackoverflow.com/a/21189044/413924
7
- #
8
- # Usage:
9
- #
10
- # yaml_load "settings.yml" # print variables
11
- # yaml_load "settings.yml" "config_" # use prefix
12
- # eval $(yaml_load "settings.yml") # create variables in scope
13
- #
14
- # ---
15
-
1
+ ## YAML parser [@bashly-upgrade yaml]
2
+ ## This file is a part of Bashly standard library
3
+ ## Does not support arrays, only hashes
4
+ ##
5
+ ## Source: https://stackoverflow.com/a/21189044/413924
6
+ ##
7
+ ## Usage:
8
+ ##
9
+ ## yaml_load "settings.yml" # print variables
10
+ ## yaml_load "settings.yml" "config_" # use prefix
11
+ ## eval $(yaml_load "settings.yml") # create variables in scope
12
+ ##
16
13
  yaml_load() {
17
14
  local prefix=$2
18
15
  local s='[[:space:]]*' w='[a-zA-Z0-9_]*'
@@ -32,3 +32,4 @@ missing_dependency: "missing dependency: %{dependency}"
32
32
  disallowed_flag: "%{name} must be one of: %{allowed}"
33
33
  disallowed_argument: "%{name} must be one of: %{allowed}"
34
34
  unsupported_bash_version: "bash version 4 or higher is required"
35
+ validation_error: "validation error in %s:\\n%s"
@@ -1,3 +1,3 @@
1
1
  module Bashly
2
- VERSION = "0.6.6"
2
+ VERSION = "0.7.0"
3
3
  end
@@ -0,0 +1,8 @@
1
+ # :argument.validations
2
+ % if validate
3
+ if [[ -n $(validate_<%= validate %> "$1") ]]; then
4
+ printf "<%= strings[:validation_error] %>\n" "<%= name.upcase %>" "$(validate_<%= validate %> "$1")"
5
+ exit 1
6
+ fi
7
+
8
+ % end
@@ -4,10 +4,5 @@ initialize() {
4
4
  long_usage=''
5
5
  set -e
6
6
 
7
- if [[ "${BASH_VERSINFO:-0}" -lt 4 ]]; then
8
- printf "<%= strings[:unsupported_bash_version] -%>\n"
9
- exit 1
10
- fi
11
-
12
7
  <%= load_user_file("initialize.sh", placeholder: false).indent 2 %>
13
8
  }
@@ -8,9 +8,9 @@ parse_requirements() {
8
8
  <%= render(:environment_variables_filter).indent 2 %>
9
9
  <%= render(:dependencies_filter).indent 2 %>
10
10
  <%= render(:command_filter).indent 2 %>
11
+ <%= render(:parse_requirements_while).indent 2 %>
11
12
  <%= render(:required_args_filter).indent 2 %>
12
13
  <%= render(:required_flags_filter).indent 2 %>
13
- <%= render(:parse_requirements_while).indent 2 %>
14
14
  <%= render(:catch_all_filter).indent 2 %>
15
15
  <%= render(:default_assignments).indent 2 %>
16
16
  <%= render(:whitelist_filter).indent 2 %>
@@ -2,7 +2,8 @@
2
2
  % if args.any?
3
3
  % condition = "if"
4
4
  % args.each do |arg|
5
- <%= condition %> [[ ! ${args[<%= arg.name %>]} ]]; then
5
+ <%= condition %> [[ -z ${args[<%= arg.name %>]+x} ]]; then
6
+ <%= arg.render(:validations).indent 2 %>
6
7
  args[<%= arg.name %>]=$1
7
8
  shift
8
9
  % condition = "elif"
@@ -1,11 +1,7 @@
1
1
  # :command.required_args_filter
2
2
  % required_args.each do |arg|
3
- if [[ $1 && $1 != -* ]]; then
4
- args[<%= arg.name %>]=$1
5
- shift
6
- else
3
+ if [[ -z ${args[<%= arg.name %>]+x} ]]; then
7
4
  printf "<%= strings[:missing_required_argument] % { arg: arg.name.upcase, usage: usage_string } %>\n"
8
5
  exit 1
9
6
  fi
10
-
11
7
  % end
@@ -1,9 +1,6 @@
1
1
  # :command.required_flags_filter
2
- % if required_flags.any?
3
- argstring="$*"
4
- % end
5
2
  % required_flags.each do |flag|
6
- if [[ <%= flag.aliases.map { |a| %Q["$argstring" != *#{a}*] }.join " && " %> ]]; then
3
+ if [[ -z ${args[<%= flag.long %>]+x} ]]; then
7
4
  printf "<%= strings[:missing_required_flag] % { usage: flag.usage_string } %>\n"
8
5
  exit 1
9
6
  fi
@@ -1,7 +1,8 @@
1
1
  # :flag.case
2
2
  <%= aliases.join " | " %> )
3
3
  % if arg
4
- if [[ $2 ]]; then
4
+ if [[ -n ${2+x} ]]; then
5
+ <%= render(:validations).indent 4 %>
5
6
  args[<%= name %>]="$2"
6
7
  shift
7
8
  shift
@@ -0,0 +1,8 @@
1
+ # :flag.validations
2
+ % if validate
3
+ if [[ -n $(validate_<%= validate %> "$2") ]]; then
4
+ printf "<%= strings[:validation_error] %>\n" "<%= usage_string %>" "$(validate_<%= validate %> "$2")"
5
+ exit 1
6
+ fi
7
+
8
+ % end
@@ -0,0 +1,5 @@
1
+ # :script.bash3_bouncer
2
+ if [[ "${BASH_VERSINFO:-0}" -lt 4 ]]; then
3
+ printf "<%= strings[:unsupported_bash_version] -%>\n"
4
+ exit 1
5
+ fi
@@ -1,3 +1,4 @@
1
1
  #!/usr/bin/env bash
2
2
  # This script was generated by bashly (https://github.com/DannyBen/bashly)
3
3
  # Modifying it manually is not recommended
4
+
File without changes
data/lib/bashly.rb CHANGED
@@ -9,6 +9,7 @@ requires 'bashly/concerns'
9
9
 
10
10
  requires 'bashly/settings'
11
11
  requires 'bashly/exceptions'
12
- requires 'bashly/models/base'
12
+ requires 'bashly/script/base'
13
13
  requires 'bashly/commands/base'
14
+ requires 'bashly/library/base'
14
15
  requires 'bashly'
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.6.6
4
+ version: 0.7.0
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: 2021-09-07 00:00:00.000000000 Z
11
+ date: 2021-10-29 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: colsole
@@ -88,24 +88,41 @@ files:
88
88
  - lib/bashly/config.rb
89
89
  - lib/bashly/exceptions.rb
90
90
  - lib/bashly/extensions/array.rb
91
+ - lib/bashly/extensions/file.rb
91
92
  - lib/bashly/extensions/string.rb
93
+ - lib/bashly/library/base.rb
94
+ - lib/bashly/library/colors.rb
95
+ - lib/bashly/library/completions.rb
96
+ - lib/bashly/library/completions_function.rb
97
+ - lib/bashly/library/completions_script.rb
98
+ - lib/bashly/library/completions_yaml.rb
99
+ - lib/bashly/library/config.rb
100
+ - lib/bashly/library/sample.rb
101
+ - lib/bashly/library/strings.rb
102
+ - lib/bashly/library/validations.rb
103
+ - lib/bashly/library/yaml.rb
92
104
  - lib/bashly/message_strings.rb
93
- - lib/bashly/models/argument.rb
94
- - lib/bashly/models/base.rb
95
- - lib/bashly/models/command.rb
96
- - lib/bashly/models/environment_variable.rb
97
- - lib/bashly/models/flag.rb
98
- - lib/bashly/models/script.rb
105
+ - lib/bashly/script/argument.rb
106
+ - lib/bashly/script/base.rb
107
+ - lib/bashly/script/command.rb
108
+ - lib/bashly/script/environment_variable.rb
109
+ - lib/bashly/script/flag.rb
110
+ - lib/bashly/script/wrapper.rb
99
111
  - lib/bashly/settings.rb
100
112
  - lib/bashly/templates/bashly.yml
101
113
  - lib/bashly/templates/lib/colors.sh
102
114
  - lib/bashly/templates/lib/config.sh
103
115
  - lib/bashly/templates/lib/sample_function.sh
116
+ - lib/bashly/templates/lib/validations/validate_dir_exists.sh
117
+ - lib/bashly/templates/lib/validations/validate_file_exists.sh
118
+ - lib/bashly/templates/lib/validations/validate_integer.sh
119
+ - lib/bashly/templates/lib/validations/validate_not_empty.sh
104
120
  - lib/bashly/templates/lib/yaml.sh
105
121
  - lib/bashly/templates/minimal.yml
106
122
  - lib/bashly/templates/strings.yml
107
123
  - lib/bashly/version.rb
108
124
  - lib/bashly/views/argument/usage.erb
125
+ - lib/bashly/views/argument/validations.erb
109
126
  - lib/bashly/views/command/catch_all_filter.erb
110
127
  - lib/bashly/views/command/command_fallback.erb
111
128
  - lib/bashly/views/command/command_filter.erb
@@ -143,8 +160,10 @@ files:
143
160
  - lib/bashly/views/environment_variable/usage.erb
144
161
  - lib/bashly/views/flag/case.erb
145
162
  - lib/bashly/views/flag/usage.erb
146
- - lib/bashly/views/script/header.erb
147
- - lib/bashly/views/script/wrapper.erb
163
+ - lib/bashly/views/flag/validations.erb
164
+ - lib/bashly/views/wrapper/bash3_bouncer.erb
165
+ - lib/bashly/views/wrapper/header.erb
166
+ - lib/bashly/views/wrapper/wrapper.erb
148
167
  homepage: https://github.com/dannyben/bashly
149
168
  licenses:
150
169
  - MIT