From fcb0b1f503b392e446e8ee4b1033e1e7c0e7d0fe Mon Sep 17 00:00:00 2001 From: drbrain Date: Mon, 14 Jan 2008 03:34:05 +0000 Subject: Renamespace lib/rdoc/markup from SM::SimpleMarkup to RDoc::Markup. git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@15033 b2dd03c8-39d4-4d8f-98ff-823fe69b080e --- lib/rdoc/README | 605 +++----- lib/rdoc/code_objects.rb | 32 +- lib/rdoc/generator.rb | 2 +- lib/rdoc/generator/html.rb | 15 +- lib/rdoc/generator/ri.rb | 9 +- lib/rdoc/markup.rb | 466 +++++++ lib/rdoc/markup/.document | 2 - lib/rdoc/markup/fragments.rb | 330 +++++ lib/rdoc/markup/inline.rb | 357 +++++ lib/rdoc/markup/lines.rb | 150 ++ lib/rdoc/markup/preprocess.rb | 71 + lib/rdoc/markup/simple_markup.rb | 463 ------- lib/rdoc/markup/simple_markup/fragments.rb | 329 ----- lib/rdoc/markup/simple_markup/inline.rb | 355 ----- lib/rdoc/markup/simple_markup/lines.rb | 152 -- lib/rdoc/markup/simple_markup/preprocess.rb | 73 - lib/rdoc/markup/simple_markup/to_flow.rb | 182 --- lib/rdoc/markup/simple_markup/to_html.rb | 287 ---- lib/rdoc/markup/simple_markup/to_latex.rb | 332 ----- lib/rdoc/markup/to_flow.rb | 182 +++ lib/rdoc/markup/to_html.rb | 286 ++++ lib/rdoc/markup/to_latex.rb | 331 +++++ lib/rdoc/parsers/parse_rb.rb | 2002 +++++++++++++-------------- lib/rdoc/parsers/parse_simple.rb | 73 +- lib/rdoc/rdoc.rb | 40 +- lib/rdoc/ri/descriptions.rb | 4 +- lib/rdoc/ri/display.rb | 2 +- lib/rdoc/ri/driver.rb | 8 +- lib/rdoc/ri/formatter.rb | 46 +- lib/rdoc/ri/reader.rb | 2 +- lib/rdoc/stats.rb | 25 + 31 files changed, 3472 insertions(+), 3741 deletions(-) create mode 100644 lib/rdoc/markup.rb delete mode 100644 lib/rdoc/markup/.document create mode 100644 lib/rdoc/markup/fragments.rb create mode 100644 lib/rdoc/markup/inline.rb create mode 100644 lib/rdoc/markup/lines.rb create mode 100644 lib/rdoc/markup/preprocess.rb delete mode 100644 lib/rdoc/markup/simple_markup.rb delete mode 100644 lib/rdoc/markup/simple_markup/fragments.rb delete mode 100644 lib/rdoc/markup/simple_markup/inline.rb delete mode 100644 lib/rdoc/markup/simple_markup/lines.rb delete mode 100644 lib/rdoc/markup/simple_markup/preprocess.rb delete mode 100644 lib/rdoc/markup/simple_markup/to_flow.rb delete mode 100644 lib/rdoc/markup/simple_markup/to_html.rb delete mode 100644 lib/rdoc/markup/simple_markup/to_latex.rb create mode 100644 lib/rdoc/markup/to_flow.rb create mode 100644 lib/rdoc/markup/to_html.rb create mode 100644 lib/rdoc/markup/to_latex.rb create mode 100644 lib/rdoc/stats.rb (limited to 'lib/rdoc') diff --git a/lib/rdoc/README b/lib/rdoc/README index 8ad023f35d..cb00a5e355 100644 --- a/lib/rdoc/README +++ b/lib/rdoc/README @@ -1,38 +1,34 @@ = RDOC - Ruby Documentation System -This package contains Rdoc and SimpleMarkup. Rdoc is an application -that produces documentation for one or more Ruby source files. We work -similarly to JavaDoc, parsing the source, and extracting the -definition for classes, modules, and methods (along with includes and -requires). We associate with these optional documentation contained -in the immediately preceding comment block, and then render the result -using a pluggable output formatter. (Currently, HTML is the only -supported format. Markup is a library that converts plain text into -various output formats. The Markup library is used to interpret the -comment blocks that Rdoc uses to document methods, classes, and so on. - -This library contains two packages, rdoc itself and a text markup -library, 'markup'. +This package contains RDoc and RDoc::Markup. RDoc is an application that +produces documentation for one or more Ruby source files. We work similarly to +JavaDoc, parsing the source, and extracting the definition for classes, +modules, and methods (along with includes and requires). We associate with +these optional documentation contained in the immediately preceding comment +block, and then render the result using a pluggable output formatter. +RDoc::Markup is a library that converts plain text into various output formats. +The markup library is used to interpret the comment blocks that RDoc uses to +document methods, classes, and so on. == Roadmap -* If you want to use Rdoc to create documentation for your Ruby source - files, read on. -* If you want to include extensions written in C, see rdoc/parsers/parse_c.rb. -* For information on the various markups available in comment - blocks, see markup/simple_markup.rb. -* If you want to drive Rdoc programatically, see RDoc::RDoc. -* If you want to use the library to format text blocks into HTML, - have a look at SM::SimpleMarkup. +* If you want to use RDoc to create documentation for your Ruby source files, + read on. +* If you want to include extensions written in C, see RDoc::C_Parser +* For information on the various markups available in comment blocks, see + RDoc::Markup. +* If you want to drive RDoc programatically, see RDoc::RDoc. +* If you want to use the library to format text blocks into HTML, have a look + at RDoc::Markup. * If you want to try writing your own HTML output template, see - RDoc::Page. + RDoc::Generator::HTML == Summary Once installed, you can create documentation using the 'rdoc' command (the command is 'rdoc.bat' under Windows) - % rdoc [options] [names...] + % rdoc [options] [names...] Type "rdoc --help" for an up-to-date option summary. @@ -42,448 +38,195 @@ source (such as rdoc itself). % rdoc This command generates documentation for all the Ruby and C source -files in and below the current directory. These will be stored in a +files in and below the current directory. These will be stored in a documentation tree starting in the subdirectory 'doc'. You can make this slightly more useful for your readers by having the -index page contain the documentation for the primary file. In our +index page contain the documentation for the primary file. In our case, we could type - % rdoc --main rdoc/rdoc.rb + % rdoc --main rdoc.rb You'll find information on the various formatting tricks you can use in comment blocks in the documentation this generates. -RDoc uses file extensions to determine how to process each file. File -names ending .rb and .rbw are assumed to be Ruby -source. Files ending .c are parsed as C files. All other -files are assumed to contain just SimpleMarkup-style markup (with or -without leading '#' comment markers). If directory names are passed to -RDoc, they are scanned recursively for C and Ruby source files only. +RDoc uses file extensions to determine how to process each file. File names +ending +.rb+ and .rbw are assumed to be Ruby source. Files +ending +.c+ are parsed as C files. All other files are assumed to +contain just Markup-style markup (with or without leading '#' comment markers). +If directory names are passed to RDoc, they are scanned recursively for C and +Ruby source files only. -== Credits += Markup -* The Ruby parser in rdoc/parse.rb is based heavily on the outstanding - work of Keiju ISHITSUKA of Nippon Rational Inc, who produced the Ruby - parser for irb and the rtags package. +For information on how to make lists, hyperlinks, & etc. with RDoc, see +RDoc::Markup. -* Code to diagram classes and modules was written by Sergey A Yanovitsky - (Jah) of Enticla. +Comment blocks can be written fairly naturally, either using '#' on successive +lines of the comment, or by including the comment in an =begin/=end block. If +you use the latter form, the =begin line must be flagged with an RDoc tag: -* Charset patch from MoonWolf. + =begin rdoc + Documentation to be processed by RDoc. + + ... + =end -* Rich Kilmer wrote the kilmer.rb output template. +RDoc stops processing comments if it finds a comment line containing '+#--+'. +This can be used to separate external from internal comments, or to stop a +comment being associated with a method, class, or module. Commenting can be +turned back on with a line that starts '+#+++'. + + ## + # Extract the age and calculate the date-of-birth. + #-- + # FIXME: fails if the birthday falls on February 29th + #++ + # The DOB is returned as a Time object. + + def get_dob(person) + # ... + end -* Dan Brickley led the design of the RDF format. +Names of classes, source files, and any method names containing an underscore +or preceded by a hash character are automatically hyperlinked from comment text +to their description. -== License +Method parameter lists are extracted and displayed with the method description. +If a method calls +yield+, then the parameters passed to yield will also be +displayed: -RDoc is Copyright (c) 2001-2003 Dave Thomas, The Pragmatic Programmers. It -is free software, and may be redistributed under the terms specified -in the README file of the Ruby distribution. + def fred + ... + yield line, address + +This will get documented as: -= Usage + fred() { |line, address| ... } -RDoc is invoked from the command line using: +You can override this using a comment containing ':yields: ...' immediately +after the method definition - % rdoc [name...] + def fred # :yields: index, position + # ... + + yield line, address -Files are parsed, and the information they contain collected, before -any output is produced. This allows cross references between all files -to be resolved. If a name is a directory, it is traversed. If no -names are specified, all Ruby files in the current directory (and -subdirectories) are processed. +which will get documented as -Options are: + fred() { |index, position| ... } -[--accessor name[,name...]] - specifies the name(s) of additional methods that should be treated - as if they were attr_xxx methods. Specifying - "--accessor db_opt" means lines such as ++:yields:+ is an example of a documentation directive. These appear immediately +after the start of the document element they are modifying. - db_opt :name, :age +== Directives + +[+:nodoc:+ / +:nodoc:+ all] + Don't include this element in the documentation. For classes + and modules, the methods, aliases, constants, and attributes + directly within the affected class or module will also be + omitted. By default, though, modules and classes within that + class of module _will_ be documented. This is turned off by + adding the +all+ modifier. + + module MyModule # :nodoc: + class Input + end + end + + module OtherModule # :nodoc: all + class Output + end + end - will get parsed and displayed in the documentation. Each name may have an - optional "=flagtext" appended, in which case the given flagtext will appear - where (for example) the 'rw' appears for attr_accessor. - -[--all] - include protected and private methods in the output (by default - only public methods are included) - -[--charset _charset_] - Set the character set for the generated HTML. - -[--diagram] - include diagrams showing modules and classes. This is currently - an experimental feature, and may not be supported by all output - templates. You need dot V1.8.6 or later to use the --diagram - option correctly (http://www.research.att.com/sw/tools/graphviz/). - -[--exclude pattern] - exclude files and directories matching this pattern from processing - -[--extension new=old] - treat files ending .new as if they ended - .old. Saying '--extension cgi=rb' causes RDoc to treat .cgi - files as Ruby source. - -[fileboxes] - Classes are put in boxes which represents files, where these - classes reside. Classes shared between more than one file are - shown with list of files that sharing them. Silently discarded if - --diagram is not given Experimental. - -[--fmt _fmt_] - generate output in a particular format. - -[--help] - generate a usage summary. - -[--help-output] - explain the various output options. - -[--image-format gif/png/jpg/jpeg] - sets output image format for diagrams. Can be png, gif, jpeg, - jpg. If this option is omitted, png is used. Requires --diagram. - -[--include dir,...] - specify one or more directories to be searched when satisfying - :+include+: directives. Multiple --include options may be - given. The directory containing the file currently being processed - is always searched. - -[--inline-source] - By default, the source code of methods is shown in a popup. With - this option, it's displayed inline. - -[line-numbers] - include line numbers in the source code - -[--main _name_] - the class of module _name_ will appear on the index page. If you - want to set a particular file as a main page (a README, for - example) simply specifiy its name as the first on the command - line. - -[--merge] - when generating _ri_ output, if classes being processed already - exist in the destination directory, merge in the current details - rather than overwrite them. - -[--one-file] - place all the output into a single file - -[--op _dir_] - set the output directory to _dir_ (the default is the directory - "doc") - -[--op-name _name_] - set the name of the output. Has no effect for HTML. - "doc") - -[--opname _name_] - set the output name (has no effect for HTML). - -[--promiscuous] - If a module or class is defined in more than one source file, and - you click on a particular file's name in the top navigation pane, - RDoc will normally only show you the inner classes and modules of - that class that are defined in the particular file. Using this - option makes it show all classes and modules defined in the class, - regardless of the file they were defined in. - -[--quiet] - do not display progress messages - -[--ri, --ri-site, _and_ --ri-system] - generate output than can be read by the _ri_ command-line tool. - By default --ri places its output in ~/.rdoc, --ri-site in - $datadir/ri//site, and --ri-system in - $datadir/ri//system. All can be overridden with a subsequent - --op option. All default directories are in ri's default search - path. - -[--show-hash] - A name of the form #name in a comment is a possible hyperlink to - an instance method name. When displayed, the '#' is removed unless - this option is specified - -[--style stylesheet url] - specifies the URL of an external stylesheet to use (rather than - generating one of our own) - -[tab-width _n_] - set the width of tab characters (default 8) - -[--template name] - specify an alternate template to use when generating output (the - default is 'standard'). This template should be in a directory - accessible via $: as rdoc/generator/xxxx_template, where 'xxxx' - depends on the output formatter. - -[--version] - display RDoc's version - -[--webcvs _url_] - Specify a URL for linking to a web frontend to CVS. If the URL - contains a '\%s', the name of the current file will be - substituted; if the URL doesn't contain a '\%s', the filename will - be appended to it. - -= Example + In the above code, only class +MyModule::Input+ will be documented. + +[+:doc:+] + Force a method or attribute to be documented even if it wouldn't otherwise + be. Useful if, for example, you want to include documentation of a + particular private method. + +[+:notnew:+] + Only applicable to the +initialize+ instance method. Normally RDoc assumes + that the documentation and parameters for #initialize are actually for the + ::new method, and so fakes out a ::new for the class. The :notnew: modifier + stops this. Remember that #initialize is protected, so you won't see the + documentation unless you use the -a command line option. + +Comment blocks can contain other directives: + +[+:section: title+] + Starts a new section in the output. The title following +:section:+ is used + as the section heading, and the remainder of the comment containing the + section is used as introductory text. Subsequent methods, aliases, + attributes, and classes will be documented in this section. A :section: + comment block may have one or more lines before the :section: directive. + These will be removed, and any identical lines at the end of the block are + also removed. This allows you to add visual cues such as: + + # ---------------------------------------- + # :section: My Section + # This is the section that I wrote. + # See it glisten in the noon-day sun. + # ---------------------------------------- + +[+:call-seq:+] + Lines up to the next blank line in the comment are treated as the method's + calling sequence, overriding the default parsing of method parameters and + yield arguments. + +[+:include:+ _filename_] + Include the contents of the named file at this point. The file will be + searched for in the directories listed by the +--include+ option, or in the + current directory by default. The contents of the file will be shifted to + have the same indentation as the ':' at the start of the :include: directive. + +[+:title:+ _text_] + Sets the title for the document. Equivalent to the --title command line + parameter. (The command line parameter overrides any :title: directive in + the source). + +[+:enddoc:+] + Document nothing further at the current level. + +[+:main:+ _name_] + Equivalent to the --main command line parameter. + +[+:stopdoc:+ / +:startdoc:+] + Stop and start adding new documentation elements to the current container. + For example, if a class has a number of constants that you don't want to + document, put a +:stopdoc:+ before the first, and a +:startdoc:+ after the + last. If you don't specifiy a +:startdoc:+ by the end of the container, + disables documentation for the entire class or module. -A typical small Ruby program commented using RDoc might be as follows. You -can see the formatted result in EXAMPLE.rb and Anagram. += Other stuff - :include: EXAMPLE.rb +Author:: Dave Thomas -= Markup +== Credits -Comment blocks can be written fairly naturally, either using '#' on -successive lines of the comment, or by including the comment in -an =begin/=end block. If you use the latter form, the =begin line -must be flagged with an RDoc tag: +* The Ruby parser in rdoc/parse.rb is based heavily on the outstanding + work of Keiju ISHITSUKA of Nippon Rational Inc, who produced the Ruby + parser for irb and the rtags package. - =begin rdoc - Documentation to - be processed by RDoc. - =end +* Code to diagram classes and modules was written by Sergey A Yanovitsky + (Jah) of Enticla. -Paragraphs are lines that share the left margin. Text indented past -this margin are formatted verbatim. - -1. Lists are typed as indented paragraphs with: - * a '*' or '-' (for bullet lists) - * a digit followed by a period for - numbered lists - * an upper or lower case letter followed - by a period for alpha lists. +* Charset patch from MoonWolf. - For example, the input that produced the above paragraph looked like - 1. Lists are typed as indented - paragraphs with: - * a '*' or '-' (for bullet lists) - * a digit followed by a period for - numbered lists - * an upper or lower case letter followed - by a period for alpha lists. - -2. Labeled lists (sometimes called description - lists) are typed using square brackets for the label. - [cat] small domestic animal - [+cat+] command to copy standard input - -3. Labeled lists may also be produced by putting a double colon - after the label. This sets the result in tabular form, so the - descriptions all line up. This was used to create the 'author' - block at the bottom of this description. - cat:: small domestic animal - +cat+:: command to copy standard input - - For both kinds of labeled lists, if the body text starts on the same - line as the label, then the start of that text determines the block - indent for the rest of the body. The text may also start on the line - following the label, indented from the start of the label. This is - often preferable if the label is long. Both the following are - valid labeled list entries: - - --output name [, name]:: - specify the name of one or more output files. If multiple - files are present, the first is used as the index. - - --quiet::: do not output the names, sizes, byte counts, - index areas, or bit ratios of units as - they are processed. - -4. Headings are entered using equals signs - - = Level One Heading - == Level Two Heading - and so on - -5. Rules (horizontal lines) are entered using three or - more hyphens. - -6. Non-verbatim text can be marked up: - - _italic_:: \_word_ or \text - *bold*:: \*word* or \text - +typewriter+:: \+word+ or \text - - The first form only works around 'words', where a word is a - sequence of upper and lower case letters and underscores. Putting a - backslash before inline markup stops it being interpreted, which is - how I created the table above: - - _italic_:: \_word_ or \text - *bold*:: \*word* or \text - +typewriter+:: \+word+ or \text - -7. Names of classes, source files, and any method names - containing an underscore or preceded by a hash - character are automatically hyperlinked from - comment text to their description. - -8. Hyperlinks to the web starting http:, mailto:, ftp:, or www. are - recognized. An HTTP url that references an external image file is - converted into an inline . Hyperlinks starting 'link:' are - assumed to refer to local files whose path is relative to the --op - directory. - - Hyperlinks can also be of the form label[url], in which - case the label is used in the displayed text, and url is - used as the target. If label contains multiple words, - put it in braces: {multi word label}[url]. - -9. Method parameter lists are extracted and displayed with - the method description. If a method calls +yield+, then - the parameters passed to yield will also be displayed: - - def fred - ... - yield line, address - - This will get documented as - - fred() { |line, address| ... } - - You can override this using a comment containing - ':yields: ...' immediately after the method definition - - def fred # :yields: index, position - ... - yield line, address - - which will get documented as - - fred() { |index, position| ... } - - -10. ':yields:' is an example of a documentation modifier. These appear - immediately after the start of the document element they are modifying. - Other modifiers include - - [:nodoc:[all]] - don't include this element in the documentation. For classes - and modules, the methods, aliases, constants, and attributes - directly within the affected class or module will also be - omitted. By default, though, modules and classes within that - class of module _will_ be documented. This is turned off by - adding the +all+ modifier. - - module SM #:nodoc: - class Input - end - end - module Markup #:nodoc: all - class Output - end - end - - In the above code, only class SM::Input will be - documented. - - [:doc:] - force a method or attribute to be documented even if it - wouldn't otherwise be. Useful if, for example, you want to - include documentation of a particular private method. - - [:notnew:] - only applicable to the +initialize+ instance method. Normally - RDoc assumes that the documentation and parameters for - #initialize are actually for the ::new method, and so fakes - out a ::new for the class. THe :notnew: modifier stops - this. Remember that #initialize is protected, so you won't - see the documentation unless you use the -a command line - option. - - -11. RDoc stops processing comments if it finds a comment - line containing '#--'. This can be used to - separate external from internal comments, or - to stop a comment being associated with a method, - class, or module. Commenting can be turned back on with - a line that starts '#++'. - - # Extract the age and calculate the - # date-of-birth. - #-- - # FIXME: fails if the birthday falls on - # February 29th - #++ - # The DOB is returned as a Time object. - - def get_dob(person) - ... - -12. Comment blocks can contain other directives: - - [:section: title] - Starts a new section in the output. The title following - :section: is used as the section heading, and the - remainder of the comment containing the section is used as - introductory text. Subsequent methods, aliases, attributes, - and classes will be documented in this section. A :section: - comment block may have one or more lines before the :section: - directive. These will be removed, and any identical lines at - the end of the block are also removed. This allows you to add - visual cues such as - - # ---------------------------------------- - # :section: My Section - # This is the section that I wrote. - # See it glisten in the noon-day sun. - # ---------------------------------------- - - [call-seq:] - lines up to the next blank line in the comment are treated as - the method's calling sequence, overriding the - default parsing of method parameters and yield arguments. - - [:include:filename] - include the contents of the named file at this point. The - file will be searched for in the directories listed by - the --include option, or in the current - directory by default. The contents of the file will be - shifted to have the same indentation as the ':' at the - start of the :include: directive. - - [:title:text] - Sets the title for the document. Equivalent to the --title command - line parameter. (The command line parameter overrides any :title: - directive in the source). - - [:enddoc:] - Document nothing further at the current level. - - [:main:name] - Equivalent to the --main command line parameter. - - [:stopdoc: / :startdoc:] - Stop and start adding new documentation elements to the - current container. For example, if a class has a number of - constants that you don't want to document, put a - :stopdoc: before the first, and a - :startdoc: after the last. If you don't specifiy a - :startdoc: by the end of the container, disables - documentation for the entire class or module. - - ---- - -See also markup/simple_markup.rb. +* Rich Kilmer wrote the kilmer.rb output template. -= Other stuff +* Dan Brickley led the design of the RDF format. -Author:: Dave Thomas -Requires:: Ruby 1.8.1 or later -License:: Copyright (c) 2001-2003 Dave Thomas. - Released under the same license as Ruby. +== License + +RDoc is Copyright (c) 2001-2003 Dave Thomas, The Pragmatic Programmers. It +is free software, and may be redistributed under the terms specified +in the README file of the Ruby distribution. == Warranty -This software is provided "as is" and without any express or -implied warranties, including, without limitation, the implied -warranties of merchantibility and fitness for a particular -purpose. +This software is provided "as is" and without any express or implied +warranties, including, without limitation, the implied warranties of +merchantibility and fitness for a particular purpose. + diff --git a/lib/rdoc/code_objects.rb b/lib/rdoc/code_objects.rb index d75670de72..dfc0fff9cc 100644 --- a/lib/rdoc/code_objects.rb +++ b/lib/rdoc/code_objects.rb @@ -458,7 +458,7 @@ module RDoc end - + ## # A TopLevel context is a source file class TopLevel < Context @@ -470,7 +470,7 @@ module RDoc @@all_classes = {} @@all_modules = {} - def TopLevel::reset + def self.reset @@all_classes = {} @@all_modules = {} end @@ -488,14 +488,15 @@ module RDoc nil end - # Adding a class or module to a TopLevel is special, as we only - # want one copy of a particular top-level class. For example, - # if both file A and file B implement class C, we only want one - # ClassModule object for C. This code arranges to share - # classes and modules between files. + ## + # Adding a class or module to a TopLevel is special, as we only want one + # copy of a particular top-level class. For example, if both file A and + # file B implement class C, we only want one ClassModule object for C. + # This code arranges to share classes and modules between files. def add_class_or_module(collection, class_type, name, superclass) cls = collection[name] + if cls puts "Reusing class/module #{name}" if $DEBUG_RDOC else @@ -504,23 +505,29 @@ module RDoc else all = @@all_classes end + cls = all[name] + if !cls cls = class_type.new(name, superclass) - all[name] = cls unless @done_documenting + all[name] = cls unless @done_documenting end - puts "Adding class/module #{name} to #@name" if $DEBUG_RDOC + + puts "Adding class/module #{name} to #{@name}" if $DEBUG_RDOC + collection[name] = cls unless @done_documenting + cls.parent = self end + cls end - def TopLevel.all_classes_and_modules + def self.all_classes_and_modules @@all_classes.values + @@all_modules.values end - def TopLevel.find_class_named(name) + def self.find_class_named(name) @@all_classes.each_value do |c| res = c.find_class_named(name) return res if res @@ -538,12 +545,13 @@ module RDoc nil end + ## # Find a named module + def find_module_named(name) find_class_or_module_named(name) || find_enclosing_module_named(name) end - end # ClassModule is the base class for objects representing either a diff --git a/lib/rdoc/generator.rb b/lib/rdoc/generator.rb index cf4bb3a6f1..7db23058e3 100644 --- a/lib/rdoc/generator.rb +++ b/lib/rdoc/generator.rb @@ -1,7 +1,7 @@ require 'cgi' require 'rdoc' require 'rdoc/options' -require 'rdoc/markup/simple_markup' +require 'rdoc/markup' require 'rdoc/template' module RDoc::Generator diff --git a/lib/rdoc/generator/html.rb b/lib/rdoc/generator/html.rb index 32f8235537..e6a6cd9ac9 100644 --- a/lib/rdoc/generator/html.rb +++ b/lib/rdoc/generator/html.rb @@ -1,7 +1,7 @@ require 'fileutils' require 'rdoc/generator' -require 'rdoc/markup/simple_markup/to_html' +require 'rdoc/markup/to_html' module RDoc::Generator @@ -34,12 +34,11 @@ module RDoc::Generator end ## - # Subclass of the SM::ToHtml class that supports looking - # up words in the AllReferences list. Those that are - # found (like AllReferences in this comment) will - # be hyperlinked + # Subclass of the RDoc::Markup::ToHtml class that supports looking up words + # in the AllReferences list. Those that are found (like AllReferences in + # this comment) will be hyperlinked - class HyperlinkHtml < SM::ToHtml + class HyperlinkHtml < RDoc::Markup::ToHtml ## # We need to record the html path of our caller so we can generate @@ -161,13 +160,13 @@ module RDoc::Generator ## # Convert a string in markup format into HTML. We keep a cached - # SimpleMarkup object lying around after the first time we're + # RDoc::Markup object lying around after the first time we're # called per object. def markup(str, remove_para=false) return '' unless str unless defined? @markup - @markup = SM::SimpleMarkup.new + @markup = RDoc::Markup.new # class names, variable names, or instance variables @markup.add_special(/( diff --git a/lib/rdoc/generator/ri.rb b/lib/rdoc/generator/ri.rb index bb0c4f4021..2452d79132 100644 --- a/lib/rdoc/generator/ri.rb +++ b/lib/rdoc/generator/ri.rb @@ -1,5 +1,5 @@ require 'rdoc/generator' -require 'rdoc/markup/simple_markup/to_flow' +require 'rdoc/markup/to_flow' require 'rdoc/ri/cache' require 'rdoc/ri/reader' @@ -26,8 +26,8 @@ class RDoc::Generator::RI def initialize(options) #:not-new: @options = options @ri_writer = RDoc::RI::Writer.new "." - @markup = SM::SimpleMarkup.new - @to_flow = SM::ToFlow.new + @markup = RDoc::Markup.new + @to_flow = RDoc::Markup::ToFlow.new @generated = {} end @@ -38,7 +38,7 @@ class RDoc::Generator::RI def generate(toplevels) RDoc::TopLevel.all_classes_and_modules.each do |cls| - process_class(cls) + process_class cls end end @@ -58,6 +58,7 @@ class RDoc::Generator::RI cls_desc = RDoc::RI::ClassDescription.new cls_desc.superclass = cls.superclass end + cls_desc.name = cls.name cls_desc.full_name = cls.full_name cls_desc.comment = markup(cls.comment) diff --git a/lib/rdoc/markup.rb b/lib/rdoc/markup.rb new file mode 100644 index 0000000000..46654ebb58 --- /dev/null +++ b/lib/rdoc/markup.rb @@ -0,0 +1,466 @@ +require 'rdoc' + +## +# RDoc::Markup parses plain text documents and attempts to decompose them into +# their constituent parts. Some of these parts are high-level: paragraphs, +# chunks of verbatim text, list entries and the like. Other parts happen at +# the character level: a piece of bold text, a word in code font. This markup +# is similar in spirit to that used on WikiWiki webs, where folks create web +# pages using a simple set of formatting rules. +# +# RDoc::Markup itself does no output formatting: this is left to a different +# set of classes. +# +# RDoc::Markup is extendable at runtime: you can add new markup elements to be +# recognised in the documents that RDoc::Markup parses. +# +# RDoc::Markup is intended to be the basis for a family of tools which share +# the common requirement that simple, plain-text should be rendered in a +# variety of different output formats and media. It is envisaged that +# RDoc::Markup could be the basis for formating RDoc style comment blocks, +# Wiki entries, and online FAQs. +# +# = Basic Formatting +# +# * RDoc::Markup looks for a document's natural left margin. This is +# used as the initial margin for the document. +# +# * Consecutive lines starting at this margin are considered to be a +# paragraph. +# +# * If a paragraph starts with a "*", "-", or with ".", then it is +# taken to be the start of a list. The margin in increased to be the +# first non-space following the list start flag. Subsequent lines +# should be indented to this new margin until the list ends. For +# example: +# +# * this is a list with three paragraphs in +# the first item. This is the first paragraph. +# +# And this is the second paragraph. +# +# 1. This is an indented, numbered list. +# 2. This is the second item in that list +# +# This is the third conventional paragraph in the +# first list item. +# +# * This is the second item in the original list +# +# * You can also construct labeled lists, sometimes called description +# or definition lists. Do this by putting the label in square brackets +# and indenting the list body: +# +# [cat] a small furry mammal +# that seems to sleep a lot +# +# [ant] a little insect that is known +# to enjoy picnics +# +# A minor variation on labeled lists uses two colons to separate the +# label from the list body: +# +# cat:: a small furry mammal +# that seems to sleep a lot +# +# ant:: a little insect that is known +# to enjoy picnics +# +# This latter style guarantees that the list bodies' left margins are +# aligned: think of them as a two column table. +# +# * Any line that starts to the right of the current margin is treated +# as verbatim text. This is useful for code listings. The example of a +# list above is also verbatim text. +# +# * A line starting with an equals sign (=) is treated as a +# heading. Level one headings have one equals sign, level two headings +# have two,and so on. +# +# * A line starting with three or more hyphens (at the current indent) +# generates a horizontal rule. The more hyphens, the thicker the rule +# (within reason, and if supported by the output device) +# +# * You can use markup within text (except verbatim) to change the +# appearance of parts of that text. Out of the box, RDoc::Markup +# supports word-based and general markup. +# +# Word-based markup uses flag characters around individual words: +# +# [\*word*] displays word in a *bold* font +# [\_word_] displays word in an _emphasized_ font +# [\+word+] displays word in a +code+ font +# +# General markup affects text between a start delimiter and and end +# delimiter. Not surprisingly, these delimiters look like HTML markup. +# +# [\text...] displays word in a *bold* font +# [\text...] displays word in an _emphasized_ font +# [\text...] displays word in an _emphasized_ font +# [\text...] displays word in a +code+ font +# +# Unlike conventional Wiki markup, general markup can cross line +# boundaries. You can turn off the interpretation of markup by +# preceding the first character with a backslash, so \\\bold +# text and \\\*bold* produce \bold text and \*bold +# respectively. +# +# * Hyperlinks to the web starting http:, mailto:, ftp:, or www. are +# recognized. An HTTP url that references an external image file is +# converted into an inline . Hyperlinks starting 'link:' are +# assumed to refer to local files whose path is relative to the --op +# directory. +# +# Hyperlinks can also be of the form label[url], in which +# case the label is used in the displayed text, and url is +# used as the target. If label contains multiple words, +# put it in braces: {multi word label}[url]. +# +# == Synopsis +# +# This code converts input_string to HTML. The conversion +# takes place in the +convert+ method, so you can use the same +# RDoc::Markup object to convert multiple input strings. +# +# require 'rdoc/markup' +# require 'rdoc/markup/to_html' +# +# p = RDoc::Markup.new +# h = RDoc::Markup::ToHtml.new +# +# puts p.convert(input_string, h) +# +# You can extend the RDoc::Markup parser to recognise new markup +# sequences, and to add special processing for text that matches a +# regular epxression. Here we make WikiWords significant to the parser, +# and also make the sequences {word} and \text... signify +# strike-through text. When then subclass the HTML output class to deal +# with these: +# +# require 'rdoc/markup' +# require 'rdoc/markup/to_html' +# +# class WikiHtml < RDoc::Markup::ToHtml +# def handle_special_WIKIWORD(special) +# "" + special.text + "" +# end +# end +# +# p = RDoc::Markup.new +# p.add_word_pair("{", "}", :STRIKE) +# p.add_html("no", :STRIKE) +# +# p.add_special(/\b([A-Z][a-z]+[A-Z]\w+)/, :WIKIWORD) +# +# h = WikiHtml.new +# h.add_tag(:STRIKE, "", "") +# +# puts "" + p.convert(ARGF.read, h) + "" +# +#-- +# Author:: Dave Thomas, dave@pragmaticprogrammer.com +# Version:: 0.0 +# License:: Ruby license + +class RDoc::Markup + + SPACE = ?\s + + # List entries look like: + # * text + # 1. text + # [label] text + # label:: text + # + # Flag it as a list entry, and work out the indent for subsequent lines + + SIMPLE_LIST_RE = /^( + ( \* (?# bullet) + |- (?# bullet) + |\d+\. (?# numbered ) + |[A-Za-z]\. (?# alphabetically numbered ) + ) + \s+ + )\S/x + + LABEL_LIST_RE = /^( + ( \[.*?\] (?# labeled ) + |\S.*:: (?# note ) + )(?:\s+|$) + )/x + + ## + # Take a block of text and use various heuristics to determine it's + # structure (paragraphs, lists, and so on). Invoke an event handler as we + # identify significant chunks. + + def initialize + @am = AttributeManager.new + @output = nil + end + + ## + # Add to the sequences used to add formatting to an individual word (such + # as *bold*). Matching entries will generate attibutes that the output + # formatters can recognize by their +name+. + + def add_word_pair(start, stop, name) + @am.add_word_pair(start, stop, name) + end + + ## + # Add to the sequences recognized as general markup. + + def add_html(tag, name) + @am.add_html(tag, name) + end + + ## + # Add to other inline sequences. For example, we could add WikiWords using + # something like: + # + # parser.add_special(/\b([A-Z][a-z]+[A-Z]\w+)/, :WIKIWORD) + # + # Each wiki word will be presented to the output formatter via the + # accept_special method. + + def add_special(pattern, name) + @am.add_special(pattern, name) + end + + ## + # We take a string, split it into lines, work out the type of each line, + # and from there deduce groups of lines (for example all lines in a + # paragraph). We then invoke the output formatter using a Visitor to + # display the result. + + def convert(str, op) + @lines = Lines.new(str.split(/\r?\n/).collect { |aLine| + Line.new(aLine) }) + return "" if @lines.empty? + @lines.normalize + assign_types_to_lines + group = group_lines + # call the output formatter to handle the result + # group.to_a.each {|i| p i} + group.accept(@am, op) + end + + private + + ## + # Look through the text at line indentation. We flag each line as being + # Blank, a paragraph, a list element, or verbatim text. + + def assign_types_to_lines(margin = 0, level = 0) + + while line = @lines.next + if line.isBlank? + line.stamp(Line::BLANK, level) + next + end + + # if a line contains non-blanks before the margin, then it must belong + # to an outer level + + text = line.text + + for i in 0...margin + if text[i] != SPACE + @lines.unget + return + end + end + + active_line = text[margin..-1] + + # Rules (horizontal lines) look like + # + # --- (three or more hyphens) + # + # The more hyphens, the thicker the rule + # + + if /^(---+)\s*$/ =~ active_line + line.stamp(Line::RULE, level, $1.length-2) + next + end + + # Then look for list entries. First the ones that have to have + # text following them (* xxx, - xxx, and dd. xxx) + + if SIMPLE_LIST_RE =~ active_line + + offset = margin + $1.length + prefix = $2 + prefix_length = prefix.length + + flag = case prefix + when "*","-" then ListBase::BULLET + when /^\d/ then ListBase::NUMBER + when /^[A-Z]/ then ListBase::UPPERALPHA + when /^[a-z]/ then ListBase::LOWERALPHA + else raise "Invalid List Type: #{self.inspect}" + end + + line.stamp(Line::LIST, level+1, prefix, flag) + text[margin, prefix_length] = " " * prefix_length + assign_types_to_lines(offset, level + 1) + next + end + + + if LABEL_LIST_RE =~ active_line + offset = margin + $1.length + prefix = $2 + prefix_length = prefix.length + + next if handled_labeled_list(line, level, margin, offset, prefix) + end + + # Headings look like + # = Main heading + # == Second level + # === Third + # + # Headings reset the level to 0 + + if active_line[0] == ?= and active_line =~ /^(=+)\s*(.*)/ + prefix_length = $1.length + prefix_length = 6 if prefix_length > 6 + line.stamp(Line::HEADING, 0, prefix_length) + line.strip_leading(margin + prefix_length) + next + end + + # If the character's a space, then we have verbatim text, + # otherwise + + if active_line[0] == SPACE + line.strip_leading(margin) if margin > 0 + line.stamp(Line::VERBATIM, level) + else + line.stamp(Line::PARAGRAPH, level) + end + end + end + + ## + # Handle labeled list entries, We have a special case to deal with. + # Because the labels can be long, they force the remaining block of text + # over the to right: + # + # this is a long label that I wrote:: and here is the + # block of text with + # a silly margin + # + # So we allow the special case. If the label is followed by nothing, and + # if the following line is indented, then we take the indent of that line + # as the new margin. + # + # this is a long label that I wrote:: + # here is a more reasonably indented block which + # will be attached to the label. + # + + def handled_labeled_list(line, level, margin, offset, prefix) + prefix_length = prefix.length + text = line.text + flag = nil + case prefix + when /^\[/ + flag = ListBase::LABELED + prefix = prefix[1, prefix.length-2] + when /:$/ + flag = ListBase::NOTE + prefix.chop! + else raise "Invalid List Type: #{self.inspect}" + end + + # body is on the next line + + if text.length <= offset + original_line = line + line = @lines.next + return(false) unless line + text = line.text + + for i in 0..margin + if text[i] != SPACE + @lines.unget + return false + end + end + i = margin + i += 1 while text[i] == SPACE + if i >= text.length + @lines.unget + return false + else + offset = i + prefix_length = 0 + @lines.delete(original_line) + end + end + + line.stamp(Line::LIST, level+1, prefix, flag) + text[margin, prefix_length] = " " * prefix_length + assign_types_to_lines(offset, level + 1) + return true + end + + ## + # Return a block consisting of fragments which are paragraphs, list + # entries or verbatim text. We merge consecutive lines of the same type + # and level together. We are also slightly tricky with lists: the lines + # following a list introduction look like paragraph lines at the next + # level, and we remap them into list entries instead. + + def group_lines + @lines.rewind + + inList = false + wantedType = wantedLevel = nil + + block = LineCollection.new + group = nil + + while line = @lines.next + if line.level == wantedLevel and line.type == wantedType + group.add_text(line.text) + else + group = block.fragment_for(line) + block.add(group) + if line.type == Line::LIST + wantedType = Line::PARAGRAPH + else + wantedType = line.type + end + wantedLevel = line.type == Line::HEADING ? line.param : line.level + end + end + + block.normalize + block + end + + ## + # For debugging, we allow access to our line contents as text. + + def content + @lines.as_text + end + public :content + + ## + # For debugging, return the list of line types. + + def get_line_types + @lines.line_types + end + public :get_line_types + +end + +require 'rdoc/markup/fragments' +require 'rdoc/markup/lines' diff --git a/lib/rdoc/markup/.document b/lib/rdoc/markup/.document deleted file mode 100644 index 3cf4f21bd7..0000000000 --- a/lib/rdoc/markup/.document +++ /dev/null @@ -1,2 +0,0 @@ -simple_markup -simple_markup.rb diff --git a/lib/rdoc/markup/fragments.rb b/lib/rdoc/markup/fragments.rb new file mode 100644 index 0000000000..2dd3614fc1 --- /dev/null +++ b/lib/rdoc/markup/fragments.rb @@ -0,0 +1,330 @@ +require 'rdoc/markup' +require 'rdoc/markup/lines' + +class RDoc::Markup + + ## + # A Fragment is a chunk of text, subclassed as a paragraph, a list + # entry, or verbatim text. + + class Fragment + attr_reader :level, :param, :txt + attr_accessor :type + + def initialize(level, param, type, txt) + @level = level + @param = param + @type = type + @txt = "" + add_text(txt) if txt + end + + def add_text(txt) + @txt << " " if @txt.length > 0 + @txt << txt.tr_s("\n ", " ").strip + end + + def to_s + "L#@level: #{self.class.name.split('::')[-1]}\n#@txt" + end + + ###### + # This is a simple factory system that lets us associate fragement + # types (a string) with a subclass of fragment + + TYPE_MAP = {} + + def Fragment.type_name(name) + TYPE_MAP[name] = self + end + + def Fragment.for(line) + klass = TYPE_MAP[line.type] || + raise("Unknown line type: '#{line.type.inspect}:' '#{line.text}'") + return klass.new(line.level, line.param, line.flag, line.text) + end + end + + ## + # A paragraph is a fragment which gets wrapped to fit. We remove all + # newlines when we're created, and have them put back on output. + + class Paragraph < Fragment + type_name Line::PARAGRAPH + end + + class BlankLine < Paragraph + type_name Line::BLANK + end + + class Heading < Paragraph + type_name Line::HEADING + + def head_level + @param.to_i + end + end + + ## + # A List is a fragment with some kind of label + + class ListBase < Paragraph + # List types + BULLET = :BULLET + NUMBER = :NUMBER + UPPERALPHA = :UPPERALPHA + LOWERALPHA = :LOWERALPHA + LABELED = :LABELED + NOTE = :NOTE + end + + class ListItem < ListBase + type_name Line::LIST + + # def label + # am = AttributeManager.new(@param) + # am.flow + # end + end + + class ListStart < ListBase + def initialize(level, param, type) + super(level, param, type, nil) + end + end + + class ListEnd < ListBase + def initialize(level, type) + super(level, "", type, nil) + end + end + + ## + # Verbatim code contains lines that don't get wrapped. + + class Verbatim < Fragment + type_name Line::VERBATIM + + def add_text(txt) + @txt << txt.chomp << "\n" + end + + end + + ## + # A horizontal rule + + class Rule < Fragment + type_name Line::RULE + end + + ## + # Collect groups of lines together. Each group will end up containing a flow + # of text. + + class LineCollection + + def initialize + @fragments = [] + end + + def add(fragment) + @fragments << fragment + end + + def each(&b) + @fragments.each(&b) + end + + def to_a # :nodoc: + @fragments.map {|fragment| fragment.to_s} + end + + ## + # Factory for different fragment types + + def fragment_for(*args) + Fragment.for(*args) + end + + ## + # Tidy up at the end + + def normalize + change_verbatim_blank_lines + add_list_start_and_ends + add_list_breaks + tidy_blank_lines + end + + def to_s + @fragments.join("\n----\n") + end + + def accept(am, visitor) + visitor.start_accepting + + @fragments.each do |fragment| + case fragment + when Verbatim + visitor.accept_verbatim(am, fragment) + when Rule + visitor.accept_rule(am, fragment) + when ListStart + visitor.accept_list_start(am, fragment) + when ListEnd + visitor.accept_list_end(am, fragment) + when ListItem + visitor.accept_list_item(am, fragment) + when BlankLine + visitor.accept_blank_line(am, fragment) + when Heading + visitor.accept_heading(am, fragment) + when Paragraph + visitor.accept_paragraph(am, fragment) + end + end + + visitor.end_accepting + end + + private + + # If you have: + # + # normal paragraph text. + # + # this is code + # + # and more code + # + # You'll end up with the fragments Paragraph, BlankLine, Verbatim, + # BlankLine, Verbatim, BlankLine, etc. + # + # The BlankLine in the middle of the verbatim chunk needs to be changed to + # a real verbatim newline, and the two verbatim blocks merged + + def change_verbatim_blank_lines + frag_block = nil + blank_count = 0 + @fragments.each_with_index do |frag, i| + if frag_block.nil? + frag_block = frag if Verbatim === frag + else + case frag + when Verbatim + blank_count.times { frag_block.add_text("\n") } + blank_count = 0 + frag_block.add_text(frag.txt) + @fragments[i] = nil # remove out current fragment + when BlankLine + if frag_block + blank_count += 1 + @fragments[i] = nil + end + else + frag_block = nil + blank_count = 0 + end + end + end + @fragments.compact! + end + + ## + # List nesting is implicit given the level of indentation. Make it + # explicit, just to make life a tad easier for the output processors + + def add_list_start_and_ends + level = 0 + res = [] + type_stack = [] + + @fragments.each do |fragment| + # $stderr.puts "#{level} : #{fragment.class.name} : #{fragment.level}" + new_level = fragment.level + while (level < new_level) + level += 1 + type = fragment.type + res << ListStart.new(level, fragment.param, type) if type + type_stack.push type + # $stderr.puts "Start: #{level}" + end + + while level > new_level + type = type_stack.pop + res << ListEnd.new(level, type) if type + level -= 1 + # $stderr.puts "End: #{level}, #{type}" + end + + res << fragment + level = fragment.level + end + level.downto(1) do |i| + type = type_stack.pop + res << ListEnd.new(i, type) if type + end + + @fragments = res + end + + ## + # Inserts start/ends between list entries at the same level that have + # different element types + + def add_list_breaks + res = @fragments + + @fragments = [] + list_stack = [] + + res.each do |fragment| + case fragment + when ListStart + list_stack.push fragment + when ListEnd + start = list_stack.pop + fragment.type = start.type + when ListItem + l = list_stack.last + if fragment.type != l.type + @fragments << ListEnd.new(l.level, l.type) + start = ListStart.new(l.level, fragment.param, fragment.type) + @fragments << start + list_stack.pop + list_stack.push start + end + else + ; + end + @fragments << fragment + end + end + + ## + # Tidy up the blank lines: + # * change Blank/ListEnd into ListEnd/Blank + # * remove blank lines at the front + + def tidy_blank_lines + (@fragments.size - 1).times do |i| + if @fragments[i].kind_of?(BlankLine) and + @fragments[i+1].kind_of?(ListEnd) + @fragments[i], @fragments[i+1] = @fragments[i+1], @fragments[i] + end + end + + # remove leading blanks + @fragments.each_with_index do |f, i| + break unless f.kind_of? BlankLine + @fragments[i] = nil + end + + @fragments.compact! + end + + end + +end + diff --git a/lib/rdoc/markup/inline.rb b/lib/rdoc/markup/inline.rb new file mode 100644 index 0000000000..cbf5032a68 --- /dev/null +++ b/lib/rdoc/markup/inline.rb @@ -0,0 +1,357 @@ +require 'rdoc/markup' + +class RDoc::Markup + + ## + # We manage a set of attributes. Each attribute has a symbol name and a bit + # value. + + class Attribute + SPECIAL = 1 + + @@name_to_bitmap = { :_SPECIAL_ => SPECIAL } + @@next_bitmap = 2 + + def Attribute.bitmap_for(name) + bitmap = @@name_to_bitmap[name] + if !bitmap + bitmap = @@next_bitmap + @@next_bitmap <<= 1 + @@name_to_bitmap[name] = bitmap + end + bitmap + end + + def Attribute.as_string(bitmap) + return "none" if bitmap.zero? + res = [] + @@name_to_bitmap.each do |name, bit| + res << name if (bitmap & bit) != 0 + end + res.join(",") + end + + def Attribute.each_name_of(bitmap) + @@name_to_bitmap.each do |name, bit| + next if bit == SPECIAL + yield name.to_s if (bitmap & bit) != 0 + end + end + end + + ## + # An AttrChanger records a change in attributes. It contains a bitmap of the + # attributes to turn on, and a bitmap of those to turn off. + + AttrChanger = Struct.new(:turn_on, :turn_off) + + class AttrChanger + def to_s + "Attr: +#{Attribute.as_string(@turn_on)}/-#{Attribute.as_string(@turn_on)}" + end + end + + ## + # An array of attributes which parallels the characters in a string. + + class AttrSpan + def initialize(length) + @attrs = Array.new(length, 0) + end + + def set_attrs(start, length, bits) + for i in start ... (start+length) + @attrs[i] |= bits + end + end + + def [](n) + @attrs[n] + end + end + + ## + # Hold details of a special sequence + + class Special + attr_reader :type + attr_accessor :text + + def initialize(type, text) + @type, @text = type, text + end + + def ==(o) + self.text == o.text && self.type == o.type + end + + def to_s + "Special: type=#{type}, name=#{RDoc::Markup::Attribute.as_string type}, text=#{text.dump}" + end + + def inspect + "#" % [ + object_id, @type, RDoc::Markup::Attribute.as_string(type), text.dump] + end + end + + class AttributeManager + + NULL = "\000".freeze + + ## + # We work by substituting non-printing characters in to the text. For now + # I'm assuming that I can substitute a character in the range 0..8 for a 7 + # bit character without damaging the encoded string, but this might be + # optimistic + + A_PROTECT = 004 + PROTECT_ATTR = A_PROTECT.chr + + ## + # This maps delimiters that occur around words (such as *bold* or +tt+) + # where the start and end delimiters and the same. This lets us optimize + # the regexp + + MATCHING_WORD_PAIRS = {} + + ## + # And this is used when the delimiters aren't the same. In this case the + # hash maps a pattern to the attribute character + + WORD_PAIR_MAP = {} + + ## + # This maps HTML tags to the corresponding attribute char + + HTML_TAGS = {} + + ## + # And this maps _special_ sequences to a name. A special sequence is + # something like a WikiWord + + SPECIAL = {} + + ## + # Return an attribute object with the given turn_on and turn_off bits set + + def attribute(turn_on, turn_off) + AttrChanger.new(turn_on, turn_off) + end + + def change_attribute(current, new) + diff = current ^ new + attribute(new & diff, current & diff) + end + + def changed_attribute_by_name(current_set, new_set) + current = new = 0 + current_set.each {|name| current |= Attribute.bitmap_for(name) } + new_set.each {|name| new |= Attribute.bitmap_for(name) } + change_attribute(current, new) + end + + def copy_string(start_pos, end_pos) + res = @str[start_pos...end_pos] + res.gsub!(/\000/, '') + res + end + + ## + # Map attributes like textto the sequence + # \001\002\001\003, where is a per-attribute specific + # character + + def convert_attrs(str, attrs) + # first do matching ones + tags = MATCHING_WORD_PAIRS.keys.join("") + re = "(^|\\W)([#{tags}])([A-Za-z_]+?)\\2(\\W|\$)" +# re = "(^|\\W)([#{tags}])(\\S+?)\\2(\\W|\$)" + 1 while str.gsub!(Regexp.new(re)) { + attr = MATCHING_WORD_PAIRS[$2]; + attrs.set_attrs($`.length + $1.length + $2.length, $3.length, attr) + $1 + NULL*$2.length + $3 + NULL*$2.length + $4 + } + + # then non-matching + unless WORD_PAIR_MAP.empty? + WORD_PAIR_MAP.each do |regexp, attr| + str.gsub!(regexp) { + attrs.set_attrs($`.length + $1.length, $2.length, attr) + NULL*$1.length + $2 + NULL*$3.length + } + end + end + end + + def convert_html(str, attrs) + tags = HTML_TAGS.keys.join("|") + re = "<(#{tags})>(.*?)" + 1 while str.gsub!(Regexp.new(re, Regexp::IGNORECASE)) { + attr = HTML_TAGS[$1.downcase] + html_length = $1.length + 2 + seq = NULL * html_length + attrs.set_attrs($`.length + html_length, $2.length, attr) + seq + $2 + seq + NULL + } + end + + def convert_specials(str, attrs) + unless SPECIAL.empty? + SPECIAL.each do |regexp, attr| + str.scan(regexp) do + attrs.set_attrs($`.length, $&.length, attr | Attribute::SPECIAL) + end + end + end + end + + ## + # A \ in front of a character that would normally be processed turns off + # processing. We do this by turning \< into <#{PROTECT} + + PROTECTABLE = [ "<" << "\\" ] #" + + + def mask_protected_sequences + protect_pattern = Regexp.new("\\\\([#{Regexp.escape(PROTECTABLE.join(''))}])") + @str.gsub!(protect_pattern, "\\1#{PROTECT_ATTR}") + end + + def unmask_protected_sequences + @str.gsub!(/(.)#{PROTECT_ATTR}/, "\\1\000") + end + + def initialize + add_word_pair("*", "*", :BOLD) + add_word_pair("_", "_", :EM) + add_word_pair("+", "+", :TT) + + add_html("em", :EM) + add_html("i", :EM) + add_html("b", :BOLD) + add_html("tt", :TT) + add_html("code", :TT) + + add_special(//, :COMMENT) + end + + def add_word_pair(start, stop, name) + raise "Word flags may not start '<'" if start[0] == ?< + bitmap = Attribute.bitmap_for(name) + if start == stop + MATCHING_WORD_PAIRS[start] = bitmap + else + pattern = Regexp.new("(" + Regexp.escape(start) + ")" + +# "([A-Za-z]+)" + + "(\\S+)" + + "(" + Regexp.escape(stop) +")") + WORD_PAIR_MAP[pattern] = bitmap + end + PROTECTABLE << start[0,1] + PROTECTABLE.uniq! + end + + def add_html(tag, name) + HTML_TAGS[tag.downcase] = Attribute.bitmap_for(name) + end + + def add_special(pattern, name) + SPECIAL[pattern] = Attribute.bitmap_for(name) + end + + def flow(str) + @str = str + + puts("Before flow, str='#{@str.dump}'") if $DEBUG_RDOC + mask_protected_sequences + + @attrs = AttrSpan.new(@str.length) + + puts("After protecting, str='#{@str.dump}'") if $DEBUG_RDOC + convert_attrs(@str, @attrs) + convert_html(@str, @attrs) + convert_specials(str, @attrs) + unmask_protected_sequences + puts("After flow, str='#{@str.dump}'") if $DEBUG_RDOC + return split_into_flow + end + + def display_attributes + puts + puts @str.tr(NULL, "!") + bit = 1 + 16.times do |bno| + line = "" + @str.length.times do |i| + if (@attrs[i] & bit) == 0 + line << " " + else + if bno.zero? + line << "S" + else + line << ("%d" % (bno+1)) + end + end + end + puts(line) unless line =~ /^ *$/ + bit <<= 1 + end + end + + def split_into_flow + + display_attributes if $DEBUG_RDOC + + res = [] + current_attr = 0 + str = "" + + str_len = @str.length + + # skip leading invisible text + i = 0 + i += 1 while i < str_len and @str[i] == "\0" + start_pos = i + + # then scan the string, chunking it on attribute changes + while i < str_len + new_attr = @attrs[i] + if new_attr != current_attr + if i > start_pos + res << copy_string(start_pos, i) + start_pos = i + end + + res << change_attribute(current_attr, new_attr) + current_attr = new_attr + + if (current_attr & Attribute::SPECIAL) != 0 + i += 1 while i < str_len and (@attrs[i] & Attribute::SPECIAL) != 0 + res << Special.new(current_attr, copy_string(start_pos, i)) + start_pos = i + next + end + end + + # move on, skipping any invisible characters + begin + i += 1 + end while i < str_len and @str[i] == "\0" + end + + # tidy up trailing text + if start_pos < str_len + res << copy_string(start_pos, str_len) + end + + # and reset to all attributes off + res << change_attribute(current_attr, 0) if current_attr != 0 + + return res + end + + end + +end + diff --git a/lib/rdoc/markup/lines.rb b/lib/rdoc/markup/lines.rb new file mode 100644 index 0000000000..5fb5bde993 --- /dev/null +++ b/lib/rdoc/markup/lines.rb @@ -0,0 +1,150 @@ +class RDoc::Markup + + ## + # We store the lines we're working on as objects of class Line. These + # contain the text of the line, along with a flag indicating the line type, + # and an indentation level. + + class Line + INFINITY = 9999 + + BLANK = :BLANK + HEADING = :HEADING + LIST = :LIST + RULE = :RULE + PARAGRAPH = :PARAGRAPH + VERBATIM = :VERBATIM + + # line type + attr_accessor :type + + # The indentation nesting level + attr_accessor :level + + # The contents + attr_accessor :text + + # A prefix or parameter. For LIST lines, this is + # the text that introduced the list item (the label) + attr_accessor :param + + # A flag. For list lines, this is the type of the list + attr_accessor :flag + + # the number of leading spaces + attr_accessor :leading_spaces + + # true if this line has been deleted from the list of lines + attr_accessor :deleted + + def initialize(text) + @text = text.dup + @deleted = false + + # expand tabs + 1 while @text.gsub!(/\t+/) { ' ' * (8*$&.length - $`.length % 8)} && $~ #` + + # Strip trailing whitespace + @text.sub!(/\s+$/, '') + + # and look for leading whitespace + if @text.length > 0 + @text =~ /^(\s*)/ + @leading_spaces = $1.length + else + @leading_spaces = INFINITY + end + end + + # Return true if this line is blank + def isBlank? + @text.length.zero? + end + + # stamp a line with a type, a level, a prefix, and a flag + def stamp(type, level, param="", flag=nil) + @type, @level, @param, @flag = type, level, param, flag + end + + ## + # Strip off the leading margin + + def strip_leading(size) + if @text.size > size + @text[0,size] = "" + else + @text = "" + end + end + + def to_s + "#@type#@level: #@text" + end + end + + ## + # A container for all the lines. + + class Lines + + include Enumerable + + attr_reader :lines # :nodoc: + + def initialize(lines) + @lines = lines + rewind + end + + def empty? + @lines.size.zero? + end + + def each + @lines.each do |line| + yield line unless line.deleted + end + end + +# def [](index) +# @lines[index] +# end + + def rewind + @nextline = 0 + end + + def next + begin + res = @lines[@nextline] + @nextline += 1 if @nextline < @lines.size + end while res and res.deleted and @nextline < @lines.size + res + end + + def unget + @nextline -= 1 + end + + def delete(a_line) + a_line.deleted = true + end + + def normalize + margin = @lines.collect{|l| l.leading_spaces}.min + margin = 0 if margin == Line::INFINITY + @lines.each {|line| line.strip_leading(margin) } if margin > 0 + end + + def as_text + @lines.map {|l| l.text}.join("\n") + end + + def line_types + @lines.map {|l| l.type } + end + + end + +end + diff --git a/lib/rdoc/markup/preprocess.rb b/lib/rdoc/markup/preprocess.rb new file mode 100644 index 0000000000..760351d386 --- /dev/null +++ b/lib/rdoc/markup/preprocess.rb @@ -0,0 +1,71 @@ +require 'rdoc/markup' + +## +# Handle common directives that can occur in a block of text: +# +# : include : filename + +class RDoc::Markup::PreProcess + + def initialize(input_file_name, include_path) + @input_file_name = input_file_name + @include_path = include_path + end + + ## + # Look for common options in a chunk of text. Options that we don't handle + # are passed back to our caller as |directive, param| + + def handle(text) + text.gsub!(/^([ \t#]*):(\w+):\s*(.+)?\n/) do + prefix = $1 + directive = $2.downcase + param = $3 + + case directive + when "include" + filename = param.split[0] + include_file(filename, prefix) + + else + yield(directive, param) + end + end + end + + private + + ## + # Include a file, indenting it correctly. + + def include_file(name, indent) + if full_name = find_include_file(name) then + content = File.open(full_name) {|f| f.read} + # strip leading '#'s, but only if all lines start with them + if content =~ /^[^#]/ + content.gsub(/^/, indent) + else + content.gsub(/^#?/, indent) + end + else + $stderr.puts "Couldn't find file to include: '#{name}'" + '' + end + end + + ## + # Look for the given file in the directory containing the current file, + # and then in each of the directories specified in the RDOC_INCLUDE path + + def find_include_file(name) + to_search = [ File.dirname(@input_file_name) ].concat @include_path + to_search.each do |dir| + full_name = File.join(dir, name) + stat = File.stat(full_name) rescue next + return full_name if stat.readable? + end + nil + end + +end + diff --git a/lib/rdoc/markup/simple_markup.rb b/lib/rdoc/markup/simple_markup.rb deleted file mode 100644 index ed3b4d3270..0000000000 --- a/lib/rdoc/markup/simple_markup.rb +++ /dev/null @@ -1,463 +0,0 @@ -require 'rdoc/markup/simple_markup/fragments' -require 'rdoc/markup/simple_markup/lines.rb' - -## -# SimpleMarkup parses plain text documents and attempts to decompose -# them into their constituent parts. Some of these parts are high-level: -# paragraphs, chunks of verbatim text, list entries and the like. Other -# parts happen at the character level: a piece of bold text, a word in -# code font. This markup is similar in spirit to that used on WikiWiki -# webs, where folks create web pages using a simple set of formatting -# rules. -# -# SimpleMarkup itself does no output formatting: this is left to a -# different set of classes. -# -# SimpleMarkup is extendable at runtime: you can add new markup -# elements to be recognised in the documents that SimpleMarkup parses. -# -# SimpleMarkup is intended to be the basis for a family of tools which -# share the common requirement that simple, plain-text should be -# rendered in a variety of different output formats and media. It is -# envisaged that SimpleMarkup could be the basis for formating RDoc -# style comment blocks, Wiki entries, and online FAQs. -# -# = Basic Formatting -# -# * SimpleMarkup looks for a document's natural left margin. This is -# used as the initial margin for the document. -# -# * Consecutive lines starting at this margin are considered to be a -# paragraph. -# -# * If a paragraph starts with a "*", "-", or with ".", then it is -# taken to be the start of a list. The margin in increased to be the -# first non-space following the list start flag. Subsequent lines -# should be indented to this new margin until the list ends. For -# example: -# -# * this is a list with three paragraphs in -# the first item. This is the first paragraph. -# -# And this is the second paragraph. -# -# 1. This is an indented, numbered list. -# 2. This is the second item in that list -# -# This is the third conventional paragraph in the -# first list item. -# -# * This is the second item in the original list -# -# * You can also construct labeled lists, sometimes called description -# or definition lists. Do this by putting the label in square brackets -# and indenting the list body: -# -# [cat] a small furry mammal -# that seems to sleep a lot -# -# [ant] a little insect that is known -# to enjoy picnics -# -# A minor variation on labeled lists uses two colons to separate the -# label from the list body: -# -# cat:: a small furry mammal -# that seems to sleep a lot -# -# ant:: a little insect that is known -# to enjoy picnics -# -# This latter style guarantees that the list bodies' left margins are -# aligned: think of them as a two column table. -# -# * Any line that starts to the right of the current margin is treated -# as verbatim text. This is useful for code listings. The example of a -# list above is also verbatim text. -# -# * A line starting with an equals sign (=) is treated as a -# heading. Level one headings have one equals sign, level two headings -# have two,and so on. -# -# * A line starting with three or more hyphens (at the current indent) -# generates a horizontal rule. THe more hyphens, the thicker the rule -# (within reason, and if supported by the output device) -# -# * You can use markup within text (except verbatim) to change the -# appearance of parts of that text. Out of the box, SimpleMarkup -# supports word-based and general markup. -# -# Word-based markup uses flag characters around individual words: -# -# [\*word*] displays word in a *bold* font -# [\_word_] displays word in an _emphasized_ font -# [\+word+] displays word in a +code+ font -# -# General markup affects text between a start delimiter and and end -# delimiter. Not surprisingly, these delimiters look like HTML markup. -# -# [\text...] displays word in a *bold* font -# [\text...] displays word in an _emphasized_ font -# [\text...] displays word in an _emphasized_ font -# [\text...] displays word in a +code+ font -# -# Unlike conventional Wiki markup, general markup can cross line -# boundaries. You can turn off the interpretation of markup by -# preceding the first character with a backslash, so \\\bold -# text and \\\*bold* produce \bold text and \*bold -# respectively. -# -# = Using SimpleMarkup -# -# For information on using SimpleMarkup programatically, see SM::SimpleMarkup. -#-- -# Author:: Dave Thomas, dave@pragmaticprogrammer.com -# Version:: 0.0 -# License:: Ruby license - -module SM - - # == Synopsis - # - # This code converts input_string, which is in the format - # described in markup/simple_markup.rb, to HTML. The conversion - # takes place in the +convert+ method, so you can use the same - # SimpleMarkup object to convert multiple input strings. - # - # require 'rdoc/markup/simple_markup' - # require 'rdoc/markup/simple_markup/to_html' - # - # p = SM::SimpleMarkup.new - # h = SM::ToHtml.new - # - # puts p.convert(input_string, h) - # - # You can extend the SimpleMarkup parser to recognise new markup - # sequences, and to add special processing for text that matches a - # regular epxression. Here we make WikiWords significant to the parser, - # and also make the sequences {word} and \text... signify - # strike-through text. When then subclass the HTML output class to deal - # with these: - # - # require 'rdoc/markup/simple_markup' - # require 'rdoc/markup/simple_markup/to_html' - # - # class WikiHtml < SM::ToHtml - # def handle_special_WIKIWORD(special) - # "" + special.text + "" - # end - # end - # - # p = SM::SimpleMarkup.new - # p.add_word_pair("{", "}", :STRIKE) - # p.add_html("no", :STRIKE) - # - # p.add_special(/\b([A-Z][a-z]+[A-Z]\w+)/, :WIKIWORD) - # - # h = WikiHtml.new - # h.add_tag(:STRIKE, "", "") - # - # puts "" + p.convert(ARGF.read, h) + "" - - class SimpleMarkup - - SPACE = ?\s - - # List entries look like: - # * text - # 1. text - # [label] text - # label:: text - # - # Flag it as a list entry, and work out the indent for subsequent lines - - SIMPLE_LIST_RE = /^( - ( \* (?# bullet) - |- (?# bullet) - |\d+\. (?# numbered ) - |[A-Za-z]\. (?# alphabetically numbered ) - ) - \s+ - )\S/x - - LABEL_LIST_RE = /^( - ( \[.*?\] (?# labeled ) - |\S.*:: (?# note ) - )(?:\s+|$) - )/x - - ## - # Take a block of text and use various heuristics to determine it's - # structure (paragraphs, lists, and so on). Invoke an event handler as we - # identify significant chunks. - - def initialize - @am = AttributeManager.new - @output = nil - end - - ## - # Add to the sequences used to add formatting to an individual word (such - # as *bold*). Matching entries will generate attibutes that the output - # formatters can recognize by their +name+. - - def add_word_pair(start, stop, name) - @am.add_word_pair(start, stop, name) - end - - ## - # Add to the sequences recognized as general markup. - - def add_html(tag, name) - @am.add_html(tag, name) - end - - ## - # Add to other inline sequences. For example, we could add WikiWords using - # something like: - # - # parser.add_special(/\b([A-Z][a-z]+[A-Z]\w+)/, :WIKIWORD) - # - # Each wiki word will be presented to the output formatter via the - # accept_special method. - - def add_special(pattern, name) - @am.add_special(pattern, name) - end - - ## - # We take a string, split it into lines, work out the type of each line, - # and from there deduce groups of lines (for example all lines in a - # paragraph). We then invoke the output formatter using a Visitor to - # display the result. - - def convert(str, op) - @lines = Lines.new(str.split(/\r?\n/).collect { |aLine| - Line.new(aLine) }) - return "" if @lines.empty? - @lines.normalize - assign_types_to_lines - group = group_lines - # call the output formatter to handle the result - # group.to_a.each {|i| p i} - group.accept(@am, op) - end - - private - - ## - # Look through the text at line indentation. We flag each line as being - # Blank, a paragraph, a list element, or verbatim text. - - def assign_types_to_lines(margin = 0, level = 0) - - while line = @lines.next - if line.isBlank? - line.stamp(Line::BLANK, level) - next - end - - # if a line contains non-blanks before the margin, then it must belong - # to an outer level - - text = line.text - - for i in 0...margin - if text[i] != SPACE - @lines.unget - return - end - end - - active_line = text[margin..-1] - - # Rules (horizontal lines) look like - # - # --- (three or more hyphens) - # - # The more hyphens, the thicker the rule - # - - if /^(---+)\s*$/ =~ active_line - line.stamp(Line::RULE, level, $1.length-2) - next - end - - # Then look for list entries. First the ones that have to have - # text following them (* xxx, - xxx, and dd. xxx) - - if SIMPLE_LIST_RE =~ active_line - - offset = margin + $1.length - prefix = $2 - prefix_length = prefix.length - - flag = case prefix - when "*","-" then ListBase::BULLET - when /^\d/ then ListBase::NUMBER - when /^[A-Z]/ then ListBase::UPPERALPHA - when /^[a-z]/ then ListBase::LOWERALPHA - else raise "Invalid List Type: #{self.inspect}" - end - - line.stamp(Line::LIST, level+1, prefix, flag) - text[margin, prefix_length] = " " * prefix_length - assign_types_to_lines(offset, level + 1) - next - end - - - if LABEL_LIST_RE =~ active_line - offset = margin + $1.length - prefix = $2 - prefix_length = prefix.length - - next if handled_labeled_list(line, level, margin, offset, prefix) - end - - # Headings look like - # = Main heading - # == Second level - # === Third - # - # Headings reset the level to 0 - - if active_line[0] == ?= and active_line =~ /^(=+)\s*(.*)/ - prefix_length = $1.length - prefix_length = 6 if prefix_length > 6 - line.stamp(Line::HEADING, 0, prefix_length) - line.strip_leading(margin + prefix_length) - next - end - - # If the character's a space, then we have verbatim text, - # otherwise - - if active_line[0] == SPACE - line.strip_leading(margin) if margin > 0 - line.stamp(Line::VERBATIM, level) - else - line.stamp(Line::PARAGRAPH, level) - end - end - end - - ## - # Handle labeled list entries, We have a special case to deal with. - # Because the labels can be long, they force the remaining block of text - # over the to right: - # - # this is a long label that I wrote:: and here is the - # block of text with - # a silly margin - # - # So we allow the special case. If the label is followed by nothing, and - # if the following line is indented, then we take the indent of that line - # as the new margin. - # - # this is a long label that I wrote:: - # here is a more reasonably indented block which - # will be attached to the label. - # - - def handled_labeled_list(line, level, margin, offset, prefix) - prefix_length = prefix.length - text = line.text - flag = nil - case prefix - when /^\[/ - flag = ListBase::LABELED - prefix = prefix[1, prefix.length-2] - when /:$/ - flag = ListBase::NOTE - prefix.chop! - else raise "Invalid List Type: #{self.inspect}" - end - - # body is on the next line - - if text.length <= offset - original_line = line - line = @lines.next - return(false) unless line - text = line.text - - for i in 0..margin - if text[i] != SPACE - @lines.unget - return false - end - end - i = margin - i += 1 while text[i] == SPACE - if i >= text.length - @lines.unget - return false - else - offset = i - prefix_length = 0 - @lines.delete(original_line) - end - end - - line.stamp(Line::LIST, level+1, prefix, flag) - text[margin, prefix_length] = " " * prefix_length - assign_types_to_lines(offset, level + 1) - return true - end - - ## - # Return a block consisting of fragments which are paragraphs, list - # entries or verbatim text. We merge consecutive lines of the same type - # and level together. We are also slightly tricky with lists: the lines - # following a list introduction look like paragraph lines at the next - # level, and we remap them into list entries instead. - - def group_lines - @lines.rewind - - inList = false - wantedType = wantedLevel = nil - - block = LineCollection.new - group = nil - - while line = @lines.next - if line.level == wantedLevel and line.type == wantedType - group.add_text(line.text) - else - group = block.fragment_for(line) - block.add(group) - if line.type == Line::LIST - wantedType = Line::PARAGRAPH - else - wantedType = line.type - end - wantedLevel = line.type == Line::HEADING ? line.param : line.level - end - end - - block.normalize - block - end - - ## - # For debugging, we allow access to our line contents as text. - - def content - @lines.as_text - end - public :content - - ## - # For debugging, return the list of line types. - - def get_line_types - @lines.line_types - end - public :get_line_types - - end - -end - diff --git a/lib/rdoc/markup/simple_markup/fragments.rb b/lib/rdoc/markup/simple_markup/fragments.rb deleted file mode 100644 index bd60ca0fec..0000000000 --- a/lib/rdoc/markup/simple_markup/fragments.rb +++ /dev/null @@ -1,329 +0,0 @@ -require 'rdoc/markup/simple_markup/lines.rb' - -module SM - - ## - # A Fragment is a chunk of text, subclassed as a paragraph, a list - # entry, or verbatim text. - - class Fragment - attr_reader :level, :param, :txt - attr_accessor :type - - def initialize(level, param, type, txt) - @level = level - @param = param - @type = type - @txt = "" - add_text(txt) if txt - end - - def add_text(txt) - @txt << " " if @txt.length > 0 - @txt << txt.tr_s("\n ", " ").strip - end - - def to_s - "L#@level: #{self.class.name.split('::')[-1]}\n#@txt" - end - - ###### - # This is a simple factory system that lets us associate fragement - # types (a string) with a subclass of fragment - - TYPE_MAP = {} - - def Fragment.type_name(name) - TYPE_MAP[name] = self - end - - def Fragment.for(line) - klass = TYPE_MAP[line.type] || - raise("Unknown line type: '#{line.type.inspect}:' '#{line.text}'") - return klass.new(line.level, line.param, line.flag, line.text) - end - end - - ## - # A paragraph is a fragment which gets wrapped to fit. We remove all - # newlines when we're created, and have them put back on output. - - class Paragraph < Fragment - type_name Line::PARAGRAPH - end - - class BlankLine < Paragraph - type_name Line::BLANK - end - - class Heading < Paragraph - type_name Line::HEADING - - def head_level - @param.to_i - end - end - - ## - # A List is a fragment with some kind of label - - class ListBase < Paragraph - # List types - BULLET = :BULLET - NUMBER = :NUMBER - UPPERALPHA = :UPPERALPHA - LOWERALPHA = :LOWERALPHA - LABELED = :LABELED - NOTE = :NOTE - end - - class ListItem < ListBase - type_name Line::LIST - - # def label - # am = AttributeManager.new(@param) - # am.flow - # end - end - - class ListStart < ListBase - def initialize(level, param, type) - super(level, param, type, nil) - end - end - - class ListEnd < ListBase - def initialize(level, type) - super(level, "", type, nil) - end - end - - ## - # Verbatim code contains lines that don't get wrapped. - - class Verbatim < Fragment - type_name Line::VERBATIM - - def add_text(txt) - @txt << txt.chomp << "\n" - end - - end - - ## - # A horizontal rule - - class Rule < Fragment - type_name Line::RULE - end - - ## - # Collect groups of lines together. Each group will end up containing a flow - # of text - - class LineCollection - - def initialize - @fragments = [] - end - - def add(fragment) - @fragments << fragment - end - - def each(&b) - @fragments.each(&b) - end - - def to_a # :nodoc: - @fragments.map {|fragment| fragment.to_s} - end - - ## - # Factory for different fragment types - - def fragment_for(*args) - Fragment.for(*args) - end - - ## - # Tidy up at the end - - def normalize - change_verbatim_blank_lines - add_list_start_and_ends - add_list_breaks - tidy_blank_lines - end - - def to_s - @fragments.join("\n----\n") - end - - def accept(am, visitor) - visitor.start_accepting - - @fragments.each do |fragment| - case fragment - when Verbatim - visitor.accept_verbatim(am, fragment) - when Rule - visitor.accept_rule(am, fragment) - when ListStart - visitor.accept_list_start(am, fragment) - when ListEnd - visitor.accept_list_end(am, fragment) - when ListItem - visitor.accept_list_item(am, fragment) - when BlankLine - visitor.accept_blank_line(am, fragment) - when Heading - visitor.accept_heading(am, fragment) - when Paragraph - visitor.accept_paragraph(am, fragment) - end - end - - visitor.end_accepting - end - - private - - # If you have: - # - # normal paragraph text. - # - # this is code - # - # and more code - # - # You'll end up with the fragments Paragraph, BlankLine, Verbatim, - # BlankLine, Verbatim, BlankLine, etc. - # - # The BlankLine in the middle of the verbatim chunk needs to be changed to - # a real verbatim newline, and the two verbatim blocks merged - - def change_verbatim_blank_lines - frag_block = nil - blank_count = 0 - @fragments.each_with_index do |frag, i| - if frag_block.nil? - frag_block = frag if Verbatim === frag - else - case frag - when Verbatim - blank_count.times { frag_block.add_text("\n") } - blank_count = 0 - frag_block.add_text(frag.txt) - @fragments[i] = nil # remove out current fragment - when BlankLine - if frag_block - blank_count += 1 - @fragments[i] = nil - end - else - frag_block = nil - blank_count = 0 - end - end - end - @fragments.compact! - end - - ## - # List nesting is implicit given the level of indentation. Make it - # explicit, just to make life a tad easier for the output processors - - def add_list_start_and_ends - level = 0 - res = [] - type_stack = [] - - @fragments.each do |fragment| - # $stderr.puts "#{level} : #{fragment.class.name} : #{fragment.level}" - new_level = fragment.level - while (level < new_level) - level += 1 - type = fragment.type - res << ListStart.new(level, fragment.param, type) if type - type_stack.push type - # $stderr.puts "Start: #{level}" - end - - while level > new_level - type = type_stack.pop - res << ListEnd.new(level, type) if type - level -= 1 - # $stderr.puts "End: #{level}, #{type}" - end - - res << fragment - level = fragment.level - end - level.downto(1) do |i| - type = type_stack.pop - res << ListEnd.new(i, type) if type - end - - @fragments = res - end - - ## - # Inserts start/ends between list entries at the same level that have - # different element types - - def add_list_breaks - res = @fragments - - @fragments = [] - list_stack = [] - - res.each do |fragment| - case fragment - when ListStart - list_stack.push fragment - when ListEnd - start = list_stack.pop - fragment.type = start.type - when ListItem - l = list_stack.last - if fragment.type != l.type - @fragments << ListEnd.new(l.level, l.type) - start = ListStart.new(l.level, fragment.param, fragment.type) - @fragments << start - list_stack.pop - list_stack.push start - end - else - ; - end - @fragments << fragment - end - end - - ## - # Tidy up the blank lines: - # * change Blank/ListEnd into ListEnd/Blank - # * remove blank lines at the front - - def tidy_blank_lines - (@fragments.size - 1).times do |i| - if @fragments[i].kind_of?(BlankLine) and - @fragments[i+1].kind_of?(ListEnd) - @fragments[i], @fragments[i+1] = @fragments[i+1], @fragments[i] - end - end - - # remove leading blanks - @fragments.each_with_index do |f, i| - break unless f.kind_of? BlankLine - @fragments[i] = nil - end - - @fragments.compact! - end - - end - -end - diff --git a/lib/rdoc/markup/simple_markup/inline.rb b/lib/rdoc/markup/simple_markup/inline.rb deleted file mode 100644 index eafda011e8..0000000000 --- a/lib/rdoc/markup/simple_markup/inline.rb +++ /dev/null @@ -1,355 +0,0 @@ -module SM - - ## - # We manage a set of attributes. Each attribute has a symbol name and a bit - # value - - class Attribute - SPECIAL = 1 - - @@name_to_bitmap = { :_SPECIAL_ => SPECIAL } - @@next_bitmap = 2 - - def Attribute.bitmap_for(name) - bitmap = @@name_to_bitmap[name] - if !bitmap - bitmap = @@next_bitmap - @@next_bitmap <<= 1 - @@name_to_bitmap[name] = bitmap - end - bitmap - end - - def Attribute.as_string(bitmap) - return "none" if bitmap.zero? - res = [] - @@name_to_bitmap.each do |name, bit| - res << name if (bitmap & bit) != 0 - end - res.join(",") - end - - def Attribute.each_name_of(bitmap) - @@name_to_bitmap.each do |name, bit| - next if bit == SPECIAL - yield name.to_s if (bitmap & bit) != 0 - end - end - end - - ## - # An AttrChanger records a change in attributes. It contains a bitmap of the - # attributes to turn on, and a bitmap of those to turn off - - AttrChanger = Struct.new(:turn_on, :turn_off) - - class AttrChanger - def to_s - "Attr: +#{Attribute.as_string(@turn_on)}/-#{Attribute.as_string(@turn_on)}" - end - end - - ## - # An array of attributes which parallels the characters in a string - - class AttrSpan - def initialize(length) - @attrs = Array.new(length, 0) - end - - def set_attrs(start, length, bits) - for i in start ... (start+length) - @attrs[i] |= bits - end - end - - def [](n) - @attrs[n] - end - end - - ## - # Hold details of a special sequence - - class Special - attr_reader :type - attr_accessor :text - - def initialize(type, text) - @type, @text = type, text - end - - def ==(o) - self.text == o.text && self.type == o.type - end - - def to_s - "Special: type=#{type}, name=#{SM::Attribute.as_string type}, text=#{text.dump}" - end - - def inspect - "#" % [ - object_id, @type, SM::Attribute.as_string(type), text.dump] - end - end - - class AttributeManager - - NULL = "\000".freeze - - ## - # We work by substituting non-printing characters in to the text. For now - # I'm assuming that I can substitute a character in the range 0..8 for a 7 - # bit character without damaging the encoded string, but this might be - # optimistic - - A_PROTECT = 004 - PROTECT_ATTR = A_PROTECT.chr - - ## - # This maps delimiters that occur around words (such as *bold* or +tt+) - # where the start and end delimiters and the same. This lets us optimize - # the regexp - - MATCHING_WORD_PAIRS = {} - - ## - # And this is used when the delimiters aren't the same. In this case the - # hash maps a pattern to the attribute character - - WORD_PAIR_MAP = {} - - ## - # This maps HTML tags to the corresponding attribute char - - HTML_TAGS = {} - - ## - # And this maps _special_ sequences to a name. A special sequence is - # something like a WikiWord - - SPECIAL = {} - - ## - # Return an attribute object with the given turn_on and turn_off bits set - - def attribute(turn_on, turn_off) - AttrChanger.new(turn_on, turn_off) - end - - def change_attribute(current, new) - diff = current ^ new - attribute(new & diff, current & diff) - end - - def changed_attribute_by_name(current_set, new_set) - current = new = 0 - current_set.each {|name| current |= Attribute.bitmap_for(name) } - new_set.each {|name| new |= Attribute.bitmap_for(name) } - change_attribute(current, new) - end - - def copy_string(start_pos, end_pos) - res = @str[start_pos...end_pos] - res.gsub!(/\000/, '') - res - end - - ## - # Map attributes like textto the sequence - # \001\002\001\003, where is a per-attribute specific - # character - - def convert_attrs(str, attrs) - # first do matching ones - tags = MATCHING_WORD_PAIRS.keys.join("") - re = "(^|\\W)([#{tags}])([A-Za-z_]+?)\\2(\\W|\$)" -# re = "(^|\\W)([#{tags}])(\\S+?)\\2(\\W|\$)" - 1 while str.gsub!(Regexp.new(re)) { - attr = MATCHING_WORD_PAIRS[$2]; - attrs.set_attrs($`.length + $1.length + $2.length, $3.length, attr) - $1 + NULL*$2.length + $3 + NULL*$2.length + $4 - } - - # then non-matching - unless WORD_PAIR_MAP.empty? - WORD_PAIR_MAP.each do |regexp, attr| - str.gsub!(regexp) { - attrs.set_attrs($`.length + $1.length, $2.length, attr) - NULL*$1.length + $2 + NULL*$3.length - } - end - end - end - - def convert_html(str, attrs) - tags = HTML_TAGS.keys.join("|") - re = "<(#{tags})>(.*?)" - 1 while str.gsub!(Regexp.new(re, Regexp::IGNORECASE)) { - attr = HTML_TAGS[$1.downcase] - html_length = $1.length + 2 - seq = NULL * html_length - attrs.set_attrs($`.length + html_length, $2.length, attr) - seq + $2 + seq + NULL - } - end - - def convert_specials(str, attrs) - unless SPECIAL.empty? - SPECIAL.each do |regexp, attr| - str.scan(regexp) do - attrs.set_attrs($`.length, $&.length, attr | Attribute::SPECIAL) - end - end - end - end - - ## - # A \ in front of a character that would normally be processed turns off - # processing. We do this by turning \< into <#{PROTECT} - - PROTECTABLE = [ "<" << "\\" ] #" - - - def mask_protected_sequences - protect_pattern = Regexp.new("\\\\([#{Regexp.escape(PROTECTABLE.join(''))}])") - @str.gsub!(protect_pattern, "\\1#{PROTECT_ATTR}") - end - - def unmask_protected_sequences - @str.gsub!(/(.)#{PROTECT_ATTR}/, "\\1\000") - end - - def initialize - add_word_pair("*", "*", :BOLD) - add_word_pair("_", "_", :EM) - add_word_pair("+", "+", :TT) - - add_html("em", :EM) - add_html("i", :EM) - add_html("b", :BOLD) - add_html("tt", :TT) - add_html("code", :TT) - - add_special(//, :COMMENT) - end - - def add_word_pair(start, stop, name) - raise "Word flags may not start '<'" if start[0] == ?< - bitmap = Attribute.bitmap_for(name) - if start == stop - MATCHING_WORD_PAIRS[start] = bitmap - else - pattern = Regexp.new("(" + Regexp.escape(start) + ")" + -# "([A-Za-z]+)" + - "(\\S+)" + - "(" + Regexp.escape(stop) +")") - WORD_PAIR_MAP[pattern] = bitmap - end - PROTECTABLE << start[0,1] - PROTECTABLE.uniq! - end - - def add_html(tag, name) - HTML_TAGS[tag.downcase] = Attribute.bitmap_for(name) - end - - def add_special(pattern, name) - SPECIAL[pattern] = Attribute.bitmap_for(name) - end - - def flow(str) - @str = str - - puts("Before flow, str='#{@str.dump}'") if $DEBUG_RDOC - mask_protected_sequences - - @attrs = AttrSpan.new(@str.length) - - puts("After protecting, str='#{@str.dump}'") if $DEBUG_RDOC - convert_attrs(@str, @attrs) - convert_html(@str, @attrs) - convert_specials(str, @attrs) - unmask_protected_sequences - puts("After flow, str='#{@str.dump}'") if $DEBUG_RDOC - return split_into_flow - end - - def display_attributes - puts - puts @str.tr(NULL, "!") - bit = 1 - 16.times do |bno| - line = "" - @str.length.times do |i| - if (@attrs[i] & bit) == 0 - line << " " - else - if bno.zero? - line << "S" - else - line << ("%d" % (bno+1)) - end - end - end - puts(line) unless line =~ /^ *$/ - bit <<= 1 - end - end - - def split_into_flow - - display_attributes if $DEBUG_RDOC - - res = [] - current_attr = 0 - str = "" - - str_len = @str.length - - # skip leading invisible text - i = 0 - i += 1 while i < str_len and @str[i] == "\0" - start_pos = i - - # then scan the string, chunking it on attribute changes - while i < str_len - new_attr = @attrs[i] - if new_attr != current_attr - if i > start_pos - res << copy_string(start_pos, i) - start_pos = i - end - - res << change_attribute(current_attr, new_attr) - current_attr = new_attr - - if (current_attr & Attribute::SPECIAL) != 0 - i += 1 while i < str_len and (@attrs[i] & Attribute::SPECIAL) != 0 - res << Special.new(current_attr, copy_string(start_pos, i)) - start_pos = i - next - end - end - - # move on, skipping any invisible characters - begin - i += 1 - end while i < str_len and @str[i] == "\0" - end - - # tidy up trailing text - if start_pos < str_len - res << copy_string(start_pos, str_len) - end - - # and reset to all attributes off - res << change_attribute(current_attr, 0) if current_attr != 0 - - return res - end - - end - -end - diff --git a/lib/rdoc/markup/simple_markup/lines.rb b/lib/rdoc/markup/simple_markup/lines.rb deleted file mode 100644 index 8ef66079b2..0000000000 --- a/lib/rdoc/markup/simple_markup/lines.rb +++ /dev/null @@ -1,152 +0,0 @@ -module SM - - ## - # We store the lines we're working on as objects of class Line. These - # contain the text of the line, along with a flag indicating the line type, - # and an indentation level. - - class Line - INFINITY = 9999 - - BLANK = :BLANK - HEADING = :HEADING - LIST = :LIST - RULE = :RULE - PARAGRAPH = :PARAGRAPH - VERBATIM = :VERBATIM - - # line type - attr_accessor :type - - # The indentation nesting level - attr_accessor :level - - # The contents - attr_accessor :text - - # A prefix or parameter. For LIST lines, this is - # the text that introduced the list item (the label) - attr_accessor :param - - # A flag. For list lines, this is the type of the list - attr_accessor :flag - - # the number of leading spaces - attr_accessor :leading_spaces - - # true if this line has been deleted from the list of lines - attr_accessor :deleted - - - def initialize(text) - @text = text.dup - @deleted = false - - # expand tabs - 1 while @text.gsub!(/\t+/) { ' ' * (8*$&.length - $`.length % 8)} && $~ #` - - # Strip trailing whitespace - @text.sub!(/\s+$/, '') - - # and look for leading whitespace - if @text.length > 0 - @text =~ /^(\s*)/ - @leading_spaces = $1.length - else - @leading_spaces = INFINITY - end - end - - # Return true if this line is blank - def isBlank? - @text.length.zero? - end - - # stamp a line with a type, a level, a prefix, and a flag - def stamp(type, level, param="", flag=nil) - @type, @level, @param, @flag = type, level, param, flag - end - - ## - # Strip off the leading margin - # - - def strip_leading(size) - if @text.size > size - @text[0,size] = "" - else - @text = "" - end - end - - def to_s - "#@type#@level: #@text" - end - end - - ## - # A container for all the lines - - class Lines - - include Enumerable - - attr_reader :lines # :nodoc: - - def initialize(lines) - @lines = lines - rewind - end - - def empty? - @lines.size.zero? - end - - def each - @lines.each do |line| - yield line unless line.deleted - end - end - -# def [](index) -# @lines[index] -# end - - def rewind - @nextline = 0 - end - - def next - begin - res = @lines[@nextline] - @nextline += 1 if @nextline < @lines.size - end while res and res.deleted and @nextline < @lines.size - res - end - - def unget - @nextline -= 1 - end - - def delete(a_line) - a_line.deleted = true - end - - def normalize - margin = @lines.collect{|l| l.leading_spaces}.min - margin = 0 if margin == Line::INFINITY - @lines.each {|line| line.strip_leading(margin) } if margin > 0 - end - - def as_text - @lines.map {|l| l.text}.join("\n") - end - - def line_types - @lines.map {|l| l.type } - end - - end - -end - diff --git a/lib/rdoc/markup/simple_markup/preprocess.rb b/lib/rdoc/markup/simple_markup/preprocess.rb deleted file mode 100644 index c7d29ad48f..0000000000 --- a/lib/rdoc/markup/simple_markup/preprocess.rb +++ /dev/null @@ -1,73 +0,0 @@ -module SM - - ## - # Handle common directives that can occur in a block of text: - # - # : include : filename - - class PreProcess - - def initialize(input_file_name, include_path) - @input_file_name = input_file_name - @include_path = include_path - end - - ## - # Look for common options in a chunk of text. Options that we don't handle - # are passed back to our caller as |directive, param| - - def handle(text) - text.gsub!(/^([ \t#]*):(\w+):\s*(.+)?\n/) do - prefix = $1 - directive = $2.downcase - param = $3 - - case directive - when "include" - filename = param.split[0] - include_file(filename, prefix) - - else - yield(directive, param) - end - end - end - - private - - ## - # Include a file, indenting it correctly. - - def include_file(name, indent) - if (full_name = find_include_file(name)) - content = File.open(full_name) {|f| f.read} - # strip leading '#'s, but only if all lines start with them - if content =~ /^[^#]/ - content.gsub(/^/, indent) - else - content.gsub(/^#?/, indent) - end - else - $stderr.puts "Couldn't find file to include: '#{name}'" - '' - end - end - - ## - # Look for the given file in the directory containing the current file, - # and then in each of the directories specified in the RDOC_INCLUDE path - - def find_include_file(name) - to_search = [ File.dirname(@input_file_name) ].concat @include_path - to_search.each do |dir| - full_name = File.join(dir, name) - stat = File.stat(full_name) rescue next - return full_name if stat.readable? - end - nil - end - - end - -end - diff --git a/lib/rdoc/markup/simple_markup/to_flow.rb b/lib/rdoc/markup/simple_markup/to_flow.rb deleted file mode 100644 index fc8c5f5ce6..0000000000 --- a/lib/rdoc/markup/simple_markup/to_flow.rb +++ /dev/null @@ -1,182 +0,0 @@ -require 'rdoc/markup/simple_markup/fragments' -require 'rdoc/markup/simple_markup/inline' -require 'cgi' - -module SM - - module Flow - P = Struct.new(:body) - VERB = Struct.new(:body) - RULE = Struct.new(:width) - class LIST - attr_reader :type, :contents - def initialize(type) - @type = type - @contents = [] - end - def <<(stuff) - @contents << stuff - end - end - LI = Struct.new(:label, :body) - H = Struct.new(:level, :text) - end - - class ToFlow - LIST_TYPE_TO_HTML = { - SM::ListBase::BULLET => [ "
    ", "
" ], - SM::ListBase::NUMBER => [ "
    ", "
" ], - SM::ListBase::UPPERALPHA => [ "
    ", "
" ], - SM::ListBase::LOWERALPHA => [ "
    ", "
" ], - SM::ListBase::LABELED => [ "
", "
" ], - SM::ListBase::NOTE => [ "", "
" ], - } - - InlineTag = Struct.new(:bit, :on, :off) - - def initialize - init_tags - end - - ## - # Set up the standard mapping of attributes to HTML tags - - def init_tags - @attr_tags = [ - InlineTag.new(SM::Attribute.bitmap_for(:BOLD), "", ""), - InlineTag.new(SM::Attribute.bitmap_for(:TT), "", ""), - InlineTag.new(SM::Attribute.bitmap_for(:EM), "", ""), - ] - end - - ## - # Add a new set of HTML tags for an attribute. We allow separate start and - # end tags for flexibility - - def add_tag(name, start, stop) - @attr_tags << InlineTag.new(SM::Attribute.bitmap_for(name), start, stop) - end - - ## - # Given an HTML tag, decorate it with class information and the like if - # required. This is a no-op in the base class, but is overridden in HTML - # output classes that implement style sheets - - def annotate(tag) - tag - end - - ## - # Here's the client side of the visitor pattern - - def start_accepting - @res = [] - @list_stack = [] - end - - def end_accepting - @res - end - - def accept_paragraph(am, fragment) - @res << Flow::P.new((convert_flow(am.flow(fragment.txt)))) - end - - def accept_verbatim(am, fragment) - @res << Flow::VERB.new((convert_flow(am.flow(fragment.txt)))) - end - - def accept_rule(am, fragment) - size = fragment.param - size = 10 if size > 10 - @res << Flow::RULE.new(size) - end - - def accept_list_start(am, fragment) - @list_stack.push(@res) - list = Flow::LIST.new(fragment.type) - @res << list - @res = list - end - - def accept_list_end(am, fragment) - @res = @list_stack.pop - end - - def accept_list_item(am, fragment) - @res << Flow::LI.new(fragment.param, convert_flow(am.flow(fragment.txt))) - end - - def accept_blank_line(am, fragment) - # @res << annotate("

") << "\n" - end - - def accept_heading(am, fragment) - @res << Flow::H.new(fragment.head_level, convert_flow(am.flow(fragment.txt))) - end - - private - - def on_tags(res, item) - attr_mask = item.turn_on - return if attr_mask.zero? - - @attr_tags.each do |tag| - if attr_mask & tag.bit != 0 - res << annotate(tag.on) - end - end - end - - def off_tags(res, item) - attr_mask = item.turn_off - return if attr_mask.zero? - - @attr_tags.reverse_each do |tag| - if attr_mask & tag.bit != 0 - res << annotate(tag.off) - end - end - end - - def convert_flow(flow) - res = "" - flow.each do |item| - case item - when String - res << convert_string(item) - when AttrChanger - off_tags(res, item) - on_tags(res, item) - when Special - res << convert_special(item) - else - raise "Unknown flow element: #{item.inspect}" - end - end - res - end - - def convert_string(item) - CGI.escapeHTML(item) - end - - def convert_special(special) - handled = false - Attribute.each_name_of(special.type) do |name| - method_name = "handle_special_#{name}" - if self.respond_to? method_name - special.text = send(method_name, special) - handled = true - end - end - - raise "Unhandled special: #{special}" unless handled - - special.text - end - - end - -end - diff --git a/lib/rdoc/markup/simple_markup/to_html.rb b/lib/rdoc/markup/simple_markup/to_html.rb deleted file mode 100644 index d78468684f..0000000000 --- a/lib/rdoc/markup/simple_markup/to_html.rb +++ /dev/null @@ -1,287 +0,0 @@ -require 'rdoc/markup/simple_markup/fragments' -require 'rdoc/markup/simple_markup/inline' - -require 'cgi' - -module SM - - class ToHtml - - LIST_TYPE_TO_HTML = { - ListBase::BULLET => [ "

    ", "
" ], - ListBase::NUMBER => [ "
    ", "
" ], - ListBase::UPPERALPHA => [ "
    ", "
" ], - ListBase::LOWERALPHA => [ "
    ", "
" ], - ListBase::LABELED => [ "
", "
" ], - ListBase::NOTE => [ "", "
" ], - } - - InlineTag = Struct.new(:bit, :on, :off) - - def initialize - init_tags - end - - ## - # Set up the standard mapping of attributes to HTML tags - - def init_tags - @attr_tags = [ - InlineTag.new(SM::Attribute.bitmap_for(:BOLD), "", ""), - InlineTag.new(SM::Attribute.bitmap_for(:TT), "", ""), - InlineTag.new(SM::Attribute.bitmap_for(:EM), "", ""), - ] - end - - ## - # Add a new set of HTML tags for an attribute. We allow separate start and - # end tags for flexibility. - - def add_tag(name, start, stop) - @attr_tags << InlineTag.new(SM::Attribute.bitmap_for(name), start, stop) - end - - ## - # Given an HTML tag, decorate it with class information and the like if - # required. This is a no-op in the base class, but is overridden in HTML - # output classes that implement style sheets. - - def annotate(tag) - tag - end - - ## - # Here's the client side of the visitor pattern - - def start_accepting - @res = "" - @in_list_entry = [] - end - - def end_accepting - @res - end - - def accept_paragraph(am, fragment) - @res << annotate("

") + "\n" - @res << wrap(convert_flow(am.flow(fragment.txt))) - @res << annotate("

") + "\n" - end - - def accept_verbatim(am, fragment) - @res << annotate("
") + "\n"
-      @res << CGI.escapeHTML(fragment.txt)
-      @res << annotate("
") << "\n" - end - - def accept_rule(am, fragment) - size = fragment.param - size = 10 if size > 10 - @res << "
" - end - - def accept_list_start(am, fragment) - @res << html_list_name(fragment.type, true) << "\n" - @in_list_entry.push false - end - - def accept_list_end(am, fragment) - if tag = @in_list_entry.pop - @res << annotate(tag) << "\n" - end - @res << html_list_name(fragment.type, false) << "\n" - end - - def accept_list_item(am, fragment) - if tag = @in_list_entry.last - @res << annotate(tag) << "\n" - end - @res << list_item_start(am, fragment) - @res << wrap(convert_flow(am.flow(fragment.txt))) << "\n" - @in_list_entry[-1] = list_end_for(fragment.type) - end - - def accept_blank_line(am, fragment) - # @res << annotate("

") << "\n" - end - - def accept_heading(am, fragment) - @res << convert_heading(fragment.head_level, am.flow(fragment.txt)) - end - - ## - # This is a higher speed (if messier) version of wrap - - def wrap(txt, line_len = 76) - res = "" - sp = 0 - ep = txt.length - while sp < ep - # scan back for a space - p = sp + line_len - 1 - if p >= ep - p = ep - else - while p > sp and txt[p] != ?\s - p -= 1 - end - if p <= sp - p = sp + line_len - while p < ep and txt[p] != ?\s - p += 1 - end - end - end - res << txt[sp...p] << "\n" - sp = p - sp += 1 while sp < ep and txt[sp] == ?\s - end - res - end - - private - - def on_tags(res, item) - attr_mask = item.turn_on - return if attr_mask.zero? - - @attr_tags.each do |tag| - if attr_mask & tag.bit != 0 - res << annotate(tag.on) - end - end - end - - def off_tags(res, item) - attr_mask = item.turn_off - return if attr_mask.zero? - - @attr_tags.reverse_each do |tag| - if attr_mask & tag.bit != 0 - res << annotate(tag.off) - end - end - end - - def convert_flow(flow) - res = "" - flow.each do |item| - case item - when String - res << convert_string(item) - when AttrChanger - off_tags(res, item) - on_tags(res, item) - when Special - res << convert_special(item) - else - raise "Unknown flow element: #{item.inspect}" - end - end - res - end - - ## - # some of these patterns are taken from SmartyPants... - - def convert_string(item) - CGI.escapeHTML(item). - - # convert -- to em-dash, (-- to en-dash) - gsub(/---?/, '—'). #gsub(/--/, '–'). - - # convert ... to elipsis (and make sure .... becomes .) - gsub(/\.\.\.\./, '.…').gsub(/\.\.\./, '…'). - - # convert single closing quote - gsub(%r{([^ \t\r\n\[\{\(])\'}) { "#$1’" }. - gsub(%r{\'(?=\W|s\b)}) { "’" }. - - # convert single opening quote - gsub(/'/, '‘'). - - # convert double closing quote - gsub(%r{([^ \t\r\n\[\{\(])\'(?=\W)}) { "#$1”" }. - - # convert double opening quote - gsub(/'/, '“'). - - # convert copyright - gsub(/\(c\)/, '©'). - - # convert and registered trademark - gsub(/\(r\)/, '®') - - end - - def convert_special(special) - handled = false - Attribute.each_name_of(special.type) do |name| - method_name = "handle_special_#{name}" - if self.respond_to? method_name - special.text = send(method_name, special) - handled = true - end - end - raise "Unhandled special: #{special}" unless handled - special.text - end - - def convert_heading(level, flow) - res = - annotate("") + - convert_flow(flow) + - annotate("\n") - end - - def html_list_name(list_type, is_open_tag) - tags = LIST_TYPE_TO_HTML[list_type] || raise("Invalid list type: #{list_type.inspect}") - annotate(tags[ is_open_tag ? 0 : 1]) - end - - def list_item_start(am, fragment) - case fragment.type - when ListBase::BULLET, ListBase::NUMBER - annotate("

  • ") - - when ListBase::UPPERALPHA - annotate("
  • ") - - when ListBase::LOWERALPHA - annotate("
  • ") - - when ListBase::LABELED - annotate("
    ") + - convert_flow(am.flow(fragment.param)) + - annotate("
    ") + - annotate("
    ") - - when ListBase::NOTE - annotate("") + - annotate("") + - convert_flow(am.flow(fragment.param)) + - annotate("") + - annotate("") - else - raise "Invalid list type" - end - end - - def list_end_for(fragment_type) - case fragment_type - when ListBase::BULLET, ListBase::NUMBER, ListBase::UPPERALPHA, - ListBase::LOWERALPHA - "
  • " - when ListBase::LABELED - "" - when ListBase::NOTE - "" - else - raise "Invalid list type" - end - end - - end - -end - diff --git a/lib/rdoc/markup/simple_markup/to_latex.rb b/lib/rdoc/markup/simple_markup/to_latex.rb deleted file mode 100644 index b014b6990b..0000000000 --- a/lib/rdoc/markup/simple_markup/to_latex.rb +++ /dev/null @@ -1,332 +0,0 @@ -require 'rdoc/markup/simple_markup/fragments' -require 'rdoc/markup/simple_markup/inline' - -require 'cgi' - -module SM - - # Convert SimpleMarkup to basic LaTeX report format - - class ToLaTeX - - BS = "\020" # \ - OB = "\021" # { - CB = "\022" # } - DL = "\023" # Dollar - - BACKSLASH = "#{BS}symbol#{OB}92#{CB}" - HAT = "#{BS}symbol#{OB}94#{CB}" - BACKQUOTE = "#{BS}symbol#{OB}0#{CB}" - TILDE = "#{DL}#{BS}sim#{DL}" - LESSTHAN = "#{DL}<#{DL}" - GREATERTHAN = "#{DL}>#{DL}" - - def self.l(str) - str.tr('\\', BS).tr('{', OB).tr('}', CB).tr('$', DL) - end - - def l(arg) - SM::ToLaTeX.l(arg) - end - - LIST_TYPE_TO_LATEX = { - ListBase::BULLET => [ l("\\begin{itemize}"), l("\\end{itemize}") ], - ListBase::NUMBER => [ l("\\begin{enumerate}"), l("\\end{enumerate}"), "\\arabic" ], - ListBase::UPPERALPHA => [ l("\\begin{enumerate}"), l("\\end{enumerate}"), "\\Alph" ], - ListBase::LOWERALPHA => [ l("\\begin{enumerate}"), l("\\end{enumerate}"), "\\alph" ], - ListBase::LABELED => [ l("\\begin{description}"), l("\\end{description}") ], - ListBase::NOTE => [ - l("\\begin{tabularx}{\\linewidth}{@{} l X @{}}"), - l("\\end{tabularx}") ], - } - - InlineTag = Struct.new(:bit, :on, :off) - - def initialize - init_tags - @list_depth = 0 - @prev_list_types = [] - end - - ## - # Set up the standard mapping of attributes to LaTeX - - def init_tags - @attr_tags = [ - InlineTag.new(SM::Attribute.bitmap_for(:BOLD), l("\\textbf{"), l("}")), - InlineTag.new(SM::Attribute.bitmap_for(:TT), l("\\texttt{"), l("}")), - InlineTag.new(SM::Attribute.bitmap_for(:EM), l("\\emph{"), l("}")), - ] - end - - ## - # Escape a LaTeX string - - def escape(str) -# $stderr.print "FE: ", str - s = str. -# sub(/\s+$/, ''). - gsub(/([_\${}&%#])/, "#{BS}\\1"). - gsub(/\\/, BACKSLASH). - gsub(/\^/, HAT). - gsub(/~/, TILDE). - gsub(//, GREATERTHAN). - gsub(/,,/, ",{},"). - gsub(/\`/, BACKQUOTE) -# $stderr.print "-> ", s, "\n" - s - end - - ## - # Add a new set of LaTeX tags for an attribute. We allow - # separate start and end tags for flexibility - - def add_tag(name, start, stop) - @attr_tags << InlineTag.new(SM::Attribute.bitmap_for(name), start, stop) - end - - ## - # Here's the client side of the visitor pattern - - def start_accepting - @res = "" - @in_list_entry = [] - end - - def end_accepting - @res.tr(BS, '\\').tr(OB, '{').tr(CB, '}').tr(DL, '$') - end - - def accept_paragraph(am, fragment) - @res << wrap(convert_flow(am.flow(fragment.txt))) - @res << "\n" - end - - def accept_verbatim(am, fragment) - @res << "\n\\begin{code}\n" - @res << fragment.txt.sub(/[\n\s]+\Z/, '') - @res << "\n\\end{code}\n\n" - end - - def accept_rule(am, fragment) - size = fragment.param - size = 10 if size > 10 - @res << "\n\n\\rule{\\linewidth}{#{size}pt}\n\n" - end - - def accept_list_start(am, fragment) - @res << list_name(fragment.type, true) << "\n" - @in_list_entry.push false - end - - def accept_list_end(am, fragment) - if tag = @in_list_entry.pop - @res << tag << "\n" - end - @res << list_name(fragment.type, false) << "\n" - end - - def accept_list_item(am, fragment) - if tag = @in_list_entry.last - @res << tag << "\n" - end - @res << list_item_start(am, fragment) - @res << wrap(convert_flow(am.flow(fragment.txt))) << "\n" - @in_list_entry[-1] = list_end_for(fragment.type) - end - - def accept_blank_line(am, fragment) - # @res << "\n" - end - - def accept_heading(am, fragment) - @res << convert_heading(fragment.head_level, am.flow(fragment.txt)) - end - - ## - # This is a higher speed (if messier) version of wrap - - def wrap(txt, line_len = 76) - res = "" - sp = 0 - ep = txt.length - while sp < ep - # scan back for a space - p = sp + line_len - 1 - if p >= ep - p = ep - else - while p > sp and txt[p] != ?\s - p -= 1 - end - if p <= sp - p = sp + line_len - while p < ep and txt[p] != ?\s - p += 1 - end - end - end - res << txt[sp...p] << "\n" - sp = p - sp += 1 while sp < ep and txt[sp] == ?\s - end - res - end - - private - - def on_tags(res, item) - attr_mask = item.turn_on - return if attr_mask.zero? - - @attr_tags.each do |tag| - if attr_mask & tag.bit != 0 - res << tag.on - end - end - end - - def off_tags(res, item) - attr_mask = item.turn_off - return if attr_mask.zero? - - @attr_tags.reverse_each do |tag| - if attr_mask & tag.bit != 0 - res << tag.off - end - end - end - - def convert_flow(flow) - res = "" - flow.each do |item| - case item - when String -# $stderr.puts "Converting '#{item}'" - res << convert_string(item) - when AttrChanger - off_tags(res, item) - on_tags(res, item) - when Special - res << convert_special(item) - else - raise "Unknown flow element: #{item.inspect}" - end - end - res - end - - ## - # some of these patterns are taken from SmartyPants... - - def convert_string(item) - escape(item). - - # convert ... to elipsis (and make sure .... becomes .) - gsub(/\.\.\.\./, '.\ldots{}').gsub(/\.\.\./, '\ldots{}'). - - # convert single closing quote - gsub(%r{([^ \t\r\n\[\{\(])\'}) { "#$1'" }. - gsub(%r{\'(?=\W|s\b)}) { "'" }. - - # convert single opening quote - gsub(/'/, '`'). - - # convert double closing quote - gsub(%r{([^ \t\r\n\[\{\(])\"(?=\W)}) { "#$1''" }. - - # convert double opening quote - gsub(/"/, "``"). - - # convert copyright - gsub(/\(c\)/, '\copyright{}') - - end - - def convert_special(special) - handled = false - Attribute.each_name_of(special.type) do |name| - method_name = "handle_special_#{name}" - if self.respond_to? method_name - special.text = send(method_name, special) - handled = true - end - end - raise "Unhandled special: #{special}" unless handled - special.text - end - - def convert_heading(level, flow) - res = - case level - when 1 then "\\chapter{" - when 2 then "\\section{" - when 3 then "\\subsection{" - when 4 then "\\subsubsection{" - else "\\paragraph{" - end + - convert_flow(flow) + - "}\n" - end - - def list_name(list_type, is_open_tag) - tags = LIST_TYPE_TO_LATEX[list_type] || raise("Invalid list type: #{list_type.inspect}") - if tags[2] # enumerate - if is_open_tag - @list_depth += 1 - if @prev_list_types[@list_depth] != tags[2] - case @list_depth - when 1 - roman = "i" - when 2 - roman = "ii" - when 3 - roman = "iii" - when 4 - roman = "iv" - else - raise("Too deep list: level #{@list_depth}") - end - @prev_list_types[@list_depth] = tags[2] - return l("\\renewcommand{\\labelenum#{roman}}{#{tags[2]}{enum#{roman}}}") + "\n" + tags[0] - end - else - @list_depth -= 1 - end - end - tags[ is_open_tag ? 0 : 1] - end - - def list_item_start(am, fragment) - case fragment.type - when ListBase::BULLET, ListBase::NUMBER, ListBase::UPPERALPHA, - ListBase::LOWERALPHA - "\\item " - - when ListBase::LABELED - "\\item[" + convert_flow(am.flow(fragment.param)) + "] " - - when ListBase::NOTE - convert_flow(am.flow(fragment.param)) + " & " - else - raise "Invalid list type" - end - end - - def list_end_for(fragment_type) - case fragment_type - when ListBase::BULLET, ListBase::NUMBER, ListBase::UPPERALPHA, - ListBase::LOWERALPHA, ListBase::LABELED - "" - when ListBase::NOTE - "\\\\\n" - else - raise "Invalid list type" - end - end - - end - -end - diff --git a/lib/rdoc/markup/to_flow.rb b/lib/rdoc/markup/to_flow.rb new file mode 100644 index 0000000000..58ae52be24 --- /dev/null +++ b/lib/rdoc/markup/to_flow.rb @@ -0,0 +1,182 @@ +require 'rdoc/markup/fragments' +require 'rdoc/markup/inline' +require 'cgi' + +class RDoc::Markup + + module Flow + P = Struct.new(:body) + VERB = Struct.new(:body) + RULE = Struct.new(:width) + class LIST + attr_reader :type, :contents + def initialize(type) + @type = type + @contents = [] + end + def <<(stuff) + @contents << stuff + end + end + LI = Struct.new(:label, :body) + H = Struct.new(:level, :text) + end + + class ToFlow + LIST_TYPE_TO_HTML = { + RDoc::Markup::ListBase::BULLET => [ "
      ", "
    " ], + RDoc::Markup::ListBase::NUMBER => [ "
      ", "
    " ], + RDoc::Markup::ListBase::UPPERALPHA => [ "
      ", "
    " ], + RDoc::Markup::ListBase::LOWERALPHA => [ "
      ", "
    " ], + RDoc::Markup::ListBase::LABELED => [ "
    ", "
    " ], + RDoc::Markup::ListBase::NOTE => [ "", "
    " ], + } + + InlineTag = Struct.new(:bit, :on, :off) + + def initialize + init_tags + end + + ## + # Set up the standard mapping of attributes to HTML tags + + def init_tags + @attr_tags = [ + InlineTag.new(RDoc::Markup::Attribute.bitmap_for(:BOLD), "", ""), + InlineTag.new(RDoc::Markup::Attribute.bitmap_for(:TT), "", ""), + InlineTag.new(RDoc::Markup::Attribute.bitmap_for(:EM), "", ""), + ] + end + + ## + # Add a new set of HTML tags for an attribute. We allow separate start and + # end tags for flexibility + + def add_tag(name, start, stop) + @attr_tags << InlineTag.new(RDoc::Markup::Attribute.bitmap_for(name), start, stop) + end + + ## + # Given an HTML tag, decorate it with class information and the like if + # required. This is a no-op in the base class, but is overridden in HTML + # output classes that implement style sheets + + def annotate(tag) + tag + end + + ## + # Here's the client side of the visitor pattern + + def start_accepting + @res = [] + @list_stack = [] + end + + def end_accepting + @res + end + + def accept_paragraph(am, fragment) + @res << Flow::P.new((convert_flow(am.flow(fragment.txt)))) + end + + def accept_verbatim(am, fragment) + @res << Flow::VERB.new((convert_flow(am.flow(fragment.txt)))) + end + + def accept_rule(am, fragment) + size = fragment.param + size = 10 if size > 10 + @res << Flow::RULE.new(size) + end + + def accept_list_start(am, fragment) + @list_stack.push(@res) + list = Flow::LIST.new(fragment.type) + @res << list + @res = list + end + + def accept_list_end(am, fragment) + @res = @list_stack.pop + end + + def accept_list_item(am, fragment) + @res << Flow::LI.new(fragment.param, convert_flow(am.flow(fragment.txt))) + end + + def accept_blank_line(am, fragment) + # @res << annotate("

    ") << "\n" + end + + def accept_heading(am, fragment) + @res << Flow::H.new(fragment.head_level, convert_flow(am.flow(fragment.txt))) + end + + private + + def on_tags(res, item) + attr_mask = item.turn_on + return if attr_mask.zero? + + @attr_tags.each do |tag| + if attr_mask & tag.bit != 0 + res << annotate(tag.on) + end + end + end + + def off_tags(res, item) + attr_mask = item.turn_off + return if attr_mask.zero? + + @attr_tags.reverse_each do |tag| + if attr_mask & tag.bit != 0 + res << annotate(tag.off) + end + end + end + + def convert_flow(flow) + res = "" + flow.each do |item| + case item + when String + res << convert_string(item) + when AttrChanger + off_tags(res, item) + on_tags(res, item) + when Special + res << convert_special(item) + else + raise "Unknown flow element: #{item.inspect}" + end + end + res + end + + def convert_string(item) + CGI.escapeHTML(item) + end + + def convert_special(special) + handled = false + Attribute.each_name_of(special.type) do |name| + method_name = "handle_special_#{name}" + if self.respond_to? method_name + special.text = send(method_name, special) + handled = true + end + end + + raise "Unhandled special: #{special}" unless handled + + special.text + end + + end + +end + diff --git a/lib/rdoc/markup/to_html.rb b/lib/rdoc/markup/to_html.rb new file mode 100644 index 0000000000..98259cfd16 --- /dev/null +++ b/lib/rdoc/markup/to_html.rb @@ -0,0 +1,286 @@ +require 'rdoc/markup/fragments' +require 'rdoc/markup/inline' + +require 'cgi' + +class RDoc::Markup::ToHtml + + LIST_TYPE_TO_HTML = { + RDoc::Markup::ListBase::BULLET => [ "

      ", "
    " ], + RDoc::Markup::ListBase::NUMBER => [ "
      ", "
    " ], + RDoc::Markup::ListBase::UPPERALPHA => [ "
      ", "
    " ], + RDoc::Markup::ListBase::LOWERALPHA => [ "
      ", "
    " ], + RDoc::Markup::ListBase::LABELED => [ "
    ", "
    " ], + RDoc::Markup::ListBase::NOTE => [ "", "
    " ], + } + + InlineTag = Struct.new(:bit, :on, :off) + + def initialize + init_tags + end + + ## + # Set up the standard mapping of attributes to HTML tags + + def init_tags + @attr_tags = [ + InlineTag.new(RDoc::Markup::Attribute.bitmap_for(:BOLD), "", ""), + InlineTag.new(RDoc::Markup::Attribute.bitmap_for(:TT), "", ""), + InlineTag.new(RDoc::Markup::Attribute.bitmap_for(:EM), "", ""), + ] + end + + ## + # Add a new set of HTML tags for an attribute. We allow separate start and + # end tags for flexibility. + + def add_tag(name, start, stop) + @attr_tags << InlineTag.new(RDoc::Markup::Attribute.bitmap_for(name), start, stop) + end + + ## + # Given an HTML tag, decorate it with class information and the like if + # required. This is a no-op in the base class, but is overridden in HTML + # output classes that implement style sheets. + + def annotate(tag) + tag + end + + ## + # Here's the client side of the visitor pattern + + def start_accepting + @res = "" + @in_list_entry = [] + end + + def end_accepting + @res + end + + def accept_paragraph(am, fragment) + @res << annotate("

    ") + "\n" + @res << wrap(convert_flow(am.flow(fragment.txt))) + @res << annotate("

    ") + "\n" + end + + def accept_verbatim(am, fragment) + @res << annotate("
    ") + "\n"
    +    @res << CGI.escapeHTML(fragment.txt)
    +    @res << annotate("
    ") << "\n" + end + + def accept_rule(am, fragment) + size = fragment.param + size = 10 if size > 10 + @res << "
    " + end + + def accept_list_start(am, fragment) + @res << html_list_name(fragment.type, true) << "\n" + @in_list_entry.push false + end + + def accept_list_end(am, fragment) + if tag = @in_list_entry.pop + @res << annotate(tag) << "\n" + end + @res << html_list_name(fragment.type, false) << "\n" + end + + def accept_list_item(am, fragment) + if tag = @in_list_entry.last + @res << annotate(tag) << "\n" + end + @res << list_item_start(am, fragment) + @res << wrap(convert_flow(am.flow(fragment.txt))) << "\n" + @in_list_entry[-1] = list_end_for(fragment.type) + end + + def accept_blank_line(am, fragment) + # @res << annotate("

    ") << "\n" + end + + def accept_heading(am, fragment) + @res << convert_heading(fragment.head_level, am.flow(fragment.txt)) + end + + ## + # This is a higher speed (if messier) version of wrap + + def wrap(txt, line_len = 76) + res = "" + sp = 0 + ep = txt.length + while sp < ep + # scan back for a space + p = sp + line_len - 1 + if p >= ep + p = ep + else + while p > sp and txt[p] != ?\s + p -= 1 + end + if p <= sp + p = sp + line_len + while p < ep and txt[p] != ?\s + p += 1 + end + end + end + res << txt[sp...p] << "\n" + sp = p + sp += 1 while sp < ep and txt[sp] == ?\s + end + res + end + + private + + def on_tags(res, item) + attr_mask = item.turn_on + return if attr_mask.zero? + + @attr_tags.each do |tag| + if attr_mask & tag.bit != 0 + res << annotate(tag.on) + end + end + end + + def off_tags(res, item) + attr_mask = item.turn_off + return if attr_mask.zero? + + @attr_tags.reverse_each do |tag| + if attr_mask & tag.bit != 0 + res << annotate(tag.off) + end + end + end + + def convert_flow(flow) + res = "" + + flow.each do |item| + case item + when String + res << convert_string(item) + when RDoc::Markup::AttrChanger + off_tags(res, item) + on_tags(res, item) + when RDoc::Markup::Special + res << convert_special(item) + else + raise "Unknown flow element: #{item.inspect}" + end + end + + res + end + + ## + # some of these patterns are taken from SmartyPants... + + def convert_string(item) + CGI.escapeHTML(item). + + # convert -- to em-dash, (-- to en-dash) + gsub(/---?/, '—'). #gsub(/--/, '–'). + + # convert ... to elipsis (and make sure .... becomes .) + gsub(/\.\.\.\./, '.…').gsub(/\.\.\./, '…'). + + # convert single closing quote + gsub(%r{([^ \t\r\n\[\{\(])\'}) { "#$1’" }. + gsub(%r{\'(?=\W|s\b)}) { "’" }. + + # convert single opening quote + gsub(/'/, '‘'). + + # convert double closing quote + gsub(%r{([^ \t\r\n\[\{\(])\'(?=\W)}) { "#$1”" }. + + # convert double opening quote + gsub(/'/, '“'). + + # convert copyright + gsub(/\(c\)/, '©'). + + # convert and registered trademark + gsub(/\(r\)/, '®') + + end + + def convert_special(special) + handled = false + RDoc::Markup::Attribute.each_name_of(special.type) do |name| + method_name = "handle_special_#{name}" + if self.respond_to? method_name + special.text = send(method_name, special) + handled = true + end + end + raise "Unhandled special: #{special}" unless handled + special.text + end + + def convert_heading(level, flow) + res = + annotate("") + + convert_flow(flow) + + annotate("\n") + end + + def html_list_name(list_type, is_open_tag) + tags = LIST_TYPE_TO_HTML[list_type] || raise("Invalid list type: #{list_type.inspect}") + annotate(tags[ is_open_tag ? 0 : 1]) + end + + def list_item_start(am, fragment) + case fragment.type + when RDoc::Markup::ListBase::BULLET, RDoc::Markup::ListBase::NUMBER then + annotate("

  • ") + + when RDoc::Markup::ListBase::UPPERALPHA then + annotate("
  • ") + + when RDoc::Markup::ListBase::LOWERALPHA then + annotate("
  • ") + + when RDoc::Markup::ListBase::LABELED then + annotate("
    ") + + convert_flow(am.flow(fragment.param)) + + annotate("
    ") + + annotate("
    ") + + when RDoc::Markup::ListBase::NOTE then + annotate("") + + annotate("") + + convert_flow(am.flow(fragment.param)) + + annotate("") + + annotate("") + else + raise "Invalid list type" + end + end + + def list_end_for(fragment_type) + case fragment_type + when RDoc::Markup::ListBase::BULLET, RDoc::Markup::ListBase::NUMBER, + RDoc::Markup::ListBase::UPPERALPHA, + RDoc::Markup::ListBase::LOWERALPHA then + "
  • " + when RDoc::Markup::ListBase::LABELED then + "" + when RDoc::Markup::ListBase::NOTE then + "" + else + raise "Invalid list type" + end + end + +end + diff --git a/lib/rdoc/markup/to_latex.rb b/lib/rdoc/markup/to_latex.rb new file mode 100644 index 0000000000..2964e315bd --- /dev/null +++ b/lib/rdoc/markup/to_latex.rb @@ -0,0 +1,331 @@ +require 'rdoc/markup/fragments' +require 'rdoc/markup/inline' + +require 'cgi' + +## +# Convert SimpleMarkup to basic LaTeX report format. + +class RDoc::Markup::ToLaTeX + + BS = "\020" # \ + OB = "\021" # { + CB = "\022" # } + DL = "\023" # Dollar + + BACKSLASH = "#{BS}symbol#{OB}92#{CB}" + HAT = "#{BS}symbol#{OB}94#{CB}" + BACKQUOTE = "#{BS}symbol#{OB}0#{CB}" + TILDE = "#{DL}#{BS}sim#{DL}" + LESSTHAN = "#{DL}<#{DL}" + GREATERTHAN = "#{DL}>#{DL}" + + def self.l(str) + str.tr('\\', BS).tr('{', OB).tr('}', CB).tr('$', DL) + end + + def l(arg) + RDoc::Markup::ToLaTeX.l(arg) + end + + LIST_TYPE_TO_LATEX = { + ListBase::BULLET => [ l("\\begin{itemize}"), l("\\end{itemize}") ], + ListBase::NUMBER => [ l("\\begin{enumerate}"), l("\\end{enumerate}"), "\\arabic" ], + ListBase::UPPERALPHA => [ l("\\begin{enumerate}"), l("\\end{enumerate}"), "\\Alph" ], + ListBase::LOWERALPHA => [ l("\\begin{enumerate}"), l("\\end{enumerate}"), "\\alph" ], + ListBase::LABELED => [ l("\\begin{description}"), l("\\end{description}") ], + ListBase::NOTE => [ + l("\\begin{tabularx}{\\linewidth}{@{} l X @{}}"), + l("\\end{tabularx}") ], + } + + InlineTag = Struct.new(:bit, :on, :off) + + def initialize + init_tags + @list_depth = 0 + @prev_list_types = [] + end + + ## + # Set up the standard mapping of attributes to LaTeX + + def init_tags + @attr_tags = [ + InlineTag.new(RDoc::Markup::Attribute.bitmap_for(:BOLD), l("\\textbf{"), l("}")), + InlineTag.new(RDoc::Markup::Attribute.bitmap_for(:TT), l("\\texttt{"), l("}")), + InlineTag.new(RDoc::Markup::Attribute.bitmap_for(:EM), l("\\emph{"), l("}")), + ] + end + + ## + # Escape a LaTeX string + + def escape(str) +$stderr.print "FE: ", str + s = str. + sub(/\s+$/, ''). + gsub(/([_\${}&%#])/, "#{BS}\\1"). + gsub(/\\/, BACKSLASH). + gsub(/\^/, HAT). + gsub(/~/, TILDE). + gsub(//, GREATERTHAN). + gsub(/,,/, ",{},"). + gsub(/\`/, BACKQUOTE) +$stderr.print "-> ", s, "\n" + s + end + + ## + # Add a new set of LaTeX tags for an attribute. We allow + # separate start and end tags for flexibility + + def add_tag(name, start, stop) + @attr_tags << InlineTag.new(RDoc::Markup::Attribute.bitmap_for(name), start, stop) + end + + ## + # Here's the client side of the visitor pattern + + def start_accepting + @res = "" + @in_list_entry = [] + end + + def end_accepting + @res.tr(BS, '\\').tr(OB, '{').tr(CB, '}').tr(DL, '$') + end + + def accept_paragraph(am, fragment) + @res << wrap(convert_flow(am.flow(fragment.txt))) + @res << "\n" + end + + def accept_verbatim(am, fragment) + @res << "\n\\begin{code}\n" + @res << fragment.txt.sub(/[\n\s]+\Z/, '') + @res << "\n\\end{code}\n\n" + end + + def accept_rule(am, fragment) + size = fragment.param + size = 10 if size > 10 + @res << "\n\n\\rule{\\linewidth}{#{size}pt}\n\n" + end + + def accept_list_start(am, fragment) + @res << list_name(fragment.type, true) << "\n" + @in_list_entry.push false + end + + def accept_list_end(am, fragment) + if tag = @in_list_entry.pop + @res << tag << "\n" + end + @res << list_name(fragment.type, false) << "\n" + end + + def accept_list_item(am, fragment) + if tag = @in_list_entry.last + @res << tag << "\n" + end + @res << list_item_start(am, fragment) + @res << wrap(convert_flow(am.flow(fragment.txt))) << "\n" + @in_list_entry[-1] = list_end_for(fragment.type) + end + + def accept_blank_line(am, fragment) + # @res << "\n" + end + + def accept_heading(am, fragment) + @res << convert_heading(fragment.head_level, am.flow(fragment.txt)) + end + + ## + # This is a higher speed (if messier) version of wrap + + def wrap(txt, line_len = 76) + res = "" + sp = 0 + ep = txt.length + while sp < ep + # scan back for a space + p = sp + line_len - 1 + if p >= ep + p = ep + else + while p > sp and txt[p] != ?\s + p -= 1 + end + if p <= sp + p = sp + line_len + while p < ep and txt[p] != ?\s + p += 1 + end + end + end + res << txt[sp...p] << "\n" + sp = p + sp += 1 while sp < ep and txt[sp] == ?\s + end + res + end + + private + + def on_tags(res, item) + attr_mask = item.turn_on + return if attr_mask.zero? + + @attr_tags.each do |tag| + if attr_mask & tag.bit != 0 + res << tag.on + end + end + end + + def off_tags(res, item) + attr_mask = item.turn_off + return if attr_mask.zero? + + @attr_tags.reverse_each do |tag| + if attr_mask & tag.bit != 0 + res << tag.off + end + end + end + + def convert_flow(flow) + res = "" + flow.each do |item| + case item + when String + $stderr.puts "Converting '#{item}'" + res << convert_string(item) + when AttrChanger + off_tags(res, item) + on_tags(res, item) + when Special + res << convert_special(item) + else + raise "Unknown flow element: #{item.inspect}" + end + end + res + end + + ## + # some of these patterns are taken from SmartyPants... + + def convert_string(item) + escape(item). + + # convert ... to elipsis (and make sure .... becomes .) + gsub(/\.\.\.\./, '.\ldots{}').gsub(/\.\.\./, '\ldots{}'). + + # convert single closing quote + gsub(%r{([^ \t\r\n\[\{\(])\'}) { "#$1'" }. + gsub(%r{\'(?=\W|s\b)}) { "'" }. + + # convert single opening quote + gsub(/'/, '`'). + + # convert double closing quote + gsub(%r{([^ \t\r\n\[\{\(])\"(?=\W)}) { "#$1''" }. + + # convert double opening quote + gsub(/"/, "``"). + + # convert copyright + gsub(/\(c\)/, '\copyright{}') + + end + + def convert_special(special) + handled = false + Attribute.each_name_of(special.type) do |name| + method_name = "handle_special_#{name}" + if self.respond_to? method_name + special.text = send(method_name, special) + handled = true + end + end + raise "Unhandled special: #{special}" unless handled + special.text + end + + def convert_heading(level, flow) + res = + case level + when 1 then "\\chapter{" + when 2 then "\\section{" + when 3 then "\\subsection{" + when 4 then "\\subsubsection{" + else "\\paragraph{" + end + + convert_flow(flow) + + "}\n" + end + + def list_name(list_type, is_open_tag) + tags = LIST_TYPE_TO_LATEX[list_type] || raise("Invalid list type: #{list_type.inspect}") + if tags[2] # enumerate + if is_open_tag + @list_depth += 1 + if @prev_list_types[@list_depth] != tags[2] + case @list_depth + when 1 + roman = "i" + when 2 + roman = "ii" + when 3 + roman = "iii" + when 4 + roman = "iv" + else + raise("Too deep list: level #{@list_depth}") + end + @prev_list_types[@list_depth] = tags[2] + return l("\\renewcommand{\\labelenum#{roman}}{#{tags[2]}{enum#{roman}}}") + "\n" + tags[0] + end + else + @list_depth -= 1 + end + end + tags[ is_open_tag ? 0 : 1] + end + + def list_item_start(am, fragment) + case fragment.type + when ListBase::BULLET, ListBase::NUMBER, ListBase::UPPERALPHA, + ListBase::LOWERALPHA + "\\item " + + when ListBase::LABELED + "\\item[" + convert_flow(am.flow(fragment.param)) + "] " + + when ListBase::NOTE + convert_flow(am.flow(fragment.param)) + " & " + else + raise "Invalid list type" + end + end + + def list_end_for(fragment_type) + case fragment_type + when ListBase::BULLET, ListBase::NUMBER, ListBase::UPPERALPHA, + ListBase::LOWERALPHA, ListBase::LABELED + "" + when ListBase::NOTE + "\\\\\n" + else + raise "Invalid list type" + end + end + +end + +d + diff --git a/lib/rdoc/parsers/parse_rb.rb b/lib/rdoc/parsers/parse_rb.rb index 42256b2523..baf90577cb 100644 --- a/lib/rdoc/parsers/parse_rb.rb +++ b/lib/rdoc/parsers/parse_rb.rb @@ -7,9 +7,9 @@ # This file contains stuff stolen outright from: # -# rtags.rb - +# rtags.rb - # ruby-lex.rb - ruby lexcal analizer -# ruby-token.rb - ruby tokens +# ruby-token.rb - ruby tokens # by Keiju ISHITSUKA (Nippon Rational Inc.) # @@ -19,11 +19,11 @@ require "irb/slex" require "rdoc/code_objects" require "rdoc/tokenstream" -require "rdoc/markup/simple_markup/preprocess" +require "rdoc/markup/preprocess" require "rdoc/parsers/parserfactory" -$TOKEN_DEBUG = $DEBUG_RDOC +#$TOKEN_DEBUG = $DEBUG_RDOC # Definitions of all tokens involved in the lexical analysis @@ -35,7 +35,7 @@ module RubyToken EXPR_FNAME = :EXPR_FNAME EXPR_DOT = :EXPR_DOT EXPR_CLASS = :EXPR_CLASS - + class Token NO_TEXT = "??".freeze attr_accessor :text @@ -117,8 +117,8 @@ module RubyToken if (tk = source[token]).nil? fail TkReading2TokenNoKey, token end - tk = Token(tk[0], value) - else + tk = Token(tk[0], value) + else tk = if (token.ancestors & [TkId, TkVal, TkOPASGN, TkUnknownChar]).empty? token.new(@prev_line_no, @prev_char_no) else @@ -218,14 +218,14 @@ module RubyToken [:TkASSOC, TkOp, "=>"], [:TkQUESTION, TkOp, "?"], #? [:TkCOLON, TkOp, ":"], #: - + [:TkfLPAREN], # func( # [:TkfLBRACK], # func[ # [:TkfLBRACE], # func{ # [:TkSTAR], # *arg [:TkAMPER], # &arg # [:TkSYMBOL, TkId], # :SYMBOL - [:TkSYMBEG, TkId], + [:TkSYMBEG, TkId], [:TkGT, TkOp, ">"], [:TkLT, TkOp, "<"], [:TkPLUS, TkOp, "+"], @@ -276,7 +276,7 @@ module RubyToken token_c = Class.new super_token RubyToken.const_set token_n, token_c # token_c.inspect - + if reading if TkReading2Token[reading] fail TkReading2TokenDuplicateError, token_n, reading @@ -338,14 +338,14 @@ class RubyLex # here document. Once complete, it needs to read the rest of the # original line, but then skip the here document body. # - + class BufferedReader - + attr_reader :line_num - + def initialize(content, options) @options = options - + if /\t/ =~ content tab_width = @options.tab_width content = content.split(/\n/).map do |line| @@ -363,34 +363,34 @@ class RubyLex @last_newline = 0 @newline_pending = false end - + def column @offset - @last_newline end - + def getc return nil if @offset >= @size ch = @content[@offset, 1] - + @offset += 1 @hwm = @offset if @hwm < @offset - + if @newline_pending @line_num += 1 @last_newline = @offset - 1 @newline_pending = false end - + if ch == "\n" @newline_pending = true end ch end - + def getc_already_read getc end - + def ungetc(ch) raise "unget past beginning of file" if @offset <= 0 @offset -= 1 @@ -398,13 +398,13 @@ class RubyLex @newline_pending = false end end - + def get_read res = @content[@read_back_offset...@offset] @read_back_offset = @offset res end - + def peek(at) pos = @offset + at if pos >= @size @@ -413,11 +413,11 @@ class RubyLex @content[pos, 1] end end - + def peek_equal(str) @content[@offset, str.length] == str end - + def divert_read_from(reserve) @content[@offset, 0] = reserve @size = @content.size @@ -430,10 +430,10 @@ class RubyLex def_exception(:AlreadyDefinedToken, "Already defined token(%s)") def_exception(:TkReading2TokenNoKey, "key nothing(key='%s')") def_exception(:TkSymbol2TokenNoKey, "key nothing(key='%s')") - def_exception(:TkReading2TokenDuplicateError, + def_exception(:TkReading2TokenDuplicateError, "key duplicate(token_n='%s', key='%s')") def_exception(:SyntaxError, "%s") - + include RubyToken include IRB @@ -459,7 +459,7 @@ class RubyLex @quoted = nil @lex_state = EXPR_BEG @space_seen = false - + @continue = false @line = "" @@ -546,10 +546,9 @@ class RubyLex get_read end # throw :eof unless tk - p tk if $DEBUG_RDOC tk end - + ENINDENT_CLAUSE = [ "case", "class", "def", "do", "for", "if", "module", "unless", "until", "while", "begin" #, "when" @@ -564,7 +563,7 @@ class RubyLex "r" => "/", "w" => "]" } - + PERCENT_PAREN = { "{" => "}", "[" => "]", @@ -647,10 +646,10 @@ class RubyLex Token(TkNL).set_text("\n") end - @OP.def_rules("*", "**", + @OP.def_rules("*", "**", "!", "!=", "!~", - "=", "==", "===", - "=~", "<=>", + "=", "==", "===", + "=~", "<=>", "<", "<=", ">", ">=", ">>") do |op, io| @@ -717,8 +716,8 @@ class RubyLex @lex_state = EXPR_BEG Token(op).set_text(op) end - - @OP.def_rules("+=", "-=", "*=", "**=", + + @OP.def_rules("+=", "-=", "*=", "**=", "&=", "|=", "^=", "<<=", ">>=", "||=", "&&=") do |op, io| @lex_state = EXPR_BEG @@ -772,7 +771,7 @@ class RubyLex lex_int2 end - + def lex_int2 @OP.def_rules("]", "}", ")") do |op, io| @@ -814,7 +813,7 @@ class RubyLex Token(TkOPASGN, :/).set_text("/=") #") elsif @lex_state == EXPR_ARG and @space_seen and peek(0) !~ /\s/ identify_string(op) - else + else @lex_state = EXPR_BEG Token("/").set_text(op) end @@ -829,7 +828,7 @@ class RubyLex # @lex_state = EXPR_BEG # Token(TkOPASGN, :^) # end - + @OP.def_rules(",", ";") do |op, io| @lex_state = EXPR_BEG @@ -845,7 +844,7 @@ class RubyLex @lex_state = EXPR_BEG Token("~").set_text("~@") end - + @OP.def_rule("(") do @indent += 1 if @lex_state == EXPR_BEG || @lex_state == EXPR_MID @@ -895,15 +894,15 @@ class RubyLex end @OP.def_rule('\\') do #' - if getc == "\n" + if getc == "\n" @space_seen = true @continue = true Token(TkSPACE).set_text("\\\n") - else + else ungetc Token("\\").set_text("\\") #" - end - end + end + end @OP.def_rule('%') do |op, io| @@ -933,7 +932,7 @@ class RubyLex end end - # @OP.def_rule("def", proc{|op, io| /\s/ =~ io.peek(0)}) do + # @OP.def_rule("def", proc{|op, io| /\s/ =~ io.peek(0)}) do # |op, io| # @indent += 1 # @lex_state = EXPR_FNAME @@ -958,10 +957,10 @@ class RubyLex printf "MATCH: end %s: %s\n", op, io.inspect if RubyLex.debug? t end - + p @OP if RubyLex.debug? end - + def identify_gvar @lex_state = EXPR_END str = "$" @@ -970,15 +969,15 @@ class RubyLex when /[~_*$?!@\/\\;,=:<>".]/ #" str << ch Token(TkGVAR, str) - + when "-" str << "-" << getc Token(TkGVAR, str) - + when "&", "`", "'", "+" str << ch Token(TkBACK_REF, str) - + when /[1-9]/ str << ch while (ch = getc) =~ /[0-9]/ @@ -990,13 +989,13 @@ class RubyLex ungetc ungetc return identify_identifier - else + else ungetc - Token("$") + Token("$") end tk.set_text(str) end - + def identify_identifier token = "" token.concat getc if peek(0) =~ /[$@]/ @@ -1007,7 +1006,7 @@ class RubyLex token.concat ch end ungetc - + if ch == "!" or ch == "?" token.concat getc end @@ -1022,7 +1021,7 @@ class RubyLex @lex_state = EXPR_END return Token(TkIVAR, token).set_text(token) end - + if @lex_state != EXPR_DOT print token, "\n" if RubyLex.debug? @@ -1120,7 +1119,7 @@ class RubyLex @lex_state = EXPR_END Token(Ltype2Token[lt], str).set_text(str.dump) end - + def identify_quotation(initial_char) ch = getc if lt = PERCENT_LTYPE[ch] @@ -1201,7 +1200,7 @@ class RubyLex end Token(type).set_text(str) end - + def identify_string(ltype, quoted = ltype, opener=nil, initial_char = nil) @ltype = ltype @quoted = quoted @@ -1213,9 +1212,9 @@ class RubyLex nest = 0 begin - while ch = getc + while ch = getc str << ch - if @quoted == ch + if @quoted == ch if nest == 0 break else @@ -1276,7 +1275,7 @@ class RubyLex if ch == "\n" ch = " " else - comment << "\\" + comment << "\\" end else if ch == "\n" @@ -1289,7 +1288,7 @@ class RubyLex end return Token(TkCOMMENT).set_text(comment) end - + def read_escape res = "" case ch = getc @@ -1306,7 +1305,7 @@ class RubyLex end res << ch end - + when "x" res << ch 2.times do @@ -1361,493 +1360,476 @@ end # # This file is based on rtags -module RDoc - - GENERAL_MODIFIERS = [ 'nodoc' ].freeze - - CLASS_MODIFIERS = GENERAL_MODIFIERS - - ATTR_MODIFIERS = GENERAL_MODIFIERS - - CONSTANT_MODIFIERS = GENERAL_MODIFIERS +class RDoc::RubyParser - METHOD_MODIFIERS = GENERAL_MODIFIERS + - [ 'arg', 'args', 'yield', 'yields', 'notnew', 'not-new', 'not_new', 'doc' ] - - - class RubyParser - include RubyToken - include TokenStream + include RubyToken + include RDoc::TokenStream - extend ParserFactory + extend RDoc::ParserFactory - parse_files_matching(/\.rbw?$/) + parse_files_matching(/\.rbw?$/) + def initialize(top_level, file_name, content, options, stats) + @options = options + @stats = stats + @size = 0 + @token_listeners = nil + @input_file_name = file_name + @scanner = RubyLex.new content, @options + @scanner.exception_on_syntax_error = false + @top_level = top_level + @progress = $stderr unless options.quiet + end - def initialize(top_level, file_name, content, options, stats) - @options = options - @stats = stats - @size = 0 - @token_listeners = nil - @input_file_name = file_name - @scanner = RubyLex.new content, @options - @scanner.exception_on_syntax_error = false - @top_level = top_level - @progress = $stderr unless options.quiet - end - - def scan - @tokens = [] - @unget_read = [] - @read = [] - catch(:eof) do - catch(:enddoc) do - begin - parse_toplevel_statements(@top_level) - rescue Exception => e - $stderr.puts "\n\n" - $stderr.puts "RDoc failure in #@input_file_name at or around " + - "line #{@scanner.line_no} column #{@scanner.char_no}" - $stderr.puts - $stderr.puts "Before reporting this, could you check that the file" - $stderr.puts "you're documenting compiles cleanly--RDoc is not a" - $stderr.puts "full Ruby parser, and gets confused easily if fed" - $stderr.puts "invalid programs." - $stderr.puts - $stderr.puts "The internal error was:\n\n" - - e.set_backtrace(e.backtrace[0,4]) - raise - end + def scan + @tokens = [] + @unget_read = [] + @read = [] + catch(:eof) do + catch(:enddoc) do + begin + parse_toplevel_statements(@top_level) + rescue Exception => e + $stderr.puts "\n\n" + $stderr.puts "RDoc failure in #@input_file_name at or around " + + "line #{@scanner.line_no} column #{@scanner.char_no}" + $stderr.puts + $stderr.puts "Before reporting this, could you check that the file" + $stderr.puts "you're documenting compiles cleanly--RDoc is not a" + $stderr.puts "full Ruby parser, and gets confused easily if fed" + $stderr.puts "invalid programs." + $stderr.puts + $stderr.puts "The internal error was:\n\n" + + e.set_backtrace(e.backtrace[0,4]) + raise end end - @top_level end + @top_level + end - private + private - def make_message(msg) - prefix = "\n" + @input_file_name + ":" - if @scanner - prefix << "#{@scanner.line_no}:#{@scanner.char_no}: " - end - return prefix + msg + def make_message(msg) + prefix = "\n" + @input_file_name + ":" + if @scanner + prefix << "#{@scanner.line_no}:#{@scanner.char_no}: " end + return prefix + msg + end - def warn(msg) - return if @options.quiet - msg = make_message msg - $stderr.puts msg - end + def warn(msg) + return if @options.quiet + msg = make_message msg + $stderr.puts msg + end - def error(msg) - msg = make_message msg - $stderr.puts msg - exit(1) - end + def error(msg) + msg = make_message msg + $stderr.puts msg + exit(1) + end - def progress(char) - unless @options.quiet - @progress.print(char) - @progress.flush - end + def progress(char) + unless @options.quiet + @progress.print(char) + @progress.flush end + end - def add_token_listener(obj) - @token_listeners ||= [] - @token_listeners << obj - end + def add_token_listener(obj) + @token_listeners ||= [] + @token_listeners << obj + end - def remove_token_listener(obj) - @token_listeners.delete(obj) - end + def remove_token_listener(obj) + @token_listeners.delete(obj) + end - def get_tk - tk = nil - if @tokens.empty? - tk = @scanner.token - @read.push @scanner.get_read - puts "get_tk1 => #{tk.inspect}" if $TOKEN_DEBUG - else - @read.push @unget_read.shift - tk = @tokens.shift - puts "get_tk2 => #{tk.inspect}" if $TOKEN_DEBUG - end + def get_tk + tk = nil + if @tokens.empty? + tk = @scanner.token + @read.push @scanner.get_read + puts "get_tk1 => #{tk.inspect}" if $TOKEN_DEBUG + else + @read.push @unget_read.shift + tk = @tokens.shift + puts "get_tk2 => #{tk.inspect}" if $TOKEN_DEBUG + end - if tk.kind_of?(TkSYMBEG) - set_token_position(tk.line_no, tk.char_no) - tk1 = get_tk - if tk1.kind_of?(TkId) || tk1.kind_of?(TkOp) || tk1.kind_of?(TkSTRING) - if tk1.respond_to?(:name) - tk = Token(TkSYMBOL).set_text(":" + tk1.name) - else - tk = Token(TkSYMBOL).set_text(":" + tk1.text) - end - # remove the identifier we just read (we're about to - # replace it with a symbol) - @token_listeners.each do |obj| - obj.pop_token - end if @token_listeners + if tk.kind_of?(TkSYMBEG) + set_token_position(tk.line_no, tk.char_no) + tk1 = get_tk + if tk1.kind_of?(TkId) || tk1.kind_of?(TkOp) || tk1.kind_of?(TkSTRING) + if tk1.respond_to?(:name) + tk = Token(TkSYMBOL).set_text(":" + tk1.name) else - warn("':' not followed by identifier or operator") - tk = tk1 + tk = Token(TkSYMBOL).set_text(":" + tk1.text) end + # remove the identifier we just read (we're about to + # replace it with a symbol) + @token_listeners.each do |obj| + obj.pop_token + end if @token_listeners + else + warn("':' not followed by identifier or operator") + tk = tk1 end + end - # inform any listeners of our shiny new token - @token_listeners.each do |obj| - obj.add_token(tk) - end if @token_listeners + # inform any listeners of our shiny new token + @token_listeners.each do |obj| + obj.add_token(tk) + end if @token_listeners - tk - end + tk + end - def peek_tk - unget_tk(tk = get_tk) - tk - end + def peek_tk + unget_tk(tk = get_tk) + tk + end - def unget_tk(tk) - @tokens.unshift tk - @unget_read.unshift @read.pop + def unget_tk(tk) + @tokens.unshift tk + @unget_read.unshift @read.pop - # Remove this token from any listeners - @token_listeners.each do |obj| - obj.pop_token - end if @token_listeners - end + # Remove this token from any listeners + @token_listeners.each do |obj| + obj.pop_token + end if @token_listeners + end - def skip_tkspace(skip_nl = true) - tokens = [] - while ((tk = get_tk).kind_of?(TkSPACE) || - (skip_nl && tk.kind_of?(TkNL))) - tokens.push tk - end - unget_tk(tk) - tokens + def skip_tkspace(skip_nl = true) + tokens = [] + while ((tk = get_tk).kind_of?(TkSPACE) || + (skip_nl && tk.kind_of?(TkNL))) + tokens.push tk end + unget_tk(tk) + tokens + end - def get_tkread - read = @read.join("") - @read = [] - read - end + def get_tkread + read = @read.join("") + @read = [] + read + end - def peek_read - @read.join('') - end + def peek_read + @read.join('') + end - NORMAL = "::" - SINGLE = "<<" + NORMAL = "::" + SINGLE = "<<" - # Look for the first comment in a file that isn't - # a shebang line. + ## + # Look for the first comment in a file that isn't a shebang line. - def collect_first_comment - skip_tkspace - res = '' - first_line = true + def collect_first_comment + skip_tkspace + res = '' + first_line = true - tk = get_tk - while tk.kind_of?(TkCOMMENT) - if first_line && tk.text[0,2] == "#!" - skip_tkspace - tk = get_tk - else - res << tk.text << "\n" + tk = get_tk + while tk.kind_of?(TkCOMMENT) + if first_line && tk.text[0,2] == "#!" + skip_tkspace + tk = get_tk + else + res << tk.text << "\n" + tk = get_tk + if tk.kind_of? TkNL + skip_tkspace(false) tk = get_tk - if tk.kind_of? TkNL - skip_tkspace(false) - tk = get_tk - end end - first_line = false end - unget_tk(tk) - res + first_line = false end + unget_tk(tk) + res + end + + def parse_toplevel_statements(container) + comment = collect_first_comment + look_for_directives_in(container, comment) + container.comment = comment unless comment.empty? + parse_statements(container, NORMAL, nil, comment) + end + + def parse_statements(container, single=NORMAL, current_method=nil, comment='') + nest = 1 + save_visibility = container.visibility - def parse_toplevel_statements(container) - comment = collect_first_comment - look_for_directives_in(container, comment) - container.comment = comment unless comment.empty? - parse_statements(container, NORMAL, nil, comment) - end - - def parse_statements(container, single=NORMAL, current_method=nil, comment='') - nest = 1 - save_visibility = container.visibility - # if container.kind_of?(TopLevel) # else # comment = '' # end - non_comment_seen = true - - while tk = get_tk - - keep_comment = false - - non_comment_seen = true unless tk.kind_of?(TkCOMMENT) - - case tk + non_comment_seen = true - when TkNL - skip_tkspace(true) # Skip blanks and newlines - tk = get_tk - if tk.kind_of?(TkCOMMENT) - if non_comment_seen - comment = '' - non_comment_seen = false - end - while tk.kind_of?(TkCOMMENT) - comment << tk.text << "\n" - tk = get_tk # this is the newline - skip_tkspace(false) # leading spaces - tk = get_tk - end - unless comment.empty? - look_for_directives_in(container, comment) - if container.done_documenting - container.ongoing_visibility = save_visibility -# return - end + while tk = get_tk + keep_comment = false + + non_comment_seen = true unless tk.kind_of?(TkCOMMENT) + + case tk + when TkNL + skip_tkspace(true) # Skip blanks and newlines + tk = get_tk + if tk.kind_of?(TkCOMMENT) + if non_comment_seen + comment = '' + non_comment_seen = false + end + while tk.kind_of?(TkCOMMENT) + comment << tk.text << "\n" + tk = get_tk # this is the newline + skip_tkspace(false) # leading spaces + tk = get_tk + end + unless comment.empty? + look_for_directives_in(container, comment) + if container.done_documenting + container.ongoing_visibility = save_visibility + # return end - keep_comment = true - else - non_comment_seen = true end - unget_tk(tk) keep_comment = true + else + non_comment_seen = true + end + unget_tk(tk) + keep_comment = true + when TkCLASS + if container.document_children + parse_class(container, single, tk, comment) + else + nest += 1 + end - when TkCLASS - if container.document_children - parse_class(container, single, tk, comment) - else - nest += 1 - end - - when TkMODULE - if container.document_children - parse_module(container, single, tk, comment) - else - nest += 1 - end + when TkMODULE + if container.document_children + parse_module(container, single, tk, comment) + else + nest += 1 + end - when TkDEF - if container.document_self - parse_method(container, single, tk, comment) - else - nest += 1 - end + when TkDEF + if container.document_self + parse_method(container, single, tk, comment) + else + nest += 1 + end - when TkCONSTANT - if container.document_self - parse_constant(container, single, tk, comment) - end + when TkCONSTANT + if container.document_self + parse_constant(container, single, tk, comment) + end - when TkALIAS - if container.document_self - parse_alias(container, single, tk, comment) - end + when TkALIAS + if container.document_self + parse_alias(container, single, tk, comment) + end - when TkYIELD - if current_method.nil? - warn("Warning: yield outside of method") if container.document_self - else - parse_yield(container, single, tk, current_method) - end + when TkYIELD + if current_method.nil? + 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 increas - # 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 + # Until and While can have a 'do', which shouldn't increas + # 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 - nest += 1 - puts "FOUND #{tk.class} in #{container.name}, nest = #{nest}, " + - "line #{tk.line_no}" if $DEBUG_RDOC - skip_optional_do_after_expression + when TkUNTIL, TkWHILE + nest += 1 + puts "Found #{tk.class} in #{container.name}, nest = #{nest}, " + + "line #{tk.line_no}" if $DEBUG_RDOC + skip_optional_do_after_expression # 'for' is trickier - when TkFOR - nest += 1 - puts "FOUND #{tk.class} in #{container.name}, nest = #{nest}, " + - "line #{tk.line_no}" if $DEBUG_RDOC - skip_for_variable - skip_optional_do_after_expression - - when TkCASE, TkDO, TkIF, TkUNLESS, TkBEGIN - nest += 1 - puts "Found #{tk.class} in #{container.name}, nest = #{nest}, " + - "line #{tk.line_no}" if $DEBUG_RDOC - - when TkIDENTIFIER - if nest == 1 and current_method.nil? - case tk.name - when "private", "protected", "public", - "private_class_method", "public_class_method" - parse_visibility(container, single, tk) - keep_comment = true - when "attr" - parse_attr(container, single, tk, comment) - when /^attr_(reader|writer|accessor)$/, @options.extra_accessors - parse_attr_accessor(container, single, tk, comment) - when "alias_method" - if container.document_self - parse_alias(container, single, tk, comment) - end - end - end - - case tk.name - when "require" - parse_require(container, comment) - when "include" - parse_include(container, comment) - end - + when TkFOR + nest += 1 + puts "Found #{tk.class} in #{container.name}, nest = #{nest}, " + + "line #{tk.line_no}" if $DEBUG_RDOC + skip_for_variable + skip_optional_do_after_expression - when TkEND - nest -= 1 - puts "Found 'end' in #{container.name}, nest = #{nest}, line #{tk.line_no}" if $DEBUG_RDOC - puts "Method = #{current_method.name}" if $DEBUG_RDOC and current_method - if nest == 0 - read_documentation_modifiers(container, CLASS_MODIFIERS) - container.ongoing_visibility = save_visibility - return + when TkCASE, TkDO, TkIF, TkUNLESS, TkBEGIN + nest += 1 + puts "Found #{tk.class} in #{container.name}, nest = #{nest}, " + + "line #{tk.line_no}" if $DEBUG_RDOC + + when TkIDENTIFIER + if nest == 1 and current_method.nil? + case tk.name + when "private", "protected", "public", + "private_class_method", "public_class_method" + parse_visibility(container, single, tk) + keep_comment = true + when "attr" + parse_attr(container, single, tk, comment) + when /^attr_(reader|writer|accessor)$/, @options.extra_accessors + parse_attr_accessor(container, single, tk, comment) + when "alias_method" + if container.document_self + parse_alias(container, single, tk, comment) + end end + end - end + case tk.name + when "require" + parse_require(container, comment) + when "include" + parse_include(container, comment) + end - comment = '' unless keep_comment - begin - get_tkread - skip_tkspace(false) - end while peek_tk == TkNL + + when TkEND + nest -= 1 + puts "Found 'end' in #{container.name}, nest = #{nest}, line #{tk.line_no}" if $DEBUG_RDOC + puts "Method = #{current_method.name}" if $DEBUG_RDOC and current_method + if nest == 0 + 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 - - def parse_class(container, single, tk, comment, &block) - progress("c") + end - @stats.num_classes += 1 + def parse_class(container, single, tk, comment, &block) + progress("c") - container, name_t = get_class_or_module(container) + @stats.num_classes += 1 - case name_t - when TkCONSTANT - name = name_t.name - superclass = "Object" - - if peek_tk.kind_of?(TkLT) - get_tk - skip_tkspace(true) - superclass = get_class_specification - superclass = "" if superclass.empty? - end + container, name_t = get_class_or_module(container) - if single == SINGLE - cls_type = SingleClass - else - cls_type = NormalClass - end + case name_t + when TkCONSTANT + name = name_t.name + superclass = "Object" + + if peek_tk.kind_of?(TkLT) + get_tk + skip_tkspace(true) + superclass = get_class_specification + superclass = "" if superclass.empty? + end - cls = container.add_class(cls_type, name, superclass) - read_documentation_modifiers(cls, CLASS_MODIFIERS) - cls.record_location(@top_level) - parse_statements(cls) - cls.comment = comment + if single == SINGLE + cls_type = RDoc::SingleClass + else + cls_type = RDoc::NormalClass + end - when TkLSHFT - case name = get_class_specification - when "self", container.name - parse_statements(container, SINGLE, &block) - else - other = TopLevel.find_class_named(name) - unless other -# other = @top_level.add_class(NormalClass, name, nil) -# other.record_location(@top_level) -# other.comment = comment - other = NormalClass.new("Dummy", nil) - end - read_documentation_modifiers(other, CLASS_MODIFIERS) - parse_statements(other, SINGLE, &block) - end + cls = container.add_class cls_type, name, superclass + read_documentation_modifiers cls, RDoc::CLASS_MODIFIERS + cls.record_location(@top_level) + parse_statements(cls) + cls.comment = comment + when TkLSHFT + case name = get_class_specification + when "self", container.name + parse_statements(container, SINGLE, &block) else - warn("Expected class name or '<<'. Got #{name_t.class}: #{name_t.text.inspect}") + other = RDoc::TopLevel.find_class_named(name) + unless other + # other = @top_level.add_class(NormalClass, name, nil) + # other.record_location(@top_level) + # other.comment = comment + other = RDoc::NormalClass.new "Dummy", nil + end + read_documentation_modifiers other, RDoc::CLASS_MODIFIERS + parse_statements(other, SINGLE, &block) end + + else + warn("Expected class name or '<<'. Got #{name_t.class}: #{name_t.text.inspect}") end + end - def parse_module(container, single, tk, comment) - progress("m") - @stats.num_modules += 1 - container, name_t = get_class_or_module(container) + def parse_module(container, single, tk, comment) + progress("m") + @stats.num_modules += 1 + container, name_t = get_class_or_module(container) # skip_tkspace - name = name_t.name - mod = container.add_module(NormalModule, name) - mod.record_location(@top_level) - read_documentation_modifiers(mod, CLASS_MODIFIERS) - parse_statements(mod) - mod.comment = comment - end + name = name_t.name + mod = container.add_module RDoc::NormalModule, name + mod.record_location @top_level + read_documentation_modifiers mod, RDoc::CLASS_MODIFIERS + parse_statements(mod) + mod.comment = comment + end - # Look for the name of a class of module (optionally with a leading :: or - # with :: separated named) and return the ultimate name and container + # Look for the name of a class of module (optionally with a leading :: or + # with :: separated named) and return the ultimate name and container - def get_class_or_module(container) - skip_tkspace - name_t = get_tk + def get_class_or_module(container) + skip_tkspace + name_t = get_tk - # class ::A -> A is in the top level - if name_t.kind_of?(TkCOLON2) - name_t = get_tk - container = @top_level - end + # class ::A -> A is in the top level + if name_t.kind_of?(TkCOLON2) + name_t = get_tk + container = @top_level + end - skip_tkspace(false) + skip_tkspace(false) - while peek_tk.kind_of?(TkCOLON2) - prev_container = container - container = container.find_module_named(name_t.name) - if !container + while peek_tk.kind_of?(TkCOLON2) + prev_container = container + container = container.find_module_named(name_t.name) + if !container # warn("Couldn't find module #{name_t.name}") - container = prev_container.add_module(NormalModule, name_t.name) - end - get_tk - name_t = get_tk + container = prev_container.add_module RDoc::NormalModule, name_t.name end - skip_tkspace(false) - return [container, name_t] + get_tk + name_t = get_tk end + skip_tkspace(false) + return [container, name_t] + end - def parse_constant(container, single, tk, comment) - name = tk.name - skip_tkspace(false) - eq_tk = get_tk + def parse_constant(container, single, tk, comment) + name = tk.name + skip_tkspace(false) + eq_tk = get_tk - unless eq_tk.kind_of?(TkASSIGN) - unget_tk(eq_tk) - return - end + unless eq_tk.kind_of?(TkASSIGN) + unget_tk(eq_tk) + return + end - nest = 0 - get_tkread + nest = 0 + get_tkread - tk = get_tk - if tk.kind_of? TkGT - unget_tk(tk) - unget_tk(eq_tk) - return - end + tk = get_tk + if tk.kind_of? TkGT + unget_tk(tk) + unget_tk(eq_tk) + return + end - loop do - puts("Param: #{tk}, #{@scanner.continue} " + - "#{@scanner.lex_state} #{nest}") if $DEBUG_RDOC + loop do + puts "Param: %p, %s %s %s" % + [tk.text, @scanner.continue, @scanner.lex_state, nest] if $DEBUG_RDOC case tk when TkSEMICOLON @@ -1868,182 +1850,184 @@ module RDoc end end tk = get_tk - end + end - res = get_tkread.tr("\n", " ").strip - res = "" if res == ";" - con = Constant.new(name, res, comment) - read_documentation_modifiers(con, CONSTANT_MODIFIERS) - if con.document_self - container.add_constant(con) - end + res = get_tkread.tr("\n", " ").strip + res = "" if res == ";" + + con = RDoc::Constant.new name, res, comment + read_documentation_modifiers con, RDoc::CONSTANT_MODIFIERS + + if con.document_self + container.add_constant(con) end + end - def parse_method(container, single, tk, comment) - progress(".") - @stats.num_methods += 1 - line_no = tk.line_no - column = tk.char_no - - start_collecting_tokens - add_token(tk) - add_token_listener(self) - + def parse_method(container, single, tk, comment) + progress(".") + @stats.num_methods += 1 + line_no = tk.line_no + column = tk.char_no + + start_collecting_tokens + add_token(tk) + add_token_listener(self) + + @scanner.instance_eval{@lex_state = EXPR_FNAME} + skip_tkspace(false) + name_t = get_tk + back_tk = skip_tkspace + meth = nil + added_container = false + + dot = get_tk + if dot.kind_of?(TkDOT) or dot.kind_of?(TkCOLON2) @scanner.instance_eval{@lex_state = EXPR_FNAME} - skip_tkspace(false) - name_t = get_tk - back_tk = skip_tkspace - meth = nil - added_container = false - - dot = get_tk - if dot.kind_of?(TkDOT) or dot.kind_of?(TkCOLON2) - @scanner.instance_eval{@lex_state = EXPR_FNAME} - skip_tkspace - name_t2 = get_tk - case name_t - when TkSELF - name = name_t2.name - when TkCONSTANT - name = name_t2.name - prev_container = container - container = container.find_module_named(name_t.name) - if !container - added_container = true - obj = name_t.name.split("::").inject(Object) do |state, item| - state.const_get(item) - end rescue nil - - type = obj.class == Class ? NormalClass : NormalModule - if not [Class, Module].include?(obj.class) - warn("Couldn't find #{name_t.name}. Assuming it's a module") - end + skip_tkspace + name_t2 = get_tk + case name_t + when TkSELF + name = name_t2.name + when TkCONSTANT + name = name_t2.name + prev_container = container + container = container.find_module_named(name_t.name) + if !container + added_container = true + obj = name_t.name.split("::").inject(Object) do |state, item| + state.const_get(item) + end rescue nil + + type = obj.class == Class ? RDoc::NormalClass : RDoc::NormalModule + if not [Class, Module].include?(obj.class) + warn("Couldn't find #{name_t.name}. Assuming it's a module") + end - if type == NormalClass then - container = prev_container.add_class(type, name_t.name, obj.superclass.name) - else - container = prev_container.add_module(type, name_t.name) - end + if type == RDoc::NormalClass then + container = prev_container.add_class(type, name_t.name, obj.superclass.name) + else + container = prev_container.add_module(type, name_t.name) end - else - # warn("Unexpected token '#{name_t2.inspect}'") - # break - skip_method(container) - return - end - meth = AnyMethod.new(get_tkread, name) - meth.singleton = true + end else - unget_tk dot - back_tk.reverse_each do |token| - unget_tk token - end - name = name_t.name - - meth = AnyMethod.new(get_tkread, name) - meth.singleton = (single == SINGLE) + # warn("Unexpected token '#{name_t2.inspect}'") + # break + skip_method(container) + return end + meth = RDoc::AnyMethod.new(get_tkread, name) + meth.singleton = true + else + unget_tk dot + back_tk.reverse_each do |token| + unget_tk token + end + name = name_t.name + + meth = RDoc::AnyMethod.new get_tkread, name + meth.singleton = (single == SINGLE) + end - remove_token_listener(self) + remove_token_listener(self) - meth.start_collecting_tokens - indent = TkSPACE.new(1,1) - indent.set_text(" " * column) + meth.start_collecting_tokens + indent = TkSPACE.new(1,1) + indent.set_text(" " * column) - meth.add_tokens([TkCOMMENT.new(line_no, - 1, - "# File #{@top_level.file_absolute_name}, line #{line_no}"), - NEWLINE_TOKEN, - indent]) + meth.add_tokens([TkCOMMENT.new(line_no, + 1, + "# File #{@top_level.file_absolute_name}, line #{line_no}"), + NEWLINE_TOKEN, + indent]) - meth.add_tokens(@token_stream) + meth.add_tokens(@token_stream) - add_token_listener(meth) + add_token_listener(meth) - @scanner.instance_eval{@continue = false} - parse_method_parameters(meth) + @scanner.instance_eval{@continue = false} + parse_method_parameters(meth) - if meth.document_self - container.add_method(meth) - elsif added_container - container.document_self = false - end + if meth.document_self + container.add_method(meth) + elsif added_container + container.document_self = false + end - # Having now read the method parameters and documentation modifiers, we - # now know whether we have to rename #initialize to ::new + # Having now read the method parameters and documentation modifiers, we + # now know whether we have to rename #initialize to ::new - if name == "initialize" && !meth.singleton - if meth.dont_rename_initialize - meth.visibility = :protected - else - meth.singleton = true - meth.name = "new" - meth.visibility = :public - end - end - - parse_statements(container, single, meth) - - remove_token_listener(meth) - - # Look for a 'call-seq' in the comment, and override the - # normal parameter stuff - - if comment.sub!(/:?call-seq:(.*?)^\s*\#?\s*$/m, '') - seq = $1 - seq.gsub!(/^\s*\#\s*/, '') - meth.call_seq = seq - end - - meth.comment = comment - - end - - def skip_method(container) - meth = AnyMethod.new("", "anon") - parse_method_parameters(meth) - parse_statements(container, false, meth) - end - - # Capture the method's parameters. Along the way, - # look for a comment containing - # - # # yields: .... - # - # and add this as the block_params for the method - - def parse_method_parameters(method) - res = parse_method_or_yield_parameters(method) - res = "(" + res + ")" unless res[0] == ?( - method.params = res unless method.params - if method.block_params.nil? - skip_tkspace(false) - read_documentation_modifiers(method, METHOD_MODIFIERS) + if name == "initialize" && !meth.singleton + if meth.dont_rename_initialize + meth.visibility = :protected + else + meth.singleton = true + meth.name = "new" + meth.visibility = :public end end - def parse_method_or_yield_parameters(method=nil, modifiers=METHOD_MODIFIERS) + parse_statements(container, single, meth) + + remove_token_listener(meth) + + # Look for a 'call-seq' in the comment, and override the + # normal parameter stuff + + if comment.sub!(/:?call-seq:(.*?)^\s*\#?\s*$/m, '') + seq = $1 + seq.gsub!(/^\s*\#\s*/, '') + meth.call_seq = seq + end + + meth.comment = comment + end + + def skip_method(container) + meth = RDoc::AnyMethod.new "", "anon" + parse_method_parameters(meth) + parse_statements(container, false, meth) + end + + # Capture the method's parameters. Along the way, look for a comment + # containing. + # + # # yields: .... + # + # and add this as the block_params for the method + + def parse_method_parameters(method) + res = parse_method_or_yield_parameters(method) + res = "(" + res + ")" unless res[0] == ?( + method.params = res unless method.params + if method.block_params.nil? skip_tkspace(false) - tk = get_tk + read_documentation_modifiers method, RDoc::METHOD_MODIFIERS + end + end - # Little hack going on here. In the statement - # f = 2*(1+yield) - # We see the RPAREN as the next token, so we need - # to exit early. This still won't catch all cases - # (such as "a = yield + 1" - end_token = case tk - when TkLPAREN, TkfLPAREN - TkRPAREN - when TkRPAREN - return "" - else - TkNL - end - nest = 0 + def parse_method_or_yield_parameters(method = nil, + modifiers = RDoc::METHOD_MODIFIERS) + skip_tkspace(false) + tk = get_tk - loop do - puts("Param: #{tk.inspect}, #{@scanner.continue} " + - "#{@scanner.lex_state} #{nest}") if $DEBUG_RDOC + # Little hack going on here. In the statement + # f = 2*(1+yield) + # We see the RPAREN as the next token, so we need + # to exit early. This still won't catch all cases + # (such as "a = yield + 1" + end_token = case tk + when TkLPAREN, TkfLPAREN + TkRPAREN + when TkRPAREN + return "" + else + TkNL + end + nest = 0 + + loop do + puts "Param: %p, %s %s %s" % + [tk.text, @scanner.continue, @scanner.lex_state, nest] if $DEBUG_RDOC case tk when TkSEMICOLON break @@ -2064,113 +2048,113 @@ module RDoc break unless @scanner.continue end when method && method.block_params.nil? && TkCOMMENT - unget_tk(tk) - read_documentation_modifiers(method, modifiers) + unget_tk(tk) + read_documentation_modifiers(method, modifiers) end - tk = get_tk - end - res = get_tkread.tr("\n", " ").strip - res = "" if res == ";" - res + tk = get_tk end + res = get_tkread.tr("\n", " ").strip + res = "" if res == ";" + res + end - # skip the var [in] part of a 'for' statement - def skip_for_variable - skip_tkspace(false) - tk = get_tk - skip_tkspace(false) - tk = get_tk - unget_tk(tk) unless tk.kind_of?(TkIN) + # skip the var [in] part of a 'for' statement + def skip_for_variable + skip_tkspace(false) + tk = get_tk + skip_tkspace(false) + tk = get_tk + unget_tk(tk) unless tk.kind_of?(TkIN) + end + + # while, until, and for have an optional + def skip_optional_do_after_expression + skip_tkspace(false) + tk = get_tk + case tk + when TkLPAREN, TkfLPAREN + end_token = TkRPAREN + else + end_token = TkNL end - # while, until, and for have an optional - def skip_optional_do_after_expression - skip_tkspace(false) - tk = get_tk + nest = 0 + @scanner.instance_eval{@continue = false} + + loop do + puts("\nWhile: #{tk.text.inspect}, #{@scanner.continue} " \ + "#{@scanner.lex_state} #{nest}") if $DEBUG_RDOC case tk + when TkSEMICOLON + break when TkLPAREN, TkfLPAREN - end_token = TkRPAREN - else - end_token = TkNL - end - - nest = 0 - @scanner.instance_eval{@continue = false} - - loop do - puts("\nWhile: #{tk}, #{@scanner.continue} " + - "#{@scanner.lex_state} #{nest}") if $DEBUG_RDOC - case tk - when TkSEMICOLON - break - when TkLPAREN, TkfLPAREN - nest += 1 - when TkDO - break if nest.zero? - when end_token - if end_token == TkRPAREN - nest -= 1 - break if @scanner.lex_state == EXPR_END and nest.zero? - else - break unless @scanner.continue - end + nest += 1 + when TkDO + break if nest.zero? + when end_token + if end_token == TkRPAREN + nest -= 1 + break if @scanner.lex_state == EXPR_END and nest.zero? + else + break unless @scanner.continue end - tk = get_tk - end - skip_tkspace(false) - if peek_tk.kind_of? TkDO - get_tk end + tk = get_tk end - - # Return a superclass, which can be either a constant - # of an expression + skip_tkspace(false) + if peek_tk.kind_of? TkDO + get_tk + end + end - def get_class_specification - tk = get_tk - return "self" if tk.kind_of?(TkSELF) - - res = "" - while tk.kind_of?(TkCOLON2) || - tk.kind_of?(TkCOLON3) || - tk.kind_of?(TkCONSTANT) - - res += tk.text - tk = get_tk - end + # Return a superclass, which can be either a constant + # of an expression - unget_tk(tk) - skip_tkspace(false) + def get_class_specification + tk = get_tk + return "self" if tk.kind_of?(TkSELF) - get_tkread # empty out read buffer + res = "" + while tk.kind_of?(TkCOLON2) || + tk.kind_of?(TkCOLON3) || + tk.kind_of?(TkCONSTANT) + res += tk.text tk = get_tk + end - case tk - when TkNL, TkCOMMENT, TkSEMICOLON - unget_tk(tk) - return res - end + unget_tk(tk) + skip_tkspace(false) - res += parse_call_parameters(tk) - res + get_tkread # empty out read buffer + + tk = get_tk + + case tk + when TkNL, TkCOMMENT, TkSEMICOLON + unget_tk(tk) + return res end - def parse_call_parameters(tk) + res += parse_call_parameters(tk) + res + end - end_token = case tk - when TkLPAREN, TkfLPAREN - TkRPAREN - when TkRPAREN - return "" - else - TkNL - end - nest = 0 + def parse_call_parameters(tk) - loop do - puts("Call param: #{tk}, #{@scanner.continue} " + - "#{@scanner.lex_state} #{nest}") if $DEBUG_RDOC + end_token = case tk + when TkLPAREN, TkfLPAREN + TkRPAREN + when TkRPAREN + return "" + else + TkNL + end + nest = 0 + + loop do + puts("Call param: #{tk}, #{@scanner.continue} " + + "#{@scanner.lex_state} #{nest}") if $DEBUG_RDOC case tk when TkSEMICOLON break @@ -2184,214 +2168,209 @@ module RDoc break unless @scanner.continue end when TkCOMMENT - unget_tk(tk) - break + unget_tk(tk) + break end tk = get_tk - end - res = get_tkread.tr("\n", " ").strip - res = "" if res == ";" - res end + res = get_tkread.tr("\n", " ").strip + res = "" if res == ";" + res + end + # Parse a constant, which might be qualified by + # one or more class or module names - # Parse a constant, which might be qualified by - # one or more class or module names + def get_constant + res = "" + skip_tkspace(false) + tk = get_tk - def get_constant - res = "" - skip_tkspace(false) - tk = get_tk + while tk.kind_of?(TkCOLON2) || + tk.kind_of?(TkCOLON3) || + tk.kind_of?(TkCONSTANT) - while tk.kind_of?(TkCOLON2) || - tk.kind_of?(TkCOLON3) || - tk.kind_of?(TkCONSTANT) - - res += tk.text - tk = get_tk - end + res += tk.text + tk = get_tk + end # if res.empty? # warn("Unexpected token #{tk} in constant") -# end - unget_tk(tk) - res - end +# end + unget_tk(tk) + res + end - # Get a constant that may be surrounded by parens - - def get_constant_with_optional_parens - skip_tkspace(false) - nest = 0 - while (tk = peek_tk).kind_of?(TkLPAREN) || tk.kind_of?(TkfLPAREN) - get_tk - skip_tkspace(true) - nest += 1 - end + # Get a constant that may be surrounded by parens - name = get_constant + def get_constant_with_optional_parens + skip_tkspace(false) + nest = 0 + while (tk = peek_tk).kind_of?(TkLPAREN) || tk.kind_of?(TkfLPAREN) + get_tk + skip_tkspace(true) + nest += 1 + end - while nest > 0 - skip_tkspace(true) - tk = get_tk - nest -= 1 if tk.kind_of?(TkRPAREN) - end - name - end - - # Directives are modifier comments that can appear after class, module, - # or method names. For example - # - # def fred # :yields: a, b - # - # or - # - # class SM # :nodoc: - # - # we return the directive name and any parameters as a two element array - - def read_directive(allowed) + name = get_constant + + while nest > 0 + skip_tkspace(true) tk = get_tk - puts "directive: #{tk.inspect}" if $DEBUG_RDOC - result = nil - if tk.kind_of?(TkCOMMENT) - if tk.text =~ /\s*:?(\w+):\s*(.*)/ - directive = $1.downcase - if allowed.include?(directive) - result = [directive, $2] - end + nest -= 1 if tk.kind_of?(TkRPAREN) + end + name + end + + # Directives are modifier comments that can appear after class, module, + # or method names. For example: + # + # def fred # :yields: a, b + # + # or: + # + # class MyClass # :nodoc: + # + # We return the directive name and any parameters as a two element array + + def read_directive(allowed) + tk = get_tk + puts "directive: #{tk.text.inspect}" if $DEBUG_RDOC + result = nil + if tk.kind_of?(TkCOMMENT) + if tk.text =~ /\s*:?(\w+):\s*(.*)/ + directive = $1.downcase + if allowed.include?(directive) + result = [directive, $2] end - else - unget_tk(tk) end - result + else + unget_tk(tk) end + result + end - - def read_documentation_modifiers(context, allow) - dir = read_directive(allow) + def read_documentation_modifiers(context, allow) + dir = read_directive(allow) - case dir[0] + case dir[0] - when "notnew", "not_new", "not-new" - context.dont_rename_initialize = true + when "notnew", "not_new", "not-new" + context.dont_rename_initialize = true - when "nodoc" - context.document_self = false - if dir[1].downcase == "all" - context.document_children = false - end + when "nodoc" + context.document_self = false + if dir[1].downcase == "all" + context.document_children = false + end - when "doc" - context.document_self = true - context.force_documentation = true + when "doc" + context.document_self = true + context.force_documentation = true - when "yield", "yields" - unless context.params.nil? - context.params.sub!(/(,|)\s*&\w+/,'') # remove parameter &proc - end - context.block_params = dir[1] - - when "arg", "args" - context.params = dir[1] - end if dir - end - - - # Look for directives in a normal comment block: - # - # #-- - don't display comment from this point forward - # - # - # This routine modifies it's parameter - - def look_for_directives_in(context, comment) - - preprocess = SM::PreProcess.new(@input_file_name, - @options.rdoc_include) - - preprocess.handle(comment) do |directive, param| - case directive - when "stopdoc" - context.stop_doc - "" - when "startdoc" - context.start_doc - context.force_documentation = true - "" - - when "enddoc" - #context.done_documenting = true - #"" - throw :enddoc - - when "main" - @options.main_page = param - "" - - when "title" - @options.title = param - "" - - when "section" - context.set_current_section(param, comment) - comment.clear - break - else - warn "Unrecognized directive '#{directive}'" - break - end + when "yield", "yields" + unless context.params.nil? + context.params.sub!(/(,|)\s*&\w+/,'') # remove parameter &proc end + context.block_params = dir[1] - remove_private_comments(comment) - end + when "arg", "args" + context.params = dir[1] + end if dir + end - def remove_private_comments(comment) - comment.gsub!(/^#--.*?^#\+\+/m, '') - comment.sub!(/^#--.*/m, '') - end + ## + # Look for directives in a normal comment block: + # + # #-- - don't display comment from this point forward + # + # This routine modifies it's parameter + + def look_for_directives_in(context, comment) + preprocess = RDoc::Markup::PreProcess.new(@input_file_name, + @options.rdoc_include) + + preprocess.handle(comment) do |directive, param| + case directive + when "stopdoc" + context.stop_doc + "" + when "startdoc" + context.start_doc + context.force_documentation = true + "" + when "enddoc" + #context.done_documenting = true + #"" + throw :enddoc + when "main" + @options.main_page = param + "" + + when "title" + @options.title = param + "" + + when "section" + context.set_current_section(param, comment) + comment.clear + break - def get_symbol_or_name - tk = get_tk - case tk - when TkSYMBOL - tk.text.sub(/^:/, '') - when TkId, TkOp - tk.name - when TkSTRING - tk.text else - raise "Name or symbol expected (got #{tk})" + warn "Unrecognized directive '#{directive}'" + break end end - - def parse_alias(context, single, tk, comment) + + remove_private_comments(comment) + end + + def remove_private_comments(comment) + comment.gsub!(/^#--.*?^#\+\+/m, '') + comment.sub!(/^#--.*/m, '') + end + + def get_symbol_or_name + tk = get_tk + case tk + when TkSYMBOL + tk.text.sub(/^:/, '') + when TkId, TkOp + tk.name + when TkSTRING + tk.text + else + raise "Name or symbol expected (got #{tk})" + end + end + + def parse_alias(context, single, tk, comment) + skip_tkspace + if (peek_tk.kind_of? TkLPAREN) + get_tk skip_tkspace - if (peek_tk.kind_of? TkLPAREN) - get_tk - skip_tkspace - end - new_name = get_symbol_or_name - @scanner.instance_eval{@lex_state = EXPR_FNAME} + end + new_name = get_symbol_or_name + @scanner.instance_eval{@lex_state = EXPR_FNAME} + skip_tkspace + if (peek_tk.kind_of? TkCOMMA) + get_tk skip_tkspace - if (peek_tk.kind_of? TkCOMMA) - get_tk - skip_tkspace - end - old_name = get_symbol_or_name - - al = Alias.new(get_tkread, old_name, new_name, comment) - read_documentation_modifiers(al, ATTR_MODIFIERS) - if al.document_self - context.add_alias(al) - end end + old_name = get_symbol_or_name - def parse_yield_parameters - parse_method_or_yield_parameters + al = RDoc::Alias.new get_tkread, old_name, new_name, comment + read_documentation_modifiers al, RDoc::ATTR_MODIFIERS + if al.document_self + context.add_alias(al) end + end + + def parse_yield_parameters + parse_method_or_yield_parameters + end def parse_yield(context, single, tk, method) if method.block_params.nil? @@ -2413,15 +2392,15 @@ module RDoc case tk when TkSTRING name = tk.text -# when TkCONSTANT, TkIDENTIFIER, TkIVAR, TkGVAR -# name = tk.name + # when TkCONSTANT, TkIDENTIFIER, TkIVAR, TkGVAR + # name = tk.name when TkDSTRING warn "Skipping require of dynamic string: #{tk.text}" - # else - # warn "'require' used as variable" + # else + # warn "'require' used as variable" end if name - context.add_require(Require.new(name, comment)) + context.add_require(RDoc::Require.new(name, comment)) else unget_tk(tk) end @@ -2432,174 +2411,171 @@ module RDoc skip_tkspace_comment name = get_constant_with_optional_parens unless name.empty? - context.add_include(Include.new(name, comment)) + context.add_include RDoc::Include.new(name, comment) end return unless peek_tk.kind_of?(TkCOMMA) get_tk end end - def get_bool - skip_tkspace + def get_bool + skip_tkspace + tk = get_tk + case tk + when TkTRUE + true + when TkFALSE, TkNIL + false + else + unget_tk tk + true + end + end + + def parse_attr(context, single, tk, comment) + args = parse_symbol_arg(1) + if args.size > 0 + name = args[0] + rw = "R" + skip_tkspace(false) tk = get_tk - case tk - when TkTRUE - true - when TkFALSE, TkNIL - false + if tk.kind_of? TkCOMMA + rw = "RW" if get_bool else unget_tk tk - true end - end - - def parse_attr(context, single, tk, comment) - args = parse_symbol_arg(1) - if args.size > 0 - name = args[0] - rw = "R" - skip_tkspace(false) - tk = get_tk - if tk.kind_of? TkCOMMA - rw = "RW" if get_bool - else - unget_tk tk - end - att = Attr.new(get_tkread, name, rw, comment) - read_documentation_modifiers(att, ATTR_MODIFIERS) - if att.document_self - context.add_attribute(att) - end - else - warn("'attr' ignored - looks like a variable") - end - - end - - def parse_visibility(container, single, tk) - singleton = (single == SINGLE) - vis = case tk.name - when "private" then :private - when "protected" then :protected - when "public" then :public - when "private_class_method" - singleton = true - :private - when "public_class_method" - singleton = true - :public - else raise "Invalid visibility: #{tk.name}" - end - - skip_tkspace_comment(false) - case peek_tk - # Ryan Davis suggested the extension to ignore modifiers, because he - # often writes - # - # protected unless $TESTING - # - when TkNL, TkUNLESS_MOD, TkIF_MOD -# error("Missing argument") if singleton - container.ongoing_visibility = vis - else - args = parse_symbol_arg - container.set_visibility_for(args, vis, singleton) + att = RDoc::Attr.new get_tkread, name, rw, comment + read_documentation_modifiers att, RDoc::ATTR_MODIFIERS + if att.document_self + context.add_attribute(att) end + else + warn("'attr' ignored - looks like a variable") end + end - def parse_attr_accessor(context, single, tk, comment) + def parse_visibility(container, single, tk) + singleton = (single == SINGLE) + vis = case tk.name + when "private" then :private + when "protected" then :protected + when "public" then :public + when "private_class_method" + singleton = true + :private + when "public_class_method" + singleton = true + :public + else raise "Invalid visibility: #{tk.name}" + end + + skip_tkspace_comment(false) + case peek_tk + # Ryan Davis suggested the extension to ignore modifiers, because he + # often writes + # + # protected unless $TESTING + # + when TkNL, TkUNLESS_MOD, TkIF_MOD + # error("Missing argument") if singleton + container.ongoing_visibility = vis + else args = parse_symbol_arg - read = get_tkread - rw = "?" + container.set_visibility_for(args, vis, singleton) + end + end - # If nodoc is given, don't document any of them + def parse_attr_accessor(context, single, tk, comment) + args = parse_symbol_arg + read = get_tkread + rw = "?" - tmp = CodeObject.new - read_documentation_modifiers(tmp, ATTR_MODIFIERS) - return unless tmp.document_self + # If nodoc is given, don't document any of them - case tk.name - when "attr_reader" then rw = "R" - when "attr_writer" then rw = "W" - when "attr_accessor" then rw = "RW" - else - rw = @options.extra_accessor_flags[tk.name] - end - - for name in args - att = Attr.new(get_tkread, name, rw, comment) - context.add_attribute(att) - end + tmp = RDoc::CodeObject.new + read_documentation_modifiers tmp, RDoc::ATTR_MODIFIERS + return unless tmp.document_self + + case tk.name + when "attr_reader" then rw = "R" + when "attr_writer" then rw = "W" + when "attr_accessor" then rw = "RW" + else + rw = @options.extra_accessor_flags[tk.name] end - def skip_tkspace_comment(skip_nl = true) - loop do - skip_tkspace(skip_nl) - return unless peek_tk.kind_of? TkCOMMENT - get_tk - end + for name in args + att = RDoc::Attr.new get_tkread, name, rw, comment + context.add_attribute att + end + end + + def skip_tkspace_comment(skip_nl = true) + loop do + skip_tkspace(skip_nl) + return unless peek_tk.kind_of? TkCOMMENT + get_tk end + end - def parse_symbol_arg(no = nil) + def parse_symbol_arg(no = nil) + args = [] + skip_tkspace_comment + case tk = get_tk + when TkLPAREN + loop do + skip_tkspace_comment + if tk1 = parse_symbol_in_arg + args.push tk1 + break if no and args.size >= no + end - args = [] - skip_tkspace_comment - case tk = get_tk - when TkLPAREN - loop do - skip_tkspace_comment - if tk1 = parse_symbol_in_arg - args.push tk1 - break if no and args.size >= no - end - - skip_tkspace_comment - case tk2 = get_tk - when TkRPAREN - break - when TkCOMMA - 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 + skip_tkspace_comment + case tk2 = get_tk + when TkRPAREN + break + when TkCOMMA + 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_comment(false) - skip_tkspace(false) + loop do + # skip_tkspace_comment(false) + skip_tkspace(false) - tk1 = get_tk - unless tk1.kind_of?(TkCOMMA) - 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 + tk1 = get_tk + unless tk1.kind_of?(TkCOMMA) + 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 - args end + args + end - def parse_symbol_in_arg - case tk = get_tk - when TkSYMBOL - tk.text.sub(/^:/, '') - when TkSTRING - eval @read[-1] - else - warn("Expected symbol or string, got #{tk.inspect}") if $DEBUG_RDOC - nil - end + def parse_symbol_in_arg + case tk = get_tk + when TkSYMBOL + tk.text.sub(/^:/, '') + when TkSTRING + eval @read[-1] + else + warn("Expected symbol or string, got #{tk.inspect}") if $DEBUG_RDOC + nil end end diff --git a/lib/rdoc/parsers/parse_simple.rb b/lib/rdoc/parsers/parse_simple.rb index 3f1a546964..f4b501fe93 100644 --- a/lib/rdoc/parsers/parse_simple.rb +++ b/lib/rdoc/parsers/parse_simple.rb @@ -1,41 +1,40 @@ -# Parse a non-source file. We basically take the whole thing -# as one big comment. If the first character in the file -# is '#', we strip leading pound signs. - - -require "rdoc/code_objects" -require "rdoc/markup/simple_markup/preprocess" - -module RDoc - # See rdoc/parsers/parse_c.rb - - class SimpleParser - - # prepare to parse a plain file - def initialize(top_level, file_name, body, options, stats) - - preprocess = SM::PreProcess.new(file_name, options.rdoc_include) - - preprocess.handle(body) do |directive, param| - $stderr.puts "Unrecognized directive '#{directive}' in #{file_name}" - end - - @body = body - @options = options - @top_level = top_level - end - - # Extract the file contents and attach them to the toplevel as a - # comment - - def scan - # @body.gsub(/^(\s\n)+/, '') - @top_level.comment = remove_private_comments(@body) - @top_level - end +require 'rdoc' +require 'rdoc/code_objects' +require 'rdoc/markup/preprocess' + +## +# Parse a non-source file. We basically take the whole thing as one big +# comment. If the first character in the file is '#', we strip leading pound +# signs. + +class RDoc::SimpleParser - def remove_private_comments(comment) - comment.gsub(/^--.*?^\+\+/m, '').sub(/^--.*/m, '') + ## + # Prepare to parse a plain file + + def initialize(top_level, file_name, body, options, stats) + preprocess = RDoc::Markup::PreProcess.new(file_name, options.rdoc_include) + + preprocess.handle(body) do |directive, param| + warn "Unrecognized directive '#{directive}' in #{file_name}" end + + @body = body + @options = options + @top_level = top_level end + + ## + # Extract the file contents and attach them to the toplevel as a comment + + def scan + @top_level.comment = remove_private_comments(@body) + @top_level + end + + def remove_private_comments(comment) + comment.gsub(/^--[^-].*?^\+\+/m, '').sub(/^--.*/m, '') + end + end + diff --git a/lib/rdoc/rdoc.rb b/lib/rdoc/rdoc.rb index 559af5e37a..ffaf56e427 100644 --- a/lib/rdoc/rdoc.rb +++ b/lib/rdoc/rdoc.rb @@ -5,6 +5,7 @@ require 'rdoc/parsers/parse_c.rb' require 'rdoc/parsers/parse_f95.rb' require 'rdoc/parsers/parse_simple.rb' +require 'rdoc/stats' require 'rdoc/options' require 'rdoc/diagram' @@ -15,31 +16,6 @@ require 'time' module RDoc - ## - # Simple stats collector - - class Stats - attr_accessor :num_files, :num_classes, :num_modules, :num_methods - def initialize - @num_files = @num_classes = @num_modules = @num_methods = 0 - @start = Time.now - end - def print - puts "Files: #@num_files" - puts "Classes: #@num_classes" - puts "Modules: #@num_modules" - puts "Methods: #@num_methods" - puts "Elapsed: " + sprintf("%0.3fs", Time.now - @start) - end - end - - ## - # Exception thrown by any rdoc error. - - class Error < RuntimeError; end - - RDocError = Error # :nodoc: - ## # Encapsulate the production of rdoc documentation. Basically # you can use this as you would invoke rdoc from the command @@ -192,22 +168,27 @@ module RDoc # for .document files. def list_files_in_directory(dir, options) - normalized_file_list(options, Dir.glob(File.join(dir, "*")), false, options.exclude) + files = Dir.glob File.join(dir, "*") + + normalized_file_list options, files, false, options.exclude end ## # Parse each file on the command line, recursively entering directories. def parse_files(options) - file_info = [] - files = options.files files = ["."] if files.empty? file_list = normalized_file_list(options, files, true) + return [] if file_list.empty? + + file_info = [] + width = file_list.map { |name| name.length }.max + 1 + file_list.each do |fn| - $stderr.printf("\n%35s: ", File.basename(fn)) unless options.quiet + $stderr.printf("\n%*s: ", width, fn) unless options.quiet content = File.open(fn, "r:ascii-8bit") {|f| f.read} if /coding:\s*(\S+)/ =~ content[/\A(?:.*\n){0,2}/] @@ -252,6 +233,7 @@ module RDoc unless options.all_one_file @last_created = setup_output_dir(options.op_dir, options.force_update) end + start_time = Time.now file_info = parse_files(options) diff --git a/lib/rdoc/ri/descriptions.rb b/lib/rdoc/ri/descriptions.rb index c9b7c9ba77..643d01fea8 100644 --- a/lib/rdoc/ri/descriptions.rb +++ b/lib/rdoc/ri/descriptions.rb @@ -1,5 +1,5 @@ require 'yaml' -require 'rdoc/markup/simple_markup/fragments' +require 'rdoc/markup/fragments' require 'rdoc/ri' #-- @@ -91,7 +91,7 @@ class RDoc::RI::ModuleDescription < RDoc::RI::Description @comment = old.comment else unless old.comment.nil? or old.comment.empty? then - @comment << SM::Flow::RULE.new + @comment << RDoc::Markup::Flow::RULE.new @comment.concat old.comment end end diff --git a/lib/rdoc/ri/display.rb b/lib/rdoc/ri/display.rb index 21fd488bb3..1aa66b7ac5 100644 --- a/lib/rdoc/ri/display.rb +++ b/lib/rdoc/ri/display.rb @@ -26,7 +26,7 @@ end ## # A paging display module. Uses the RDoc::RI::Formatter class to do the actual -# presentation +# presentation. class RDoc::RI::DefaultDisplay diff --git a/lib/rdoc/ri/driver.rb b/lib/rdoc/ri/driver.rb index a1aa64e598..3b3c5fa9d8 100644 --- a/lib/rdoc/ri/driver.rb +++ b/lib/rdoc/ri/driver.rb @@ -6,8 +6,8 @@ require 'rdoc/ri/paths' require 'rdoc/ri/formatter' require 'rdoc/ri/display' require 'fileutils' -require 'rdoc/markup/simple_markup' -require 'rdoc/markup/simple_markup/to_flow' +require 'rdoc/markup' +require 'rdoc/markup/to_flow' class RDoc::RI::Driver @@ -222,7 +222,7 @@ Options may also be set in the 'RI' environment variable. return @class_cache if @class_cache newest = map_dirs('created.rid', :all) do |f| - File.mtime f if test ?f, f + File.mtime f if test ?f, f end.max up_to_date = (File.exist?(class_cache_file_path) and @@ -341,7 +341,7 @@ Options may also be set in the 'RI' environment variable. end def read_yaml(path) - YAML.load File.read(path).gsub(/ \!ruby\/(object|struct):(RDoc|RI).*/, '') + YAML.load File.read(path).gsub(/ \!ruby\/(object|struct):(RDoc::RI|RI).*/, '') end def run diff --git a/lib/rdoc/ri/formatter.rb b/lib/rdoc/ri/formatter.rb index 0a98508dde..960f2e3c91 100644 --- a/lib/rdoc/ri/formatter.rb +++ b/lib/rdoc/ri/formatter.rb @@ -99,17 +99,17 @@ class RDoc::RI::Formatter def display_list(list) case list.type - when SM::ListBase::BULLET + when RDoc::Markup::ListBase::BULLET prefixer = proc { |ignored| @indent + "* " } - when SM::ListBase::NUMBER, - SM::ListBase::UPPERALPHA, - SM::ListBase::LOWERALPHA + when RDoc::Markup::ListBase::NUMBER, + RDoc::Markup::ListBase::UPPERALPHA, + RDoc::Markup::ListBase::LOWERALPHA start = case list.type - when SM::ListBase::NUMBER then 1 - when SM::ListBase::UPPERALPHA then 'A' - when SM::ListBase::LOWERALPHA then 'a' + when RDoc::Markup::ListBase::NUMBER then 1 + when RDoc::Markup::ListBase::UPPERALPHA then 'A' + when RDoc::Markup::ListBase::LOWERALPHA then 'a' end prefixer = proc do |ignored| res = @indent + "#{start}.".ljust(4) @@ -117,15 +117,15 @@ class RDoc::RI::Formatter res end - when SM::ListBase::LABELED + when RDoc::Markup::ListBase::LABELED prefixer = proc do |li| li.label end - when SM::ListBase::NOTE + when RDoc::Markup::ListBase::NOTE longest = 0 list.contents.each do |item| - if item.kind_of?(SM::Flow::LI) && item.label.length > longest + if item.kind_of?(RDoc::Markup::Flow::LI) && item.label.length > longest longest = item.label.length end end @@ -140,7 +140,7 @@ class RDoc::RI::Formatter end list.contents.each do |item| - if item.kind_of? SM::Flow::LI + if item.kind_of? RDoc::Markup::Flow::LI prefix = prefixer.call(item) display_flow_item(item, prefix) else @@ -153,20 +153,20 @@ class RDoc::RI::Formatter def display_flow_item(item, prefix=@indent) case item - when SM::Flow::P, SM::Flow::LI + when RDoc::Markup::Flow::P, RDoc::Markup::Flow::LI wrap(conv_html(item.body), prefix) blankline - when SM::Flow::LIST + when RDoc::Markup::Flow::LIST display_list(item) - when SM::Flow::VERB + when RDoc::Markup::Flow::VERB display_verbatim_flow_item(item, @indent) - when SM::Flow::H + when RDoc::Markup::Flow::H display_heading(conv_html(item.text), item.level, @indent) - when SM::Flow::RULE + when RDoc::Markup::Flow::RULE draw_line else @@ -508,23 +508,23 @@ class RDoc::RI::HtmlFormatter < RDoc::RI::AttributeFormatter def display_list(list) case list.type - when SM::ListBase::BULLET + when RDoc::Markup::ListBase::BULLET list_type = "ul" prefixer = proc { |ignored| "
  • " } - when SM::ListBase::NUMBER, - SM::ListBase::UPPERALPHA, - SM::ListBase::LOWERALPHA + when RDoc::Markup::ListBase::NUMBER, + RDoc::Markup::ListBase::UPPERALPHA, + RDoc::Markup::ListBase::LOWERALPHA list_type = "ol" prefixer = proc { |ignored| "
  • " } - when SM::ListBase::LABELED + when RDoc::Markup::ListBase::LABELED list_type = "dl" prefixer = proc do |li| "
    " + escape(li.label) + "
    " end - when SM::ListBase::NOTE + when RDoc::Markup::ListBase::NOTE list_type = "table" prefixer = proc do |li| %{#{li.label.gsub(/ /, ' ')}} @@ -535,7 +535,7 @@ class RDoc::RI::HtmlFormatter < RDoc::RI::AttributeFormatter print "<#{list_type}>" list.contents.each do |item| - if item.kind_of? SM::Flow::LI + if item.kind_of? RDoc::Markup::Flow::LI prefix = prefixer.call(item) print prefix display_flow_item(item, prefix) diff --git a/lib/rdoc/ri/reader.rb b/lib/rdoc/ri/reader.rb index e56c9fb76e..986bb75954 100644 --- a/lib/rdoc/ri/reader.rb +++ b/lib/rdoc/ri/reader.rb @@ -1,7 +1,7 @@ require 'rdoc/ri' require 'rdoc/ri/descriptions' require 'rdoc/ri/writer' -require 'rdoc/markup/simple_markup/to_flow' +require 'rdoc/markup/to_flow' class RDoc::RI::Reader diff --git a/lib/rdoc/stats.rb b/lib/rdoc/stats.rb new file mode 100644 index 0000000000..0fa2dadf61 --- /dev/null +++ b/lib/rdoc/stats.rb @@ -0,0 +1,25 @@ +require 'rdoc' + +## +# Simple stats collector + +class RDoc::Stats + + attr_accessor :num_files, :num_classes, :num_modules, :num_methods + + def initialize + @num_files = @num_classes = @num_modules = @num_methods = 0 + @start = Time.now + end + + def print + puts "Files: #@num_files" + puts "Classes: #@num_classes" + puts "Modules: #@num_modules" + puts "Methods: #@num_methods" + puts "Elapsed: " + sprintf("%0.3fs", Time.now - @start) + end + +end + + -- cgit v1.2.3