help_parser 3.0.1 → 4.0.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/README.md +151 -0
- data/lib/help_parser.rb +1 -1
- data/lib/help_parser/constants.rb +56 -11
- data/lib/help_parser/help_parser.rb +75 -47
- data/lib/help_parser/pattern.rb +91 -72
- data/lib/help_parser/usage.rb +65 -18
- metadata +7 -11
- data/README.rdoc +0 -63
- data/lib/help_parser/version.rb +0 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 2b2c0821ccb920314039d7e8f92851e7be14382a
|
4
|
+
data.tar.gz: f5184113ef5d80ae592c2f923d895527d078d954
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 1afa018ea4504b0dfc00a443e26dda59338f4d7aa0f68f64b72b4c61c1d05d5558a3be3da9427bb07e57152da2fd6c2c77caf2e37d62729a5e3f905df18ec711
|
7
|
+
data.tar.gz: 70bb2be1242c6c094bba0b61e209f7028a8cb47c63f30ed9a9c5e3088d19f23107aa55d872c89bd153dfe73201a32936c967809de63dc16c0ba4ec8e10c4f2ea
|
data/README.md
ADDED
@@ -0,0 +1,151 @@
|
|
1
|
+
# HelpParser
|
2
|
+
## Version 4!!!
|
3
|
+
|
4
|
+
* [github](https://www.github.com/carlosjhr64/help_parser)
|
5
|
+
* [rubygems](https://rubygems.org/gems/help_parser)
|
6
|
+
|
7
|
+
## DESCRIPTION:
|
8
|
+
HelpParser - Parses your help text to define your command line options.
|
9
|
+
|
10
|
+
## INSTALL:
|
11
|
+
|
12
|
+
$ sudo gem install help_parser
|
13
|
+
|
14
|
+
## LICENSE:
|
15
|
+
[MIT](https://en.wikipedia.org/wiki/MIT_License) Copyright (c) 2017 CarlosJHR64
|
16
|
+
|
17
|
+
## MINIMAL
|
18
|
+
|
19
|
+
#!/usr/bin/env ruby
|
20
|
+
require 'help_parser'
|
21
|
+
|
22
|
+
OPTIONS = HelpParser.new
|
23
|
+
p OPTIONS._hash
|
24
|
+
|
25
|
+
## SYNOPSIS:
|
26
|
+
|
27
|
+
#!/usr/bin/env ruby
|
28
|
+
require 'help_parser'
|
29
|
+
|
30
|
+
OPTIONS = HelpParser.new('1.2.3', <<-HELP)
|
31
|
+
Usage:
|
32
|
+
synopsis [:options] <x> <y>
|
33
|
+
Options:
|
34
|
+
--verbose
|
35
|
+
HELP
|
36
|
+
|
37
|
+
# Existentials via missing "?" methods
|
38
|
+
puts 'Whats x * y?' if OPTIONS.verbose?
|
39
|
+
|
40
|
+
# Values via missing methods.
|
41
|
+
puts OPTIONS.x.to_f * OPTIONS.y.to_f
|
42
|
+
|
43
|
+
## MORE:
|
44
|
+
|
45
|
+
#!/usr/bin/env ruby
|
46
|
+
require 'help_parser'
|
47
|
+
|
48
|
+
# Any version string, does not have to be semantic versioning.
|
49
|
+
VERSION = '1.2.3'
|
50
|
+
|
51
|
+
HELP = <<-HELP
|
52
|
+
### more ###########################################
|
53
|
+
# Lines starting with the pound sign are comments.
|
54
|
+
# HelpPaser parses the help text by sections
|
55
|
+
# with a heading of the form `<Section Name>:`.
|
56
|
+
# The Usage section gives the allowed forms of the
|
57
|
+
# command. Keys, arguments, and selectors in
|
58
|
+
# square bracket are optional and can be nested.
|
59
|
+
# A plus sign, `+`, marks the associated token as
|
60
|
+
# taking multiple values. Note that variable
|
61
|
+
# arguments have the form `<name>`.
|
62
|
+
####################################################
|
63
|
+
Usage:
|
64
|
+
more --wut <price> <count> [<name>]
|
65
|
+
more [:options] <args>+
|
66
|
+
####################################################
|
67
|
+
# Tipically you'll have an `Options:` section, but
|
68
|
+
# you can give the section any name.
|
69
|
+
# An `Options:` section should match an `:options`
|
70
|
+
# selector in at least one of your usages.
|
71
|
+
# Assigning values to keys via the command line
|
72
|
+
# is done in the form `--key=value`.
|
73
|
+
####################################################
|
74
|
+
Options:
|
75
|
+
-h --help \tTab marks comment
|
76
|
+
-v --version \tShort and long synonyms
|
77
|
+
--val 5.0 \tProvided default to long
|
78
|
+
--number \tLike --number=3
|
79
|
+
--usage
|
80
|
+
####################################################
|
81
|
+
# The `Defaults:` section can give default values
|
82
|
+
# to arguments and options.
|
83
|
+
# So for options you have a choice of where to set
|
84
|
+
# your defaults, but for arguments, it's done here.
|
85
|
+
####################################################
|
86
|
+
Defaults:
|
87
|
+
name Carlos \tDefault for name
|
88
|
+
####################################################
|
89
|
+
# HelpPaser has two automatic types, `Float` and
|
90
|
+
# `Int`, which will convert values from String to
|
91
|
+
# Floats and Integers repectively.
|
92
|
+
# You can also provide you own custom validation
|
93
|
+
# with regular expressions.
|
94
|
+
####################################################
|
95
|
+
Types:
|
96
|
+
Float --val price \tPredefined Float and Int
|
97
|
+
Int --number count
|
98
|
+
^[A-Z][a-z]+$ name \tUser defined type
|
99
|
+
####################################################
|
100
|
+
# The start of a `Notes:` section tells HelpParser
|
101
|
+
# to break out of it's parsing.
|
102
|
+
# Anything else written after this heading is of
|
103
|
+
# no consequence to HelpParser.
|
104
|
+
####################################################
|
105
|
+
Notes:
|
106
|
+
Stuff the help parser will ignore.
|
107
|
+
HELP
|
108
|
+
|
109
|
+
OPTIONS = HelpParser.new(VERSION, HELP)
|
110
|
+
|
111
|
+
# Short and long values via missing methods.
|
112
|
+
# Short and long existentials(booleans) via missing `?` methods.
|
113
|
+
# To avoid name collisions with command line argument and options,
|
114
|
+
# the HelpParser object was subclassed from BasicObject.
|
115
|
+
if OPTIONS.wut?
|
116
|
+
puts "Count: #{_=OPTIONS.count}\t#{_.class}"
|
117
|
+
puts "Price: #{_=OPTIONS.price}\t#{_.class}"
|
118
|
+
puts "Name: #{_=OPTIONS.name}\t#{_.class}"
|
119
|
+
else
|
120
|
+
puts "Args: #{OPTIONS.args.join(', ')}"
|
121
|
+
end
|
122
|
+
puts "Name: #{_=OPTIONS.name}\t#{_.class}"
|
123
|
+
puts "Val: #{_=OPTIONS.val}\t#{_.class}"
|
124
|
+
puts "Number: #{_=OPTIONS.number}\t#{_.class}"
|
125
|
+
|
126
|
+
# The objects's hash data method is `_hash`.
|
127
|
+
# You can also get the help text parsed data from `_usage._hash`
|
128
|
+
puts '###'
|
129
|
+
if OPTIONS.usage?
|
130
|
+
p OPTIONS._usage._hash
|
131
|
+
else
|
132
|
+
p OPTIONS._hash
|
133
|
+
end
|
134
|
+
|
135
|
+
## WHY?
|
136
|
+
Even with the really nice options parsers available,
|
137
|
+
for quick little scripts
|
138
|
+
I kept writing stuff like `ARGV.include?(key)`
|
139
|
+
or `options=ARGV.select{|a|a[0]=='-'}`.
|
140
|
+
Just to have to go back and reimplement with a proper options parser
|
141
|
+
when the little script ends up being useful and grows into something bigger.
|
142
|
+
And I was doing this after I had written my own previous versions of HelpParser.
|
143
|
+
So now I should have no excuse to just use HelpParser. Always:
|
144
|
+
|
145
|
+
require 'HelpParser'
|
146
|
+
OPTIONS = HelpParser.new # DONE!
|
147
|
+
|
148
|
+
Can't be any simpler.
|
149
|
+
|
150
|
+
As the script evolves... add version.. add help text..
|
151
|
+
no need to rework the code for not having started out with a proper options parser.
|
data/lib/help_parser.rb
CHANGED
@@ -1,30 +1,75 @@
|
|
1
1
|
module HELP_PARSER # Constants
|
2
2
|
|
3
|
-
|
4
|
-
Z,M,E,P,Q,I = '','-','=','[',']',''
|
5
|
-
H,LH,V,LV = '-h','--help','-v','--version'
|
3
|
+
### OPTIONS LANGUAGE ###
|
6
4
|
|
7
|
-
|
5
|
+
H,LH = :h,:help
|
6
|
+
V,LV = :v,:version
|
7
|
+
|
8
|
+
C,Z,M,E,P,Q,I = '#','-','-','=','[',']',''
|
9
|
+
MM = '--'
|
10
|
+
|
11
|
+
### HELP TEXT KEYS ###
|
12
|
+
|
13
|
+
NOTES,USAGE,TYPES,DEFAULTS = 'notes','usage','types','defaults'
|
14
|
+
|
15
|
+
### ERROR COLOR CODES ###
|
8
16
|
|
9
17
|
COLOR = [
|
10
18
|
"\e[0m", # no color
|
11
19
|
"\e[1;31m", # red
|
12
20
|
]
|
13
21
|
|
14
|
-
|
22
|
+
### TYPE MAPPINGS ###
|
23
|
+
|
24
|
+
FLOAT,INT = :Float, :Int
|
15
25
|
Types = {
|
16
26
|
INT => /^[+-]?\d+$/,
|
17
27
|
FLOAT => /^[+-]?\d+\.\d+$/,
|
18
28
|
}
|
19
29
|
|
20
|
-
|
21
|
-
|
30
|
+
### PATTERNS ###
|
31
|
+
|
32
|
+
S = /\s/ # space
|
33
|
+
SNS = /\s*\n\s*/
|
34
|
+
KEY = /^[-][-]?(.*)$/ #
|
35
|
+
SPS = /\s+/ # spaces
|
36
|
+
MNS = /^--?/
|
37
|
+
ERROR_KEY = /:\s+(\S+)/
|
38
|
+
COMMENTARY = /\s*\t.*$/ # tab marks start of field comment
|
39
|
+
|
40
|
+
SELECTION = /^(\w+):$/
|
41
|
+
SELECTION_P = /^:(.*?)([+]?)$/
|
42
|
+
|
43
|
+
VARIABLE = /^<(.*)>$/
|
44
|
+
VARIABLE_P = /^<(.*)>([+])?$/
|
45
|
+
|
46
|
+
### Error Messages ###
|
47
|
+
|
48
|
+
MATCH_USAGE = 'Please match usage!'
|
49
|
+
DUP_KEY = 'Duplicate Key: '
|
50
|
+
ILLEGAL_KEY = 'Illegal Key: '
|
51
|
+
SECTION_REDEFINITION = 'Section Redefinition: '
|
52
|
+
UNBALANCE_BRACKETS = 'Unbalanced Brackets in Usage: '
|
53
|
+
MISSING_MINUS = 'Missing Minus For Flag: '
|
54
|
+
NOT_A_FLAG = 'No Minus For Types Or Defaults: '
|
55
|
+
|
56
|
+
### Errors ###
|
57
|
+
|
58
|
+
class UsageError < RuntimeError
|
22
59
|
end
|
23
|
-
class
|
60
|
+
class HelpError < RuntimeError
|
24
61
|
end
|
25
|
-
class NoMatch <
|
62
|
+
class NoMatch < RuntimeError
|
26
63
|
end
|
27
|
-
|
64
|
+
|
65
|
+
### REFINEMENTS ###
|
66
|
+
|
67
|
+
module Refinements
|
68
|
+
refine Array do
|
69
|
+
def detect_duplicate
|
70
|
+
self.detect{|_|self.rindex(_) != self.index(_)}
|
71
|
+
end
|
72
|
+
end
|
28
73
|
end
|
29
|
-
|
74
|
+
|
30
75
|
end
|
@@ -1,71 +1,99 @@
|
|
1
1
|
module HELP_PARSER
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
2
|
+
|
3
|
+
# HelpParser should not alter argv
|
4
|
+
class HelpParser < BasicObject
|
5
|
+
USAGE_ERROR = lambda do |msg|
|
6
|
+
::Kernel::raise ::HELP_PARSER::UsageError,msg
|
7
|
+
end
|
8
|
+
|
9
|
+
Ms,Es = ::HELP_PARSER::M.to_sym, ::HELP_PARSER::E.to_sym
|
10
|
+
VALIDATE_KEY = lambda do |hsh,k|
|
11
|
+
USAGE_ERROR[DUP_KEY+k.to_s] if hsh.key?(k)
|
12
|
+
USAGE_ERROR[ILLEGAL_KEY+k.to_s] if Ms==k or Es==k
|
13
|
+
end
|
14
|
+
|
15
|
+
PARSE_ARGV = lambda do |argv|
|
16
|
+
hsh,n,i = {},0,0
|
17
|
+
argv.each do |a|
|
7
18
|
if a[0]==M # '-'
|
8
|
-
if a==M
|
9
|
-
|
10
|
-
|
19
|
+
break if a==M
|
20
|
+
if a[1]==M
|
21
|
+
s,v = a.split(E,2) # split by '='
|
22
|
+
k = s[2..-1].to_sym
|
23
|
+
VALIDATE_KEY[hsh,k]
|
24
|
+
hsh[k] = (v)? v : true
|
11
25
|
else
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
a[1..-1].chars.each{|c|self[M+c]=Z}
|
26
|
+
# set each flag
|
27
|
+
a[1..-1].chars.each do |c|
|
28
|
+
k = c.to_sym
|
29
|
+
VALIDATE_KEY[hsh,k]
|
30
|
+
hsh[k]=true
|
18
31
|
end
|
19
32
|
end
|
20
33
|
else
|
21
34
|
# the nth argument
|
22
|
-
|
35
|
+
hsh[n] = a
|
23
36
|
n+=1
|
24
37
|
end
|
38
|
+
i+=1
|
25
39
|
end
|
40
|
+
return hsh
|
26
41
|
end
|
27
42
|
|
28
43
|
def initialize(version=nil, help=nil, argv=nil)
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
if
|
36
|
-
|
37
|
-
|
44
|
+
begin
|
45
|
+
hsh = PARSE_ARGV[(argv.nil?)? [::File.basename($0)]+::ARGV : argv]
|
46
|
+
if version and (hsh.key?(V) or hsh.key?(LV)) # -v or --version
|
47
|
+
$stdout.puts version
|
48
|
+
::Kernel::exit(0)
|
49
|
+
end
|
50
|
+
if help
|
51
|
+
if hsh.key?(H) or hsh.key?(LH) # -h or --help
|
52
|
+
$stdout.puts help
|
53
|
+
::Kernel::exit(0)
|
54
|
+
end
|
55
|
+
@usage = ::HELP_PARSER::Usage.new(help).validate(hsh)
|
38
56
|
end
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
57
|
+
rescue ::HELP_PARSER::UsageError => e
|
58
|
+
$stderr.puts COLOR[1]+e.message+COLOR[0]
|
59
|
+
$stderr.puts help if help
|
60
|
+
::Kernel::exit(64) # Usage
|
61
|
+
rescue ::HELP_PARSER::HelpError => e
|
62
|
+
msg = e.message
|
63
|
+
$stderr.puts COLOR[1]+msg+COLOR[0]
|
64
|
+
if ERROR_KEY =~ msg
|
65
|
+
$stderr.puts help.gsub($1,COLOR[1]+$1+COLOR[0])
|
66
|
+
else
|
43
67
|
$stderr.puts help
|
44
|
-
exit(64) # Usage
|
45
68
|
end
|
69
|
+
::Kernel::exit(78) # Config
|
46
70
|
end
|
71
|
+
@hash = hsh.freeze
|
72
|
+
end
|
73
|
+
|
74
|
+
def [](k)
|
75
|
+
@hash[k]
|
76
|
+
end
|
77
|
+
|
78
|
+
def _hash
|
79
|
+
@hash
|
80
|
+
end
|
81
|
+
|
82
|
+
def _usage
|
83
|
+
@usage
|
47
84
|
end
|
48
85
|
|
49
|
-
# obj.m? :: !obj['--m'].nil?
|
50
|
-
# obj.method! :: obj['--method']
|
51
|
-
# obj.method :: obj['method']
|
52
86
|
def method_missing(mthd, *args, &block)
|
53
|
-
if args.length
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
when '!'
|
63
|
-
return self[M+M+m[0..-2]]
|
64
|
-
else
|
65
|
-
return self[m]
|
66
|
-
end
|
87
|
+
super if block or args.length > 0
|
88
|
+
m = mthd.to_s
|
89
|
+
case m[-1]
|
90
|
+
when '?'
|
91
|
+
@hash.key? m[0..-2].to_sym
|
92
|
+
when '!'
|
93
|
+
super
|
94
|
+
else
|
95
|
+
@hash[mthd]
|
67
96
|
end
|
68
|
-
super
|
69
97
|
end
|
70
98
|
end
|
71
99
|
end
|
data/lib/help_parser/pattern.rb
CHANGED
@@ -1,46 +1,121 @@
|
|
1
1
|
module HELP_PARSER
|
2
2
|
class Pattern
|
3
|
+
|
4
|
+
TYPE_MAP = lambda do |type,values,&block|
|
5
|
+
case type
|
6
|
+
when INT
|
7
|
+
case values
|
8
|
+
when Array
|
9
|
+
block.call values.map{|v|v.to_i}
|
10
|
+
when String
|
11
|
+
block.call values.to_i
|
12
|
+
end
|
13
|
+
when FLOAT
|
14
|
+
case values
|
15
|
+
when Array
|
16
|
+
block.call values.map{|v|v.to_f}
|
17
|
+
when String
|
18
|
+
block.call values.to_f
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
TYPE_VALIDATE = lambda do |values,tx,msg|
|
24
|
+
[*values].each do |value|
|
25
|
+
unless tx.match(value)
|
26
|
+
raise UsageError, sprintf(msg,value)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
3
31
|
def initialize(options,usage)
|
4
32
|
@options,@usage = options,usage
|
5
33
|
@keys = options.keys
|
6
34
|
@cache = {}
|
7
35
|
end
|
8
36
|
|
37
|
+
def types
|
38
|
+
@usage[TYPES].each do |type,*words|
|
39
|
+
tx = Types[type] || Regexp.new(type.to_s)
|
40
|
+
words.each do |word|
|
41
|
+
values = @options[word]
|
42
|
+
next if values.nil?
|
43
|
+
TYPE_VALIDATE[values,tx,"#{word}: %s !~ /#{type}/"]
|
44
|
+
TYPE_MAP.call(type,values){|m| @options[word]=m}
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def pad
|
50
|
+
@cache.each{|k,v|@options[k]=v}
|
51
|
+
@usage.each do |name,words|
|
52
|
+
next if [USAGE,TYPES,DEFAULTS].include?(name)
|
53
|
+
words.each do |word|
|
54
|
+
next unless word.length==2
|
55
|
+
k,v = word
|
56
|
+
if v.class==Symbol
|
57
|
+
# Synonyms
|
58
|
+
if @options[k].nil?
|
59
|
+
@options[k]=@options[v] if @options.key?(v)
|
60
|
+
else
|
61
|
+
@options[v]=@options[k] unless @options.key?(v)
|
62
|
+
end
|
63
|
+
else
|
64
|
+
# Default
|
65
|
+
@options[k]=v unless @options.key?(k)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
if @usage.key?(DEFAULTS)
|
70
|
+
@usage[DEFAULTS].each do |words|
|
71
|
+
*ks,v = words
|
72
|
+
ks.each do |k|
|
73
|
+
@options[k]=v.to_s unless @options.key?(k)
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
9
79
|
def _matches(pattern, i)
|
10
80
|
pattern.each do |token|
|
81
|
+
key = @keys[i] # Might be nil, but need to proceed...
|
11
82
|
case token
|
12
|
-
when Array # it's optional
|
83
|
+
when Array # it's optional, so a key.nil? might be ok
|
13
84
|
begin
|
14
85
|
i = _matches(token,i)
|
15
86
|
rescue NoMatch
|
16
87
|
# Ok, no problem!
|
17
88
|
end
|
18
89
|
next
|
19
|
-
when
|
20
|
-
|
21
|
-
|
22
|
-
|
90
|
+
when SELECTION_P # it's a selection
|
91
|
+
raise NoMatch if key.nil?
|
92
|
+
section,plus = $1,$2
|
93
|
+
list = @usage[section]
|
94
|
+
list = list.flatten
|
95
|
+
raise NoMatch unless list.include?(key)
|
96
|
+
unless plus.nil?
|
23
97
|
while list.include?(@keys[i+1])
|
24
98
|
i+=1
|
25
99
|
end
|
26
100
|
end
|
27
|
-
when
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
@cache[word] = [@options[
|
32
|
-
while
|
101
|
+
when VARIABLE_P # it's a variable
|
102
|
+
raise NoMatch if key.to_s.to_i==0 # nil.to_s.to_i==0 also works
|
103
|
+
word,plus = $1.to_sym,$2
|
104
|
+
unless plus.nil?
|
105
|
+
@cache[word] = [@options[key]]
|
106
|
+
while (key=@keys[i+1]).to_s.to_i > 0
|
33
107
|
i+=1
|
34
|
-
@cache[word].push(@options[
|
108
|
+
@cache[word].push(@options[key])
|
35
109
|
end
|
36
110
|
else
|
37
|
-
@cache[word] = @options[
|
111
|
+
@cache[word] = @options[key]
|
38
112
|
end
|
39
113
|
else # it's a literal
|
114
|
+
raise NoMatch if key.nil?
|
40
115
|
if token[0]==M
|
41
|
-
raise NoMatch unless
|
116
|
+
raise NoMatch unless token == ((key.length>1)? MM : M)+key.to_s
|
42
117
|
else
|
43
|
-
raise NoMatch unless @options[
|
118
|
+
raise NoMatch unless @options[key]==token
|
44
119
|
end
|
45
120
|
end
|
46
121
|
i+=1
|
@@ -48,74 +123,18 @@ module HELP_PARSER
|
|
48
123
|
return i
|
49
124
|
end
|
50
125
|
|
51
|
-
def pad
|
52
|
-
@cache.each{|k,v|@options[k]=v}
|
53
|
-
@usage.each do |name,words|
|
54
|
-
next if [M,USAGE,TYPES].include?(name)
|
55
|
-
words.each do |word|
|
56
|
-
next unless word.length==2
|
57
|
-
k,v = word
|
58
|
-
next unless @options[k].nil?
|
59
|
-
if k[0]==M
|
60
|
-
if k[1]==M
|
61
|
-
# long option default
|
62
|
-
@options[k] = v
|
63
|
-
elsif v[0]==M and !@options[v].nil?
|
64
|
-
# long option synonym
|
65
|
-
@options[k] = @options[v]
|
66
|
-
end
|
67
|
-
else
|
68
|
-
# argument default
|
69
|
-
@options[k] = v
|
70
|
-
end
|
71
|
-
end
|
72
|
-
end
|
73
|
-
end
|
74
|
-
|
75
|
-
def types
|
76
|
-
@usage[TYPES].each do |type,*words|
|
77
|
-
tx = Types[type] || Regexp.new(type)
|
78
|
-
words.each do |word|
|
79
|
-
values = @options[word]
|
80
|
-
next if values.nil?
|
81
|
-
[*values].each do |value|
|
82
|
-
unless tx.match(value)
|
83
|
-
raise UsageError, "ERROR: #{word}=#{value} is not /#{type}/"
|
84
|
-
end
|
85
|
-
end
|
86
|
-
case type
|
87
|
-
when INT
|
88
|
-
case values
|
89
|
-
when Array
|
90
|
-
@options[word]=values.map{|v|v.to_i}
|
91
|
-
when String
|
92
|
-
@options[word]=values.to_i
|
93
|
-
end
|
94
|
-
when FLOAT
|
95
|
-
case values
|
96
|
-
when Array
|
97
|
-
@options[word]=values.map{|v|v.to_f}
|
98
|
-
when String
|
99
|
-
@options[word]=values.to_f
|
100
|
-
end
|
101
|
-
end
|
102
|
-
end
|
103
|
-
end
|
104
|
-
end
|
105
|
-
|
106
126
|
def matches(pattern)
|
107
127
|
begin
|
108
128
|
i = _matches(pattern, 0)
|
109
129
|
raise NoMatch if i != @options.length
|
110
130
|
pad
|
111
131
|
types if @usage[TYPES]
|
112
|
-
return true
|
113
132
|
rescue NoMatch
|
114
133
|
return false
|
115
134
|
ensure
|
116
135
|
@cache.clear
|
117
136
|
end
|
118
|
-
|
137
|
+
return true
|
119
138
|
end
|
120
139
|
end
|
121
140
|
end
|
data/lib/help_parser/usage.rb
CHANGED
@@ -1,6 +1,31 @@
|
|
1
1
|
module HELP_PARSER
|
2
|
-
class Usage
|
3
|
-
|
2
|
+
class Usage
|
3
|
+
using HELP_PARSER::Refinements
|
4
|
+
|
5
|
+
DUPLICATES = lambda do |hsh|
|
6
|
+
a = hsh.select{|k| k!=TYPES and k!=DEFAULTS}.values.flatten.select{|e|e.class==Symbol}
|
7
|
+
if d = a.detect_duplicate
|
8
|
+
raise HelpError, DUP_KEY+d.to_s
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
VALIDATE_USAGE = lambda do |tokens|
|
13
|
+
seen = {}
|
14
|
+
tokens.flatten.each do |token|
|
15
|
+
t = token
|
16
|
+
if VARIABLE=~token
|
17
|
+
t = $1
|
18
|
+
elsif KEY=~token
|
19
|
+
t = $1
|
20
|
+
end
|
21
|
+
#raise HelpError, 'Duplicate Token: '+t if seen[t]
|
22
|
+
raise HelpError, DUP_KEY+t if seen[t]
|
23
|
+
seen[t]=true
|
24
|
+
end
|
25
|
+
return tokens
|
26
|
+
end
|
27
|
+
|
28
|
+
PARSE_USAGE = lambda do |chars|
|
4
29
|
tokens, token = [], I
|
5
30
|
while c = chars.shift
|
6
31
|
case c
|
@@ -9,7 +34,7 @@ module HELP_PARSER
|
|
9
34
|
tokens.push(token)
|
10
35
|
token = I
|
11
36
|
end
|
12
|
-
tokens.push
|
37
|
+
tokens.push PARSE_USAGE[chars] if c==P
|
13
38
|
return tokens if c==Q
|
14
39
|
else
|
15
40
|
token += c
|
@@ -19,35 +44,57 @@ module HELP_PARSER
|
|
19
44
|
return tokens
|
20
45
|
end
|
21
46
|
|
47
|
+
BALANCED = lambda do |chars|
|
48
|
+
count = 0
|
49
|
+
chars.each do |c|
|
50
|
+
if c==P then count+=1 elsif c==Q then count-=1 end
|
51
|
+
break if count<0
|
52
|
+
end
|
53
|
+
return count==0
|
54
|
+
end
|
55
|
+
|
22
56
|
def initialize(help)
|
23
|
-
name =
|
24
|
-
self[name] = []
|
57
|
+
hsh,name = {},Z
|
25
58
|
help.strip.split(SNS).each do |line|
|
26
|
-
line
|
59
|
+
next if line[0]==C # skip comment lines
|
60
|
+
line.sub!(COMMENTARY,'') # strip out commentary
|
27
61
|
case line
|
28
|
-
when
|
62
|
+
when SELECTION
|
29
63
|
name = $1.downcase
|
30
64
|
break if name==NOTES
|
31
|
-
raise
|
32
|
-
|
33
|
-
when /^\s*#/
|
34
|
-
next
|
65
|
+
raise HelpError, SECTION_REDEFINITION+name if hsh[name]
|
66
|
+
hsh[name] = []
|
35
67
|
else
|
36
|
-
|
37
|
-
|
68
|
+
case name
|
69
|
+
when Z
|
70
|
+
# Nothing to do
|
71
|
+
when USAGE
|
72
|
+
chars = line.chars
|
73
|
+
raise HelpError, UNBALANCE_BRACKETS+line unless BALANCED[chars]
|
74
|
+
hsh[USAGE].push(VALIDATE_USAGE[PARSE_USAGE[chars]])
|
75
|
+
when TYPES,DEFAULTS
|
76
|
+
raise HelpError, NOT_A_FLAG+line if line[0]==M
|
77
|
+
hsh[name].push line.split(SPS).map{|_|_.to_sym}
|
38
78
|
else
|
39
|
-
|
79
|
+
raise HelpError, MISSING_MINUS+line unless line[0]==M
|
80
|
+
hsh[name].push line.split(SPS).map{|_|(_[0]==M)? _.sub(MNS,'').to_sym : _}
|
40
81
|
end
|
41
82
|
end
|
42
83
|
end
|
84
|
+
DUPLICATES[hsh]
|
85
|
+
@hash = hsh.freeze
|
86
|
+
end
|
87
|
+
|
88
|
+
def _hash
|
89
|
+
@hash
|
43
90
|
end
|
44
91
|
|
45
92
|
def validate(options)
|
46
|
-
pattern = Pattern.new(options
|
47
|
-
|
48
|
-
return if pattern.matches(cmd)
|
93
|
+
pattern = Pattern.new(options,@hash)
|
94
|
+
@hash[USAGE].each do |cmd|
|
95
|
+
return self if pattern.matches(cmd)
|
49
96
|
end
|
50
|
-
raise UsageError,
|
97
|
+
raise UsageError, MATCH_USAGE
|
51
98
|
end
|
52
99
|
end
|
53
100
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: help_parser
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 4.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- carlosjhr64
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2017-03-01 00:00:00.000000000 Z
|
12
12
|
dependencies: []
|
13
13
|
description: 'HelpParser - Parses your help text to define your command line options.
|
14
14
|
|
@@ -16,24 +16,20 @@ description: 'HelpParser - Parses your help text to define your command line opt
|
|
16
16
|
email: carlosjhr64@gmail.com
|
17
17
|
executables: []
|
18
18
|
extensions: []
|
19
|
-
extra_rdoc_files:
|
20
|
-
- README.rdoc
|
19
|
+
extra_rdoc_files: []
|
21
20
|
files:
|
22
|
-
- README.
|
21
|
+
- README.md
|
23
22
|
- lib/help_parser.rb
|
24
23
|
- lib/help_parser/constants.rb
|
25
24
|
- lib/help_parser/help_parser.rb
|
26
25
|
- lib/help_parser/pattern.rb
|
27
26
|
- lib/help_parser/usage.rb
|
28
|
-
- lib/help_parser/version.rb
|
29
27
|
homepage: https://github.com/carlosjhr64/help_parser
|
30
28
|
licenses:
|
31
29
|
- MIT
|
32
30
|
metadata: {}
|
33
31
|
post_install_message:
|
34
|
-
rdoc_options:
|
35
|
-
- "--main"
|
36
|
-
- README.rdoc
|
32
|
+
rdoc_options: []
|
37
33
|
require_paths:
|
38
34
|
- lib
|
39
35
|
required_ruby_version: !ruby/object:Gem::Requirement
|
@@ -47,9 +43,9 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
47
43
|
- !ruby/object:Gem::Version
|
48
44
|
version: '0'
|
49
45
|
requirements:
|
50
|
-
- 'ruby: ruby 2.
|
46
|
+
- 'ruby: ruby 2.4.0p0 (2016-12-24 revision 57164) [x86_64-linux]'
|
51
47
|
rubyforge_project:
|
52
|
-
rubygems_version: 2.
|
48
|
+
rubygems_version: 2.6.8
|
53
49
|
signing_key:
|
54
50
|
specification_version: 4
|
55
51
|
summary: HelpParser - Parses your help text to define your command line options.
|
data/README.rdoc
DELETED
@@ -1,63 +0,0 @@
|
|
1
|
-
= HelpParser
|
2
|
-
|
3
|
-
This is version 3!
|
4
|
-
|
5
|
-
github :: https://www.github.com/carlosjhr64/help_parser
|
6
|
-
rubygems :: https://rubygems.org/gems/help_parser
|
7
|
-
|
8
|
-
== DESCRIPTION:
|
9
|
-
|
10
|
-
HelpParser - Parses your help text to define your command line options.
|
11
|
-
|
12
|
-
== FEATURES/PROBLEMS:
|
13
|
-
|
14
|
-
* long to short option mapping
|
15
|
-
|
16
|
-
== SYNOPSIS:
|
17
|
-
|
18
|
-
#!/usr/bin/env ruby
|
19
|
-
require 'help_parser'
|
20
|
-
VERSION = '1.2.3'
|
21
|
-
HELP = <<HELP
|
22
|
-
### cmd ###
|
23
|
-
Usage:
|
24
|
-
cmd --wut <price> <count> <name>
|
25
|
-
cmd [:options] <args>+
|
26
|
-
Options:
|
27
|
-
-h --help \tTab marks comment
|
28
|
-
-v --version\tShort and long synonyms
|
29
|
-
-q --quiet
|
30
|
-
-V --verbose
|
31
|
-
--val 5.0 \tProvided default to long
|
32
|
-
--number \tLike --number=3
|
33
|
-
Types:
|
34
|
-
Float --val price \tPredefined Float and Int
|
35
|
-
Int --number count
|
36
|
-
^[A-Z][a-z]+$ name \tUser defined type
|
37
|
-
Notes:
|
38
|
-
Stuff the help parser will ignore.
|
39
|
-
HELP
|
40
|
-
OPTIONS = HelpParser.new(VERSION, HELP)
|
41
|
-
# Shorts and longs as booleans via missing ? methods
|
42
|
-
"Got Options!" unless OPTIONS.q?
|
43
|
-
puts OPTIONS if OPTIONS.V?
|
44
|
-
# Long values via missing ! methods
|
45
|
-
puts "val = #{OPTIONS.val!}"
|
46
|
-
# Arguments via plain missing methods
|
47
|
-
if OPTIONS.wut?
|
48
|
-
puts "price = '#{OPTIONS.price}'"
|
49
|
-
puts "count = '#{OPTIONS.count}'"
|
50
|
-
puts "name = '#{OPTIONS.name}'"
|
51
|
-
else
|
52
|
-
puts "args = '#{OPTIONS.args.join(',')}'"
|
53
|
-
end
|
54
|
-
|
55
|
-
== INSTALL:
|
56
|
-
|
57
|
-
$ sudo gem install help_parser
|
58
|
-
|
59
|
-
== LICENSE:
|
60
|
-
|
61
|
-
(The "Can't touch this, b/c it's all mine!" License)
|
62
|
-
|
63
|
-
Copyright (c) 2016 CarlosJHR64
|
data/lib/help_parser/version.rb
DELETED