inversion 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (78) hide show
  1. data.tar.gz.sig +2 -0
  2. data/.gemtest +0 -0
  3. data/ChangeLog +836 -0
  4. data/History.md +4 -0
  5. data/Manifest.txt +74 -0
  6. data/README.rdoc +171 -0
  7. data/Rakefile +55 -0
  8. data/bin/inversion +276 -0
  9. data/lib/inversion.rb +98 -0
  10. data/lib/inversion/exceptions.rb +21 -0
  11. data/lib/inversion/mixins.rb +236 -0
  12. data/lib/inversion/monkeypatches.rb +20 -0
  13. data/lib/inversion/renderstate.rb +337 -0
  14. data/lib/inversion/sinatra.rb +35 -0
  15. data/lib/inversion/template.rb +250 -0
  16. data/lib/inversion/template/attrtag.rb +120 -0
  17. data/lib/inversion/template/calltag.rb +16 -0
  18. data/lib/inversion/template/codetag.rb +164 -0
  19. data/lib/inversion/template/commenttag.rb +54 -0
  20. data/lib/inversion/template/conditionaltag.rb +49 -0
  21. data/lib/inversion/template/configtag.rb +60 -0
  22. data/lib/inversion/template/containertag.rb +45 -0
  23. data/lib/inversion/template/elsetag.rb +62 -0
  24. data/lib/inversion/template/elsiftag.rb +49 -0
  25. data/lib/inversion/template/endtag.rb +55 -0
  26. data/lib/inversion/template/escapetag.rb +26 -0
  27. data/lib/inversion/template/fortag.rb +120 -0
  28. data/lib/inversion/template/iftag.rb +69 -0
  29. data/lib/inversion/template/importtag.rb +70 -0
  30. data/lib/inversion/template/includetag.rb +51 -0
  31. data/lib/inversion/template/node.rb +102 -0
  32. data/lib/inversion/template/parser.rb +297 -0
  33. data/lib/inversion/template/pptag.rb +28 -0
  34. data/lib/inversion/template/publishtag.rb +72 -0
  35. data/lib/inversion/template/subscribetag.rb +88 -0
  36. data/lib/inversion/template/tag.rb +150 -0
  37. data/lib/inversion/template/textnode.rb +43 -0
  38. data/lib/inversion/template/unlesstag.rb +60 -0
  39. data/lib/inversion/template/uriencodetag.rb +30 -0
  40. data/lib/inversion/template/yieldtag.rb +51 -0
  41. data/lib/inversion/tilt.rb +82 -0
  42. data/lib/inversion/utils.rb +235 -0
  43. data/spec/data/sinatra/hello.inversion +1 -0
  44. data/spec/inversion/mixins_spec.rb +177 -0
  45. data/spec/inversion/monkeypatches_spec.rb +35 -0
  46. data/spec/inversion/renderstate_spec.rb +291 -0
  47. data/spec/inversion/sinatra_spec.rb +59 -0
  48. data/spec/inversion/template/attrtag_spec.rb +216 -0
  49. data/spec/inversion/template/calltag_spec.rb +30 -0
  50. data/spec/inversion/template/codetag_spec.rb +51 -0
  51. data/spec/inversion/template/commenttag_spec.rb +84 -0
  52. data/spec/inversion/template/configtag_spec.rb +105 -0
  53. data/spec/inversion/template/containertag_spec.rb +54 -0
  54. data/spec/inversion/template/elsetag_spec.rb +105 -0
  55. data/spec/inversion/template/elsiftag_spec.rb +87 -0
  56. data/spec/inversion/template/endtag_spec.rb +78 -0
  57. data/spec/inversion/template/escapetag_spec.rb +59 -0
  58. data/spec/inversion/template/fortag_spec.rb +98 -0
  59. data/spec/inversion/template/iftag_spec.rb +241 -0
  60. data/spec/inversion/template/importtag_spec.rb +106 -0
  61. data/spec/inversion/template/includetag_spec.rb +108 -0
  62. data/spec/inversion/template/node_spec.rb +81 -0
  63. data/spec/inversion/template/parser_spec.rb +170 -0
  64. data/spec/inversion/template/pptag_spec.rb +51 -0
  65. data/spec/inversion/template/publishtag_spec.rb +69 -0
  66. data/spec/inversion/template/subscribetag_spec.rb +60 -0
  67. data/spec/inversion/template/tag_spec.rb +97 -0
  68. data/spec/inversion/template/textnode_spec.rb +86 -0
  69. data/spec/inversion/template/unlesstag_spec.rb +84 -0
  70. data/spec/inversion/template/uriencodetag_spec.rb +49 -0
  71. data/spec/inversion/template/yieldtag_spec.rb +54 -0
  72. data/spec/inversion/template_spec.rb +269 -0
  73. data/spec/inversion/tilt_spec.rb +47 -0
  74. data/spec/inversion_spec.rb +95 -0
  75. data/spec/lib/constants.rb +9 -0
  76. data/spec/lib/helpers.rb +160 -0
  77. metadata +316 -0
  78. metadata.gz.sig +0 -0
@@ -0,0 +1,4 @@
1
+ ## 0.0.1 [2011-02-02] Michael Granger <ged@FaerieMUD.org>
2
+
3
+ Initial release.
4
+
@@ -0,0 +1,74 @@
1
+ ChangeLog
2
+ History.md
3
+ Manifest.txt
4
+ README.rdoc
5
+ Rakefile
6
+ bin/inversion
7
+ lib/inversion.rb
8
+ lib/inversion/exceptions.rb
9
+ lib/inversion/mixins.rb
10
+ lib/inversion/monkeypatches.rb
11
+ lib/inversion/renderstate.rb
12
+ lib/inversion/sinatra.rb
13
+ lib/inversion/template.rb
14
+ lib/inversion/template/attrtag.rb
15
+ lib/inversion/template/calltag.rb
16
+ lib/inversion/template/codetag.rb
17
+ lib/inversion/template/commenttag.rb
18
+ lib/inversion/template/conditionaltag.rb
19
+ lib/inversion/template/configtag.rb
20
+ lib/inversion/template/containertag.rb
21
+ lib/inversion/template/elsetag.rb
22
+ lib/inversion/template/elsiftag.rb
23
+ lib/inversion/template/endtag.rb
24
+ lib/inversion/template/escapetag.rb
25
+ lib/inversion/template/fortag.rb
26
+ lib/inversion/template/iftag.rb
27
+ lib/inversion/template/importtag.rb
28
+ lib/inversion/template/includetag.rb
29
+ lib/inversion/template/node.rb
30
+ lib/inversion/template/parser.rb
31
+ lib/inversion/template/pptag.rb
32
+ lib/inversion/template/publishtag.rb
33
+ lib/inversion/template/subscribetag.rb
34
+ lib/inversion/template/tag.rb
35
+ lib/inversion/template/textnode.rb
36
+ lib/inversion/template/unlesstag.rb
37
+ lib/inversion/template/uriencodetag.rb
38
+ lib/inversion/template/yieldtag.rb
39
+ lib/inversion/tilt.rb
40
+ lib/inversion/utils.rb
41
+ spec/data/sinatra/hello.inversion
42
+ spec/inversion/mixins_spec.rb
43
+ spec/inversion/monkeypatches_spec.rb
44
+ spec/inversion/renderstate_spec.rb
45
+ spec/inversion/sinatra_spec.rb
46
+ spec/inversion/template/attrtag_spec.rb
47
+ spec/inversion/template/calltag_spec.rb
48
+ spec/inversion/template/codetag_spec.rb
49
+ spec/inversion/template/commenttag_spec.rb
50
+ spec/inversion/template/configtag_spec.rb
51
+ spec/inversion/template/containertag_spec.rb
52
+ spec/inversion/template/elsetag_spec.rb
53
+ spec/inversion/template/elsiftag_spec.rb
54
+ spec/inversion/template/endtag_spec.rb
55
+ spec/inversion/template/escapetag_spec.rb
56
+ spec/inversion/template/fortag_spec.rb
57
+ spec/inversion/template/iftag_spec.rb
58
+ spec/inversion/template/importtag_spec.rb
59
+ spec/inversion/template/includetag_spec.rb
60
+ spec/inversion/template/node_spec.rb
61
+ spec/inversion/template/parser_spec.rb
62
+ spec/inversion/template/pptag_spec.rb
63
+ spec/inversion/template/publishtag_spec.rb
64
+ spec/inversion/template/subscribetag_spec.rb
65
+ spec/inversion/template/tag_spec.rb
66
+ spec/inversion/template/textnode_spec.rb
67
+ spec/inversion/template/unlesstag_spec.rb
68
+ spec/inversion/template/uriencodetag_spec.rb
69
+ spec/inversion/template/yieldtag_spec.rb
70
+ spec/inversion/template_spec.rb
71
+ spec/inversion/tilt_spec.rb
72
+ spec/inversion_spec.rb
73
+ spec/lib/constants.rb
74
+ spec/lib/helpers.rb
@@ -0,0 +1,171 @@
1
+ = inversion
2
+
3
+ * http://deveiate.org/projects/Inversion
4
+
5
+
6
+ == Description
7
+
8
+ Inversion is a templating system for Ruby. It uses the "Inversion of Control" principle to decouple
9
+ the contents and structure of the template from the code that uses it, making it easier to use,
10
+ test-friendly, and clean.
11
+
12
+
13
+ === Details
14
+
15
+ Inversion, like most other templating systems, works by giving you a way of defining the static
16
+ parts of your output, and then letting you combine that at a later point with the dynamic parts:
17
+
18
+ Create the template and use it to render an exciting message:
19
+
20
+ tmpl = Inversion::Template.new( "Hello, <?attr name ?>!" )
21
+ tmpl.name = "World"
22
+ puts tmpl.render
23
+
24
+ The <tt><?attr name ?></tt> tag defines the _name_ accessor on the template object, the value of
25
+ which is substituted for any occurrences of +name+ in the template:
26
+
27
+ Hello, World!
28
+
29
+ This by itself isn't fantastically useful, but it does illustrate one of the ways in which Inversion
30
+ is different: the program and the template share data through an API, instead of through a complex
31
+ data structure, which establishes a clear delineation between what responsibility is the program's
32
+ and which is the template's. The program doesn't have to know how the view uses the data it's given,
33
+ and tests of the controller can substitute a Mock Object for the template to test the interaction
34
+ between the two instead of having to match patterns in the eventual output like an integration test.
35
+
36
+ You can also interact with the values set in the template:
37
+
38
+ Name: <?attr employee.full_name ?>
39
+
40
+ This will call the +#full_name+ method on whatever is set as the +employee+ attribute when
41
+ rendered, and the result will take the place of the tag.
42
+
43
+ Inversion also comes with a collection of other tags that provide flow control, exception-handling,
44
+ etc.
45
+
46
+ Here's a slightly more complex example: Say we have a layout template that contains all the
47
+ boilerplate, navigation, etc. for the site, and then an `<?attr body ?>` somewhere in the content
48
+ area for the content specific to each view:
49
+
50
+ layout = Inversion::Template.load( 'templates/layout.tmpl' )
51
+
52
+ Then there's a view template that displays a bulleted list of article titles:
53
+
54
+ <!-- articlelist.tmpl -->
55
+ <section id="articles">
56
+ <ul>
57
+ <?for article in articles ?>
58
+ <li><?call article.title ?></li>
59
+ <?end for ?>
60
+ </ul>
61
+ </section>
62
+
63
+ Loading this template results in a Ruby object whose API contains one method: `#articles`. To render
64
+ the view, we just call that accessor with instances of an `Article` domain class we defined
65
+ elsewhere, and then drop the 'alist' template into the layout and render them:
66
+
67
+ alist = Inversion::Template.load( 'templates/alist.tmpl' )
68
+ alist.articles = Articles.latest( 10 )
69
+
70
+ layout.body = alist
71
+ puts layout.render
72
+
73
+ The `for` tag in the alist will iterate over the enumerable Articles and generate an `<li>` for each
74
+ one. The resulting template object will be set as the body of the layout template, and stringified
75
+ when the enclosing template is rendered. Templates can be nested this way as deeply as you like.
76
+
77
+
78
+ === Tags To Implement
79
+
80
+ * <?prettyprint «attr/methodchain» ?>
81
+
82
+ * <?render «attr/methodchain» AS «identifier» IN «template path» ?>
83
+
84
+
85
+ === Tags Implemented
86
+
87
+ * <?attr?>
88
+ * <?call?>
89
+ * <?comment?>…<?end?>
90
+ * <?for obj, i in attr.each_with_index ?>
91
+ <?end?>
92
+ * <?config
93
+ escaping: 'html'
94
+ ignore_unknown_tags: false
95
+ ?>
96
+ * <?include other_template.tmpl ?>
97
+ * <?escape obj.some_method ?>
98
+ * <?urlencode obj.some_method ?>
99
+ * <?pp obj ?>
100
+ * <?if obj.some_method ?>…<?elsif otherobj.some_method ?>…<?else?>…<?end?>
101
+ * <?unless obj.some_method ?>..<?end ?>
102
+ * <?import attrname ?>
103
+ * <?publish identifier ?>...<?end publish ?>
104
+ * <?subscribe identifier ?>
105
+ * <?timedelta obj.time_method ?>
106
+ * <?yield ?>
107
+ * <?default attribute to value ?>
108
+ * <?begin ?>…<?rescue ?>…<?end?>
109
+
110
+
111
+ == References
112
+
113
+ * Inversion of Control: http://en.wikipedia.org/wiki/Inversion_of_control
114
+ * Passive View: http://martinfowler.com/eaaDev/PassiveScreen.html
115
+ * Supervising Controller: http://martinfowler.com/eaaDev/SupervisingPresenter.html
116
+
117
+
118
+ == Installation
119
+
120
+ gem install inversion
121
+
122
+
123
+ == Contributing
124
+
125
+ You can check out the current development source
126
+ {with Mercurial}[http://repo.deveiate.org/Inversion], or if you prefer Git, via the
127
+ project's {Github mirror}[https://github.com/ged/Inversion].
128
+
129
+ You can submit bug reports, suggestions, and read more about future plans at
130
+ {the project page}[http://deveiate.org/projects/Inversion].
131
+
132
+ After checking out the source, run:
133
+
134
+ $ rake newb
135
+
136
+ This task will install any missing dependencies, run the tests/specs,
137
+ and generate the API documentation.
138
+
139
+
140
+ == License
141
+
142
+ Copyright © 2011, Michael Granger and Mahlon E. Smith
143
+ All rights reserved.
144
+
145
+ Redistribution and use in source and binary forms, with or without
146
+ modification, are permitted provided that the following conditions are met:
147
+
148
+ * Redistributions of source code must retain the above copyright notice,
149
+ this list of conditions and the following disclaimer.
150
+
151
+ * Redistributions in binary form must reproduce the above copyright notice,
152
+ this list of conditions and the following disclaimer in the documentation
153
+ and/or other materials provided with the distribution.
154
+
155
+ * Neither the name of the author/s, nor the names of the project's
156
+ contributors may be used to endorse or promote products derived from this
157
+ software without specific prior written permission.
158
+
159
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
160
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
161
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
162
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
163
+ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
164
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
165
+ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
166
+ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
167
+ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
168
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
169
+
170
+
171
+
@@ -0,0 +1,55 @@
1
+ #!/usr/bin/env rake
2
+
3
+ begin
4
+ require 'hoe'
5
+ rescue LoadError
6
+ abort "This Rakefile requires hoe (gem install hoe)"
7
+ end
8
+
9
+ Hoe.plugin :mercurial
10
+ Hoe.plugin :signing
11
+ Hoe.plugin :manualgen
12
+
13
+ Hoe.plugins.delete :rubyforge
14
+
15
+ hoespec = Hoe.spec 'inversion' do
16
+ self.readme_file = 'README.rdoc'
17
+ self.history_file = 'History.md'
18
+ self.extra_rdoc_files = ['README.rdoc']
19
+
20
+ self.developer 'Michael Granger', 'ged@FaerieMUD.org'
21
+ self.developer 'Mahlon E. Smith', 'mahlon@martini.nu'
22
+
23
+ self.dependency 'ripper', '~> 1.0', :development unless defined?( Encoding )
24
+ self.dependency 'rspec', '~> 2.6.0', :development
25
+ self.dependency 'tilt', '~> 1.3.2', :development
26
+ self.dependency 'sinatra', '~> 1.2.6', :development
27
+ self.dependency 'rack-test', '~> 0.6.0', :development
28
+ self.dependency 'simplecov', '~> 0.4.2', :development
29
+ self.dependency 'hoe-manualgen', '~> 0.2.0', :development
30
+
31
+ # bin/inversion deps
32
+ self.dependency 'trollop', '~> 0.16.2', :development
33
+ self.dependency 'highline', '~> 1.6.2', :development
34
+ self.dependency 'sysexits', '~> 1.0.2', :development
35
+
36
+ self.spec_extras[:licenses] = ["BSD"]
37
+ self.require_ruby_version( '>=1.9.2' )
38
+ self.hg_sign_tags = true if self.respond_to?( :hg_sign_tags= )
39
+ self.rdoc_locations << "deveiate:/usr/local/www/public/code/#{remote_rdoc_dir}"
40
+ end
41
+
42
+ ENV['VERSION'] ||= hoespec.spec.version.to_s
43
+
44
+ # Ensure the specs pass before checking in
45
+ task 'hg:precheckin' => :spec
46
+
47
+ # Rebuild the ChangeLog immediately before release (hoe hook)
48
+ task :prerelease => 'ChangeLog'
49
+
50
+ desc "Build a coverage report"
51
+ task :coverage do
52
+ ENV["COVERAGE"] = 'yes'
53
+ Rake::Task[:spec].invoke
54
+ end
55
+
@@ -0,0 +1,276 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'logger'
4
+ require 'inversion'
5
+ require 'trollop'
6
+ require 'highline'
7
+ require 'sysexits'
8
+ require 'shellwords'
9
+
10
+ # Command class for the 'inversion' command-line tool.
11
+ class Inversion::Command
12
+ extend Sysexits
13
+
14
+ # The list of valid subcommands
15
+ SUBCOMMANDS = %w[api tagtokens tree]
16
+
17
+ # Class-instance variable for the HighLine prompt object
18
+ @prompt = nil
19
+
20
+
21
+ ### Run the command
22
+ def self::run( args )
23
+ opts, args = self.parse_options( args )
24
+ subcommand = args.shift
25
+
26
+ command = self.new( opts )
27
+ command.run( subcommand, args )
28
+ rescue => err
29
+ $stderr.puts "%p: %s" % [ err.class, err.message ]
30
+ $stderr.puts( err.backtrace.join("\n ") ) if opts && opts.debug
31
+ end
32
+
33
+
34
+ ### Fetch the HighLine instance for the command, creating it if necessary.
35
+ def self::prompt
36
+ unless @prompt
37
+ @prompt = HighLine.new
38
+ @prompt.page_at = @prompt.output_rows - 5
39
+ @prompt.wrap_at = @prompt.output_cols - 2
40
+ end
41
+
42
+ @prompt
43
+ end
44
+
45
+
46
+ ### Create an option parser for the command and return it
47
+ def self::create_option_parser
48
+ pr = self.prompt
49
+ progname = pr.color( File.basename($0), :bold, :yellow )
50
+
51
+ return Trollop::Parser.new do
52
+ version Inversion.version_string( true )
53
+
54
+ banner (<<-END_BANNER).gsub(/^\t+/, '')
55
+ #{progname} OPTIONS SUBCOMMAND ARGS
56
+
57
+ Run the specified SUBCOMMAND with the given ARGS.
58
+ END_BANNER
59
+ text ''
60
+
61
+ stop_on( *SUBCOMMANDS )
62
+ text pr.color('Subcommands', :bold, :white)
63
+ text pr.list( SUBCOMMANDS, :columns_across )
64
+ text ''
65
+
66
+ text pr.color('Inversion Config', :bold, :white)
67
+ opt :ignore_unknown_tags, "Ignore unknown tags instead of displaying an error"
68
+ opt :path, "Add one or more directories to the template search path",
69
+ :type => :string, :multi => true
70
+ text ''
71
+
72
+
73
+ text pr.color('Other Options', :bold, :white)
74
+ opt :debug, "Enable debugging output"
75
+ end
76
+ end
77
+
78
+
79
+ ### Parse the given command line +args+, returning a populated options struct
80
+ ### and any remaining arguments.
81
+ def self::parse_options( args )
82
+ oparser = self.create_option_parser
83
+ opts = oparser.parse( args )
84
+
85
+ if oparser.leftovers.empty?
86
+ $stderr.puts "No subcommand given.\nUsage: "
87
+ oparser.educate( $stderr )
88
+ exit :usage
89
+ end
90
+ args.replace( oparser.leftovers )
91
+
92
+ return opts, args
93
+ rescue Trollop::HelpNeeded
94
+ oparser.educate( $stderr )
95
+ exit :ok
96
+ rescue Trollop::VersionNeeded
97
+ $stderr.puts( oparser.version )
98
+ exit :ok
99
+ end
100
+
101
+
102
+ ### Create a new instance of the command that will use the specified +opts+
103
+ ### to parse and dump info about the given +templates+.
104
+ def initialize( opts )
105
+ @opts = opts
106
+ @prompt = self.class.prompt
107
+
108
+ # Configure Inversion's strictness and logging
109
+ Inversion.log.level = opts.debug ? Logger::DEBUG : Logger::ERROR
110
+ Inversion.log.formatter = Inversion::ColorLogFormatter.new( Inversion.log )
111
+ Inversion::Template.configure(
112
+ :ignore_unknown_tags => opts.ignore_unknown_tags,
113
+ :template_paths => opts.path,
114
+ )
115
+ end
116
+
117
+
118
+ ######
119
+ public
120
+ ######
121
+
122
+ # The command-line options
123
+ attr_reader :opts
124
+
125
+ # The command's prompt object (HighLine)
126
+ attr_reader :prompt
127
+
128
+
129
+ ### Run the given +subcommand+ with the specified +args+.
130
+ def run( subcommand, args )
131
+ case subcommand.to_sym
132
+ when :tree
133
+ self.dump_node_trees( args )
134
+ when :api
135
+ self.describe_templates( args )
136
+ when :tagtokens
137
+ self.dump_tokens( args )
138
+ else
139
+ self.output_error( "No such command #{subcommand.dump}" )
140
+ end
141
+ end
142
+
143
+
144
+ ### Load the Inversion::Template from the specified +tmplpath+ and return it. If there
145
+ ### is an error loading the template, output the error and return +nil+.
146
+ def load_template( tmplpath )
147
+ template = Inversion::Template.load( tmplpath )
148
+ return template
149
+ rescue Errno => err
150
+ self.prompt.say "Failed to load %s: %s" % [ tmplpath, err.message ]
151
+ rescue Inversion::ParseError => err
152
+ self.prompt.say "%s: Invalid template: %p: %s" %
153
+ [ tmplpath, err.class, err.message ]
154
+ self.prompt.say( err.backtrace.join("\n ") ) if self.opts.debug
155
+ end
156
+
157
+
158
+ ### Dump the node tree of the given +templates+.
159
+ def dump_node_trees( templates )
160
+ templates.each do |path|
161
+ template = self.load_template( path )
162
+ self.output_blank_line
163
+ self.output_template_header( template )
164
+ self.output_template_nodes( template.node_tree )
165
+ end
166
+ end
167
+
168
+
169
+ ### Output the given +tree+ of nodes at the specified +indent+ level.
170
+ def output_template_nodes( tree, indent=0 )
171
+ indenttxt = ' ' * indent
172
+ tree.each do |node|
173
+ self.prompt.say( indenttxt + node.as_comment_body )
174
+ self.output_template_nodes( node.subnodes, indent+4 ) if node.is_container?
175
+ end
176
+ end
177
+
178
+
179
+ ### Output a description of the templates.
180
+ def describe_templates( templates )
181
+ templates.each do |path|
182
+ template = self.load_template( path )
183
+ self.output_blank_line
184
+ self.output_template_header( template )
185
+ self.describe_template_api( template )
186
+ self.describe_publications( template )
187
+ self.describe_subscriptions( template )
188
+ end
189
+ end
190
+
191
+
192
+ ### Output a header between each template.
193
+ def output_template_header( template )
194
+ header_info = "%s (%0.2fK, %s)" %
195
+ [ template.source_file, template.source.bytesize/1024.0, template.source.encoding ]
196
+ header_line = "-- %s" % [ header_info ]
197
+ self.prompt.say( self.prompt.color(header_line, :bold, :white) )
198
+ end
199
+
200
+
201
+ ### Output a description of the +template+'s attributes, subscriptions, etc.
202
+ def describe_template_api( template )
203
+ attrs = template.attributes.keys.map( &:to_s )
204
+ return if attrs.empty?
205
+
206
+ self.output_subheader "%d Attribute/s" % [ attrs.length ]
207
+ self.output_list( attrs.sort )
208
+ self.output_blank_line
209
+ end
210
+
211
+
212
+ ### Output a list of sections the template publishes.
213
+ def describe_publications( template )
214
+ ptags = template.node_tree.find_all {|node| node.is_a?(Inversion::Template::PublishTag) }
215
+ return if ptags.empty?
216
+
217
+ pubnames = ptags.map( &:key ).map( &:to_s ).uniq.sort
218
+ self.output_subheader "%d Publication/s" % [ pubnames.length ]
219
+ self.output_list( pubnames )
220
+ self.output_blank_line
221
+ end
222
+
223
+
224
+ ### Output a list of sections the template subscribes to.
225
+ def describe_subscriptions( template )
226
+ stags = template.node_tree.find_all {|node| node.is_a?(Inversion::Template::SubscribeTag) }
227
+ return if stags.empty?
228
+
229
+ subnames = stags.map( &:key ).map( &:to_s ).uniq.sort
230
+ self.output_subheader "%d Subscription/s" % [ subnames.length ]
231
+ self.output_list( subnames )
232
+ self.output_blank_line
233
+ end
234
+
235
+
236
+ ### Attempt to parse the given +code+ and dump its tokens as a tagpattern.
237
+ def dump_tokens( args )
238
+ code = args.join(' ')
239
+
240
+ require 'ripper'
241
+ tokens = Ripper.lex( code ).collect do |(pos, tok, text)|
242
+ "%s<%p>" % [ tok.to_s.sub(/^on_/,''), text ]
243
+ end.join(' ')
244
+
245
+ self.prompt.say( tokens )
246
+ end
247
+
248
+
249
+ ### Display a columnar list.
250
+ def output_list( columns )
251
+ self.prompt.say( self.prompt.list(columns, :columns_down) )
252
+ end
253
+
254
+
255
+ ### Display an error message.
256
+ def output_error( message )
257
+ self.prompt.say( self.prompt.color(message, :red) )
258
+ end
259
+
260
+
261
+ ### Output a subheader with the given +caption+.
262
+ def output_subheader( caption )
263
+ self.prompt.say( self.prompt.color(caption, :cyan) )
264
+ end
265
+
266
+
267
+ ### Output a blank line
268
+ def output_blank_line
269
+ self.prompt.say( "\n" )
270
+ end
271
+
272
+ end # class Inversion::Command
273
+
274
+ Inversion::Command.run( ARGV )
275
+
276
+