mustermann-contrib 1.0.0.beta2
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.
- checksums.yaml +7 -0
- data/README.md +1239 -0
- data/examples/highlighting.rb +35 -0
- data/highlighting.png +0 -0
- data/irb.png +0 -0
- data/lib/mustermann/cake.rb +18 -0
- data/lib/mustermann/express.rb +37 -0
- data/lib/mustermann/file_utils.rb +217 -0
- data/lib/mustermann/file_utils/glob_pattern.rb +39 -0
- data/lib/mustermann/fileutils.rb +1 -0
- data/lib/mustermann/flask.rb +198 -0
- data/lib/mustermann/grape.rb +35 -0
- data/lib/mustermann/pyramid.rb +28 -0
- data/lib/mustermann/rails.rb +46 -0
- data/lib/mustermann/shell.rb +56 -0
- data/lib/mustermann/simple.rb +50 -0
- data/lib/mustermann/string_scanner.rb +313 -0
- data/lib/mustermann/strscan.rb +1 -0
- data/lib/mustermann/template.rb +62 -0
- data/lib/mustermann/uri_template.rb +1 -0
- data/lib/mustermann/versions.rb +46 -0
- data/lib/mustermann/visualizer.rb +38 -0
- data/lib/mustermann/visualizer/highlight.rb +137 -0
- data/lib/mustermann/visualizer/highlighter.rb +37 -0
- data/lib/mustermann/visualizer/highlighter/ad_hoc.rb +94 -0
- data/lib/mustermann/visualizer/highlighter/ast.rb +102 -0
- data/lib/mustermann/visualizer/highlighter/composite.rb +45 -0
- data/lib/mustermann/visualizer/highlighter/dummy.rb +18 -0
- data/lib/mustermann/visualizer/highlighter/regular.rb +104 -0
- data/lib/mustermann/visualizer/pattern_extension.rb +68 -0
- data/lib/mustermann/visualizer/renderer/ansi.rb +23 -0
- data/lib/mustermann/visualizer/renderer/generic.rb +46 -0
- data/lib/mustermann/visualizer/renderer/hansi_template.rb +34 -0
- data/lib/mustermann/visualizer/renderer/html.rb +50 -0
- data/lib/mustermann/visualizer/renderer/sexp.rb +37 -0
- data/lib/mustermann/visualizer/tree.rb +63 -0
- data/lib/mustermann/visualizer/tree_renderer.rb +78 -0
- data/mustermann-contrib.gemspec +19 -0
- data/spec/cake_spec.rb +90 -0
- data/spec/express_spec.rb +209 -0
- data/spec/file_utils_spec.rb +119 -0
- data/spec/flask_spec.rb +361 -0
- data/spec/flask_subclass_spec.rb +368 -0
- data/spec/grape_spec.rb +747 -0
- data/spec/pattern_extension_spec.rb +49 -0
- data/spec/pyramid_spec.rb +101 -0
- data/spec/rails_spec.rb +647 -0
- data/spec/shell_spec.rb +147 -0
- data/spec/simple_spec.rb +268 -0
- data/spec/string_scanner_spec.rb +271 -0
- data/spec/template_spec.rb +841 -0
- data/spec/visualizer_spec.rb +199 -0
- data/theme.png +0 -0
- data/tree.png +0 -0
- metadata +126 -0
@@ -0,0 +1,45 @@
|
|
1
|
+
module Mustermann
|
2
|
+
module Visualizer
|
3
|
+
# @!visibility private
|
4
|
+
module Highlighter
|
5
|
+
# @!visibility private
|
6
|
+
module Composite
|
7
|
+
extend self
|
8
|
+
|
9
|
+
# @!visibility private
|
10
|
+
def highlight?(pattern)
|
11
|
+
pattern.is_a? Mustermann::Composite
|
12
|
+
end
|
13
|
+
|
14
|
+
# @!visibility private
|
15
|
+
def highlight(pattern, renderer)
|
16
|
+
operator = " #{pattern.operator} "
|
17
|
+
patterns = pattern.patterns.map { |p| highlight_nested(p, renderer) }.join(quote(renderer, operator))
|
18
|
+
renderer.pre(:composite) + patterns + renderer.post(:composite)
|
19
|
+
end
|
20
|
+
|
21
|
+
# @!visibility private
|
22
|
+
def highlight_nested(pattern, renderer)
|
23
|
+
highlighter = Highlighter.highlighter_for(pattern)
|
24
|
+
if highlighter.respond_to? :nested_highlight
|
25
|
+
highlighter.nested_highlight(pattern, renderer)
|
26
|
+
else
|
27
|
+
type = quote(renderer, pattern.class.name[/[^:]+$/].downcase + ":", :type)
|
28
|
+
quote = quote(renderer, ?")
|
29
|
+
type + quote + highlighter.highlight(pattern, renderer) + quote
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
# @!visibility private
|
34
|
+
def nested_highlight(pattern, renderer)
|
35
|
+
quote(renderer, ?() + highlight(pattern, renderer) + quote(renderer, ?))
|
36
|
+
end
|
37
|
+
|
38
|
+
# @!visibility private
|
39
|
+
def quote(renderer, string, type = :quote)
|
40
|
+
renderer.pre(type) + renderer.escape(string, string) + renderer.post(type)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module Mustermann
|
2
|
+
module Visualizer
|
3
|
+
# @!visibility private
|
4
|
+
module Highlighter
|
5
|
+
# Provides highlighting for patterns that don't have a highlighter.
|
6
|
+
# @!visibility private
|
7
|
+
module Dummy
|
8
|
+
# @!visibility private
|
9
|
+
def self.highlight(pattern, renderer)
|
10
|
+
output = ""
|
11
|
+
output << renderer.pre(:root) << renderer.pre(:unknown)
|
12
|
+
output << renderer.escape(pattern.to_s)
|
13
|
+
output << renderer.post(:unknown) << renderer.post(:root)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,104 @@
|
|
1
|
+
require 'strscan'
|
2
|
+
|
3
|
+
module Mustermann
|
4
|
+
module Visualizer
|
5
|
+
# @!visibility private
|
6
|
+
module Highlighter
|
7
|
+
# Provides highlighting for {Mustermann::Regular}
|
8
|
+
# @!visibility private
|
9
|
+
class Regular
|
10
|
+
# @!visibility private
|
11
|
+
SPECIAL_ESCAPE = ['w', 'W', 'd', 'D', 'h', 'H', 's', 'S', 'G', 'b', 'B']
|
12
|
+
private_constant(:SPECIAL_ESCAPE)
|
13
|
+
|
14
|
+
# @!visibility private
|
15
|
+
def self.highlight?(pattern)
|
16
|
+
pattern.class.name == "Mustermann::Regular"
|
17
|
+
end
|
18
|
+
|
19
|
+
# @!visibility private
|
20
|
+
def self.highlight(pattern, renderer)
|
21
|
+
new(renderer).highlight(pattern)
|
22
|
+
end
|
23
|
+
|
24
|
+
# @!visibility private
|
25
|
+
attr_reader :renderer, :output, :scanner
|
26
|
+
|
27
|
+
# @!visibility private
|
28
|
+
def initialize(renderer)
|
29
|
+
@renderer = renderer
|
30
|
+
@output = ""
|
31
|
+
end
|
32
|
+
|
33
|
+
# @!visibility private
|
34
|
+
def highlight(pattern)
|
35
|
+
output << renderer.pre(:root)
|
36
|
+
@scanner = ::StringScanner.new(pattern.to_s)
|
37
|
+
scan
|
38
|
+
output << renderer.post(:root)
|
39
|
+
end
|
40
|
+
|
41
|
+
# @!visibility private
|
42
|
+
def scan(stop = nil)
|
43
|
+
until scanner.eos?
|
44
|
+
case char = scanner.getch
|
45
|
+
when stop then return char
|
46
|
+
when ?/ then element(:separator, char)
|
47
|
+
when Regexp.escape(char) then element(:char, char)
|
48
|
+
when ?\\ then escaped(scanner.getch)
|
49
|
+
when ?( then potential_capture
|
50
|
+
when ?[ then char_class
|
51
|
+
when ?^, ?$ then element(:illegal, char)
|
52
|
+
when ?{ then element(:special, "\{#{scanner.scan(/[^\}]*\}/)}")
|
53
|
+
else element(:special, char)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
# @!visibility private
|
59
|
+
def char_class
|
60
|
+
if result = scanner.scan(/\[:\w+:\]\]/)
|
61
|
+
element(:special, "[#{result}")
|
62
|
+
else
|
63
|
+
element(:special, ?[)
|
64
|
+
element(:special, ?^) if scanner.scan(/\^/)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
# @!visibility private
|
69
|
+
def potential_capture
|
70
|
+
if scanner.scan(/\?<(\w+)>/)
|
71
|
+
element(:capture, "(?<") do
|
72
|
+
element(:name, scanner[1])
|
73
|
+
output << ">" << scan(?))
|
74
|
+
end
|
75
|
+
elsif scanner.scan(/\?(?:(?:-\w+)?:|>|<=|<!|!|=)/)
|
76
|
+
element(:special, "(#{scanner[0]}")
|
77
|
+
else
|
78
|
+
element(:capture, "(") { output << scan(?)) }
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
# @!visibility private
|
83
|
+
def escaped(char)
|
84
|
+
case char
|
85
|
+
when *SPECIAL_ESCAPE then element(:special, "\\#{char}")
|
86
|
+
when 'A', 'Z', 'z' then element(:illegal, "\\#{char}")
|
87
|
+
when 'g' then element(:special, "\\#{char}#{scanner.scan(/<\w*>/)}")
|
88
|
+
when 'p', 'u' then element(:special, "\\#{char}#{scanner.scan(/\{[^\}]*\}/)}")
|
89
|
+
when ?/ then element(:separator, char)
|
90
|
+
else element(:escaped, ?\\) { element(:escaped_char, char) }
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
# @!visibility private
|
95
|
+
def element(type, content = nil)
|
96
|
+
output << renderer.pre(type)
|
97
|
+
output << renderer.escape(content) if content
|
98
|
+
yield if block_given?
|
99
|
+
output << renderer.post(type)
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
module Mustermann
|
2
|
+
module Visualizer
|
3
|
+
# Mixin that will be added to {Mustermann::Pattern}.
|
4
|
+
module PatternExtension
|
5
|
+
prepend_features Composite
|
6
|
+
prepend_features Pattern
|
7
|
+
|
8
|
+
# @example
|
9
|
+
# puts Mustermann.new("/:page").to_ansi
|
10
|
+
#
|
11
|
+
# @return [String] ANSI colorized version of the pattern.
|
12
|
+
def to_ansi(inspect: nil, **theme)
|
13
|
+
Visualizer.highlight(self, **theme).to_ansi(inspect: inspect)
|
14
|
+
end
|
15
|
+
|
16
|
+
# @example
|
17
|
+
# puts Mustermann.new("/:page").to_html
|
18
|
+
#
|
19
|
+
# @return [String] HTML version of the pattern.
|
20
|
+
def to_html(inspect: nil, tag: :span, class_prefix: "mustermann_", css: :inline, **theme)
|
21
|
+
Visualizer.highlight(self, **theme).to_html(inspect: inspect, tag: tag, class_prefix: class_prefix, css: css)
|
22
|
+
end
|
23
|
+
|
24
|
+
# @example
|
25
|
+
# puts Mustermann.new("/:page").to_tree
|
26
|
+
#
|
27
|
+
# @return [String] tree version of the pattern.
|
28
|
+
def to_tree
|
29
|
+
Visualizer.tree(self).to_s
|
30
|
+
end
|
31
|
+
|
32
|
+
# If invoked directly by puts: ANSI colorized version of the pattern.
|
33
|
+
# If invoked by anything else: String version of the pattern.
|
34
|
+
#
|
35
|
+
# @example
|
36
|
+
# require 'mustermann/visualizer'
|
37
|
+
# pattern = Mustermann.new('/:page')
|
38
|
+
# puts pattern # will have color
|
39
|
+
# puts pattern.to_s # will not have color
|
40
|
+
#
|
41
|
+
# @return [String] non-colorized or colorized version of the pattern
|
42
|
+
def to_s
|
43
|
+
caller_locations.first.label == 'puts' ? to_ansi : super
|
44
|
+
end
|
45
|
+
|
46
|
+
# If invoked directly by IRB, same as {#color_inspect}, otherwise same as {Mustermann::Pattern#inspect}.
|
47
|
+
def inspect
|
48
|
+
caller_locations.first.base_label == '<module:IRB>' ? color_inspect : super
|
49
|
+
end
|
50
|
+
|
51
|
+
# @return [String] ANSI colorized version of {Mustermann::Pattern#inspect}
|
52
|
+
def color_inspect(base_color = nil, **theme)
|
53
|
+
base_color ||= Highlight::DEFAULT_THEME[:base01]
|
54
|
+
template = is_a?(Composite) ? "*#<%p:(*%s*)>*" : "*#<%p:*%s*>*"
|
55
|
+
Hansi.render(template, self.class, to_ansi(inspect: true, **theme), "*" => base_color)
|
56
|
+
end
|
57
|
+
|
58
|
+
# If invoked directly by IRB, same as {#color_inspect}, otherwise same as Object#pretty_print.
|
59
|
+
def pretty_print(q)
|
60
|
+
if q.class.name.to_s[/[^:]+$/] == "ColorPrinter"
|
61
|
+
q.text(color_inspect, inspect.length)
|
62
|
+
else
|
63
|
+
super
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module Mustermann
|
2
|
+
module Visualizer
|
3
|
+
# @!visibility private
|
4
|
+
module Renderer
|
5
|
+
# Generates ANSI colored strings.
|
6
|
+
# @!visibility private
|
7
|
+
class ANSI
|
8
|
+
# @!visibility private
|
9
|
+
def initialize(target, mode: Hansi.mode, **options)
|
10
|
+
@target = target
|
11
|
+
@mode = mode
|
12
|
+
@options = options
|
13
|
+
end
|
14
|
+
|
15
|
+
# @!visibility private
|
16
|
+
def render
|
17
|
+
template = @target.to_hansi_template(**@options)
|
18
|
+
Hansi.render(template, tags: true, theme: @target.theme, mode: @mode)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
module Mustermann
|
2
|
+
module Visualizer
|
3
|
+
# @!visibility private
|
4
|
+
module Renderer
|
5
|
+
# Logic shared by most renderers.
|
6
|
+
class Generic
|
7
|
+
# @!visibility private
|
8
|
+
def initialize(target, inspect: nil, add_qoutes: true)
|
9
|
+
@target = target
|
10
|
+
@inspect = inspect
|
11
|
+
@add_qoutes = !target.pattern.is_a?(Mustermann::Composite)
|
12
|
+
end
|
13
|
+
|
14
|
+
# @!visibility private
|
15
|
+
def render
|
16
|
+
quote = "#{pre(:quote)}#{escape_string(?")}#{post(:quote)}" if @inspect and @add_qoutes
|
17
|
+
pre(:pattern).to_s + preamble.to_s + quote.to_s + @target.render(self) + quote.to_s + post(:pattern).to_s
|
18
|
+
end
|
19
|
+
|
20
|
+
# @!visibility private
|
21
|
+
def preamble
|
22
|
+
end
|
23
|
+
|
24
|
+
# @!visibility private
|
25
|
+
def escape(value, inspect_value = value.to_s.inspect[1..-2])
|
26
|
+
escape_string(@inspect ? inspect_value : value.to_s)
|
27
|
+
end
|
28
|
+
|
29
|
+
# @!visibility private
|
30
|
+
def escape_string(string)
|
31
|
+
string
|
32
|
+
end
|
33
|
+
|
34
|
+
# @!visibility private
|
35
|
+
def pre(type)
|
36
|
+
""
|
37
|
+
end
|
38
|
+
|
39
|
+
# @!visibility private
|
40
|
+
def post(type)
|
41
|
+
""
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
require 'mustermann/visualizer/renderer/generic'
|
2
|
+
|
3
|
+
module Mustermann
|
4
|
+
module Visualizer
|
5
|
+
# @!visibility private
|
6
|
+
module Renderer
|
7
|
+
# Generates Hansi template string.
|
8
|
+
# @see Mustermann::Visualizer::Renderer::ANSI
|
9
|
+
# @!visibility private
|
10
|
+
class HansiTemplate < Generic
|
11
|
+
# @!visibility private
|
12
|
+
def initialize(*)
|
13
|
+
@hansi = Hansi::StringRenderer.new(tags: true)
|
14
|
+
super
|
15
|
+
end
|
16
|
+
|
17
|
+
# @!visibility private
|
18
|
+
def escape_string(string)
|
19
|
+
@hansi.escape(string)
|
20
|
+
end
|
21
|
+
|
22
|
+
# @!visibility private
|
23
|
+
def pre(type)
|
24
|
+
"<#{type}>"
|
25
|
+
end
|
26
|
+
|
27
|
+
# @!visibility private
|
28
|
+
def post(type)
|
29
|
+
"</#{type}>"
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
require 'mustermann/visualizer/renderer/generic'
|
2
|
+
require 'cgi'
|
3
|
+
|
4
|
+
module Mustermann
|
5
|
+
module Visualizer
|
6
|
+
# @!visibility private
|
7
|
+
module Renderer
|
8
|
+
# Generates HTML output.
|
9
|
+
# @!visibility private
|
10
|
+
class HTML < Generic
|
11
|
+
# @!visibility private
|
12
|
+
def initialize(target, tag: :span, class_prefix: "mustermann_", css: :inline, **options)
|
13
|
+
raise ArgumentError, 'css option %p not supported, should be true, false or inline' if css != true and css != false and css != :inline
|
14
|
+
super(target, **options)
|
15
|
+
@css, @tag, @class_prefix = css, tag, class_prefix
|
16
|
+
end
|
17
|
+
|
18
|
+
# @!visibility private
|
19
|
+
def preamble
|
20
|
+
"<style type=\"text/css\">\n%s</style>" % stylesheet if @css == true
|
21
|
+
end
|
22
|
+
|
23
|
+
# @!visibility private
|
24
|
+
def stylesheet
|
25
|
+
@target.theme.to_css { |name| ".#{@class_prefix}pattern .#{@class_prefix}#{name}" }
|
26
|
+
end
|
27
|
+
|
28
|
+
# @!visibility private
|
29
|
+
def escape_string(string)
|
30
|
+
CGI.escape_html(string)
|
31
|
+
end
|
32
|
+
|
33
|
+
# @!visibility private
|
34
|
+
def pre(type)
|
35
|
+
if @css == :inline
|
36
|
+
return "" unless rule = @target.theme[type]
|
37
|
+
"<#{@tag} style=\"#{rule.to_css_rule}\">"
|
38
|
+
else
|
39
|
+
"<#{@tag} class=\"#{@class_prefix}#{type}\">"
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
# @!visibility private
|
44
|
+
def post(type)
|
45
|
+
"</#{@tag}>"
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
require 'mustermann/visualizer/renderer/generic'
|
2
|
+
|
3
|
+
module Mustermann
|
4
|
+
module Visualizer
|
5
|
+
# @!visibility private
|
6
|
+
module Renderer
|
7
|
+
# Generates a s-expression like string.
|
8
|
+
# @!visibility private
|
9
|
+
class Sexp < Generic
|
10
|
+
# @!visibility private
|
11
|
+
def render
|
12
|
+
@inspect = false
|
13
|
+
super.gsub(/ ?\)( \))*/) { |s| s.gsub(' ', '') }.strip
|
14
|
+
end
|
15
|
+
|
16
|
+
|
17
|
+
# @!visibility private
|
18
|
+
def pre(type)
|
19
|
+
"(#{type} " if type != :pattern
|
20
|
+
end
|
21
|
+
|
22
|
+
# @!visibility private
|
23
|
+
def escape_string(input)
|
24
|
+
inspect = input.inspect
|
25
|
+
input = inspect if inspect != "\"#{input}\""
|
26
|
+
input = inspect if input =~ /[\s\"\'\(\)]/
|
27
|
+
input + " "
|
28
|
+
end
|
29
|
+
|
30
|
+
# @!visibility private
|
31
|
+
def post(type)
|
32
|
+
") " if type != :pattern
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
require 'hansi'
|
2
|
+
|
3
|
+
module Mustermann
|
4
|
+
module Visualizer
|
5
|
+
# Represents a (sub)tree and at the same time a node in the tree.
|
6
|
+
class Tree
|
7
|
+
# @!visibility private
|
8
|
+
attr_reader :line, :children, :prefix_color, :before, :after
|
9
|
+
|
10
|
+
# @!visibility private
|
11
|
+
def initialize(line, *children, prefix_color: :default, before: "", after: "")
|
12
|
+
@line = line
|
13
|
+
@children = children
|
14
|
+
@prefix_color = prefix_color
|
15
|
+
@before = before
|
16
|
+
@after = after
|
17
|
+
end
|
18
|
+
|
19
|
+
# used for positioning {#after}
|
20
|
+
# @!visibility private
|
21
|
+
def line_widths(offset = 0)
|
22
|
+
child_widths = children.flat_map { |c| c.line_widths(offset + 2) }
|
23
|
+
width = length(line + before) + offset
|
24
|
+
[width, *child_widths]
|
25
|
+
end
|
26
|
+
|
27
|
+
# Renders the tree.
|
28
|
+
# @return [String] rendered version of the tree
|
29
|
+
def to_s
|
30
|
+
render("", "", line_widths.max)
|
31
|
+
end
|
32
|
+
|
33
|
+
# Renders tree, including nesting.
|
34
|
+
# @!visibility private
|
35
|
+
def render(first_prefix, prefix, width)
|
36
|
+
output = before + Hansi.render(prefix_color, first_prefix) + line
|
37
|
+
output = ljust(output, width) + " " + after + "\n"
|
38
|
+
children[0..-2].each { |child| output += child.render(prefix + "├ ", prefix + "│ ", width) }
|
39
|
+
output += children.last.render(prefix + "└ ", prefix + " ", width) if children.last
|
40
|
+
output
|
41
|
+
end
|
42
|
+
|
43
|
+
# @!visibility private
|
44
|
+
def length(string)
|
45
|
+
deansi(string).length
|
46
|
+
end
|
47
|
+
|
48
|
+
# @!visibility private
|
49
|
+
def deansi(string)
|
50
|
+
string.gsub(/\e\[[^m]+m/, '')
|
51
|
+
end
|
52
|
+
|
53
|
+
# @!visibility private
|
54
|
+
def ljust(string, width)
|
55
|
+
missing = width - length(string)
|
56
|
+
append = missing > 0 ? " " * missing : ""
|
57
|
+
string + append
|
58
|
+
end
|
59
|
+
|
60
|
+
private :ljust, :deansi, :length
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|