shellopts 2.0.0.pre.7 → 2.0.0.pre.14

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,15 +0,0 @@
1
-
2
- require 'shellopts/idr.rb'
3
-
4
- module ShellOpts
5
- module Idr
6
- # Generates an Idr::Program from a ShellOpts object
7
- def self.generate(shellopts)
8
- Idr::Program.new(shellopts)
9
- end
10
- end
11
- end
12
-
13
-
14
-
15
-
@@ -1,33 +0,0 @@
1
- module ShellOpts
2
- module Grammar
3
- # Root class for Grammar objects
4
- #
5
- # Node objects are created by ShellOpts::Grammar.compile that returns a
6
- # Program object that in turn contains other node objects in a hierarchical
7
- # structure that reflects the grammar of the program. Only
8
- # ShellOpts::Grammar.compile should create node objects
9
- class Node
10
- # Key (Symbol) of node. Unique within the enclosing command
11
- attr_reader :key
12
-
13
- # Name of node. The name of an option is without the prefixed '-' or
14
- # '--', the name of a command is without the suffixed '!'. Note that name
15
- # collisions can happen between options and commands names
16
- attr_reader :name
17
-
18
- def initialize(key, name)
19
- @key, @name = key, name
20
- end
21
-
22
- # :nocov:
23
- def dump(&block)
24
- puts key.inspect
25
- indent {
26
- puts "name: #{name.inspect}"
27
- yield if block_given?
28
- }
29
- end
30
- # :nocov:
31
- end
32
- end
33
- 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
-
@@ -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
-