options_by_example 1.2.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 +6 -1
- data/lib/options_by_example.rb +30 -70
- metadata +3 -2
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,6 +11,11 @@ __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.3.0
|
15
|
+
|
16
|
+
- Extract parsing functionality into class
|
17
|
+
- Better error messages
|
18
|
+
|
14
19
|
1.2.0
|
15
20
|
|
16
21
|
- Ensure compatibility with Ruby versions 1.9.3 and newer
|
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,84 +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('-', ''),
|
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
|
-
min_length = @argument_names_required.size
|
110
|
-
max_length = @argument_names_optional.size + min_length
|
111
|
-
raise "Expected #{min_length}#{"-#{max_length}" if max_length > min_length} arguments, got more"
|
112
|
-
end
|
70
|
+
@options = parser.options
|
71
|
+
@arguments = parser.arguments
|
113
72
|
|
114
73
|
return self
|
115
|
-
|
116
74
|
rescue RuntimeError => err
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
puts @
|
121
|
-
exit
|
75
|
+
raise unless @settings[:exit_on_error]
|
76
|
+
|
77
|
+
if err.message == "puts @usage_message"
|
78
|
+
puts @usage_message
|
122
79
|
else
|
123
|
-
|
80
|
+
puts "ERROR: #{err.message}"
|
124
81
|
end
|
82
|
+
|
83
|
+
exit 1
|
125
84
|
end
|
126
85
|
|
86
|
+
|
127
87
|
def method_missing(sym, *args, &block)
|
128
88
|
case sym
|
129
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:
|