simple_scripting 0.9.4 → 0.10.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
  SHA1:
3
- metadata.gz: fb9a4aca2b7495bf0ce6236f6052bcc7c55c2624
4
- data.tar.gz: 5e4c2c1cdc733a142955b1462546ef7ab0535e07
3
+ metadata.gz: bb1844c9f2da48418b9fc91c3b4026c7fd1a1fbf
4
+ data.tar.gz: 5a30040d716b0e544027261f5bbe6fba834d75af
5
5
  SHA512:
6
- metadata.gz: 6c96951fcb96d49f0e4b4e512788b8f45c860d28d1dba22be29023db869f44bebf87b188458e8f264fc23503f104e0bd53f50bbe9b3df6efeec880c4c944b836
7
- data.tar.gz: 2f8f5c74c08f01e5a5ac020df7bd3011b21e2c0ebfc6282a7fc6e6a0d347ffd87acf4d59513dccba9a3fdf8c646229f9b455aceae55d4b14293bd88f0a0c9a90
6
+ metadata.gz: 39c51fd57bb624894fbb9b0adbfc731d365c2fd36d7b6e9e0a17d2e06fc36ba2f78c014f007b5dd360079daf449beb1ed433d694d827d15f17e664bf10bfdd0e
7
+ data.tar.gz: 6013724a8eb488ed73b2c8918096af8fc4c37a70e5e04aedcf66abd228412ec19606716b9a80a5b4ec829a99f4b071fe0978079ba35db9e023c58a4ea8265190
data/.simplecov ADDED
@@ -0,0 +1,8 @@
1
+ require 'simplecov'
2
+ require 'coveralls'
3
+
4
+ SimpleCov.formatter = Coveralls::SimpleCov::Formatter
5
+
6
+ SimpleCov.start do
7
+ add_filter '/spec/'
8
+ end
data/.travis.yml CHANGED
@@ -1,9 +1,6 @@
1
1
  language: ruby
2
+ cache: bundler
2
3
  rvm:
3
- - 2.2
4
4
  - 2.3
5
5
  - 2.4
6
6
  - 2.5
7
- # Bundler+Rubygems issue; see https://github.com/travis-ci/travis-ci/issues/8978
8
- before_install:
9
- - gem update --system
data/Gemfile CHANGED
@@ -1,12 +1,12 @@
1
1
  source 'https://rubygems.org'
2
2
 
3
- gem 'parseconfig', '~> 1.0'
4
-
5
- group :development do
6
- gem 'rake', '~> 12.0'
7
- end
3
+ gemspec
8
4
 
9
5
  group :test do
10
- gem 'rspec', '~> 3.6'
11
6
  gem 'coveralls', '~> 0.8.21', require: false
12
7
  end
8
+
9
+ group :tools do
10
+ gem 'byebug', '~> 10.0.2'
11
+ gem 'rubocop', '= 0.58.1'
12
+ end
data/Gemfile.lock CHANGED
@@ -1,6 +1,14 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ simple_scripting (0.9.4)
5
+ parseconfig (~> 1.0)
6
+
1
7
  GEM
2
8
  remote: https://rubygems.org/
3
9
  specs:
10
+ ast (2.4.0)
11
+ byebug (10.0.2)
4
12
  coveralls (0.8.21)
5
13
  json (>= 1.8, < 3)
6
14
  simplecov (~> 0.14.1)
@@ -9,8 +17,14 @@ GEM
9
17
  tins (~> 1.6)
10
18
  diff-lcs (1.3)
11
19
  docile (1.1.5)
20
+ jaro_winkler (1.5.1)
12
21
  json (2.1.0)
22
+ parallel (1.12.1)
13
23
  parseconfig (1.0.8)
24
+ parser (2.5.1.2)
25
+ ast (~> 2.4.0)
26
+ powerpack (0.1.2)
27
+ rainbow (3.0.0)
14
28
  rake (12.0.0)
15
29
  rspec (3.6.0)
16
30
  rspec-core (~> 3.6.0)
@@ -25,6 +39,15 @@ GEM
25
39
  diff-lcs (>= 1.2.0, < 2.0)
26
40
  rspec-support (~> 3.6.0)
27
41
  rspec-support (3.6.0)
42
+ rubocop (0.58.1)
43
+ jaro_winkler (~> 1.5.1)
44
+ parallel (~> 1.10)
45
+ parser (>= 2.5, != 2.5.1.1)
46
+ powerpack (~> 0.1)
47
+ rainbow (>= 2.2.2, < 4.0)
48
+ ruby-progressbar (~> 1.7)
49
+ unicode-display_width (~> 1.0, >= 1.0.1)
50
+ ruby-progressbar (1.9.0)
28
51
  simplecov (0.14.1)
29
52
  docile (~> 1.1.0)
30
53
  json (>= 1.8, < 3)
@@ -34,15 +57,18 @@ GEM
34
57
  tins (~> 1.0)
35
58
  thor (0.19.4)
36
59
  tins (1.15.0)
60
+ unicode-display_width (1.4.0)
37
61
 
38
62
  PLATFORMS
39
63
  ruby
40
64
 
41
65
  DEPENDENCIES
66
+ byebug (~> 10.0.2)
42
67
  coveralls (~> 0.8.21)
43
- parseconfig (~> 1.0)
44
68
  rake (~> 12.0)
45
69
  rspec (~> 3.6)
70
+ rubocop (= 0.58.1)
71
+ simple_scripting!
46
72
 
47
73
  BUNDLED WITH
48
- 1.11.2
74
+ 1.16.1
data/README.md CHANGED
@@ -1,21 +1,93 @@
1
1
  [![Gem Version][GV img]](https://rubygems.org/gems/simple_scripting)
2
2
  [![Build Status][BS img]](https://travis-ci.org/saveriomiroddi/simple_scripting)
3
- [![Dependency Status][DS img]](https://gemnasium.com/saveriomiroddi/simple_scripting)
4
3
  [![Code Climate][CC img]](https://codeclimate.com/github/saveriomiroddi/simple_scripting)
5
4
  [![Coverage Status][CS img]](https://coveralls.io/r/saveriomiroddi/simple_scripting)
6
5
 
7
6
  # SimpleScripting
8
7
 
9
- `SS` is a library composed of two modules (`Argv` and `Configuration`) which simplify two common scripting tasks:
8
+ `SimpleScripting` is a library composed of three modules (`TabCompletion`, `Argv` and `Configuration`) that simplify three common scripting tasks:
10
9
 
10
+ - writing autocompletion scripts
11
11
  - implementing the commandline options parsing (and the related help)
12
12
  - loading and decoding the configuration for the script/application
13
13
 
14
- `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).
14
+ `SimpleScripting` is an interesting (and useful) exercise in design, aimed at finding the simplest and most expressive data/structures that accomplish the given task(s). For this reason, the library can be useful for people who frequently write small scripts (eg. devops or nerds).
15
+
16
+ ## SimpleScripting::TabCompletion
17
+
18
+ `TabCompletion` makes trivial to define tab-completion for terminal commands on Linux/Mac systems; it's so easy that an example is much simpler than an explanation.
19
+
20
+ ### Example
21
+
22
+ Suppose we have the command:
23
+
24
+ ```sh
25
+ open_project [-e|--with-editor EDITOR] <project_name>
26
+ ```
27
+
28
+ We want to add tab completion both for the option and the project name. Easy!!
29
+
30
+ Install the gem (`simple_scripting`), then create this class (`/my/completion_scripts/open_project_completion.rb`):
31
+
32
+ ```ruby
33
+ #!/usr/bin/env ruby
34
+
35
+ require 'simple_scripting/tab_completion'
36
+
37
+ class OpenProjectTabCompletion
38
+ SYSTEM_EDITORS = `update-alternatives --list editor`.split("\n").map { |filename| File.basename(filename) }
39
+
40
+ def with_editor(prefix, suffix, context)
41
+ SYSTEM_EDITORS.grep /^#{prefix}/
42
+ end
43
+
44
+ def project_name(prefix, suffix, context)
45
+ Dir["/my/home/my_projects/#{prefix}*"]
46
+ end
47
+ end
48
+
49
+ if __FILE__ == $PROGRAM_NAME
50
+ completion_definition = [
51
+ ["-e", "--with-editor EDITOR"],
52
+ 'project_name'
53
+ ]
54
+
55
+ SimpleScripting::TabCompletion.new(completion_definition).complete(OpenProjectTabCompletion.new)
56
+ end
57
+ ```
58
+
59
+ then chmod and register it:
60
+
61
+ ```sh
62
+ $ chmod +x /my/completion_scripts/open_project_completion.rb
63
+ $ complete -C /my/completion_scripts/open_project_completion.rb -o default open_project
64
+ ```
65
+
66
+ Done!
67
+
68
+ Now type the following, and get:
69
+
70
+ ```sh
71
+ $ open_project g<tab> # lists: "geet", "gitlab-ce", "gnome-terminal"
72
+ $ open_project --with-editor v # lists: "vim.basic", "vim.tiny"
73
+ $ open_project --wi<tab> # autocompletes "--with-editor"; this is built-in!
74
+ ```
75
+
76
+ Happy completion!
77
+
78
+ ### Supported shells
79
+
80
+ TabCompletion supports Bash, and Zsh with bashcompinit.
81
+
82
+ Note that a recent version of Zsh is required - the Ubuntu 16.04 standard version has a bug that breaks bash-compatible completion.
83
+
84
+ ### More complex use cases
85
+
86
+ 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).
15
87
 
16
88
  ## SimpleScripting::Argv
17
89
 
18
- `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.
90
+ `Argv` is a module which acts as frontend to the standard Option Parser library (`optparse`), giving a very convenient format for specifying the arguments. `Argv` also generates the help.
19
91
 
20
92
  This is a definition example:
21
93
 
@@ -76,7 +148,7 @@ For the guide, see the [wiki page](https://github.com/saveriomiroddi/simple_scri
76
148
 
77
149
  ## SimpleScripting::Configuration
78
150
 
79
- `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.
151
+ `Configuration` 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.
80
152
 
81
153
  Say one writes a script (`foo_my_bar.rb`), with a corresponding (`$HOME/.foo_my_bar`) configuration, which contains:
82
154
 
@@ -88,7 +160,7 @@ Say one writes a script (`foo_my_bar.rb`), with a corresponding (`$HOME/.foo_my_
88
160
  [a_group]
89
161
  group_key=baz
90
162
 
91
- This is the workflow and functionality offered by `SS::C`:
163
+ This is the workflow and functionality offered by `Configuration`:
92
164
 
93
165
  require 'simple_scripting/configuration'
94
166
 
@@ -115,6 +187,5 @@ See the [wiki](https://github.com/saveriomiroddi/simple_scripting/wiki) for addi
115
187
 
116
188
  [GV img]: https://badge.fury.io/rb/simple_scripting.png
117
189
  [BS img]: https://travis-ci.org/saveriomiroddi/simple_scripting.svg?branch=master
118
- [DS img]: https://gemnasium.com/saveriomiroddi/simple_scripting.png
119
190
  [CC img]: https://codeclimate.com/github/saveriomiroddi/simple_scripting.png
120
191
  [CS img]: https://coveralls.io/repos/saveriomiroddi/simple_scripting/badge.png?branch=master
@@ -4,32 +4,57 @@ module SimpleScripting
4
4
 
5
5
  module Argv
6
6
 
7
- class ExitError < StandardError; end
7
+ # The fact that the following errors don't descend from OptionParser::InvalidOption is somewhat
8
+ # annoying, however, there should be no practical problem.
9
+ #
10
+ class InvalidCommand < StandardError; end
11
+ class ArgumentError < StandardError; end
12
+
13
+ class ExitWithCommandsHelpPrinting < Struct.new(:commands_definition)
14
+ # Note that :long_help is not used.
15
+ def print_help(output, long_help)
16
+ output.puts "Valid commands:", "", " " + commands_definition.keys.join(', ')
17
+ end
18
+ end
19
+
20
+ class ExitWithArgumentsHelpPrinting < Struct.new(:commands_stack, :args, :parser_opts_copy)
21
+ def print_help(output, long_help)
22
+ parser_opts_help = parser_opts_copy.to_s
23
+
24
+ if commands_stack.size > 0
25
+ parser_opts_help = parser_opts_help.sub!('[options]', commands_stack.join(' ') + ' [options]')
26
+ end
27
+
28
+ if args.size > 0
29
+ args_display = args.map { |name, mandatory| mandatory ? "<#{ name }>" : "[<#{ name }>]" }.join(' ')
30
+ parser_opts_help = parser_opts_help.sub!(/^(Usage: .*)/) { |text| "#{text} #{args_display}" }
31
+ end
32
+
33
+ output.puts parser_opts_help
34
+ output.puts "", long_help if long_help
35
+ end
36
+ end
8
37
 
9
38
  extend self
10
39
 
11
- def decode(*params_definition, arguments: ARGV, long_help: nil, output: $stdout)
40
+ def decode(*params_definition, arguments: ARGV, long_help: nil, auto_help: true, output: $stdout)
12
41
  # WATCH OUT! @long_help can also be set in :decode_command!. See issue #17.
13
42
  #
14
43
  @long_help = long_help
15
- @output = output
16
44
 
17
- if params_definition.first.is_a?(Hash)
18
- decode_command!(params_definition, arguments)
19
- else
20
- decode_arguments!(params_definition, arguments)
45
+ exit_data = catch(:exit) do
46
+ if params_definition.first.is_a?(Hash)
47
+ return decode_command!(params_definition, arguments, auto_help)
48
+ else
49
+ return decode_arguments!(params_definition, arguments, auto_help)
50
+ end
21
51
  end
22
- rescue ExitError
23
- # return nil, to be used with the 'decode(...) || exit' pattern
24
- ensure
25
- # Clean up the instance variables.
26
- #
27
- # There is a balance to strike between instance variables, and local variables
28
- # passed around. One of the options, which is this case, is to set and instance
29
- # variables only these two, which are constant.
30
52
 
53
+ exit_data.print_help(output, @long_help)
54
+
55
+ nil # to be used with the 'decode(...) || exit' pattern
56
+ ensure
31
57
  @long_help = nil
32
- @output = nil
33
58
  end
34
59
 
35
60
  private
@@ -40,7 +65,7 @@ module SimpleScripting
40
65
  #
41
66
  # [{"command1"=>["arg1", {:long_help=>"This is the long help."}], "command2"=>["arg2"]}]
42
67
  #
43
- def decode_command!(params_definition, arguments, commands_stack=[])
68
+ def decode_command!(params_definition, arguments, auto_help, commands_stack=[])
44
69
  commands_definition = params_definition.first
45
70
 
46
71
  # Set the `command` variable only after; in the case where we print the help, this variable
@@ -48,8 +73,19 @@ module SimpleScripting
48
73
  #
49
74
  command_for_check = arguments.shift
50
75
 
76
+ # Note that `--help` is baked into OptParse, so without a workaround, we need to always include
77
+ # it.
78
+ #
51
79
  if command_for_check == '-h' || command_for_check == '--help'
52
- print_optparse_commands_help(nil, commands_definition)
80
+ if auto_help
81
+ throw :exit, ExitWithCommandsHelpPrinting.new(commands_definition)
82
+ else
83
+ # This is tricky. Since the common behavior of `--help` is to trigger an unconditional
84
+ # help, it's not clear what to do with other tokens. For simplicity, we just return
85
+ # this flag.
86
+ #
87
+ return { help: true }
88
+ end
53
89
  end
54
90
 
55
91
  command = command_for_check
@@ -57,13 +93,13 @@ module SimpleScripting
57
93
 
58
94
  case command_params_definition
59
95
  when nil
60
- print_optparse_commands_help(command, commands_definition)
96
+ raise InvalidCommand.new("Invalid command: #{command}")
61
97
  when Hash
62
98
  commands_stack << command
63
99
 
64
100
  # Nested case! Decode recursively
65
101
  #
66
- decode_command!([command_params_definition], arguments, commands_stack)
102
+ decode_command!([command_params_definition], arguments, auto_help, commands_stack)
67
103
  else
68
104
  commands_stack << command
69
105
 
@@ -74,12 +110,12 @@ module SimpleScripting
74
110
 
75
111
  [
76
112
  compose_returned_commands(commands_stack),
77
- decode_arguments!(command_params_definition, arguments, commands_stack),
113
+ decode_arguments!(command_params_definition, arguments, auto_help, commands_stack),
78
114
  ]
79
115
  end
80
116
  end
81
117
 
82
- def decode_arguments!(params_definition, arguments, commands_stack=[])
118
+ def decode_arguments!(params_definition, arguments, auto_help, commands_stack=[])
83
119
  result = {}
84
120
  parser_opts_copy = nil # not available outside the block
85
121
  args = {} # { 'name' => mandatory? }
@@ -92,12 +128,24 @@ module SimpleScripting
92
128
  when String
93
129
  process_argument_definition!(param_definition, args)
94
130
  else
131
+ # This is an error in the params definition, so it doesn't follow the user error/help
132
+ # workflow.
133
+ #
95
134
  raise "Unrecognized value: #{param_definition}"
96
135
  end
97
136
  end
98
137
 
138
+ # See --help note in :decode_command!.
139
+ #
99
140
  parser_opts.on('-h', '--help', 'Help') do
100
- print_optparse_arguments_help(commands_stack, args, parser_opts_copy)
141
+ if auto_help
142
+ throw :exit, ExitWithArgumentsHelpPrinting.new(commands_stack, args, parser_opts_copy)
143
+ else
144
+ # Needs to be better handled. When help is required, generally, it trumps the
145
+ # correctness of the rest of the options/arguments.
146
+ #
147
+ result[:help] = true
148
+ end
101
149
  end
102
150
 
103
151
  parser_opts_copy = parser_opts
@@ -146,7 +194,7 @@ module SimpleScripting
146
194
  # Mandatory argument
147
195
  if args.fetch(first_arg_name.to_sym)
148
196
  if arguments.empty?
149
- print_optparse_arguments_help(commands_stack, args, parser_opts_copy)
197
+ raise ArgumentError.new("Missing mandatory argument(s)")
150
198
  else
151
199
  name = args.keys.first[1..-1].to_sym
152
200
 
@@ -163,43 +211,17 @@ module SimpleScripting
163
211
  def process_regular_argument!(arguments, result, commands_stack, args, parser_opts_copy)
164
212
  min_args_size = args.count { |_, mandatory| mandatory }
165
213
 
166
- case arguments.size
167
- when (min_args_size .. args.size)
214
+ if arguments.size < min_args_size
215
+ raise ArgumentError.new("Missing mandatory argument(s)")
216
+ elsif arguments.size > args.size
217
+ raise ArgumentError.new("Too many arguments")
218
+ else
168
219
  arguments.zip(args) do |value, (name, _)|
169
220
  result[name] = value
170
221
  end
171
- else
172
- print_optparse_arguments_help(commands_stack, args, parser_opts_copy)
173
222
  end
174
223
  end
175
224
 
176
- # HELP #################################################
177
-
178
- def print_optparse_commands_help(command, commands_definition)
179
- @output.print "Invalid command. " if command
180
- @output.puts "Valid commands:", "", " " + commands_definition.keys.join(', ')
181
-
182
- raise ExitError
183
- end
184
-
185
- def print_optparse_arguments_help(commands_stack, args, parser_opts_copy)
186
- parser_opts_help = parser_opts_copy.to_s
187
-
188
- if commands_stack.size > 0
189
- parser_opts_help = parser_opts_help.sub!('[options]', commands_stack.join(' ') + ' [options]')
190
- end
191
-
192
- if args.size > 0
193
- args_display = args.map { |name, mandatory| mandatory ? "<#{ name }>" : "[<#{ name }>]" }.join(' ')
194
- parser_opts_help = parser_opts_help.sub!(/^(Usage: .*)/) { |text| "#{text} #{args_display}" }
195
- end
196
-
197
- @output.puts parser_opts_help
198
- @output.puts "", @long_help if @long_help
199
-
200
- raise ExitError
201
- end
202
-
203
225
  # HELPERS ##############################################
204
226
 
205
227
  def compose_returned_commands(commands_stack)
@@ -0,0 +1,71 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'tab_completion/commandline_processor'
4
+
5
+ module SimpleScripting
6
+
7
+ # The naming of each of the the commandline units is not standard, therefore we establish the
8
+ # following arbitrary, but consistent, naming:
9
+ #
10
+ # executable --option option_parameter argument
11
+ #
12
+ # The commandline is divided into words, as Bash would split them. All the words, except the
13
+ # executable, compose an array that we call `argv` (as Ruby would do).
14
+ #
15
+ # We define each {option name => value} or {argument name => value} as `pair`.
16
+ #
17
+ # In the context of a pair, each pair is composed of a `key` and a `value`.
18
+ #
19
+ class TabCompletion
20
+
21
+ def initialize(switches_definition, output: $stdout)
22
+ @switches_definition = switches_definition
23
+ @output = output
24
+ end
25
+
26
+ # Currently, any completion suffix is ignored and stripped.
27
+ #
28
+ def complete(execution_target, source_commandline=ENV.fetch('COMP_LINE'), cursor_position=ENV.fetch('COMP_POINT').to_i)
29
+ commandline_processor = CommandlineProcessor.process_commandline(source_commandline, cursor_position, @switches_definition)
30
+
31
+ if commandline_processor.completing_an_option?
32
+ complete_option(commandline_processor, execution_target)
33
+ elsif commandline_processor.parsing_error?
34
+ return
35
+ else # completing_a_value?
36
+ complete_value(commandline_processor, execution_target)
37
+ end
38
+ end
39
+
40
+ private
41
+
42
+ #############################################
43
+ # Completion!
44
+ #############################################
45
+
46
+ def complete_option(commandline_processor, execution_target)
47
+ all_switches = @switches_definition.select { |definition| definition.is_a?(Array) }.map { |definition| definition[1][/^--\S+/] }
48
+
49
+ matching_switches = all_switches.select { |switch| switch.start_with?(commandline_processor.completing_word_prefix) }
50
+
51
+ output_entries(matching_switches)
52
+ end
53
+
54
+ def complete_value(commandline_processor, execution_target)
55
+ key, value_prefix, value_suffix, other_pairs = commandline_processor.parsed_pairs
56
+
57
+ selected_entries = execution_target.send(key, value_prefix, value_suffix, other_pairs)
58
+
59
+ output_entries(selected_entries)
60
+ end
61
+
62
+ #############################################
63
+ # Helpers
64
+ #############################################
65
+
66
+ def output_entries(entries)
67
+ @output.print entries.join("\n")
68
+ end
69
+ end
70
+
71
+ end
@@ -0,0 +1,118 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'shellwords'
4
+
5
+ require_relative '../argv'
6
+
7
+ module SimpleScripting
8
+
9
+ class TabCompletion
10
+
11
+ class CommandlineProcessor < Struct.new(:processed_argv, :cursor_marker, :switches_definition)
12
+
13
+ # Arbitrary; can be anything (except an empty string).
14
+ BASE_CURSOR_MARKER = "<tab>"
15
+
16
+ OPTIONS_TERMINATOR = "--"
17
+ LONG_OPTIONS_PREFIX = "--"
18
+
19
+ def self.process_commandline(source_commandline, cursor_position, switches_definition)
20
+ # An input string with infinite "<tabN>" substrings will cause an infinite cycle (hehe).
21
+ 0.upto(Float::INFINITY) do |i|
22
+ cursor_marker = BASE_CURSOR_MARKER.sub(">", "#{i}>")
23
+
24
+ if !source_commandline.include?(cursor_marker)
25
+ commandline_with_marker = source_commandline[0...cursor_position] + cursor_marker + source_commandline[cursor_position..-1].to_s
26
+
27
+ # Remove the executable.
28
+ processed_argv = Shellwords.split(commandline_with_marker)[1..-1]
29
+
30
+ return new(processed_argv, cursor_marker, switches_definition)
31
+ end
32
+ end
33
+ end
34
+
35
+ # We're abstracted from the commandline, with this exception. This is because while an option
36
+ # is being completed, the decoder would not recognize the key.
37
+ #
38
+ def completing_an_option?
39
+ processed_argv[marked_word_position].start_with?(LONG_OPTIONS_PREFIX) && marked_word_position < options_terminator_position
40
+ end
41
+
42
+ def parsing_error?
43
+ parse_argv.nil?
44
+ end
45
+
46
+ def completing_word_prefix
47
+ word = processed_argv[marked_word_position]
48
+
49
+ # Regex alternative: [/\A(.*?)#{cursor_marker}/m, 1]
50
+ word[0, word.index(cursor_marker)]
51
+ end
52
+
53
+ # Returns key, value prefix (before marker), value suffix (after marker), other_pairs
54
+ #
55
+ def parsed_pairs
56
+ parsed_pairs = parse_argv || raise("Parsing error")
57
+
58
+ key, value = parsed_pairs.detect do |_, value|
59
+ !boolean?(value) && value.include?(cursor_marker)
60
+ end
61
+
62
+ # Impossible case, unless there is a programmatic error.
63
+ #
64
+ key || raise("Guru meditation! (#{self.class}##{__method__}:#{__LINE__})")
65
+
66
+ value_prefix, value_suffix = value.split(cursor_marker)
67
+
68
+ parsed_pairs.delete(key)
69
+
70
+ [key, value_prefix || "", value_suffix || "", parsed_pairs]
71
+ end
72
+
73
+ private
74
+
75
+ def marked_word_position
76
+ processed_argv.index { |word| word.include?(cursor_marker) }
77
+ end
78
+
79
+ # Returns Float::INFINITY when there is no options terminator.
80
+ #
81
+ def options_terminator_position
82
+ processed_argv.index(OPTIONS_TERMINATOR) || Float::INFINITY
83
+ end
84
+
85
+ #############################################
86
+ # Helpers
87
+ #############################################
88
+
89
+ def parse_argv
90
+ # We need to convert all the arguments to optional, otherwise it's not possible to
91
+ # autocomplete arguments when not all the mandatory ones are not typed yet, eg:
92
+ #
93
+ # `my_command <tab>` with definition ['mand1', 'mand2']
94
+ #
95
+ adapted_switches_definition = switches_definition.dup
96
+
97
+ adapted_switches_definition.each_with_index do |definition, i|
98
+ adapted_switches_definition[i] = "[#{definition}]" if definition.is_a?(String) && !definition.start_with?('[')
99
+ end
100
+
101
+ SimpleScripting::Argv.decode(*adapted_switches_definition, arguments: processed_argv.dup, auto_help: false)
102
+ rescue Argv::InvalidCommand, Argv::ArgumentError, OptionParser::InvalidOption
103
+ # OptionParser::InvalidOption: see case "-O<tab>" in test suite.
104
+
105
+ # return nil
106
+ end
107
+
108
+ # For the lulz.
109
+ #
110
+ def boolean?(value)
111
+ !!value == value
112
+ end
113
+
114
+ end # CommandlineProcessor
115
+
116
+ end # TabCompletion
117
+
118
+ end # SimpleScripting
@@ -1,5 +1,5 @@
1
1
  module SimpleScripting
2
2
 
3
- VERSION = "0.9.4"
3
+ VERSION = "0.10.0"
4
4
 
5
5
  end
@@ -8,8 +8,9 @@ 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.3.0'
11
12
  s.authors = ["Saverio Miroddi"]
12
- s.date = "2018-01-26"
13
+ s.date = "2018-07-26"
13
14
  s.email = ["saverio.pub2@gmail.com"]
14
15
  s.homepage = "https://github.com/saveriomiroddi/simple_scripting"
15
16
  s.summary = "Library for simplifying some typical scripting functionalities."
@@ -20,7 +21,6 @@ Gem::Specification.new do |s|
20
21
 
21
22
  s.add_development_dependency "rake", "~> 12.0"
22
23
  s.add_development_dependency "rspec", "~> 3.6"
23
- s.add_development_dependency 'coveralls', "~> 0.8.21"
24
24
 
25
25
  s.files = `git ls-files`.split("\n")
26
26
  s.test_files = `git ls-files -- spec/*`.split("\n")
@@ -0,0 +1,55 @@
1
+ require 'stringio'
2
+
3
+ # Custom matchers for the tab completion test suite.
4
+ #
5
+ # Require :subject and :output_buffer to be defined/accessible.
6
+ #
7
+ # The matchers are simplistic (but still adequate); the (most) appropriate choice would be
8
+ # [diffable matchers](https://relishapp.com/rspec/rspec-expectations/v/3-6/docs/custom-matchers/define-diffable-matcher)
9
+ #
10
+ # Note that the semantic of the expected value is different from the standard rspec one, since we
11
+ # process it, therefore, we need to use intermediate instance variables.
12
+ #
13
+ module TabCompletionCustomRSpecMatchers
14
+
15
+ extend RSpec::Matchers::DSL
16
+
17
+ # Doesn't matter in this context.
18
+ #
19
+ PHONY_EXECUTABLE = '/path/to/executable'
20
+
21
+ matcher :complete_with do |expected_entries|
22
+ match do |symbolic_commandline_options|
23
+ commandline = "#{PHONY_EXECUTABLE} #{symbolic_commandline_options}"
24
+ cursor_position = commandline.index("<tab>")
25
+ commandline = commandline.sub("<tab>", "")
26
+
27
+ subject.complete(execution_target, commandline, cursor_position)
28
+
29
+ @actual_output = output_buffer.string
30
+ expected_output = expected_entries.join("\n")
31
+
32
+ expect(@actual_output).to eql(expected_output)
33
+ end
34
+
35
+ failure_message do |actual|
36
+ actual_entries = @actual_output.split("\n")
37
+
38
+ "#{actual} listed #{actual_entries} instead of #{expected}"
39
+ end
40
+ end
41
+
42
+ matcher :not_complete do
43
+ match do |symbolic_commandline_options|
44
+ expect(symbolic_commandline_options).to complete_with([])
45
+ end
46
+
47
+ failure_message do |actual|
48
+ @actual_output = output_buffer.string
49
+ actual_entries = @actual_output.split("\n")
50
+
51
+ "#{actual} listed #{actual_entries} instead of no entries"
52
+ end
53
+ end
54
+
55
+ end # TabCompletionCustomRSpecMatchers
@@ -23,27 +23,59 @@ describe SimpleScripting::Argv do
23
23
  output: output_buffer,
24
24
  ]}
25
25
 
26
- it 'should implement the help' do
27
- decoder_params.last[:arguments] = ['-h']
28
-
29
- return_value = 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
- expect(return_value).to be(nil)
46
- end
26
+ context 'help' do
27
+
28
+ it 'should print help automatically by default' do
29
+ decoder_params.last[:arguments] = ['-h']
30
+
31
+ return_value = described_class.decode(*decoder_params)
32
+
33
+ expected_output = <<~OUTPUT
34
+ Usage: rspec [options] <mandatory> [<optional>]
35
+ -a
36
+ -b "-b" description
37
+ -c, --c-switch
38
+ -d, --d-switch "-d" description
39
+ -e, --e-switch VALUE
40
+ -f, --f-switch VALUE "-f" description
41
+ -h, --help Help
42
+
43
+ This is the long help!
44
+ OUTPUT
45
+
46
+ expect(output_buffer.string).to eql(expected_output)
47
+ expect(return_value).to be(nil)
48
+ end
49
+
50
+ it 'should not interpret the --help argument, and not print the help, on auto_help: false' do
51
+ decoder_params.last.merge!(
52
+ arguments: ['--help', 'm_arg'],
53
+ auto_help: false
54
+ )
55
+
56
+ actual_result = described_class.decode(*decoder_params)
57
+
58
+ expected_result = {
59
+ help: true,
60
+ mandatory: 'm_arg',
61
+ }
62
+
63
+ expect(output_buffer.string).to eql('')
64
+ expect(actual_result).to eql(expected_result)
65
+ end
66
+
67
+ it "should check all the options/arguments when --help is passed, raising an error when they're not correct" do
68
+ decoder_params.last.merge!(
69
+ arguments: ['--help'],
70
+ auto_help: false
71
+ )
72
+
73
+ decoding = -> { described_class.decode(*decoder_params) }
74
+
75
+ expect(decoding).to raise_error(SimpleScripting::Argv::ArgumentError, "Missing mandatory argument(s)")
76
+ end
77
+
78
+ end # context 'help'
47
79
 
48
80
  it "should implement basic switches and arguments (all set)" do
49
81
  decoder_params.last[:arguments] = ['-a', '-b', '-c', '-d', '-ev_swt', '-fv_swt', 'm_arg', 'o_arg']
@@ -76,7 +108,62 @@ This is the long help!
76
108
  expect(actual_result).to eql(expected_result)
77
109
  end
78
110
 
79
- end
111
+ context "multiple optional arguments" do
112
+
113
+ let(:decoder_params) {[
114
+ '[optional1]',
115
+ '[optional2]',
116
+ output: output_buffer,
117
+ ]}
118
+
119
+ it "should correctly decode a single argument passed" do
120
+ decoder_params.last[:arguments] = ['o_arg1']
121
+
122
+ actual_result = described_class.decode(*decoder_params)
123
+
124
+ expected_result = {
125
+ optional1: 'o_arg1',
126
+ }
127
+
128
+ expect(actual_result).to eql(expected_result)
129
+ end
130
+
131
+ it "should correctly decode all arguments passed" do
132
+ decoder_params.last[:arguments] = ['o_arg1', 'o_arg2']
133
+
134
+ actual_result = described_class.decode(*decoder_params)
135
+
136
+ expected_result = {
137
+ optional1: 'o_arg1',
138
+ optional2: 'o_arg2',
139
+ }
140
+
141
+ expect(actual_result).to eql(expected_result)
142
+ end
143
+
144
+ end
145
+
146
+ context "error handling" do
147
+
148
+ it "should raise an error when mandatory arguments are missing" do
149
+ decoder_params.last[:arguments] = []
150
+
151
+ decoding = -> { described_class.decode(*decoder_params) }
152
+
153
+ expect(decoding).to raise_error(SimpleScripting::Argv::ArgumentError, "Missing mandatory argument(s)")
154
+ end
155
+
156
+ it "should print an error and the help when there are too many arguments" do
157
+ decoder_params.last[:arguments] = ['arg1', 'arg2', 'excessive_arg']
158
+
159
+ decoding = -> { described_class.decode(*decoder_params) }
160
+
161
+ expect(decoding).to raise_error(SimpleScripting::Argv::ArgumentError, "Too many arguments")
162
+ end
163
+
164
+ end # context "error handling"
165
+
166
+ end # describe 'Basic functionality'
80
167
 
81
168
  describe 'Varargs' do
82
169
 
@@ -99,17 +186,19 @@ This is the long help!
99
186
  expect(actual_result).to eql(expected_result)
100
187
  end
101
188
 
102
- it "should exit when they are not specified" do
103
- decoder_params.last[:arguments] = []
189
+ context "error handling" do
104
190
 
105
- actual_result = described_class.decode(*decoder_params)
191
+ it "should exit when they are not specified" do
192
+ decoder_params.last[:arguments] = []
106
193
 
107
- expected_result = nil
194
+ decoding = -> { described_class.decode(*decoder_params) }
108
195
 
109
- expect(actual_result).to eql(expected_result)
110
- end
196
+ expect(decoding).to raise_error(SimpleScripting::Argv::ArgumentError, "Missing mandatory argument(s)")
197
+ end
111
198
 
112
- end
199
+ end # context "error handling"
200
+
201
+ end # describe '(mandatory)'
113
202
 
114
203
  describe '(optional)' do
115
204
 
@@ -142,9 +231,9 @@ This is the long help!
142
231
  expect(actual_result).to eql(expected_result)
143
232
  end
144
233
 
145
- end
234
+ end # describe '(optional)'
146
235
 
147
- end
236
+ end # describe 'Varargs'
148
237
 
149
238
  describe 'Commands' do
150
239
 
@@ -171,50 +260,86 @@ This is the long help!
171
260
  expect(actual_result).to eql(expected_result)
172
261
  end
173
262
 
174
- it 'print a message on wrong command' do
175
- decoder_params[:arguments] = ['pizza']
263
+ context "error handling" do
176
264
 
177
- described_class.decode(decoder_params)
265
+ it "should raise an error on invalid command" do
266
+ decoder_params[:arguments] = ['pizza']
178
267
 
179
- expected_output = %Q{\
180
- Invalid command. Valid commands:
268
+ decoding = -> { described_class.decode(decoder_params) }
181
269
 
182
- command1, command2
183
- }
270
+ expect(decoding).to raise_error(SimpleScripting::Argv::InvalidCommand, "Invalid command: pizza")
271
+ end
184
272
 
185
- expect(output_buffer.string).to eql(expected_output)
186
- end
273
+ end # context "error handling"
187
274
 
188
- it 'should implement the commands help' do
189
- decoder_params[:arguments] = ['-h']
275
+ context "help" do
190
276
 
191
- described_class.decode(decoder_params)
277
+ it 'should implement the commands help' do
278
+ decoder_params[:arguments] = ['-h']
192
279
 
193
- expected_output = %Q{\
194
- Valid commands:
280
+ described_class.decode(decoder_params)
195
281
 
196
- command1, command2
197
- }
282
+ expected_output = <<~OUTPUT
283
+ Valid commands:
198
284
 
199
- expect(output_buffer.string).to eql(expected_output)
200
- end
285
+ command1, command2
286
+ OUTPUT
201
287
 
202
- it "should display the command given command's help" do
203
- decoder_params[:arguments] = ['command1', '-h']
204
- $a = true
205
- described_class.decode(decoder_params)
288
+ expect(output_buffer.string).to eql(expected_output)
289
+ end
206
290
 
207
- expected_output = %Q{\
208
- Usage: rspec command1 [options] <arg1>
209
- -h, --help Help
291
+ it "should display the command given command's help" do
292
+ decoder_params[:arguments] = ['command1', '-h']
210
293
 
211
- This is the long help.
212
- }
294
+ described_class.decode(decoder_params)
213
295
 
214
- expect(output_buffer.string).to eql(expected_output)
215
- end
296
+ expected_output = <<~OUTPUT
297
+ Usage: rspec command1 [options] <arg1>
298
+ -h, --help Help
216
299
 
217
- end
300
+ This is the long help.
301
+ OUTPUT
302
+
303
+ expect(output_buffer.string).to eql(expected_output)
304
+ end
305
+
306
+ context 'auto_help: false' do
307
+
308
+ it 'should not interpret the --help argument, and not print the help' do
309
+ decoder_params.merge!(
310
+ arguments: ['-h'],
311
+ auto_help: false,
312
+ )
313
+
314
+ actual_result = described_class.decode(decoder_params)
315
+
316
+ expected_result = {
317
+ help: true,
318
+ }
319
+
320
+ expect(actual_result).to eql(expected_result)
321
+ end
322
+
323
+ it 'should ignore and not return all the other arguments' do
324
+ decoder_params.merge!(
325
+ arguments: ['-h', 'pizza'],
326
+ auto_help: false,
327
+ )
328
+
329
+ actual_result = described_class.decode(decoder_params)
330
+
331
+ expected_result = {
332
+ help: true,
333
+ }
334
+
335
+ expect(actual_result).to eql(expected_result)
336
+ end
337
+
338
+ end # context 'auto_help: false'
339
+
340
+ end # context 'help'
341
+
342
+ end # describe 'regular case'
218
343
 
219
344
  describe 'Nested commands' do
220
345
 
@@ -259,11 +384,11 @@ This is the long help.
259
384
 
260
385
  actual_result = described_class.decode(decoder_params)
261
386
 
262
- expected_output = "\
263
- Valid commands:
387
+ expected_output = <<~OUTPUT
388
+ Valid commands:
264
389
 
265
- nested1a, nested1b
266
- "
390
+ nested1a, nested1b
391
+ OUTPUT
267
392
 
268
393
  expect(output_buffer.string).to eql(expected_output)
269
394
  end
@@ -273,33 +398,35 @@ Valid commands:
273
398
 
274
399
  actual_result = described_class.decode(decoder_params)
275
400
 
276
- expected_output = "\
277
- Usage: rspec command1 nested1a [options] <arg1>
278
- -h, --help Help
401
+ expected_output = <<~OUTPUT
402
+ Usage: rspec command1 nested1a [options] <arg1>
403
+ -h, --help Help
279
404
 
280
- nested1a long help.
281
- "
405
+ nested1a long help.
406
+ OUTPUT
282
407
 
283
408
  expect(output_buffer.string).to eql(expected_output)
284
409
  end
285
- end
410
+ end # describe 'Nested commands'
286
411
 
287
- describe 'No argv case' do
412
+ end # describe 'Commands'
288
413
 
289
- let(:decoder_params) {{
290
- output: output_buffer,
291
- }}
414
+ # Special case.
415
+ #
416
+ describe 'No definitions given' do
292
417
 
293
- it 'should avoided options being interpreted as definitions' do
294
- decoder_params[:arguments] = ['pizza']
418
+ let(:decoder_params) {{
419
+ output: output_buffer,
420
+ }}
295
421
 
296
- actual_result = described_class.decode(decoder_params)
422
+ it 'should avoid options being interpreted as definitions' do
423
+ decoder_params[:arguments] = ['pizza']
297
424
 
298
- expect(actual_result).to be(nil)
299
- end
425
+ decoding = -> { described_class.decode(decoder_params) }
300
426
 
427
+ expect(decoding).to raise_error(SimpleScripting::Argv::ArgumentError, "Too many arguments")
301
428
  end
302
429
 
303
- end
430
+ end # describe 'No definitions given'
304
431
 
305
432
  end
@@ -0,0 +1,140 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../../lib/simple_scripting/tab_completion.rb'
4
+
5
+ describe SimpleScripting::TabCompletion do
6
+
7
+ include TabCompletionCustomRSpecMatchers
8
+
9
+ let(:output_buffer) {
10
+ StringIO.new
11
+ }
12
+
13
+ let(:switches_definition) {
14
+ [
15
+ ["-o", "--opt1 ARG"],
16
+ ["-O", "--opt2"],
17
+ "arg1", # this and the following are internally converted to optional, as
18
+ "arg2", # according to the Argv spec, without brackets they are mandatory.
19
+ ]
20
+ }
21
+
22
+ let(:execution_target) {
23
+ # Simplistic implementation. In real world, regex are not to be used this way, for multiple
24
+ # reasons.
25
+ #
26
+ Class.new do
27
+ def opt1(prefix, suffix, context)
28
+ %w(opt1v1 _opt1v2).select { |entry| entry =~ /^#{prefix}#{suffix}/ }
29
+ end
30
+
31
+ def arg1(prefix, suffix, context)
32
+ # A value starting with space is valid.
33
+ #
34
+ ['arg1v1', 'arg1v2', '_arg1v3', ' _argv1spc'].select { |entry| entry =~ /^#{prefix}#{suffix}/ }
35
+ end
36
+
37
+ def arg2(prefix, suffix, context)
38
+ # A value starting with minus is valid.
39
+ #
40
+ %w(arg2v1 arg2v2 --arg2v3).select { |entry| entry =~ /^#{prefix}#{suffix}/ }
41
+ end
42
+ end.new
43
+ }
44
+
45
+ subject { described_class.new(switches_definition, output: output_buffer) }
46
+
47
+ context "with a correct configuration" do
48
+
49
+ context "standard cases" do
50
+
51
+ # Note that the conversion of mandatory to optional argument is defined by most of the cases.
52
+ #
53
+ STANDARD_CASES = {
54
+ "<tab>" => ["arg1v1", "arg1v2", "_arg1v3", " _argv1spc"],
55
+ "a<tab>" => %w(arg1v1 arg1v2),
56
+ "--opt2 <tab>" => ["arg1v1", "arg1v2", "_arg1v3", " _argv1spc"],
57
+ "-- <tab>" => ["arg1v1", "arg1v2", "_arg1v3", " _argv1spc"],
58
+
59
+ "a <tab>" => %w(arg2v1 arg2v2 --arg2v3),
60
+ "a -- --<tab>" => %w(--arg2v3),
61
+ "-- --aaa <tab>" => %w(arg2v1 arg2v2 --arg2v3),
62
+
63
+ "--<tab>" => %w(--opt1 --opt2),
64
+ "--<tab> a" => %w(--opt1 --opt2),
65
+ "--<tab> -- a" => %w(--opt1 --opt2),
66
+ "--<tab> -- b" => %w(--opt1 --opt2),
67
+ "--<tab> --xyz" => %w(--opt1 --opt2),
68
+ "--opt1 <tab> a" => %w(opt1v1 _opt1v2),
69
+ "--opt1 o<tab> a" => %w(opt1v1),
70
+
71
+ "-o<tab>" => %w(opt1v1 _opt1v2),
72
+ "-o <tab>" => %w(opt1v1 _opt1v2),
73
+ "-o -O <tab>" => ["arg1v1", "arg1v2", "_arg1v3", " _argv1spc"],
74
+ "-O <tab>" => ["arg1v1", "arg1v2", "_arg1v3", " _argv1spc"],
75
+ }
76
+
77
+ STANDARD_CASES.each do |symbolic_commandline_options, expected_entries|
78
+ it "should output the entries for #{symbolic_commandline_options.inspect}" do
79
+ expect(symbolic_commandline_options).to complete_with(expected_entries)
80
+ end
81
+ end
82
+
83
+ end # context "standard cases"
84
+
85
+ context "suffix management" do
86
+
87
+ SUFFIX_CASES = {
88
+ "arg1<tab>v" => %w(arg1v1 arg1v2), # the execution target of the test suite doesn't
89
+ "arg1<tab>x" => %w(), # ignore the suffix; programmer-defined
90
+
91
+ "--o<tab>p" => %w(--opt1 --opt2), # options ignore the suffix (like bash); can't be
92
+ "--o<tab>x" => %w(--opt1 --opt2), # currently changed by the programmer.
93
+ }
94
+
95
+ SUFFIX_CASES.each do |symbolic_commandline_options, expected_entries|
96
+ it "should output the entries for #{symbolic_commandline_options.inspect}, ignoring the suffix" do
97
+ expect(symbolic_commandline_options).to complete_with(expected_entries)
98
+ end
99
+ end
100
+
101
+ end # context "suffix management"
102
+
103
+ context "escaped cases" do
104
+
105
+ ESCAPED_CASES = {
106
+ "\ <tab>" => [" _argv1spc"],
107
+ '\-<tab>' => %w(), # this is the result of typing `command "\-<tab>`
108
+ 'a \-<tab>' => %w(--arg2v3),
109
+ }
110
+
111
+ ESCAPED_CASES.each do |symbolic_commandline_options, _|
112
+ it "should output the entries for #{symbolic_commandline_options.inspect}"
113
+ end
114
+
115
+ end # context "escaped cases"
116
+
117
+ it "should support multiple values for an option"
118
+
119
+ it "should keep parsing also when --help is passed" do
120
+ expect("--help a<tab>").to complete_with(%w(arg1v1 arg1v2))
121
+ end
122
+
123
+ end # context "with a correct configuration"
124
+
125
+ context "with an incorrect configuration" do
126
+
127
+ INCORRECT_CASES = [
128
+ "a b <tab>", # too many args
129
+ "-O<tab>", # no values for this option
130
+ ]
131
+
132
+ INCORRECT_CASES.each do |symbolic_commandline_options|
133
+ it "should not output any entries for #{symbolic_commandline_options.inspect}" do
134
+ expect(symbolic_commandline_options).to not_complete
135
+ end
136
+ end
137
+
138
+ end # context "with an incorrect configuration"
139
+
140
+ end # describe SimpleScripting::TabCompletion
data/spec/spec_helper.rb CHANGED
@@ -49,3 +49,5 @@ RSpec.configure do |config|
49
49
  # triggering implicit auto-inclusion in groups with matching metadata.
50
50
  config.shared_context_metadata_behavior = :apply_to_host_groups
51
51
  end
52
+
53
+ require_relative 'helpers/tab_completion_custom_rspec_matchers'
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: simple_scripting
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.9.4
4
+ version: 0.10.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Saverio Miroddi
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2018-01-26 00:00:00.000000000 Z
11
+ date: 2018-07-26 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: parseconfig
@@ -52,20 +52,6 @@ dependencies:
52
52
  - - "~>"
53
53
  - !ruby/object:Gem::Version
54
54
  version: '3.6'
55
- - !ruby/object:Gem::Dependency
56
- name: coveralls
57
- requirement: !ruby/object:Gem::Requirement
58
- requirements:
59
- - - "~>"
60
- - !ruby/object:Gem::Version
61
- version: 0.8.21
62
- type: :development
63
- prerelease: false
64
- version_requirements: !ruby/object:Gem::Requirement
65
- requirements:
66
- - - "~>"
67
- - !ruby/object:Gem::Version
68
- version: 0.8.21
69
55
  description: Simplifies options parsing and configuration loading.
70
56
  email:
71
57
  - saverio.pub2@gmail.com
@@ -75,6 +61,7 @@ extra_rdoc_files: []
75
61
  files:
76
62
  - ".gitignore"
77
63
  - ".rspec"
64
+ - ".simplecov"
78
65
  - ".travis.yml"
79
66
  - Gemfile
80
67
  - Gemfile.lock
@@ -84,11 +71,15 @@ files:
84
71
  - lib/simple_scripting/argv.rb
85
72
  - lib/simple_scripting/configuration.rb
86
73
  - lib/simple_scripting/configuration/value.rb
74
+ - lib/simple_scripting/tab_completion.rb
75
+ - lib/simple_scripting/tab_completion/commandline_processor.rb
87
76
  - lib/simple_scripting/version.rb
88
77
  - simple_scripting.gemspec
78
+ - spec/helpers/tab_completion_custom_rspec_matchers.rb
89
79
  - spec/simple_scripting/argv_spec.rb
90
80
  - spec/simple_scripting/configuration/value_spec.rb
91
81
  - spec/simple_scripting/configuration_spec.rb
82
+ - spec/simple_scripting/tab_completion_spec.rb
92
83
  - spec/spec_helper.rb
93
84
  homepage: https://github.com/saveriomiroddi/simple_scripting
94
85
  licenses:
@@ -102,7 +93,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
102
93
  requirements:
103
94
  - - ">="
104
95
  - !ruby/object:Gem::Version
105
- version: '0'
96
+ version: 2.3.0
106
97
  required_rubygems_version: !ruby/object:Gem::Requirement
107
98
  requirements:
108
99
  - - ">="
@@ -110,12 +101,14 @@ required_rubygems_version: !ruby/object:Gem::Requirement
110
101
  version: '0'
111
102
  requirements: []
112
103
  rubyforge_project:
113
- rubygems_version: 2.6.14
104
+ rubygems_version: 2.6.13
114
105
  signing_key:
115
106
  specification_version: 4
116
107
  summary: Library for simplifying some typical scripting functionalities.
117
108
  test_files:
109
+ - spec/helpers/tab_completion_custom_rspec_matchers.rb
118
110
  - spec/simple_scripting/argv_spec.rb
119
111
  - spec/simple_scripting/configuration/value_spec.rb
120
112
  - spec/simple_scripting/configuration_spec.rb
113
+ - spec/simple_scripting/tab_completion_spec.rb
121
114
  - spec/spec_helper.rb