adhearsion-loquacious 1.9.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,29 @@
1
+ # Using Ruby heredocs for descriptions, the Loquacious configuration will
2
+ # strip out leading whitespace but preserve line breaks. Gutter lines can be
3
+ # used to mark where leading whitespace should be preserved. This is useful
4
+ # if you need to provide example code in your descriptions.
5
+
6
+ require 'loquacious'
7
+ include Loquacious
8
+
9
+ Configuration.for(:gutters) {
10
+ log_path "log/development.log", :desc => <<-__
11
+ The path to the log file to use. Defaults to log/\#{environment}.log
12
+ (e.g. log/development.log or log/production.log).
13
+ |
14
+ | config.log_path = File.join(ROOT, "log", "\#{environment}.log
15
+ |
16
+ __
17
+
18
+ log_level :warn, :desc => <<-__
19
+ |The log level to use for the default Rails logger. In production mode,
20
+ |this defaults to :info. In development mode, it defaults to :debug.
21
+ |
22
+ | config.log_level = 'debug'
23
+ | config.log_level = :warn
24
+ |
25
+ __
26
+ }
27
+
28
+ help = Configuration.help_for :gutters
29
+ help.show :values => true
@@ -0,0 +1,43 @@
1
+ # Here we show how to used nested configuration options by taking a subset
2
+ # of some common Rails configuration options. Also, descriptions can be give
3
+ # before the option or they can be given inline using Ruby hash notation. If
4
+ # both are present, then the inline description takes precedence.
5
+ #
6
+ # Multiline descriptions are provided using Ruby heredocs. Leading
7
+ # whitespace is stripped and line breaks are preserved when descriptions
8
+ # are printed using the help object.
9
+
10
+ require 'loquacious'
11
+ include Loquacious
12
+
13
+ Configuration.for(:nested) {
14
+ root_path '.', :desc => "The application's base directory."
15
+
16
+ desc "Configuration options for ActiveRecord::Base."
17
+ active_record {
18
+ colorize_logging true, :desc => <<-__
19
+ Determines whether to use ANSI codes to colorize the logging statements committed
20
+ by the connection adapter. These colors make it much easier to overview things
21
+ during debugging (when used through a reader like +tail+ and on a black background),
22
+ but may complicate matters if you use software like syslog. This is true, by default.
23
+ __
24
+
25
+ default_timezone :local, :desc => <<-__
26
+ Determines whether to use Time.local (using :local) or Time.utc (using :utc)
27
+ when pulling dates and times from the database. This is set to :local by default.
28
+ __
29
+ }
30
+
31
+ log_level :info, :desc => <<-__
32
+ The log level to use for the default Rails logger. In production mode,
33
+ this defaults to :info. In development mode, it defaults to :debug.
34
+ __
35
+
36
+ log_path 'log/development.log', :desc => <<-__
37
+ The path to the log file to use. Defaults to log/\#{environment}.log
38
+ (e.g. log/development.log or log/production.log).
39
+ __
40
+ }
41
+
42
+ help = Configuration.help_for :nested
43
+ help.show :values => true
@@ -0,0 +1,20 @@
1
+ # A simple example that configures three options (a b c) along with
2
+ # descriptions for each option. The descriptions along with the
3
+ # values for the configuration options are printed to the terminal.
4
+
5
+ require 'loquacious'
6
+ include Loquacious
7
+
8
+ Configuration.for(:simple) {
9
+ desc 'Your first configuration option'
10
+ a "value for 'a'"
11
+
12
+ desc 'To be or not to be'
13
+ b "William Shakespeare"
14
+
15
+ desc 'The underpinings of Ruby'
16
+ c 42
17
+ }
18
+
19
+ help = Configuration.help_for :simple
20
+ help.show :values => true
@@ -0,0 +1,165 @@
1
+ module Loquacious
2
+
3
+ # :stopdoc:
4
+ LIBPATH = ::File.expand_path(::File.dirname(__FILE__)) + ::File::SEPARATOR
5
+ PATH = ::File.dirname(LIBPATH) + ::File::SEPARATOR
6
+ KEEPERS = (RUBY_PLATFORM == 'java') ?
7
+ %r/^__|^object_id$|^initialize$|^instance_eval$|^singleton_method_added$|^\w+\?$/ :
8
+ %r/^__|^object_id$|^initialize$|^instance_eval$|^\w+\?$/
9
+ # :startdoc:
10
+
11
+
12
+ class << self
13
+ # These control respectively if ENV overrides are used, and which prefix is used
14
+ # Defaults are true and LOQ, and are declared at the bottom"
15
+ attr_accessor :env_config
16
+ attr_accessor :env_prefix
17
+
18
+
19
+ # Returns the configuration associated with the given _name_. If a
20
+ # _block_ is given, then it will be used to create the configuration.
21
+ #
22
+ # The same _name_ can be used multiple times with different
23
+ # configuration blocks. Each different block will be used to add to the
24
+ # configuration; i.e. the configurations are additive.
25
+ #
26
+ def configuration_for( name, &block )
27
+ ::Loquacious::Configuration.for(name, &block)
28
+ end
29
+ alias :configuration :configuration_for
30
+ alias :config_for :configuration_for
31
+ alias :config :configuration_for
32
+
33
+ # Set the default properties for the configuration associated with the
34
+ # given _name_. A _block_ must be provided to this method.
35
+ #
36
+ # The same _name_ can be used multiple times with different configuration
37
+ # blocks. Each block will add or modify the configuration; i.e. the
38
+ # configurations are additive.
39
+ #
40
+ def defaults_for( name, &block )
41
+ ::Loquacious::Configuration.defaults_for(name, &block)
42
+ end
43
+ alias :defaults :defaults_for
44
+
45
+ # Returns a Help instance for the configuration associated with the
46
+ # given _name_. See the Help#initialize method for the options that
47
+ # can be used with this method.
48
+ #
49
+ def help_for( name, opts = {} )
50
+ ::Loquacious::Configuration.help_for(name, opts)
51
+ end
52
+ alias :help :help_for
53
+
54
+ # Returns the version string for the library.
55
+ #
56
+ def version
57
+ @version ||= File.read(path('version.txt')).strip
58
+ end
59
+
60
+ # Returns the library path for the module. If any arguments are given,
61
+ # they will be joined to the end of the libray path using
62
+ # <tt>File.join</tt>.
63
+ #
64
+ def libpath( *args, &block )
65
+ rv = args.empty? ? LIBPATH : ::File.join(LIBPATH, args.flatten)
66
+ if block
67
+ begin
68
+ $LOAD_PATH.unshift LIBPATH
69
+ rv = block.call
70
+ ensure
71
+ $LOAD_PATH.shift
72
+ end
73
+ end
74
+ return rv
75
+ end
76
+
77
+ # Returns the lpath for the module. If any arguments are given, they
78
+ # will be joined to the end of the path using <tt>File.join</tt>.
79
+ #
80
+ def path( *args, &block )
81
+ rv = args.empty? ? PATH : ::File.join(PATH, args.flatten)
82
+ if block
83
+ begin
84
+ $LOAD_PATH.unshift PATH
85
+ rv = block.call
86
+ ensure
87
+ $LOAD_PATH.shift
88
+ end
89
+ end
90
+ return rv
91
+ end
92
+
93
+ # This is merely a convenience method to remove methods from the
94
+ # Loquacious::Configuration class. Some ruby gems add lots of crap to the
95
+ # Kernel module, and this interferes with the configuration system. The
96
+ # remove method should be used to anihilate unwanted methods from the
97
+ # configuration class as needed.
98
+ #
99
+ # Loquacious.remove :gem # courtesy of rubygems
100
+ # Loquacious.remove :test, :file # courtesy of rake
101
+ # Loquacious.remove :main # courtesy of main
102
+ # Loquacious.remove :timeout # courtesy of timeout
103
+ #
104
+ def remove( *args )
105
+ args.each { |name|
106
+ name = name.to_s.delete('=')
107
+ code = <<-__
108
+ undef_method :#{name} rescue nil
109
+ undef_method :#{name}= rescue nil
110
+ __
111
+ Loquacious::Configuration.module_eval code
112
+ Loquacious::Configuration::DSL.module_eval code
113
+ }
114
+ end
115
+
116
+ # A helper method that will create a deep copy of a given Configuration
117
+ # object. This method accepts either a Configuration instance or a name
118
+ # that can be used to lookup the Configuration instance (via the
119
+ # "Loquacious.configuration_for" method).
120
+ #
121
+ # Loquacious.copy(config)
122
+ # Loquacious.copy('name')
123
+ #
124
+ # Optionally a block can be given. It will be used to modify the returned
125
+ # copy with the given values. The Configuration object being copied is
126
+ # never modified by this method.
127
+ #
128
+ # Loquacious.copy(config) {
129
+ # foo 'bar'
130
+ # baz 'buz'
131
+ # }
132
+ #
133
+ def copy( config, &block )
134
+ config = Configuration.for(config) unless config.instance_of? Configuration
135
+ return unless config
136
+
137
+ rv = Configuration.new
138
+ rv.merge!(config)
139
+
140
+ # deep copy
141
+ rv.__desc.each do |key,desc|
142
+ value = rv.__send(key)
143
+ next unless value.instance_of? Configuration
144
+ rv.__send("#{key}=", ::Loquacious.copy(value))
145
+ end
146
+
147
+ rv.merge!(Configuration::DSL.evaluate(&block)) if block
148
+ rv
149
+ end
150
+
151
+ end # class << self
152
+
153
+ @env_config = false
154
+ @env_prefix = "LOQ"
155
+ end # module Loquacious
156
+
157
+ Loquacious.libpath {
158
+ require 'loquacious/core_ext/string'
159
+ require 'loquacious/undefined'
160
+ require 'loquacious/utility'
161
+ require 'loquacious/configuration'
162
+ require 'loquacious/configuration/iterator'
163
+ require 'loquacious/configuration/help'
164
+ }
165
+
@@ -0,0 +1,406 @@
1
+
2
+ module Loquacious
3
+
4
+ # A Configuration provides a "blank slate" for storing configuration
5
+ # properties along with descriptions and default values. Configurations are
6
+ # accessed by name, and hence, the configuration properties can be retrieved
7
+ # from any location in your code.
8
+ #
9
+ # Each property has an associated description that can be displayed to the
10
+ # user via the Configuration::Help class. This is the main point of the
11
+ # Loquacious library - tell the user what all yoru configruation properties
12
+ # actually do!
13
+ #
14
+ # Each configurationp property can also have a default value that is
15
+ # returned if no value has been set for that property. Each property should
16
+ # hae a sensible default - the user should not have to configure every
17
+ # property in order to use a piece of code.
18
+ #
19
+ class Configuration
20
+
21
+ # :stopdoc:
22
+ class Error < StandardError; end
23
+ @table = Hash.new
24
+ # :startdoc:
25
+
26
+ class << self
27
+ # call-seq:
28
+ # Configuration.for( name )
29
+ # Configuration.for( name ) { block }
30
+ #
31
+ # Returns the configuration associated with the given _name_. If a
32
+ # _block_ is given, then it will be used to create the configuration.
33
+ #
34
+ # The same _name_ can be used multiple times with different
35
+ # configuration blocks. Each different block will be used to add to the
36
+ # configuration; i.e. the configurations are additive.
37
+ #
38
+ def for( name, &block )
39
+ if block.nil?
40
+ return @table.has_key?(name) ? @table[name] : nil
41
+ end
42
+
43
+ if @table.has_key? name
44
+ DSL.evaluate(:config_name => name, :config => @table[name], &block)
45
+ else
46
+ @table[name] = DSL.evaluate(:config_name => name, &block)
47
+ end
48
+ end
49
+
50
+ # call-seq:
51
+ # Configuration.defaults_for( name ) { block }
52
+ #
53
+ # Set the default values for the configuration associated with the given
54
+ # _name_. A _block_ is required by this method.
55
+ #
56
+ # Default values do not interfere with normal configuration values. If
57
+ # both are defined for a particualr configruation setting, then the
58
+ # regular configuration value will be returned.
59
+ #
60
+ # Defaults allow the user to define configuration values before the
61
+ # library defaults have been loaded. They prevent library defaults from
62
+ # overriding user settings.
63
+ #
64
+ def defaults_for( name, &block )
65
+ raise "defaults require a block" if block.nil?
66
+
67
+ if @table.has_key? name
68
+ DSL.evaluate(:config => @table[name], :defaults_mode => true, &block)
69
+ else
70
+ @table[name] = DSL.evaluate(:config_name => name, :defaults_mode => true, &block)
71
+ end
72
+ end
73
+
74
+ # call-seq:
75
+ # Configuration.help_for( name, opts = {} )
76
+ #
77
+ # Returns a Help instance for the configuration associated with the
78
+ # given _name_. See the Help#initialize method for the options that
79
+ # can be used with this method.
80
+ #
81
+ def help_for( name, opts = {} )
82
+ ::Loquacious::Configuration::Help.new(name, opts)
83
+ end
84
+ alias :help :help_for
85
+
86
+ # call-seq:
87
+ # Configuration.to_hash( config )
88
+ #
89
+ # Recursively convert a configuration object to a hash.
90
+ #
91
+ def to_hash( config )
92
+ cache = { nil => {} }
93
+
94
+ Iterator.new(config).each do |node|
95
+ ary = node.name.split('.')
96
+ name = ary.pop.to_sym
97
+ parent = ary.empty? ? nil : ary.join('.')
98
+
99
+ if node.config?
100
+ cache[node.name] = cache[parent][name] = {}
101
+ else
102
+ cache[parent][name] = node.obj
103
+ end
104
+ end
105
+
106
+ return cache[nil]
107
+ end
108
+
109
+ # Returns a string array with the parent tree for the config
110
+ #
111
+ def parent_list(config)
112
+ current_parent = config.__parent
113
+ parents = []
114
+ until current_parent.nil? do
115
+ parents.unshift current_parent.__name
116
+ current_parent = current_parent.__parent
117
+ end
118
+ parents
119
+ end
120
+ end#self methods
121
+
122
+
123
+ instance_methods(true).each do |m|
124
+ next if m[::Loquacious::KEEPERS]
125
+ undef_method m
126
+ end
127
+ Kernel.methods.each do |m|
128
+ next if m[::Loquacious::KEEPERS]
129
+ module_eval <<-CODE
130
+ def #{m}( *args, &block )
131
+ self.method_missing('#{m}', *args, &block)
132
+ end
133
+ CODE
134
+ end
135
+ undef_method :method_missing rescue nil
136
+
137
+ # Accessor for the description hash.
138
+ attr_reader :__desc
139
+
140
+ # Accessor for configuration values
141
+ attr_reader :__values
142
+
143
+ # Accessor for configuration defaults
144
+ attr_reader :__defaults
145
+
146
+ # Flag to switch the configuration object into defaults mode. This allows
147
+ # default values to be set instead regular values.
148
+ attr_accessor :__defaults_mode
149
+
150
+ # Name for this configuration object
151
+ attr_accessor :__name
152
+
153
+ # Name of the parent configuration object, used for traversal
154
+ attr_accessor :__parent
155
+
156
+ # Hash holding the transform procs
157
+ attr_accessor :__transforms
158
+
159
+ # Create a new configuration object and initialize it using an optional
160
+ # _block_ of code.
161
+ #
162
+ def initialize( &block )
163
+ @__desc = Hash.new
164
+ @__values = Hash.new
165
+ @__defaults = Hash.new
166
+ @__transforms = Hash.new
167
+ @__defaults_mode = false
168
+ @__parent = nil
169
+ DSL.evaluate(:config => self, &block) if block
170
+ end
171
+
172
+ # When invoked, an attribute reader and writer are defined for the
173
+ # _method_. Any arguments given are used to set the value of the
174
+ # attributes. If a _block_ is given, then the attribute is a nested
175
+ # configuration and the _block_ is evaluated in the context of a new
176
+ # configuration object.
177
+ #
178
+ def method_missing( method, *args, &block )
179
+ m = method.to_s.delete('=').to_sym
180
+
181
+ __eigenclass_eval "def #{m}=( value ) @__values[#{m.inspect}] = value; end", __FILE__, __LINE__
182
+ __eigenclass_eval <<-CODE, __FILE__, __LINE__+1
183
+ def #{m}( *args, &block )
184
+ value = @__values[#{m.inspect}]
185
+
186
+ if args.empty? and !block
187
+ return value if value.kind_of?(Configuration)
188
+ value = @__defaults[#{m.inspect}] if value.kind_of?(Loquacious::Undefined) and @__defaults.has_key? #{m.inspect}
189
+ if Loquacious.env_config
190
+ env_name = Loquacious::Utility.env_var_name(__method__, self)
191
+ if ENV.has_key? env_name
192
+ if @__transforms.has_key? __method__
193
+ return @__transforms[__method__].call ENV[env_name]
194
+ else
195
+ return ENV[env_name]
196
+ end
197
+ end
198
+ end
199
+ return value.respond_to?(:call) ? value.call : value
200
+ end
201
+
202
+ if block
203
+ v = DSL.evaluate(:parent_config => self, :config_name => __method__.to_s, :defaults_mode => __defaults_mode, &block)
204
+ if value.kind_of?(Configuration)
205
+ value.merge! v
206
+ else
207
+ @__values[#{m.inspect}] = v
208
+ end
209
+ else
210
+ v = (1 == args.length ? args.first : args)
211
+ if __defaults_mode
212
+ @__defaults[#{m.inspect}] = v
213
+ else
214
+ @__values[#{m.inspect}] = v
215
+ end
216
+ end
217
+ end
218
+ CODE
219
+
220
+ __desc[m] = nil unless __desc.has_key? m
221
+
222
+ default = ((__defaults_mode or args.empty?) and !block) ? Loquacious::Undefined.new(m.to_s) : nil
223
+ self.__send("#{m}=", default)
224
+ self.__send("#{m}", *args, &block)
225
+ end
226
+
227
+ # Only invoke public methods on the Configuration instances.
228
+ #
229
+ def __send( symbol, *args, &block )
230
+ if self.respond_to? symbol
231
+ self.__send__(symbol, *args, &block)
232
+ else
233
+ self.method_missing(symbol, *args, &block)
234
+ end
235
+ end
236
+
237
+ # Evaluate the given _code_ string in the context of this object's
238
+ # eigenclass (singleton class).
239
+ #
240
+ def __eigenclass_eval( code, file, line )
241
+ ec = class << self; self; end
242
+ ec.module_eval code, file, line
243
+ rescue StandardError
244
+ Kernel.raise Error, "cannot evalutate this code:\n#{code}\n"
245
+ end
246
+
247
+ # Merge the contents of the _other_ configuration into this one. Values
248
+ # from the _other_ configuratin will overwite values in this
249
+ # configuration.
250
+ #
251
+ # This function is recursive. Nested configurations will be merged with
252
+ # their counterparts in the _other_ configuration.
253
+ #
254
+ def merge!( other )
255
+ return self if other.equal? self
256
+ Kernel.raise Error, "can only merge another Configuration" unless other.kind_of?(Configuration)
257
+
258
+ other_values = other.__values
259
+ other_defaults = other.__defaults
260
+
261
+ other.__desc.each do |key,desc|
262
+ value = @__values[key]
263
+ other_value = other_values[key]
264
+
265
+ if value.kind_of?(Configuration) and other_value.kind_of?(Configuration)
266
+ value.merge! other_value
267
+ elsif !other_value.kind_of?(Loquacious::Undefined)
268
+ self.__send__(key, other_value)
269
+ end
270
+
271
+ if other_defaults.has_key? key
272
+ @__defaults[key] = other_defaults[key]
273
+ end
274
+
275
+ if desc
276
+ __desc[key] = desc
277
+ end
278
+ end
279
+
280
+ self
281
+ end
282
+
283
+ # Provides hash accessor notation for configuration values.
284
+ #
285
+ # config = Configuration.for('app') {
286
+ # port 1234
287
+ # }
288
+ # config[:port] #=> 1234
289
+ # config.port #=> 1234
290
+ #
291
+ def []( key )
292
+ self.__send(key)
293
+ end
294
+
295
+ # Provides hash accessor notation for configuration values.
296
+ #
297
+ # config = Configuration.for('app')
298
+ # config[:port] = 8808
299
+ # config.port #=> 8808
300
+ #
301
+ def []=( key, value )
302
+ self.__send(key, value)
303
+ end
304
+
305
+ # Recursively convert the configuration object to a hash.
306
+ #
307
+ def to_hash
308
+ ::Loquacious::Configuration.to_hash(self)
309
+ end
310
+
311
+ # Returns an array of the parents in descending order
312
+ def parent_list
313
+ ::Loquacious::Configuration.parent_list(self)
314
+ end
315
+
316
+ # Implementation of a domain specific language for creating configuration
317
+ # objects. Blocks of code are evaluted by the DSL which returns a new
318
+ # configuration object.
319
+ #
320
+ class DSL
321
+ instance_methods(true).each do |m|
322
+ next if m[::Loquacious::KEEPERS]
323
+ undef_method m
324
+ end
325
+ private_instance_methods(true).each do |m|
326
+ next if m[::Loquacious::KEEPERS]
327
+ undef_method m
328
+ end
329
+ Kernel.methods.each do |m|
330
+ next if m[::Loquacious::KEEPERS]
331
+ module_eval <<-CODE, __FILE__, __LINE__+1
332
+ def #{m}( *args, &block )
333
+ self.method_missing('#{m}', *args, &block)
334
+ end
335
+ CODE
336
+ end
337
+ undef_method :method_missing rescue nil
338
+
339
+ # Create a new DSL and evaluate the given _block_ in the context of
340
+ # the DSL. Returns a newly created configuration object.
341
+ #
342
+ def self.evaluate( opts = {}, &block )
343
+ dsl = self.new(opts, &block)
344
+ dsl.__config
345
+ end
346
+
347
+ # Returns the configuration object.
348
+ attr_reader :__config
349
+
350
+ # Creates a new DSL and evaluates the given _block_ in the context of
351
+ # the DSL.
352
+ #
353
+ def initialize( opts = {}, &block )
354
+ @description = nil
355
+ @transform_to_store = nil
356
+ @__config = opts[:config] || Configuration.new
357
+ @__config.__defaults_mode = opts.key?(:defaults_mode) ? opts[:defaults_mode] : false
358
+ @__config.__name = opts[:config_name] || nil
359
+ @__config.__parent = opts[:parent_config] || nil
360
+ instance_eval(&block)
361
+ ensure
362
+ @__config.__defaults_mode = false
363
+ end
364
+
365
+ # Dynamically adds the given _method_ to the configuration as an
366
+ # attribute. The _args_ will be used to set the value of the
367
+ # attribute. If a _block_ is given then the _args_ are ignored and the
368
+ # attribute will be a nested configuration object.
369
+ #
370
+ def method_missing( method, *args, &block )
371
+ m = method.to_s.delete('=').to_sym
372
+
373
+ if args.length > 1
374
+ opts = args.last.instance_of?(Hash) ? args.pop : {}
375
+ self.desc(opts[:desc]) if opts.has_key? :desc
376
+ self.transform(opts[:transform]) if opts.has_key? :transform
377
+ end
378
+
379
+ rv = __config.__send(m, *args, &block)
380
+ __config.__desc[m] = @description if @description
381
+ __config.__transforms[m] = @transform_to_store if @transform_to_store
382
+ @description = nil
383
+ @transform_to_store = nil
384
+ rv
385
+ end
386
+
387
+ # Store the _string_ as the description for the next attribute that
388
+ # will be configured. This description will be overwritten if the
389
+ # attribute has a description passed as an options hash.
390
+ #
391
+ def desc( string )
392
+ string = string.to_s
393
+ string.strip!
394
+ string.gutter!
395
+ @description = string.empty? ? nil : string
396
+ end
397
+
398
+ def transform( transform_proc )
399
+ @transform_to_store = transform_proc
400
+ end
401
+
402
+ end # class DSL
403
+
404
+ end # class Configuration
405
+ end # module Loquacious
406
+