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.
- data/.gitignore +23 -0
- data/Guardfile +5 -0
- data/History.txt +127 -0
- data/README.rdoc +229 -0
- data/Rakefile +30 -0
- data/adhearsion-loquacious.gemspec +32 -0
- data/examples/gutters.rb +29 -0
- data/examples/nested.rb +43 -0
- data/examples/simple.rb +20 -0
- data/lib/loquacious.rb +165 -0
- data/lib/loquacious/configuration.rb +406 -0
- data/lib/loquacious/configuration/help.rb +249 -0
- data/lib/loquacious/configuration/iterator.rb +158 -0
- data/lib/loquacious/core_ext/string.rb +75 -0
- data/lib/loquacious/undefined.rb +92 -0
- data/lib/loquacious/utility.rb +14 -0
- data/loquacious.gemspec +32 -0
- data/spec/configuration_spec.rb +513 -0
- data/spec/help_spec.rb +369 -0
- data/spec/iterator_spec.rb +70 -0
- data/spec/loquacious_spec.rb +76 -0
- data/spec/spec_helper.rb +70 -0
- data/spec/string_spec.rb +53 -0
- data/spec/utility_spec.rb +28 -0
- data/version.txt +1 -0
- metadata +102 -0
|
@@ -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
|