bashly 0.1.0

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