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 +4 -4
- data/.simplecov +2 -0
- data/README.md +4 -0
- data/lib/simple_scripting/configuration.rb +13 -3
- data/lib/simple_scripting/tab_completion/commandline_processor.rb +20 -4
- data/lib/simple_scripting/version.rb +1 -1
- data/simple_scripting.gemspec +1 -1
- data/spec/simple_scripting/argv_spec.rb +12 -0
- data/spec/simple_scripting/configuration_spec.rb +36 -0
- data/spec/simple_scripting/tab_completion_spec.rb +30 -15
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 19d1802651a6d6c7ba48675475a2031da57ccde2ca508977f35c4865897734b1
|
|
4
|
+
data.tar.gz: 81108ceef64d225fd28c2752992aba5f46053ece551e3179364426cd708c9788
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 64373b69f2c72da9d253d8ad0bfa4660ca8e39de83ba3d3c7140454c71e76a82a80b33f1e5a1e885216c6ac8d573b3e0c373de1316db459e44b0a43f532f9c83
|
|
7
|
+
data.tar.gz: 6dd6a9ef7ceec62cf139f006f66a364ae881325bdfa5e2af61baad56377ce02ad8536eb440a9fc445cc110c977c7d1856655067f75707d946f61a15d901dcff5
|
data/.simplecov
CHANGED
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
|
-
|
|
19
|
+
params = ParseConfig.new(config_file).params
|
|
20
20
|
|
|
21
|
-
|
|
21
|
+
local_config_file = "#{config_file}.local"
|
|
22
22
|
|
|
23
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
data/simple_scripting.gemspec
CHANGED
|
@@ -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-
|
|
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
|
-
|
|
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>",
|
|
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
|
|
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.
|
|
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-
|
|
10
|
+
date: 2026-07-07 00:00:00.000000000 Z
|
|
11
11
|
dependencies:
|
|
12
12
|
- !ruby/object:Gem::Dependency
|
|
13
13
|
name: parseconfig
|