bashly 1.1.7 → 1.1.9

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: 0a7e677e8b16a4711ece6638e16e585b1bf74fca12cc6c3e9ea4810c209d4693
4
- data.tar.gz: 630667bb3c3d36055a0160f05719a9561d8ec7cdbd02af80fd9c2a7a5edabd17
3
+ metadata.gz: 57e204b6791653f135c34bdf18a2b882d180087bbec57044db09540e342d81f0
4
+ data.tar.gz: ef1bc1015e325d227b3bc32d537a51fc369ea7833972f6f9a15e592d79ddc131
5
5
  SHA512:
6
- metadata.gz: 7dd08f97c66d81979c68b43d356b650ec50d30b0c2d0d8663388f0fec56f881bd5b140a55719143e00d155b2f0ae321d6207bf63c7e248d324489cf7dc3b7cee
7
- data.tar.gz: 754c2b4aaa9e289b04e83e10b861933b26ef3168620315a7a9655c8dc991c095d0e42037a43d379b2d1f86d6f4eb5a6e3d6ea70009642d873e7dafb1627cfffa
6
+ metadata.gz: 4947a104aa165d0e216a53a07908b72c290ff8e905e96acde044025bccefbab783ed5c87f3c217b674bdfb57db21c5d149827a9d8383065fef78eaac701826c0
7
+ data.tar.gz: 99eb5313b587980c540f22596cc8e99794cf7ed24cb0f62e06c4179a64399e80099c6cbc7d2803fe96827cdbf5734bbb9fc7364533979da45cd44973d0511748
@@ -0,0 +1,45 @@
1
+ module Bashly
2
+ # A helper class, used by the `Array#indent` extension.
3
+ # It will return the array of strings with all strings prefixed by `indentation`
4
+ # unless the line is within a heredoc block.
5
+ class IndentationHelper
6
+ attr_reader :marker, :indentation
7
+
8
+ def initialize(indentation)
9
+ @indentation = indentation
10
+ @marker = nil
11
+ end
12
+
13
+ def indent(line)
14
+ if inside_heredoc?
15
+ reset_marker if heredoc_closed?(line)
16
+ line
17
+ else
18
+ set_heredoc_state(line)
19
+ "#{indentation}#{line}"
20
+ end
21
+ end
22
+
23
+ private
24
+
25
+ def reset_marker
26
+ @marker = nil
27
+ end
28
+
29
+ def inside_heredoc?
30
+ !!marker
31
+ end
32
+
33
+ def set_heredoc_state(line)
34
+ @marker = extract_heredoc_marker(line)
35
+ end
36
+
37
+ def extract_heredoc_marker(line)
38
+ line =~ /<<-?\s*(\w+)/ ? $1 : nil
39
+ end
40
+
41
+ def heredoc_closed?(line)
42
+ inside_heredoc? && /^#{marker}\n?$/.match?(line)
43
+ end
44
+ end
45
+ end
@@ -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,9 +1,13 @@
1
+ require 'bashly/concerns/indentation_helper'
2
+
1
3
  class Array
2
4
  def indent(offset)
3
5
  return self unless offset.positive?
4
6
 
5
7
  indentation = ' ' * offset
6
- map { |line| "#{indentation}#{line}" }
8
+ indenter = Bashly::IndentationHelper.new indentation
9
+
10
+ map { |line| indenter.indent line }
7
11
  end
8
12
 
9
13
  def nonuniq
@@ -1,7 +1,10 @@
1
- # approvals.bash v0.4.2
1
+ # approvals.bash v0.5.1
2
2
  #
3
3
  # Interactive approval testing for Bash.
4
4
  # https://github.com/DannyBen/approvals.bash
5
+ #
6
+ # shellcheck disable=SC2059
7
+ # Disabling SC2059 (quoted format string) because we use dynamic format strings
5
8
  approve() {
6
9
  local expected approval approval_file actual cmd
7
10
  approvals_dir=${APPROVALS_DIR:=approvals}
@@ -21,9 +24,9 @@ approve() {
21
24
  if [[ -f "$approval_file" ]]; then
22
25
  expected=$(cat "$approval_file")
23
26
  else
24
- echo "--- [$(blue "new: $cmd")] ---"
27
+ printf -- "$new_diff_string\n" "$cmd"
25
28
  printf "%b\n" "$actual"
26
- echo "--- [$(blue "new: $cmd")] ---"
29
+ printf -- "$new_diff_string\n" "$cmd"
27
30
  expected="$actual"
28
31
  user_approval "$cmd" "$actual" "$approval_file"
29
32
  return
@@ -32,9 +35,9 @@ approve() {
32
35
  if [[ "$(printf "%b" "$actual")" = "$(printf "%b" "$expected")" ]]; then
33
36
  pass "$cmd"
34
37
  else
35
- echo "--- [$(blue "diff: $cmd")] ---"
38
+ printf -- "$changed_diff_string\n" "$cmd"
36
39
  $diff_cmd <(printf "%b" "$expected\n") <(printf "%b" "$actual\n") | tail -n +4
37
- echo "--- [$(blue "diff: $cmd")] ---"
40
+ printf -- "$changed_diff_string\n" "$cmd"
38
41
  user_approval "$cmd" "$actual" "$approval_file"
39
42
  fi
40
43
  }
@@ -44,22 +47,24 @@ allow_diff() {
44
47
  }
45
48
 
46
49
  describe() {
47
- echo
48
- blue "= $*"
50
+ printf "$describe_string\n" "$*"
49
51
  }
50
52
 
51
53
  context() {
52
- echo
53
- magenta "= $*"
54
+ printf "$context_string\n" "$*"
55
+ }
56
+
57
+ it() {
58
+ printf "$it_string\n" "$*"
54
59
  }
55
60
 
56
61
  fail() {
57
- red " FAILED: $*"
62
+ printf "$fail_string\n" "$*"
58
63
  exit 1
59
64
  }
60
65
 
61
66
  pass() {
62
- green " approved: $*"
67
+ printf "$pass_string\n" "$*"
63
68
  return 0
64
69
  }
65
70
 
@@ -67,31 +72,61 @@ expect_exit_code() {
67
72
  if [[ $last_exit_code == "$1" ]]; then
68
73
  pass "exit $last_exit_code"
69
74
  else
70
- fail "Expected exit code $1, got $last_exit_code"
75
+ fail "expected exit code $1, got $last_exit_code"
71
76
  fi
72
77
  }
73
78
 
74
- red() { printf "\e[31m%b\e[0m\n" "$*"; }
75
- green() { printf "\e[32m%b\e[0m\n" "$*"; }
76
- blue() { printf "\e[34m%b\e[0m\n" "$*"; }
77
- magenta() { printf "\e[35m%b\e[0m\n" "$*"; }
78
- cyan() { printf "\e[36m%b\e[0m\n" "$*"; }
79
-
80
79
  # Private
81
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
+
82
112
  user_approval() {
83
113
  local cmd="$1"
84
114
  local actual="$2"
85
115
  local approval_file="$3"
86
116
 
87
- if [[ -v CI || -v GITHUB_ACTIONS ]]; then
117
+ if [[ -v CI || -v GITHUB_ACTIONS ]] && [[ -z "${AUTO_APPROVE+x}" ]]; then
88
118
  fail "$cmd"
89
119
  fi
90
120
 
91
- echo
92
- printf "[A]pprove? \n"
93
- response=$(bash -c "read -n 1 key; echo \$key")
94
- printf "\r"
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
+
95
130
  if [[ $response =~ [Aa] ]]; then
96
131
  printf "%b\n" "$actual" >"$approval_file"
97
132
  pass "$cmd"
@@ -103,21 +138,33 @@ user_approval() {
103
138
  onexit() {
104
139
  exitcode=$?
105
140
  if [[ "$exitcode" == 0 ]]; then
106
- green "\nFinished successfully"
141
+ printf "$exit_success_string\n" "$0"
107
142
  else
108
- red "\nFinished with failures"
143
+ printf "$exit_failed_string\n" "$0"
109
144
  fi
145
+ echo
110
146
  exit $exitcode
111
147
  }
112
148
 
113
149
  onerror() {
114
- fail "Caller: $(caller)"
150
+ fail "caller: $(caller)"
115
151
  }
116
152
 
117
153
  set -e
118
154
  trap 'onexit' EXIT
119
155
  trap 'onerror' ERR
120
156
 
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"
161
+ pass_string=" $(green approved) %s"
162
+ exit_success_string="$(green ▌ exit) $(bold %s finished successfully)"
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) ├────"
166
+ approval_string="[A]pprove? "
167
+
121
168
  if diff --help | grep -- --color >/dev/null 2>&1; then
122
169
  diff_cmd="diff --unified --color=always"
123
170
  else
@@ -1,41 +1,43 @@
1
- module ComposeRefinements
2
- refine Hash do
3
- def compose(keyword = 'import')
4
- result = {}
5
- each do |k, v|
6
- if k.to_s == keyword
7
- sub = safe_load_yaml(v).compose keyword
8
- if sub.is_a? Array
9
- result = sub
1
+ module Bashly
2
+ module ComposeRefinements
3
+ refine Hash do
4
+ def compose(keyword = 'import')
5
+ result = {}
6
+ each do |k, v|
7
+ if k.to_s == keyword
8
+ sub = safe_load_yaml(v).compose keyword
9
+ if sub.is_a? Array
10
+ result = sub
11
+ else
12
+ result.merge! sub
13
+ end
14
+ elsif v.respond_to? :compose
15
+ result[k] = v.compose keyword
10
16
  else
11
- result.merge! sub
17
+ result[k] = v
12
18
  end
13
- elsif v.respond_to? :compose
14
- result[k] = v.compose keyword
15
- else
16
- result[k] = v
17
19
  end
20
+ result
18
21
  end
19
- result
20
- end
21
22
 
22
- def safe_load_yaml(path)
23
- loaded = YAML.load_erb_file path
24
- return loaded if loaded.is_a?(Array) || loaded.is_a?(Hash)
23
+ def safe_load_yaml(path)
24
+ loaded = YAML.load_erb_file path
25
+ return loaded if loaded.is_a?(Array) || loaded.is_a?(Hash)
25
26
 
26
- raise Bashly::ConfigurationError, "Cannot find a valid YAML in g`#{path}`"
27
- rescue Errno::ENOENT
28
- raise Bashly::ConfigurationError, "Cannot find import file g`#{path}`"
27
+ raise Bashly::ConfigurationError, "Cannot find a valid YAML in g`#{path}`"
28
+ rescue Errno::ENOENT
29
+ raise Bashly::ConfigurationError, "Cannot find import file g`#{path}`"
30
+ end
29
31
  end
30
- end
31
32
 
32
- refine Array do
33
- def compose(keyword = 'import')
34
- map do |x|
35
- if x.respond_to? :compose
36
- x.compose keyword
37
- else
38
- x
33
+ refine Array do
34
+ def compose(keyword = 'import')
35
+ map do |x|
36
+ if x.respond_to? :compose
37
+ x.compose keyword
38
+ else
39
+ x
40
+ end
39
41
  end
40
42
  end
41
43
  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
@@ -332,7 +343,7 @@ module Bashly
332
343
  args.select(&:allowed)
333
344
  end
334
345
 
335
- # Returns an array of all the environemnt_variables with a whitelist arg
346
+ # Returns an array of all the environment_variables with a whitelist arg
336
347
  def whitelisted_environment_variables
337
348
  environment_variables.select(&:allowed)
338
349
  end
@@ -1,3 +1,3 @@
1
1
  module Bashly
2
- VERSION = '1.1.7'
2
+ VERSION = '1.1.9'
3
3
  end
@@ -1,4 +1,4 @@
1
- # View Tempaltes
1
+ # View Templates
2
2
 
3
3
  These are [GTX](https://github.com/dannyben/gtx) templates.
4
4
 
@@ -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
@@ -27,3 +27,4 @@ end
27
27
  > shift
28
28
  > done
29
29
  > }
30
+ >
@@ -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.7
4
+ version: 1.1.9
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-02-17 00:00:00.000000000 Z
11
+ date: 2024-03-20 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: colsole
@@ -161,6 +161,7 @@ files:
161
161
  - lib/bashly/completions/completely.yaml.gtx
162
162
  - lib/bashly/concerns/asset_helper.rb
163
163
  - lib/bashly/concerns/completions.rb
164
+ - lib/bashly/concerns/indentation_helper.rb
164
165
  - lib/bashly/concerns/renderable.rb
165
166
  - lib/bashly/concerns/validation_helpers.rb
166
167
  - lib/bashly/config.rb
@@ -225,6 +226,8 @@ files:
225
226
  - lib/bashly/templates/minimal.yml
226
227
  - lib/bashly/version.rb
227
228
  - lib/bashly/views/README.md
229
+ - lib/bashly/views/argument/case.gtx
230
+ - lib/bashly/views/argument/case_repeatable.gtx
228
231
  - lib/bashly/views/argument/usage.gtx
229
232
  - lib/bashly/views/argument/validations.gtx
230
233
  - lib/bashly/views/command/catch_all_filter.gtx