simple_scripting 0.14.1 → 0.16.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ccda16c3d0d547b879118105a4a8e6612dcdeab5a2b4304f5a173ae853640d19
4
- data.tar.gz: f0a608185cf3896b7e7a70028396d35573532ac9b72af592b0f459482fcb7f0b
3
+ metadata.gz: 19d1802651a6d6c7ba48675475a2031da57ccde2ca508977f35c4865897734b1
4
+ data.tar.gz: 81108ceef64d225fd28c2752992aba5f46053ece551e3179364426cd708c9788
5
5
  SHA512:
6
- metadata.gz: f8e954db6fcf8db093451e1b92873e4a5d16ed5c84fc802e2dc006b2d35d10c88a3b74744905210fe389036898bc8b171f2e323ea249db14c80da2612e51e97a
7
- data.tar.gz: 7db2ee472ef700028e861774b8c85dffc050752adab3d63b71a0596cff43fbef00c53f25ab0b148e4be7c2a29fe6fa0cf867c8a4a90257a9d99942e2ee171a2d
6
+ metadata.gz: 64373b69f2c72da9d253d8ad0bfa4660ca8e39de83ba3d3c7140454c71e76a82a80b33f1e5a1e885216c6ac8d573b3e0c373de1316db459e44b0a43f532f9c83
7
+ data.tar.gz: 6dd6a9ef7ceec62cf139f006f66a364ae881325bdfa5e2af61baad56377ce02ad8536eb440a9fc445cc110c977c7d1856655067f75707d946f61a15d901dcff5
@@ -4,21 +4,28 @@ on: [pull_request]
4
4
 
5
5
  jobs:
6
6
  test:
7
+ name: Test Suite
7
8
  runs-on: ubuntu-latest
8
9
  strategy:
9
10
  matrix:
10
- ruby-version: [head, 3.2, 3.1, 3.0, 2.7]
11
+ ruby-version: [head, 4.0, 3.4, 3.3, 3.2]
11
12
  fail-fast: false
12
- continue-on-error: ${{ endsWith(matrix.ruby, 'head') || matrix.ruby == 'debug' }}
13
+ continue-on-error: ${{ endsWith(matrix['ruby-version'], 'head') }}
13
14
  steps:
14
- - uses: actions/checkout@v3
15
- - run: echo "💡 The ${{ github.repository }} repository has been cloned to the runner."
15
+ - uses: actions/checkout@v7
16
16
  - name: Set up Ruby ${{ matrix.ruby-version }}
17
17
  uses: ruby/setup-ruby@v1
18
18
  with:
19
19
  ruby-version: ${{ matrix.ruby-version }}
20
20
  bundler-cache: true
21
- - name: Install dependencies
22
- run: bundle install
23
- - name: Run tests
24
- run: bundle exec rspec
21
+ - run: bundle exec rspec
22
+ # Summary job for branch-protection rules; head failures are ignored via continue-on-error above.
23
+ all-tests:
24
+ name: All (non-head) tests passed
25
+ runs-on: ubuntu-latest
26
+ needs: [test]
27
+ if: always()
28
+ steps:
29
+ - name: Check test suite result
30
+ run: |
31
+ [[ "${{ needs.test.result }}" == "success" ]] || exit 1
data/.simplecov CHANGED
@@ -1,6 +1,5 @@
1
- require 'simplecov'
2
- require 'coveralls'
1
+ SimpleCov.start do
2
+ add_filter '/spec/'
3
3
 
4
- SimpleCov.formatter = Coveralls::SimpleCov::Formatter
5
-
6
- SimpleCov.add_filter '/spec/'
4
+ enable_coverage :branch
5
+ end
data/Gemfile CHANGED
@@ -3,10 +3,10 @@ source 'https://rubygems.org'
3
3
  gemspec
4
4
 
5
5
  group :test do
6
- gem 'coveralls', '~> 0.8.23', require: false
6
+ gem 'simplecov', '~> 0.22', require: false
7
7
  end
8
8
 
9
9
  group :tools do
10
- gem 'byebug', '~> 10.0.2'
10
+ gem 'byebug'
11
11
  gem 'rubocop', '= 0.58.1'
12
12
  end
data/README.md CHANGED
@@ -1,7 +1,5 @@
1
1
  [![Gem Version][GV img]](https://rubygems.org/gems/simple_scripting)
2
- [![Build Status][BS img]](https://travis-ci.org/saveriomiroddi/simple_scripting)
3
- [![Code Climate][CC img]](https://codeclimate.com/github/saveriomiroddi/simple_scripting)
4
- [![Coverage Status][CS img]](https://coveralls.io/r/saveriomiroddi/simple_scripting)
2
+ [![CI][CI img]](https://github.com/64kramsystem/simple_scripting/actions/workflows/ci.yml)
5
3
 
6
4
  # SimpleScripting
7
5
 
@@ -90,7 +88,7 @@ Note that a **recent version of Zsh is required**; the stock Ubuntu 16.04 versio
90
88
 
91
89
  ### More complex use cases
92
90
 
93
- For a description of the more complex use cases, including edge cases and error handling, see the [wiki](https://github.com/saveriomiroddi/simple_scripting/wiki/SimpleScripting::TabCompletion-Guide).
91
+ For a description of the more complex use cases, including edge cases and error handling, see the [wiki](https://github.com/64kramsystem/simple_scripting/wiki/SimpleScripting::TabCompletion-Guide).
94
92
 
95
93
  ## SimpleScripting::Argv
96
94
 
@@ -151,7 +149,7 @@ Commands are also supported (with unlimited depth), by using a hash:
151
149
  }
152
150
  ) || exit
153
151
 
154
- For the guide, see the [wiki page](https://github.com/saveriomiroddi/simple_scripting/wiki/SimpleScripting::Argv-Guide).
152
+ For the guide, see the [wiki page](https://github.com/64kramsystem/simple_scripting/wiki/SimpleScripting::Argv-Guide).
155
153
 
156
154
  ## SimpleScripting::Configuration
157
155
 
@@ -184,15 +182,17 @@ This is the workflow and functionality offered by `Configuration`:
184
182
 
185
183
  configuration.a_group.group_key # 'baz'; also supports #full_path and #decrypted
186
184
 
185
+ ### Local configuration
186
+
187
+ If a file with the same name plus the `.local` suffix exists (e.g. `$HOME/.foo_my_bar.local`), its keys are merged over the main configuration's (per-group). This is useful for keeping the main file shared across machines, with machine-specific overrides in the local one.
188
+
187
189
  ### Encryption note
188
190
 
189
191
  The purpose of encryption in this library is just to avoid displaying passwords in plaintext; it's not considered safe against attacks.
190
192
 
191
193
  ## Help
192
194
 
193
- See the [wiki](https://github.com/saveriomiroddi/simple_scripting/wiki) for additional help.
195
+ See the [wiki](https://github.com/64kramsystem/simple_scripting/wiki) for additional help.
194
196
 
195
197
  [GV img]: https://badge.fury.io/rb/simple_scripting.png
196
- [BS img]: https://travis-ci.org/saveriomiroddi/simple_scripting.svg?branch=master
197
- [CC img]: https://codeclimate.com/github/saveriomiroddi/simple_scripting.png
198
- [CS img]: https://coveralls.io/repos/saveriomiroddi/simple_scripting/badge.png?branch=master
198
+ [CI img]: https://github.com/64kramsystem/simple_scripting/actions/workflows/ci.yml/badge.svg
@@ -21,14 +21,13 @@ module SimpleScripting
21
21
  end
22
22
 
23
23
  class ExitWithCommandsHelpPrinting < Struct.new(:commands_definition)
24
- # Note that :long_help is not used.
25
- def print_help(output, long_help)
24
+ def print_help(output)
26
25
  output.puts "Valid commands:", "", " " + commands_definition.keys.join(', ')
27
26
  end
28
27
  end
29
28
 
30
- class ExitWithArgumentsHelpPrinting < Struct.new(:commands_stack, :args, :parser_opts_copy)
31
- def print_help(output, long_help)
29
+ class ExitWithArgumentsHelpPrinting < Struct.new(:commands_stack, :args, :parser_opts_copy, :long_help)
30
+ def print_help(output)
32
31
  parser_opts_help = parser_opts_copy.to_s
33
32
 
34
33
  if commands_stack.size > 0
@@ -56,19 +55,15 @@ module SimpleScripting
56
55
  output = options.fetch(:output, $stdout)
57
56
  raise_errors = options.fetch(:raise_errors, false)
58
57
 
59
- # WATCH OUT! @long_help can also be set in :decode_command!. See issue #17.
60
- #
61
- @long_help = long_help
62
-
63
58
  exit_data = catch(:exit) do
64
59
  if params_definition.first.is_a?(Hash)
65
- return decode_command!(params_definition, arguments, auto_help)
60
+ return decode_command!(params_definition, arguments, auto_help, long_help)
66
61
  else
67
- return decode_arguments!(params_definition, arguments, auto_help)
62
+ return decode_arguments!(params_definition, arguments, auto_help, long_help)
68
63
  end
69
64
  end
70
65
 
71
- exit_data.print_help(output, @long_help)
66
+ exit_data.print_help(output)
72
67
 
73
68
  nil # to be used with the 'decode(...) || exit' pattern
74
69
  rescue SimpleScripting::Argv::ArgumentError, OptionParser::InvalidOption => error
@@ -79,12 +74,10 @@ module SimpleScripting
79
74
  raise if raise_errors
80
75
 
81
76
  output.puts <<~MESSAGE
82
- Command error!: #{error.message}"
77
+ Command error!: #{error.message}
83
78
 
84
79
  Valid commands: #{error.valid_commands.join(", ")}
85
80
  MESSAGE
86
- ensure
87
- @long_help = nil
88
81
  end
89
82
 
90
83
  private
@@ -120,7 +113,7 @@ module SimpleScripting
120
113
  #
121
114
  # [{"command1"=>["arg1", {:long_help=>"This is the long help."}], "command2"=>["arg2"]}]
122
115
  #
123
- def decode_command!(params_definition, arguments, auto_help, commands_stack=[])
116
+ def decode_command!(params_definition, arguments, auto_help, long_help, commands_stack=[])
124
117
  commands_definition = params_definition.first
125
118
 
126
119
  # Set the `command` variable only after; in the case where we print the help, this variable
@@ -157,23 +150,23 @@ module SimpleScripting
157
150
 
158
151
  # Nested case! Decode recursively
159
152
  #
160
- decode_command!([command_params_definition], arguments, auto_help, commands_stack)
153
+ decode_command!([command_params_definition], arguments, auto_help, long_help, commands_stack)
161
154
  else
162
155
  commands_stack << command
163
156
 
164
157
  if command_params_definition.last.is_a?(Hash)
165
158
  internal_params = command_params_definition.pop # only long_help is here, if present
166
- @long_help = internal_params.delete(:long_help)
159
+ long_help = internal_params.delete(:long_help)
167
160
  end
168
161
 
169
162
  [
170
163
  compose_returned_commands(commands_stack),
171
- decode_arguments!(command_params_definition, arguments, auto_help, commands_stack),
164
+ decode_arguments!(command_params_definition, arguments, auto_help, long_help, commands_stack),
172
165
  ]
173
166
  end
174
167
  end
175
168
 
176
- def decode_arguments!(params_definition, arg_values, auto_help, commands_stack=[])
169
+ def decode_arguments!(params_definition, arg_values, auto_help, long_help, commands_stack=[])
177
170
  result = {}
178
171
  parser_opts_copy = nil # not available outside the block
179
172
  arg_definitions = {} # { 'name' => mandatory? }
@@ -197,7 +190,7 @@ module SimpleScripting
197
190
  #
198
191
  parser_opts.on('-h', '--help', 'Help') do
199
192
  if auto_help
200
- throw :exit, ExitWithArgumentsHelpPrinting.new(commands_stack, arg_definitions, parser_opts_copy)
193
+ throw :exit, ExitWithArgumentsHelpPrinting.new(commands_stack, arg_definitions, parser_opts_copy, long_help)
201
194
  else
202
195
  # Needs to be better handled. When help is required, generally, it trumps the
203
196
  # correctness of the rest of the options/arguments.
@@ -212,9 +205,9 @@ module SimpleScripting
212
205
  arg_definitions.each do |arg_name, arg_is_mandatory|
213
206
  if arg_name.to_s.start_with?('*')
214
207
  arg_name = arg_name.to_s[1..-1].to_sym
215
- process_varargs!(arg_values, result, commands_stack, arg_name, arg_is_mandatory)
208
+ process_varargs!(arg_values, result, arg_name, arg_is_mandatory)
216
209
  else
217
- process_regular_argument!(arg_values, result, commands_stack, arg_name, arg_is_mandatory)
210
+ process_regular_argument!(arg_values, result, arg_name, arg_is_mandatory)
218
211
  end
219
212
  end
220
213
 
@@ -260,14 +253,14 @@ module SimpleScripting
260
253
  end
261
254
  end
262
255
 
263
- def process_varargs!(arg_values, result, commands_stack, arg_name, arg_is_mandatory)
256
+ def process_varargs!(arg_values, result, arg_name, arg_is_mandatory)
264
257
  raise ArgumentError.new("Missing mandatory argument(s)") if arg_is_mandatory && arg_values.empty?
265
258
 
266
259
  result[arg_name] = arg_values.dup
267
260
  arg_values.clear
268
261
  end
269
262
 
270
- def process_regular_argument!(arg_values, result, commands_stack, arg_name, arg_is_mandatory)
263
+ def process_regular_argument!(arg_values, result, arg_name, arg_is_mandatory)
271
264
  if arg_values.empty?
272
265
  if arg_is_mandatory
273
266
  raise ArgumentError.new("Missing mandatory argument(s)")
@@ -16,11 +16,21 @@ module SimpleScripting
16
16
  def load(config_file: default_config_file, passwords_key: nil, required: [])
17
17
  create_empty_file(config_file) if !File.exist?(config_file)
18
18
 
19
- configuration = ParseConfig.new(config_file)
19
+ params = ParseConfig.new(config_file).params
20
20
 
21
- enforce_required_keys(configuration.params, required)
21
+ local_config_file = "#{config_file}.local"
22
22
 
23
- convert_to_cool_format(OpenStruct.new, configuration.params, passwords_key)
23
+ if File.exist?(local_config_file)
24
+ local_params = ParseConfig.new(local_config_file).params
25
+
26
+ params = params.merge(local_params) do |_, value, local_value|
27
+ value.is_a?(Hash) && local_value.is_a?(Hash) ? value.merge(local_value) : local_value
28
+ end
29
+ end
30
+
31
+ enforce_required_keys(params, required)
32
+
33
+ convert_to_cool_format(OpenStruct.new, params, passwords_key)
24
34
  end
25
35
 
26
36
  private
@@ -8,7 +8,7 @@ module SimpleScripting
8
8
 
9
9
  class TabCompletion
10
10
 
11
- class CommandlineProcessor < Struct.new(:processed_argv, :cursor_marker, :switches_definition)
11
+ class CommandlineProcessor < Struct.new(:processed_argv, :cursor_marker, :switches_definition, :escaped_dash)
12
12
 
13
13
  # Arbitrary; can be anything (except an empty string).
14
14
  BASE_CURSOR_MARKER = "<tab>"
@@ -27,7 +27,12 @@ module SimpleScripting
27
27
  # Remove the executable.
28
28
  processed_argv = Shellwords.split(commandline_with_marker)[1..-1]
29
29
 
30
- return new(processed_argv, cursor_marker, switches_definition)
30
+ # Shellwords strips the backslash, losing the information that the word is a value, not
31
+ # an option.
32
+ #
33
+ escaped_dash = commandline_with_marker.match?(/(?:\A|\s)\\-\S*#{Regexp.escape(cursor_marker)}/)
34
+
35
+ return new(processed_argv, cursor_marker, switches_definition, escaped_dash)
31
36
  end
32
37
  end
33
38
  end
@@ -56,15 +61,23 @@ module SimpleScripting
56
61
  parsed_pairs = parse_argv || raise("Parsing error")
57
62
 
58
63
  key, value = parsed_pairs.detect do |_, value|
59
- !boolean?(value) && value.include?(cursor_marker)
64
+ if value.is_a?(Array)
65
+ value.any? { |entry| entry.include?(cursor_marker) }
66
+ else
67
+ !boolean?(value) && value.include?(cursor_marker)
68
+ end
60
69
  end
61
70
 
62
71
  # Impossible case, unless there is a programmatic error.
63
72
  #
64
73
  key || raise("Guru meditation! (#{self.class}##{__method__}:#{__LINE__})")
65
74
 
75
+ value = value.detect { |entry| entry.include?(cursor_marker) } if value.is_a?(Array)
76
+
66
77
  value_prefix, value_suffix = value.split(cursor_marker)
67
78
 
79
+ value_prefix = "-#{value_prefix}" if escaped_dash
80
+
68
81
  parsed_pairs.delete(key)
69
82
 
70
83
  [key, value_prefix || "", value_suffix || "", parsed_pairs]
@@ -98,7 +111,10 @@ module SimpleScripting
98
111
  adapted_switches_definition[i] = "[#{definition}]" if definition.is_a?(String) && !definition.start_with?('[')
99
112
  end
100
113
 
101
- SimpleScripting::Argv.decode(*adapted_switches_definition, arguments: processed_argv.dup, auto_help: false)
114
+ adapted_argv = processed_argv.dup
115
+ adapted_argv[marked_word_position] = adapted_argv[marked_word_position].delete_prefix("-") if escaped_dash
116
+
117
+ SimpleScripting::Argv.decode(*adapted_switches_definition, arguments: adapted_argv, auto_help: false)
102
118
  rescue Argv::InvalidCommand, Argv::ArgumentError, OptionParser::InvalidOption
103
119
  # OptionParser::InvalidOption: see case "-O<tab>" in test suite.
104
120
 
@@ -29,7 +29,7 @@ module SimpleScripting
29
29
  commandline_processor = CommandlineProcessor.process_commandline(source_commandline, cursor_position, @switches_definition)
30
30
 
31
31
  if commandline_processor.completing_an_option?
32
- complete_option(commandline_processor, execution_target)
32
+ complete_option(commandline_processor)
33
33
  elsif commandline_processor.parsing_error?
34
34
  return
35
35
  else # completing_a_value?
@@ -43,7 +43,7 @@ module SimpleScripting
43
43
  # Completion!
44
44
  #############################################
45
45
 
46
- def complete_option(commandline_processor, execution_target)
46
+ def complete_option(commandline_processor)
47
47
  all_switches = @switches_definition.select { |definition| definition.is_a?(Array) }.map { |definition| definition[1][/^--\S+/] }
48
48
 
49
49
  matching_switches = all_switches.select { |switch| switch.start_with?(commandline_processor.completing_word_prefix) }
@@ -1,5 +1,5 @@
1
1
  module SimpleScripting
2
2
 
3
- VERSION = "0.14.1"
3
+ VERSION = "0.16.0"
4
4
 
5
5
  end
@@ -8,17 +8,18 @@ Gem::Specification.new do |s|
8
8
  s.name = "simple_scripting"
9
9
  s.version = SimpleScripting::VERSION
10
10
  s.platform = Gem::Platform::RUBY
11
- s.required_ruby_version = '>= 2.7.0'
11
+ s.required_ruby_version = '>= 3.2.0'
12
12
  s.authors = ["Saverio Miroddi"]
13
- s.date = "2024-12-25"
13
+ s.date = "2026-07-07"
14
14
  s.email = ["saverio.pub2@gmail.com"]
15
- s.homepage = "https://github.com/saveriomiroddi/simple_scripting"
15
+ s.homepage = "https://github.com/64kramsystem/simple_scripting"
16
16
  s.summary = "Library for simplifying some typical scripting functionalities."
17
17
  s.description = "Simplifies options parsing and configuration loading."
18
18
  s.license = "GPL-3.0"
19
19
 
20
20
  s.add_runtime_dependency "parseconfig", "~> 1.0"
21
21
  s.add_runtime_dependency "ostruct", "~> 0.6.1"
22
+ s.add_runtime_dependency "base64", "~> 0.3"
22
23
 
23
24
  s.add_development_dependency "rake", "~> 12.0"
24
25
  s.add_development_dependency "rspec", "~> 3.7"
@@ -45,6 +45,18 @@ module SimpleScripting
45
45
  expect(return_value).to be(nil)
46
46
  end
47
47
 
48
+ it 'should print help without arguments usage and long help, when they are not defined' do
49
+ described_class.decode(['-a'], output: output_buffer, arguments: ['-h'])
50
+
51
+ expected_output = <<~OUTPUT
52
+ Usage: rspec [options]
53
+ -a
54
+ -h, --help Help
55
+ OUTPUT
56
+
57
+ expect(output_buffer.string).to eql(expected_output)
58
+ end
59
+
48
60
  it 'should not interpret the --help argument, and not print the help, on auto_help: false' do
49
61
  decoder_params.last.merge!(
50
62
  arguments: ['--help', 'm_arg'],
@@ -359,6 +371,19 @@ module SimpleScripting
359
371
  valid_commands: ["command1", "command2"],
360
372
  ))
361
373
  end
374
+
375
+ it "should print the invalid-command error and valid commands by default, instead of raising" do
376
+ decoder_params[:arguments] = ['pizza']
377
+
378
+ actual_result = described_class.decode(decoder_params)
379
+
380
+ expect(actual_result).to be(nil)
381
+ expect(output_buffer.string).to eql(<<~OUTPUT)
382
+ Command error!: Invalid command: pizza
383
+
384
+ Valid commands: command1, command2
385
+ OUTPUT
386
+ end
362
387
  end # context "error handling"
363
388
 
364
389
  context "help" do
@@ -509,5 +534,26 @@ module SimpleScripting
509
534
  }.to raise_error(Argv::ArgumentError, "Too many arguments")
510
535
  end
511
536
  end # describe 'No definitions given'
537
+
538
+ describe 'Definition/options handling' do
539
+ it "should work when no options hash is passed, reading ARGV" do
540
+ original_argv = ARGV.dup
541
+ ARGV.replace(['m_arg'])
542
+
543
+ begin
544
+ actual_result = described_class.decode('mandatory')
545
+
546
+ expect(actual_result).to eql(mandatory: 'm_arg')
547
+ ensure
548
+ ARGV.replace(original_argv)
549
+ end
550
+ end
551
+
552
+ it "should raise on an unrecognized param definition" do
553
+ expect {
554
+ described_class.decode(123, arguments: [], output: output_buffer)
555
+ }.to raise_error(RuntimeError, "Unrecognized value: 123")
556
+ end
557
+ end # describe 'Definition/options handling'
512
558
  end # describe Argv
513
559
  end # module SimpleScripting
@@ -51,6 +51,42 @@ g2_key=bang
51
51
  end
52
52
  end
53
53
 
54
+ it "should merge the .local configuration file, when present, over the main one" do
55
+ with_tempfile(configuration_text) do |config_file|
56
+ local_configuration_text = "
57
+ relpath_key=local_foo
58
+
59
+ [group1]
60
+ g_key=local_baz
61
+ "
62
+
63
+ File.write("#{config_file}.local", local_configuration_text)
64
+
65
+ begin
66
+ configuration = described_class.load(config_file: config_file)
67
+
68
+ expect(configuration.relpath_key).to eql('local_foo')
69
+ expect(configuration.group1.g_key).to eql('local_baz')
70
+
71
+ # Keys not overridden are preserved.
72
+ expect(configuration.abspath_key).to eql('/tmp/bar')
73
+ expect(configuration.group2.g2_key).to eql('bang')
74
+ ensure
75
+ File.delete("#{config_file}.local")
76
+ end
77
+ end
78
+ end
79
+
80
+ it "should raise an error when decrypting a value without the passwords key" do
81
+ with_tempfile(configuration_text) do |config_file|
82
+ configuration = described_class.load(config_file: config_file)
83
+
84
+ expect {
85
+ configuration.encr_key.decrypted
86
+ }.to raise_error("Encryption key not provided!")
87
+ end
88
+ end
89
+
54
90
  it "should raise an error when required keys are missing" do
55
91
  with_tempfile(configuration_text) do |config_file|
56
92
  expect {
@@ -70,4 +106,22 @@ g2_key=bang
70
106
  File.delete(temp_config_file)
71
107
  end
72
108
  end
109
+
110
+ it "should derive the default config file from the program name, when none is given" do
111
+ Dir.mktmpdir do |home|
112
+ original_home = ENV['HOME']
113
+ original_prog = $PROGRAM_NAME
114
+ ENV['HOME'] = home
115
+ $PROGRAM_NAME = 'my_test_prog.rb'
116
+
117
+ begin
118
+ described_class.load
119
+
120
+ expect(File).to exist(File.join(home, '.my_test_prog'))
121
+ ensure
122
+ ENV['HOME'] = original_home
123
+ $PROGRAM_NAME = original_prog
124
+ end
125
+ end
126
+ end
73
127
  end # describe SimpleScripting::Configuration
@@ -13,6 +13,7 @@ describe SimpleScripting::TabCompletion do
13
13
  [
14
14
  ["-o", "--opt1 ARG"],
15
15
  ["-O", "--opt2"],
16
+ ["-m", "--multi V1,V2"],
16
17
  "arg1", # this and the following are internally converted to optional, as
17
18
  "arg2", # according to the Argv spec, without brackets they are mandatory.
18
19
  ]
@@ -33,6 +34,10 @@ describe SimpleScripting::TabCompletion do
33
34
  ['arg1v1', 'arg1v2', '_arg1v3', ' _argv1spc'].select { |entry| entry =~ /^#{prefix}#{suffix}/ }
34
35
  end
35
36
 
37
+ def multi(prefix, suffix, context)
38
+ %w(multiv1 _multiv2).select { |entry| entry =~ /^#{prefix}#{suffix}/ }
39
+ end
40
+
36
41
  def arg2(prefix, suffix, context)
37
42
  # A value starting with minus is valid.
38
43
  #
@@ -57,11 +62,11 @@ describe SimpleScripting::TabCompletion do
57
62
  "a -- --<tab>" => %w(--arg2v3),
58
63
  "-- --aaa <tab>" => %w(arg2v1 arg2v2 --arg2v3),
59
64
 
60
- "--<tab>" => %w(--opt1 --opt2),
61
- "--<tab> a" => %w(--opt1 --opt2),
62
- "--<tab> -- a" => %w(--opt1 --opt2),
63
- "--<tab> -- b" => %w(--opt1 --opt2),
64
- "--<tab> --xyz" => %w(--opt1 --opt2),
65
+ "--<tab>" => %w(--opt1 --opt2 --multi),
66
+ "--<tab> a" => %w(--opt1 --opt2 --multi),
67
+ "--<tab> -- a" => %w(--opt1 --opt2 --multi),
68
+ "--<tab> -- b" => %w(--opt1 --opt2 --multi),
69
+ "--<tab> --xyz" => %w(--opt1 --opt2 --multi),
65
70
  "--opt1 <tab> a" => %w(opt1v1 _opt1v2),
66
71
  "--opt1 o<tab> a" => %w(opt1v1),
67
72
 
@@ -96,17 +101,25 @@ describe SimpleScripting::TabCompletion do
96
101
 
97
102
  context "escaped cases" do
98
103
  ESCAPED_CASES = {
99
- "\ <tab>" => [" _argv1spc"],
104
+ '\ <tab>' => [" _argv1spc"],
100
105
  '\-<tab>' => %w(), # this is the result of typing `command "\-<tab>`
101
106
  'a \-<tab>' => %w(--arg2v3),
102
107
  }
103
108
 
104
- ESCAPED_CASES.each do |symbolic_commandline_options, _|
105
- it "should output the entries for #{symbolic_commandline_options.inspect}"
109
+ ESCAPED_CASES.each do |symbolic_commandline_options, expected_entries|
110
+ it "should output the entries for #{symbolic_commandline_options.inspect}" do
111
+ expect(symbolic_commandline_options).to complete_with(expected_entries)
112
+ end
106
113
  end
107
114
  end # context "escaped cases"
108
115
 
109
- it "should support multiple values for an option"
116
+ it "should support multiple values for an option" do
117
+ expect("--multi multiv1,m<tab>").to complete_with(%w(multiv1))
118
+ end
119
+
120
+ it "should use the next cursor marker when the commandline contains the base one" do
121
+ expect("a<tab0> <tab>").to complete_with(%w(arg2v1 arg2v2 --arg2v3))
122
+ end
110
123
 
111
124
  it "should keep parsing also when --help is passed" do
112
125
  expect("--help a<tab>").to complete_with(%w(arg1v1 arg1v2))
@@ -114,14 +127,16 @@ describe SimpleScripting::TabCompletion do
114
127
  end # context "with a correct configuration"
115
128
 
116
129
  context "with an incorrect configuration" do
117
- INCORRECT_CASES = [
118
- "a b <tab>", # too many args
119
- "-O<tab>", # no values for this option
120
- ]
130
+ INCORRECT_CASES = {
131
+ "a b <tab>" => "Command error!: Too many arguments\n", # too many args
132
+ "-O<tab>" => "Command error!: invalid option: -<tab0>\n", # no values for this option
133
+ }
121
134
 
122
- INCORRECT_CASES.each do |symbolic_commandline_options|
135
+ INCORRECT_CASES.each do |symbolic_commandline_options, expected_error_message|
123
136
  it "should not output any entries for #{symbolic_commandline_options.inspect}" do
124
- expect(symbolic_commandline_options).to not_complete
137
+ expect {
138
+ expect(symbolic_commandline_options).to not_complete
139
+ }.to output(expected_error_message).to_stdout
125
140
  end
126
141
  end
127
142
  end # context "with an incorrect configuration"
data/spec/spec_helper.rb CHANGED
@@ -1,7 +1,6 @@
1
1
  # Coverage setup
2
2
 
3
- require 'coveralls'
4
- Coveralls.wear!
3
+ require 'simplecov'
5
4
 
6
5
  # This file was generated by the `rspec --init` command. Conventionally, all
7
6
  # specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`.
metadata CHANGED
@@ -1,14 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: simple_scripting
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.14.1
4
+ version: 0.16.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Saverio Miroddi
8
- autorequire:
9
8
  bindir: bin
10
9
  cert_chain: []
11
- date: 2024-12-25 00:00:00.000000000 Z
10
+ date: 2026-07-07 00:00:00.000000000 Z
12
11
  dependencies:
13
12
  - !ruby/object:Gem::Dependency
14
13
  name: parseconfig
@@ -38,6 +37,20 @@ dependencies:
38
37
  - - "~>"
39
38
  - !ruby/object:Gem::Version
40
39
  version: 0.6.1
40
+ - !ruby/object:Gem::Dependency
41
+ name: base64
42
+ requirement: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - "~>"
45
+ - !ruby/object:Gem::Version
46
+ version: '0.3'
47
+ type: :runtime
48
+ prerelease: false
49
+ version_requirements: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - "~>"
52
+ - !ruby/object:Gem::Version
53
+ version: '0.3'
41
54
  - !ruby/object:Gem::Dependency
42
55
  name: rake
43
56
  requirement: !ruby/object:Gem::Requirement
@@ -94,11 +107,10 @@ files:
94
107
  - spec/simple_scripting/configuration_spec.rb
95
108
  - spec/simple_scripting/tab_completion_spec.rb
96
109
  - spec/spec_helper.rb
97
- homepage: https://github.com/saveriomiroddi/simple_scripting
110
+ homepage: https://github.com/64kramsystem/simple_scripting
98
111
  licenses:
99
112
  - GPL-3.0
100
113
  metadata: {}
101
- post_install_message:
102
114
  rdoc_options: []
103
115
  require_paths:
104
116
  - lib
@@ -106,15 +118,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
106
118
  requirements:
107
119
  - - ">="
108
120
  - !ruby/object:Gem::Version
109
- version: 2.7.0
121
+ version: 3.2.0
110
122
  required_rubygems_version: !ruby/object:Gem::Requirement
111
123
  requirements:
112
124
  - - ">="
113
125
  - !ruby/object:Gem::Version
114
126
  version: '0'
115
127
  requirements: []
116
- rubygems_version: 3.5.22
117
- signing_key:
128
+ rubygems_version: 4.0.10
118
129
  specification_version: 4
119
130
  summary: Library for simplifying some typical scripting functionalities.
120
131
  test_files: