simple_scripting 0.9.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|