Class: RDoc::Generator::Babel

Inherits:
Object
  • Object
show all
Includes:
ERB::Util
Defined in:
lib/rdoc/generator/babel.rb

Overview

Babel RDoc HTML Generator. (Initially a variation of Darkfish.)

Defined Under Namespace

Modules: Options

Constant Summary collapse

VERSION =
'1.6.0'
DESCRIPTION =
'Alternate HTML documentation'
TEMPLATE_ROOT =

Directory containing babel templates. Each template is in a subdirectory named after the template.

Pathname.new(__FILE__).expand_path.dirname

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(store, options) ⇒ Babel

Saves the options, makes sure the template is found.



154
155
156
157
158
159
160
161
162
163
164
# File 'lib/rdoc/generator/babel.rb', line 154

def initialize(store, options)
  @store = store
  @options = options
  @babel_options = options.babel_options
  @see_standard_ancestors = @babel_options[:see_standard_ancestors]
  RDoc::AnyMethod.add_line_numbers = options.line_numbers if RDoc::AnyMethod.respond_to? :add_line_numbers
  @options.template = 'ruby-lang' if @options.template == 'babel' # TODO leave template nil
  @template_dir = TEMPLATE_ROOT + @options.template
  @template_dir.directory? or raise RDoc::Error, "template not found: '#@template_dir'"
  @source_dir = Pathname.pwd.expand_path
end

Class Method Details

.setup_options(rdoc_options) ⇒ Object

Add Babel options to RDoc standard options.



90
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
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
# File 'lib/rdoc/generator/babel.rb', line 90

def setup_options(rdoc_options)

  options = {
    :stylesheet_url => nil,
    #:index_attributes => false,
    #:ancestor_lists => false,
    #:list_standard_ancestors => false,
    :see_standard_ancestors => false,
  }

  rdoc_options.extend Options         # 1. extend the existing object
  rdoc_options.class.include Options  # 2. make sure #babel_options will be there on #dup'ed objects
  rdoc_options.babel_options = options

  opt = rdoc_options.option_parser

  opt.separator "Babel options:"
  opt.separator nil

  opt.on('--style=URL', '-s',
      'Specifies the URL of a stylesheet',
      'that the template should use.',
      'The default is "rdoc.css".') do |value|
    options[:stylesheet_url] = value
  end
  opt.separator nil

=begin
  opt.on('--index-attributes',
      'Include attributes in the method index.',
      'By default, only methods are included.') do |value|
    options[:index_attributes] = true
  end
  opt.separator nil

  opt.on('--ancestor-lists',
      'Add lists of ancestor methods, attributes,',
      'aliases and constants in the documentation',
      'of a class/module.') do |value|
    options[:ancestor_lists] = true
  end
  opt.separator nil

  opt.on('--list-standard-ancestors',
      'Include Kernel/Object methods',
      'in ancestor methods.') do |value|
    options[:list_standard_ancestors] = true
  end
  opt.separator nil
=end

  opt.on('--see-standard-ancestors',
      'Add links to Kernel/Object',
      'ancestor methods.') do |value|
    options[:see_standard_ancestors] = true
  end
  opt.separator nil

end

Instance Method Details

#class_dirObject

Directory for generated class/module files. Used by RDoc::ClassModule to build paths.



169
170
171
# File 'lib/rdoc/generator/babel.rb', line 169

def class_dir
  'classes'
end

#decorated_call_seq(method, method_name_class) ⇒ Object

Decorates a call-seq to highlight the method name. The output will have the method name inside HTML span tags with CSS class method_name_class.



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
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
# File 'lib/rdoc/generator/babel.rb', line 401

def decorated_call_seq(method, method_name_class)

  # empty call_seq (RDoc bug)
  if method.call_seq.strip.empty?
    warn "call-seq empty: #{method.parent.full_name}#{method.name_prefix}#{method.name}"
    return ''
  end

  # I assume it is safe to use \001 \002 \003 \004 to escape & < > "
  text = method.call_seq.strip.gsub(/->/, "\001rarr;")
  ospan = "\002span class=\004#{method_name_class}\004\003"
  cspan = "\002/span\003"

  # the "or warn" below fire when the alias is not in the call-seq:
  # ARGF#inspect: nope 1
  # Array#size: nope 1
  # BasicObject#singleton_method_removed: nope 1
  # BasicObject#singleton_method_undefined: nope 1
  # Complex#real?: nope 1
  # Enumerator::ArithmeticSequence#===: nope 3.2
  # Enumerator::ArithmeticSequence#eql?: nope 1
  # Enumerator::Lazy#_enumerable_drop: nope 1
  # Enumerator::Lazy#_enumerable_drop_while: nope 1
  # Enumerator::Lazy#_enumerable_filter_map: nope 1
  # Enumerator::Lazy#_enumerable_flat_map: nope 1
  # Enumerator::Lazy#_enumerable_grep: nope 1
  # Enumerator::Lazy#_enumerable_grep_v: nope 1
  # Enumerator::Lazy#_enumerable_map: nope 1
  # Enumerator::Lazy#_enumerable_reject: nope 1
  # Enumerator::Lazy#_enumerable_select: nope 1
  # Enumerator::Lazy#_enumerable_take: nope 1
  # Enumerator::Lazy#_enumerable_take_while: nope 1
  # Enumerator::Lazy#_enumerable_uniq: nope 1
  # Enumerator::Lazy#_enumerable_zip: nope 1
  # Enumerator::Lazy#_enumerable_with_index: nope 1
  # FalseClass#inspect: nope 1
  # Fiber#inspect: nope 1
  # File::empty?: nope 1
  # File::Stat#size?: nope 1
  # FileTest#empty?: nope 1
  # Float#===: nope 3.2
  # Float#inspect: nope 1
  # Hash#initialize_copy: nope 1
  # Integer#===: nope 3.2
  # Integer#inspect: nope 1
  # Method#===: nope 3.2
  # Module#extended: nope 1
  # Module#inspect: nope 1
  # Module#method_added: nope 1
  # Module#method_removed: nope 1
  # Module#method_undefined: nope 1
  # Module#prepended: nope 1
  # Numeric#%: nope 3.2
  # Proc#===: nope 3.2
  # Proc#inspect: nope 1
  # Process::Sys::getegid: nope 1
  # String#initialize_copy: nope 1
  # Struct#deconstruct: nope 1
  # Symbol#===: nope 3.2
  # Symbol#next: nope 1
  # Thread#inspect: nope 1
  # TrueClass#inspect: nope 1

  if method.name =~ /^\w/
    # look for things like 'IO.open' or 'open' at the beginning of lines
    name = Regexp.escape(method.name.sub(/=$/, ''))
    re = /^(\s*)([:\w]+\.)?(#{name})/
    text.gsub!(re, "\\1\\2#{ospan}\\3#{cspan}") #or warn "#{method.parent.full_name}#{method.name_prefix}#{method.name}: nope 1"
  elsif method.name =~ /\[\]=?/
    # [] and []=
    re = /[\[\]]/
    sub_hash = { '[' => "#{ospan}[#{cspan}", ']' => "#{ospan}]#{cspan}" }
    text.gsub!(re, sub_hash) #or warn "#{method.parent.full_name}#{method.name_prefix}#{method.name}: nope 2"
  else
    # operators:
    # +(unary) -(unary)  ~  !
    # **
    # *  /  %
    # +  -
    # <<  >>
    # &
    # |  ^
    # >  >=  <  <=
    # <=>  ==  ===  =~
    # !=  `  !~
    case method.name
    # -(unary) +(unary)
    when '+@', '-@'
      re = /^(\s*)(#{Regexp.escape(method.name[0])})/
      text.gsub!(re, "\\1#{ospan}\\2#{cspan}") #or warn "#{method.parent.full_name}#{method.name_prefix}#{method.name}: nope 3.1"
    else
      re = /^(.*?)(#{Regexp.escape(method.name)})/
      text.gsub!(re, "\\1#{ospan}\\2#{cspan}") #or warn "#{method.parent.full_name}#{method.name_prefix}#{method.name}: nope 3.2"
    end
  end

  h(text).tr("\001\002\003\004", '&<>"').gsub("\n", '<br/>')
end

#description(method) ⇒ Object

Returns the HTML for the description of a method, with alias information and link to ancestor method/attribute.



373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
# File 'lib/rdoc/generator/babel.rb', line 373

def description(method)
  desc = method.documented? ? method.description.strip : +''
  if method.is_alias_for
    text = method.is_alias_for.documented? ? +'<p>' : +'<p class="nodoc">'
    text << 'Alias for ' << link(method, method.is_alias_for) << '</p>'
    append_with_nl desc, text
  end
  if method.see &&
      (@see_standard_ancestors ||
       method.see.parent.full_name !~ /^(Object|Kernel)$/)
    text = method.see.documented? ? +'<p>' : +'<p class="nodoc">'
    text << (desc.empty? ? 'See ' : 'See also ')
    text << link(method, method.see) << '</p>'
    append_with_nl desc, text
  end
  desc = '<p class="nodoc">(not documented)</p>' if desc.empty?
  unless method.aliases.empty?
    text = +'<p>Also aliased as '
    text << method.aliases.map { |a| link(method, a) }.join(', ') << '</p>'
    append_with_nl desc, text
  end
  desc
end

#file_dirObject

Directory for generated TopLevel files. Used by RDoc::TopLevel to build paths.



176
177
178
# File 'lib/rdoc/generator/babel.rb', line 176

def file_dir
  'files'
end

#first_pageObject

Returns the first Context object to display in the main frame. This is, in order:

  • The file designated by the main_page option, if there is a TopLevel or class/module with that name.
  • The first simple file that contains a comment (in the order given on the command line).
  • The first class or module that contains a comment.
  • The first file that contains a comment (in the order given on the command line).
  • The first class or module that has any kind of content.
  • The first class or module.
  • The first file.


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
# File 'lib/rdoc/generator/babel.rb', line 341

def first_page
  if @main_page
    debug_msg "main as --main: #{@main_page}"
    @main_page
  elsif (file = @simple_files.first)
    debug_msg "main as first simple file: #{file}"
    file
  elsif (cm = @unique_classes_and_modules.find { |k| !k.comment.empty? })
    debug_msg "main as first module/class with comment: #{cm}"
    cm
  # this is not a simple file, so currently not displayed in files
  # elsif (file = @files_with_comment.first)
  #   debug_msg "main as first file with comment: #{file}"
  #   file
  elsif !@unique_classes_and_modules.empty?
    cm = @unique_classes_and_modules.find { |k| k.any_content }
    if cm
      debug_msg "main as first module/class with any content: #{cm}"
    else
      debug_msg "main as first module/class (no content): #{cm}"
      cm = @unique_classes_and_modules.first
    end
    cm
  else
    debug_msg "main as first file: #{cm}"
    @all_files.first
  end
end

#generateObject

Generates the documentation.



182
183
184
185
186
187
188
189
190
191
192
193
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
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
# File 'lib/rdoc/generator/babel.rb', line 182

def generate

  # rdoc bug fix: public class/module methods appear as external_aliases (unresolved aliases)

  @store.all_classes_and_modules.each do |cm|
    next if cm.external_aliases.empty?
    class_level_names = cm.method_list.select { |m| m.type == 'class' }.map(&:name)
    cm.external_aliases.dup.each do |a|
      cm.external_aliases.delete a if class_level_names.include?(a.name)
    end
  end

  # other bug fix: method aliases with singleton mismatch

  @store.all_classes_and_modules.each do |cm|
    cm.class_method_list.each do |m|
      m.is_alias_for.singleton = true if m.is_alias_for
      m.aliases.each { |a| a.singleton = true }
    end
  end

  # set instance variables

  @output_dir = Pathname.new(@options.op_dir).expand_path(@source_dir)
  @all_classes_and_modules = @store.all_classes_and_modules.sort
  @unique_classes_and_modules = @store.unique_classes_and_modules.sort

  @all_methods = @unique_classes_and_modules.
    inject([]) { |a,m| a.concat m.method_list }.
    sort { |a,b| [a, a.parent_name] <=> [b, b.parent_name] }

  @all_files = @store.all_files # not sorted: keep command line order
  @files_with_comment = @all_files.reject { |f| f.comment.empty? }
  @simple_files = @files_with_comment.select { |f| f.parser == RDoc::Parser::Simple || f.parser == RDoc::Parser::Markdown }

  @files_to_display =
    if @unique_classes_and_modules.empty?
      @all_files
    else
      @simple_files
    end

  if @options.main_page
    # look for a TopLevel matching the main page
    @main_page = @all_files.find { |f| f.full_name == @options.main_page }
    if @main_page
      # TODO are there cases where main_page = 'README' for 'lib/README'?
      unless @files_to_display.find { |f| f.full_name == @options.main_page }
        @files_to_display.unshift @main_page
      end
    # look for a class/module with that name
    else
      match = @unique_classes_and_modules.find { |cm| cm.full_name == @options.main_page }
      if match
        @main_page = match
      else
        warn "no such page: --main #{@options.main_page}"
      end
    end
  else
    @main_page = nil
  end

  # write the output

  write_static_files
  generate_indexes
  generate_class_and_module_files
  generate_file_files

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

#generate_class_and_module_filesObject

Generates a documentation page for each class.



296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
# File 'lib/rdoc/generator/babel.rb', line 296

def generate_class_and_module_files
  template_file = @template_dir + 'class-page.html.erb'
  debug_msg "Generating class documentation"
  @unique_classes_and_modules.each do |klass|
    debug_msg "  %s %s" % [klass.type, klass.full_name]
    outfile = @output_dir + klass.path
    @class = klass
    @sections = klass.sections
    if @sections.size > 1
      anonymous_section = @sections.find { |s| !s.title }
      if anonymous_section
        @sections.delete anonymous_section
        @sections << anonymous_section
      end
    end
    self.render_template(template_file, binding(), outfile)
  end
end

#generate_file_filesObject

Generates a documentation page for each file.



317
318
319
320
321
322
323
324
325
326
# File 'lib/rdoc/generator/babel.rb', line 317

def generate_file_files
  template_file = @template_dir + 'file-page.html.erb'
  debug_msg "Generating file documentation"
  @all_files.each do |file|
    debug_msg "  file #{file.path}"
    outfile = @output_dir + file.path
    @file = file
    self.render_template(template_file, binding(), outfile)
  end
end

#generate_index(basename) ⇒ Object

Generates an index page.



287
288
289
290
291
292
# File 'lib/rdoc/generator/babel.rb', line 287

def generate_index(basename)
  debug_msg "Generating index #{basename}.html"
  template_file = @template_dir + "#{basename}.html.erb"
  outfile = @output_dir + "#{basename}.html"
  render_template(template_file, binding(), outfile)
end

#generate_indexesObject

Generates index.html and indexes.html.



279
280
281
282
283
# File 'lib/rdoc/generator/babel.rb', line 279

def generate_indexes
  @first_page = first_page
  generate_index('index')
  generate_index('indexes')
end

#section_methods_hash(methods) ⇒ Object

Returns a Hash { section => [methods] } for the current class methods methods.



502
503
504
505
506
507
508
509
# File 'lib/rdoc/generator/babel.rb', line 502

def section_methods_hash(methods)
  h = methods.group_by(&:section)

  @sections.to_h do |section|
    [section, h[section]]
  end
  .compact
end

#write_static_filesObject

Copy static files to the output directory. Static files are all files in the template directory that do not have the .erb extension.



261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
# File 'lib/rdoc/generator/babel.rb', line 261

def write_static_files
  debug_msg "Copying static files"
  options = { :verbose => $DEBUG_RDOC, :noop => @options.dry_run }
  static_files = Pathname.
    glob(@template_dir.to_s + '/**/*').
    reject { |f| f.extname == '.erb' }
  static_files.sort.each do |source_path|
    out_path = @output_dir + source_path.relative_path_from(@template_dir)
    if source_path.directory?
      out_path.mkpath unless @options.dry_run
    else
      FileUtils.cp source_path.to_s, out_path.dirname.to_s, **options
    end
  end
end