options_by_example 1.2.0 → 1.3.0
Sign up to get free protection for your applications and to get access to all the features.
- 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:
|