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,249 @@
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
+ :nesting_nodes => true,
23
+ :colorize => false,
24
+ :colors => {
25
+ :name => :white,
26
+ :value => :cyan,
27
+ :description => :green,
28
+ :leader => :yellow
29
+ }.freeze
30
+ }.freeze
31
+
32
+ class Error < StandardError; end
33
+ # :startdoc:
34
+
35
+ # Create a new Help instance for the given configuration where _config_
36
+ # can be either a Configuration instance or a configuration name or
37
+ # symbol. Several options can be provided to determine how the
38
+ # configuration information will be printed to the IO stream.
39
+ #
40
+ # :name_leader String appearing before the attribute name
41
+ # :name_length Maximum length for an attribute name
42
+ # :name_value_sep String separating the attribute name from the value
43
+ # :desc_leader String appearing before the description
44
+ # :io The IO object where help will be written
45
+ # :nesting_nodes Flag to enable or disable output of nesting nodes
46
+ # (this does not affect display of attributes
47
+ # contained by the nesting nodes)
48
+ # :colorize Flag to colorize the output or not
49
+ # :colors Hash of colors for the name, value, description
50
+ # :name Name color
51
+ # :value Value color
52
+ # :description Description color
53
+ # :leader Leader and spacer color
54
+ #
55
+ # The description is printed before each attribute name and value on its
56
+ # own line.
57
+ #
58
+ def initialize( config, opts = {} )
59
+ opts = @@defaults.merge opts
60
+ @config = config.kind_of?(::Loquacious::Configuration) ? config :
61
+ ::Loquacious::Configuration.for(config)
62
+
63
+ @io = opts[:io]
64
+ @name_length = Integer(opts[:name_length])
65
+ @desc_leader = opts[:desc_leader]
66
+ @nesting_nodes = opts[:nesting_nodes]
67
+ @colorize = opts[:colorize]
68
+ @colors = opts[:colors]
69
+
70
+ unless @name_length > 0
71
+ Iterator.new(@config).each do |node|
72
+ length = node.name.length
73
+ @name_length = length if length > @name_length
74
+ end
75
+ end
76
+
77
+ name_leader = opts[:name_leader]
78
+ name_value_sep = opts[:name_value_sep]
79
+ extra_length = name_leader.length + name_value_sep.length
80
+ name_value_sep = name_value_sep.gsub('%', '%%')
81
+
82
+ @value_length = 78 - @name_length - extra_length
83
+ @value_leader = "\n" + ' '*(@name_length + extra_length)
84
+ @format = "#{name_leader}%-#{@name_length}s#{name_value_sep}%s"
85
+ @name_format = "#{name_leader}%s"
86
+
87
+ if colorize?
88
+ @desc_leader = self.__send__(@colors[:leader], @desc_leader)
89
+ name_leader = self.__send__(@colors[:leader], name_leader)
90
+ name_value_sep = self.__send__(@colors[:leader], name_value_sep)
91
+
92
+ @format = name_leader.dup
93
+ @format << self.__send__(@colors[:name], "%-#{@name_length}s")
94
+ @format << name_value_sep.dup
95
+ @format << self.__send__(@colors[:value], "%s")
96
+
97
+ @name_format = name_leader.dup
98
+ @name_format << self.__send__(@colors[:name], "%s")
99
+ end
100
+
101
+ @desc_leader.freeze
102
+ @value_leader.freeze
103
+ @format.freeze
104
+ @name_format.freeze
105
+ end
106
+
107
+ # Returns +true+ if the help instance is configured to colorize the
108
+ # output messages. Returns +false+ otherwise.
109
+ #
110
+ def colorize?
111
+ @colorize
112
+ end
113
+
114
+ # Returns +true+ if the help instance is configured to show nesting
115
+ # configuration nodes when iterating over the attributes. This only
116
+ # prevents the nesting node name from being displayed. The attributes
117
+ # nested under the node are still displayed regardless of this setting.
118
+ #
119
+ def show_nesting_nodes?
120
+ @nesting_nodes
121
+ end
122
+
123
+ # call-seq:
124
+ # show_attribute( name = nil, opts = {} )
125
+ #
126
+ # Use this method to show the description for a single attribute or for
127
+ # all the attributes if no _name_ is given. The options allow you to
128
+ # show the values along with the attributes and to hide the descriptions
129
+ # (if all you want to see are the values).
130
+ #
131
+ # :descriptions => true to show descriptions and false to hide them
132
+ # :values => true to show values and false to hide them
133
+ #
134
+ def show_attribute( name = nil, opts = {} )
135
+ name, opts = nil, name if name.is_a?(Hash)
136
+ opts = {
137
+ :descriptions => true,
138
+ :values => false
139
+ }.merge!(opts)
140
+
141
+ rgxp = Regexp.new(normalize_attr(name))
142
+ show_description = opts[:descriptions]
143
+ show_value = opts[:values]
144
+
145
+ Iterator.new(@config).each do |node|
146
+ next unless rgxp =~ node.name
147
+ next if !show_nesting_nodes? and node.config?
148
+ print_node(node, show_description, show_value)
149
+ end
150
+ end
151
+ alias :show :show_attribute
152
+
153
+ # Show all attributes for the configuration. The same options allowed by
154
+ # the +show+ method are also supported by this method.
155
+ #
156
+ def show_all( opts = {} )
157
+ show_attribute(nil, opts)
158
+ end
159
+ alias :show_attributes :show_all
160
+
161
+ # Normalize the attribute _name_.
162
+ #
163
+ def normalize_attr( name )
164
+ case name
165
+ when String, nil; name.to_s
166
+ when Symbol; name.to_s
167
+ when Array; name.join('.')
168
+ else
169
+ raise Error, "cannot convert #{name.inspect} into an attribute identifier"
170
+ end
171
+ end
172
+
173
+ # Format the attribute name, value, and description and print the
174
+ # results. The value can be printed or not by setting the _show_value_
175
+ # flag to either +true+ or +false+. The description can be printed or
176
+ # not by setting the _show_description_ flag to either +true+ or
177
+ # +false+.
178
+ #
179
+ def print_node( node, show_description, show_value )
180
+ desc = node.desc.to_s.dup
181
+ show_description = false if desc.empty?
182
+
183
+ if show_description
184
+ if colorize?
185
+ desc = desc.gsub(%r/([^\n]+)/,
186
+ self.__send__(@colors[:description], '\1'))
187
+ end
188
+ @io.puts(desc.indent(@desc_leader))
189
+ end
190
+
191
+ @io.puts(format_name(node, show_value))
192
+ @io.puts if show_description
193
+ end
194
+
195
+ # Format the name of the attribute pointed at by the given _node_. If
196
+ # the _show_value_ flag is set to +true+, then the attribute value will
197
+ # also be included in the returned string.
198
+ #
199
+ def format_name( node, show_value )
200
+ name = node.name.reduce @name_length
201
+ return @name_format % name if node.config? or !show_value
202
+
203
+ sio = StringIO.new
204
+ PP.pp(node.obj, sio, @value_length)
205
+ sio.seek 0
206
+ obj = sio.read.chomp.gsub("\n", @value_leader)
207
+ @format % [name, obj]
208
+ end
209
+
210
+ [ [ :clear , 0 ],
211
+ [ :reset , 0 ], # synonym for :clear
212
+ [ :bold , 1 ],
213
+ [ :dark , 2 ],
214
+ [ :italic , 3 ], # not widely implemented
215
+ [ :underline , 4 ],
216
+ [ :underscore , 4 ], # synonym for :underline
217
+ [ :blink , 5 ],
218
+ [ :rapid_blink , 6 ], # not widely implemented
219
+ [ :negative , 7 ], # no reverse because of String#reverse
220
+ [ :concealed , 8 ],
221
+ [ :strikethrough, 9 ], # not widely implemented
222
+ [ :black , 30 ],
223
+ [ :red , 31 ],
224
+ [ :green , 32 ],
225
+ [ :yellow , 33 ],
226
+ [ :blue , 34 ],
227
+ [ :magenta , 35 ],
228
+ [ :cyan , 36 ],
229
+ [ :white , 37 ],
230
+ [ :on_black , 40 ],
231
+ [ :on_red , 41 ],
232
+ [ :on_green , 42 ],
233
+ [ :on_yellow , 43 ],
234
+ [ :on_blue , 44 ],
235
+ [ :on_magenta , 45 ],
236
+ [ :on_cyan , 46 ],
237
+ [ :on_white , 47 ] ].each do |name,code|
238
+
239
+ class_eval <<-CODE
240
+ def #{name.to_s}( str )
241
+ "\e[#{code}m\#{str}\e[0m"
242
+ end
243
+ CODE
244
+ end
245
+
246
+ end # class Help
247
+ end # module Loquacious
248
+
249
+ # EOF
@@ -0,0 +1,158 @@
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
+ node = next_node if node.undefined?
112
+ return node
113
+ end
114
+
115
+ # Create a new stack frame from the given _cfg_ (configuration object)
116
+ # and the optional _prefix_. The _prefix_ is used to complete the full
117
+ # name for each attribute key in the configuration object.
118
+ #
119
+ def new_frame( cfg, prefix = nil )
120
+ keys = cfg.__desc.keys.map {|k| k.to_s}
121
+ keys.sort!
122
+ keys.map! {|k| k.to_sym}
123
+
124
+ Frame.new(cfg, prefix.to_s, keys, 0)
125
+ end
126
+
127
+ # Create the next iteration node from the given stack _frame_. Returns
128
+ # +nil+ when there are no more nodes in the _frame_.
129
+ #
130
+ def new_node( frame )
131
+ key = frame.keys[frame.index]
132
+ return if key.nil?
133
+
134
+ cfg = frame.config
135
+ name = frame.prefix.empty? ? key.to_s : frame.prefix + ".#{key}"
136
+ description = cfg.__desc[key]
137
+ if Loquacious.env_config && description
138
+ description += " [" + Loquacious::Utility.env_var_name(name, cfg) + "]"
139
+ end
140
+ Node.new(cfg, name, description, key)
141
+ end
142
+
143
+ # Structure describing a single iteration stack frame. A new stack frame
144
+ # is created when we descend into a nested Configuration object.
145
+ #
146
+ Frame = Struct.new( :config, :prefix, :keys, :index )
147
+
148
+ # This is a single node in a Configuration object. It corresponds to a
149
+ # single configuration attribute.
150
+ #
151
+ Node = Struct.new( :config, :name, :desc, :key ) {
152
+ def config?() obj.kind_of? ::Loquacious::Configuration; end
153
+ def undefined?() obj.kind_of? ::Loquacious::Undefined; end
154
+ def obj() config.__send__(key); end
155
+ }
156
+
157
+ end # class Iterator
158
+ end # class Loquacious::Configuration
@@ -0,0 +1,75 @@
1
+
2
+ class String
3
+
4
+ # call-seq:
5
+ # reduce( width, ellipses = '...' ) #=> string
6
+ #
7
+ # Reduce the size of the current string to the given _width_ by removing
8
+ # characters from the middle of the string and replacing them with
9
+ # _ellipses_. If the _width_ is greater than the length of the string, the
10
+ # string is returned unchanged. If the _width_ is less than the length of
11
+ # the _ellipses_, then the _ellipses_ are returned.
12
+ #
13
+ def reduce( width, ellipses = '...')
14
+ raise ArgumentError, "width cannot be negative: #{width}" if width < 0
15
+
16
+ return self if length <= width
17
+
18
+ remove = length - width + ellipses.length
19
+ return ellipses.dup if remove >= length
20
+
21
+ left_end = (length + 1 - remove) / 2
22
+ right_start = left_end + remove
23
+
24
+ left = self[0,left_end]
25
+ right = self[right_start,length-right_start]
26
+
27
+ left << ellipses << right
28
+ end
29
+
30
+ # call-seq:
31
+ # "foo".indent( 2 ) #=> " foo"
32
+ # "foo".indent( '# ' ) #=> "# foo"
33
+ #
34
+ # Indent the string by the given number of spaces. Alternately, if a
35
+ # leader string is given it will be used to indent with instead of spaces.
36
+ # Indentation is performed at the beginning of the string and after every
37
+ # newline character.
38
+ #
39
+ # "foo\nbar".indent( 2 ) #=> " foo\n bar"
40
+ #
41
+ def indent( leader )
42
+ leader =
43
+ Numeric === leader ? ' ' * leader.to_i : leader.to_s
44
+ str = self.gsub("\n", "\n"+leader)
45
+ str.insert(0, leader)
46
+ str
47
+ end
48
+
49
+ # call-seq:
50
+ # " | foo\n | bar".gutter! #=> " foo\n bar"
51
+ #
52
+ # Removes a leading _gutter_ from all lines in the string. The gutter is
53
+ # defined leading whitespace followed by a single pipe character. This
54
+ # method is very useful with heredocs.
55
+ #
56
+ # The string will be altered by this method.
57
+ #
58
+ def gutter!
59
+ gsub! %r/^[\t\f\r ]*\|?/, ''
60
+ self
61
+ end
62
+
63
+ # call-seq:
64
+ # " | foo\n | bar".gutter! #=> " foo\n bar"
65
+ #
66
+ # Removes a leading _gutter_ from all lines in the string. The gutter is
67
+ # defined leading whitespace followed by a single pipe character. This
68
+ # method is very useful with heredocs.
69
+ #
70
+ def gutter
71
+ self.dup.gutter!
72
+ end
73
+ end # class String
74
+
75
+ # EOF