help_parser 3.0.1 → 4.0.0
Sign up to get free protection for your applications and to get access to all the features.
- 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