Class: RDoc::Generator::Shomen

Inherits:
Object
  • Object
show all
Defined in:
lib/shomen/rdoc.rb

Overview

Shomen Adaptor for RDoc utilizes the rdoc tool to parse ruby source code to build a Shomen documenation file.

RDoc is almost entirely a free-form documentation system, so it is not possible for Shomen to fully harness all the details it can support from the RDoc documentation, such as method argument descriptions.

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(options) ⇒ Shomen (protected)

Returns a new instance of Shomen.



144
145
146
147
148
149
150
151
152
# File 'lib/shomen/rdoc.rb', line 144

def initialize(options)
  @options = options
  #@options.diagram = false  # why?

  @path_base   = Pathname.pwd.expand_path

  # TODO: This is probably not needed any more.
  @path_output = Pathname.new(@options.op_dir).expand_path(@path_base)
end

Instance Attribute Details

#optionsObject (readonly)

User options from the command line.



43
44
45
# File 'lib/shomen/rdoc.rb', line 43

def options
  @options
end

#path_baseObject (readonly, protected)

Current pathname.



155
156
157
# File 'lib/shomen/rdoc.rb', line 155

def path_base
  @path_base
end

#path_outputObject (readonly, protected)

The output path.



158
159
160
# File 'lib/shomen/rdoc.rb', line 158

def path_output
  @path_output
end

Class Method Details

.for(options) ⇒ Object

Standard generator factory method.



38
39
40
# File 'lib/shomen/rdoc.rb', line 38

def self.for(options)
  new(options)
end

Instance Method Details

#attributes_allObject

List of all attributes in all classes and modules.



89
90
91
# File 'lib/shomen/rdoc.rb', line 89

def attributes_all
  @attributes_all ||= classes.map{ |m| m.attributes }.flatten.sort
end

#class_dirObject

RDoc needs this to function.



104
# File 'lib/shomen/rdoc.rb', line 104

def class_dir ; nil ; end

#classesObject

In the world of the RDoc Generators #classes is the same as #all_classes_and_modules. Well, except that its sorted too. For classes sans modules, see #types.



54
55
56
# File 'lib/shomen/rdoc.rb', line 54

def classes
  @classes ||= RDoc::TopLevel.all_classes_and_modules.sort
end

#classes_toplevelObject

Only toplevel classes and modules.



59
60
61
# File 'lib/shomen/rdoc.rb', line 59

def classes_toplevel
  @classes_toplevel ||= classes.select {|klass| !(RDoc::ClassModule === klass.parent) }
end

#collect_attributes(class_module, singleton = false) ⇒ Object (protected)



468
469
470
471
472
473
474
475
476
477
478
479
480
481
# File 'lib/shomen/rdoc.rb', line 468

def collect_attributes(class_module, singleton=false)
  list = []
  class_module.attributes.each do |a|
    next if singleton ^ a.singleton
    #p a.rw
    #case a.rw
    #when :write, 'W'
    #  list << "#{method_name(a)}="
    #else
      list << method_name(a)
    #end
  end
  list.uniq
end

#collect_methods(class_module, singleton = false) ⇒ Object (protected)



458
459
460
461
462
463
464
465
# File 'lib/shomen/rdoc.rb', line 458

def collect_methods(class_module, singleton=false)
  list = []
  class_module.method_list.each do |m|
    next if singleton ^ m.singleton
    list << method_name(m)
  end
  list.uniq
end

#complete_name(name, namespace) ⇒ Object (protected)

Returns String of fully qualified name.



449
450
451
452
453
454
455
# File 'lib/shomen/rdoc.rb', line 449

def complete_name(name, namespace)
  if name !~ /^#{namespace}/
    "#{namespace}::#{name}"
  else
    name
  end
end

#constants_allObject



94
95
96
# File 'lib/shomen/rdoc.rb', line 94

def constants_all
  @constants_all ||= classes.map{ |c| c.constants }.flatten
end

#debug_msg(msg) ⇒ Object (protected)

Output progress information if rdoc debugging is enabled



507
508
509
510
511
512
513
514
515
# File 'lib/shomen/rdoc.rb', line 507

def debug_msg(msg)
  return unless $DEBUG_RDOC
  case msg[-1,1]
    when '.' then tab = "= "
    when ':' then tab = "== "
    else          tab = "* "
  end
  $stderr.puts(tab + msg)
end

#file_dirObject

RDoc needs this to function.



107
# File 'lib/shomen/rdoc.rb', line 107

def file_dir  ; nil ; end

#filesObject



64
65
66
67
68
# File 'lib/shomen/rdoc.rb', line 64

def files
  @files ||= (
    @files_rdoc.select{ |f| f.parser != RDoc::Parser::Simple }
  )
end

#files_hashObject



79
80
81
# File 'lib/shomen/rdoc.rb', line 79

def files_hash
  @files ||= RDoc::TopLevel.files_hash
end

#files_toplevelObject

List of toplevel files. RDoc supplies this via the #generate method.



71
72
73
74
75
# File 'lib/shomen/rdoc.rb', line 71

def files_toplevel
  @files_toplevel ||= (
    @files_rdoc.select{ |f| f.parser == RDoc::Parser::Simple }
  )
end

#generate(files) ⇒ Object

Build the initial indices and output objects based on an array of top level objects containing the extracted information.



117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
# File 'lib/shomen/rdoc.rb', line 117

def generate(files)
  @files_rdoc = files.sort

  @table = {}

  
  generate_constants
  generate_classes
  #generate_attributes
  generate_methods
  generate_documents
  generate_scripts   # must be last b/c it depends on the others

  # TODO: method accessor fields need to be handled

  # THINK: Internal referencing model, YAML and JSYNC ?
  #ref_table = reference_table(@table)

#rescue StandardError => err
#  debug_msg "%s: %s\n  %s" % [ err.class.name, err.message, err.backtrace.join("\n  ") ]
#  raise err
end

#generate_classesObject (protected)

Add classes (and modules) to table.



194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
# File 'lib/shomen/rdoc.rb', line 194

def generate_classes
  debug_msg "Generating class/module documentation:"

  classes.each do |rdoc_class|
    debug_msg "%s (%s)" % [ rdoc_class.full_name, rdoc_class.path ]

    if rdoc_class.type=='class'
      model = Shomen::Model::Class.new
    else
      model = Shomen::Model::Module.new
    end

    model.path             = rdoc_class.full_name
    model.name             = rdoc_class.name
    model.namespace        = rdoc_class.full_name.split('::')[0...-1].join('::')
    model.includes         = rdoc_class.includes.map{ |x| x.name }  # FIXME: How to "lookup" full name?
    model.extensions       = []                                     # TODO:  How to get extensions?
    model.comment          = rdoc_class.comment
    model.format           = 'rdoc'
    model.constants        = rdoc_class.constants.map{ |x| complete_name(x.name, rdoc_class.full_name) }
    model.modules          = rdoc_class.modules.map{ |x| complete_name(x.name, rdoc_class.full_name) }
    model.classes          = rdoc_class.classes.map{ |x| complete_name(x.name, rdoc_class.full_name) }
    model.methods          = rdoc_class.method_list.map{ |m| method_name(m) }.uniq
    model.accessors        = rdoc_class.attributes.map{ |a| method_name(a) }.uniq  #+ ":#{a.rw}" }.uniq
    model.files            = rdoc_class.in_files.map{ |x| "/#{x.full_name}" }

    if rdoc_class.type == 'class'
      # HACK: No idea why RDoc is returning some weird superclass:
      #   <RDoc::NormalClass:0xd924d4 class Object < BasicObject includes: []
      #     attributes: [] methods: [#<RDoc::AnyMethod:0xd92b8c Object#fileutils
      #     (public)>] aliases: []>
      # Maybe it has something to do with #fileutils?
      model.superclass = (
        case rdoc_class.superclass
        when nil
        when String
          rdoc_class.superclass
        else
          rdoc_class.superclass.full_name
        end
      )
    end

    @table[model.path] = model.to_h
  end
end

#generate_constantsObject (protected)

Add constants to table.



176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
# File 'lib/shomen/rdoc.rb', line 176

def generate_constants
  debug_msg "Generating constant documentation:"
  constants_all.each do |rdoc|
    model = Shomen::Model::Constant.new

    model.path      = rdoc.parent.full_name + '::' + rdoc.name
    model.name      = rdoc.name
    model.namespace = rdoc.parent.full_name
    model.comment   = rdoc.comment
    model.format    = 'rdoc'
    model.value     = rdoc.value
    model.files     = ["/#{rdoc.file.full_name}"]

    @table[model.path] = model.to_h
  end
end

#generate_documentsObject (protected)

Generate entries for information files, e.g. ‘README.rdoc`.



380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
# File 'lib/shomen/rdoc.rb', line 380

def generate_documents
  files_toplevel.each do |rdoc_document|
    absolute_path = File.join(path_base, rdoc_document.full_name)

    model = Shomen::Model::Document.new

    model.path   = rdoc_document.full_name
    model.name   = File.basename(absolute_path)
    model.mtime  = File.mtime(absolute_path)
    model.text   = File.read(absolute_path) #file.comment
    model.format = mime_type(absolute_path)

    @table['/'+model.path] = model.to_h
  end
end

#generate_metadataObject (protected)



170
171
172
173
# File 'lib/shomen/rdoc.rb', line 170

def 
   = Shomen::Metadata.new
  @table['(metadata)'] = .to_h
end

#generate_methodsObject (protected)

Transform RDoc methods to Shomen model and add to table.

TODO: How to get literal interface separate from call-sequnces?



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
# File 'lib/shomen/rdoc.rb', line 244

def generate_methods
  debug_msg "Generating method documentation:"

  list = methods_all + attributes_all

  list.each do |rdoc_method|
    #debug_msg "%s" % [rdoc_method.full_name]

    #full_name  = method_name(m)
    #'prettyname'   => m.pretty_name,
    #'type'         => m.type, # class or instance

    model = Shomen::Model::Method.new

    model.path        = method_name(rdoc_method)
    model.name        = rdoc_method.name
    model.namespace   = rdoc_method.parent_name
    model.comment     = rdoc_method.comment
    model.format      = 'rdoc'
    model.aliases     = rdoc_method.aliases.map{ |a| method_name(a) }
    model.alias_for   = method_name(rdoc_method.is_alias_for)
    model.singleton   = rdoc_method.singleton

    model.declarations << rdoc_method.type.to_s #singleton ? 'class' : 'instance'
    model.declarations << rdoc_method.visibility.to_s

    model.interfaces = []
    if rdoc_method.call_seq
      rdoc_method.call_seq.split("\n").each do |cs|
        cs = cs.to_s.strip
        model.interfaces << parse_interface(cs) unless cs == ''
      end
    end
    model.interfaces << parse_interface("#{rdoc_method.name}#{rdoc_method.params}")

    model.returns    = []  # RDoc doesn't support specifying return values
    model.file       = '/'+rdoc_method.source_code_location.first
    model.line       = rdoc_method.source_code_location.last.to_i
    model.source     = rdoc_method.source_code_raw

    if rdoc_method.respond_to?(:c_function)
      model.language = rdoc_method.c_function ? 'c' : 'ruby'
    else
      model.language = 'ruby'
    end

    @table[model.path] = model.to_h
  end
end

#generate_scriptsObject (protected)

Generate script entries.



399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
# File 'lib/shomen/rdoc.rb', line 399

def generate_scripts
  #debug_msg "Generating file documentation in #{path_output_relative}:"
  #templatefile = self.path_template + 'file.rhtml'

  files.each do |rdoc_file|
    debug_msg "%s" % [rdoc_file.full_name]

    absolute_path = File.join(path_base, rdoc_file.full_name)
    #rel_prefix  = self.path_output.relative_path_from(outfile.dirname)

    model = Shomen::Model::Script.new

    model.path      = rdoc_file.full_name
    model.name      = File.basename(rdoc_file.full_name)
    model.mtime     = File.mtime(absolute_path)

    if Shomen.source?
      model.source    = File.read(absolute_path) #file.comment
      model.language  = mime_type(absolute_path)
    end

    #model.header   =
    #model.footer   =
    model.requires  = rdoc_file.requires.map{ |r| r.name }
    model.constants = rdoc_file.constants.map{ |c| c.full_name }

    # note that this utilizes the table we are building
    # so it needs to be the last thing done.
    @table.each do |k, h|
      case h['!']
      when 'module'
        model.modules ||= []
        model.modules << k if h['files'].include?(rdoc_file.full_name)
      when 'class'
        model.classes ||= []
        model.classes << k if h['files'].include?(rdoc_file.full_name)
      when 'method'
        model.methods ||= []
        model.methods << k if h['file'] == rdoc_file.full_name
      when 'class-method'
        model.class_methods ||= []
        model.class_methods << k if h['file'] == rdoc_file.full_name
      end
    end

    @table['/'+model.path] = model.to_h
  end
end

#method_name(method) ⇒ Object (protected)



484
485
486
487
488
489
490
491
492
# File 'lib/shomen/rdoc.rb', line 484

def method_name(method)
  return nil if method.nil?
  if method.singleton
    i = method.full_name.rindex('::')     
    method.full_name[0...i] + '.' + method.full_name[i+2..-1]
  else
    method.full_name
  end
end

#methods_allObject

List of all methods in all classes and modules.



84
85
86
# File 'lib/shomen/rdoc.rb', line 84

def methods_all
  @methods_all ||= classes.map{ |m| m.method_list }.flatten.sort
end

#mime_type(path) ⇒ Object (protected)



495
496
497
498
499
500
501
502
503
# File 'lib/shomen/rdoc.rb', line 495

def mime_type(path)
  case File.extname(path)
  when '.rb', '.rbx' then 'text/ruby'
  when '.c' then 'text/c-source'
  when '.rdoc' then 'text/rdoc'
  when '.md', '.markdown' then 'text/markdown'
  else 'text/plain'
  end
end

#parse_interface(interface) ⇒ Object (private)

Parse method interface.

TODO: remove any trailing comment too



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
# File 'lib/shomen/rdoc.rb', line 349

def parse_interface(interface)
  args, block = [], {}

  interface, returns = interface.split(/[=-]\>/)
  interface = interface.strip
  if i = interface.index(/\)\s*\{/)
    block['signature'] = interface[i+1..-1].strip
    interface = interface[0..i].strip
  end

  arguments = interface.strip.sub(/^.*?\(/,'').chomp(')')
  arguments = arguments.split(/\s*\,\s*/)
  arguments.each do |a|
    if a.start_with?('&')
      block['name'] = a
    else
      n,v = a.split('=')
      args << (v ? {'name'=>n,'default'=>v} : {'name'=>n})
    end
  end

  result = {}
  result['signature'] = interface
  result['arguments'] = args
  result['block']     = block unless block.empty?
  result['returns']   = returns.strip if returns
  return result
end

#path_output_relative(path = nil) ⇒ Object (protected)



161
162
163
164
165
166
167
# File 'lib/shomen/rdoc.rb', line 161

def path_output_relative(path=nil)
  if path
    path.to_s.sub(path_base.to_s+'/', '')
  else
    @path_output_relative ||= path_output.to_s.sub(path_base.to_s+'/', '')
  end
end

#shomenObject

TODO: Rename ?



110
111
112
# File 'lib/shomen/rdoc.rb', line 110

def shomen
  @table || {}
end