options_by_example 1.1.0 → 1.3.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/lib/options_by_example/parser.rb +102 -0
- data/lib/options_by_example/version.rb +13 -3
- data/lib/options_by_example.rb +31 -68
- metadata +4 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 26769b40ccdbee8bcb810e85755f7e3774145dda2ea8860edda1c99a65e168a5
|
|
4
|
+
data.tar.gz: 5d11ede203262af12bb65613ea5d153bb699188443453d82f8fb5bccb13068a1
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 1cb7a3d1732b19543557ed113094204451f9d24e2fae91029aa19d3f2985482e5536cc8c185e1dbef15fd77d330dd7ea76e7d0a6c596f7e198278f4afdf3bc74
|
|
7
|
+
data.tar.gz: 61a4d2a408fbe795040f7963e6d9cdb82076872bf7118c7fbb633b940684bd5eed9884f2d4a3f73cc4f321eb4e976d9ff5b252a17694a6316c4849314a5aa586
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class OptionsByExample
|
|
5
|
+
class Parser
|
|
6
|
+
|
|
7
|
+
attr_reader :options
|
|
8
|
+
attr_reader :arguments
|
|
9
|
+
|
|
10
|
+
def initialize(argument_names_required, argument_names_optional, option_names)
|
|
11
|
+
@argument_names_required = argument_names_required
|
|
12
|
+
@argument_names_optional = argument_names_optional
|
|
13
|
+
@option_names = option_names
|
|
14
|
+
|
|
15
|
+
@arguments = {}
|
|
16
|
+
@options = {}
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def parse(array)
|
|
20
|
+
|
|
21
|
+
# Separate command-line options and their respective arguments into
|
|
22
|
+
# chunks plus handling any remaining arguments. This organization
|
|
23
|
+
# facilitates further processing and validation of the input.
|
|
24
|
+
|
|
25
|
+
@chunks = []
|
|
26
|
+
@remainder = current = []
|
|
27
|
+
array.each do |each|
|
|
28
|
+
@chunks << current = [] if each.start_with?(?-)
|
|
29
|
+
current << each
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
find_help_option
|
|
33
|
+
find_unknown_options
|
|
34
|
+
parse_options
|
|
35
|
+
|
|
36
|
+
validate_number_of_arguments
|
|
37
|
+
parse_required_arguments
|
|
38
|
+
parse_optional_arguments
|
|
39
|
+
|
|
40
|
+
raise "Internal error: unreachable state" unless @remainder.empty?
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
private
|
|
44
|
+
|
|
45
|
+
def find_help_option
|
|
46
|
+
@chunks.each do |option, *args|
|
|
47
|
+
case option
|
|
48
|
+
when '-h', '--help'
|
|
49
|
+
raise "puts @usage_message"
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def find_unknown_options
|
|
55
|
+
@chunks.each do |option, *args|
|
|
56
|
+
raise "Found unknown option '#{option}'" unless @option_names.include?(option)
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def parse_options
|
|
61
|
+
@chunks.each do |option, *args|
|
|
62
|
+
if @remainder.any?
|
|
63
|
+
raise "Unexpected arguments found before option '#{option}', please provide all options before arguments"
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
option_name, argument_name = @option_names[option]
|
|
67
|
+
@options[option_name] = true
|
|
68
|
+
|
|
69
|
+
if argument_name
|
|
70
|
+
raise "Expected argument for option '#{option}', got none" if args.empty?
|
|
71
|
+
@arguments[option_name] = args.shift
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
@remainder = args
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def validate_number_of_arguments
|
|
79
|
+
min_length = @argument_names_required.size
|
|
80
|
+
max_length = @argument_names_optional.size + min_length
|
|
81
|
+
if @remainder.size > max_length
|
|
82
|
+
range = [min_length, max_length].uniq.join(?-)
|
|
83
|
+
raise "Expected #{range} arguments, but received too many"
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def parse_required_arguments
|
|
88
|
+
stash = @remainder.pop(@argument_names_required.length)
|
|
89
|
+
@argument_names_required.each do |argument_name|
|
|
90
|
+
raise "Missing required argument '#{argument_name}'" if stash.empty?
|
|
91
|
+
@arguments[argument_name] = stash.shift
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def parse_optional_arguments
|
|
96
|
+
@argument_names_optional.each do |argument_name|
|
|
97
|
+
break if @remainder.empty?
|
|
98
|
+
@arguments[argument_name] = @remainder.shift
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
end
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
class OptionsByExample
|
|
4
|
-
VERSION = '1.
|
|
4
|
+
VERSION = '1.3.0'
|
|
5
5
|
end
|
|
6
6
|
|
|
7
7
|
|
|
@@ -11,10 +11,20 @@ __END__
|
|
|
11
11
|
# Minor version bump when backward-compatible changes or enhancements
|
|
12
12
|
# Patch version bump when backward-compatible bug fixes, security updates etc
|
|
13
13
|
|
|
14
|
-
1.0
|
|
14
|
+
1.3.0
|
|
15
15
|
|
|
16
|
-
-
|
|
16
|
+
- Extract parsing functionality into class
|
|
17
|
+
- Better error messages
|
|
18
|
+
|
|
19
|
+
1.2.0
|
|
20
|
+
|
|
21
|
+
- Ensure compatibility with Ruby versions 1.9.3 and newer
|
|
22
|
+
|
|
23
|
+
1.1.0
|
|
24
|
+
|
|
25
|
+
- Renamed the gem from usage_by_example to options_by_example
|
|
17
26
|
- Update the gemspec to include readme and ruby files only
|
|
27
|
+
- Update readme file with features and an example
|
|
18
28
|
|
|
19
29
|
1.0.0
|
|
20
30
|
|
data/lib/options_by_example.rb
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
require 'options_by_example/version'
|
|
4
|
+
require 'options_by_example/parser'
|
|
4
5
|
|
|
5
6
|
|
|
6
7
|
class OptionsByExample
|
|
@@ -18,21 +19,22 @@ class OptionsByExample
|
|
|
18
19
|
end
|
|
19
20
|
|
|
20
21
|
def initialize(text)
|
|
21
|
-
@
|
|
22
|
+
@settings = {exit_on_error: true}
|
|
23
|
+
@usage_message = text.gsub('$0', File.basename($0)).gsub(/\n+\Z/, "\n\n")
|
|
22
24
|
|
|
23
|
-
#
|
|
25
|
+
# --- 1) Parse argument names -------------------------------------
|
|
24
26
|
#
|
|
25
27
|
# Parse the usage string and extract both optional argument names
|
|
26
28
|
# and required argument names, for example:
|
|
27
29
|
#
|
|
28
30
|
# Usage: connect [options] [mode] host port
|
|
29
31
|
|
|
30
|
-
text =~ /Usage: (\w+|\$0) \[options\](( \[\w+\])*)(( \w+)*)/
|
|
32
|
+
text =~ /Usage: (\w+|\$0)( \[options\])?(( \[\w+\])*)(( \w+)*)/
|
|
31
33
|
raise RuntimeError, "Expected usage string, got none" unless $1
|
|
32
|
-
@argument_names_optional = $
|
|
33
|
-
@argument_names_required = $
|
|
34
|
+
@argument_names_optional = $3.to_s.split.map { |match| match.tr('[]', '').downcase }
|
|
35
|
+
@argument_names_required = $5.to_s.split.map(&:downcase)
|
|
34
36
|
|
|
35
|
-
#
|
|
37
|
+
# --- 2) Parse option names ---------------------------------------
|
|
36
38
|
#
|
|
37
39
|
# Parse the usage message and extract option names, their short and
|
|
38
40
|
# long forms, and the associated argument name (if any), eg:
|
|
@@ -46,81 +48,42 @@ class OptionsByExample
|
|
|
46
48
|
@option_names = {}
|
|
47
49
|
text.scan(/((--?\w+)(, --?\w+)*) ?(\w+)?/) do
|
|
48
50
|
opts = $1.split(", ")
|
|
49
|
-
opts.each { |each| @option_names[each] = [opts.last.tr('-', ''), $4
|
|
51
|
+
opts.each { |each| @option_names[each] = [opts.last.tr('-', ''), $4] }
|
|
50
52
|
end
|
|
51
|
-
|
|
52
|
-
# ---- 3) Include help option by default --------------------------
|
|
53
|
-
|
|
54
|
-
@option_names.update("-h" => :help, "--help" => :help)
|
|
55
53
|
end
|
|
56
54
|
|
|
57
|
-
def
|
|
58
|
-
|
|
59
|
-
@arguments = {}
|
|
60
|
-
@options = {}
|
|
61
|
-
|
|
62
|
-
# --- 1) Parse options --------------------------------------------
|
|
63
|
-
|
|
64
|
-
most_recent_option = nil
|
|
65
|
-
until array.empty? do
|
|
66
|
-
break unless array.first.start_with?(?-)
|
|
67
|
-
most_recent_option = option = array.shift
|
|
68
|
-
option_name, argument_name = @option_names[option]
|
|
69
|
-
raise "Got unknown option #{option}" if option_name.nil?
|
|
70
|
-
raise if option_name == :help # Show usage without error message
|
|
71
|
-
@options[option_name] = true
|
|
72
|
-
|
|
73
|
-
# Consume argument, if expected by most recent option
|
|
74
|
-
if argument_name
|
|
75
|
-
argument = array.shift
|
|
76
|
-
raise "Expected argument for option #{option}" unless /^[^-]/ === argument
|
|
77
|
-
@arguments[option_name] = argument
|
|
78
|
-
most_recent_option = nil
|
|
79
|
-
end
|
|
80
|
-
end
|
|
81
|
-
|
|
82
|
-
# --- 2) Parse optional arguments ---------------------------------
|
|
55
|
+
def use(settings)
|
|
56
|
+
@settings.update settings
|
|
83
57
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
stash = array.pop(@argument_names_required.length)
|
|
88
|
-
@argument_names_optional.each do |argument_name|
|
|
89
|
-
break if array.empty?
|
|
90
|
-
argument = array.shift
|
|
91
|
-
raise "Expected more arguments, got option #{option}" unless /^[^-]/ === argument
|
|
92
|
-
@arguments[argument_name] = argument
|
|
93
|
-
end
|
|
94
|
-
|
|
95
|
-
# --- 3) Parse required arguments ---------------------------------
|
|
58
|
+
return self
|
|
59
|
+
end
|
|
96
60
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
@
|
|
102
|
-
|
|
61
|
+
def parse(argv)
|
|
62
|
+
parser = Parser.new(
|
|
63
|
+
@argument_names_required,
|
|
64
|
+
@argument_names_optional,
|
|
65
|
+
@option_names,
|
|
66
|
+
)
|
|
103
67
|
|
|
104
|
-
|
|
68
|
+
parser.parse argv
|
|
105
69
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
raise "Got unexpected argument for option #{most_recent_option}" if most_recent_option
|
|
109
|
-
raise "Expected #{min_length}#{"-#{max_length}" if max_length > min_length} arguments, got more"
|
|
110
|
-
end
|
|
70
|
+
@options = parser.options
|
|
71
|
+
@arguments = parser.arguments
|
|
111
72
|
|
|
112
73
|
return self
|
|
113
|
-
|
|
114
74
|
rescue RuntimeError => err
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
75
|
+
raise unless @settings[:exit_on_error]
|
|
76
|
+
|
|
77
|
+
if err.message == "puts @usage_message"
|
|
78
|
+
puts @usage_message
|
|
119
79
|
else
|
|
120
|
-
|
|
80
|
+
puts "ERROR: #{err.message}"
|
|
121
81
|
end
|
|
82
|
+
|
|
83
|
+
exit 1
|
|
122
84
|
end
|
|
123
85
|
|
|
86
|
+
|
|
124
87
|
def method_missing(sym, *args, &block)
|
|
125
88
|
case sym
|
|
126
89
|
when /^argument_(\w+)$/
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: options_by_example
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.
|
|
4
|
+
version: 1.3.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Adrian Kuhn
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2023-04-
|
|
11
|
+
date: 2023-04-29 00:00:00.000000000 Z
|
|
12
12
|
dependencies: []
|
|
13
13
|
description:
|
|
14
14
|
email:
|
|
@@ -19,6 +19,7 @@ extra_rdoc_files: []
|
|
|
19
19
|
files:
|
|
20
20
|
- README.md
|
|
21
21
|
- lib/options_by_example.rb
|
|
22
|
+
- lib/options_by_example/parser.rb
|
|
22
23
|
- lib/options_by_example/version.rb
|
|
23
24
|
homepage: https://github.com/akuhn/options_by_example
|
|
24
25
|
licenses:
|
|
@@ -34,7 +35,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
34
35
|
requirements:
|
|
35
36
|
- - ">="
|
|
36
37
|
- !ruby/object:Gem::Version
|
|
37
|
-
version:
|
|
38
|
+
version: 1.9.3
|
|
38
39
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
39
40
|
requirements:
|
|
40
41
|
- - ">="
|