Class: RDoc::Parser::Rails

Inherits:
Ruby
  • Object
show all
Includes:
RubyToken
Defined in:
lib/rdoc_rails/rdoc/parser/rails.rb

Constant Summary collapse

RAILS_IDENTIFIERS =

The identifiers that should be processed as rails meta-calls

[
  'belongs_to',
  'has_one',
  'has_many',
  'has_and_belongs_to_many',
  'delegate',
  'validates_uniqueness_of'
]

Instance Method Summary collapse

Instance Method Details

#parse_ar_association(container, single, tk, comment, args, opts) ⇒ Object Also known as: parse_belongs_to, parse_has_one, parse_has_many, parse_has_and_belongs_to_many



29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
# File 'lib/rdoc_rails/rdoc/parser/rails.rb', line 29

def parse_ar_association(container, single, tk, comment, args, opts)
  ara =  RDoc::ArAssociation.new(
    :atype   => tk.name,
    :name    => args[0],
    :opts    => opts,
    :comment => comment
  )
  
  ara.start_collecting_tokens
  ara.add_tokens [position_comment(tk), NEWLINE_TOKEN]
  ara.add_tokens token_stream
  
  ara.parent = container
  container.ar_associations << ara
end

#parse_delegate(container, single, tk, comment, args, opts) ⇒ Object

Take the args, opts, and tokens collected by parse_rails_meta and generate method documentation for delegated methods.



51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
# File 'lib/rdoc_rails/rdoc/parser/rails.rb', line 51

def parse_delegate(container, single, tk, comment, args, opts)
  add_token_listener self
  skip_to_eol
  remove_token_listener self
  
  args.each do |arg|
    d_meth = RDoc::AnyMethod.new('', arg)
    @stats.add_method    d_meth
    container.add_method d_meth
    
    d_meth.start_collecting_tokens
    d_meth.add_tokens [position_comment(tk), NEWLINE_TOKEN]
    d_meth.add_tokens token_stream
    
    d_meth.params   = "(?) - delegated to #{opts[:to].inspect}" if opts[:to]
    d_meth.params ||= '(?) - delegated method...'
    
    d_meth.comment = comment
  end
end

#parse_final_hashObject

Parse tokens assumed to represent a final hash argument, thus will parse either a {} enclosed hash or a naked final hash.

The purpose of this is mainly to be able to generate documentation for Rails meta calls, which generally have symbols/strings for keys and values, so that is what I’ve prioritized being able to parse. Shouldn’t be difficult to add parsing of numeric keys/values. Parsing array/hash keys/values would be more difficult and isn’t in the scope of what I currently plan to do. If this hits something it doesn’t know how to parse, it rewinds all its tokens and quits.

The best way to set up a call to parse_final_hash is as is done in parse_rails_meta, where we first call parse_symbol_arg. This is good setup because parse_symbol_arg will rewind to the comma before the final hash if it detects a hash in parsing other args.



91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
# File 'lib/rdoc_rails/rdoc/parser/rails.rb', line 91

def parse_final_hash
  buffer = TokenStream.new
  add_token_listener(buffer)
  skip_tkspace(true)
  
  case tk = get_tk
    when TkLBRACE           then bracketed = true
    when TkSYMBOL, TkSTRING then bracketed = false
    else
      unget_tk(tk) until buffer.token_stream.empty?
      remove_token_listener(buffer)
      return
  end
  
  last_tk = tk
  while tk = get_tk do
    case tk
      when TkSEMICOLON then break
      when TkNL
        unget_tk(tk) and break unless last_tk and TkCOMMA === last_tk
      when TkSPACE, TkCOMMENT
      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.
      else
        break                  if  bracketed and tk.is_a?(TkRBRACE)
        unget_tk(tk) and break if !bracketed and tk.is_a?(TkDO)
        
        unget_tk(tk) until buffer.token_stream.empty?
        remove_token_listener(buffer)
        return
    end
  end
  
  remove_token_listener(buffer)
  read = buffer.token_stream.collect{|tk|tk.text}.join
  read = "{#{read}\n}" if !bracketed # We need the \n in case #{read} ends with a comment
  eval(read) rescue nil
end

#parse_rails_debug(container, single, tk, comment, args, opts) ⇒ Object



19
20
21
22
23
24
# File 'lib/rdoc_rails/rdoc/parser/rails.rb', line 19

def parse_rails_debug(container, single, tk, comment, args, opts)
  puts tk.name
  puts args.inspect
  puts opts.inspect if opts
  puts ""
end

#parse_rails_meta(container, single, tk, comment) ⇒ Object



6
7
8
9
10
11
12
13
14
15
16
17
# File 'lib/rdoc_rails/rdoc/parser/rails.rb', line 6

def parse_rails_meta(container, single, tk, comment)
  return unless container.document_children
  restore_init_token(tk)
  # Start listening to get_tk and saving read tokens into @token_stream, parse
  # symbol args
  add_token_listener self
  args = parse_symbol_arg # This gets any symbol or string args
  opts = (parse_final_hash if token_stream[-1].is_a?(TkCOMMA)) || {}
  remove_token_listener self
  
  send("parse_#{tk.name}", container, single, tk, comment, args, opts)
end

#parse_rails_pending(container, single, tk, comment, args, opts) ⇒ Object Also known as: parse_validates_uniqueness_of



26
# File 'lib/rdoc_rails/rdoc/parser/rails.rb', line 26

def parse_rails_pending(container, single, tk, comment, args, opts); end

#parse_statements(container, single = NORMAL, current_method = nil, comment = '') ⇒ Object

Copied from super, with a minor tweak to the TkIDENTIFIER parsing portion.



234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
# File 'lib/rdoc_rails/rdoc/parser/rails.rb', line 234

def parse_statements(container, single = NORMAL, current_method = nil, comment = '')
  nest = 1
  save_visibility = container.visibility
  
  non_comment_seen = true
  
  while tk = get_tk do
    keep_comment = false
    
    non_comment_seen = true unless TkCOMMENT === tk
    
    case tk
    when TkNL then
      skip_tkspace true # Skip blanks and newlines
      tk = get_tk
      
      if TkCOMMENT === tk then
        if non_comment_seen then
          # Look for RDoc in a comment about to be thrown away
          parse_comment container, tk, comment unless comment.empty?
          
          comment = ''
          non_comment_seen = false
        end
        
        while TkCOMMENT === tk do
          comment << tk.text << "\n"
          tk = get_tk          # this is the newline
          skip_tkspace(false)  # leading spaces
          tk = get_tk
        end
        
        unless comment.empty? then
          look_for_directives_in container, comment
          
          if container.done_documenting then
            container.ongoing_visibility = save_visibility
          end
        end
        
        keep_comment = true
      else
        non_comment_seen = true
      end
      
      unget_tk tk
      keep_comment = true
      
    when TkCLASS then
      if container.document_children then
        parse_class container, single, tk, comment
      else
        nest += 1
      end
      
    when TkMODULE then
      if container.document_children then
        parse_module container, single, tk, comment
      else
        nest += 1
      end
      
    when TkDEF then
      if container.document_self then
        parse_method container, single, tk, comment
      else
        nest += 1
      end
      
    when TkCONSTANT then
      if container.document_self then
#          parse_constant container, single, tk, comment
#	change for rdoc > 2.4.3
        parse_constant container, tk, comment
      end
      
    when TkALIAS then
      if container.document_self then
        parse_alias container, single, tk, comment
      end
      
    when TkYIELD then
      if current_method.nil? then
        warn "Warning: yield outside of method" if container.document_self
      else
        parse_yield container, single, tk, current_method
      end
      
    # Until and While can have a 'do', which shouldn't increase the nesting.
    # We can't solve the general case, but we can handle most occurrences by
    # ignoring a do at the end of a line.
    when  TkUNTIL, TkWHILE then
      nest += 1
      skip_optional_do_after_expression
      
    # 'for' is trickier
    when TkFOR then
      nest += 1
      skip_for_variable
      skip_optional_do_after_expression
      
    when TkCASE, TkDO, TkIF, TkUNLESS, TkBEGIN then
      nest += 1
      
    when TkIDENTIFIER then
      if nest == 1 and current_method.nil? then
        case tk.name
        when 'private', 'protected', 'public', 'private_class_method',
             'public_class_method', 'module_function' then
          parse_visibility container, single, tk
          keep_comment = true
        when 'attr' then
          parse_attr container, single, tk, comment
        when /^attr_(reader|writer|accessor)$/ then
          parse_attr_accessor container, single, tk, comment
        when 'alias_method' then
          if container.document_self then
            parse_alias container, single, tk, comment
          end
        when *RAILS_IDENTIFIERS then parse_rails_meta container, single, tk, comment
        else
          if container.document_self and comment =~ /\A#\#$/ then
            parse_meta_method container, single, tk, comment
          end
        end
      end
      
      case tk.name
      when "require" then
        parse_require container, comment
      when "include" then
        parse_include container, comment
      end
      
    when TkEND then
      nest -= 1
      if nest == 0 then
        read_documentation_modifiers container, RDoc::CLASS_MODIFIERS
        container.ongoing_visibility = save_visibility
        return
      end
      
    end
    
    comment = '' unless keep_comment
    
    begin
      get_tkread
      skip_tkspace(false)
    end while peek_tk == TkNL
  end
end

#parse_symbol_arg(no = nil) ⇒ Object

Largely copied from super, but rewinds if it hits a =>, indicating the last symbol/string read should have been part of the final hash arg. Rewinds to the comma before the final hash, which provides a good check after we return of whether there are still more arguments to parse.



133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
# File 'lib/rdoc_rails/rdoc/parser/rails.rb', line 133

def parse_symbol_arg(no=nil)
  buffer = TokenStream.new
  add_token_listener(buffer)
  
  args = []
  skip_tkspace_comment
  case tk = get_tk
  when TkLPAREN
    loop do
      skip_tkspace_comment
      if tk = parse_symbol_in_arg
        args.push tk
        break if no and args.size >= no
      end
      
      skip_tkspace_comment
      case tk2 = get_tk
      when TkRPAREN
        break
      when TkCOMMA
      when TkASSOC, TkASSIGN, TkGT
        # Oops, we started slurping the final Hash!
        # So rewind back past the symbol or string that came before the =>
        unget_tk(buffer.token_stream[-1]) until buffer.token_stream[-1].is_a?(TkCOMMA) or buffer.token_stream.empty?
        args.pop
        break
      when TkLBRACE
        # We hit the beginning of a hash or block, so rewind to the comma
        unget_tk(buffer.token_stream[-1]) until buffer.token_stream[-1].is_a?(TkCOMMA) or buffer.token_stream.empty?
        break
      else
        warn("unexpected token: '#{tk2.inspect}'") if $DEBUG_RDOC
        break
      end
    end
  else
    unget_tk tk
    if tk = parse_symbol_in_arg
      args.push tk
      return args if no and args.size >= no
    end
    
    loop do
      skip_tkspace(false)
      
      tk1 = get_tk
      if TkCOMMA === tk1
      elsif TkASSOC === tk1 or TkASSIGN === tk1 or TkGT === tk1
        # Oops, we started slurping the final Hash!
        # So rewind back past the symbol or string that came before the =>
        unget_tk(buffer.token_stream[-1]) until buffer.token_stream[-1].is_a?(TkCOMMA) or buffer.token_stream.empty?
        args.pop
        break
      elsif TkLBRACE === tk1
        # We hit the beginning of a hash or block, so rewind to the comma
        unget_tk(buffer.token_stream[-1]) until buffer.token_stream[-1].is_a?(TkCOMMA) or buffer.token_stream.empty?
        break
      else
        unget_tk tk1
        break
      end
      
      skip_tkspace_comment
      if tk = parse_symbol_in_arg
        args.push tk
        break if no and args.size >= no
      end
    end
  end
  
  remove_token_listener buffer
  args
end

#position_comment(tk) ⇒ Object

Comment line required to help generator put line numbers on included source code.



208
209
210
# File 'lib/rdoc_rails/rdoc/parser/rails.rb', line 208

def position_comment(tk)
  TkCOMMENT.new(tk.line_no, 1, "# File #{@top_level.absolute_name}, line #{tk.line_no}")
end

#restore_init_token(tk) ⇒ Object

Clear @token_stream and then put back the indentation and initial token; basically assumes tk is the first non-whitespace token on the line.



214
215
216
217
218
219
220
221
# File 'lib/rdoc_rails/rdoc/parser/rails.rb', line 214

def restore_init_token(tk)
  start_collecting_tokens
#    indent = TkSPACE.new(1, 1)
#	change for rdoc > 2.4.3
  indent = TkSPACE.new(nil,1, 1)
  indent.set_text(' ' * tk.char_no)
  add_tokens([indent, tk])
end

#skip_to_eolObject



72
73
74
# File 'lib/rdoc_rails/rdoc/parser/rails.rb', line 72

def skip_to_eol
  tk = get_tk until tk.is_a?(TkNL)
end