loquacious 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/History.txt +4 -0
- data/README.rdoc +234 -0
- data/Rakefile +43 -0
- data/examples/gutters.rb +30 -0
- data/examples/nested.rb +47 -0
- data/examples/simple.rb +20 -0
- data/lib/loquacious.rb +75 -0
- data/lib/loquacious/configuration.rb +197 -0
- data/lib/loquacious/configuration/help.rb +151 -0
- data/lib/loquacious/configuration/iterator.rb +152 -0
- data/lib/loquacious/core_ext/string.rb +75 -0
- data/loquacious.gemspec +37 -0
- data/spec/configuration_spec.rb +152 -0
- data/spec/help_spec.rb +323 -0
- data/spec/iterator_spec.rb +62 -0
- data/spec/loquacious_spec.rb +22 -0
- data/spec/spec.opts +1 -0
- data/spec/spec_helper.rb +59 -0
- data/spec/string_spec.rb +53 -0
- data/tasks/ann.rake +80 -0
- data/tasks/bones.rake +20 -0
- data/tasks/gem.rake +201 -0
- data/tasks/git.rake +40 -0
- data/tasks/notes.rake +27 -0
- data/tasks/post_load.rake +34 -0
- data/tasks/rdoc.rake +51 -0
- data/tasks/rubyforge.rake +55 -0
- data/tasks/setup.rb +292 -0
- data/tasks/spec.rake +54 -0
- data/tasks/svn.rake +47 -0
- data/tasks/test.rake +40 -0
- metadata +104 -0
@@ -0,0 +1,197 @@
|
|
1
|
+
|
2
|
+
module Loquacious
|
3
|
+
|
4
|
+
#
|
5
|
+
#
|
6
|
+
class Configuration
|
7
|
+
|
8
|
+
# :stopdoc:
|
9
|
+
class Error < StandardError; end
|
10
|
+
@table = Hash.new
|
11
|
+
# :startdoc:
|
12
|
+
|
13
|
+
class << self
|
14
|
+
# call-seq:
|
15
|
+
# Configuration.for( name )
|
16
|
+
# Configuration.for( name ) { block }
|
17
|
+
#
|
18
|
+
# Returns the configuration associated with the given _name_. If a
|
19
|
+
# _block_ is given, then it will be used to create the configuration.
|
20
|
+
#
|
21
|
+
# The same _name_ can be used multiple times with different
|
22
|
+
# configuration blocks. Each different block will be used to add to the
|
23
|
+
# configuration; i.e. the configurations are additive.
|
24
|
+
#
|
25
|
+
def for( name, &block )
|
26
|
+
if block.nil?
|
27
|
+
return @table.has_key?(name) ? @table[name] : nil
|
28
|
+
end
|
29
|
+
|
30
|
+
cfg = DSL.evaluate(&block)
|
31
|
+
|
32
|
+
if @table.has_key? name
|
33
|
+
@table[name].merge! cfg
|
34
|
+
else
|
35
|
+
@table[name] = cfg
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
# call-seq:
|
40
|
+
# Configuration.help_for( name, opts = {} )
|
41
|
+
#
|
42
|
+
# Returns a Help instance for the configuration associated with the
|
43
|
+
# given _name_. See the Help#initialize method for the options that
|
44
|
+
# can be used with this method.
|
45
|
+
#
|
46
|
+
def help_for( name, opts = {} )
|
47
|
+
::Loquacious::Configuration::Help.new(name, opts)
|
48
|
+
end
|
49
|
+
alias :help :help_for
|
50
|
+
end
|
51
|
+
|
52
|
+
exceptions = %w[instance_of? kind_of? equal?]
|
53
|
+
instance_methods.each do |m|
|
54
|
+
undef_method m unless m[%r/^__/] or exceptions.include? m.to_s
|
55
|
+
end
|
56
|
+
|
57
|
+
# Accessor for the description hash.
|
58
|
+
attr_reader :__desc
|
59
|
+
|
60
|
+
# Create a new configuration object and initialize it using an optional
|
61
|
+
# _block_ of code.
|
62
|
+
#
|
63
|
+
def initialize( &block )
|
64
|
+
@__desc = Hash.new
|
65
|
+
self.merge!(DSL.evaluate(&block)) if block
|
66
|
+
end
|
67
|
+
|
68
|
+
# When invoked, an attribute reader and writer are defined for the
|
69
|
+
# _method_. Any arguments given are used to set the value of the
|
70
|
+
# attributes. If a _block_ is given, then the attribute is a nested
|
71
|
+
# configuration and the _block_ is evaluated in the context of a new
|
72
|
+
# configuration object.
|
73
|
+
#
|
74
|
+
def method_missing( method, *args, &block )
|
75
|
+
m = method.to_s.delete('=').to_sym
|
76
|
+
|
77
|
+
__eigenclass_eval "attr_writer :#{m}"
|
78
|
+
__eigenclass_eval <<-CODE
|
79
|
+
def #{m}( *args, &block )
|
80
|
+
v = (1 == args.length ? args.first : args)
|
81
|
+
v = nil if args.empty?
|
82
|
+
v = DSL.evaluate(&block) if block
|
83
|
+
|
84
|
+
return @#{m} unless v
|
85
|
+
|
86
|
+
if @#{m}.kind_of?(Configuration)
|
87
|
+
@#{m}.merge! v
|
88
|
+
else
|
89
|
+
@#{m} = v
|
90
|
+
end
|
91
|
+
return @#{m}
|
92
|
+
end
|
93
|
+
CODE
|
94
|
+
|
95
|
+
__desc[m]
|
96
|
+
self.__send__("#{m}=", nil)
|
97
|
+
self.__send__("#{m}", *args, &block)
|
98
|
+
end
|
99
|
+
|
100
|
+
# Evaluate the given _code_ string in the context of this object's
|
101
|
+
# eigenclass (singleton class).
|
102
|
+
#
|
103
|
+
def __eigenclass_eval( code )
|
104
|
+
ec = class << self; self; end
|
105
|
+
ec.module_eval code
|
106
|
+
rescue StandardError
|
107
|
+
raise Error, "cannot evalutate this code:\n#{code}\n"
|
108
|
+
end
|
109
|
+
|
110
|
+
# Merge the contents of the _other_ configuration into this one. Values
|
111
|
+
# from the _other_ configuratin will overwite values in this
|
112
|
+
# configuration.
|
113
|
+
#
|
114
|
+
# This function is recursive. Nested configurations will be merged with
|
115
|
+
# their counterparts in the _other_ configuration.
|
116
|
+
#
|
117
|
+
def merge!( other )
|
118
|
+
return self if other.equal? self
|
119
|
+
raise Error, "can only merge another Configuration" unless other.kind_of?(Configuration)
|
120
|
+
|
121
|
+
other.__desc.each do |key,desc|
|
122
|
+
value = other.__send__(key)
|
123
|
+
if self.__send__(key).kind_of?(Configuration)
|
124
|
+
self.__send__(key).merge! value
|
125
|
+
else
|
126
|
+
self.__send__("#{key}=", value)
|
127
|
+
end
|
128
|
+
__desc[key] = desc
|
129
|
+
end
|
130
|
+
|
131
|
+
self
|
132
|
+
end
|
133
|
+
|
134
|
+
# Implementation of a doman specific language for creating configuration
|
135
|
+
# objects. Blocks of code are evaluted by the DSL which returns a new
|
136
|
+
# configuration object.
|
137
|
+
#
|
138
|
+
class DSL
|
139
|
+
alias :__instance_eval :instance_eval
|
140
|
+
|
141
|
+
instance_methods.each do |m|
|
142
|
+
undef_method m unless m[%r/^__/]
|
143
|
+
end
|
144
|
+
|
145
|
+
# Create a new DSL and evaluate the given _block_ in the context of
|
146
|
+
# the DSL. Returns a newly created configuration object.
|
147
|
+
#
|
148
|
+
def self.evaluate( &block )
|
149
|
+
dsl = self.new(&block)
|
150
|
+
dsl.__config
|
151
|
+
end
|
152
|
+
|
153
|
+
# Returns the configuration object.
|
154
|
+
attr_reader :__config
|
155
|
+
|
156
|
+
# Creates a new DSL and evaluates the given _block_ in the context of
|
157
|
+
# the DSL.
|
158
|
+
#
|
159
|
+
def initialize( &block )
|
160
|
+
@description = nil
|
161
|
+
@__config = Configuration.new
|
162
|
+
self.__instance_eval(&block) if block
|
163
|
+
end
|
164
|
+
|
165
|
+
# Dynamically adds the given _method_ to the configuration as an
|
166
|
+
# attribute. The _args_ will be used to set the value of the
|
167
|
+
# attribute. If a _block_ is given then the _args_ are ignored and the
|
168
|
+
# attribute will be a nested configuration object.
|
169
|
+
#
|
170
|
+
def method_missing( method, *args, &block )
|
171
|
+
m = method.to_s.delete('=').to_sym
|
172
|
+
|
173
|
+
opts = args.last.instance_of?(Hash) ? args.pop : {}
|
174
|
+
self.desc(opts[:desc]) if opts.has_key? :desc
|
175
|
+
|
176
|
+
__config.__send__(m, *args, &block)
|
177
|
+
__config.__desc[m] = @description
|
178
|
+
|
179
|
+
@description = nil
|
180
|
+
end
|
181
|
+
|
182
|
+
# Store the _string_ as the description for the next attribute that
|
183
|
+
# will be configured. This description will be overwritten if the
|
184
|
+
# attribute has a description passed as an options hash.
|
185
|
+
#
|
186
|
+
def desc( string )
|
187
|
+
string = string.to_s
|
188
|
+
string.strip!
|
189
|
+
string.gutter!
|
190
|
+
@description = string.empty? ? nil : string
|
191
|
+
end
|
192
|
+
end # class DSL
|
193
|
+
|
194
|
+
end # class Configuration
|
195
|
+
end # module Loquacious
|
196
|
+
|
197
|
+
# EOF
|
@@ -0,0 +1,151 @@
|
|
1
|
+
|
2
|
+
require 'pp'
|
3
|
+
require 'stringio'
|
4
|
+
|
5
|
+
class Loquacious::Configuration
|
6
|
+
|
7
|
+
# Generate nicely formatted help messages for a configuration. The Help
|
8
|
+
# class iterates over all the attributes in a configuration and outputs
|
9
|
+
# the name, value, and description to an IO stream. The format of the
|
10
|
+
# messages can be configured, and the description and/or value of the
|
11
|
+
# attribute can be shown or hidden independently.
|
12
|
+
#
|
13
|
+
class Help
|
14
|
+
|
15
|
+
# :stopdoc:
|
16
|
+
@@defaults = {
|
17
|
+
:io => $stdout,
|
18
|
+
:name_leader => ' - '.freeze,
|
19
|
+
:name_length => 0,
|
20
|
+
:name_value_sep => ' => '.freeze,
|
21
|
+
:desc_leader => ' '.freeze
|
22
|
+
}.freeze
|
23
|
+
|
24
|
+
class Error < StandardError; end
|
25
|
+
# :startdoc:
|
26
|
+
|
27
|
+
# Create a new Help instance for the given configuration where _config_
|
28
|
+
# can be either a Configuration instance or a configuration name or
|
29
|
+
# symbol. Several options can be provided to determine how the
|
30
|
+
# configuration information will be printed to the IO stream.
|
31
|
+
#
|
32
|
+
# :name_leader String appearing before the attribute name
|
33
|
+
# :name_length Maximum length for an attribute name
|
34
|
+
# :name_value_sep String separating the attribute name from the value
|
35
|
+
# :desc_leader String appearing before the description
|
36
|
+
# :io The IO object where help will be written
|
37
|
+
#
|
38
|
+
# The description is printed before each attribute name and value on its
|
39
|
+
# own line.
|
40
|
+
#
|
41
|
+
def initialize( config, opts = {} )
|
42
|
+
opts = @@defaults.merge opts
|
43
|
+
@config = config.kind_of?(::Loquacious::Configuration) ? config :
|
44
|
+
::Loquacious::Configuration.for(config)
|
45
|
+
|
46
|
+
@io = opts[:io]
|
47
|
+
@name_length = Integer(opts[:name_length])
|
48
|
+
@desc_leader = opts[:desc_leader]
|
49
|
+
|
50
|
+
unless @name_length > 0
|
51
|
+
Iterator.new(@config).each do |node|
|
52
|
+
length = node.name.length
|
53
|
+
@name_length = length if length > @name_length
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
name_leader = opts[:name_leader]
|
58
|
+
name_value_sep = opts[:name_value_sep]
|
59
|
+
extra_length = name_leader.length + name_value_sep.length
|
60
|
+
name_value_sep = name_value_sep.gsub('%', '%%')
|
61
|
+
|
62
|
+
@value_length = 78 - @name_length - extra_length
|
63
|
+
@value_leader = "\n" + ' '*(@name_length + extra_length)
|
64
|
+
@format = "#{name_leader}%-#{@name_length}s#{name_value_sep}%s"
|
65
|
+
@name_format = "#{name_leader}%s"
|
66
|
+
|
67
|
+
@desc_leader.freeze
|
68
|
+
@value_leader.freeze
|
69
|
+
@format.freeze
|
70
|
+
@name_format.freeze
|
71
|
+
end
|
72
|
+
|
73
|
+
# call-seq:
|
74
|
+
# show_attribute( name = nil, opts = {} )
|
75
|
+
#
|
76
|
+
# TODO: finish comments and docos
|
77
|
+
#
|
78
|
+
# show available attributes (with/without descriptions)
|
79
|
+
# show current config
|
80
|
+
# show everything
|
81
|
+
#
|
82
|
+
def show_attribute( name = nil, opts = {} )
|
83
|
+
name, opts = nil, name if name.is_a?(Hash)
|
84
|
+
opts = {
|
85
|
+
:descriptions => true,
|
86
|
+
:values => false
|
87
|
+
}.merge!(opts)
|
88
|
+
|
89
|
+
name = normalize_attr(name)
|
90
|
+
show_description = opts[:descriptions]
|
91
|
+
show_value = opts[:values]
|
92
|
+
|
93
|
+
Iterator.new(@config).each(name) do |node|
|
94
|
+
print_node(node, show_description, show_value)
|
95
|
+
end
|
96
|
+
end
|
97
|
+
alias :show :show_attribute
|
98
|
+
|
99
|
+
# Show all attributes for the configuration. The same options allowed by
|
100
|
+
# the +show+ method are also supported by this method.
|
101
|
+
#
|
102
|
+
def show_all( opts = {} )
|
103
|
+
show_attribute(nil, opts)
|
104
|
+
end
|
105
|
+
alias :show_attributes :show_all
|
106
|
+
|
107
|
+
# Normalize the attribute _name_.
|
108
|
+
#
|
109
|
+
def normalize_attr( name )
|
110
|
+
case name
|
111
|
+
when String, nil; name
|
112
|
+
when Symbol; name.to_s
|
113
|
+
when Array; name.join('.')
|
114
|
+
else
|
115
|
+
raise Error, "cannot convert #{name.inspect} into an attribute identifier"
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
# Format the attribute name, value, and description and print the
|
120
|
+
# results. The value can be printed or not by setting the _show_value_
|
121
|
+
# flag to either +true+ or +false+. The description can be printed or
|
122
|
+
# not by setting the _show_description_ flag to either +true+ or
|
123
|
+
# +false+.
|
124
|
+
#
|
125
|
+
def print_node( node, show_description, show_value )
|
126
|
+
desc = node.desc.to_s.dup
|
127
|
+
show_description = false if desc.empty?
|
128
|
+
@io.puts(desc.indent(@desc_leader)) if show_description
|
129
|
+
@io.puts(format_name(node, show_value))
|
130
|
+
@io.puts if show_description
|
131
|
+
end
|
132
|
+
|
133
|
+
# Format the name of the attribute pointed at by the given _node_. If
|
134
|
+
# the _show_value_ flag is set to +true+, then the attribute value will
|
135
|
+
# also be included in the returned string.
|
136
|
+
#
|
137
|
+
def format_name( node, show_value )
|
138
|
+
name = node.name.reduce @name_length
|
139
|
+
return @name_format % name if node.config? or !show_value
|
140
|
+
|
141
|
+
sio = StringIO.new
|
142
|
+
PP.pp(node.obj, sio, @value_length)
|
143
|
+
sio.seek 0
|
144
|
+
obj = sio.read.chomp.gsub("\n", @value_leader)
|
145
|
+
@format % [name, obj]
|
146
|
+
end
|
147
|
+
|
148
|
+
end # class Help
|
149
|
+
end # module Loquacious
|
150
|
+
|
151
|
+
# EOF
|
@@ -0,0 +1,152 @@
|
|
1
|
+
|
2
|
+
class Loquacious::Configuration
|
3
|
+
|
4
|
+
# Provides an external iteraotr for a Loquacious::Configuration object.
|
5
|
+
# The iterator allows the user to retrieve all the configuration settings
|
6
|
+
# along with their descriptions and values.
|
7
|
+
#
|
8
|
+
# cfg = Configuration.for('foo') {
|
9
|
+
# bar 'value', :desc => 'the bar attribute'
|
10
|
+
# baz 42, :desc => 'the baz attribute'
|
11
|
+
# }
|
12
|
+
#
|
13
|
+
# i = Iterator.new(cfg)
|
14
|
+
# i.each do |node|
|
15
|
+
# puts "#{node.name} :: #{node.desc}"
|
16
|
+
# end
|
17
|
+
#
|
18
|
+
# Results in
|
19
|
+
#
|
20
|
+
# bar :: the bar attribute
|
21
|
+
# baz :: the baz attribute
|
22
|
+
#
|
23
|
+
class Iterator
|
24
|
+
|
25
|
+
# :stopdoc:
|
26
|
+
attr_reader :stack
|
27
|
+
private :stack
|
28
|
+
# :startdoc:
|
29
|
+
|
30
|
+
# Create a new iterator that will operate on the _config_ (configuration
|
31
|
+
# object). The iterator allows the attributes of the configuration object
|
32
|
+
# to be accessed -- this includes nested configuration objects.
|
33
|
+
#
|
34
|
+
def initialize( config )
|
35
|
+
@config = config
|
36
|
+
@stack = []
|
37
|
+
reset
|
38
|
+
end
|
39
|
+
|
40
|
+
# Iterate over each node in the configuration object yielding each to
|
41
|
+
# the supplied block in turn. The return value of the block is returned
|
42
|
+
# from this method. +nil+ is returned if there are no nodes in the
|
43
|
+
# iterator.
|
44
|
+
#
|
45
|
+
# If an _attribute_ is given, then the iteration starts at that
|
46
|
+
# particular attribute and recurse if it is a nested configuration.
|
47
|
+
# Otherwise, only that attribute is yielded to the block.
|
48
|
+
#
|
49
|
+
def each( attribute = nil )
|
50
|
+
reset
|
51
|
+
rv = nil
|
52
|
+
|
53
|
+
if attribute and !attribute.empty?
|
54
|
+
node = while (n = next_node) do
|
55
|
+
break n if n.name == attribute
|
56
|
+
end
|
57
|
+
return if node.nil?
|
58
|
+
|
59
|
+
rv = yield node
|
60
|
+
return rv unless node.config?
|
61
|
+
|
62
|
+
stack.clear
|
63
|
+
stack << new_frame(node.obj, node.name) if node.config?
|
64
|
+
end
|
65
|
+
|
66
|
+
while (node = next_node) do
|
67
|
+
rv = yield node
|
68
|
+
end
|
69
|
+
return rv
|
70
|
+
end
|
71
|
+
|
72
|
+
# Find the given named _attribute_ in the iterator. Returns a node
|
73
|
+
# representing the attribute; or +nil+ is returned if the named
|
74
|
+
# attribute could not be found.
|
75
|
+
#
|
76
|
+
def find( attribute )
|
77
|
+
attribute = attribute.to_s
|
78
|
+
return if attribute.empty?
|
79
|
+
|
80
|
+
node = self.each {|n| break n if n.name == attribute}
|
81
|
+
reset
|
82
|
+
return node
|
83
|
+
end
|
84
|
+
|
85
|
+
private
|
86
|
+
|
87
|
+
# Reset the iterator back to the beginning.
|
88
|
+
#
|
89
|
+
def reset
|
90
|
+
stack.clear
|
91
|
+
stack << new_frame(@config)
|
92
|
+
end
|
93
|
+
|
94
|
+
# Returns the next node from the current iteration stack frame. Returns
|
95
|
+
# +nil+ if there are no more nodes in the iterator.
|
96
|
+
#
|
97
|
+
def next_node
|
98
|
+
frame = stack.last
|
99
|
+
node = new_node(frame)
|
100
|
+
|
101
|
+
while node.nil?
|
102
|
+
stack.pop
|
103
|
+
return if stack.empty?
|
104
|
+
frame = stack.last
|
105
|
+
node = new_node(frame)
|
106
|
+
end
|
107
|
+
|
108
|
+
frame.index += 1
|
109
|
+
stack << new_frame(node.obj, node.name) if node.config?
|
110
|
+
|
111
|
+
return node
|
112
|
+
end
|
113
|
+
|
114
|
+
# Create a new stack frame from the given _cfg_ (configuration object)
|
115
|
+
# and the optional _prefix_. The _prefix_ is used to complete the full
|
116
|
+
# name for each attribute key in the configuration object.
|
117
|
+
#
|
118
|
+
def new_frame( cfg, prefix = nil )
|
119
|
+
keys = cfg.__desc.keys.map {|k| k.to_s}
|
120
|
+
keys.sort!
|
121
|
+
keys.map! {|k| k.to_sym}
|
122
|
+
|
123
|
+
Frame.new(cfg, prefix.to_s, keys, 0)
|
124
|
+
end
|
125
|
+
|
126
|
+
# Create the next iteration node from the given stack _frame_. Returns
|
127
|
+
# +nil+ when there are no more nodes in the _frame_.
|
128
|
+
#
|
129
|
+
def new_node( frame )
|
130
|
+
key = frame.keys[frame.index]
|
131
|
+
return if key.nil?
|
132
|
+
|
133
|
+
cfg = frame.config
|
134
|
+
name = frame.prefix.empty? ? key.to_s : frame.prefix + ".#{key}"
|
135
|
+
Node.new(cfg, name, cfg.__desc[key], key)
|
136
|
+
end
|
137
|
+
|
138
|
+
# Structure describing a single iteration stack frame. A new stack frame
|
139
|
+
# is created when we descend into a nested Configuration object.
|
140
|
+
#
|
141
|
+
Frame = Struct.new( :config, :prefix, :keys, :index )
|
142
|
+
|
143
|
+
# This is a single node in a Configuration object. It corresponds to a
|
144
|
+
# single configuration attribute.
|
145
|
+
#
|
146
|
+
Node = Struct.new( :config, :name, :desc, :key ) {
|
147
|
+
def obj() config.__send__(key); end
|
148
|
+
def config?() obj.kind_of? ::Loquacious::Configuration; end
|
149
|
+
}
|
150
|
+
|
151
|
+
end # class Iterator
|
152
|
+
end # class Loquacious::Configuration
|