adhearsion-loquacious 1.9.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.
@@ -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
+