bashly 1.1.8 → 1.1.10

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d11d229ff42dc84aa910ee8292e145b63641c5520315583e92d5aa0747e6e578
4
- data.tar.gz: 61a2ddcb970af41f7a086da0a75d0452a637e3fe650569e2768f1a0eefe49849
3
+ metadata.gz: '0857ce9cc52b919c113242568610ed9abbd447221b6d473bbf05f1cd8f29de69'
4
+ data.tar.gz: 3eb8279ffdbf23a69a5c7eb52d98f2f0c3ca1730f54eb6a18b4957038b69810a
5
5
  SHA512:
6
- metadata.gz: b9ffdaa7a7a862b11d932e6533d4b7e1b1e76407292bbaf5ca8afdc192249b0f51ea504adfc459e37b712074291ef612fc5b29965700043676bc94709062e696
7
- data.tar.gz: 629b81b7ef43fa64523a465968828d74192fbd17440e9f20f724a6ef040ebe0c805bed339f899ca6047f53043c29d88537829075bd60be2f752a69e09eaaee55
6
+ metadata.gz: 2aa95822cebdc83d0e3a1af47a2d75886994a7d79976188049b8ea6732ca3d6a83003bfea48c3789e8d3a4db562e16f8f9ed169da414065567c7abf2d2d4c40b
7
+ data.tar.gz: 3cf237064dfe7f9cfb2523c85d838141f9869e246619b9ed2bf9862499a0345ea1da12178802ee6d7a654e1ad8fec674b751442566b8b8ca1e22b77055105fd1
@@ -2,7 +2,10 @@ require 'gtx'
2
2
 
3
3
  module Bashly
4
4
  module Renderable
5
- def render(view)
5
+ attr_reader :render_options
6
+
7
+ def render(view, render_options = {})
8
+ @render_options = render_options
6
9
  GTX.render_file view_path(view), context: binding, filename: "#{views_subfolder}.#{view}"
7
10
  end
8
11
 
@@ -1,6 +1,6 @@
1
1
  class String
2
2
  def sanitize_for_print
3
- gsub("\n", '\\n').gsub('"', '\"').gsub('`', '\\\\`')
3
+ gsub("\n", '\\n').gsub('"', '\"').gsub('`', '\\\\`').gsub('%', '%%')
4
4
  end
5
5
 
6
6
  def for_markdown
@@ -42,6 +42,11 @@ tab_indent: false
42
42
  # `-abc` as if it is `-a -b -c`.
43
43
  compact_short_flags: true
44
44
 
45
+ # When true, the generated script will consider any argument in the form of
46
+ # `--flag=value` and `-f=value` as if it is `--flag value` and `-f value`
47
+ # respectively.
48
+ conjoined_flag_args: true
49
+
45
50
  # Set to 'production' or 'development':
46
51
  # env: production Generate a smaller script, without file markers
47
52
  # env: development Generate with file markers
@@ -1,4 +1,4 @@
1
- # approvals.bash v0.5.0
1
+ # approvals.bash v0.5.1
2
2
  #
3
3
  # Interactive approval testing for Bash.
4
4
  # https://github.com/DannyBen/approvals.bash
@@ -54,6 +54,10 @@ context() {
54
54
  printf "$context_string\n" "$*"
55
55
  }
56
56
 
57
+ it() {
58
+ printf "$it_string\n" "$*"
59
+ }
60
+
57
61
  fail() {
58
62
  printf "$fail_string\n" "$*"
59
63
  exit 1
@@ -72,29 +76,57 @@ expect_exit_code() {
72
76
  fi
73
77
  }
74
78
 
75
- bold() { printf "\e[1m%b\e[0m\n" "$*"; }
76
- blue() { printf "\e[34m%b\e[0m\n" "$*"; }
77
- cyan() { printf "\e[36m%b\e[0m\n" "$*"; }
78
- green() { printf "\e[32m%b\e[0m\n" "$*"; }
79
- magenta() { printf "\e[35m%b\e[0m\n" "$*"; }
80
- red() { printf "\e[31m%b\e[0m\n" "$*"; }
81
- yellow() { printf "\e[33m%b\e[0m\n" "$*"; }
82
-
83
79
  # Private
84
80
 
81
+ print_in_color() {
82
+ local color="$1"
83
+ shift
84
+ if [[ -z ${NO_COLOR+x} ]]; then
85
+ printf "$color%b\e[0m\n" "$*"
86
+ else
87
+ printf "%b\n" "$*"
88
+ fi
89
+ }
90
+
91
+ red() { print_in_color "\e[31m" "$*"; }
92
+ green() { print_in_color "\e[32m" "$*"; }
93
+ yellow() { print_in_color "\e[33m" "$*"; }
94
+ blue() { print_in_color "\e[34m" "$*"; }
95
+ magenta() { print_in_color "\e[35m" "$*"; }
96
+ cyan() { print_in_color "\e[36m" "$*"; }
97
+ bold() { print_in_color "\e[1m" "$*"; }
98
+ underlined() { print_in_color "\e[4m" "$*"; }
99
+ red_bold() { print_in_color "\e[1;31m" "$*"; }
100
+ green_bold() { print_in_color "\e[1;32m" "$*"; }
101
+ yellow_bold() { print_in_color "\e[1;33m" "$*"; }
102
+ blue_bold() { print_in_color "\e[1;34m" "$*"; }
103
+ magenta_bold() { print_in_color "\e[1;35m" "$*"; }
104
+ cyan_bold() { print_in_color "\e[1;36m" "$*"; }
105
+ red_underlined() { print_in_color "\e[4;31m" "$*"; }
106
+ green_underlined() { print_in_color "\e[4;32m" "$*"; }
107
+ yellow_underlined() { print_in_color "\e[4;33m" "$*"; }
108
+ blue_underlined() { print_in_color "\e[4;34m" "$*"; }
109
+ magenta_underlined() { print_in_color "\e[4;35m" "$*"; }
110
+ cyan_underlined() { print_in_color "\e[4;36m" "$*"; }
111
+
85
112
  user_approval() {
86
113
  local cmd="$1"
87
114
  local actual="$2"
88
115
  local approval_file="$3"
89
116
 
90
- if [[ -v CI || -v GITHUB_ACTIONS ]]; then
117
+ if [[ -v CI || -v GITHUB_ACTIONS ]] && [[ -z "${AUTO_APPROVE+x}" ]]; then
91
118
  fail "$cmd"
92
119
  fi
93
120
 
94
- echo
95
- printf "$approval_string"
96
- response=$(bash -c "read -n 1 key; echo \$key")
97
- printf "\b%.s" $(seq 1 $((${#approval_string} + 1)))
121
+ if [[ -v AUTO_APPROVE ]]; then
122
+ response=a
123
+ else
124
+ echo
125
+ printf "$approval_string"
126
+ response=$(bash -c "read -n 1 key; echo \$key")
127
+ printf "\b%.s" $(seq 1 $((${#approval_string} + 1)))
128
+ fi
129
+
98
130
  if [[ $response =~ [Aa] ]]; then
99
131
  printf "%b\n" "$actual" >"$approval_file"
100
132
  pass "$cmd"
@@ -122,14 +154,15 @@ set -e
122
154
  trap 'onexit' EXIT
123
155
  trap 'onerror' ERR
124
156
 
125
- describe_string="$(blue ▌ describe) %s"
126
- context_string="$(magenta ▌ context) %s"
127
- fail_string=" $(red FAILED) %s"
157
+ describe_string="$(bold ▌ describe) %s"
158
+ context_string="$(bold ▌ context) %s"
159
+ it_string="$(bold ▌ it) %s"
160
+ fail_string=" $(red_bold failed) %s"
128
161
  pass_string=" $(green approved) %s"
129
162
  exit_success_string="$(green ▌ exit) $(bold %s finished successfully)"
130
- exit_failed_string="$(red ▌ exit) $(bold %s finished with errors)"
131
- new_diff_string="────┤ $(yellow new): $(bold %s)) ├────"
132
- changed_diff_string="────┤ $(cyan changed): $(bold %s)) ├────"
163
+ exit_failed_string="$(red_bold ▌ exit) $(bold %s finished with errors)"
164
+ new_diff_string="────┤ $(yellow new): $(bold %s) ├────"
165
+ changed_diff_string="────┤ $(blue changed): $(bold %s) ├────"
133
166
  approval_string="[A]pprove? "
134
167
 
135
168
  if diff --help | grep -- --color >/dev/null 2>&1; then
@@ -29,7 +29,7 @@ module Bashly
29
29
  options['help'] ||= ''
30
30
  end
31
31
 
32
- def method_missing(method_name, *arguments, &block)
32
+ def method_missing(method_name, *arguments, &)
33
33
  key = method_name.to_s
34
34
  respond_to?(method_name) ? options[key] : super
35
35
  end
@@ -48,7 +48,7 @@ module Bashly
48
48
  def usage_string
49
49
  return nil unless enabled?
50
50
 
51
- required? ? label : "[#{label}]"
51
+ required? ? "[--] #{label}" : "[--] [#{label}]"
52
52
  end
53
53
  end
54
54
  end
@@ -101,9 +101,10 @@ module Bashly
101
101
  end
102
102
 
103
103
  # Returns a flat array containing all the commands in this tree.
104
- # This includes self + children + grandchildres + ...
105
- def deep_commands
104
+ # This includes children + grandchildren (recursive), and may include self
105
+ def deep_commands(include_self: false)
106
106
  result = []
107
+ result << self if include_self
107
108
  commands.each do |command|
108
109
  result << command
109
110
  if command.commands.any?
@@ -217,6 +218,16 @@ module Bashly
217
218
  result
218
219
  end
219
220
 
221
+ # Returns true if this command, or any subcommand (deep) as any arg or
222
+ # flag with arg that is defined as unique
223
+ def has_unique_args_or_flags?
224
+ deep_commands(include_self: true).each do |command|
225
+ return true if command.args.count(&:unique).positive? ||
226
+ command.flags.count(&:unique).positive?
227
+ end
228
+ false
229
+ end
230
+
220
231
  # Returns a mode identifier
221
232
  def mode
222
233
  @mode ||= if global_flags? then :global_flags
@@ -6,6 +6,7 @@ module Bashly
6
6
  attr_writer(
7
7
  :commands_dir,
8
8
  :compact_short_flags,
9
+ :conjoined_flag_args,
9
10
  :config_path,
10
11
  :lib_dir,
11
12
  :partials_extension,
@@ -24,6 +25,10 @@ module Bashly
24
25
  @compact_short_flags ||= get :compact_short_flags
25
26
  end
26
27
 
28
+ def conjoined_flag_args
29
+ @conjoined_flag_args ||= get :conjoined_flag_args
30
+ end
31
+
27
32
  def config_path
28
33
  @config_path ||= get(:config_path) % { source_dir: source_dir }
29
34
  end
@@ -1,3 +1,3 @@
1
1
  module Bashly
2
- VERSION = '1.1.8'
2
+ VERSION = '1.1.10'
3
3
  end
@@ -0,0 +1,6 @@
1
+ = view_marker
2
+
3
+ condition = render_options[:index].zero? ? 'if' : 'elif'
4
+ > {{ condition }} [[ -z ${args['{{ name }}']+x} ]]; then
5
+ > args['{{ name }}']=$1
6
+ > shift
@@ -0,0 +1,25 @@
1
+ = view_marker
2
+
3
+ condition = render_options[:index].zero? ? 'if' : 'elif'
4
+
5
+ if render_options[:index] == 0
6
+ > escaped="$(printf '%q' "$1")"
7
+ end
8
+
9
+ > {{ condition }} [[ -z ${args['{{ name }}']+x} ]]; then
10
+ if repeatable
11
+ > args['{{ name }}']="$escaped"
12
+ if unique
13
+ > unique_lookup["{{ name }}:$escaped"]=1
14
+ > elif [[ -z "${unique_lookup["{{ name }}:$escaped"]:-}" ]]; then
15
+ > args['{{ name }}']="${args['{{ name }}']} $escaped"
16
+ > unique_lookup["{{ name }}:$escaped"]=1
17
+ else
18
+ > else
19
+ > args['{{ name }}']="${args['{{ name }}']} $escaped"
20
+ end
21
+
22
+ else
23
+ > args['{{ name }}']="$1"
24
+
25
+ end
@@ -1,30 +1,7 @@
1
1
  = view_marker
2
2
 
3
- > normalize_input() {
4
- > local arg flags
5
- >
6
- > while [[ $# -gt 0 ]]; do
7
- > arg="$1"
8
- > if [[ $arg =~ ^(--[a-zA-Z0-9_\-]+)=(.+)$ ]]; then
9
- > input+=("${BASH_REMATCH[1]}")
10
- > input+=("${BASH_REMATCH[2]}")
11
- > elif [[ $arg =~ ^(-[a-zA-Z0-9])=(.+)$ ]]; then
12
- > input+=("${BASH_REMATCH[1]}")
13
- > input+=("${BASH_REMATCH[2]}")
14
-
15
- if Settings.compact_short_flags
16
- > elif [[ $arg =~ ^-([a-zA-Z0-9][a-zA-Z0-9]+)$ ]]; then
17
- > flags="${BASH_REMATCH[1]}"
18
- > for ((i = 0; i < ${#flags}; i++)); do
19
- > input+=("-${flags:i:1}")
20
- > done
3
+ if Settings.compact_short_flags || Settings.conjoined_flag_args
4
+ = render :normalize_input_function
5
+ else
6
+ = render :normalize_input_simple
21
7
  end
22
-
23
- > else
24
- > input+=("$arg")
25
- > fi
26
- >
27
- > shift
28
- > done
29
- > }
30
- >
@@ -0,0 +1,39 @@
1
+ = view_marker
2
+
3
+ > normalize_input() {
4
+ > local arg flags passthru
5
+ > passthru=false
6
+ >
7
+ > while [[ $# -gt 0 ]]; do
8
+ > arg="$1"
9
+ > if [[ $passthru == true ]]; then
10
+ > input+=("$arg")
11
+
12
+ if Settings.conjoined_flag_args
13
+ > elif [[ $arg =~ ^(--[a-zA-Z0-9_\-]+)=(.+)$ ]]; then
14
+ > input+=("${BASH_REMATCH[1]}")
15
+ > input+=("${BASH_REMATCH[2]}")
16
+ > elif [[ $arg =~ ^(-[a-zA-Z0-9])=(.+)$ ]]; then
17
+ > input+=("${BASH_REMATCH[1]}")
18
+ > input+=("${BASH_REMATCH[2]}")
19
+ end
20
+
21
+ if Settings.compact_short_flags
22
+ > elif [[ $arg =~ ^-([a-zA-Z0-9][a-zA-Z0-9]+)$ ]]; then
23
+ > flags="${BASH_REMATCH[1]}"
24
+ > for ((i = 0; i < ${#flags}; i++)); do
25
+ > input+=("-${flags:i:1}")
26
+ > done
27
+ end
28
+
29
+ > elif [[ "$arg" == "--" ]]; then
30
+ > passthru=true
31
+ > input+=("$arg")
32
+ > else
33
+ > input+=("$arg")
34
+ > fi
35
+ >
36
+ > shift
37
+ > done
38
+ > }
39
+ >
@@ -0,0 +1,6 @@
1
+ = view_marker
2
+
3
+ > normalize_input() {
4
+ > input=("$@")
5
+ > }
6
+ >
@@ -1,13 +1,8 @@
1
1
  = view_marker
2
2
 
3
3
  if args.any?
4
- condition = "if"
5
- args.each do |arg|
6
- > {{ condition }} [[ -z ${args['{{ arg.name }}']+x} ]]; then
7
- > args['{{ arg.name }}']=$1
8
- > shift
9
-
10
- condition = "elif"
4
+ args.each_with_index do |arg, index|
5
+ = arg.render :case, index: index
11
6
  end
12
7
 
13
8
  > else
@@ -1,30 +1,9 @@
1
1
  = view_marker
2
2
 
3
- condition = "if"
4
- args.each do |arg|
5
- > {{ condition }} [[ -z ${args['{{ arg.name }}']+x} ]]; then
6
- if arg.repeatable
7
- > args['{{ arg.name }}']="\"$1\""
8
- > shift
9
- if arg.unique
10
- > elif [[ ! "${args['{{ arg.name }}']}" =~ \"$1\" ]]; then
11
- > args['{{ arg.name }}']="${args[{{ arg.name }}]} \"$1\""
12
- > shift
13
- > else
14
- > shift
15
- else
16
- > else
17
- > args['{{ arg.name }}']="${args[{{ arg.name }}]} \"$1\""
18
- > shift
19
- end
20
-
21
- else
22
- > args['{{ arg.name }}']=$1
23
- > shift
24
-
25
- end
26
- condition = "elif"
3
+ args.each_with_index do |arg, index|
4
+ = arg.render :case_repeatable, index: index
27
5
  end
28
6
 
29
7
  > fi
30
- >
8
+ > shift
9
+ >
@@ -1,13 +1,8 @@
1
1
  = view_marker
2
2
 
3
3
  if args.any?
4
- condition = "if"
5
- args.each do |arg|
6
- > {{ condition }} [[ -z ${args['{{ arg.name }}']+x} ]]; then
7
- > args['{{ arg.name }}']=$1
8
- > shift
9
-
10
- condition = "elif"
4
+ args.each_with_index do |arg, index|
5
+ = arg.render :case, index: index
11
6
  end
12
7
 
13
8
  > else
@@ -6,6 +6,9 @@
6
6
  > declare -a other_args=()
7
7
  > declare -a env_var_names=()
8
8
  > declare -a input=()
9
+ if has_unique_args_or_flags?
10
+ > declare -A unique_lookup=()
11
+ end
9
12
  > normalize_input "$@"
10
13
  > parse_requirements "${input[@]}"
11
14
  if user_file_exist?('before')
@@ -29,5 +32,3 @@ if user_file_exist?('after')
29
32
  end
30
33
 
31
34
  > }
32
-
33
-
@@ -3,15 +3,19 @@
3
3
  > if [[ -n ${2+x} ]]; then
4
4
 
5
5
  if repeatable
6
+ > escaped="$(printf '%q' "$2")"
6
7
  > if [[ -z ${args['{{ name }}']+x} ]]; then
7
- > args['{{ name }}']="\"$2\""
8
+ > args['{{ name }}']="$escaped"
8
9
  if unique
9
- > elif [[ ! "${args['{{ name }}']}" =~ \"$2\" ]]; then
10
+ > elif [[ -z "${unique_lookup["{{ name }}:${escaped}"]:-}" ]]; then
10
11
  else
11
12
  > else
12
13
  end
13
- > args['{{ name }}']="${args['{{ name }}']} \"$2\""
14
+ > args['{{ name }}']="${args['{{ name }}']} $escaped"
14
15
  > fi
16
+ if unique
17
+ > unique_lookup["{{ name }}:${escaped}"]=1
18
+ end
15
19
 
16
20
  else
17
21
  > args['{{ name }}']="$2"
@@ -25,4 +29,4 @@ end
25
29
  > exit 1
26
30
  > fi
27
31
  > ;;
28
- >
32
+ >
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: 1.1.8
4
+ version: 1.1.10
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: 2024-03-02 00:00:00.000000000 Z
11
+ date: 2024-04-01 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: colsole
@@ -226,6 +226,8 @@ files:
226
226
  - lib/bashly/templates/minimal.yml
227
227
  - lib/bashly/version.rb
228
228
  - lib/bashly/views/README.md
229
+ - lib/bashly/views/argument/case.gtx
230
+ - lib/bashly/views/argument/case_repeatable.gtx
229
231
  - lib/bashly/views/argument/usage.gtx
230
232
  - lib/bashly/views/argument/validations.gtx
231
233
  - lib/bashly/views/command/catch_all_filter.gtx
@@ -246,6 +248,8 @@ files:
246
248
  - lib/bashly/views/command/long_usage.gtx
247
249
  - lib/bashly/views/command/master_script.gtx
248
250
  - lib/bashly/views/command/normalize_input.gtx
251
+ - lib/bashly/views/command/normalize_input_function.gtx
252
+ - lib/bashly/views/command/normalize_input_simple.gtx
249
253
  - lib/bashly/views/command/parse_requirements.gtx
250
254
  - lib/bashly/views/command/parse_requirements_case.gtx
251
255
  - lib/bashly/views/command/parse_requirements_case_catch_all.gtx
@@ -296,7 +300,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
296
300
  requirements:
297
301
  - - ">="
298
302
  - !ruby/object:Gem::Version
299
- version: '3.0'
303
+ version: '3.1'
300
304
  required_rubygems_version: !ruby/object:Gem::Requirement
301
305
  requirements:
302
306
  - - ">="