ark-cli 0.6.0 → 0.6.8

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: a54741976deb70204c112ab5ee3ec996463ddaf6
4
- data.tar.gz: bf135aed0b692a6d591dff1160773a83b896ad32
3
+ metadata.gz: 7c49459aed6110db0623fdd6a8404476f396e7c5
4
+ data.tar.gz: 0d928dbdf78fc4f50b8bc304b7bca4ba2897048c
5
5
  SHA512:
6
- metadata.gz: b84c3e4562701df554ab07d821140ff4cb02c765c6aebe8d633f4a54f30e595b8d5f7eda3bf0c3b6046f2c4bca6c03eee032c790139cdcc5b431f7ef72207811
7
- data.tar.gz: 819682f6427f6d4e0397a43f11ae91cb83ad54b6e4ef411dacded492c7384a6bfa43af917248cb9b1ff6f4fd8a57986f31ee3debb82f3ba59ad5242aa26b95c2
6
+ metadata.gz: dafceeb2dba2b2d27f70124c298814a750841dc51f8140c81df519cb5b6cdfabc3af7e2b8adc30e0a8f5bfafddb379bfac67fb4d3ce26d79cd40b7134927c09a
7
+ data.tar.gz: aad0ec6ed111baa93a6e430dc2b3050b474176c7f888e0d8252cfa551ecf5a5e69d0efd864049383ecfebdcacdbf93d3e168b271db73a3a0b43d50ea4e022823
@@ -18,8 +18,9 @@
18
18
  require 'ark/utility'
19
19
  include Ark::Log
20
20
 
21
- require_relative 'cli/interface.rb'
21
+ require_relative 'cli/argument.rb'
22
22
  require_relative 'cli/option.rb'
23
+ require_relative 'cli/interface.rb'
23
24
  require_relative 'cli/spec.rb'
24
25
  require_relative 'cli/report.rb'
25
26
 
@@ -0,0 +1,127 @@
1
+ module Ark # :nodoc:
2
+ module CLI
3
+
4
+ # Represents an argument, either to the program itself or for options which
5
+ # take arguments.
6
+ class Argument
7
+
8
+ # Raised when invalid argument syntax is given
9
+ class ArgumentSyntaxError < ArgumentError
10
+ end
11
+
12
+ # Raised when an argument value is set improperly
13
+ class ArgumentSetError < RuntimeError
14
+ end
15
+
16
+ # Parse an argument name and return an Argument object
17
+ def self.parse(arg)
18
+ arg = arg.to_s
19
+ name = self.strip_arg(arg)
20
+ if self.has_default?(arg)
21
+ default = self.parse_default(arg)
22
+ return Argument.new(name, default)
23
+ elsif self.is_glob?(arg)
24
+ return Argument.new(name, variad: true)
25
+ else
26
+ return Argument.new(name)
27
+ end
28
+ end
29
+
30
+ # Strip any special syntax from a given argument name
31
+ def self.strip_arg(arg)
32
+ return arg.to_s[/^(\S+?)(:|\.\.\.|$)/, 1]
33
+ end
34
+
35
+ # Return +true+ if the given argument has a default value, like +'arg:defaultvalue'+
36
+ def self.has_default?(arg)
37
+ return !arg[/^\S+?:.+/].nil?
38
+ end
39
+
40
+ # Parse the default value from an arg with one
41
+ def self.parse_default(arg)
42
+ return arg[/^.+?:(.+)/, 1]
43
+ end
44
+
45
+ # Return +true+ if the given argument is a glob, like +'arg...'+
46
+ def self.is_glob?(arg)
47
+ return !arg[/\.\.\.$/].nil?
48
+ end
49
+
50
+ # Validate an option name. Names must be alphanumeric, and must begin with a
51
+ # letter.
52
+ def self.valid_name?(name)
53
+ return !name.to_s[/^[[:alpha:]][[:alnum:]]+$/].nil?
54
+ end
55
+
56
+ # Initialize a new Argument object. +name+ must be alphanumeric and must begin
57
+ # with a letter. If this argument is unfulfilled, +default+ will be returned
58
+ # as its value, if default is non-nil. If +variad+ is true, then this argument
59
+ # will act as a glob for all trailing args.
60
+ def initialize(name, default=nil, variad: false)
61
+ unless self.class.valid_name?(name)
62
+ raise ArgumentSyntaxError, "Invalid argument name: #{name}"
63
+ end
64
+ @name = name.to_s
65
+ @default = default
66
+ @variad = variad
67
+ if self.variadic?
68
+ @value = []
69
+ else
70
+ @value = nil
71
+ end
72
+ end
73
+
74
+ # Return the name of this Argument
75
+ attr_reader :name
76
+
77
+ # Return the value for this argument. The default value will be returned if
78
+ # the argument is unset and the default is non-nil. If the argument is unset
79
+ # and there is no default, return nil.
80
+ def value
81
+ if @value.nil?
82
+ return @default
83
+ else
84
+ return @value
85
+ end
86
+ end
87
+
88
+ # Push +val+ onto this argument. Only valid for variadic args. For normal
89
+ # arguments, use #set instead.
90
+ def push(val)
91
+ unless self.variadic?
92
+ raise ArgumentSetError, "Cannot push onto a normal argument. Use the #set method instead."
93
+ end
94
+ @value << val
95
+ end
96
+
97
+ # Set the value for this argument to +val+. Only valid for non-variadic
98
+ # arguments. For variadic args, use #push instead.
99
+ def set(val)
100
+ if self.variadic?
101
+ raise ArgumentSetError, "Cannot set the value of a glob, use the #push method instead."
102
+ end
103
+ @value = val
104
+ end
105
+
106
+ # Return true if this argument is a glob
107
+ def variadic?
108
+ return @variad
109
+ end
110
+
111
+ # Return true if this argument has a default value. Variadic arguments always
112
+ # return true
113
+ def has_default?
114
+ return !@default.nil? || self.variadic?
115
+ end
116
+
117
+ # Return true if this argument has been given a value, or if it has a default
118
+ # value. Variadic arguments will always return true, since they are never
119
+ # required and always have a default value of +[]+
120
+ def fulfilled?
121
+ return !@value.nil? || self.has_default?
122
+ end
123
+ end
124
+
125
+ end # module CLI
126
+ end # module Ark
127
+
@@ -32,7 +32,10 @@ class Interface
32
32
  @input = input
33
33
  @spec = Spec.new
34
34
  yield @spec
35
- @spec.opt :help, :h, desc: "Print usage information"
35
+ @spec.opt :help, :h, desc: "Print usage information and exit"
36
+ if @spec.get_version
37
+ @spec.opt :version, :V, desc: "Print version information and exit"
38
+ end
36
39
  self.parse
37
40
  end
38
41
 
@@ -40,13 +43,13 @@ class Interface
40
43
  def parse()
41
44
  taking_options = true
42
45
  last_opt = nil
43
- refargs = @spec.get_args.clone
44
46
 
45
47
  args = []
46
48
  trailing = []
47
49
  named = {}
48
50
  options = {}
49
51
  counts = {}
52
+ arg_index = 0
50
53
 
51
54
  @input.each do |word|
52
55
  dbg "Parsing '#{word}'"
@@ -82,48 +85,47 @@ class Interface
82
85
  else
83
86
  dbg "Parsed output arg", 1
84
87
  taking_options = false
85
- args << word
86
- key = refargs.shift
87
- if key
88
- if key == @spec.get_variad
89
- named[key] = []
90
- named[key] << word
91
- else
92
- named[key] = word
93
- end
88
+ arg = @spec.get_args.values[arg_index]
89
+ arg_index += 1
90
+ if arg && !arg.variadic?
91
+ arg.set(word)
94
92
  elsif @spec.is_variadic?
95
- named[@spec.get_variad] << word
93
+ @spec.get_variad.push(word)
96
94
  else
97
95
  trailing << word
98
96
  end
99
97
  end
100
98
  end
101
99
  end
102
- if @spec.trailing_error && !trailing.empty?
103
- raise InterfaceError, "Error: got trailing option(s): #{trailing.join(', ')}"
104
- end
100
+
105
101
  @spec.get_opts.each do |name, opt|
106
102
  options[name] = opt.value
107
103
  counts[name] = opt.count
108
104
  end
109
- @spec.get_args.each do |name|
110
- if named[name].nil?
111
- if @spec.has_default?(name)
112
- named[name] = @spec.get_default(name)
113
- args << named[name]
114
- else
115
- unless @spec.is_variadic? && @spec.get_variad == name
116
- raise InterfaceError, "Required argument '#{name.upcase}' was not given."
117
- end
118
- end
119
- end
120
- end
121
- if @spec.is_variadic?
122
- named[@spec.get_variad] ||= []
105
+
106
+ @spec.get_args.each do |name, arg|
107
+ args << arg.value
108
+ named[name] = arg.value
123
109
  end
110
+ args.flatten!
111
+ args += trailing
112
+
124
113
  @report = Report.new(args, named, trailing, options, counts)
114
+
125
115
  if @report.opt(:help)
126
- self.print_usage()
116
+ self.print_usage
117
+ end
118
+
119
+ if @report.opt(:version)
120
+ self.print_version
121
+ end
122
+
123
+ unless @spec.get_args.values.all? {|arg| arg.fulfilled? }
124
+ raise InterfaceError, "Required argument '#{name.upcase}' was not given."
125
+ end
126
+
127
+ if @spec.trailing_error && !@report.trailing.empty?
128
+ raise InterfaceError, "Error: got trailing argument(s): #{trailing.join(', ')}"
127
129
  end
128
130
  end
129
131
 
@@ -131,69 +133,72 @@ class Interface
131
133
  def usage()
132
134
  tb = TextBuilder.new()
133
135
 
134
- tb.next 'USAGE:'
135
- tb.push @spec.get_name if @spec.get_name
136
+ tb.next('USAGE:')
137
+ tb.push(@spec.get_name) if @spec.get_name
136
138
 
137
139
  if @spec.get_opts.values.uniq.length < 5 || @spec.option_listing
138
140
  @spec.get_opts.values.uniq.each do |opt|
139
- tb.push "[#{opt.header}]"
141
+ tb.push("[#{opt.header}]")
140
142
  end
141
143
  else
142
- tb.push '[OPTION...]'
144
+ tb.push('[OPTION...]')
143
145
  end
144
146
 
145
147
  if @spec.has_args?
146
148
  if @spec.is_variadic?
147
- singles = @spec.get_args[0..-2].map do |a|
148
- if @spec.has_default?(a)
149
- a = "[#{a}]"
149
+ singles = @spec.get_args.values[0..-2].map do |a|
150
+ name = a.name
151
+ if a.has_default?
152
+ name = "[#{name}]"
150
153
  end
151
- a.upcase
154
+ name.upcase
152
155
  end
153
- tb.push singles
154
- v = @spec.get_args.last.upcase
155
- tb.push "[#{v}1 #{v}2...]"
156
+ tb.push(singles)
157
+ v = @spec.get_variad.name.upcase
158
+ tb.push("[#{v}1 #{v}2...]")
156
159
  else
157
- argmap = @spec.get_args.map do |a|
158
- if @spec.has_default?(a)
159
- a = "[#{a}]"
160
+ argmap = @spec.get_args.values.map do |a|
161
+ name = a.name
162
+ if a.has_default?
163
+ name = "[#{name}]"
160
164
  end
161
- a.upcase
165
+ name.upcase
162
166
  end
163
- tb.push argmap
167
+ tb.push(argmap)
164
168
  end
165
169
  end
166
170
 
167
- tb.wrap indent: 7, indent_after: true, segments: true
171
+ tb.wrap(indent: 7, indent_after: true, segments: true)
168
172
 
169
173
  if @spec.get_desc
170
- tb.skip @spec.get_desc
174
+ tb.skip(@spec.get_desc)
171
175
  tb.wrap(indent: 4)
172
176
  end
173
177
 
174
- tb.skip 'OPTIONS:'
175
- tb.skip
178
+ tb.skip('OPTIONS:').skip
176
179
 
177
180
  @spec.get_opts.values.uniq.each do |opt|
178
- tb.indent 4
179
- tb.push opt.header
181
+ tb.indent(4).push(opt.header)
180
182
  if opt.desc
181
- tb.next
182
- tb.indent 8
183
- tb.push opt.desc
183
+ tb.next.indent(8).push(opt.desc)
184
184
  end
185
185
  tb.skip
186
186
  end
187
187
 
188
- return tb.print
188
+ return tb.to_s
189
189
  end
190
190
 
191
191
  # Print usage information and exit
192
- def print_usage()
192
+ def print_usage
193
193
  puts self.usage
194
194
  exit 0
195
195
  end
196
196
 
197
+ def print_version
198
+ puts @spec.get_version
199
+ exit 0
200
+ end
201
+
197
202
  end # class Interface
198
203
 
199
204
  end # module CLI
@@ -1,4 +1,4 @@
1
- module Ark
1
+ module Ark # :nodoc:
2
2
  module CLI
3
3
 
4
4
  # Represents an option and stores the option's current state, as well as
@@ -9,15 +9,14 @@ class Option
9
9
  # [+keys+] A list of names this option will be identified by
10
10
  # [+args+] A list of argument named this option will expect
11
11
  # [+desc+] A short description of this option
12
- def initialize(long, short=nil, args=nil, defaults=nil, desc=nil)
13
- @long = long
14
- @short = short
15
- @args = args || []
16
- @defaults = defaults || {}
17
- @vals = []
18
- @flag = false
19
- @count = 0
20
- @desc = desc || ''
12
+ def initialize(long, short=nil, args=nil, desc=nil)
13
+ @long = long
14
+ @short = short
15
+ @args = args || []
16
+ @flag = false
17
+ @count = 0
18
+ @desc = desc || ''
19
+ @arg_index = 0
21
20
  end
22
21
 
23
22
  # A count of how many times this option has been given on the command line.
@@ -39,19 +38,14 @@ class Option
39
38
  return @args.length
40
39
  end
41
40
 
42
- # Return a count of how many arguments this option still expects
43
- def vals_needed()
44
- if self.flag?
45
- return 0
46
- else
47
- return @args.length - @vals.length
48
- end
49
- end
50
-
51
41
  # True if this option has received all the arguments it expects, or if this
52
42
  # option expects no arguments
53
43
  def full?
54
- return self.vals_needed == 0
44
+ if self.flag?
45
+ return true
46
+ else
47
+ return (@args.length - @arg_index) < 1
48
+ end
55
49
  end
56
50
 
57
51
  # True if this option expects no arguments; opposite of #has_args?
@@ -71,9 +65,11 @@ class Option
71
65
  end
72
66
  end
73
67
 
74
- # Pass an argument +arg+ to this option
75
- def push(arg)
76
- @vals << arg
68
+ # Pass an argument to this option
69
+ def push(val)
70
+ arg = @args[@arg_index]
71
+ @arg_index += 1
72
+ arg.set(val)
77
73
  end
78
74
 
79
75
  # Return the current value of this option
@@ -81,17 +77,9 @@ class Option
81
77
  if self.flag?
82
78
  return @flag
83
79
  else
84
- if self.full? && @vals.length == 1
85
- return @vals[0]
86
- elsif self.full?
87
- return @vals
88
- else
89
- if !@defaults.compact.empty?
90
- return @defaults.length > 1 ? @defaults : @defaults.first
91
- else
92
- return nil
93
- end
94
- end
80
+ vals = @args.map {|a| a.value }
81
+ vals = vals.first if vals.length == 1
82
+ return vals
95
83
  end
96
84
  end
97
85
 
@@ -105,7 +93,7 @@ class Option
105
93
  if self.flag?
106
94
  args = ''
107
95
  else
108
- args = ' ' + @args.join(', ').upcase
96
+ args = ' ' + @args.map {|a| a.name }.join(', ').upcase
109
97
  end
110
98
  short = @short ? "-#{@short} " : ''
111
99
  return "#{short}--#{@long}#{args}"
@@ -27,6 +27,11 @@ class Report
27
27
  return @trailing
28
28
  end
29
29
 
30
+ # Get a hash of all options and their values
31
+ def opts
32
+ return @options
33
+ end
34
+
30
35
  # Get the value of an option by +name+
31
36
  def opt(name)
32
37
  return @options[name.to_s]
@@ -17,7 +17,7 @@ class Spec
17
17
 
18
18
  # Initialize a bare interface +Spec+
19
19
  def initialize()
20
- @args = []
20
+ @arguments = {}
21
21
  @options = {}
22
22
  @variadic = false
23
23
  @option_listing = false
@@ -31,49 +31,6 @@ class Spec
31
31
  # If true, an error will be raised if trailing arguments are given
32
32
  attr_reader :trailing_error
33
33
 
34
- private
35
-
36
- # Strip any special syntax from a given argument. Used when parsing arguments
37
- def strip(arg)
38
- return arg[/^(\S+?)(:|\.\.\.|$)/, 1]
39
- end
40
-
41
- # Return +true+ if the given argument has a default value, like +'arg:defaultvalue'+
42
- def defaulted?(arg)
43
- return !arg[/^\S+?:.+/].nil?
44
- end
45
-
46
- # Parse the default value from an arg with one
47
- def parse_default(arg)
48
- return arg[/^.+?:(.+)/, 1]
49
- end
50
-
51
- # Return +true+ if the given argument is a glob, like +'arg...'+
52
- def variadic?(arg)
53
- return !arg[/\.\.\.$/].nil?
54
- end
55
-
56
- # Parse a given argument, interpreting any special syntax, and storing
57
- # argument information as needed
58
- def parse_arg(arg, default: nil, last: false)
59
- stripped = strip(arg)
60
- @args << stripped
61
- if defaulted?(arg)
62
- @defaults[stripped] = parse_default(arg)
63
- end
64
- if variadic?(arg)
65
- if last
66
- @variadic = true
67
- @variad = stripped
68
- else
69
- raise ArgumentSyntaxError,
70
- "Variadic arguments must come last. Offending variad is '#{arg}'"
71
- end
72
- end
73
- end
74
-
75
- public
76
-
77
34
  # Get the name defined for this spec
78
35
  def get_name
79
36
  return @name
@@ -86,7 +43,12 @@ class Spec
86
43
 
87
44
  # Get an array of argument names defined for this spec
88
45
  def get_args
89
- return @args
46
+ return @arguments
47
+ end
48
+
49
+ # Get version information defined for this spec
50
+ def get_version
51
+ return @version
90
52
  end
91
53
 
92
54
  # Return +true+ if this interface has any options defined for it
@@ -108,6 +70,15 @@ class Spec
108
70
  return @options[name]
109
71
  end
110
72
 
73
+ # Get an +Argument+ object for the given argument +name+
74
+ def get_arg(name)
75
+ name = name.to_s
76
+ if !@arguments.keys.member?(name)
77
+ raise NoSuchArgumentError, "Error, no such argument: '#{name}'"
78
+ end
79
+ return @arguments[name]
80
+ end
81
+
111
82
  # Return +true+ if this interface is variadic
112
83
  def is_variadic?
113
84
  return @variadic
@@ -118,34 +89,21 @@ class Spec
118
89
  return @variad
119
90
  end
120
91
 
121
- # Return +true+ if the given argument +arg+ has a default value
122
- def has_default?(arg)
123
- @defaults.key?(arg.to_s)
124
- end
125
-
126
- # Return a hash of all default values
127
- def get_defaults
128
- return @defaults
129
- end
130
-
131
- # Return the default value of the given argument +arg+
132
- def get_default(arg)
133
- @defaults[arg.to_s]
134
- end
135
-
136
92
  # Return +true+ if this interface has any arguments defined
137
93
  def has_args?
138
- @args.length > 0
94
+ @arguments.length > 0
139
95
  end
140
96
 
141
97
  # Specify general information about the program
142
98
  # [+name+] Name of the program
143
99
  # [+desc+] Short description of the program
144
100
  # [+args+] A list of named arguments
145
- def header(name: nil, desc: nil, args: [])
101
+ # [+version+] Current version information for the program
102
+ def header(name: nil, desc: nil, args: [], version: nil)
146
103
  self.name(name)
147
104
  self.desc(desc)
148
105
  self.args(args)
106
+ self.version(version)
149
107
  end
150
108
 
151
109
  # Set the name of the program to +str+
@@ -160,16 +118,26 @@ class Spec
160
118
 
161
119
  # Define what arguments the program will accept
162
120
  def args(*input)
163
- @args = []
164
- @defaults = {}
165
-
121
+ @arguments = {}
166
122
  input.flatten.each_with_index do |item, i|
167
123
  item = item.to_s
168
124
  last = (input.length - (i + 1)) == 0
169
- parse_arg(item, last: last)
125
+ arg = Argument.parse(item)
126
+ if arg.variadic?
127
+ if !last
128
+ raise ArgumentSyntaxError, "Variadic arguments must come last. Offending variad is '#{arg}'"
129
+ else
130
+ @variadic = true
131
+ @variad = arg
132
+ end
133
+ end
134
+ @arguments[arg.name] = arg
170
135
  end
136
+ end
171
137
 
172
- @refargs = @args.clone
138
+ # Set the version information for the program to +str+
139
+ def version(str)
140
+ @version = str.to_s if str
173
141
  end
174
142
 
175
143
  # Define an Option
@@ -180,20 +148,8 @@ class Spec
180
148
  long = long.to_s
181
149
  short = short.to_s if short
182
150
  args = [args] if args.is_a?(String)
183
- defaults = []
184
- if args
185
- args.map! do |arg|
186
- arg = arg.to_s
187
- if defaulted?(arg)
188
- defaults << parse_default(arg)
189
- arg = strip(arg)
190
- else
191
- defaults << nil
192
- end
193
- arg
194
- end
195
- end
196
- o = Option.new(long, short, args, defaults, desc)
151
+ args.map! {|a| Argument.parse(a) } if args
152
+ o = Option.new(long, short, args, desc)
197
153
  @options[long] = o
198
154
  @options[short] = o if short
199
155
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ark-cli
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.6.0
4
+ version: 0.6.8
5
5
  platform: ruby
6
6
  authors:
7
7
  - Macquarie Sharpless
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-09-09 00:00:00.000000000 Z
11
+ date: 2015-09-17 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: ark-util
@@ -16,20 +16,20 @@ dependencies:
16
16
  requirements:
17
17
  - - "~>"
18
18
  - !ruby/object:Gem::Version
19
- version: '0.3'
19
+ version: '0.4'
20
20
  - - ">="
21
21
  - !ruby/object:Gem::Version
22
- version: 0.3.0
22
+ version: 0.4.0
23
23
  type: :runtime
24
24
  prerelease: false
25
25
  version_requirements: !ruby/object:Gem::Requirement
26
26
  requirements:
27
27
  - - "~>"
28
28
  - !ruby/object:Gem::Version
29
- version: '0.3'
29
+ version: '0.4'
30
30
  - - ">="
31
31
  - !ruby/object:Gem::Version
32
- version: 0.3.0
32
+ version: 0.4.0
33
33
  description: |
34
34
  A library for parsing options and arguments from the commandline. Parses ARGV
35
35
  and returns an object holding information about what options were set on the
@@ -42,6 +42,7 @@ extra_rdoc_files: []
42
42
  files:
43
43
  - README.md
44
44
  - lib/ark/cli.rb
45
+ - lib/ark/cli/argument.rb
45
46
  - lib/ark/cli/interface.rb
46
47
  - lib/ark/cli/option.rb
47
48
  - lib/ark/cli/report.rb