diff options
author | drbrain <drbrain@b2dd03c8-39d4-4d8f-98ff-823fe69b080e> | 2008-01-14 03:34:05 +0000 |
---|---|---|
committer | drbrain <drbrain@b2dd03c8-39d4-4d8f-98ff-823fe69b080e> | 2008-01-14 03:34:05 +0000 |
commit | 49ecbb73cc188b7eadac6cd1a459b3649a3262dc (patch) | |
tree | 347720b09a9bb0f08d442141b51ed074ce43e6b9 | |
parent | 933da53cb15c52b34a4b4b3def7282f4b5fc9631 (diff) | |
download | ruby-49ecbb73cc188b7eadac6cd1a459b3649a3262dc.tar.gz ruby-49ecbb73cc188b7eadac6cd1a459b3649a3262dc.tar.xz ruby-49ecbb73cc188b7eadac6cd1a459b3649a3262dc.zip |
Renamespace lib/rdoc/markup from SM::SimpleMarkup to RDoc::Markup.
git-svn-id: http://svn.ruby-lang.org/repos/ruby/trunk@15033 b2dd03c8-39d4-4d8f-98ff-823fe69b080e
31 files changed, 2509 insertions, 2799 deletions
@@ -1,3 +1,8 @@ +Mon Jan 14 12:33:07 2008 Eric Hodel <drbrain@segment7.net> + + * lib/rdoc/markup*: Renamespace from SM::SimpleMarkup to + RDoc::Markup. + Mon Jan 14 10:45:45 2008 Martin Duerst <duerst@it.aoyama.ac.jp> * enc/ascii.c: Exchanged order of arguments for one ENC_ALIAS @@ -8,59 +8,14 @@ # # $Revision$ -## Transitional Hack #### -# -# RDoc was initially distributed independently, and installed -# itself into <prefix>/lib/ruby/site_ruby/<ver>/rdoc... -# -# Now that RDoc is part of the distribution, it's installed into -# <prefix>/lib/ruby/<ver>, which unfortunately appears later in the -# search path. This means that if you have previously installed RDoc, -# and then install from ruby-lang, you'll pick up the old one by -# default. This hack checks for the condition, and readjusts the -# search path if necessary. - -def adjust_for_existing_rdoc(path) - - $stderr.puts %{ - It seems as if you have a previously-installed RDoc in - the directory #{path}. - - Because this is now out-of-date, you might want to consider - removing the directories: - - #{File.join(path, "rdoc")} - - and - - #{File.join(path, "markup")} - - } - - # Move all the site_ruby directories to the end - p $: - $:.replace($:.partition {|path| /site_ruby/ !~ path}.flatten) - p $: -end - -$:.each do |path| - if /site_ruby/ =~ path - rdoc_path = File.join(path, 'rdoc', 'rdoc.rb') - if File.exist?(rdoc_path) - adjust_for_existing_rdoc(path) - break - end - end -end - -## End of Transitional Hack ## - - require 'rdoc/rdoc' begin r = RDoc::RDoc.new r.document ARGV +rescue Interrupt + $stderr.puts + $stderr.puts "Interrupted" rescue RDoc::Error => e $stderr.puts e.message exit 1 diff --git a/lib/.document b/lib/.document index a7582bf10..f6b301327 100644 --- a/lib/.document +++ b/lib/.document @@ -64,6 +64,7 @@ profiler.rb pstore.rb racc rational.rb +rdoc.rb rdoc readbytes.rb resolv-replace.rb diff --git a/lib/rdoc.rb b/lib/rdoc.rb index 0d2dc2676..25989189a 100644 --- a/lib/rdoc.rb +++ b/lib/rdoc.rb @@ -4,6 +4,13 @@ module RDoc ## + # Exception thrown by any rdoc error. + + class Error < RuntimeError; end + + RDocError = Error # :nodoc: + + ## # RDoc version you are using VERSION = "2.0.0" @@ -14,5 +21,16 @@ module RDoc DOT_DOC_FILENAME = ".document" + GENERAL_MODIFIERS = %w[nodoc].freeze + + CLASS_MODIFIERS = GENERAL_MODIFIERS + + ATTR_MODIFIERS = GENERAL_MODIFIERS + + CONSTANT_MODIFIERS = GENERAL_MODIFIERS + + METHOD_MODIFIERS = GENERAL_MODIFIERS + + %w[arg args yield yields notnew not-new not_new doc] + end diff --git a/lib/rdoc/README b/lib/rdoc/README index 8ad023f35..cb00a5e35 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 <tt>.rb</tt> and <tt>.rbw</tt> are assumed to be Ruby -source. Files ending <tt>.c</tt> 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 <tt>.rbw</tt> 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 <options> [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| ... } -[<tt>--accessor</tt> <i>name[,name...]</i>] - specifies the name(s) of additional methods that should be treated - as if they were <tt>attr_</tt><i>xxx</i> 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. - -[<tt>--all</tt>] - include protected and private methods in the output (by default - only public methods are included) - -[<tt>--charset</tt> _charset_] - Set the character set for the generated HTML. - -[<tt>--diagram</tt>] - 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/). - -[<tt>--exclude</tt> <i>pattern</i>] - exclude files and directories matching this pattern from processing - -[<tt>--extension</tt> <i>new=old</i>] - treat files ending <i>.new</i> as if they ended - <i>.old</i>. Saying '--extension cgi=rb' causes RDoc to treat .cgi - files as Ruby source. - -[<tt>fileboxes</tt>] - 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. - -[<tt>--fmt</tt> _fmt_] - generate output in a particular format. - -[<tt>--help</tt>] - generate a usage summary. - -[<tt>--help-output</tt>] - explain the various output options. - -[<tt>--image-format</tt> <i>gif/png/jpg/jpeg</i>] - sets output image format for diagrams. Can be png, gif, jpeg, - jpg. If this option is omitted, png is used. Requires --diagram. - -[<tt>--include</tt> <i>dir,...</i>] - specify one or more directories to be searched when satisfying - :+include+: directives. Multiple <tt>--include</tt> options may be - given. The directory containing the file currently being processed - is always searched. - -[<tt>--inline-source</tt>] - By default, the source code of methods is shown in a popup. With - this option, it's displayed inline. - -[<tt>line-numbers</tt>] - include line numbers in the source code - -[<tt>--main</tt> _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. - -[<tt>--merge</tt>] - when generating _ri_ output, if classes being processed already - exist in the destination directory, merge in the current details - rather than overwrite them. - -[<tt>--one-file</tt>] - place all the output into a single file - -[<tt>--op</tt> _dir_] - set the output directory to _dir_ (the default is the directory - "doc") - -[<tt>--op-name</tt> _name_] - set the name of the output. Has no effect for HTML. - "doc") - -[<tt>--opname</tt> _name_] - set the output name (has no effect for HTML). - -[<tt>--promiscuous</tt>] - 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. - -[<tt>--quiet</tt>] - do not display progress messages - -[<tt>--ri</tt>, <tt>--ri-site</tt>, _and_ <tt>--ri-system</tt>] - 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/<ver>/site, and --ri-system in - $datadir/ri/<ver>/system. All can be overridden with a subsequent - --op option. All default directories are in ri's default search - path. - -[<tt>--show-hash</tt>] - 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 - -[<tt>--style</tt> <i>stylesheet url</i>] - specifies the URL of an external stylesheet to use (rather than - generating one of our own) - -[<tt>tab-width</tt> _n_] - set the width of tab characters (default 8) - -[<tt>--template</tt> <i>name</i>] - 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. - -[<tt>--version</tt>] - display RDoc's version - -[<tt>--webcvs</tt> _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 <dave@pragmaticprogrammer.com> -= 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: - - <tt>--output</tt> <i>name [, name]</i>:: - specify the name of one or more output files. If multiple - files are present, the first is used as the index. - - <tt>--quiet:</tt>:: 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 \<em>text</em> - *bold*:: \*word* or \<b>text</b> - +typewriter+:: \+word+ or \<tt>text</tt> - - 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 \<em>text</em> - *bold*:: \*word* or \<b>text</b> - +typewriter+:: \+word+ or \<tt>text</tt> - -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 <IMG..>. 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 <tt>label</tt>[url], in which - case the label is used in the displayed text, and <tt>url</tt> is - used as the target. If <tt>label</tt> contains multiple words, - put it in braces: <em>{multi word label}[</em>url<em>]</em>. - -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 - - [<tt>:nodoc:</tt><i>[all]</i>] - 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 <tt>SM::Input</tt> will be - documented. - - [<tt>:doc:</tt>] - 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. - - [<tt>:notnew:</tt>] - 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 '<tt>#--</tt>'. 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 '<tt>#++</tt>'. - - # 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: - - [<tt>:section: title</tt>] - Starts a new section in the output. The title following - <tt>:section:</tt> 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. - # ---------------------------------------- - - [<tt>call-seq:</tt>] - 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. - - [<tt>:include:</tt><i>filename</i>] - include the contents of the named file at this point. The - file will be searched for in the directories listed by - the <tt>--include</tt> 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. - - [<tt>:title:</tt><i>text</i>] - Sets the title for the document. Equivalent to the --title command - line parameter. (The command line parameter overrides any :title: - directive in the source). - - [<tt>:enddoc:</tt>] - Document nothing further at the current level. - - [<tt>:main:</tt><i>name</i>] - Equivalent to the --main command line parameter. - - [<tt>:stopdoc: / :startdoc:</tt>] - 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 - <tt>:stopdoc:</tt> before the first, and a - <tt>:startdoc:</tt> after the last. If you don't specifiy a - <tt>:startdoc:</tt> 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 <dave@pragmaticprogrammer.com> -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 d75670de7..dfc0fff9c 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 cf4bb3a6f..7db23058e 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 32f823553..e6a6cd9ac 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 bb0c4f402..2452d7913 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 000000000..46654ebb5 --- /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 "<digit>.", 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. +# +# [\<b>text...</b>] displays word in a *bold* font +# [\<em>text...</em>] displays word in an _emphasized_ font +# [\<i>text...</i>] displays word in an _emphasized_ font +# [\<tt>text...</tt>] 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 \\\<b>bold +# text</b> and \\\*bold* produce \<b>bold text</b> 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 <IMG..>. 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 <tt>label</tt>[url], in which +# case the label is used in the displayed text, and <tt>url</tt> is +# used as the target. If <tt>label</tt> contains multiple words, +# put it in braces: <em>{multi word label}[</em>url<em>]</em>. +# +# == Synopsis +# +# This code converts <tt>input_string</tt> 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 \<no>text...</no> 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) +# "<font color=red>" + special.text + "</font>" +# 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, "<strike>", "</strike>") +# +# puts "<body>" + p.convert(ARGF.read, h) + "</body>" +# +#-- +# 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 3cf4f21bd..000000000 --- a/lib/rdoc/markup/.document +++ /dev/null @@ -1,2 +0,0 @@ -simple_markup -simple_markup.rb diff --git a/lib/rdoc/markup/simple_markup/fragments.rb b/lib/rdoc/markup/fragments.rb index bd60ca0fe..2dd3614fc 100644 --- a/lib/rdoc/markup/simple_markup/fragments.rb +++ b/lib/rdoc/markup/fragments.rb @@ -1,6 +1,7 @@ -require 'rdoc/markup/simple_markup/lines.rb' +require 'rdoc/markup' +require 'rdoc/markup/lines' -module SM +class RDoc::Markup ## # A Fragment is a chunk of text, subclassed as a paragraph, a list @@ -119,7 +120,7 @@ module SM ## # Collect groups of lines together. Each group will end up containing a flow - # of text + # of text. class LineCollection diff --git a/lib/rdoc/markup/simple_markup/inline.rb b/lib/rdoc/markup/inline.rb index eafda011e..cbf5032a6 100644 --- a/lib/rdoc/markup/simple_markup/inline.rb +++ b/lib/rdoc/markup/inline.rb @@ -1,8 +1,10 @@ -module SM +require 'rdoc/markup' + +class RDoc::Markup ## # We manage a set of attributes. Each attribute has a symbol name and a bit - # value + # value. class Attribute SPECIAL = 1 @@ -39,7 +41,7 @@ module SM ## # 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 + # attributes to turn on, and a bitmap of those to turn off. AttrChanger = Struct.new(:turn_on, :turn_off) @@ -50,7 +52,7 @@ module SM end ## - # An array of attributes which parallels the characters in a string + # An array of attributes which parallels the characters in a string. class AttrSpan def initialize(length) @@ -84,12 +86,12 @@ module SM end def to_s - "Special: type=#{type}, name=#{SM::Attribute.as_string type}, text=#{text.dump}" + "Special: type=#{type}, name=#{RDoc::Markup::Attribute.as_string type}, text=#{text.dump}" end def inspect - "#<SM::Special:0x%x @type=%p, name=%p @text=%p>" % [ - object_id, @type, SM::Attribute.as_string(type), text.dump] + "#<RDoc::Markup::Special:0x%x @type=%p, name=%p @text=%p>" % [ + object_id, @type, RDoc::Markup::Attribute.as_string(type), text.dump] end end diff --git a/lib/rdoc/markup/simple_markup/lines.rb b/lib/rdoc/markup/lines.rb index 8ef66079b..5fb5bde99 100644 --- a/lib/rdoc/markup/simple_markup/lines.rb +++ b/lib/rdoc/markup/lines.rb @@ -1,4 +1,4 @@ -module SM +class RDoc::Markup ## # We store the lines we're working on as objects of class Line. These @@ -14,7 +14,7 @@ module SM RULE = :RULE PARAGRAPH = :PARAGRAPH VERBATIM = :VERBATIM - + # line type attr_accessor :type @@ -36,7 +36,6 @@ module SM # true if this line has been deleted from the list of lines attr_accessor :deleted - def initialize(text) @text = text.dup @@ -69,7 +68,6 @@ module SM ## # Strip off the leading margin - # def strip_leading(size) if @text.size > size @@ -85,7 +83,7 @@ module SM end ## - # A container for all the lines + # A container for all the lines. class Lines diff --git a/lib/rdoc/markup/preprocess.rb b/lib/rdoc/markup/preprocess.rb new file mode 100644 index 000000000..760351d38 --- /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 ed3b4d327..000000000 --- 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 "<digit>.", 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. -# -# [\<b>text...</b>] displays word in a *bold* font -# [\<em>text...</em>] displays word in an _emphasized_ font -# [\<i>text...</i>] displays word in an _emphasized_ font -# [\<tt>text...</tt>] 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 \\\<b>bold -# text</b> and \\\*bold* produce \<b>bold text</b> 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 <tt>input_string</tt>, 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 \<no>text...</no> 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) - # "<font color=red>" + special.text + "</font>" - # 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, "<strike>", "</strike>") - # - # puts "<body>" + p.convert(ARGF.read, h) + "</body>" - - 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/preprocess.rb b/lib/rdoc/markup/simple_markup/preprocess.rb deleted file mode 100644 index c7d29ad48..000000000 --- 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_html.rb b/lib/rdoc/markup/simple_markup/to_html.rb deleted file mode 100644 index d78468684..000000000 --- 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 => [ "<ul>", "</ul>" ], - ListBase::NUMBER => [ "<ol>", "</ol>" ], - ListBase::UPPERALPHA => [ "<ol>", "</ol>" ], - ListBase::LOWERALPHA => [ "<ol>", "</ol>" ], - ListBase::LABELED => [ "<dl>", "</dl>" ], - ListBase::NOTE => [ "<table>", "</table>" ], - } - - 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), "<b>", "</b>"), - InlineTag.new(SM::Attribute.bitmap_for(:TT), "<tt>", "</tt>"), - InlineTag.new(SM::Attribute.bitmap_for(:EM), "<em>", "</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("<p>") + "\n" - @res << wrap(convert_flow(am.flow(fragment.txt))) - @res << annotate("</p>") + "\n" - end - - def accept_verbatim(am, fragment) - @res << annotate("<pre>") + "\n" - @res << CGI.escapeHTML(fragment.txt) - @res << annotate("</pre>") << "\n" - end - - def accept_rule(am, fragment) - size = fragment.param - size = 10 if size > 10 - @res << "<hr size=\"#{size}\"></hr>" - 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("<p />") << "\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 .<elipsis>) - 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("<h#{level}>") + - convert_flow(flow) + - annotate("</h#{level}>\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("<li>") - - when ListBase::UPPERALPHA - annotate("<li type=\"A\">") - - when ListBase::LOWERALPHA - annotate("<li type=\"a\">") - - when ListBase::LABELED - annotate("<dt>") + - convert_flow(am.flow(fragment.param)) + - annotate("</dt>") + - annotate("<dd>") - - when ListBase::NOTE - annotate("<tr>") + - annotate("<td valign=\"top\">") + - convert_flow(am.flow(fragment.param)) + - annotate("</td>") + - annotate("<td>") - 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 - "</li>" - when ListBase::LABELED - "</dd>" - when ListBase::NOTE - "</td></tr>" - 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 b014b6990..000000000 --- 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(/</, LESSTHAN). - 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 .<elipsis>) - 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/simple_markup/to_flow.rb b/lib/rdoc/markup/to_flow.rb index fc8c5f5ce..58ae52be2 100644 --- a/lib/rdoc/markup/simple_markup/to_flow.rb +++ b/lib/rdoc/markup/to_flow.rb @@ -1,8 +1,8 @@ -require 'rdoc/markup/simple_markup/fragments' -require 'rdoc/markup/simple_markup/inline' +require 'rdoc/markup/fragments' +require 'rdoc/markup/inline' require 'cgi' -module SM +class RDoc::Markup module Flow P = Struct.new(:body) @@ -24,12 +24,12 @@ module SM class ToFlow LIST_TYPE_TO_HTML = { - SM::ListBase::BULLET => [ "<ul>", "</ul>" ], - SM::ListBase::NUMBER => [ "<ol>", "</ol>" ], - SM::ListBase::UPPERALPHA => [ "<ol>", "</ol>" ], - SM::ListBase::LOWERALPHA => [ "<ol>", "</ol>" ], - SM::ListBase::LABELED => [ "<dl>", "</dl>" ], - SM::ListBase::NOTE => [ "<table>", "</table>" ], + RDoc::Markup::ListBase::BULLET => [ "<ul>", "</ul>" ], + RDoc::Markup::ListBase::NUMBER => [ "<ol>", "</ol>" ], + RDoc::Markup::ListBase::UPPERALPHA => [ "<ol>", "</ol>" ], + RDoc::Markup::ListBase::LOWERALPHA => [ "<ol>", "</ol>" ], + RDoc::Markup::ListBase::LABELED => [ "<dl>", "</dl>" ], + RDoc::Markup::ListBase::NOTE => [ "<table>", "</table>" ], } InlineTag = Struct.new(:bit, :on, :off) @@ -43,9 +43,9 @@ module SM def init_tags @attr_tags = [ - InlineTag.new(SM::Attribute.bitmap_for(:BOLD), "<b>", "</b>"), - InlineTag.new(SM::Attribute.bitmap_for(:TT), "<tt>", "</tt>"), - InlineTag.new(SM::Attribute.bitmap_for(:EM), "<em>", "</em>"), + InlineTag.new(RDoc::Markup::Attribute.bitmap_for(:BOLD), "<b>", "</b>"), + InlineTag.new(RDoc::Markup::Attribute.bitmap_for(:TT), "<tt>", "</tt>"), + InlineTag.new(RDoc::Markup::Attribute.bitmap_for(:EM), "<em>", "</em>"), ] end @@ -54,7 +54,7 @@ module SM # end tags for flexibility def add_tag(name, start, stop) - @attr_tags << InlineTag.new(SM::Attribute.bitmap_for(name), start, stop) + @attr_tags << InlineTag.new(RDoc::Markup::Attribute.bitmap_for(name), start, stop) end ## diff --git a/lib/rdoc/markup/to_html.rb b/lib/rdoc/markup/to_html.rb new file mode 100644 index 000000000..98259cfd1 --- /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 => [ "<ul>", "</ul>" ], + RDoc::Markup::ListBase::NUMBER => [ "<ol>", "</ol>" ], + RDoc::Markup::ListBase::UPPERALPHA => [ "<ol>", "</ol>" ], + RDoc::Markup::ListBase::LOWERALPHA => [ "<ol>", "</ol>" ], + RDoc::Markup::ListBase::LABELED => [ "<dl>", "</dl>" ], + RDoc::Markup::ListBase::NOTE => [ "<table>", "</table>" ], + } + + 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), "<b>", "</b>"), + InlineTag.new(RDoc::Markup::Attribute.bitmap_for(:TT), "<tt>", "</tt>"), + InlineTag.new(RDoc::Markup::Attribute.bitmap_for(:EM), "<em>", "</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("<p>") + "\n" + @res << wrap(convert_flow(am.flow(fragment.txt))) + @res << annotate("</p>") + "\n" + end + + def accept_verbatim(am, fragment) + @res << annotate("<pre>") + "\n" + @res << CGI.escapeHTML(fragment.txt) + @res << annotate("</pre>") << "\n" + end + + def accept_rule(am, fragment) + size = fragment.param + size = 10 if size > 10 + @res << "<hr size=\"#{size}\"></hr>" + 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("<p />") << "\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 .<elipsis>) + 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("<h#{level}>") + + convert_flow(flow) + + annotate("</h#{level}>\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("<li>") + + when RDoc::Markup::ListBase::UPPERALPHA then + annotate("<li type=\"A\">") + + when RDoc::Markup::ListBase::LOWERALPHA then + annotate("<li type=\"a\">") + + when RDoc::Markup::ListBase::LABELED then + annotate("<dt>") + + convert_flow(am.flow(fragment.param)) + + annotate("</dt>") + + annotate("<dd>") + + when RDoc::Markup::ListBase::NOTE then + annotate("<tr>") + + annotate("<td valign=\"top\">") + + convert_flow(am.flow(fragment.param)) + + annotate("</td>") + + annotate("<td>") + 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 + "</li>" + when RDoc::Markup::ListBase::LABELED then + "</dd>" + when RDoc::Markup::ListBase::NOTE then + "</td></tr>" + 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 000000000..2964e315b --- /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(/</, LESSTHAN). + 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 .<elipsis>) + 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 42256b252..baf90577c 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 +class RDoc::RubyParser - CONSTANT_MODIFIERS = GENERAL_MODIFIERS - - 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 = "<unknown>" 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" - 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 peek_tk.kind_of?(TkLT) + get_tk + skip_tkspace(true) + superclass = get_class_specification + superclass = "<unknown>" if superclass.empty? + 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 + if single == SINGLE + cls_type = RDoc::SingleClass + else + cls_type = RDoc::NormalClass + 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 - remove_token_listener(self) + meth = RDoc::AnyMethod.new get_tkread, name + meth.singleton = (single == SINGLE) + end - meth.start_collecting_tokens - indent = TkSPACE.new(1,1) - indent.set_text(" " * column) + remove_token_listener(self) - meth.add_tokens([TkCOMMENT.new(line_no, - 1, - "# File #{@top_level.file_absolute_name}, line #{line_no}"), - NEWLINE_TOKEN, - indent]) + meth.start_collecting_tokens + indent = TkSPACE.new(1,1) + indent.set_text(" " * column) - meth.add_tokens(@token_stream) + meth.add_tokens([TkCOMMENT.new(line_no, + 1, + "# File #{@top_level.file_absolute_name}, line #{line_no}"), + NEWLINE_TOKEN, + indent]) - add_token_listener(meth) + meth.add_tokens(@token_stream) - @scanner.instance_eval{@continue = false} - parse_method_parameters(meth) + add_token_listener(meth) - if meth.document_self - container.add_method(meth) - elsif added_container - container.document_self = false - end + @scanner.instance_eval{@continue = false} + parse_method_parameters(meth) - # Having now read the method parameters and documentation modifiers, we - # now know whether we have to rename #initialize to ::new + if meth.document_self + container.add_method(meth) + elsif added_container + container.document_self = false + end - 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) + # 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 - 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 + + def parse_call_parameters(tk) - end_token = case tk - when TkLPAREN, TkfLPAREN - TkRPAREN - when TkRPAREN - return "" - else - TkNL - end - nest = 0 + 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 + 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 + + 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 - name = get_constant + name = get_constant - 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) + 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_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 - def parse_attr_accessor(context, single, tk, comment) + 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 parse_symbol_arg(no = nil) + def skip_tkspace_comment(skip_nl = true) + loop do + skip_tkspace(skip_nl) + return unless peek_tk.kind_of? TkCOMMENT + get_tk + end + 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 + 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 - loop do -# skip_tkspace_comment(false) - skip_tkspace(false) + 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 - 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 + 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 - 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 3f1a54696..f4b501fe9 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 559af5e37..ffaf56e42 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' @@ -16,31 +17,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 # line: @@ -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 c9b7c9ba7..643d01fea 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 21fd488bb..1aa66b7ac 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 a1aa64e59..3b3c5fa9d 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 0a98508dd..960f2e3c9 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| "<li>" } - 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| "<li>" } - when SM::ListBase::LABELED + when RDoc::Markup::ListBase::LABELED list_type = "dl" prefixer = proc do |li| "<dt><b>" + escape(li.label) + "</b><dd>" end - when SM::ListBase::NOTE + when RDoc::Markup::ListBase::NOTE list_type = "table" prefixer = proc do |li| %{<tr valign="top"><td>#{li.label.gsub(/ /, ' ')}</td><td>} @@ -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 e56c9fb76..986bb7595 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 000000000..0fa2dadf6 --- /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 + + |