simple_scripting 0.9.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 +7 -0
- data/.gitignore +1 -0
- data/.travis.yml +7 -0
- data/Gemfile +11 -0
- data/Gemfile.lock +30 -0
- data/LICENSE +674 -0
- data/README.md +87 -0
- data/Rakefile +5 -0
- data/lib/simple_scripting/argv.rb +126 -0
- data/lib/simple_scripting/configuration.rb +46 -0
- data/lib/simple_scripting/configuration/value.rb +61 -0
- data/lib/simple_scripting/version.rb +5 -0
- data/simple_scripting.gemspec +28 -0
- data/spec/simple_scripting/argv_spec.rb +222 -0
- data/spec/simple_scripting/configuration/value_spec.rb +17 -0
- data/spec/simple_scripting/configuration_spec.rb +53 -0
- metadata +104 -0
data/README.md
ADDED
@@ -0,0 +1,87 @@
|
|
1
|
+
[![Build Status][BS img]](https://travis-ci.org/saveriomiroddi/simple_scripting)
|
2
|
+
|
3
|
+
# SimpleScripting
|
4
|
+
|
5
|
+
`SS` is a library composed of two modules (`Argv` and `Configuration`) which simplify two common scripting tasks:
|
6
|
+
|
7
|
+
- implementing the commandline options parsing (and the related help)
|
8
|
+
- loading and decoding the configuration for the script/application
|
9
|
+
|
10
|
+
`SS` is an interesting (and useful) exercise in design, aimed at finding the simplest and most expressive data/structures which accomplish the given task(s). For this reason, the library can be useful for people who frequently write small scripts (eg. devops or nerds).
|
11
|
+
|
12
|
+
## SimpleScripting::Argv
|
13
|
+
|
14
|
+
`SS::A` is a module which acts as frontend to the standard Option Parser library (`optparse`), giving a very convenient format for specifying the arguments. `SS::A` also generates the help.
|
15
|
+
|
16
|
+
This is a definition example:
|
17
|
+
|
18
|
+
result = SimpleOptParse::Argv.decode(
|
19
|
+
['-s', '--only-scheduled-days', 'Only print scheduled days' ],
|
20
|
+
['-d', '--print-defaults TEMPLATE', 'Print the default activities from the named template'],
|
21
|
+
'schedule',
|
22
|
+
'[weeks]',
|
23
|
+
long_help: 'This is the long help! It can span multiple lines.'
|
24
|
+
)
|
25
|
+
|
26
|
+
which:
|
27
|
+
|
28
|
+
- optionally accepts the `-s`/`--only-scheduled-days` switch, interpreting it as boolean,
|
29
|
+
- optionally accepts the `-d`/`--print-defaults` switch, interpreting it as string,
|
30
|
+
- requires the `schedule` argument,
|
31
|
+
- optionally accepts the `weeks` argument,
|
32
|
+
- automatically adds the `-h` and `--help` switches,
|
33
|
+
- prints all the options and the long help if the help is invoked,
|
34
|
+
- prints the help and exits if invalid parameters are passed (eg. too many).
|
35
|
+
|
36
|
+
This is a sample result:
|
37
|
+
|
38
|
+
{
|
39
|
+
only_scheduled_days: true,
|
40
|
+
print_defaults: 'my_defaults',
|
41
|
+
schedule: 'schedule.txt',
|
42
|
+
weeks: '3',
|
43
|
+
}
|
44
|
+
|
45
|
+
This is the corresponding help:
|
46
|
+
|
47
|
+
Usage: tmpfile [options] <schedule> [<weeks>]
|
48
|
+
-s, --only-scheduled-days Only print scheduled days
|
49
|
+
-d, --print-defaults TEMPLATE Print the default activities from the named template
|
50
|
+
-h, --help Help
|
51
|
+
|
52
|
+
This is the long help! It can span multiple lines.
|
53
|
+
|
54
|
+
For the guide, see the [wiki page](https://github.com/saveriomiroddi/simple_scripting/wiki/SimpleScripting::Argv-Guide).
|
55
|
+
|
56
|
+
## SimpleScripting::Configuration
|
57
|
+
|
58
|
+
`SS::C` is a module which acts as frontend to the ParseConfig gem (`parseconfig`), giving compact access to the configuration and its values, and adding a few helpers for common tasks.
|
59
|
+
|
60
|
+
Say one writes a script (`foo_my_bar.rb`), with a corresponding (`$HOME/.foo_my_bar`) configuration, which contains:
|
61
|
+
|
62
|
+
some_relative_file_path=foo
|
63
|
+
some_absolute_file_path=/path/to/bar
|
64
|
+
my_password=uTxllKRD2S+IH92oi30luwu0JIqp7kKA
|
65
|
+
|
66
|
+
[a_group]
|
67
|
+
group_key=baz
|
68
|
+
|
69
|
+
This is the workflow and functionality offered by `SS::C`:
|
70
|
+
|
71
|
+
# Picks up automatically the configuration file name, based on the calling program
|
72
|
+
#
|
73
|
+
configuration = SimpleScripting::Configuration.load(passwords_key: 'encryption_key')
|
74
|
+
|
75
|
+
configuration.some_relative_file_path.full_path # '$HOME/foo'
|
76
|
+
configuration.some_absolute_file_path # '/path/to/bar'
|
77
|
+
configuration.some_absolute_file_path.full_path # '/path/to/bar' (recognized as absolute)
|
78
|
+
|
79
|
+
configuration.my_password.decrypted # 'encrypted_value'
|
80
|
+
|
81
|
+
configuration.a_group.group_key # 'baz'; also supports #full_path and #decrypted
|
82
|
+
|
83
|
+
### Encryption note
|
84
|
+
|
85
|
+
The purpose of encryption in this library is just to avoid displaying passwords in plaintext; it's not considered safe against attacks.
|
86
|
+
|
87
|
+
[BS img]: https://travis-ci.org/saveriomiroddi/simple_scripting.svg?branch=master
|
data/Rakefile
ADDED
@@ -0,0 +1,126 @@
|
|
1
|
+
require 'optparse'
|
2
|
+
|
3
|
+
module SimpleScripting
|
4
|
+
|
5
|
+
module Argv
|
6
|
+
|
7
|
+
extend self
|
8
|
+
|
9
|
+
def decode(*params_definition, arguments: ARGV, long_help: nil, output: $stdout)
|
10
|
+
# If the param is a Hash, we have multiple commands. We check and if the command is correct,
|
11
|
+
# recursively call the function with the specific parameters.
|
12
|
+
#
|
13
|
+
if params_definition.first.is_a?(Hash)
|
14
|
+
command = arguments.shift
|
15
|
+
commands_definition = params_definition.first
|
16
|
+
|
17
|
+
if command == '-h' || command == '--help'
|
18
|
+
print_optparse_commands_help(commands_definition, output, false)
|
19
|
+
output == $stdout ? exit : return
|
20
|
+
end
|
21
|
+
|
22
|
+
command_params_definition = commands_definition[command]
|
23
|
+
|
24
|
+
if command_params_definition.nil?
|
25
|
+
print_optparse_commands_help(commands_definition, output, true)
|
26
|
+
output == $stdout ? exit : return
|
27
|
+
else
|
28
|
+
return [command, decode(*command_params_definition, arguments: arguments, output: output)]
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
result = {}
|
33
|
+
parser_opts_ref = nil # not available outside the block
|
34
|
+
args = {} # { 'name' => mandatory? }
|
35
|
+
|
36
|
+
OptionParser.new do | parser_opts |
|
37
|
+
params_definition.each do | param_definition |
|
38
|
+
case param_definition
|
39
|
+
when Array
|
40
|
+
if param_definition[1] && param_definition[1].start_with?('--')
|
41
|
+
key = param_definition[1].split(' ')[0][2 .. -1].gsub('-', '_').to_sym
|
42
|
+
else
|
43
|
+
key = param_definition[0][1 .. -1].to_sym
|
44
|
+
end
|
45
|
+
|
46
|
+
parser_opts.on(*param_definition) do |value|
|
47
|
+
result[key] = value || true
|
48
|
+
end
|
49
|
+
when String
|
50
|
+
if param_definition.start_with?('[')
|
51
|
+
arg_name = param_definition[1 .. -2].to_sym
|
52
|
+
|
53
|
+
args[arg_name] = false
|
54
|
+
else
|
55
|
+
arg_name = param_definition.to_sym
|
56
|
+
|
57
|
+
args[arg_name] = true
|
58
|
+
end
|
59
|
+
else
|
60
|
+
raise "Unrecognized value: #{param_definition}"
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
parser_opts.on( '-h', '--help', 'Help' ) do
|
65
|
+
print_optparse_help( parser_opts, args, long_help, output )
|
66
|
+
output == $stdout ? exit : return
|
67
|
+
end
|
68
|
+
|
69
|
+
parser_opts_ref = parser_opts
|
70
|
+
end.parse!(arguments)
|
71
|
+
|
72
|
+
first_arg_name = args.keys.first.to_s
|
73
|
+
|
74
|
+
# Varargs
|
75
|
+
if first_arg_name.start_with?('*')
|
76
|
+
# Mandatory?
|
77
|
+
if args.fetch(first_arg_name.to_sym)
|
78
|
+
if arguments.empty?
|
79
|
+
print_optparse_help( parser_opts_ref, args, long_help, output )
|
80
|
+
output == $stdout ? exit : return
|
81
|
+
else
|
82
|
+
name = args.keys.first[ 1 .. - 1 ].to_sym
|
83
|
+
|
84
|
+
result[ name ] = arguments
|
85
|
+
end
|
86
|
+
# Optional
|
87
|
+
else
|
88
|
+
name = args.keys.first[ 1 .. - 1 ].to_sym
|
89
|
+
|
90
|
+
result[ name ] = arguments
|
91
|
+
end
|
92
|
+
else
|
93
|
+
min_args_size = args.count { | name, mandatory | mandatory }
|
94
|
+
|
95
|
+
case arguments.size
|
96
|
+
when (min_args_size .. args.size)
|
97
|
+
arguments.zip(args) do | value, (name, mandatory) |
|
98
|
+
result[name] = value
|
99
|
+
end
|
100
|
+
else
|
101
|
+
print_optparse_help(parser_opts_ref, args, long_help, output)
|
102
|
+
output == $stdout ? exit : return
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
result
|
107
|
+
end
|
108
|
+
|
109
|
+
private
|
110
|
+
|
111
|
+
def print_optparse_commands_help(commands_definition, output, is_error)
|
112
|
+
output.print "Invalid command. " if is_error
|
113
|
+
output.puts "Valid commands:", "", " " + commands_definition.keys.join(', ')
|
114
|
+
end
|
115
|
+
|
116
|
+
def print_optparse_help(parser_opts, args, long_help, output)
|
117
|
+
args_display = args.map { | name, mandatory | mandatory ? "<#{ name }>" : "[<#{ name }>]" }.join(' ')
|
118
|
+
parser_opts_help = parser_opts.to_s.sub!(/^(Usage: .*)/, "\\1 #{args_display}")
|
119
|
+
|
120
|
+
output.puts parser_opts_help
|
121
|
+
output.puts "", long_help if long_help
|
122
|
+
end
|
123
|
+
|
124
|
+
end
|
125
|
+
|
126
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
require_relative 'configuration/value'
|
2
|
+
|
3
|
+
require 'ostruct'
|
4
|
+
require 'parseconfig'
|
5
|
+
|
6
|
+
module SimpleScripting
|
7
|
+
|
8
|
+
module Configuration
|
9
|
+
|
10
|
+
extend self
|
11
|
+
|
12
|
+
def load(config_file: default_config_file, passwords_key: nil)
|
13
|
+
configuration = ParseConfig.new(config_file)
|
14
|
+
|
15
|
+
convert_to_cool_format(OpenStruct.new, configuration.params, passwords_key)
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
def default_config_file
|
21
|
+
base_config_filename = '.' + File.basename($PROGRAM_NAME).chomp('.rb')
|
22
|
+
|
23
|
+
File.expand_path(base_config_filename, '~')
|
24
|
+
end
|
25
|
+
|
26
|
+
# Performs two conversions:
|
27
|
+
#
|
28
|
+
# 1. the configuration as a whole is converted to an OpenStruct
|
29
|
+
# 2. the values are converted to SimpleScripting::Configuration::Value
|
30
|
+
#
|
31
|
+
def convert_to_cool_format(result_node, configuration_node, encryption_key)
|
32
|
+
configuration_node.each do |key, value|
|
33
|
+
if value.is_a?(Hash)
|
34
|
+
result_node[key] = OpenStruct.new
|
35
|
+
convert_to_cool_format(result_node[key], value, encryption_key)
|
36
|
+
else
|
37
|
+
result_node[key] = Value.new(value, encryption_key)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
result_node
|
42
|
+
end
|
43
|
+
|
44
|
+
end
|
45
|
+
|
46
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
require 'openssl'
|
2
|
+
require 'base64'
|
3
|
+
|
4
|
+
module SimpleScripting
|
5
|
+
|
6
|
+
module Configuration
|
7
|
+
|
8
|
+
# The purpose of encryption in this library is just to avoid displaying passwords in
|
9
|
+
# plaintext; it's not considered safe against attacks.
|
10
|
+
#
|
11
|
+
class Value < String
|
12
|
+
|
13
|
+
ENCRYPTION_CIPHER = 'des3'
|
14
|
+
|
15
|
+
def initialize(string, encryption_key = nil)
|
16
|
+
super(string)
|
17
|
+
|
18
|
+
if encryption_key
|
19
|
+
@encryption_key = encryption_key + '*' * (24 - encryption_key.bytesize)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def full_path
|
24
|
+
start_with?('/') ? self : File.expand_path(self, '~')
|
25
|
+
end
|
26
|
+
|
27
|
+
def decrypted
|
28
|
+
raise "Encryption key not provided!" if @encryption_key.nil?
|
29
|
+
|
30
|
+
ciphertext = Base64.decode64(self)
|
31
|
+
|
32
|
+
cipher = OpenSSL::Cipher::Cipher.new(ENCRYPTION_CIPHER)
|
33
|
+
cipher.decrypt
|
34
|
+
|
35
|
+
cipher.key = @encryption_key
|
36
|
+
|
37
|
+
cipher.iv = ciphertext[0...cipher.iv_len]
|
38
|
+
plaintext = cipher.update(ciphertext[cipher.iv_len..-1]) + cipher.final
|
39
|
+
|
40
|
+
plaintext
|
41
|
+
end
|
42
|
+
|
43
|
+
def encrypted
|
44
|
+
cipher = OpenSSL::Cipher::Cipher.new(ENCRYPTION_CIPHER)
|
45
|
+
cipher.encrypt
|
46
|
+
|
47
|
+
iv = cipher.random_iv
|
48
|
+
|
49
|
+
cipher.key = @encryption_key
|
50
|
+
cipher.iv = iv
|
51
|
+
|
52
|
+
ciphertext = iv + cipher.update(self) + cipher.final
|
53
|
+
|
54
|
+
Base64.encode64(ciphertext).rstrip
|
55
|
+
end
|
56
|
+
|
57
|
+
end
|
58
|
+
|
59
|
+
end
|
60
|
+
|
61
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
3
|
+
$LOAD_PATH << File.expand_path("../lib", __FILE__)
|
4
|
+
|
5
|
+
require "simple_scripting/version"
|
6
|
+
|
7
|
+
Gem::Specification.new do |s|
|
8
|
+
s.name = "simple_scripting"
|
9
|
+
s.version = SimpleScripting::VERSION
|
10
|
+
s.platform = Gem::Platform::RUBY
|
11
|
+
s.authors = ["Saverio Miroddi"]
|
12
|
+
s.date = "2017-06-27"
|
13
|
+
s.email = ["saverio.pub2@gmail.com"]
|
14
|
+
s.homepage = "https://github.com/saveriomiroddi/simple_scripting"
|
15
|
+
s.summary = "Library for simplifying some typical scripting functionalities."
|
16
|
+
s.description = "Simplifies options parsing and configuration loading."
|
17
|
+
s.license = "GPL-3.0"
|
18
|
+
|
19
|
+
s.add_runtime_dependency "parseconfig", "~> 1.0"
|
20
|
+
|
21
|
+
s.add_development_dependency "rake", "~> 12.0"
|
22
|
+
s.add_development_dependency "rspec", "~> 3.6"
|
23
|
+
|
24
|
+
s.files = `git ls-files`.split("\n")
|
25
|
+
s.test_files = `git ls-files -- spec/*`.split("\n")
|
26
|
+
s.executables = []
|
27
|
+
s.require_paths = ["lib"]
|
28
|
+
end
|
@@ -0,0 +1,222 @@
|
|
1
|
+
require_relative '../../lib/simple_scripting/argv.rb'
|
2
|
+
|
3
|
+
require 'stringio'
|
4
|
+
|
5
|
+
describe SimpleScripting::Argv do
|
6
|
+
|
7
|
+
let(:output_buffer) do
|
8
|
+
StringIO.new
|
9
|
+
end
|
10
|
+
|
11
|
+
describe 'Basic functionality' do
|
12
|
+
|
13
|
+
let(:decoder_params) {[
|
14
|
+
['-a' ],
|
15
|
+
['-b', '"-b" description'],
|
16
|
+
['-c', '--c-switch' ],
|
17
|
+
['-d', '--d-switch', '"-d" description'],
|
18
|
+
['-e', '--e-switch VALUE' ],
|
19
|
+
['-f', '--f-switch VALUE', '"-f" description'],
|
20
|
+
'mandatory',
|
21
|
+
'[optional]',
|
22
|
+
long_help: 'This is the long help!',
|
23
|
+
output: output_buffer,
|
24
|
+
]}
|
25
|
+
|
26
|
+
it 'should implement the help' do
|
27
|
+
decoder_params.last[:arguments] = ['-h']
|
28
|
+
|
29
|
+
described_class.decode(*decoder_params)
|
30
|
+
|
31
|
+
expected_output = %Q{\
|
32
|
+
Usage: rspec [options] <mandatory> [<optional>]
|
33
|
+
-a
|
34
|
+
-b "-b" description
|
35
|
+
-c, --c-switch
|
36
|
+
-d, --d-switch "-d" description
|
37
|
+
-e, --e-switch VALUE
|
38
|
+
-f, --f-switch VALUE "-f" description
|
39
|
+
-h, --help Help
|
40
|
+
|
41
|
+
This is the long help!
|
42
|
+
}
|
43
|
+
|
44
|
+
expect(output_buffer.string).to eql(expected_output)
|
45
|
+
end
|
46
|
+
|
47
|
+
it "should implement basic switches and arguments (all set)" do
|
48
|
+
decoder_params.last[:arguments] = ['-a', '-b', '-c', '-d', '-ev_swt', '-fv_swt', 'm_arg', 'o_arg']
|
49
|
+
|
50
|
+
actual_result = described_class.decode(*decoder_params)
|
51
|
+
|
52
|
+
expected_result = {
|
53
|
+
a: true,
|
54
|
+
b: true,
|
55
|
+
c_switch: true,
|
56
|
+
d_switch: true,
|
57
|
+
e_switch: 'v_swt',
|
58
|
+
f_switch: 'v_swt',
|
59
|
+
mandatory: 'm_arg',
|
60
|
+
optional: 'o_arg',
|
61
|
+
}
|
62
|
+
|
63
|
+
expect(actual_result).to eql(expected_result)
|
64
|
+
end
|
65
|
+
|
66
|
+
it "should implement basic switches and arguments (no optional argument)" do
|
67
|
+
decoder_params.last[:arguments] = ['m_arg']
|
68
|
+
|
69
|
+
actual_result = described_class.decode(*decoder_params)
|
70
|
+
|
71
|
+
expected_result = {
|
72
|
+
mandatory: 'm_arg',
|
73
|
+
}
|
74
|
+
|
75
|
+
expect(actual_result).to eql(expected_result)
|
76
|
+
end
|
77
|
+
|
78
|
+
end
|
79
|
+
|
80
|
+
describe 'Varargs' do
|
81
|
+
|
82
|
+
describe '(mandatory)' do
|
83
|
+
|
84
|
+
let(:decoder_params) {[
|
85
|
+
'*varargs',
|
86
|
+
output: output_buffer,
|
87
|
+
]}
|
88
|
+
|
89
|
+
it "should be decoded" do
|
90
|
+
decoder_params.last[:arguments] = ['varval1', 'varval2']
|
91
|
+
|
92
|
+
actual_result = described_class.decode(*decoder_params)
|
93
|
+
|
94
|
+
expected_result = {
|
95
|
+
varargs: ['varval1', 'varval2'],
|
96
|
+
}
|
97
|
+
|
98
|
+
expect(actual_result).to eql(expected_result)
|
99
|
+
end
|
100
|
+
|
101
|
+
it "should exit when they are not specified" do
|
102
|
+
decoder_params.last[:arguments] = []
|
103
|
+
|
104
|
+
actual_result = described_class.decode(*decoder_params)
|
105
|
+
|
106
|
+
expected_result = nil
|
107
|
+
|
108
|
+
expect(actual_result).to eql(expected_result)
|
109
|
+
end
|
110
|
+
|
111
|
+
end
|
112
|
+
|
113
|
+
describe '(optional)' do
|
114
|
+
|
115
|
+
let(:decoder_params) {[
|
116
|
+
'[*varargs]',
|
117
|
+
output: output_buffer,
|
118
|
+
]}
|
119
|
+
|
120
|
+
it "should be decoded" do
|
121
|
+
decoder_params.last[:arguments] = ['varval1', 'varval2']
|
122
|
+
|
123
|
+
actual_result = described_class.decode(*decoder_params)
|
124
|
+
|
125
|
+
expected_result = {
|
126
|
+
varargs: ['varval1', 'varval2'],
|
127
|
+
}
|
128
|
+
|
129
|
+
expect(actual_result).to eql(expected_result)
|
130
|
+
end
|
131
|
+
|
132
|
+
it "should be allowed not to be specified" do
|
133
|
+
decoder_params.last[:arguments] = []
|
134
|
+
|
135
|
+
actual_result = described_class.decode(*decoder_params)
|
136
|
+
|
137
|
+
expected_result = {
|
138
|
+
varargs: [],
|
139
|
+
}
|
140
|
+
|
141
|
+
expect(actual_result).to eql(expected_result)
|
142
|
+
end
|
143
|
+
|
144
|
+
end
|
145
|
+
|
146
|
+
end
|
147
|
+
|
148
|
+
describe 'Multiple commands' do
|
149
|
+
|
150
|
+
describe 'regular case' do
|
151
|
+
|
152
|
+
let(:decoder_params) {{
|
153
|
+
'command1' => [
|
154
|
+
'arg1'
|
155
|
+
],
|
156
|
+
'command2' => [
|
157
|
+
'arg2'
|
158
|
+
],
|
159
|
+
output: output_buffer,
|
160
|
+
}}
|
161
|
+
|
162
|
+
it 'should be decoded' do
|
163
|
+
decoder_params[:arguments] = ['command1', 'value1']
|
164
|
+
|
165
|
+
actual_result = described_class.decode(decoder_params)
|
166
|
+
|
167
|
+
expected_result = ['command1', arg1: 'value1']
|
168
|
+
|
169
|
+
expect(actual_result).to eql(expected_result)
|
170
|
+
end
|
171
|
+
|
172
|
+
it 'print a message on wrong command' do
|
173
|
+
decoder_params[:arguments] = ['pizza']
|
174
|
+
|
175
|
+
described_class.decode(decoder_params)
|
176
|
+
|
177
|
+
expected_output = %Q{\
|
178
|
+
Invalid command. Valid commands:
|
179
|
+
|
180
|
+
command1, command2
|
181
|
+
}
|
182
|
+
|
183
|
+
expect(output_buffer.string).to eql(expected_output)
|
184
|
+
end
|
185
|
+
|
186
|
+
it 'should implement the help' do
|
187
|
+
decoder_params[:arguments] = ['-h']
|
188
|
+
|
189
|
+
described_class.decode(decoder_params)
|
190
|
+
|
191
|
+
expected_output = %Q{\
|
192
|
+
Valid commands:
|
193
|
+
|
194
|
+
command1, command2
|
195
|
+
}
|
196
|
+
|
197
|
+
expect(output_buffer.string).to eql(expected_output)
|
198
|
+
end
|
199
|
+
|
200
|
+
end
|
201
|
+
|
202
|
+
describe 'pitfall' do
|
203
|
+
|
204
|
+
let(:decoder_params) {{
|
205
|
+
output: output_buffer,
|
206
|
+
}}
|
207
|
+
|
208
|
+
# Make sure that the options (in this case, :output) are not interpreted as commands definition.
|
209
|
+
#
|
210
|
+
it 'should be avoided' do
|
211
|
+
decoder_params[:arguments] = ['pizza']
|
212
|
+
|
213
|
+
actual_result = described_class.decode(decoder_params)
|
214
|
+
|
215
|
+
expect(actual_result).to be(nil)
|
216
|
+
end
|
217
|
+
|
218
|
+
end
|
219
|
+
|
220
|
+
end
|
221
|
+
|
222
|
+
end
|