simple_scripting 0.15.0 → 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: 0a10422c52637cb7df7e9bacac1c8268c642f3632aaabc0db20e756178f353bb
4
- data.tar.gz: 6e7a8bb568dda330e5c134465adcb2e460217181c1bf13be9d061b381cdf6657
3
+ metadata.gz: 19d1802651a6d6c7ba48675475a2031da57ccde2ca508977f35c4865897734b1
4
+ data.tar.gz: 81108ceef64d225fd28c2752992aba5f46053ece551e3179364426cd708c9788
5
5
  SHA512:
6
- metadata.gz: 7f4443a5315920f06f596a79fd2a784cc3839b7d5746e8d4ba96ca6c12d9991c23bc4b5b9fd82979d90087740dedf71afe2ec0cb10ec150b9d45c565383db57b
7
- data.tar.gz: 43ea35ead6a6720660e3ab199cec87264c9ce2d6db4646be91a33db47dc6a806a96f12061afe06b98e9cf3fbc9f15880447cf2bf8b2c9250d66b3f7b64a313b0
6
+ metadata.gz: 64373b69f2c72da9d253d8ad0bfa4660ca8e39de83ba3d3c7140454c71e76a82a80b33f1e5a1e885216c6ac8d573b3e0c373de1316db459e44b0a43f532f9c83
7
+ data.tar.gz: 6dd6a9ef7ceec62cf139f006f66a364ae881325bdfa5e2af61baad56377ce02ad8536eb440a9fc445cc110c977c7d1856655067f75707d946f61a15d901dcff5
data/.simplecov CHANGED
@@ -1,3 +1,5 @@
1
1
  SimpleCov.start do
2
2
  add_filter '/spec/'
3
+
4
+ enable_coverage :branch
3
5
  end
data/README.md CHANGED
@@ -182,6 +182,10 @@ This is the workflow and functionality offered by `Configuration`:
182
182
 
183
183
  configuration.a_group.group_key # 'baz'; also supports #full_path and #decrypted
184
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
+
185
189
  ### Encryption note
186
190
 
187
191
  The purpose of encryption in this library is just to avoid displaying passwords in plaintext; it's not considered safe against attacks.
@@ -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
 
@@ -1,5 +1,5 @@
1
1
  module SimpleScripting
2
2
 
3
- VERSION = "0.15.0"
3
+ VERSION = "0.16.0"
4
4
 
5
5
  end
@@ -10,7 +10,7 @@ Gem::Specification.new do |s|
10
10
  s.platform = Gem::Platform::RUBY
11
11
  s.required_ruby_version = '>= 3.2.0'
12
12
  s.authors = ["Saverio Miroddi"]
13
- s.date = "2026-07-01"
13
+ s.date = "2026-07-07"
14
14
  s.email = ["saverio.pub2@gmail.com"]
15
15
  s.homepage = "https://github.com/64kramsystem/simple_scripting"
16
16
  s.summary = "Library for simplifying some typical scripting functionalities."
@@ -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'],
@@ -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 {
@@ -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"
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: simple_scripting
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.15.0
4
+ version: 0.16.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Saverio Miroddi
8
8
  bindir: bin
9
9
  cert_chain: []
10
- date: 2026-07-01 00:00:00.000000000 Z
10
+ date: 2026-07-07 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
13
  name: parseconfig