jakewendt-rdoc_rails 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -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