shellopts 2.0.0.pre.13 → 2.0.1
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/.gitignore +2 -1
- data/.ruby-version +1 -1
- data/README.md +201 -267
- data/TODO +46 -134
- data/doc/format.rb +95 -0
- data/doc/grammar.txt +27 -0
- data/doc/syntax.rb +110 -0
- data/doc/syntax.txt +10 -0
- data/lib/ext/array.rb +58 -5
- data/lib/ext/forward_to.rb +15 -0
- data/lib/ext/lcs.rb +34 -0
- data/lib/shellopts/analyzer.rb +130 -0
- data/lib/shellopts/ansi.rb +8 -0
- data/lib/shellopts/args.rb +29 -21
- data/lib/shellopts/argument_type.rb +139 -0
- data/lib/shellopts/dump.rb +158 -0
- data/lib/shellopts/formatter.rb +325 -0
- data/lib/shellopts/grammar.rb +375 -0
- data/lib/shellopts/interpreter.rb +103 -0
- data/lib/shellopts/lexer.rb +175 -0
- data/lib/shellopts/parser.rb +269 -82
- data/lib/shellopts/program.rb +279 -0
- data/lib/shellopts/renderer.rb +227 -0
- data/lib/shellopts/stack.rb +7 -0
- data/lib/shellopts/token.rb +44 -0
- data/lib/shellopts/version.rb +2 -2
- data/lib/shellopts.rb +439 -220
- data/main +1180 -0
- data/shellopts.gemspec +9 -15
- metadata +85 -42
- data/lib/main.rb +0 -1
- data/lib/shellopts/ast/command.rb +0 -41
- data/lib/shellopts/ast/node.rb +0 -37
- data/lib/shellopts/ast/option.rb +0 -21
- data/lib/shellopts/ast/program.rb +0 -14
- data/lib/shellopts/compiler.rb +0 -128
- data/lib/shellopts/generator.rb +0 -15
- data/lib/shellopts/grammar/command.rb +0 -80
- data/lib/shellopts/grammar/node.rb +0 -33
- data/lib/shellopts/grammar/option.rb +0 -66
- data/lib/shellopts/grammar/program.rb +0 -65
- data/lib/shellopts/idr.rb +0 -236
- data/lib/shellopts/main.rb +0 -10
- data/lib/shellopts/option_struct.rb +0 -148
- data/lib/shellopts/shellopts.rb +0 -123
@@ -1,66 +0,0 @@
|
|
1
|
-
module ShellOpts
|
2
|
-
module Grammar
|
3
|
-
# Models an Option
|
4
|
-
#
|
5
|
-
# Sets Node#key to the first long option name if present or else the first short option
|
6
|
-
class Option < Node
|
7
|
-
# List of short names (incl. '-')
|
8
|
-
attr_reader :short_names
|
9
|
-
|
10
|
-
# List of long names (incl. '--')
|
11
|
-
attr_reader :long_names
|
12
|
-
|
13
|
-
# Name of the key attribute (eg. if key is :all then key_name is '--all'
|
14
|
-
attr_reader :key_name
|
15
|
-
|
16
|
-
# List of flags (Symbol)
|
17
|
-
def flags() @flags.keys end
|
18
|
-
|
19
|
-
# Informal name of argument (eg. 'FILE'). nil if not present
|
20
|
-
attr_reader :label
|
21
|
-
|
22
|
-
# Initialize an option. Short and long names are arrays of the short/long
|
23
|
-
# option names (incl. the '-'/'--' prefix). It is assumed that at least
|
24
|
-
# one name is given. Flags is a list of symbolic flags. Allowed flags are
|
25
|
-
# :repeated, :argument, :optional, :integer, and :float. Note that
|
26
|
-
# there's no :string flag, it's status is inferred. label is the optional
|
27
|
-
# informal name of the option argument (eg. 'FILE') or nil if not present
|
28
|
-
def initialize(short_names, long_names, flags, label = nil)
|
29
|
-
@key_name = long_names.first || short_names.first
|
30
|
-
name = @key_name.sub(/^-+/, "")
|
31
|
-
super(name.to_sym, name)
|
32
|
-
@short_names, @long_names = short_names, long_names
|
33
|
-
@flags = flags.map { |flag| [flag, true] }.to_h
|
34
|
-
@label = label
|
35
|
-
end
|
36
|
-
|
37
|
-
# Array of option names with short names first and then the long names
|
38
|
-
def names() @short_names + @long_names end
|
39
|
-
|
40
|
-
# Array of names and the key
|
41
|
-
def identifiers() names + [key] end
|
42
|
-
|
43
|
-
# Return true if +ident+ is equal to any name or to key
|
44
|
-
def match?(ident) names.include?(ident) || ident == key end
|
45
|
-
|
46
|
-
# Flag query methods. Returns true if the flag is present and otherwise nil
|
47
|
-
def repeated?() @flags[:repeated] || false end
|
48
|
-
def argument?() @flags[:argument] || false end
|
49
|
-
def optional?() argument? && @flags[:optional] || false end
|
50
|
-
def string?() argument? && !integer? && !float? end
|
51
|
-
def integer?() argument? && @flags[:integer] || false end
|
52
|
-
def float?() argument? && @flags[:float] || false end
|
53
|
-
|
54
|
-
# :nocov:
|
55
|
-
def dump
|
56
|
-
super {
|
57
|
-
puts "short_names: #{short_names.inspect}"
|
58
|
-
puts "long_names: #{long_names.inspect}"
|
59
|
-
puts "flags: #{flags.inspect}"
|
60
|
-
puts "label: #{label.inspect}"
|
61
|
-
}
|
62
|
-
end
|
63
|
-
# :nocov:
|
64
|
-
end
|
65
|
-
end
|
66
|
-
end
|
@@ -1,65 +0,0 @@
|
|
1
|
-
module ShellOpts
|
2
|
-
module Grammar
|
3
|
-
# Program is the root object of the grammar
|
4
|
-
class Program < Command
|
5
|
-
# Array of non-option litteral arguments (ie. what comes after the double dash ('+--+') in
|
6
|
-
# the usage definition). Initially empty but filled out during compilation
|
7
|
-
attr_reader :args
|
8
|
-
|
9
|
-
# Initialize a top-level Program object
|
10
|
-
def initialize(name, option_list)
|
11
|
-
super(nil, name, option_list)
|
12
|
-
@args = []
|
13
|
-
end
|
14
|
-
|
15
|
-
# Usage string to be used in error messages. The string is kept short by
|
16
|
-
# only listing the shortest option (if there is more than one)
|
17
|
-
def usage
|
18
|
-
(
|
19
|
-
render_options(option_list) +
|
20
|
-
subcommand_list.map { |cmd| render_subcommand(cmd) } +
|
21
|
-
args
|
22
|
-
).flatten.join(" ")
|
23
|
-
end
|
24
|
-
|
25
|
-
# :nocov:
|
26
|
-
def dump(&block)
|
27
|
-
super {
|
28
|
-
puts "args: #{args.inspect}"
|
29
|
-
puts "usage: #{usage.inspect}"
|
30
|
-
}
|
31
|
-
end
|
32
|
-
# :nocov:
|
33
|
-
|
34
|
-
private
|
35
|
-
def render_subcommand(subcommand)
|
36
|
-
[subcommand.name] + render_options(subcommand.option_list) +
|
37
|
-
subcommand.subcommand_list.map { |cmd| render_subcommand(cmd) }.flatten
|
38
|
-
end
|
39
|
-
|
40
|
-
def render_options(options)
|
41
|
-
options.map { |opt|
|
42
|
-
s = opt.names.first
|
43
|
-
if opt.argument?
|
44
|
-
arg_string =
|
45
|
-
if opt.label
|
46
|
-
opt.label
|
47
|
-
elsif opt.integer?
|
48
|
-
"INT"
|
49
|
-
elsif opt.float?
|
50
|
-
"FLOAT"
|
51
|
-
else
|
52
|
-
"ARG"
|
53
|
-
end
|
54
|
-
if opt.optional?
|
55
|
-
s += "[=#{arg_string}]"
|
56
|
-
else
|
57
|
-
s += "=#{arg_string}"
|
58
|
-
end
|
59
|
-
end
|
60
|
-
s
|
61
|
-
}
|
62
|
-
end
|
63
|
-
end
|
64
|
-
end
|
65
|
-
end
|
data/lib/shellopts/idr.rb
DELETED
@@ -1,236 +0,0 @@
|
|
1
|
-
|
2
|
-
module ShellOpts
|
3
|
-
# Idr models the Internal Data Representation of a program. It is the native
|
4
|
-
# representation of a command
|
5
|
-
#
|
6
|
-
# The IDR should ideally be completely detached from the compile-time grammar
|
7
|
-
# and AST but they are only hidden from view in this implementation. Create
|
8
|
-
# a Shellopts object instead to access the compiler data
|
9
|
-
#
|
10
|
-
module Idr
|
11
|
-
# Base class for the Idr class hierarchy. It is constructed from an Ast
|
12
|
-
# object by #generate. Node is modelled as an element of a hash with a key
|
13
|
-
# and a value. Options have their (optional) argument as value while
|
14
|
-
# commands use +self+ as value
|
15
|
-
class Node
|
16
|
-
# Parent node. nil for the top-level Program object
|
17
|
-
attr_reader :parent
|
18
|
-
|
19
|
-
# Unique key (within context) for the option or command. nil for the
|
20
|
-
# top-level Program object
|
21
|
-
attr_reader :key
|
22
|
-
|
23
|
-
# Name of command or option as used on the command line
|
24
|
-
attr_reader :name
|
25
|
-
|
26
|
-
# Value of node. This can be a simple value (String, Integer, or Float),
|
27
|
-
# an Array of values, or a Idr::Command object. Note that the value of a
|
28
|
-
# Command object is the object itself
|
29
|
-
#
|
30
|
-
# Repeated options are implemented as an Array with one element for each
|
31
|
-
# use of the option. The element is nil if the option doesn't take
|
32
|
-
# arguments or if an optional argument is missing.
|
33
|
-
attr_reader :value
|
34
|
-
|
35
|
-
# The top-level Program object
|
36
|
-
def program() @program ||= (parent&.program || self) end
|
37
|
-
|
38
|
-
protected
|
39
|
-
# Copy arguments into instance variables
|
40
|
-
def initialize(parent, ast, key, name, value)
|
41
|
-
@parent, @ast, @key, @name, @value = parent, ast, key, name, value
|
42
|
-
end
|
43
|
-
|
44
|
-
# The AST node for this Idr object
|
45
|
-
attr_reader :ast
|
46
|
-
|
47
|
-
# Shorthand to the grammar node for this Idr object
|
48
|
-
def grammar() @ast.grammar end
|
49
|
-
end
|
50
|
-
|
51
|
-
# Base class for Options
|
52
|
-
class Option < Node
|
53
|
-
end
|
54
|
-
|
55
|
-
class SimpleOption < Option
|
56
|
-
protected
|
57
|
-
# Initialize with defauls from the Ast. +value+ is set to true if option
|
58
|
-
# doesn't take an argument
|
59
|
-
def initialize(parent, ast)
|
60
|
-
value = ast.grammar.argument? ? ast.value : true
|
61
|
-
super(parent, ast, ast.key, ast.name, value)
|
62
|
-
end
|
63
|
-
end
|
64
|
-
|
65
|
-
# An OptionGroup models repeated options collapsed into a single key. The
|
66
|
-
# name of the group should be set to the name of the key (eg. '--all' if
|
67
|
-
# the key is :all)
|
68
|
-
class OptionGroup < Option
|
69
|
-
# Array of names of the options
|
70
|
-
attr_reader :names
|
71
|
-
|
72
|
-
# Array of values of the options
|
73
|
-
alias :values :value
|
74
|
-
|
75
|
-
# Name is set to the key name and value to an array of option values
|
76
|
-
def initialize(parent, key, name, options)
|
77
|
-
@names = options.map(&:name)
|
78
|
-
super(parent, nil, key, name, options.map(&:value))
|
79
|
-
end
|
80
|
-
end
|
81
|
-
|
82
|
-
class Command < Node
|
83
|
-
# Hash from key to options with repeated option_list collapsed into a
|
84
|
-
# option group. It also include an entry for the subcommand. Options are
|
85
|
-
# ordered by first use on the command line. The command entry will always
|
86
|
-
# be last
|
87
|
-
attr_reader :options
|
88
|
-
|
89
|
-
# List of command line options in the same order as on the command line
|
90
|
-
attr_reader :option_list
|
91
|
-
|
92
|
-
# Subcommand object. Possibly nil
|
93
|
-
attr_reader :subcommand
|
94
|
-
|
95
|
-
# True if ident is declared
|
96
|
-
def declared?(ident) option?(ident) || subcommand?(ident) end
|
97
|
-
|
98
|
-
# True if ident is declared as an option
|
99
|
-
def option?(ident) grammar.options.key?(ident) end
|
100
|
-
|
101
|
-
# True if ident is declared as a command
|
102
|
-
def subcommand?(ident) grammar.subcommands.key?(ident) end
|
103
|
-
|
104
|
-
# True if ident is present
|
105
|
-
def key?(ident)
|
106
|
-
declared?(ident) or raise InternalError, "Undefined identifier: #{ident.inspect}"
|
107
|
-
key = grammar.identifier2key(ident)
|
108
|
-
@options.key?(key)
|
109
|
-
end
|
110
|
-
|
111
|
-
# Value of ident. Repeated options are collapsed into an OptionGroup object
|
112
|
-
def [](ident)
|
113
|
-
declared?(ident) or raise InternalError, "Undefined identifier: #{ident.inspect}"
|
114
|
-
key = grammar.identifier2key(ident)
|
115
|
-
if @options.key?(key)
|
116
|
-
@options[key].value
|
117
|
-
elsif option?(key)
|
118
|
-
false
|
119
|
-
else
|
120
|
-
nil
|
121
|
-
end
|
122
|
-
end
|
123
|
-
|
124
|
-
# Apply defaults recursively. Values can be lambdas that will be evaluated to
|
125
|
-
# get the default value. TODO
|
126
|
-
def apply(defaults = {}) raise InternalError, "Not implemented" end
|
127
|
-
|
128
|
-
# Return options and command as an array
|
129
|
-
def to_a() @ast.values end
|
130
|
-
|
131
|
-
# Return options and command as a hash. The hash also define the
|
132
|
-
# singleton method #subcommand that returns the key of the subcommand
|
133
|
-
#
|
134
|
-
# +key_type+ controls the type of keys used: +:key+ (the default) use the
|
135
|
-
# symbolic key, +:name+ use #name. Note that using +:name+ can cause
|
136
|
-
# name collisions between option and command names
|
137
|
-
#
|
138
|
-
# +aliases+ maps from key to replacement key (which could be any object).
|
139
|
-
# +aliases+ can be used to avoid name collisions between options and
|
140
|
-
# commands when using key_format: :name
|
141
|
-
#
|
142
|
-
# IDEA: Add a singleton methods to the hash with #name, #usage, etc.
|
143
|
-
#
|
144
|
-
def to_h(key_type: ::ShellOpts.default_key_type, aliases: {})
|
145
|
-
keys = map_keys(key_type, aliases)
|
146
|
-
value = {}
|
147
|
-
value.define_singleton_method(:subcommand) { nil }
|
148
|
-
options.values.each { |opt| # includes subcommand
|
149
|
-
key = keys[opt.key]
|
150
|
-
case opt
|
151
|
-
when Option
|
152
|
-
value[key] = opt.value
|
153
|
-
when Command
|
154
|
-
value[key] = opt.value.to_h(key_type: key_type, aliases: aliases[opt.key] || {})
|
155
|
-
value.define_singleton_method(:subcommand) { key } # Redefine
|
156
|
-
else
|
157
|
-
# :nocov:
|
158
|
-
raise InternalError, "Oops"
|
159
|
-
# :nocov:
|
160
|
-
end
|
161
|
-
}
|
162
|
-
value
|
163
|
-
end
|
164
|
-
|
165
|
-
# Return options and command as a struct
|
166
|
-
def to_struct(key_type: ::ShellOpts.default_key_type, aliases: {})
|
167
|
-
OptionStruct.new(self, key_type, aliases)
|
168
|
-
end
|
169
|
-
|
170
|
-
protected
|
171
|
-
# Initialize an Idr::Command object and all dependent objects
|
172
|
-
def initialize(parent, ast)
|
173
|
-
super(parent, ast, ast.key, ast.name, self)
|
174
|
-
@option_list = ast.options.map { |node| SimpleOption.new(self, node) }
|
175
|
-
@subcommand = Command.new(self, ast.subcommand) if ast.subcommand
|
176
|
-
@options = @option_list.group_by { |option| option.key }.map { |key, option_list|
|
177
|
-
option =
|
178
|
-
if ast.grammar.options[key].repeated?
|
179
|
-
OptionGroup.new(self, key, ast.grammar.options[key].key_name, option_list)
|
180
|
-
else
|
181
|
-
option_list.first
|
182
|
-
end
|
183
|
-
[key, option]
|
184
|
-
}.to_h
|
185
|
-
@options[subcommand.key] = @subcommand if @subcommand
|
186
|
-
end
|
187
|
-
|
188
|
-
# Internal-key to used-key map. Checks for reserved words and
|
189
|
-
# name-collisions
|
190
|
-
def map_keys(key_type, aliases, reserved_words = [])
|
191
|
-
keys = {}
|
192
|
-
used_keys = {}
|
193
|
-
(grammar.option_list + grammar.subcommand_list).each { |node|
|
194
|
-
internal_key = node.key
|
195
|
-
key = aliases[internal_key] || (key_type == :name ? node.name.to_sym : internal_key)
|
196
|
-
!reserved_words.include?(key) or
|
197
|
-
raise ::ShellOpts::ConversionError, "'#{key}' is a reserved word"
|
198
|
-
!used_keys.key?(key) or
|
199
|
-
raise ::ShellOpts::ConversionError, "Name collision between '--#{key}' and '#{key}!'"
|
200
|
-
keys[internal_key] = key
|
201
|
-
used_keys[key] = true
|
202
|
-
}
|
203
|
-
keys
|
204
|
-
end
|
205
|
-
end
|
206
|
-
|
207
|
-
class Program < Command
|
208
|
-
# Name of program
|
209
|
-
def name() @shellopts.name end
|
210
|
-
def name=(name) @shellopts.name = name end
|
211
|
-
|
212
|
-
# Usage string
|
213
|
-
def usage() @shellopts.usage end
|
214
|
-
def usage=(usage) @shellopts.usage = usage end
|
215
|
-
|
216
|
-
# #key is nil for the top-level Program object
|
217
|
-
def key() nil end
|
218
|
-
|
219
|
-
# Remaining command line arguments
|
220
|
-
def args() @shellopts.args end
|
221
|
-
|
222
|
-
# Initialize the top-level Idr::Program object
|
223
|
-
def initialize(shellopts)
|
224
|
-
@shellopts = shellopts
|
225
|
-
super(nil, shellopts.ast)
|
226
|
-
end
|
227
|
-
|
228
|
-
# Emit error message and a usage description before exiting with status 1
|
229
|
-
def error(*args) @shellopts.error(*error_messages) end
|
230
|
-
|
231
|
-
# Emit error message before exiting with status 1
|
232
|
-
def fail(*args) @shellopts.fail(*error_messages) end
|
233
|
-
end
|
234
|
-
end
|
235
|
-
end
|
236
|
-
|
data/lib/shellopts/main.rb
DELETED
@@ -1,148 +0,0 @@
|
|
1
|
-
|
2
|
-
require 'shellopts/shellopts.rb'
|
3
|
-
require 'shellopts/idr'
|
4
|
-
|
5
|
-
module ShellOpts
|
6
|
-
# FIXME: Outdated
|
7
|
-
#
|
8
|
-
# Struct representation of options. Usually created by ShellOpts::to_struct
|
9
|
-
#
|
10
|
-
# OptionStruct objects give easy access to configuration option values but
|
11
|
-
# meta data are more circuitously accessed through class methods with an
|
12
|
-
# explicit instance argument
|
13
|
-
#
|
14
|
-
# Option values are accessed through a member methods named after the key of
|
15
|
-
# the option. Repeated options have an Array value with one element (possibly
|
16
|
-
# nil) for each use of the option. A query method with a '?' suffixed to the
|
17
|
-
# name returns true or false depending on whether the option was used or not
|
18
|
-
#
|
19
|
-
# option - Value of option. Either an object or an Array if the option can
|
20
|
-
# be repeated
|
21
|
-
# option? - True iff option was given
|
22
|
-
#
|
23
|
-
# Command methods return a nested OptionStruct object while the special
|
24
|
-
# #command method returns the key of actual command (if any). Use
|
25
|
-
# +strukt.send(strukt.command)+ to get the subcommand of a OptionStruct. It
|
26
|
-
# is possible to rename #command method to avoid name collisions
|
27
|
-
#
|
28
|
-
# name! - Command. An OptionStruct or nil if not given on the command line
|
29
|
-
# subcommand - Key of command. Can be renamed
|
30
|
-
#
|
31
|
-
# ---------------------------------
|
32
|
-
# name! - Command. An OptionStruct or nil if not given on the command line
|
33
|
-
#
|
34
|
-
# key! - Key of command
|
35
|
-
# value! - Value of command (a subcommand). Can be renamed
|
36
|
-
#
|
37
|
-
# Note: There is no command query method because option and command names
|
38
|
-
# live in seperate namespaces and could cause colllisions. Check +name!+ for
|
39
|
-
# nil to detect if a command was given
|
40
|
-
#
|
41
|
-
# Meta data are extracted through class methods to avoid polluting the object
|
42
|
-
# namespace. OptionStruct use an OptionsHash object internally and
|
43
|
-
# implements a subset of its meta methods by forwarding to it. The
|
44
|
-
# OptionsHash object can be accessed through the #options_hash method
|
45
|
-
#
|
46
|
-
# Note that #command is defined as both an instance method and a class
|
47
|
-
# method. Use the class method to make the code work with all OptionStruct
|
48
|
-
# objects even if #command has been renamed
|
49
|
-
#
|
50
|
-
# +ShellOpts+ is derived from +BascicObject+ that reserves some words for
|
51
|
-
# internal use (+__id__+, +__send__+, +instance_eval+, +instance_exec+,
|
52
|
-
# +method_missing+, +singleton_method_added+, +singleton_method_removed+,
|
53
|
-
# +singleton_method_undefined+). ShellOpts also define two reserved words of
|
54
|
-
# its own (+__options_hash__+ and +__command__+). ShellOpts raise an
|
55
|
-
# ShellOpts::ConversionError if an option collides with one of the
|
56
|
-
# reserved words or with the special #command method
|
57
|
-
#
|
58
|
-
class OptionStruct < BasicObject
|
59
|
-
# Create a OptionStruct object recursively from an Idr::Command object
|
60
|
-
def self.new(idr, key_type, aliases = {})
|
61
|
-
# Shorthands
|
62
|
-
ast = idr.instance_variable_get("@ast")
|
63
|
-
grammar = ast.grammar
|
64
|
-
|
65
|
-
# Get key map
|
66
|
-
keys = idr.send(:map_keys, key_type, aliases, RESERVED_WORDS)
|
67
|
-
|
68
|
-
# Allocate OptionStruct instance
|
69
|
-
instance = allocate
|
70
|
-
|
71
|
-
# Set reference to Idr object. Is currently unused
|
72
|
-
set_variable(instance, "@__idr__", idr)
|
73
|
-
|
74
|
-
# Generate general option accessor methods
|
75
|
-
grammar.option_list.each { |option|
|
76
|
-
key = keys[option.key]
|
77
|
-
instance.instance_eval("def #{key}() @#{key} end")
|
78
|
-
instance.instance_eval("def #{key}?() false end")
|
79
|
-
}
|
80
|
-
|
81
|
-
# Generate accessor method for present options
|
82
|
-
idr.option_list.each { |option|
|
83
|
-
key = keys[option.key]
|
84
|
-
set_variable(instance, "@#{key}", idr[option.key])
|
85
|
-
instance.instance_eval("def #{key}?() true end")
|
86
|
-
}
|
87
|
-
|
88
|
-
# Generate general #subcommand methods
|
89
|
-
if !idr.subcommand
|
90
|
-
instance.instance_eval("def subcommand() nil end")
|
91
|
-
instance.instance_eval("def subcommand?() false end")
|
92
|
-
instance.instance_eval %(
|
93
|
-
def subcommand!(*msgs)
|
94
|
-
$stderr.puts "in subcommand!"
|
95
|
-
::Kernel.raise ::ShellOpts::UserError, (msgs.empty? ? 'No command' : msgs.join)
|
96
|
-
end
|
97
|
-
)
|
98
|
-
end
|
99
|
-
|
100
|
-
# Generate individual subcommand methods
|
101
|
-
grammar.subcommand_list.each { |subcommand|
|
102
|
-
key = keys[subcommand.key]
|
103
|
-
if subcommand.key == idr.subcommand&.key
|
104
|
-
struct = OptionStruct.new(idr.subcommand, key_type, aliases[idr.subcommand.key] || {})
|
105
|
-
set_variable(instance, "@subcommand", struct)
|
106
|
-
instance.instance_eval("def #{key}() @subcommand end")
|
107
|
-
instance.instance_eval("def subcommand() :#{key} end")
|
108
|
-
instance.instance_eval("def subcommand?() true end")
|
109
|
-
instance.instance_eval("def subcommand!(*msgs) :#{key} end")
|
110
|
-
else
|
111
|
-
instance.instance_eval("def #{key}() nil end")
|
112
|
-
end
|
113
|
-
}
|
114
|
-
|
115
|
-
instance
|
116
|
-
end
|
117
|
-
|
118
|
-
private
|
119
|
-
# Return class of object. #class is not defined for BasicObjects so this
|
120
|
-
# method provides an alternative way of getting the class
|
121
|
-
def self.class_of(object)
|
122
|
-
# https://stackoverflow.com/a/18621313/2130986
|
123
|
-
::Kernel.instance_method(:class).bind(object).call
|
124
|
-
end
|
125
|
-
|
126
|
-
# Class method implementation of ObjectStruct#instance_variable_set that is
|
127
|
-
# not defined in a BasicObject
|
128
|
-
def self.set_variable(this, var, value)
|
129
|
-
# https://stackoverflow.com/a/18621313/2130986
|
130
|
-
::Kernel.instance_method(:instance_variable_set).bind(this).call(var, value)
|
131
|
-
end
|
132
|
-
|
133
|
-
# Class method implementation of ObjectStruct#instance_variable_get that is
|
134
|
-
# not defined in a BasicObject
|
135
|
-
def self.get_variable(this, var)
|
136
|
-
# https://stackoverflow.com/a/18621313/2130986
|
137
|
-
::Kernel.instance_method(:instance_variable_get).bind(this).call(var)
|
138
|
-
end
|
139
|
-
|
140
|
-
BASIC_OBJECT_RESERVED_WORDS = %w(
|
141
|
-
__id__ __send__ instance_eval instance_exec method_missing
|
142
|
-
singleton_method_added singleton_method_removed
|
143
|
-
singleton_method_undefined).map(&:to_sym)
|
144
|
-
OPTIONS_STRUCT_RESERVED_WORDS = %w(__idr__ subcommand).map(&:to_sym)
|
145
|
-
RESERVED_WORDS = BASIC_OBJECT_RESERVED_WORDS + OPTIONS_STRUCT_RESERVED_WORDS
|
146
|
-
end
|
147
|
-
end
|
148
|
-
|
data/lib/shellopts/shellopts.rb
DELETED
@@ -1,123 +0,0 @@
|
|
1
|
-
|
2
|
-
require "shellopts"
|
3
|
-
|
4
|
-
require "shellopts/args.rb"
|
5
|
-
|
6
|
-
# TODO
|
7
|
-
#
|
8
|
-
# PROCESSING
|
9
|
-
# 1. Compile spec string and yield a grammar
|
10
|
-
# 2. Parse the options using the grammar and yield an AST
|
11
|
-
# 3. Construct the Program model from the AST
|
12
|
-
# 4. Apply defaults to the model
|
13
|
-
# 6. Run validations on the model
|
14
|
-
# 5. Create representation from the model
|
15
|
-
#
|
16
|
-
|
17
|
-
module ShellOpts
|
18
|
-
# The command line processing object
|
19
|
-
class ShellOpts
|
20
|
-
# Name of program
|
21
|
-
attr_accessor :name
|
22
|
-
|
23
|
-
# Usage string. If #usage is nil, the auto-generated default is used
|
24
|
-
def usage() @usage || @grammar.usage end
|
25
|
-
def usage=(usage) @usage = usage end
|
26
|
-
|
27
|
-
# Specification of the command
|
28
|
-
attr_reader :spec
|
29
|
-
|
30
|
-
# Original argv argument
|
31
|
-
attr_reader :argv
|
32
|
-
|
33
|
-
# The grammar compiled from the spec string
|
34
|
-
attr_reader :grammar
|
35
|
-
|
36
|
-
# The AST parsed from the command line arguments
|
37
|
-
attr_reader :ast
|
38
|
-
|
39
|
-
# The IDR generated from the Ast
|
40
|
-
attr_reader :idr
|
41
|
-
|
42
|
-
# Compile a spec string into a grammar
|
43
|
-
#
|
44
|
-
# +spec+ is the spec string, and +argv+ the command line (typically the
|
45
|
-
# global ARGV array). +name+ is the name of the program and defaults to the
|
46
|
-
# basename of the program
|
47
|
-
#
|
48
|
-
# Syntax errors in the spec string are caused by the developer and cause
|
49
|
-
# #initialize to raise a +ShellOpts::CompilerError+ exception. Errors in
|
50
|
-
# the +argv+ arguments are caused by the user and cause #process to raise
|
51
|
-
# ShellOpts::UserError exception
|
52
|
-
#
|
53
|
-
# TODO: Change to (name, spec, argv, usage: nil) because
|
54
|
-
# ShellOpts::ShellOpts isn't a magician like the ShellOpts module
|
55
|
-
def initialize(spec, argv, name: ::ShellOpts.default_name, usage: ::ShellOpts.default_usage)
|
56
|
-
@name = name
|
57
|
-
@spec = spec
|
58
|
-
@usage = usage
|
59
|
-
@argv = argv
|
60
|
-
begin
|
61
|
-
@grammar = Grammar.compile(@name, @spec)
|
62
|
-
rescue Grammar::Compiler::Error => ex
|
63
|
-
raise CompilerError.new(5, ex.message)
|
64
|
-
end
|
65
|
-
end
|
66
|
-
|
67
|
-
# Process command line arguments and return self. Raises a
|
68
|
-
# ShellOpts::UserError in case of an error
|
69
|
-
def process
|
70
|
-
begin
|
71
|
-
@ast = Ast.parse(@grammar, @argv)
|
72
|
-
@idr = Idr.generate(self)
|
73
|
-
rescue Ast::Parser::Error => ex
|
74
|
-
raise UserError.new(ex.message)
|
75
|
-
end
|
76
|
-
self
|
77
|
-
end
|
78
|
-
|
79
|
-
# Return an array representation of options and commands in the same order
|
80
|
-
# as on the command line. Each option or command is represented by a [name,
|
81
|
-
# value] pair. The value of an option is be nil if the option didn't have
|
82
|
-
# an argument and else either a String, Integer, or Float. The value of a
|
83
|
-
# command is an array of its options and commands
|
84
|
-
def to_a() idr.to_a end
|
85
|
-
|
86
|
-
# Return a hash representation of the options. See {ShellOpts::OptionsHash}
|
87
|
-
def to_h(key_type: ::ShellOpts.default_key_type, aliases: {})
|
88
|
-
@idr.to_h(key_type: :key_type, aliases: aliases)
|
89
|
-
end
|
90
|
-
|
91
|
-
# TODO
|
92
|
-
# Return OptionHash object
|
93
|
-
# def to_hash(...)
|
94
|
-
|
95
|
-
# Return a struct representation of the options. See {ShellOpts::OptionStruct}
|
96
|
-
def to_struct(key_type: ::ShellOpts.default_key_type, aliases: {})
|
97
|
-
@idr.to_struct(key_type: key_type, aliases: aliases)
|
98
|
-
end
|
99
|
-
|
100
|
-
# List of remaining non-option command line arguments. Returns a Argv object
|
101
|
-
def args() Args.new(self, ast&.arguments) end
|
102
|
-
|
103
|
-
# Iterate options and commands as name/value pairs. Same as +to_a.each+
|
104
|
-
def each(&block) to_a.each(&block) end
|
105
|
-
|
106
|
-
# Print error messages and spec string and exit with status 1. This method
|
107
|
-
# should be called in response to user-errors (eg. specifying an illegal
|
108
|
-
# option)
|
109
|
-
def error(*msgs, exit: true)
|
110
|
-
msg = "#{name}: #{msgs.join}\n" + (@usage ? usage : "Usage: #{name} #{usage}")
|
111
|
-
$stderr.puts msg.rstrip
|
112
|
-
exit(1) if exit
|
113
|
-
end
|
114
|
-
|
115
|
-
# Print error message and exit with status 1. This method should called in
|
116
|
-
# response to system errors (like disk full)
|
117
|
-
def fail(*msgs, exit: true)
|
118
|
-
$stderr.puts "#{name}: #{msgs.join}"
|
119
|
-
exit(1) if exit
|
120
|
-
end
|
121
|
-
end
|
122
|
-
end
|
123
|
-
|