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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 33ad117bb5e036d38e871d90ea4a6434c1c004f1
4
- data.tar.gz: 2e94e3e19e4c763bbf67ae4dc958b4d6dcfecf8c
3
+ metadata.gz: 2b2c0821ccb920314039d7e8f92851e7be14382a
4
+ data.tar.gz: f5184113ef5d80ae592c2f923d895527d078d954
5
5
  SHA512:
6
- metadata.gz: 05bda5c57650c34545975a91bce8b87218dde3a7d52c23ccaf539bb037f8de7560071daedb8bf564bb1e30d68121438bec43aa7ea853c161da0f1837c1c5cc13
7
- data.tar.gz: c4e7bc126d53c3df6bb3f9c9c83b1882da560536f442dca83c040c87ee7088a33b4983ab095873bf1ac77d9ea711be33a6808fae93649dcbddf6c19a11fe14e0
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,9 +1,9 @@
1
- require 'help_parser/version'
2
1
  require 'help_parser/constants'
3
2
  require 'help_parser/pattern'
4
3
  require 'help_parser/usage'
5
4
  require 'help_parser/help_parser'
6
5
  HelpParser = HELP_PARSER::HelpParser
6
+ HELP_PARSER::VERSION = '4.0.0'
7
7
 
8
8
  # Requires:
9
9
  #`ruby`
@@ -1,30 +1,75 @@
1
1
  module HELP_PARSER # Constants
2
2
 
3
- NOTES,USAGE,TYPES = 'notes','usage','types'
4
- Z,M,E,P,Q,I = '','-','=','[',']',''
5
- H,LH,V,LV = '-h','--help','-v','--version'
3
+ ### OPTIONS LANGUAGE ###
6
4
 
7
- S,SNS = /\s/,/\s*\n\s*/
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
- FLOAT,INT = 'Float', 'Int'
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
- # Errors
21
- class UsageError < StandardError
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 HelpFormatError < StandardError
60
+ class HelpError < RuntimeError
24
61
  end
25
- class NoMatch < StandardError
62
+ class NoMatch < RuntimeError
26
63
  end
27
- class SoftwareError < StandardError
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
- SOFTWARE_ERROR_MSG = 'please submit a bug report'
74
+
30
75
  end
@@ -1,71 +1,99 @@
1
1
  module HELP_PARSER
2
- # HelpParser Hash will act as if a GoLang map[string]string.
3
- class HelpParser < Hash
4
- def consume(argv)
5
- n = 0
6
- while a = argv.shift
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
- self[M] = argv
10
- break # end of parse
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
- if a[1]==M
13
- k,v = a.split(E,2) # split by '='
14
- self[k] = (v)? v : Z # ''
15
- else
16
- # set each flag
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
- self[n.to_s] = a
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
- consume (argv.nil?)? [File.basename($0)]+ARGV : argv
30
- if version and (self[V] or self[LV]) # -v or --version
31
- puts version
32
- exit(0)
33
- end
34
- if help
35
- if self[H] or self[LH] # -h or --help
36
- puts help
37
- exit(0)
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
- begin
40
- Usage.new(help).validate(self)
41
- rescue UsageError
42
- $stderr.puts COLOR[1]+$!.message+COLOR[0]
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==0
54
- m=mthd.to_s
55
- case m[-1]
56
- when '?'
57
- if m.length > 2
58
- return !self[M+M+m[0..-2]].nil?
59
- else
60
- return !self[M+m[0]].nil?
61
- end
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
@@ -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 /^:(.*?)([+]?)$/ # it's a selection
20
- list,plus = @usage[$1].flatten.select{|_|_[0]==M},$2
21
- raise NoMatch unless list.include?(@keys[i])
22
- if plus=='+'
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 /^<(.*)>([+]?)$/ # it's a variable
28
- word,plus = $1,$2
29
- raise NoMatch if @keys[i].to_i==0
30
- if plus=='+'
31
- @cache[word] = [@options[@keys[i]]]
32
- while @keys[i+1].to_i > 0
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[@keys[i]])
108
+ @cache[word].push(@options[key])
35
109
  end
36
110
  else
37
- @cache[word] = @options[@keys[i]]
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 @keys[i]==token
116
+ raise NoMatch unless token == ((key.length>1)? MM : M)+key.to_s
42
117
  else
43
- raise NoMatch unless @options[@keys[i]]==token
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
- raise SoftwareError, SOFTWARE_ERROR_MSG
137
+ return true
119
138
  end
120
139
  end
121
140
  end
@@ -1,6 +1,31 @@
1
1
  module HELP_PARSER
2
- class Usage < Hash
3
- def parse(chars)
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 parse(chars) if c==P
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 = M # '-'
24
- self[name] = []
57
+ hsh,name = {},Z
25
58
  help.strip.split(SNS).each do |line|
26
- line.sub!(/\s*\t.*$/,'') # tabs marks comment
59
+ next if line[0]==C # skip comment lines
60
+ line.sub!(COMMENTARY,'') # strip out commentary
27
61
  case line
28
- when /^(\w+):$/
62
+ when SELECTION
29
63
  name = $1.downcase
30
64
  break if name==NOTES
31
- raise HelpFormatError, "#{name} section redefinition" if self[name]
32
- self[name] = []
33
- when /^\s*#/
34
- next
65
+ raise HelpError, SECTION_REDEFINITION+name if hsh[name]
66
+ hsh[name] = []
35
67
  else
36
- if name==USAGE
37
- self[USAGE].push(parse(line.chars))
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
- self[name].push line.split(/\s+/)
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,self)
47
- self[USAGE].each do |cmd|
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, 'Please match usage!'
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: 3.0.1
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: 2016-11-08 00:00:00.000000000 Z
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.rdoc
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.3.1p112 (2016-04-26 revision 54768) [x86_64-linux]'
46
+ - 'ruby: ruby 2.4.0p0 (2016-12-24 revision 57164) [x86_64-linux]'
51
47
  rubyforge_project:
52
- rubygems_version: 2.5.1
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
@@ -1,3 +0,0 @@
1
- module HELP_PARSER
2
- VERSION = '3.0.1'
3
- end