bashly 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. checksums.yaml +7 -0
  2. data/README.md +48 -0
  3. data/bin/bashly +17 -0
  4. data/lib/bashly.rb +12 -0
  5. data/lib/bashly/cli.rb +19 -0
  6. data/lib/bashly/commands/generate.rb +67 -0
  7. data/lib/bashly/commands/init.rb +53 -0
  8. data/lib/bashly/commands/preview.rb +20 -0
  9. data/lib/bashly/concerns/renderable.rb +26 -0
  10. data/lib/bashly/config.rb +17 -0
  11. data/lib/bashly/exceptions.rb +6 -0
  12. data/lib/bashly/extensions/array.rb +7 -0
  13. data/lib/bashly/extensions/string.rb +20 -0
  14. data/lib/bashly/models/argument.rb +18 -0
  15. data/lib/bashly/models/base.rb +38 -0
  16. data/lib/bashly/models/command.rb +104 -0
  17. data/lib/bashly/models/flag.rb +31 -0
  18. data/lib/bashly/settings.rb +15 -0
  19. data/lib/bashly/templates/bashly.yml +39 -0
  20. data/lib/bashly/templates/minimal.yml +16 -0
  21. data/lib/bashly/version.rb +3 -0
  22. data/lib/bashly/views/argument/usage.erb +4 -0
  23. data/lib/bashly/views/command/command_filter.erb +25 -0
  24. data/lib/bashly/views/command/command_functions.erb +4 -0
  25. data/lib/bashly/views/command/default_root_script.erb +3 -0
  26. data/lib/bashly/views/command/default_script.erb +4 -0
  27. data/lib/bashly/views/command/fixed_flags_filter.erb +14 -0
  28. data/lib/bashly/views/command/function.erb +4 -0
  29. data/lib/bashly/views/command/initialize.erb +6 -0
  30. data/lib/bashly/views/command/inspect_args.erb +5 -0
  31. data/lib/bashly/views/command/master_script.erb +13 -0
  32. data/lib/bashly/views/command/parse_args.erb +12 -0
  33. data/lib/bashly/views/command/parse_args_case.erb +17 -0
  34. data/lib/bashly/views/command/parse_args_secondary.erb +7 -0
  35. data/lib/bashly/views/command/parse_args_while.erb +20 -0
  36. data/lib/bashly/views/command/required_args_filter.erb +12 -0
  37. data/lib/bashly/views/command/required_flags_filter.erb +7 -0
  38. data/lib/bashly/views/command/root_command.erb +4 -0
  39. data/lib/bashly/views/command/run.erb +25 -0
  40. data/lib/bashly/views/command/usage.erb +21 -0
  41. data/lib/bashly/views/command/usage_args.erb +6 -0
  42. data/lib/bashly/views/command/usage_commands.erb +7 -0
  43. data/lib/bashly/views/command/usage_fixed_flags.erb +7 -0
  44. data/lib/bashly/views/command/usage_flags.erb +4 -0
  45. data/lib/bashly/views/command/version_command.erb +4 -0
  46. data/lib/bashly/views/flag/case.erb +16 -0
  47. data/lib/bashly/views/flag/usage.erb +4 -0
  48. metadata +131 -0
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: a4a6b6397239e1fc2b30d3f7cace59aa6bff9b305cfad97b43567dd49cd9eeb8
4
+ data.tar.gz: 27eb14cc3d2f833dec9e218b3affc0e5d90fc0a4cb258e769127590b501a50ac
5
+ SHA512:
6
+ metadata.gz: 0d907d581476cd66f71078319e1d1290f824d2fbbeac6315cd54988ce1068daf59c31e8ab383b498e7aae4955b7d3b2c225e3a3c701aead077527266d7e7dbe5
7
+ data.tar.gz: 8ca10f7db1dd3f2fb03909e60b32f858220b85fd3a961d5c8c2b19e19026238fbce5a1c99ac66b8f3949e217ebdd0cd107cb430d74ff060f4e960f511ab9c6a5
@@ -0,0 +1,48 @@
1
+ Bashly - Bbash CLI Generator
2
+ ==================================================
3
+
4
+ Create beautiful bash scripts from simple YAML configuration.
5
+
6
+ ![demo](demo/cast.svg)
7
+
8
+ ---
9
+
10
+ Installation
11
+ --------------------------------------------------
12
+
13
+ $ gem install bashly
14
+
15
+
16
+ Usage
17
+ --------------------------------------------------
18
+
19
+ In an empty directory, create a sample configuration file by running
20
+
21
+ $ bashly init
22
+ # or, to generate a simpler configuration:
23
+ $ bashly init --minimal
24
+
25
+ This will create a sample `src/bashly.yml` file.
26
+ You can edit this file to specify which arguments, flags and subcommands you
27
+ need in your bash script.
28
+
29
+ Then, generate an initial bash script and function placeholder scripts by
30
+ running
31
+
32
+ $ bashly generate
33
+
34
+ This will:
35
+
36
+ 1. Create the bash executable script.
37
+ 2. Create files for you to edit in the `src` folder.
38
+
39
+ Finally, edit the files in the `src` folder. Each of your script's commands
40
+ get their own file. Once you edit, run `bashly generate` again to merge the
41
+ content from your functions back into the script.
42
+
43
+
44
+ Contributing / Support
45
+ --------------------------------------------------
46
+
47
+ If you experience any issue, have a question or a suggestion, or if you wish
48
+ to contribute, feel free to [open an issue][issues].
@@ -0,0 +1,17 @@
1
+ #!/usr/bin/env ruby
2
+ require 'bashly'
3
+ require 'colsole'
4
+ include Colsole
5
+
6
+ runner = Bashly::CLI.runner
7
+
8
+ begin
9
+ exit runner.run ARGV
10
+ rescue Bashly::Interrupt
11
+ say! "\nGoodbye"
12
+ exit 1
13
+ rescue => e
14
+ puts e.backtrace.reverse if ENV['DEBUG']
15
+ say! "!undred!#{e.class}!txtrst!\n#{e.message}"
16
+ exit 1
17
+ end
@@ -0,0 +1,12 @@
1
+ require 'requires'
2
+
3
+ if ENV['BYEBUG']
4
+ require 'byebug'
5
+ require 'lp'
6
+ end
7
+
8
+ requires 'bashly/settings'
9
+ requires 'bashly/exceptions'
10
+ requires 'bashly/concerns'
11
+ requires 'bashly/models/base'
12
+ requires 'bashly'
@@ -0,0 +1,19 @@
1
+ require 'mister_bin'
2
+
3
+ module Bashly
4
+ # The CLI class is used by the bashly binary and forwards incoming CLI
5
+ # commands to the relevant Bashly::Commands class
6
+ class CLI
7
+ def self.runner
8
+ runner = MisterBin::Runner.new version: Bashly::VERSION,
9
+ header: "Bashly - Bash CLI Generator"
10
+
11
+ runner.route 'init', to: Commands::Init
12
+ runner.route 'preview', to: Commands::Preview
13
+ runner.route 'generate', to: Commands::Generate
14
+
15
+ runner
16
+ end
17
+ end
18
+
19
+ end
@@ -0,0 +1,67 @@
1
+ require 'mister_bin'
2
+
3
+ module Bashly
4
+ module Commands
5
+ class Generate < MisterBin::Command
6
+ help "Generate the bash script and required files"
7
+
8
+ usage "bashly generate [--force]"
9
+ usage "bashly generate (-h|--help)"
10
+
11
+ option "-f --force", "Overwrite existing files"
12
+
13
+ environment "BASHLY_SOURCE_DIR", "The path to use for creating the configuration file [default: src]"
14
+ environment "BASHLY_TARGET_DIR", "The path to use for creating the bash script [default: .]"
15
+
16
+ def run
17
+ create_user_files
18
+ create_master_script
19
+ say "run !txtpur!#{master_script_path} --help!txtrst! to test your bash script"
20
+ end
21
+
22
+ private
23
+
24
+ def create_user_files
25
+ say "creating user files in !txtgrn!#{Settings.source_dir}"
26
+
27
+ if command.commands.empty?
28
+ create_file "#{Settings.source_dir}/root_command.sh", command.render(:default_root_script)
29
+ end
30
+
31
+ command.commands.each do |subcommand|
32
+ file = "#{Settings.source_dir}/#{subcommand.full_name.to_underscore}_command.sh"
33
+ content = subcommand.render :default_script
34
+ create_file file, content
35
+ end
36
+ end
37
+
38
+ def create_file(file, content)
39
+ if File.exist? file and !args['--force']
40
+ say "skipped !txtgrn!#{file}!txtrst! (exists)"
41
+ else
42
+ File.write file, content
43
+ say "created !txtgrn!#{file}"
44
+ end
45
+ end
46
+
47
+ def create_master_script
48
+ master_script = command.render 'master_script'
49
+ File.write master_script_path, master_script
50
+ say "created !txtgrn!#{master_script_path}"
51
+ end
52
+
53
+ def master_script_path
54
+ "#{Settings.target_dir}/#{command.name}"
55
+ end
56
+
57
+ def config
58
+ @config ||= Config.new "#{Settings.source_dir}/bashly.yml"
59
+ end
60
+
61
+ def command
62
+ @command ||= Models::Command.new config
63
+ end
64
+
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,53 @@
1
+ require 'mister_bin'
2
+
3
+ module Bashly
4
+ module Commands
5
+ class Init < MisterBin::Command
6
+ summary "Initialize a new workspace"
7
+ help "This command will create the source folder, and place a template configuration file in it."
8
+
9
+ usage "bashly init [--minimal]"
10
+ usage "bashly init (-h|--help)"
11
+
12
+ option "-m --minimal", "Use a minimal configuration file (without subcommands)"
13
+
14
+ environment "BASHLY_SOURCE_DIR", "The path to use for creating the configuration file [default: src]"
15
+
16
+ def run
17
+ if Dir.exist? target_dir and !Dir.empty? target_dir
18
+ raise InitError, "Directory !txtgrn!#{target_dir}!txtrst! already exists and is not empty"
19
+ end
20
+ Dir.mkdir target_dir unless Dir.exist? target_dir
21
+ File.write "#{target_dir}/bashly.yml", yaml_content
22
+ say "created !txtgrn!#{target_dir}/bashly.yml"
23
+ say "run !txtpur!bashly generate!txtrst! to create the bash script"
24
+ end
25
+
26
+ private
27
+
28
+ def yaml_content
29
+ @yaml_content ||= yaml_content!
30
+ end
31
+
32
+ def yaml_content!
33
+ if args['--minimal']
34
+ File.read minimal_template_file
35
+ else
36
+ File.read template_file
37
+ end
38
+ end
39
+
40
+ def template_file
41
+ File.expand_path '../templates/bashly.yml', __dir__
42
+ end
43
+
44
+ def minimal_template_file
45
+ File.expand_path '../templates/minimal.yml', __dir__
46
+ end
47
+
48
+ def target_dir
49
+ @target_dir ||= Settings.source_dir
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,20 @@
1
+ require 'mister_bin'
2
+
3
+ module Bashly
4
+ module Commands
5
+ class Preview < MisterBin::Command
6
+ help "Generate the bash script to STDOUT"
7
+
8
+ usage "bashly preview"
9
+ usage "bashly preview (-h|--help)"
10
+
11
+ environment "BASHLY_SOURCE_DIR", "The path containing the bashly configuration and source files [default: src]"
12
+
13
+ def run
14
+ config = Config.new "#{Settings.source_dir}/bashly.yml"
15
+ command = Models::Command.new(config)
16
+ puts command.render 'master_script'
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,26 @@
1
+ module Bashly
2
+ module Renderable
3
+ def render(view)
4
+ template = File.read view_path(view)
5
+ ERB.new(template, nil, '%-').result(binding)
6
+ end
7
+
8
+ private
9
+
10
+ def view_path(view)
11
+ "#{self_views_path}/#{view}.erb"
12
+ end
13
+
14
+ def self_views_path
15
+ @self_view_path ||= "#{base_views_path}/#{views_subfolder}"
16
+ end
17
+
18
+ def base_views_path
19
+ @base_views_path ||= File.expand_path("../views/", __dir__)
20
+ end
21
+
22
+ def views_subfolder
23
+ @views_subfolder ||= self.class.name.split('::').last.to_underscore
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,17 @@
1
+ require 'yaml'
2
+
3
+ module Bashly
4
+ # A convenience class to use either a hash or a filename as a configuration
5
+ # source
6
+ class Config
7
+ attr_reader :config
8
+
9
+ def self.new(config)
10
+ if config.is_a? String
11
+ YAML.load_file config
12
+ else
13
+ config
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,6 @@
1
+ module Bashly
2
+ class Interrupt < Interrupt; end
3
+ class Error < StandardError; end
4
+ class InitError < Error; end
5
+ class ConfigurationError < Error; end
6
+ end
@@ -0,0 +1,7 @@
1
+ class Array
2
+ def indent(offset)
3
+ return self unless offset > 0
4
+ indentation = " " * offset
5
+ map { |line| "#{indentation}#{line}" }
6
+ end
7
+ end
@@ -0,0 +1,20 @@
1
+ require 'erb'
2
+ require 'ostruct'
3
+
4
+ class String
5
+ def indent(offset)
6
+ return self unless offset > 0
7
+ split("\n").indent(offset).join("\n")
8
+ end
9
+
10
+ def to_underscore!
11
+ gsub!(/(.)([A-Z])/,'\1_\2')
12
+ gsub!(' ', '_')
13
+ downcase!
14
+ end
15
+
16
+ def to_underscore
17
+ dup.tap { |s| s.to_underscore! }
18
+ end
19
+
20
+ end
@@ -0,0 +1,18 @@
1
+ module Bashly
2
+ module Models
3
+ class Argument < Base
4
+ def optional
5
+ !required
6
+ end
7
+
8
+ def usage_string
9
+ required ? name.upcase : "[#{name.upcase}]"
10
+ end
11
+
12
+ def summary
13
+ return "" unless help
14
+ help.split("\n").first
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,38 @@
1
+ module Bashly
2
+ module Models
3
+ class Base
4
+ include Renderable
5
+
6
+ attr_reader :options
7
+
8
+ OPTION_KEYS = %i[
9
+ arg
10
+ description
11
+ flags
12
+ help
13
+ long
14
+ name
15
+ parent_name
16
+ required
17
+ short
18
+ version
19
+ ]
20
+
21
+ def initialize(options)
22
+ raise Error, "Invalid options provided" unless options.respond_to? :keys
23
+ @options = options
24
+ verify if respond_to? :verify
25
+ end
26
+
27
+ def method_missing(method_name, *arguments, &block)
28
+ key = method_name.to_s
29
+ respond_to?(method_name) ? options[key] : super
30
+ end
31
+
32
+ def respond_to?(method_name, include_private = false)
33
+ # options.has_key?(method_name.to_s) || super
34
+ OPTION_KEYS.include?(method_name) || super
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,104 @@
1
+ module Bashly
2
+ module Models
3
+ class Command < Base
4
+ # Returns all the possible aliases for this command
5
+ def aliases
6
+ short ? [name, short] : [name]
7
+ end
8
+
9
+ # Returns an array of Arguments
10
+ def args
11
+ return [] unless options["args"]
12
+ options["args"].map do |options|
13
+ Argument.new options
14
+ end
15
+ end
16
+
17
+ # Returns a string suitable to be a headline
18
+ def caption_string
19
+ help ? "#{full_name} - #{summary}" : full_name
20
+ end
21
+
22
+ # Returns only the names of the subcommands (Commands)
23
+ def command_names
24
+ commands.map &:name
25
+ end
26
+
27
+ # Returns an array of the subcommands (Commands)
28
+ def commands
29
+ return [] unless options["commands"]
30
+ options["commands"].map do |options|
31
+ options['parent_name'] = full_name
32
+ command = Command.new options
33
+ end
34
+ end
35
+
36
+ # Returns an array of Flags
37
+ def flags
38
+ return [] unless options["flags"]
39
+ options["flags"].map do |options|
40
+ Flag.new options
41
+ end
42
+ end
43
+
44
+ # Returns the name of the command, including its parent name (in case
45
+ # this is a subcommand)
46
+ def full_name
47
+ parent_name ? "#{parent_name} #{name}" : name
48
+ end
49
+
50
+ # Reads a file from the userspace (Settings.source_dir) and returns
51
+ # its contents.
52
+ # If the file is not found, returns a string with a hint.
53
+ def load_user_file(file)
54
+ path = "#{Settings.source_dir}/#{file}"
55
+ content = if File.exist? path
56
+ File.read path
57
+ else
58
+ "# error: cannot load file"
59
+ end
60
+ "# :#{path}\n#{content}"
61
+ end
62
+
63
+ # Returns an array of all the required Arguments
64
+ def required_args
65
+ args.select &:required
66
+ end
67
+
68
+ # Returns an array of all the required Flags
69
+ def required_flags
70
+ flags.select &:required
71
+ end
72
+
73
+ # Returns the first line of the help message
74
+ def summary
75
+ return "" unless help
76
+ help.split("\n").first
77
+ end
78
+
79
+ # Returns a constructed string suitable for Usage pattern
80
+ def usage_string
81
+ result = [full_name]
82
+ result << "[command]" if commands.any?
83
+ args.each do |arg|
84
+ result << arg.usage_string
85
+ end
86
+ result << "[options]"
87
+ result.join " "
88
+ end
89
+
90
+ def verify
91
+ if commands.any?
92
+ if args.any? or flags.any?
93
+ raise ConfigurationError, "Error in the !txtgrn!#{full_name}!txtrst! command.\nThe !txtgrn!commands!txtrst! key cannot be at the same level as the !txtgrn!args!txtrst! or !txtgrn!flags!txtrst! keys."
94
+ end
95
+
96
+ if parent_name
97
+ raise ConfigurationError, "Error in the !txtgrn!#{full_name}!txtrst! command.\nNested commands are not supported."
98
+ end
99
+ end
100
+ end
101
+
102
+ end
103
+ end
104
+ end
@@ -0,0 +1,31 @@
1
+ module Bashly
2
+ module Models
3
+ class Flag < Base
4
+ def aliases
5
+ if long and short
6
+ [long, short]
7
+ elsif long
8
+ [long]
9
+ else
10
+ [short]
11
+ end
12
+ end
13
+
14
+ def optional
15
+ !required
16
+ end
17
+
18
+ def summary
19
+ return "" unless help
20
+ help.split("\n").first
21
+ end
22
+
23
+ def usage_string(extended: false)
24
+ result = [aliases.join(", ")]
25
+ result << arg.upcase if arg
26
+ result << "(required)" if required and extended
27
+ result.join " "
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,15 @@
1
+ module Bashly
2
+ class Settings
3
+ class << self
4
+ attr_writer :source_dir, :target_dir
5
+
6
+ def source_dir
7
+ @source_dir ||= ENV['BASHLY_SOURCE_DIR'] || 'src'
8
+ end
9
+
10
+ def target_dir
11
+ @target_dir ||= ENV['BASHLY_TARGET_DIR'] || '.'
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,39 @@
1
+ name: cli
2
+ help: Sample application
3
+ version: 0.1.0
4
+
5
+ commands:
6
+ - name: download
7
+ short: d
8
+ help: Download a file
9
+
10
+ args:
11
+ - name: source
12
+ required: true
13
+ help: URL to download from
14
+ - name: target
15
+ help: "Target filename (default: same as source)"
16
+
17
+ flags:
18
+ - long: --force
19
+ short: -f
20
+ help: Overwrite existing files
21
+
22
+ - name: upload
23
+ short: u
24
+ help: Upload a file
25
+ args:
26
+ - name: source
27
+ required: true
28
+ help: File to upload
29
+
30
+ flags:
31
+ - long: --user
32
+ short: -u
33
+ arg: user
34
+ help: Username to use for logging in
35
+ required: true
36
+ - long: --password
37
+ short: -p
38
+ arg: password
39
+ help: Password to use for logging in
@@ -0,0 +1,16 @@
1
+ name: download
2
+ help: Sample minimal application without subcommands
3
+ version: 0.1.0
4
+
5
+ args:
6
+ - name: source
7
+ required: true
8
+ help: URL to download from
9
+ - name: target
10
+ help: "Target filename (default: same as source)"
11
+
12
+ flags:
13
+ - long: --force
14
+ short: -f
15
+ help: Overwrite existing files
16
+
@@ -0,0 +1,3 @@
1
+ module Bashly
2
+ VERSION = "0.1.0"
3
+ end
@@ -0,0 +1,4 @@
1
+ # :argument.usage
2
+ echo " <%= name.upcase %>"
3
+ echo " <%= summary %>"
4
+ echo
@@ -0,0 +1,25 @@
1
+ # :command.command_filter
2
+ <%- if commands.any? -%>
3
+ action=$1
4
+
5
+ case $action in
6
+ -* )
7
+ ;;
8
+
9
+ <%- commands.each do |command| -%>
10
+ <%= command.aliases.join " | " %> )
11
+ shift
12
+ <%= command.name %>_parse_args "$@"
13
+ shift $#
14
+ ;;
15
+
16
+ <%- end -%>
17
+ * )
18
+ <%= name %>_usage
19
+ exit 1
20
+ ;;
21
+
22
+ esac
23
+ <%- else -%>
24
+ action=root
25
+ <%- end -%>
@@ -0,0 +1,4 @@
1
+ # :command.command_functions
2
+ <%- commands.each do |command| -%>
3
+ <%= command.render :function %>
4
+ <%- end -%>
@@ -0,0 +1,3 @@
1
+ echo "# this file is located in '<%= Settings.source_dir %>/root_command.sh'"
2
+ echo "# you can edit it freely and regenerate (it will not be overwritten)"
3
+ inspect_args
@@ -0,0 +1,4 @@
1
+ echo "# this file is located in '<%= Settings.source_dir %>/<%= full_name.to_underscore %>_command.sh'"
2
+ echo "# code for '<%= full_name %>' goes here"
3
+ echo "# you can edit it freely and regenerate (it will not be overwritten)"
4
+ inspect_args
@@ -0,0 +1,14 @@
1
+ # :command.fixed_flag_filter
2
+ case "$1" in
3
+ --version | -v )
4
+ version_command
5
+ exit 1
6
+ ;;
7
+
8
+ --help | -h )
9
+ long_usage=yes
10
+ <%= full_name.to_underscore %>_usage
11
+ exit 1
12
+ ;;
13
+
14
+ esac
@@ -0,0 +1,4 @@
1
+ # :command.function
2
+ <%= full_name.to_underscore %>_command() {
3
+ <%= load_user_file("#{full_name.to_underscore}_command.sh").indent 2 %>
4
+ }
@@ -0,0 +1,6 @@
1
+ # :command.initialize
2
+ initialize() {
3
+ version="<%= version %>"
4
+ long_usage=''
5
+ set -e
6
+ }
@@ -0,0 +1,5 @@
1
+ # :command.inspect_args
2
+ inspect_args() {
3
+ echo args:
4
+ for k in "${!args[@]}"; do echo "- \${args[$k]}" = ${args[$k]}; done
5
+ }
@@ -0,0 +1,13 @@
1
+ #!/usr/bin/env bash
2
+
3
+ <%= render :root_command if commands.empty? %>
4
+ <%= render :version_command %>
5
+ <%= render :usage %>
6
+ <%= render :inspect_args %>
7
+ <%= render :command_functions %>
8
+ <%= render :parse_args %>
9
+ <%= render :initialize %>
10
+ <%= render :run %>
11
+
12
+ initialize
13
+ run "$@"
@@ -0,0 +1,12 @@
1
+ # :command.parse_args
2
+ parse_args() {
3
+ <%= render(:fixed_flags_filter).indent 2 %>
4
+ <%= render(:command_filter).indent 2 %>
5
+ <%= render(:required_args_filter).indent 2 %>
6
+ <%= render(:required_flags_filter).indent 2 %>
7
+ <%= render(:parse_args_while).indent 2 %>
8
+ }
9
+
10
+ <%- commands.each do |command| %>
11
+ <%= command.render 'parse_args_secondary' %>
12
+ <%- end -%>
@@ -0,0 +1,17 @@
1
+ # :command.parse_args_case
2
+ <%- if args.any? -%>
3
+ <%- condition = "if" -%>
4
+ <%- args.each do |arg| -%>
5
+ <%= condition %> [[ ! ${args[<%= arg.name %>]} ]]; then
6
+ args[<%= arg.name %>]=$1
7
+ shift
8
+ <%- condition = "elif" -%>
9
+ <%- end -%>
10
+ else
11
+ echo invalid argument: $key
12
+ exit 1
13
+ fi
14
+ <%- else -%>
15
+ echo invalid argument: $key
16
+ exit 1
17
+ <%- end -%>
@@ -0,0 +1,7 @@
1
+ # :command.parse_args_secondary
2
+ <%= name %>_parse_args() {
3
+ <%= render(:fixed_flags_filter).indent 2 %>
4
+ <%= render(:required_args_filter).indent 2 %>
5
+ <%= render(:required_flags_filter).indent 2 %>
6
+ <%= render(:parse_args_while).indent 2 %>
7
+ }
@@ -0,0 +1,20 @@
1
+ # :command.parse_args_while
2
+ while [[ $# -gt 0 ]]; do
3
+ key="$1"
4
+ case "$key" in
5
+ <%- flags.each do |flag| -%>
6
+ <%= flag.render(:case).indent 2 %>
7
+
8
+ <%- end -%>
9
+
10
+ -* )
11
+ echo invalid option: $key
12
+ exit 1
13
+ ;;
14
+
15
+ * )
16
+ <%= render(:parse_args_case).indent 4 %>
17
+ ;;
18
+
19
+ esac
20
+ done
@@ -0,0 +1,12 @@
1
+ # :command.required_args_filter
2
+ <%- required_args.each do |arg| -%>
3
+ if [[ $1 && $1 != -* ]]; then
4
+ args[<%= arg.name %>]=$1
5
+ shift
6
+ else
7
+ echo missing required argument: <%= arg.name.upcase %>
8
+ echo Usage: <%= usage_string %>
9
+ exit 1
10
+ fi
11
+
12
+ <%- end %>
@@ -0,0 +1,7 @@
1
+ # :command.required_flags_filter
2
+ <%- required_flags.each do |flag| -%>
3
+ if [[ <%= flag.aliases.map { |a| "$* != *#{a}*" }.join " && " %> ]]; then
4
+ echo "missing required flag: <%= flag.usage_string %>"
5
+ exit 1
6
+ fi
7
+ <%- end %>
@@ -0,0 +1,4 @@
1
+ # :command.root_command
2
+ root_command() {
3
+ <%= load_user_file("root_command.sh").indent 2 %>
4
+ }
@@ -0,0 +1,25 @@
1
+ # :command.run
2
+ run() {
3
+ declare -A args
4
+ parse_args "$@"
5
+
6
+ <%- condition = "if" -%>
7
+ <%- commands.each do |command| -%>
8
+ <%= condition %> [[ $action == "<%= command.name %>" ]]; then
9
+ if [[ ${args[--help]} ]]; then
10
+ long_usage=yes
11
+ <%= command.full_name.to_underscore %>_usage
12
+ else
13
+ <%= command.full_name.to_underscore %>_command
14
+ fi
15
+ <% condition = "elif" %>
16
+ <%- end -%>
17
+ <%= condition %> [[ ${args[--version]} ]]; then
18
+ version_command
19
+ elif [[ ${args[--help]} ]]; then
20
+ long_usage=yes
21
+ <%= name %>_usage
22
+ elif [[ $action == "root" ]]; then
23
+ root_command
24
+ fi
25
+ }
@@ -0,0 +1,21 @@
1
+ # :command.usage
2
+ <%= full_name.to_underscore %>_usage() {
3
+ echo "<%= caption_string %>"
4
+ echo
5
+ echo "Usage:"
6
+ echo " <%= usage_string %>"
7
+ echo
8
+ <%= render(:usage_commands).indent 2 if commands.any? %>
9
+
10
+ if [[ -n $long_usage ]]; then
11
+ echo "Options:"
12
+ <%= render(:usage_fixed_flags).indent 4 %>
13
+ <%= render(:usage_flags).indent 4 if flags.any? %>
14
+ <%= render(:usage_args).indent 4 if args.any? %>
15
+
16
+ fi
17
+ }
18
+
19
+ <%- commands.each do |command| -%>
20
+ <%= command.render 'usage' %>
21
+ <%- end -%>
@@ -0,0 +1,6 @@
1
+ # :command.usage_args
2
+ echo "Arguments:"
3
+
4
+ <%- args.each do |arg| -%>
5
+ <%= arg.render(:usage) %>
6
+ <%- end -%>
@@ -0,0 +1,7 @@
1
+ # :command.usage_commands
2
+ echo "Commands:"
3
+ <%- maxlen = command_names.map(&:size).max -%>
4
+ <%- commands.each do |command| -%>
5
+ echo " <%= command.name.ljust maxlen %> <%= command.summary %>"
6
+ <%- end -%>
7
+ echo
@@ -0,0 +1,7 @@
1
+ # :command.usage_fixed_flags
2
+ echo " --help, -h"
3
+ echo " Show this help"
4
+ echo
5
+ echo " --version"
6
+ echo " Show version number"
7
+ echo
@@ -0,0 +1,4 @@
1
+ # :command.usage_flags
2
+ <%- flags.each do |flag| -%>
3
+ <%= flag.render(:usage) %>
4
+ <%- end -%>
@@ -0,0 +1,4 @@
1
+ # :command.version_command
2
+ version_command() {
3
+ echo $version
4
+ }
@@ -0,0 +1,16 @@
1
+ # :flag.case
2
+ <%= aliases.join " | " %> )
3
+ <%- if arg -%>
4
+ if [[ $2 && $2 != -* ]]; then
5
+ args[<%= long %>]="$2"
6
+ shift
7
+ shift
8
+ else
9
+ echo <%= long %> requires an argument: <%= usage_string %>
10
+ exit 1
11
+ fi
12
+ <%- else -%>
13
+ args[<%= long %>]=1
14
+ shift
15
+ <%- end -%>
16
+ ;;
@@ -0,0 +1,4 @@
1
+ # :flag.usage
2
+ echo " <%= usage_string extended: true %>"
3
+ echo " <%= summary %>"
4
+ echo
metadata ADDED
@@ -0,0 +1,131 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: bashly
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Danny Ben Shitrit
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2019-11-19 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: colsole
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '0.5'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '0.5'
27
+ - !ruby/object:Gem::Dependency
28
+ name: mister_bin
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '0.6'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '0.6'
41
+ - !ruby/object:Gem::Dependency
42
+ name: requires
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '0.1'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '0.1'
55
+ description: Generate bash command line tools using YAML configuration
56
+ email: db@dannyben.com
57
+ executables:
58
+ - bashly
59
+ extensions: []
60
+ extra_rdoc_files: []
61
+ files:
62
+ - README.md
63
+ - bin/bashly
64
+ - lib/bashly.rb
65
+ - lib/bashly/cli.rb
66
+ - lib/bashly/commands/generate.rb
67
+ - lib/bashly/commands/init.rb
68
+ - lib/bashly/commands/preview.rb
69
+ - lib/bashly/concerns/renderable.rb
70
+ - lib/bashly/config.rb
71
+ - lib/bashly/exceptions.rb
72
+ - lib/bashly/extensions/array.rb
73
+ - lib/bashly/extensions/string.rb
74
+ - lib/bashly/models/argument.rb
75
+ - lib/bashly/models/base.rb
76
+ - lib/bashly/models/command.rb
77
+ - lib/bashly/models/flag.rb
78
+ - lib/bashly/settings.rb
79
+ - lib/bashly/templates/bashly.yml
80
+ - lib/bashly/templates/minimal.yml
81
+ - lib/bashly/version.rb
82
+ - lib/bashly/views/argument/usage.erb
83
+ - lib/bashly/views/command/command_filter.erb
84
+ - lib/bashly/views/command/command_functions.erb
85
+ - lib/bashly/views/command/default_root_script.erb
86
+ - lib/bashly/views/command/default_script.erb
87
+ - lib/bashly/views/command/fixed_flags_filter.erb
88
+ - lib/bashly/views/command/function.erb
89
+ - lib/bashly/views/command/initialize.erb
90
+ - lib/bashly/views/command/inspect_args.erb
91
+ - lib/bashly/views/command/master_script.erb
92
+ - lib/bashly/views/command/parse_args.erb
93
+ - lib/bashly/views/command/parse_args_case.erb
94
+ - lib/bashly/views/command/parse_args_secondary.erb
95
+ - lib/bashly/views/command/parse_args_while.erb
96
+ - lib/bashly/views/command/required_args_filter.erb
97
+ - lib/bashly/views/command/required_flags_filter.erb
98
+ - lib/bashly/views/command/root_command.erb
99
+ - lib/bashly/views/command/run.erb
100
+ - lib/bashly/views/command/usage.erb
101
+ - lib/bashly/views/command/usage_args.erb
102
+ - lib/bashly/views/command/usage_commands.erb
103
+ - lib/bashly/views/command/usage_fixed_flags.erb
104
+ - lib/bashly/views/command/usage_flags.erb
105
+ - lib/bashly/views/command/version_command.erb
106
+ - lib/bashly/views/flag/case.erb
107
+ - lib/bashly/views/flag/usage.erb
108
+ homepage: https://github.com/dannyben/bashly
109
+ licenses:
110
+ - MIT
111
+ metadata: {}
112
+ post_install_message:
113
+ rdoc_options: []
114
+ require_paths:
115
+ - lib
116
+ required_ruby_version: !ruby/object:Gem::Requirement
117
+ requirements:
118
+ - - ">="
119
+ - !ruby/object:Gem::Version
120
+ version: 2.3.0
121
+ required_rubygems_version: !ruby/object:Gem::Requirement
122
+ requirements:
123
+ - - ">="
124
+ - !ruby/object:Gem::Version
125
+ version: '0'
126
+ requirements: []
127
+ rubygems_version: 3.0.3
128
+ signing_key:
129
+ specification_version: 4
130
+ summary: Bash Command Line Tool Generator
131
+ test_files: []