TwP-loquacious 1.0.0

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,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