filigree 0.1.2
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 +7 -0
- data/AUTHORS +1 -0
- data/LICENSE +12 -0
- data/README.md +336 -0
- data/Rakefile +101 -0
- data/lib/filigree/abstract_class.rb +90 -0
- data/lib/filigree/application.rb +107 -0
- data/lib/filigree/array.rb +35 -0
- data/lib/filigree/boolean.rb +40 -0
- data/lib/filigree/class.rb +48 -0
- data/lib/filigree/class_methods_module.rb +40 -0
- data/lib/filigree/commands.rb +261 -0
- data/lib/filigree/configuration.rb +411 -0
- data/lib/filigree/match.rb +499 -0
- data/lib/filigree/object.rb +40 -0
- data/lib/filigree/request_file.rb +33 -0
- data/lib/filigree/string.rb +52 -0
- data/lib/filigree/types.rb +159 -0
- data/lib/filigree/version.rb +8 -0
- data/lib/filigree/visitor.rb +195 -0
- data/lib/filigree.rb +27 -0
- data/test/tc_abstract_class.rb +74 -0
- data/test/tc_application.rb +53 -0
- data/test/tc_array.rb +28 -0
- data/test/tc_boolean.rb +38 -0
- data/test/tc_class.rb +45 -0
- data/test/tc_class_methods_module.rb +71 -0
- data/test/tc_commands.rb +78 -0
- data/test/tc_configuration.rb +173 -0
- data/test/tc_match.rb +307 -0
- data/test/tc_object.rb +43 -0
- data/test/tc_string.rb +36 -0
- data/test/tc_types.rb +116 -0
- data/test/tc_visitor.rb +236 -0
- data/test/ts_filigree.rb +33 -0
- metadata +247 -0
@@ -0,0 +1,411 @@
|
|
1
|
+
# Author: Chris Wailes <chris.wailes@gmail.com>
|
2
|
+
# Project: Filigree
|
3
|
+
# Date: 2013/05/14
|
4
|
+
# Description: Easy application configuration.
|
5
|
+
|
6
|
+
############
|
7
|
+
# Requires #
|
8
|
+
############
|
9
|
+
|
10
|
+
# Standard Library
|
11
|
+
|
12
|
+
# Filigree
|
13
|
+
require 'filigree/class_methods_module'
|
14
|
+
require 'filigree/string'
|
15
|
+
|
16
|
+
#######################
|
17
|
+
# Classes and Modules #
|
18
|
+
#######################
|
19
|
+
|
20
|
+
module Filigree
|
21
|
+
module Configuration
|
22
|
+
include ClassMethodsModule
|
23
|
+
|
24
|
+
#############
|
25
|
+
# Constants #
|
26
|
+
#############
|
27
|
+
|
28
|
+
####################
|
29
|
+
# Instance Methods #
|
30
|
+
####################
|
31
|
+
|
32
|
+
# @return [Array<String>] Remaining strings that weren't used in configuration
|
33
|
+
attr_accessor :rest
|
34
|
+
|
35
|
+
# Dump the state of the Configuration object. This will dump the
|
36
|
+
# state, encoded in YAML, to different destinations depending on the
|
37
|
+
# io parameter.
|
38
|
+
#
|
39
|
+
# @overload dump(io, *fields)
|
40
|
+
# Dump the state to stdout.
|
41
|
+
# @param [nil] io Tells the method to serialize to stdout
|
42
|
+
# @param [Symbol] fields Fields to serialize
|
43
|
+
#
|
44
|
+
# @overload dump(str, *fields)
|
45
|
+
# Dump the state to a file.
|
46
|
+
# @param [String] io Name of file to serialize to
|
47
|
+
# @param [Symbol] fields Fields to serialize
|
48
|
+
#
|
49
|
+
# @overload dump(io, *fields)
|
50
|
+
# Dump the state to the provided IO instance.
|
51
|
+
# @param [IO] io IO object to serialize to
|
52
|
+
# @param [Symbol] fields Fields to serialize
|
53
|
+
#
|
54
|
+
# @return [void]
|
55
|
+
def dump(io = nil, *fields)
|
56
|
+
require 'yaml'
|
57
|
+
|
58
|
+
vals =
|
59
|
+
if fields.empty? then self.class.options_long.keys else fields end.inject(Hash.new) do |hash, field|
|
60
|
+
hash.tap { hash[field.to_s] = self.send(field) }
|
61
|
+
end
|
62
|
+
|
63
|
+
case io
|
64
|
+
when nil
|
65
|
+
YAML.dump vals
|
66
|
+
|
67
|
+
when String
|
68
|
+
File.open(io, 'w') { |file| YAML.dump vals, file }
|
69
|
+
|
70
|
+
when IO
|
71
|
+
YAML.dump vals, io
|
72
|
+
end
|
73
|
+
end
|
74
|
+
alias :serialize :dump
|
75
|
+
|
76
|
+
# Configures the object based on the overloaded parameter.
|
77
|
+
#
|
78
|
+
# @overload initialize(args)
|
79
|
+
# Configure the object from an array of strings.
|
80
|
+
# @param [Array<String>] args String arguments
|
81
|
+
#
|
82
|
+
# @overload initialize(source)
|
83
|
+
# Configure the object from a serialized source. If source is a
|
84
|
+
# string then it will be treated as a file name and the
|
85
|
+
# configuration will be loaded from the specified string. If it
|
86
|
+
# an IO object then that will be used as the source.
|
87
|
+
# @param [String, IO] source Serialized configuration source
|
88
|
+
#
|
89
|
+
# @return [void]
|
90
|
+
def initialize(overloaded = ARGV.clone)
|
91
|
+
set_opts = Array.new
|
92
|
+
|
93
|
+
case overloaded
|
94
|
+
when Array
|
95
|
+
handle_array_options(overloaded, set_opts)
|
96
|
+
|
97
|
+
when String, IO
|
98
|
+
handle_serialized_options(overloaded, set_opts)
|
99
|
+
end
|
100
|
+
|
101
|
+
(self.class.options_long.keys - set_opts).each do |opt_name|
|
102
|
+
default = self.class.options_long[opt_name].default
|
103
|
+
default = self.instance_exec(&default) if default.is_a? Proc
|
104
|
+
|
105
|
+
self.send("#{opt_name}=", default)
|
106
|
+
end
|
107
|
+
|
108
|
+
# Check to make sure all the required options are set.
|
109
|
+
self.class.required_options.each do |option|
|
110
|
+
raise ArgumentError, "Option #{option} not set." if self.send(option).nil?
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
# Find the appropriate option object given a string.
|
115
|
+
#
|
116
|
+
# @param [String] str Search string
|
117
|
+
#
|
118
|
+
# @return [Option, nil] Desired option or nil if it wasn't found
|
119
|
+
def find_option(str)
|
120
|
+
if str[0,2] == '--'
|
121
|
+
self.class.options_long[str[2..-1]]
|
122
|
+
|
123
|
+
elsif str[0,1] == '-'
|
124
|
+
self.class.options_short[str[1..-1]]
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
# Configure the object from an array of strings.
|
129
|
+
#
|
130
|
+
# @param [Array<String>] argv String options
|
131
|
+
# @param [Array<String>] set_opts List of names of options already added
|
132
|
+
#
|
133
|
+
# @return [void]
|
134
|
+
def handle_array_options(argv, set_opts)
|
135
|
+
while str = argv.shift
|
136
|
+
|
137
|
+
break if str == '--'
|
138
|
+
|
139
|
+
if option = find_option(str)
|
140
|
+
args = argv.shift(option.arity == -1 ? argv.index { |str| str[0,1] == '-' } : option.arity)
|
141
|
+
|
142
|
+
case option.handler
|
143
|
+
when Array
|
144
|
+
tmp = args.zip(option.handler).map { |arg, sym| arg.send sym }
|
145
|
+
self.send("#{option.long}=", (option.arity == 1 and tmp.length == 1) ? tmp.first : tmp)
|
146
|
+
|
147
|
+
when Proc
|
148
|
+
self.send("#{option.long}=", self.instance_exec(*args, &option.handler))
|
149
|
+
end
|
150
|
+
|
151
|
+
set_opts << option.long
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
# Save the rest of the command line for later.
|
156
|
+
self.rest = argv
|
157
|
+
end
|
158
|
+
|
159
|
+
# Configure the object from a serialization source.
|
160
|
+
#
|
161
|
+
# @param [String, IO] overloaded Serialization source
|
162
|
+
# @param [Array<String>] set_opts List of names of options already added
|
163
|
+
#
|
164
|
+
# @return [void]
|
165
|
+
def handle_serialized_options(overloaded, set_opts)
|
166
|
+
options =
|
167
|
+
if overloaded.is_a? String
|
168
|
+
if File.exists? overloaded
|
169
|
+
YAML.load_file overloaded
|
170
|
+
else
|
171
|
+
YAML.load overloaded
|
172
|
+
end
|
173
|
+
else
|
174
|
+
YAML.load overloaded
|
175
|
+
end
|
176
|
+
|
177
|
+
options.each do |option, val|
|
178
|
+
set_opts << option
|
179
|
+
self.send "#{option}=", val
|
180
|
+
end
|
181
|
+
end
|
182
|
+
|
183
|
+
#################
|
184
|
+
# Class Methods #
|
185
|
+
#################
|
186
|
+
|
187
|
+
module ClassMethods
|
188
|
+
# @return [Hash<String, Option>] Hash of options with long names used as keys
|
189
|
+
attr_reader :options_long
|
190
|
+
# @return [Hash<String, Option>] hash of options with short name used as keys
|
191
|
+
attr_reader :options_short
|
192
|
+
|
193
|
+
# Add an option to the necessary data structures.
|
194
|
+
#
|
195
|
+
# @param [Option] opt Option to add
|
196
|
+
#
|
197
|
+
# @return [void]
|
198
|
+
def add_option(opt)
|
199
|
+
@options_long[opt.long] = opt
|
200
|
+
@options_short[opt.short] = opt unless opt.short.nil?
|
201
|
+
end
|
202
|
+
|
203
|
+
# Define an automatic configuration variable.
|
204
|
+
#
|
205
|
+
# @param [Symbol] name Name of the configuration variable
|
206
|
+
# @param [Proc] block Block to be executed to generate the value
|
207
|
+
#
|
208
|
+
# @return [void]
|
209
|
+
def auto(name, &block)
|
210
|
+
define_method(name, &block)
|
211
|
+
end
|
212
|
+
|
213
|
+
# Define a boolean option. The variable will be set to true if
|
214
|
+
# the flag is seen and be false otherwise.
|
215
|
+
#
|
216
|
+
# @param [String] long Long name of the option
|
217
|
+
# @param [String] short Short name of the option
|
218
|
+
#
|
219
|
+
# @return [void]
|
220
|
+
def bool_option(long, short = nil)
|
221
|
+
@next_default = false
|
222
|
+
option(long, short) { true }
|
223
|
+
end
|
224
|
+
|
225
|
+
# Sets the default value for the next command. If a block is
|
226
|
+
# provided it will be used. If not, the val parameter will be.
|
227
|
+
#
|
228
|
+
# @param [Object] val Default value
|
229
|
+
# @param [Proc] block Default value generator block
|
230
|
+
#
|
231
|
+
# @return [void]
|
232
|
+
def default(val = nil, &block)
|
233
|
+
@next_default = block ? block : val
|
234
|
+
end
|
235
|
+
|
236
|
+
# Sets the help string for the next command.
|
237
|
+
#
|
238
|
+
# @param [String] str Command help string
|
239
|
+
#
|
240
|
+
# @return [void]
|
241
|
+
def help(str)
|
242
|
+
@help_string = str
|
243
|
+
end
|
244
|
+
|
245
|
+
# Install the instance class variables in the including class.
|
246
|
+
#
|
247
|
+
# @return [void]
|
248
|
+
def install_icvars
|
249
|
+
@help_string = ''
|
250
|
+
@next_default = nil
|
251
|
+
@next_required = false
|
252
|
+
@options_long = Hash.new
|
253
|
+
@options_short = Hash.new
|
254
|
+
@required = Array.new
|
255
|
+
@usage = ''
|
256
|
+
end
|
257
|
+
|
258
|
+
# Define a new option.
|
259
|
+
#
|
260
|
+
# @param [String] long Long option name
|
261
|
+
# @param [String] short Short option name
|
262
|
+
# @param [Array<Symbol>] conversions List of methods used to convert string arguments
|
263
|
+
# @param [Proc] block Block used when the option is encountered
|
264
|
+
#
|
265
|
+
# @return [void]
|
266
|
+
def option(long, short = nil, conversions: nil, &block)
|
267
|
+
|
268
|
+
attr_accessor long.to_sym
|
269
|
+
|
270
|
+
long = long.to_s
|
271
|
+
short = short.to_s if short
|
272
|
+
|
273
|
+
add_option Option.new(long, short, @help_string, @next_default,
|
274
|
+
conversions.nil? ? block : conversions)
|
275
|
+
|
276
|
+
@required << long.to_sym if @next_required
|
277
|
+
|
278
|
+
# Reset state between option declarations.
|
279
|
+
@help_string = ''
|
280
|
+
@next_default = nil
|
281
|
+
@next_required = false
|
282
|
+
end
|
283
|
+
|
284
|
+
# Mark some options as required. If no names are provided then
|
285
|
+
# the next option to be defined is required; if names are
|
286
|
+
# provided they are all marked as required.
|
287
|
+
#
|
288
|
+
# @param [Symbol] names Options to be marked as required.
|
289
|
+
#
|
290
|
+
# @return [void]
|
291
|
+
def required(*names)
|
292
|
+
if names.empty?
|
293
|
+
@next_required = true
|
294
|
+
else
|
295
|
+
@required += names
|
296
|
+
end
|
297
|
+
end
|
298
|
+
|
299
|
+
# @return [Array<Symbol>] Options that need to be marked as required
|
300
|
+
def required_options
|
301
|
+
@required
|
302
|
+
end
|
303
|
+
|
304
|
+
# Define an option that takes a single string argument.
|
305
|
+
#
|
306
|
+
# @param [String] long Long option name
|
307
|
+
# @param [String] short Short option name
|
308
|
+
#
|
309
|
+
# @return [void]
|
310
|
+
def string_option(long, short = nil)
|
311
|
+
option(long, short) { |str| str }
|
312
|
+
end
|
313
|
+
|
314
|
+
# Add's a usage string to the entire configuration object. If
|
315
|
+
# no string is provided the current usage string is returned.
|
316
|
+
#
|
317
|
+
# @param [String, nil] str Usage string
|
318
|
+
#
|
319
|
+
# @return [String] Current or new usage string
|
320
|
+
def usage(str = nil)
|
321
|
+
if str then @usage = str else @usage end
|
322
|
+
end
|
323
|
+
|
324
|
+
#############
|
325
|
+
# Callbacks #
|
326
|
+
#############
|
327
|
+
|
328
|
+
def self.extended(klass)
|
329
|
+
klass.install_icvars
|
330
|
+
end
|
331
|
+
end
|
332
|
+
|
333
|
+
#################
|
334
|
+
# Inner Classes #
|
335
|
+
#################
|
336
|
+
|
337
|
+
# This class represents an option that can appear in the
|
338
|
+
# configuration.
|
339
|
+
class Option < Struct.new(:long, :short, :help, :default, :handler)
|
340
|
+
# Returns the number of arguments that this option takes.
|
341
|
+
#
|
342
|
+
# @return [Fixnum] Number of arguments the option takes
|
343
|
+
def arity
|
344
|
+
case self.handler
|
345
|
+
when Array then self.handler.length
|
346
|
+
when Proc then self.handler.arity
|
347
|
+
end
|
348
|
+
end
|
349
|
+
|
350
|
+
# Print the option information out as a string.
|
351
|
+
#
|
352
|
+
# Layout:
|
353
|
+
# | ||--`long`,|| ||-`short`|| - |
|
354
|
+
# |_______||_________||_||________||___|
|
355
|
+
# indent max_l+3 1 max_s+1 3
|
356
|
+
#
|
357
|
+
# @param [Fixnum] max_long Maximim length of all long argumetns being printed in a block
|
358
|
+
# @param [Fixnum] max_short Maximum length of all short arguments being printed in a block
|
359
|
+
# @param [Fixnum] indent Indentation to be placed before each line
|
360
|
+
#
|
361
|
+
# @return [String]
|
362
|
+
def to_s(max_long, max_short, indent = 0)
|
363
|
+
segment_indent = indent + max_long + max_short + 8
|
364
|
+
segmented_help = self.help.segment(segment_indent)
|
365
|
+
|
366
|
+
if self.short
|
367
|
+
sprintf "#{' ' * indent}%-#{max_long + 3}s %-#{max_short + 1}s - %s", "--#{self.long},", '-' + self.short, segmented_help
|
368
|
+
else
|
369
|
+
sprintf "#{' ' * indent}%-#{max_long + max_short + 5}s - %s", '--' + self.long, segmented_help
|
370
|
+
end
|
371
|
+
end
|
372
|
+
|
373
|
+
# Helper method used to print out information on a set of options.
|
374
|
+
#
|
375
|
+
# @param [Array<Option>] options Options to be printed
|
376
|
+
# @param [Fixnum] indent Indentation to be placed before each line
|
377
|
+
#
|
378
|
+
# @return [String]
|
379
|
+
def self.to_s(options, indent = 0)
|
380
|
+
lines = []
|
381
|
+
|
382
|
+
max_long = options.inject(0) { |max, opt| max <= opt.long.length ? opt.long.length : max }
|
383
|
+
max_short = options.inject(0) { |max, opt| !opt.short.nil? && max <= opt.short.length ? opt.short.length : max }
|
384
|
+
|
385
|
+
options.each do |opt|
|
386
|
+
lines << opt.to_s(max_long, max_short, indent)
|
387
|
+
end
|
388
|
+
|
389
|
+
lines.join("\n")
|
390
|
+
end
|
391
|
+
end
|
392
|
+
|
393
|
+
#######################
|
394
|
+
# Pre-defined Options #
|
395
|
+
#######################
|
396
|
+
|
397
|
+
# The default help option. This can be added to your class via
|
398
|
+
# add_option.
|
399
|
+
HELP_OPTION = Option.new('help', 'h', 'Prints this help message.', nil, Proc.new do
|
400
|
+
puts "Usage: #{self.class.usage}"
|
401
|
+
puts
|
402
|
+
puts 'Options:'
|
403
|
+
|
404
|
+
options = self.class.options_long.values.sort { |a, b| a.long <=> b.long }
|
405
|
+
puts Option.to_s(options, 2)
|
406
|
+
|
407
|
+
# Quit the application after printing the help message.
|
408
|
+
exit
|
409
|
+
end)
|
410
|
+
end
|
411
|
+
end
|