jakewendt-rdoc_rails 0.0.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,94 @@
1
+ = RDoc::Rails
2
+
3
+ This package adds Rails-specific information to RDoc generated docs on Rails
4
+ projects. It could be particularly useful for people wanting to document Rails
5
+ applications for internal use within a team of developers. It includes
6
+ <tt>RDoc::Parser::Rails</tt> based on <tt>RDoc::Parser::Ruby</tt> as well as
7
+ <tt>RDoc::Generator::Railsfish</tt> based on <tt>RDoc::Generator::Darkfish</tt>
8
+
9
+ == Installation
10
+ Currently, this is written as a Rails plugin, although I will look into
11
+ packaging it as a gem as well. It works with RDoc version 2.4.3, the most
12
+ current version as of August, 2009. I have tested it only with Rails 2.0, but I
13
+ would expect it to work for projects written in earlier and later versions of
14
+ Rails as well.
15
+
16
+ To install as a Rails plugin, try some variation on one of the following:
17
+ ruby script/plugin install git://github.com/chinasaur/rdoc_rails.git
18
+ ruby script/plugin install http://github.com/chinasaur/rdoc_rails.git
19
+ ruby script/plugin install http://github.com/chinasaur/rdoc_rails.git/
20
+
21
+ You may then need/want to rename the directory
22
+ <tt>vendor/plugins/rdoc_rails.git/</tt> to just +rdoc_rails+
23
+
24
+ After that, you should be good to go. The standard
25
+ > rake doc:app
26
+ or
27
+ > rake doc:reapp
28
+ should now run the Rails customized version of RDoc.
29
+
30
+ == Features
31
+ Right now there are just two basic features above and beyond the standard Ruby
32
+ RDoc:
33
+
34
+ 1. Documentation for ActiveRecord associations on Models
35
+ 2. Documentation for methods delegated to other models through the +delegate+ method.
36
+
37
+ But the infrastructure is all there to make it straightforward to parse
38
+ additional features in Rails code and generate documentation based on those
39
+ parsings.
40
+
41
+ == Suggested Features
42
+
43
+ * named_scope parsing
44
+ * validate_* parsing
45
+ * before_* parsing
46
+ * after_* parsing
47
+ * attr_protected parsing
48
+ * attr_accessible parsing
49
+ * custom user grouping and parsing (probably more core rdoc)
50
+
51
+ == Tests
52
+
53
+ * Tests are in development
54
+ * Both tests for Comment model's belongs_to associations fail
55
+ * one is polymorphic
56
+ * the other has a redefined class_name and counter_cache => true
57
+ * tests run, but I don't parse the output yet
58
+
59
+
60
+ == Contributing
61
+ I will try to document things better to make it easy for others to contribute
62
+ additional documentation features. For now, get in touch if you have any
63
+ questions and fork away.
64
+
65
+ == License http://i.creativecommons.org/l/by/3.0/us/80x15.png
66
+ Rdoc::Rails is released under a {Creative Commons Attribution 3.0 United States
67
+ License}[http://creativecommons.org/licenses/by/3.0/us/]. Please credit me
68
+ somewhere in your project's documentation if you are using this.
69
+
70
+
71
+
72
+
73
+
74
+ == Dev by Jake
75
+
76
+ Gemified with Jeweler
77
+
78
+ rake version:bump:patch
79
+ rake version:bump:minor
80
+ rake version:bump:major
81
+
82
+ rake gemspec
83
+
84
+ rake install
85
+
86
+ rake release
87
+
88
+
89
+ This seems to be incompatible with the latest rdoc 3.4.
90
+ Attempting to remedy.
91
+ Still not working.
92
+
93
+
94
+
@@ -0,0 +1 @@
1
+ require 'rdoc_rails'
@@ -0,0 +1,8 @@
1
+ $: << File.expand_path(File.dirname(__FILE__))
2
+
3
+ require 'rdoc_rails/rake'
4
+ require 'rdoc_rails/rdoc/context'
5
+ require 'rdoc_rails/rdoc/token_stream'
6
+ require 'rdoc_rails/rdoc/ar_association'
7
+ require 'rdoc_rails/rdoc/parser/rails'
8
+ require 'rdoc_rails/rdoc/generator/railsfish'
@@ -0,0 +1,21 @@
1
+ module Rake
2
+ def self.remove_task(task_name)
3
+ Rake.application.instance_variable_get(:@tasks).delete(task_name.to_s) || raise('No such task!')
4
+ end
5
+
6
+ class RDocTask
7
+ def self.remove_task(task='rdoc', opts={})
8
+ Rake.remove_task(Rake::Task[task].prerequisites[0])
9
+ Rake.remove_task(task)
10
+
11
+ task = task.to_s.split(':')
12
+ name = task[-1]
13
+ path = task[0..-2] * ':'
14
+ rerdoc = opts[:rerdoc] || "re#{name}"
15
+ clobber = opts[:clobber_rdoc] || "clobber_#{name}"
16
+
17
+ Rake.remove_task("#{path}:#{rerdoc}")
18
+ Rake.remove_task("#{path}:#{clobber}")
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,103 @@
1
+ require 'active_support'
2
+ require 'active_support/inflector'
3
+
4
+ require 'cgi'
5
+ class CGI
6
+ def self.escapeHTML_with_nil_check(string)
7
+ string = '' if string.nil?
8
+ escapeHTML_without_nil_check(string)
9
+ end
10
+ class << self
11
+ alias_method_chain :escapeHTML, :nil_check
12
+ end
13
+ end
14
+
15
+ class RDoc::ArAssociation < RDoc::Context
16
+ include RDoc::TokenStream
17
+ attr_accessor :atype
18
+ attr_accessor :name
19
+ attr_writer :opts
20
+
21
+ def initialize(init={})
22
+ super()
23
+ init.each{ |k,v| send("#{k}=", v) }
24
+ end
25
+
26
+ def opts
27
+ @opts || {}
28
+ end
29
+
30
+ def class_name
31
+ class_name = opts[:class_name]
32
+ class_name ||= ActiveSupport::Inflector.classify(name) if ['has_many', 'has_and_belongs_to_many'].include?(atype.to_s)
33
+ class_name ||= name.camelize
34
+ class_name
35
+ end
36
+
37
+ def path
38
+ parent.path
39
+ end
40
+
41
+ # Pulled from RDoc::Generator::Markup
42
+ # Would be nice if this were moved into a module so it could be includable
43
+ # without copy/paste.
44
+ def add_line_numbers(src)
45
+ if src =~ /\A.*, line (\d+)/ then
46
+ first = $1.to_i - 1
47
+ last = first + src.count("\n")
48
+ size = last.to_s.length
49
+
50
+ line = first
51
+ src.gsub!(/^/) do
52
+ res = if line == first
53
+ " " * (size + 2)
54
+ else
55
+ "%#{size}d: " % line
56
+ end
57
+
58
+ line += 1
59
+ res
60
+ end
61
+ end
62
+ end
63
+
64
+ # Pulled from RDoc::Generator::Markup
65
+ # Would be nice if this were moved into a module so it could be includable
66
+ # without copy/paste.
67
+ def markup_code
68
+ return '' unless @token_stream
69
+
70
+ src = ""
71
+
72
+ @token_stream.each do |t|
73
+ next unless t
74
+ # style = STYLE_MAP[t.class]
75
+ style = case t
76
+ when RDoc::RubyToken::TkCONSTANT then "ruby-constant"
77
+ when RDoc::RubyToken::TkKW then "ruby-keyword kw"
78
+ when RDoc::RubyToken::TkIVAR then "ruby-ivar"
79
+ when RDoc::RubyToken::TkOp then "ruby-operator"
80
+ when RDoc::RubyToken::TkId then "ruby-identifier"
81
+ when RDoc::RubyToken::TkNode then "ruby-node"
82
+ when RDoc::RubyToken::TkCOMMENT then "ruby-comment cmt"
83
+ when RDoc::RubyToken::TkREGEXP then "ruby-regexp re"
84
+ when RDoc::RubyToken::TkSTRING then "ruby-value str"
85
+ when RDoc::RubyToken::TkVal then "ruby-value"
86
+ else nil
87
+ end
88
+
89
+ text = CGI.escapeHTML(t.text)
90
+
91
+ if style
92
+ src << "<span class=\"#{style}\">#{text}</span>"
93
+ else
94
+ src << text
95
+ end
96
+ end
97
+
98
+ # add_line_numbers src if RDoc::RDoc.current.options.include_line_numbers
99
+ # change for rdoc > 2.4.3
100
+
101
+ src
102
+ end
103
+ end
@@ -0,0 +1,19 @@
1
+ require 'rdoc/context'
2
+ class RDoc::Context
3
+ attr_accessor :ar_associations
4
+
5
+ # Overriding to initialize ar_associations
6
+ def initialize_methods_etc
7
+ @method_list = []
8
+ @attributes = []
9
+ @aliases = []
10
+ @requires = []
11
+ @includes = []
12
+ @constants = []
13
+ @ar_associations = []
14
+
15
+ # This Hash maps a method name to a list of unmatched aliases (aliases of
16
+ # a method not yet encountered).
17
+ @unmatched_alias_lists = {}
18
+ end
19
+ end
@@ -0,0 +1,27 @@
1
+ require 'rdoc/generator/darkfish'
2
+ class RDoc::Generator::Railsfish < RDoc::Generator::Darkfish
3
+ RDoc::RDoc.add_generator(self)
4
+ VERSION = '0.1.0'
5
+
6
+ # Override template path to use standard darkfish template for most pages
7
+ def initialize(opts)
8
+ opts.instance_variable_set(:@template, 'darkfish') if opts.template == 'railsfish'
9
+ super
10
+ end
11
+
12
+ # Overriding to allow setting templatefile to my Rails customized version.
13
+ def generate_class_files
14
+ debug_msg "Generating class documentation in #@outputdir"
15
+ templatefile = Pathname.new(File.dirname(__FILE__) + '/template/railsfish/classpage.rhtml')
16
+
17
+ @classes.each do |klass|
18
+ debug_msg " working on %s (%s)" % [ klass.full_name, klass.path ]
19
+ outfile = @outputdir + klass.path
20
+ rel_prefix = @outputdir.relative_path_from( outfile.dirname )
21
+ svninfo = self.get_svninfo( klass )
22
+
23
+ debug_msg " rendering #{outfile}"
24
+ self.render_template( templatefile, binding(), outfile )
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,308 @@
1
+ <?xml version="1.0" encoding="utf-8"?>
2
+ <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
3
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
4
+ <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
5
+ <head>
6
+ <meta content="text/html; charset=<%= @options.charset %>" http-equiv="Content-Type" />
7
+
8
+ <title><%= klass.type.capitalize %>: <%= klass.full_name %></title>
9
+
10
+ <link rel="stylesheet" href="<%= rel_prefix %>/rdoc.css" type="text/css" media="screen" />
11
+
12
+ <script src="<%= rel_prefix %>/js/jquery.js" type="text/javascript" charset="utf-8"></script>
13
+ <script src="<%= rel_prefix %>/js/thickbox-compressed.js" type="text/javascript" charset="utf-8"></script>
14
+ <script src="<%= rel_prefix %>/js/quicksearch.js" type="text/javascript" charset="utf-8"></script>
15
+ <script src="<%= rel_prefix %>/js/darkfish.js" type="text/javascript" charset="utf-8"></script>
16
+
17
+ </head>
18
+
19
+ <body class="<%= klass.type %>">
20
+
21
+ <div id="metadata">
22
+ <div id="file-metadata">
23
+ <div id="file-list-section" class="section">
24
+ <h3 class="section-header">In Files</h3>
25
+ <div class="section-body">
26
+ <ul>
27
+ <% klass.in_files.each do |tl| %>
28
+ <li><a href="<%= rel_prefix %>/<%= h tl.path %>?TB_iframe=true&amp;height=550&amp;width=785"
29
+ class="thickbox" title="<%= h tl.absolute_name %>"><%= h tl.absolute_name %></a></li>
30
+ <% end %>
31
+ </ul>
32
+ </div>
33
+ </div>
34
+
35
+ <% if !svninfo.empty? %>
36
+ <div id="file-svninfo-section" class="section">
37
+ <h3 class="section-header">Subversion Info</h3>
38
+ <div class="section-body">
39
+ <dl class="svninfo">
40
+ <dt>Rev</dt>
41
+ <dd><%= svninfo[:rev] %></dd>
42
+
43
+ <dt>Last Checked In</dt>
44
+ <dd><%= svninfo[:commitdate].strftime('%Y-%m-%d %H:%M:%S') %>
45
+ (<%= svninfo[:commitdelta] %> ago)</dd>
46
+
47
+ <dt>Checked in by</dt>
48
+ <dd><%= svninfo[:committer] %></dd>
49
+ </dl>
50
+ </div>
51
+ </div>
52
+ <% end %>
53
+ </div>
54
+
55
+ <div id="class-metadata">
56
+
57
+ <!-- Parent Class -->
58
+ <% if klass.type == 'class' %>
59
+ <div id="parent-class-section" class="section">
60
+ <h3 class="section-header">Parent</h3>
61
+ <% unless String === klass.superclass %>
62
+ <p class="link"><a href="<%= klass.aref_to klass.superclass.path %>"><%= klass.superclass.full_name %></a></p>
63
+ <% else %>
64
+ <p class="link"><%= klass.superclass %></p>
65
+ <% end %>
66
+ </div>
67
+ <% end %>
68
+
69
+ <!-- Namespace Contents -->
70
+ <% unless klass.classes_and_modules.empty? %>
71
+ <div id="namespace-list-section" class="section">
72
+ <h3 class="section-header">Namespace</h3>
73
+ <ul class="link-list">
74
+ <% (klass.modules.sort + klass.classes.sort).each do |mod| %>
75
+ <li><span class="type"><%= mod.type.upcase %></span> <a href="<%= klass.aref_to mod.path %>"><%= mod.full_name %></a></li>
76
+ <% end %>
77
+ </ul>
78
+ </div>
79
+ <% end %>
80
+
81
+ <!-- Method Quickref -->
82
+ <% unless klass.method_list.empty? %>
83
+ <div id="method-list-section" class="section">
84
+ <h3 class="section-header">Methods</h3>
85
+ <ul class="link-list">
86
+ <% klass.each_method do |meth| %>
87
+ <li><a href="#<%= meth.aref %>"><%= meth.singleton ? '::' : '#' %><%= meth.name %></a></li>
88
+ <% end %>
89
+ </ul>
90
+ </div>
91
+ <% end %>
92
+
93
+ <!-- Included Modules -->
94
+ <% unless klass.includes.empty? %>
95
+ <div id="includes-section" class="section">
96
+ <h3 class="section-header">Included Modules</h3>
97
+ <ul class="link-list">
98
+ <% klass.each_include do |inc| %>
99
+ <% unless String === inc.module %>
100
+ <li><a class="include" href="<%= klass.aref_to inc.module.path %>"><%= inc.module.full_name %></a></li>
101
+ <% else %>
102
+ <li><span class="include"><%= inc.name %></span></li>
103
+ <% end %>
104
+ <% end %>
105
+ </ul>
106
+ </div>
107
+ <% end %>
108
+ </div>
109
+
110
+ <div id="project-metadata">
111
+ <% simple_files = @files.select {|tl| tl.parser == RDoc::Parser::Simple } %>
112
+ <% unless simple_files.empty? then %>
113
+ <div id="fileindex-section" class="section project-section">
114
+ <h3 class="section-header">Files</h3>
115
+ <ul>
116
+ <% simple_files.each do |file| %>
117
+ <li class="file"><a href="<%= rel_prefix %>/<%= file.path %>"><%= h file.base_name %></a></li>
118
+ <% end %>
119
+ </ul>
120
+ </div>
121
+ <% end %>
122
+
123
+ <div id="classindex-section" class="section project-section">
124
+ <h3 class="section-header">Class Index
125
+ <span class="search-toggle">
126
+ <img src="<%= rel_prefix %>/images/find.png"
127
+ height="16" width="16" alt="[+]"
128
+ title="show/hide quicksearch" />
129
+ </span>
130
+ </h3>
131
+ <form action="#" method="get" accept-charset="utf-8" class="initially-hidden">
132
+ <fieldset>
133
+ <legend>Quicksearch</legend>
134
+ <input type="text" name="quicksearch" value="" class="quicksearch-field" />
135
+ </fieldset>
136
+ </form>
137
+
138
+ <ul class="link-list">
139
+ <% @modsort.each do |index_klass| %>
140
+ <li><a href="<%= rel_prefix %>/<%= index_klass.path %>"><%= index_klass.full_name %></a></li>
141
+ <% end %>
142
+ </ul>
143
+ <div id="no-class-search-results" style="display: none;">No matching classes.</div>
144
+ </div>
145
+
146
+ <% if $DEBUG_RDOC %>
147
+ <div id="debugging-toggle"><img src="<%= rel_prefix %>/images/bug.png"
148
+ alt="toggle debugging" height="16" width="16" /></div>
149
+ <% end %>
150
+ </div>
151
+ </div>
152
+
153
+ <div id="documentation">
154
+ <h1 class="<%= klass.type %>"><%= klass.full_name %></h1>
155
+
156
+ <div id="description"><%= klass.description %></div>
157
+
158
+ <!-- Constants -->
159
+ <% unless klass.constants.empty? %>
160
+ <div id="constants-list" class="section">
161
+ <h3 class="section-header">Constants</h3>
162
+ <dl>
163
+ <% klass.each_constant do |const| %>
164
+ <dt><a name="<%= const.name %>"><%= const.name %></a></dt>
165
+ <% if const.comment %>
166
+ <dd class="description"><%= const.description.strip %></dd>
167
+ <% else %>
168
+ <dd class="description missing-docs">(Not documented)</dd>
169
+ <% end %>
170
+ <% end %>
171
+ </dl>
172
+ </div>
173
+ <% end %>
174
+
175
+ <!-- Attributes -->
176
+ <% unless klass.attributes.empty? %>
177
+ <div id="attribute-method-details" class="method-section section">
178
+ <h3 class="section-header">Attributes</h3>
179
+
180
+ <% klass.each_attribute do |attrib| %>
181
+ <div id="<%= attrib.html_name %>-attribute-method" class="method-detail">
182
+ <a name="<%= h attrib.name %>"></a>
183
+ <% if attrib.rw =~ /w/i %>
184
+ <a name="<%= h attrib.name %>="></a>
185
+ <% end %>
186
+ <div class="method-heading attribute-method-heading">
187
+ <span class="method-name"><%= h attrib.name %></span>
188
+ <span class="attribute-access-type">[<%= attrib.rw %>]</span>
189
+ </div>
190
+
191
+ <div class="method-description">
192
+ <% if attrib.comment %>
193
+ <%= attrib.description.strip %>
194
+ <% else %>
195
+ <p class="missing-docs">(Not documented)</p>
196
+ <% end %>
197
+ </div>
198
+ </div>
199
+ <% end %>
200
+ </div>
201
+ <% end %>
202
+
203
+ <!-- ActiveRecord Associations -->
204
+ <% unless klass.ar_associations.empty? %>
205
+ <div id="ara-method-details" class="method-section section">
206
+ <h3 class="section-header">ActiveRecord Associations</h3>
207
+ <% klass.ar_associations.each do |ara| %>
208
+ <div id="<%= ara.name %>-ara-method" class="method-detail">
209
+ <div class="method-heading ara-method-heading">
210
+ <span class="method-name">
211
+ <%= %Q|#{ara.atype} #{h ara.name} (<a href="#{ara.class_name}.html"><tt>#{ara.class_name}</tt></a>)| %>
212
+ </span>
213
+ <% if ara.token_stream %>
214
+ <span class="method-click-advice">click to toggle source</span>
215
+ <% end %>
216
+ </div>
217
+
218
+ <div class="method-description">
219
+ <% if ara.comment %>
220
+ <%= ara.description.strip %>
221
+ <% end %>
222
+
223
+ <% if ara.token_stream %>
224
+ <div class="method-source-code" id="<%= ara.name %>-source">
225
+ <pre>
226
+ <%= ara.markup_code %>
227
+ </pre>
228
+ </div>
229
+ <% end %>
230
+ </div>
231
+ </div>
232
+ <% end %>
233
+ </div>
234
+ <% end %>
235
+
236
+ <!-- Methods -->
237
+ <% klass.methods_by_type.each do |type, visibilities|
238
+ next if visibilities.empty?
239
+ visibilities.each do |visibility, methods|
240
+ next if methods.empty? %>
241
+ <div id="<%= visibility %>-<%= type %>-method-details" class="method-section section">
242
+ <h3 class="section-header"><%= visibility.to_s.capitalize %> <%= type.capitalize %> Methods</h3>
243
+
244
+ <% methods.each do |method| %>
245
+ <div id="<%= method.html_name %>-method" class="method-detail <%= method.is_alias_for ? "method-alias" : '' %>">
246
+ <a name="<%= h method.aref %>"></a>
247
+
248
+ <div class="method-heading">
249
+ <% if method.call_seq %>
250
+ <span class="method-callseq"><%= method.call_seq.strip.gsub(/->/, '&rarr;').gsub( /^\w.*?\./m, '') %></span>
251
+ <span class="method-click-advice">click to toggle source</span>
252
+ <% else %>
253
+ <span class="method-name"><%= h method.name %></span>
254
+ <span class="method-args"><%= method.params %></span>
255
+ <span class="method-click-advice">click to toggle source</span>
256
+ <% end %>
257
+ </div>
258
+
259
+ <div class="method-description">
260
+ <% if method.comment %>
261
+ <%= method.description.strip %>
262
+ <% else %>
263
+ <p class="missing-docs">(Not documented)</p>
264
+ <% end %>
265
+
266
+ <% if method.token_stream %>
267
+ <div class="method-source-code" id="<%= method.html_name %>-source">
268
+ <pre>
269
+ <%= method.markup_code %>
270
+ </pre>
271
+ </div>
272
+ <% end %>
273
+ </div>
274
+
275
+ <% unless method.aliases.empty? %>
276
+ <div class="aliases">
277
+ Also aliased as: <%= method.aliases.map do |aka|
278
+ %{<a href="#{ klass.aref_to aka.path}">#{h aka.name}</a>}
279
+ end.join(", ") %>
280
+ </div>
281
+ <% end %>
282
+ </div>
283
+
284
+ <% end %>
285
+ </div>
286
+ <% end %>
287
+ <% end %>
288
+
289
+ <div id="rdoc-debugging-section-dump" class="debugging-section">
290
+ <% if $DEBUG_RDOC
291
+ require 'pp' %>
292
+ <pre><%= h PP.pp(klass, _erbout) %></pre>
293
+ <% else %>
294
+ <p>Disabled; run with --debug to generate this.</p>
295
+ <% end %>
296
+ </div>
297
+
298
+ </div>
299
+
300
+ <div id="validator-badges">
301
+ <p><small><a href="http://validator.w3.org/check/referer">[Validate]</a></small></p>
302
+ <p><small>Generated with the Railsfish Rdoc Generator
303
+ <%= RDoc::Generator::Railsfish::VERSION %></small>.</p>
304
+ </div>
305
+
306
+ </body>
307
+ </html>
308
+
@@ -0,0 +1,387 @@
1
+ require 'rdoc/parser/ruby'
2
+ class RDoc::Parser::Rails < RDoc::Parser::Ruby
3
+ include RDoc::RubyToken
4
+ parse_files_matching(/\.rbw?$/)
5
+
6
+ def parse_rails_meta(container, single, tk, comment)
7
+ return unless container.document_children
8
+ restore_init_token(tk)
9
+ # Start listening to get_tk and saving read tokens into @token_stream, parse
10
+ # symbol args
11
+ add_token_listener self
12
+ args = parse_symbol_arg # This gets any symbol or string args
13
+ opts = (parse_final_hash if token_stream[-1].is_a?(TkCOMMA)) || {}
14
+ remove_token_listener self
15
+
16
+ send("parse_#{tk.name}", container, single, tk, comment, args, opts)
17
+ end
18
+
19
+ def parse_rails_debug(container, single, tk, comment, args, opts)
20
+ puts tk.name
21
+ puts args.inspect
22
+ puts opts.inspect if opts
23
+ puts ""
24
+ end
25
+
26
+ def parse_rails_pending(container, single, tk, comment, args, opts); end
27
+ alias parse_validates_uniqueness_of parse_rails_pending
28
+
29
+ def parse_ar_association(container, single, tk, comment, args, opts)
30
+ ara = RDoc::ArAssociation.new(
31
+ :atype => tk.name,
32
+ :name => args[0],
33
+ :opts => opts,
34
+ :comment => comment
35
+ )
36
+
37
+ ara.start_collecting_tokens
38
+ ara.add_tokens [position_comment(tk), NEWLINE_TOKEN]
39
+ ara.add_tokens token_stream
40
+
41
+ ara.parent = container
42
+ container.ar_associations << ara
43
+ end
44
+ alias parse_belongs_to parse_ar_association
45
+ alias parse_has_one parse_ar_association
46
+ alias parse_has_many parse_ar_association
47
+ alias parse_has_and_belongs_to_many parse_ar_association
48
+
49
+ # Take the args, opts, and tokens collected by parse_rails_meta and generate
50
+ # method documentation for delegated methods.
51
+ def parse_delegate(container, single, tk, comment, args, opts)
52
+ add_token_listener self
53
+ skip_to_eol
54
+ remove_token_listener self
55
+
56
+ args.each do |arg|
57
+ d_meth = RDoc::AnyMethod.new('', arg)
58
+ @stats.add_method d_meth
59
+ container.add_method d_meth
60
+
61
+ d_meth.start_collecting_tokens
62
+ d_meth.add_tokens [position_comment(tk), NEWLINE_TOKEN]
63
+ d_meth.add_tokens token_stream
64
+
65
+ d_meth.params = "(?) - delegated to #{opts[:to].inspect}" if opts[:to]
66
+ d_meth.params ||= '(?) - delegated method...'
67
+
68
+ d_meth.comment = comment
69
+ end
70
+ end
71
+
72
+ def skip_to_eol
73
+ tk = get_tk until tk.is_a?(TkNL)
74
+ end
75
+
76
+ # Parse tokens assumed to represent a final hash argument, thus will parse
77
+ # either a {} enclosed hash or a naked final hash.
78
+ #
79
+ # The purpose of this is mainly to be able to generate documentation for Rails
80
+ # meta calls, which generally have symbols/strings for keys and values, so
81
+ # that is what I've prioritized being able to parse. Shouldn't be difficult to
82
+ # add parsing of numeric keys/values. Parsing array/hash keys/values would be
83
+ # more difficult and isn't in the scope of what I currently plan to do. If
84
+ # this hits something it doesn't know how to parse, it rewinds all its tokens
85
+ # and quits.
86
+ #
87
+ # The best way to set up a call to parse_final_hash is as is done in
88
+ # parse_rails_meta, where we first call parse_symbol_arg. This is good setup
89
+ # because parse_symbol_arg will rewind to the comma before the final hash if
90
+ # it detects a hash in parsing other args.
91
+ def parse_final_hash
92
+ buffer = TokenStream.new
93
+ add_token_listener(buffer)
94
+ skip_tkspace(true)
95
+
96
+ case tk = get_tk
97
+ when TkLBRACE then bracketed = true
98
+ when TkSYMBOL, TkSTRING then bracketed = false
99
+ else
100
+ unget_tk(tk) until buffer.token_stream.empty?
101
+ remove_token_listener(buffer)
102
+ return
103
+ end
104
+
105
+ last_tk = tk
106
+ while tk = get_tk do
107
+ case tk
108
+ when TkSEMICOLON then break
109
+ when TkNL
110
+ unget_tk(tk) and break unless last_tk and TkCOMMA === last_tk
111
+ when TkSPACE, TkCOMMENT
112
+ when TkSYMBOL, TkSTRING, TkCOMMA, TkASSIGN, TkGT, TkASSOC then last_tk = tk # Will probably want to expand this to include numerics, possibly others; let's cross that bridge when we come to it.
113
+ else
114
+ break if bracketed and tk.is_a?(TkRBRACE)
115
+ unget_tk(tk) and break if !bracketed and tk.is_a?(TkDO)
116
+
117
+ unget_tk(tk) until buffer.token_stream.empty?
118
+ remove_token_listener(buffer)
119
+ return
120
+ end
121
+ end
122
+
123
+ remove_token_listener(buffer)
124
+ read = buffer.token_stream.collect{|tk|tk.text}.join
125
+ read = "{#{read}\n}" if !bracketed # We need the \n in case #{read} ends with a comment
126
+ eval(read) rescue nil
127
+ end
128
+
129
+ # Largely copied from super, but rewinds if it hits a =>, indicating the last
130
+ # symbol/string read should have been part of the final hash arg. Rewinds to
131
+ # the comma before the final hash, which provides a good check after we return
132
+ # of whether there are still more arguments to parse.
133
+ def parse_symbol_arg(no=nil)
134
+ buffer = TokenStream.new
135
+ add_token_listener(buffer)
136
+
137
+ args = []
138
+ skip_tkspace_comment
139
+ case tk = get_tk
140
+ when TkLPAREN
141
+ loop do
142
+ skip_tkspace_comment
143
+ if tk = parse_symbol_in_arg
144
+ args.push tk
145
+ break if no and args.size >= no
146
+ end
147
+
148
+ skip_tkspace_comment
149
+ case tk2 = get_tk
150
+ when TkRPAREN
151
+ break
152
+ when TkCOMMA
153
+ when TkASSOC, TkASSIGN, TkGT
154
+ # Oops, we started slurping the final Hash!
155
+ # So rewind back past the symbol or string that came before the =>
156
+ unget_tk(buffer.token_stream[-1]) until buffer.token_stream[-1].is_a?(TkCOMMA) or buffer.token_stream.empty?
157
+ args.pop
158
+ break
159
+ when TkLBRACE
160
+ # We hit the beginning of a hash or block, so rewind to the comma
161
+ unget_tk(buffer.token_stream[-1]) until buffer.token_stream[-1].is_a?(TkCOMMA) or buffer.token_stream.empty?
162
+ break
163
+ else
164
+ warn("unexpected token: '#{tk2.inspect}'") if $DEBUG_RDOC
165
+ break
166
+ end
167
+ end
168
+ else
169
+ unget_tk tk
170
+ if tk = parse_symbol_in_arg
171
+ args.push tk
172
+ return args if no and args.size >= no
173
+ end
174
+
175
+ loop do
176
+ skip_tkspace(false)
177
+
178
+ tk1 = get_tk
179
+ if TkCOMMA === tk1
180
+ elsif TkASSOC === tk1 or TkASSIGN === tk1 or TkGT === tk1
181
+ # Oops, we started slurping the final Hash!
182
+ # So rewind back past the symbol or string that came before the =>
183
+ unget_tk(buffer.token_stream[-1]) until buffer.token_stream[-1].is_a?(TkCOMMA) or buffer.token_stream.empty?
184
+ args.pop
185
+ break
186
+ elsif TkLBRACE === tk1
187
+ # We hit the beginning of a hash or block, so rewind to the comma
188
+ unget_tk(buffer.token_stream[-1]) until buffer.token_stream[-1].is_a?(TkCOMMA) or buffer.token_stream.empty?
189
+ break
190
+ else
191
+ unget_tk tk1
192
+ break
193
+ end
194
+
195
+ skip_tkspace_comment
196
+ if tk = parse_symbol_in_arg
197
+ args.push tk
198
+ break if no and args.size >= no
199
+ end
200
+ end
201
+ end
202
+
203
+ remove_token_listener buffer
204
+ args
205
+ end
206
+
207
+ # Comment line required to help generator put line numbers on included source code.
208
+ def position_comment(tk)
209
+ TkCOMMENT.new(tk.line_no, 1, "# File #{@top_level.absolute_name}, line #{tk.line_no}")
210
+ end
211
+
212
+ # Clear @token_stream and then put back the indentation and initial token;
213
+ # basically assumes tk is the first non-whitespace token on the line.
214
+ def restore_init_token(tk)
215
+ start_collecting_tokens
216
+ # indent = TkSPACE.new(1, 1)
217
+ # change for rdoc > 2.4.3
218
+ indent = TkSPACE.new(nil,1, 1)
219
+ indent.set_text(' ' * tk.char_no)
220
+ add_tokens([indent, tk])
221
+ end
222
+
223
+ # The identifiers that should be processed as rails meta-calls
224
+ RAILS_IDENTIFIERS = [
225
+ 'belongs_to',
226
+ 'has_one',
227
+ 'has_many',
228
+ 'has_and_belongs_to_many',
229
+ 'delegate',
230
+ 'validates_uniqueness_of'
231
+ ]
232
+
233
+ # Copied from super, with a minor tweak to the TkIDENTIFIER parsing portion.
234
+ def parse_statements(container, single = NORMAL, current_method = nil, comment = '')
235
+ nest = 1
236
+ save_visibility = container.visibility
237
+
238
+ non_comment_seen = true
239
+
240
+ while tk = get_tk do
241
+ keep_comment = false
242
+
243
+ non_comment_seen = true unless TkCOMMENT === tk
244
+
245
+ case tk
246
+ when TkNL then
247
+ skip_tkspace true # Skip blanks and newlines
248
+ tk = get_tk
249
+
250
+ if TkCOMMENT === tk then
251
+ if non_comment_seen then
252
+ # Look for RDoc in a comment about to be thrown away
253
+ parse_comment container, tk, comment unless comment.empty?
254
+
255
+ comment = ''
256
+ non_comment_seen = false
257
+ end
258
+
259
+ while TkCOMMENT === tk do
260
+ comment << tk.text << "\n"
261
+ tk = get_tk # this is the newline
262
+ skip_tkspace(false) # leading spaces
263
+ tk = get_tk
264
+ end
265
+
266
+ unless comment.empty? then
267
+ look_for_directives_in container, comment
268
+
269
+ if container.done_documenting then
270
+ container.ongoing_visibility = save_visibility
271
+ end
272
+ end
273
+
274
+ keep_comment = true
275
+ else
276
+ non_comment_seen = true
277
+ end
278
+
279
+ unget_tk tk
280
+ keep_comment = true
281
+
282
+ when TkCLASS then
283
+ if container.document_children then
284
+ parse_class container, single, tk, comment
285
+ else
286
+ nest += 1
287
+ end
288
+
289
+ when TkMODULE then
290
+ if container.document_children then
291
+ parse_module container, single, tk, comment
292
+ else
293
+ nest += 1
294
+ end
295
+
296
+ when TkDEF then
297
+ if container.document_self then
298
+ parse_method container, single, tk, comment
299
+ else
300
+ nest += 1
301
+ end
302
+
303
+ when TkCONSTANT then
304
+ if container.document_self then
305
+ # parse_constant container, single, tk, comment
306
+ # change for rdoc > 2.4.3
307
+ parse_constant container, tk, comment
308
+ end
309
+
310
+ when TkALIAS then
311
+ if container.document_self then
312
+ parse_alias container, single, tk, comment
313
+ end
314
+
315
+ when TkYIELD then
316
+ if current_method.nil? then
317
+ warn "Warning: yield outside of method" if container.document_self
318
+ else
319
+ parse_yield container, single, tk, current_method
320
+ end
321
+
322
+ # Until and While can have a 'do', which shouldn't increase the nesting.
323
+ # We can't solve the general case, but we can handle most occurrences by
324
+ # ignoring a do at the end of a line.
325
+ when TkUNTIL, TkWHILE then
326
+ nest += 1
327
+ skip_optional_do_after_expression
328
+
329
+ # 'for' is trickier
330
+ when TkFOR then
331
+ nest += 1
332
+ skip_for_variable
333
+ skip_optional_do_after_expression
334
+
335
+ when TkCASE, TkDO, TkIF, TkUNLESS, TkBEGIN then
336
+ nest += 1
337
+
338
+ when TkIDENTIFIER then
339
+ if nest == 1 and current_method.nil? then
340
+ case tk.name
341
+ when 'private', 'protected', 'public', 'private_class_method',
342
+ 'public_class_method', 'module_function' then
343
+ parse_visibility container, single, tk
344
+ keep_comment = true
345
+ when 'attr' then
346
+ parse_attr container, single, tk, comment
347
+ when /^attr_(reader|writer|accessor)$/ then
348
+ parse_attr_accessor container, single, tk, comment
349
+ when 'alias_method' then
350
+ if container.document_self then
351
+ parse_alias container, single, tk, comment
352
+ end
353
+ when *RAILS_IDENTIFIERS then parse_rails_meta container, single, tk, comment
354
+ else
355
+ if container.document_self and comment =~ /\A#\#$/ then
356
+ parse_meta_method container, single, tk, comment
357
+ end
358
+ end
359
+ end
360
+
361
+ case tk.name
362
+ when "require" then
363
+ parse_require container, comment
364
+ when "include" then
365
+ parse_include container, comment
366
+ end
367
+
368
+ when TkEND then
369
+ nest -= 1
370
+ if nest == 0 then
371
+ read_documentation_modifiers container, RDoc::CLASS_MODIFIERS
372
+ container.ongoing_visibility = save_visibility
373
+ return
374
+ end
375
+
376
+ end
377
+
378
+ comment = '' unless keep_comment
379
+
380
+ begin
381
+ get_tkread
382
+ skip_tkspace(false)
383
+ end while peek_tk == TkNL
384
+ end
385
+ end
386
+
387
+ end
@@ -0,0 +1,9 @@
1
+ require 'rdoc/tokenstream'
2
+ module RDoc::TokenStream
3
+ class TokenStream
4
+ include RDoc::TokenStream
5
+ def initialize
6
+ start_collecting_tokens
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,19 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../rdoc_rails')
2
+
3
+ Rake::RDocTask.remove_task('doc:app')
4
+
5
+ namespace :doc do
6
+ desc "Generate documentation for the application. Set custom template with TEMPLATE=/path/to/rdoc/template.rb Set custom format with FORMAT=format_name"
7
+ Rake::RDocTask.new('app') { |rdoc|
8
+ ENV['format'] ||= 'railsfish'
9
+ rdoc.rdoc_dir = 'doc/app'
10
+ rdoc.template = ENV['template'] if ENV['template']
11
+ rdoc.title = "Rails Application Documentation"
12
+ rdoc.options << '--line-numbers' << '--inline-source'
13
+ rdoc.options << '--charset' << 'utf-8'
14
+ rdoc.options << '--format' << ENV['format']
15
+ rdoc.rdoc_files.include('doc/README_FOR_APP')
16
+ rdoc.rdoc_files.include('app/**/*.rb')
17
+ rdoc.rdoc_files.include('lib/**/*.rb')
18
+ }
19
+ end
@@ -0,0 +1,4 @@
1
+ class Blog < ActiveRecord::Base
2
+ belongs_to :user
3
+ has_many :posts
4
+ end
@@ -0,0 +1,12 @@
1
+ class Comment < ActiveRecord::Base
2
+ # This will parse incorrectly, ignoring the definition
3
+ # of :class_name => "User"
4
+ belongs_to :commenter,
5
+ :class_name => "User",
6
+ :counter_cache => true
7
+
8
+ # This has no idea who or what commentable is.
9
+ belongs_to :commentable,
10
+ :polymorphic => true,
11
+ :counter_cache => true
12
+ end
@@ -0,0 +1,6 @@
1
+ class Post < ActiveRecord::Base
2
+ has_many :comments,
3
+ :as => :commentable
4
+ belongs_to :user,
5
+ :counter_cache => true
6
+ end
@@ -0,0 +1,13 @@
1
+ class User < ActiveRecord::Base
2
+ has_many :blogs
3
+ has_many :posts
4
+ has_many :comments, :as => :commenter
5
+
6
+ def self.something_classy
7
+
8
+ end
9
+
10
+ def something_instancey
11
+
12
+ end
13
+ end
@@ -0,0 +1,15 @@
1
+ require File.dirname(__FILE__) + '/test_helper'
2
+ require 'fileutils'
3
+
4
+ class RDocRailsTest < ActiveSupport::TestCase
5
+
6
+ # test "should say hello" do
7
+ # puts 'hello'
8
+ # end
9
+
10
+ test "should do something" do
11
+ FileUtils.remove_dir("test/rdoc") if File.exists?("test/rdoc")
12
+ Rake::Task[:rdoc_test].invoke
13
+ end
14
+
15
+ end
@@ -0,0 +1,9 @@
1
+ require 'rdoc'
2
+ require 'rdoc/rdoc'
3
+ #require 'rake'
4
+ #require 'rake/testtask'
5
+ require 'rake/rdoctask'
6
+ require File.dirname(__FILE__) + '/../../lib/rdoc_rails'
7
+
8
+ # Load Rails rakefile extensions
9
+ Dir["#{File.dirname(__FILE__)}/*.rake"].each { |ext| load ext }
@@ -0,0 +1,86 @@
1
+ require 'test/unit'
2
+ require 'rubygems'
3
+ require 'active_support'
4
+ require 'active_support/test_case'
5
+ require 'active_record'
6
+
7
+ require 'tasks/testing'
8
+ $: << File.expand_path(File.dirname(__FILE__) + "/../lib/" )
9
+ require 'rdoc_rails'
10
+
11
+ $: << File.expand_path(File.dirname(__FILE__) + "/app/models/" )
12
+ %w( user blog post comment ).each{|m| require m }
13
+
14
+ #ActiveRecord::Base.establish_connection(
15
+ # :adapter => "sqlite3",
16
+ # :database => ":memory:")
17
+ #
18
+ #def setup_db
19
+ # ActiveRecord::Schema.define(:version => 1) do
20
+ # create_table :users do |t|
21
+ # end
22
+ # create_table :blogs do |t|
23
+ # t.references :user
24
+ # end
25
+ # create_table :posts do |t|
26
+ # t.references :blog
27
+ # t.references :user
28
+ # end
29
+ # create_table :comments do |t|
30
+ # t.references :commenter
31
+ # t.references :commentable, :polymorphic => true
32
+ # end
33
+ # end
34
+ #end
35
+ #
36
+ #def teardown_db
37
+ # ActiveRecord::Base.connection.tables.each do |table|
38
+ # ActiveRecord::Base.connection.drop_table(table)
39
+ # end
40
+ #end
41
+
42
+
43
+ class ActiveSupport::TestCase
44
+
45
+ end
46
+
47
+
48
+ Rake::Task.class_eval do
49
+
50
+ # For some reason, a blank prerequisite is added
51
+ # which causes
52
+ # RuntimeError: Don't know how to build task ''
53
+ # so I make sure that they are gone
54
+ def invoke_prerequisites_with_compressing(
55
+ task_args, invocation_chain)
56
+ @prerequisites.delete_if{|a|a.blank?} # || a =~ /\A\s*\z/ }
57
+ invoke_prerequisites_without_compressing(
58
+ task_args, invocation_chain
59
+ )
60
+ end
61
+ alias_method_chain :invoke_prerequisites, :compressing
62
+
63
+ end
64
+
65
+
66
+ RDoc::Generator::Railsfish.class_eval do
67
+ def generate_class_files
68
+ debug_msg "Generating class documentation in #@outputdir"
69
+
70
+ # The pathname ends up being incorrect by "../."
71
+ # That's all it takes and since there is not apparent
72
+ # variable, I just override the method for testing.
73
+
74
+ templatefile = Pathname.new("../../lib/rdoc_rails/rdoc/generator/template/railsfish/classpage.rhtml")
75
+
76
+ @classes.each do |klass|
77
+ debug_msg " working on %s (%s)" % [ klass.full_name, klass.path ]
78
+ outfile = @outputdir + klass.path
79
+ rel_prefix = @outputdir.relative_path_from( outfile.dirname )
80
+ svninfo = self.get_svninfo( klass )
81
+ debug_msg " rendering #{outfile}"
82
+ self.render_template( templatefile, binding(), outfile )
83
+ end
84
+ end
85
+ end
86
+
metadata ADDED
@@ -0,0 +1,118 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: jakewendt-rdoc_rails
3
+ version: !ruby/object:Gem::Version
4
+ hash: 27
5
+ prerelease:
6
+ segments:
7
+ - 0
8
+ - 0
9
+ - 2
10
+ version: 0.0.2
11
+ platform: ruby
12
+ authors:
13
+ - Chinasaur
14
+ - George 'Jake' Wendt
15
+ autorequire:
16
+ bindir: bin
17
+ cert_chain: []
18
+
19
+ date: 2011-02-08 00:00:00 -08:00
20
+ default_executable:
21
+ dependencies:
22
+ - !ruby/object:Gem::Dependency
23
+ name: rails
24
+ prerelease: false
25
+ requirement: &id001 !ruby/object:Gem::Requirement
26
+ none: false
27
+ requirements:
28
+ - - ~>
29
+ - !ruby/object:Gem::Version
30
+ hash: 7
31
+ segments:
32
+ - 2
33
+ version: "2"
34
+ type: :runtime
35
+ version_requirements: *id001
36
+ - !ruby/object:Gem::Dependency
37
+ name: rdoc
38
+ prerelease: false
39
+ requirement: &id002 !ruby/object:Gem::Requirement
40
+ none: false
41
+ requirements:
42
+ - - ~>
43
+ - !ruby/object:Gem::Version
44
+ hash: 7
45
+ segments:
46
+ - 2
47
+ version: "2"
48
+ type: :runtime
49
+ version_requirements: *id002
50
+ description: longer description of your gem
51
+ email: github@jakewendt.com
52
+ executables: []
53
+
54
+ extensions: []
55
+
56
+ extra_rdoc_files:
57
+ - README.rdoc
58
+ files:
59
+ - lib/jakewendt-rdoc_rails.rb
60
+ - lib/rdoc_rails.rb
61
+ - lib/rdoc_rails/rake.rb
62
+ - lib/rdoc_rails/rdoc/ar_association.rb
63
+ - lib/rdoc_rails/rdoc/context.rb
64
+ - lib/rdoc_rails/rdoc/generator/railsfish.rb
65
+ - lib/rdoc_rails/rdoc/generator/template/railsfish/classpage.rhtml
66
+ - lib/rdoc_rails/rdoc/parser/rails.rb
67
+ - lib/rdoc_rails/rdoc/token_stream.rb
68
+ - lib/tasks/rdoc_rails.rake
69
+ - README.rdoc
70
+ - test/app/models/blog.rb
71
+ - test/app/models/comment.rb
72
+ - test/app/models/post.rb
73
+ - test/app/models/user.rb
74
+ - test/rdoc_rails_test.rb
75
+ - test/tasks/testing.rb
76
+ - test/test_helper.rb
77
+ has_rdoc: true
78
+ homepage: http://github.com/jakewendt/rdoc_rails
79
+ licenses: []
80
+
81
+ post_install_message:
82
+ rdoc_options: []
83
+
84
+ require_paths:
85
+ - lib
86
+ required_ruby_version: !ruby/object:Gem::Requirement
87
+ none: false
88
+ requirements:
89
+ - - ">="
90
+ - !ruby/object:Gem::Version
91
+ hash: 3
92
+ segments:
93
+ - 0
94
+ version: "0"
95
+ required_rubygems_version: !ruby/object:Gem::Requirement
96
+ none: false
97
+ requirements:
98
+ - - ">="
99
+ - !ruby/object:Gem::Version
100
+ hash: 3
101
+ segments:
102
+ - 0
103
+ version: "0"
104
+ requirements: []
105
+
106
+ rubyforge_project:
107
+ rubygems_version: 1.5.0
108
+ signing_key:
109
+ specification_version: 3
110
+ summary: one-line summary of your gem
111
+ test_files:
112
+ - test/app/models/blog.rb
113
+ - test/app/models/comment.rb
114
+ - test/app/models/post.rb
115
+ - test/app/models/user.rb
116
+ - test/rdoc_rails_test.rb
117
+ - test/tasks/testing.rb
118
+ - test/test_helper.rb