shellopts 2.0.0.pre.13 → 2.0.0.pre.14

Sign up to get free protection for your applications and to get access to all the features.
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,10 +0,0 @@
1
-
2
- module ShellOpts
3
- # Gives access to the ruby main object
4
- module Main
5
- CALLER_RE = /^.*:in `<main>'$/
6
- def self.main() TOPLEVEL_BINDING.eval("self") end
7
- end
8
- end
9
-
10
-
@@ -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
-
@@ -1,106 +0,0 @@
1
-
2
- require 'shellopts/ast/node.rb'
3
- require 'shellopts/ast/option.rb'
4
- require 'shellopts/ast/command.rb'
5
- require 'shellopts/ast/program.rb'
6
-
7
- module ShellOpts
8
- module Ast
9
- # Parse ARGV according to grammar. Returns a Ast::Program object
10
- def self.parse(grammar, argv)
11
- grammar.is_a?(Grammar::Program) or
12
- raise InternalError, "Expected Grammar::Program object, got #{grammar.class}"
13
- argv.is_a?(Array) or
14
- raise InternalError, "Expected Array object, got #{argv.class}"
15
- Parser.new(grammar, argv).call
16
- end
17
-
18
- private
19
- # Parse a subcommand line
20
- class Parser
21
- class Error < RuntimeError; end
22
-
23
- def initialize(grammar, argv)
24
- @grammar, @argv = grammar, argv.dup
25
- @seen_options = {} # Used to keep track of repeated options
26
- end
27
-
28
- def call
29
- program = Ast::Program.new(@grammar)
30
- parse_subcommand(program)
31
- program.arguments = @argv
32
- program
33
- end
34
-
35
- private
36
- def parse_subcommand(subcommand)
37
- @seen_options = {} # Every new subcommand resets the seen options
38
- while arg = @argv.first
39
- if arg == "--"
40
- @argv.shift
41
- break
42
- elsif arg.start_with?("-")
43
- parse_option(subcommand)
44
- elsif cmd = subcommand.grammar.subcommands[arg]
45
- @argv.shift
46
- subcommand.subcommand = Ast::Command.new(cmd, arg)
47
- parse_subcommand(subcommand.subcommand)
48
- break
49
- else
50
- break
51
- end
52
- end
53
- end
54
-
55
- def parse_option(subcommand)
56
- # Split into name and argument
57
- case @argv.first
58
- when /^(--.+?)(?:=(.*))?$/
59
- name, arg, short = $1, $2, false
60
- when /^(-.)(.+)?$/
61
- name, arg, short = $1, $2, true
62
- end
63
- @argv.shift
64
-
65
- option = subcommand.grammar.options[name] or raise Error, "Unknown option '#{name}'"
66
- !@seen_options.key?(option.key) || option.repeated? or raise Error, "Duplicate option '#{name}'"
67
- @seen_options[option.key] = true
68
-
69
- # Parse (optional) argument
70
- if option.argument?
71
- if arg.nil? && !option.optional?
72
- if !@argv.empty?
73
- arg = @argv.shift
74
- else
75
- raise Error, "Missing argument for option '#{name}'"
76
- end
77
- end
78
- arg &&= parse_arg(option, name, arg)
79
- elsif arg && short
80
- @argv.unshift("-#{arg}")
81
- arg = nil
82
- elsif !arg.nil?
83
- raise Error, "No argument allowed for option '#{name}'"
84
- end
85
-
86
- subcommand.options << Ast::Option.new(option, name, arg)
87
- end
88
-
89
- def parse_arg(option, name, arg)
90
- if option.string?
91
- arg
92
- elsif arg == ""
93
- nil
94
- elsif option.integer?
95
- arg =~ /^-?\d+$/ or raise Error, "Illegal integer in '#{name}' argument: '#{arg}'"
96
- arg.to_i
97
- else # option.float?
98
- # https://stackoverflow.com/a/21891705/2130986
99
- arg =~ /^[+-]?(?:0|[1-9]\d*)(?:\.(?:\d*[1-9]|0))?$/ or
100
- raise Error, "Illegal float in '#{name}' argument: '#{arg}'"
101
- arg.to_f
102
- end
103
- end
104
- end
105
- end
106
- end