cog 0.2.2 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (79) hide show
  1. data/BuiltIn.cogfile +96 -0
  2. data/bin/cog +30 -39
  3. data/built_in/generators/sort.rb +3 -0
  4. data/built_in/plugins/basic/Cogfile +6 -0
  5. data/built_in/plugins/basic/templates/basic/generator.rb.erb +1 -0
  6. data/built_in/templates/cog/Cogfile.erb +40 -0
  7. data/built_in/templates/cog/plugin/generator.rb.erb.erb +3 -0
  8. data/{templates/cog/custom_tool/tool.rb.erb → built_in/templates/cog/plugin/plugin.rb.erb} +4 -11
  9. data/{templates → built_in/templates}/warning.erb +0 -0
  10. data/lib/cog.rb +25 -11
  11. data/lib/cog/config.rb +112 -49
  12. data/lib/cog/config/{language_methods.rb → language_config.rb} +23 -27
  13. data/lib/cog/config/plugin_config.rb +31 -0
  14. data/lib/cog/config/project_config.rb +48 -0
  15. data/lib/cog/controllers.rb +6 -8
  16. data/lib/cog/controllers/generator_controller.rb +30 -21
  17. data/lib/cog/controllers/plugin_controller.rb +43 -0
  18. data/lib/cog/controllers/template_controller.rb +12 -33
  19. data/lib/cog/dsl.rb +9 -0
  20. data/lib/cog/dsl/cogfile.rb +145 -0
  21. data/lib/cog/dsl/language_dsl.rb +142 -0
  22. data/lib/cog/embed_context.rb +0 -2
  23. data/lib/cog/embeds.rb +2 -7
  24. data/lib/cog/errors.rb +11 -24
  25. data/lib/cog/generator.rb +10 -15
  26. data/lib/cog/generator/file_methods.rb +2 -2
  27. data/lib/cog/generator/filters.rb +10 -14
  28. data/lib/cog/generator/language_methods.rb +2 -3
  29. data/lib/cog/generator_sandbox.rb +32 -0
  30. data/lib/cog/helpers.rb +10 -2
  31. data/lib/cog/helpers/cascading_set.rb +104 -0
  32. data/lib/cog/{embeds → helpers}/file_scanner.rb +1 -1
  33. data/lib/cog/language.rb +140 -0
  34. data/lib/cog/native_extensions.rb +2 -0
  35. data/lib/cog/native_extensions/array.rb +19 -0
  36. data/lib/cog/native_extensions/string.rb +54 -0
  37. data/lib/cog/plugin.rb +35 -0
  38. data/lib/cog/spec_helpers.rb +36 -14
  39. data/lib/cog/spec_helpers/matchers.rb +14 -4
  40. data/lib/cog/spec_helpers/matchers/match_maker.rb +1 -1
  41. data/lib/cog/spec_helpers/runner.rb +3 -9
  42. data/lib/cog/version.rb +1 -1
  43. metadata +27 -44
  44. data/Default.cogfile +0 -25
  45. data/lib/cog/built_in_tools.rb +0 -9
  46. data/lib/cog/built_in_tools/basic.rb +0 -10
  47. data/lib/cog/built_in_tools/basic/cog_tool.rb +0 -11
  48. data/lib/cog/config/cogfile.rb +0 -117
  49. data/lib/cog/config/lang_info.rb +0 -40
  50. data/lib/cog/config/project_methods.rb +0 -35
  51. data/lib/cog/config/tool_methods.rb +0 -94
  52. data/lib/cog/controllers/tool_controller.rb +0 -50
  53. data/lib/cog/helpers/cascading_template_set.rb +0 -75
  54. data/lib/cog/helpers/string.rb +0 -26
  55. data/lib/cog/languages.rb +0 -52
  56. data/lib/cog/languages/c_language.rb +0 -29
  57. data/lib/cog/languages/c_plus_plus_language.rb +0 -28
  58. data/lib/cog/languages/c_sharp_language.rb +0 -24
  59. data/lib/cog/languages/java_language.rb +0 -24
  60. data/lib/cog/languages/java_script_language.rb +0 -24
  61. data/lib/cog/languages/language.rb +0 -73
  62. data/lib/cog/languages/mixins.rb +0 -10
  63. data/lib/cog/languages/mixins/c_style_comments.rb +0 -23
  64. data/lib/cog/languages/mixins/hash_comments.rb +0 -19
  65. data/lib/cog/languages/python_language.rb +0 -20
  66. data/lib/cog/languages/qt_project_language.rb +0 -16
  67. data/lib/cog/languages/ruby_language.rb +0 -28
  68. data/lib/cog/tool.rb +0 -61
  69. data/lib/cog/tool/dsl.rb +0 -26
  70. data/templates/basic/generator.rb.erb +0 -4
  71. data/templates/cog/custom_tool/Gemfile.erb +0 -8
  72. data/templates/cog/custom_tool/LICENSE.erb +0 -18
  73. data/templates/cog/custom_tool/README.markdown.erb +0 -18
  74. data/templates/cog/custom_tool/Rakefile.erb +0 -15
  75. data/templates/cog/custom_tool/cog_tool.rb.erb +0 -17
  76. data/templates/cog/custom_tool/generator.rb.erb.erb +0 -9
  77. data/templates/cog/custom_tool/template.txt.erb.erb +0 -1
  78. data/templates/cog/custom_tool/tool.gemspec.erb +0 -17
  79. data/templates/cog/custom_tool/version.rb.erb +0 -5
@@ -0,0 +1,142 @@
1
+ module Cog
2
+ module DSL
3
+
4
+ # DSL for defining a language
5
+ class LanguageDSL
6
+
7
+ # @api developer
8
+ # @param key [String] unique case-insensitive identifier
9
+ def initialize(key)
10
+ @lang = Cog::Language.new key
11
+ end
12
+
13
+ # Define a readable name for the language
14
+ # @param value [String] readable name of the language
15
+ # @return [nil]
16
+ def name(value)
17
+ lang_eval { @name = value }
18
+ nil
19
+ end
20
+
21
+ # Define single line comment notation
22
+ # @param prefix [String] starts a single line comment in the language
23
+ # @return [nil]
24
+ def comment(prefix)
25
+ lang_eval { @comment_prefix = prefix }
26
+ nil
27
+ end
28
+
29
+ # Define multi-line comment notation
30
+ # @param prefix [String] starts a multi-line comment in the language
31
+ # @param postfix [String] ends a multi-line comment in the language
32
+ # @return [nil]
33
+ def multiline_comment(prefix, postfix)
34
+ lang_eval do
35
+ @multiline_comment_prefix = prefix
36
+ @multiline_comment_postfix = postfix
37
+ end
38
+ nil
39
+ end
40
+
41
+ # Borrow comment notation from another language
42
+ # @param lang_key [String] use comment notation from the language with the given key
43
+ # @return [nil]
44
+ def comment_style(lang_key)
45
+ lang_eval { @comment_style = lang_key.to_s.downcase }
46
+ nil
47
+ end
48
+
49
+ # Define file extensions
50
+ # @param values [Array<String>] list of file extensions for this language
51
+ def extension(*values)
52
+ lang_eval do
53
+ @extensions = values.collect {|key| key.to_s.downcase}
54
+ end
55
+ end
56
+
57
+ # Define a block to call when using a named scopes in this language
58
+ # @yieldparam name [String] name of the scope to use
59
+ # @yieldreturn [String] a using named scope statement in this language
60
+ # @return [nil]
61
+ def use_named_scope(&block)
62
+ lang_eval { @use_named_scope_block = block }
63
+ nil
64
+ end
65
+
66
+ # Define a block to call when beginning a named scope in this language
67
+ # @yieldparam name [String] name of the scope to begin
68
+ # @yieldreturn [String] a begin named scope statement in this language
69
+ # @return [nil]
70
+ def named_scope_begin(&block)
71
+ lang_eval { @named_scope_begin_block = block }
72
+ nil
73
+ end
74
+
75
+ # Define a block to call when ending a named scope in this language
76
+ # @yieldparam name [String] name of the scope to end
77
+ # @yieldreturn [String] an end named scope statement in this language
78
+ # @return [nil]
79
+ def named_scope_end(&block)
80
+ lang_eval { @named_scope_end_block = block }
81
+ nil
82
+ end
83
+
84
+ # Define a block to call when beginning an include guard in this language
85
+ # @yieldparam name [String] name of the guard
86
+ # @yieldreturn [String] a begin guard in this language
87
+ # @return [nil]
88
+ def include_guard_begin(&block)
89
+ lang_eval { @include_guard_begin_block = block }
90
+ nil
91
+ end
92
+
93
+ # Define a block to call when ending an include guard in this language
94
+ # @yieldparam name [String] name of the guard
95
+ # @yieldreturn [String] an end guard in this language
96
+ # @return [nil]
97
+ def include_guard_end(&block)
98
+ lang_eval { @include_guard_end_block = block }
99
+ nil
100
+ end
101
+
102
+ # Borrow include guard notation from another language
103
+ # @param lang_key [String] use include guard notation from the language with the given key
104
+ # @return [nil]
105
+ def include_guard_style(lang_key)
106
+ lang_eval { @include_guard_style = lang_key.to_s.downcase }
107
+ nil
108
+ end
109
+
110
+ # @api developer
111
+ # Compute the comment pattern
112
+ # @return [Cog::Language] the defined language
113
+ def finalize
114
+ pattern = /[*]/
115
+ esc = lambda do |x|
116
+ x.gsub(pattern) {|match| "\\#{match}"}
117
+ end
118
+
119
+ lang_eval do
120
+ @include_guard_style ||= key
121
+ @comment_style ||= key
122
+ @comment_pattern = if @comment_prefix && @multiline_comment_prefix
123
+ '^\s*(?:%s|%s)\s*%%s\s*(?:%s)?\s*$' % [@comment_prefix, @multiline_comment_prefix, @multiline_comment_postfix].collect(&esc)
124
+ elsif @comment_prefix
125
+ '^\s*%s\s*%%s\s*$' % esc.call(@comment_prefix)
126
+ elsif @multiline_comment_prefix
127
+ '^\s*%s\s*%%s\s*%s\s*$' % [@multiline_comment_prefix, @multiline_comment_postfix].collect(&esc)
128
+ else
129
+ '^\s*%s\s*$'
130
+ end
131
+ end
132
+ @lang
133
+ end
134
+
135
+ private
136
+
137
+ def lang_eval(&block)
138
+ @lang.instance_eval &block
139
+ end
140
+ end
141
+ end
142
+ end
@@ -1,5 +1,3 @@
1
- require 'cog/config'
2
-
3
1
  module Cog
4
2
 
5
3
  # Describes the environment of an embed statement including the file in which it was found, the line number, the language, an more.
@@ -1,14 +1,9 @@
1
- require 'cog/config'
2
- require 'cog/embed_context'
3
- require 'cog/embeds/file_scanner'
4
- require 'cog/errors'
5
-
6
1
  module Cog
7
2
 
8
3
  # @api developer
9
4
  # Methods for querying and manipulating project files
10
5
  module Embeds
11
-
6
+
12
7
  # Search through all project files for cog embeds, and remember them so that generators can refer to them later
13
8
  def gather_from_project
14
9
  @embeds ||= {}
@@ -45,7 +40,7 @@ module Cog
45
40
  # @yieldreturn [String] the value to substitute into the embed expansion
46
41
  # @return [Hash] whether or not the expansion was updated
47
42
  def update(c, &block)
48
- FileScanner.scan(c.path, statement(c.hook), :occurrence => c.actual_index) do |s|
43
+ Helpers::FileScanner.scan(c.path, statement(c.hook), :occurrence => c.actual_index) do |s|
49
44
  c.lineno = s.marked_line_number
50
45
  c.args = s.match[2].split if s.match[2]
51
46
  c.once = !s.match[3].nil?
@@ -28,37 +28,24 @@ module Cog
28
28
  end
29
29
  end
30
30
 
31
- define_error :ActionRequiresProject, 'action' do
32
- "the action requires a project, but no Cogfile was found"
33
- end
34
-
35
- define_error :CouldNotLoadTool, 'tool'
31
+ define_error :ActionRequiresProjectGeneratorPath, 'action'
32
+ define_error :ActionRequiresProjectTemplatePath, 'action'
33
+ define_error :ActionRequiresProjectPluginPath, 'action'
36
34
 
37
- define_error :DestinationAlreadyExists, 'path' do
38
- "a file or directory at the given path already exists, cannot create anything there"
39
- end
40
-
41
35
  define_error :DuplicateGenerator, 'generator'
42
-
43
- define_error :DuplicateTemplate, 'template'
44
-
45
- define_error :DuplicateTool, 'tool'
36
+ define_error :DuplicatePlugin, 'plugin'
46
37
 
47
- define_error :InvalidToolConfiguration, 'path to cog_tool.rb file' do
48
- "invalid directory structure for a cog tool"
38
+ define_error :InvalidPluginConfiguration, 'path to cog_plugin.rb file' do
39
+ "invalid directory structure for a cog plugin"
49
40
  end
50
41
 
51
42
  define_error :NoSuchFilter, 'filter'
52
-
53
43
  define_error :NoSuchGenerator, 'generator'
54
-
55
44
  define_error :NoSuchLanguage, 'language'
56
-
57
45
  define_error :NoSuchTemplate, 'template'
58
-
59
- define_error :NoSuchTool, 'tool' do
60
- "no such tool, make sure it appears in the COG_TOOLS environment variable"
61
- end
46
+ define_error :NoSuchPlugin, 'plugin'
47
+
48
+ define_error :PluginPathIsNotADirectory, 'plugin_path'
62
49
 
63
50
  define_error :ScopeStackUnderflow, 'caller' do
64
51
  "scope stack underflow: this can happen if you have too many *_end calls in a template"
@@ -68,8 +55,8 @@ module Cog
68
55
  "a embed expansion in the given file is missing the 'cog: }' terminator"
69
56
  end
70
57
 
71
- define_error :ToolMissingDefinition, 'missing' do
72
- "the tool was not fully defined"
58
+ define_error :PluginMissingDefinition, 'missing' do
59
+ "the plugin was not fully defined"
73
60
  end
74
61
  end
75
62
  end
@@ -1,13 +1,3 @@
1
- require 'cog/config'
2
- require 'cog/embeds'
3
- require 'cog/errors'
4
- require 'cog/generator/file_methods'
5
- require 'cog/generator/filters'
6
- require 'cog/generator/language_methods'
7
- require 'cog/helpers'
8
- require 'erb'
9
- require 'rainbow'
10
-
11
1
  module Cog
12
2
 
13
3
  # This module defines an interface which can be used by generator objects.
@@ -16,6 +6,10 @@ module Cog
16
6
  #
17
7
  # @see https://github.com/ktonon/cog#generators Introduction to Generators
18
8
  module Generator
9
+
10
+ autoload :FileMethods, 'cog/generator/file_methods'
11
+ autoload :Filters, 'cog/generator/filters'
12
+ autoload :LanguageMethods, 'cog/generator/language_methods'
19
13
 
20
14
  include FileMethods
21
15
  include Filters
@@ -33,10 +27,11 @@ module Cog
33
27
  end
34
28
 
35
29
  # Stamp a template into a file or return it as a string
36
- # @param template_path [String] path to template file relative one of the {Config#template_paths}
37
- # @param destination [String] path to which the generated file should be written, relative to the {Config::ProjectMethods#project_source_path}
30
+ # @param template_path [String] path to template file relative to {Config#template_path}
31
+ # @param destination [String] path to which the generated file should be written, relative to the {Config::ProjectConfig#project_path}
38
32
  # @option opt [Boolean] :absolute_template_path (false) is the +template_path+ absolute?
39
33
  # @option opt [Boolean] :absolute_destination (false) is the +destination+ absolute?
34
+ # @option opt [Boolean] :once (false) if +true+, the file will not be updated if it already exists
40
35
  # @option opt [Binding] :binding (nil) an optional binding to use while evaluating the template
41
36
  # @option opt [String, Array<String>] :filter (nil) name(s) of {Filters}
42
37
  # @option opt [Boolean] :quiet (false) suppress writing to STDOUT?
@@ -52,7 +47,7 @@ module Cog
52
47
 
53
48
  # Place it in a file
54
49
  write_scratch_file(destination, r, opt[:absolute_destination]) do |path, scratch|
55
- if files_are_same? path, scratch
50
+ if files_are_same?(path, scratch) || (opt[:once] && File.exists?(path))
56
51
  FileUtils.rm scratch
57
52
  else
58
53
  updated = File.exists? path
@@ -65,7 +60,7 @@ module Cog
65
60
 
66
61
  # Provide a value for embeds with the given hook
67
62
  # @param hook [String] hook name used in the embed statements
68
- # @yieldparam context [Embeds::EmbedContext] provides information about the environment in which the embed statement was found
63
+ # @yieldparam context [EmbedContext] provides information about the environment in which the embed statement was found
69
64
  # @yieldreturn The value which will be used to expand the embed (or replace the embedded content)
70
65
  # @return [nil]
71
66
  def embed(hook, &block)
@@ -108,7 +103,7 @@ module Cog
108
103
  # @yieldparam scratch [String] path to the scratch file
109
104
  # @return [nil]
110
105
  def write_scratch_file(original, text, absolute=false, &block)
111
- path = absolute ? original : File.join(Cog.project_source_path, original)
106
+ path = absolute ? original : File.join(Cog.project_path, original)
112
107
  FileUtils.mkpath File.dirname(path) unless File.exists? path
113
108
  scratch = "#{path}.scratch"
114
109
  File.open(scratch, 'w') {|file| file.write text}
@@ -5,7 +5,7 @@ module Cog
5
5
  module FileMethods
6
6
 
7
7
  # Get the template with the given name
8
- # @param path [String] path to template file relative one of the {Config#template_paths}
8
+ # @param path [String] path to file relative to the {Config#template_path}
9
9
  # @option opt [Boolean] :absolute (false) is the +path+ argument absolute?
10
10
  # @option opt [Boolean] :as_path (false) return the template as an ERB instance (+false+) or an absolute path to the file (+true+)
11
11
  # @return [ERB, String] an instance of {http://ruby-doc.org/stdlib-1.9.3/libdoc/erb/rdoc/ERB.html ERB} or an absolute path to the template
@@ -15,7 +15,7 @@ module Cog
15
15
  fullpath = if opt[:absolute]
16
16
  path
17
17
  else
18
- Cog.template_paths.inject('') do |found, prefix|
18
+ Cog.template_path.reverse.inject('') do |found, prefix|
19
19
  x = File.join prefix, path
20
20
  found.empty? && File.exists?(x) ? x : found
21
21
  end
@@ -1,5 +1,3 @@
1
- require 'cog/errors'
2
-
3
1
  module Cog
4
2
  module Generator
5
3
 
@@ -12,19 +10,17 @@ module Cog
12
10
  Cog.active_language.comment text
13
11
  end
14
12
 
15
- # @api developer
16
- # Adds a call_filter method which throws NoSuchFilter if
17
- # the filter is invalid
18
- def self.included(other)
19
- valid_filters = Filters.instance_eval {instance_methods}
20
- other.instance_eval do
21
- define_method "call_filter" do |name, text|
22
- raise Errors::NoSuchFilter.new(name) unless valid_filters.member?(name.to_s)
23
- method(name).call text
24
- end
25
- end
13
+ # Call a filter by name
14
+ # @param name [Symbol] the filter to call
15
+ # @param text [String] the text to pass through the filter
16
+ # @return [String] the filtered text
17
+ def call_filter(name, text)
18
+ gcontext[:filters] ||= %w(comment)
19
+ name = name.to_s
20
+ raise Errors::NoSuchFilter.new(name) unless gcontext[:filters].member? name
21
+ method(name).call text
26
22
  end
27
-
23
+
28
24
  end
29
25
  end
30
26
  end
@@ -1,12 +1,11 @@
1
- require 'cog/errors'
2
- require 'cog/generator/language_methods/scope'
3
-
4
1
  module Cog
5
2
  module Generator
6
3
 
7
4
  # Methods to help with generating language constructs
8
5
  module LanguageMethods
9
6
 
7
+ autoload :Scope, 'cog/generator/language_methods/scope'
8
+
10
9
  # @return [String] a warning comment not to edit the generated file
11
10
  def warning
12
11
  stamp 'warning', :filter => 'comment'
@@ -0,0 +1,32 @@
1
+ module Cog
2
+
3
+ # Generators files are executed as instances of this type.
4
+ # Plugins make themselves available to generators via a call to {DSL::Cogfile#autoload_plugin}
5
+ class GeneratorSandbox
6
+
7
+ include Generator
8
+
9
+ # @api developer
10
+ # @param path [String] path to the generator ruby file
11
+ def initialize(path)
12
+ @path = path
13
+ end
14
+
15
+ # Interpret the generator ruby file as this instance
16
+ # @api developer
17
+ # @return [nil]
18
+ def interpret
19
+ eval File.read(@path), binding
20
+ nil
21
+ end
22
+
23
+ # Register an autoload variable.
24
+ # @api developer
25
+ # @return [nil]
26
+ def self.autoload_plugin(name, path)
27
+ autoload name, path
28
+ nil
29
+ end
30
+
31
+ end
32
+ end
@@ -1,2 +1,10 @@
1
- require 'cog/helpers/cascading_template_set'
2
- require 'cog/helpers/string'
1
+ module Cog
2
+
3
+ # @api developer
4
+ module Helpers
5
+
6
+ autoload :CascadingSet, 'cog/helpers/cascading_set'
7
+ autoload :FileScanner, 'cog/helpers/file_scanner'
8
+
9
+ end
10
+ end
@@ -0,0 +1,104 @@
1
+ module Cog
2
+ module Helpers
3
+
4
+ # @api developer
5
+ class SourceInfo
6
+ attr_reader :name
7
+ attr_accessor :path
8
+
9
+ def initialize(name)
10
+ @info = []
11
+ @types = []
12
+ @name = name
13
+ end
14
+
15
+ def add_source(source, type=nil)
16
+ type ||= source
17
+ @info << source
18
+ @types << type
19
+ end
20
+
21
+ def style(text, type)
22
+ case type
23
+ when :built_in
24
+ text.color :cyan
25
+ when :gem
26
+ text.color :blue
27
+ when :user
28
+ text.color :green
29
+ when :plugin
30
+ text.color :yellow
31
+ when :project
32
+ text.color(:white).bright
33
+ else
34
+ text
35
+ end
36
+ end
37
+
38
+ def override_s(width=nil)
39
+ colorless = "[#{@info.join ' < '}]"
40
+ if width
41
+ x = @info.zip(@types).collect {|source, type| style source, type}
42
+ "[#{x.join ' < '}]" + " " * (width - colorless.length)
43
+ else
44
+ colorless
45
+ end
46
+ end
47
+
48
+ def <=>(other)
49
+ (@path || @name) <=> (other.path || other.name)
50
+ end
51
+
52
+ def to_s(override_column_width)
53
+ "#{override_s override_column_width} #{style @path || @name, @types.last}"
54
+ end
55
+ end
56
+
57
+ # @api developer
58
+ class CascadingSet
59
+ def initialize
60
+ @info = {}
61
+ end
62
+
63
+ # Look for sources in each of the given paths
64
+ # @param paths [Array<String>] a list of file system paths containing sources
65
+ # @option opt [String] :ext File extension of sources to glob for in each path
66
+ # @return [Array<String>] formatted listing of the sources
67
+ def self.process_paths(paths, opt={})
68
+ cs = Helpers::CascadingSet.new
69
+ paths.each_with_cog_source do |source, type, path|
70
+ opt[:source] = source
71
+ opt[:type] = type
72
+ opt[:root_dir] = path
73
+ cs.add_sources opt
74
+ end
75
+ cs.to_a
76
+ end
77
+
78
+ # @option opt [String] :source (nil) the name of the source
79
+ # @option opt [Symbol] :type (nil) must be one of <tt>:built_in</tt>, <tt>:user</tt>, <tt>:plugin</tt>, or <tt>:project</tt>
80
+ # @option opt [String] :root_dir (nil) directory in which to look for sources
81
+ def add_sources(opt={})
82
+ Dir.glob("#{opt[:root_dir]}/**/*.#{opt[:ext]}") do |path|
83
+ name = path.relative_to(opt[:root_dir]).slice(0..-(2 + opt[:ext].length))
84
+ @info[name] ||= SourceInfo.new name
85
+ @info[name].path = path if Cog.show_fullpaths?
86
+ @info[name].add_source opt[:source], opt[:type]
87
+ end
88
+ end
89
+
90
+ # @param plugin [Plugin] name of the plugin
91
+ def add_plugin(plugin, opt={})
92
+ @info[plugin.name] ||= SourceInfo.new plugin.name
93
+ @info[plugin.name].path = plugin.path if Cog.show_fullpaths?
94
+ @info[plugin.name].add_source *plugin.path.cog_source_and_type
95
+ end
96
+
97
+ def to_a
98
+ w = @info.values.collect {|t| t.override_s.length}.max
99
+ @info.values.sort.collect {|t| t.to_s(w)}
100
+ end
101
+ end
102
+
103
+ end
104
+ end