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