PageRenderTime 46ms CodeModel.GetById 10ms RepoModel.GetById 0ms app.codeStats 0ms

/Util/IronRuby/lib/ruby/1.8/rdoc/generators/html_generator.rb

http://github.com/IronLanguages/main
Ruby | 1509 lines | 1007 code | 284 blank | 218 comment | 112 complexity | 1247e5f863f903aa964ddcfc8dd3de4f MD5 | raw file
Possible License(s): CPL-1.0, BSD-3-Clause, ISC, GPL-2.0, MPL-2.0-no-copyleft-exception
  1. # We're responsible for generating all the HTML files
  2. # from the object tree defined in code_objects.rb. We
  3. # generate:
  4. #
  5. # [files] an html file for each input file given. These
  6. # input files appear as objects of class
  7. # TopLevel
  8. #
  9. # [classes] an html file for each class or module encountered.
  10. # These classes are not grouped by file: if a file
  11. # contains four classes, we'll generate an html
  12. # file for the file itself, and four html files
  13. # for the individual classes.
  14. #
  15. # [indices] we generate three indices for files, classes,
  16. # and methods. These are displayed in a browser
  17. # like window with three index panes across the
  18. # top and the selected description below
  19. #
  20. # Method descriptions appear in whatever entity (file, class,
  21. # or module) that contains them.
  22. #
  23. # We generate files in a structure below a specified subdirectory,
  24. # normally +doc+.
  25. #
  26. # opdir
  27. # |
  28. # |___ files
  29. # | |__ per file summaries
  30. # |
  31. # |___ classes
  32. # |__ per class/module descriptions
  33. #
  34. # HTML is generated using the Template class.
  35. #
  36. require 'ftools'
  37. require 'rdoc/options'
  38. require 'rdoc/template'
  39. require 'rdoc/markup/simple_markup'
  40. require 'rdoc/markup/simple_markup/to_html'
  41. require 'cgi'
  42. module Generators
  43. # Name of sub-direcories that hold file and class/module descriptions
  44. FILE_DIR = "files"
  45. CLASS_DIR = "classes"
  46. CSS_NAME = "rdoc-style.css"
  47. ##
  48. # Build a hash of all items that can be cross-referenced.
  49. # This is used when we output required and included names:
  50. # if the names appear in this hash, we can generate
  51. # an html cross reference to the appropriate description.
  52. # We also use this when parsing comment blocks: any decorated
  53. # words matching an entry in this list are hyperlinked.
  54. class AllReferences
  55. @@refs = {}
  56. def AllReferences::reset
  57. @@refs = {}
  58. end
  59. def AllReferences.add(name, html_class)
  60. @@refs[name] = html_class
  61. end
  62. def AllReferences.[](name)
  63. @@refs[name]
  64. end
  65. def AllReferences.keys
  66. @@refs.keys
  67. end
  68. end
  69. ##
  70. # Subclass of the SM::ToHtml class that supports looking
  71. # up words in the AllReferences list. Those that are
  72. # found (like AllReferences in this comment) will
  73. # be hyperlinked
  74. class HyperlinkHtml < SM::ToHtml
  75. # We need to record the html path of our caller so we can generate
  76. # correct relative paths for any hyperlinks that we find
  77. def initialize(from_path, context)
  78. super()
  79. @from_path = from_path
  80. @parent_name = context.parent_name
  81. @parent_name += "::" if @parent_name
  82. @context = context
  83. end
  84. # We're invoked when any text matches the CROSSREF pattern
  85. # (defined in MarkUp). If we fine the corresponding reference,
  86. # generate a hyperlink. If the name we're looking for contains
  87. # no punctuation, we look for it up the module/class chain. For
  88. # example, HyperlinkHtml is found, even without the Generators::
  89. # prefix, because we look for it in module Generators first.
  90. def handle_special_CROSSREF(special)
  91. name = special.text
  92. if name[0,1] == '#'
  93. lookup = name[1..-1]
  94. name = lookup unless Options.instance.show_hash
  95. else
  96. lookup = name
  97. end
  98. # Find class, module, or method in class or module.
  99. if /([A-Z]\w*)[.\#](\w+[!?=]?)/ =~ lookup
  100. container = $1
  101. method = $2
  102. ref = @context.find_symbol(container, method)
  103. elsif /([A-Za-z]\w*)[.\#](\w+(\([\.\w+\*\/\+\-\=\<\>]+\))?)/ =~ lookup
  104. container = $1
  105. method = $2
  106. ref = @context.find_symbol(container, method)
  107. else
  108. ref = @context.find_symbol(lookup)
  109. end
  110. if ref and ref.document_self
  111. "<a href=\"#{ref.as_href(@from_path)}\">#{name}</a>"
  112. else
  113. name
  114. end
  115. end
  116. # Generate a hyperlink for url, labeled with text. Handle the
  117. # special cases for img: and link: described under handle_special_HYPEDLINK
  118. def gen_url(url, text)
  119. if url =~ /([A-Za-z]+):(.*)/
  120. type = $1
  121. path = $2
  122. else
  123. type = "http"
  124. path = url
  125. url = "http://#{url}"
  126. end
  127. if type == "link"
  128. if path[0,1] == '#' # is this meaningful?
  129. url = path
  130. else
  131. url = HTMLGenerator.gen_url(@from_path, path)
  132. end
  133. end
  134. if (type == "http" || type == "link") &&
  135. url =~ /\.(gif|png|jpg|jpeg|bmp)$/
  136. "<img src=\"#{url}\" />"
  137. else
  138. "<a href=\"#{url}\">#{text.sub(%r{^#{type}:/*}, '')}</a>"
  139. end
  140. end
  141. # And we're invoked with a potential external hyperlink mailto:
  142. # just gets inserted. http: links are checked to see if they
  143. # reference an image. If so, that image gets inserted using an
  144. # <img> tag. Otherwise a conventional <a href> is used. We also
  145. # support a special type of hyperlink, link:, which is a reference
  146. # to a local file whose path is relative to the --op directory.
  147. def handle_special_HYPERLINK(special)
  148. url = special.text
  149. gen_url(url, url)
  150. end
  151. # HEre's a hypedlink where the label is different to the URL
  152. # <label>[url]
  153. #
  154. def handle_special_TIDYLINK(special)
  155. text = special.text
  156. # unless text =~ /(\S+)\[(.*?)\]/
  157. unless text =~ /\{(.*?)\}\[(.*?)\]/ or text =~ /(\S+)\[(.*?)\]/
  158. return text
  159. end
  160. label = $1
  161. url = $2
  162. gen_url(url, label)
  163. end
  164. end
  165. #####################################################################
  166. #
  167. # Handle common markup tasks for the various Html classes
  168. #
  169. module MarkUp
  170. # Convert a string in markup format into HTML. We keep a cached
  171. # SimpleMarkup object lying around after the first time we're
  172. # called per object.
  173. def markup(str, remove_para=false)
  174. return '' unless str
  175. unless defined? @markup
  176. @markup = SM::SimpleMarkup.new
  177. # class names, variable names, or instance variables
  178. @markup.add_special(/(
  179. \w+(::\w+)*[.\#]\w+(\([\.\w+\*\/\+\-\=\<\>]+\))? # A::B.meth(**) (for operator in Fortran95)
  180. | \#\w+(\([.\w\*\/\+\-\=\<\>]+\))? # meth(**) (for operator in Fortran95)
  181. | \b([A-Z]\w*(::\w+)*[.\#]\w+) # A::B.meth
  182. | \b([A-Z]\w+(::\w+)*) # A::B..
  183. | \#\w+[!?=]? # #meth_name
  184. | \b\w+([_\/\.]+\w+)*[!?=]? # meth_name
  185. )/x,
  186. :CROSSREF)
  187. # external hyperlinks
  188. @markup.add_special(/((link:|https?:|mailto:|ftp:|www\.)\S+\w)/, :HYPERLINK)
  189. # and links of the form <text>[<url>]
  190. @markup.add_special(/(((\{.*?\})|\b\S+?)\[\S+?\.\S+?\])/, :TIDYLINK)
  191. # @markup.add_special(/\b(\S+?\[\S+?\.\S+?\])/, :TIDYLINK)
  192. end
  193. unless defined? @html_formatter
  194. @html_formatter = HyperlinkHtml.new(self.path, self)
  195. end
  196. # Convert leading comment markers to spaces, but only
  197. # if all non-blank lines have them
  198. if str =~ /^(?>\s*)[^\#]/
  199. content = str
  200. else
  201. content = str.gsub(/^\s*(#+)/) { $1.tr('#',' ') }
  202. end
  203. res = @markup.convert(content, @html_formatter)
  204. if remove_para
  205. res.sub!(/^<p>/, '')
  206. res.sub!(/<\/p>$/, '')
  207. end
  208. res
  209. end
  210. # Qualify a stylesheet URL; if if +css_name+ does not begin with '/' or
  211. # 'http[s]://', prepend a prefix relative to +path+. Otherwise, return it
  212. # unmodified.
  213. def style_url(path, css_name=nil)
  214. # $stderr.puts "style_url( #{path.inspect}, #{css_name.inspect} )"
  215. css_name ||= CSS_NAME
  216. if %r{^(https?:/)?/} =~ css_name
  217. return css_name
  218. else
  219. return HTMLGenerator.gen_url(path, css_name)
  220. end
  221. end
  222. # Build a webcvs URL with the given 'url' argument. URLs with a '%s' in them
  223. # get the file's path sprintfed into them; otherwise they're just catenated
  224. # together.
  225. def cvs_url(url, full_path)
  226. if /%s/ =~ url
  227. return sprintf( url, full_path )
  228. else
  229. return url + full_path
  230. end
  231. end
  232. end
  233. #####################################################################
  234. #
  235. # A Context is built by the parser to represent a container: contexts
  236. # hold classes, modules, methods, require lists and include lists.
  237. # ClassModule and TopLevel are the context objects we process here
  238. #
  239. class ContextUser
  240. include MarkUp
  241. attr_reader :context
  242. def initialize(context, options)
  243. @context = context
  244. @options = options
  245. end
  246. # convenience method to build a hyperlink
  247. def href(link, cls, name)
  248. %{<a href="#{link}" class="#{cls}">#{name}</a>} #"
  249. end
  250. # return a reference to outselves to be used as an href=
  251. # the form depends on whether we're all in one file
  252. # or in multiple files
  253. def as_href(from_path)
  254. if @options.all_one_file
  255. "#" + path
  256. else
  257. HTMLGenerator.gen_url(from_path, path)
  258. end
  259. end
  260. # Create a list of HtmlMethod objects for each method
  261. # in the corresponding context object. If the @options.show_all
  262. # variable is set (corresponding to the <tt>--all</tt> option,
  263. # we include all methods, otherwise just the public ones.
  264. def collect_methods
  265. list = @context.method_list
  266. unless @options.show_all
  267. list = list.find_all {|m| m.visibility == :public || m.visibility == :protected || m.force_documentation }
  268. end
  269. @methods = list.collect {|m| HtmlMethod.new(m, self, @options) }
  270. end
  271. # Build a summary list of all the methods in this context
  272. def build_method_summary_list(path_prefix="")
  273. collect_methods unless @methods
  274. meths = @methods.sort
  275. res = []
  276. meths.each do |meth|
  277. res << {
  278. "name" => CGI.escapeHTML(meth.name),
  279. "aref" => "#{path_prefix}\##{meth.aref}"
  280. }
  281. end
  282. res
  283. end
  284. # Build a list of aliases for which we couldn't find a
  285. # corresponding method
  286. def build_alias_summary_list(section)
  287. values = []
  288. @context.aliases.each do |al|
  289. next unless al.section == section
  290. res = {
  291. 'old_name' => al.old_name,
  292. 'new_name' => al.new_name,
  293. }
  294. if al.comment && !al.comment.empty?
  295. res['desc'] = markup(al.comment, true)
  296. end
  297. values << res
  298. end
  299. values
  300. end
  301. # Build a list of constants
  302. def build_constants_summary_list(section)
  303. values = []
  304. @context.constants.each do |co|
  305. next unless co.section == section
  306. res = {
  307. 'name' => co.name,
  308. 'value' => CGI.escapeHTML(co.value)
  309. }
  310. res['desc'] = markup(co.comment, true) if co.comment && !co.comment.empty?
  311. values << res
  312. end
  313. values
  314. end
  315. def build_requires_list(context)
  316. potentially_referenced_list(context.requires) {|fn| [fn + ".rb"] }
  317. end
  318. def build_include_list(context)
  319. potentially_referenced_list(context.includes)
  320. end
  321. # Build a list from an array of <i>Htmlxxx</i> items. Look up each
  322. # in the AllReferences hash: if we find a corresponding entry,
  323. # we generate a hyperlink to it, otherwise just output the name.
  324. # However, some names potentially need massaging. For example,
  325. # you may require a Ruby file without the .rb extension,
  326. # but the file names we know about may have it. To deal with
  327. # this, we pass in a block which performs the massaging,
  328. # returning an array of alternative names to match
  329. def potentially_referenced_list(array)
  330. res = []
  331. array.each do |i|
  332. ref = AllReferences[i.name]
  333. # if !ref
  334. # container = @context.parent
  335. # while !ref && container
  336. # name = container.name + "::" + i.name
  337. # ref = AllReferences[name]
  338. # container = container.parent
  339. # end
  340. # end
  341. ref = @context.find_symbol(i.name)
  342. ref = ref.viewer if ref
  343. if !ref && block_given?
  344. possibles = yield(i.name)
  345. while !ref and !possibles.empty?
  346. ref = AllReferences[possibles.shift]
  347. end
  348. end
  349. h_name = CGI.escapeHTML(i.name)
  350. if ref and ref.document_self
  351. path = url(ref.path)
  352. res << { "name" => h_name, "aref" => path }
  353. else
  354. res << { "name" => h_name }
  355. end
  356. end
  357. res
  358. end
  359. # Build an array of arrays of method details. The outer array has up
  360. # to six entries, public, private, and protected for both class
  361. # methods, the other for instance methods. The inner arrays contain
  362. # a hash for each method
  363. def build_method_detail_list(section)
  364. outer = []
  365. methods = @methods.sort
  366. for singleton in [true, false]
  367. for vis in [ :public, :protected, :private ]
  368. res = []
  369. methods.each do |m|
  370. if m.section == section and
  371. m.document_self and
  372. m.visibility == vis and
  373. m.singleton == singleton
  374. row = {}
  375. if m.call_seq
  376. row["callseq"] = m.call_seq.gsub(/->/, '&rarr;')
  377. else
  378. row["name"] = CGI.escapeHTML(m.name)
  379. row["params"] = m.params
  380. end
  381. desc = m.description.strip
  382. row["m_desc"] = desc unless desc.empty?
  383. row["aref"] = m.aref
  384. row["visibility"] = m.visibility.to_s
  385. alias_names = []
  386. m.aliases.each do |other|
  387. if other.viewer # won't be if the alias is private
  388. alias_names << {
  389. 'name' => other.name,
  390. 'aref' => other.viewer.as_href(path)
  391. }
  392. end
  393. end
  394. unless alias_names.empty?
  395. row["aka"] = alias_names
  396. end
  397. if @options.inline_source
  398. code = m.source_code
  399. row["sourcecode"] = code if code
  400. else
  401. code = m.src_url
  402. if code
  403. row["codeurl"] = code
  404. row["imgurl"] = m.img_url
  405. end
  406. end
  407. res << row
  408. end
  409. end
  410. if res.size > 0
  411. outer << {
  412. "type" => vis.to_s.capitalize,
  413. "category" => singleton ? "Class" : "Instance",
  414. "methods" => res
  415. }
  416. end
  417. end
  418. end
  419. outer
  420. end
  421. # Build the structured list of classes and modules contained
  422. # in this context.
  423. def build_class_list(level, from, section, infile=nil)
  424. res = ""
  425. prefix = "&nbsp;&nbsp;::" * level;
  426. from.modules.sort.each do |mod|
  427. next unless mod.section == section
  428. next if infile && !mod.defined_in?(infile)
  429. if mod.document_self
  430. res <<
  431. prefix <<
  432. "Module " <<
  433. href(url(mod.viewer.path), "link", mod.full_name) <<
  434. "<br />\n" <<
  435. build_class_list(level + 1, mod, section, infile)
  436. end
  437. end
  438. from.classes.sort.each do |cls|
  439. next unless cls.section == section
  440. next if infile && !cls.defined_in?(infile)
  441. if cls.document_self
  442. res <<
  443. prefix <<
  444. "Class " <<
  445. href(url(cls.viewer.path), "link", cls.full_name) <<
  446. "<br />\n" <<
  447. build_class_list(level + 1, cls, section, infile)
  448. end
  449. end
  450. res
  451. end
  452. def url(target)
  453. HTMLGenerator.gen_url(path, target)
  454. end
  455. def aref_to(target)
  456. if @options.all_one_file
  457. "#" + target
  458. else
  459. url(target)
  460. end
  461. end
  462. def document_self
  463. @context.document_self
  464. end
  465. def diagram_reference(diagram)
  466. res = diagram.gsub(/((?:src|href)=")(.*?)"/) {
  467. $1 + url($2) + '"'
  468. }
  469. res
  470. end
  471. # Find a symbol in ourselves or our parent
  472. def find_symbol(symbol, method=nil)
  473. res = @context.find_symbol(symbol, method)
  474. if res
  475. res = res.viewer
  476. end
  477. res
  478. end
  479. # create table of contents if we contain sections
  480. def add_table_of_sections
  481. toc = []
  482. @context.sections.each do |section|
  483. if section.title
  484. toc << {
  485. 'secname' => section.title,
  486. 'href' => section.sequence
  487. }
  488. end
  489. end
  490. @values['toc'] = toc unless toc.empty?
  491. end
  492. end
  493. #####################################################################
  494. #
  495. # Wrap a ClassModule context
  496. class HtmlClass < ContextUser
  497. attr_reader :path
  498. def initialize(context, html_file, prefix, options)
  499. super(context, options)
  500. @html_file = html_file
  501. @is_module = context.is_module?
  502. @values = {}
  503. context.viewer = self
  504. if options.all_one_file
  505. @path = context.full_name
  506. else
  507. @path = http_url(context.full_name, prefix)
  508. end
  509. collect_methods
  510. AllReferences.add(name, self)
  511. end
  512. # return the relative file name to store this class in,
  513. # which is also its url
  514. def http_url(full_name, prefix)
  515. path = full_name.dup
  516. if path['<<']
  517. path.gsub!(/<<\s*(\w*)/) { "from-#$1" }
  518. end
  519. File.join(prefix, path.split("::")) + ".html"
  520. end
  521. def name
  522. @context.full_name
  523. end
  524. def parent_name
  525. @context.parent.full_name
  526. end
  527. def index_name
  528. name
  529. end
  530. def write_on(f)
  531. value_hash
  532. template = TemplatePage.new(RDoc::Page::BODY,
  533. RDoc::Page::CLASS_PAGE,
  534. RDoc::Page::METHOD_LIST)
  535. template.write_html_on(f, @values)
  536. end
  537. def value_hash
  538. class_attribute_values
  539. add_table_of_sections
  540. @values["charset"] = @options.charset
  541. @values["style_url"] = style_url(path, @options.css)
  542. d = markup(@context.comment)
  543. @values["description"] = d unless d.empty?
  544. ml = build_method_summary_list
  545. @values["methods"] = ml unless ml.empty?
  546. il = build_include_list(@context)
  547. @values["includes"] = il unless il.empty?
  548. @values["sections"] = @context.sections.map do |section|
  549. secdata = {
  550. "sectitle" => section.title,
  551. "secsequence" => section.sequence,
  552. "seccomment" => markup(section.comment)
  553. }
  554. al = build_alias_summary_list(section)
  555. secdata["aliases"] = al unless al.empty?
  556. co = build_constants_summary_list(section)
  557. secdata["constants"] = co unless co.empty?
  558. al = build_attribute_list(section)
  559. secdata["attributes"] = al unless al.empty?
  560. cl = build_class_list(0, @context, section)
  561. secdata["classlist"] = cl unless cl.empty?
  562. mdl = build_method_detail_list(section)
  563. secdata["method_list"] = mdl unless mdl.empty?
  564. secdata
  565. end
  566. @values
  567. end
  568. def build_attribute_list(section)
  569. atts = @context.attributes.sort
  570. res = []
  571. atts.each do |att|
  572. next unless att.section == section
  573. if att.visibility == :public || att.visibility == :protected || @options.show_all
  574. entry = {
  575. "name" => CGI.escapeHTML(att.name),
  576. "rw" => att.rw,
  577. "a_desc" => markup(att.comment, true)
  578. }
  579. unless att.visibility == :public || att.visibility == :protected
  580. entry["rw"] << "-"
  581. end
  582. res << entry
  583. end
  584. end
  585. res
  586. end
  587. def class_attribute_values
  588. h_name = CGI.escapeHTML(name)
  589. @values["classmod"] = @is_module ? "Module" : "Class"
  590. @values["title"] = "#{@values['classmod']}: #{h_name}"
  591. c = @context
  592. c = c.parent while c and !c.diagram
  593. if c && c.diagram
  594. @values["diagram"] = diagram_reference(c.diagram)
  595. end
  596. @values["full_name"] = h_name
  597. parent_class = @context.superclass
  598. if parent_class
  599. @values["parent"] = CGI.escapeHTML(parent_class)
  600. if parent_name
  601. lookup = parent_name + "::" + parent_class
  602. else
  603. lookup = parent_class
  604. end
  605. parent_url = AllReferences[lookup] || AllReferences[parent_class]
  606. if parent_url and parent_url.document_self
  607. @values["par_url"] = aref_to(parent_url.path)
  608. end
  609. end
  610. files = []
  611. @context.in_files.each do |f|
  612. res = {}
  613. full_path = CGI.escapeHTML(f.file_absolute_name)
  614. res["full_path"] = full_path
  615. res["full_path_url"] = aref_to(f.viewer.path) if f.document_self
  616. if @options.webcvs
  617. res["cvsurl"] = cvs_url( @options.webcvs, full_path )
  618. end
  619. files << res
  620. end
  621. @values['infiles'] = files
  622. end
  623. def <=>(other)
  624. self.name <=> other.name
  625. end
  626. end
  627. #####################################################################
  628. #
  629. # Handles the mapping of a file's information to HTML. In reality,
  630. # a file corresponds to a +TopLevel+ object, containing modules,
  631. # classes, and top-level methods. In theory it _could_ contain
  632. # attributes and aliases, but we ignore these for now.
  633. class HtmlFile < ContextUser
  634. attr_reader :path
  635. attr_reader :name
  636. def initialize(context, options, file_dir)
  637. super(context, options)
  638. @values = {}
  639. if options.all_one_file
  640. @path = filename_to_label
  641. else
  642. @path = http_url(file_dir)
  643. end
  644. @name = @context.file_relative_name
  645. collect_methods
  646. AllReferences.add(name, self)
  647. context.viewer = self
  648. end
  649. def http_url(file_dir)
  650. File.join(file_dir, @context.file_relative_name.tr('.', '_')) +
  651. ".html"
  652. end
  653. def filename_to_label
  654. @context.file_relative_name.gsub(/%|\/|\?|\#/) {|s| '%' + ("%x" % s[0]) }
  655. end
  656. def index_name
  657. name
  658. end
  659. def parent_name
  660. nil
  661. end
  662. def value_hash
  663. file_attribute_values
  664. add_table_of_sections
  665. @values["charset"] = @options.charset
  666. @values["href"] = path
  667. @values["style_url"] = style_url(path, @options.css)
  668. if @context.comment
  669. d = markup(@context.comment)
  670. @values["description"] = d if d.size > 0
  671. end
  672. ml = build_method_summary_list
  673. @values["methods"] = ml unless ml.empty?
  674. il = build_include_list(@context)
  675. @values["includes"] = il unless il.empty?
  676. rl = build_requires_list(@context)
  677. @values["requires"] = rl unless rl.empty?
  678. if @options.promiscuous
  679. file_context = nil
  680. else
  681. file_context = @context
  682. end
  683. @values["sections"] = @context.sections.map do |section|
  684. secdata = {
  685. "sectitle" => section.title,
  686. "secsequence" => section.sequence,
  687. "seccomment" => markup(section.comment)
  688. }
  689. cl = build_class_list(0, @context, section, file_context)
  690. @values["classlist"] = cl unless cl.empty?
  691. mdl = build_method_detail_list(section)
  692. secdata["method_list"] = mdl unless mdl.empty?
  693. al = build_alias_summary_list(section)
  694. secdata["aliases"] = al unless al.empty?
  695. co = build_constants_summary_list(section)
  696. @values["constants"] = co unless co.empty?
  697. secdata
  698. end
  699. @values
  700. end
  701. def write_on(f)
  702. value_hash
  703. template = TemplatePage.new(RDoc::Page::BODY,
  704. RDoc::Page::FILE_PAGE,
  705. RDoc::Page::METHOD_LIST)
  706. template.write_html_on(f, @values)
  707. end
  708. def file_attribute_values
  709. full_path = @context.file_absolute_name
  710. short_name = File.basename(full_path)
  711. @values["title"] = CGI.escapeHTML("File: #{short_name}")
  712. if @context.diagram
  713. @values["diagram"] = diagram_reference(@context.diagram)
  714. end
  715. @values["short_name"] = CGI.escapeHTML(short_name)
  716. @values["full_path"] = CGI.escapeHTML(full_path)
  717. @values["dtm_modified"] = @context.file_stat.mtime.to_s
  718. if @options.webcvs
  719. @values["cvsurl"] = cvs_url( @options.webcvs, @values["full_path"] )
  720. end
  721. end
  722. def <=>(other)
  723. self.name <=> other.name
  724. end
  725. end
  726. #####################################################################
  727. class HtmlMethod
  728. include MarkUp
  729. attr_reader :context
  730. attr_reader :src_url
  731. attr_reader :img_url
  732. attr_reader :source_code
  733. @@seq = "M000000"
  734. @@all_methods = []
  735. def HtmlMethod::reset
  736. @@all_methods = []
  737. end
  738. def initialize(context, html_class, options)
  739. @context = context
  740. @html_class = html_class
  741. @options = options
  742. @@seq = @@seq.succ
  743. @seq = @@seq
  744. @@all_methods << self
  745. context.viewer = self
  746. if (ts = @context.token_stream)
  747. @source_code = markup_code(ts)
  748. unless @options.inline_source
  749. @src_url = create_source_code_file(@source_code)
  750. @img_url = HTMLGenerator.gen_url(path, 'source.png')
  751. end
  752. end
  753. AllReferences.add(name, self)
  754. end
  755. # return a reference to outselves to be used as an href=
  756. # the form depends on whether we're all in one file
  757. # or in multiple files
  758. def as_href(from_path)
  759. if @options.all_one_file
  760. "#" + path
  761. else
  762. HTMLGenerator.gen_url(from_path, path)
  763. end
  764. end
  765. def name
  766. @context.name
  767. end
  768. def section
  769. @context.section
  770. end
  771. def index_name
  772. "#{@context.name} (#{@html_class.name})"
  773. end
  774. def parent_name
  775. if @context.parent.parent
  776. @context.parent.parent.full_name
  777. else
  778. nil
  779. end
  780. end
  781. def aref
  782. @seq
  783. end
  784. def path
  785. if @options.all_one_file
  786. aref
  787. else
  788. @html_class.path + "#" + aref
  789. end
  790. end
  791. def description
  792. markup(@context.comment)
  793. end
  794. def visibility
  795. @context.visibility
  796. end
  797. def singleton
  798. @context.singleton
  799. end
  800. def call_seq
  801. cs = @context.call_seq
  802. if cs
  803. cs.gsub(/\n/, "<br />\n")
  804. else
  805. nil
  806. end
  807. end
  808. def params
  809. # params coming from a call-seq in 'C' will start with the
  810. # method name
  811. p = @context.params
  812. if p !~ /^\w/
  813. p = @context.params.gsub(/\s*\#.*/, '')
  814. p = p.tr("\n", " ").squeeze(" ")
  815. p = "(" + p + ")" unless p[0] == ?(
  816. if (block = @context.block_params)
  817. # If this method has explicit block parameters, remove any
  818. # explicit &block
  819. p.sub!(/,?\s*&\w+/, '')
  820. block.gsub!(/\s*\#.*/, '')
  821. block = block.tr("\n", " ").squeeze(" ")
  822. if block[0] == ?(
  823. block.sub!(/^\(/, '').sub!(/\)/, '')
  824. end
  825. p << " {|#{block.strip}| ...}"
  826. end
  827. end
  828. CGI.escapeHTML(p)
  829. end
  830. def create_source_code_file(code_body)
  831. meth_path = @html_class.path.sub(/\.html$/, '.src')
  832. File.makedirs(meth_path)
  833. file_path = File.join(meth_path, @seq) + ".html"
  834. template = TemplatePage.new(RDoc::Page::SRC_PAGE)
  835. File.open(file_path, "w") do |f|
  836. values = {
  837. 'title' => CGI.escapeHTML(index_name),
  838. 'code' => code_body,
  839. 'style_url' => style_url(file_path, @options.css),
  840. 'charset' => @options.charset
  841. }
  842. template.write_html_on(f, values)
  843. end
  844. HTMLGenerator.gen_url(path, file_path)
  845. end
  846. def HtmlMethod.all_methods
  847. @@all_methods
  848. end
  849. def <=>(other)
  850. @context <=> other.context
  851. end
  852. ##
  853. # Given a sequence of source tokens, mark up the source code
  854. # to make it look purty.
  855. def markup_code(tokens)
  856. src = ""
  857. tokens.each do |t|
  858. next unless t
  859. # p t.class
  860. # style = STYLE_MAP[t.class]
  861. style = case t
  862. when RubyToken::TkCONSTANT then "ruby-constant"
  863. when RubyToken::TkKW then "ruby-keyword kw"
  864. when RubyToken::TkIVAR then "ruby-ivar"
  865. when RubyToken::TkOp then "ruby-operator"
  866. when RubyToken::TkId then "ruby-identifier"
  867. when RubyToken::TkNode then "ruby-node"
  868. when RubyToken::TkCOMMENT then "ruby-comment cmt"
  869. when RubyToken::TkREGEXP then "ruby-regexp re"
  870. when RubyToken::TkSTRING then "ruby-value str"
  871. when RubyToken::TkVal then "ruby-value"
  872. else
  873. nil
  874. end
  875. text = CGI.escapeHTML(t.text)
  876. if style
  877. src << "<span class=\"#{style}\">#{text}</span>"
  878. else
  879. src << text
  880. end
  881. end
  882. add_line_numbers(src) if Options.instance.include_line_numbers
  883. src
  884. end
  885. # we rely on the fact that the first line of a source code
  886. # listing has
  887. # # File xxxxx, line dddd
  888. def add_line_numbers(src)
  889. if src =~ /\A.*, line (\d+)/
  890. first = $1.to_i - 1
  891. last = first + src.count("\n")
  892. size = last.to_s.length
  893. real_fmt = "%#{size}d: "
  894. fmt = " " * (size+2)
  895. src.gsub!(/^/) do
  896. res = sprintf(fmt, first)
  897. first += 1
  898. fmt = real_fmt
  899. res
  900. end
  901. end
  902. end
  903. def document_self
  904. @context.document_self
  905. end
  906. def aliases
  907. @context.aliases
  908. end
  909. def find_symbol(symbol, method=nil)
  910. res = @context.parent.find_symbol(symbol, method)
  911. if res
  912. res = res.viewer
  913. end
  914. res
  915. end
  916. end
  917. #####################################################################
  918. class HTMLGenerator
  919. include MarkUp
  920. ##
  921. # convert a target url to one that is relative to a given
  922. # path
  923. def HTMLGenerator.gen_url(path, target)
  924. from = File.dirname(path)
  925. to, to_file = File.split(target)
  926. from = from.split("/")
  927. to = to.split("/")
  928. while from.size > 0 and to.size > 0 and from[0] == to[0]
  929. from.shift
  930. to.shift
  931. end
  932. from.fill("..")
  933. from.concat(to)
  934. from << to_file
  935. File.join(*from)
  936. end
  937. # Generators may need to return specific subclasses depending
  938. # on the options they are passed. Because of this
  939. # we create them using a factory
  940. def HTMLGenerator.for(options)
  941. AllReferences::reset
  942. HtmlMethod::reset
  943. if options.all_one_file
  944. HTMLGeneratorInOne.new(options)
  945. else
  946. HTMLGenerator.new(options)
  947. end
  948. end
  949. class <<self
  950. protected :new
  951. end
  952. # Set up a new HTML generator. Basically all we do here is load
  953. # up the correct output temlate
  954. def initialize(options) #:not-new:
  955. @options = options
  956. load_html_template
  957. end
  958. ##
  959. # Build the initial indices and output objects
  960. # based on an array of TopLevel objects containing
  961. # the extracted information.
  962. def generate(toplevels)
  963. @toplevels = toplevels
  964. @files = []
  965. @classes = []
  966. write_style_sheet
  967. gen_sub_directories()
  968. build_indices
  969. generate_html
  970. end
  971. private
  972. ##
  973. # Load up the HTML template specified in the options.
  974. # If the template name contains a slash, use it literally
  975. #
  976. def load_html_template
  977. template = @options.template
  978. unless template =~ %r{/|\\}
  979. template = File.join("rdoc/generators/template",
  980. @options.generator.key, template)
  981. end
  982. require template
  983. extend RDoc::Page
  984. rescue LoadError
  985. $stderr.puts "Could not find HTML template '#{template}'"
  986. exit 99
  987. end
  988. ##
  989. # Write out the style sheet used by the main frames
  990. #
  991. def write_style_sheet
  992. template = TemplatePage.new(RDoc::Page::STYLE)
  993. unless @options.css
  994. File.open(CSS_NAME, "w") do |f|
  995. values = { "fonts" => RDoc::Page::FONTS }
  996. template.write_html_on(f, values)
  997. end
  998. end
  999. end
  1000. ##
  1001. # See the comments at the top for a description of the
  1002. # directory structure
  1003. def gen_sub_directories
  1004. File.makedirs(FILE_DIR, CLASS_DIR)
  1005. rescue
  1006. $stderr.puts $!.message
  1007. exit 1
  1008. end
  1009. ##
  1010. # Generate:
  1011. #
  1012. # * a list of HtmlFile objects for each TopLevel object.
  1013. # * a list of HtmlClass objects for each first level
  1014. # class or module in the TopLevel objects
  1015. # * a complete list of all hyperlinkable terms (file,
  1016. # class, module, and method names)
  1017. def build_indices
  1018. @toplevels.each do |toplevel|
  1019. @files << HtmlFile.new(toplevel, @options, FILE_DIR)
  1020. end
  1021. RDoc::TopLevel.all_classes_and_modules.each do |cls|
  1022. build_class_list(cls, @files[0], CLASS_DIR)
  1023. end
  1024. end
  1025. def build_class_list(from, html_file, class_dir)
  1026. @classes << HtmlClass.new(from, html_file, class_dir, @options)
  1027. from.each_classmodule do |mod|
  1028. build_class_list(mod, html_file, class_dir)
  1029. end
  1030. end
  1031. ##
  1032. # Generate all the HTML
  1033. #
  1034. def generate_html
  1035. # the individual descriptions for files and classes
  1036. gen_into(@files)
  1037. gen_into(@classes)
  1038. # and the index files
  1039. gen_file_index
  1040. gen_class_index
  1041. gen_method_index
  1042. gen_main_index
  1043. # this method is defined in the template file
  1044. write_extra_pages if defined? write_extra_pages
  1045. end
  1046. def gen_into(list)
  1047. list.each do |item|
  1048. if item.document_self
  1049. op_file = item.path
  1050. File.makedirs(File.dirname(op_file))
  1051. File.open(op_file, "w") { |file| item.write_on(file) }
  1052. end
  1053. end
  1054. end
  1055. def gen_file_index
  1056. gen_an_index(@files, 'Files',
  1057. RDoc::Page::FILE_INDEX,
  1058. "fr_file_index.html")
  1059. end
  1060. def gen_class_index
  1061. gen_an_index(@classes, 'Classes',
  1062. RDoc::Page::CLASS_INDEX,
  1063. "fr_class_index.html")
  1064. end
  1065. def gen_method_index
  1066. gen_an_index(HtmlMethod.all_methods, 'Methods',
  1067. RDoc::Page::METHOD_INDEX,
  1068. "fr_method_index.html")
  1069. end
  1070. def gen_an_index(collection, title, template, filename)
  1071. template = TemplatePage.new(RDoc::Page::FR_INDEX_BODY, template)
  1072. res = []
  1073. collection.sort.each do |f|
  1074. if f.document_self
  1075. res << { "href" => f.path, "name" => f.index_name }
  1076. end
  1077. end
  1078. values = {
  1079. "entries" => res,
  1080. 'list_title' => CGI.escapeHTML(title),
  1081. 'index_url' => main_url,
  1082. 'charset' => @options.charset,
  1083. 'style_url' => style_url('', @options.css),
  1084. }
  1085. File.open(filename, "w") do |f|
  1086. template.write_html_on(f, values)
  1087. end
  1088. end
  1089. # The main index page is mostly a template frameset, but includes
  1090. # the initial page. If the <tt>--main</tt> option was given,
  1091. # we use this as our main page, otherwise we use the
  1092. # first file specified on the command line.
  1093. def gen_main_index
  1094. template = TemplatePage.new(RDoc::Page::INDEX)
  1095. File.open("index.html", "w") do |f|
  1096. values = {
  1097. "initial_page" => main_url,
  1098. 'title' => CGI.escapeHTML(@options.title),
  1099. 'charset' => @options.charset
  1100. }
  1101. if @options.inline_source
  1102. values['inline_source'] = true
  1103. end
  1104. template.write_html_on(f, values)
  1105. end
  1106. end
  1107. # return the url of the main page
  1108. def main_url
  1109. main_page = @options.main_page
  1110. ref = nil
  1111. if main_page
  1112. ref = AllReferences[main_page]
  1113. if ref
  1114. ref = ref.path
  1115. else
  1116. $stderr.puts "Could not find main page #{main_page}"
  1117. end
  1118. end
  1119. unless ref
  1120. for file in @files
  1121. if file.document_self
  1122. ref = file.path
  1123. break
  1124. end
  1125. end
  1126. end
  1127. unless ref
  1128. $stderr.puts "Couldn't find anything to document"
  1129. $stderr.puts "Perhaps you've used :stopdoc: in all classes"
  1130. exit(1)
  1131. end
  1132. ref
  1133. end
  1134. end
  1135. ######################################################################
  1136. class HTMLGeneratorInOne < HTMLGenerator
  1137. def initialize(*args)
  1138. super
  1139. end
  1140. ##
  1141. # Build the initial indices and output objects
  1142. # based on an array of TopLevel objects containing
  1143. # the extracted information.
  1144. def generate(info)
  1145. @toplevels = info
  1146. @files = []
  1147. @classes = []
  1148. @hyperlinks = {}
  1149. build_indices
  1150. generate_xml
  1151. end
  1152. ##
  1153. # Generate:
  1154. #
  1155. # * a list of HtmlFile objects for each TopLevel object.
  1156. # * a list of HtmlClass objects for each first level
  1157. # class or module in the TopLevel objects
  1158. # * a complete list of all hyperlinkable terms (file,
  1159. # class, module, and method names)
  1160. def build_indices
  1161. @toplevels.each do |toplevel|
  1162. @files << HtmlFile.new(toplevel, @options, FILE_DIR)
  1163. end
  1164. RDoc::TopLevel.all_classes_and_modules.each do |cls|
  1165. build_class_list(cls, @files[0], CLASS_DIR)
  1166. end
  1167. end
  1168. def build_class_list(from, html_file, class_dir)
  1169. @classes << HtmlClass.new(from, html_file, class_dir, @options)
  1170. from.each_classmodule do |mod|
  1171. build_class_list(mod, html_file, class_dir)
  1172. end
  1173. end
  1174. ##
  1175. # Generate all the HTML. For the one-file case, we generate
  1176. # all the information in to one big hash
  1177. #
  1178. def generate_xml
  1179. values = {
  1180. 'charset' => @options.charset,
  1181. 'files' => gen_into(@files),
  1182. 'classes' => gen_into(@classes),
  1183. 'title' => CGI.escapeHTML(@options.title),
  1184. }
  1185. # this method is defined in the template file
  1186. write_extra_pages if defined? write_extra_pages
  1187. template = TemplatePage.new(RDoc::Page::ONE_PAGE)
  1188. if @options.op_name
  1189. opfile = File.open(@options.op_name, "w")
  1190. else
  1191. opfile = $stdout
  1192. end
  1193. template.write_html_on(opfile, values)
  1194. end
  1195. def gen_into(list)
  1196. res = []
  1197. list.each do |item|
  1198. res << item.value_hash
  1199. end
  1200. res
  1201. end
  1202. def gen_file_index
  1203. gen_an_index(@files, 'Files')
  1204. end
  1205. def gen_class_index
  1206. gen_an_index(@classes, 'Classes')
  1207. end
  1208. def gen_method_index
  1209. gen_an_index(HtmlMethod.all_methods, 'Methods')
  1210. end
  1211. def gen_an_index(collection, title)
  1212. res = []
  1213. collection.sort.each do |f|
  1214. if f.document_self
  1215. res << { "href" => f.path, "name" => f.index_name }
  1216. end
  1217. end
  1218. return {
  1219. "entries" => res,
  1220. 'list_title' => title,
  1221. 'index_url' => main_url,
  1222. }
  1223. end
  1224. end
  1225. end