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

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.
@@ -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
-