diff options
Diffstat (limited to 'lib/rdoc')
222 files changed, 48411 insertions, 17983 deletions
diff --git a/lib/rdoc/.document b/lib/rdoc/.document new file mode 100644 index 0000000000..41333c6411 --- /dev/null +++ b/lib/rdoc/.document @@ -0,0 +1 @@ +*.rb diff --git a/lib/rdoc/README b/lib/rdoc/README deleted file mode 100644 index 89ea0fbd3f..0000000000 --- a/lib/rdoc/README +++ /dev/null @@ -1,489 +0,0 @@ -= 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'. - -== 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 try writing your own HTML output template, see - RDoc::Page. - -== Summary - -Once installed, you can create documentation using the 'rdoc' command -(the command is 'rdoc.bat' under Windows) - - % rdoc [options] [names...] - -Type "rdoc --help" for an up-to-date option summary. - -A typical use might be to generate documentation for a package of Ruby -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 -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 -case, we could type - - % rdoc --main rdoc/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. - -== Credits - -* 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. - -* Code to diagram classes and modules was written by Sergey A Yanovitsky - (Jah) of Enticla. - -* Charset patch from MoonWolf. - -* Rich Kilmer wrote the kilmer.rb output template. - -* Dan Brickley led the design of the RDF format. - -== 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. - -= Usage - -RDoc is invoked from the command line using: - - % rdoc <options> [name...] - -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. - -Options are: - -[<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 - - db_opt :name, :age - - 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/generators/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 - -A typical small Ruby program commented using RDoc might be as follows. You -can see the formatted result in EXAMPLE.rb and Anagram. - - :include: EXAMPLE.rb - -= Markup - -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: - - =begin rdoc - Documentation to - be processed by RDoc. - =end - -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. - - 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. - -= Other stuff - -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. - -== 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. diff --git a/lib/rdoc/alias.rb b/lib/rdoc/alias.rb new file mode 100644 index 0000000000..858e053049 --- /dev/null +++ b/lib/rdoc/alias.rb @@ -0,0 +1,112 @@ +# frozen_string_literal: true +## +# Represent an alias, which is an old_name/new_name pair associated with a +# particular context +#-- +# TODO implement Alias as a proxy to a method/attribute, inheriting from +# MethodAttr + +class RDoc::Alias < RDoc::CodeObject + + ## + # Aliased method's name + + attr_reader :new_name + + alias name new_name + + ## + # Aliasee method's name + + attr_reader :old_name + + ## + # Is this an alias declared in a singleton context? + + attr_accessor :singleton + + ## + # Source file token stream + + attr_reader :text + + ## + # Creates a new Alias with a token stream of +text+ that aliases +old_name+ + # to +new_name+, has +comment+ and is a +singleton+ context. + + def initialize(text, old_name, new_name, comment, singleton = false) + super() + + @text = text + @singleton = singleton + @old_name = old_name + @new_name = new_name + self.comment = comment + end + + ## + # Order by #singleton then #new_name + + def <=>(other) + [@singleton ? 0 : 1, new_name] <=> [other.singleton ? 0 : 1, other.new_name] + end + + ## + # HTML fragment reference for this alias + + def aref + type = singleton ? 'c' : 'i' + "#alias-#{type}-#{html_name}" + end + + ## + # Full old name including namespace + + def full_old_name + @full_name || "#{parent.name}#{pretty_old_name}" + end + + ## + # HTML id-friendly version of +#new_name+. + + def html_name + CGI.escape(@new_name.gsub('-', '-2D')).gsub('%','-').sub(/^-/, '') + end + + def inspect # :nodoc: + parent_name = parent ? parent.name : '(unknown)' + "#<%s:0x%x %s.alias_method %s, %s>" % [ + self.class, object_id, + parent_name, @old_name, @new_name, + ] + end + + ## + # '::' for the alias of a singleton method/attribute, '#' for instance-level. + + def name_prefix + singleton ? '::' : '#' + end + + ## + # Old name with prefix '::' or '#'. + + def pretty_old_name + "#{singleton ? '::' : '#'}#{@old_name}" + end + + ## + # New name with prefix '::' or '#'. + + def pretty_new_name + "#{singleton ? '::' : '#'}#{@new_name}" + end + + alias pretty_name pretty_new_name + + def to_s # :nodoc: + "alias: #{self.new_name} -> #{self.pretty_old_name} in: #{parent}" + end + +end + diff --git a/lib/rdoc/anon_class.rb b/lib/rdoc/anon_class.rb new file mode 100644 index 0000000000..d02a38c2cf --- /dev/null +++ b/lib/rdoc/anon_class.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true +## +# An anonymous class like: +# +# c = Class.new do end +# +# AnonClass is currently not used. + +class RDoc::AnonClass < RDoc::ClassModule +end + diff --git a/lib/rdoc/any_method.rb b/lib/rdoc/any_method.rb new file mode 100644 index 0000000000..9b0d309653 --- /dev/null +++ b/lib/rdoc/any_method.rb @@ -0,0 +1,316 @@ +# frozen_string_literal: true +## +# AnyMethod is the base class for objects representing methods + +class RDoc::AnyMethod < RDoc::MethodAttr + + ## + # 2:: + # RDoc 4 + # Added calls_super + # Added parent name and class + # Added section title + # 3:: + # RDoc 4.1 + # Added is_alias_for + + MARSHAL_VERSION = 3 # :nodoc: + + ## + # Don't rename \#initialize to \::new + + attr_accessor :dont_rename_initialize + + ## + # The C function that implements this method (if it was defined in a C file) + + attr_accessor :c_function + + ## + # Different ways to call this method + + attr_reader :call_seq + + ## + # Parameters for this method + + attr_accessor :params + + ## + # If true this method uses +super+ to call a superclass version + + attr_accessor :calls_super + + include RDoc::TokenStream + + ## + # Creates a new AnyMethod with a token stream +text+ and +name+ + + def initialize text, name + super + + @c_function = nil + @dont_rename_initialize = false + @token_stream = nil + @calls_super = false + @superclass_method = nil + end + + ## + # Adds +an_alias+ as an alias for this method in +context+. + + def add_alias an_alias, context = nil + method = self.class.new an_alias.text, an_alias.new_name + + method.record_location an_alias.file + method.singleton = self.singleton + method.params = self.params + method.visibility = self.visibility + method.comment = an_alias.comment + method.is_alias_for = self + @aliases << method + context.add_method method if context + method + end + + ## + # Prefix for +aref+ is 'method'. + + def aref_prefix + 'method' + end + + ## + # The call_seq or the param_seq with method name, if there is no call_seq. + # + # Use this for displaying a method's argument lists. + + def arglists + if @call_seq then + @call_seq + elsif @params then + "#{name}#{param_seq}" + end + end + + ## + # Sets the different ways you can call this method. If an empty +call_seq+ + # is given nil is assumed. + # + # See also #param_seq + + def call_seq= call_seq + return if call_seq.empty? + + @call_seq = call_seq + end + + ## + # Loads is_alias_for from the internal name. Returns nil if the alias + # cannot be found. + + def is_alias_for # :nodoc: + case @is_alias_for + when RDoc::MethodAttr then + @is_alias_for + when Array then + return nil unless @store + + klass_name, singleton, method_name = @is_alias_for + + return nil unless klass = @store.find_class_or_module(klass_name) + + @is_alias_for = klass.find_method method_name, singleton + end + end + + ## + # Dumps this AnyMethod for use by ri. See also #marshal_load + + def marshal_dump + aliases = @aliases.map do |a| + [a.name, parse(a.comment)] + end + + is_alias_for = [ + @is_alias_for.parent.full_name, + @is_alias_for.singleton, + @is_alias_for.name + ] if @is_alias_for + + [ MARSHAL_VERSION, + @name, + full_name, + @singleton, + @visibility, + parse(@comment), + @call_seq, + @block_params, + aliases, + @params, + @file.relative_name, + @calls_super, + @parent.name, + @parent.class, + @section.title, + is_alias_for, + ] + end + + ## + # Loads this AnyMethod from +array+. For a loaded AnyMethod the following + # methods will return cached values: + # + # * #full_name + # * #parent_name + + def marshal_load array + initialize_visibility + + @dont_rename_initialize = nil + @token_stream = nil + @aliases = [] + @parent = nil + @parent_name = nil + @parent_class = nil + @section = nil + @file = nil + + version = array[0] + @name = array[1] + @full_name = array[2] + @singleton = array[3] + @visibility = array[4] + @comment = array[5] + @call_seq = array[6] + @block_params = array[7] + # 8 handled below + @params = array[9] + # 10 handled below + @calls_super = array[11] + @parent_name = array[12] + @parent_title = array[13] + @section_title = array[14] + @is_alias_for = array[15] + + array[8].each do |new_name, comment| + add_alias RDoc::Alias.new(nil, @name, new_name, comment, @singleton) + end + + @parent_name ||= if @full_name =~ /#/ then + $` + else + name = @full_name.split('::') + name.pop + name.join '::' + end + + @file = RDoc::TopLevel.new array[10] if version > 0 + end + + ## + # Method name + # + # If the method has no assigned name, it extracts it from #call_seq. + + def name + return @name if @name + + @name = + @call_seq[/^.*?\.(\w+)/, 1] || + @call_seq[/^.*?(\w+)/, 1] || + @call_seq if @call_seq + end + + ## + # A list of this method's method and yield parameters. +call-seq+ params + # are preferred over parsed method and block params. + + def param_list + if @call_seq then + params = @call_seq.split("\n").last + params = params.sub(/.*?\((.*)\)/, '\1') + params = params.sub(/(\{|do)\s*\|([^|]*)\|.*/, ',\2') + elsif @params then + params = @params.sub(/\((.*)\)/, '\1') + + params << ",#{@block_params}" if @block_params + elsif @block_params then + params = @block_params + else + return [] + end + + if @block_params then + # If this method has explicit block parameters, remove any explicit + # &block + params = params.sub(/,?\s*&\w+/, '') + else + params = params.sub(/\&(\w+)/, '\1') + end + + params = params.gsub(/\s+/, '').split(',').reject(&:empty?) + + params.map { |param| param.sub(/=.*/, '') } + end + + ## + # Pretty parameter list for this method. If the method's parameters were + # given by +call-seq+ it is preferred over the parsed values. + + def param_seq + if @call_seq then + params = @call_seq.split("\n").last + params = params.sub(/[^( ]+/, '') + params = params.sub(/(\|[^|]+\|)\s*\.\.\.\s*(end|\})/, '\1 \2') + elsif @params then + params = @params.gsub(/\s*\#.*/, '') + params = params.tr_s("\n ", " ") + params = "(#{params})" unless params[0] == ?( + else + params = '' + end + + if @block_params then + # If this method has explicit block parameters, remove any explicit + # &block + params = params.sub(/,?\s*&\w+/, '') + + block = @block_params.tr_s("\n ", " ") + if block[0] == ?( + block = block.sub(/^\(/, '').sub(/\)/, '') + end + params << " { |#{block}| ... }" + end + + params + end + + ## + # Sets the store for this method and its referenced code objects. + + def store= store + super + + @file = @store.add_file @file.full_name if @file + end + + ## + # For methods that +super+, find the superclass method that would be called. + + def superclass_method + return unless @calls_super + return @superclass_method if @superclass_method + + parent.each_ancestor do |ancestor| + if method = ancestor.method_list.find { |m| m.name == @name } then + @superclass_method = method + break + end + end + + @superclass_method + end + +end + diff --git a/lib/rdoc/attr.rb b/lib/rdoc/attr.rb new file mode 100644 index 0000000000..f780b3b976 --- /dev/null +++ b/lib/rdoc/attr.rb @@ -0,0 +1,176 @@ +# frozen_string_literal: true +## +# An attribute created by \#attr, \#attr_reader, \#attr_writer or +# \#attr_accessor + +class RDoc::Attr < RDoc::MethodAttr + + ## + # 3:: + # RDoc 4 + # Added parent name and class + # Added section title + + MARSHAL_VERSION = 3 # :nodoc: + + ## + # Is the attribute readable ('R'), writable ('W') or both ('RW')? + + attr_accessor :rw + + ## + # Creates a new Attr with body +text+, +name+, read/write status +rw+ and + # +comment+. +singleton+ marks this as a class attribute. + + def initialize(text, name, rw, comment, singleton = false) + super text, name + + @rw = rw + @singleton = singleton + self.comment = comment + end + + ## + # Attributes are equal when their names, singleton and rw are identical + + def == other + self.class == other.class and + self.name == other.name and + self.rw == other.rw and + self.singleton == other.singleton + end + + ## + # Add +an_alias+ as an attribute in +context+. + + def add_alias(an_alias, context) + new_attr = self.class.new(self.text, an_alias.new_name, self.rw, + self.comment, self.singleton) + + new_attr.record_location an_alias.file + new_attr.visibility = self.visibility + new_attr.is_alias_for = self + @aliases << new_attr + context.add_attribute new_attr + new_attr + end + + ## + # The #aref prefix for attributes + + def aref_prefix + 'attribute' + end + + ## + # Attributes never call super. See RDoc::AnyMethod#calls_super + # + # An RDoc::Attr can show up in the method list in some situations (see + # Gem::ConfigFile) + + def calls_super # :nodoc: + false + end + + ## + # Returns attr_reader, attr_writer or attr_accessor as appropriate. + + def definition + case @rw + when 'RW' then 'attr_accessor' + when 'R' then 'attr_reader' + when 'W' then 'attr_writer' + end + end + + def inspect # :nodoc: + alias_for = @is_alias_for ? " (alias for #{@is_alias_for.name})" : nil + visibility = self.visibility + visibility = "forced #{visibility}" if force_documentation + "#<%s:0x%x %s %s (%s)%s>" % [ + self.class, object_id, + full_name, + rw, + visibility, + alias_for, + ] + end + + ## + # Dumps this Attr for use by ri. See also #marshal_load + + def marshal_dump + [ MARSHAL_VERSION, + @name, + full_name, + @rw, + @visibility, + parse(@comment), + singleton, + @file.relative_name, + @parent.full_name, + @parent.class, + @section.title + ] + end + + ## + # Loads this Attr from +array+. For a loaded Attr the following + # methods will return cached values: + # + # * #full_name + # * #parent_name + + def marshal_load array + initialize_visibility + + @aliases = [] + @parent = nil + @parent_name = nil + @parent_class = nil + @section = nil + @file = nil + + version = array[0] + @name = array[1] + @full_name = array[2] + @rw = array[3] + @visibility = array[4] + @comment = array[5] + @singleton = array[6] || false # MARSHAL_VERSION == 0 + # 7 handled below + @parent_name = array[8] + @parent_class = array[9] + @section_title = array[10] + + @file = RDoc::TopLevel.new array[7] if version > 1 + + @parent_name ||= @full_name.split('#', 2).first + end + + def pretty_print q # :nodoc: + q.group 2, "[#{self.class.name} #{full_name} #{rw} #{visibility}", "]" do + unless comment.empty? then + q.breakable + q.text "comment:" + q.breakable + q.pp @comment + end + end + end + + def to_s # :nodoc: + "#{definition} #{name} in: #{parent}" + end + + ## + # Attributes do not have token streams. + # + # An RDoc::Attr can show up in the method list in some situations (see + # Gem::ConfigFile) + + def token_stream # :nodoc: + end + +end + diff --git a/lib/rdoc/class_module.rb b/lib/rdoc/class_module.rb new file mode 100644 index 0000000000..fdd56e236b --- /dev/null +++ b/lib/rdoc/class_module.rb @@ -0,0 +1,802 @@ +# frozen_string_literal: true +## +# ClassModule is the base class for objects representing either a class or a +# module. + +class RDoc::ClassModule < RDoc::Context + + ## + # 1:: + # RDoc 3.7 + # * Added visibility, singleton and file to attributes + # * Added file to constants + # * Added file to includes + # * Added file to methods + # 2:: + # RDoc 3.13 + # * Added extends + # 3:: + # RDoc 4.0 + # * Added sections + # * Added in_files + # * Added parent name + # * Complete Constant dump + + MARSHAL_VERSION = 3 # :nodoc: + + ## + # Constants that are aliases for this class or module + + attr_accessor :constant_aliases + + ## + # Comment and the location it came from. Use #add_comment to add comments + + attr_accessor :comment_location + + attr_accessor :diagram # :nodoc: + + ## + # Class or module this constant is an alias for + + attr_accessor :is_alias_for + + ## + # Return a RDoc::ClassModule of class +class_type+ that is a copy + # of module +module+. Used to promote modules to classes. + #-- + # TODO move to RDoc::NormalClass (I think) + + def self.from_module class_type, mod + klass = class_type.new mod.name + + mod.comment_location.each do |comment, location| + klass.add_comment comment, location + end + + klass.parent = mod.parent + klass.section = mod.section + klass.viewer = mod.viewer + + klass.attributes.concat mod.attributes + klass.method_list.concat mod.method_list + klass.aliases.concat mod.aliases + klass.external_aliases.concat mod.external_aliases + klass.constants.concat mod.constants + klass.includes.concat mod.includes + klass.extends.concat mod.extends + + klass.methods_hash.update mod.methods_hash + klass.constants_hash.update mod.constants_hash + + klass.current_section = mod.current_section + klass.in_files.concat mod.in_files + klass.sections.concat mod.sections + klass.unmatched_alias_lists = mod.unmatched_alias_lists + klass.current_section = mod.current_section + klass.visibility = mod.visibility + + klass.classes_hash.update mod.classes_hash + klass.modules_hash.update mod.modules_hash + klass.metadata.update mod.metadata + + klass.document_self = mod.received_nodoc ? nil : mod.document_self + klass.document_children = mod.document_children + klass.force_documentation = mod.force_documentation + klass.done_documenting = mod.done_documenting + + # update the parent of all children + + (klass.attributes + + klass.method_list + + klass.aliases + + klass.external_aliases + + klass.constants + + klass.includes + + klass.extends + + klass.classes + + klass.modules).each do |obj| + obj.parent = klass + obj.full_name = nil + end + + klass + end + + ## + # Creates a new ClassModule with +name+ with optional +superclass+ + # + # This is a constructor for subclasses, and must never be called directly. + + def initialize(name, superclass = nil) + @constant_aliases = [] + @diagram = nil + @is_alias_for = nil + @name = name + @superclass = superclass + @comment_location = [] # [[comment, location]] + + super() + end + + ## + # Adds +comment+ to this ClassModule's list of comments at +location+. This + # method is preferred over #comment= since it allows ri data to be updated + # across multiple runs. + + def add_comment comment, location + return unless document_self + + original = comment + + comment = case comment + when RDoc::Comment then + comment.normalize + else + normalize_comment comment + end + + if location.parser == RDoc::Parser::C + @comment_location.delete_if { |(_, l)| l == location } + end + + @comment_location << [comment, location] + + self.comment = original + end + + def add_things my_things, other_things # :nodoc: + other_things.each do |group, things| + my_things[group].each { |thing| yield false, thing } if + my_things.include? group + + things.each do |thing| + yield true, thing + end + end + end + + ## + # Ancestors list for this ClassModule: the list of included modules + # (classes will add their superclass if any). + # + # Returns the included classes or modules, not the includes + # themselves. The returned values are either String or + # RDoc::NormalModule instances (see RDoc::Include#module). + # + # The values are returned in reverse order of their inclusion, + # which is the order suitable for searching methods/attributes + # in the ancestors. The superclass, if any, comes last. + + def ancestors + includes.map { |i| i.module }.reverse + end + + def aref_prefix # :nodoc: + raise NotImplementedError, "missing aref_prefix for #{self.class}" + end + + ## + # HTML fragment reference for this module or class. See + # RDoc::NormalClass#aref and RDoc::NormalModule#aref + + def aref + "#{aref_prefix}-#{full_name}" + end + + ## + # Ancestors of this class or module only + + alias direct_ancestors ancestors + + ## + # Clears the comment. Used by the Ruby parser. + + def clear_comment + @comment = '' + end + + ## + # This method is deprecated, use #add_comment instead. + # + # Appends +comment+ to the current comment, but separated by a rule. Works + # more like <tt>+=</tt>. + + def comment= comment # :nodoc: + comment = case comment + when RDoc::Comment then + comment.normalize + else + normalize_comment comment + end + + comment = "#{@comment}\n---\n#{comment}" unless @comment.empty? + + super comment + end + + ## + # Prepares this ClassModule for use by a generator. + # + # See RDoc::Store#complete + + def complete min_visibility + update_aliases + remove_nodoc_children + update_includes + remove_invisible min_visibility + end + + ## + # Does this ClassModule or any of its methods have document_self set? + + def document_self_or_methods + document_self || method_list.any?{ |m| m.document_self } + end + + ## + # Does this class or module have a comment with content or is + # #received_nodoc true? + + def documented? + return true if @received_nodoc + return false if @comment_location.empty? + @comment_location.any? { |comment, _| not comment.empty? } + end + + ## + # Iterates the ancestors of this class or module for which an + # RDoc::ClassModule exists. + + def each_ancestor # :yields: module + return enum_for __method__ unless block_given? + + ancestors.each do |mod| + next if String === mod + next if self == mod + yield mod + end + end + + ## + # Looks for a symbol in the #ancestors. See Context#find_local_symbol. + + def find_ancestor_local_symbol symbol + each_ancestor do |m| + res = m.find_local_symbol(symbol) + return res if res + end + + nil + end + + ## + # Finds a class or module with +name+ in this namespace or its descendants + + def find_class_named name + return self if full_name == name + return self if @name == name + + @classes.values.find do |klass| + next if klass == self + klass.find_class_named name + end + end + + ## + # Return the fully qualified name of this class or module + + def full_name + @full_name ||= if RDoc::ClassModule === parent then + "#{parent.full_name}::#{@name}" + else + @name + end + end + + ## + # TODO: filter included items by #display? + + def marshal_dump # :nodoc: + attrs = attributes.sort.map do |attr| + next unless attr.display? + [ attr.name, attr.rw, + attr.visibility, attr.singleton, attr.file_name, + ] + end.compact + + method_types = methods_by_type.map do |type, visibilities| + visibilities = visibilities.map do |visibility, methods| + method_names = methods.map do |method| + next unless method.display? + [method.name, method.file_name] + end.compact + + [visibility, method_names.uniq] + end + + [type, visibilities] + end + + [ MARSHAL_VERSION, + @name, + full_name, + @superclass, + parse(@comment_location), + attrs, + constants.select { |constant| constant.display? }, + includes.map do |incl| + next unless incl.display? + [incl.name, parse(incl.comment), incl.file_name] + end.compact, + method_types, + extends.map do |ext| + next unless ext.display? + [ext.name, parse(ext.comment), ext.file_name] + end.compact, + @sections.values, + @in_files.map do |tl| + tl.relative_name + end, + parent.full_name, + parent.class, + ] + end + + def marshal_load array # :nodoc: + initialize_visibility + initialize_methods_etc + @current_section = nil + @document_self = true + @done_documenting = false + @parent = nil + @temporary_section = nil + @visibility = nil + @classes = {} + @modules = {} + + @name = array[1] + @full_name = array[2] + @superclass = array[3] + @comment = array[4] + + @comment_location = if RDoc::Markup::Document === @comment.parts.first then + @comment + else + RDoc::Markup::Document.new @comment + end + + array[5].each do |name, rw, visibility, singleton, file| + singleton ||= false + visibility ||= :public + + attr = RDoc::Attr.new nil, name, rw, nil, singleton + + add_attribute attr + attr.visibility = visibility + attr.record_location RDoc::TopLevel.new file + end + + array[6].each do |constant, comment, file| + case constant + when RDoc::Constant then + add_constant constant + else + constant = add_constant RDoc::Constant.new(constant, nil, comment) + constant.record_location RDoc::TopLevel.new file + end + end + + array[7].each do |name, comment, file| + incl = add_include RDoc::Include.new(name, comment) + incl.record_location RDoc::TopLevel.new file + end + + array[8].each do |type, visibilities| + visibilities.each do |visibility, methods| + @visibility = visibility + + methods.each do |name, file| + method = RDoc::AnyMethod.new nil, name + method.singleton = true if type == 'class' + method.record_location RDoc::TopLevel.new file + add_method method + end + end + end + + array[9].each do |name, comment, file| + ext = add_extend RDoc::Extend.new(name, comment) + ext.record_location RDoc::TopLevel.new file + end if array[9] # Support Marshal version 1 + + sections = (array[10] || []).map do |section| + [section.title, section] + end + + @sections = Hash[*sections.flatten] + @current_section = add_section nil + + @in_files = [] + + (array[11] || []).each do |filename| + record_location RDoc::TopLevel.new filename + end + + @parent_name = array[12] + @parent_class = array[13] + end + + ## + # Merges +class_module+ into this ClassModule. + # + # The data in +class_module+ is preferred over the receiver. + + def merge class_module + @parent = class_module.parent + @parent_name = class_module.parent_name + + other_document = parse class_module.comment_location + + if other_document then + document = parse @comment_location + + document = document.merge other_document + + @comment = @comment_location = document + end + + cm = class_module + other_files = cm.in_files + + merge_collections attributes, cm.attributes, other_files do |add, attr| + if add then + add_attribute attr + else + @attributes.delete attr + @methods_hash.delete attr.pretty_name + end + end + + merge_collections constants, cm.constants, other_files do |add, const| + if add then + add_constant const + else + @constants.delete const + @constants_hash.delete const.name + end + end + + merge_collections includes, cm.includes, other_files do |add, incl| + if add then + add_include incl + else + @includes.delete incl + end + end + + @includes.uniq! # clean up + + merge_collections extends, cm.extends, other_files do |add, ext| + if add then + add_extend ext + else + @extends.delete ext + end + end + + @extends.uniq! # clean up + + merge_collections method_list, cm.method_list, other_files do |add, meth| + if add then + add_method meth + else + @method_list.delete meth + @methods_hash.delete meth.pretty_name + end + end + + merge_sections cm + + self + end + + ## + # Merges collection +mine+ with +other+ preferring other. +other_files+ is + # used to help determine which items should be deleted. + # + # Yields whether the item should be added or removed (true or false) and the + # item to be added or removed. + # + # merge_collections things, other.things, other.in_files do |add, thing| + # if add then + # # add the thing + # else + # # remove the thing + # end + # end + + def merge_collections mine, other, other_files, &block # :nodoc: + my_things = mine. group_by { |thing| thing.file } + other_things = other.group_by { |thing| thing.file } + + remove_things my_things, other_files, &block + add_things my_things, other_things, &block + end + + ## + # Merges the comments in this ClassModule with the comments in the other + # ClassModule +cm+. + + def merge_sections cm # :nodoc: + my_sections = sections.group_by { |section| section.title } + other_sections = cm.sections.group_by { |section| section.title } + + other_files = cm.in_files + + remove_things my_sections, other_files do |_, section| + @sections.delete section.title + end + + other_sections.each do |group, sections| + if my_sections.include? group + my_sections[group].each do |my_section| + other_section = cm.sections_hash[group] + + my_comments = my_section.comments + other_comments = other_section.comments + + other_files = other_section.in_files + + merge_collections my_comments, other_comments, other_files do |add, comment| + if add then + my_section.add_comment comment + else + my_section.remove_comment comment + end + end + end + else + sections.each do |section| + add_section group, section.comments + end + end + end + end + + ## + # Does this object represent a module? + + def module? + false + end + + ## + # Allows overriding the initial name. + # + # Used for modules and classes that are constant aliases. + + def name= new_name + @name = new_name + end + + ## + # Parses +comment_location+ into an RDoc::Markup::Document composed of + # multiple RDoc::Markup::Documents with their file set. + + def parse comment_location + case comment_location + when String then + super + when Array then + docs = comment_location.map do |comment, location| + doc = super comment + doc.file = location + doc + end + + RDoc::Markup::Document.new(*docs) + when RDoc::Comment then + doc = super comment_location.text, comment_location.format + doc.file = comment_location.location + doc + when RDoc::Markup::Document then + return comment_location + else + raise ArgumentError, "unknown comment class #{comment_location.class}" + end + end + + ## + # Path to this class or module for use with HTML generator output. + + def path + http_url @store.rdoc.generator.class_dir + end + + ## + # Name to use to generate the url: + # modules and classes that are aliases for another + # module or class return the name of the latter. + + def name_for_path + is_alias_for ? is_alias_for.full_name : full_name + end + + ## + # Returns the classes and modules that are not constants + # aliasing another class or module. For use by formatters + # only (caches its result). + + def non_aliases + @non_aliases ||= classes_and_modules.reject { |cm| cm.is_alias_for } + end + + ## + # Updates the child modules or classes of class/module +parent+ by + # deleting the ones that have been removed from the documentation. + # + # +parent_hash+ is either <tt>parent.modules_hash</tt> or + # <tt>parent.classes_hash</tt> and +all_hash+ is ::all_modules_hash or + # ::all_classes_hash. + + def remove_nodoc_children + prefix = self.full_name + '::' + + modules_hash.each_key do |name| + full_name = prefix + name + modules_hash.delete name unless @store.modules_hash[full_name] + end + + classes_hash.each_key do |name| + full_name = prefix + name + classes_hash.delete name unless @store.classes_hash[full_name] + end + end + + def remove_things my_things, other_files # :nodoc: + my_things.delete_if do |file, things| + next false unless other_files.include? file + + things.each do |thing| + yield false, thing + end + + true + end + end + + ## + # Search record used by RDoc::Generator::JsonIndex + + def search_record + [ + name, + full_name, + full_name, + '', + path, + '', + snippet(@comment_location), + ] + end + + ## + # Sets the store for this class or module and its contained code objects. + + def store= store + super + + @attributes .each do |attr| attr.store = store end + @constants .each do |const| const.store = store end + @includes .each do |incl| incl.store = store end + @extends .each do |ext| ext.store = store end + @method_list.each do |meth| meth.store = store end + end + + ## + # Get the superclass of this class. Attempts to retrieve the superclass + # object, returns the name if it is not known. + + def superclass + @store.find_class_named(@superclass) || @superclass + end + + ## + # Set the superclass of this class to +superclass+ + + def superclass=(superclass) + raise NoMethodError, "#{full_name} is a module" if module? + @superclass = superclass + end + + def to_s # :nodoc: + if is_alias_for then + "#{self.class.name} #{self.full_name} -> #{is_alias_for}" + else + super + end + end + + ## + # 'module' or 'class' + + def type + module? ? 'module' : 'class' + end + + ## + # Updates the child modules & classes by replacing the ones that are + # aliases through a constant. + # + # The aliased module/class is replaced in the children and in + # RDoc::Store#modules_hash or RDoc::Store#classes_hash + # by a copy that has <tt>RDoc::ClassModule#is_alias_for</tt> set to + # the aliased module/class, and this copy is added to <tt>#aliases</tt> + # of the aliased module/class. + # + # Formatters can use the #non_aliases method to retrieve children that + # are not aliases, for instance to list the namespace content, since + # the aliased modules are included in the constants of the class/module, + # that are listed separately. + + def update_aliases + constants.each do |const| + next unless cm = const.is_alias_for + cm_alias = cm.dup + cm_alias.name = const.name + + # Don't move top-level aliases under Object, they look ugly there + unless RDoc::TopLevel === cm_alias.parent then + cm_alias.parent = self + cm_alias.full_name = nil # force update for new parent + end + + cm_alias.aliases.clear + cm_alias.is_alias_for = cm + + if cm.module? then + @store.modules_hash[cm_alias.full_name] = cm_alias + modules_hash[const.name] = cm_alias + else + @store.classes_hash[cm_alias.full_name] = cm_alias + classes_hash[const.name] = cm_alias + end + + cm.aliases << cm_alias + end + end + + ## + # Deletes from #includes those whose module has been removed from the + # documentation. + #-- + # FIXME: includes are not reliably removed, see _possible_bug test case + + def update_includes + includes.reject! do |include| + mod = include.module + !(String === mod) && @store.modules_hash[mod.full_name].nil? + end + + includes.uniq! + end + + ## + # Deletes from #extends those whose module has been removed from the + # documentation. + #-- + # FIXME: like update_includes, extends are not reliably removed + + def update_extends + extends.reject! do |ext| + mod = ext.module + + !(String === mod) && @store.modules_hash[mod.full_name].nil? + end + + extends.uniq! + end + +end + diff --git a/lib/rdoc/code_object.rb b/lib/rdoc/code_object.rb new file mode 100644 index 0000000000..aeb4b4762e --- /dev/null +++ b/lib/rdoc/code_object.rb @@ -0,0 +1,421 @@ +# frozen_string_literal: true +## +# Base class for the RDoc code tree. +# +# We contain the common stuff for contexts (which are containers) and other +# elements (methods, attributes and so on) +# +# Here's the tree of the CodeObject subclasses: +# +# * RDoc::Context +# * RDoc::TopLevel +# * RDoc::ClassModule +# * RDoc::AnonClass (never used so far) +# * RDoc::NormalClass +# * RDoc::NormalModule +# * RDoc::SingleClass +# * RDoc::MethodAttr +# * RDoc::Attr +# * RDoc::AnyMethod +# * RDoc::GhostMethod +# * RDoc::MetaMethod +# * RDoc::Alias +# * RDoc::Constant +# * RDoc::Mixin +# * RDoc::Require +# * RDoc::Include + +class RDoc::CodeObject + + include RDoc::Text + + ## + # Our comment + + attr_reader :comment + + ## + # Do we document our children? + + attr_reader :document_children + + ## + # Do we document ourselves? + + attr_reader :document_self + + ## + # Are we done documenting (ie, did we come across a :enddoc:)? + + attr_reader :done_documenting + + ## + # Which file this code object was defined in + + attr_reader :file + + ## + # Force documentation of this CodeObject + + attr_reader :force_documentation + + ## + # Line in #file where this CodeObject was defined + + attr_accessor :line + + ## + # Hash of arbitrary metadata for this CodeObject + + attr_reader :metadata + + ## + # Sets the parent CodeObject + + attr_writer :parent + + ## + # Did we ever receive a +:nodoc:+ directive? + + attr_reader :received_nodoc + + ## + # Set the section this CodeObject is in + + attr_writer :section + + ## + # The RDoc::Store for this object. + + attr_reader :store + + ## + # We are the model of the code, but we know that at some point we will be + # worked on by viewers. By implementing the Viewable protocol, viewers can + # associated themselves with these objects. + + attr_accessor :viewer + + ## + # Creates a new CodeObject that will document itself and its children + + def initialize + @metadata = {} + @comment = '' + @parent = nil + @parent_name = nil # for loading + @parent_class = nil # for loading + @section = nil + @section_title = nil # for loading + @file = nil + @full_name = nil + @store = nil + @track_visibility = true + + initialize_visibility + end + + ## + # Initializes state for visibility of this CodeObject and its children. + + def initialize_visibility # :nodoc: + @document_children = true + @document_self = true + @done_documenting = false + @force_documentation = false + @received_nodoc = false + @ignored = false + @suppressed = false + @track_visibility = true + end + + ## + # Replaces our comment with +comment+, unless it is empty. + + def comment=(comment) + @comment = case comment + when NilClass then '' + when RDoc::Markup::Document then comment + when RDoc::Comment then comment.normalize + else + if comment and not comment.empty? then + normalize_comment comment + else + # HACK correct fix is to have #initialize create @comment + # with the correct encoding + if String === @comment and @comment.empty? then + @comment = RDoc::Encoding.change_encoding @comment, comment.encoding + end + @comment + end + end + end + + ## + # Should this CodeObject be displayed in output? + # + # A code object should be displayed if: + # + # * The item didn't have a nodoc or wasn't in a container that had nodoc + # * The item wasn't ignored + # * The item has documentation and was not suppressed + + def display? + @document_self and not @ignored and + (documented? or not @suppressed) + end + + ## + # Enables or disables documentation of this CodeObject's children unless it + # has been turned off by :enddoc: + + def document_children=(document_children) + return unless @track_visibility + + @document_children = document_children unless @done_documenting + end + + ## + # Enables or disables documentation of this CodeObject unless it has been + # turned off by :enddoc:. If the argument is +nil+ it means the + # documentation is turned off by +:nodoc:+. + + def document_self=(document_self) + return unless @track_visibility + return if @done_documenting + + @document_self = document_self + @received_nodoc = true if document_self.nil? + end + + ## + # Does this object have a comment with content or is #received_nodoc true? + + def documented? + @received_nodoc or !@comment.empty? + end + + ## + # Turns documentation on/off, and turns on/off #document_self + # and #document_children. + # + # Once documentation has been turned off (by +:enddoc:+), + # the object will refuse to turn #document_self or + # #document_children on, so +:doc:+ and +:start_doc:+ directives + # will have no effect in the current file. + + def done_documenting=(value) + return unless @track_visibility + @done_documenting = value + @document_self = !value + @document_children = @document_self + end + + ## + # Yields each parent of this CodeObject. See also + # RDoc::ClassModule#each_ancestor + + def each_parent + code_object = self + + while code_object = code_object.parent do + yield code_object + end + + self + end + + ## + # File name where this CodeObject was found. + # + # See also RDoc::Context#in_files + + def file_name + return unless @file + + @file.absolute_name + end + + ## + # Force the documentation of this object unless documentation + # has been turned off by :enddoc: + #-- + # HACK untested, was assigning to an ivar + + def force_documentation=(value) + @force_documentation = value unless @done_documenting + end + + ## + # Sets the full_name overriding any computed full name. + # + # Set to +nil+ to clear RDoc's cached value + + def full_name= full_name + @full_name = full_name + end + + ## + # Use this to ignore a CodeObject and all its children until found again + # (#record_location is called). An ignored item will not be displayed in + # documentation. + # + # See github issue #55 + # + # The ignored status is temporary in order to allow implementation details + # to be hidden. At the end of processing a file RDoc allows all classes + # and modules to add new documentation to previously created classes. + # + # If a class was ignored (via stopdoc) then reopened later with additional + # documentation it should be displayed. If a class was ignored and never + # reopened it should not be displayed. The ignore flag allows this to + # occur. + + def ignore + return unless @track_visibility + + @ignored = true + + stop_doc + end + + ## + # Has this class been ignored? + # + # See also #ignore + + def ignored? + @ignored + end + + ## + # The options instance from the store this CodeObject is attached to, or a + # default options instance if the CodeObject is not attached. + # + # This is used by Text#snippet + + def options + if @store and @store.rdoc then + @store.rdoc.options + else + RDoc::Options.new + end + end + + ## + # Our parent CodeObject. The parent may be missing for classes loaded from + # legacy RI data stores. + + def parent + return @parent if @parent + return nil unless @parent_name + + if @parent_class == RDoc::TopLevel then + @parent = @store.add_file @parent_name + else + @parent = @store.find_class_or_module @parent_name + + return @parent if @parent + + begin + @parent = @store.load_class @parent_name + rescue RDoc::Store::MissingFileError + nil + end + end + end + + ## + # File name of our parent + + def parent_file_name + @parent ? @parent.base_name : '(unknown)' + end + + ## + # Name of our parent + + def parent_name + @parent ? @parent.full_name : '(unknown)' + end + + ## + # Records the RDoc::TopLevel (file) where this code object was defined + + def record_location top_level + @ignored = false + @suppressed = false + @file = top_level + end + + ## + # The section this CodeObject is in. Sections allow grouping of constants, + # attributes and methods inside a class or module. + + def section + return @section if @section + + @section = parent.add_section @section_title if parent + end + + ## + # Enable capture of documentation unless documentation has been + # turned off by :enddoc: + + def start_doc + return if @done_documenting + + @document_self = true + @document_children = true + @ignored = false + @suppressed = false + end + + ## + # Disable capture of documentation + + def stop_doc + return unless @track_visibility + + @document_self = false + @document_children = false + end + + ## + # Sets the +store+ that contains this CodeObject + + def store= store + @store = store + + return unless @track_visibility + + if :nodoc == options.visibility then + initialize_visibility + @track_visibility = false + end + end + + ## + # Use this to suppress a CodeObject and all its children until the next file + # it is seen in or documentation is discovered. A suppressed item with + # documentation will be displayed while an ignored item with documentation + # may not be displayed. + + def suppress + return unless @track_visibility + + @suppressed = true + + stop_doc + end + + ## + # Has this class been suppressed? + # + # See also #suppress + + def suppressed? + @suppressed + end + +end diff --git a/lib/rdoc/code_objects.rb b/lib/rdoc/code_objects.rb index d6c4f1bdb9..434a25ac7f 100644 --- a/lib/rdoc/code_objects.rb +++ b/lib/rdoc/code_objects.rb @@ -1,765 +1,6 @@ -# We represent the various high-level code constructs that appear -# in Ruby programs: classes, modules, methods, and so on. +# frozen_string_literal: true +# This file was used to load all the RDoc::CodeObject subclasses at once. Now +# autoload handles this. -require 'rdoc/tokenstream' +require 'rdoc' -module RDoc - - - # We contain the common stuff for contexts (which are containers) - # and other elements (methods, attributes and so on) - # - class CodeObject - - attr_accessor :parent - - # We are the model of the code, but we know that at some point - # we will be worked on by viewers. By implementing the Viewable - # protocol, viewers can associated themselves with these objects. - - attr_accessor :viewer - - # are we done documenting (ie, did we come across a :enddoc:)? - - attr_accessor :done_documenting - - # Which section are we in - - attr_accessor :section - - # do we document ourselves? - - attr_reader :document_self - - def document_self=(val) - @document_self = val - if !val - remove_methods_etc - end - end - - # set and cleared by :startdoc: and :enddoc:, this is used to toggle - # the capturing of documentation - def start_doc - @document_self = true - @document_children = true - end - - def stop_doc - @document_self = false - @document_children = false - end - - # do we document ourselves and our children - - attr_reader :document_children - - def document_children=(val) - @document_children = val - if !val - remove_classes_and_modules - end - end - - # Do we _force_ documentation, even is we wouldn't normally show the entity - attr_accessor :force_documentation - - # Default callbacks to nothing, but this is overridden for classes - # and modules - def remove_classes_and_modules - end - - def remove_methods_etc - end - - def initialize - @document_self = true - @document_children = true - @force_documentation = false - @done_documenting = false - end - - # Access the code object's comment - attr_reader :comment - - # Update the comment, but don't overwrite a real comment - # with an empty one - def comment=(comment) - @comment = comment unless comment.empty? - end - - # There's a wee trick we pull. Comment blocks can have directives that - # override the stuff we extract during the parse. So, we have a special - # class method, attr_overridable, that lets code objects list - # those directives. Wehn a comment is assigned, we then extract - # out any matching directives and update our object - - def CodeObject.attr_overridable(name, *aliases) - @overridables ||= {} - - attr_accessor name - - aliases.unshift name - aliases.each do |directive_name| - @overridables[directive_name.to_s] = name - end - end - - end - - # A Context is something that can hold modules, classes, methods, - # attributes, aliases, requires, and includes. Classes, modules, and - # files are all Contexts. - - class Context < CodeObject - attr_reader :name, :method_list, :attributes, :aliases, :constants - attr_reader :requires, :includes, :in_files, :visibility - - attr_reader :sections - - class Section - attr_reader :title, :comment, :sequence - - @@sequence = "SEC00000" - - def initialize(title, comment) - @title = title - @@sequence.succ! - @sequence = @@sequence.dup - set_comment(comment) - end - - private - - # Set the comment for this section from the original comment block - # If the first line contains :section:, strip it and use the rest. Otherwise - # remove lines up to the line containing :section:, and look for - # those lines again at the end and remove them. This lets us write - # - # # --------------------- - # # :SECTION: The title - # # The body - # # --------------------- - - def set_comment(comment) - return unless comment - - if comment =~ /^.*?:section:.*$/ - start = $` - rest = $' - if start.empty? - @comment = rest - else - @comment = rest.sub(/#{start.chomp}\Z/, '') - end - else - @comment = comment - end - @comment = nil if @comment.empty? - end - end - - - def initialize - super() - - @in_files = [] - - @name ||= "unknown" - @comment ||= "" - @parent = nil - @visibility = :public - - @current_section = Section.new(nil, nil) - @sections = [ @current_section ] - - initialize_methods_etc - initialize_classes_and_modules - end - - # map the class hash to an array externally - def classes - @classes.values - end - - # map the module hash to an array externally - def modules - @modules.values - end - - # Change the default visibility for new methods - def ongoing_visibility=(vis) - @visibility = vis - end - - # Given an array +methods+ of method names, set the - # visibility of the corresponding AnyMethod object - - def set_visibility_for(methods, vis, singleton=false) - count = 0 - @method_list.each do |m| - if methods.include?(m.name) && m.singleton == singleton - m.visibility = vis - count += 1 - end - end - - return if count == methods.size || singleton - - # perhaps we need to look at attributes - - @attributes.each do |a| - if methods.include?(a.name) - a.visibility = vis - count += 1 - end - end - end - - # Record the file that we happen to find it in - def record_location(toplevel) - @in_files << toplevel unless @in_files.include?(toplevel) - end - - # Return true if at least part of this thing was defined in +file+ - def defined_in?(file) - @in_files.include?(file) - end - - def add_class(class_type, name, superclass) - add_class_or_module(@classes, class_type, name, superclass) - end - - def add_module(class_type, name) - add_class_or_module(@modules, class_type, name, nil) - end - - def add_method(a_method) - puts "Adding #@visibility method #{a_method.name} to #@name" if $DEBUG - a_method.visibility = @visibility - add_to(@method_list, a_method) - end - - def add_attribute(an_attribute) - add_to(@attributes, an_attribute) - end - - def add_alias(an_alias) - meth = find_instance_method_named(an_alias.old_name) - if meth - new_meth = AnyMethod.new(an_alias.text, an_alias.new_name) - new_meth.is_alias_for = meth - new_meth.singleton = meth.singleton - new_meth.params = meth.params - new_meth.comment = "Alias for \##{meth.name}" - meth.add_alias(new_meth) - add_method(new_meth) - else - add_to(@aliases, an_alias) - end - end - - def add_include(an_include) - add_to(@includes, an_include) - end - - def add_constant(const) - add_to(@constants, const) - end - - # Requires always get added to the top-level (file) context - def add_require(a_require) - if self.kind_of? TopLevel - add_to(@requires, a_require) - else - parent.add_require(a_require) - end - end - - def add_class_or_module(collection, class_type, name, superclass=nil) - cls = collection[name] - if cls - puts "Reusing class/module #{name}" if $DEBUG - else - cls = class_type.new(name, superclass) - puts "Adding class/module #{name} to #@name" if $DEBUG -# collection[name] = cls if @document_self && !@done_documenting - collection[name] = cls if !@done_documenting - cls.parent = self - cls.section = @current_section - end - cls - end - - def add_to(array, thing) - array << thing if @document_self && !@done_documenting - thing.parent = self - thing.section = @current_section - end - - # If a class's documentation is turned off after we've started - # collecting methods etc., we need to remove the ones - # we have - - def remove_methods_etc - initialize_methods_etc - end - - def initialize_methods_etc - @method_list = [] - @attributes = [] - @aliases = [] - @requires = [] - @includes = [] - @constants = [] - end - - # and remove classes and modules when we see a :nodoc: all - def remove_classes_and_modules - initialize_classes_and_modules - end - - def initialize_classes_and_modules - @classes = {} - @modules = {} - end - - # Find a named module - def find_module_named(name) - return self if self.name == name - res = @modules[name] || @classes[name] - return res if res - find_enclosing_module_named(name) - end - - # find a module at a higher scope - def find_enclosing_module_named(name) - parent && parent.find_module_named(name) - end - - # Iterate over all the classes and modules in - # this object - - def each_classmodule - @modules.each_value {|m| yield m} - @classes.each_value {|c| yield c} - end - - def each_method - @method_list.each {|m| yield m} - end - - def each_attribute - @attributes.each {|a| yield a} - end - - def each_constant - @constants.each {|c| yield c} - end - - # Return the toplevel that owns us - - def toplevel - return @toplevel if defined? @toplevel - @toplevel = self - @toplevel = @toplevel.parent until TopLevel === @toplevel - @toplevel - end - - # allow us to sort modules by name - def <=>(other) - name <=> other.name - end - - # Look up the given symbol. If method is non-nil, then - # we assume the symbol references a module that - # contains that method - def find_symbol(symbol, method=nil) - result = nil - case symbol - when /^::(.*)/ - result = toplevel.find_symbol($1) - when /::/ - modules = symbol.split(/::/) - unless modules.empty? - module_name = modules.shift - result = find_module_named(module_name) - if result - modules.each do |module_name| - result = result.find_module_named(module_name) - break unless result - end - end - end - else - # if a method is specified, then we're definitely looking for - # a module, otherwise it could be any symbol - if method - result = find_module_named(symbol) - else - result = find_local_symbol(symbol) - if result.nil? - if symbol =~ /^[A-Z]/ - result = parent - while result && result.name != symbol - result = result.parent - end - end - end - end - end - if result && method - if !result.respond_to?(:find_local_symbol) - p result.name - p method - fail - end - result = result.find_local_symbol(method) - end - result - end - - def find_local_symbol(symbol) - res = find_method_named(symbol) || - find_constant_named(symbol) || - find_attribute_named(symbol) || - find_module_named(symbol) - end - - # Handle sections - - def set_current_section(title, comment) - @current_section = Section.new(title, comment) - @sections << @current_section - end - - private - - # Find a named method, or return nil - def find_method_named(name) - @method_list.find {|meth| meth.name == name} - end - - # Find a named instance method, or return nil - def find_instance_method_named(name) - @method_list.find {|meth| meth.name == name && !meth.singleton} - end - - # Find a named constant, or return nil - def find_constant_named(name) - @constants.find {|m| m.name == name} - end - - # Find a named attribute, or return nil - def find_attribute_named(name) - @attributes.find {|m| m.name == name} - end - - end - - - # A TopLevel context is a source file - - class TopLevel < Context - attr_accessor :file_stat - attr_accessor :file_relative_name - attr_accessor :file_absolute_name - attr_accessor :diagram - - @@all_classes = {} - @@all_modules = {} - - def TopLevel::reset - @@all_classes = {} - @@all_modules = {} - end - - def initialize(file_name) - super() - @name = "TopLevel" - @file_relative_name = file_name - @file_absolute_name = file_name - @file_stat = File.stat(file_name) - @diagram = nil - end - - def full_name - 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. - - def add_class_or_module(collection, class_type, name, superclass) - cls = collection[name] - if cls - puts "Reusing class/module #{name}" if $DEBUG - else - if class_type == NormalModule - all = @@all_modules - else - all = @@all_classes - end - cls = all[name] - if !cls - cls = class_type.new(name, superclass) - all[name] = cls unless @done_documenting - end - puts "Adding class/module #{name} to #@name" if $DEBUG - collection[name] = cls unless @done_documenting - cls.parent = self - end - cls - end - - def TopLevel.all_classes_and_modules - @@all_classes.values + @@all_modules.values - end - - def TopLevel.find_class_named(name) - @@all_classes.each_value do |c| - res = c.find_class_named(name) - return res if res - end - nil - end - - def find_local_symbol(symbol) - find_class_or_module_named(symbol) || super - end - - def find_class_or_module_named(symbol) - @@all_classes.each_value {|c| return c if c.name == symbol} - @@all_modules.each_value {|m| return m if m.name == symbol} - 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 - # class or a module. - - class ClassModule < Context - - attr_reader :superclass - attr_accessor :diagram - - def initialize(name, superclass = nil) - @name = name - @diagram = nil - @superclass = superclass - @comment = "" - super() - end - - # Return the fully qualified name of this class or module - def full_name - if @parent && @parent.full_name - @parent.full_name + "::" + @name - else - @name - end - end - - def http_url(prefix) - path = full_name.split("::") - File.join(prefix, *path) + ".html" - end - - # Return +true+ if this object represents a module - def is_module? - false - end - - # to_s is simply for debugging - def to_s - res = self.class.name + ": " + @name - res << @comment.to_s - res << super - res - end - - def find_class_named(name) - return self if full_name == name - @classes.each_value {|c| return c if c.find_class_named(name) } - nil - end - end - - # Anonymous classes - class AnonClass < ClassModule - end - - # Normal classes - class NormalClass < ClassModule - end - - # Singleton classes - class SingleClass < ClassModule - end - - # Module - class NormalModule < ClassModule - def is_module? - true - end - end - - - # AnyMethod is the base class for objects representing methods - - class AnyMethod < CodeObject - attr_accessor :name - attr_accessor :visibility - attr_accessor :block_params - attr_accessor :dont_rename_initialize - attr_accessor :singleton - attr_reader :aliases # list of other names for this method - attr_accessor :is_alias_for # or a method we're aliasing - - attr_overridable :params, :param, :parameters, :parameter - - attr_accessor :call_seq - - - include TokenStream - - def initialize(text, name) - super() - @text = text - @name = name - @token_stream = nil - @visibility = :public - @dont_rename_initialize = false - @block_params = nil - @aliases = [] - @is_alias_for = nil - @comment = "" - @call_seq = nil - end - - def <=>(other) - @name <=> other.name - end - - def to_s - res = self.class.name + ": " + @name + " (" + @text + ")\n" - res << @comment.to_s - res - end - - def param_seq - p = params.gsub(/\s*\#.*/, '') - p = p.tr("\n", " ").squeeze(" ") - p = "(" + p + ")" unless p[0] == ?( - - if (block = block_params) - # If this method has explicit block parameters, remove any - # explicit &block -$stderr.puts p - p.sub!(/,?\s*&\w+/) -$stderr.puts p - - block.gsub!(/\s*\#.*/, '') - block = block.tr("\n", " ").squeeze(" ") - if block[0] == ?( - block.sub!(/^\(/, '').sub!(/\)/, '') - end - p << " {|#{block}| ...}" - end - p - end - - def add_alias(method) - @aliases << method - end - end - - - # Represent an alias, which is an old_name/ new_name pair associated - # with a particular context - class Alias < CodeObject - attr_accessor :text, :old_name, :new_name, :comment - - def initialize(text, old_name, new_name, comment) - super() - @text = text - @old_name = old_name - @new_name = new_name - self.comment = comment - end - - def to_s - "alias: #{self.old_name} -> #{self.new_name}\n#{self.comment}" - end - end - - # Represent a constant - class Constant < CodeObject - attr_accessor :name, :value - - def initialize(name, value, comment) - super() - @name = name - @value = value - self.comment = comment - end - end - - # Represent attributes - class Attr < CodeObject - attr_accessor :text, :name, :rw, :visibility - - def initialize(text, name, rw, comment) - super() - @text = text - @name = name - @rw = rw - @visibility = :public - self.comment = comment - end - - def to_s - "attr: #{self.name} #{self.rw}\n#{self.comment}" - end - - def <=>(other) - self.name <=> other.name - end - end - - # a required file - - class Require < CodeObject - attr_accessor :name - - def initialize(name, comment) - super() - @name = name.gsub(/'|"/, "") #' - self.comment = comment - end - - end - - # an included module - class Include < CodeObject - attr_accessor :name - - def initialize(name, comment) - super() - @name = name - self.comment = comment - end - - end - -end diff --git a/lib/rdoc/comment.rb b/lib/rdoc/comment.rb new file mode 100644 index 0000000000..134f6440a0 --- /dev/null +++ b/lib/rdoc/comment.rb @@ -0,0 +1,239 @@ +# frozen_string_literal: true +## +# A comment holds the text comment for a RDoc::CodeObject and provides a +# unified way of cleaning it up and parsing it into an RDoc::Markup::Document. +# +# Each comment may have a different markup format set by #format=. By default +# 'rdoc' is used. The :markup: directive tells RDoc which format to use. +# +# See RDoc::Markup@Other+directives for instructions on adding an alternate +# format. + +class RDoc::Comment + + include RDoc::Text + + ## + # The format of this comment. Defaults to RDoc::Markup + + attr_reader :format + + ## + # The RDoc::TopLevel this comment was found in + + attr_accessor :location + + ## + # For duck-typing when merging classes at load time + + alias file location # :nodoc: + + ## + # The text for this comment + + attr_reader :text + + ## + # Overrides the content returned by #parse. Use when there is no #text + # source for this comment + + attr_writer :document + + ## + # Creates a new comment with +text+ that is found in the RDoc::TopLevel + # +location+. + + def initialize text = nil, location = nil + @location = location + @text = text.nil? ? nil : text.dup + + @document = nil + @format = 'rdoc' + @normalized = false + end + + ## + #-- + # TODO deep copy @document + + def initialize_copy copy # :nodoc: + @text = copy.text.dup + end + + def == other # :nodoc: + self.class === other and + other.text == @text and other.location == @location + end + + ## + # Look for a 'call-seq' in the comment to override the normal parameter + # handling. The :call-seq: is indented from the baseline. All lines of the + # same indentation level and prefix are consumed. + # + # For example, all of the following will be used as the :call-seq: + # + # # :call-seq: + # # ARGF.readlines(sep=$/) -> array + # # ARGF.readlines(limit) -> array + # # ARGF.readlines(sep, limit) -> array + # # + # # ARGF.to_a(sep=$/) -> array + # # ARGF.to_a(limit) -> array + # # ARGF.to_a(sep, limit) -> array + + def extract_call_seq method + # we must handle situations like the above followed by an unindented first + # comment. The difficulty is to make sure not to match lines starting + # with ARGF at the same indent, but that are after the first description + # paragraph. + if @text =~ /^\s*:?call-seq:(.*?(?:\S).*?)^\s*$/m then + all_start, all_stop = $~.offset(0) + seq_start, seq_stop = $~.offset(1) + + # we get the following lines that start with the leading word at the + # same indent, even if they have blank lines before + if $1 =~ /(^\s*\n)+^(\s*\w+)/m then + leading = $2 # ' * ARGF' in the example above + re = %r% + \A( + (^\s*\n)+ + (^#{Regexp.escape leading}.*?\n)+ + )+ + ^\s*$ + %xm + + if @text[seq_stop..-1] =~ re then + all_stop = seq_stop + $~.offset(0).last + seq_stop = seq_stop + $~.offset(1).last + end + end + + seq = @text[seq_start..seq_stop] + seq.gsub!(/^\s*(\S|\n)/m, '\1') + @text.slice! all_start...all_stop + + method.call_seq = seq.chomp + + else + regexp = /^\s*:?call-seq:(.*?)(^\s*$|\z)/m + if regexp =~ @text then + @text = @text.sub(regexp, '') + seq = $1 + seq.gsub!(/^\s*/, '') + method.call_seq = seq + end + end + + method + end + + ## + # A comment is empty if its text String is empty. + + def empty? + @text.empty? + end + + ## + # HACK dubious + + def encode! encoding + # TODO: Remove this condition after Ruby 2.2 EOL + if RUBY_VERSION < '2.3.0' + @text = @text.force_encoding encoding + else + @text = String.new @text, encoding: encoding + end + self + end + + ## + # Sets the format of this comment and resets any parsed document + + def format= format + @format = format + @document = nil + end + + def inspect # :nodoc: + location = @location ? @location.relative_name : '(unknown)' + + "#<%s:%x %s %p>" % [self.class, object_id, location, @text] + end + + ## + # Normalizes the text. See RDoc::Text#normalize_comment for details + + def normalize + return self unless @text + return self if @normalized # TODO eliminate duplicate normalization + + @text = normalize_comment @text + + @normalized = true + + self + end + + ## + # Was this text normalized? + + def normalized? # :nodoc: + @normalized + end + + ## + # Parses the comment into an RDoc::Markup::Document. The parsed document is + # cached until the text is changed. + + def parse + return @document if @document + + @document = super @text, @format + @document.file = @location + @document + end + + ## + # Removes private sections from this comment. Private sections are flush to + # the comment marker and start with <tt>--</tt> and end with <tt>++</tt>. + # For C-style comments, a private marker may not start at the opening of the + # comment. + # + # /* + # *-- + # * private + # *++ + # * public + # */ + + def remove_private + # Workaround for gsub encoding for Ruby 1.9.2 and earlier + empty = '' + empty = RDoc::Encoding.change_encoding empty, @text.encoding + + @text = @text.gsub(%r%^\s*([#*]?)--.*?^\s*(\1)\+\+\n?%m, empty) + @text = @text.sub(%r%^\s*[#*]?--.*%m, '') + end + + ## + # Replaces this comment's text with +text+ and resets the parsed document. + # + # An error is raised if the comment contains a document but no text. + + def text= text + raise RDoc::Error, 'replacing document-only comment is not allowed' if + @text.nil? and @document + + @document = nil + @text = text.nil? ? nil : text.dup + end + + ## + # Returns true if this comment is in TomDoc format. + + def tomdoc? + @format == 'tomdoc' + end + +end diff --git a/lib/rdoc/constant.rb b/lib/rdoc/constant.rb new file mode 100644 index 0000000000..0c3d7505a1 --- /dev/null +++ b/lib/rdoc/constant.rb @@ -0,0 +1,187 @@ +# frozen_string_literal: true +## +# A constant + +class RDoc::Constant < RDoc::CodeObject + + MARSHAL_VERSION = 0 # :nodoc: + + ## + # Sets the module or class this is constant is an alias for. + + attr_writer :is_alias_for + + ## + # The constant's name + + attr_accessor :name + + ## + # The constant's value + + attr_accessor :value + + ## + # The constant's visibility + + attr_accessor :visibility + + ## + # Creates a new constant with +name+, +value+ and +comment+ + + def initialize(name, value, comment) + super() + + @name = name + @value = value + + @is_alias_for = nil + @visibility = :public + + self.comment = comment + end + + ## + # Constants are ordered by name + + def <=> other + return unless self.class === other + + [parent_name, name] <=> [other.parent_name, other.name] + end + + ## + # Constants are equal when their #parent and #name is the same + + def == other + self.class == other.class and + @parent == other.parent and + @name == other.name + end + + ## + # A constant is documented if it has a comment, or is an alias + # for a documented class or module. + + def documented? + return true if super + return false unless @is_alias_for + case @is_alias_for + when String then + found = @store.find_class_or_module @is_alias_for + return false unless found + @is_alias_for = found + end + @is_alias_for.documented? + end + + ## + # Full constant name including namespace + + def full_name + @full_name ||= "#{parent_name}::#{@name}" + end + + ## + # The module or class this constant is an alias for + + def is_alias_for + case @is_alias_for + when String then + found = @store.find_class_or_module @is_alias_for + @is_alias_for = found if found + @is_alias_for + else + @is_alias_for + end + end + + def inspect # :nodoc: + "#<%s:0x%x %s::%s>" % [ + self.class, object_id, + parent_name, @name, + ] + end + + ## + # Dumps this Constant for use by ri. See also #marshal_load + + def marshal_dump + alias_name = case found = is_alias_for + when RDoc::CodeObject then found.full_name + else found + end + + [ MARSHAL_VERSION, + @name, + full_name, + @visibility, + alias_name, + parse(@comment), + @file.relative_name, + parent.name, + parent.class, + section.title, + ] + end + + ## + # Loads this Constant from +array+. For a loaded Constant the following + # methods will return cached values: + # + # * #full_name + # * #parent_name + + def marshal_load array + initialize array[1], nil, array[5] + + @full_name = array[2] + @visibility = array[3] || :public + @is_alias_for = array[4] + # 5 handled above + # 6 handled below + @parent_name = array[7] + @parent_class = array[8] + @section_title = array[9] + + @file = RDoc::TopLevel.new array[6] + end + + ## + # Path to this constant for use with HTML generator output. + + def path + "#{@parent.path}##{@name}" + end + + def pretty_print q # :nodoc: + q.group 2, "[#{self.class.name} #{full_name}", "]" do + unless comment.empty? then + q.breakable + q.text "comment:" + q.breakable + q.pp @comment + end + end + end + + ## + # Sets the store for this class or module and its contained code objects. + + def store= store + super + + @file = @store.add_file @file.full_name if @file + end + + def to_s # :nodoc: + parent_name = parent ? parent.full_name : '(unknown)' + if is_alias_for + "constant #{parent_name}::#@name -> #{is_alias_for}" + else + "constant #{parent_name}::#@name" + end + end + +end + diff --git a/lib/rdoc/context.rb b/lib/rdoc/context.rb new file mode 100644 index 0000000000..58b1c54269 --- /dev/null +++ b/lib/rdoc/context.rb @@ -0,0 +1,1235 @@ +# frozen_string_literal: true +require 'cgi' + +## +# A Context is something that can hold modules, classes, methods, attributes, +# aliases, requires, and includes. Classes, modules, and files are all +# Contexts. + +class RDoc::Context < RDoc::CodeObject + + include Comparable + + ## + # Types of methods + + TYPES = %w[class instance] + + ## + # If a context has these titles it will be sorted in this order. + + TOMDOC_TITLES = [nil, 'Public', 'Internal', 'Deprecated'] # :nodoc: + TOMDOC_TITLES_SORT = TOMDOC_TITLES.sort_by { |title| title.to_s } # :nodoc: + + ## + # Class/module aliases + + attr_reader :aliases + + ## + # All attr* methods + + attr_reader :attributes + + ## + # Block params to be used in the next MethodAttr parsed under this context + + attr_accessor :block_params + + ## + # Constants defined + + attr_reader :constants + + ## + # Sets the current documentation section of documentation + + attr_writer :current_section + + ## + # Files this context is found in + + attr_reader :in_files + + ## + # Modules this context includes + + attr_reader :includes + + ## + # Modules this context is extended with + + attr_reader :extends + + ## + # Methods defined in this context + + attr_reader :method_list + + ## + # Name of this class excluding namespace. See also full_name + + attr_reader :name + + ## + # Files this context requires + + attr_reader :requires + + ## + # Use this section for the next method, attribute or constant added. + + attr_accessor :temporary_section + + ## + # Hash <tt>old_name => [aliases]</tt>, for aliases + # that haven't (yet) been resolved to a method/attribute. + # (Not to be confused with the aliases of the context.) + + attr_accessor :unmatched_alias_lists + + ## + # Aliases that could not be resolved. + + attr_reader :external_aliases + + ## + # Current visibility of this context + + attr_accessor :visibility + + ## + # Current visibility of this line + + attr_writer :current_line_visibility + + ## + # Hash of registered methods. Attributes are also registered here, + # twice if they are RW. + + attr_reader :methods_hash + + ## + # Params to be used in the next MethodAttr parsed under this context + + attr_accessor :params + + ## + # Hash of registered constants. + + attr_reader :constants_hash + + ## + # Creates an unnamed empty context with public current visibility + + def initialize + super + + @in_files = [] + + @name ||= "unknown" + @parent = nil + @visibility = :public + + @current_section = Section.new self, nil, nil + @sections = { nil => @current_section } + @temporary_section = nil + + @classes = {} + @modules = {} + + initialize_methods_etc + end + + ## + # Sets the defaults for methods and so-forth + + def initialize_methods_etc + @method_list = [] + @attributes = [] + @aliases = [] + @requires = [] + @includes = [] + @extends = [] + @constants = [] + @external_aliases = [] + @current_line_visibility = nil + + # This Hash maps a method name to a list of unmatched aliases (aliases of + # a method not yet encountered). + @unmatched_alias_lists = {} + + @methods_hash = {} + @constants_hash = {} + + @params = nil + + @store ||= nil + end + + ## + # Contexts are sorted by full_name + + def <=>(other) + return nil unless RDoc::CodeObject === other + + full_name <=> other.full_name + end + + ## + # Adds an item of type +klass+ with the given +name+ and +comment+ to the + # context. + # + # Currently only RDoc::Extend and RDoc::Include are supported. + + def add klass, name, comment + if RDoc::Extend == klass then + ext = RDoc::Extend.new name, comment + add_extend ext + elsif RDoc::Include == klass then + incl = RDoc::Include.new name, comment + add_include incl + else + raise NotImplementedError, "adding a #{klass} is not implemented" + end + end + + ## + # Adds +an_alias+ that is automatically resolved + + def add_alias an_alias + return an_alias unless @document_self + + method_attr = find_method(an_alias.old_name, an_alias.singleton) || + find_attribute(an_alias.old_name, an_alias.singleton) + + if method_attr then + method_attr.add_alias an_alias, self + else + add_to @external_aliases, an_alias + unmatched_alias_list = + @unmatched_alias_lists[an_alias.pretty_old_name] ||= [] + unmatched_alias_list.push an_alias + end + + an_alias + end + + ## + # Adds +attribute+ if not already there. If it is (as method(s) or attribute), + # updates the comment if it was empty. + # + # The attribute is registered only if it defines a new method. + # For instance, <tt>attr_reader :foo</tt> will not be registered + # if method +foo+ exists, but <tt>attr_accessor :foo</tt> will be registered + # if method +foo+ exists, but <tt>foo=</tt> does not. + + def add_attribute attribute + return attribute unless @document_self + + # mainly to check for redefinition of an attribute as a method + # TODO find a policy for 'attr_reader :foo' + 'def foo=()' + register = false + + key = nil + + if attribute.rw.index 'R' then + key = attribute.pretty_name + known = @methods_hash[key] + + if known then + known.comment = attribute.comment if known.comment.empty? + elsif registered = @methods_hash[attribute.pretty_name + '='] and + RDoc::Attr === registered then + registered.rw = 'RW' + else + @methods_hash[key] = attribute + register = true + end + end + + if attribute.rw.index 'W' then + key = attribute.pretty_name + '=' + known = @methods_hash[key] + + if known then + known.comment = attribute.comment if known.comment.empty? + elsif registered = @methods_hash[attribute.pretty_name] and + RDoc::Attr === registered then + registered.rw = 'RW' + else + @methods_hash[key] = attribute + register = true + end + end + + if register then + attribute.visibility = @visibility + add_to @attributes, attribute + resolve_aliases attribute + end + + attribute + end + + ## + # Adds a class named +given_name+ with +superclass+. + # + # Both +given_name+ and +superclass+ may contain '::', and are + # interpreted relative to the +self+ context. This allows handling correctly + # examples like these: + # class RDoc::Gauntlet < Gauntlet + # module Mod + # class Object # implies < ::Object + # class SubObject < Object # this is _not_ ::Object + # + # Given <tt>class Container::Item</tt> RDoc assumes +Container+ is a module + # unless it later sees <tt>class Container</tt>. +add_class+ automatically + # upgrades +given_name+ to a class in this case. + + def add_class class_type, given_name, superclass = '::Object' + # superclass +nil+ is passed by the C parser in the following cases: + # - registering Object in 1.8 (correct) + # - registering BasicObject in 1.9 (correct) + # - registering RubyVM in 1.9 in iseq.c (incorrect: < Object in vm.c) + # + # If we later find a superclass for a registered class with a nil + # superclass, we must honor it. + + # find the name & enclosing context + if given_name =~ /^:+(\w+)$/ then + full_name = $1 + enclosing = top_level + name = full_name.split(/:+/).last + else + full_name = child_name given_name + + if full_name =~ /^(.+)::(\w+)$/ then + name = $2 + ename = $1 + enclosing = @store.classes_hash[ename] || @store.modules_hash[ename] + # HACK: crashes in actionpack/lib/action_view/helpers/form_helper.rb (metaprogramming) + unless enclosing then + # try the given name at top level (will work for the above example) + enclosing = @store.classes_hash[given_name] || + @store.modules_hash[given_name] + return enclosing if enclosing + # not found: create the parent(s) + names = ename.split('::') + enclosing = self + names.each do |n| + enclosing = enclosing.classes_hash[n] || + enclosing.modules_hash[n] || + enclosing.add_module(RDoc::NormalModule, n) + end + end + else + name = full_name + enclosing = self + end + end + + # fix up superclass + if full_name == 'BasicObject' then + superclass = nil + elsif full_name == 'Object' then + superclass = '::BasicObject' + end + + # find the superclass full name + if superclass then + if superclass =~ /^:+/ then + superclass = $' #' + else + if superclass =~ /^(\w+):+(.+)$/ then + suffix = $2 + mod = find_module_named($1) + superclass = mod.full_name + '::' + suffix if mod + else + mod = find_module_named(superclass) + superclass = mod.full_name if mod + end + end + + # did we believe it was a module? + mod = @store.modules_hash.delete superclass + + upgrade_to_class mod, RDoc::NormalClass, mod.parent if mod + + # e.g., Object < Object + superclass = nil if superclass == full_name + end + + klass = @store.classes_hash[full_name] + + if klass then + # if TopLevel, it may not be registered in the classes: + enclosing.classes_hash[name] = klass + + # update the superclass if needed + if superclass then + existing = klass.superclass + existing = existing.full_name unless existing.is_a?(String) if existing + if existing.nil? || + (existing == 'Object' && superclass != 'Object') then + klass.superclass = superclass + end + end + else + # this is a new class + mod = @store.modules_hash.delete full_name + + if mod then + klass = upgrade_to_class mod, RDoc::NormalClass, enclosing + + klass.superclass = superclass unless superclass.nil? + else + klass = class_type.new name, superclass + + enclosing.add_class_or_module(klass, enclosing.classes_hash, + @store.classes_hash) + end + end + + klass.parent = self + + klass + end + + ## + # Adds the class or module +mod+ to the modules or + # classes Hash +self_hash+, and to +all_hash+ (either + # <tt>TopLevel::modules_hash</tt> or <tt>TopLevel::classes_hash</tt>), + # unless #done_documenting is +true+. Sets the #parent of +mod+ + # to +self+, and its #section to #current_section. Returns +mod+. + + def add_class_or_module mod, self_hash, all_hash + mod.section = current_section # TODO declaring context? something is + # wrong here... + mod.parent = self + mod.store = @store + + unless @done_documenting then + self_hash[mod.name] = mod + # this must be done AFTER adding mod to its parent, so that the full + # name is correct: + all_hash[mod.full_name] = mod + end + + mod + end + + ## + # Adds +constant+ if not already there. If it is, updates the comment, + # value and/or is_alias_for of the known constant if they were empty/nil. + + def add_constant constant + return constant unless @document_self + + # HACK: avoid duplicate 'PI' & 'E' in math.c (1.8.7 source code) + # (this is a #ifdef: should be handled by the C parser) + known = @constants_hash[constant.name] + + if known then + known.comment = constant.comment if known.comment.empty? + + known.value = constant.value if + known.value.nil? or known.value.strip.empty? + + known.is_alias_for ||= constant.is_alias_for + else + @constants_hash[constant.name] = constant + add_to @constants, constant + end + + constant + end + + ## + # Adds included module +include+ which should be an RDoc::Include + + def add_include include + add_to @includes, include + + include + end + + ## + # Adds extension module +ext+ which should be an RDoc::Extend + + def add_extend ext + add_to @extends, ext + + ext + end + + ## + # Adds +method+ if not already there. If it is (as method or attribute), + # updates the comment if it was empty. + + def add_method method + return method unless @document_self + + # HACK: avoid duplicate 'new' in io.c & struct.c (1.8.7 source code) + key = method.pretty_name + known = @methods_hash[key] + + if known then + if @store then # otherwise we are loading + known.comment = method.comment if known.comment.empty? + previously = ", previously in #{known.file}" unless + method.file == known.file + @store.rdoc.options.warn \ + "Duplicate method #{known.full_name} in #{method.file}#{previously}" + end + else + @methods_hash[key] = method + if @current_line_visibility + method.visibility, @current_line_visibility = @current_line_visibility, nil + else + method.visibility = @visibility + end + add_to @method_list, method + resolve_aliases method + end + + method + end + + ## + # Adds a module named +name+. If RDoc already knows +name+ is a class then + # that class is returned instead. See also #add_class. + + def add_module(class_type, name) + mod = @classes[name] || @modules[name] + return mod if mod + + full_name = child_name name + mod = @store.modules_hash[full_name] || class_type.new(name) + + add_class_or_module mod, @modules, @store.modules_hash + end + + ## + # Adds an alias from +from+ (a class or module) to +name+ which was defined + # in +file+. + + def add_module_alias from, name, file + return from if @done_documenting + + to_name = child_name name + + # if we already know this name, don't register an alias: + # see the metaprogramming in lib/active_support/basic_object.rb, + # where we already know BasicObject is a class when we find + # BasicObject = BlankSlate + return from if @store.find_class_or_module to_name + + to = from.dup + to.name = name + to.full_name = nil + + if to.module? then + @store.modules_hash[to_name] = to + @modules[name] = to + else + @store.classes_hash[to_name] = to + @classes[name] = to + end + + # Registers a constant for this alias. The constant value and comment + # will be updated later, when the Ruby parser adds the constant + const = RDoc::Constant.new name, nil, to.comment + const.record_location file + const.is_alias_for = from + add_constant const + + to + end + + ## + # Adds +require+ to this context's top level + + def add_require(require) + return require unless @document_self + + if RDoc::TopLevel === self then + add_to @requires, require + else + parent.add_require require + end + end + + ## + # Returns a section with +title+, creating it if it doesn't already exist. + # +comment+ will be appended to the section's comment. + # + # A section with a +title+ of +nil+ will return the default section. + # + # See also RDoc::Context::Section + + def add_section title, comment = nil + if section = @sections[title] then + section.add_comment comment if comment + else + section = Section.new self, title, comment + @sections[title] = section + end + + section + end + + ## + # Adds +thing+ to the collection +array+ + + def add_to array, thing + array << thing if @document_self + + thing.parent = self + thing.store = @store if @store + thing.section = current_section + end + + ## + # Is there any content? + # + # This means any of: comment, aliases, methods, attributes, external + # aliases, require, constant. + # + # Includes and extends are also checked unless <tt>includes == false</tt>. + + def any_content(includes = true) + @any_content ||= !( + @comment.empty? && + @method_list.empty? && + @attributes.empty? && + @aliases.empty? && + @external_aliases.empty? && + @requires.empty? && + @constants.empty? + ) + @any_content || (includes && !(@includes + @extends).empty? ) + end + + ## + # Creates the full name for a child with +name+ + + def child_name name + if name =~ /^:+/ + $' #' + elsif RDoc::TopLevel === self then + name + else + "#{self.full_name}::#{name}" + end + end + + ## + # Class attributes + + def class_attributes + @class_attributes ||= attributes.select { |a| a.singleton } + end + + ## + # Class methods + + def class_method_list + @class_method_list ||= method_list.select { |a| a.singleton } + end + + ## + # Array of classes in this context + + def classes + @classes.values + end + + ## + # All classes and modules in this namespace + + def classes_and_modules + classes + modules + end + + ## + # Hash of classes keyed by class name + + def classes_hash + @classes + end + + ## + # The current documentation section that new items will be added to. If + # temporary_section is available it will be used. + + def current_section + if section = @temporary_section then + @temporary_section = nil + else + section = @current_section + end + + section + end + + ## + # Is part of this thing was defined in +file+? + + def defined_in?(file) + @in_files.include?(file) + end + + def display(method_attr) # :nodoc: + if method_attr.is_a? RDoc::Attr + "#{method_attr.definition} #{method_attr.pretty_name}" + else + "method #{method_attr.pretty_name}" + end + end + + ## + # Iterator for ancestors for duck-typing. Does nothing. See + # RDoc::ClassModule#each_ancestor. + # + # This method exists to make it easy to work with Context subclasses that + # aren't part of RDoc. + + def each_ancestor # :nodoc: + end + + ## + # Iterator for attributes + + def each_attribute # :yields: attribute + @attributes.each { |a| yield a } + end + + ## + # Iterator for classes and modules + + def each_classmodule(&block) # :yields: module + classes_and_modules.sort.each(&block) + end + + ## + # Iterator for constants + + def each_constant # :yields: constant + @constants.each {|c| yield c} + end + + ## + # Iterator for included modules + + def each_include # :yields: include + @includes.each do |i| yield i end + end + + ## + # Iterator for extension modules + + def each_extend # :yields: extend + @extends.each do |e| yield e end + end + + ## + # Iterator for methods + + def each_method # :yields: method + return enum_for __method__ unless block_given? + + @method_list.sort.each { |m| yield m } + end + + ## + # Iterator for each section's contents sorted by title. The +section+, the + # section's +constants+ and the sections +attributes+ are yielded. The + # +constants+ and +attributes+ collections are sorted. + # + # To retrieve methods in a section use #methods_by_type with the optional + # +section+ parameter. + # + # NOTE: Do not edit collections yielded by this method + + def each_section # :yields: section, constants, attributes + return enum_for __method__ unless block_given? + + constants = @constants.group_by do |constant| constant.section end + attributes = @attributes.group_by do |attribute| attribute.section end + + constants.default = [] + attributes.default = [] + + sort_sections.each do |section| + yield section, constants[section].select(&:display?).sort, attributes[section].select(&:display?).sort + end + end + + ## + # Finds an attribute +name+ with singleton value +singleton+. + + def find_attribute(name, singleton) + name = $1 if name =~ /^(.*)=$/ + @attributes.find { |a| a.name == name && a.singleton == singleton } + end + + ## + # Finds an attribute with +name+ in this context + + def find_attribute_named(name) + case name + when /\A#/ then + find_attribute name[1..-1], false + when /\A::/ then + find_attribute name[2..-1], true + else + @attributes.find { |a| a.name == name } + end + end + + ## + # Finds a class method with +name+ in this context + + def find_class_method_named(name) + @method_list.find { |meth| meth.singleton && meth.name == name } + end + + ## + # Finds a constant with +name+ in this context + + def find_constant_named(name) + @constants.find do |m| + m.name == name || m.full_name == name + end + end + + ## + # Find a module at a higher scope + + def find_enclosing_module_named(name) + parent && parent.find_module_named(name) + end + + ## + # Finds an external alias +name+ with singleton value +singleton+. + + def find_external_alias(name, singleton) + @external_aliases.find { |m| m.name == name && m.singleton == singleton } + end + + ## + # Finds an external alias with +name+ in this context + + def find_external_alias_named(name) + case name + when /\A#/ then + find_external_alias name[1..-1], false + when /\A::/ then + find_external_alias name[2..-1], true + else + @external_aliases.find { |a| a.name == name } + end + end + + ## + # Finds a file with +name+ in this context + + def find_file_named name + @store.find_file_named name + end + + ## + # Finds an instance method with +name+ in this context + + def find_instance_method_named(name) + @method_list.find { |meth| !meth.singleton && meth.name == name } + end + + ## + # Finds a method, constant, attribute, external alias, module or file + # named +symbol+ in this context. + + def find_local_symbol(symbol) + find_method_named(symbol) or + find_constant_named(symbol) or + find_attribute_named(symbol) or + find_external_alias_named(symbol) or + find_module_named(symbol) or + find_file_named(symbol) + end + + ## + # Finds a method named +name+ with singleton value +singleton+. + + def find_method(name, singleton) + @method_list.find { |m| m.name == name && m.singleton == singleton } + end + + ## + # Finds a instance or module method with +name+ in this context + + def find_method_named(name) + case name + when /\A#/ then + find_method name[1..-1], false + when /\A::/ then + find_method name[2..-1], true + else + @method_list.find { |meth| meth.name == name } + end + end + + ## + # Find a module with +name+ using ruby's scoping rules + + def find_module_named(name) + res = @modules[name] || @classes[name] + return res if res + return self if self.name == name + find_enclosing_module_named name + end + + ## + # Look up +symbol+, first as a module, then as a local symbol. + + def find_symbol(symbol) + find_symbol_module(symbol) || find_local_symbol(symbol) + end + + ## + # Look up a module named +symbol+. + + def find_symbol_module(symbol) + result = nil + + # look for a class or module 'symbol' + case symbol + when /^::/ then + result = @store.find_class_or_module symbol + when /^(\w+):+(.+)$/ + suffix = $2 + top = $1 + searched = self + while searched do + mod = searched.find_module_named(top) + break unless mod + result = @store.find_class_or_module "#{mod.full_name}::#{suffix}" + break if result || searched.is_a?(RDoc::TopLevel) + searched = searched.parent + end + else + searched = self + while searched do + result = searched.find_module_named(symbol) + break if result || searched.is_a?(RDoc::TopLevel) + searched = searched.parent + end + end + + result + end + + ## + # The full name for this context. This method is overridden by subclasses. + + def full_name + '(unknown)' + end + + ## + # Does this context and its methods and constants all have documentation? + # + # (Yes, fully documented doesn't mean everything.) + + def fully_documented? + documented? and + attributes.all? { |a| a.documented? } and + method_list.all? { |m| m.documented? } and + constants.all? { |c| c.documented? } + end + + ## + # URL for this with a +prefix+ + + def http_url(prefix) + path = name_for_path + path = path.gsub(/<<\s*(\w*)/, 'from-\1') if path =~ /<</ + path = [prefix] + path.split('::') + + File.join(*path.compact) + '.html' + end + + ## + # Instance attributes + + def instance_attributes + @instance_attributes ||= attributes.reject { |a| a.singleton } + end + + ## + # Instance methods + #-- + # TODO rename to instance_methods + + def instance_method_list + @instance_method_list ||= method_list.reject { |a| a.singleton } + end + + ## + # Breaks method_list into a nested hash by type (<tt>'class'</tt> or + # <tt>'instance'</tt>) and visibility (+:public+, +:protected+, +:private+). + # + # If +section+ is provided only methods in that RDoc::Context::Section will + # be returned. + + def methods_by_type section = nil + methods = {} + + TYPES.each do |type| + visibilities = {} + RDoc::VISIBILITIES.each do |vis| + visibilities[vis] = [] + end + + methods[type] = visibilities + end + + each_method do |method| + next if section and not method.section == section + methods[method.type][method.visibility] << method + end + + methods + end + + ## + # Yields AnyMethod and Attr entries matching the list of names in +methods+. + + def methods_matching(methods, singleton = false, &block) + (@method_list + @attributes).each do |m| + yield m if methods.include?(m.name) and m.singleton == singleton + end + + each_ancestor do |parent| + parent.methods_matching(methods, singleton, &block) + end + end + + ## + # Array of modules in this context + + def modules + @modules.values + end + + ## + # Hash of modules keyed by module name + + def modules_hash + @modules + end + + ## + # Name to use to generate the url. + # <tt>#full_name</tt> by default. + + def name_for_path + full_name + end + + ## + # Changes the visibility for new methods to +visibility+ + + def ongoing_visibility=(visibility) + @visibility = visibility + end + + ## + # Record +top_level+ as a file +self+ is in. + + def record_location(top_level) + @in_files << top_level unless @in_files.include?(top_level) + end + + ## + # Should we remove this context from the documentation? + # + # The answer is yes if: + # * #received_nodoc is +true+ + # * #any_content is +false+ (not counting includes) + # * All #includes are modules (not a string), and their module has + # <tt>#remove_from_documentation? == true</tt> + # * All classes and modules have <tt>#remove_from_documentation? == true</tt> + + def remove_from_documentation? + @remove_from_documentation ||= + @received_nodoc && + !any_content(false) && + @includes.all? { |i| !i.module.is_a?(String) && i.module.remove_from_documentation? } && + classes_and_modules.all? { |cm| cm.remove_from_documentation? } + end + + ## + # Removes methods and attributes with a visibility less than +min_visibility+. + #-- + # TODO mark the visibility of attributes in the template (if not public?) + + def remove_invisible min_visibility + return if [:private, :nodoc].include? min_visibility + remove_invisible_in @method_list, min_visibility + remove_invisible_in @attributes, min_visibility + remove_invisible_in @constants, min_visibility + end + + ## + # Only called when min_visibility == :public or :private + + def remove_invisible_in array, min_visibility # :nodoc: + if min_visibility == :public then + array.reject! { |e| + e.visibility != :public and not e.force_documentation + } + else + array.reject! { |e| + e.visibility == :private and not e.force_documentation + } + end + end + + ## + # Tries to resolve unmatched aliases when a method or attribute has just + # been added. + + def resolve_aliases added + # resolve any pending unmatched aliases + key = added.pretty_name + unmatched_alias_list = @unmatched_alias_lists[key] + return unless unmatched_alias_list + unmatched_alias_list.each do |unmatched_alias| + added.add_alias unmatched_alias, self + @external_aliases.delete unmatched_alias + end + @unmatched_alias_lists.delete key + end + + ## + # Returns RDoc::Context::Section objects referenced in this context for use + # in a table of contents. + + def section_contents + used_sections = {} + + each_method do |method| + next unless method.display? + + used_sections[method.section] = true + end + + # order found sections + sections = sort_sections.select do |section| + used_sections[section] + end + + # only the default section is used + return [] if + sections.length == 1 and not sections.first.title + + sections + end + + ## + # Sections in this context + + def sections + @sections.values + end + + def sections_hash # :nodoc: + @sections + end + + ## + # Sets the current section to a section with +title+. See also #add_section + + def set_current_section title, comment + @current_section = add_section title, comment + end + + ## + # Given an array +methods+ of method names, set the visibility of each to + # +visibility+ + + def set_visibility_for(methods, visibility, singleton = false) + methods_matching methods, singleton do |m| + m.visibility = visibility + end + end + + ## + # Given an array +names+ of constants, set the visibility of each constant to + # +visibility+ + + def set_constant_visibility_for(names, visibility) + names.each do |name| + constant = @constants_hash[name] or next + constant.visibility = visibility + end + end + + ## + # Sorts sections alphabetically (default) or in TomDoc fashion (none, + # Public, Internal, Deprecated) + + def sort_sections + titles = @sections.map { |title, _| title } + + if titles.length > 1 and + TOMDOC_TITLES_SORT == + (titles | TOMDOC_TITLES).sort_by { |title| title.to_s } then + @sections.values_at(*TOMDOC_TITLES).compact + else + @sections.sort_by { |title, _| + title.to_s + }.map { |_, section| + section + } + end + end + + def to_s # :nodoc: + "#{self.class.name} #{self.full_name}" + end + + ## + # Return the TopLevel that owns us + #-- + # FIXME we can be 'owned' by several TopLevel (see #record_location & + # #in_files) + + def top_level + return @top_level if defined? @top_level + @top_level = self + @top_level = @top_level.parent until RDoc::TopLevel === @top_level + @top_level + end + + ## + # Upgrades NormalModule +mod+ in +enclosing+ to a +class_type+ + + def upgrade_to_class mod, class_type, enclosing + enclosing.modules_hash.delete mod.name + + klass = RDoc::ClassModule.from_module class_type, mod + klass.store = @store + + # if it was there, then we keep it even if done_documenting + @store.classes_hash[mod.full_name] = klass + enclosing.classes_hash[mod.name] = klass + + klass + end + + autoload :Section, 'rdoc/context/section' + +end diff --git a/lib/rdoc/context/section.rb b/lib/rdoc/context/section.rb new file mode 100644 index 0000000000..11f9ceaf87 --- /dev/null +++ b/lib/rdoc/context/section.rb @@ -0,0 +1,245 @@ +# frozen_string_literal: true +## +# A section of documentation like: +# +# # :section: The title +# # The body +# +# Sections can be referenced multiple times and will be collapsed into a +# single section. + +class RDoc::Context::Section + + include RDoc::Text + + MARSHAL_VERSION = 0 # :nodoc: + + ## + # Section comment + + attr_reader :comment + + ## + # Section comments + + attr_reader :comments + + ## + # Context this Section lives in + + attr_reader :parent + + ## + # Section title + + attr_reader :title + + @@sequence = "SEC00000" + + ## + # Creates a new section with +title+ and +comment+ + + def initialize parent, title, comment + @parent = parent + @title = title ? title.strip : title + + @@sequence = @@sequence.succ + @sequence = @@sequence.dup + + @comments = [] + + add_comment comment + end + + ## + # Sections are equal when they have the same #title + + def == other + self.class === other and @title == other.title + end + + alias eql? == + + ## + # Adds +comment+ to this section + + def add_comment comment + comment = extract_comment comment + + return if comment.empty? + + case comment + when RDoc::Comment then + @comments << comment + when RDoc::Markup::Document then + @comments.concat comment.parts + when Array then + @comments.concat comment + else + raise TypeError, "unknown comment type: #{comment.inspect}" + end + end + + ## + # Anchor reference for linking to this section + + def aref + title = @title || '[untitled]' + + CGI.escape(title).gsub('%', '-').sub(/^-/, '') + end + + ## + # Extracts the comment for this section from the original comment block. + # If the first line contains :section:, strip it and use the rest. + # Otherwise remove lines up to the line containing :section:, and look + # for those lines again at the end and remove them. This lets us write + # + # # :section: The title + # # The body + + def extract_comment comment + case comment + when Array then + comment.map do |c| + extract_comment c + end + when nil + RDoc::Comment.new '' + when RDoc::Comment then + if comment.text =~ /^#[ \t]*:section:.*\n/ then + start = $` + rest = $' + + comment.text = if start.empty? then + rest + else + rest.sub(/#{start.chomp}\Z/, '') + end + end + + comment + when RDoc::Markup::Document then + comment + else + raise TypeError, "unknown comment #{comment.inspect}" + end + end + + def inspect # :nodoc: + "#<%s:0x%x %p>" % [self.class, object_id, title] + end + + def hash # :nodoc: + @title.hash + end + + ## + # The files comments in this section come from + + def in_files + return [] if @comments.empty? + + case @comments + when Array then + @comments.map do |comment| + comment.file + end + when RDoc::Markup::Document then + @comment.parts.map do |document| + document.file + end + else + raise RDoc::Error, "BUG: unknown comment class #{@comments.class}" + end + end + + ## + # Serializes this Section. The title and parsed comment are saved, but not + # the section parent which must be restored manually. + + def marshal_dump + [ + MARSHAL_VERSION, + @title, + parse, + ] + end + + ## + # De-serializes this Section. The section parent must be restored manually. + + def marshal_load array + @parent = nil + + @title = array[1] + @comments = array[2] + end + + ## + # Parses +comment_location+ into an RDoc::Markup::Document composed of + # multiple RDoc::Markup::Documents with their file set. + + def parse + case @comments + when String then + super + when Array then + docs = @comments.map do |comment, location| + doc = super comment + doc.file = location if location + doc + end + + RDoc::Markup::Document.new(*docs) + when RDoc::Comment then + doc = super @comments.text, comments.format + doc.file = @comments.location + doc + when RDoc::Markup::Document then + return @comments + else + raise ArgumentError, "unknown comment class #{comments.class}" + end + end + + ## + # The section's title, or 'Top Section' if the title is nil. + # + # This is used by the table of contents template so the name is silly. + + def plain_html + @title || 'Top Section' + end + + ## + # Removes a comment from this section if it is from the same file as + # +comment+ + + def remove_comment comment + return if @comments.empty? + + case @comments + when Array then + @comments.delete_if do |my_comment| + my_comment.file == comment.file + end + when RDoc::Markup::Document then + @comments.parts.delete_if do |document| + document.file == comment.file.name + end + else + raise RDoc::Error, "BUG: unknown comment class #{@comments.class}" + end + end + + ## + # Section sequence number (deprecated) + + def sequence + warn "RDoc::Context::Section#sequence is deprecated, use #aref" + @sequence + end + +end + diff --git a/lib/rdoc/cross_reference.rb b/lib/rdoc/cross_reference.rb new file mode 100644 index 0000000000..d76ebaf2d0 --- /dev/null +++ b/lib/rdoc/cross_reference.rb @@ -0,0 +1,184 @@ +# frozen_string_literal: true +## +# RDoc::CrossReference is a reusable way to create cross references for names. + +class RDoc::CrossReference + + ## + # Regular expression to match class references + # + # 1. There can be a '\\' in front of text to suppress the cross-reference + # 2. There can be a '::' in front of class names to reference from the + # top-level namespace. + # 3. The method can be followed by parenthesis (not recommended) + + CLASS_REGEXP_STR = '\\\\?((?:\:{2})?[A-Z]\w*(?:\:\:\w+)*)' + + ## + # Regular expression to match method references. + # + # See CLASS_REGEXP_STR + + METHOD_REGEXP_STR = '([a-z]\w*[!?=]?|%|===|\[\]=?|<<|>>)(?:\([\w.+*/=<>-]*\))?' + + ## + # Regular expressions matching text that should potentially have + # cross-reference links generated are passed to add_special. Note that + # these expressions are meant to pick up text for which cross-references + # have been suppressed, since the suppression characters are removed by the + # code that is triggered. + + CROSSREF_REGEXP = /(?:^|\s) + ( + (?: + # A::B::C.meth + #{CLASS_REGEXP_STR}(?:[.#]|::)#{METHOD_REGEXP_STR} + + # Stand-alone method (preceded by a #) + | \\?\##{METHOD_REGEXP_STR} + + # Stand-alone method (preceded by ::) + | ::#{METHOD_REGEXP_STR} + + # A::B::C + # The stuff after CLASS_REGEXP_STR is a + # nasty hack. CLASS_REGEXP_STR unfortunately matches + # words like dog and cat (these are legal "class" + # names in Fortran 95). When a word is flagged as a + # potential cross-reference, limitations in the markup + # engine suppress other processing, such as typesetting. + # This is particularly noticeable for contractions. + # In order that words like "can't" not + # be flagged as potential cross-references, only + # flag potential class cross-references if the character + # after the cross-reference is a space, sentence + # punctuation, tag start character, or attribute + # marker. + | #{CLASS_REGEXP_STR}(?=[@\s).?!,;<\000]|\z) + + # Things that look like filenames + # The key thing is that there must be at least + # one special character (period, slash, or + # underscore). + | (?:\.\.\/)*[-\/\w]+[_\/.][-\w\/.]+ + + # Things that have markup suppressed + # Don't process things like '\<' in \<tt>, though. + # TODO: including < is a hack, not very satisfying. + | \\[^\s<] + ) + + # labels for headings + (?:@[\w+%-]+(?:\.[\w|%-]+)?)? + )/x + + ## + # Version of CROSSREF_REGEXP used when <tt>--hyperlink-all</tt> is specified. + + ALL_CROSSREF_REGEXP = / + (?:^|\s) + ( + (?: + # A::B::C.meth + #{CLASS_REGEXP_STR}(?:[.#]|::)#{METHOD_REGEXP_STR} + + # Stand-alone method + | \\?#{METHOD_REGEXP_STR} + + # A::B::C + | #{CLASS_REGEXP_STR}(?=[@\s).?!,;<\000]|\z) + + # Things that look like filenames + | (?:\.\.\/)*[-\/\w]+[_\/.][-\w\/.]+ + + # Things that have markup suppressed + | \\[^\s<] + ) + + # labels for headings + (?:@[\w+%-]+)? + )/x + + ## + # Hash of references that have been looked-up to their replacements + + attr_accessor :seen + + ## + # Allows cross-references to be created based on the given +context+ + # (RDoc::Context). + + def initialize context + @context = context + @store = context.store + + @seen = {} + end + + ## + # Returns a reference to +name+. + # + # If the reference is found and +name+ is not documented +text+ will be + # returned. If +name+ is escaped +name+ is returned. If +name+ is not + # found +text+ is returned. + + def resolve name, text + return @seen[name] if @seen.include? name + + if /#{CLASS_REGEXP_STR}([.#]|::)#{METHOD_REGEXP_STR}/o =~ name then + type = $2 + type = '' if type == '.' # will find either #method or ::method + method = "#{type}#{$3}" + container = @context.find_symbol_module($1) + elsif /^([.#]|::)#{METHOD_REGEXP_STR}/o =~ name then + type = $1 + type = '' if type == '.' + method = "#{type}#{$2}" + container = @context + else + container = nil + end + + if container then + ref = container.find_local_symbol method + + unless ref || RDoc::TopLevel === container then + ref = container.find_ancestor_local_symbol method + end + end + + ref = case name + when /^\\(#{CLASS_REGEXP_STR})$/o then + @context.find_symbol $1 + else + @context.find_symbol name + end unless ref + + # Try a page name + ref = @store.page name if not ref and name =~ /^\w+$/ + + ref = nil if RDoc::Alias === ref # external alias, can't link to it + + out = if name == '\\' then + name + elsif name =~ /^\\/ then + # we remove the \ only in front of what we know: + # other backslashes are treated later, only outside of <tt> + ref ? $' : name + elsif ref then + if ref.display? then + ref + else + text + end + else + text + end + + @seen[name] = out + + out + end + +end + diff --git a/lib/rdoc/diagram.rb b/lib/rdoc/diagram.rb deleted file mode 100644 index 9fdc49c02e..0000000000 --- a/lib/rdoc/diagram.rb +++ /dev/null @@ -1,335 +0,0 @@ -# A wonderful hack by to draw package diagrams using the dot package. -# Originally written by Jah, team Enticla. -# -# You must have the V1.7 or later in your path -# http://www.research.att.com/sw/tools/graphviz/ - -require "rdoc/dot/dot" -require 'rdoc/options' - -module RDoc - - # Draw a set of diagrams representing the modules and classes in the - # system. We draw one diagram for each file, and one for each toplevel - # class or module. This means there will be overlap. However, it also - # means that you'll get better context for objects. - # - # To use, simply - # - # d = Diagram.new(info) # pass in collection of top level infos - # d.draw - # - # The results will be written to the +dot+ subdirectory. The process - # also sets the +diagram+ attribute in each object it graphs to - # the name of the file containing the image. This can be used - # by output generators to insert images. - - class Diagram - - FONT = "Arial" - - DOT_PATH = "dot" - - # Pass in the set of top level objects. The method also creates - # the subdirectory to hold the images - - def initialize(info, options) - @info = info - @options = options - @counter = 0 - File.makedirs(DOT_PATH) - @diagram_cache = {} - end - - # Draw the diagrams. We traverse the files, drawing a diagram for - # each. We also traverse each top-level class and module in that - # file drawing a diagram for these too. - - def draw - unless @options.quiet - $stderr.print "Diagrams: " - $stderr.flush - end - - @info.each_with_index do |i, file_count| - @done_modules = {} - @local_names = find_names(i) - @global_names = [] - @global_graph = graph = DOT::DOTDigraph.new('name' => 'TopLevel', - 'fontname' => FONT, - 'fontsize' => '8', - 'bgcolor' => 'lightcyan1', - 'compound' => 'true') - - # it's a little hack %) i'm too lazy to create a separate class - # for default node - graph << DOT::DOTNode.new('name' => 'node', - 'fontname' => FONT, - 'color' => 'black', - 'fontsize' => 8) - - i.modules.each do |mod| - draw_module(mod, graph, true, i.file_relative_name) - end - add_classes(i, graph, i.file_relative_name) - - i.diagram = convert_to_png("f_#{file_count}", graph) - - # now go through and document each top level class and - # module independently - i.modules.each_with_index do |mod, count| - @done_modules = {} - @local_names = find_names(mod) - @global_names = [] - - @global_graph = graph = DOT::DOTDigraph.new('name' => 'TopLevel', - 'fontname' => FONT, - 'fontsize' => '8', - 'bgcolor' => 'lightcyan1', - 'compound' => 'true') - - graph << DOT::DOTNode.new('name' => 'node', - 'fontname' => FONT, - 'color' => 'black', - 'fontsize' => 8) - draw_module(mod, graph, true) - mod.diagram = convert_to_png("m_#{file_count}_#{count}", - graph) - end - end - $stderr.puts unless @options.quiet - end - - ####### - private - ####### - - def find_names(mod) - return [mod.full_name] + mod.classes.collect{|cl| cl.full_name} + - mod.modules.collect{|m| find_names(m)}.flatten - end - - def find_full_name(name, mod) - full_name = name.dup - return full_name if @local_names.include?(full_name) - mod_path = mod.full_name.split('::')[0..-2] - unless mod_path.nil? - until mod_path.empty? - full_name = mod_path.pop + '::' + full_name - return full_name if @local_names.include?(full_name) - end - end - return name - end - - def draw_module(mod, graph, toplevel = false, file = nil) - return if @done_modules[mod.full_name] and not toplevel - - @counter += 1 - url = mod.http_url("classes") - m = DOT::DOTSubgraph.new('name' => "cluster_#{mod.full_name.gsub( /:/,'_' )}", - 'label' => mod.name, - 'fontname' => FONT, - 'color' => 'blue', - 'style' => 'filled', - 'URL' => %{"#{url}"}, - 'fillcolor' => toplevel ? 'palegreen1' : 'palegreen3') - - @done_modules[mod.full_name] = m - add_classes(mod, m, file) - graph << m - - unless mod.includes.empty? - mod.includes.each do |m| - m_full_name = find_full_name(m.name, mod) - if @local_names.include?(m_full_name) - @global_graph << DOT::DOTEdge.new('from' => "#{m_full_name.gsub( /:/,'_' )}", - 'to' => "#{mod.full_name.gsub( /:/,'_' )}", - 'ltail' => "cluster_#{m_full_name.gsub( /:/,'_' )}", - 'lhead' => "cluster_#{mod.full_name.gsub( /:/,'_' )}") - else - unless @global_names.include?(m_full_name) - path = m_full_name.split("::") - url = File.join('classes', *path) + ".html" - @global_graph << DOT::DOTNode.new('name' => "#{m_full_name.gsub( /:/,'_' )}", - 'shape' => 'box', - 'label' => "#{m_full_name}", - 'URL' => %{"#{url}"}) - @global_names << m_full_name - end - @global_graph << DOT::DOTEdge.new('from' => "#{m_full_name.gsub( /:/,'_' )}", - 'to' => "#{mod.full_name.gsub( /:/,'_' )}", - 'lhead' => "cluster_#{mod.full_name.gsub( /:/,'_' )}") - end - end - end - end - - def add_classes(container, graph, file = nil ) - - use_fileboxes = Options.instance.fileboxes - - files = {} - - # create dummy node (needed if empty and for module includes) - if container.full_name - graph << DOT::DOTNode.new('name' => "#{container.full_name.gsub( /:/,'_' )}", - 'label' => "", - 'width' => (container.classes.empty? and - container.modules.empty?) ? - '0.75' : '0.01', - 'height' => '0.01', - 'shape' => 'plaintext') - end - container.classes.each_with_index do |cl, cl_index| - last_file = cl.in_files[-1].file_relative_name - - if use_fileboxes && !files.include?(last_file) - @counter += 1 - files[last_file] = - DOT::DOTSubgraph.new('name' => "cluster_#{@counter}", - 'label' => "#{last_file}", - 'fontname' => FONT, - 'color'=> - last_file == file ? 'red' : 'black') - end - - next if cl.name == 'Object' || cl.name[0,2] == "<<" - - url = cl.http_url("classes") - - label = cl.name.dup - if use_fileboxes && cl.in_files.length > 1 - label << '\n[' + - cl.in_files.collect {|i| - i.file_relative_name - }.sort.join( '\n' ) + - ']' - end - - attrs = { - 'name' => "#{cl.full_name.gsub( /:/, '_' )}", - 'fontcolor' => 'black', - 'style'=>'filled', - 'color'=>'palegoldenrod', - 'label' => label, - 'shape' => 'ellipse', - 'URL' => %{"#{url}"} - } - - c = DOT::DOTNode.new(attrs) - - if use_fileboxes - files[last_file].push c - else - graph << c - end - end - - if use_fileboxes - files.each_value do |val| - graph << val - end - end - - unless container.classes.empty? - container.classes.each_with_index do |cl, cl_index| - cl.includes.each do |m| - m_full_name = find_full_name(m.name, cl) - if @local_names.include?(m_full_name) - @global_graph << DOT::DOTEdge.new('from' => "#{m_full_name.gsub( /:/,'_' )}", - 'to' => "#{cl.full_name.gsub( /:/,'_' )}", - 'ltail' => "cluster_#{m_full_name.gsub( /:/,'_' )}") - else - unless @global_names.include?(m_full_name) - path = m_full_name.split("::") - url = File.join('classes', *path) + ".html" - @global_graph << DOT::DOTNode.new('name' => "#{m_full_name.gsub( /:/,'_' )}", - 'shape' => 'box', - 'label' => "#{m_full_name}", - 'URL' => %{"#{url}"}) - @global_names << m_full_name - end - @global_graph << DOT::DOTEdge.new('from' => "#{m_full_name.gsub( /:/,'_' )}", - 'to' => "#{cl.full_name.gsub( /:/, '_')}") - end - end - - sclass = cl.superclass - next if sclass.nil? || sclass == 'Object' - sclass_full_name = find_full_name(sclass,cl) - unless @local_names.include?(sclass_full_name) or @global_names.include?(sclass_full_name) - path = sclass_full_name.split("::") - url = File.join('classes', *path) + ".html" - @global_graph << DOT::DOTNode.new( - 'name' => "#{sclass_full_name.gsub( /:/, '_' )}", - 'label' => sclass_full_name, - 'URL' => %{"#{url}"}) - @global_names << sclass_full_name - end - @global_graph << DOT::DOTEdge.new('from' => "#{sclass_full_name.gsub( /:/,'_' )}", - 'to' => "#{cl.full_name.gsub( /:/, '_')}") - end - end - - container.modules.each do |submod| - draw_module(submod, graph) - end - - end - - def convert_to_png(file_base, graph) - str = graph.to_s - return @diagram_cache[str] if @diagram_cache[str] - op_type = Options.instance.image_format - dotfile = File.join(DOT_PATH, file_base) - src = dotfile + ".dot" - dot = dotfile + "." + op_type - - unless @options.quiet - $stderr.print "." - $stderr.flush - end - - File.open(src, 'w+' ) do |f| - f << str << "\n" - end - - system "dot", "-T#{op_type}", src, "-o", dot - - # Now construct the imagemap wrapper around - # that png - - ret = wrap_in_image_map(src, dot) - @diagram_cache[str] = ret - return ret - end - - # Extract the client-side image map from dot, and use it - # to generate the imagemap proper. Return the whole - # <map>..<img> combination, suitable for inclusion on - # the page - - def wrap_in_image_map(src, dot) - res = %{<map id="map" name="map">\n} - dot_map = `dot -Tismap #{src}` - dot_map.each do |area| - unless area =~ /^rectangle \((\d+),(\d+)\) \((\d+),(\d+)\) ([\/\w.]+)\s*(.*)/ - $stderr.puts "Unexpected output from dot:\n#{area}" - return nil - end - - xs, ys = [$1.to_i, $3.to_i], [$2.to_i, $4.to_i] - url, area_name = $5, $6 - - res << %{ <area shape="rect" coords="#{xs.min},#{ys.min},#{xs.max},#{ys.max}" } - res << %{ href="#{url}" alt="#{area_name}" />\n} - end - res << "</map>\n" -# map_file = src.sub(/.dot/, '.map') -# system("dot -Timap #{src} -o #{map_file}") - res << %{<img src="#{dot}" usemap="#map" border="0" alt="#{dot}">} - return res - end - end -end diff --git a/lib/rdoc/dot/dot.rb b/lib/rdoc/dot/dot.rb deleted file mode 100644 index 6dbb7cb237..0000000000 --- a/lib/rdoc/dot/dot.rb +++ /dev/null @@ -1,255 +0,0 @@ -module DOT - - # these glogal vars are used to make nice graph source - $tab = ' ' - $tab2 = $tab * 2 - - # if we don't like 4 spaces, we can change it any time - def change_tab( t ) - $tab = t - $tab2 = t * 2 - end - - # options for node declaration - NODE_OPTS = [ - 'bgcolor', - 'color', - 'fontcolor', - 'fontname', - 'fontsize', - 'height', - 'width', - 'label', - 'layer', - 'rank', - 'shape', - 'shapefile', - 'style', - 'URL', - ] - - # options for edge declaration - EDGE_OPTS = [ - 'color', - 'decorate', - 'dir', - 'fontcolor', - 'fontname', - 'fontsize', - 'id', - 'label', - 'layer', - 'lhead', - 'ltail', - 'minlen', - 'style', - 'weight' - ] - - # options for graph declaration - GRAPH_OPTS = [ - 'bgcolor', - 'center', - 'clusterrank', - 'color', - 'compound', - 'concentrate', - 'fillcolor', - 'fontcolor', - 'fontname', - 'fontsize', - 'label', - 'layerseq', - 'margin', - 'mclimit', - 'nodesep', - 'nslimit', - 'ordering', - 'orientation', - 'page', - 'rank', - 'rankdir', - 'ranksep', - 'ratio', - 'size', - 'style', - 'URL' - ] - - # a root class for any element in dot notation - class DOTSimpleElement - attr_accessor :name - - def initialize( params = {} ) - @label = params['name'] ? params['name'] : '' - end - - def to_s - @name - end - end - - # an element that has options ( node, edge or graph ) - class DOTElement < DOTSimpleElement - #attr_reader :parent - attr_accessor :name, :options - - def initialize( params = {}, option_list = [] ) - super( params ) - @name = params['name'] ? params['name'] : nil - @parent = params['parent'] ? params['parent'] : nil - @options = {} - option_list.each{ |i| - @options[i] = params[i] if params[i] - } - @options['label'] ||= @name if @name != 'node' - end - - def each_option - @options.each{ |i| yield i } - end - - def each_option_pair - @options.each_pair{ |key, val| yield key, val } - end - - #def parent=( thing ) - # @parent.delete( self ) if defined?( @parent ) and @parent - # @parent = thing - #end - end - - - # this is used when we build nodes that have shape=record - # ports don't have options :) - class DOTPort < DOTSimpleElement - attr_accessor :label - - def initialize( params = {} ) - super( params ) - @name = params['label'] ? params['label'] : '' - end - def to_s - ( @name && @name != "" ? "<#{@name}>" : "" ) + "#{@label}" - end - end - - # node element - class DOTNode < DOTElement - - def initialize( params = {}, option_list = NODE_OPTS ) - super( params, option_list ) - @ports = params['ports'] ? params['ports'] : [] - end - - def each_port - @ports.each{ |i| yield i } - end - - def << ( thing ) - @ports << thing - end - - def push ( thing ) - @ports.push( thing ) - end - - def pop - @ports.pop - end - - def to_s( t = '' ) - - label = @options['shape'] != 'record' && @ports.length == 0 ? - @options['label'] ? - t + $tab + "label = \"#{@options['label']}\"\n" : - '' : - t + $tab + 'label = "' + " \\\n" + - t + $tab2 + "#{@options['label']}| \\\n" + - @ports.collect{ |i| - t + $tab2 + i.to_s - }.join( "| \\\n" ) + " \\\n" + - t + $tab + '"' + "\n" - - t + "#{@name} [\n" + - @options.to_a.collect{ |i| - i[1] && i[0] != 'label' ? - t + $tab + "#{i[0]} = #{i[1]}" : nil - }.compact.join( ",\n" ) + ( label != '' ? ",\n" : "\n" ) + - label + - t + "]\n" - end - end - - # subgraph element is the same to graph, but has another header in dot - # notation - class DOTSubgraph < DOTElement - - def initialize( params = {}, option_list = GRAPH_OPTS ) - super( params, option_list ) - @nodes = params['nodes'] ? params['nodes'] : [] - @dot_string = 'subgraph' - end - - def each_node - @nodes.each{ |i| yield i } - end - - def << ( thing ) - @nodes << thing - end - - def push( thing ) - @nodes.push( thing ) - end - - def pop - @nodes.pop - end - - def to_s( t = '' ) - hdr = t + "#{@dot_string} #{@name} {\n" - - options = @options.to_a.collect{ |name, val| - val && name != 'label' ? - t + $tab + "#{name} = #{val}" : - name ? t + $tab + "#{name} = \"#{val}\"" : nil - }.compact.join( "\n" ) + "\n" - - nodes = @nodes.collect{ |i| - i.to_s( t + $tab ) - }.join( "\n" ) + "\n" - hdr + options + nodes + t + "}\n" - end - end - - # this is graph - class DOTDigraph < DOTSubgraph - def initialize( params = {}, option_list = GRAPH_OPTS ) - super( params, option_list ) - @dot_string = 'digraph' - end - end - - # this is edge - class DOTEdge < DOTElement - attr_accessor :from, :to - def initialize( params = {}, option_list = EDGE_OPTS ) - super( params, option_list ) - @from = params['from'] ? params['from'] : nil - @to = params['to'] ? params['to'] : nil - end - - def to_s( t = '' ) - t + "#{@from} -> #{to} [\n" + - @options.to_a.collect{ |i| - i[1] && i[0] != 'label' ? - t + $tab + "#{i[0]} = #{i[1]}" : - i[1] ? t + $tab + "#{i[0]} = \"#{i[1]}\"" : nil - }.compact.join( "\n" ) + "\n" + t + "]\n" - end - end -end - - - diff --git a/lib/rdoc/encoding.rb b/lib/rdoc/encoding.rb new file mode 100644 index 0000000000..54ecd89816 --- /dev/null +++ b/lib/rdoc/encoding.rb @@ -0,0 +1,130 @@ +# coding: US-ASCII +# frozen_string_literal: true + +## +# This class is a wrapper around File IO and Encoding that helps RDoc load +# files and convert them to the correct encoding. + +module RDoc::Encoding + + ## + # Reads the contents of +filename+ and handles any encoding directives in + # the file. + # + # The content will be converted to the +encoding+. If the file cannot be + # converted a warning will be printed and nil will be returned. + # + # If +force_transcode+ is true the document will be transcoded and any + # unknown character in the target encoding will be replaced with '?' + + def self.read_file filename, encoding, force_transcode = false + content = open filename, "rb" do |f| f.read end + content.gsub!("\r\n", "\n") if RUBY_PLATFORM =~ /mswin|mingw/ + + utf8 = content.sub!(/\A\xef\xbb\xbf/, '') + + content = RDoc::Encoding.set_encoding content + + begin + encoding ||= Encoding.default_external + orig_encoding = content.encoding + + if not orig_encoding.ascii_compatible? then + content = content.encode encoding + elsif utf8 then + content = RDoc::Encoding.change_encoding content, Encoding::UTF_8 + content = content.encode encoding + else + # assume the content is in our output encoding + content = RDoc::Encoding.change_encoding content, encoding + end + + unless content.valid_encoding? then + # revert and try to transcode + content = RDoc::Encoding.change_encoding content, orig_encoding + content = content.encode encoding + end + + unless content.valid_encoding? then + warn "unable to convert #{filename} to #{encoding}, skipping" + content = nil + end + rescue Encoding::InvalidByteSequenceError, + Encoding::UndefinedConversionError => e + if force_transcode then + content = RDoc::Encoding.change_encoding content, orig_encoding + content = content.encode(encoding, + :invalid => :replace, + :undef => :replace, + :replace => '?') + return content + else + warn "unable to convert #{e.message} for #{filename}, skipping" + return nil + end + end + + content + rescue ArgumentError => e + raise unless e.message =~ /unknown encoding name - (.*)/ + warn "unknown encoding name \"#{$1}\" for #{filename}, skipping" + nil + rescue Errno::EISDIR, Errno::ENOENT + nil + end + + def self.remove_frozen_string_literal string + string =~ /\A(?:#!.*\n)?(.*\n)/ + first_line = $1 + + if first_line =~ /\A# +frozen[-_]string[-_]literal[=:].+$/i + string = string.sub first_line, '' + end + + string + end + + ## + # Sets the encoding of +string+ based on the magic comment + + def self.set_encoding string + string = remove_frozen_string_literal string + + string =~ /\A(?:#!.*\n)?(.*\n)/ + + first_line = $1 + + name = case first_line + when /^<\?xml[^?]*encoding=(["'])(.*?)\1/ then $2 + when /\b(?:en)?coding[=:]\s*([^\s;]+)/i then $1 + else return string + end + + string = string.sub first_line, '' + + string = remove_frozen_string_literal string + + enc = Encoding.find name + string = RDoc::Encoding.change_encoding string, enc if enc + + string + end + + ## + # Changes encoding based on +encoding+ without converting and returns new + # string + + def self.change_encoding text, encoding + if text.kind_of? RDoc::Comment + text.encode! encoding + else + # TODO: Remove this condition after Ruby 2.2 EOL + if RUBY_VERSION < '2.3.0' + text.force_encoding encoding + else + String.new text, encoding: encoding + end + end + end + +end diff --git a/lib/rdoc/erb_partial.rb b/lib/rdoc/erb_partial.rb new file mode 100644 index 0000000000..8dc2c46013 --- /dev/null +++ b/lib/rdoc/erb_partial.rb @@ -0,0 +1,19 @@ +# frozen_string_literal: true +## +# Allows an ERB template to be rendered in the context (binding) of an +# existing ERB template evaluation. + +class RDoc::ERBPartial < ERB + + ## + # Overrides +compiler+ startup to set the +eoutvar+ to an empty string only + # if it isn't already set. + + def set_eoutvar compiler, eoutvar = '_erbout' + super + + compiler.pre_cmd = ["#{eoutvar} ||= ''"] + end + +end + diff --git a/lib/rdoc/erbio.rb b/lib/rdoc/erbio.rb new file mode 100644 index 0000000000..42ce895fb3 --- /dev/null +++ b/lib/rdoc/erbio.rb @@ -0,0 +1,38 @@ +# frozen_string_literal: true +require 'erb' + +## +# A subclass of ERB that writes directly to an IO. Credit to Aaron Patterson +# and Masatoshi SEKI. +# +# To use: +# +# erbio = RDoc::ERBIO.new '<%= "hello world" %>', nil, nil +# +# open 'hello.txt', 'w' do |io| +# erbio.result binding +# end +# +# Note that binding must enclose the io you wish to output on. + +class RDoc::ERBIO < ERB + + ## + # Defaults +eoutvar+ to 'io', otherwise is identical to ERB's initialize + + def initialize str, safe_level = nil, trim_mode = nil, eoutvar = 'io' + super + end + + ## + # Instructs +compiler+ how to write to +io_variable+ + + def set_eoutvar compiler, io_variable + compiler.put_cmd = "#{io_variable}.write" + compiler.insert_cmd = "#{io_variable}.write" + compiler.pre_cmd = [] + compiler.post_cmd = [] + end + +end + diff --git a/lib/rdoc/extend.rb b/lib/rdoc/extend.rb new file mode 100644 index 0000000000..e1b182902e --- /dev/null +++ b/lib/rdoc/extend.rb @@ -0,0 +1,10 @@ +# frozen_string_literal: true +## +# A Module extension to a class with \#extend +# +# RDoc::Extend.new 'Enumerable', 'comment ...' + +class RDoc::Extend < RDoc::Mixin + +end + diff --git a/lib/rdoc/generator.rb b/lib/rdoc/generator.rb new file mode 100644 index 0000000000..340dcbf7ae --- /dev/null +++ b/lib/rdoc/generator.rb @@ -0,0 +1,51 @@ +# frozen_string_literal: true +## +# RDoc uses generators to turn parsed source code in the form of an +# RDoc::CodeObject tree into some form of output. RDoc comes with the HTML +# generator RDoc::Generator::Darkfish and an ri data generator +# RDoc::Generator::RI. +# +# == Registering a Generator +# +# Generators are registered by calling RDoc::RDoc.add_generator with the class +# of the generator: +# +# class My::Awesome::Generator +# RDoc::RDoc.add_generator self +# end +# +# == Adding Options to +rdoc+ +# +# Before option processing in +rdoc+, RDoc::Options will call ::setup_options +# on the generator class with an RDoc::Options instance. The generator can +# use RDoc::Options#option_parser to add command-line options to the +rdoc+ +# tool. See RDoc::Options@Custom+Options for an example and see OptionParser +# for details on how to add options. +# +# You can extend the RDoc::Options instance with additional accessors for your +# generator. +# +# == Generator Instantiation +# +# After parsing, RDoc::RDoc will instantiate a generator by calling +# #initialize with an RDoc::Store instance and an RDoc::Options instance. +# +# The RDoc::Store instance holds documentation for parsed source code. In +# RDoc 3 and earlier the RDoc::TopLevel class held this data. When upgrading +# a generator from RDoc 3 and earlier you should only need to replace +# RDoc::TopLevel with the store instance. +# +# RDoc will then call #generate on the generator instance. You can use the +# various methods on RDoc::Store and in the RDoc::CodeObject tree to create +# your desired output format. + +module RDoc::Generator + + autoload :Markup, 'rdoc/generator/markup' + + autoload :Darkfish, 'rdoc/generator/darkfish' + autoload :JsonIndex, 'rdoc/generator/json_index' + autoload :RI, 'rdoc/generator/ri' + autoload :POT, 'rdoc/generator/pot' + +end diff --git a/lib/rdoc/generator/darkfish.rb b/lib/rdoc/generator/darkfish.rb new file mode 100644 index 0000000000..bf4eb1f530 --- /dev/null +++ b/lib/rdoc/generator/darkfish.rb @@ -0,0 +1,786 @@ +# frozen_string_literal: true +# -*- mode: ruby; ruby-indent-level: 2; tab-width: 2 -*- + +require 'erb' +require 'fileutils' +require 'pathname' +require 'rdoc/generator/markup' + +## +# Darkfish RDoc HTML Generator +# +# $Id: darkfish.rb 52 2009-01-07 02:08:11Z deveiant $ +# +# == Author/s +# * Michael Granger (ged@FaerieMUD.org) +# +# == Contributors +# * Mahlon E. Smith (mahlon@martini.nu) +# * Eric Hodel (drbrain@segment7.net) +# +# == License +# +# Copyright (c) 2007, 2008, Michael Granger. All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# * Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. +# +# * Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# +# * Neither the name of the author/s, nor the names of the project's +# contributors may be used to endorse or promote products derived from this +# software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE +# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# +# == Attributions +# +# Darkfish uses the {Silk Icons}[http://www.famfamfam.com/lab/icons/silk/] set +# by Mark James. + +class RDoc::Generator::Darkfish + + RDoc::RDoc.add_generator self + + include ERB::Util + + ## + # Stylesheets, fonts, etc. that are included in RDoc. + + BUILTIN_STYLE_ITEMS = # :nodoc: + %w[ + css/fonts.css + fonts/Lato-Light.ttf + fonts/Lato-LightItalic.ttf + fonts/Lato-Regular.ttf + fonts/Lato-RegularItalic.ttf + fonts/SourceCodePro-Bold.ttf + fonts/SourceCodePro-Regular.ttf + css/rdoc.css + ] + + ## + # Path to this file's parent directory. Used to find templates and other + # resources. + + GENERATOR_DIR = File.join 'rdoc', 'generator' + + ## + # Release Version + + VERSION = '3' + + ## + # Description of this generator + + DESCRIPTION = 'HTML generator, written by Michael Granger' + + ## + # The relative path to style sheets and javascript. By default this is set + # the same as the rel_prefix. + + attr_accessor :asset_rel_path + + ## + # The path to generate files into, combined with <tt>--op</tt> from the + # options for a full path. + + attr_reader :base_dir + + ## + # Classes and modules to be used by this generator, not necessarily + # displayed. See also #modsort + + attr_reader :classes + + ## + # No files will be written when dry_run is true. + + attr_accessor :dry_run + + ## + # When false the generate methods return a String instead of writing to a + # file. The default is true. + + attr_accessor :file_output + + ## + # Files to be displayed by this generator + + attr_reader :files + + ## + # The JSON index generator for this Darkfish generator + + attr_reader :json_index + + ## + # Methods to be displayed by this generator + + attr_reader :methods + + ## + # Sorted list of classes and modules to be displayed by this generator + + attr_reader :modsort + + ## + # The RDoc::Store that is the source of the generated content + + attr_reader :store + + ## + # The directory where the template files live + + attr_reader :template_dir # :nodoc: + + ## + # The output directory + + attr_reader :outputdir + + ## + # Initialize a few instance variables before we start + + def initialize store, options + @store = store + @options = options + + @asset_rel_path = '' + @base_dir = Pathname.pwd.expand_path + @dry_run = @options.dry_run + @file_output = true + @template_dir = Pathname.new options.template_dir + @template_cache = {} + + @classes = nil + @context = nil + @files = nil + @methods = nil + @modsort = nil + + @json_index = RDoc::Generator::JsonIndex.new self, options + end + + ## + # Output progress information if debugging is enabled + + def debug_msg *msg + return unless $DEBUG_RDOC + $stderr.puts(*msg) + end + + ## + # Directory where generated class HTML files live relative to the output + # dir. + + def class_dir + nil + end + + ## + # Directory where generated class HTML files live relative to the output + # dir. + + def file_dir + nil + end + + ## + # Create the directories the generated docs will live in if they don't + # already exist. + + def gen_sub_directories + @outputdir.mkpath + end + + ## + # Copy over the stylesheet into the appropriate place in the output + # directory. + + def write_style_sheet + debug_msg "Copying static files" + options = { :verbose => $DEBUG_RDOC, :noop => @dry_run } + + BUILTIN_STYLE_ITEMS.each do |item| + install_rdoc_static_file @template_dir + item, "./#{item}", options + end + + @options.template_stylesheets.each do |stylesheet| + FileUtils.cp stylesheet, '.', options + end + + Dir[(@template_dir + "{js,images}/**/*").to_s].each do |path| + next if File.directory? path + next if File.basename(path) =~ /^\./ + + dst = Pathname.new(path).relative_path_from @template_dir + + install_rdoc_static_file @template_dir + path, dst, options + end + end + + ## + # Build the initial indices and output objects based on an array of TopLevel + # objects containing the extracted information. + + def generate + setup + + write_style_sheet + generate_index + generate_class_files + generate_file_files + generate_table_of_contents + @json_index.generate + @json_index.generate_gzipped + + copy_static + + rescue => e + debug_msg "%s: %s\n %s" % [ + e.class.name, e.message, e.backtrace.join("\n ") + ] + + raise + end + + ## + # Copies static files from the static_path into the output directory + + def copy_static + return if @options.static_path.empty? + + fu_options = { :verbose => $DEBUG_RDOC, :noop => @dry_run } + + @options.static_path.each do |path| + unless File.directory? path then + FileUtils.install path, @outputdir, fu_options.merge(:mode => 0644) + next + end + + Dir.chdir path do + Dir[File.join('**', '*')].each do |entry| + dest_file = @outputdir + entry + + if File.directory? entry then + FileUtils.mkdir_p entry, fu_options + else + FileUtils.install entry, dest_file, fu_options.merge(:mode => 0644) + end + end + end + end + end + + ## + # Return a list of the documented modules sorted by salience first, then + # by name. + + def get_sorted_module_list classes + classes.select do |klass| + klass.display? + end.sort + end + + ## + # Generate an index page which lists all the classes which are documented. + + def generate_index + setup + + template_file = @template_dir + 'index.rhtml' + return unless template_file.exist? + + debug_msg "Rendering the index page..." + + out_file = @base_dir + @options.op_dir + 'index.html' + rel_prefix = @outputdir.relative_path_from out_file.dirname + search_index_rel_prefix = rel_prefix + search_index_rel_prefix += @asset_rel_path if @file_output + + asset_rel_prefix = rel_prefix + @asset_rel_path + + @title = @options.title + + render_template template_file, out_file do |io| + here = binding + # suppress 1.9.3 warning + here.local_variable_set(:asset_rel_prefix, asset_rel_prefix) + here + end + rescue => e + error = RDoc::Error.new \ + "error generating index.html: #{e.message} (#{e.class})" + error.set_backtrace e.backtrace + + raise error + end + + ## + # Generates a class file for +klass+ + + def generate_class klass, template_file = nil + setup + + current = klass + + template_file ||= @template_dir + 'class.rhtml' + + debug_msg " working on %s (%s)" % [klass.full_name, klass.path] + out_file = @outputdir + klass.path + rel_prefix = @outputdir.relative_path_from out_file.dirname + search_index_rel_prefix = rel_prefix + search_index_rel_prefix += @asset_rel_path if @file_output + + asset_rel_prefix = rel_prefix + @asset_rel_path + svninfo = get_svninfo(current) + + @title = "#{klass.type} #{klass.full_name} - #{@options.title}" + + debug_msg " rendering #{out_file}" + render_template template_file, out_file do |io| + here = binding + # suppress 1.9.3 warning + here.local_variable_set(:asset_rel_prefix, asset_rel_prefix) + here.local_variable_set(:svninfo, svninfo) + here + end + end + + ## + # Generate a documentation file for each class and module + + def generate_class_files + setup + + template_file = @template_dir + 'class.rhtml' + template_file = @template_dir + 'classpage.rhtml' unless + template_file.exist? + return unless template_file.exist? + debug_msg "Generating class documentation in #{@outputdir}" + + current = nil + + @classes.each do |klass| + current = klass + + generate_class klass, template_file + end + rescue => e + error = RDoc::Error.new \ + "error generating #{current.path}: #{e.message} (#{e.class})" + error.set_backtrace e.backtrace + + raise error + end + + ## + # Generate a documentation file for each file + + def generate_file_files + setup + + page_file = @template_dir + 'page.rhtml' + fileinfo_file = @template_dir + 'fileinfo.rhtml' + + # for legacy templates + filepage_file = @template_dir + 'filepage.rhtml' unless + page_file.exist? or fileinfo_file.exist? + + return unless + page_file.exist? or fileinfo_file.exist? or filepage_file.exist? + + debug_msg "Generating file documentation in #{@outputdir}" + + out_file = nil + current = nil + + @files.each do |file| + current = file + + if file.text? and page_file.exist? then + generate_page file + next + end + + template_file = nil + out_file = @outputdir + file.path + debug_msg " working on %s (%s)" % [file.full_name, out_file] + rel_prefix = @outputdir.relative_path_from out_file.dirname + search_index_rel_prefix = rel_prefix + search_index_rel_prefix += @asset_rel_path if @file_output + + asset_rel_prefix = rel_prefix + @asset_rel_path + + unless filepage_file then + if file.text? then + next unless page_file.exist? + template_file = page_file + @title = file.page_name + else + next unless fileinfo_file.exist? + template_file = fileinfo_file + @title = "File: #{file.base_name}" + end + end + + @title += " - #{@options.title}" + template_file ||= filepage_file + + render_template template_file, out_file do |io| + here = binding + # suppress 1.9.3 warning + here.local_variable_set(:asset_rel_prefix, asset_rel_prefix) + here.local_variable_set(:current, current) + here + end + end + rescue => e + error = + RDoc::Error.new "error generating #{out_file}: #{e.message} (#{e.class})" + error.set_backtrace e.backtrace + + raise error + end + + ## + # Generate a page file for +file+ + + def generate_page file + setup + + template_file = @template_dir + 'page.rhtml' + + out_file = @outputdir + file.path + debug_msg " working on %s (%s)" % [file.full_name, out_file] + rel_prefix = @outputdir.relative_path_from out_file.dirname + search_index_rel_prefix = rel_prefix + search_index_rel_prefix += @asset_rel_path if @file_output + + current = file + asset_rel_prefix = rel_prefix + @asset_rel_path + + @title = "#{file.page_name} - #{@options.title}" + + debug_msg " rendering #{out_file}" + render_template template_file, out_file do |io| + here = binding + # suppress 1.9.3 warning + here.local_variable_set(:current, current) + here.local_variable_set(:asset_rel_prefix, asset_rel_prefix) + here + end + end + + ## + # Generates the 404 page for the RDoc servlet + + def generate_servlet_not_found message + setup + + template_file = @template_dir + 'servlet_not_found.rhtml' + return unless template_file.exist? + + debug_msg "Rendering the servlet 404 Not Found page..." + + rel_prefix = rel_prefix = '' + search_index_rel_prefix = rel_prefix + search_index_rel_prefix += @asset_rel_path if @file_output + + asset_rel_prefix = '' + + @title = 'Not Found' + + render_template template_file do |io| + here = binding + # suppress 1.9.3 warning + here.local_variable_set(:asset_rel_prefix, asset_rel_prefix) + here + end + rescue => e + error = RDoc::Error.new \ + "error generating servlet_not_found: #{e.message} (#{e.class})" + error.set_backtrace e.backtrace + + raise error + end + + ## + # Generates the servlet root page for the RDoc servlet + + def generate_servlet_root installed + setup + + template_file = @template_dir + 'servlet_root.rhtml' + return unless template_file.exist? + + debug_msg 'Rendering the servlet root page...' + + rel_prefix = '.' + asset_rel_prefix = rel_prefix + search_index_rel_prefix = asset_rel_prefix + search_index_rel_prefix += @asset_rel_path if @file_output + + @title = 'Local RDoc Documentation' + + render_template template_file do |io| binding end + rescue => e + error = RDoc::Error.new \ + "error generating servlet_root: #{e.message} (#{e.class})" + error.set_backtrace e.backtrace + + raise error + end + + ## + # Generate an index page which lists all the classes which are documented. + + def generate_table_of_contents + setup + + template_file = @template_dir + 'table_of_contents.rhtml' + return unless template_file.exist? + + debug_msg "Rendering the Table of Contents..." + + out_file = @outputdir + 'table_of_contents.html' + rel_prefix = @outputdir.relative_path_from out_file.dirname + search_index_rel_prefix = rel_prefix + search_index_rel_prefix += @asset_rel_path if @file_output + + asset_rel_prefix = rel_prefix + @asset_rel_path + + @title = "Table of Contents - #{@options.title}" + + render_template template_file, out_file do |io| + here = binding + # suppress 1.9.3 warning + here.local_variable_set(:asset_rel_prefix, asset_rel_prefix) + here + end + rescue => e + error = RDoc::Error.new \ + "error generating table_of_contents.html: #{e.message} (#{e.class})" + error.set_backtrace e.backtrace + + raise error + end + + def install_rdoc_static_file source, destination, options # :nodoc: + return unless source.exist? + + begin + FileUtils.mkdir_p File.dirname(destination), options + + begin + FileUtils.ln source, destination, options + rescue Errno::EEXIST + FileUtils.rm destination + retry + end + rescue + FileUtils.cp source, destination, options + end + end + + ## + # Prepares for generation of output from the current directory + + def setup + return if instance_variable_defined? :@outputdir + + @outputdir = Pathname.new(@options.op_dir).expand_path @base_dir + + return unless @store + + @classes = @store.all_classes_and_modules.sort + @files = @store.all_files.sort + @methods = @classes.map { |m| m.method_list }.flatten.sort + @modsort = get_sorted_module_list @classes + end + + ## + # Return a string describing the amount of time in the given number of + # seconds in terms a human can understand easily. + + def time_delta_string seconds + return 'less than a minute' if seconds < 60 + return "#{seconds / 60} minute#{seconds / 60 == 1 ? '' : 's'}" if + seconds < 3000 # 50 minutes + return 'about one hour' if seconds < 5400 # 90 minutes + return "#{seconds / 3600} hours" if seconds < 64800 # 18 hours + return 'one day' if seconds < 86400 # 1 day + return 'about one day' if seconds < 172800 # 2 days + return "#{seconds / 86400} days" if seconds < 604800 # 1 week + return 'about one week' if seconds < 1209600 # 2 week + return "#{seconds / 604800} weeks" if seconds < 7257600 # 3 months + return "#{seconds / 2419200} months" if seconds < 31536000 # 1 year + return "#{seconds / 31536000} years" + end + + # %q$Id: darkfish.rb 52 2009-01-07 02:08:11Z deveiant $" + SVNID_PATTERN = / + \$Id:\s + (\S+)\s # filename + (\d+)\s # rev + (\d{4}-\d{2}-\d{2})\s # Date (YYYY-MM-DD) + (\d{2}:\d{2}:\d{2}Z)\s # Time (HH:MM:SSZ) + (\w+)\s # committer + \$$ + /x + + ## + # Try to extract Subversion information out of the first constant whose + # value looks like a subversion Id tag. If no matching constant is found, + # and empty hash is returned. + + def get_svninfo klass + constants = klass.constants or return {} + + constants.find { |c| c.value =~ SVNID_PATTERN } or return {} + + filename, rev, date, time, committer = $~.captures + commitdate = Time.parse "#{date} #{time}" + + return { + :filename => filename, + :rev => Integer(rev), + :commitdate => commitdate, + :commitdelta => time_delta_string(Time.now - commitdate), + :committer => committer, + } + end + + ## + # Creates a template from its components and the +body_file+. + # + # For backwards compatibility, if +body_file+ contains "<html" the body is + # used directly. + + def assemble_template body_file + body = body_file.read + return body if body =~ /<html/ + + head_file = @template_dir + '_head.rhtml' + footer_file = @template_dir + '_footer.rhtml' + + <<-TEMPLATE +<!DOCTYPE html> + +<html> +<head> +#{head_file.read} + +#{body} + +#{footer_file.read} + TEMPLATE + end + + ## + # Renders the ERb contained in +file_name+ relative to the template + # directory and returns the result based on the current context. + + def render file_name + template_file = @template_dir + file_name + + template = template_for template_file, false, RDoc::ERBPartial + + template.filename = template_file.to_s + + template.result @context + end + + ## + # Load and render the erb template in the given +template_file+ and write + # it out to +out_file+. + # + # Both +template_file+ and +out_file+ should be Pathname-like objects. + # + # An io will be yielded which must be captured by binding in the caller. + + def render_template template_file, out_file = nil # :yield: io + io_output = out_file && !@dry_run && @file_output + erb_klass = io_output ? RDoc::ERBIO : ERB + + template = template_for template_file, true, erb_klass + + if io_output then + debug_msg "Outputting to %s" % [out_file.expand_path] + + out_file.dirname.mkpath + out_file.open 'w', 0644 do |io| + io.set_encoding @options.encoding + + @context = yield io + + template_result template, @context, template_file + end + else + @context = yield nil + + output = template_result template, @context, template_file + + debug_msg " would have written %d characters to %s" % [ + output.length, out_file.expand_path + ] if @dry_run + + output + end + end + + ## + # Creates the result for +template+ with +context+. If an error is raised a + # Pathname +template_file+ will indicate the file where the error occurred. + + def template_result template, context, template_file + template.filename = template_file.to_s + template.result context + rescue NoMethodError => e + raise RDoc::Error, "Error while evaluating %s: %s" % [ + template_file.expand_path, + e.message, + ], e.backtrace + end + + ## + # Retrieves a cache template for +file+, if present, or fills the cache. + + def template_for file, page = true, klass = ERB + template = @template_cache[file] + + return template if template + + if page then + template = assemble_template file + erbout = 'io' + else + template = file.read + template = template.encode @options.encoding + + file_var = File.basename(file).sub(/\..*/, '') + + erbout = "_erbout_#{file_var}" + end + + template = klass.new template, nil, '<>', erbout + @template_cache[file] = template + template + end + +end diff --git a/lib/rdoc/generator/json_index.rb b/lib/rdoc/generator/json_index.rb new file mode 100644 index 0000000000..e4cfe967c6 --- /dev/null +++ b/lib/rdoc/generator/json_index.rb @@ -0,0 +1,297 @@ +# frozen_string_literal: true +require 'json' +begin + require 'zlib' +rescue LoadError +end + +## +# The JsonIndex generator is designed to complement an HTML generator and +# produces a JSON search index. This generator is derived from sdoc by +# Vladimir Kolesnikov and contains verbatim code written by him. +# +# This generator is designed to be used with a regular HTML generator: +# +# class RDoc::Generator::Darkfish +# def initialize options +# # ... +# @base_dir = Pathname.pwd.expand_path +# +# @json_index = RDoc::Generator::JsonIndex.new self, options +# end +# +# def generate +# # ... +# @json_index.generate +# end +# end +# +# == Index Format +# +# The index is output as a JSON file assigned to the global variable +# +search_data+. The structure is: +# +# var search_data = { +# "index": { +# "searchIndex": +# ["a", "b", ...], +# "longSearchIndex": +# ["a", "a::b", ...], +# "info": [ +# ["A", "A", "A.html", "", ""], +# ["B", "A::B", "A::B.html", "", ""], +# ... +# ] +# } +# } +# +# The same item is described across the +searchIndex+, +longSearchIndex+ and +# +info+ fields. The +searchIndex+ field contains the item's short name, the +# +longSearchIndex+ field contains the full_name (when appropriate) and the +# +info+ field contains the item's name, full_name, path, parameters and a +# snippet of the item's comment. +# +# == LICENSE +# +# Copyright (c) 2009 Vladimir Kolesnikov +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +class RDoc::Generator::JsonIndex + + include RDoc::Text + + ## + # Where the search index lives in the generated output + + SEARCH_INDEX_FILE = File.join 'js', 'search_index.js' + + attr_reader :index # :nodoc: + + ## + # Creates a new generator. +parent_generator+ is used to determine the + # class_dir and file_dir of links in the output index. + # + # +options+ are the same options passed to the parent generator. + + def initialize parent_generator, options + @parent_generator = parent_generator + @store = parent_generator.store + @options = options + + @template_dir = File.expand_path '../template/json_index', __FILE__ + @base_dir = @parent_generator.base_dir + + @classes = nil + @files = nil + @index = nil + end + + ## + # Builds the JSON index as a Hash. + + def build_index + reset @store.all_files.sort, @store.all_classes_and_modules.sort + + index_classes + index_methods + index_pages + + { :index => @index } + end + + ## + # Output progress information if debugging is enabled + + def debug_msg *msg + return unless $DEBUG_RDOC + $stderr.puts(*msg) + end + + ## + # Writes the JSON index to disk + + def generate + debug_msg "Generating JSON index" + + debug_msg " writing search index to %s" % SEARCH_INDEX_FILE + data = build_index + + return if @options.dry_run + + out_dir = @base_dir + @options.op_dir + index_file = out_dir + SEARCH_INDEX_FILE + + FileUtils.mkdir_p index_file.dirname, :verbose => $DEBUG_RDOC + + index_file.open 'w', 0644 do |io| + io.set_encoding Encoding::UTF_8 + io.write 'var search_data = ' + + JSON.dump data, io, 0 + end + + Dir.chdir @template_dir do + Dir['**/*.js'].each do |source| + dest = File.join out_dir, source + + FileUtils.install source, dest, :mode => 0644, :verbose => $DEBUG_RDOC + end + end + end + + ## + # Compress the search_index.js file using gzip + + def generate_gzipped + return if @options.dry_run or not defined?(Zlib) + + debug_msg "Compressing generated JSON index" + out_dir = @base_dir + @options.op_dir + + search_index_file = out_dir + SEARCH_INDEX_FILE + outfile = out_dir + "#{search_index_file}.gz" + + debug_msg "Reading the JSON index file from %s" % search_index_file + search_index = search_index_file.read(mode: 'r:utf-8') + + debug_msg "Writing gzipped search index to %s" % outfile + + Zlib::GzipWriter.open(outfile) do |gz| + gz.mtime = File.mtime(search_index_file) + gz.orig_name = search_index_file.basename.to_s + gz.write search_index + gz.close + end + + # GZip the rest of the js files + Dir.chdir @template_dir do + Dir['**/*.js'].each do |source| + dest = out_dir + source + outfile = out_dir + "#{dest}.gz" + + debug_msg "Reading the original js file from %s" % dest + data = dest.read + + debug_msg "Writing gzipped file to %s" % outfile + + Zlib::GzipWriter.open(outfile) do |gz| + gz.mtime = File.mtime(dest) + gz.orig_name = dest.basename.to_s + gz.write data + gz.close + end + end + end + end + + ## + # Adds classes and modules to the index + + def index_classes + debug_msg " generating class search index" + + documented = @classes.uniq.select do |klass| + klass.document_self_or_methods + end + + documented.each do |klass| + debug_msg " #{klass.full_name}" + record = klass.search_record + @index[:searchIndex] << search_string(record.shift) + @index[:longSearchIndex] << search_string(record.shift) + @index[:info] << record + end + end + + ## + # Adds methods to the index + + def index_methods + debug_msg " generating method search index" + + list = @classes.uniq.map do |klass| + klass.method_list + end.flatten.sort_by do |method| + [method.name, method.parent.full_name] + end + + list.each do |method| + debug_msg " #{method.full_name}" + record = method.search_record + @index[:searchIndex] << "#{search_string record.shift}()" + @index[:longSearchIndex] << "#{search_string record.shift}()" + @index[:info] << record + end + end + + ## + # Adds pages to the index + + def index_pages + debug_msg " generating pages search index" + + pages = @files.select do |file| + file.text? + end + + pages.each do |page| + debug_msg " #{page.page_name}" + record = page.search_record + @index[:searchIndex] << search_string(record.shift) + @index[:longSearchIndex] << '' + record.shift + @index[:info] << record + end + end + + ## + # The directory classes are written to + + def class_dir + @parent_generator.class_dir + end + + ## + # The directory files are written to + + def file_dir + @parent_generator.file_dir + end + + def reset files, classes # :nodoc: + @files = files + @classes = classes + + @index = { + :searchIndex => [], + :longSearchIndex => [], + :info => [] + } + end + + ## + # Removes whitespace and downcases +string+ + + def search_string string + string.downcase.gsub(/\s/, '') + end + +end diff --git a/lib/rdoc/generator/markup.rb b/lib/rdoc/generator/markup.rb new file mode 100644 index 0000000000..fef982d378 --- /dev/null +++ b/lib/rdoc/generator/markup.rb @@ -0,0 +1,170 @@ +# frozen_string_literal: true +## +# Handle common RDoc::Markup tasks for various CodeObjects +# +# This module is loaded by generators. It allows RDoc's CodeObject tree to +# avoid loading generator code to improve startup time for +ri+. + +module RDoc::Generator::Markup + + ## + # Generates a relative URL from this object's path to +target_path+ + + def aref_to(target_path) + RDoc::Markup::ToHtml.gen_relative_url path, target_path + end + + ## + # Generates a relative URL from +from_path+ to this object's path + + def as_href(from_path) + RDoc::Markup::ToHtml.gen_relative_url from_path, path + end + + ## + # Handy wrapper for marking up this object's comment + + def description + markup @comment + end + + ## + # Creates an RDoc::Markup::ToHtmlCrossref formatter + + def formatter + return @formatter if defined? @formatter + + options = @store.rdoc.options + this = RDoc::Context === self ? self : @parent + + @formatter = RDoc::Markup::ToHtmlCrossref.new options, this.path, this + @formatter.code_object = self + @formatter + end + + ## + # Build a webcvs URL starting for the given +url+ with +full_path+ appended + # as the destination path. If +url+ contains '%s' +full_path+ will be + # will replace the %s using sprintf on the +url+. + + def cvs_url(url, full_path) + if /%s/ =~ url then + sprintf url, full_path + else + url + full_path + end + end + +end + +class RDoc::CodeObject + + include RDoc::Generator::Markup + +end + +class RDoc::MethodAttr + + @add_line_numbers = false + + class << self + ## + # Allows controlling whether <tt>#markup_code</tt> adds line numbers to + # the source code. + + attr_accessor :add_line_numbers + end + + ## + # Prepend +src+ with line numbers. Relies on the first line of a source + # code listing having: + # + # # File xxxxx, line dddd + # + # If it has this comment then line numbers are added to +src+ and the <tt>, + # line dddd</tt> portion of the comment is removed. + + def add_line_numbers(src) + return unless src.sub!(/\A(.*)(, line (\d+))/, '\1') + first = $3.to_i - 1 + last = first + src.count("\n") + size = last.to_s.length + + line = first + src.gsub!(/^/) do + res = if line == first then + " " * (size + 1) + else + "<span class=\"line-num\">%2$*1$d</span> " % [size, line] + end + + line += 1 + res + end + end + + ## + # Turns the method's token stream into HTML. + # + # Prepends line numbers if +add_line_numbers+ is true. + + def markup_code + return '' unless @token_stream + + src = RDoc::TokenStream.to_html @token_stream + + # dedent the source + indent = src.length + lines = src.lines.to_a + lines.shift if src =~ /\A.*#\ *File/i # remove '# File' comment + lines.each do |line| + if line =~ /^ *(?=\S)/ + n = $&.length + indent = n if n < indent + break if n == 0 + end + end + src.gsub!(/^#{' ' * indent}/, '') if indent > 0 + + add_line_numbers(src) if RDoc::MethodAttr.add_line_numbers + + src + end + +end + +class RDoc::ClassModule + + ## + # Handy wrapper for marking up this class or module's comment + + def description + markup @comment_location + end + +end + +class RDoc::Context::Section + + include RDoc::Generator::Markup + +end + +class RDoc::TopLevel + + ## + # Returns a URL for this source file on some web repository. Use the -W + # command line option to set. + + def cvs_url + url = @store.rdoc.options.webcvs + + if /%s/ =~ url then + url % @relative_name + else + url + @relative_name + end + end + +end + diff --git a/lib/rdoc/generator/pot.rb b/lib/rdoc/generator/pot.rb new file mode 100644 index 0000000000..8a1e0b4bd0 --- /dev/null +++ b/lib/rdoc/generator/pot.rb @@ -0,0 +1,98 @@ +# frozen_string_literal: true +## +# Generates a POT file. +# +# Here is a translator work flow with the generator. +# +# == Create .pot +# +# You create .pot file by pot formatter: +# +# % rdoc --format pot +# +# It generates doc/rdoc.pot. +# +# == Create .po +# +# You create .po file from doc/rdoc.pot. This operation is needed only +# the first time. This work flow assumes that you are a translator +# for Japanese. +# +# You create locale/ja/rdoc.po from doc/rdoc.pot. You can use msginit +# provided by GNU gettext or rmsginit provided by gettext gem. This +# work flow uses gettext gem because it is more portable than GNU +# gettext for Rubyists. Gettext gem is implemented by pure Ruby. +# +# % gem install gettext +# % mkdir -p locale/ja +# % rmsginit --input doc/rdoc.pot --output locale/ja/rdoc.po --locale ja +# +# Translate messages in .po +# +# You translate messages in .po by a PO file editor. po-mode.el exists +# for Emacs users. There are some GUI tools such as GTranslator. +# There are some Web services such as POEditor and Tansifex. You can +# edit by your favorite text editor because .po is a text file. +# Generate localized documentation +# +# You can generate localized documentation with locale/ja/rdoc.po: +# +# % rdoc --locale ja +# +# You can find documentation in Japanese in doc/. Yay! +# +# == Update translation +# +# You need to update translation when your application is added or +# modified messages. +# +# You can update .po by the following command lines: +# +# % rdoc --format pot +# % rmsgmerge --update locale/ja/rdoc.po doc/rdoc.pot +# +# You edit locale/ja/rdoc.po to translate new messages. + +class RDoc::Generator::POT + + RDoc::RDoc.add_generator self + + ## + # Description of this generator + + DESCRIPTION = 'creates .pot file' + + ## + # Set up a new .pot generator + + def initialize store, options #:not-new: + @options = options + @store = store + end + + ## + # Writes .pot to disk. + + def generate + po = extract_messages + pot_path = 'rdoc.pot' + File.open(pot_path, "w") do |pot| + pot.print(po.to_s) + end + end + + def class_dir + nil + end + + private + def extract_messages + extractor = MessageExtractor.new(@store) + extractor.extract + end + + autoload :MessageExtractor, 'rdoc/generator/pot/message_extractor' + autoload :PO, 'rdoc/generator/pot/po' + autoload :POEntry, 'rdoc/generator/pot/po_entry' + +end diff --git a/lib/rdoc/generator/pot/message_extractor.rb b/lib/rdoc/generator/pot/message_extractor.rb new file mode 100644 index 0000000000..313dfd2dc7 --- /dev/null +++ b/lib/rdoc/generator/pot/message_extractor.rb @@ -0,0 +1,68 @@ +# frozen_string_literal: true +## +# Extracts message from RDoc::Store + +class RDoc::Generator::POT::MessageExtractor + + ## + # Creates a message extractor for +store+. + + def initialize store + @store = store + @po = RDoc::Generator::POT::PO.new + end + + ## + # Extracts messages from +store+, stores them into + # RDoc::Generator::POT::PO and returns it. + + def extract + @store.all_classes_and_modules.each do |klass| + extract_from_klass(klass) + end + @po + end + + private + + def extract_from_klass klass + extract_text(klass.comment_location, klass.full_name) + + klass.each_section do |section, constants, attributes| + extract_text(section.title ,"#{klass.full_name}: section title") + section.comments.each do |comment| + extract_text(comment, "#{klass.full_name}: #{section.title}") + end + end + + klass.each_constant do |constant| + extract_text(constant.comment, constant.full_name) + end + + klass.each_attribute do |attribute| + extract_text(attribute.comment, attribute.full_name) + end + + klass.each_method do |method| + extract_text(method.comment, method.full_name) + end + end + + def extract_text text, comment, location = nil + return if text.nil? + + options = { + :extracted_comment => comment, + :references => [location].compact, + } + i18n_text = RDoc::I18n::Text.new(text) + i18n_text.extract_messages do |part| + @po.add(entry(part[:paragraph], options)) + end + end + + def entry msgid, options + RDoc::Generator::POT::POEntry.new(msgid, options) + end + +end diff --git a/lib/rdoc/generator/pot/po.rb b/lib/rdoc/generator/pot/po.rb new file mode 100644 index 0000000000..37d45e5258 --- /dev/null +++ b/lib/rdoc/generator/pot/po.rb @@ -0,0 +1,84 @@ +# frozen_string_literal: true +## +# Generates a PO format text + +class RDoc::Generator::POT::PO + + ## + # Creates an object that represents PO format. + + def initialize + @entries = {} + add_header + end + + ## + # Adds a PO entry to the PO. + + def add entry + existing_entry = @entries[entry.msgid] + if existing_entry + entry = existing_entry.merge(entry) + end + @entries[entry.msgid] = entry + end + + ## + # Returns PO format text for the PO. + + def to_s + po = '' + sort_entries.each do |entry| + po += "\n" unless po.empty? + po += entry.to_s + end + po + end + + private + + def add_header + add(header_entry) + end + + def header_entry + comment = <<-COMMENT +SOME DESCRIPTIVE TITLE. +Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +This file is distributed under the same license as the PACKAGE package. +FIRST AUTHOR <EMAIL@ADDRESS>, YEAR. + COMMENT + + content = <<-CONTENT +Project-Id-Version: PACKAGE VERSEION +Report-Msgid-Bugs-To: +PO-Revision-Date: YEAR-MO_DA HO:MI+ZONE +Last-Translator: FULL NAME <EMAIL@ADDRESS> +Language-Team: LANGUAGE <LL@li.org> +Language: +MIME-Version: 1.0 +Content-Type: text/plain; charset=CHARSET +Content-Transfer-Encoding: 8bit +Plural-Forms: nplurals=INTEGER; plural=EXPRESSION; + CONTENT + + options = { + :msgstr => content, + :translator_comment => comment, + :flags => ['fuzzy'], + } + RDoc::Generator::POT::POEntry.new('', options) + end + + def sort_entries + headers, messages = @entries.values.partition do |entry| + entry.msgid.empty? + end + # TODO: sort by location + sorted_messages = messages.sort_by do |entry| + entry.msgid + end + headers + sorted_messages + end + +end diff --git a/lib/rdoc/generator/pot/po_entry.rb b/lib/rdoc/generator/pot/po_entry.rb new file mode 100644 index 0000000000..3c278826f4 --- /dev/null +++ b/lib/rdoc/generator/pot/po_entry.rb @@ -0,0 +1,141 @@ +# frozen_string_literal: true +## +# A PO entry in PO + +class RDoc::Generator::POT::POEntry + + # The msgid content + attr_reader :msgid + + # The msgstr content + attr_reader :msgstr + + # The comment content created by translator (PO editor) + attr_reader :translator_comment + + # The comment content extracted from source file + attr_reader :extracted_comment + + # The locations where the PO entry is extracted + attr_reader :references + + # The flags of the PO entry + attr_reader :flags + + ## + # Creates a PO entry for +msgid+. Other valus can be specified by + # +options+. + + def initialize msgid, options = {} + @msgid = msgid + @msgstr = options[:msgstr] || "" + @translator_comment = options[:translator_comment] + @extracted_comment = options[:extracted_comment] + @references = options[:references] || [] + @flags = options[:flags] || [] + end + + ## + # Returns the PO entry in PO format. + + def to_s + entry = '' + entry += format_translator_comment + entry += format_extracted_comment + entry += format_references + entry += format_flags + entry += <<-ENTRY +msgid #{format_message(@msgid)} +msgstr #{format_message(@msgstr)} + ENTRY + end + + ## + # Merges the PO entry with +other_entry+. + + def merge other_entry + options = { + :extracted_comment => merge_string(@extracted_comment, + other_entry.extracted_comment), + :translator_comment => merge_string(@translator_comment, + other_entry.translator_comment), + :references => merge_array(@references, + other_entry.references), + :flags => merge_array(@flags, + other_entry.flags), + } + self.class.new(@msgid, options) + end + + private + + def format_comment mark, comment + return '' unless comment + return '' if comment.empty? + + formatted_comment = '' + comment.each_line do |line| + formatted_comment += "#{mark} #{line}" + end + formatted_comment += "\n" unless formatted_comment.end_with?("\n") + formatted_comment + end + + def format_translator_comment + format_comment('#', @translator_comment) + end + + def format_extracted_comment + format_comment('#.', @extracted_comment) + end + + def format_references + return '' if @references.empty? + + formatted_references = '' + @references.sort.each do |file, line| + formatted_references += "\#: #{file}:#{line}\n" + end + formatted_references + end + + def format_flags + return '' if @flags.empty? + + formatted_flags = flags.join(",") + "\#, #{formatted_flags}\n" + end + + def format_message message + return "\"#{escape(message)}\"" unless message.include?("\n") + + formatted_message = '""' + message.each_line do |line| + formatted_message += "\n" + formatted_message += "\"#{escape(line)}\"" + end + formatted_message + end + + def escape string + string.gsub(/["\\\t\n]/) do |special_character| + case special_character + when "\t" + "\\t" + when "\n" + "\\n" + else + "\\#{special_character}" + end + end + end + + def merge_string string1, string2 + [string1, string2].compact.join("\n") + end + + def merge_array array1, array2 + (array1 + array2).uniq + end + +end diff --git a/lib/rdoc/generator/ri.rb b/lib/rdoc/generator/ri.rb new file mode 100644 index 0000000000..0eef1d03f5 --- /dev/null +++ b/lib/rdoc/generator/ri.rb @@ -0,0 +1,31 @@ +# frozen_string_literal: true +## +# Generates ri data files + +class RDoc::Generator::RI + + RDoc::RDoc.add_generator self + + ## + # Description of this generator + + DESCRIPTION = 'creates ri data files' + + ## + # Set up a new ri generator + + def initialize store, options #:not-new: + @options = options + @store = store + @store.path = '.' + end + + ## + # Writes the parsed data store to disk for use by ri. + + def generate + @store.save + end + +end + diff --git a/lib/rdoc/generator/template/darkfish/.document b/lib/rdoc/generator/template/darkfish/.document new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/lib/rdoc/generator/template/darkfish/.document diff --git a/lib/rdoc/generator/template/darkfish/_footer.rhtml b/lib/rdoc/generator/template/darkfish/_footer.rhtml new file mode 100644 index 0000000000..9791b42901 --- /dev/null +++ b/lib/rdoc/generator/template/darkfish/_footer.rhtml @@ -0,0 +1,5 @@ +<footer id="validator-badges" role="contentinfo"> + <p><a href="https://validator.w3.org/check/referer">Validate</a> + <p>Generated by <a href="https://ruby.github.io/rdoc/">RDoc</a> <%= RDoc::VERSION %>. + <p>Based on <a href="http://deveiate.org/projects/Darkfish-RDoc/">Darkfish</a> by <a href="http://deveiate.org">Michael Granger</a>. +</footer> diff --git a/lib/rdoc/generator/template/darkfish/_head.rhtml b/lib/rdoc/generator/template/darkfish/_head.rhtml new file mode 100644 index 0000000000..8304310d4b --- /dev/null +++ b/lib/rdoc/generator/template/darkfish/_head.rhtml @@ -0,0 +1,23 @@ +<meta charset="<%= @options.charset %>"> + +<title><%= h @title %></title> + +<script type="text/javascript"> + var rdoc_rel_prefix = "<%= asset_rel_prefix %>/"; + var index_rel_prefix = "<%= rel_prefix %>/"; +</script> + +<script src="<%= asset_rel_prefix %>/js/navigation.js" defer></script> +<script src="<%= asset_rel_prefix %>/js/search.js" defer></script> +<script src="<%= asset_rel_prefix %>/js/search_index.js" defer></script> +<script src="<%= asset_rel_prefix %>/js/searcher.js" defer></script> +<script src="<%= asset_rel_prefix %>/js/darkfish.js" defer></script> + +<link href="<%= asset_rel_prefix %>/css/fonts.css" rel="stylesheet"> +<link href="<%= asset_rel_prefix %>/css/rdoc.css" rel="stylesheet"> +<% if @options.template_stylesheets.flatten.any? then %> +<% @options.template_stylesheets.flatten.each do |stylesheet| %> +<link href="<%= asset_rel_prefix %>/<%= File.basename stylesheet %>" rel="stylesheet"> +<% end %> +<% end %> + diff --git a/lib/rdoc/generator/template/darkfish/_sidebar_VCS_info.rhtml b/lib/rdoc/generator/template/darkfish/_sidebar_VCS_info.rhtml new file mode 100644 index 0000000000..e889f8063d --- /dev/null +++ b/lib/rdoc/generator/template/darkfish/_sidebar_VCS_info.rhtml @@ -0,0 +1,19 @@ +<% if !svninfo.empty? then %> +<div id="file-svninfo-section" class="nav-section"> + <h3>VCS Info</h3> + + <div class="section-body"> + <dl class="svninfo"> + <dt>Rev + <dd><%= svninfo[:rev] %> + + <dt>Last Checked In + <dd><%= svninfo[:commitdate].strftime('%Y-%m-%d %H:%M:%S') %> + (<%= svninfo[:commitdelta] %> ago) + + <dt>Checked in by + <dd><%= svninfo[:committer] %> + </dl> + </div> +</div> +<% end %> diff --git a/lib/rdoc/generator/template/darkfish/_sidebar_classes.rhtml b/lib/rdoc/generator/template/darkfish/_sidebar_classes.rhtml new file mode 100644 index 0000000000..fe54d8339f --- /dev/null +++ b/lib/rdoc/generator/template/darkfish/_sidebar_classes.rhtml @@ -0,0 +1,9 @@ +<div id="classindex-section" class="nav-section"> + <h3>Class and Module Index</h3> + + <ul class="link-list"> + <% @modsort.each do |index_klass| %> + <li><a href="<%= rel_prefix %>/<%= index_klass.path %>"><%= index_klass.full_name %></a> + <% end %> + </ul> +</div> diff --git a/lib/rdoc/generator/template/darkfish/_sidebar_extends.rhtml b/lib/rdoc/generator/template/darkfish/_sidebar_extends.rhtml new file mode 100644 index 0000000000..2bd8efee99 --- /dev/null +++ b/lib/rdoc/generator/template/darkfish/_sidebar_extends.rhtml @@ -0,0 +1,15 @@ +<% unless klass.extends.empty? then %> +<div id="extends-section" class="nav-section"> + <h3>Extended With Modules</h3> + + <ul class="link-list"> + <% klass.each_extend do |ext| %> + <% unless String === ext.module then %> + <li><a class="extend" href="<%= klass.aref_to ext.module.path %>"><%= ext.module.full_name %></a> + <% else %> + <li><span class="extend"><%= ext.name %></span> + <% end %> + <% end %> + </ul> +</div> +<% end %> diff --git a/lib/rdoc/generator/template/darkfish/_sidebar_in_files.rhtml b/lib/rdoc/generator/template/darkfish/_sidebar_in_files.rhtml new file mode 100644 index 0000000000..0ba1d2be80 --- /dev/null +++ b/lib/rdoc/generator/template/darkfish/_sidebar_in_files.rhtml @@ -0,0 +1,9 @@ +<div id="file-list-section" class="nav-section"> + <h3>Defined In</h3> + + <ul> +<% klass.in_files.each do |tl| %> + <li><%= h tl.relative_name %> +<% end %> + </ul> +</div> diff --git a/lib/rdoc/generator/template/darkfish/_sidebar_includes.rhtml b/lib/rdoc/generator/template/darkfish/_sidebar_includes.rhtml new file mode 100644 index 0000000000..d141098ecd --- /dev/null +++ b/lib/rdoc/generator/template/darkfish/_sidebar_includes.rhtml @@ -0,0 +1,15 @@ +<% unless klass.includes.empty? then %> +<div id="includes-section" class="nav-section"> + <h3>Included Modules</h3> + + <ul class="link-list"> + <% klass.each_include do |inc| %> + <% unless String === inc.module then %> + <li><a class="include" href="<%= klass.aref_to inc.module.path %>"><%= inc.module.full_name %></a> + <% else %> + <li><span class="include"><%= inc.name %></span> + <% end %> + <% end %> + </ul> +</div> +<% end %> diff --git a/lib/rdoc/generator/template/darkfish/_sidebar_installed.rhtml b/lib/rdoc/generator/template/darkfish/_sidebar_installed.rhtml new file mode 100644 index 0000000000..1285bfd732 --- /dev/null +++ b/lib/rdoc/generator/template/darkfish/_sidebar_installed.rhtml @@ -0,0 +1,15 @@ +<div id="home-section" class="nav-section"> + <h3>Documentation</h3> + + <ul> + <% installed.each do |name, href, exists, type, _| %> + <% next if type == :extra %> + <li class="folder"> + <% if exists then %> + <a href="<%= href %>"><%= h name %></a> + <% else %> + <%= h name %> + <% end %> + <% end %> + </ul> +</div> diff --git a/lib/rdoc/generator/template/darkfish/_sidebar_methods.rhtml b/lib/rdoc/generator/template/darkfish/_sidebar_methods.rhtml new file mode 100644 index 0000000000..45df08d8fe --- /dev/null +++ b/lib/rdoc/generator/template/darkfish/_sidebar_methods.rhtml @@ -0,0 +1,12 @@ +<% unless klass.method_list.empty? then %> +<!-- Method Quickref --> +<div id="method-list-section" class="nav-section"> + <h3>Methods</h3> + + <ul class="link-list" role="directory"> + <% klass.each_method do |meth| %> + <li <% if meth.calls_super %>class="calls-super" <% end %>><a href="#<%= meth.aref %>"><%= meth.singleton ? '::' : '#' %><%= h meth.name %></a> + <% end %> + </ul> +</div> +<% end %> diff --git a/lib/rdoc/generator/template/darkfish/_sidebar_navigation.rhtml b/lib/rdoc/generator/template/darkfish/_sidebar_navigation.rhtml new file mode 100644 index 0000000000..d7f330840a --- /dev/null +++ b/lib/rdoc/generator/template/darkfish/_sidebar_navigation.rhtml @@ -0,0 +1,11 @@ +<div id="home-section" role="region" title="Quick navigation" class="nav-section"> + <h2> + <a href="<%= rel_prefix %>/index.html" rel="home">Home</a> + </h2> + + <div id="table-of-contents-navigation"> + <a href="<%= rel_prefix %>/table_of_contents.html#pages">Pages</a> + <a href="<%= rel_prefix %>/table_of_contents.html#classes">Classes</a> + <a href="<%= rel_prefix %>/table_of_contents.html#methods">Methods</a> + </div> +</div> diff --git a/lib/rdoc/generator/template/darkfish/_sidebar_pages.rhtml b/lib/rdoc/generator/template/darkfish/_sidebar_pages.rhtml new file mode 100644 index 0000000000..5f39825f08 --- /dev/null +++ b/lib/rdoc/generator/template/darkfish/_sidebar_pages.rhtml @@ -0,0 +1,12 @@ +<% simple_files = @files.select { |f| f.text? } %> +<% unless simple_files.empty? then %> +<div id="fileindex-section" class="nav-section"> + <h3>Pages</h3> + + <ul class="link-list"> + <% simple_files.each do |f| %> + <li><a href="<%= rel_prefix %>/<%= f.path %>"><%= h f.page_name %></a> + <% end %> + </ul> +</div> +<% end %> diff --git a/lib/rdoc/generator/template/darkfish/_sidebar_parent.rhtml b/lib/rdoc/generator/template/darkfish/_sidebar_parent.rhtml new file mode 100644 index 0000000000..cc04852652 --- /dev/null +++ b/lib/rdoc/generator/template/darkfish/_sidebar_parent.rhtml @@ -0,0 +1,11 @@ +<% if klass.type == 'class' then %> +<div id="parent-class-section" class="nav-section"> + <h3>Parent</h3> + + <% if klass.superclass and not String === klass.superclass then %> + <p class="link"><a href="<%= klass.aref_to klass.superclass.path %>"><%= klass.superclass.full_name %></a> + <% else %> + <p class="link"><%= klass.superclass %> + <% end %> +</div> +<% end %> diff --git a/lib/rdoc/generator/template/darkfish/_sidebar_search.rhtml b/lib/rdoc/generator/template/darkfish/_sidebar_search.rhtml new file mode 100644 index 0000000000..9c49b31376 --- /dev/null +++ b/lib/rdoc/generator/template/darkfish/_sidebar_search.rhtml @@ -0,0 +1,14 @@ +<div id="search-section" role="search" class="project-section initially-hidden"> + <form action="#" method="get" accept-charset="utf-8"> + <div id="search-field-wrapper"> + <input id="search-field" role="combobox" aria-label="Search" + aria-autocomplete="list" aria-controls="search-results" + type="text" name="search" placeholder="Search" spellcheck="false" + title="Type to search, Up and Down to navigate, Enter to load"> + </div> + + <ul id="search-results" aria-label="Search Results" + aria-busy="false" aria-expanded="false" + aria-atomic="false" class="initially-hidden"></ul> + </form> +</div> diff --git a/lib/rdoc/generator/template/darkfish/_sidebar_sections.rhtml b/lib/rdoc/generator/template/darkfish/_sidebar_sections.rhtml new file mode 100644 index 0000000000..15ff78ba91 --- /dev/null +++ b/lib/rdoc/generator/template/darkfish/_sidebar_sections.rhtml @@ -0,0 +1,11 @@ +<% unless klass.sections.length == 1 then %> +<div id="sections-section" class="nav-section"> + <h3>Sections</h3> + + <ul class="link-list" role="directory"> + <% klass.sort_sections.each do |section| %> + <li><a href="#<%= section.aref %>"><%= h section.title %></a></li> + <% end %> + </ul> +</div> +<% end %> diff --git a/lib/rdoc/generator/template/darkfish/_sidebar_table_of_contents.rhtml b/lib/rdoc/generator/template/darkfish/_sidebar_table_of_contents.rhtml new file mode 100644 index 0000000000..b58e6b3c61 --- /dev/null +++ b/lib/rdoc/generator/template/darkfish/_sidebar_table_of_contents.rhtml @@ -0,0 +1,18 @@ +<% comment = if current.respond_to? :comment_location then + current.comment_location + else + current.comment + end + table = current.parse(comment).table_of_contents + + if table.length > 1 then %> +<div class="nav-section"> + <h3>Table of Contents</h3> + + <ul class="link-list" role="directory"> +<% table.each do |heading| %> + <li><a href="#<%= heading.label current %>"><%= heading.plain_html %></a> +<% end %> + </ul> +</div> +<% end %> diff --git a/lib/rdoc/generator/template/darkfish/class.rhtml b/lib/rdoc/generator/template/darkfish/class.rhtml new file mode 100644 index 0000000000..7733095086 --- /dev/null +++ b/lib/rdoc/generator/template/darkfish/class.rhtml @@ -0,0 +1,172 @@ +<body id="top" role="document" class="<%= klass.type %>"> +<nav role="navigation"> + <div id="project-navigation"> + <%= render '_sidebar_navigation.rhtml' %> + <%= render '_sidebar_search.rhtml' %> + </div> + + <%= render '_sidebar_table_of_contents.rhtml' %> + + <div id="class-metadata"> + <%= render '_sidebar_sections.rhtml' %> + <%= render '_sidebar_parent.rhtml' %> + <%= render '_sidebar_includes.rhtml' %> + <%= render '_sidebar_extends.rhtml' %> + <%= render '_sidebar_methods.rhtml' %> + </div> +</nav> + +<main role="main" aria-labelledby="<%=h klass.aref %>"> + <h1 id="<%=h klass.aref %>" class="<%= klass.type %>"> + <%= klass.type %> <%= klass.full_name %> + </h1> + + <section class="description"> + <%= klass.description %> + </section> + + <% klass.each_section do |section, constants, attributes| %> + <section id="<%= section.aref %>" class="documentation-section"> + <% if section.title then %> + <header class="documentation-section-title"> + <h2> + <%= section.title %> + </h2> + <span class="section-click-top"> + <a href="#top">↑ top</a> + </span> + </header> + <% end %> + + <% if section.comment then %> + <div> + <%= section.description %> + </div> + <% end %> + + <% unless constants.empty? then %> + <section class="constants-list"> + <header> + <h3>Constants</h3> + </header> + <dl> + <% constants.each do |const| %> + <dt id="<%= const.name %>"><%= const.name %> + <% if const.comment then %> + <dd><%= const.description.strip %> + <% else %> + <dd class="missing-docs">(Not documented) + <% end %> + <% end %> + </dl> + </section> + <% end %> + + <% unless attributes.empty? then %> + <section class="attribute-method-details" class="method-section"> + <header> + <h3>Attributes</h3> + </header> + + <% attributes.each do |attrib| %> + <div id="<%= attrib.aref %>" class="method-detail"> + <div class="method-heading attribute-method-heading"> + <span class="method-name"><%= h attrib.name %></span><span + class="attribute-access-type">[<%= attrib.rw %>]</span> + </div> + + <div class="method-description"> + <% if attrib.comment then %> + <%= attrib.description.strip %> + <% else %> + <p class="missing-docs">(Not documented) + <% end %> + </div> + </div> + <% end %> + </section> + <% end %> + + <% klass.methods_by_type(section).each do |type, visibilities| + next if visibilities.empty? + visibilities.each do |visibility, methods| + next if methods.empty? %> + <section id="<%= visibility %>-<%= type %>-<%= section.aref %>-method-details" class="method-section"> + <header> + <h3><%= visibility.to_s.capitalize %> <%= type.capitalize %> Methods</h3> + </header> + + <% methods.each do |method| %> + <div id="<%= method.aref %>" class="method-detail <%= method.is_alias_for ? "method-alias" : '' %>"> + <% if method.call_seq then %> + <% method.call_seq.strip.split("\n").each_with_index do |call_seq, i| %> + <div class="method-heading"> + <span class="method-callseq"> + <%= h(call_seq.strip. + gsub( /^\w+\./m, '')). + gsub(/(.*)[-=]>/, '\1→') %> + </span> + <% if i == 0 and method.token_stream then %> + <span class="method-click-advice">click to toggle source</span> + <% end %> + </div> + <% end %> + <% else %> + <div class="method-heading"> + <span class="method-name"><%= h method.name %></span><span + class="method-args"><%= h method.param_seq %></span> + <% if method.token_stream then %> + <span class="method-click-advice">click to toggle source</span> + <% end %> + </div> + <% end %> + + <div class="method-description"> + <% if method.comment then %> + <%= method.description.strip %> + <% else %> + <p class="missing-docs">(Not documented) + <% end %> + <% if method.calls_super then %> + <div class="method-calls-super"> + Calls superclass method + <%= + method.superclass_method ? + method.formatter.link(method.superclass_method.full_name, method.superclass_method.full_name) : nil + %> + </div> + <% end %> + + <% if method.token_stream then %> + <div class="method-source-code" id="<%= method.html_name %>-source"> + <pre><%= method.markup_code %></pre> + </div> + <% end %> + </div> + + <% unless method.aliases.empty? then %> + <div class="aliases"> + Also aliased as: <%= method.aliases.map do |aka| + if aka.parent then # HACK lib/rexml/encodings + %{<a href="#{klass.aref_to aka.path}">#{h aka.name}</a>} + else + h aka.name + end + end.join ", " %> + </div> + <% end %> + + <% if method.is_alias_for then %> + <div class="aliases"> + Alias for: <a href="<%= klass.aref_to method.is_alias_for.path %>"><%= h method.is_alias_for.name %></a> + </div> + <% end %> + </div> + + <% end %> + </section> + <% end + end %> + </section> +<% end %> +</main> diff --git a/lib/rdoc/generator/template/darkfish/css/fonts.css b/lib/rdoc/generator/template/darkfish/css/fonts.css new file mode 100644 index 0000000000..57302b5183 --- /dev/null +++ b/lib/rdoc/generator/template/darkfish/css/fonts.css @@ -0,0 +1,167 @@ +/* + * Copyright 2010, 2012 Adobe Systems Incorporated (http://www.adobe.com/), + * with Reserved Font Name "Source". All Rights Reserved. Source is a + * trademark of Adobe Systems Incorporated in the United States and/or other + * countries. + * + * This Font Software is licensed under the SIL Open Font License, Version + * 1.1. + * + * This license is copied below, and is also available with a FAQ at: + * http://scripts.sil.org/OFL + */ + +@font-face { + font-family: "Source Code Pro"; + font-style: normal; + font-weight: 400; + src: local("Source Code Pro"), + local("SourceCodePro-Regular"), + url("../fonts/SourceCodePro-Regular.ttf") format("truetype"); +} + +@font-face { + font-family: "Source Code Pro"; + font-style: normal; + font-weight: 700; + src: local("Source Code Pro Bold"), + local("SourceCodePro-Bold"), + url("../fonts/SourceCodePro-Bold.ttf") format("truetype"); +} + +/* + * Copyright (c) 2010, Łukasz Dziedzic (dziedzic@typoland.com), + * with Reserved Font Name Lato. + * + * This Font Software is licensed under the SIL Open Font License, Version + * 1.1. + * + * This license is copied below, and is also available with a FAQ at: + * http://scripts.sil.org/OFL + */ + +@font-face { + font-family: "Lato"; + font-style: normal; + font-weight: 300; + src: local("Lato Light"), + local("Lato-Light"), + url("../fonts/Lato-Light.ttf") format("truetype"); +} + +@font-face { + font-family: "Lato"; + font-style: italic; + font-weight: 300; + src: local("Lato Light Italic"), + local("Lato-LightItalic"), + url("../fonts/Lato-LightItalic.ttf") format("truetype"); +} + +@font-face { + font-family: "Lato"; + font-style: normal; + font-weight: 700; + src: local("Lato Regular"), + local("Lato-Regular"), + url("../fonts/Lato-Regular.ttf") format("truetype"); +} + +@font-face { + font-family: "Lato"; + font-style: italic; + font-weight: 700; + src: local("Lato Italic"), + local("Lato-Italic"), + url("../fonts/Lato-RegularItalic.ttf") format("truetype"); +} + +/* + * ----------------------------------------------------------- + * SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 + * ----------------------------------------------------------- + * + * PREAMBLE + * The goals of the Open Font License (OFL) are to stimulate worldwide + * development of collaborative font projects, to support the font creation + * efforts of academic and linguistic communities, and to provide a free and + * open framework in which fonts may be shared and improved in partnership + * with others. + * + * The OFL allows the licensed fonts to be used, studied, modified and + * redistributed freely as long as they are not sold by themselves. The + * fonts, including any derivative works, can be bundled, embedded, + * redistributed and/or sold with any software provided that any reserved + * names are not used by derivative works. The fonts and derivatives, + * however, cannot be released under any other type of license. The + * requirement for fonts to remain under this license does not apply + * to any document created using the fonts or their derivatives. + * + * DEFINITIONS + * "Font Software" refers to the set of files released by the Copyright + * Holder(s) under this license and clearly marked as such. This may + * include source files, build scripts and documentation. + * + * "Reserved Font Name" refers to any names specified as such after the + * copyright statement(s). + * + * "Original Version" refers to the collection of Font Software components as + * distributed by the Copyright Holder(s). + * + * "Modified Version" refers to any derivative made by adding to, deleting, + * or substituting -- in part or in whole -- any of the components of the + * Original Version, by changing formats or by porting the Font Software to a + * new environment. + * + * "Author" refers to any designer, engineer, programmer, technical + * writer or other person who contributed to the Font Software. + * + * PERMISSION & CONDITIONS + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of the Font Software, to use, study, copy, merge, embed, modify, + * redistribute, and sell modified and unmodified copies of the Font + * Software, subject to the following conditions: + * + * 1) Neither the Font Software nor any of its individual components, + * in Original or Modified Versions, may be sold by itself. + * + * 2) Original or Modified Versions of the Font Software may be bundled, + * redistributed and/or sold with any software, provided that each copy + * contains the above copyright notice and this license. These can be + * included either as stand-alone text files, human-readable headers or + * in the appropriate machine-readable metadata fields within text or + * binary files as long as those fields can be easily viewed by the user. + * + * 3) No Modified Version of the Font Software may use the Reserved Font + * Name(s) unless explicit written permission is granted by the corresponding + * Copyright Holder. This restriction only applies to the primary font name as + * presented to the users. + * + * 4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font + * Software shall not be used to promote, endorse or advertise any + * Modified Version, except to acknowledge the contribution(s) of the + * Copyright Holder(s) and the Author(s) or with their explicit written + * permission. + * + * 5) The Font Software, modified or unmodified, in part or in whole, + * must be distributed entirely under this license, and must not be + * distributed under any other license. The requirement for fonts to + * remain under this license does not apply to any document created + * using the Font Software. + * + * TERMINATION + * This license becomes null and void if any of the above conditions are + * not met. + * + * DISCLAIMER + * THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT + * OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE + * COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL + * DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM + * OTHER DEALINGS IN THE FONT SOFTWARE. + */ + diff --git a/lib/rdoc/generator/template/darkfish/css/rdoc.css b/lib/rdoc/generator/template/darkfish/css/rdoc.css new file mode 100644 index 0000000000..1bdb6e6223 --- /dev/null +++ b/lib/rdoc/generator/template/darkfish/css/rdoc.css @@ -0,0 +1,611 @@ +/* + * "Darkfish" Rdoc CSS + * $Id: rdoc.css 54 2009-01-27 01:09:48Z deveiant $ + * + * Author: Michael Granger <ged@FaerieMUD.org> + * + */ + +/* vim: ft=css et sw=2 ts=2 sts=2 */ +/* Base Green is: #6C8C22 */ + +.hide { display: none !important; } + +* { padding: 0; margin: 0; } + +body { + background: #fafafa; + font-family: Lato, sans-serif; + font-weight: 300; +} + +h1 span, +h2 span, +h3 span, +h4 span, +h5 span, +h6 span { + position: relative; + + display: none; + padding-left: 1em; + line-height: 0; + vertical-align: baseline; + font-size: 10px; +} + +h1 span { top: -1.3em; } +h2 span { top: -1.2em; } +h3 span { top: -1.0em; } +h4 span { top: -0.8em; } +h5 span { top: -0.5em; } +h6 span { top: -0.5em; } + +h1:hover span, +h2:hover span, +h3:hover span, +h4:hover span, +h5:hover span, +h6:hover span { + display: inline; +} + +h1:target, +h2:target, +h3:target, +h4:target, +h5:target, +h6:target { + margin-left: -10px; + border-left: 10px solid #f1edba; +} + +:link, +:visited { + color: #6C8C22; + text-decoration: none; +} + +:link:hover, +:visited:hover { + border-bottom: 1px dotted #6C8C22; +} + +code, +pre { + font-family: "Source Code Pro", Monaco, monospace; +} + +/* @group Generic Classes */ + +.initially-hidden { + display: none; +} + +#search-field { + width: 98%; + background: white; + border: none; + height: 1.5em; + -webkit-border-radius: 4px; + -moz-border-radius: 4px; + border-radius: 4px; + text-align: left; +} +#search-field:focus { + background: #f1edba; +} +#search-field:-moz-placeholder, +#search-field::-webkit-input-placeholder { + font-weight: bold; + color: #666; +} + +.missing-docs { + font-size: 120%; + background: white url(images/wrench_orange.png) no-repeat 4px center; + color: #ccc; + line-height: 2em; + border: 1px solid #d00; + opacity: 1; + padding-left: 20px; + text-indent: 24px; + letter-spacing: 3px; + font-weight: bold; + -webkit-border-radius: 5px; + -moz-border-radius: 5px; +} + +.target-section { + border: 2px solid #dcce90; + border-left-width: 8px; + padding: 0 1em; + background: #fff3c2; +} + +/* @end */ + +/* @group Index Page, Standalone file pages */ +.table-of-contents ul { + margin: 1em; + list-style: none; +} + +.table-of-contents ul ul { + margin-top: 0.25em; +} + +.table-of-contents ul :link, +.table-of-contents ul :visited { + font-size: 16px; +} + +.table-of-contents li { + margin-bottom: 0.25em; +} + +.table-of-contents li .toc-toggle { + width: 16px; + height: 16px; + background: url(images/add.png) no-repeat; +} + +.table-of-contents li .toc-toggle.open { + background: url(images/delete.png) no-repeat; +} + +/* @end */ + +/* @group Top-Level Structure */ + +nav { + float: left; + width: 260px; + font-family: Helvetica, sans-serif; + font-size: 14px; +} + +main { + display: block; + margin: 0 2em 5em 260px; + padding-left: 20px; + min-width: 340px; + font-size: 16px; +} + +main h1, +main h2, +main h3, +main h4, +main h5, +main h6 { + font-family: Helvetica, sans-serif; +} + +.table-of-contents main { + margin-left: 2em; +} + +#validator-badges { + clear: both; + margin: 1em 1em 2em; + font-size: smaller; +} + +/* @end */ + +/* @group navigation */ +nav { + margin-bottom: 1em; +} + +nav .nav-section { + margin-top: 2em; + border-top: 2px solid #aaa; + font-size: 90%; + overflow: hidden; +} + +nav h2 { + margin: 0; + padding: 2px 8px 2px 8px; + background-color: #e8e8e8; + color: #555; + font-size: 125%; + text-align: center; +} + +nav h3, +#table-of-contents-navigation { + margin: 0; + padding: 2px 8px 2px 8px; + text-align: right; + background-color: #e8e8e8; + color: #555; +} + +nav ul, +nav dl, +nav p { + padding: 4px 8px 0; + list-style: none; +} + +#project-navigation .nav-section { + margin: 0; + border-top: 0; +} + +#home-section h2 { + text-align: center; +} + +#table-of-contents-navigation { + font-size: 1.2em; + font-weight: bold; + text-align: center; +} + +#search-section { + margin-top: 0; + border-top: 0; +} + +#search-field-wrapper { + border-top: 1px solid #aaa; + border-bottom: 1px solid #aaa; + padding: 3px 8px; + background-color: #e8e8e8; + color: #555; +} + +ul.link-list li { + white-space: nowrap; + line-height: 1.4em; +} + +ul.link-list .type { + font-size: 8px; + text-transform: uppercase; + color: white; + background: #969696; + padding: 2px 4px; + -webkit-border-radius: 5px; +} + +.calls-super { + background: url(images/arrow_up.png) no-repeat right center; +} + +/* @end */ + +/* @group Documentation Section */ +main { + color: #333; +} + +main > h1:first-child, +main > h2:first-child, +main > h3:first-child, +main > h4:first-child, +main > h5:first-child, +main > h6:first-child { + margin-top: 0px; +} + +main sup { + vertical-align: super; + font-size: 0.8em; +} + +/* The heading with the class name */ +main h1[class] { + margin-top: 0; + margin-bottom: 1em; + font-size: 2em; + color: #6C8C22; +} + +main h1 { + margin: 2em 0 0.5em; + font-size: 1.7em; +} + +main h2 { + margin: 2em 0 0.5em; + font-size: 1.5em; +} + +main h3 { + margin: 2em 0 0.5em; + font-size: 1.2em; +} + +main h4 { + margin: 2em 0 0.5em; + font-size: 1.1em; +} + +main h5 { + margin: 2em 0 0.5em; + font-size: 1em; +} + +main h6 { + margin: 2em 0 0.5em; + font-size: 1em; +} + +main p { + margin: 0 0 0.5em; + line-height: 1.4em; +} + +main pre { + margin: 1.2em 0.5em; + padding: 1em; + font-size: 0.8em; +} + +main hr { + margin: 1.5em 1em; + border: 2px solid #ddd; +} + +main blockquote { + margin: 0 2em 1.2em 1.2em; + padding-left: 0.5em; + border-left: 2px solid #ddd; +} + +main ol, +main ul { + margin: 1em 2em; +} + +main li > p { + margin-bottom: 0.5em; +} + +main dl { + margin: 1em 0.5em; +} + +main dt { + margin-bottom: 0.5em; + font-weight: bold; +} + +main dd { + margin: 0 1em 1em 0.5em; +} + +main header h2 { + margin-top: 2em; + border-width: 0; + border-top: 4px solid #bbb; + font-size: 130%; +} + +main header h3 { + margin: 2em 0 1.5em; + border-width: 0; + border-top: 3px solid #bbb; + font-size: 120%; +} + +.documentation-section-title { + position: relative; +} +.documentation-section-title .section-click-top { + position: absolute; + top: 6px; + left: 12px; + font-size: 10px; + color: #9b9877; + visibility: hidden; + padding-left: 0.5px; +} + +.documentation-section-title:hover .section-click-top { + visibility: visible; +} + +.constants-list > dl { + margin: 1em 0 2em; + border: 0; +} + +.constants-list > dl dt { + margin-bottom: 0.75em; + padding-left: 0; + font-family: "Source Code Pro", Monaco, monospace; + font-size: 110%; +} + +.constants-list > dl dt a { + color: inherit; +} + +.constants-list > dl dd { + margin: 0 0 2em 0; + padding: 0; + color: #666; +} + +.documentation-section h2 { + position: relative; +} + +.documentation-section h2 a { + position: absolute; + top: 8px; + right: 10px; + font-size: 12px; + color: #9b9877; + visibility: hidden; +} + +.documentation-section h2:hover a { + visibility: visible; +} + +/* @group Method Details */ + +main .method-source-code { + max-height: 0; + overflow: hidden; + transition-duration: 200ms; + transition-delay: 0ms; + transition-property: all; + transition-timing-function: ease-in-out; +} + +main .method-source-code.active-menu { + max-height: 100vh; +} + +main .method-description .method-calls-super { + color: #333; + font-weight: bold; +} + +main .method-detail { + margin-bottom: 2.5em; + cursor: pointer; +} + +main .method-detail:target { + margin-left: -10px; + border-left: 10px solid #f1edba; +} + +main .method-heading { + position: relative; + font-family: "Source Code Pro", Monaco, monospace; + font-size: 110%; + font-weight: bold; + color: #333; +} +main .method-heading :link, +main .method-heading :visited { + color: inherit; +} +main .method-click-advice { + position: absolute; + top: 2px; + right: 5px; + font-size: 12px; + color: #9b9877; + visibility: hidden; + padding-right: 20px; + line-height: 20px; + background: url(images/zoom.png) no-repeat right top; +} +main .method-heading:hover .method-click-advice { + visibility: visible; +} + +main .method-alias .method-heading { + color: #666; +} + +main .method-description, +main .aliases { + margin-top: 0.75em; + color: #333; +} + +main .aliases { + padding-top: 4px; + font-style: italic; + cursor: default; +} +main .method-description ul { + margin-left: 1.5em; +} + +main #attribute-method-details .method-detail:hover { + background-color: transparent; + cursor: default; +} +main .attribute-access-type { + text-transform: uppercase; + padding: 0 1em; +} +/* @end */ + +/* @end */ + +/* @group Source Code */ + +pre { + margin: 0.5em 0; + border: 1px dashed #999; + padding: 0.5em; + background: #262626; + color: white; + overflow: auto; +} + +.ruby-constant { color: #7fffd4; background: transparent; } +.ruby-keyword { color: #00ffff; background: transparent; } +.ruby-ivar { color: #eedd82; background: transparent; } +.ruby-operator { color: #00ffee; background: transparent; } +.ruby-identifier { color: #ffdead; background: transparent; } +.ruby-node { color: #ffa07a; background: transparent; } +.ruby-comment { color: #dc0000; background: transparent; } +.ruby-regexp { color: #ffa07a; background: transparent; } +.ruby-value { color: #7fffd4; background: transparent; } + +/* @end */ + + +/* @group search results */ +#search-results { + font-family: Lato, sans-serif; + font-weight: 300; +} + +#search-results .search-match { + font-family: Helvetica, sans-serif; + font-weight: normal; +} + +#search-results .search-selected { + background: #e8e8e8; + border-bottom: 1px solid transparent; +} + +#search-results li { + list-style: none; + border-bottom: 1px solid #aaa; + margin-bottom: 0.5em; +} + +#search-results li:last-child { + border-bottom: none; + margin-bottom: 0; +} + +#search-results li p { + padding: 0; + margin: 0.5em; +} + +#search-results .search-namespace { + font-weight: bold; +} + +#search-results li em { + background: yellow; + font-style: normal; +} + +#search-results pre { + margin: 0.5em; + font-family: "Source Code Pro", Monaco, monospace; +} + +/* @end */ + diff --git a/lib/rdoc/generator/template/darkfish/fonts/Lato-Light.ttf b/lib/rdoc/generator/template/darkfish/fonts/Lato-Light.ttf Binary files differnew file mode 100644 index 0000000000..b49dd43729 --- /dev/null +++ b/lib/rdoc/generator/template/darkfish/fonts/Lato-Light.ttf diff --git a/lib/rdoc/generator/template/darkfish/fonts/Lato-LightItalic.ttf b/lib/rdoc/generator/template/darkfish/fonts/Lato-LightItalic.ttf Binary files differnew file mode 100644 index 0000000000..7959fef075 --- /dev/null +++ b/lib/rdoc/generator/template/darkfish/fonts/Lato-LightItalic.ttf diff --git a/lib/rdoc/generator/template/darkfish/fonts/Lato-Regular.ttf b/lib/rdoc/generator/template/darkfish/fonts/Lato-Regular.ttf Binary files differnew file mode 100644 index 0000000000..839cd589dc --- /dev/null +++ b/lib/rdoc/generator/template/darkfish/fonts/Lato-Regular.ttf diff --git a/lib/rdoc/generator/template/darkfish/fonts/Lato-RegularItalic.ttf b/lib/rdoc/generator/template/darkfish/fonts/Lato-RegularItalic.ttf Binary files differnew file mode 100644 index 0000000000..bababa09e3 --- /dev/null +++ b/lib/rdoc/generator/template/darkfish/fonts/Lato-RegularItalic.ttf diff --git a/lib/rdoc/generator/template/darkfish/fonts/SourceCodePro-Bold.ttf b/lib/rdoc/generator/template/darkfish/fonts/SourceCodePro-Bold.ttf Binary files differnew file mode 100644 index 0000000000..61e3090c1c --- /dev/null +++ b/lib/rdoc/generator/template/darkfish/fonts/SourceCodePro-Bold.ttf diff --git a/lib/rdoc/generator/template/darkfish/fonts/SourceCodePro-Regular.ttf b/lib/rdoc/generator/template/darkfish/fonts/SourceCodePro-Regular.ttf Binary files differnew file mode 100644 index 0000000000..85686d967d --- /dev/null +++ b/lib/rdoc/generator/template/darkfish/fonts/SourceCodePro-Regular.ttf diff --git a/lib/rdoc/generator/template/darkfish/images/add.png b/lib/rdoc/generator/template/darkfish/images/add.png Binary files differnew file mode 100644 index 0000000000..6332fefea4 --- /dev/null +++ b/lib/rdoc/generator/template/darkfish/images/add.png diff --git a/lib/rdoc/generator/template/darkfish/images/arrow_up.png b/lib/rdoc/generator/template/darkfish/images/arrow_up.png Binary files differnew file mode 100644 index 0000000000..1ebb193243 --- /dev/null +++ b/lib/rdoc/generator/template/darkfish/images/arrow_up.png diff --git a/lib/rdoc/generator/template/darkfish/images/brick.png b/lib/rdoc/generator/template/darkfish/images/brick.png Binary files differnew file mode 100644 index 0000000000..7851cf34c9 --- /dev/null +++ b/lib/rdoc/generator/template/darkfish/images/brick.png diff --git a/lib/rdoc/generator/template/darkfish/images/brick_link.png b/lib/rdoc/generator/template/darkfish/images/brick_link.png Binary files differnew file mode 100644 index 0000000000..9ebf013a23 --- /dev/null +++ b/lib/rdoc/generator/template/darkfish/images/brick_link.png diff --git a/lib/rdoc/generator/template/darkfish/images/bug.png b/lib/rdoc/generator/template/darkfish/images/bug.png Binary files differnew file mode 100644 index 0000000000..2d5fb90ec6 --- /dev/null +++ b/lib/rdoc/generator/template/darkfish/images/bug.png diff --git a/lib/rdoc/generator/template/darkfish/images/bullet_black.png b/lib/rdoc/generator/template/darkfish/images/bullet_black.png Binary files differnew file mode 100644 index 0000000000..57619706d1 --- /dev/null +++ b/lib/rdoc/generator/template/darkfish/images/bullet_black.png diff --git a/lib/rdoc/generator/template/darkfish/images/bullet_toggle_minus.png b/lib/rdoc/generator/template/darkfish/images/bullet_toggle_minus.png Binary files differnew file mode 100644 index 0000000000..b47ce55f68 --- /dev/null +++ b/lib/rdoc/generator/template/darkfish/images/bullet_toggle_minus.png diff --git a/lib/rdoc/generator/template/darkfish/images/bullet_toggle_plus.png b/lib/rdoc/generator/template/darkfish/images/bullet_toggle_plus.png Binary files differnew file mode 100644 index 0000000000..9ab4a89664 --- /dev/null +++ b/lib/rdoc/generator/template/darkfish/images/bullet_toggle_plus.png diff --git a/lib/rdoc/generator/template/darkfish/images/date.png b/lib/rdoc/generator/template/darkfish/images/date.png Binary files differnew file mode 100644 index 0000000000..783c83357f --- /dev/null +++ b/lib/rdoc/generator/template/darkfish/images/date.png diff --git a/lib/rdoc/generator/template/darkfish/images/delete.png b/lib/rdoc/generator/template/darkfish/images/delete.png Binary files differnew file mode 100644 index 0000000000..08f249365a --- /dev/null +++ b/lib/rdoc/generator/template/darkfish/images/delete.png diff --git a/lib/rdoc/generator/template/darkfish/images/find.png b/lib/rdoc/generator/template/darkfish/images/find.png Binary files differnew file mode 100644 index 0000000000..1547479646 --- /dev/null +++ b/lib/rdoc/generator/template/darkfish/images/find.png diff --git a/lib/rdoc/generator/template/darkfish/images/loadingAnimation.gif b/lib/rdoc/generator/template/darkfish/images/loadingAnimation.gif Binary files differnew file mode 100644 index 0000000000..82290f4833 --- /dev/null +++ b/lib/rdoc/generator/template/darkfish/images/loadingAnimation.gif diff --git a/lib/rdoc/generator/template/darkfish/images/macFFBgHack.png b/lib/rdoc/generator/template/darkfish/images/macFFBgHack.png Binary files differnew file mode 100644 index 0000000000..c6473b324e --- /dev/null +++ b/lib/rdoc/generator/template/darkfish/images/macFFBgHack.png diff --git a/lib/rdoc/generator/template/darkfish/images/package.png b/lib/rdoc/generator/template/darkfish/images/package.png Binary files differnew file mode 100644 index 0000000000..da3c2a2d74 --- /dev/null +++ b/lib/rdoc/generator/template/darkfish/images/package.png diff --git a/lib/rdoc/generator/template/darkfish/images/page_green.png b/lib/rdoc/generator/template/darkfish/images/page_green.png Binary files differnew file mode 100644 index 0000000000..de8e003f9f --- /dev/null +++ b/lib/rdoc/generator/template/darkfish/images/page_green.png diff --git a/lib/rdoc/generator/template/darkfish/images/page_white_text.png b/lib/rdoc/generator/template/darkfish/images/page_white_text.png Binary files differnew file mode 100644 index 0000000000..813f712f72 --- /dev/null +++ b/lib/rdoc/generator/template/darkfish/images/page_white_text.png diff --git a/lib/rdoc/generator/template/darkfish/images/page_white_width.png b/lib/rdoc/generator/template/darkfish/images/page_white_width.png Binary files differnew file mode 100644 index 0000000000..1eb880947d --- /dev/null +++ b/lib/rdoc/generator/template/darkfish/images/page_white_width.png diff --git a/lib/rdoc/generator/template/darkfish/images/plugin.png b/lib/rdoc/generator/template/darkfish/images/plugin.png Binary files differnew file mode 100644 index 0000000000..6187b15aec --- /dev/null +++ b/lib/rdoc/generator/template/darkfish/images/plugin.png diff --git a/lib/rdoc/generator/template/darkfish/images/ruby.png b/lib/rdoc/generator/template/darkfish/images/ruby.png Binary files differnew file mode 100644 index 0000000000..f763a16880 --- /dev/null +++ b/lib/rdoc/generator/template/darkfish/images/ruby.png diff --git a/lib/rdoc/generator/template/darkfish/images/tag_blue.png b/lib/rdoc/generator/template/darkfish/images/tag_blue.png Binary files differnew file mode 100644 index 0000000000..3f02b5f8f8 --- /dev/null +++ b/lib/rdoc/generator/template/darkfish/images/tag_blue.png diff --git a/lib/rdoc/generator/template/darkfish/images/tag_green.png b/lib/rdoc/generator/template/darkfish/images/tag_green.png Binary files differnew file mode 100644 index 0000000000..83ec984bd7 --- /dev/null +++ b/lib/rdoc/generator/template/darkfish/images/tag_green.png diff --git a/lib/rdoc/generator/template/darkfish/images/transparent.png b/lib/rdoc/generator/template/darkfish/images/transparent.png Binary files differnew file mode 100644 index 0000000000..d665e179ef --- /dev/null +++ b/lib/rdoc/generator/template/darkfish/images/transparent.png diff --git a/lib/rdoc/generator/template/darkfish/images/wrench.png b/lib/rdoc/generator/template/darkfish/images/wrench.png Binary files differnew file mode 100644 index 0000000000..5c8213fef5 --- /dev/null +++ b/lib/rdoc/generator/template/darkfish/images/wrench.png diff --git a/lib/rdoc/generator/template/darkfish/images/wrench_orange.png b/lib/rdoc/generator/template/darkfish/images/wrench_orange.png Binary files differnew file mode 100644 index 0000000000..565a9330e0 --- /dev/null +++ b/lib/rdoc/generator/template/darkfish/images/wrench_orange.png diff --git a/lib/rdoc/generator/template/darkfish/images/zoom.png b/lib/rdoc/generator/template/darkfish/images/zoom.png Binary files differnew file mode 100644 index 0000000000..908612e394 --- /dev/null +++ b/lib/rdoc/generator/template/darkfish/images/zoom.png diff --git a/lib/rdoc/generator/template/darkfish/index.rhtml b/lib/rdoc/generator/template/darkfish/index.rhtml new file mode 100644 index 0000000000..7d1c74807b --- /dev/null +++ b/lib/rdoc/generator/template/darkfish/index.rhtml @@ -0,0 +1,23 @@ +<body id="top" role="document" class="file"> +<nav role="navigation"> + <div id="project-navigation"> + <%= render '_sidebar_navigation.rhtml' %> + + <%= render '_sidebar_search.rhtml' %> + </div> + + <div id="project-metadata"> + <%= render '_sidebar_pages.rhtml' %> + <%= render '_sidebar_classes.rhtml' %> + </div> +</nav> + +<main role="main"> +<% if @options.main_page and + main_page = @files.find { |f| f.full_name == @options.main_page } then %> +<%= main_page.description %> +<% else %> +<p>This is the API documentation for <%= @title %>. +<% end %> +</main> + diff --git a/lib/rdoc/generator/template/darkfish/js/darkfish.js b/lib/rdoc/generator/template/darkfish/js/darkfish.js new file mode 100644 index 0000000000..111bbf8eb9 --- /dev/null +++ b/lib/rdoc/generator/template/darkfish/js/darkfish.js @@ -0,0 +1,84 @@ +/** + * + * Darkfish Page Functions + * $Id: darkfish.js 53 2009-01-07 02:52:03Z deveiant $ + * + * Author: Michael Granger <mgranger@laika.com> + * + */ + +/* Provide console simulation for firebug-less environments */ +/* +if (!("console" in window) || !("firebug" in console)) { + var names = ["log", "debug", "info", "warn", "error", "assert", "dir", "dirxml", + "group", "groupEnd", "time", "timeEnd", "count", "trace", "profile", "profileEnd"]; + + window.console = {}; + for (var i = 0; i < names.length; ++i) + window.console[names[i]] = function() {}; +}; +*/ + + +function showSource( e ) { + var target = e.target; + while (!target.classList.contains('method-detail')) { + target = target.parentNode; + } + if (typeof target !== "undefined" && target !== null) { + target = target.querySelector('.method-source-code'); + } + if (typeof target !== "undefined" && target !== null) { + target.classList.toggle('active-menu') + } +}; + +function hookSourceViews() { + document.querySelectorAll('.method-heading').forEach(function (codeObject) { + codeObject.addEventListener('click', showSource); + }); +}; + +function hookSearch() { + var input = document.querySelector('#search-field'); + var result = document.querySelector('#search-results'); + result.classList.remove("initially-hidden"); + + var search_section = document.querySelector('#search-section'); + search_section.classList.remove("initially-hidden"); + + var search = new Search(search_data, input, result); + + search.renderItem = function(result) { + var li = document.createElement('li'); + var html = ''; + + // TODO add relative path to <script> per-page + html += '<p class="search-match"><a href="' + index_rel_prefix + result.path + '">' + this.hlt(result.title); + if (result.params) + html += '<span class="params">' + result.params + '</span>'; + html += '</a>'; + + + if (result.namespace) + html += '<p class="search-namespace">' + this.hlt(result.namespace); + + if (result.snippet) + html += '<div class="search-snippet">' + result.snippet + '</div>'; + + li.innerHTML = html; + + return li; + } + + search.select = function(result) { + window.location.href = result.firstChild.firstChild.href; + } + + search.scrollIntoView = search.scrollInWindow; +}; + +document.addEventListener('DOMContentLoaded', function() { + hookSourceViews(); + hookSearch(); +}); diff --git a/lib/rdoc/generator/template/darkfish/js/search.js b/lib/rdoc/generator/template/darkfish/js/search.js new file mode 100644 index 0000000000..b558ca5b4f --- /dev/null +++ b/lib/rdoc/generator/template/darkfish/js/search.js @@ -0,0 +1,110 @@ +Search = function(data, input, result) { + this.data = data; + this.input = input; + this.result = result; + + this.current = null; + this.view = this.result.parentNode; + this.searcher = new Searcher(data.index); + this.init(); +} + +Search.prototype = Object.assign({}, Navigation, new function() { + var suid = 1; + + this.init = function() { + var _this = this; + var observer = function(e) { + switch(e.keyCode) { + case 38: // Event.KEY_UP + case 40: // Event.KEY_DOWN + return; + } + _this.search(_this.input.value); + }; + this.input.addEventListener('keyup', observer); + this.input.addEventListener('click', observer); // mac's clear field + + this.searcher.ready(function(results, isLast) { + _this.addResults(results, isLast); + }) + + this.initNavigation(); + this.setNavigationActive(false); + } + + this.search = function(value, selectFirstMatch) { + value = value.trim().toLowerCase(); + if (value) { + this.setNavigationActive(true); + } else { + this.setNavigationActive(false); + } + + if (value == '') { + this.lastQuery = value; + this.result.innerHTML = ''; + this.result.setAttribute('aria-expanded', 'false'); + this.setNavigationActive(false); + } else if (value != this.lastQuery) { + this.lastQuery = value; + this.result.setAttribute('aria-busy', 'true'); + this.result.setAttribute('aria-expanded', 'true'); + this.firstRun = true; + this.searcher.find(value); + } + } + + this.addResults = function(results, isLast) { + var target = this.result; + if (this.firstRun && (results.length > 0 || isLast)) { + this.current = null; + this.result.innerHTML = ''; + } + + for (var i=0, l = results.length; i < l; i++) { + var item = this.renderItem.call(this, results[i]); + item.setAttribute('id', 'search-result-' + target.childElementCount); + target.appendChild(item); + }; + + if (this.firstRun && results.length > 0) { + this.firstRun = false; + this.current = target.firstChild; + this.current.classList.add('search-selected'); + } + //TODO: ECMAScript + //if (jQuery.browser.msie) this.$element[0].className += ''; + + if (isLast) this.result.setAttribute('aria-busy', 'false'); + } + + this.move = function(isDown) { + if (!this.current) return; + var next = isDown ? this.current.nextElementSibling : this.current.previousElementSibling; + if (next) { + this.current.classList.remove('search-selected'); + next.classList.add('search-selected'); + this.input.setAttribute('aria-activedescendant', next.getAttribute('id')); + this.scrollIntoView(next, this.view); + this.current = next; + this.input.value = next.firstChild.firstChild.text; + this.input.select(); + } + return true; + } + + this.hlt = function(html) { + return this.escapeHTML(html). + replace(/\u0001/g, '<em>'). + replace(/\u0002/g, '</em>'); + } + + this.escapeHTML = function(html) { + return html.replace(/[&<>]/g, function(c) { + return '&#' + c.charCodeAt(0) + ';'; + }); + } + +}); + diff --git a/lib/rdoc/generator/template/darkfish/page.rhtml b/lib/rdoc/generator/template/darkfish/page.rhtml new file mode 100644 index 0000000000..4a6b006bb3 --- /dev/null +++ b/lib/rdoc/generator/template/darkfish/page.rhtml @@ -0,0 +1,18 @@ +<body id="top" role="document" class="file"> +<nav role="navigation"> + <div id="project-navigation"> + <%= render '_sidebar_navigation.rhtml' %> + <%= render '_sidebar_search.rhtml' %> + </div> + + <%= render '_sidebar_table_of_contents.rhtml' %> + + <div id="project-metadata"> + <%= render '_sidebar_pages.rhtml' %> + </div> +</nav> + +<main role="main" aria-label="Page <%=h file.full_name%>"> +<%= file.description %> +</main> + diff --git a/lib/rdoc/generator/template/darkfish/servlet_not_found.rhtml b/lib/rdoc/generator/template/darkfish/servlet_not_found.rhtml new file mode 100644 index 0000000000..f0841572c3 --- /dev/null +++ b/lib/rdoc/generator/template/darkfish/servlet_not_found.rhtml @@ -0,0 +1,18 @@ +<body role="document"> +<nav role="navigation"> + <%= render '_sidebar_navigation.rhtml' %> + + <%= render '_sidebar_search.rhtml' %> + + <div id="project-metadata"> + <%= render '_sidebar_pages.rhtml' %> + <%= render '_sidebar_classes.rhtml' %> + </div> +</nav> + +<main role="main"> + <h1>Not Found</h1> + + <p><%= message %> +</main> + diff --git a/lib/rdoc/generator/template/darkfish/servlet_root.rhtml b/lib/rdoc/generator/template/darkfish/servlet_root.rhtml new file mode 100644 index 0000000000..3a33659aea --- /dev/null +++ b/lib/rdoc/generator/template/darkfish/servlet_root.rhtml @@ -0,0 +1,63 @@ +<body role="document"> +<nav role="navigation"> + <div id="project-navigation"> + <div id="home-section" class="nav-section"> + <h2> + <a href="<%= rel_prefix %>/" rel="home">Home</a> + </h2> + </div> + + <%= render '_sidebar_search.rhtml' %> + </div> + +<%= render '_sidebar_installed.rhtml' %> +</nav> + +<main role="main"> + <h1>Local RDoc Documentation</h1> + + <p>Here you can browse local documentation from the ruby standard library and + your installed gems. + +<% extra_dirs = installed.select { |_, _, _, type,| type == :extra } %> +<% unless extra_dirs.empty? %> + <h2>Extra Documentation Directories</h2> + + <p>The following additional documentation directories are available:</p> + + <ol> + <% extra_dirs.each do |name, href, exists, _, path| %> + <li> + <% if exists %> + <a href="<%= href %>"><%= h name %></a> (<%= h path %>) + <% else %> + <%= h name %> (<%= h path %>; <i>not available</i>) + <% end %> + </li> + <% end %> + </ol> +<% end %> + +<% gems = installed.select { |_, _, _, type,| type == :gem } %> +<% missing = gems.reject { |_, _, exists,| exists } %> +<% unless missing.empty? then %> + <h2>Missing Gem Documentation</h2> + + <p>You are missing documentation for some of your installed gems. + You can install missing documentation for gems by running + <kbd>gem rdoc --all</kbd>. After installing the missing documentation you + only need to reload this page. The newly created documentation will + automatically appear. + + <p>You can also install documentation for a specific gem by running one of + the following commands. + + <ul> + <% names = missing.map { |name,| name.sub(/-([^-]*)$/, '') }.uniq %> + <% names.each do |name| %> + <li><kbd>gem rdoc <%=h name %></kbd> + <% end %> + </ul> +<% end %> +</main> + diff --git a/lib/rdoc/generator/template/darkfish/table_of_contents.rhtml b/lib/rdoc/generator/template/darkfish/table_of_contents.rhtml new file mode 100644 index 0000000000..7ff1a9e93e --- /dev/null +++ b/lib/rdoc/generator/template/darkfish/table_of_contents.rhtml @@ -0,0 +1,58 @@ +<body id="top" class="table-of-contents"> +<main role="main"> +<h1 class="class"><%= h @title %></h1> + +<% simple_files = @files.select { |f| f.text? } %> +<% unless simple_files.empty? then %> +<h2 id="pages">Pages</h2> +<ul> +<% simple_files.sort.each do |file| %> + <li class="file"> + <a href="<%= file.path %>"><%= h file.page_name %></a> +<% + # HACK table_of_contents should not exist on Document + table = file.parse(file.comment).table_of_contents + unless table.empty? then %> + <ul> +<% table.each do |heading| %> + <li><a href="<%= file.path %>#<%= heading.aref %>"><%= heading.plain_html %></a> +<% end %> + </ul> +<% end %> + </li> + <% end %> +</ul> +<% end %> + +<h2 id="classes">Classes and Modules</h2> +<ul> +<% @modsort.each do |klass| %> + <li class="<%= klass.type %>"> + <a href="<%= klass.path %>"><%= klass.full_name %></a> +<% table = [] + table.concat klass.parse(klass.comment_location).table_of_contents + table.concat klass.section_contents + + unless table.empty? then %> + <ul> +<% table.each do |item| %> + <li><a href="<%= klass.path %>#<%= item.aref %>"><%= item.plain_html %></a> +<% end %> + </ul> +<% end %> + </li> +<% end %> +</ul> + +<h2 id="methods">Methods</h2> +<ul> +<% @store.all_classes_and_modules.map do |mod| + mod.method_list + end.flatten.sort.each do |method| %> + <li class="method"> + <a href="<%= method.path %>"><%= h method.pretty_name %></a> + — + <span class="container"><%= method.parent.full_name %></span> +<% end %> +</ul> +</main> diff --git a/lib/rdoc/generator/template/json_index/.document b/lib/rdoc/generator/template/json_index/.document new file mode 100644 index 0000000000..1713b67654 --- /dev/null +++ b/lib/rdoc/generator/template/json_index/.document @@ -0,0 +1 @@ +# ignore all files in this directory diff --git a/lib/rdoc/generator/template/json_index/js/navigation.js b/lib/rdoc/generator/template/json_index/js/navigation.js new file mode 100644 index 0000000000..4866fff819 --- /dev/null +++ b/lib/rdoc/generator/template/json_index/js/navigation.js @@ -0,0 +1,106 @@ +/* + * Navigation allows movement using the arrow keys through the search results. + * + * When using this library you will need to set scrollIntoView to the + * appropriate function for your layout. Use scrollInWindow if the container + * is not scrollable and scrollInElement if the container is a separate + * scrolling region. + */ +Navigation = new function() { + this.initNavigation = function() { + var _this = this; + + document.addEventListener('keydown', function(e) { + _this.onkeydown(e); + }); + + this.navigationActive = true; + } + + this.setNavigationActive = function(state) { + this.navigationActive = state; + } + + this.onkeydown = function(e) { + if (!this.navigationActive) return; + switch(e.keyCode) { + case 37: //Event.KEY_LEFT: + if (this.moveLeft()) e.preventDefault(); + break; + case 38: //Event.KEY_UP: + if (e.keyCode == 38 || e.ctrlKey) { + if (this.moveUp()) e.preventDefault(); + } + break; + case 39: //Event.KEY_RIGHT: + if (this.moveRight()) e.preventDefault(); + break; + case 40: //Event.KEY_DOWN: + if (e.keyCode == 40 || e.ctrlKey) { + if (this.moveDown()) e.preventDefault(); + } + break; + case 13: //Event.KEY_RETURN: + if (this.current) + e.preventDefault(); + this.select(this.current); + break; + } + if (e.ctrlKey && e.shiftKey) this.select(this.current); + } + + this.moveRight = function() { + } + + this.moveLeft = function() { + } + + this.move = function(isDown) { + } + + this.moveUp = function() { + return this.move(false); + } + + this.moveDown = function() { + return this.move(true); + } + + /* + * Scrolls to the given element in the scrollable element view. + */ + this.scrollInElement = function(element, view) { + var offset, viewHeight, viewScroll, height; + offset = element.offsetTop; + height = element.offsetHeight; + viewHeight = view.offsetHeight; + viewScroll = view.scrollTop; + + if (offset - viewScroll + height > viewHeight) { + view.scrollTop = offset - viewHeight + height; + } + if (offset < viewScroll) { + view.scrollTop = offset; + } + } + + /* + * Scrolls to the given element in the window. The second argument is + * ignored + */ + this.scrollInWindow = function(element, ignored) { + var offset, viewHeight, viewScroll, height; + offset = element.offsetTop; + height = element.offsetHeight; + viewHeight = window.innerHeight; + viewScroll = window.scrollY; + + if (offset - viewScroll + height > viewHeight) { + window.scrollTo(window.scrollX, offset - viewHeight + height); + } + if (offset < viewScroll) { + window.scrollTo(window.scrollX, offset); + } + } +} + diff --git a/lib/rdoc/generator/template/json_index/js/searcher.js b/lib/rdoc/generator/template/json_index/js/searcher.js new file mode 100644 index 0000000000..e200a168b0 --- /dev/null +++ b/lib/rdoc/generator/template/json_index/js/searcher.js @@ -0,0 +1,229 @@ +Searcher = function(data) { + this.data = data; + this.handlers = []; +} + +Searcher.prototype = new function() { + // search is performed in chunks of 1000 for non-blocking user input + var CHUNK_SIZE = 1000; + // do not try to find more than 100 results + var MAX_RESULTS = 100; + var huid = 1; + var suid = 1; + var runs = 0; + + this.find = function(query) { + var queries = splitQuery(query); + var regexps = buildRegexps(queries); + var highlighters = buildHilighters(queries); + var state = { from: 0, pass: 0, limit: MAX_RESULTS, n: suid++}; + var _this = this; + + this.currentSuid = state.n; + + if (!query) return; + + var run = function() { + // stop current search thread if new search started + if (state.n != _this.currentSuid) return; + + var results = + performSearch(_this.data, regexps, queries, highlighters, state); + var hasMore = (state.limit > 0 && state.pass < 4); + + triggerResults.call(_this, results, !hasMore); + if (hasMore) { + setTimeout(run, 2); + } + runs++; + }; + runs = 0; + + // start search thread + run(); + } + + /* ----- Events ------ */ + this.ready = function(fn) { + fn.huid = huid; + this.handlers.push(fn); + } + + /* ----- Utilities ------ */ + function splitQuery(query) { + return query.split(/(\s+|::?|\(\)?)/).filter(function(string) { + return string.match(/\S/); + }); + } + + function buildRegexps(queries) { + return queries.map(function(query) { + return new RegExp(query.replace(/(.)/g, '([$1])([^$1]*?)'), 'i'); + }); + } + + function buildHilighters(queries) { + return queries.map(function(query) { + return query.split('').map(function(l, i) { + return '\u0001$' + (i*2+1) + '\u0002$' + (i*2+2); + }).join(''); + }); + } + + // function longMatchRegexp(index, longIndex, regexps) { + // for (var i = regexps.length - 1; i >= 0; i--){ + // if (!index.match(regexps[i]) && !longIndex.match(regexps[i])) return false; + // }; + // return true; + // } + + + /* ----- Mathchers ------ */ + + /* + * This record matches if the index starts with queries[0] and the record + * matches all of the regexps + */ + function matchPassBeginning(index, longIndex, queries, regexps) { + if (index.indexOf(queries[0]) != 0) return false; + for (var i=1, l = regexps.length; i < l; i++) { + if (!index.match(regexps[i]) && !longIndex.match(regexps[i])) + return false; + }; + return true; + } + + /* + * This record matches if the longIndex starts with queries[0] and the + * longIndex matches all of the regexps + */ + function matchPassLongIndex(index, longIndex, queries, regexps) { + if (longIndex.indexOf(queries[0]) != 0) return false; + for (var i=1, l = regexps.length; i < l; i++) { + if (!longIndex.match(regexps[i])) + return false; + }; + return true; + } + + /* + * This record matches if the index contains queries[0] and the record + * matches all of the regexps + */ + function matchPassContains(index, longIndex, queries, regexps) { + if (index.indexOf(queries[0]) == -1) return false; + for (var i=1, l = regexps.length; i < l; i++) { + if (!index.match(regexps[i]) && !longIndex.match(regexps[i])) + return false; + }; + return true; + } + + /* + * This record matches if regexps[0] matches the index and the record + * matches all of the regexps + */ + function matchPassRegexp(index, longIndex, queries, regexps) { + if (!index.match(regexps[0])) return false; + for (var i=1, l = regexps.length; i < l; i++) { + if (!index.match(regexps[i]) && !longIndex.match(regexps[i])) + return false; + }; + return true; + } + + + /* ----- Highlighters ------ */ + function highlightRegexp(info, queries, regexps, highlighters) { + var result = createResult(info); + for (var i=0, l = regexps.length; i < l; i++) { + result.title = result.title.replace(regexps[i], highlighters[i]); + result.namespace = result.namespace.replace(regexps[i], highlighters[i]); + }; + return result; + } + + function hltSubstring(string, pos, length) { + return string.substring(0, pos) + '\u0001' + string.substring(pos, pos + length) + '\u0002' + string.substring(pos + length); + } + + function highlightQuery(info, queries, regexps, highlighters) { + var result = createResult(info); + var pos = 0; + var lcTitle = result.title.toLowerCase(); + + pos = lcTitle.indexOf(queries[0]); + if (pos != -1) { + result.title = hltSubstring(result.title, pos, queries[0].length); + } + + result.namespace = result.namespace.replace(regexps[0], highlighters[0]); + for (var i=1, l = regexps.length; i < l; i++) { + result.title = result.title.replace(regexps[i], highlighters[i]); + result.namespace = result.namespace.replace(regexps[i], highlighters[i]); + }; + return result; + } + + function createResult(info) { + var result = {}; + result.title = info[0]; + result.namespace = info[1]; + result.path = info[2]; + result.params = info[3]; + result.snippet = info[4]; + result.badge = info[6]; + return result; + } + + /* ----- Searching ------ */ + function performSearch(data, regexps, queries, highlighters, state) { + var searchIndex = data.searchIndex; + var longSearchIndex = data.longSearchIndex; + var info = data.info; + var result = []; + var i = state.from; + var l = searchIndex.length; + var togo = CHUNK_SIZE; + var matchFunc, hltFunc; + + while (state.pass < 4 && state.limit > 0 && togo > 0) { + if (state.pass == 0) { + matchFunc = matchPassBeginning; + hltFunc = highlightQuery; + } else if (state.pass == 1) { + matchFunc = matchPassLongIndex; + hltFunc = highlightQuery; + } else if (state.pass == 2) { + matchFunc = matchPassContains; + hltFunc = highlightQuery; + } else if (state.pass == 3) { + matchFunc = matchPassRegexp; + hltFunc = highlightRegexp; + } + + for (; togo > 0 && i < l && state.limit > 0; i++, togo--) { + if (info[i].n == state.n) continue; + if (matchFunc(searchIndex[i], longSearchIndex[i], queries, regexps)) { + info[i].n = state.n; + result.push(hltFunc(info[i], queries, regexps, highlighters)); + state.limit--; + } + }; + if (searchIndex.length <= i) { + state.pass++; + i = state.from = 0; + } else { + state.from = i; + } + } + return result; + } + + function triggerResults(results, isLast) { + this.handlers.forEach(function(fn) { + fn.call(this, results, isLast) + }); + } +} + diff --git a/lib/rdoc/generators/chm_generator.rb b/lib/rdoc/generators/chm_generator.rb deleted file mode 100644 index 51eeda8dd1..0000000000 --- a/lib/rdoc/generators/chm_generator.rb +++ /dev/null @@ -1,112 +0,0 @@ -require 'rdoc/generators/html_generator' - -module Generators - - class CHMGenerator < HTMLGenerator - - HHC_PATH = "c:/Program Files/HTML Help Workshop/hhc.exe" - - # Standard generator factory - def CHMGenerator.for(options) - CHMGenerator.new(options) - end - - - def initialize(*args) - super - @op_name = @options.op_name || "rdoc" - check_for_html_help_workshop - end - - def check_for_html_help_workshop - stat = File.stat(HHC_PATH) - rescue - $stderr << - "\n.chm output generation requires that Microsoft's Html Help\n" << - "Workshop is installed. RDoc looks for it in:\n\n " << - HHC_PATH << - "\n\nYou can download a copy for free from:\n\n" << - " http://msdn.microsoft.com/library/default.asp?" << - "url=/library/en-us/htmlhelp/html/hwMicrosoftHTMLHelpDownloads.asp\n\n" - - exit 99 - end - - # Generate the html as normal, then wrap it - # in a help project - def generate(info) - super - @project_name = @op_name + ".hhp" - create_help_project - end - - # The project contains the project file, a table of contents - # and an index - def create_help_project - create_project_file - create_contents_and_index - compile_project - end - - # The project file links together all the various - # files that go to make up the help. - - def create_project_file - template = TemplatePage.new(RDoc::Page::HPP_FILE) - values = { "title" => @options.title, "opname" => @op_name } - files = [] - @files.each do |f| - files << { "html_file_name" => f.path } - end - - values['all_html_files'] = files - - File.open(@project_name, "w") do |f| - template.write_html_on(f, values) - end - end - - # The contents is a list of all files and modules. - # For each we include as sub-entries the list - # of methods they contain. As we build the contents - # we also build an index file - - def create_contents_and_index - contents = [] - index = [] - - (@files+@classes).sort.each do |entry| - content_entry = { "c_name" => entry.name, "ref" => entry.path } - index << { "name" => entry.name, "aref" => entry.path } - - internals = [] - - methods = entry.build_method_summary_list(entry.path) - - content_entry["methods"] = methods unless methods.empty? - contents << content_entry - index.concat methods - end - - values = { "contents" => contents } - template = TemplatePage.new(RDoc::Page::CONTENTS) - File.open("contents.hhc", "w") do |f| - template.write_html_on(f, values) - end - - values = { "index" => index } - template = TemplatePage.new(RDoc::Page::CHM_INDEX) - File.open("index.hhk", "w") do |f| - template.write_html_on(f, values) - end - end - - # Invoke the windows help compiler to compiler the project - def compile_project - system(HHC_PATH, @project_name) - end - - end - - -end diff --git a/lib/rdoc/generators/html_generator.rb b/lib/rdoc/generators/html_generator.rb deleted file mode 100644 index 1f9b808e8d..0000000000 --- a/lib/rdoc/generators/html_generator.rb +++ /dev/null @@ -1,1509 +0,0 @@ -# We're responsible for generating all the HTML files -# from the object tree defined in code_objects.rb. We -# generate: -# -# [files] an html file for each input file given. These -# input files appear as objects of class -# TopLevel -# -# [classes] an html file for each class or module encountered. -# These classes are not grouped by file: if a file -# contains four classes, we'll generate an html -# file for the file itself, and four html files -# for the individual classes. -# -# [indices] we generate three indices for files, classes, -# and methods. These are displayed in a browser -# like window with three index panes across the -# top and the selected description below -# -# Method descriptions appear in whatever entity (file, class, -# or module) that contains them. -# -# We generate files in a structure below a specified subdirectory, -# normally +doc+. -# -# opdir -# | -# |___ files -# | |__ per file summaries -# | -# |___ classes -# |__ per class/module descriptions -# -# HTML is generated using the Template class. -# - -require 'ftools' - -require 'rdoc/options' -require 'rdoc/template' -require 'rdoc/markup/simple_markup' -require 'rdoc/markup/simple_markup/to_html' -require 'cgi' - -module Generators - - # Name of sub-direcories that hold file and class/module descriptions - - FILE_DIR = "files" - CLASS_DIR = "classes" - CSS_NAME = "rdoc-style.css" - - - ## - # Build a hash of all items that can be cross-referenced. - # This is used when we output required and included names: - # if the names appear in this hash, we can generate - # an html cross reference to the appropriate description. - # We also use this when parsing comment blocks: any decorated - # words matching an entry in this list are hyperlinked. - - class AllReferences - @@refs = {} - - def AllReferences::reset - @@refs = {} - end - - def AllReferences.add(name, html_class) - @@refs[name] = html_class - end - - def AllReferences.[](name) - @@refs[name] - end - - def AllReferences.keys - @@refs.keys - end - 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 - - class HyperlinkHtml < SM::ToHtml - # We need to record the html path of our caller so we can generate - # correct relative paths for any hyperlinks that we find - def initialize(from_path, context) - super() - @from_path = from_path - - @parent_name = context.parent_name - @parent_name += "::" if @parent_name - @context = context - end - - # We're invoked when any text matches the CROSSREF pattern - # (defined in MarkUp). If we fine the corresponding reference, - # generate a hyperlink. If the name we're looking for contains - # no punctuation, we look for it up the module/class chain. For - # example, HyperlinkHtml is found, even without the Generators:: - # prefix, because we look for it in module Generators first. - - def handle_special_CROSSREF(special) - name = special.text - if name[0,1] == '#' - lookup = name[1..-1] - name = lookup unless Options.instance.show_hash - else - lookup = name - end - - # Find class, module, or method in class or module. - if /([A-Z]\w*)[.\#](\w+[!?=]?)/ =~ lookup - container = $1 - method = $2 - ref = @context.find_symbol(container, method) - elsif /([A-Za-z]\w*)[.\#](\w+(\([\.\w+\*\/\+\-\=\<\>]+\))?)/ =~ lookup - container = $1 - method = $2 - ref = @context.find_symbol(container, method) - else - ref = @context.find_symbol(lookup) - end - - if ref and ref.document_self - "<a href=\"#{ref.as_href(@from_path)}\">#{name}</a>" - else - name - end - end - - - # Generate a hyperlink for url, labeled with text. Handle the - # special cases for img: and link: described under handle_special_HYPEDLINK - def gen_url(url, text) - if url =~ /([A-Za-z]+):(.*)/ - type = $1 - path = $2 - else - type = "http" - path = url - url = "http://#{url}" - end - - if type == "link" - if path[0,1] == '#' # is this meaningful? - url = path - else - url = HTMLGenerator.gen_url(@from_path, path) - end - end - - if (type == "http" || type == "link") && - url =~ /\.(gif|png|jpg|jpeg|bmp)$/ - - "<img src=\"#{url}\" />" - else - "<a href=\"#{url}\">#{text.sub(%r{^#{type}:/*}, '')}</a>" - end - end - - # And we're invoked with a potential external hyperlink mailto: - # just gets inserted. http: links are checked to see if they - # reference an image. If so, that image gets inserted using an - # <img> tag. Otherwise a conventional <a href> is used. We also - # support a special type of hyperlink, link:, which is a reference - # to a local file whose path is relative to the --op directory. - - def handle_special_HYPERLINK(special) - url = special.text - gen_url(url, url) - end - - # HEre's a hypedlink where the label is different to the URL - # <label>[url] - # - - def handle_special_TIDYLINK(special) - text = special.text -# unless text =~ /(\S+)\[(.*?)\]/ - unless text =~ /\{(.*?)\}\[(.*?)\]/ or text =~ /(\S+)\[(.*?)\]/ - return text - end - label = $1 - url = $2 - gen_url(url, label) - end - - end - - - - ##################################################################### - # - # Handle common markup tasks for the various Html classes - # - - module MarkUp - - # Convert a string in markup format into HTML. We keep a cached - # SimpleMarkup 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 - - # class names, variable names, or instance variables - @markup.add_special(/( - \w+(::\w+)*[.\#]\w+(\([\.\w+\*\/\+\-\=\<\>]+\))? # A::B.meth(**) (for operator in Fortran95) - | \#\w+(\([.\w\*\/\+\-\=\<\>]+\))? # meth(**) (for operator in Fortran95) - | \b([A-Z]\w*(::\w+)*[.\#]\w+) # A::B.meth - | \b([A-Z]\w+(::\w+)*) # A::B.. - | \#\w+[!?=]? # #meth_name - | \b\w+([_\/\.]+\w+)*[!?=]? # meth_name - )/x, - :CROSSREF) - - # external hyperlinks - @markup.add_special(/((link:|https?:|mailto:|ftp:|www\.)\S+\w)/, :HYPERLINK) - - # and links of the form <text>[<url>] - @markup.add_special(/(((\{.*?\})|\b\S+?)\[\S+?\.\S+?\])/, :TIDYLINK) -# @markup.add_special(/\b(\S+?\[\S+?\.\S+?\])/, :TIDYLINK) - - end - unless defined? @html_formatter - @html_formatter = HyperlinkHtml.new(self.path, self) - end - - # Convert leading comment markers to spaces, but only - # if all non-blank lines have them - - if str =~ /^(?>\s*)[^\#]/ - content = str - else - content = str.gsub(/^\s*(#+)/) { $1.tr('#',' ') } - end - - res = @markup.convert(content, @html_formatter) - if remove_para - res.sub!(/^<p>/, '') - res.sub!(/<\/p>$/, '') - end - res - end - - # Qualify a stylesheet URL; if if +css_name+ does not begin with '/' or - # 'http[s]://', prepend a prefix relative to +path+. Otherwise, return it - # unmodified. - - def style_url(path, css_name=nil) -# $stderr.puts "style_url( #{path.inspect}, #{css_name.inspect} )" - css_name ||= CSS_NAME - if %r{^(https?:/)?/} =~ css_name - return css_name - else - return HTMLGenerator.gen_url(path, css_name) - end - end - - # Build a webcvs URL with the given 'url' argument. URLs with a '%s' in them - # get the file's path sprintfed into them; otherwise they're just catenated - # together. - - def cvs_url(url, full_path) - if /%s/ =~ url - return sprintf( url, full_path ) - else - return url + full_path - end - end - end - - - ##################################################################### - # - # A Context is built by the parser to represent a container: contexts - # hold classes, modules, methods, require lists and include lists. - # ClassModule and TopLevel are the context objects we process here - # - class ContextUser - - include MarkUp - - attr_reader :context - - def initialize(context, options) - @context = context - @options = options - end - - # convenience method to build a hyperlink - def href(link, cls, name) - %{<a href="#{link}" class="#{cls}">#{name}</a>} #" - end - - # return a reference to outselves to be used as an href= - # the form depends on whether we're all in one file - # or in multiple files - - def as_href(from_path) - if @options.all_one_file - "#" + path - else - HTMLGenerator.gen_url(from_path, path) - end - end - - # Create a list of HtmlMethod objects for each method - # in the corresponding context object. If the @options.show_all - # variable is set (corresponding to the <tt>--all</tt> option, - # we include all methods, otherwise just the public ones. - - def collect_methods - list = @context.method_list - unless @options.show_all - list = list.find_all {|m| m.visibility == :public || m.visibility == :protected || m.force_documentation } - end - @methods = list.collect {|m| HtmlMethod.new(m, self, @options) } - end - - # Build a summary list of all the methods in this context - def build_method_summary_list(path_prefix="") - collect_methods unless @methods - meths = @methods.sort - res = [] - meths.each do |meth| - res << { - "name" => CGI.escapeHTML(meth.name), - "aref" => "#{path_prefix}\##{meth.aref}" - } - end - res - end - - - # Build a list of aliases for which we couldn't find a - # corresponding method - def build_alias_summary_list(section) - values = [] - @context.aliases.each do |al| - next unless al.section == section - res = { - 'old_name' => al.old_name, - 'new_name' => al.new_name, - } - if al.comment && !al.comment.empty? - res['desc'] = markup(al.comment, true) - end - values << res - end - values - end - - # Build a list of constants - def build_constants_summary_list(section) - values = [] - @context.constants.each do |co| - next unless co.section == section - res = { - 'name' => co.name, - 'value' => CGI.escapeHTML(co.value) - } - res['desc'] = markup(co.comment, true) if co.comment && !co.comment.empty? - values << res - end - values - end - - def build_requires_list(context) - potentially_referenced_list(context.requires) {|fn| [fn + ".rb"] } - end - - def build_include_list(context) - potentially_referenced_list(context.includes) - end - - # Build a list from an array of <i>Htmlxxx</i> items. Look up each - # in the AllReferences hash: if we find a corresponding entry, - # we generate a hyperlink to it, otherwise just output the name. - # However, some names potentially need massaging. For example, - # you may require a Ruby file without the .rb extension, - # but the file names we know about may have it. To deal with - # this, we pass in a block which performs the massaging, - # returning an array of alternative names to match - - def potentially_referenced_list(array) - res = [] - array.each do |i| - ref = AllReferences[i.name] -# if !ref -# container = @context.parent -# while !ref && container -# name = container.name + "::" + i.name -# ref = AllReferences[name] -# container = container.parent -# end -# end - - ref = @context.find_symbol(i.name) - ref = ref.viewer if ref - - if !ref && block_given? - possibles = yield(i.name) - while !ref and !possibles.empty? - ref = AllReferences[possibles.shift] - end - end - h_name = CGI.escapeHTML(i.name) - if ref and ref.document_self - path = url(ref.path) - res << { "name" => h_name, "aref" => path } - else - res << { "name" => h_name } - end - end - res - end - - # Build an array of arrays of method details. The outer array has up - # to six entries, public, private, and protected for both class - # methods, the other for instance methods. The inner arrays contain - # a hash for each method - - def build_method_detail_list(section) - outer = [] - - methods = @methods.sort - for singleton in [true, false] - for vis in [ :public, :protected, :private ] - res = [] - methods.each do |m| - if m.section == section and - m.document_self and - m.visibility == vis and - m.singleton == singleton - row = {} - if m.call_seq - row["callseq"] = m.call_seq.gsub(/->/, '→') - else - row["name"] = CGI.escapeHTML(m.name) - row["params"] = m.params - end - desc = m.description.strip - row["m_desc"] = desc unless desc.empty? - row["aref"] = m.aref - row["visibility"] = m.visibility.to_s - - alias_names = [] - m.aliases.each do |other| - if other.viewer # won't be if the alias is private - alias_names << { - 'name' => other.name, - 'aref' => other.viewer.as_href(path) - } - end - end - unless alias_names.empty? - row["aka"] = alias_names - end - - if @options.inline_source - code = m.source_code - row["sourcecode"] = code if code - else - code = m.src_url - if code - row["codeurl"] = code - row["imgurl"] = m.img_url - end - end - res << row - end - end - if res.size > 0 - outer << { - "type" => vis.to_s.capitalize, - "category" => singleton ? "Class" : "Instance", - "methods" => res - } - end - end - end - outer - end - - # Build the structured list of classes and modules contained - # in this context. - - def build_class_list(level, from, section, infile=nil) - res = "" - prefix = " ::" * level; - - from.modules.sort.each do |mod| - next unless mod.section == section - next if infile && !mod.defined_in?(infile) - if mod.document_self - res << - prefix << - "Module " << - href(url(mod.viewer.path), "link", mod.full_name) << - "<br />\n" << - build_class_list(level + 1, mod, section, infile) - end - end - - from.classes.sort.each do |cls| - next unless cls.section == section - next if infile && !cls.defined_in?(infile) - if cls.document_self - res << - prefix << - "Class " << - href(url(cls.viewer.path), "link", cls.full_name) << - "<br />\n" << - build_class_list(level + 1, cls, section, infile) - end - end - - res - end - - def url(target) - HTMLGenerator.gen_url(path, target) - end - - def aref_to(target) - if @options.all_one_file - "#" + target - else - url(target) - end - end - - def document_self - @context.document_self - end - - def diagram_reference(diagram) - res = diagram.gsub(/((?:src|href)=")(.*?)"/) { - $1 + url($2) + '"' - } - res - end - - - # Find a symbol in ourselves or our parent - def find_symbol(symbol, method=nil) - res = @context.find_symbol(symbol, method) - if res - res = res.viewer - end - res - end - - # create table of contents if we contain sections - - def add_table_of_sections - toc = [] - @context.sections.each do |section| - if section.title - toc << { - 'secname' => section.title, - 'href' => section.sequence - } - end - end - - @values['toc'] = toc unless toc.empty? - end - - - end - - ##################################################################### - # - # Wrap a ClassModule context - - class HtmlClass < ContextUser - - attr_reader :path - - def initialize(context, html_file, prefix, options) - super(context, options) - - @html_file = html_file - @is_module = context.is_module? - @values = {} - - context.viewer = self - - if options.all_one_file - @path = context.full_name - else - @path = http_url(context.full_name, prefix) - end - - collect_methods - - AllReferences.add(name, self) - end - - # return the relative file name to store this class in, - # which is also its url - def http_url(full_name, prefix) - path = full_name.dup - if path['<<'] - path.gsub!(/<<\s*(\w*)/) { "from-#$1" } - end - File.join(prefix, path.split("::")) + ".html" - end - - - def name - @context.full_name - end - - def parent_name - @context.parent.full_name - end - - def index_name - name - end - - def write_on(f) - value_hash - template = TemplatePage.new(RDoc::Page::BODY, - RDoc::Page::CLASS_PAGE, - RDoc::Page::METHOD_LIST) - template.write_html_on(f, @values) - end - - def value_hash - class_attribute_values - add_table_of_sections - - @values["charset"] = @options.charset - @values["style_url"] = style_url(path, @options.css) - - d = markup(@context.comment) - @values["description"] = d unless d.empty? - - ml = build_method_summary_list - @values["methods"] = ml unless ml.empty? - - il = build_include_list(@context) - @values["includes"] = il unless il.empty? - - @values["sections"] = @context.sections.map do |section| - - secdata = { - "sectitle" => section.title, - "secsequence" => section.sequence, - "seccomment" => markup(section.comment) - } - - al = build_alias_summary_list(section) - secdata["aliases"] = al unless al.empty? - - co = build_constants_summary_list(section) - secdata["constants"] = co unless co.empty? - - al = build_attribute_list(section) - secdata["attributes"] = al unless al.empty? - - cl = build_class_list(0, @context, section) - secdata["classlist"] = cl unless cl.empty? - - mdl = build_method_detail_list(section) - secdata["method_list"] = mdl unless mdl.empty? - - secdata - end - - @values - end - - def build_attribute_list(section) - atts = @context.attributes.sort - res = [] - atts.each do |att| - next unless att.section == section - if att.visibility == :public || att.visibility == :protected || @options.show_all - entry = { - "name" => CGI.escapeHTML(att.name), - "rw" => att.rw, - "a_desc" => markup(att.comment, true) - } - unless att.visibility == :public || att.visibility == :protected - entry["rw"] << "-" - end - res << entry - end - end - res - end - - def class_attribute_values - h_name = CGI.escapeHTML(name) - - @values["classmod"] = @is_module ? "Module" : "Class" - @values["title"] = "#{@values['classmod']}: #{h_name}" - - c = @context - c = c.parent while c and !c.diagram - if c && c.diagram - @values["diagram"] = diagram_reference(c.diagram) - end - - @values["full_name"] = h_name - - parent_class = @context.superclass - - if parent_class - @values["parent"] = CGI.escapeHTML(parent_class) - - if parent_name - lookup = parent_name + "::" + parent_class - else - lookup = parent_class - end - - parent_url = AllReferences[lookup] || AllReferences[parent_class] - - if parent_url and parent_url.document_self - @values["par_url"] = aref_to(parent_url.path) - end - end - - files = [] - @context.in_files.each do |f| - res = {} - full_path = CGI.escapeHTML(f.file_absolute_name) - - res["full_path"] = full_path - res["full_path_url"] = aref_to(f.viewer.path) if f.document_self - - if @options.webcvs - res["cvsurl"] = cvs_url( @options.webcvs, full_path ) - end - - files << res - end - - @values['infiles'] = files - end - - def <=>(other) - self.name <=> other.name - end - - end - - ##################################################################### - # - # Handles the mapping of a file's information to HTML. In reality, - # a file corresponds to a +TopLevel+ object, containing modules, - # classes, and top-level methods. In theory it _could_ contain - # attributes and aliases, but we ignore these for now. - - class HtmlFile < ContextUser - - attr_reader :path - attr_reader :name - - def initialize(context, options, file_dir) - super(context, options) - - @values = {} - - if options.all_one_file - @path = filename_to_label - else - @path = http_url(file_dir) - end - - @name = @context.file_relative_name - - collect_methods - AllReferences.add(name, self) - context.viewer = self - end - - def http_url(file_dir) - File.join(file_dir, @context.file_relative_name.tr('.', '_')) + - ".html" - end - - def filename_to_label - @context.file_relative_name.gsub(/%|\/|\?|\#/) {|s| '%' + ("%x" % s[0]) } - end - - def index_name - name - end - - def parent_name - nil - end - - def value_hash - file_attribute_values - add_table_of_sections - - @values["charset"] = @options.charset - @values["href"] = path - @values["style_url"] = style_url(path, @options.css) - - if @context.comment - d = markup(@context.comment) - @values["description"] = d if d.size > 0 - end - - ml = build_method_summary_list - @values["methods"] = ml unless ml.empty? - - il = build_include_list(@context) - @values["includes"] = il unless il.empty? - - rl = build_requires_list(@context) - @values["requires"] = rl unless rl.empty? - - if @options.promiscuous - file_context = nil - else - file_context = @context - end - - - @values["sections"] = @context.sections.map do |section| - - secdata = { - "sectitle" => section.title, - "secsequence" => section.sequence, - "seccomment" => markup(section.comment) - } - - cl = build_class_list(0, @context, section, file_context) - @values["classlist"] = cl unless cl.empty? - - mdl = build_method_detail_list(section) - secdata["method_list"] = mdl unless mdl.empty? - - al = build_alias_summary_list(section) - secdata["aliases"] = al unless al.empty? - - co = build_constants_summary_list(section) - @values["constants"] = co unless co.empty? - - secdata - end - - @values - end - - def write_on(f) - value_hash - template = TemplatePage.new(RDoc::Page::BODY, - RDoc::Page::FILE_PAGE, - RDoc::Page::METHOD_LIST) - template.write_html_on(f, @values) - end - - def file_attribute_values - full_path = @context.file_absolute_name - short_name = File.basename(full_path) - - @values["title"] = CGI.escapeHTML("File: #{short_name}") - - if @context.diagram - @values["diagram"] = diagram_reference(@context.diagram) - end - - @values["short_name"] = CGI.escapeHTML(short_name) - @values["full_path"] = CGI.escapeHTML(full_path) - @values["dtm_modified"] = @context.file_stat.mtime.to_s - - if @options.webcvs - @values["cvsurl"] = cvs_url( @options.webcvs, @values["full_path"] ) - end - end - - def <=>(other) - self.name <=> other.name - end - end - - ##################################################################### - - class HtmlMethod - include MarkUp - - attr_reader :context - attr_reader :src_url - attr_reader :img_url - attr_reader :source_code - - @@seq = "M000000" - - @@all_methods = [] - - def HtmlMethod::reset - @@all_methods = [] - end - - def initialize(context, html_class, options) - @context = context - @html_class = html_class - @options = options - @@seq = @@seq.succ - @seq = @@seq - @@all_methods << self - - context.viewer = self - - if (ts = @context.token_stream) - @source_code = markup_code(ts) - unless @options.inline_source - @src_url = create_source_code_file(@source_code) - @img_url = HTMLGenerator.gen_url(path, 'source.png') - end - end - - AllReferences.add(name, self) - end - - # return a reference to outselves to be used as an href= - # the form depends on whether we're all in one file - # or in multiple files - - def as_href(from_path) - if @options.all_one_file - "#" + path - else - HTMLGenerator.gen_url(from_path, path) - end - end - - def name - @context.name - end - - def section - @context.section - end - - def index_name - "#{@context.name} (#{@html_class.name})" - end - - def parent_name - if @context.parent.parent - @context.parent.parent.full_name - else - nil - end - end - - def aref - @seq - end - - def path - if @options.all_one_file - aref - else - @html_class.path + "#" + aref - end - end - - def description - markup(@context.comment) - end - - def visibility - @context.visibility - end - - def singleton - @context.singleton - end - - def call_seq - cs = @context.call_seq - if cs - cs.gsub(/\n/, "<br />\n") - else - nil - end - end - - def params - # params coming from a call-seq in 'C' will start with the - # method name - p = @context.params - if p !~ /^\w/ - p = @context.params.gsub(/\s*\#.*/, '') - p = p.tr("\n", " ").squeeze(" ") - p = "(" + p + ")" unless p[0] == ?( - - if (block = @context.block_params) - # If this method has explicit block parameters, remove any - # explicit &block - - p.sub!(/,?\s*&\w+/, '') - - block.gsub!(/\s*\#.*/, '') - block = block.tr("\n", " ").squeeze(" ") - if block[0] == ?( - block.sub!(/^\(/, '').sub!(/\)/, '') - end - p << " {|#{block.strip}| ...}" - end - end - CGI.escapeHTML(p) - end - - def create_source_code_file(code_body) - meth_path = @html_class.path.sub(/\.html$/, '.src') - File.makedirs(meth_path) - file_path = File.join(meth_path, @seq) + ".html" - - template = TemplatePage.new(RDoc::Page::SRC_PAGE) - File.open(file_path, "w") do |f| - values = { - 'title' => CGI.escapeHTML(index_name), - 'code' => code_body, - 'style_url' => style_url(file_path, @options.css), - 'charset' => @options.charset - } - template.write_html_on(f, values) - end - HTMLGenerator.gen_url(path, file_path) - end - - def HtmlMethod.all_methods - @@all_methods - end - - def <=>(other) - @context <=> other.context - end - - ## - # Given a sequence of source tokens, mark up the source code - # to make it look purty. - - - def markup_code(tokens) - src = "" - tokens.each do |t| - next unless t - # p t.class -# style = STYLE_MAP[t.class] - style = case t - when RubyToken::TkCONSTANT then "ruby-constant" - when RubyToken::TkKW then "ruby-keyword kw" - when RubyToken::TkIVAR then "ruby-ivar" - when RubyToken::TkOp then "ruby-operator" - when RubyToken::TkId then "ruby-identifier" - when RubyToken::TkNode then "ruby-node" - when RubyToken::TkCOMMENT then "ruby-comment cmt" - when RubyToken::TkREGEXP then "ruby-regexp re" - when RubyToken::TkSTRING then "ruby-value str" - when RubyToken::TkVal then "ruby-value" - else - nil - end - - text = CGI.escapeHTML(t.text) - - if style - src << "<span class=\"#{style}\">#{text}</span>" - else - src << text - end - end - - add_line_numbers(src) if Options.instance.include_line_numbers - src - end - - # we rely on the fact that the first line of a source code - # listing has - # # File xxxxx, line dddd - - def add_line_numbers(src) - if src =~ /\A.*, line (\d+)/ - first = $1.to_i - 1 - last = first + src.count("\n") - size = last.to_s.length - real_fmt = "%#{size}d: " - fmt = " " * (size+2) - src.gsub!(/^/) do - res = sprintf(fmt, first) - first += 1 - fmt = real_fmt - res - end - end - end - - def document_self - @context.document_self - end - - def aliases - @context.aliases - end - - def find_symbol(symbol, method=nil) - res = @context.parent.find_symbol(symbol, method) - if res - res = res.viewer - end - res - end - end - - ##################################################################### - - class HTMLGenerator - - include MarkUp - - ## - # convert a target url to one that is relative to a given - # path - - def HTMLGenerator.gen_url(path, target) - from = File.dirname(path) - to, to_file = File.split(target) - - from = from.split("/") - to = to.split("/") - - while from.size > 0 and to.size > 0 and from[0] == to[0] - from.shift - to.shift - end - - from.fill("..") - from.concat(to) - from << to_file - File.join(*from) - end - - # Generators may need to return specific subclasses depending - # on the options they are passed. Because of this - # we create them using a factory - - def HTMLGenerator.for(options) - AllReferences::reset - HtmlMethod::reset - - if options.all_one_file - HTMLGeneratorInOne.new(options) - else - HTMLGenerator.new(options) - end - end - - class <<self - protected :new - end - - # Set up a new HTML generator. Basically all we do here is load - # up the correct output temlate - - def initialize(options) #:not-new: - @options = options - load_html_template - end - - - ## - # Build the initial indices and output objects - # based on an array of TopLevel objects containing - # the extracted information. - - def generate(toplevels) - @toplevels = toplevels - @files = [] - @classes = [] - - write_style_sheet - gen_sub_directories() - build_indices - generate_html - end - - private - - ## - # Load up the HTML template specified in the options. - # If the template name contains a slash, use it literally - # - def load_html_template - template = @options.template - unless template =~ %r{/|\\} - template = File.join("rdoc/generators/template", - @options.generator.key, template) - end - require template - extend RDoc::Page - rescue LoadError - $stderr.puts "Could not find HTML template '#{template}'" - exit 99 - end - - ## - # Write out the style sheet used by the main frames - # - - def write_style_sheet - template = TemplatePage.new(RDoc::Page::STYLE) - unless @options.css - File.open(CSS_NAME, "w") do |f| - values = { "fonts" => RDoc::Page::FONTS } - template.write_html_on(f, values) - end - end - end - - ## - # See the comments at the top for a description of the - # directory structure - - def gen_sub_directories - File.makedirs(FILE_DIR, CLASS_DIR) - rescue - $stderr.puts $!.message - exit 1 - end - - ## - # Generate: - # - # * a list of HtmlFile objects for each TopLevel object. - # * a list of HtmlClass objects for each first level - # class or module in the TopLevel objects - # * a complete list of all hyperlinkable terms (file, - # class, module, and method names) - - def build_indices - - @toplevels.each do |toplevel| - @files << HtmlFile.new(toplevel, @options, FILE_DIR) - end - - RDoc::TopLevel.all_classes_and_modules.each do |cls| - build_class_list(cls, @files[0], CLASS_DIR) - end - end - - def build_class_list(from, html_file, class_dir) - @classes << HtmlClass.new(from, html_file, class_dir, @options) - from.each_classmodule do |mod| - build_class_list(mod, html_file, class_dir) - end - end - - ## - # Generate all the HTML - # - def generate_html - # the individual descriptions for files and classes - gen_into(@files) - gen_into(@classes) - # and the index files - gen_file_index - gen_class_index - gen_method_index - gen_main_index - - # this method is defined in the template file - write_extra_pages if defined? write_extra_pages - end - - def gen_into(list) - list.each do |item| - if item.document_self - op_file = item.path - File.makedirs(File.dirname(op_file)) - File.open(op_file, "w") { |file| item.write_on(file) } - end - end - - end - - def gen_file_index - gen_an_index(@files, 'Files', - RDoc::Page::FILE_INDEX, - "fr_file_index.html") - end - - def gen_class_index - gen_an_index(@classes, 'Classes', - RDoc::Page::CLASS_INDEX, - "fr_class_index.html") - end - - def gen_method_index - gen_an_index(HtmlMethod.all_methods, 'Methods', - RDoc::Page::METHOD_INDEX, - "fr_method_index.html") - end - - - def gen_an_index(collection, title, template, filename) - template = TemplatePage.new(RDoc::Page::FR_INDEX_BODY, template) - res = [] - collection.sort.each do |f| - if f.document_self - res << { "href" => f.path, "name" => f.index_name } - end - end - - values = { - "entries" => res, - 'list_title' => CGI.escapeHTML(title), - 'index_url' => main_url, - 'charset' => @options.charset, - 'style_url' => style_url('', @options.css), - } - - File.open(filename, "w") do |f| - template.write_html_on(f, values) - end - end - - # The main index page is mostly a template frameset, but includes - # the initial page. If the <tt>--main</tt> option was given, - # we use this as our main page, otherwise we use the - # first file specified on the command line. - - def gen_main_index - template = TemplatePage.new(RDoc::Page::INDEX) - File.open("index.html", "w") do |f| - values = { - "initial_page" => main_url, - 'title' => CGI.escapeHTML(@options.title), - 'charset' => @options.charset - } - if @options.inline_source - values['inline_source'] = true - end - template.write_html_on(f, values) - end - end - - # return the url of the main page - def main_url - main_page = @options.main_page - ref = nil - if main_page - ref = AllReferences[main_page] - if ref - ref = ref.path - else - $stderr.puts "Could not find main page #{main_page}" - end - end - - unless ref - for file in @files - if file.document_self - ref = file.path - break - end - end - end - - unless ref - $stderr.puts "Couldn't find anything to document" - $stderr.puts "Perhaps you've used :stopdoc: in all classes" - exit(1) - end - - ref - end - - - end - - - ###################################################################### - - - class HTMLGeneratorInOne < HTMLGenerator - - def initialize(*args) - super - end - - ## - # Build the initial indices and output objects - # based on an array of TopLevel objects containing - # the extracted information. - - def generate(info) - @toplevels = info - @files = [] - @classes = [] - @hyperlinks = {} - - build_indices - generate_xml - end - - - ## - # Generate: - # - # * a list of HtmlFile objects for each TopLevel object. - # * a list of HtmlClass objects for each first level - # class or module in the TopLevel objects - # * a complete list of all hyperlinkable terms (file, - # class, module, and method names) - - def build_indices - - @toplevels.each do |toplevel| - @files << HtmlFile.new(toplevel, @options, FILE_DIR) - end - - RDoc::TopLevel.all_classes_and_modules.each do |cls| - build_class_list(cls, @files[0], CLASS_DIR) - end - end - - def build_class_list(from, html_file, class_dir) - @classes << HtmlClass.new(from, html_file, class_dir, @options) - from.each_classmodule do |mod| - build_class_list(mod, html_file, class_dir) - end - end - - ## - # Generate all the HTML. For the one-file case, we generate - # all the information in to one big hash - # - def generate_xml - values = { - 'charset' => @options.charset, - 'files' => gen_into(@files), - 'classes' => gen_into(@classes), - 'title' => CGI.escapeHTML(@options.title), - } - - # this method is defined in the template file - write_extra_pages if defined? write_extra_pages - - template = TemplatePage.new(RDoc::Page::ONE_PAGE) - - if @options.op_name - opfile = File.open(@options.op_name, "w") - else - opfile = $stdout - end - template.write_html_on(opfile, values) - end - - def gen_into(list) - res = [] - list.each do |item| - res << item.value_hash - end - res - end - - def gen_file_index - gen_an_index(@files, 'Files') - end - - def gen_class_index - gen_an_index(@classes, 'Classes') - end - - def gen_method_index - gen_an_index(HtmlMethod.all_methods, 'Methods') - end - - - def gen_an_index(collection, title) - res = [] - collection.sort.each do |f| - if f.document_self - res << { "href" => f.path, "name" => f.index_name } - end - end - - return { - "entries" => res, - 'list_title' => title, - 'index_url' => main_url, - } - end - - end -end diff --git a/lib/rdoc/generators/ri_generator.rb b/lib/rdoc/generators/ri_generator.rb deleted file mode 100644 index c4b4a7e17c..0000000000 --- a/lib/rdoc/generators/ri_generator.rb +++ /dev/null @@ -1,268 +0,0 @@ -# We're responsible for generating all the HTML files -# from the object tree defined in code_objects.rb. We -# generate: -# -# [files] an html file for each input file given. These -# input files appear as objects of class -# TopLevel -# -# [classes] an html file for each class or module encountered. -# These classes are not grouped by file: if a file -# contains four classes, we'll generate an html -# file for the file itself, and four html files -# for the individual classes. -# -# [indices] we generate three indices for files, classes, -# and methods. These are displayed in a browser -# like window with three index panes across the -# top and the selected description below -# -# Method descriptions appear in whatever entity (file, class, -# or module) that contains them. -# -# We generate files in a structure below a specified subdirectory, -# normally +doc+. -# -# opdir -# | -# |___ files -# | |__ per file summaries -# | -# |___ classes -# |__ per class/module descriptions -# -# HTML is generated using the Template class. -# - -require 'ftools' - -require 'rdoc/options' -require 'rdoc/template' -require 'rdoc/markup/simple_markup' -require 'rdoc/markup/simple_markup/to_flow' -require 'cgi' - -require 'rdoc/ri/ri_cache' -require 'rdoc/ri/ri_reader' -require 'rdoc/ri/ri_writer' -require 'rdoc/ri/ri_descriptions' - -module Generators - - - class RIGenerator - - # Generators may need to return specific subclasses depending - # on the options they are passed. Because of this - # we create them using a factory - - def RIGenerator.for(options) - new(options) - end - - class <<self - protected :new - end - - # Set up a new HTML generator. Basically all we do here is load - # up the correct output temlate - - def initialize(options) #:not-new: - @options = options - @ri_writer = RI::RiWriter.new(options.op_dir) - @markup = SM::SimpleMarkup.new - @to_flow = SM::ToFlow.new - end - - - ## - # Build the initial indices and output objects - # based on an array of TopLevel objects containing - # the extracted information. - - def generate(toplevels) - RDoc::TopLevel.all_classes_and_modules.each do |cls| - process_class(cls) - end - end - - def process_class(from_class) - generate_class_info(from_class) - - # now recure into this classes constituent classess - from_class.each_classmodule do |mod| - process_class(mod) - end - end - - def generate_class_info(cls) - if cls === RDoc::NormalModule - cls_desc = RI::ModuleDescription.new - else - cls_desc = 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) - - cls_desc.attributes =cls.attributes.sort.map do |a| - RI::Attribute.new(a.name, a.rw, markup(a.comment)) - end - - cls_desc.constants = cls.constants.map do |c| - RI::Constant.new(c.name, c.value, markup(c.comment)) - end - - cls_desc.includes = cls.includes.map do |i| - RI::IncludedModule.new(i.name) - end - - class_methods, instance_methods = method_list(cls) - - cls_desc.class_methods = class_methods.map do |m| - RI::MethodSummary.new(m.name) - end - cls_desc.instance_methods = instance_methods.map do |m| - RI::MethodSummary.new(m.name) - end - - update_or_replace(cls_desc) - - class_methods.each do |m| - generate_method_info(cls_desc, m) - end - - instance_methods.each do |m| - generate_method_info(cls_desc, m) - end - end - - - def generate_method_info(cls_desc, method) - meth_desc = RI::MethodDescription.new - meth_desc.name = method.name - meth_desc.full_name = cls_desc.full_name - if method.singleton - meth_desc.full_name += "::" - else - meth_desc.full_name += "#" - end - meth_desc.full_name << method.name - - meth_desc.comment = markup(method.comment) - meth_desc.params = params_of(method) - meth_desc.visibility = method.visibility.to_s - meth_desc.is_singleton = method.singleton - meth_desc.block_params = method.block_params - - meth_desc.aliases = method.aliases.map do |a| - RI::AliasName.new(a.name) - end - - @ri_writer.add_method(cls_desc, meth_desc) - end - - private - - # return a list of class and instance methods that we'll be - # documenting - - def method_list(cls) - list = cls.method_list - unless @options.show_all - list = list.find_all do |m| - m.visibility == :public || m.visibility == :protected || m.force_documentation - end - end - - c = [] - i = [] - list.sort.each do |m| - if m.singleton - c << m - else - i << m - end - end - return c,i - end - - def params_of(method) - if method.call_seq - method.call_seq - else - params = method.params || "" - - p = params.gsub(/\s*\#.*/, '') - p = p.tr("\n", " ").squeeze(" ") - p = "(" + p + ")" unless p[0] == ?( - - if (block = method.block_params) - block.gsub!(/\s*\#.*/, '') - block = block.tr("\n", " ").squeeze(" ") - if block[0] == ?( - block.sub!(/^\(/, '').sub!(/\)/, '') - end - p << " {|#{block.strip}| ...}" - end - p - end - end - - def markup(comment) - return nil if !comment || comment.empty? - - # Convert leading comment markers to spaces, but only - # if all non-blank lines have them - - if comment =~ /^(?>\s*)[^\#]/ - content = comment - else - content = comment.gsub(/^\s*(#+)/) { $1.tr('#',' ') } - end - @markup.convert(content, @to_flow) - end - - - # By default we replace existing classes with the - # same name. If the --merge option was given, we instead - # merge this definition into an existing class. We add - # our methods, aliases, etc to that class, but do not - # change the class's description. - - def update_or_replace(cls_desc) - old_cls = nil - - if @options.merge - rdr = RI::RiReader.new(RI::RiCache.new(@options.op_dir)) - - namespace = rdr.top_level_namespace - namespace = rdr.lookup_namespace_in(cls_desc.name, namespace) - if namespace.empty? - $stderr.puts "You asked me to merge this source into existing " - $stderr.puts "documentation. This file references a class or " - $stderr.puts "module called #{cls_desc.name} which I don't" - $stderr.puts "have existing documentation for." - $stderr.puts - $stderr.puts "Perhaps you need to generate its documentation first" - exit 1 - else - old_cls = namespace[0] - end - end - - if old_cls.nil? - # no merge: simply overwrite - @ri_writer.remove_class(cls_desc) - @ri_writer.add_class(cls_desc) - else - # existing class: merge in - old_desc = rdr.get_class(old_cls) - - old_desc.merge_in(cls_desc) - @ri_writer.add_class(old_desc) - end - end - end -end diff --git a/lib/rdoc/generators/template/chm/chm.rb b/lib/rdoc/generators/template/chm/chm.rb deleted file mode 100644 index 4a89c26520..0000000000 --- a/lib/rdoc/generators/template/chm/chm.rb +++ /dev/null @@ -1,87 +0,0 @@ -module RDoc -module Page - -require "rdoc/generators/template/html/html" - -# This is a nasty little hack, but hhc doesn't support the <?xml -# tag, so... - -BODY.sub!(/<\?xml.*\?>/, '') -SRC_PAGE.sub!(/<\?xml.*\?>/, '') - -HPP_FILE = %{ -[OPTIONS] -Auto Index = Yes -Compatibility=1.1 or later -Compiled file=%opname%.chm -Contents file=contents.hhc -Full-text search=Yes -Index file=index.hhk -Language=0x409 English(United States) -Title=%title% - -[FILES] -START:all_html_files -%html_file_name% -END:all_html_files -} - -CONTENTS = %{ -<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML//EN"> -<HTML> -<HEAD> -<meta name="GENERATOR" content="Microsoft® HTML Help Workshop 4.1"> -<!-- Sitemap 1.0 --> -</HEAD><BODY> -<OBJECT type="text/site properties"> - <param name="Foreground" value="0x80"> - <param name="Window Styles" value="0x800025"> - <param name="ImageType" value="Folder"> -</OBJECT> -<UL> -START:contents - <LI> <OBJECT type="text/sitemap"> - <param name="Name" value="%c_name%"> - <param name="Local" value="%ref%"> - </OBJECT> -IF:methods -<ul> -START:methods - <LI> <OBJECT type="text/sitemap"> - <param name="Name" value="%name%"> - <param name="Local" value="%aref%"> - </OBJECT> -END:methods -</ul> -ENDIF:methods - </LI> -END:contents -</UL> -</BODY></HTML> -} - - -CHM_INDEX = %{ -<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML//EN"> -<HTML> -<HEAD> -<meta name="GENERATOR" content="Microsoft® HTML Help Workshop 4.1"> -<!-- Sitemap 1.0 --> -</HEAD><BODY> -<OBJECT type="text/site properties"> - <param name="Foreground" value="0x80"> - <param name="Window Styles" value="0x800025"> - <param name="ImageType" value="Folder"> -</OBJECT> -<UL> -START:index - <LI> <OBJECT type="text/sitemap"> - <param name="Name" value="%name%"> - <param name="Local" value="%aref%"> - </OBJECT> -END:index -</UL> -</BODY></HTML> -} -end -end diff --git a/lib/rdoc/generators/template/html/hefss.rb b/lib/rdoc/generators/template/html/hefss.rb deleted file mode 100644 index e68ca85823..0000000000 --- a/lib/rdoc/generators/template/html/hefss.rb +++ /dev/null @@ -1,418 +0,0 @@ -module RDoc -module Page - - -FONTS = "Verdana, Arial, Helvetica, sans-serif" - -STYLE = %{ -body,p { font-family: Verdana, Arial, Helvetica, sans-serif; - color: #000040; background: #BBBBBB; -} - -td { font-family: Verdana, Arial, Helvetica, sans-serif; - color: #000040; -} - -.attr-rw { font-size: small; color: #444488 } - -.title-row {color: #eeeeff; - background: #BBBBDD; -} - -.big-title-font { color: white; - font-family: Verdana, Arial, Helvetica, sans-serif; - font-size: large; - height: 50px} - -.small-title-font { color: purple; - font-family: Verdana, Arial, Helvetica, sans-serif; - font-size: small; } - -.aqua { color: purple } - -.method-name, attr-name { - font-family: monospace; font-weight: bold; -} - -.tablesubtitle { - width: 100%; - margin-top: 1ex; - margin-bottom: .5ex; - padding: 5px 0px 5px 20px; - font-size: large; - color: purple; - background: #BBBBCC; -} - -.tablesubsubtitle { - width: 100%; - margin-top: 1ex; - margin-bottom: .5ex; - padding: 5px 0px 5px 20px; - font-size: medium; - color: white; - background: #BBBBCC; -} - -.name-list { - font-family: monospace; - margin-left: 40px; - margin-bottom: 2ex; - line-height: 140%; -} - -.description { - margin-left: 40px; - margin-bottom: 2ex; - line-height: 140%; -} - -.methodtitle { - font-size: medium; - text_decoration: none; - padding: 3px 3px 3px 20px; - color: #0000AA; -} - -.column-title { - font-size: medium; - font-weight: bold; - text_decoration: none; - padding: 3px 3px 3px 20px; - color: #3333CC; - } - -.variable-name { - font-family: monospace; - font-size: medium; - text_decoration: none; - padding: 3px 3px 3px 20px; - color: #0000AA; -} - -.row-name { - font-size: medium; - font-weight: medium; - font-family: monospace; - text_decoration: none; - padding: 3px 3px 3px 20px; -} - -.paramsig { - font-size: small; -} - -.srcbut { float: right } - -} - - -############################################################################ - - -BODY = %{ -<html><head> - <title>%title%</title> - <meta http-equiv="Content-Type" content="text/html; charset=%charset%"> - <link rel="stylesheet" href="%style_url%" type="text/css" media="screen" /> - <script type="text/javascript" language="JavaScript"> - <!-- - function popCode(url) { - parent.frames.source.location = url - } - //--> - </script> -</head> -<body bgcolor="#BBBBBB"> - -!INCLUDE! <!-- banner header --> - -IF:diagram -<table width="100%"><tr><td align="center"> -%diagram% -</td></tr></table> -ENDIF:diagram - -IF:description -<div class="description">%description%</div> -ENDIF:description - -IF:requires -<table cellpadding="5" width="100%"> -<tr><td class="tablesubtitle">Required files</td></tr> -</table><br /> -<div class="name-list"> -START:requires -HREF:aref:name: -END:requires -ENDIF:requires -</div> - -IF:methods -<table cellpadding="5" width="100%"> -<tr><td class="tablesubtitle">Subroutines and Functions</td></tr> -</table><br /> -<div class="name-list"> -START:methods -HREF:aref:name:, -END:methods -</div> -ENDIF:methods - -IF:attributes -<table cellpadding="5" width="100%"> -<tr><td class="tablesubtitle">Arguments</td></tr> -</table><br /> -<table cellspacing="5"> -START:attributes - <tr valign="top"> -IF:rw - <td align="center" class="attr-rw"> [%rw%] </td> -ENDIF:rw -IFNOT:rw - <td></td> -ENDIF:rw - <td class="attr-name">%name%</td> - <td>%a_desc%</td> - </tr> -END:attributes -</table> -ENDIF:attributes - -IF:classlist -<table cellpadding="5" width="100%"> -<tr><td class="tablesubtitle">Modules</td></tr> -</table><br /> -%classlist%<br /> -ENDIF:classlist - - !INCLUDE! <!-- method descriptions --> - -</body> -</html> -} - -############################################################################### - -FILE_PAGE = <<_FILE_PAGE_ -<table width="100%"> - <tr class="title-row"> - <td><table width="100%"><tr> - <td class="big-title-font" colspan="2"><font size="-3"><b>File</b><br /></font>%short_name%</td> - <td align="right"><table cellspacing="0" cellpadding="2"> - <tr> - <td class="small-title-font">Path:</td> - <td class="small-title-font">%full_path% -IF:cvsurl - (<a href="%cvsurl%"><acronym title="Concurrent Versioning System">CVS</acronym></a>) -ENDIF:cvsurl - </td> - </tr> - <tr> - <td class="small-title-font">Modified:</td> - <td class="small-title-font">%dtm_modified%</td> - </tr> - </table> - </td></tr></table></td> - </tr> -</table><br /> -_FILE_PAGE_ - -################################################################### - -CLASS_PAGE = %{ -<table width="100%" border="0" cellspacing="0"> - <tr class="title-row"> - <td class="big-title-font"> - <font size="-3"><b>%classmod%</b><br /></font>%full_name% - </td> - <td align="right"> - <table cellspacing="0" cellpadding="2"> - <tr valign="top"> - <td class="small-title-font">In:</td> - <td class="small-title-font"> -START:infiles -HREF:full_path_url:full_path: -IF:cvsurl - (<a href="%cvsurl%"><acronym title="Concurrent Versioning System">CVS</acronym></a>) -ENDIF:cvsurl -END:infiles - </td> - </tr> -IF:parent - <tr> - <td class="small-title-font">Parent:</td> - <td class="small-title-font"> -IF:par_url - <a href="%par_url%" class="cyan"> -ENDIF:par_url -%parent% -IF:par_url - </a> -ENDIF:par_url - </td> - </tr> -ENDIF:parent - </table> - </td> - </tr> -</table><br /> -} - -################################################################### - -METHOD_LIST = %{ -IF:includes -<div class="tablesubsubtitle">Uses</div><br /> -<div class="name-list"> -START:includes - <span class="method-name">HREF:aref:name:</span> -END:includes -</div> -ENDIF:includes - -IF:method_list -START:method_list -IF:methods -<table cellpadding="5" width="100%"> -<tr><td class="tablesubtitle">%type% %category% methods</td></tr> -</table> -START:methods -<table width="100%" cellspacing="0" cellpadding="5" border="0"> -<tr><td class="methodtitle"> -<a name="%aref%"> -<b>%name%</b>%params% -IF:codeurl -<a href="%codeurl%" target="source" class="srclink">src</a> -ENDIF:codeurl -</a></td></tr> -</table> -IF:m_desc -<div class="description"> -%m_desc% -</div> -ENDIF:m_desc -END:methods -ENDIF:methods -END:method_list -ENDIF:method_list -} - -=begin -=end - -########################## Source code ########################## - -SRC_PAGE = %{ -<html> -<head><title>%title%</title> -<meta http-equiv="Content-Type" content="text/html; charset=%charset%"> -<style type="text/css"> - .kw { color: #3333FF; font-weight: bold } - .cmt { color: green; font-style: italic } - .str { color: #662222; font-style: italic } - .re { color: #662222; } -.ruby-comment { color: green; font-style: italic } -.ruby-constant { color: #4433aa; font-weight: bold; } -.ruby-identifier { color: #222222; } -.ruby-ivar { color: #2233dd; } -.ruby-keyword { color: #3333FF; font-weight: bold } -.ruby-node { color: #777777; } -.ruby-operator { color: #111111; } -.ruby-regexp { color: #662222; } -.ruby-value { color: #662222; font-style: italic } -</style> -</head> -<body bgcolor="#BBBBBB"> -<pre>%code%</pre> -</body> -</html> -} - -########################## Index ################################ - -FR_INDEX_BODY = %{ -!INCLUDE! -} - -FILE_INDEX = %{ -<html> -<head> -<meta http-equiv="Content-Type" content="text/html; charset=%charset%"> -<style type="text/css"> -<!-- - body { -background-color: #bbbbbb; - font-family: #{FONTS}; - font-size: 11px; - font-style: normal; - line-height: 14px; - color: #000040; - } -div.banner { - background: #bbbbcc; - color: white; - padding: 1; - margin: 0; - font-size: 90%; - font-weight: bold; - line-height: 1.1; - text-align: center; - width: 100%; -} - ---> -</style> -<base target="docwin"> -</head> -<body> -<div class="banner">%list_title%</div> -START:entries -<a href="%href%">%name%</a><br /> -END:entries -</body></html> -} - -CLASS_INDEX = FILE_INDEX -METHOD_INDEX = FILE_INDEX - -INDEX = %{ -<html> -<head> - <title>%title%</title> - <meta http-equiv="Content-Type" content="text/html; charset=%charset%"> -</head> - -<frameset cols="20%,*"> - <frameset rows="15%,35%,50%"> - <frame src="fr_file_index.html" title="Files" name="Files"> - <frame src="fr_class_index.html" name="Modules"> - <frame src="fr_method_index.html" name="Subroutines and Functions"> - </frameset> - <frameset rows="80%,20%"> - <frame src="%initial_page%" name="docwin"> - <frame src="blank.html" name="source"> - </frameset> - <noframes> - <body bgcolor="#BBBBBB"> - Click <a href="html/index.html">here</a> for a non-frames - version of this page. - </body> - </noframes> -</frameset> - -</html> -} - -# and a blank page to use as a target -BLANK = %{ -<html><body bgcolor="#BBBBBB"></body></html> -} - -def write_extra_pages - template = TemplatePage.new(BLANK) - File.open("blank.html", "w") { |f| template.write_html_on(f, {}) } -end - -end -end diff --git a/lib/rdoc/generators/template/html/html.rb b/lib/rdoc/generators/template/html/html.rb deleted file mode 100644 index 7f9e599465..0000000000 --- a/lib/rdoc/generators/template/html/html.rb +++ /dev/null @@ -1,711 +0,0 @@ -# -# = CSS2 RDoc HTML template -# -# This is a template for RDoc that uses XHTML 1.0 Transitional and dictates a -# bit more of the appearance of the output to cascading stylesheets than the -# default. It was designed for clean inline code display, and uses DHTMl to -# toggle the visbility of each method's source with each click on the '[source]' -# link. -# -# == Authors -# -# * Michael Granger <ged@FaerieMUD.org> -# -# Copyright (c) 2002, 2003 The FaerieMUD Consortium. Some rights reserved. -# -# This work is licensed under the Creative Commons Attribution License. To view -# a copy of this license, visit http://creativecommons.org/licenses/by/1.0/ or -# send a letter to Creative Commons, 559 Nathan Abbott Way, Stanford, California -# 94305, USA. -# - -module RDoc - module Page - - FONTS = "Verdana,Arial,Helvetica,sans-serif" - -STYLE = %{ -body { - font-family: Verdana,Arial,Helvetica,sans-serif; - font-size: 90%; - margin: 0; - margin-left: 40px; - padding: 0; - background: white; -} - -h1,h2,h3,h4 { margin: 0; color: #efefef; background: transparent; } -h1 { font-size: 150%; } -h2,h3,h4 { margin-top: 1em; } - -a { background: #eef; color: #039; text-decoration: none; } -a:hover { background: #039; color: #eef; } - -/* Override the base stylesheet's Anchor inside a table cell */ -td > a { - background: transparent; - color: #039; - text-decoration: none; -} - -/* and inside a section title */ -.section-title > a { - background: transparent; - color: #eee; - text-decoration: none; -} - -/* === Structural elements =================================== */ - -div#index { - margin: 0; - margin-left: -40px; - padding: 0; - font-size: 90%; -} - - -div#index a { - margin-left: 0.7em; -} - -div#index .section-bar { - margin-left: 0px; - padding-left: 0.7em; - background: #ccc; - font-size: small; -} - - -div#classHeader, div#fileHeader { - width: auto; - color: white; - padding: 0.5em 1.5em 0.5em 1.5em; - margin: 0; - margin-left: -40px; - border-bottom: 3px solid #006; -} - -div#classHeader a, div#fileHeader a { - background: inherit; - color: white; -} - -div#classHeader td, div#fileHeader td { - background: inherit; - color: white; -} - - -div#fileHeader { - background: #057; -} - -div#classHeader { - background: #048; -} - - -.class-name-in-header { - font-size: 180%; - font-weight: bold; -} - - -div#bodyContent { - padding: 0 1.5em 0 1.5em; -} - -div#description { - padding: 0.5em 1.5em; - background: #efefef; - border: 1px dotted #999; -} - -div#description h1,h2,h3,h4,h5,h6 { - color: #125;; - background: transparent; -} - -div#validator-badges { - text-align: center; -} -div#validator-badges img { border: 0; } - -div#copyright { - color: #333; - background: #efefef; - font: 0.75em sans-serif; - margin-top: 5em; - margin-bottom: 0; - padding: 0.5em 2em; -} - - -/* === Classes =================================== */ - -table.header-table { - color: white; - font-size: small; -} - -.type-note { - font-size: small; - color: #DEDEDE; -} - -.xxsection-bar { - background: #eee; - color: #333; - padding: 3px; -} - -.section-bar { - color: #333; - border-bottom: 1px solid #999; - margin-left: -20px; -} - - -.section-title { - background: #79a; - color: #eee; - padding: 3px; - margin-top: 2em; - margin-left: -30px; - border: 1px solid #999; -} - -.top-aligned-row { vertical-align: top } -.bottom-aligned-row { vertical-align: bottom } - -/* --- Context section classes ----------------------- */ - -.context-row { } -.context-item-name { font-family: monospace; font-weight: bold; color: black; } -.context-item-value { font-size: small; color: #448; } -.context-item-desc { color: #333; padding-left: 2em; } - -/* --- Method classes -------------------------- */ -.method-detail { - background: #efefef; - padding: 0; - margin-top: 0.5em; - margin-bottom: 1em; - border: 1px dotted #ccc; -} -.method-heading { - color: black; - background: #ccc; - border-bottom: 1px solid #666; - padding: 0.2em 0.5em 0 0.5em; -} -.method-signature { color: black; background: inherit; } -.method-name { font-weight: bold; } -.method-args { font-style: italic; } -.method-description { padding: 0 0.5em 0 0.5em; } - -/* --- Source code sections -------------------- */ - -a.source-toggle { font-size: 90%; } -div.method-source-code { - background: #262626; - color: #ffdead; - margin: 1em; - padding: 0.5em; - border: 1px dashed #999; - overflow: hidden; -} - -div.method-source-code pre { color: #ffdead; overflow: hidden; } - -/* --- Ruby keyword styles --------------------- */ - -.standalone-code { background: #221111; color: #ffdead; overflow: hidden; } - -.ruby-constant { color: #7fffd4; background: transparent; } -.ruby-keyword { color: #00ffff; background: transparent; } -.ruby-ivar { color: #eedd82; background: transparent; } -.ruby-operator { color: #00ffee; background: transparent; } -.ruby-identifier { color: #ffdead; background: transparent; } -.ruby-node { color: #ffa07a; background: transparent; } -.ruby-comment { color: #b22222; font-weight: bold; background: transparent; } -.ruby-regexp { color: #ffa07a; background: transparent; } -.ruby-value { color: #7fffd4; background: transparent; } -} - - -##################################################################### -### H E A D E R T E M P L A T E -##################################################################### - -XHTML_PREAMBLE = %{<?xml version="1.0" encoding="%charset%"?> -<!DOCTYPE html - PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" - "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> -} - -HEADER = XHTML_PREAMBLE + %{ -<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> -<head> - <title>%title%</title> - <meta http-equiv="Content-Type" content="text/html; charset=%charset%" /> - <meta http-equiv="Content-Script-Type" content="text/javascript" /> - <link rel="stylesheet" href="%style_url%" type="text/css" media="screen" /> - <script type="text/javascript"> - // <![CDATA[ - - function popupCode( url ) { - window.open(url, "Code", "resizable=yes,scrollbars=yes,toolbar=no,status=no,height=150,width=400") - } - - function toggleCode( id ) { - if ( document.getElementById ) - elem = document.getElementById( id ); - else if ( document.all ) - elem = eval( "document.all." + id ); - else - return false; - - elemStyle = elem.style; - - if ( elemStyle.display != "block" ) { - elemStyle.display = "block" - } else { - elemStyle.display = "none" - } - - return true; - } - - // Make codeblocks hidden by default - document.writeln( "<style type=\\"text/css\\">div.method-source-code { display: none }</style>" ) - - // ]]> - </script> - -</head> -<body> -} - - -##################################################################### -### C O N T E X T C O N T E N T T E M P L A T E -##################################################################### - -CONTEXT_CONTENT = %{ -} - - -##################################################################### -### F O O T E R T E M P L A T E -##################################################################### -FOOTER = %{ -<div id="validator-badges"> - <p><small><a href="http://validator.w3.org/check/referer">[Validate]</a></small></p> -</div> - -</body> -</html> -} - - -##################################################################### -### F I L E P A G E H E A D E R T E M P L A T E -##################################################################### - -FILE_PAGE = %{ - <div id="fileHeader"> - <h1>%short_name%</h1> - <table class="header-table"> - <tr class="top-aligned-row"> - <td><strong>Path:</strong></td> - <td>%full_path% -IF:cvsurl - (<a href="%cvsurl%"><acronym title="Concurrent Versioning System">CVS</acronym></a>) -ENDIF:cvsurl - </td> - </tr> - <tr class="top-aligned-row"> - <td><strong>Last Update:</strong></td> - <td>%dtm_modified%</td> - </tr> - </table> - </div> -} - - -##################################################################### -### C L A S S P A G E H E A D E R T E M P L A T E -##################################################################### - -CLASS_PAGE = %{ - <div id="classHeader"> - <table class="header-table"> - <tr class="top-aligned-row"> - <td><strong>%classmod%</strong></td> - <td class="class-name-in-header">%full_name%</td> - </tr> - <tr class="top-aligned-row"> - <td><strong>In:</strong></td> - <td> -START:infiles -IF:full_path_url - <a href="%full_path_url%"> -ENDIF:full_path_url - %full_path% -IF:full_path_url - </a> -ENDIF:full_path_url -IF:cvsurl - (<a href="%cvsurl%"><acronym title="Concurrent Versioning System">CVS</acronym></a>) -ENDIF:cvsurl - <br /> -END:infiles - </td> - </tr> - -IF:parent - <tr class="top-aligned-row"> - <td><strong>Parent:</strong></td> - <td> -IF:par_url - <a href="%par_url%"> -ENDIF:par_url - %parent% -IF:par_url - </a> -ENDIF:par_url - </td> - </tr> -ENDIF:parent - </table> - </div> -} - - -##################################################################### -### M E T H O D L I S T T E M P L A T E -##################################################################### - -METHOD_LIST = %{ - - <div id="contextContent"> -IF:diagram - <div id="diagram"> - %diagram% - </div> -ENDIF:diagram - -IF:description - <div id="description"> - %description% - </div> -ENDIF:description - -IF:requires - <div id="requires-list"> - <h3 class="section-bar">Required files</h3> - - <div class="name-list"> -START:requires - HREF:aref:name: -END:requires - </div> - </div> -ENDIF:requires - -IF:toc - <div id="contents-list"> - <h3 class="section-bar">Contents</h3> - <ul> -START:toc - <li><a href="#%href%">%secname%</a></li> -END:toc - </ul> -ENDIF:toc - </div> - -IF:methods - <div id="method-list"> - <h3 class="section-bar">Methods</h3> - - <div class="name-list"> -START:methods - HREF:aref:name: -END:methods - </div> - </div> -ENDIF:methods - - </div> - - - <!-- if includes --> -IF:includes - <div id="includes"> - <h3 class="section-bar">Included Modules</h3> - - <div id="includes-list"> -START:includes - <span class="include-name">HREF:aref:name:</span> -END:includes - </div> - </div> -ENDIF:includes - -START:sections - <div id="section"> -IF:sectitle - <h2 class="section-title"><a name="%secsequence%">%sectitle%</a></h2> -IF:seccomment - <div class="section-comment"> - %seccomment% - </div> -ENDIF:seccomment -ENDIF:sectitle - -IF:classlist - <div id="class-list"> - <h3 class="section-bar">Classes and Modules</h3> - - %classlist% - </div> -ENDIF:classlist - -IF:constants - <div id="constants-list"> - <h3 class="section-bar">Constants</h3> - - <div class="name-list"> - <table summary="Constants"> -START:constants - <tr class="top-aligned-row context-row"> - <td class="context-item-name">%name%</td> - <td>=</td> - <td class="context-item-value">%value%</td> -IF:desc - <td width="3em"> </td> - <td class="context-item-desc">%desc%</td> -ENDIF:desc - </tr> -END:constants - </table> - </div> - </div> -ENDIF:constants - -IF:aliases - <div id="aliases-list"> - <h3 class="section-bar">External Aliases</h3> - - <div class="name-list"> - <table summary="aliases"> -START:aliases - <tr class="top-aligned-row context-row"> - <td class="context-item-name">%old_name%</td> - <td>-></td> - <td class="context-item-value">%new_name%</td> - </tr> -IF:desc - <tr class="top-aligned-row context-row"> - <td> </td> - <td colspan="2" class="context-item-desc">%desc%</td> - </tr> -ENDIF:desc -END:aliases - </table> - </div> - </div> -ENDIF:aliases - - -IF:attributes - <div id="attribute-list"> - <h3 class="section-bar">Attributes</h3> - - <div class="name-list"> - <table> -START:attributes - <tr class="top-aligned-row context-row"> - <td class="context-item-name">%name%</td> -IF:rw - <td class="context-item-value"> [%rw%] </td> -ENDIF:rw -IFNOT:rw - <td class="context-item-value"> </td> -ENDIF:rw - <td class="context-item-desc">%a_desc%</td> - </tr> -END:attributes - </table> - </div> - </div> -ENDIF:attributes - - - - <!-- if method_list --> -IF:method_list - <div id="methods"> -START:method_list -IF:methods - <h3 class="section-bar">%type% %category% methods</h3> - -START:methods - <div id="method-%aref%" class="method-detail"> - <a name="%aref%"></a> - - <div class="method-heading"> -IF:codeurl - <a href="%codeurl%" target="Code" class="method-signature" - onclick="popupCode('%codeurl%');return false;"> -ENDIF:codeurl -IF:sourcecode - <a href="#%aref%" class="method-signature"> -ENDIF:sourcecode -IF:callseq - <span class="method-name">%callseq%</span> -ENDIF:callseq -IFNOT:callseq - <span class="method-name">%name%</span><span class="method-args">%params%</span> -ENDIF:callseq -IF:codeurl - </a> -ENDIF:codeurl -IF:sourcecode - </a> -ENDIF:sourcecode - </div> - - <div class="method-description"> -IF:m_desc - %m_desc% -ENDIF:m_desc -IF:sourcecode - <p><a class="source-toggle" href="#" - onclick="toggleCode('%aref%-source');return false;">[Source]</a></p> - <div class="method-source-code" id="%aref%-source"> -<pre> -%sourcecode% -</pre> - </div> -ENDIF:sourcecode - </div> - </div> - -END:methods -ENDIF:methods -END:method_list - - </div> -ENDIF:method_list -END:sections -} - - -##################################################################### -### B O D Y T E M P L A T E -##################################################################### - -BODY = HEADER + %{ - -!INCLUDE! <!-- banner header --> - - <div id="bodyContent"> - -} + METHOD_LIST + %{ - - </div> - -} + FOOTER - - - -##################################################################### -### S O U R C E C O D E T E M P L A T E -##################################################################### - -SRC_PAGE = XHTML_PREAMBLE + %{ -<html> -<head> - <title>%title%</title> - <meta http-equiv="Content-Type" content="text/html; charset=%charset%" /> - <link rel="stylesheet" href="%style_url%" type="text/css" media="screen" /> -</head> -<body class="standalone-code"> - <pre>%code%</pre> -</body> -</html> -} - - -##################################################################### -### I N D E X F I L E T E M P L A T E S -##################################################################### - -FR_INDEX_BODY = %{ -!INCLUDE! -} - -FILE_INDEX = XHTML_PREAMBLE + %{ -<!-- - - %list_title% - - --> -<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> -<head> - <title>%list_title%</title> - <meta http-equiv="Content-Type" content="text/html; charset=%charset%" /> - <link rel="stylesheet" href="%style_url%" type="text/css" /> - <base target="docwin" /> -</head> -<body> -<div id="index"> - <h1 class="section-bar">%list_title%</h1> - <div id="index-entries"> -START:entries - <a href="%href%">%name%</a><br /> -END:entries - </div> -</div> -</body> -</html> -} - -CLASS_INDEX = FILE_INDEX -METHOD_INDEX = FILE_INDEX - -INDEX = %{<?xml version="1.0" encoding="%charset%"?> -<!DOCTYPE html - PUBLIC "-//W3C//DTD XHTML 1.0 Frameset//EN" - "http://www.w3.org/TR/xhtml1/DTD/xhtml1-frameset.dtd"> - -<!-- - - %title% - - --> -<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> -<head> - <title>%title%</title> - <meta http-equiv="Content-Type" content="text/html; charset=%charset%" /> -</head> -<frameset rows="20%, 80%"> - <frameset cols="25%,35%,45%"> - <frame src="fr_file_index.html" title="Files" name="Files" /> - <frame src="fr_class_index.html" name="Classes" /> - <frame src="fr_method_index.html" name="Methods" /> - </frameset> - <frame src="%initial_page%" name="docwin" /> -</frameset> -</html> -} - - - - end # module Page -end # class RDoc - -require 'rdoc/generators/template/html/one_page_html' diff --git a/lib/rdoc/generators/template/html/kilmer.rb b/lib/rdoc/generators/template/html/kilmer.rb deleted file mode 100644 index 55071fc026..0000000000 --- a/lib/rdoc/generators/template/html/kilmer.rb +++ /dev/null @@ -1,435 +0,0 @@ -module RDoc -module Page - - -FONTS = "Verdana, Arial, Helvetica, sans-serif" - -STYLE = %{ -body,td,p { font-family: %fonts%; - color: #000040; -} - -.attr-rw { font-size: xx-small; color: #444488 } - -.title-row { background-color: #CCCCFF; - color: #000010; -} - -.big-title-font { - color: black; - font-weight: bold; - font-family: %fonts%; - font-size: large; - height: 60px; - padding: 10px 3px 10px 3px; -} - -.small-title-font { color: black; - font-family: %fonts%; - font-size:10; } - -.aqua { color: black } - -.method-name, .attr-name { - font-family: font-family: %fonts%; - font-weight: bold; - font-size: small; - margin-left: 20px; - color: #000033; -} - -.tablesubtitle, .tablesubsubtitle { - width: 100%; - margin-top: 1ex; - margin-bottom: .5ex; - padding: 5px 0px 5px 3px; - font-size: large; - color: black; - background-color: #CCCCFF; - border: thin; -} - -.name-list { - margin-left: 5px; - margin-bottom: 2ex; - line-height: 105%; -} - -.description { - margin-left: 5px; - margin-bottom: 2ex; - line-height: 105%; - font-size: small; -} - -.methodtitle { - font-size: small; - font-weight: bold; - text-decoration: none; - color: #000033; - background-color: white; -} - -.srclink { - font-size: small; - font-weight: bold; - text-decoration: none; - color: #0000DD; - background-color: white; -} - -.paramsig { - font-size: small; -} - -.srcbut { float: right } - -} - - -############################################################################ - - -BODY = %{ -<html><head> - <title>%title%</title> - <meta http-equiv="Content-Type" content="text/html; charset=%charset%"> - <link rel="stylesheet" href="%style_url%" type="text/css" media="screen" /> - <script type="text/javascript" language="JavaScript"> - <!-- - function popCode(url) { - parent.frames.source.location = url - } - //--> - </script> -</head> -<body bgcolor="white"> - -!INCLUDE! <!-- banner header --> - -IF:diagram -<table width="100%"><tr><td align="center"> -%diagram% -</td></tr></table> -ENDIF:diagram - -IF:description -<div class="description">%description%</div> -ENDIF:description - -IF:requires -<table cellpadding="5" width="100%"> -<tr><td class="tablesubtitle">Required files</td></tr> -</table><br /> -<div class="name-list"> -START:requires -HREF:aref:name: -END:requires -ENDIF:requires -</div> - -IF:methods -<table cellpadding="5" width="100%"> -<tr><td class="tablesubtitle">Methods</td></tr> -</table><br /> -<div class="name-list"> -START:methods -HREF:aref:name:, -END:methods -</div> -ENDIF:methods - - -START:sections - <div id="section"> -IF:sectitle - <h2 class="section-title"><a name="%secsequence%">%sectitle%</a></h2> -IF:seccomment - <div class="section-comment"> - %seccomment% - </div> -ENDIF:seccomment -ENDIF:sectitle - -IF:attributes -<table cellpadding="5" width="100%"> -<tr><td class="tablesubtitle">Attributes</td></tr> -</table><br /> -<table cellspacing="5"> -START:attributes - <tr valign="top"> -IF:rw - <td align="center" class="attr-rw"> [%rw%] </td> -ENDIF:rw -IFNOT:rw - <td></td> -ENDIF:rw - <td class="attr-name">%name%</td> - <td>%a_desc%</td> - </tr> -END:attributes -</table> -ENDIF:attributes - -IF:classlist -<table cellpadding="5" width="100%"> -<tr><td class="tablesubtitle">Classes and Modules</td></tr> -</table><br /> -%classlist%<br /> -ENDIF:classlist - - !INCLUDE! <!-- method descriptions --> - -END:sections - -</body> -</html> -} - -############################################################################### - -FILE_PAGE = <<_FILE_PAGE_ -<table width="100%"> - <tr class="title-row"> - <td><table width="100%"><tr> - <td class="big-title-font" colspan="2"><font size="-3"><b>File</b><br /></font>%short_name%</td> - <td align="right"><table cellspacing="0" cellpadding="2"> - <tr> - <td class="small-title-font">Path:</td> - <td class="small-title-font">%full_path% -IF:cvsurl - (<a href="%cvsurl%"><acronym title="Concurrent Versioning System">CVS</acronym></a>) -ENDIF:cvsurl - </td> - </tr> - <tr> - <td class="small-title-font">Modified:</td> - <td class="small-title-font">%dtm_modified%</td> - </tr> - </table> - </td></tr></table></td> - </tr> -</table><br /> -_FILE_PAGE_ - -################################################################### - -CLASS_PAGE = %{ -<table width="100%" border="0" cellspacing="0"> - <tr class="title-row"> - <td class="big-title-font"> - <font size="-3"><b>%classmod%</b><br /></font>%full_name% - </td> - <td align="right"> - <table cellspacing="0" cellpadding="2"> - <tr valign="top"> - <td class="small-title-font">In:</td> - <td class="small-title-font"> -START:infiles -HREF:full_path_url:full_path: -IF:cvsurl - (<a href="%cvsurl%"><acronym title="Concurrent Versioning System">CVS</acronym></a>) -ENDIF:cvsurl -END:infiles - </td> - </tr> -IF:parent - <tr> - <td class="small-title-font">Parent:</td> - <td class="small-title-font"> -IF:par_url - <a href="%par_url%" class="cyan"> -ENDIF:par_url -%parent% -IF:par_url - </a> -ENDIF:par_url - </td> - </tr> -ENDIF:parent - </table> - </td> - </tr> -</table><br /> -} - -################################################################### - -METHOD_LIST = %{ -IF:includes -<div class="tablesubsubtitle">Included modules</div><br /> -<div class="name-list"> -START:includes - <span class="method-name">HREF:aref:name:</span> -END:includes -</div> -ENDIF:includes - -IF:method_list -START:method_list -IF:methods -<table cellpadding=5 width="100%"> -<tr><td class="tablesubtitle">%type% %category% methods</td></tr> -</table> -START:methods -<table width="100%" cellspacing="0" cellpadding="5" border="0"> -<tr><td class="methodtitle"> -<a name="%aref%"> -IF:callseq -<b>%callseq%</b> -ENDIF:callseq -IFNOT:callseq - <b>%name%</b>%params% -ENDIF:callseq -IF:codeurl -<a href="%codeurl%" target="source" class="srclink">src</a> -ENDIF:codeurl -</a></td></tr> -</table> -IF:m_desc -<div class="description"> -%m_desc% -</div> -ENDIF:m_desc -IF:aka -<div class="aka"> -This method is also aliased as -START:aka -<a href="%aref%">%name%</a> -END:aka -</div> -ENDIF:aka -IF:sourcecode -<pre class="source"> -%sourcecode% -</pre> -ENDIF:sourcecode -END:methods -ENDIF:methods -END:method_list -ENDIF:method_list -} - -=begin -=end - -########################## Source code ########################## - -SRC_PAGE = %{ -<html> -<head><title>%title%</title> -<meta http-equiv="Content-Type" content="text/html; charset=%charset%"> -<style type="text/css"> -.ruby-comment { color: green; font-style: italic } -.ruby-constant { color: #4433aa; font-weight: bold; } -.ruby-identifier { color: #222222; } -.ruby-ivar { color: #2233dd; } -.ruby-keyword { color: #3333FF; font-weight: bold } -.ruby-node { color: #777777; } -.ruby-operator { color: #111111; } -.ruby-regexp { color: #662222; } -.ruby-value { color: #662222; font-style: italic } - .kw { color: #3333FF; font-weight: bold } - .cmt { color: green; font-style: italic } - .str { color: #662222; font-style: italic } - .re { color: #662222; } -</style> -</head> -<body bgcolor="white"> -<pre>%code%</pre> -</body> -</html> -} - -########################## Index ################################ - -FR_INDEX_BODY = %{ -!INCLUDE! -} - -FILE_INDEX = %{ -<html> -<head> -<meta http-equiv="Content-Type" content="text/html; charset=%charset%"> -<style> -<!-- - body { -background-color: #ddddff; - font-family: #{FONTS}; - font-size: 11px; - font-style: normal; - line-height: 14px; - color: #000040; - } -div.banner { - background: #0000aa; - color: white; - padding: 1; - margin: 0; - font-size: 90%; - font-weight: bold; - line-height: 1.1; - text-align: center; - width: 100%; -} - ---> -</style> -<base target="docwin"> -</head> -<body> -<div class="banner">%list_title%</div> -START:entries -<a href="%href%">%name%</a><br /> -END:entries -</body></html> -} - -CLASS_INDEX = FILE_INDEX -METHOD_INDEX = FILE_INDEX - -INDEX = %{ -<html> -<head> - <title>%title%</title> - <meta http-equiv="Content-Type" content="text/html; charset=%charset%"> -</head> - -<frameset cols="20%,*"> - <frameset rows="15%,35%,50%"> - <frame src="fr_file_index.html" title="Files" name="Files"> - <frame src="fr_class_index.html" name="Classes"> - <frame src="fr_method_index.html" name="Methods"> - </frameset> -IF:inline_source - <frame src="%initial_page%" name="docwin"> -ENDIF:inline_source -IFNOT:inline_source - <frameset rows="80%,20%"> - <frame src="%initial_page%" name="docwin"> - <frame src="blank.html" name="source"> - </frameset> -ENDIF:inline_source - <noframes> - <body bgcolor="white"> - Click <a href="html/index.html">here</a> for a non-frames - version of this page. - </body> - </noframes> -</frameset> - -</html> -} - -# and a blank page to use as a target -BLANK = %{ -<html><body bgcolor="white"></body></html> -} - -def write_extra_pages - template = TemplatePage.new(BLANK) - File.open("blank.html", "w") { |f| template.write_html_on(f, {}) } -end - -end -end diff --git a/lib/rdoc/generators/template/html/old_html.rb b/lib/rdoc/generators/template/html/old_html.rb deleted file mode 100644 index ca66302a08..0000000000 --- a/lib/rdoc/generators/template/html/old_html.rb +++ /dev/null @@ -1,728 +0,0 @@ -module RDoc - -# This is how you define the HTML that RDoc generates. Simply create -# a file in rdoc/generators/html_templates that creates the -# module RDoc::Page and populate it as described below. Then invoke -# rdoc using the --template <name of your file> option, and -# your template will be used. -# -# The constants defining pages use a simple templating system: -# -# * The templating system is passed a hash. Keys in the hash correspond -# to tags on this page. The tag %abc% is looked up in the hash, -# and is replaced by the corresponding hash value. -# -# * Some tags are optional. You can detect this using IF/ENDIF -# -# IF: title -# The value of title is %title% -# ENDIF: title -# -# * Some entries in the hash have values that are arrays, where each -# entry in the array is itself a hash. These are used to generate -# lists using the START: construct. For example, given a hash -# containing -# -# { 'people' => [ { 'name' => 'Fred', 'age' => '12' }, -# { 'name' => 'Mary', 'age' => '21' } ] -# -# You could generate a simple table using -# -# <table> -# START:people -# <tr><td>%name%<td>%age%</tr> -# END:people -# </table> -# -# These lists can be nested to an arbitrary depth -# -# * the construct HREF:url:name: generates <a href="%url%">%name%</a> -# if +url+ is defined in the hash, or %name% otherwise. -# -# -# Your file must contain the following constants -# -# [*FONTS*] a list of fonts to be used -# [*STYLE*] a CSS section (without the <style> or comments). This is -# used to generate a style.css file -# -# [*BODY*] -# The main body of all non-index RDoc pages. BODY will contain -# two !INCLUDE!s. The first is used to include a document-type -# specific header (FILE_PAGE or CLASS_PAGE). The second include -# is for the method list (METHOD_LIST). THe body is passed: -# -# %title%:: -# the page's title -# -# %style_url%:: -# the url of a style sheet for this page -# -# %diagram%:: -# the optional URL of a diagram for this page -# -# %description%:: -# a (potentially multi-paragraph) string containing the -# description for th file/class/module. -# -# %requires%:: -# an optional list of %aref%/%name% pairs, one for each module -# required by this file. -# -# %methods%:: -# an optional list of %aref%/%name%, one for each method -# documented on this page. This is intended to be an index. -# -# %attributes%:: -# An optional list. For each attribute it contains: -# %name%:: the attribute name -# %rw%:: r/o, w/o, or r/w -# %a_desc%:: description of the attribute -# -# %classlist%:: -# An optional string containing an already-formatted list of -# classes and modules documented in this file -# -# For FILE_PAGE entries, the body will be passed -# -# %short_name%:: -# The name of the file -# -# %full_path%:: -# The full path to the file -# -# %dtm_modified%:: -# The date/time the file was last changed -# -# For class and module pages, the body will be passed -# -# %classmod%:: -# The name of the class or module -# -# %files%:: -# A list. For each file this class is defined in, it contains: -# %full_path_url%:: an (optional) URL of the RDoc page -# for this file -# %full_path%:: the name of the file -# -# %par_url%:: -# The (optional) URL of the RDoc page documenting this class's -# parent class -# -# %parent%:: -# The name of this class's parent. -# -# For both files and classes, the body is passed the following information -# on includes and methods: -# -# %includes%:: -# Optional list of included modules. For each, it receives -# %aref%:: optional URL to RDoc page for the module -# %name%:: the name of the module -# -# %method_list%:: -# Optional list of methods of a particular class and category. -# -# Each method list entry contains: -# -# %type%:: public/private/protected -# %category%:: instance/class -# %methods%:: a list of method descriptions -# -# Each method description contains: -# -# %aref%:: a target aref, used when referencing this method -# description. You should code this as <a name="%aref%"> -# %codeurl%:: the optional URL to the page containing this method's -# source code. -# %name%:: the method's name -# %params%:: the method's parameters -# %callseq%:: a full calling sequence -# %m_desc%:: the (potentially multi-paragraph) description of -# this method. -# -# [*CLASS_PAGE*] -# Header for pages documenting classes and modules. See -# BODY above for the available parameters. -# -# [*FILE_PAGE*] -# Header for pages documenting files. See -# BODY above for the available parameters. -# -# [*METHOD_LIST*] -# Controls the display of the listing of methods. See BODY for -# parameters. -# -# [*INDEX*] -# The top-level index page. For a browser-like environment -# define a frame set that includes the file, class, and -# method indices. Passed -# %title%:: title of page -# %initial_page% :: url of initial page to display -# -# [*CLASS_INDEX*] -# Individual files for the three indexes. Passed: -# %index_url%:: URL of main index page -# %entries%:: List of -# %name%:: name of an index entry -# %href%:: url of corresponding page -# [*METHOD_INDEX*] -# Same as CLASS_INDEX for methods -# -# [*FILE_INDEX*] -# Same as CLASS_INDEX for methods -# -# [*FR_INDEX_BODY*] -# A wrapper around CLASS_INDEX, METHOD_INDEX, and FILE_INDEX. -# If those index strings contain the complete HTML for the -# output, then FR_INDEX_BODY can simply be !INCLUDE! -# -# [*SRC_PAGE*] -# Page used to display source code. Passed %title% and %code%, -# the latter being a multi-line string of code. - -module Page - -FONTS = "Verdana, Arial, Helvetica, sans-serif" - -STYLE = %{ -body,td,p { font-family: %fonts%; - color: #000040; -} - -.attr-rw { font-size: x-small; color: #444488 } - -.title-row { background: #0000aa; - color: #eeeeff; -} - -.big-title-font { color: white; - font-family: %fonts%; - font-size: large; - height: 50px} - -.small-title-font { color: aqua; - font-family: %fonts%; - font-size: xx-small; } - -.aqua { color: aqua } - -.method-name, attr-name { - font-family: monospace; font-weight: bold; -} - -.tablesubtitle, .tablesubsubtitle { - width: 100%; - margin-top: 1ex; - margin-bottom: .5ex; - padding: 5px 0px 5px 20px; - font-size: large; - color: aqua; - background: #3333cc; -} - -.name-list { - font-family: monospace; - margin-left: 40px; - margin-bottom: 2ex; - line-height: 140%; -} - -.description { - margin-left: 40px; - margin-top: -2ex; - margin-bottom: 2ex; -} - -.description p { - line-height: 140%; -} - -.aka { - margin-left: 40px; - margin-bottom: 2ex; - line-height: 100%; - font-size: small; - color: #808080; -} - -.methodtitle { - font-size: medium; - text-decoration: none; - color: #0000AA; - background: white; -} - -.paramsig { - font-size: small; -} - -.srcbut { float: right } - -pre { font-size: 1.2em; } -tt { font-size: 1.2em; } - -pre.source { - border-style: groove; - background-color: #ddddff; - margin-left: 40px; - padding: 1em 0em 1em 2em; -} - -.classlist { - margin-left: 40px; - margin-bottom: 2ex; - line-height: 140%; -} - -li { - display: list-item; - margin-top: .6em; -} - -.ruby-comment { color: green; font-style: italic } -.ruby-constant { color: #4433aa; font-weight: bold; } -.ruby-identifier { color: #222222; } -.ruby-ivar { color: #2233dd; } -.ruby-keyword { color: #3333FF; font-weight: bold } -.ruby-node { color: #777777; } -.ruby-operator { color: #111111; } -.ruby-regexp { color: #662222; } -.ruby-value { color: #662222; font-style: italic } - -} - - -############################################################################ - - -HEADER = %{ -<?xml version="1.0" encoding="utf-8"?> -<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" -"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> -<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> -<head> - <title>%title%</title> - <meta http-equiv="Content-Type" content="text/html; charset=%charset%" /> - <link rel=StyleSheet href="%style_url%" type="text/css" media="screen" /> - <script type="text/javascript" language="JavaScript"> - <!-- - function popCode(url) { - window.open(url, "Code", - "resizable=yes,scrollbars=yes,toolbar=no,status=no,height=150,width=400") - } - //--> - </script> -</head> -} - - -################################################################### - -METHOD_LIST = %{ -IF:includes -<table summary="Included modules" cellpadding="5" width="100%"> -<tr><td class="tablesubtitle">Included modules</td></tr> -</table> -<div class="name-list"> -START:includes - <span class="method-name">HREF:aref:name:</span> -END:includes -</div> -ENDIF:includes - -IF:method_list -START:method_list -IF:methods -<table summary="Method list" cellpadding="5" width="100%"> -<tr><td class="tablesubtitle">%type% %category% methods</td></tr> -</table> -START:methods -<table summary="method" width="100%" cellspacing="0" cellpadding="5" border="0"> -<tr><td class="methodtitle"> -<a name="%aref%"></a> -IF:codeurl -<a href="%codeurl%" target="Code" class="methodtitle" - onClick="popCode('%codeurl%');return false;"> -ENDIF:codeurl -IF:callseq -<b>%callseq%</b> -ENDIF:callseq -IFNOT:callseq -<b>%name%</b>%params% -ENDIF:callseq -IF:codeurl -</a> -ENDIF:codeurl -</td></tr> -</table> -IF:m_desc -<div class="description"> -%m_desc% -</div> -ENDIF:m_desc -IF:aka -<div class="aka"> -This method is also aliased as -START:aka -<a href="%aref%">%name%</a> -END:aka -</div> -ENDIF:aka -IF:sourcecode -<pre class="source"> -%sourcecode% -</pre> -ENDIF:sourcecode -END:methods -ENDIF:methods -END:method_list -ENDIF:method_list -} - -################################################################### - -CONTEXT_CONTENT = %{ -IF:diagram -<table summary="Diagram of classes and modules" width="100%"> -<tr><td align="center"> -%diagram% -</td></tr></table> -ENDIF:diagram - - -IF:description -<div class="description">%description%</div> -ENDIF:description - -IF:requires -<table summary="Requires" cellpadding="5" width="100%"> -<tr><td class="tablesubtitle">Required files</td></tr> -</table> -<div class="name-list"> -START:requires -HREF:aref:name: -END:requires -</div> -ENDIF:requires - -IF:methods -<table summary="Methods" cellpadding="5" width="100%"> -<tr><td class="tablesubtitle">Methods</td></tr> -</table> -<div class="name-list"> -START:methods -HREF:aref:name: -END:methods -</div> -ENDIF:methods - -IF:constants -<table summary="Constants" cellpadding="5" width="100%"> -<tr><td class="tablesubtitle">Constants</td></tr> -</table> -<table cellpadding="5"> -START:constants -<tr valign="top"><td>%name%</td><td>=</td><td>%value%</td></tr> -IF:desc -<tr><td></td><td></td><td>%desc%</td></tr> -ENDIF:desc -END:constants -</table> -ENDIF:constants - -IF:aliases -<table summary="Aliases" cellpadding="5" width="100%"> -<tr><td class="tablesubtitle">External Aliases</td></tr> -</table> -<div class="name-list"> -START:aliases -%old_name% -> %new_name%<br /> -END:aliases -</div> -ENDIF:aliases - -IF:attributes -<table summary="Attributes" cellpadding="5" width="100%"> -<tr><td class="tablesubtitle">Attributes</td></tr> -</table> -<table summary="Attribute details" cellspacing="5"> -START:attributes - <tr valign="top"> - <td class="attr-name">%name%</td> -IF:rw - <td align="center" class="attr-rw"> [%rw%] </td> -ENDIF:rw -IFNOT:rw - <td></td> -ENDIF:rw - <td>%a_desc%</td> - </tr> -END:attributes -</table> -ENDIF:attributes - -IF:classlist -<table summary="List of classes" cellpadding="5" width="100%"> -<tr><td class="tablesubtitle">Classes and Modules</td></tr> -</table> -<div class="classlist"> -%classlist% -</div> -ENDIF:classlist -} - -############################################################################### - -BODY = HEADER + %{ -<body bgcolor="white"> -!INCLUDE! <!-- banner header --> -} + -CONTEXT_CONTENT + METHOD_LIST + -%{ -</body> -</html> -} - - -############################################################################### - -FILE_PAGE = <<_FILE_PAGE_ -<table summary="Information on file" width="100%"> - <tr class="title-row"> - <td><table summary="layout" width="100%"><tr> - <td class="big-title-font" colspan="2">%short_name%</td> - <td align="right"><table summary="layout" cellspacing="0" cellpadding="2"> - <tr> - <td class="small-title-font">Path:</td> - <td class="small-title-font">%full_path% -IF:cvsurl - (<a href="%cvsurl%"><acronym title="Concurrent Versioning System">CVS</acronym></a>) -ENDIF:cvsurl - </td> - </tr> - <tr> - <td class="small-title-font">Modified:</td> - <td class="small-title-font">%dtm_modified%</td> - </tr> - </table> - </td></tr></table></td> - </tr> -</table> -_FILE_PAGE_ - -################################################################### - -CLASS_PAGE = %{ -<table summary="Information on class" width="100%" border="0" cellspacing="0"> - <tr class="title-row"> - <td class="big-title-font"> - <sup><font color="aqua">%classmod%</font></sup> %full_name% - </td> - <td align="right"> - <table summary="layout" cellspacing="0" cellpadding="2"> - <tr valign="top"> - <td class="small-title-font">In:</td> - <td class="small-title-font"> -START:infiles -IF:full_path_url - <a href="%full_path_url%" class="aqua"> -ENDIF:full_path_url -%full_path% -IF:full_path_url - </a> -ENDIF:full_path_url -IF:cvsurl - (<a href="%cvsurl%"><acronym title="Concurrent Versioning System">CVS</acronym></a>) -ENDIF:cvsurl -<br /> -END:infiles - </td> - </tr> -IF:parent - <tr> - <td class="small-title-font">Parent:</td> - <td class="small-title-font"> -IF:par_url - <a href="%par_url%" class="aqua"> -ENDIF:par_url -%parent% -IF:par_url - </a> -ENDIF:par_url - </td> - </tr> -ENDIF:parent - </table> - </td> - </tr> -</table> -} - -=begin -=end - -########################## Source code ########################## - -SRC_PAGE = %{ -<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> -<html> -<head> -<meta http-equiv="Content-Type" content="text/html; charset=%charset%"> -<title>%title%</title> -<link rel="stylesheet" href="%style_url%" type="text/css" media="screen" /> -</head> -<body bgcolor="white"> -<pre>%code%</pre> -</body> -</html> -} - -########################## Index ################################ - -FR_INDEX_BODY = %{ -!INCLUDE! -} - -FILE_INDEX = %{ -<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> -<html> -<head> -<meta http-equiv="Content-Type" content="text/html; charset=%charset%"> -<title>%list_title%</title> -<style type="text/css"> -<!-- - body { -background-color: #ddddff; - font-family: #{FONTS}; - font-size: 11px; - font-style: normal; - line-height: 14px; - color: #000040; - } -div.banner { - background: #0000aa; - color: white; - padding: 1; - margin: 0; - font-size: 90%; - font-weight: bold; - line-height: 1.1; - text-align: center; - width: 100%; -} - -A.xx { color: white; font-weight: bold; } ---> -</style> -<base target="docwin"> -</head> -<body> -<div class="banner"><a href="%index_url%" class="xx">%list_title%</a></div> -START:entries -<a href="%href%">%name%</a><br /> -END:entries -</body></html> -} - -CLASS_INDEX = FILE_INDEX -METHOD_INDEX = FILE_INDEX - -INDEX = %{ -<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Frameset//EN"> -<html> -<head> -<meta http-equiv="Content-Type" content="text/html; charset=%charset%"> -<title>%title%</title></head> - -<frameset rows="20%, 80%"> - <frameset cols="25%,35%,45%"> - <frame src="fr_file_index.html" title="Files" name="Files"> - <frame src="fr_class_index.html" name="Classes"> - <frame src="fr_method_index.html" name="Methods"> - </frameset> - <frame src="%initial_page%" name="docwin"> - <noframes> - <body bgcolor="white"> - Sorry, RDoc currently only generates HTML using frames. - </body> - </noframes> -</frameset> - -</html> -} - -###################################################################### -# -# The following is used for the -1 option -# - -CONTENTS_XML = %{ -IF:description -%description% -ENDIF:description - -IF:requires -<h4>Requires:</h4> -<ul> -START:requires -IF:aref -<li><a href="%aref%">%name%</a></li> -ENDIF:aref -IFNOT:aref -<li>%name%</li> -ENDIF:aref -END:requires -</ul> -ENDIF:requires - -IF:attributes -<h4>Attributes</h4> -<table> -START:attributes -<tr><td>%name%</td><td>%rw%</td><td>%a_desc%</td></tr> -END:attributes -</table> -ENDIF:attributes - -IF:includes -<h4>Includes</h4> -<ul> -START:includes -IF:aref -<li><a href="%aref%">%name%</a></li> -ENDIF:aref -IFNOT:aref -<li>%name%</li> -ENDIF:aref -END:includes -</ul> -ENDIF:includes - -IF:method_list -<h3>Methods</h3> -START:method_list -IF:methods -START:methods -<h4>%type% %category% method: <a name="%aref%">%name%%params%</a></h4> - -IF:m_desc -%m_desc% -ENDIF:m_desc - -IF:sourcecode -<blockquote><pre> -%sourcecode% -</pre></blockquote> -ENDIF:sourcecode -END:methods -ENDIF:methods -END:method_list -ENDIF:method_list -} - - -end -end - -require 'rdoc/generators/template/html/one_page_html' diff --git a/lib/rdoc/generators/template/html/one_page_html.rb b/lib/rdoc/generators/template/html/one_page_html.rb deleted file mode 100644 index 19441f4725..0000000000 --- a/lib/rdoc/generators/template/html/one_page_html.rb +++ /dev/null @@ -1,122 +0,0 @@ -module RDoc -module Page -###################################################################### -# -# The following is used for the -1 option -# - -CONTENTS_XML = %{ -IF:description -%description% -ENDIF:description - -IF:requires -<h4>Requires:</h4> -<ul> -START:requires -IF:aref -<li><a href="%aref%">%name%</a></li> -ENDIF:aref -IFNOT:aref -<li>%name%</li> -ENDIF:aref -END:requires -</ul> -ENDIF:requires - -IF:attributes -<h4>Attributes</h4> -<table> -START:attributes -<tr><td>%name%</td><td>%rw%</td><td>%a_desc%</td></tr> -END:attributes -</table> -ENDIF:attributes - -IF:includes -<h4>Includes</h4> -<ul> -START:includes -IF:aref -<li><a href="%aref%">%name%</a></li> -ENDIF:aref -IFNOT:aref -<li>%name%</li> -ENDIF:aref -END:includes -</ul> -ENDIF:includes - -IF:method_list -<h3>Methods</h3> -START:method_list -IF:methods -START:methods -<h4>%type% %category% method: -IF:callseq -<a name="%aref%">%callseq%</a> -ENDIF:callseq -IFNOT:callseq -<a name="%aref%">%name%%params%</a></h4> -ENDIF:callseq - -IF:m_desc -%m_desc% -ENDIF:m_desc - -IF:sourcecode -<blockquote><pre> -%sourcecode% -</pre></blockquote> -ENDIF:sourcecode -END:methods -ENDIF:methods -END:method_list -ENDIF:method_list -} - -######################################################################## - -ONE_PAGE = %{ -<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> -<html> -<head> - <title>%title%</title> - <meta http-equiv="Content-Type" content="text/html; charset=%charset%" /> -</head> -<body> -START:files -<h2>File: %short_name%</h2> -<table> - <tr><td>Path:</td><td>%full_path%</td></tr> - <tr><td>Modified:</td><td>%dtm_modified%</td></tr> -</table> -} + CONTENTS_XML + %{ -END:files - -IF:classes -<h2>Classes</h2> -START:classes -IF:parent -<h3>%classmod% %full_name% < HREF:par_url:parent:</h3> -ENDIF:parent -IFNOT:parent -<h3>%classmod% %full_name%</h3> -ENDIF:parent - -IF:infiles -(in files -START:infiles -HREF:full_path_url:full_path: -END:infiles -) -ENDIF:infiles -} + CONTENTS_XML + %{ -END:classes -ENDIF:classes -</body> -</html> -} - -end -end diff --git a/lib/rdoc/generators/template/xml/rdf.rb b/lib/rdoc/generators/template/xml/rdf.rb deleted file mode 100644 index 1545d81a2f..0000000000 --- a/lib/rdoc/generators/template/xml/rdf.rb +++ /dev/null @@ -1,112 +0,0 @@ -module RDoc -module Page - - - -CONTENTS_RDF = %{ -IF:description - <description rd:parseType="Literal"> -%description% - </description> -ENDIF:description - -IF:requires -START:requires - <rd:required-file rd:name="%name%" /> -END:requires -ENDIF:requires - -IF:attributes -START:attributes - <contents> - <Attribute rd:name="%name%"> -IF:rw - <attribute-rw>%rw%</attribute-rw> -ENDIF:rw - <description rdf:parseType="Literal">%a_desc%</description> - </Attribute> - </contents> -END:attributes -ENDIF:attributes - -IF:includes - <IncludedModuleList> -START:includes - <included-module rd:name="%name%" /> -END:includes - </IncludedModuleList> -ENDIF:includes - -IF:method_list -START:method_list -IF:methods -START:methods - <contents> - <Method rd:name="%name%" rd:visibility="%type%" - rd:category="%category%" rd:id="%aref%"> - <parameters>%params%</parameters> -IF:m_desc - <description rdf:parseType="Literal"> -%m_desc% - </description> -ENDIF:m_desc -IF:sourcecode - <source-code-listing rdf:parseType="Literal"> -%sourcecode% - </source-code-listing> -ENDIF:sourcecode - </Method> - </contents> -END:methods -ENDIF:methods -END:method_list -ENDIF:method_list - <!-- end method list --> -} - -######################################################################## - -ONE_PAGE = %{<?xml version="1.0" encoding="utf-8"?> -<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" - xmlns="http://pragprog.com/rdoc/rdoc.rdf#" - xmlns:rd="http://pragprog.com/rdoc/rdoc.rdf#"> - -<!-- RDoc --> -START:files - <rd:File rd:name="%short_name%" rd:id="%href%"> - <path>%full_path%</path> - <dtm-modified>%dtm_modified%</dtm-modified> -} + CONTENTS_RDF + %{ - </rd:File> -END:files -START:classes - <%classmod% rd:name="%full_name%" rd:id="%full_name%"> - <classmod-info> -IF:infiles - <InFiles> -START:infiles - <infile> - <File rd:name="%full_path%" -IF:full_path_url - rdf:about="%full_path_url%" -ENDIF:full_path_url - /> - </infile> -END:infiles - </InFiles> -ENDIF:infiles -IF:parent - <superclass>HREF:par_url:parent:</superclass> -ENDIF:parent - </classmod-info> -} + CONTENTS_RDF + %{ - </%classmod%> -END:classes -<!-- /RDoc --> -</rdf:RDF> -} - - -end -end - diff --git a/lib/rdoc/generators/template/xml/xml.rb b/lib/rdoc/generators/template/xml/xml.rb deleted file mode 100644 index 4a0c8c9ac4..0000000000 --- a/lib/rdoc/generators/template/xml/xml.rb +++ /dev/null @@ -1,112 +0,0 @@ -module RDoc -module Page - - - -CONTENTS_XML = %{ -IF:description - <description> -%description% - </description> -ENDIF:description - <contents> -IF:requires - <required-file-list> -START:requires - <required-file name="%name%" -IF:aref - href="%aref%" -ENDIF:aref - /> -END:requires - </required-file-list> -ENDIF:requires -IF:attributes - <attribute-list> -START:attributes - <attribute name="%name%"> -IF:rw - <attribute-rw>%rw%</attribute-rw> -ENDIF:rw - <description>%a_desc%</description> - </attribute> -END:attributes - </attribute-list> -ENDIF:attributes -IF:includes - <included-module-list> -START:includes - <included-module name="%name%" -IF:aref - href="%aref%" -ENDIF:aref - /> -END:includes - </included-module-list> -ENDIF:includes -IF:method_list - <method-list> -START:method_list -IF:methods -START:methods - <method name="%name%" type="%type%" category="%category%" id="%aref%"> - <parameters>%params%</parameters> -IF:m_desc - <description> -%m_desc% - </description> -ENDIF:m_desc -IF:sourcecode - <source-code-listing> -%sourcecode% - </source-code-listing> -ENDIF:sourcecode - </method> -END:methods -ENDIF:methods -END:method_list - </method-list> -ENDIF:method_list - </contents> -} - -######################################################################## - -ONE_PAGE = %{<?xml version="1.0" encoding="utf-8"?> -<rdoc> -<file-list> -START:files - <file name="%short_name%" id="%href%"> - <file-info> - <path>%full_path%</path> - <dtm-modified>%dtm_modified%</dtm-modified> - </file-info> -} + CONTENTS_XML + %{ - </file> -END:files -</file-list> -<class-module-list> -START:classes - <%classmod% name="%full_name%" id="%full_name%"> - <classmod-info> -IF:infiles - <infiles> -START:infiles - <infile>HREF:full_path_url:full_path:</infile> -END:infiles - </infiles> -ENDIF:infiles -IF:parent - <superclass>HREF:par_url:parent:</superclass> -ENDIF:parent - </classmod-info> -} + CONTENTS_XML + %{ - </%classmod%> -END:classes -</class-module-list> -</rdoc> -} - - -end -end diff --git a/lib/rdoc/generators/xml_generator.rb b/lib/rdoc/generators/xml_generator.rb deleted file mode 100644 index 8c1a76d62b..0000000000 --- a/lib/rdoc/generators/xml_generator.rb +++ /dev/null @@ -1,130 +0,0 @@ - -require 'ftools' - -require 'rdoc/options' -require 'rdoc/markup/simple_markup' -require 'rdoc/markup/simple_markup/to_html' -require 'rdoc/generators/html_generator' - -module Generators - - # Generate XML output as one big file - - class XMLGenerator < HTMLGenerator - - # Standard generator factory - def XMLGenerator.for(options) - XMLGenerator.new(options) - end - - - def initialize(*args) - super - end - - ## - # Build the initial indices and output objects - # based on an array of TopLevel objects containing - # the extracted information. - - def generate(info) - @info = info - @files = [] - @classes = [] - @hyperlinks = {} - - build_indices - generate_xml - end - - - ## - # Generate: - # - # * a list of HtmlFile objects for each TopLevel object. - # * a list of HtmlClass objects for each first level - # class or module in the TopLevel objects - # * a complete list of all hyperlinkable terms (file, - # class, module, and method names) - - def build_indices - - @info.each do |toplevel| - @files << HtmlFile.new(toplevel, @options, FILE_DIR) - end - - RDoc::TopLevel.all_classes_and_modules.each do |cls| - build_class_list(cls, @files[0], CLASS_DIR) - end - end - - def build_class_list(from, html_file, class_dir) - @classes << HtmlClass.new(from, html_file, class_dir, @options) - from.each_classmodule do |mod| - build_class_list(mod, html_file, class_dir) - end - end - - ## - # Generate all the HTML. For the one-file case, we generate - # all the information in to one big hash - # - def generate_xml - values = { - 'charset' => @options.charset, - 'files' => gen_into(@files), - 'classes' => gen_into(@classes) - } - - # this method is defined in the template file - write_extra_pages if defined? write_extra_pages - - template = TemplatePage.new(RDoc::Page::ONE_PAGE) - - if @options.op_name - opfile = File.open(@options.op_name, "w") - else - opfile = $stdout - end - template.write_html_on(opfile, values) - end - - def gen_into(list) - res = [] - list.each do |item| - res << item.value_hash - end - res - end - - def gen_file_index - gen_an_index(@files, 'Files') - end - - def gen_class_index - gen_an_index(@classes, 'Classes') - end - - def gen_method_index - gen_an_index(HtmlMethod.all_methods, 'Methods') - end - - - def gen_an_index(collection, title) - res = [] - collection.sort.each do |f| - if f.document_self - res << { "href" => f.path, "name" => f.index_name } - end - end - - return { - "entries" => res, - 'list_title' => title, - 'index_url' => main_url, - } - end - - end - -end diff --git a/lib/rdoc/ghost_method.rb b/lib/rdoc/ghost_method.rb new file mode 100644 index 0000000000..2488feb9d7 --- /dev/null +++ b/lib/rdoc/ghost_method.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true +## +# GhostMethod represents a method referenced only by a comment + +class RDoc::GhostMethod < RDoc::AnyMethod +end + diff --git a/lib/rdoc/i18n.rb b/lib/rdoc/i18n.rb new file mode 100644 index 0000000000..4cb5986155 --- /dev/null +++ b/lib/rdoc/i18n.rb @@ -0,0 +1,10 @@ +# frozen_string_literal: true +## +# This module provides i18n related features. + +module RDoc::I18n + + autoload :Locale, 'rdoc/i18n/locale' + autoload :Text, 'rdoc/i18n/text' + +end diff --git a/lib/rdoc/i18n/locale.rb b/lib/rdoc/i18n/locale.rb new file mode 100644 index 0000000000..6a70d6c986 --- /dev/null +++ b/lib/rdoc/i18n/locale.rb @@ -0,0 +1,102 @@ +# frozen_string_literal: true +## +# A message container for a locale. +# +# This object provides the following two features: +# +# * Loads translated messages from .po file. +# * Translates a message into the locale. + +class RDoc::I18n::Locale + + @@locales = {} # :nodoc: + + class << self + + ## + # Returns the locale object for +locale_name+. + + def [](locale_name) + @@locales[locale_name] ||= new(locale_name) + end + + ## + # Sets the locale object for +locale_name+. + # + # Normally, this method is not used. This method is useful for + # testing. + + def []=(locale_name, locale) + @@locales[locale_name] = locale + end + + end + + ## + # The name of the locale. It uses IETF language tag format + # +[language[_territory][.codeset][@modifier]]+. + # + # See also {BCP 47 - Tags for Identifying + # Languages}[http://tools.ietf.org/rfc/bcp/bcp47.txt]. + + attr_reader :name + + ## + # Creates a new locale object for +name+ locale. +name+ must + # follow IETF language tag format. + + def initialize(name) + @name = name + @messages = {} + end + + ## + # Loads translation messages from +locale_directory+/+@name+/rdoc.po + # or +locale_directory+/+@name+.po. The former has high priority. + # + # This method requires gettext gem for parsing .po file. If you + # don't have gettext gem, this method doesn't load .po file. This + # method warns and returns +false+. + # + # Returns +true+ if succeeded, +false+ otherwise. + + def load(locale_directory) + return false if @name.nil? + + po_file_candidates = [ + File.join(locale_directory, @name, 'rdoc.po'), + File.join(locale_directory, "#{@name}.po"), + ] + po_file = po_file_candidates.find do |po_file_candidate| + File.exist?(po_file_candidate) + end + return false unless po_file + + begin + require 'gettext/po_parser' + require 'gettext/mo' + rescue LoadError + warn('Need gettext gem for i18n feature:') + warn(' gem install gettext') + return false + end + + po_parser = GetText::POParser.new + messages = GetText::MO.new + po_parser.report_warning = false + po_parser.parse_file(po_file, messages) + + @messages.merge!(messages) + + true + end + + ## + # Translates the +message+ into locale. If there is no translation + # messages for +message+ in locale, +message+ itself is returned. + + def translate(message) + @messages[message] || message + end + +end diff --git a/lib/rdoc/i18n/text.rb b/lib/rdoc/i18n/text.rb new file mode 100644 index 0000000000..7ea6664442 --- /dev/null +++ b/lib/rdoc/i18n/text.rb @@ -0,0 +1,126 @@ +# frozen_string_literal: true +## +# An i18n supported text. +# +# This object provides the following two features: +# +# * Extracts translation messages from wrapped raw text. +# * Translates wrapped raw text in specified locale. +# +# Wrapped raw text is one of String, RDoc::Comment or Array of them. + +class RDoc::I18n::Text + + ## + # Creates a new i18n supported text for +raw+ text. + + def initialize(raw) + @raw = raw + end + + ## + # Extracts translation target messages and yields each message. + # + # Each yielded message is a Hash. It consists of the followings: + # + # :type :: :paragraph + # :paragraph :: String (The translation target message itself.) + # :line_no :: Integer (The line number of the :paragraph is started.) + # + # The above content may be added in the future. + + def extract_messages + parse do |part| + case part[:type] + when :empty_line + # ignore + when :paragraph + yield(part) + end + end + end + + # Translates raw text into +locale+. + def translate(locale) + translated_text = '' + parse do |part| + case part[:type] + when :paragraph + translated_text += locale.translate(part[:paragraph]) + when :empty_line + translated_text += part[:line] + else + raise "should not reach here: unexpected type: #{type}" + end + end + translated_text + end + + private + def parse(&block) + paragraph = '' + paragraph_start_line = 0 + line_no = 0 + + each_line(@raw) do |line| + line_no += 1 + case line + when /\A\s*\z/ + if paragraph.empty? + emit_empty_line_event(line, line_no, &block) + else + paragraph += line + emit_paragraph_event(paragraph, paragraph_start_line, line_no, + &block) + paragraph = '' + end + else + paragraph_start_line = line_no if paragraph.empty? + paragraph += line + end + end + + unless paragraph.empty? + emit_paragraph_event(paragraph, paragraph_start_line, line_no, &block) + end + end + + def each_line(raw, &block) + case raw + when RDoc::Comment + raw.text.each_line(&block) + when Array + raw.each do |comment, location| + each_line(comment, &block) + end + else + raw.each_line(&block) + end + end + + def emit_empty_line_event(line, line_no) + part = { + :type => :empty_line, + :line => line, + :line_no => line_no, + } + yield(part) + end + + def emit_paragraph_event(paragraph, paragraph_start_line, line_no, &block) + paragraph_part = { + :type => :paragraph, + :line_no => paragraph_start_line, + } + match_data = /(\s*)\z/.match(paragraph) + if match_data + paragraph_part[:paragraph] = match_data.pre_match + yield(paragraph_part) + emit_empty_line_event(match_data[1], line_no, &block) + else + paragraph_part[:paragraph] = paragraph + yield(paragraph_part) + end + end + +end diff --git a/lib/rdoc/include.rb b/lib/rdoc/include.rb new file mode 100644 index 0000000000..b3ad610649 --- /dev/null +++ b/lib/rdoc/include.rb @@ -0,0 +1,10 @@ +# frozen_string_literal: true +## +# A Module included in a class with \#include +# +# RDoc::Include.new 'Enumerable', 'comment ...' + +class RDoc::Include < RDoc::Mixin + +end + diff --git a/lib/rdoc/known_classes.rb b/lib/rdoc/known_classes.rb new file mode 100644 index 0000000000..4d7f4aa995 --- /dev/null +++ b/lib/rdoc/known_classes.rb @@ -0,0 +1,73 @@ +# frozen_string_literal: true +module RDoc + + ## + # Ruby's built-in classes, modules and exceptions + + KNOWN_CLASSES = { + "rb_cArray" => "Array", + "rb_cBasicObject" => "BasicObject", + "rb_cBignum" => "Bignum", + "rb_cClass" => "Class", + "rb_cData" => "Data", + "rb_cDir" => "Dir", + "rb_cEncoding" => "Encoding", + "rb_cFalseClass" => "FalseClass", + "rb_cFile" => "File", + "rb_cFixnum" => "Fixnum", + "rb_cFloat" => "Float", + "rb_cHash" => "Hash", + "rb_cIO" => "IO", + "rb_cInteger" => "Integer", + "rb_cModule" => "Module", + "rb_cNilClass" => "NilClass", + "rb_cNumeric" => "Numeric", + "rb_cObject" => "Object", + "rb_cProc" => "Proc", + "rb_cRange" => "Range", + "rb_cRegexp" => "Regexp", + "rb_cRubyVM" => "RubyVM", + "rb_cSocket" => "Socket", + "rb_cString" => "String", + "rb_cStruct" => "Struct", + "rb_cSymbol" => "Symbol", + "rb_cThread" => "Thread", + "rb_cTime" => "Time", + "rb_cTrueClass" => "TrueClass", + + "rb_eArgError" => "ArgError", + "rb_eEOFError" => "EOFError", + "rb_eException" => "Exception", + "rb_eFatal" => "fatal", + "rb_eFloatDomainError" => "FloatDomainError", + "rb_eIOError" => "IOError", + "rb_eIndexError" => "IndexError", + "rb_eInterrupt" => "Interrupt", + "rb_eLoadError" => "LoadError", + "rb_eNameError" => "NameError", + "rb_eNoMemError" => "NoMemError", + "rb_eNotImpError" => "NotImpError", + "rb_eRangeError" => "RangeError", + "rb_eRuntimeError" => "RuntimeError", + "rb_eScriptError" => "ScriptError", + "rb_eSecurityError" => "SecurityError", + "rb_eSignal" => "SignalException", + "rb_eStandardError" => "StandardError", + "rb_eSyntaxError" => "SyntaxError", + "rb_eSystemCallError" => "SystemCallError", + "rb_eSystemExit" => "SystemExit", + "rb_eTypeError" => "TypeError", + "rb_eZeroDivError" => "ZeroDivError", + + "rb_mComparable" => "Comparable", + "rb_mEnumerable" => "Enumerable", + "rb_mErrno" => "Errno", + "rb_mFConst" => "File::Constants", + "rb_mFileTest" => "FileTest", + "rb_mGC" => "GC", + "rb_mKernel" => "Kernel", + "rb_mMath" => "Math", + "rb_mProcess" => "Process" + } + +end diff --git a/lib/rdoc/markdown.rb b/lib/rdoc/markdown.rb new file mode 100644 index 0000000000..44dd50b0f7 --- /dev/null +++ b/lib/rdoc/markdown.rb @@ -0,0 +1,16286 @@ +# coding: UTF-8 +# :markup: markdown + +## +# RDoc::Markdown as described by the [markdown syntax][syntax]. +# +# To choose Markdown as your only default format see +# RDoc::Options@Saved+Options for instructions on setting up a `.doc_options` +# file to store your project default. +# +# ## Usage +# +# Here is a brief example of using this parse to read a markdown file by hand. +# +# data = File.read("README.md") +# formatter = RDoc::Markup::ToHtml.new(RDoc::Options.new, nil) +# html = RDoc::Markdown.parse(data).accept(formatter) +# +# # do something with html +# +# ## Extensions +# +# The following markdown extensions are supported by the parser, but not all +# are used in RDoc output by default. +# +# ### RDoc +# +# The RDoc Markdown parser has the following built-in behaviors that cannot be +# disabled. +# +# Underscores embedded in words are never interpreted as emphasis. (While the +# [markdown dingus][dingus] emphasizes in-word underscores, neither the +# Markdown syntax nor MarkdownTest mention this behavior.) +# +# For HTML output, RDoc always auto-links bare URLs. +# +# ### Break on Newline +# +# The break_on_newline extension converts all newlines into hard line breaks +# as in [Github Flavored Markdown][GFM]. This extension is disabled by +# default. +# +# ### CSS +# +# The #css extension enables CSS blocks to be included in the output, but they +# are not used for any built-in RDoc output format. This extension is disabled +# by default. +# +# Example: +# +# <style type="text/css"> +# h1 { font-size: 3em } +# </style> +# +# ### Definition Lists +# +# The definition_lists extension allows definition lists using the [PHP +# Markdown Extra syntax][PHPE], but only one label and definition are supported +# at this time. This extension is enabled by default. +# +# Example: +# +# ``` +# cat +# : A small furry mammal +# that seems to sleep a lot +# +# ant +# : A little insect that is known +# to enjoy picnics +# +# ``` +# +# Produces: +# +# cat +# : A small furry mammal +# that seems to sleep a lot +# +# ant +# : A little insect that is known +# to enjoy picnics +# +# ### Strike +# +# Example: +# +# ``` +# This is ~~striked~~. +# ``` +# +# Produces: +# +# This is ~~striked~~. +# +# ### Github +# +# The #github extension enables a partial set of [Github Flavored Markdown] +# [GFM]. This extension is enabled by default. +# +# Supported github extensions include: +# +# #### Fenced code blocks +# +# Use ` ``` ` around a block of code instead of indenting it four spaces. +# +# #### Syntax highlighting +# +# Use ` ``` ruby ` as the start of a code fence to add syntax highlighting. +# (Currently only `ruby` syntax is supported). +# +# ### HTML +# +# Enables raw HTML to be included in the output. This extension is enabled by +# default. +# +# Example: +# +# <table> +# ... +# </table> +# +# ### Notes +# +# The #notes extension enables footnote support. This extension is enabled by +# default. +# +# Example: +# +# Here is some text[^1] including an inline footnote ^[for short footnotes] +# +# ... +# +# [^1]: With the footnote text down at the bottom +# +# Produces: +# +# Here is some text[^1] including an inline footnote ^[for short footnotes] +# +# [^1]: With the footnote text down at the bottom +# +# ## Limitations +# +# * Link titles are not used +# * Footnotes are collapsed into a single paragraph +# +# ## Author +# +# This markdown parser is a port to kpeg from [peg-markdown][pegmarkdown] by +# John MacFarlane. +# +# It is used under the MIT license: +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. +# +# The port to kpeg was performed by Eric Hodel and Evan Phoenix +# +# [dingus]: http://daringfireball.net/projects/markdown/dingus +# [GFM]: http://github.github.com/github-flavored-markdown/ +# [pegmarkdown]: https://github.com/jgm/peg-markdown +# [PHPE]: http://michelf.com/projects/php-markdown/extra/#def-list +# [syntax]: http://daringfireball.net/projects/markdown/syntax +#-- +# Last updated to jgm/peg-markdown commit 8f8fc22ef0 +class RDoc::Markdown + # :stopdoc: + + # This is distinct from setup_parser so that a standalone parser + # can redefine #initialize and still have access to the proper + # parser setup code. + def initialize(str, debug=false) + setup_parser(str, debug) + end + + + + # Prepares for parsing +str+. If you define a custom initialize you must + # call this method before #parse + def setup_parser(str, debug=false) + set_string str, 0 + @memoizations = Hash.new { |h,k| h[k] = {} } + @result = nil + @failed_rule = nil + @failing_rule_offset = -1 + + setup_foreign_grammar + end + + attr_reader :string + attr_reader :failing_rule_offset + attr_accessor :result, :pos + + def current_column(target=pos) + if c = string.rindex("\n", target-1) + return target - c - 1 + end + + target + 1 + end + + def current_line(target=pos) + cur_offset = 0 + cur_line = 0 + + string.each_line do |line| + cur_line += 1 + cur_offset += line.size + return cur_line if cur_offset >= target + end + + -1 + end + + def lines + lines = [] + string.each_line { |l| lines << l } + lines + end + + + + def get_text(start) + @string[start..@pos-1] + end + + # Sets the string and current parsing position for the parser. + def set_string string, pos + @string = string + @string_size = string ? string.size : 0 + @pos = pos + end + + def show_pos + width = 10 + if @pos < width + "#{@pos} (\"#{@string[0,@pos]}\" @ \"#{@string[@pos,width]}\")" + else + "#{@pos} (\"... #{@string[@pos - width, width]}\" @ \"#{@string[@pos,width]}\")" + end + end + + def failure_info + l = current_line @failing_rule_offset + c = current_column @failing_rule_offset + + if @failed_rule.kind_of? Symbol + info = self.class::Rules[@failed_rule] + "line #{l}, column #{c}: failed rule '#{info.name}' = '#{info.rendered}'" + else + "line #{l}, column #{c}: failed rule '#{@failed_rule}'" + end + end + + def failure_caret + l = current_line @failing_rule_offset + c = current_column @failing_rule_offset + + line = lines[l-1] + "#{line}\n#{' ' * (c - 1)}^" + end + + def failure_character + l = current_line @failing_rule_offset + c = current_column @failing_rule_offset + lines[l-1][c-1, 1] + end + + def failure_oneline + l = current_line @failing_rule_offset + c = current_column @failing_rule_offset + + char = lines[l-1][c-1, 1] + + if @failed_rule.kind_of? Symbol + info = self.class::Rules[@failed_rule] + "@#{l}:#{c} failed rule '#{info.name}', got '#{char}'" + else + "@#{l}:#{c} failed rule '#{@failed_rule}', got '#{char}'" + end + end + + class ParseError < RuntimeError + end + + def raise_error + raise ParseError, failure_oneline + end + + def show_error(io=STDOUT) + error_pos = @failing_rule_offset + line_no = current_line(error_pos) + col_no = current_column(error_pos) + + io.puts "On line #{line_no}, column #{col_no}:" + + if @failed_rule.kind_of? Symbol + info = self.class::Rules[@failed_rule] + io.puts "Failed to match '#{info.rendered}' (rule '#{info.name}')" + else + io.puts "Failed to match rule '#{@failed_rule}'" + end + + io.puts "Got: #{string[error_pos,1].inspect}" + line = lines[line_no-1] + io.puts "=> #{line}" + io.print(" " * (col_no + 3)) + io.puts "^" + end + + def set_failed_rule(name) + if @pos > @failing_rule_offset + @failed_rule = name + @failing_rule_offset = @pos + end + end + + attr_reader :failed_rule + + def match_string(str) + len = str.size + if @string[pos,len] == str + @pos += len + return str + end + + return nil + end + + def scan(reg) + if m = reg.match(@string[@pos..-1]) + width = m.end(0) + @pos += width + return true + end + + return nil + end + + if "".respond_to? :ord + def get_byte + if @pos >= @string_size + return nil + end + + s = @string[@pos].ord + @pos += 1 + s + end + else + def get_byte + if @pos >= @string_size + return nil + end + + s = @string[@pos] + @pos += 1 + s + end + end + + def parse(rule=nil) + # We invoke the rules indirectly via apply + # instead of by just calling them as methods because + # if the rules use left recursion, apply needs to + # manage that. + + if !rule + apply(:_root) + else + method = rule.gsub("-","_hyphen_") + apply :"_#{method}" + end + end + + class MemoEntry + def initialize(ans, pos) + @ans = ans + @pos = pos + @result = nil + @set = false + @left_rec = false + end + + attr_reader :ans, :pos, :result, :set + attr_accessor :left_rec + + def move!(ans, pos, result) + @ans = ans + @pos = pos + @result = result + @set = true + @left_rec = false + end + end + + def external_invoke(other, rule, *args) + old_pos = @pos + old_string = @string + + set_string other.string, other.pos + + begin + if val = __send__(rule, *args) + other.pos = @pos + other.result = @result + else + other.set_failed_rule "#{self.class}##{rule}" + end + val + ensure + set_string old_string, old_pos + end + end + + def apply_with_args(rule, *args) + memo_key = [rule, args] + if m = @memoizations[memo_key][@pos] + @pos = m.pos + if !m.set + m.left_rec = true + return nil + end + + @result = m.result + + return m.ans + else + m = MemoEntry.new(nil, @pos) + @memoizations[memo_key][@pos] = m + start_pos = @pos + + ans = __send__ rule, *args + + lr = m.left_rec + + m.move! ans, @pos, @result + + # Don't bother trying to grow the left recursion + # if it's failing straight away (thus there is no seed) + if ans and lr + return grow_lr(rule, args, start_pos, m) + else + return ans + end + end + end + + def apply(rule) + if m = @memoizations[rule][@pos] + @pos = m.pos + if !m.set + m.left_rec = true + return nil + end + + @result = m.result + + return m.ans + else + m = MemoEntry.new(nil, @pos) + @memoizations[rule][@pos] = m + start_pos = @pos + + ans = __send__ rule + + lr = m.left_rec + + m.move! ans, @pos, @result + + # Don't bother trying to grow the left recursion + # if it's failing straight away (thus there is no seed) + if ans and lr + return grow_lr(rule, nil, start_pos, m) + else + return ans + end + end + end + + def grow_lr(rule, args, start_pos, m) + while true + @pos = start_pos + @result = m.result + + if args + ans = __send__ rule, *args + else + ans = __send__ rule + end + return nil unless ans + + break if @pos <= m.pos + + m.move! ans, @pos, @result + end + + @result = m.result + @pos = m.pos + return m.ans + end + + class RuleInfo + def initialize(name, rendered) + @name = name + @rendered = rendered + end + + attr_reader :name, :rendered + end + + def self.rule_info(name, rendered) + RuleInfo.new(name, rendered) + end + + + # :startdoc: + + + + require 'rdoc' + require 'rdoc/markup/to_joined_paragraph' + require 'rdoc/markdown/entities' + + require 'rdoc/markdown/literals' + + ## + # Supported extensions + + EXTENSIONS = [] + + ## + # Extensions enabled by default + + DEFAULT_EXTENSIONS = [ + :definition_lists, + :github, + :html, + :notes, + :strike, + ] + + # :section: Extensions + + ## + # Creates extension methods for the `name` extension to enable and disable + # the extension and to query if they are active. + + def self.extension name + EXTENSIONS << name + + define_method "#{name}?" do + extension? name + end + + define_method "#{name}=" do |enable| + extension name, enable + end + end + + ## + # Converts all newlines into hard breaks + + extension :break_on_newline + + ## + # Allow style blocks + + extension :css + + ## + # Allow PHP Markdown Extras style definition lists + + extension :definition_lists + + ## + # Allow Github Flavored Markdown + + extension :github + + ## + # Allow HTML + + extension :html + + ## + # Enables the notes extension + + extension :notes + + ## + # Enables the strike extension + + extension :strike + + # :section: + + ## + # Parses the `markdown` document into an RDoc::Document using the default + # extensions. + + def self.parse markdown + parser = new + + parser.parse markdown + end + + # TODO remove when kpeg 0.10 is released + alias orig_initialize initialize # :nodoc: + + ## + # Creates a new markdown parser that enables the given +extensions+. + + def initialize extensions = DEFAULT_EXTENSIONS, debug = false + @debug = debug + @formatter = RDoc::Markup::ToJoinedParagraph.new + @extensions = extensions + + @references = nil + @unlinked_references = nil + + @footnotes = nil + @note_order = nil + end + + ## + # Wraps `text` in emphasis for rdoc inline formatting + + def emphasis text + if text =~ /\A[a-z\d.\/]+\z/i then + "_#{text}_" + else + "<em>#{text}</em>" + end + end + + ## + # :category: Extensions + # + # Is the extension `name` enabled? + + def extension? name + @extensions.include? name + end + + ## + # :category: Extensions + # + # Enables or disables the extension with `name` + + def extension name, enable + if enable then + @extensions |= [name] + else + @extensions -= [name] + end + end + + ## + # Parses `text` in a clone of this parser. This is used for handling nested + # lists the same way as markdown_parser. + + def inner_parse text # :nodoc: + parser = clone + + parser.setup_parser text, @debug + + parser.peg_parse + + doc = parser.result + + doc.accept @formatter + + doc.parts + end + + ## + # Finds a link reference for `label` and creates a new link to it with + # `content` as the link text. If `label` was not encountered in the + # reference-gathering parser pass the label and content are reconstructed + # with the linking `text` (usually whitespace). + + def link_to content, label = content, text = nil + raise ParseError, 'enable notes extension' if + content.start_with? '^' and label.equal? content + + if ref = @references[label] then + "{#{content}}[#{ref}]" + elsif label.equal? content then + "[#{content}]#{text}" + else + "[#{content}]#{text}[#{label}]" + end + end + + ## + # Creates an RDoc::Markup::ListItem by parsing the `unparsed` content from + # the first parsing pass. + + def list_item_from unparsed + parsed = inner_parse unparsed.join + RDoc::Markup::ListItem.new nil, *parsed + end + + ## + # Stores `label` as a note and fills in previously unknown note references. + + def note label + #foottext = "rdoc-label:foottext-#{label}:footmark-#{label}" + + #ref.replace foottext if ref = @unlinked_notes.delete(label) + + @notes[label] = foottext + + #"{^1}[rdoc-label:footmark-#{label}:foottext-#{label}] " + end + + ## + # Creates a new link for the footnote `reference` and adds the reference to + # the note order list for proper display at the end of the document. + + def note_for ref + @note_order << ref + + label = @note_order.length + + "{*#{label}}[rdoc-label:foottext-#{label}:footmark-#{label}]" + end + + ## + # The internal kpeg parse method + + alias peg_parse parse # :nodoc: + + ## + # Creates an RDoc::Markup::Paragraph from `parts` and including + # extension-specific behavior + + def paragraph parts + parts = parts.map do |part| + if "\n" == part then + RDoc::Markup::HardBreak.new + else + part + end + end if break_on_newline? + + RDoc::Markup::Paragraph.new(*parts) + end + + ## + # Parses `markdown` into an RDoc::Document + + def parse markdown + @references = {} + @unlinked_references = {} + + markdown += "\n\n" + + setup_parser markdown, @debug + peg_parse 'References' + + if notes? then + @footnotes = {} + + setup_parser markdown, @debug + peg_parse 'Notes' + + # using note_order on the first pass would be a bug + @note_order = [] + end + + setup_parser markdown, @debug + peg_parse + + doc = result + + if notes? and not @footnotes.empty? then + doc << RDoc::Markup::Rule.new(1) + + @note_order.each_with_index do |ref, index| + label = index + 1 + note = @footnotes[ref] + + link = "{^#{label}}[rdoc-label:footmark-#{label}:foottext-#{label}] " + note.parts.unshift link + + doc << note + end + end + + doc.accept @formatter + + doc + end + + ## + # Stores `label` as a reference to `link` and fills in previously unknown + # link references. + + def reference label, link + if ref = @unlinked_references.delete(label) then + ref.replace link + end + + @references[label] = link + end + + ## + # Wraps `text` in strong markup for rdoc inline formatting + + def strong text + if text =~ /\A[a-z\d.\/-]+\z/i then + "*#{text}*" + else + "<b>#{text}</b>" + end + end + + ## + # Wraps `text` in strike markup for rdoc inline formatting + + def strike text + if text =~ /\A[a-z\d.\/-]+\z/i then + "~#{text}~" + else + "<s>#{text}</s>" + end + end + + + # :stopdoc: + def setup_foreign_grammar + @_grammar_literals = RDoc::Markdown::Literals.new(nil) + end + + # root = Doc + def _root + _tmp = apply(:_Doc) + set_failed_rule :_root unless _tmp + return _tmp + end + + # Doc = BOM? Block*:a { RDoc::Markup::Document.new(*a.compact) } + def _Doc + + _save = self.pos + while true # sequence + _save1 = self.pos + _tmp = apply(:_BOM) + unless _tmp + _tmp = true + self.pos = _save1 + end + unless _tmp + self.pos = _save + break + end + _ary = [] + while true + _tmp = apply(:_Block) + _ary << @result if _tmp + break unless _tmp + end + _tmp = true + @result = _ary + a = @result + unless _tmp + self.pos = _save + break + end + @result = begin; RDoc::Markup::Document.new(*a.compact) ; end + _tmp = true + unless _tmp + self.pos = _save + end + break + end # end sequence + + set_failed_rule :_Doc unless _tmp + return _tmp + end + + # Block = @BlankLine* (BlockQuote | Verbatim | CodeFence | Note | Reference | HorizontalRule | Heading | OrderedList | BulletList | DefinitionList | HtmlBlock | StyleBlock | Para | Plain) + def _Block + + _save = self.pos + while true # sequence + while true + _tmp = _BlankLine() + break unless _tmp + end + _tmp = true + unless _tmp + self.pos = _save + break + end + + _save2 = self.pos + while true # choice + _tmp = apply(:_BlockQuote) + break if _tmp + self.pos = _save2 + _tmp = apply(:_Verbatim) + break if _tmp + self.pos = _save2 + _tmp = apply(:_CodeFence) + break if _tmp + self.pos = _save2 + _tmp = apply(:_Note) + break if _tmp + self.pos = _save2 + _tmp = apply(:_Reference) + break if _tmp + self.pos = _save2 + _tmp = apply(:_HorizontalRule) + break if _tmp + self.pos = _save2 + _tmp = apply(:_Heading) + break if _tmp + self.pos = _save2 + _tmp = apply(:_OrderedList) + break if _tmp + self.pos = _save2 + _tmp = apply(:_BulletList) + break if _tmp + self.pos = _save2 + _tmp = apply(:_DefinitionList) + break if _tmp + self.pos = _save2 + _tmp = apply(:_HtmlBlock) + break if _tmp + self.pos = _save2 + _tmp = apply(:_StyleBlock) + break if _tmp + self.pos = _save2 + _tmp = apply(:_Para) + break if _tmp + self.pos = _save2 + _tmp = apply(:_Plain) + break if _tmp + self.pos = _save2 + break + end # end choice + + unless _tmp + self.pos = _save + end + break + end # end sequence + + set_failed_rule :_Block unless _tmp + return _tmp + end + + # Para = @NonindentSpace Inlines:a @BlankLine+ { paragraph a } + def _Para + + _save = self.pos + while true # sequence + _tmp = _NonindentSpace() + unless _tmp + self.pos = _save + break + end + _tmp = apply(:_Inlines) + a = @result + unless _tmp + self.pos = _save + break + end + _save1 = self.pos + _tmp = _BlankLine() + if _tmp + while true + _tmp = _BlankLine() + break unless _tmp + end + _tmp = true + else + self.pos = _save1 + end + unless _tmp + self.pos = _save + break + end + @result = begin; paragraph a ; end + _tmp = true + unless _tmp + self.pos = _save + end + break + end # end sequence + + set_failed_rule :_Para unless _tmp + return _tmp + end + + # Plain = Inlines:a { paragraph a } + def _Plain + + _save = self.pos + while true # sequence + _tmp = apply(:_Inlines) + a = @result + unless _tmp + self.pos = _save + break + end + @result = begin; paragraph a ; end + _tmp = true + unless _tmp + self.pos = _save + end + break + end # end sequence + + set_failed_rule :_Plain unless _tmp + return _tmp + end + + # AtxInline = !@Newline !(@Sp /#*/ @Sp @Newline) Inline + def _AtxInline + + _save = self.pos + while true # sequence + _save1 = self.pos + _tmp = _Newline() + _tmp = _tmp ? nil : true + self.pos = _save1 + unless _tmp + self.pos = _save + break + end + _save2 = self.pos + + _save3 = self.pos + while true # sequence + _tmp = _Sp() + unless _tmp + self.pos = _save3 + break + end + _tmp = scan(/\A(?-mix:#*)/) + unless _tmp + self.pos = _save3 + break + end + _tmp = _Sp() + unless _tmp + self.pos = _save3 + break + end + _tmp = _Newline() + unless _tmp + self.pos = _save3 + end + break + end # end sequence + + _tmp = _tmp ? nil : true + self.pos = _save2 + unless _tmp + self.pos = _save + break + end + _tmp = apply(:_Inline) + unless _tmp + self.pos = _save + end + break + end # end sequence + + set_failed_rule :_AtxInline unless _tmp + return _tmp + end + + # AtxStart = < /\#{1,6}/ > { text.length } + def _AtxStart + + _save = self.pos + while true # sequence + _text_start = self.pos + _tmp = scan(/\A(?-mix:\#{1,6})/) + if _tmp + text = get_text(_text_start) + end + unless _tmp + self.pos = _save + break + end + @result = begin; text.length ; end + _tmp = true + unless _tmp + self.pos = _save + end + break + end # end sequence + + set_failed_rule :_AtxStart unless _tmp + return _tmp + end + + # AtxHeading = AtxStart:s @Sp AtxInline+:a (@Sp /#*/ @Sp)? @Newline { RDoc::Markup::Heading.new(s, a.join) } + def _AtxHeading + + _save = self.pos + while true # sequence + _tmp = apply(:_AtxStart) + s = @result + unless _tmp + self.pos = _save + break + end + _tmp = _Sp() + unless _tmp + self.pos = _save + break + end + _save1 = self.pos + _ary = [] + _tmp = apply(:_AtxInline) + if _tmp + _ary << @result + while true + _tmp = apply(:_AtxInline) + _ary << @result if _tmp + break unless _tmp + end + _tmp = true + @result = _ary + else + self.pos = _save1 + end + a = @result + unless _tmp + self.pos = _save + break + end + _save2 = self.pos + + _save3 = self.pos + while true # sequence + _tmp = _Sp() + unless _tmp + self.pos = _save3 + break + end + _tmp = scan(/\A(?-mix:#*)/) + unless _tmp + self.pos = _save3 + break + end + _tmp = _Sp() + unless _tmp + self.pos = _save3 + end + break + end # end sequence + + unless _tmp + _tmp = true + self.pos = _save2 + end + unless _tmp + self.pos = _save + break + end + _tmp = _Newline() + unless _tmp + self.pos = _save + break + end + @result = begin; RDoc::Markup::Heading.new(s, a.join) ; end + _tmp = true + unless _tmp + self.pos = _save + end + break + end # end sequence + + set_failed_rule :_AtxHeading unless _tmp + return _tmp + end + + # SetextHeading = (SetextHeading1 | SetextHeading2) + def _SetextHeading + + _save = self.pos + while true # choice + _tmp = apply(:_SetextHeading1) + break if _tmp + self.pos = _save + _tmp = apply(:_SetextHeading2) + break if _tmp + self.pos = _save + break + end # end choice + + set_failed_rule :_SetextHeading unless _tmp + return _tmp + end + + # SetextBottom1 = /={1,}/ @Newline + def _SetextBottom1 + + _save = self.pos + while true # sequence + _tmp = scan(/\A(?-mix:={1,})/) + unless _tmp + self.pos = _save + break + end + _tmp = _Newline() + unless _tmp + self.pos = _save + end + break + end # end sequence + + set_failed_rule :_SetextBottom1 unless _tmp + return _tmp + end + + # SetextBottom2 = /-{1,}/ @Newline + def _SetextBottom2 + + _save = self.pos + while true # sequence + _tmp = scan(/\A(?-mix:-{1,})/) + unless _tmp + self.pos = _save + break + end + _tmp = _Newline() + unless _tmp + self.pos = _save + end + break + end # end sequence + + set_failed_rule :_SetextBottom2 unless _tmp + return _tmp + end + + # SetextHeading1 = &(@RawLine SetextBottom1) @StartList:a (!@Endline Inline:b { a << b })+ @Sp @Newline SetextBottom1 { RDoc::Markup::Heading.new(1, a.join) } + def _SetextHeading1 + + _save = self.pos + while true # sequence + _save1 = self.pos + + _save2 = self.pos + while true # sequence + _tmp = _RawLine() + unless _tmp + self.pos = _save2 + break + end + _tmp = apply(:_SetextBottom1) + unless _tmp + self.pos = _save2 + end + break + end # end sequence + + self.pos = _save1 + unless _tmp + self.pos = _save + break + end + _tmp = _StartList() + a = @result + unless _tmp + self.pos = _save + break + end + _save3 = self.pos + + _save4 = self.pos + while true # sequence + _save5 = self.pos + _tmp = _Endline() + _tmp = _tmp ? nil : true + self.pos = _save5 + unless _tmp + self.pos = _save4 + break + end + _tmp = apply(:_Inline) + b = @result + unless _tmp + self.pos = _save4 + break + end + @result = begin; a << b ; end + _tmp = true + unless _tmp + self.pos = _save4 + end + break + end # end sequence + + if _tmp + while true + + _save6 = self.pos + while true # sequence + _save7 = self.pos + _tmp = _Endline() + _tmp = _tmp ? nil : true + self.pos = _save7 + unless _tmp + self.pos = _save6 + break + end + _tmp = apply(:_Inline) + b = @result + unless _tmp + self.pos = _save6 + break + end + @result = begin; a << b ; end + _tmp = true + unless _tmp + self.pos = _save6 + end + break + end # end sequence + + break unless _tmp + end + _tmp = true + else + self.pos = _save3 + end + unless _tmp + self.pos = _save + break + end + _tmp = _Sp() + unless _tmp + self.pos = _save + break + end + _tmp = _Newline() + unless _tmp + self.pos = _save + break + end + _tmp = apply(:_SetextBottom1) + unless _tmp + self.pos = _save + break + end + @result = begin; RDoc::Markup::Heading.new(1, a.join) ; end + _tmp = true + unless _tmp + self.pos = _save + end + break + end # end sequence + + set_failed_rule :_SetextHeading1 unless _tmp + return _tmp + end + + # SetextHeading2 = &(@RawLine SetextBottom2) @StartList:a (!@Endline Inline:b { a << b })+ @Sp @Newline SetextBottom2 { RDoc::Markup::Heading.new(2, a.join) } + def _SetextHeading2 + + _save = self.pos + while true # sequence + _save1 = self.pos + + _save2 = self.pos + while true # sequence + _tmp = _RawLine() + unless _tmp + self.pos = _save2 + break + end + _tmp = apply(:_SetextBottom2) + unless _tmp + self.pos = _save2 + end + break + end # end sequence + + self.pos = _save1 + unless _tmp + self.pos = _save + break + end + _tmp = _StartList() + a = @result + unless _tmp + self.pos = _save + break + end + _save3 = self.pos + + _save4 = self.pos + while true # sequence + _save5 = self.pos + _tmp = _Endline() + _tmp = _tmp ? nil : true + self.pos = _save5 + unless _tmp + self.pos = _save4 + break + end + _tmp = apply(:_Inline) + b = @result + unless _tmp + self.pos = _save4 + break + end + @result = begin; a << b ; end + _tmp = true + unless _tmp + self.pos = _save4 + end + break + end # end sequence + + if _tmp + while true + + _save6 = self.pos + while true # sequence + _save7 = self.pos + _tmp = _Endline() + _tmp = _tmp ? nil : true + self.pos = _save7 + unless _tmp + self.pos = _save6 + break + end + _tmp = apply(:_Inline) + b = @result + unless _tmp + self.pos = _save6 + break + end + @result = begin; a << b ; end + _tmp = true + unless _tmp + self.pos = _save6 + end + break + end # end sequence + + break unless _tmp + end + _tmp = true + else + self.pos = _save3 + end + unless _tmp + self.pos = _save + break + end + _tmp = _Sp() + unless _tmp + self.pos = _save + break + end + _tmp = _Newline() + unless _tmp + self.pos = _save + break + end + _tmp = apply(:_SetextBottom2) + unless _tmp + self.pos = _save + break + end + @result = begin; RDoc::Markup::Heading.new(2, a.join) ; end + _tmp = true + unless _tmp + self.pos = _save + end + break + end # end sequence + + set_failed_rule :_SetextHeading2 unless _tmp + return _tmp + end + + # Heading = (SetextHeading | AtxHeading) + def _Heading + + _save = self.pos + while true # choice + _tmp = apply(:_SetextHeading) + break if _tmp + self.pos = _save + _tmp = apply(:_AtxHeading) + break if _tmp + self.pos = _save + break + end # end choice + + set_failed_rule :_Heading unless _tmp + return _tmp + end + + # BlockQuote = BlockQuoteRaw:a { RDoc::Markup::BlockQuote.new(*a) } + def _BlockQuote + + _save = self.pos + while true # sequence + _tmp = apply(:_BlockQuoteRaw) + a = @result + unless _tmp + self.pos = _save + break + end + @result = begin; RDoc::Markup::BlockQuote.new(*a) ; end + _tmp = true + unless _tmp + self.pos = _save + end + break + end # end sequence + + set_failed_rule :_BlockQuote unless _tmp + return _tmp + end + + # BlockQuoteRaw = @StartList:a (">" " "? Line:l { a << l } (!">" !@BlankLine Line:c { a << c })* (@BlankLine:n { a << n })*)+ { inner_parse a.join } + def _BlockQuoteRaw + + _save = self.pos + while true # sequence + _tmp = _StartList() + a = @result + unless _tmp + self.pos = _save + break + end + _save1 = self.pos + + _save2 = self.pos + while true # sequence + _tmp = match_string(">") + unless _tmp + self.pos = _save2 + break + end + _save3 = self.pos + _tmp = match_string(" ") + unless _tmp + _tmp = true + self.pos = _save3 + end + unless _tmp + self.pos = _save2 + break + end + _tmp = apply(:_Line) + l = @result + unless _tmp + self.pos = _save2 + break + end + @result = begin; a << l ; end + _tmp = true + unless _tmp + self.pos = _save2 + break + end + while true + + _save5 = self.pos + while true # sequence + _save6 = self.pos + _tmp = match_string(">") + _tmp = _tmp ? nil : true + self.pos = _save6 + unless _tmp + self.pos = _save5 + break + end + _save7 = self.pos + _tmp = _BlankLine() + _tmp = _tmp ? nil : true + self.pos = _save7 + unless _tmp + self.pos = _save5 + break + end + _tmp = apply(:_Line) + c = @result + unless _tmp + self.pos = _save5 + break + end + @result = begin; a << c ; end + _tmp = true + unless _tmp + self.pos = _save5 + end + break + end # end sequence + + break unless _tmp + end + _tmp = true + unless _tmp + self.pos = _save2 + break + end + while true + + _save9 = self.pos + while true # sequence + _tmp = _BlankLine() + n = @result + unless _tmp + self.pos = _save9 + break + end + @result = begin; a << n ; end + _tmp = true + unless _tmp + self.pos = _save9 + end + break + end # end sequence + + break unless _tmp + end + _tmp = true + unless _tmp + self.pos = _save2 + end + break + end # end sequence + + if _tmp + while true + + _save10 = self.pos + while true # sequence + _tmp = match_string(">") + unless _tmp + self.pos = _save10 + break + end + _save11 = self.pos + _tmp = match_string(" ") + unless _tmp + _tmp = true + self.pos = _save11 + end + unless _tmp + self.pos = _save10 + break + end + _tmp = apply(:_Line) + l = @result + unless _tmp + self.pos = _save10 + break + end + @result = begin; a << l ; end + _tmp = true + unless _tmp + self.pos = _save10 + break + end + while true + + _save13 = self.pos + while true # sequence + _save14 = self.pos + _tmp = match_string(">") + _tmp = _tmp ? nil : true + self.pos = _save14 + unless _tmp + self.pos = _save13 + break + end + _save15 = self.pos + _tmp = _BlankLine() + _tmp = _tmp ? nil : true + self.pos = _save15 + unless _tmp + self.pos = _save13 + break + end + _tmp = apply(:_Line) + c = @result + unless _tmp + self.pos = _save13 + break + end + @result = begin; a << c ; end + _tmp = true + unless _tmp + self.pos = _save13 + end + break + end # end sequence + + break unless _tmp + end + _tmp = true + unless _tmp + self.pos = _save10 + break + end + while true + + _save17 = self.pos + while true # sequence + _tmp = _BlankLine() + n = @result + unless _tmp + self.pos = _save17 + break + end + @result = begin; a << n ; end + _tmp = true + unless _tmp + self.pos = _save17 + end + break + end # end sequence + + break unless _tmp + end + _tmp = true + unless _tmp + self.pos = _save10 + end + break + end # end sequence + + break unless _tmp + end + _tmp = true + else + self.pos = _save1 + end + unless _tmp + self.pos = _save + break + end + @result = begin; inner_parse a.join ; end + _tmp = true + unless _tmp + self.pos = _save + end + break + end # end sequence + + set_failed_rule :_BlockQuoteRaw unless _tmp + return _tmp + end + + # NonblankIndentedLine = !@BlankLine IndentedLine + def _NonblankIndentedLine + + _save = self.pos + while true # sequence + _save1 = self.pos + _tmp = _BlankLine() + _tmp = _tmp ? nil : true + self.pos = _save1 + unless _tmp + self.pos = _save + break + end + _tmp = apply(:_IndentedLine) + unless _tmp + self.pos = _save + end + break + end # end sequence + + set_failed_rule :_NonblankIndentedLine unless _tmp + return _tmp + end + + # VerbatimChunk = @BlankLine*:a NonblankIndentedLine+:b { a.concat b } + def _VerbatimChunk + + _save = self.pos + while true # sequence + _ary = [] + while true + _tmp = _BlankLine() + _ary << @result if _tmp + break unless _tmp + end + _tmp = true + @result = _ary + a = @result + unless _tmp + self.pos = _save + break + end + _save2 = self.pos + _ary = [] + _tmp = apply(:_NonblankIndentedLine) + if _tmp + _ary << @result + while true + _tmp = apply(:_NonblankIndentedLine) + _ary << @result if _tmp + break unless _tmp + end + _tmp = true + @result = _ary + else + self.pos = _save2 + end + b = @result + unless _tmp + self.pos = _save + break + end + @result = begin; a.concat b ; end + _tmp = true + unless _tmp + self.pos = _save + end + break + end # end sequence + + set_failed_rule :_VerbatimChunk unless _tmp + return _tmp + end + + # Verbatim = VerbatimChunk+:a { RDoc::Markup::Verbatim.new(*a.flatten) } + def _Verbatim + + _save = self.pos + while true # sequence + _save1 = self.pos + _ary = [] + _tmp = apply(:_VerbatimChunk) + if _tmp + _ary << @result + while true + _tmp = apply(:_VerbatimChunk) + _ary << @result if _tmp + break unless _tmp + end + _tmp = true + @result = _ary + else + self.pos = _save1 + end + a = @result + unless _tmp + self.pos = _save + break + end + @result = begin; RDoc::Markup::Verbatim.new(*a.flatten) ; end + _tmp = true + unless _tmp + self.pos = _save + end + break + end # end sequence + + set_failed_rule :_Verbatim unless _tmp + return _tmp + end + + # HorizontalRule = @NonindentSpace ("*" @Sp "*" @Sp "*" (@Sp "*")* | "-" @Sp "-" @Sp "-" (@Sp "-")* | "_" @Sp "_" @Sp "_" (@Sp "_")*) @Sp @Newline @BlankLine+ { RDoc::Markup::Rule.new 1 } + def _HorizontalRule + + _save = self.pos + while true # sequence + _tmp = _NonindentSpace() + unless _tmp + self.pos = _save + break + end + + _save1 = self.pos + while true # choice + + _save2 = self.pos + while true # sequence + _tmp = match_string("*") + unless _tmp + self.pos = _save2 + break + end + _tmp = _Sp() + unless _tmp + self.pos = _save2 + break + end + _tmp = match_string("*") + unless _tmp + self.pos = _save2 + break + end + _tmp = _Sp() + unless _tmp + self.pos = _save2 + break + end + _tmp = match_string("*") + unless _tmp + self.pos = _save2 + break + end + while true + + _save4 = self.pos + while true # sequence + _tmp = _Sp() + unless _tmp + self.pos = _save4 + break + end + _tmp = match_string("*") + unless _tmp + self.pos = _save4 + end + break + end # end sequence + + break unless _tmp + end + _tmp = true + unless _tmp + self.pos = _save2 + end + break + end # end sequence + + break if _tmp + self.pos = _save1 + + _save5 = self.pos + while true # sequence + _tmp = match_string("-") + unless _tmp + self.pos = _save5 + break + end + _tmp = _Sp() + unless _tmp + self.pos = _save5 + break + end + _tmp = match_string("-") + unless _tmp + self.pos = _save5 + break + end + _tmp = _Sp() + unless _tmp + self.pos = _save5 + break + end + _tmp = match_string("-") + unless _tmp + self.pos = _save5 + break + end + while true + + _save7 = self.pos + while true # sequence + _tmp = _Sp() + unless _tmp + self.pos = _save7 + break + end + _tmp = match_string("-") + unless _tmp + self.pos = _save7 + end + break + end # end sequence + + break unless _tmp + end + _tmp = true + unless _tmp + self.pos = _save5 + end + break + end # end sequence + + break if _tmp + self.pos = _save1 + + _save8 = self.pos + while true # sequence + _tmp = match_string("_") + unless _tmp + self.pos = _save8 + break + end + _tmp = _Sp() + unless _tmp + self.pos = _save8 + break + end + _tmp = match_string("_") + unless _tmp + self.pos = _save8 + break + end + _tmp = _Sp() + unless _tmp + self.pos = _save8 + break + end + _tmp = match_string("_") + unless _tmp + self.pos = _save8 + break + end + while true + + _save10 = self.pos + while true # sequence + _tmp = _Sp() + unless _tmp + self.pos = _save10 + break + end + _tmp = match_string("_") + unless _tmp + self.pos = _save10 + end + break + end # end sequence + + break unless _tmp + end + _tmp = true + unless _tmp + self.pos = _save8 + end + break + end # end sequence + + break if _tmp + self.pos = _save1 + break + end # end choice + + unless _tmp + self.pos = _save + break + end + _tmp = _Sp() + unless _tmp + self.pos = _save + break + end + _tmp = _Newline() + unless _tmp + self.pos = _save + break + end + _save11 = self.pos + _tmp = _BlankLine() + if _tmp + while true + _tmp = _BlankLine() + break unless _tmp + end + _tmp = true + else + self.pos = _save11 + end + unless _tmp + self.pos = _save + break + end + @result = begin; RDoc::Markup::Rule.new 1 ; end + _tmp = true + unless _tmp + self.pos = _save + end + break + end # end sequence + + set_failed_rule :_HorizontalRule unless _tmp + return _tmp + end + + # Bullet = !HorizontalRule @NonindentSpace /[+*-]/ @Spacechar+ + def _Bullet + + _save = self.pos + while true # sequence + _save1 = self.pos + _tmp = apply(:_HorizontalRule) + _tmp = _tmp ? nil : true + self.pos = _save1 + unless _tmp + self.pos = _save + break + end + _tmp = _NonindentSpace() + unless _tmp + self.pos = _save + break + end + _tmp = scan(/\A(?-mix:[+*-])/) + unless _tmp + self.pos = _save + break + end + _save2 = self.pos + _tmp = _Spacechar() + if _tmp + while true + _tmp = _Spacechar() + break unless _tmp + end + _tmp = true + else + self.pos = _save2 + end + unless _tmp + self.pos = _save + end + break + end # end sequence + + set_failed_rule :_Bullet unless _tmp + return _tmp + end + + # BulletList = &Bullet (ListTight | ListLoose):a { RDoc::Markup::List.new(:BULLET, *a) } + def _BulletList + + _save = self.pos + while true # sequence + _save1 = self.pos + _tmp = apply(:_Bullet) + self.pos = _save1 + unless _tmp + self.pos = _save + break + end + + _save2 = self.pos + while true # choice + _tmp = apply(:_ListTight) + break if _tmp + self.pos = _save2 + _tmp = apply(:_ListLoose) + break if _tmp + self.pos = _save2 + break + end # end choice + + a = @result + unless _tmp + self.pos = _save + break + end + @result = begin; RDoc::Markup::List.new(:BULLET, *a) ; end + _tmp = true + unless _tmp + self.pos = _save + end + break + end # end sequence + + set_failed_rule :_BulletList unless _tmp + return _tmp + end + + # ListTight = ListItemTight+:a @BlankLine* !(Bullet | Enumerator) { a } + def _ListTight + + _save = self.pos + while true # sequence + _save1 = self.pos + _ary = [] + _tmp = apply(:_ListItemTight) + if _tmp + _ary << @result + while true + _tmp = apply(:_ListItemTight) + _ary << @result if _tmp + break unless _tmp + end + _tmp = true + @result = _ary + else + self.pos = _save1 + end + a = @result + unless _tmp + self.pos = _save + break + end + while true + _tmp = _BlankLine() + break unless _tmp + end + _tmp = true + unless _tmp + self.pos = _save + break + end + _save3 = self.pos + + _save4 = self.pos + while true # choice + _tmp = apply(:_Bullet) + break if _tmp + self.pos = _save4 + _tmp = apply(:_Enumerator) + break if _tmp + self.pos = _save4 + break + end # end choice + + _tmp = _tmp ? nil : true + self.pos = _save3 + unless _tmp + self.pos = _save + break + end + @result = begin; a ; end + _tmp = true + unless _tmp + self.pos = _save + end + break + end # end sequence + + set_failed_rule :_ListTight unless _tmp + return _tmp + end + + # ListLoose = @StartList:a (ListItem:b @BlankLine* { a << b })+ { a } + def _ListLoose + + _save = self.pos + while true # sequence + _tmp = _StartList() + a = @result + unless _tmp + self.pos = _save + break + end + _save1 = self.pos + + _save2 = self.pos + while true # sequence + _tmp = apply(:_ListItem) + b = @result + unless _tmp + self.pos = _save2 + break + end + while true + _tmp = _BlankLine() + break unless _tmp + end + _tmp = true + unless _tmp + self.pos = _save2 + break + end + @result = begin; a << b ; end + _tmp = true + unless _tmp + self.pos = _save2 + end + break + end # end sequence + + if _tmp + while true + + _save4 = self.pos + while true # sequence + _tmp = apply(:_ListItem) + b = @result + unless _tmp + self.pos = _save4 + break + end + while true + _tmp = _BlankLine() + break unless _tmp + end + _tmp = true + unless _tmp + self.pos = _save4 + break + end + @result = begin; a << b ; end + _tmp = true + unless _tmp + self.pos = _save4 + end + break + end # end sequence + + break unless _tmp + end + _tmp = true + else + self.pos = _save1 + end + unless _tmp + self.pos = _save + break + end + @result = begin; a ; end + _tmp = true + unless _tmp + self.pos = _save + end + break + end # end sequence + + set_failed_rule :_ListLoose unless _tmp + return _tmp + end + + # ListItem = (Bullet | Enumerator) @StartList:a ListBlock:b { a << b } (ListContinuationBlock:c { a.push(*c) })* { list_item_from a } + def _ListItem + + _save = self.pos + while true # sequence + + _save1 = self.pos + while true # choice + _tmp = apply(:_Bullet) + break if _tmp + self.pos = _save1 + _tmp = apply(:_Enumerator) + break if _tmp + self.pos = _save1 + break + end # end choice + + unless _tmp + self.pos = _save + break + end + _tmp = _StartList() + a = @result + unless _tmp + self.pos = _save + break + end + _tmp = apply(:_ListBlock) + b = @result + unless _tmp + self.pos = _save + break + end + @result = begin; a << b ; end + _tmp = true + unless _tmp + self.pos = _save + break + end + while true + + _save3 = self.pos + while true # sequence + _tmp = apply(:_ListContinuationBlock) + c = @result + unless _tmp + self.pos = _save3 + break + end + @result = begin; a.push(*c) ; end + _tmp = true + unless _tmp + self.pos = _save3 + end + break + end # end sequence + + break unless _tmp + end + _tmp = true + unless _tmp + self.pos = _save + break + end + @result = begin; list_item_from a ; end + _tmp = true + unless _tmp + self.pos = _save + end + break + end # end sequence + + set_failed_rule :_ListItem unless _tmp + return _tmp + end + + # ListItemTight = (Bullet | Enumerator) ListBlock:a (!@BlankLine ListContinuationBlock:b { a.push(*b) })* !ListContinuationBlock { list_item_from a } + def _ListItemTight + + _save = self.pos + while true # sequence + + _save1 = self.pos + while true # choice + _tmp = apply(:_Bullet) + break if _tmp + self.pos = _save1 + _tmp = apply(:_Enumerator) + break if _tmp + self.pos = _save1 + break + end # end choice + + unless _tmp + self.pos = _save + break + end + _tmp = apply(:_ListBlock) + a = @result + unless _tmp + self.pos = _save + break + end + while true + + _save3 = self.pos + while true # sequence + _save4 = self.pos + _tmp = _BlankLine() + _tmp = _tmp ? nil : true + self.pos = _save4 + unless _tmp + self.pos = _save3 + break + end + _tmp = apply(:_ListContinuationBlock) + b = @result + unless _tmp + self.pos = _save3 + break + end + @result = begin; a.push(*b) ; end + _tmp = true + unless _tmp + self.pos = _save3 + end + break + end # end sequence + + break unless _tmp + end + _tmp = true + unless _tmp + self.pos = _save + break + end + _save5 = self.pos + _tmp = apply(:_ListContinuationBlock) + _tmp = _tmp ? nil : true + self.pos = _save5 + unless _tmp + self.pos = _save + break + end + @result = begin; list_item_from a ; end + _tmp = true + unless _tmp + self.pos = _save + end + break + end # end sequence + + set_failed_rule :_ListItemTight unless _tmp + return _tmp + end + + # ListBlock = !@BlankLine Line:a ListBlockLine*:c { [a, *c] } + def _ListBlock + + _save = self.pos + while true # sequence + _save1 = self.pos + _tmp = _BlankLine() + _tmp = _tmp ? nil : true + self.pos = _save1 + unless _tmp + self.pos = _save + break + end + _tmp = apply(:_Line) + a = @result + unless _tmp + self.pos = _save + break + end + _ary = [] + while true + _tmp = apply(:_ListBlockLine) + _ary << @result if _tmp + break unless _tmp + end + _tmp = true + @result = _ary + c = @result + unless _tmp + self.pos = _save + break + end + @result = begin; [a, *c] ; end + _tmp = true + unless _tmp + self.pos = _save + end + break + end # end sequence + + set_failed_rule :_ListBlock unless _tmp + return _tmp + end + + # ListContinuationBlock = @StartList:a @BlankLine* { a << "\n" } (Indent ListBlock:b { a.concat b })+ { a } + def _ListContinuationBlock + + _save = self.pos + while true # sequence + _tmp = _StartList() + a = @result + unless _tmp + self.pos = _save + break + end + while true + _tmp = _BlankLine() + break unless _tmp + end + _tmp = true + unless _tmp + self.pos = _save + break + end + @result = begin; a << "\n" ; end + _tmp = true + unless _tmp + self.pos = _save + break + end + _save2 = self.pos + + _save3 = self.pos + while true # sequence + _tmp = apply(:_Indent) + unless _tmp + self.pos = _save3 + break + end + _tmp = apply(:_ListBlock) + b = @result + unless _tmp + self.pos = _save3 + break + end + @result = begin; a.concat b ; end + _tmp = true + unless _tmp + self.pos = _save3 + end + break + end # end sequence + + if _tmp + while true + + _save4 = self.pos + while true # sequence + _tmp = apply(:_Indent) + unless _tmp + self.pos = _save4 + break + end + _tmp = apply(:_ListBlock) + b = @result + unless _tmp + self.pos = _save4 + break + end + @result = begin; a.concat b ; end + _tmp = true + unless _tmp + self.pos = _save4 + end + break + end # end sequence + + break unless _tmp + end + _tmp = true + else + self.pos = _save2 + end + unless _tmp + self.pos = _save + break + end + @result = begin; a ; end + _tmp = true + unless _tmp + self.pos = _save + end + break + end # end sequence + + set_failed_rule :_ListContinuationBlock unless _tmp + return _tmp + end + + # Enumerator = @NonindentSpace [0-9]+ "." @Spacechar+ + def _Enumerator + + _save = self.pos + while true # sequence + _tmp = _NonindentSpace() + unless _tmp + self.pos = _save + break + end + _save1 = self.pos + _save2 = self.pos + _tmp = get_byte + if _tmp + unless _tmp >= 48 and _tmp <= 57 + self.pos = _save2 + _tmp = nil + end + end + if _tmp + while true + _save3 = self.pos + _tmp = get_byte + if _tmp + unless _tmp >= 48 and _tmp <= 57 + self.pos = _save3 + _tmp = nil + end + end + break unless _tmp + end + _tmp = true + else + self.pos = _save1 + end + unless _tmp + self.pos = _save + break + end + _tmp = match_string(".") + unless _tmp + self.pos = _save + break + end + _save4 = self.pos + _tmp = _Spacechar() + if _tmp + while true + _tmp = _Spacechar() + break unless _tmp + end + _tmp = true + else + self.pos = _save4 + end + unless _tmp + self.pos = _save + end + break + end # end sequence + + set_failed_rule :_Enumerator unless _tmp + return _tmp + end + + # OrderedList = &Enumerator (ListTight | ListLoose):a { RDoc::Markup::List.new(:NUMBER, *a) } + def _OrderedList + + _save = self.pos + while true # sequence + _save1 = self.pos + _tmp = apply(:_Enumerator) + self.pos = _save1 + unless _tmp + self.pos = _save + break + end + + _save2 = self.pos + while true # choice + _tmp = apply(:_ListTight) + break if _tmp + self.pos = _save2 + _tmp = apply(:_ListLoose) + break if _tmp + self.pos = _save2 + break + end # end choice + + a = @result + unless _tmp + self.pos = _save + break + end + @result = begin; RDoc::Markup::List.new(:NUMBER, *a) ; end + _tmp = true + unless _tmp + self.pos = _save + end + break + end # end sequence + + set_failed_rule :_OrderedList unless _tmp + return _tmp + end + + # ListBlockLine = !@BlankLine !(Indent? (Bullet | Enumerator)) !HorizontalRule OptionallyIndentedLine + def _ListBlockLine + + _save = self.pos + while true # sequence + _save1 = self.pos + _tmp = _BlankLine() + _tmp = _tmp ? nil : true + self.pos = _save1 + unless _tmp + self.pos = _save + break + end + _save2 = self.pos + + _save3 = self.pos + while true # sequence + _save4 = self.pos + _tmp = apply(:_Indent) + unless _tmp + _tmp = true + self.pos = _save4 + end + unless _tmp + self.pos = _save3 + break + end + + _save5 = self.pos + while true # choice + _tmp = apply(:_Bullet) + break if _tmp + self.pos = _save5 + _tmp = apply(:_Enumerator) + break if _tmp + self.pos = _save5 + break + end # end choice + + unless _tmp + self.pos = _save3 + end + break + end # end sequence + + _tmp = _tmp ? nil : true + self.pos = _save2 + unless _tmp + self.pos = _save + break + end + _save6 = self.pos + _tmp = apply(:_HorizontalRule) + _tmp = _tmp ? nil : true + self.pos = _save6 + unless _tmp + self.pos = _save + break + end + _tmp = apply(:_OptionallyIndentedLine) + unless _tmp + self.pos = _save + end + break + end # end sequence + + set_failed_rule :_ListBlockLine unless _tmp + return _tmp + end + + # HtmlOpenAnchor = "<" Spnl ("a" | "A") Spnl HtmlAttribute* ">" + def _HtmlOpenAnchor + + _save = self.pos + while true # sequence + _tmp = match_string("<") + unless _tmp + self.pos = _save + break + end + _tmp = apply(:_Spnl) + unless _tmp + self.pos = _save + break + end + + _save1 = self.pos + while true # choice + _tmp = match_string("a") + break if _tmp + self.pos = _save1 + _tmp = match_string("A") + break if _tmp + self.pos = _save1 + break + end # end choice + + unless _tmp + self.pos = _save + break + end + _tmp = apply(:_Spnl) + unless _tmp + self.pos = _save + break + end + while true + _tmp = apply(:_HtmlAttribute) + break unless _tmp + end + _tmp = true + unless _tmp + self.pos = _save + break + end + _tmp = match_string(">") + unless _tmp + self.pos = _save + end + break + end # end sequence + + set_failed_rule :_HtmlOpenAnchor unless _tmp + return _tmp + end + + # HtmlCloseAnchor = "<" Spnl "/" ("a" | "A") Spnl ">" + def _HtmlCloseAnchor + + _save = self.pos + while true # sequence + _tmp = match_string("<") + unless _tmp + self.pos = _save + break + end + _tmp = apply(:_Spnl) + unless _tmp + self.pos = _save + break + end + _tmp = match_string("/") + unless _tmp + self.pos = _save + break + end + + _save1 = self.pos + while true # choice + _tmp = match_string("a") + break if _tmp + self.pos = _save1 + _tmp = match_string("A") + break if _tmp + self.pos = _save1 + break + end # end choice + + unless _tmp + self.pos = _save + break + end + _tmp = apply(:_Spnl) + unless _tmp + self.pos = _save + break + end + _tmp = match_string(">") + unless _tmp + self.pos = _save + end + break + end # end sequence + + set_failed_rule :_HtmlCloseAnchor unless _tmp + return _tmp + end + + # HtmlAnchor = HtmlOpenAnchor (HtmlAnchor | !HtmlCloseAnchor .)* HtmlCloseAnchor + def _HtmlAnchor + + _save = self.pos + while true # sequence + _tmp = apply(:_HtmlOpenAnchor) + unless _tmp + self.pos = _save + break + end + while true + + _save2 = self.pos + while true # choice + _tmp = apply(:_HtmlAnchor) + break if _tmp + self.pos = _save2 + + _save3 = self.pos + while true # sequence + _save4 = self.pos + _tmp = apply(:_HtmlCloseAnchor) + _tmp = _tmp ? nil : true + self.pos = _save4 + unless _tmp + self.pos = _save3 + break + end + _tmp = get_byte + unless _tmp + self.pos = _save3 + end + break + end # end sequence + + break if _tmp + self.pos = _save2 + break + end # end choice + + break unless _tmp + end + _tmp = true + unless _tmp + self.pos = _save + break + end + _tmp = apply(:_HtmlCloseAnchor) + unless _tmp + self.pos = _save + end + break + end # end sequence + + set_failed_rule :_HtmlAnchor unless _tmp + return _tmp + end + + # HtmlBlockOpenAddress = "<" Spnl ("address" | "ADDRESS") Spnl HtmlAttribute* ">" + def _HtmlBlockOpenAddress + + _save = self.pos + while true # sequence + _tmp = match_string("<") + unless _tmp + self.pos = _save + break + end + _tmp = apply(:_Spnl) + unless _tmp + self.pos = _save + break + end + + _save1 = self.pos + while true # choice + _tmp = match_string("address") + break if _tmp + self.pos = _save1 + _tmp = match_string("ADDRESS") + break if _tmp + self.pos = _save1 + break + end # end choice + + unless _tmp + self.pos = _save + break + end + _tmp = apply(:_Spnl) + unless _tmp + self.pos = _save + break + end + while true + _tmp = apply(:_HtmlAttribute) + break unless _tmp + end + _tmp = true + unless _tmp + self.pos = _save + break + end + _tmp = match_string(">") + unless _tmp + self.pos = _save + end + break + end # end sequence + + set_failed_rule :_HtmlBlockOpenAddress unless _tmp + return _tmp + end + + # HtmlBlockCloseAddress = "<" Spnl "/" ("address" | "ADDRESS") Spnl ">" + def _HtmlBlockCloseAddress + + _save = self.pos + while true # sequence + _tmp = match_string("<") + unless _tmp + self.pos = _save + break + end + _tmp = apply(:_Spnl) + unless _tmp + self.pos = _save + break + end + _tmp = match_string("/") + unless _tmp + self.pos = _save + break + end + + _save1 = self.pos + while true # choice + _tmp = match_string("address") + break if _tmp + self.pos = _save1 + _tmp = match_string("ADDRESS") + break if _tmp + self.pos = _save1 + break + end # end choice + + unless _tmp + self.pos = _save + break + end + _tmp = apply(:_Spnl) + unless _tmp + self.pos = _save + break + end + _tmp = match_string(">") + unless _tmp + self.pos = _save + end + break + end # end sequence + + set_failed_rule :_HtmlBlockCloseAddress unless _tmp + return _tmp + end + + # HtmlBlockAddress = HtmlBlockOpenAddress (HtmlBlockAddress | !HtmlBlockCloseAddress .)* HtmlBlockCloseAddress + def _HtmlBlockAddress + + _save = self.pos + while true # sequence + _tmp = apply(:_HtmlBlockOpenAddress) + unless _tmp + self.pos = _save + break + end + while true + + _save2 = self.pos + while true # choice + _tmp = apply(:_HtmlBlockAddress) + break if _tmp + self.pos = _save2 + + _save3 = self.pos + while true # sequence + _save4 = self.pos + _tmp = apply(:_HtmlBlockCloseAddress) + _tmp = _tmp ? nil : true + self.pos = _save4 + unless _tmp + self.pos = _save3 + break + end + _tmp = get_byte + unless _tmp + self.pos = _save3 + end + break + end # end sequence + + break if _tmp + self.pos = _save2 + break + end # end choice + + break unless _tmp + end + _tmp = true + unless _tmp + self.pos = _save + break + end + _tmp = apply(:_HtmlBlockCloseAddress) + unless _tmp + self.pos = _save + end + break + end # end sequence + + set_failed_rule :_HtmlBlockAddress unless _tmp + return _tmp + end + + # HtmlBlockOpenBlockquote = "<" Spnl ("blockquote" | "BLOCKQUOTE") Spnl HtmlAttribute* ">" + def _HtmlBlockOpenBlockquote + + _save = self.pos + while true # sequence + _tmp = match_string("<") + unless _tmp + self.pos = _save + break + end + _tmp = apply(:_Spnl) + unless _tmp + self.pos = _save + break + end + + _save1 = self.pos + while true # choice + _tmp = match_string("blockquote") + break if _tmp + self.pos = _save1 + _tmp = match_string("BLOCKQUOTE") + break if _tmp + self.pos = _save1 + break + end # end choice + + unless _tmp + self.pos = _save + break + end + _tmp = apply(:_Spnl) + unless _tmp + self.pos = _save + break + end + while true + _tmp = apply(:_HtmlAttribute) + break unless _tmp + end + _tmp = true + unless _tmp + self.pos = _save + break + end + _tmp = match_string(">") + unless _tmp + self.pos = _save + end + break + end # end sequence + + set_failed_rule :_HtmlBlockOpenBlockquote unless _tmp + return _tmp + end + + # HtmlBlockCloseBlockquote = "<" Spnl "/" ("blockquote" | "BLOCKQUOTE") Spnl ">" + def _HtmlBlockCloseBlockquote + + _save = self.pos + while true # sequence + _tmp = match_string("<") + unless _tmp + self.pos = _save + break + end + _tmp = apply(:_Spnl) + unless _tmp + self.pos = _save + break + end + _tmp = match_string("/") + unless _tmp + self.pos = _save + break + end + + _save1 = self.pos + while true # choice + _tmp = match_string("blockquote") + break if _tmp + self.pos = _save1 + _tmp = match_string("BLOCKQUOTE") + break if _tmp + self.pos = _save1 + break + end # end choice + + unless _tmp + self.pos = _save + break + end + _tmp = apply(:_Spnl) + unless _tmp + self.pos = _save + break + end + _tmp = match_string(">") + unless _tmp + self.pos = _save + end + break + end # end sequence + + set_failed_rule :_HtmlBlockCloseBlockquote unless _tmp + return _tmp + end + + # HtmlBlockBlockquote = HtmlBlockOpenBlockquote (HtmlBlockBlockquote | !HtmlBlockCloseBlockquote .)* HtmlBlockCloseBlockquote + def _HtmlBlockBlockquote + + _save = self.pos + while true # sequence + _tmp = apply(:_HtmlBlockOpenBlockquote) + unless _tmp + self.pos = _save + break + end + while true + + _save2 = self.pos + while true # choice + _tmp = apply(:_HtmlBlockBlockquote) + break if _tmp + self.pos = _save2 + + _save3 = self.pos + while true # sequence + _save4 = self.pos + _tmp = apply(:_HtmlBlockCloseBlockquote) + _tmp = _tmp ? nil : true + self.pos = _save4 + unless _tmp + self.pos = _save3 + break + end + _tmp = get_byte + unless _tmp + self.pos = _save3 + end + break + end # end sequence + + break if _tmp + self.pos = _save2 + break + end # end choice + + break unless _tmp + end + _tmp = true + unless _tmp + self.pos = _save + break + end + _tmp = apply(:_HtmlBlockCloseBlockquote) + unless _tmp + self.pos = _save + end + break + end # end sequence + + set_failed_rule :_HtmlBlockBlockquote unless _tmp + return _tmp + end + + # HtmlBlockOpenCenter = "<" Spnl ("center" | "CENTER") Spnl HtmlAttribute* ">" + def _HtmlBlockOpenCenter + + _save = self.pos + while true # sequence + _tmp = match_string("<") + unless _tmp + self.pos = _save + break + end + _tmp = apply(:_Spnl) + unless _tmp + self.pos = _save + break + end + + _save1 = self.pos + while true # choice + _tmp = match_string("center") + break if _tmp + self.pos = _save1 + _tmp = match_string("CENTER") + break if _tmp + self.pos = _save1 + break + end # end choice + + unless _tmp + self.pos = _save + break + end + _tmp = apply(:_Spnl) + unless _tmp + self.pos = _save + break + end + while true + _tmp = apply(:_HtmlAttribute) + break unless _tmp + end + _tmp = true + unless _tmp + self.pos = _save + break + end + _tmp = match_string(">") + unless _tmp + self.pos = _save + end + break + end # end sequence + + set_failed_rule :_HtmlBlockOpenCenter unless _tmp + return _tmp + end + + # HtmlBlockCloseCenter = "<" Spnl "/" ("center" | "CENTER") Spnl ">" + def _HtmlBlockCloseCenter + + _save = self.pos + while true # sequence + _tmp = match_string("<") + unless _tmp + self.pos = _save + break + end + _tmp = apply(:_Spnl) + unless _tmp + self.pos = _save + break + end + _tmp = match_string("/") + unless _tmp + self.pos = _save + break + end + + _save1 = self.pos + while true # choice + _tmp = match_string("center") + break if _tmp + self.pos = _save1 + _tmp = match_string("CENTER") + break if _tmp + self.pos = _save1 + break + end # end choice + + unless _tmp + self.pos = _save + break + end + _tmp = apply(:_Spnl) + unless _tmp + self.pos = _save + break + end + _tmp = match_string(">") + unless _tmp + self.pos = _save + end + break + end # end sequence + + set_failed_rule :_HtmlBlockCloseCenter unless _tmp + return _tmp + end + + # HtmlBlockCenter = HtmlBlockOpenCenter (HtmlBlockCenter | !HtmlBlockCloseCenter .)* HtmlBlockCloseCenter + def _HtmlBlockCenter + + _save = self.pos + while true # sequence + _tmp = apply(:_HtmlBlockOpenCenter) + unless _tmp + self.pos = _save + break + end + while true + + _save2 = self.pos + while true # choice + _tmp = apply(:_HtmlBlockCenter) + break if _tmp + self.pos = _save2 + + _save3 = self.pos + while true # sequence + _save4 = self.pos + _tmp = apply(:_HtmlBlockCloseCenter) + _tmp = _tmp ? nil : true + self.pos = _save4 + unless _tmp + self.pos = _save3 + break + end + _tmp = get_byte + unless _tmp + self.pos = _save3 + end + break + end # end sequence + + break if _tmp + self.pos = _save2 + break + end # end choice + + break unless _tmp + end + _tmp = true + unless _tmp + self.pos = _save + break + end + _tmp = apply(:_HtmlBlockCloseCenter) + unless _tmp + self.pos = _save + end + break + end # end sequence + + set_failed_rule :_HtmlBlockCenter unless _tmp + return _tmp + end + + # HtmlBlockOpenDir = "<" Spnl ("dir" | "DIR") Spnl HtmlAttribute* ">" + def _HtmlBlockOpenDir + + _save = self.pos + while true # sequence + _tmp = match_string("<") + unless _tmp + self.pos = _save + break + end + _tmp = apply(:_Spnl) + unless _tmp + self.pos = _save + break + end + + _save1 = self.pos + while true # choice + _tmp = match_string("dir") + break if _tmp + self.pos = _save1 + _tmp = match_string("DIR") + break if _tmp + self.pos = _save1 + break + end # end choice + + unless _tmp + self.pos = _save + break + end + _tmp = apply(:_Spnl) + unless _tmp + self.pos = _save + break + end + while true + _tmp = apply(:_HtmlAttribute) + break unless _tmp + end + _tmp = true + unless _tmp + self.pos = _save + break + end + _tmp = match_string(">") + unless _tmp + self.pos = _save + end + break + end # end sequence + + set_failed_rule :_HtmlBlockOpenDir unless _tmp + return _tmp + end + + # HtmlBlockCloseDir = "<" Spnl "/" ("dir" | "DIR") Spnl ">" + def _HtmlBlockCloseDir + + _save = self.pos + while true # sequence + _tmp = match_string("<") + unless _tmp + self.pos = _save + break + end + _tmp = apply(:_Spnl) + unless _tmp + self.pos = _save + break + end + _tmp = match_string("/") + unless _tmp + self.pos = _save + break + end + + _save1 = self.pos + while true # choice + _tmp = match_string("dir") + break if _tmp + self.pos = _save1 + _tmp = match_string("DIR") + break if _tmp + self.pos = _save1 + break + end # end choice + + unless _tmp + self.pos = _save + break + end + _tmp = apply(:_Spnl) + unless _tmp + self.pos = _save + break + end + _tmp = match_string(">") + unless _tmp + self.pos = _save + end + break + end # end sequence + + set_failed_rule :_HtmlBlockCloseDir unless _tmp + return _tmp + end + + # HtmlBlockDir = HtmlBlockOpenDir (HtmlBlockDir | !HtmlBlockCloseDir .)* HtmlBlockCloseDir + def _HtmlBlockDir + + _save = self.pos + while true # sequence + _tmp = apply(:_HtmlBlockOpenDir) + unless _tmp + self.pos = _save + break + end + while true + + _save2 = self.pos + while true # choice + _tmp = apply(:_HtmlBlockDir) + break if _tmp + self.pos = _save2 + + _save3 = self.pos + while true # sequence + _save4 = self.pos + _tmp = apply(:_HtmlBlockCloseDir) + _tmp = _tmp ? nil : true + self.pos = _save4 + unless _tmp + self.pos = _save3 + break + end + _tmp = get_byte + unless _tmp + self.pos = _save3 + end + break + end # end sequence + + break if _tmp + self.pos = _save2 + break + end # end choice + + break unless _tmp + end + _tmp = true + unless _tmp + self.pos = _save + break + end + _tmp = apply(:_HtmlBlockCloseDir) + unless _tmp + self.pos = _save + end + break + end # end sequence + + set_failed_rule :_HtmlBlockDir unless _tmp + return _tmp + end + + # HtmlBlockOpenDiv = "<" Spnl ("div" | "DIV") Spnl HtmlAttribute* ">" + def _HtmlBlockOpenDiv + + _save = self.pos + while true # sequence + _tmp = match_string("<") + unless _tmp + self.pos = _save + break + end + _tmp = apply(:_Spnl) + unless _tmp + self.pos = _save + break + end + + _save1 = self.pos + while true # choice + _tmp = match_string("div") + break if _tmp + self.pos = _save1 + _tmp = match_string("DIV") + break if _tmp + self.pos = _save1 + break + end # end choice + + unless _tmp + self.pos = _save + break + end + _tmp = apply(:_Spnl) + unless _tmp + self.pos = _save + break + end + while true + _tmp = apply(:_HtmlAttribute) + break unless _tmp + end + _tmp = true + unless _tmp + self.pos = _save + break + end + _tmp = match_string(">") + unless _tmp + self.pos = _save + end + break + end # end sequence + + set_failed_rule :_HtmlBlockOpenDiv unless _tmp + return _tmp + end + + # HtmlBlockCloseDiv = "<" Spnl "/" ("div" | "DIV") Spnl ">" + def _HtmlBlockCloseDiv + + _save = self.pos + while true # sequence + _tmp = match_string("<") + unless _tmp + self.pos = _save + break + end + _tmp = apply(:_Spnl) + unless _tmp + self.pos = _save + break + end + _tmp = match_string("/") + unless _tmp + self.pos = _save + break + end + + _save1 = self.pos + while true # choice + _tmp = match_string("div") + break if _tmp + self.pos = _save1 + _tmp = match_string("DIV") + break if _tmp + self.pos = _save1 + break + end # end choice + + unless _tmp + self.pos = _save + break + end + _tmp = apply(:_Spnl) + unless _tmp + self.pos = _save + break + end + _tmp = match_string(">") + unless _tmp + self.pos = _save + end + break + end # end sequence + + set_failed_rule :_HtmlBlockCloseDiv unless _tmp + return _tmp + end + + # HtmlBlockDiv = HtmlBlockOpenDiv (HtmlBlockDiv | !HtmlBlockCloseDiv .)* HtmlBlockCloseDiv + def _HtmlBlockDiv + + _save = self.pos + while true # sequence + _tmp = apply(:_HtmlBlockOpenDiv) + unless _tmp + self.pos = _save + break + end + while true + + _save2 = self.pos + while true # choice + _tmp = apply(:_HtmlBlockDiv) + break if _tmp + self.pos = _save2 + + _save3 = self.pos + while true # sequence + _save4 = self.pos + _tmp = apply(:_HtmlBlockCloseDiv) + _tmp = _tmp ? nil : true + self.pos = _save4 + unless _tmp + self.pos = _save3 + break + end + _tmp = get_byte + unless _tmp + self.pos = _save3 + end + break + end # end sequence + + break if _tmp + self.pos = _save2 + break + end # end choice + + break unless _tmp + end + _tmp = true + unless _tmp + self.pos = _save + break + end + _tmp = apply(:_HtmlBlockCloseDiv) + unless _tmp + self.pos = _save + end + break + end # end sequence + + set_failed_rule :_HtmlBlockDiv unless _tmp + return _tmp + end + + # HtmlBlockOpenDl = "<" Spnl ("dl" | "DL") Spnl HtmlAttribute* ">" + def _HtmlBlockOpenDl + + _save = self.pos + while true # sequence + _tmp = match_string("<") + unless _tmp + self.pos = _save + break + end + _tmp = apply(:_Spnl) + unless _tmp + self.pos = _save + break + end + + _save1 = self.pos + while true # choice + _tmp = match_string("dl") + break if _tmp + self.pos = _save1 + _tmp = match_string("DL") + break if _tmp + self.pos = _save1 + break + end # end choice + + unless _tmp + self.pos = _save + break + end + _tmp = apply(:_Spnl) + unless _tmp + self.pos = _save + break + end + while true + _tmp = apply(:_HtmlAttribute) + break unless _tmp + end + _tmp = true + unless _tmp + self.pos = _save + break + end + _tmp = match_string(">") + unless _tmp + self.pos = _save + end + break + end # end sequence + + set_failed_rule :_HtmlBlockOpenDl unless _tmp + return _tmp + end + + # HtmlBlockCloseDl = "<" Spnl "/" ("dl" | "DL") Spnl ">" + def _HtmlBlockCloseDl + + _save = self.pos + while true # sequence + _tmp = match_string("<") + unless _tmp + self.pos = _save + break + end + _tmp = apply(:_Spnl) + unless _tmp + self.pos = _save + break + end + _tmp = match_string("/") + unless _tmp + self.pos = _save + break + end + + _save1 = self.pos + while true # choice + _tmp = match_string("dl") + break if _tmp + self.pos = _save1 + _tmp = match_string("DL") + break if _tmp + self.pos = _save1 + break + end # end choice + + unless _tmp + self.pos = _save + break + end + _tmp = apply(:_Spnl) + unless _tmp + self.pos = _save + break + end + _tmp = match_string(">") + unless _tmp + self.pos = _save + end + break + end # end sequence + + set_failed_rule :_HtmlBlockCloseDl unless _tmp + return _tmp + end + + # HtmlBlockDl = HtmlBlockOpenDl (HtmlBlockDl | !HtmlBlockCloseDl .)* HtmlBlockCloseDl + def _HtmlBlockDl + + _save = self.pos + while true # sequence + _tmp = apply(:_HtmlBlockOpenDl) + unless _tmp + self.pos = _save + break + end + while true + + _save2 = self.pos + while true # choice + _tmp = apply(:_HtmlBlockDl) + break if _tmp + self.pos = _save2 + + _save3 = self.pos + while true # sequence + _save4 = self.pos + _tmp = apply(:_HtmlBlockCloseDl) + _tmp = _tmp ? nil : true + self.pos = _save4 + unless _tmp + self.pos = _save3 + break + end + _tmp = get_byte + unless _tmp + self.pos = _save3 + end + break + end # end sequence + + break if _tmp + self.pos = _save2 + break + end # end choice + + break unless _tmp + end + _tmp = true + unless _tmp + self.pos = _save + break + end + _tmp = apply(:_HtmlBlockCloseDl) + unless _tmp + self.pos = _save + end + break + end # end sequence + + set_failed_rule :_HtmlBlockDl unless _tmp + return _tmp + end + + # HtmlBlockOpenFieldset = "<" Spnl ("fieldset" | "FIELDSET") Spnl HtmlAttribute* ">" + def _HtmlBlockOpenFieldset + + _save = self.pos + while true # sequence + _tmp = match_string("<") + unless _tmp + self.pos = _save + break + end + _tmp = apply(:_Spnl) + unless _tmp + self.pos = _save + break + end + + _save1 = self.pos + while true # choice + _tmp = match_string("fieldset") + break if _tmp + self.pos = _save1 + _tmp = match_string("FIELDSET") + break if _tmp + self.pos = _save1 + break + end # end choice + + unless _tmp + self.pos = _save + break + end + _tmp = apply(:_Spnl) + unless _tmp + self.pos = _save + break + end + while true + _tmp = apply(:_HtmlAttribute) + break unless _tmp + end + _tmp = true + unless _tmp + self.pos = _save + break + end + _tmp = match_string(">") + unless _tmp + self.pos = _save + end + break + end # end sequence + + set_failed_rule :_HtmlBlockOpenFieldset unless _tmp + return _tmp + end + + # HtmlBlockCloseFieldset = "<" Spnl "/" ("fieldset" | "FIELDSET") Spnl ">" + def _HtmlBlockCloseFieldset + + _save = self.pos + while true # sequence + _tmp = match_string("<") + unless _tmp + self.pos = _save + break + end + _tmp = apply(:_Spnl) + unless _tmp + self.pos = _save + break + end + _tmp = match_string("/") + unless _tmp + self.pos = _save + break + end + + _save1 = self.pos + while true # choice + _tmp = match_string("fieldset") + break if _tmp + self.pos = _save1 + _tmp = match_string("FIELDSET") + break if _tmp + self.pos = _save1 + break + end # end choice + + unless _tmp + self.pos = _save + break + end + _tmp = apply(:_Spnl) + unless _tmp + self.pos = _save + break + end + _tmp = match_string(">") + unless _tmp + self.pos = _save + end + break + end # end sequence + + set_failed_rule :_HtmlBlockCloseFieldset unless _tmp + return _tmp + end + + # HtmlBlockFieldset = HtmlBlockOpenFieldset (HtmlBlockFieldset | !HtmlBlockCloseFieldset .)* HtmlBlockCloseFieldset + def _HtmlBlockFieldset + + _save = self.pos + while true # sequence + _tmp = apply(:_HtmlBlockOpenFieldset) + unless _tmp + self.pos = _save + break + end + while true + + _save2 = self.pos + while true # choice + _tmp = apply(:_HtmlBlockFieldset) + break if _tmp + self.pos = _save2 + + _save3 = self.pos + while true # sequence + _save4 = self.pos + _tmp = apply(:_HtmlBlockCloseFieldset) + _tmp = _tmp ? nil : true + self.pos = _save4 + unless _tmp + self.pos = _save3 + break + end + _tmp = get_byte + unless _tmp + self.pos = _save3 + end + break + end # end sequence + + break if _tmp + self.pos = _save2 + break + end # end choice + + break unless _tmp + end + _tmp = true + unless _tmp + self.pos = _save + break + end + _tmp = apply(:_HtmlBlockCloseFieldset) + unless _tmp + self.pos = _save + end + break + end # end sequence + + set_failed_rule :_HtmlBlockFieldset unless _tmp + return _tmp + end + + # HtmlBlockOpenForm = "<" Spnl ("form" | "FORM") Spnl HtmlAttribute* ">" + def _HtmlBlockOpenForm + + _save = self.pos + while true # sequence + _tmp = match_string("<") + unless _tmp + self.pos = _save + break + end + _tmp = apply(:_Spnl) + unless _tmp + self.pos = _save + break + end + + _save1 = self.pos + while true # choice + _tmp = match_string("form") + break if _tmp + self.pos = _save1 + _tmp = match_string("FORM") + break if _tmp + self.pos = _save1 + break + end # end choice + + unless _tmp + self.pos = _save + break + end + _tmp = apply(:_Spnl) + unless _tmp + self.pos = _save + break + end + while true + _tmp = apply(:_HtmlAttribute) + break unless _tmp + end + _tmp = true + unless _tmp + self.pos = _save + break + end + _tmp = match_string(">") + unless _tmp + self.pos = _save + end + break + end # end sequence + + set_failed_rule :_HtmlBlockOpenForm unless _tmp + return _tmp + end + + # HtmlBlockCloseForm = "<" Spnl "/" ("form" | "FORM") Spnl ">" + def _HtmlBlockCloseForm + + _save = self.pos + while true # sequence + _tmp = match_string("<") + unless _tmp + self.pos = _save + break + end + _tmp = apply(:_Spnl) + unless _tmp + self.pos = _save + break + end + _tmp = match_string("/") + unless _tmp + self.pos = _save + break + end + + _save1 = self.pos + while true # choice + _tmp = match_string("form") + break if _tmp + self.pos = _save1 + _tmp = match_string("FORM") + break if _tmp + self.pos = _save1 + break + end # end choice + + unless _tmp + self.pos = _save + break + end + _tmp = apply(:_Spnl) + unless _tmp + self.pos = _save + break + end + _tmp = match_string(">") + unless _tmp + self.pos = _save + end + break + end # end sequence + + set_failed_rule :_HtmlBlockCloseForm unless _tmp + return _tmp + end + + # HtmlBlockForm = HtmlBlockOpenForm (HtmlBlockForm | !HtmlBlockCloseForm .)* HtmlBlockCloseForm + def _HtmlBlockForm + + _save = self.pos + while true # sequence + _tmp = apply(:_HtmlBlockOpenForm) + unless _tmp + self.pos = _save + break + end + while true + + _save2 = self.pos + while true # choice + _tmp = apply(:_HtmlBlockForm) + break if _tmp + self.pos = _save2 + + _save3 = self.pos + while true # sequence + _save4 = self.pos + _tmp = apply(:_HtmlBlockCloseForm) + _tmp = _tmp ? nil : true + self.pos = _save4 + unless _tmp + self.pos = _save3 + break + end + _tmp = get_byte + unless _tmp + self.pos = _save3 + end + break + end # end sequence + + break if _tmp + self.pos = _save2 + break + end # end choice + + break unless _tmp + end + _tmp = true + unless _tmp + self.pos = _save + break + end + _tmp = apply(:_HtmlBlockCloseForm) + unless _tmp + self.pos = _save + end + break + end # end sequence + + set_failed_rule :_HtmlBlockForm unless _tmp + return _tmp + end + + # HtmlBlockOpenH1 = "<" Spnl ("h1" | "H1") Spnl HtmlAttribute* ">" + def _HtmlBlockOpenH1 + + _save = self.pos + while true # sequence + _tmp = match_string("<") + unless _tmp + self.pos = _save + break + end + _tmp = apply(:_Spnl) + unless _tmp + self.pos = _save + break + end + + _save1 = self.pos + while true # choice + _tmp = match_string("h1") + break if _tmp + self.pos = _save1 + _tmp = match_string("H1") + break if _tmp + self.pos = _save1 + break + end # end choice + + unless _tmp + self.pos = _save + break + end + _tmp = apply(:_Spnl) + unless _tmp + self.pos = _save + break + end + while true + _tmp = apply(:_HtmlAttribute) + break unless _tmp + end + _tmp = true + unless _tmp + self.pos = _save + break + end + _tmp = match_string(">") + unless _tmp + self.pos = _save + end + break + end # end sequence + + set_failed_rule :_HtmlBlockOpenH1 unless _tmp + return _tmp + end + + # HtmlBlockCloseH1 = "<" Spnl "/" ("h1" | "H1") Spnl ">" + def _HtmlBlockCloseH1 + + _save = self.pos + while true # sequence + _tmp = match_string("<") + unless _tmp + self.pos = _save + break + end + _tmp = apply(:_Spnl) + unless _tmp + self.pos = _save + break + end + _tmp = match_string("/") + unless _tmp + self.pos = _save + break + end + + _save1 = self.pos + while true # choice + _tmp = match_string("h1") + break if _tmp + self.pos = _save1 + _tmp = match_string("H1") + break if _tmp + self.pos = _save1 + break + end # end choice + + unless _tmp + self.pos = _save + break + end + _tmp = apply(:_Spnl) + unless _tmp + self.pos = _save + break + end + _tmp = match_string(">") + unless _tmp + self.pos = _save + end + break + end # end sequence + + set_failed_rule :_HtmlBlockCloseH1 unless _tmp + return _tmp + end + + # HtmlBlockH1 = HtmlBlockOpenH1 (HtmlBlockH1 | !HtmlBlockCloseH1 .)* HtmlBlockCloseH1 + def _HtmlBlockH1 + + _save = self.pos + while true # sequence + _tmp = apply(:_HtmlBlockOpenH1) + unless _tmp + self.pos = _save + break + end + while true + + _save2 = self.pos + while true # choice + _tmp = apply(:_HtmlBlockH1) + break if _tmp + self.pos = _save2 + + _save3 = self.pos + while true # sequence + _save4 = self.pos + _tmp = apply(:_HtmlBlockCloseH1) + _tmp = _tmp ? nil : true + self.pos = _save4 + unless _tmp + self.pos = _save3 + break + end + _tmp = get_byte + unless _tmp + self.pos = _save3 + end + break + end # end sequence + + break if _tmp + self.pos = _save2 + break + end # end choice + + break unless _tmp + end + _tmp = true + unless _tmp + self.pos = _save + break + end + _tmp = apply(:_HtmlBlockCloseH1) + unless _tmp + self.pos = _save + end + break + end # end sequence + + set_failed_rule :_HtmlBlockH1 unless _tmp + return _tmp + end + + # HtmlBlockOpenH2 = "<" Spnl ("h2" | "H2") Spnl HtmlAttribute* ">" + def _HtmlBlockOpenH2 + + _save = self.pos + while true # sequence + _tmp = match_string("<") + unless _tmp + self.pos = _save + break + end + _tmp = apply(:_Spnl) + unless _tmp + self.pos = _save + break + end + + _save1 = self.pos + while true # choice + _tmp = match_string("h2") + break if _tmp + self.pos = _save1 + _tmp = match_string("H2") + break if _tmp + self.pos = _save1 + break + end # end choice + + unless _tmp + self.pos = _save + break + end + _tmp = apply(:_Spnl) + unless _tmp + self.pos = _save + break + end + while true + _tmp = apply(:_HtmlAttribute) + break unless _tmp + end + _tmp = true + unless _tmp + self.pos = _save + break + end + _tmp = match_string(">") + unless _tmp + self.pos = _save + end + break + end # end sequence + + set_failed_rule :_HtmlBlockOpenH2 unless _tmp + return _tmp + end + + # HtmlBlockCloseH2 = "<" Spnl "/" ("h2" | "H2") Spnl ">" + def _HtmlBlockCloseH2 + + _save = self.pos + while true # sequence + _tmp = match_string("<") + unless _tmp + self.pos = _save + break + end + _tmp = apply(:_Spnl) + unless _tmp + self.pos = _save + break + end + _tmp = match_string("/") + unless _tmp + self.pos = _save + break + end + + _save1 = self.pos + while true # choice + _tmp = match_string("h2") + break if _tmp + self.pos = _save1 + _tmp = match_string("H2") + break if _tmp + self.pos = _save1 + break + end # end choice + + unless _tmp + self.pos = _save + break + end + _tmp = apply(:_Spnl) + unless _tmp + self.pos = _save + break + end + _tmp = match_string(">") + unless _tmp + self.pos = _save + end + break + end # end sequence + + set_failed_rule :_HtmlBlockCloseH2 unless _tmp + return _tmp + end + + # HtmlBlockH2 = HtmlBlockOpenH2 (HtmlBlockH2 | !HtmlBlockCloseH2 .)* HtmlBlockCloseH2 + def _HtmlBlockH2 + + _save = self.pos + while true # sequence + _tmp = apply(:_HtmlBlockOpenH2) + unless _tmp + self.pos = _save + break + end + while true + + _save2 = self.pos + while true # choice + _tmp = apply(:_HtmlBlockH2) + break if _tmp + self.pos = _save2 + + _save3 = self.pos + while true # sequence + _save4 = self.pos + _tmp = apply(:_HtmlBlockCloseH2) + _tmp = _tmp ? nil : true + self.pos = _save4 + unless _tmp + self.pos = _save3 + break + end + _tmp = get_byte + unless _tmp + self.pos = _save3 + end + break + end # end sequence + + break if _tmp + self.pos = _save2 + break + end # end choice + + break unless _tmp + end + _tmp = true + unless _tmp + self.pos = _save + break + end + _tmp = apply(:_HtmlBlockCloseH2) + unless _tmp + self.pos = _save + end + break + end # end sequence + + set_failed_rule :_HtmlBlockH2 unless _tmp + return _tmp + end + + # HtmlBlockOpenH3 = "<" Spnl ("h3" | "H3") Spnl HtmlAttribute* ">" + def _HtmlBlockOpenH3 + + _save = self.pos + while true # sequence + _tmp = match_string("<") + unless _tmp + self.pos = _save + break + end + _tmp = apply(:_Spnl) + unless _tmp + self.pos = _save + break + end + + _save1 = self.pos + while true # choice + _tmp = match_string("h3") + break if _tmp + self.pos = _save1 + _tmp = match_string("H3") + break if _tmp + self.pos = _save1 + break + end # end choice + + unless _tmp + self.pos = _save + break + end + _tmp = apply(:_Spnl) + unless _tmp + self.pos = _save + break + end + while true + _tmp = apply(:_HtmlAttribute) + break unless _tmp + end + _tmp = true + unless _tmp + self.pos = _save + break + end + _tmp = match_string(">") + unless _tmp + self.pos = _save + end + break + end # end sequence + + set_failed_rule :_HtmlBlockOpenH3 unless _tmp + return _tmp + end + + # HtmlBlockCloseH3 = "<" Spnl "/" ("h3" | "H3") Spnl ">" + def _HtmlBlockCloseH3 + + _save = self.pos + while true # sequence + _tmp = match_string("<") + unless _tmp + self.pos = _save + break + end + _tmp = apply(:_Spnl) + unless _tmp + self.pos = _save + break + end + _tmp = match_string("/") + unless _tmp + self.pos = _save + break + end + + _save1 = self.pos + while true # choice + _tmp = match_string("h3") + break if _tmp + self.pos = _save1 + _tmp = match_string("H3") + break if _tmp + self.pos = _save1 + break + end # end choice + + unless _tmp + self.pos = _save + break + end + _tmp = apply(:_Spnl) + unless _tmp + self.pos = _save + break + end + _tmp = match_string(">") + unless _tmp + self.pos = _save + end + break + end # end sequence + + set_failed_rule :_HtmlBlockCloseH3 unless _tmp + return _tmp + end + + # HtmlBlockH3 = HtmlBlockOpenH3 (HtmlBlockH3 | !HtmlBlockCloseH3 .)* HtmlBlockCloseH3 + def _HtmlBlockH3 + + _save = self.pos + while true # sequence + _tmp = apply(:_HtmlBlockOpenH3) + unless _tmp + self.pos = _save + break + end + while true + + _save2 = self.pos + while true # choice + _tmp = apply(:_HtmlBlockH3) + break if _tmp + self.pos = _save2 + + _save3 = self.pos + while true # sequence + _save4 = self.pos + _tmp = apply(:_HtmlBlockCloseH3) + _tmp = _tmp ? nil : true + self.pos = _save4 + unless _tmp + self.pos = _save3 + break + end + _tmp = get_byte + unless _tmp + self.pos = _save3 + end + break + end # end sequence + + break if _tmp + self.pos = _save2 + break + end # end choice + + break unless _tmp + end + _tmp = true + unless _tmp + self.pos = _save + break + end + _tmp = apply(:_HtmlBlockCloseH3) + unless _tmp + self.pos = _save + end + break + end # end sequence + + set_failed_rule :_HtmlBlockH3 unless _tmp + return _tmp + end + + # HtmlBlockOpenH4 = "<" Spnl ("h4" | "H4") Spnl HtmlAttribute* ">" + def _HtmlBlockOpenH4 + + _save = self.pos + while true # sequence + _tmp = match_string("<") + unless _tmp + self.pos = _save + break + end + _tmp = apply(:_Spnl) + unless _tmp + self.pos = _save + break + end + + _save1 = self.pos + while true # choice + _tmp = match_string("h4") + break if _tmp + self.pos = _save1 + _tmp = match_string("H4") + break if _tmp + self.pos = _save1 + break + end # end choice + + unless _tmp + self.pos = _save + break + end + _tmp = apply(:_Spnl) + unless _tmp + self.pos = _save + break + end + while true + _tmp = apply(:_HtmlAttribute) + break unless _tmp + end + _tmp = true + unless _tmp + self.pos = _save + break + end + _tmp = match_string(">") + unless _tmp + self.pos = _save + end + break + end # end sequence + + set_failed_rule :_HtmlBlockOpenH4 unless _tmp + return _tmp + end + + # HtmlBlockCloseH4 = "<" Spnl "/" ("h4" | "H4") Spnl ">" + def _HtmlBlockCloseH4 + + _save = self.pos + while true # sequence + _tmp = match_string("<") + unless _tmp + self.pos = _save + break + end + _tmp = apply(:_Spnl) + unless _tmp + self.pos = _save + break + end + _tmp = match_string("/") + unless _tmp + self.pos = _save + break + end + + _save1 = self.pos + while true # choice + _tmp = match_string("h4") + break if _tmp + self.pos = _save1 + _tmp = match_string("H4") + break if _tmp + self.pos = _save1 + break + end # end choice + + unless _tmp + self.pos = _save + break + end + _tmp = apply(:_Spnl) + unless _tmp + self.pos = _save + break + end + _tmp = match_string(">") + unless _tmp + self.pos = _save + end + break + end # end sequence + + set_failed_rule :_HtmlBlockCloseH4 unless _tmp + return _tmp + end + + # HtmlBlockH4 = HtmlBlockOpenH4 (HtmlBlockH4 | !HtmlBlockCloseH4 .)* HtmlBlockCloseH4 + def _HtmlBlockH4 + + _save = self.pos + while true # sequence + _tmp = apply(:_HtmlBlockOpenH4) + unless _tmp + self.pos = _save + break + end + while true + + _save2 = self.pos + while true # choice + _tmp = apply(:_HtmlBlockH4) + break if _tmp + self.pos = _save2 + + _save3 = self.pos + while true # sequence + _save4 = self.pos + _tmp = apply(:_HtmlBlockCloseH4) + _tmp = _tmp ? nil : true + self.pos = _save4 + unless _tmp + self.pos = _save3 + break + end + _tmp = get_byte + unless _tmp + self.pos = _save3 + end + break + end # end sequence + + break if _tmp + self.pos = _save2 + break + end # end choice + + break unless _tmp + end + _tmp = true + unless _tmp + self.pos = _save + break + end + _tmp = apply(:_HtmlBlockCloseH4) + unless _tmp + self.pos = _save + end + break + end # end sequence + + set_failed_rule :_HtmlBlockH4 unless _tmp + return _tmp + end + + # HtmlBlockOpenH5 = "<" Spnl ("h5" | "H5") Spnl HtmlAttribute* ">" + def _HtmlBlockOpenH5 + + _save = self.pos + while true # sequence + _tmp = match_string("<") + unless _tmp + self.pos = _save + break + end + _tmp = apply(:_Spnl) + unless _tmp + self.pos = _save + break + end + + _save1 = self.pos + while true # choice + _tmp = match_string("h5") + break if _tmp + self.pos = _save1 + _tmp = match_string("H5") + break if _tmp + self.pos = _save1 + break + end # end choice + + unless _tmp + self.pos = _save + break + end + _tmp = apply(:_Spnl) + unless _tmp + self.pos = _save + break + end + while true + _tmp = apply(:_HtmlAttribute) + break unless _tmp + end + _tmp = true + unless _tmp + self.pos = _save + break + end + _tmp = match_string(">") + unless _tmp + self.pos = _save + end + break + end # end sequence + + set_failed_rule :_HtmlBlockOpenH5 unless _tmp + return _tmp + end + + # HtmlBlockCloseH5 = "<" Spnl "/" ("h5" | "H5") Spnl ">" + def _HtmlBlockCloseH5 + + _save = self.pos + while true # sequence + _tmp = match_string("<") + unless _tmp + self.pos = _save + break + end + _tmp = apply(:_Spnl) + unless _tmp + self.pos = _save + break + end + _tmp = match_string("/") + unless _tmp + self.pos = _save + break + end + + _save1 = self.pos + while true # choice + _tmp = match_string("h5") + break if _tmp + self.pos = _save1 + _tmp = match_string("H5") + break if _tmp + self.pos = _save1 + break + end # end choice + + unless _tmp + self.pos = _save + break + end + _tmp = apply(:_Spnl) + unless _tmp + self.pos = _save + break + end + _tmp = match_string(">") + unless _tmp + self.pos = _save + end + break + end # end sequence + + set_failed_rule :_HtmlBlockCloseH5 unless _tmp + return _tmp + end + + # HtmlBlockH5 = HtmlBlockOpenH5 (HtmlBlockH5 | !HtmlBlockCloseH5 .)* HtmlBlockCloseH5 + def _HtmlBlockH5 + + _save = self.pos + while true # sequence + _tmp = apply(:_HtmlBlockOpenH5) + unless _tmp + self.pos = _save + break + end + while true + + _save2 = self.pos + while true # choice + _tmp = apply(:_HtmlBlockH5) + break if _tmp + self.pos = _save2 + + _save3 = self.pos + while true # sequence + _save4 = self.pos + _tmp = apply(:_HtmlBlockCloseH5) + _tmp = _tmp ? nil : true + self.pos = _save4 + unless _tmp + self.pos = _save3 + break + end + _tmp = get_byte + unless _tmp + self.pos = _save3 + end + break + end # end sequence + + break if _tmp + self.pos = _save2 + break + end # end choice + + break unless _tmp + end + _tmp = true + unless _tmp + self.pos = _save + break + end + _tmp = apply(:_HtmlBlockCloseH5) + unless _tmp + self.pos = _save + end + break + end # end sequence + + set_failed_rule :_HtmlBlockH5 unless _tmp + return _tmp + end + + # HtmlBlockOpenH6 = "<" Spnl ("h6" | "H6") Spnl HtmlAttribute* ">" + def _HtmlBlockOpenH6 + + _save = self.pos + while true # sequence + _tmp = match_string("<") + unless _tmp + self.pos = _save + break + end + _tmp = apply(:_Spnl) + unless _tmp + self.pos = _save + break + end + + _save1 = self.pos + while true # choice + _tmp = match_string("h6") + break if _tmp + self.pos = _save1 + _tmp = match_string("H6") + break if _tmp + self.pos = _save1 + break + end # end choice + + unless _tmp + self.pos = _save + break + end + _tmp = apply(:_Spnl) + unless _tmp + self.pos = _save + break + end + while true + _tmp = apply(:_HtmlAttribute) + break unless _tmp + end + _tmp = true + unless _tmp + self.pos = _save + break + end + _tmp = match_string(">") + unless _tmp + self.pos = _save + end + break + end # end sequence + + set_failed_rule :_HtmlBlockOpenH6 unless _tmp + return _tmp + end + + # HtmlBlockCloseH6 = "<" Spnl "/" ("h6" | "H6") Spnl ">" + def _HtmlBlockCloseH6 + + _save = self.pos + while true # sequence + _tmp = match_string("<") + unless _tmp + self.pos = _save + break + end + _tmp = apply(:_Spnl) + unless _tmp + self.pos = _save + break + end + _tmp = match_string("/") + unless _tmp + self.pos = _save + break + end + + _save1 = self.pos + while true # choice + _tmp = match_string("h6") + break if _tmp + self.pos = _save1 + _tmp = match_string("H6") + break if _tmp + self.pos = _save1 + break + end # end choice + + unless _tmp + self.pos = _save + break + end + _tmp = apply(:_Spnl) + unless _tmp + self.pos = _save + break + end + _tmp = match_string(">") + unless _tmp + self.pos = _save + end + break + end # end sequence + + set_failed_rule :_HtmlBlockCloseH6 unless _tmp + return _tmp + end + + # HtmlBlockH6 = HtmlBlockOpenH6 (HtmlBlockH6 | !HtmlBlockCloseH6 .)* HtmlBlockCloseH6 + def _HtmlBlockH6 + + _save = self.pos + while true # sequence + _tmp = apply(:_HtmlBlockOpenH6) + unless _tmp + self.pos = _save + break + end + while true + + _save2 = self.pos + while true # choice + _tmp = apply(:_HtmlBlockH6) + break if _tmp + self.pos = _save2 + + _save3 = self.pos + while true # sequence + _save4 = self.pos + _tmp = apply(:_HtmlBlockCloseH6) + _tmp = _tmp ? nil : true + self.pos = _save4 + unless _tmp + self.pos = _save3 + break + end + _tmp = get_byte + unless _tmp + self.pos = _save3 + end + break + end # end sequence + + break if _tmp + self.pos = _save2 + break + end # end choice + + break unless _tmp + end + _tmp = true + unless _tmp + self.pos = _save + break + end + _tmp = apply(:_HtmlBlockCloseH6) + unless _tmp + self.pos = _save + end + break + end # end sequence + + set_failed_rule :_HtmlBlockH6 unless _tmp + return _tmp + end + + # HtmlBlockOpenMenu = "<" Spnl ("menu" | "MENU") Spnl HtmlAttribute* ">" + def _HtmlBlockOpenMenu + + _save = self.pos + while true # sequence + _tmp = match_string("<") + unless _tmp + self.pos = _save + break + end + _tmp = apply(:_Spnl) + unless _tmp + self.pos = _save + break + end + + _save1 = self.pos + while true # choice + _tmp = match_string("menu") + break if _tmp + self.pos = _save1 + _tmp = match_string("MENU") + break if _tmp + self.pos = _save1 + break + end # end choice + + unless _tmp + self.pos = _save + break + end + _tmp = apply(:_Spnl) + unless _tmp + self.pos = _save + break + end + while true + _tmp = apply(:_HtmlAttribute) + break unless _tmp + end + _tmp = true + unless _tmp + self.pos = _save + break + end + _tmp = match_string(">") + unless _tmp + self.pos = _save + end + break + end # end sequence + + set_failed_rule :_HtmlBlockOpenMenu unless _tmp + return _tmp + end + + # HtmlBlockCloseMenu = "<" Spnl "/" ("menu" | "MENU") Spnl ">" + def _HtmlBlockCloseMenu + + _save = self.pos + while true # sequence + _tmp = match_string("<") + unless _tmp + self.pos = _save + break + end + _tmp = apply(:_Spnl) + unless _tmp + self.pos = _save + break + end + _tmp = match_string("/") + unless _tmp + self.pos = _save + break + end + + _save1 = self.pos + while true # choice + _tmp = match_string("menu") + break if _tmp + self.pos = _save1 + _tmp = match_string("MENU") + break if _tmp + self.pos = _save1 + break + end # end choice + + unless _tmp + self.pos = _save + break + end + _tmp = apply(:_Spnl) + unless _tmp + self.pos = _save + break + end + _tmp = match_string(">") + unless _tmp + self.pos = _save + end + break + end # end sequence + + set_failed_rule :_HtmlBlockCloseMenu unless _tmp + return _tmp + end + + # HtmlBlockMenu = HtmlBlockOpenMenu (HtmlBlockMenu | !HtmlBlockCloseMenu .)* HtmlBlockCloseMenu + def _HtmlBlockMenu + + _save = self.pos + while true # sequence + _tmp = apply(:_HtmlBlockOpenMenu) + unless _tmp + self.pos = _save + break + end + while true + + _save2 = self.pos + while true # choice + _tmp = apply(:_HtmlBlockMenu) + break if _tmp + self.pos = _save2 + + _save3 = self.pos + while true # sequence + _save4 = self.pos + _tmp = apply(:_HtmlBlockCloseMenu) + _tmp = _tmp ? nil : true + self.pos = _save4 + unless _tmp + self.pos = _save3 + break + end + _tmp = get_byte + unless _tmp + self.pos = _save3 + end + break + end # end sequence + + break if _tmp + self.pos = _save2 + break + end # end choice + + break unless _tmp + end + _tmp = true + unless _tmp + self.pos = _save + break + end + _tmp = apply(:_HtmlBlockCloseMenu) + unless _tmp + self.pos = _save + end + break + end # end sequence + + set_failed_rule :_HtmlBlockMenu unless _tmp + return _tmp + end + + # HtmlBlockOpenNoframes = "<" Spnl ("noframes" | "NOFRAMES") Spnl HtmlAttribute* ">" + def _HtmlBlockOpenNoframes + + _save = self.pos + while true # sequence + _tmp = match_string("<") + unless _tmp + self.pos = _save + break + end + _tmp = apply(:_Spnl) + unless _tmp + self.pos = _save + break + end + + _save1 = self.pos + while true # choice + _tmp = match_string("noframes") + break if _tmp + self.pos = _save1 + _tmp = match_string("NOFRAMES") + break if _tmp + self.pos = _save1 + break + end # end choice + + unless _tmp + self.pos = _save + break + end + _tmp = apply(:_Spnl) + unless _tmp + self.pos = _save + break + end + while true + _tmp = apply(:_HtmlAttribute) + break unless _tmp + end + _tmp = true + unless _tmp + self.pos = _save + break + end + _tmp = match_string(">") + unless _tmp + self.pos = _save + end + break + end # end sequence + + set_failed_rule :_HtmlBlockOpenNoframes unless _tmp + return _tmp + end + + # HtmlBlockCloseNoframes = "<" Spnl "/" ("noframes" | "NOFRAMES") Spnl ">" + def _HtmlBlockCloseNoframes + + _save = self.pos + while true # sequence + _tmp = match_string("<") + unless _tmp + self.pos = _save + break + end + _tmp = apply(:_Spnl) + unless _tmp + self.pos = _save + break + end + _tmp = match_string("/") + unless _tmp + self.pos = _save + break + end + + _save1 = self.pos + while true # choice + _tmp = match_string("noframes") + break if _tmp + self.pos = _save1 + _tmp = match_string("NOFRAMES") + break if _tmp + self.pos = _save1 + break + end # end choice + + unless _tmp + self.pos = _save + break + end + _tmp = apply(:_Spnl) + unless _tmp + self.pos = _save + break + end + _tmp = match_string(">") + unless _tmp + self.pos = _save + end + break + end # end sequence + + set_failed_rule :_HtmlBlockCloseNoframes unless _tmp + return _tmp + end + + # HtmlBlockNoframes = HtmlBlockOpenNoframes (HtmlBlockNoframes | !HtmlBlockCloseNoframes .)* HtmlBlockCloseNoframes + def _HtmlBlockNoframes + + _save = self.pos + while true # sequence + _tmp = apply(:_HtmlBlockOpenNoframes) + unless _tmp + self.pos = _save + break + end + while true + + _save2 = self.pos + while true # choice + _tmp = apply(:_HtmlBlockNoframes) + break if _tmp + self.pos = _save2 + + _save3 = self.pos + while true # sequence + _save4 = self.pos + _tmp = apply(:_HtmlBlockCloseNoframes) + _tmp = _tmp ? nil : true + self.pos = _save4 + unless _tmp + self.pos = _save3 + break + end + _tmp = get_byte + unless _tmp + self.pos = _save3 + end + break + end # end sequence + + break if _tmp + self.pos = _save2 + break + end # end choice + + break unless _tmp + end + _tmp = true + unless _tmp + self.pos = _save + break + end + _tmp = apply(:_HtmlBlockCloseNoframes) + unless _tmp + self.pos = _save + end + break + end # end sequence + + set_failed_rule :_HtmlBlockNoframes unless _tmp + return _tmp + end + + # HtmlBlockOpenNoscript = "<" Spnl ("noscript" | "NOSCRIPT") Spnl HtmlAttribute* ">" + def _HtmlBlockOpenNoscript + + _save = self.pos + while true # sequence + _tmp = match_string("<") + unless _tmp + self.pos = _save + break + end + _tmp = apply(:_Spnl) + unless _tmp + self.pos = _save + break + end + + _save1 = self.pos + while true # choice + _tmp = match_string("noscript") + break if _tmp + self.pos = _save1 + _tmp = match_string("NOSCRIPT") + break if _tmp + self.pos = _save1 + break + end # end choice + + unless _tmp + self.pos = _save + break + end + _tmp = apply(:_Spnl) + unless _tmp + self.pos = _save + break + end + while true + _tmp = apply(:_HtmlAttribute) + break unless _tmp + end + _tmp = true + unless _tmp + self.pos = _save + break + end + _tmp = match_string(">") + unless _tmp + self.pos = _save + end + break + end # end sequence + + set_failed_rule :_HtmlBlockOpenNoscript unless _tmp + return _tmp + end + + # HtmlBlockCloseNoscript = "<" Spnl "/" ("noscript" | "NOSCRIPT") Spnl ">" + def _HtmlBlockCloseNoscript + + _save = self.pos + while true # sequence + _tmp = match_string("<") + unless _tmp + self.pos = _save + break + end + _tmp = apply(:_Spnl) + unless _tmp + self.pos = _save + break + end + _tmp = match_string("/") + unless _tmp + self.pos = _save + break + end + + _save1 = self.pos + while true # choice + _tmp = match_string("noscript") + break if _tmp + self.pos = _save1 + _tmp = match_string("NOSCRIPT") + break if _tmp + self.pos = _save1 + break + end # end choice + + unless _tmp + self.pos = _save + break + end + _tmp = apply(:_Spnl) + unless _tmp + self.pos = _save + break + end + _tmp = match_string(">") + unless _tmp + self.pos = _save + end + break + end # end sequence + + set_failed_rule :_HtmlBlockCloseNoscript unless _tmp + return _tmp + end + + # HtmlBlockNoscript = HtmlBlockOpenNoscript (HtmlBlockNoscript | !HtmlBlockCloseNoscript .)* HtmlBlockCloseNoscript + def _HtmlBlockNoscript + + _save = self.pos + while true # sequence + _tmp = apply(:_HtmlBlockOpenNoscript) + unless _tmp + self.pos = _save + break + end + while true + + _save2 = self.pos + while true # choice + _tmp = apply(:_HtmlBlockNoscript) + break if _tmp + self.pos = _save2 + + _save3 = self.pos + while true # sequence + _save4 = self.pos + _tmp = apply(:_HtmlBlockCloseNoscript) + _tmp = _tmp ? nil : true + self.pos = _save4 + unless _tmp + self.pos = _save3 + break + end + _tmp = get_byte + unless _tmp + self.pos = _save3 + end + break + end # end sequence + + break if _tmp + self.pos = _save2 + break + end # end choice + + break unless _tmp + end + _tmp = true + unless _tmp + self.pos = _save + break + end + _tmp = apply(:_HtmlBlockCloseNoscript) + unless _tmp + self.pos = _save + end + break + end # end sequence + + set_failed_rule :_HtmlBlockNoscript unless _tmp + return _tmp + end + + # HtmlBlockOpenOl = "<" Spnl ("ol" | "OL") Spnl HtmlAttribute* ">" + def _HtmlBlockOpenOl + + _save = self.pos + while true # sequence + _tmp = match_string("<") + unless _tmp + self.pos = _save + break + end + _tmp = apply(:_Spnl) + unless _tmp + self.pos = _save + break + end + + _save1 = self.pos + while true # choice + _tmp = match_string("ol") + break if _tmp + self.pos = _save1 + _tmp = match_string("OL") + break if _tmp + self.pos = _save1 + break + end # end choice + + unless _tmp + self.pos = _save + break + end + _tmp = apply(:_Spnl) + unless _tmp + self.pos = _save + break + end + while true + _tmp = apply(:_HtmlAttribute) + break unless _tmp + end + _tmp = true + unless _tmp + self.pos = _save + break + end + _tmp = match_string(">") + unless _tmp + self.pos = _save + end + break + end # end sequence + + set_failed_rule :_HtmlBlockOpenOl unless _tmp + return _tmp + end + + # HtmlBlockCloseOl = "<" Spnl "/" ("ol" | "OL") Spnl ">" + def _HtmlBlockCloseOl + + _save = self.pos + while true # sequence + _tmp = match_string("<") + unless _tmp + self.pos = _save + break + end + _tmp = apply(:_Spnl) + unless _tmp + self.pos = _save + break + end + _tmp = match_string("/") + unless _tmp + self.pos = _save + break + end + + _save1 = self.pos + while true # choice + _tmp = match_string("ol") + break if _tmp + self.pos = _save1 + _tmp = match_string("OL") + break if _tmp + self.pos = _save1 + break + end # end choice + + unless _tmp + self.pos = _save + break + end + _tmp = apply(:_Spnl) + unless _tmp + self.pos = _save + break + end + _tmp = match_string(">") + unless _tmp + self.pos = _save + end + break + end # end sequence + + set_failed_rule :_HtmlBlockCloseOl unless _tmp + return _tmp + end + + # HtmlBlockOl = HtmlBlockOpenOl (HtmlBlockOl | !HtmlBlockCloseOl .)* HtmlBlockCloseOl + def _HtmlBlockOl + + _save = self.pos + while true # sequence + _tmp = apply(:_HtmlBlockOpenOl) + unless _tmp + self.pos = _save + break + end + while true + + _save2 = self.pos + while true # choice + _tmp = apply(:_HtmlBlockOl) + break if _tmp + self.pos = _save2 + + _save3 = self.pos + while true # sequence + _save4 = self.pos + _tmp = apply(:_HtmlBlockCloseOl) + _tmp = _tmp ? nil : true + self.pos = _save4 + unless _tmp + self.pos = _save3 + break + end + _tmp = get_byte + unless _tmp + self.pos = _save3 + end + break + end # end sequence + + break if _tmp + self.pos = _save2 + break + end # end choice + + break unless _tmp + end + _tmp = true + unless _tmp + self.pos = _save + break + end + _tmp = apply(:_HtmlBlockCloseOl) + unless _tmp + self.pos = _save + end + break + end # end sequence + + set_failed_rule :_HtmlBlockOl unless _tmp + return _tmp + end + + # HtmlBlockOpenP = "<" Spnl ("p" | "P") Spnl HtmlAttribute* ">" + def _HtmlBlockOpenP + + _save = self.pos + while true # sequence + _tmp = match_string("<") + unless _tmp + self.pos = _save + break + end + _tmp = apply(:_Spnl) + unless _tmp + self.pos = _save + break + end + + _save1 = self.pos + while true # choice + _tmp = match_string("p") + break if _tmp + self.pos = _save1 + _tmp = match_string("P") + break if _tmp + self.pos = _save1 + break + end # end choice + + unless _tmp + self.pos = _save + break + end + _tmp = apply(:_Spnl) + unless _tmp + self.pos = _save + break + end + while true + _tmp = apply(:_HtmlAttribute) + break unless _tmp + end + _tmp = true + unless _tmp + self.pos = _save + break + end + _tmp = match_string(">") + unless _tmp + self.pos = _save + end + break + end # end sequence + + set_failed_rule :_HtmlBlockOpenP unless _tmp + return _tmp + end + + # HtmlBlockCloseP = "<" Spnl "/" ("p" | "P") Spnl ">" + def _HtmlBlockCloseP + + _save = self.pos + while true # sequence + _tmp = match_string("<") + unless _tmp + self.pos = _save + break + end + _tmp = apply(:_Spnl) + unless _tmp + self.pos = _save + break + end + _tmp = match_string("/") + unless _tmp + self.pos = _save + break + end + + _save1 = self.pos + while true # choice + _tmp = match_string("p") + break if _tmp + self.pos = _save1 + _tmp = match_string("P") + break if _tmp + self.pos = _save1 + break + end # end choice + + unless _tmp + self.pos = _save + break + end + _tmp = apply(:_Spnl) + unless _tmp + self.pos = _save + break + end + _tmp = match_string(">") + unless _tmp + self.pos = _save + end + break + end # end sequence + + set_failed_rule :_HtmlBlockCloseP unless _tmp + return _tmp + end + + # HtmlBlockP = HtmlBlockOpenP (HtmlBlockP | !HtmlBlockCloseP .)* HtmlBlockCloseP + def _HtmlBlockP + + _save = self.pos + while true # sequence + _tmp = apply(:_HtmlBlockOpenP) + unless _tmp + self.pos = _save + break + end + while true + + _save2 = self.pos + while true # choice + _tmp = apply(:_HtmlBlockP) + break if _tmp + self.pos = _save2 + + _save3 = self.pos + while true # sequence + _save4 = self.pos + _tmp = apply(:_HtmlBlockCloseP) + _tmp = _tmp ? nil : true + self.pos = _save4 + unless _tmp + self.pos = _save3 + break + end + _tmp = get_byte + unless _tmp + self.pos = _save3 + end + break + end # end sequence + + break if _tmp + self.pos = _save2 + break + end # end choice + + break unless _tmp + end + _tmp = true + unless _tmp + self.pos = _save + break + end + _tmp = apply(:_HtmlBlockCloseP) + unless _tmp + self.pos = _save + end + break + end # end sequence + + set_failed_rule :_HtmlBlockP unless _tmp + return _tmp + end + + # HtmlBlockOpenPre = "<" Spnl ("pre" | "PRE") Spnl HtmlAttribute* ">" + def _HtmlBlockOpenPre + + _save = self.pos + while true # sequence + _tmp = match_string("<") + unless _tmp + self.pos = _save + break + end + _tmp = apply(:_Spnl) + unless _tmp + self.pos = _save + break + end + + _save1 = self.pos + while true # choice + _tmp = match_string("pre") + break if _tmp + self.pos = _save1 + _tmp = match_string("PRE") + break if _tmp + self.pos = _save1 + break + end # end choice + + unless _tmp + self.pos = _save + break + end + _tmp = apply(:_Spnl) + unless _tmp + self.pos = _save + break + end + while true + _tmp = apply(:_HtmlAttribute) + break unless _tmp + end + _tmp = true + unless _tmp + self.pos = _save + break + end + _tmp = match_string(">") + unless _tmp + self.pos = _save + end + break + end # end sequence + + set_failed_rule :_HtmlBlockOpenPre unless _tmp + return _tmp + end + + # HtmlBlockClosePre = "<" Spnl "/" ("pre" | "PRE") Spnl ">" + def _HtmlBlockClosePre + + _save = self.pos + while true # sequence + _tmp = match_string("<") + unless _tmp + self.pos = _save + break + end + _tmp = apply(:_Spnl) + unless _tmp + self.pos = _save + break + end + _tmp = match_string("/") + unless _tmp + self.pos = _save + break + end + + _save1 = self.pos + while true # choice + _tmp = match_string("pre") + break if _tmp + self.pos = _save1 + _tmp = match_string("PRE") + break if _tmp + self.pos = _save1 + break + end # end choice + + unless _tmp + self.pos = _save + break + end + _tmp = apply(:_Spnl) + unless _tmp + self.pos = _save + break + end + _tmp = match_string(">") + unless _tmp + self.pos = _save + end + break + end # end sequence + + set_failed_rule :_HtmlBlockClosePre unless _tmp + return _tmp + end + + # HtmlBlockPre = HtmlBlockOpenPre (HtmlBlockPre | !HtmlBlockClosePre .)* HtmlBlockClosePre + def _HtmlBlockPre + + _save = self.pos + while true # sequence + _tmp = apply(:_HtmlBlockOpenPre) + unless _tmp + self.pos = _save + break + end + while true + + _save2 = self.pos + while true # choice + _tmp = apply(:_HtmlBlockPre) + break if _tmp + self.pos = _save2 + + _save3 = self.pos + while true # sequence + _save4 = self.pos + _tmp = apply(:_HtmlBlockClosePre) + _tmp = _tmp ? nil : true + self.pos = _save4 + unless _tmp + self.pos = _save3 + break + end + _tmp = get_byte + unless _tmp + self.pos = _save3 + end + break + end # end sequence + + break if _tmp + self.pos = _save2 + break + end # end choice + + break unless _tmp + end + _tmp = true + unless _tmp + self.pos = _save + break + end + _tmp = apply(:_HtmlBlockClosePre) + unless _tmp + self.pos = _save + end + break + end # end sequence + + set_failed_rule :_HtmlBlockPre unless _tmp + return _tmp + end + + # HtmlBlockOpenTable = "<" Spnl ("table" | "TABLE") Spnl HtmlAttribute* ">" + def _HtmlBlockOpenTable + + _save = self.pos + while true # sequence + _tmp = match_string("<") + unless _tmp + self.pos = _save + break + end + _tmp = apply(:_Spnl) + unless _tmp + self.pos = _save + break + end + + _save1 = self.pos + while true # choice + _tmp = match_string("table") + break if _tmp + self.pos = _save1 + _tmp = match_string("TABLE") + break if _tmp + self.pos = _save1 + break + end # end choice + + unless _tmp + self.pos = _save + break + end + _tmp = apply(:_Spnl) + unless _tmp + self.pos = _save + break + end + while true + _tmp = apply(:_HtmlAttribute) + break unless _tmp + end + _tmp = true + unless _tmp + self.pos = _save + break + end + _tmp = match_string(">") + unless _tmp + self.pos = _save + end + break + end # end sequence + + set_failed_rule :_HtmlBlockOpenTable unless _tmp + return _tmp + end + + # HtmlBlockCloseTable = "<" Spnl "/" ("table" | "TABLE") Spnl ">" + def _HtmlBlockCloseTable + + _save = self.pos + while true # sequence + _tmp = match_string("<") + unless _tmp + self.pos = _save + break + end + _tmp = apply(:_Spnl) + unless _tmp + self.pos = _save + break + end + _tmp = match_string("/") + unless _tmp + self.pos = _save + break + end + + _save1 = self.pos + while true # choice + _tmp = match_string("table") + break if _tmp + self.pos = _save1 + _tmp = match_string("TABLE") + break if _tmp + self.pos = _save1 + break + end # end choice + + unless _tmp + self.pos = _save + break + end + _tmp = apply(:_Spnl) + unless _tmp + self.pos = _save + break + end + _tmp = match_string(">") + unless _tmp + self.pos = _save + end + break + end # end sequence + + set_failed_rule :_HtmlBlockCloseTable unless _tmp + return _tmp + end + + # HtmlBlockTable = HtmlBlockOpenTable (HtmlBlockTable | !HtmlBlockCloseTable .)* HtmlBlockCloseTable + def _HtmlBlockTable + + _save = self.pos + while true # sequence + _tmp = apply(:_HtmlBlockOpenTable) + unless _tmp + self.pos = _save + break + end + while true + + _save2 = self.pos + while true # choice + _tmp = apply(:_HtmlBlockTable) + break if _tmp + self.pos = _save2 + + _save3 = self.pos + while true # sequence + _save4 = self.pos + _tmp = apply(:_HtmlBlockCloseTable) + _tmp = _tmp ? nil : true + self.pos = _save4 + unless _tmp + self.pos = _save3 + break + end + _tmp = get_byte + unless _tmp + self.pos = _save3 + end + break + end # end sequence + + break if _tmp + self.pos = _save2 + break + end # end choice + + break unless _tmp + end + _tmp = true + unless _tmp + self.pos = _save + break + end + _tmp = apply(:_HtmlBlockCloseTable) + unless _tmp + self.pos = _save + end + break + end # end sequence + + set_failed_rule :_HtmlBlockTable unless _tmp + return _tmp + end + + # HtmlBlockOpenUl = "<" Spnl ("ul" | "UL") Spnl HtmlAttribute* ">" + def _HtmlBlockOpenUl + + _save = self.pos + while true # sequence + _tmp = match_string("<") + unless _tmp + self.pos = _save + break + end + _tmp = apply(:_Spnl) + unless _tmp + self.pos = _save + break + end + + _save1 = self.pos + while true # choice + _tmp = match_string("ul") + break if _tmp + self.pos = _save1 + _tmp = match_string("UL") + break if _tmp + self.pos = _save1 + break + end # end choice + + unless _tmp + self.pos = _save + break + end + _tmp = apply(:_Spnl) + unless _tmp + self.pos = _save + break + end + while true + _tmp = apply(:_HtmlAttribute) + break unless _tmp + end + _tmp = true + unless _tmp + self.pos = _save + break + end + _tmp = match_string(">") + unless _tmp + self.pos = _save + end + break + end # end sequence + + set_failed_rule :_HtmlBlockOpenUl unless _tmp + return _tmp + end + + # HtmlBlockCloseUl = "<" Spnl "/" ("ul" | "UL") Spnl ">" + def _HtmlBlockCloseUl + + _save = self.pos + while true # sequence + _tmp = match_string("<") + unless _tmp + self.pos = _save + break + end + _tmp = apply(:_Spnl) + unless _tmp + self.pos = _save + break + end + _tmp = match_string("/") + unless _tmp + self.pos = _save + break + end + + _save1 = self.pos + while true # choice + _tmp = match_string("ul") + break if _tmp + self.pos = _save1 + _tmp = match_string("UL") + break if _tmp + self.pos = _save1 + break + end # end choice + + unless _tmp + self.pos = _save + break + end + _tmp = apply(:_Spnl) + unless _tmp + self.pos = _save + break + end + _tmp = match_string(">") + unless _tmp + self.pos = _save + end + break + end # end sequence + + set_failed_rule :_HtmlBlockCloseUl unless _tmp + return _tmp + end + + # HtmlBlockUl = HtmlBlockOpenUl (HtmlBlockUl | !HtmlBlockCloseUl .)* HtmlBlockCloseUl + def _HtmlBlockUl + + _save = self.pos + while true # sequence + _tmp = apply(:_HtmlBlockOpenUl) + unless _tmp + self.pos = _save + break + end + while true + + _save2 = self.pos + while true # choice + _tmp = apply(:_HtmlBlockUl) + break if _tmp + self.pos = _save2 + + _save3 = self.pos + while true # sequence + _save4 = self.pos + _tmp = apply(:_HtmlBlockCloseUl) + _tmp = _tmp ? nil : true + self.pos = _save4 + unless _tmp + self.pos = _save3 + break + end + _tmp = get_byte + unless _tmp + self.pos = _save3 + end + break + end # end sequence + + break if _tmp + self.pos = _save2 + break + end # end choice + + break unless _tmp + end + _tmp = true + unless _tmp + self.pos = _save + break + end + _tmp = apply(:_HtmlBlockCloseUl) + unless _tmp + self.pos = _save + end + break + end # end sequence + + set_failed_rule :_HtmlBlockUl unless _tmp + return _tmp + end + + # HtmlBlockOpenDd = "<" Spnl ("dd" | "DD") Spnl HtmlAttribute* ">" + def _HtmlBlockOpenDd + + _save = self.pos + while true # sequence + _tmp = match_string("<") + unless _tmp + self.pos = _save + break + end + _tmp = apply(:_Spnl) + unless _tmp + self.pos = _save + break + end + + _save1 = self.pos + while true # choice + _tmp = match_string("dd") + break if _tmp + self.pos = _save1 + _tmp = match_string("DD") + break if _tmp + self.pos = _save1 + break + end # end choice + + unless _tmp + self.pos = _save + break + end + _tmp = apply(:_Spnl) + unless _tmp + self.pos = _save + break + end + while true + _tmp = apply(:_HtmlAttribute) + break unless _tmp + end + _tmp = true + unless _tmp + self.pos = _save + break + end + _tmp = match_string(">") + unless _tmp + self.pos = _save + end + break + end # end sequence + + set_failed_rule :_HtmlBlockOpenDd unless _tmp + return _tmp + end + + # HtmlBlockCloseDd = "<" Spnl "/" ("dd" | "DD") Spnl ">" + def _HtmlBlockCloseDd + + _save = self.pos + while true # sequence + _tmp = match_string("<") + unless _tmp + self.pos = _save + break + end + _tmp = apply(:_Spnl) + unless _tmp + self.pos = _save + break + end + _tmp = match_string("/") + unless _tmp + self.pos = _save + break + end + + _save1 = self.pos + while true # choice + _tmp = match_string("dd") + break if _tmp + self.pos = _save1 + _tmp = match_string("DD") + break if _tmp + self.pos = _save1 + break + end # end choice + + unless _tmp + self.pos = _save + break + end + _tmp = apply(:_Spnl) + unless _tmp + self.pos = _save + break + end + _tmp = match_string(">") + unless _tmp + self.pos = _save + end + break + end # end sequence + + set_failed_rule :_HtmlBlockCloseDd unless _tmp + return _tmp + end + + # HtmlBlockDd = HtmlBlockOpenDd (HtmlBlockDd | !HtmlBlockCloseDd .)* HtmlBlockCloseDd + def _HtmlBlockDd + + _save = self.pos + while true # sequence + _tmp = apply(:_HtmlBlockOpenDd) + unless _tmp + self.pos = _save + break + end + while true + + _save2 = self.pos + while true # choice + _tmp = apply(:_HtmlBlockDd) + break if _tmp + self.pos = _save2 + + _save3 = self.pos + while true # sequence + _save4 = self.pos + _tmp = apply(:_HtmlBlockCloseDd) + _tmp = _tmp ? nil : true + self.pos = _save4 + unless _tmp + self.pos = _save3 + break + end + _tmp = get_byte + unless _tmp + self.pos = _save3 + end + break + end # end sequence + + break if _tmp + self.pos = _save2 + break + end # end choice + + break unless _tmp + end + _tmp = true + unless _tmp + self.pos = _save + break + end + _tmp = apply(:_HtmlBlockCloseDd) + unless _tmp + self.pos = _save + end + break + end # end sequence + + set_failed_rule :_HtmlBlockDd unless _tmp + return _tmp + end + + # HtmlBlockOpenDt = "<" Spnl ("dt" | "DT") Spnl HtmlAttribute* ">" + def _HtmlBlockOpenDt + + _save = self.pos + while true # sequence + _tmp = match_string("<") + unless _tmp + self.pos = _save + break + end + _tmp = apply(:_Spnl) + unless _tmp + self.pos = _save + break + end + + _save1 = self.pos + while true # choice + _tmp = match_string("dt") + break if _tmp + self.pos = _save1 + _tmp = match_string("DT") + break if _tmp + self.pos = _save1 + break + end # end choice + + unless _tmp + self.pos = _save + break + end + _tmp = apply(:_Spnl) + unless _tmp + self.pos = _save + break + end + while true + _tmp = apply(:_HtmlAttribute) + break unless _tmp + end + _tmp = true + unless _tmp + self.pos = _save + break + end + _tmp = match_string(">") + unless _tmp + self.pos = _save + end + break + end # end sequence + + set_failed_rule :_HtmlBlockOpenDt unless _tmp + return _tmp + end + + # HtmlBlockCloseDt = "<" Spnl "/" ("dt" | "DT") Spnl ">" + def _HtmlBlockCloseDt + + _save = self.pos + while true # sequence + _tmp = match_string("<") + unless _tmp + self.pos = _save + break + end + _tmp = apply(:_Spnl) + unless _tmp + self.pos = _save + break + end + _tmp = match_string("/") + unless _tmp + self.pos = _save + break + end + + _save1 = self.pos + while true # choice + _tmp = match_string("dt") + break if _tmp + self.pos = _save1 + _tmp = match_string("DT") + break if _tmp + self.pos = _save1 + break + end # end choice + + unless _tmp + self.pos = _save + break + end + _tmp = apply(:_Spnl) + unless _tmp + self.pos = _save + break + end + _tmp = match_string(">") + unless _tmp + self.pos = _save + end + break + end # end sequence + + set_failed_rule :_HtmlBlockCloseDt unless _tmp + return _tmp + end + + # HtmlBlockDt = HtmlBlockOpenDt (HtmlBlockDt | !HtmlBlockCloseDt .)* HtmlBlockCloseDt + def _HtmlBlockDt + + _save = self.pos + while true # sequence + _tmp = apply(:_HtmlBlockOpenDt) + unless _tmp + self.pos = _save + break + end + while true + + _save2 = self.pos + while true # choice + _tmp = apply(:_HtmlBlockDt) + break if _tmp + self.pos = _save2 + + _save3 = self.pos + while true # sequence + _save4 = self.pos + _tmp = apply(:_HtmlBlockCloseDt) + _tmp = _tmp ? nil : true + self.pos = _save4 + unless _tmp + self.pos = _save3 + break + end + _tmp = get_byte + unless _tmp + self.pos = _save3 + end + break + end # end sequence + + break if _tmp + self.pos = _save2 + break + end # end choice + + break unless _tmp + end + _tmp = true + unless _tmp + self.pos = _save + break + end + _tmp = apply(:_HtmlBlockCloseDt) + unless _tmp + self.pos = _save + end + break + end # end sequence + + set_failed_rule :_HtmlBlockDt unless _tmp + return _tmp + end + + # HtmlBlockOpenFrameset = "<" Spnl ("frameset" | "FRAMESET") Spnl HtmlAttribute* ">" + def _HtmlBlockOpenFrameset + + _save = self.pos + while true # sequence + _tmp = match_string("<") + unless _tmp + self.pos = _save + break + end + _tmp = apply(:_Spnl) + unless _tmp + self.pos = _save + break + end + + _save1 = self.pos + while true # choice + _tmp = match_string("frameset") + break if _tmp + self.pos = _save1 + _tmp = match_string("FRAMESET") + break if _tmp + self.pos = _save1 + break + end # end choice + + unless _tmp + self.pos = _save + break + end + _tmp = apply(:_Spnl) + unless _tmp + self.pos = _save + break + end + while true + _tmp = apply(:_HtmlAttribute) + break unless _tmp + end + _tmp = true + unless _tmp + self.pos = _save + break + end + _tmp = match_string(">") + unless _tmp + self.pos = _save + end + break + end # end sequence + + set_failed_rule :_HtmlBlockOpenFrameset unless _tmp + return _tmp + end + + # HtmlBlockCloseFrameset = "<" Spnl "/" ("frameset" | "FRAMESET") Spnl ">" + def _HtmlBlockCloseFrameset + + _save = self.pos + while true # sequence + _tmp = match_string("<") + unless _tmp + self.pos = _save + break + end + _tmp = apply(:_Spnl) + unless _tmp + self.pos = _save + break + end + _tmp = match_string("/") + unless _tmp + self.pos = _save + break + end + + _save1 = self.pos + while true # choice + _tmp = match_string("frameset") + break if _tmp + self.pos = _save1 + _tmp = match_string("FRAMESET") + break if _tmp + self.pos = _save1 + break + end # end choice + + unless _tmp + self.pos = _save + break + end + _tmp = apply(:_Spnl) + unless _tmp + self.pos = _save + break + end + _tmp = match_string(">") + unless _tmp + self.pos = _save + end + break + end # end sequence + + set_failed_rule :_HtmlBlockCloseFrameset unless _tmp + return _tmp + end + + # HtmlBlockFrameset = HtmlBlockOpenFrameset (HtmlBlockFrameset | !HtmlBlockCloseFrameset .)* HtmlBlockCloseFrameset + def _HtmlBlockFrameset + + _save = self.pos + while true # sequence + _tmp = apply(:_HtmlBlockOpenFrameset) + unless _tmp + self.pos = _save + break + end + while true + + _save2 = self.pos + while true # choice + _tmp = apply(:_HtmlBlockFrameset) + break if _tmp + self.pos = _save2 + + _save3 = self.pos + while true # sequence + _save4 = self.pos + _tmp = apply(:_HtmlBlockCloseFrameset) + _tmp = _tmp ? nil : true + self.pos = _save4 + unless _tmp + self.pos = _save3 + break + end + _tmp = get_byte + unless _tmp + self.pos = _save3 + end + break + end # end sequence + + break if _tmp + self.pos = _save2 + break + end # end choice + + break unless _tmp + end + _tmp = true + unless _tmp + self.pos = _save + break + end + _tmp = apply(:_HtmlBlockCloseFrameset) + unless _tmp + self.pos = _save + end + break + end # end sequence + + set_failed_rule :_HtmlBlockFrameset unless _tmp + return _tmp + end + + # HtmlBlockOpenLi = "<" Spnl ("li" | "LI") Spnl HtmlAttribute* ">" + def _HtmlBlockOpenLi + + _save = self.pos + while true # sequence + _tmp = match_string("<") + unless _tmp + self.pos = _save + break + end + _tmp = apply(:_Spnl) + unless _tmp + self.pos = _save + break + end + + _save1 = self.pos + while true # choice + _tmp = match_string("li") + break if _tmp + self.pos = _save1 + _tmp = match_string("LI") + break if _tmp + self.pos = _save1 + break + end # end choice + + unless _tmp + self.pos = _save + break + end + _tmp = apply(:_Spnl) + unless _tmp + self.pos = _save + break + end + while true + _tmp = apply(:_HtmlAttribute) + break unless _tmp + end + _tmp = true + unless _tmp + self.pos = _save + break + end + _tmp = match_string(">") + unless _tmp + self.pos = _save + end + break + end # end sequence + + set_failed_rule :_HtmlBlockOpenLi unless _tmp + return _tmp + end + + # HtmlBlockCloseLi = "<" Spnl "/" ("li" | "LI") Spnl ">" + def _HtmlBlockCloseLi + + _save = self.pos + while true # sequence + _tmp = match_string("<") + unless _tmp + self.pos = _save + break + end + _tmp = apply(:_Spnl) + unless _tmp + self.pos = _save + break + end + _tmp = match_string("/") + unless _tmp + self.pos = _save + break + end + + _save1 = self.pos + while true # choice + _tmp = match_string("li") + break if _tmp + self.pos = _save1 + _tmp = match_string("LI") + break if _tmp + self.pos = _save1 + break + end # end choice + + unless _tmp + self.pos = _save + break + end + _tmp = apply(:_Spnl) + unless _tmp + self.pos = _save + break + end + _tmp = match_string(">") + unless _tmp + self.pos = _save + end + break + end # end sequence + + set_failed_rule :_HtmlBlockCloseLi unless _tmp + return _tmp + end + + # HtmlBlockLi = HtmlBlockOpenLi (HtmlBlockLi | !HtmlBlockCloseLi .)* HtmlBlockCloseLi + def _HtmlBlockLi + + _save = self.pos + while true # sequence + _tmp = apply(:_HtmlBlockOpenLi) + unless _tmp + self.pos = _save + break + end + while true + + _save2 = self.pos + while true # choice + _tmp = apply(:_HtmlBlockLi) + break if _tmp + self.pos = _save2 + + _save3 = self.pos + while true # sequence + _save4 = self.pos + _tmp = apply(:_HtmlBlockCloseLi) + _tmp = _tmp ? nil : true + self.pos = _save4 + unless _tmp + self.pos = _save3 + break + end + _tmp = get_byte + unless _tmp + self.pos = _save3 + end + break + end # end sequence + + break if _tmp + self.pos = _save2 + break + end # end choice + + break unless _tmp + end + _tmp = true + unless _tmp + self.pos = _save + break + end + _tmp = apply(:_HtmlBlockCloseLi) + unless _tmp + self.pos = _save + end + break + end # end sequence + + set_failed_rule :_HtmlBlockLi unless _tmp + return _tmp + end + + # HtmlBlockOpenTbody = "<" Spnl ("tbody" | "TBODY") Spnl HtmlAttribute* ">" + def _HtmlBlockOpenTbody + + _save = self.pos + while true # sequence + _tmp = match_string("<") + unless _tmp + self.pos = _save + break + end + _tmp = apply(:_Spnl) + unless _tmp + self.pos = _save + break + end + + _save1 = self.pos + while true # choice + _tmp = match_string("tbody") + break if _tmp + self.pos = _save1 + _tmp = match_string("TBODY") + break if _tmp + self.pos = _save1 + break + end # end choice + + unless _tmp + self.pos = _save + break + end + _tmp = apply(:_Spnl) + unless _tmp + self.pos = _save + break + end + while true + _tmp = apply(:_HtmlAttribute) + break unless _tmp + end + _tmp = true + unless _tmp + self.pos = _save + break + end + _tmp = match_string(">") + unless _tmp + self.pos = _save + end + break + end # end sequence + + set_failed_rule :_HtmlBlockOpenTbody unless _tmp + return _tmp + end + + # HtmlBlockCloseTbody = "<" Spnl "/" ("tbody" | "TBODY") Spnl ">" + def _HtmlBlockCloseTbody + + _save = self.pos + while true # sequence + _tmp = match_string("<") + unless _tmp + self.pos = _save + break + end + _tmp = apply(:_Spnl) + unless _tmp + self.pos = _save + break + end + _tmp = match_string("/") + unless _tmp + self.pos = _save + break + end + + _save1 = self.pos + while true # choice + _tmp = match_string("tbody") + break if _tmp + self.pos = _save1 + _tmp = match_string("TBODY") + break if _tmp + self.pos = _save1 + break + end # end choice + + unless _tmp + self.pos = _save + break + end + _tmp = apply(:_Spnl) + unless _tmp + self.pos = _save + break + end + _tmp = match_string(">") + unless _tmp + self.pos = _save + end + break + end # end sequence + + set_failed_rule :_HtmlBlockCloseTbody unless _tmp + return _tmp + end + + # HtmlBlockTbody = HtmlBlockOpenTbody (HtmlBlockTbody | !HtmlBlockCloseTbody .)* HtmlBlockCloseTbody + def _HtmlBlockTbody + + _save = self.pos + while true # sequence + _tmp = apply(:_HtmlBlockOpenTbody) + unless _tmp + self.pos = _save + break + end + while true + + _save2 = self.pos + while true # choice + _tmp = apply(:_HtmlBlockTbody) + break if _tmp + self.pos = _save2 + + _save3 = self.pos + while true # sequence + _save4 = self.pos + _tmp = apply(:_HtmlBlockCloseTbody) + _tmp = _tmp ? nil : true + self.pos = _save4 + unless _tmp + self.pos = _save3 + break + end + _tmp = get_byte + unless _tmp + self.pos = _save3 + end + break + end # end sequence + + break if _tmp + self.pos = _save2 + break + end # end choice + + break unless _tmp + end + _tmp = true + unless _tmp + self.pos = _save + break + end + _tmp = apply(:_HtmlBlockCloseTbody) + unless _tmp + self.pos = _save + end + break + end # end sequence + + set_failed_rule :_HtmlBlockTbody unless _tmp + return _tmp + end + + # HtmlBlockOpenTd = "<" Spnl ("td" | "TD") Spnl HtmlAttribute* ">" + def _HtmlBlockOpenTd + + _save = self.pos + while true # sequence + _tmp = match_string("<") + unless _tmp + self.pos = _save + break + end + _tmp = apply(:_Spnl) + unless _tmp + self.pos = _save + break + end + + _save1 = self.pos + while true # choice + _tmp = match_string("td") + break if _tmp + self.pos = _save1 + _tmp = match_string("TD") + break if _tmp + self.pos = _save1 + break + end # end choice + + unless _tmp + self.pos = _save + break + end + _tmp = apply(:_Spnl) + unless _tmp + self.pos = _save + break + end + while true + _tmp = apply(:_HtmlAttribute) + break unless _tmp + end + _tmp = true + unless _tmp + self.pos = _save + break + end + _tmp = match_string(">") + unless _tmp + self.pos = _save + end + break + end # end sequence + + set_failed_rule :_HtmlBlockOpenTd unless _tmp + return _tmp + end + + # HtmlBlockCloseTd = "<" Spnl "/" ("td" | "TD") Spnl ">" + def _HtmlBlockCloseTd + + _save = self.pos + while true # sequence + _tmp = match_string("<") + unless _tmp + self.pos = _save + break + end + _tmp = apply(:_Spnl) + unless _tmp + self.pos = _save + break + end + _tmp = match_string("/") + unless _tmp + self.pos = _save + break + end + + _save1 = self.pos + while true # choice + _tmp = match_string("td") + break if _tmp + self.pos = _save1 + _tmp = match_string("TD") + break if _tmp + self.pos = _save1 + break + end # end choice + + unless _tmp + self.pos = _save + break + end + _tmp = apply(:_Spnl) + unless _tmp + self.pos = _save + break + end + _tmp = match_string(">") + unless _tmp + self.pos = _save + end + break + end # end sequence + + set_failed_rule :_HtmlBlockCloseTd unless _tmp + return _tmp + end + + # HtmlBlockTd = HtmlBlockOpenTd (HtmlBlockTd | !HtmlBlockCloseTd .)* HtmlBlockCloseTd + def _HtmlBlockTd + + _save = self.pos + while true # sequence + _tmp = apply(:_HtmlBlockOpenTd) + unless _tmp + self.pos = _save + break + end + while true + + _save2 = self.pos + while true # choice + _tmp = apply(:_HtmlBlockTd) + break if _tmp + self.pos = _save2 + + _save3 = self.pos + while true # sequence + _save4 = self.pos + _tmp = apply(:_HtmlBlockCloseTd) + _tmp = _tmp ? nil : true + self.pos = _save4 + unless _tmp + self.pos = _save3 + break + end + _tmp = get_byte + unless _tmp + self.pos = _save3 + end + break + end # end sequence + + break if _tmp + self.pos = _save2 + break + end # end choice + + break unless _tmp + end + _tmp = true + unless _tmp + self.pos = _save + break + end + _tmp = apply(:_HtmlBlockCloseTd) + unless _tmp + self.pos = _save + end + break + end # end sequence + + set_failed_rule :_HtmlBlockTd unless _tmp + return _tmp + end + + # HtmlBlockOpenTfoot = "<" Spnl ("tfoot" | "TFOOT") Spnl HtmlAttribute* ">" + def _HtmlBlockOpenTfoot + + _save = self.pos + while true # sequence + _tmp = match_string("<") + unless _tmp + self.pos = _save + break + end + _tmp = apply(:_Spnl) + unless _tmp + self.pos = _save + break + end + + _save1 = self.pos + while true # choice + _tmp = match_string("tfoot") + break if _tmp + self.pos = _save1 + _tmp = match_string("TFOOT") + break if _tmp + self.pos = _save1 + break + end # end choice + + unless _tmp + self.pos = _save + break + end + _tmp = apply(:_Spnl) + unless _tmp + self.pos = _save + break + end + while true + _tmp = apply(:_HtmlAttribute) + break unless _tmp + end + _tmp = true + unless _tmp + self.pos = _save + break + end + _tmp = match_string(">") + unless _tmp + self.pos = _save + end + break + end # end sequence + + set_failed_rule :_HtmlBlockOpenTfoot unless _tmp + return _tmp + end + + # HtmlBlockCloseTfoot = "<" Spnl "/" ("tfoot" | "TFOOT") Spnl ">" + def _HtmlBlockCloseTfoot + + _save = self.pos + while true # sequence + _tmp = match_string("<") + unless _tmp + self.pos = _save + break + end + _tmp = apply(:_Spnl) + unless _tmp + self.pos = _save + break + end + _tmp = match_string("/") + unless _tmp + self.pos = _save + break + end + + _save1 = self.pos + while true # choice + _tmp = match_string("tfoot") + break if _tmp + self.pos = _save1 + _tmp = match_string("TFOOT") + break if _tmp + self.pos = _save1 + break + end # end choice + + unless _tmp + self.pos = _save + break + end + _tmp = apply(:_Spnl) + unless _tmp + self.pos = _save + break + end + _tmp = match_string(">") + unless _tmp + self.pos = _save + end + break + end # end sequence + + set_failed_rule :_HtmlBlockCloseTfoot unless _tmp + return _tmp + end + + # HtmlBlockTfoot = HtmlBlockOpenTfoot (HtmlBlockTfoot | !HtmlBlockCloseTfoot .)* HtmlBlockCloseTfoot + def _HtmlBlockTfoot + + _save = self.pos + while true # sequence + _tmp = apply(:_HtmlBlockOpenTfoot) + unless _tmp + self.pos = _save + break + end + while true + + _save2 = self.pos + while true # choice + _tmp = apply(:_HtmlBlockTfoot) + break if _tmp + self.pos = _save2 + + _save3 = self.pos + while true # sequence + _save4 = self.pos + _tmp = apply(:_HtmlBlockCloseTfoot) + _tmp = _tmp ? nil : true + self.pos = _save4 + unless _tmp + self.pos = _save3 + break + end + _tmp = get_byte + unless _tmp + self.pos = _save3 + end + break + end # end sequence + + break if _tmp + self.pos = _save2 + break + end # end choice + + break unless _tmp + end + _tmp = true + unless _tmp + self.pos = _save + break + end + _tmp = apply(:_HtmlBlockCloseTfoot) + unless _tmp + self.pos = _save + end + break + end # end sequence + + set_failed_rule :_HtmlBlockTfoot unless _tmp + return _tmp + end + + # HtmlBlockOpenTh = "<" Spnl ("th" | "TH") Spnl HtmlAttribute* ">" + def _HtmlBlockOpenTh + + _save = self.pos + while true # sequence + _tmp = match_string("<") + unless _tmp + self.pos = _save + break + end + _tmp = apply(:_Spnl) + unless _tmp + self.pos = _save + break + end + + _save1 = self.pos + while true # choice + _tmp = match_string("th") + break if _tmp + self.pos = _save1 + _tmp = match_string("TH") + break if _tmp + self.pos = _save1 + break + end # end choice + + unless _tmp + self.pos = _save + break + end + _tmp = apply(:_Spnl) + unless _tmp + self.pos = _save + break + end + while true + _tmp = apply(:_HtmlAttribute) + break unless _tmp + end + _tmp = true + unless _tmp + self.pos = _save + break + end + _tmp = match_string(">") + unless _tmp + self.pos = _save + end + break + end # end sequence + + set_failed_rule :_HtmlBlockOpenTh unless _tmp + return _tmp + end + + # HtmlBlockCloseTh = "<" Spnl "/" ("th" | "TH") Spnl ">" + def _HtmlBlockCloseTh + + _save = self.pos + while true # sequence + _tmp = match_string("<") + unless _tmp + self.pos = _save + break + end + _tmp = apply(:_Spnl) + unless _tmp + self.pos = _save + break + end + _tmp = match_string("/") + unless _tmp + self.pos = _save + break + end + + _save1 = self.pos + while true # choice + _tmp = match_string("th") + break if _tmp + self.pos = _save1 + _tmp = match_string("TH") + break if _tmp + self.pos = _save1 + break + end # end choice + + unless _tmp + self.pos = _save + break + end + _tmp = apply(:_Spnl) + unless _tmp + self.pos = _save + break + end + _tmp = match_string(">") + unless _tmp + self.pos = _save + end + break + end # end sequence + + set_failed_rule :_HtmlBlockCloseTh unless _tmp + return _tmp + end + + # HtmlBlockTh = HtmlBlockOpenTh (HtmlBlockTh | !HtmlBlockCloseTh .)* HtmlBlockCloseTh + def _HtmlBlockTh + + _save = self.pos + while true # sequence + _tmp = apply(:_HtmlBlockOpenTh) + unless _tmp + self.pos = _save + break + end + while true + + _save2 = self.pos + while true # choice + _tmp = apply(:_HtmlBlockTh) + break if _tmp + self.pos = _save2 + + _save3 = self.pos + while true # sequence + _save4 = self.pos + _tmp = apply(:_HtmlBlockCloseTh) + _tmp = _tmp ? nil : true + self.pos = _save4 + unless _tmp + self.pos = _save3 + break + end + _tmp = get_byte + unless _tmp + self.pos = _save3 + end + break + end # end sequence + + break if _tmp + self.pos = _save2 + break + end # end choice + + break unless _tmp + end + _tmp = true + unless _tmp + self.pos = _save + break + end + _tmp = apply(:_HtmlBlockCloseTh) + unless _tmp + self.pos = _save + end + break + end # end sequence + + set_failed_rule :_HtmlBlockTh unless _tmp + return _tmp + end + + # HtmlBlockOpenThead = "<" Spnl ("thead" | "THEAD") Spnl HtmlAttribute* ">" + def _HtmlBlockOpenThead + + _save = self.pos + while true # sequence + _tmp = match_string("<") + unless _tmp + self.pos = _save + break + end + _tmp = apply(:_Spnl) + unless _tmp + self.pos = _save + break + end + + _save1 = self.pos + while true # choice + _tmp = match_string("thead") + break if _tmp + self.pos = _save1 + _tmp = match_string("THEAD") + break if _tmp + self.pos = _save1 + break + end # end choice + + unless _tmp + self.pos = _save + break + end + _tmp = apply(:_Spnl) + unless _tmp + self.pos = _save + break + end + while true + _tmp = apply(:_HtmlAttribute) + break unless _tmp + end + _tmp = true + unless _tmp + self.pos = _save + break + end + _tmp = match_string(">") + unless _tmp + self.pos = _save + end + break + end # end sequence + + set_failed_rule :_HtmlBlockOpenThead unless _tmp + return _tmp + end + + # HtmlBlockCloseThead = "<" Spnl "/" ("thead" | "THEAD") Spnl ">" + def _HtmlBlockCloseThead + + _save = self.pos + while true # sequence + _tmp = match_string("<") + unless _tmp + self.pos = _save + break + end + _tmp = apply(:_Spnl) + unless _tmp + self.pos = _save + break + end + _tmp = match_string("/") + unless _tmp + self.pos = _save + break + end + + _save1 = self.pos + while true # choice + _tmp = match_string("thead") + break if _tmp + self.pos = _save1 + _tmp = match_string("THEAD") + break if _tmp + self.pos = _save1 + break + end # end choice + + unless _tmp + self.pos = _save + break + end + _tmp = apply(:_Spnl) + unless _tmp + self.pos = _save + break + end + _tmp = match_string(">") + unless _tmp + self.pos = _save + end + break + end # end sequence + + set_failed_rule :_HtmlBlockCloseThead unless _tmp + return _tmp + end + + # HtmlBlockThead = HtmlBlockOpenThead (HtmlBlockThead | !HtmlBlockCloseThead .)* HtmlBlockCloseThead + def _HtmlBlockThead + + _save = self.pos + while true # sequence + _tmp = apply(:_HtmlBlockOpenThead) + unless _tmp + self.pos = _save + break + end + while true + + _save2 = self.pos + while true # choice + _tmp = apply(:_HtmlBlockThead) + break if _tmp + self.pos = _save2 + + _save3 = self.pos + while true # sequence + _save4 = self.pos + _tmp = apply(:_HtmlBlockCloseThead) + _tmp = _tmp ? nil : true + self.pos = _save4 + unless _tmp + self.pos = _save3 + break + end + _tmp = get_byte + unless _tmp + self.pos = _save3 + end + break + end # end sequence + + break if _tmp + self.pos = _save2 + break + end # end choice + + break unless _tmp + end + _tmp = true + unless _tmp + self.pos = _save + break + end + _tmp = apply(:_HtmlBlockCloseThead) + unless _tmp + self.pos = _save + end + break + end # end sequence + + set_failed_rule :_HtmlBlockThead unless _tmp + return _tmp + end + + # HtmlBlockOpenTr = "<" Spnl ("tr" | "TR") Spnl HtmlAttribute* ">" + def _HtmlBlockOpenTr + + _save = self.pos + while true # sequence + _tmp = match_string("<") + unless _tmp + self.pos = _save + break + end + _tmp = apply(:_Spnl) + unless _tmp + self.pos = _save + break + end + + _save1 = self.pos + while true # choice + _tmp = match_string("tr") + break if _tmp + self.pos = _save1 + _tmp = match_string("TR") + break if _tmp + self.pos = _save1 + break + end # end choice + + unless _tmp + self.pos = _save + break + end + _tmp = apply(:_Spnl) + unless _tmp + self.pos = _save + break + end + while true + _tmp = apply(:_HtmlAttribute) + break unless _tmp + end + _tmp = true + unless _tmp + self.pos = _save + break + end + _tmp = match_string(">") + unless _tmp + self.pos = _save + end + break + end # end sequence + + set_failed_rule :_HtmlBlockOpenTr unless _tmp + return _tmp + end + + # HtmlBlockCloseTr = "<" Spnl "/" ("tr" | "TR") Spnl ">" + def _HtmlBlockCloseTr + + _save = self.pos + while true # sequence + _tmp = match_string("<") + unless _tmp + self.pos = _save + break + end + _tmp = apply(:_Spnl) + unless _tmp + self.pos = _save + break + end + _tmp = match_string("/") + unless _tmp + self.pos = _save + break + end + + _save1 = self.pos + while true # choice + _tmp = match_string("tr") + break if _tmp + self.pos = _save1 + _tmp = match_string("TR") + break if _tmp + self.pos = _save1 + break + end # end choice + + unless _tmp + self.pos = _save + break + end + _tmp = apply(:_Spnl) + unless _tmp + self.pos = _save + break + end + _tmp = match_string(">") + unless _tmp + self.pos = _save + end + break + end # end sequence + + set_failed_rule :_HtmlBlockCloseTr unless _tmp + return _tmp + end + + # HtmlBlockTr = HtmlBlockOpenTr (HtmlBlockTr | !HtmlBlockCloseTr .)* HtmlBlockCloseTr + def _HtmlBlockTr + + _save = self.pos + while true # sequence + _tmp = apply(:_HtmlBlockOpenTr) + unless _tmp + self.pos = _save + break + end + while true + + _save2 = self.pos + while true # choice + _tmp = apply(:_HtmlBlockTr) + break if _tmp + self.pos = _save2 + + _save3 = self.pos + while true # sequence + _save4 = self.pos + _tmp = apply(:_HtmlBlockCloseTr) + _tmp = _tmp ? nil : true + self.pos = _save4 + unless _tmp + self.pos = _save3 + break + end + _tmp = get_byte + unless _tmp + self.pos = _save3 + end + break + end # end sequence + + break if _tmp + self.pos = _save2 + break + end # end choice + + break unless _tmp + end + _tmp = true + unless _tmp + self.pos = _save + break + end + _tmp = apply(:_HtmlBlockCloseTr) + unless _tmp + self.pos = _save + end + break + end # end sequence + + set_failed_rule :_HtmlBlockTr unless _tmp + return _tmp + end + + # HtmlBlockOpenScript = "<" Spnl ("script" | "SCRIPT") Spnl HtmlAttribute* ">" + def _HtmlBlockOpenScript + + _save = self.pos + while true # sequence + _tmp = match_string("<") + unless _tmp + self.pos = _save + break + end + _tmp = apply(:_Spnl) + unless _tmp + self.pos = _save + break + end + + _save1 = self.pos + while true # choice + _tmp = match_string("script") + break if _tmp + self.pos = _save1 + _tmp = match_string("SCRIPT") + break if _tmp + self.pos = _save1 + break + end # end choice + + unless _tmp + self.pos = _save + break + end + _tmp = apply(:_Spnl) + unless _tmp + self.pos = _save + break + end + while true + _tmp = apply(:_HtmlAttribute) + break unless _tmp + end + _tmp = true + unless _tmp + self.pos = _save + break + end + _tmp = match_string(">") + unless _tmp + self.pos = _save + end + break + end # end sequence + + set_failed_rule :_HtmlBlockOpenScript unless _tmp + return _tmp + end + + # HtmlBlockCloseScript = "<" Spnl "/" ("script" | "SCRIPT") Spnl ">" + def _HtmlBlockCloseScript + + _save = self.pos + while true # sequence + _tmp = match_string("<") + unless _tmp + self.pos = _save + break + end + _tmp = apply(:_Spnl) + unless _tmp + self.pos = _save + break + end + _tmp = match_string("/") + unless _tmp + self.pos = _save + break + end + + _save1 = self.pos + while true # choice + _tmp = match_string("script") + break if _tmp + self.pos = _save1 + _tmp = match_string("SCRIPT") + break if _tmp + self.pos = _save1 + break + end # end choice + + unless _tmp + self.pos = _save + break + end + _tmp = apply(:_Spnl) + unless _tmp + self.pos = _save + break + end + _tmp = match_string(">") + unless _tmp + self.pos = _save + end + break + end # end sequence + + set_failed_rule :_HtmlBlockCloseScript unless _tmp + return _tmp + end + + # HtmlBlockScript = HtmlBlockOpenScript (!HtmlBlockCloseScript .)* HtmlBlockCloseScript + def _HtmlBlockScript + + _save = self.pos + while true # sequence + _tmp = apply(:_HtmlBlockOpenScript) + unless _tmp + self.pos = _save + break + end + while true + + _save2 = self.pos + while true # sequence + _save3 = self.pos + _tmp = apply(:_HtmlBlockCloseScript) + _tmp = _tmp ? nil : true + self.pos = _save3 + unless _tmp + self.pos = _save2 + break + end + _tmp = get_byte + unless _tmp + self.pos = _save2 + end + break + end # end sequence + + break unless _tmp + end + _tmp = true + unless _tmp + self.pos = _save + break + end + _tmp = apply(:_HtmlBlockCloseScript) + unless _tmp + self.pos = _save + end + break + end # end sequence + + set_failed_rule :_HtmlBlockScript unless _tmp + return _tmp + end + + # HtmlBlockOpenHead = "<" Spnl ("head" | "HEAD") Spnl HtmlAttribute* ">" + def _HtmlBlockOpenHead + + _save = self.pos + while true # sequence + _tmp = match_string("<") + unless _tmp + self.pos = _save + break + end + _tmp = apply(:_Spnl) + unless _tmp + self.pos = _save + break + end + + _save1 = self.pos + while true # choice + _tmp = match_string("head") + break if _tmp + self.pos = _save1 + _tmp = match_string("HEAD") + break if _tmp + self.pos = _save1 + break + end # end choice + + unless _tmp + self.pos = _save + break + end + _tmp = apply(:_Spnl) + unless _tmp + self.pos = _save + break + end + while true + _tmp = apply(:_HtmlAttribute) + break unless _tmp + end + _tmp = true + unless _tmp + self.pos = _save + break + end + _tmp = match_string(">") + unless _tmp + self.pos = _save + end + break + end # end sequence + + set_failed_rule :_HtmlBlockOpenHead unless _tmp + return _tmp + end + + # HtmlBlockCloseHead = "<" Spnl "/" ("head" | "HEAD") Spnl ">" + def _HtmlBlockCloseHead + + _save = self.pos + while true # sequence + _tmp = match_string("<") + unless _tmp + self.pos = _save + break + end + _tmp = apply(:_Spnl) + unless _tmp + self.pos = _save + break + end + _tmp = match_string("/") + unless _tmp + self.pos = _save + break + end + + _save1 = self.pos + while true # choice + _tmp = match_string("head") + break if _tmp + self.pos = _save1 + _tmp = match_string("HEAD") + break if _tmp + self.pos = _save1 + break + end # end choice + + unless _tmp + self.pos = _save + break + end + _tmp = apply(:_Spnl) + unless _tmp + self.pos = _save + break + end + _tmp = match_string(">") + unless _tmp + self.pos = _save + end + break + end # end sequence + + set_failed_rule :_HtmlBlockCloseHead unless _tmp + return _tmp + end + + # HtmlBlockHead = HtmlBlockOpenHead (!HtmlBlockCloseHead .)* HtmlBlockCloseHead + def _HtmlBlockHead + + _save = self.pos + while true # sequence + _tmp = apply(:_HtmlBlockOpenHead) + unless _tmp + self.pos = _save + break + end + while true + + _save2 = self.pos + while true # sequence + _save3 = self.pos + _tmp = apply(:_HtmlBlockCloseHead) + _tmp = _tmp ? nil : true + self.pos = _save3 + unless _tmp + self.pos = _save2 + break + end + _tmp = get_byte + unless _tmp + self.pos = _save2 + end + break + end # end sequence + + break unless _tmp + end + _tmp = true + unless _tmp + self.pos = _save + break + end + _tmp = apply(:_HtmlBlockCloseHead) + unless _tmp + self.pos = _save + end + break + end # end sequence + + set_failed_rule :_HtmlBlockHead unless _tmp + return _tmp + end + + # HtmlBlockInTags = (HtmlAnchor | HtmlBlockAddress | HtmlBlockBlockquote | HtmlBlockCenter | HtmlBlockDir | HtmlBlockDiv | HtmlBlockDl | HtmlBlockFieldset | HtmlBlockForm | HtmlBlockH1 | HtmlBlockH2 | HtmlBlockH3 | HtmlBlockH4 | HtmlBlockH5 | HtmlBlockH6 | HtmlBlockMenu | HtmlBlockNoframes | HtmlBlockNoscript | HtmlBlockOl | HtmlBlockP | HtmlBlockPre | HtmlBlockTable | HtmlBlockUl | HtmlBlockDd | HtmlBlockDt | HtmlBlockFrameset | HtmlBlockLi | HtmlBlockTbody | HtmlBlockTd | HtmlBlockTfoot | HtmlBlockTh | HtmlBlockThead | HtmlBlockTr | HtmlBlockScript | HtmlBlockHead) + def _HtmlBlockInTags + + _save = self.pos + while true # choice + _tmp = apply(:_HtmlAnchor) + break if _tmp + self.pos = _save + _tmp = apply(:_HtmlBlockAddress) + break if _tmp + self.pos = _save + _tmp = apply(:_HtmlBlockBlockquote) + break if _tmp + self.pos = _save + _tmp = apply(:_HtmlBlockCenter) + break if _tmp + self.pos = _save + _tmp = apply(:_HtmlBlockDir) + break if _tmp + self.pos = _save + _tmp = apply(:_HtmlBlockDiv) + break if _tmp + self.pos = _save + _tmp = apply(:_HtmlBlockDl) + break if _tmp + self.pos = _save + _tmp = apply(:_HtmlBlockFieldset) + break if _tmp + self.pos = _save + _tmp = apply(:_HtmlBlockForm) + break if _tmp + self.pos = _save + _tmp = apply(:_HtmlBlockH1) + break if _tmp + self.pos = _save + _tmp = apply(:_HtmlBlockH2) + break if _tmp + self.pos = _save + _tmp = apply(:_HtmlBlockH3) + break if _tmp + self.pos = _save + _tmp = apply(:_HtmlBlockH4) + break if _tmp + self.pos = _save + _tmp = apply(:_HtmlBlockH5) + break if _tmp + self.pos = _save + _tmp = apply(:_HtmlBlockH6) + break if _tmp + self.pos = _save + _tmp = apply(:_HtmlBlockMenu) + break if _tmp + self.pos = _save + _tmp = apply(:_HtmlBlockNoframes) + break if _tmp + self.pos = _save + _tmp = apply(:_HtmlBlockNoscript) + break if _tmp + self.pos = _save + _tmp = apply(:_HtmlBlockOl) + break if _tmp + self.pos = _save + _tmp = apply(:_HtmlBlockP) + break if _tmp + self.pos = _save + _tmp = apply(:_HtmlBlockPre) + break if _tmp + self.pos = _save + _tmp = apply(:_HtmlBlockTable) + break if _tmp + self.pos = _save + _tmp = apply(:_HtmlBlockUl) + break if _tmp + self.pos = _save + _tmp = apply(:_HtmlBlockDd) + break if _tmp + self.pos = _save + _tmp = apply(:_HtmlBlockDt) + break if _tmp + self.pos = _save + _tmp = apply(:_HtmlBlockFrameset) + break if _tmp + self.pos = _save + _tmp = apply(:_HtmlBlockLi) + break if _tmp + self.pos = _save + _tmp = apply(:_HtmlBlockTbody) + break if _tmp + self.pos = _save + _tmp = apply(:_HtmlBlockTd) + break if _tmp + self.pos = _save + _tmp = apply(:_HtmlBlockTfoot) + break if _tmp + self.pos = _save + _tmp = apply(:_HtmlBlockTh) + break if _tmp + self.pos = _save + _tmp = apply(:_HtmlBlockThead) + break if _tmp + self.pos = _save + _tmp = apply(:_HtmlBlockTr) + break if _tmp + self.pos = _save + _tmp = apply(:_HtmlBlockScript) + break if _tmp + self.pos = _save + _tmp = apply(:_HtmlBlockHead) + break if _tmp + self.pos = _save + break + end # end choice + + set_failed_rule :_HtmlBlockInTags unless _tmp + return _tmp + end + + # HtmlBlock = < (HtmlBlockInTags | HtmlComment | HtmlBlockSelfClosing | HtmlUnclosed) > @BlankLine+ { if html? then RDoc::Markup::Raw.new text end } + def _HtmlBlock + + _save = self.pos + while true # sequence + _text_start = self.pos + + _save1 = self.pos + while true # choice + _tmp = apply(:_HtmlBlockInTags) + break if _tmp + self.pos = _save1 + _tmp = apply(:_HtmlComment) + break if _tmp + self.pos = _save1 + _tmp = apply(:_HtmlBlockSelfClosing) + break if _tmp + self.pos = _save1 + _tmp = apply(:_HtmlUnclosed) + break if _tmp + self.pos = _save1 + break + end # end choice + + if _tmp + text = get_text(_text_start) + end + unless _tmp + self.pos = _save + break + end + _save2 = self.pos + _tmp = _BlankLine() + if _tmp + while true + _tmp = _BlankLine() + break unless _tmp + end + _tmp = true + else + self.pos = _save2 + end + unless _tmp + self.pos = _save + break + end + @result = begin; if html? then + RDoc::Markup::Raw.new text + end ; end + _tmp = true + unless _tmp + self.pos = _save + end + break + end # end sequence + + set_failed_rule :_HtmlBlock unless _tmp + return _tmp + end + + # HtmlUnclosed = "<" Spnl HtmlUnclosedType Spnl HtmlAttribute* Spnl ">" + def _HtmlUnclosed + + _save = self.pos + while true # sequence + _tmp = match_string("<") + unless _tmp + self.pos = _save + break + end + _tmp = apply(:_Spnl) + unless _tmp + self.pos = _save + break + end + _tmp = apply(:_HtmlUnclosedType) + unless _tmp + self.pos = _save + break + end + _tmp = apply(:_Spnl) + unless _tmp + self.pos = _save + break + end + while true + _tmp = apply(:_HtmlAttribute) + break unless _tmp + end + _tmp = true + unless _tmp + self.pos = _save + break + end + _tmp = apply(:_Spnl) + unless _tmp + self.pos = _save + break + end + _tmp = match_string(">") + unless _tmp + self.pos = _save + end + break + end # end sequence + + set_failed_rule :_HtmlUnclosed unless _tmp + return _tmp + end + + # HtmlUnclosedType = ("HR" | "hr") + def _HtmlUnclosedType + + _save = self.pos + while true # choice + _tmp = match_string("HR") + break if _tmp + self.pos = _save + _tmp = match_string("hr") + break if _tmp + self.pos = _save + break + end # end choice + + set_failed_rule :_HtmlUnclosedType unless _tmp + return _tmp + end + + # HtmlBlockSelfClosing = "<" Spnl HtmlBlockType Spnl HtmlAttribute* "/" Spnl ">" + def _HtmlBlockSelfClosing + + _save = self.pos + while true # sequence + _tmp = match_string("<") + unless _tmp + self.pos = _save + break + end + _tmp = apply(:_Spnl) + unless _tmp + self.pos = _save + break + end + _tmp = apply(:_HtmlBlockType) + unless _tmp + self.pos = _save + break + end + _tmp = apply(:_Spnl) + unless _tmp + self.pos = _save + break + end + while true + _tmp = apply(:_HtmlAttribute) + break unless _tmp + end + _tmp = true + unless _tmp + self.pos = _save + break + end + _tmp = match_string("/") + unless _tmp + self.pos = _save + break + end + _tmp = apply(:_Spnl) + unless _tmp + self.pos = _save + break + end + _tmp = match_string(">") + unless _tmp + self.pos = _save + end + break + end # end sequence + + set_failed_rule :_HtmlBlockSelfClosing unless _tmp + return _tmp + end + + # HtmlBlockType = ("ADDRESS" | "BLOCKQUOTE" | "CENTER" | "DD" | "DIR" | "DIV" | "DL" | "DT" | "FIELDSET" | "FORM" | "FRAMESET" | "H1" | "H2" | "H3" | "H4" | "H5" | "H6" | "HR" | "ISINDEX" | "LI" | "MENU" | "NOFRAMES" | "NOSCRIPT" | "OL" | "P" | "PRE" | "SCRIPT" | "TABLE" | "TBODY" | "TD" | "TFOOT" | "TH" | "THEAD" | "TR" | "UL" | "address" | "blockquote" | "center" | "dd" | "dir" | "div" | "dl" | "dt" | "fieldset" | "form" | "frameset" | "h1" | "h2" | "h3" | "h4" | "h5" | "h6" | "hr" | "isindex" | "li" | "menu" | "noframes" | "noscript" | "ol" | "p" | "pre" | "script" | "table" | "tbody" | "td" | "tfoot" | "th" | "thead" | "tr" | "ul") + def _HtmlBlockType + + _save = self.pos + while true # choice + _tmp = match_string("ADDRESS") + break if _tmp + self.pos = _save + _tmp = match_string("BLOCKQUOTE") + break if _tmp + self.pos = _save + _tmp = match_string("CENTER") + break if _tmp + self.pos = _save + _tmp = match_string("DD") + break if _tmp + self.pos = _save + _tmp = match_string("DIR") + break if _tmp + self.pos = _save + _tmp = match_string("DIV") + break if _tmp + self.pos = _save + _tmp = match_string("DL") + break if _tmp + self.pos = _save + _tmp = match_string("DT") + break if _tmp + self.pos = _save + _tmp = match_string("FIELDSET") + break if _tmp + self.pos = _save + _tmp = match_string("FORM") + break if _tmp + self.pos = _save + _tmp = match_string("FRAMESET") + break if _tmp + self.pos = _save + _tmp = match_string("H1") + break if _tmp + self.pos = _save + _tmp = match_string("H2") + break if _tmp + self.pos = _save + _tmp = match_string("H3") + break if _tmp + self.pos = _save + _tmp = match_string("H4") + break if _tmp + self.pos = _save + _tmp = match_string("H5") + break if _tmp + self.pos = _save + _tmp = match_string("H6") + break if _tmp + self.pos = _save + _tmp = match_string("HR") + break if _tmp + self.pos = _save + _tmp = match_string("ISINDEX") + break if _tmp + self.pos = _save + _tmp = match_string("LI") + break if _tmp + self.pos = _save + _tmp = match_string("MENU") + break if _tmp + self.pos = _save + _tmp = match_string("NOFRAMES") + break if _tmp + self.pos = _save + _tmp = match_string("NOSCRIPT") + break if _tmp + self.pos = _save + _tmp = match_string("OL") + break if _tmp + self.pos = _save + _tmp = match_string("P") + break if _tmp + self.pos = _save + _tmp = match_string("PRE") + break if _tmp + self.pos = _save + _tmp = match_string("SCRIPT") + break if _tmp + self.pos = _save + _tmp = match_string("TABLE") + break if _tmp + self.pos = _save + _tmp = match_string("TBODY") + break if _tmp + self.pos = _save + _tmp = match_string("TD") + break if _tmp + self.pos = _save + _tmp = match_string("TFOOT") + break if _tmp + self.pos = _save + _tmp = match_string("TH") + break if _tmp + self.pos = _save + _tmp = match_string("THEAD") + break if _tmp + self.pos = _save + _tmp = match_string("TR") + break if _tmp + self.pos = _save + _tmp = match_string("UL") + break if _tmp + self.pos = _save + _tmp = match_string("address") + break if _tmp + self.pos = _save + _tmp = match_string("blockquote") + break if _tmp + self.pos = _save + _tmp = match_string("center") + break if _tmp + self.pos = _save + _tmp = match_string("dd") + break if _tmp + self.pos = _save + _tmp = match_string("dir") + break if _tmp + self.pos = _save + _tmp = match_string("div") + break if _tmp + self.pos = _save + _tmp = match_string("dl") + break if _tmp + self.pos = _save + _tmp = match_string("dt") + break if _tmp + self.pos = _save + _tmp = match_string("fieldset") + break if _tmp + self.pos = _save + _tmp = match_string("form") + break if _tmp + self.pos = _save + _tmp = match_string("frameset") + break if _tmp + self.pos = _save + _tmp = match_string("h1") + break if _tmp + self.pos = _save + _tmp = match_string("h2") + break if _tmp + self.pos = _save + _tmp = match_string("h3") + break if _tmp + self.pos = _save + _tmp = match_string("h4") + break if _tmp + self.pos = _save + _tmp = match_string("h5") + break if _tmp + self.pos = _save + _tmp = match_string("h6") + break if _tmp + self.pos = _save + _tmp = match_string("hr") + break if _tmp + self.pos = _save + _tmp = match_string("isindex") + break if _tmp + self.pos = _save + _tmp = match_string("li") + break if _tmp + self.pos = _save + _tmp = match_string("menu") + break if _tmp + self.pos = _save + _tmp = match_string("noframes") + break if _tmp + self.pos = _save + _tmp = match_string("noscript") + break if _tmp + self.pos = _save + _tmp = match_string("ol") + break if _tmp + self.pos = _save + _tmp = match_string("p") + break if _tmp + self.pos = _save + _tmp = match_string("pre") + break if _tmp + self.pos = _save + _tmp = match_string("script") + break if _tmp + self.pos = _save + _tmp = match_string("table") + break if _tmp + self.pos = _save + _tmp = match_string("tbody") + break if _tmp + self.pos = _save + _tmp = match_string("td") + break if _tmp + self.pos = _save + _tmp = match_string("tfoot") + break if _tmp + self.pos = _save + _tmp = match_string("th") + break if _tmp + self.pos = _save + _tmp = match_string("thead") + break if _tmp + self.pos = _save + _tmp = match_string("tr") + break if _tmp + self.pos = _save + _tmp = match_string("ul") + break if _tmp + self.pos = _save + break + end # end choice + + set_failed_rule :_HtmlBlockType unless _tmp + return _tmp + end + + # StyleOpen = "<" Spnl ("style" | "STYLE") Spnl HtmlAttribute* ">" + def _StyleOpen + + _save = self.pos + while true # sequence + _tmp = match_string("<") + unless _tmp + self.pos = _save + break + end + _tmp = apply(:_Spnl) + unless _tmp + self.pos = _save + break + end + + _save1 = self.pos + while true # choice + _tmp = match_string("style") + break if _tmp + self.pos = _save1 + _tmp = match_string("STYLE") + break if _tmp + self.pos = _save1 + break + end # end choice + + unless _tmp + self.pos = _save + break + end + _tmp = apply(:_Spnl) + unless _tmp + self.pos = _save + break + end + while true + _tmp = apply(:_HtmlAttribute) + break unless _tmp + end + _tmp = true + unless _tmp + self.pos = _save + break + end + _tmp = match_string(">") + unless _tmp + self.pos = _save + end + break + end # end sequence + + set_failed_rule :_StyleOpen unless _tmp + return _tmp + end + + # StyleClose = "<" Spnl "/" ("style" | "STYLE") Spnl ">" + def _StyleClose + + _save = self.pos + while true # sequence + _tmp = match_string("<") + unless _tmp + self.pos = _save + break + end + _tmp = apply(:_Spnl) + unless _tmp + self.pos = _save + break + end + _tmp = match_string("/") + unless _tmp + self.pos = _save + break + end + + _save1 = self.pos + while true # choice + _tmp = match_string("style") + break if _tmp + self.pos = _save1 + _tmp = match_string("STYLE") + break if _tmp + self.pos = _save1 + break + end # end choice + + unless _tmp + self.pos = _save + break + end + _tmp = apply(:_Spnl) + unless _tmp + self.pos = _save + break + end + _tmp = match_string(">") + unless _tmp + self.pos = _save + end + break + end # end sequence + + set_failed_rule :_StyleClose unless _tmp + return _tmp + end + + # InStyleTags = StyleOpen (!StyleClose .)* StyleClose + def _InStyleTags + + _save = self.pos + while true # sequence + _tmp = apply(:_StyleOpen) + unless _tmp + self.pos = _save + break + end + while true + + _save2 = self.pos + while true # sequence + _save3 = self.pos + _tmp = apply(:_StyleClose) + _tmp = _tmp ? nil : true + self.pos = _save3 + unless _tmp + self.pos = _save2 + break + end + _tmp = get_byte + unless _tmp + self.pos = _save2 + end + break + end # end sequence + + break unless _tmp + end + _tmp = true + unless _tmp + self.pos = _save + break + end + _tmp = apply(:_StyleClose) + unless _tmp + self.pos = _save + end + break + end # end sequence + + set_failed_rule :_InStyleTags unless _tmp + return _tmp + end + + # StyleBlock = < InStyleTags > @BlankLine* { if css? then RDoc::Markup::Raw.new text end } + def _StyleBlock + + _save = self.pos + while true # sequence + _text_start = self.pos + _tmp = apply(:_InStyleTags) + if _tmp + text = get_text(_text_start) + end + unless _tmp + self.pos = _save + break + end + while true + _tmp = _BlankLine() + break unless _tmp + end + _tmp = true + unless _tmp + self.pos = _save + break + end + @result = begin; if css? then + RDoc::Markup::Raw.new text + end ; end + _tmp = true + unless _tmp + self.pos = _save + end + break + end # end sequence + + set_failed_rule :_StyleBlock unless _tmp + return _tmp + end + + # Inlines = (!@Endline Inline:i { i } | @Endline:c &Inline { c })+:chunks @Endline? { chunks } + def _Inlines + + _save = self.pos + while true # sequence + _save1 = self.pos + _ary = [] + + _save2 = self.pos + while true # choice + + _save3 = self.pos + while true # sequence + _save4 = self.pos + _tmp = _Endline() + _tmp = _tmp ? nil : true + self.pos = _save4 + unless _tmp + self.pos = _save3 + break + end + _tmp = apply(:_Inline) + i = @result + unless _tmp + self.pos = _save3 + break + end + @result = begin; i ; end + _tmp = true + unless _tmp + self.pos = _save3 + end + break + end # end sequence + + break if _tmp + self.pos = _save2 + + _save5 = self.pos + while true # sequence + _tmp = _Endline() + c = @result + unless _tmp + self.pos = _save5 + break + end + _save6 = self.pos + _tmp = apply(:_Inline) + self.pos = _save6 + unless _tmp + self.pos = _save5 + break + end + @result = begin; c ; end + _tmp = true + unless _tmp + self.pos = _save5 + end + break + end # end sequence + + break if _tmp + self.pos = _save2 + break + end # end choice + + if _tmp + _ary << @result + while true + + _save7 = self.pos + while true # choice + + _save8 = self.pos + while true # sequence + _save9 = self.pos + _tmp = _Endline() + _tmp = _tmp ? nil : true + self.pos = _save9 + unless _tmp + self.pos = _save8 + break + end + _tmp = apply(:_Inline) + i = @result + unless _tmp + self.pos = _save8 + break + end + @result = begin; i ; end + _tmp = true + unless _tmp + self.pos = _save8 + end + break + end # end sequence + + break if _tmp + self.pos = _save7 + + _save10 = self.pos + while true # sequence + _tmp = _Endline() + c = @result + unless _tmp + self.pos = _save10 + break + end + _save11 = self.pos + _tmp = apply(:_Inline) + self.pos = _save11 + unless _tmp + self.pos = _save10 + break + end + @result = begin; c ; end + _tmp = true + unless _tmp + self.pos = _save10 + end + break + end # end sequence + + break if _tmp + self.pos = _save7 + break + end # end choice + + _ary << @result if _tmp + break unless _tmp + end + _tmp = true + @result = _ary + else + self.pos = _save1 + end + chunks = @result + unless _tmp + self.pos = _save + break + end + _save12 = self.pos + _tmp = _Endline() + unless _tmp + _tmp = true + self.pos = _save12 + end + unless _tmp + self.pos = _save + break + end + @result = begin; chunks ; end + _tmp = true + unless _tmp + self.pos = _save + end + break + end # end sequence + + set_failed_rule :_Inlines unless _tmp + return _tmp + end + + # Inline = (Str | @Endline | UlOrStarLine | @Space | Strong | Emph | Strike | Image | Link | NoteReference | InlineNote | Code | RawHtml | Entity | EscapedChar | Symbol) + def _Inline + + _save = self.pos + while true # choice + _tmp = apply(:_Str) + break if _tmp + self.pos = _save + _tmp = _Endline() + break if _tmp + self.pos = _save + _tmp = apply(:_UlOrStarLine) + break if _tmp + self.pos = _save + _tmp = _Space() + break if _tmp + self.pos = _save + _tmp = apply(:_Strong) + break if _tmp + self.pos = _save + _tmp = apply(:_Emph) + break if _tmp + self.pos = _save + _tmp = apply(:_Strike) + break if _tmp + self.pos = _save + _tmp = apply(:_Image) + break if _tmp + self.pos = _save + _tmp = apply(:_Link) + break if _tmp + self.pos = _save + _tmp = apply(:_NoteReference) + break if _tmp + self.pos = _save + _tmp = apply(:_InlineNote) + break if _tmp + self.pos = _save + _tmp = apply(:_Code) + break if _tmp + self.pos = _save + _tmp = apply(:_RawHtml) + break if _tmp + self.pos = _save + _tmp = apply(:_Entity) + break if _tmp + self.pos = _save + _tmp = apply(:_EscapedChar) + break if _tmp + self.pos = _save + _tmp = apply(:_Symbol) + break if _tmp + self.pos = _save + break + end # end choice + + set_failed_rule :_Inline unless _tmp + return _tmp + end + + # Space = @Spacechar+ { " " } + def _Space + + _save = self.pos + while true # sequence + _save1 = self.pos + _tmp = _Spacechar() + if _tmp + while true + _tmp = _Spacechar() + break unless _tmp + end + _tmp = true + else + self.pos = _save1 + end + unless _tmp + self.pos = _save + break + end + @result = begin; " " ; end + _tmp = true + unless _tmp + self.pos = _save + end + break + end # end sequence + + set_failed_rule :_Space unless _tmp + return _tmp + end + + # Str = @StartList:a < @NormalChar+ > { a = text } (StrChunk:c { a << c })* { a } + def _Str + + _save = self.pos + while true # sequence + _tmp = _StartList() + a = @result + unless _tmp + self.pos = _save + break + end + _text_start = self.pos + _save1 = self.pos + _tmp = _NormalChar() + if _tmp + while true + _tmp = _NormalChar() + break unless _tmp + end + _tmp = true + else + self.pos = _save1 + end + if _tmp + text = get_text(_text_start) + end + unless _tmp + self.pos = _save + break + end + @result = begin; a = text ; end + _tmp = true + unless _tmp + self.pos = _save + break + end + while true + + _save3 = self.pos + while true # sequence + _tmp = apply(:_StrChunk) + c = @result + unless _tmp + self.pos = _save3 + break + end + @result = begin; a << c ; end + _tmp = true + unless _tmp + self.pos = _save3 + end + break + end # end sequence + + break unless _tmp + end + _tmp = true + unless _tmp + self.pos = _save + break + end + @result = begin; a ; end + _tmp = true + unless _tmp + self.pos = _save + end + break + end # end sequence + + set_failed_rule :_Str unless _tmp + return _tmp + end + + # StrChunk = < (@NormalChar | /_+/ &Alphanumeric)+ > { text } + def _StrChunk + + _save = self.pos + while true # sequence + _text_start = self.pos + _save1 = self.pos + + _save2 = self.pos + while true # choice + _tmp = _NormalChar() + break if _tmp + self.pos = _save2 + + _save3 = self.pos + while true # sequence + _tmp = scan(/\A(?-mix:_+)/) + unless _tmp + self.pos = _save3 + break + end + _save4 = self.pos + _tmp = apply(:_Alphanumeric) + self.pos = _save4 + unless _tmp + self.pos = _save3 + end + break + end # end sequence + + break if _tmp + self.pos = _save2 + break + end # end choice + + if _tmp + while true + + _save5 = self.pos + while true # choice + _tmp = _NormalChar() + break if _tmp + self.pos = _save5 + + _save6 = self.pos + while true # sequence + _tmp = scan(/\A(?-mix:_+)/) + unless _tmp + self.pos = _save6 + break + end + _save7 = self.pos + _tmp = apply(:_Alphanumeric) + self.pos = _save7 + unless _tmp + self.pos = _save6 + end + break + end # end sequence + + break if _tmp + self.pos = _save5 + break + end # end choice + + break unless _tmp + end + _tmp = true + else + self.pos = _save1 + end + if _tmp + text = get_text(_text_start) + end + unless _tmp + self.pos = _save + break + end + @result = begin; text ; end + _tmp = true + unless _tmp + self.pos = _save + end + break + end # end sequence + + set_failed_rule :_StrChunk unless _tmp + return _tmp + end + + # EscapedChar = "\\" !@Newline < /[:\\`|*_{}\[\]()#+.!><-]/ > { text } + def _EscapedChar + + _save = self.pos + while true # sequence + _tmp = match_string("\\") + unless _tmp + self.pos = _save + break + end + _save1 = self.pos + _tmp = _Newline() + _tmp = _tmp ? nil : true + self.pos = _save1 + unless _tmp + self.pos = _save + break + end + _text_start = self.pos + _tmp = scan(/\A(?-mix:[:\\`|*_{}\[\]()#+.!><-])/) + if _tmp + text = get_text(_text_start) + end + unless _tmp + self.pos = _save + break + end + @result = begin; text ; end + _tmp = true + unless _tmp + self.pos = _save + end + break + end # end sequence + + set_failed_rule :_EscapedChar unless _tmp + return _tmp + end + + # Entity = (HexEntity | DecEntity | CharEntity):a { a } + def _Entity + + _save = self.pos + while true # sequence + + _save1 = self.pos + while true # choice + _tmp = apply(:_HexEntity) + break if _tmp + self.pos = _save1 + _tmp = apply(:_DecEntity) + break if _tmp + self.pos = _save1 + _tmp = apply(:_CharEntity) + break if _tmp + self.pos = _save1 + break + end # end choice + + a = @result + unless _tmp + self.pos = _save + break + end + @result = begin; a ; end + _tmp = true + unless _tmp + self.pos = _save + end + break + end # end sequence + + set_failed_rule :_Entity unless _tmp + return _tmp + end + + # Endline = (@LineBreak | @TerminalEndline | @NormalEndline) + def _Endline + + _save = self.pos + while true # choice + _tmp = _LineBreak() + break if _tmp + self.pos = _save + _tmp = _TerminalEndline() + break if _tmp + self.pos = _save + _tmp = _NormalEndline() + break if _tmp + self.pos = _save + break + end # end choice + + set_failed_rule :_Endline unless _tmp + return _tmp + end + + # NormalEndline = @Sp @Newline !@BlankLine !">" !AtxStart !(Line /={1,}|-{1,}/ @Newline) { "\n" } + def _NormalEndline + + _save = self.pos + while true # sequence + _tmp = _Sp() + unless _tmp + self.pos = _save + break + end + _tmp = _Newline() + unless _tmp + self.pos = _save + break + end + _save1 = self.pos + _tmp = _BlankLine() + _tmp = _tmp ? nil : true + self.pos = _save1 + unless _tmp + self.pos = _save + break + end + _save2 = self.pos + _tmp = match_string(">") + _tmp = _tmp ? nil : true + self.pos = _save2 + unless _tmp + self.pos = _save + break + end + _save3 = self.pos + _tmp = apply(:_AtxStart) + _tmp = _tmp ? nil : true + self.pos = _save3 + unless _tmp + self.pos = _save + break + end + _save4 = self.pos + + _save5 = self.pos + while true # sequence + _tmp = apply(:_Line) + unless _tmp + self.pos = _save5 + break + end + _tmp = scan(/\A(?-mix:={1,}|-{1,})/) + unless _tmp + self.pos = _save5 + break + end + _tmp = _Newline() + unless _tmp + self.pos = _save5 + end + break + end # end sequence + + _tmp = _tmp ? nil : true + self.pos = _save4 + unless _tmp + self.pos = _save + break + end + @result = begin; "\n" ; end + _tmp = true + unless _tmp + self.pos = _save + end + break + end # end sequence + + set_failed_rule :_NormalEndline unless _tmp + return _tmp + end + + # TerminalEndline = @Sp @Newline @Eof + def _TerminalEndline + + _save = self.pos + while true # sequence + _tmp = _Sp() + unless _tmp + self.pos = _save + break + end + _tmp = _Newline() + unless _tmp + self.pos = _save + break + end + _tmp = _Eof() + unless _tmp + self.pos = _save + end + break + end # end sequence + + set_failed_rule :_TerminalEndline unless _tmp + return _tmp + end + + # LineBreak = " " @NormalEndline { RDoc::Markup::HardBreak.new } + def _LineBreak + + _save = self.pos + while true # sequence + _tmp = match_string(" ") + unless _tmp + self.pos = _save + break + end + _tmp = _NormalEndline() + unless _tmp + self.pos = _save + break + end + @result = begin; RDoc::Markup::HardBreak.new ; end + _tmp = true + unless _tmp + self.pos = _save + end + break + end # end sequence + + set_failed_rule :_LineBreak unless _tmp + return _tmp + end + + # Symbol = < @SpecialChar > { text } + def _Symbol + + _save = self.pos + while true # sequence + _text_start = self.pos + _tmp = _SpecialChar() + if _tmp + text = get_text(_text_start) + end + unless _tmp + self.pos = _save + break + end + @result = begin; text ; end + _tmp = true + unless _tmp + self.pos = _save + end + break + end # end sequence + + set_failed_rule :_Symbol unless _tmp + return _tmp + end + + # UlOrStarLine = (UlLine | StarLine):a { a } + def _UlOrStarLine + + _save = self.pos + while true # sequence + + _save1 = self.pos + while true # choice + _tmp = apply(:_UlLine) + break if _tmp + self.pos = _save1 + _tmp = apply(:_StarLine) + break if _tmp + self.pos = _save1 + break + end # end choice + + a = @result + unless _tmp + self.pos = _save + break + end + @result = begin; a ; end + _tmp = true + unless _tmp + self.pos = _save + end + break + end # end sequence + + set_failed_rule :_UlOrStarLine unless _tmp + return _tmp + end + + # StarLine = (< /\*{4,}/ > { text } | < @Spacechar /\*+/ &@Spacechar > { text }) + def _StarLine + + _save = self.pos + while true # choice + + _save1 = self.pos + while true # sequence + _text_start = self.pos + _tmp = scan(/\A(?-mix:\*{4,})/) + if _tmp + text = get_text(_text_start) + end + unless _tmp + self.pos = _save1 + break + end + @result = begin; text ; end + _tmp = true + unless _tmp + self.pos = _save1 + end + break + end # end sequence + + break if _tmp + self.pos = _save + + _save2 = self.pos + while true # sequence + _text_start = self.pos + + _save3 = self.pos + while true # sequence + _tmp = _Spacechar() + unless _tmp + self.pos = _save3 + break + end + _tmp = scan(/\A(?-mix:\*+)/) + unless _tmp + self.pos = _save3 + break + end + _save4 = self.pos + _tmp = _Spacechar() + self.pos = _save4 + unless _tmp + self.pos = _save3 + end + break + end # end sequence + + if _tmp + text = get_text(_text_start) + end + unless _tmp + self.pos = _save2 + break + end + @result = begin; text ; end + _tmp = true + unless _tmp + self.pos = _save2 + end + break + end # end sequence + + break if _tmp + self.pos = _save + break + end # end choice + + set_failed_rule :_StarLine unless _tmp + return _tmp + end + + # UlLine = (< /_{4,}/ > { text } | < @Spacechar /_+/ &@Spacechar > { text }) + def _UlLine + + _save = self.pos + while true # choice + + _save1 = self.pos + while true # sequence + _text_start = self.pos + _tmp = scan(/\A(?-mix:_{4,})/) + if _tmp + text = get_text(_text_start) + end + unless _tmp + self.pos = _save1 + break + end + @result = begin; text ; end + _tmp = true + unless _tmp + self.pos = _save1 + end + break + end # end sequence + + break if _tmp + self.pos = _save + + _save2 = self.pos + while true # sequence + _text_start = self.pos + + _save3 = self.pos + while true # sequence + _tmp = _Spacechar() + unless _tmp + self.pos = _save3 + break + end + _tmp = scan(/\A(?-mix:_+)/) + unless _tmp + self.pos = _save3 + break + end + _save4 = self.pos + _tmp = _Spacechar() + self.pos = _save4 + unless _tmp + self.pos = _save3 + end + break + end # end sequence + + if _tmp + text = get_text(_text_start) + end + unless _tmp + self.pos = _save2 + break + end + @result = begin; text ; end + _tmp = true + unless _tmp + self.pos = _save2 + end + break + end # end sequence + + break if _tmp + self.pos = _save + break + end # end choice + + set_failed_rule :_UlLine unless _tmp + return _tmp + end + + # Emph = (EmphStar | EmphUl) + def _Emph + + _save = self.pos + while true # choice + _tmp = apply(:_EmphStar) + break if _tmp + self.pos = _save + _tmp = apply(:_EmphUl) + break if _tmp + self.pos = _save + break + end # end choice + + set_failed_rule :_Emph unless _tmp + return _tmp + end + + # Whitespace = (@Spacechar | @Newline) + def _Whitespace + + _save = self.pos + while true # choice + _tmp = _Spacechar() + break if _tmp + self.pos = _save + _tmp = _Newline() + break if _tmp + self.pos = _save + break + end # end choice + + set_failed_rule :_Whitespace unless _tmp + return _tmp + end + + # EmphStar = "*" !@Whitespace @StartList:a (!"*" Inline:b { a << b } | StrongStar:b { a << b })+ "*" { emphasis a.join } + def _EmphStar + + _save = self.pos + while true # sequence + _tmp = match_string("*") + unless _tmp + self.pos = _save + break + end + _save1 = self.pos + _tmp = _Whitespace() + _tmp = _tmp ? nil : true + self.pos = _save1 + unless _tmp + self.pos = _save + break + end + _tmp = _StartList() + a = @result + unless _tmp + self.pos = _save + break + end + _save2 = self.pos + + _save3 = self.pos + while true # choice + + _save4 = self.pos + while true # sequence + _save5 = self.pos + _tmp = match_string("*") + _tmp = _tmp ? nil : true + self.pos = _save5 + unless _tmp + self.pos = _save4 + break + end + _tmp = apply(:_Inline) + b = @result + unless _tmp + self.pos = _save4 + break + end + @result = begin; a << b ; end + _tmp = true + unless _tmp + self.pos = _save4 + end + break + end # end sequence + + break if _tmp + self.pos = _save3 + + _save6 = self.pos + while true # sequence + _tmp = apply(:_StrongStar) + b = @result + unless _tmp + self.pos = _save6 + break + end + @result = begin; a << b ; end + _tmp = true + unless _tmp + self.pos = _save6 + end + break + end # end sequence + + break if _tmp + self.pos = _save3 + break + end # end choice + + if _tmp + while true + + _save7 = self.pos + while true # choice + + _save8 = self.pos + while true # sequence + _save9 = self.pos + _tmp = match_string("*") + _tmp = _tmp ? nil : true + self.pos = _save9 + unless _tmp + self.pos = _save8 + break + end + _tmp = apply(:_Inline) + b = @result + unless _tmp + self.pos = _save8 + break + end + @result = begin; a << b ; end + _tmp = true + unless _tmp + self.pos = _save8 + end + break + end # end sequence + + break if _tmp + self.pos = _save7 + + _save10 = self.pos + while true # sequence + _tmp = apply(:_StrongStar) + b = @result + unless _tmp + self.pos = _save10 + break + end + @result = begin; a << b ; end + _tmp = true + unless _tmp + self.pos = _save10 + end + break + end # end sequence + + break if _tmp + self.pos = _save7 + break + end # end choice + + break unless _tmp + end + _tmp = true + else + self.pos = _save2 + end + unless _tmp + self.pos = _save + break + end + _tmp = match_string("*") + unless _tmp + self.pos = _save + break + end + @result = begin; emphasis a.join ; end + _tmp = true + unless _tmp + self.pos = _save + end + break + end # end sequence + + set_failed_rule :_EmphStar unless _tmp + return _tmp + end + + # EmphUl = "_" !@Whitespace @StartList:a (!"_" Inline:b { a << b } | StrongUl:b { a << b })+ "_" { emphasis a.join } + def _EmphUl + + _save = self.pos + while true # sequence + _tmp = match_string("_") + unless _tmp + self.pos = _save + break + end + _save1 = self.pos + _tmp = _Whitespace() + _tmp = _tmp ? nil : true + self.pos = _save1 + unless _tmp + self.pos = _save + break + end + _tmp = _StartList() + a = @result + unless _tmp + self.pos = _save + break + end + _save2 = self.pos + + _save3 = self.pos + while true # choice + + _save4 = self.pos + while true # sequence + _save5 = self.pos + _tmp = match_string("_") + _tmp = _tmp ? nil : true + self.pos = _save5 + unless _tmp + self.pos = _save4 + break + end + _tmp = apply(:_Inline) + b = @result + unless _tmp + self.pos = _save4 + break + end + @result = begin; a << b ; end + _tmp = true + unless _tmp + self.pos = _save4 + end + break + end # end sequence + + break if _tmp + self.pos = _save3 + + _save6 = self.pos + while true # sequence + _tmp = apply(:_StrongUl) + b = @result + unless _tmp + self.pos = _save6 + break + end + @result = begin; a << b ; end + _tmp = true + unless _tmp + self.pos = _save6 + end + break + end # end sequence + + break if _tmp + self.pos = _save3 + break + end # end choice + + if _tmp + while true + + _save7 = self.pos + while true # choice + + _save8 = self.pos + while true # sequence + _save9 = self.pos + _tmp = match_string("_") + _tmp = _tmp ? nil : true + self.pos = _save9 + unless _tmp + self.pos = _save8 + break + end + _tmp = apply(:_Inline) + b = @result + unless _tmp + self.pos = _save8 + break + end + @result = begin; a << b ; end + _tmp = true + unless _tmp + self.pos = _save8 + end + break + end # end sequence + + break if _tmp + self.pos = _save7 + + _save10 = self.pos + while true # sequence + _tmp = apply(:_StrongUl) + b = @result + unless _tmp + self.pos = _save10 + break + end + @result = begin; a << b ; end + _tmp = true + unless _tmp + self.pos = _save10 + end + break + end # end sequence + + break if _tmp + self.pos = _save7 + break + end # end choice + + break unless _tmp + end + _tmp = true + else + self.pos = _save2 + end + unless _tmp + self.pos = _save + break + end + _tmp = match_string("_") + unless _tmp + self.pos = _save + break + end + @result = begin; emphasis a.join ; end + _tmp = true + unless _tmp + self.pos = _save + end + break + end # end sequence + + set_failed_rule :_EmphUl unless _tmp + return _tmp + end + + # Strong = (StrongStar | StrongUl) + def _Strong + + _save = self.pos + while true # choice + _tmp = apply(:_StrongStar) + break if _tmp + self.pos = _save + _tmp = apply(:_StrongUl) + break if _tmp + self.pos = _save + break + end # end choice + + set_failed_rule :_Strong unless _tmp + return _tmp + end + + # StrongStar = "**" !@Whitespace @StartList:a (!"**" Inline:b { a << b })+ "**" { strong a.join } + def _StrongStar + + _save = self.pos + while true # sequence + _tmp = match_string("**") + unless _tmp + self.pos = _save + break + end + _save1 = self.pos + _tmp = _Whitespace() + _tmp = _tmp ? nil : true + self.pos = _save1 + unless _tmp + self.pos = _save + break + end + _tmp = _StartList() + a = @result + unless _tmp + self.pos = _save + break + end + _save2 = self.pos + + _save3 = self.pos + while true # sequence + _save4 = self.pos + _tmp = match_string("**") + _tmp = _tmp ? nil : true + self.pos = _save4 + unless _tmp + self.pos = _save3 + break + end + _tmp = apply(:_Inline) + b = @result + unless _tmp + self.pos = _save3 + break + end + @result = begin; a << b ; end + _tmp = true + unless _tmp + self.pos = _save3 + end + break + end # end sequence + + if _tmp + while true + + _save5 = self.pos + while true # sequence + _save6 = self.pos + _tmp = match_string("**") + _tmp = _tmp ? nil : true + self.pos = _save6 + unless _tmp + self.pos = _save5 + break + end + _tmp = apply(:_Inline) + b = @result + unless _tmp + self.pos = _save5 + break + end + @result = begin; a << b ; end + _tmp = true + unless _tmp + self.pos = _save5 + end + break + end # end sequence + + break unless _tmp + end + _tmp = true + else + self.pos = _save2 + end + unless _tmp + self.pos = _save + break + end + _tmp = match_string("**") + unless _tmp + self.pos = _save + break + end + @result = begin; strong a.join ; end + _tmp = true + unless _tmp + self.pos = _save + end + break + end # end sequence + + set_failed_rule :_StrongStar unless _tmp + return _tmp + end + + # StrongUl = "__" !@Whitespace @StartList:a (!"__" Inline:b { a << b })+ "__" { strong a.join } + def _StrongUl + + _save = self.pos + while true # sequence + _tmp = match_string("__") + unless _tmp + self.pos = _save + break + end + _save1 = self.pos + _tmp = _Whitespace() + _tmp = _tmp ? nil : true + self.pos = _save1 + unless _tmp + self.pos = _save + break + end + _tmp = _StartList() + a = @result + unless _tmp + self.pos = _save + break + end + _save2 = self.pos + + _save3 = self.pos + while true # sequence + _save4 = self.pos + _tmp = match_string("__") + _tmp = _tmp ? nil : true + self.pos = _save4 + unless _tmp + self.pos = _save3 + break + end + _tmp = apply(:_Inline) + b = @result + unless _tmp + self.pos = _save3 + break + end + @result = begin; a << b ; end + _tmp = true + unless _tmp + self.pos = _save3 + end + break + end # end sequence + + if _tmp + while true + + _save5 = self.pos + while true # sequence + _save6 = self.pos + _tmp = match_string("__") + _tmp = _tmp ? nil : true + self.pos = _save6 + unless _tmp + self.pos = _save5 + break + end + _tmp = apply(:_Inline) + b = @result + unless _tmp + self.pos = _save5 + break + end + @result = begin; a << b ; end + _tmp = true + unless _tmp + self.pos = _save5 + end + break + end # end sequence + + break unless _tmp + end + _tmp = true + else + self.pos = _save2 + end + unless _tmp + self.pos = _save + break + end + _tmp = match_string("__") + unless _tmp + self.pos = _save + break + end + @result = begin; strong a.join ; end + _tmp = true + unless _tmp + self.pos = _save + end + break + end # end sequence + + set_failed_rule :_StrongUl unless _tmp + return _tmp + end + + # Strike = &{ strike? } "~~" !@Whitespace @StartList:a (!"~~" Inline:b { a << b })+ "~~" { strike a.join } + def _Strike + + _save = self.pos + while true # sequence + _save1 = self.pos + _tmp = begin; strike? ; end + self.pos = _save1 + unless _tmp + self.pos = _save + break + end + _tmp = match_string("~~") + unless _tmp + self.pos = _save + break + end + _save2 = self.pos + _tmp = _Whitespace() + _tmp = _tmp ? nil : true + self.pos = _save2 + unless _tmp + self.pos = _save + break + end + _tmp = _StartList() + a = @result + unless _tmp + self.pos = _save + break + end + _save3 = self.pos + + _save4 = self.pos + while true # sequence + _save5 = self.pos + _tmp = match_string("~~") + _tmp = _tmp ? nil : true + self.pos = _save5 + unless _tmp + self.pos = _save4 + break + end + _tmp = apply(:_Inline) + b = @result + unless _tmp + self.pos = _save4 + break + end + @result = begin; a << b ; end + _tmp = true + unless _tmp + self.pos = _save4 + end + break + end # end sequence + + if _tmp + while true + + _save6 = self.pos + while true # sequence + _save7 = self.pos + _tmp = match_string("~~") + _tmp = _tmp ? nil : true + self.pos = _save7 + unless _tmp + self.pos = _save6 + break + end + _tmp = apply(:_Inline) + b = @result + unless _tmp + self.pos = _save6 + break + end + @result = begin; a << b ; end + _tmp = true + unless _tmp + self.pos = _save6 + end + break + end # end sequence + + break unless _tmp + end + _tmp = true + else + self.pos = _save3 + end + unless _tmp + self.pos = _save + break + end + _tmp = match_string("~~") + unless _tmp + self.pos = _save + break + end + @result = begin; strike a.join ; end + _tmp = true + unless _tmp + self.pos = _save + end + break + end # end sequence + + set_failed_rule :_Strike unless _tmp + return _tmp + end + + # Image = "!" (ExplicitLink | ReferenceLink):a { "rdoc-image:#{a[/\[(.*)\]/, 1]}" } + def _Image + + _save = self.pos + while true # sequence + _tmp = match_string("!") + unless _tmp + self.pos = _save + break + end + + _save1 = self.pos + while true # choice + _tmp = apply(:_ExplicitLink) + break if _tmp + self.pos = _save1 + _tmp = apply(:_ReferenceLink) + break if _tmp + self.pos = _save1 + break + end # end choice + + a = @result + unless _tmp + self.pos = _save + break + end + @result = begin; "rdoc-image:#{a[/\[(.*)\]/, 1]}" ; end + _tmp = true + unless _tmp + self.pos = _save + end + break + end # end sequence + + set_failed_rule :_Image unless _tmp + return _tmp + end + + # Link = (ExplicitLink | ReferenceLink | AutoLink) + def _Link + + _save = self.pos + while true # choice + _tmp = apply(:_ExplicitLink) + break if _tmp + self.pos = _save + _tmp = apply(:_ReferenceLink) + break if _tmp + self.pos = _save + _tmp = apply(:_AutoLink) + break if _tmp + self.pos = _save + break + end # end choice + + set_failed_rule :_Link unless _tmp + return _tmp + end + + # ReferenceLink = (ReferenceLinkDouble | ReferenceLinkSingle) + def _ReferenceLink + + _save = self.pos + while true # choice + _tmp = apply(:_ReferenceLinkDouble) + break if _tmp + self.pos = _save + _tmp = apply(:_ReferenceLinkSingle) + break if _tmp + self.pos = _save + break + end # end choice + + set_failed_rule :_ReferenceLink unless _tmp + return _tmp + end + + # ReferenceLinkDouble = Label:content < Spnl > !"[]" Label:label { link_to content, label, text } + def _ReferenceLinkDouble + + _save = self.pos + while true # sequence + _tmp = apply(:_Label) + content = @result + unless _tmp + self.pos = _save + break + end + _text_start = self.pos + _tmp = apply(:_Spnl) + if _tmp + text = get_text(_text_start) + end + unless _tmp + self.pos = _save + break + end + _save1 = self.pos + _tmp = match_string("[]") + _tmp = _tmp ? nil : true + self.pos = _save1 + unless _tmp + self.pos = _save + break + end + _tmp = apply(:_Label) + label = @result + unless _tmp + self.pos = _save + break + end + @result = begin; link_to content, label, text ; end + _tmp = true + unless _tmp + self.pos = _save + end + break + end # end sequence + + set_failed_rule :_ReferenceLinkDouble unless _tmp + return _tmp + end + + # ReferenceLinkSingle = Label:content < (Spnl "[]")? > { link_to content, content, text } + def _ReferenceLinkSingle + + _save = self.pos + while true # sequence + _tmp = apply(:_Label) + content = @result + unless _tmp + self.pos = _save + break + end + _text_start = self.pos + _save1 = self.pos + + _save2 = self.pos + while true # sequence + _tmp = apply(:_Spnl) + unless _tmp + self.pos = _save2 + break + end + _tmp = match_string("[]") + unless _tmp + self.pos = _save2 + end + break + end # end sequence + + unless _tmp + _tmp = true + self.pos = _save1 + end + if _tmp + text = get_text(_text_start) + end + unless _tmp + self.pos = _save + break + end + @result = begin; link_to content, content, text ; end + _tmp = true + unless _tmp + self.pos = _save + end + break + end # end sequence + + set_failed_rule :_ReferenceLinkSingle unless _tmp + return _tmp + end + + # ExplicitLink = Label:l "(" @Sp Source:s Spnl Title @Sp ")" { "{#{l}}[#{s}]" } + def _ExplicitLink + + _save = self.pos + while true # sequence + _tmp = apply(:_Label) + l = @result + unless _tmp + self.pos = _save + break + end + _tmp = match_string("(") + unless _tmp + self.pos = _save + break + end + _tmp = _Sp() + unless _tmp + self.pos = _save + break + end + _tmp = apply(:_Source) + s = @result + unless _tmp + self.pos = _save + break + end + _tmp = apply(:_Spnl) + unless _tmp + self.pos = _save + break + end + _tmp = apply(:_Title) + unless _tmp + self.pos = _save + break + end + _tmp = _Sp() + unless _tmp + self.pos = _save + break + end + _tmp = match_string(")") + unless _tmp + self.pos = _save + break + end + @result = begin; "{#{l}}[#{s}]" ; end + _tmp = true + unless _tmp + self.pos = _save + end + break + end # end sequence + + set_failed_rule :_ExplicitLink unless _tmp + return _tmp + end + + # Source = ("<" < SourceContents > ">" | < SourceContents >) { text } + def _Source + + _save = self.pos + while true # sequence + + _save1 = self.pos + while true # choice + + _save2 = self.pos + while true # sequence + _tmp = match_string("<") + unless _tmp + self.pos = _save2 + break + end + _text_start = self.pos + _tmp = apply(:_SourceContents) + if _tmp + text = get_text(_text_start) + end + unless _tmp + self.pos = _save2 + break + end + _tmp = match_string(">") + unless _tmp + self.pos = _save2 + end + break + end # end sequence + + break if _tmp + self.pos = _save1 + _text_start = self.pos + _tmp = apply(:_SourceContents) + if _tmp + text = get_text(_text_start) + end + break if _tmp + self.pos = _save1 + break + end # end choice + + unless _tmp + self.pos = _save + break + end + @result = begin; text ; end + _tmp = true + unless _tmp + self.pos = _save + end + break + end # end sequence + + set_failed_rule :_Source unless _tmp + return _tmp + end + + # SourceContents = ((!"(" !")" !">" Nonspacechar)+ | "(" SourceContents ")")* + def _SourceContents + while true + + _save1 = self.pos + while true # choice + _save2 = self.pos + + _save3 = self.pos + while true # sequence + _save4 = self.pos + _tmp = match_string("(") + _tmp = _tmp ? nil : true + self.pos = _save4 + unless _tmp + self.pos = _save3 + break + end + _save5 = self.pos + _tmp = match_string(")") + _tmp = _tmp ? nil : true + self.pos = _save5 + unless _tmp + self.pos = _save3 + break + end + _save6 = self.pos + _tmp = match_string(">") + _tmp = _tmp ? nil : true + self.pos = _save6 + unless _tmp + self.pos = _save3 + break + end + _tmp = apply(:_Nonspacechar) + unless _tmp + self.pos = _save3 + end + break + end # end sequence + + if _tmp + while true + + _save7 = self.pos + while true # sequence + _save8 = self.pos + _tmp = match_string("(") + _tmp = _tmp ? nil : true + self.pos = _save8 + unless _tmp + self.pos = _save7 + break + end + _save9 = self.pos + _tmp = match_string(")") + _tmp = _tmp ? nil : true + self.pos = _save9 + unless _tmp + self.pos = _save7 + break + end + _save10 = self.pos + _tmp = match_string(">") + _tmp = _tmp ? nil : true + self.pos = _save10 + unless _tmp + self.pos = _save7 + break + end + _tmp = apply(:_Nonspacechar) + unless _tmp + self.pos = _save7 + end + break + end # end sequence + + break unless _tmp + end + _tmp = true + else + self.pos = _save2 + end + break if _tmp + self.pos = _save1 + + _save11 = self.pos + while true # sequence + _tmp = match_string("(") + unless _tmp + self.pos = _save11 + break + end + _tmp = apply(:_SourceContents) + unless _tmp + self.pos = _save11 + break + end + _tmp = match_string(")") + unless _tmp + self.pos = _save11 + end + break + end # end sequence + + break if _tmp + self.pos = _save1 + break + end # end choice + + break unless _tmp + end + _tmp = true + set_failed_rule :_SourceContents unless _tmp + return _tmp + end + + # Title = (TitleSingle | TitleDouble | ""):a { a } + def _Title + + _save = self.pos + while true # sequence + + _save1 = self.pos + while true # choice + _tmp = apply(:_TitleSingle) + break if _tmp + self.pos = _save1 + _tmp = apply(:_TitleDouble) + break if _tmp + self.pos = _save1 + _tmp = match_string("") + break if _tmp + self.pos = _save1 + break + end # end choice + + a = @result + unless _tmp + self.pos = _save + break + end + @result = begin; a ; end + _tmp = true + unless _tmp + self.pos = _save + end + break + end # end sequence + + set_failed_rule :_Title unless _tmp + return _tmp + end + + # TitleSingle = "'" (!("'" @Sp (")" | @Newline)) .)* "'" + def _TitleSingle + + _save = self.pos + while true # sequence + _tmp = match_string("'") + unless _tmp + self.pos = _save + break + end + while true + + _save2 = self.pos + while true # sequence + _save3 = self.pos + + _save4 = self.pos + while true # sequence + _tmp = match_string("'") + unless _tmp + self.pos = _save4 + break + end + _tmp = _Sp() + unless _tmp + self.pos = _save4 + break + end + + _save5 = self.pos + while true # choice + _tmp = match_string(")") + break if _tmp + self.pos = _save5 + _tmp = _Newline() + break if _tmp + self.pos = _save5 + break + end # end choice + + unless _tmp + self.pos = _save4 + end + break + end # end sequence + + _tmp = _tmp ? nil : true + self.pos = _save3 + unless _tmp + self.pos = _save2 + break + end + _tmp = get_byte + unless _tmp + self.pos = _save2 + end + break + end # end sequence + + break unless _tmp + end + _tmp = true + unless _tmp + self.pos = _save + break + end + _tmp = match_string("'") + unless _tmp + self.pos = _save + end + break + end # end sequence + + set_failed_rule :_TitleSingle unless _tmp + return _tmp + end + + # TitleDouble = "\"" (!("\"" @Sp (")" | @Newline)) .)* "\"" + def _TitleDouble + + _save = self.pos + while true # sequence + _tmp = match_string("\"") + unless _tmp + self.pos = _save + break + end + while true + + _save2 = self.pos + while true # sequence + _save3 = self.pos + + _save4 = self.pos + while true # sequence + _tmp = match_string("\"") + unless _tmp + self.pos = _save4 + break + end + _tmp = _Sp() + unless _tmp + self.pos = _save4 + break + end + + _save5 = self.pos + while true # choice + _tmp = match_string(")") + break if _tmp + self.pos = _save5 + _tmp = _Newline() + break if _tmp + self.pos = _save5 + break + end # end choice + + unless _tmp + self.pos = _save4 + end + break + end # end sequence + + _tmp = _tmp ? nil : true + self.pos = _save3 + unless _tmp + self.pos = _save2 + break + end + _tmp = get_byte + unless _tmp + self.pos = _save2 + end + break + end # end sequence + + break unless _tmp + end + _tmp = true + unless _tmp + self.pos = _save + break + end + _tmp = match_string("\"") + unless _tmp + self.pos = _save + end + break + end # end sequence + + set_failed_rule :_TitleDouble unless _tmp + return _tmp + end + + # AutoLink = (AutoLinkUrl | AutoLinkEmail) + def _AutoLink + + _save = self.pos + while true # choice + _tmp = apply(:_AutoLinkUrl) + break if _tmp + self.pos = _save + _tmp = apply(:_AutoLinkEmail) + break if _tmp + self.pos = _save + break + end # end choice + + set_failed_rule :_AutoLink unless _tmp + return _tmp + end + + # AutoLinkUrl = "<" < /[A-Za-z]+/ "://" (!@Newline !">" .)+ > ">" { text } + def _AutoLinkUrl + + _save = self.pos + while true # sequence + _tmp = match_string("<") + unless _tmp + self.pos = _save + break + end + _text_start = self.pos + + _save1 = self.pos + while true # sequence + _tmp = scan(/\A(?-mix:[A-Za-z]+)/) + unless _tmp + self.pos = _save1 + break + end + _tmp = match_string("://") + unless _tmp + self.pos = _save1 + break + end + _save2 = self.pos + + _save3 = self.pos + while true # sequence + _save4 = self.pos + _tmp = _Newline() + _tmp = _tmp ? nil : true + self.pos = _save4 + unless _tmp + self.pos = _save3 + break + end + _save5 = self.pos + _tmp = match_string(">") + _tmp = _tmp ? nil : true + self.pos = _save5 + unless _tmp + self.pos = _save3 + break + end + _tmp = get_byte + unless _tmp + self.pos = _save3 + end + break + end # end sequence + + if _tmp + while true + + _save6 = self.pos + while true # sequence + _save7 = self.pos + _tmp = _Newline() + _tmp = _tmp ? nil : true + self.pos = _save7 + unless _tmp + self.pos = _save6 + break + end + _save8 = self.pos + _tmp = match_string(">") + _tmp = _tmp ? nil : true + self.pos = _save8 + unless _tmp + self.pos = _save6 + break + end + _tmp = get_byte + unless _tmp + self.pos = _save6 + end + break + end # end sequence + + break unless _tmp + end + _tmp = true + else + self.pos = _save2 + end + unless _tmp + self.pos = _save1 + end + break + end # end sequence + + if _tmp + text = get_text(_text_start) + end + unless _tmp + self.pos = _save + break + end + _tmp = match_string(">") + unless _tmp + self.pos = _save + break + end + @result = begin; text ; end + _tmp = true + unless _tmp + self.pos = _save + end + break + end # end sequence + + set_failed_rule :_AutoLinkUrl unless _tmp + return _tmp + end + + # AutoLinkEmail = "<" "mailto:"? < /[\w+.\/!%~$-]+/i "@" (!@Newline !">" .)+ > ">" { "mailto:#{text}" } + def _AutoLinkEmail + + _save = self.pos + while true # sequence + _tmp = match_string("<") + unless _tmp + self.pos = _save + break + end + _save1 = self.pos + _tmp = match_string("mailto:") + unless _tmp + _tmp = true + self.pos = _save1 + end + unless _tmp + self.pos = _save + break + end + _text_start = self.pos + + _save2 = self.pos + while true # sequence + _tmp = scan(/\A(?i-mx:[\w+.\/!%~$-]+)/) + unless _tmp + self.pos = _save2 + break + end + _tmp = match_string("@") + unless _tmp + self.pos = _save2 + break + end + _save3 = self.pos + + _save4 = self.pos + while true # sequence + _save5 = self.pos + _tmp = _Newline() + _tmp = _tmp ? nil : true + self.pos = _save5 + unless _tmp + self.pos = _save4 + break + end + _save6 = self.pos + _tmp = match_string(">") + _tmp = _tmp ? nil : true + self.pos = _save6 + unless _tmp + self.pos = _save4 + break + end + _tmp = get_byte + unless _tmp + self.pos = _save4 + end + break + end # end sequence + + if _tmp + while true + + _save7 = self.pos + while true # sequence + _save8 = self.pos + _tmp = _Newline() + _tmp = _tmp ? nil : true + self.pos = _save8 + unless _tmp + self.pos = _save7 + break + end + _save9 = self.pos + _tmp = match_string(">") + _tmp = _tmp ? nil : true + self.pos = _save9 + unless _tmp + self.pos = _save7 + break + end + _tmp = get_byte + unless _tmp + self.pos = _save7 + end + break + end # end sequence + + break unless _tmp + end + _tmp = true + else + self.pos = _save3 + end + unless _tmp + self.pos = _save2 + end + break + end # end sequence + + if _tmp + text = get_text(_text_start) + end + unless _tmp + self.pos = _save + break + end + _tmp = match_string(">") + unless _tmp + self.pos = _save + break + end + @result = begin; "mailto:#{text}" ; end + _tmp = true + unless _tmp + self.pos = _save + end + break + end # end sequence + + set_failed_rule :_AutoLinkEmail unless _tmp + return _tmp + end + + # Reference = @NonindentSpace !"[]" Label:label ":" Spnl RefSrc:link RefTitle @BlankLine+ { # TODO use title reference label, link nil } + def _Reference + + _save = self.pos + while true # sequence + _tmp = _NonindentSpace() + unless _tmp + self.pos = _save + break + end + _save1 = self.pos + _tmp = match_string("[]") + _tmp = _tmp ? nil : true + self.pos = _save1 + unless _tmp + self.pos = _save + break + end + _tmp = apply(:_Label) + label = @result + unless _tmp + self.pos = _save + break + end + _tmp = match_string(":") + unless _tmp + self.pos = _save + break + end + _tmp = apply(:_Spnl) + unless _tmp + self.pos = _save + break + end + _tmp = apply(:_RefSrc) + link = @result + unless _tmp + self.pos = _save + break + end + _tmp = apply(:_RefTitle) + unless _tmp + self.pos = _save + break + end + _save2 = self.pos + _tmp = _BlankLine() + if _tmp + while true + _tmp = _BlankLine() + break unless _tmp + end + _tmp = true + else + self.pos = _save2 + end + unless _tmp + self.pos = _save + break + end + @result = begin; # TODO use title + reference label, link + nil + ; end + _tmp = true + unless _tmp + self.pos = _save + end + break + end # end sequence + + set_failed_rule :_Reference unless _tmp + return _tmp + end + + # Label = "[" (!"^" &{ notes? } | &. &{ !notes? }) @StartList:a (!"]" Inline:l { a << l })* "]" { a.join.gsub(/\s+/, ' ') } + def _Label + + _save = self.pos + while true # sequence + _tmp = match_string("[") + unless _tmp + self.pos = _save + break + end + + _save1 = self.pos + while true # choice + + _save2 = self.pos + while true # sequence + _save3 = self.pos + _tmp = match_string("^") + _tmp = _tmp ? nil : true + self.pos = _save3 + unless _tmp + self.pos = _save2 + break + end + _save4 = self.pos + _tmp = begin; notes? ; end + self.pos = _save4 + unless _tmp + self.pos = _save2 + end + break + end # end sequence + + break if _tmp + self.pos = _save1 + + _save5 = self.pos + while true # sequence + _save6 = self.pos + _tmp = get_byte + self.pos = _save6 + unless _tmp + self.pos = _save5 + break + end + _save7 = self.pos + _tmp = begin; !notes? ; end + self.pos = _save7 + unless _tmp + self.pos = _save5 + end + break + end # end sequence + + break if _tmp + self.pos = _save1 + break + end # end choice + + unless _tmp + self.pos = _save + break + end + _tmp = _StartList() + a = @result + unless _tmp + self.pos = _save + break + end + while true + + _save9 = self.pos + while true # sequence + _save10 = self.pos + _tmp = match_string("]") + _tmp = _tmp ? nil : true + self.pos = _save10 + unless _tmp + self.pos = _save9 + break + end + _tmp = apply(:_Inline) + l = @result + unless _tmp + self.pos = _save9 + break + end + @result = begin; a << l ; end + _tmp = true + unless _tmp + self.pos = _save9 + end + break + end # end sequence + + break unless _tmp + end + _tmp = true + unless _tmp + self.pos = _save + break + end + _tmp = match_string("]") + unless _tmp + self.pos = _save + break + end + @result = begin; a.join.gsub(/\s+/, ' ') ; end + _tmp = true + unless _tmp + self.pos = _save + end + break + end # end sequence + + set_failed_rule :_Label unless _tmp + return _tmp + end + + # RefSrc = < Nonspacechar+ > { text } + def _RefSrc + + _save = self.pos + while true # sequence + _text_start = self.pos + _save1 = self.pos + _tmp = apply(:_Nonspacechar) + if _tmp + while true + _tmp = apply(:_Nonspacechar) + break unless _tmp + end + _tmp = true + else + self.pos = _save1 + end + if _tmp + text = get_text(_text_start) + end + unless _tmp + self.pos = _save + break + end + @result = begin; text ; end + _tmp = true + unless _tmp + self.pos = _save + end + break + end # end sequence + + set_failed_rule :_RefSrc unless _tmp + return _tmp + end + + # RefTitle = (RefTitleSingle | RefTitleDouble | RefTitleParens | EmptyTitle) + def _RefTitle + + _save = self.pos + while true # choice + _tmp = apply(:_RefTitleSingle) + break if _tmp + self.pos = _save + _tmp = apply(:_RefTitleDouble) + break if _tmp + self.pos = _save + _tmp = apply(:_RefTitleParens) + break if _tmp + self.pos = _save + _tmp = apply(:_EmptyTitle) + break if _tmp + self.pos = _save + break + end # end choice + + set_failed_rule :_RefTitle unless _tmp + return _tmp + end + + # EmptyTitle = "" + def _EmptyTitle + _tmp = match_string("") + set_failed_rule :_EmptyTitle unless _tmp + return _tmp + end + + # RefTitleSingle = Spnl "'" < (!("'" @Sp @Newline | @Newline) .)* > "'" { text } + def _RefTitleSingle + + _save = self.pos + while true # sequence + _tmp = apply(:_Spnl) + unless _tmp + self.pos = _save + break + end + _tmp = match_string("'") + unless _tmp + self.pos = _save + break + end + _text_start = self.pos + while true + + _save2 = self.pos + while true # sequence + _save3 = self.pos + + _save4 = self.pos + while true # choice + + _save5 = self.pos + while true # sequence + _tmp = match_string("'") + unless _tmp + self.pos = _save5 + break + end + _tmp = _Sp() + unless _tmp + self.pos = _save5 + break + end + _tmp = _Newline() + unless _tmp + self.pos = _save5 + end + break + end # end sequence + + break if _tmp + self.pos = _save4 + _tmp = _Newline() + break if _tmp + self.pos = _save4 + break + end # end choice + + _tmp = _tmp ? nil : true + self.pos = _save3 + unless _tmp + self.pos = _save2 + break + end + _tmp = get_byte + unless _tmp + self.pos = _save2 + end + break + end # end sequence + + break unless _tmp + end + _tmp = true + if _tmp + text = get_text(_text_start) + end + unless _tmp + self.pos = _save + break + end + _tmp = match_string("'") + unless _tmp + self.pos = _save + break + end + @result = begin; text ; end + _tmp = true + unless _tmp + self.pos = _save + end + break + end # end sequence + + set_failed_rule :_RefTitleSingle unless _tmp + return _tmp + end + + # RefTitleDouble = Spnl "\"" < (!("\"" @Sp @Newline | @Newline) .)* > "\"" { text } + def _RefTitleDouble + + _save = self.pos + while true # sequence + _tmp = apply(:_Spnl) + unless _tmp + self.pos = _save + break + end + _tmp = match_string("\"") + unless _tmp + self.pos = _save + break + end + _text_start = self.pos + while true + + _save2 = self.pos + while true # sequence + _save3 = self.pos + + _save4 = self.pos + while true # choice + + _save5 = self.pos + while true # sequence + _tmp = match_string("\"") + unless _tmp + self.pos = _save5 + break + end + _tmp = _Sp() + unless _tmp + self.pos = _save5 + break + end + _tmp = _Newline() + unless _tmp + self.pos = _save5 + end + break + end # end sequence + + break if _tmp + self.pos = _save4 + _tmp = _Newline() + break if _tmp + self.pos = _save4 + break + end # end choice + + _tmp = _tmp ? nil : true + self.pos = _save3 + unless _tmp + self.pos = _save2 + break + end + _tmp = get_byte + unless _tmp + self.pos = _save2 + end + break + end # end sequence + + break unless _tmp + end + _tmp = true + if _tmp + text = get_text(_text_start) + end + unless _tmp + self.pos = _save + break + end + _tmp = match_string("\"") + unless _tmp + self.pos = _save + break + end + @result = begin; text ; end + _tmp = true + unless _tmp + self.pos = _save + end + break + end # end sequence + + set_failed_rule :_RefTitleDouble unless _tmp + return _tmp + end + + # RefTitleParens = Spnl "(" < (!(")" @Sp @Newline | @Newline) .)* > ")" { text } + def _RefTitleParens + + _save = self.pos + while true # sequence + _tmp = apply(:_Spnl) + unless _tmp + self.pos = _save + break + end + _tmp = match_string("(") + unless _tmp + self.pos = _save + break + end + _text_start = self.pos + while true + + _save2 = self.pos + while true # sequence + _save3 = self.pos + + _save4 = self.pos + while true # choice + + _save5 = self.pos + while true # sequence + _tmp = match_string(")") + unless _tmp + self.pos = _save5 + break + end + _tmp = _Sp() + unless _tmp + self.pos = _save5 + break + end + _tmp = _Newline() + unless _tmp + self.pos = _save5 + end + break + end # end sequence + + break if _tmp + self.pos = _save4 + _tmp = _Newline() + break if _tmp + self.pos = _save4 + break + end # end choice + + _tmp = _tmp ? nil : true + self.pos = _save3 + unless _tmp + self.pos = _save2 + break + end + _tmp = get_byte + unless _tmp + self.pos = _save2 + end + break + end # end sequence + + break unless _tmp + end + _tmp = true + if _tmp + text = get_text(_text_start) + end + unless _tmp + self.pos = _save + break + end + _tmp = match_string(")") + unless _tmp + self.pos = _save + break + end + @result = begin; text ; end + _tmp = true + unless _tmp + self.pos = _save + end + break + end # end sequence + + set_failed_rule :_RefTitleParens unless _tmp + return _tmp + end + + # References = (Reference | SkipBlock)* + def _References + while true + + _save1 = self.pos + while true # choice + _tmp = apply(:_Reference) + break if _tmp + self.pos = _save1 + _tmp = apply(:_SkipBlock) + break if _tmp + self.pos = _save1 + break + end # end choice + + break unless _tmp + end + _tmp = true + set_failed_rule :_References unless _tmp + return _tmp + end + + # Ticks1 = "`" !"`" + def _Ticks1 + + _save = self.pos + while true # sequence + _tmp = match_string("`") + unless _tmp + self.pos = _save + break + end + _save1 = self.pos + _tmp = match_string("`") + _tmp = _tmp ? nil : true + self.pos = _save1 + unless _tmp + self.pos = _save + end + break + end # end sequence + + set_failed_rule :_Ticks1 unless _tmp + return _tmp + end + + # Ticks2 = "``" !"`" + def _Ticks2 + + _save = self.pos + while true # sequence + _tmp = match_string("``") + unless _tmp + self.pos = _save + break + end + _save1 = self.pos + _tmp = match_string("`") + _tmp = _tmp ? nil : true + self.pos = _save1 + unless _tmp + self.pos = _save + end + break + end # end sequence + + set_failed_rule :_Ticks2 unless _tmp + return _tmp + end + + # Ticks3 = "```" !"`" + def _Ticks3 + + _save = self.pos + while true # sequence + _tmp = match_string("```") + unless _tmp + self.pos = _save + break + end + _save1 = self.pos + _tmp = match_string("`") + _tmp = _tmp ? nil : true + self.pos = _save1 + unless _tmp + self.pos = _save + end + break + end # end sequence + + set_failed_rule :_Ticks3 unless _tmp + return _tmp + end + + # Ticks4 = "````" !"`" + def _Ticks4 + + _save = self.pos + while true # sequence + _tmp = match_string("````") + unless _tmp + self.pos = _save + break + end + _save1 = self.pos + _tmp = match_string("`") + _tmp = _tmp ? nil : true + self.pos = _save1 + unless _tmp + self.pos = _save + end + break + end # end sequence + + set_failed_rule :_Ticks4 unless _tmp + return _tmp + end + + # Ticks5 = "`````" !"`" + def _Ticks5 + + _save = self.pos + while true # sequence + _tmp = match_string("`````") + unless _tmp + self.pos = _save + break + end + _save1 = self.pos + _tmp = match_string("`") + _tmp = _tmp ? nil : true + self.pos = _save1 + unless _tmp + self.pos = _save + end + break + end # end sequence + + set_failed_rule :_Ticks5 unless _tmp + return _tmp + end + + # Code = (Ticks1 @Sp < ((!"`" Nonspacechar)+ | !Ticks1 /`+/ | !(@Sp Ticks1) (@Spacechar | @Newline !@BlankLine))+ > @Sp Ticks1 | Ticks2 @Sp < ((!"`" Nonspacechar)+ | !Ticks2 /`+/ | !(@Sp Ticks2) (@Spacechar | @Newline !@BlankLine))+ > @Sp Ticks2 | Ticks3 @Sp < ((!"`" Nonspacechar)+ | !Ticks3 /`+/ | !(@Sp Ticks3) (@Spacechar | @Newline !@BlankLine))+ > @Sp Ticks3 | Ticks4 @Sp < ((!"`" Nonspacechar)+ | !Ticks4 /`+/ | !(@Sp Ticks4) (@Spacechar | @Newline !@BlankLine))+ > @Sp Ticks4 | Ticks5 @Sp < ((!"`" Nonspacechar)+ | !Ticks5 /`+/ | !(@Sp Ticks5) (@Spacechar | @Newline !@BlankLine))+ > @Sp Ticks5) { "<code>#{text}</code>" } + def _Code + + _save = self.pos + while true # sequence + + _save1 = self.pos + while true # choice + + _save2 = self.pos + while true # sequence + _tmp = apply(:_Ticks1) + unless _tmp + self.pos = _save2 + break + end + _tmp = _Sp() + unless _tmp + self.pos = _save2 + break + end + _text_start = self.pos + _save3 = self.pos + + _save4 = self.pos + while true # choice + _save5 = self.pos + + _save6 = self.pos + while true # sequence + _save7 = self.pos + _tmp = match_string("`") + _tmp = _tmp ? nil : true + self.pos = _save7 + unless _tmp + self.pos = _save6 + break + end + _tmp = apply(:_Nonspacechar) + unless _tmp + self.pos = _save6 + end + break + end # end sequence + + if _tmp + while true + + _save8 = self.pos + while true # sequence + _save9 = self.pos + _tmp = match_string("`") + _tmp = _tmp ? nil : true + self.pos = _save9 + unless _tmp + self.pos = _save8 + break + end + _tmp = apply(:_Nonspacechar) + unless _tmp + self.pos = _save8 + end + break + end # end sequence + + break unless _tmp + end + _tmp = true + else + self.pos = _save5 + end + break if _tmp + self.pos = _save4 + + _save10 = self.pos + while true # sequence + _save11 = self.pos + _tmp = apply(:_Ticks1) + _tmp = _tmp ? nil : true + self.pos = _save11 + unless _tmp + self.pos = _save10 + break + end + _tmp = scan(/\A(?-mix:`+)/) + unless _tmp + self.pos = _save10 + end + break + end # end sequence + + break if _tmp + self.pos = _save4 + + _save12 = self.pos + while true # sequence + _save13 = self.pos + + _save14 = self.pos + while true # sequence + _tmp = _Sp() + unless _tmp + self.pos = _save14 + break + end + _tmp = apply(:_Ticks1) + unless _tmp + self.pos = _save14 + end + break + end # end sequence + + _tmp = _tmp ? nil : true + self.pos = _save13 + unless _tmp + self.pos = _save12 + break + end + + _save15 = self.pos + while true # choice + _tmp = _Spacechar() + break if _tmp + self.pos = _save15 + + _save16 = self.pos + while true # sequence + _tmp = _Newline() + unless _tmp + self.pos = _save16 + break + end + _save17 = self.pos + _tmp = _BlankLine() + _tmp = _tmp ? nil : true + self.pos = _save17 + unless _tmp + self.pos = _save16 + end + break + end # end sequence + + break if _tmp + self.pos = _save15 + break + end # end choice + + unless _tmp + self.pos = _save12 + end + break + end # end sequence + + break if _tmp + self.pos = _save4 + break + end # end choice + + if _tmp + while true + + _save18 = self.pos + while true # choice + _save19 = self.pos + + _save20 = self.pos + while true # sequence + _save21 = self.pos + _tmp = match_string("`") + _tmp = _tmp ? nil : true + self.pos = _save21 + unless _tmp + self.pos = _save20 + break + end + _tmp = apply(:_Nonspacechar) + unless _tmp + self.pos = _save20 + end + break + end # end sequence + + if _tmp + while true + + _save22 = self.pos + while true # sequence + _save23 = self.pos + _tmp = match_string("`") + _tmp = _tmp ? nil : true + self.pos = _save23 + unless _tmp + self.pos = _save22 + break + end + _tmp = apply(:_Nonspacechar) + unless _tmp + self.pos = _save22 + end + break + end # end sequence + + break unless _tmp + end + _tmp = true + else + self.pos = _save19 + end + break if _tmp + self.pos = _save18 + + _save24 = self.pos + while true # sequence + _save25 = self.pos + _tmp = apply(:_Ticks1) + _tmp = _tmp ? nil : true + self.pos = _save25 + unless _tmp + self.pos = _save24 + break + end + _tmp = scan(/\A(?-mix:`+)/) + unless _tmp + self.pos = _save24 + end + break + end # end sequence + + break if _tmp + self.pos = _save18 + + _save26 = self.pos + while true # sequence + _save27 = self.pos + + _save28 = self.pos + while true # sequence + _tmp = _Sp() + unless _tmp + self.pos = _save28 + break + end + _tmp = apply(:_Ticks1) + unless _tmp + self.pos = _save28 + end + break + end # end sequence + + _tmp = _tmp ? nil : true + self.pos = _save27 + unless _tmp + self.pos = _save26 + break + end + + _save29 = self.pos + while true # choice + _tmp = _Spacechar() + break if _tmp + self.pos = _save29 + + _save30 = self.pos + while true # sequence + _tmp = _Newline() + unless _tmp + self.pos = _save30 + break + end + _save31 = self.pos + _tmp = _BlankLine() + _tmp = _tmp ? nil : true + self.pos = _save31 + unless _tmp + self.pos = _save30 + end + break + end # end sequence + + break if _tmp + self.pos = _save29 + break + end # end choice + + unless _tmp + self.pos = _save26 + end + break + end # end sequence + + break if _tmp + self.pos = _save18 + break + end # end choice + + break unless _tmp + end + _tmp = true + else + self.pos = _save3 + end + if _tmp + text = get_text(_text_start) + end + unless _tmp + self.pos = _save2 + break + end + _tmp = _Sp() + unless _tmp + self.pos = _save2 + break + end + _tmp = apply(:_Ticks1) + unless _tmp + self.pos = _save2 + end + break + end # end sequence + + break if _tmp + self.pos = _save1 + + _save32 = self.pos + while true # sequence + _tmp = apply(:_Ticks2) + unless _tmp + self.pos = _save32 + break + end + _tmp = _Sp() + unless _tmp + self.pos = _save32 + break + end + _text_start = self.pos + _save33 = self.pos + + _save34 = self.pos + while true # choice + _save35 = self.pos + + _save36 = self.pos + while true # sequence + _save37 = self.pos + _tmp = match_string("`") + _tmp = _tmp ? nil : true + self.pos = _save37 + unless _tmp + self.pos = _save36 + break + end + _tmp = apply(:_Nonspacechar) + unless _tmp + self.pos = _save36 + end + break + end # end sequence + + if _tmp + while true + + _save38 = self.pos + while true # sequence + _save39 = self.pos + _tmp = match_string("`") + _tmp = _tmp ? nil : true + self.pos = _save39 + unless _tmp + self.pos = _save38 + break + end + _tmp = apply(:_Nonspacechar) + unless _tmp + self.pos = _save38 + end + break + end # end sequence + + break unless _tmp + end + _tmp = true + else + self.pos = _save35 + end + break if _tmp + self.pos = _save34 + + _save40 = self.pos + while true # sequence + _save41 = self.pos + _tmp = apply(:_Ticks2) + _tmp = _tmp ? nil : true + self.pos = _save41 + unless _tmp + self.pos = _save40 + break + end + _tmp = scan(/\A(?-mix:`+)/) + unless _tmp + self.pos = _save40 + end + break + end # end sequence + + break if _tmp + self.pos = _save34 + + _save42 = self.pos + while true # sequence + _save43 = self.pos + + _save44 = self.pos + while true # sequence + _tmp = _Sp() + unless _tmp + self.pos = _save44 + break + end + _tmp = apply(:_Ticks2) + unless _tmp + self.pos = _save44 + end + break + end # end sequence + + _tmp = _tmp ? nil : true + self.pos = _save43 + unless _tmp + self.pos = _save42 + break + end + + _save45 = self.pos + while true # choice + _tmp = _Spacechar() + break if _tmp + self.pos = _save45 + + _save46 = self.pos + while true # sequence + _tmp = _Newline() + unless _tmp + self.pos = _save46 + break + end + _save47 = self.pos + _tmp = _BlankLine() + _tmp = _tmp ? nil : true + self.pos = _save47 + unless _tmp + self.pos = _save46 + end + break + end # end sequence + + break if _tmp + self.pos = _save45 + break + end # end choice + + unless _tmp + self.pos = _save42 + end + break + end # end sequence + + break if _tmp + self.pos = _save34 + break + end # end choice + + if _tmp + while true + + _save48 = self.pos + while true # choice + _save49 = self.pos + + _save50 = self.pos + while true # sequence + _save51 = self.pos + _tmp = match_string("`") + _tmp = _tmp ? nil : true + self.pos = _save51 + unless _tmp + self.pos = _save50 + break + end + _tmp = apply(:_Nonspacechar) + unless _tmp + self.pos = _save50 + end + break + end # end sequence + + if _tmp + while true + + _save52 = self.pos + while true # sequence + _save53 = self.pos + _tmp = match_string("`") + _tmp = _tmp ? nil : true + self.pos = _save53 + unless _tmp + self.pos = _save52 + break + end + _tmp = apply(:_Nonspacechar) + unless _tmp + self.pos = _save52 + end + break + end # end sequence + + break unless _tmp + end + _tmp = true + else + self.pos = _save49 + end + break if _tmp + self.pos = _save48 + + _save54 = self.pos + while true # sequence + _save55 = self.pos + _tmp = apply(:_Ticks2) + _tmp = _tmp ? nil : true + self.pos = _save55 + unless _tmp + self.pos = _save54 + break + end + _tmp = scan(/\A(?-mix:`+)/) + unless _tmp + self.pos = _save54 + end + break + end # end sequence + + break if _tmp + self.pos = _save48 + + _save56 = self.pos + while true # sequence + _save57 = self.pos + + _save58 = self.pos + while true # sequence + _tmp = _Sp() + unless _tmp + self.pos = _save58 + break + end + _tmp = apply(:_Ticks2) + unless _tmp + self.pos = _save58 + end + break + end # end sequence + + _tmp = _tmp ? nil : true + self.pos = _save57 + unless _tmp + self.pos = _save56 + break + end + + _save59 = self.pos + while true # choice + _tmp = _Spacechar() + break if _tmp + self.pos = _save59 + + _save60 = self.pos + while true # sequence + _tmp = _Newline() + unless _tmp + self.pos = _save60 + break + end + _save61 = self.pos + _tmp = _BlankLine() + _tmp = _tmp ? nil : true + self.pos = _save61 + unless _tmp + self.pos = _save60 + end + break + end # end sequence + + break if _tmp + self.pos = _save59 + break + end # end choice + + unless _tmp + self.pos = _save56 + end + break + end # end sequence + + break if _tmp + self.pos = _save48 + break + end # end choice + + break unless _tmp + end + _tmp = true + else + self.pos = _save33 + end + if _tmp + text = get_text(_text_start) + end + unless _tmp + self.pos = _save32 + break + end + _tmp = _Sp() + unless _tmp + self.pos = _save32 + break + end + _tmp = apply(:_Ticks2) + unless _tmp + self.pos = _save32 + end + break + end # end sequence + + break if _tmp + self.pos = _save1 + + _save62 = self.pos + while true # sequence + _tmp = apply(:_Ticks3) + unless _tmp + self.pos = _save62 + break + end + _tmp = _Sp() + unless _tmp + self.pos = _save62 + break + end + _text_start = self.pos + _save63 = self.pos + + _save64 = self.pos + while true # choice + _save65 = self.pos + + _save66 = self.pos + while true # sequence + _save67 = self.pos + _tmp = match_string("`") + _tmp = _tmp ? nil : true + self.pos = _save67 + unless _tmp + self.pos = _save66 + break + end + _tmp = apply(:_Nonspacechar) + unless _tmp + self.pos = _save66 + end + break + end # end sequence + + if _tmp + while true + + _save68 = self.pos + while true # sequence + _save69 = self.pos + _tmp = match_string("`") + _tmp = _tmp ? nil : true + self.pos = _save69 + unless _tmp + self.pos = _save68 + break + end + _tmp = apply(:_Nonspacechar) + unless _tmp + self.pos = _save68 + end + break + end # end sequence + + break unless _tmp + end + _tmp = true + else + self.pos = _save65 + end + break if _tmp + self.pos = _save64 + + _save70 = self.pos + while true # sequence + _save71 = self.pos + _tmp = apply(:_Ticks3) + _tmp = _tmp ? nil : true + self.pos = _save71 + unless _tmp + self.pos = _save70 + break + end + _tmp = scan(/\A(?-mix:`+)/) + unless _tmp + self.pos = _save70 + end + break + end # end sequence + + break if _tmp + self.pos = _save64 + + _save72 = self.pos + while true # sequence + _save73 = self.pos + + _save74 = self.pos + while true # sequence + _tmp = _Sp() + unless _tmp + self.pos = _save74 + break + end + _tmp = apply(:_Ticks3) + unless _tmp + self.pos = _save74 + end + break + end # end sequence + + _tmp = _tmp ? nil : true + self.pos = _save73 + unless _tmp + self.pos = _save72 + break + end + + _save75 = self.pos + while true # choice + _tmp = _Spacechar() + break if _tmp + self.pos = _save75 + + _save76 = self.pos + while true # sequence + _tmp = _Newline() + unless _tmp + self.pos = _save76 + break + end + _save77 = self.pos + _tmp = _BlankLine() + _tmp = _tmp ? nil : true + self.pos = _save77 + unless _tmp + self.pos = _save76 + end + break + end # end sequence + + break if _tmp + self.pos = _save75 + break + end # end choice + + unless _tmp + self.pos = _save72 + end + break + end # end sequence + + break if _tmp + self.pos = _save64 + break + end # end choice + + if _tmp + while true + + _save78 = self.pos + while true # choice + _save79 = self.pos + + _save80 = self.pos + while true # sequence + _save81 = self.pos + _tmp = match_string("`") + _tmp = _tmp ? nil : true + self.pos = _save81 + unless _tmp + self.pos = _save80 + break + end + _tmp = apply(:_Nonspacechar) + unless _tmp + self.pos = _save80 + end + break + end # end sequence + + if _tmp + while true + + _save82 = self.pos + while true # sequence + _save83 = self.pos + _tmp = match_string("`") + _tmp = _tmp ? nil : true + self.pos = _save83 + unless _tmp + self.pos = _save82 + break + end + _tmp = apply(:_Nonspacechar) + unless _tmp + self.pos = _save82 + end + break + end # end sequence + + break unless _tmp + end + _tmp = true + else + self.pos = _save79 + end + break if _tmp + self.pos = _save78 + + _save84 = self.pos + while true # sequence + _save85 = self.pos + _tmp = apply(:_Ticks3) + _tmp = _tmp ? nil : true + self.pos = _save85 + unless _tmp + self.pos = _save84 + break + end + _tmp = scan(/\A(?-mix:`+)/) + unless _tmp + self.pos = _save84 + end + break + end # end sequence + + break if _tmp + self.pos = _save78 + + _save86 = self.pos + while true # sequence + _save87 = self.pos + + _save88 = self.pos + while true # sequence + _tmp = _Sp() + unless _tmp + self.pos = _save88 + break + end + _tmp = apply(:_Ticks3) + unless _tmp + self.pos = _save88 + end + break + end # end sequence + + _tmp = _tmp ? nil : true + self.pos = _save87 + unless _tmp + self.pos = _save86 + break + end + + _save89 = self.pos + while true # choice + _tmp = _Spacechar() + break if _tmp + self.pos = _save89 + + _save90 = self.pos + while true # sequence + _tmp = _Newline() + unless _tmp + self.pos = _save90 + break + end + _save91 = self.pos + _tmp = _BlankLine() + _tmp = _tmp ? nil : true + self.pos = _save91 + unless _tmp + self.pos = _save90 + end + break + end # end sequence + + break if _tmp + self.pos = _save89 + break + end # end choice + + unless _tmp + self.pos = _save86 + end + break + end # end sequence + + break if _tmp + self.pos = _save78 + break + end # end choice + + break unless _tmp + end + _tmp = true + else + self.pos = _save63 + end + if _tmp + text = get_text(_text_start) + end + unless _tmp + self.pos = _save62 + break + end + _tmp = _Sp() + unless _tmp + self.pos = _save62 + break + end + _tmp = apply(:_Ticks3) + unless _tmp + self.pos = _save62 + end + break + end # end sequence + + break if _tmp + self.pos = _save1 + + _save92 = self.pos + while true # sequence + _tmp = apply(:_Ticks4) + unless _tmp + self.pos = _save92 + break + end + _tmp = _Sp() + unless _tmp + self.pos = _save92 + break + end + _text_start = self.pos + _save93 = self.pos + + _save94 = self.pos + while true # choice + _save95 = self.pos + + _save96 = self.pos + while true # sequence + _save97 = self.pos + _tmp = match_string("`") + _tmp = _tmp ? nil : true + self.pos = _save97 + unless _tmp + self.pos = _save96 + break + end + _tmp = apply(:_Nonspacechar) + unless _tmp + self.pos = _save96 + end + break + end # end sequence + + if _tmp + while true + + _save98 = self.pos + while true # sequence + _save99 = self.pos + _tmp = match_string("`") + _tmp = _tmp ? nil : true + self.pos = _save99 + unless _tmp + self.pos = _save98 + break + end + _tmp = apply(:_Nonspacechar) + unless _tmp + self.pos = _save98 + end + break + end # end sequence + + break unless _tmp + end + _tmp = true + else + self.pos = _save95 + end + break if _tmp + self.pos = _save94 + + _save100 = self.pos + while true # sequence + _save101 = self.pos + _tmp = apply(:_Ticks4) + _tmp = _tmp ? nil : true + self.pos = _save101 + unless _tmp + self.pos = _save100 + break + end + _tmp = scan(/\A(?-mix:`+)/) + unless _tmp + self.pos = _save100 + end + break + end # end sequence + + break if _tmp + self.pos = _save94 + + _save102 = self.pos + while true # sequence + _save103 = self.pos + + _save104 = self.pos + while true # sequence + _tmp = _Sp() + unless _tmp + self.pos = _save104 + break + end + _tmp = apply(:_Ticks4) + unless _tmp + self.pos = _save104 + end + break + end # end sequence + + _tmp = _tmp ? nil : true + self.pos = _save103 + unless _tmp + self.pos = _save102 + break + end + + _save105 = self.pos + while true # choice + _tmp = _Spacechar() + break if _tmp + self.pos = _save105 + + _save106 = self.pos + while true # sequence + _tmp = _Newline() + unless _tmp + self.pos = _save106 + break + end + _save107 = self.pos + _tmp = _BlankLine() + _tmp = _tmp ? nil : true + self.pos = _save107 + unless _tmp + self.pos = _save106 + end + break + end # end sequence + + break if _tmp + self.pos = _save105 + break + end # end choice + + unless _tmp + self.pos = _save102 + end + break + end # end sequence + + break if _tmp + self.pos = _save94 + break + end # end choice + + if _tmp + while true + + _save108 = self.pos + while true # choice + _save109 = self.pos + + _save110 = self.pos + while true # sequence + _save111 = self.pos + _tmp = match_string("`") + _tmp = _tmp ? nil : true + self.pos = _save111 + unless _tmp + self.pos = _save110 + break + end + _tmp = apply(:_Nonspacechar) + unless _tmp + self.pos = _save110 + end + break + end # end sequence + + if _tmp + while true + + _save112 = self.pos + while true # sequence + _save113 = self.pos + _tmp = match_string("`") + _tmp = _tmp ? nil : true + self.pos = _save113 + unless _tmp + self.pos = _save112 + break + end + _tmp = apply(:_Nonspacechar) + unless _tmp + self.pos = _save112 + end + break + end # end sequence + + break unless _tmp + end + _tmp = true + else + self.pos = _save109 + end + break if _tmp + self.pos = _save108 + + _save114 = self.pos + while true # sequence + _save115 = self.pos + _tmp = apply(:_Ticks4) + _tmp = _tmp ? nil : true + self.pos = _save115 + unless _tmp + self.pos = _save114 + break + end + _tmp = scan(/\A(?-mix:`+)/) + unless _tmp + self.pos = _save114 + end + break + end # end sequence + + break if _tmp + self.pos = _save108 + + _save116 = self.pos + while true # sequence + _save117 = self.pos + + _save118 = self.pos + while true # sequence + _tmp = _Sp() + unless _tmp + self.pos = _save118 + break + end + _tmp = apply(:_Ticks4) + unless _tmp + self.pos = _save118 + end + break + end # end sequence + + _tmp = _tmp ? nil : true + self.pos = _save117 + unless _tmp + self.pos = _save116 + break + end + + _save119 = self.pos + while true # choice + _tmp = _Spacechar() + break if _tmp + self.pos = _save119 + + _save120 = self.pos + while true # sequence + _tmp = _Newline() + unless _tmp + self.pos = _save120 + break + end + _save121 = self.pos + _tmp = _BlankLine() + _tmp = _tmp ? nil : true + self.pos = _save121 + unless _tmp + self.pos = _save120 + end + break + end # end sequence + + break if _tmp + self.pos = _save119 + break + end # end choice + + unless _tmp + self.pos = _save116 + end + break + end # end sequence + + break if _tmp + self.pos = _save108 + break + end # end choice + + break unless _tmp + end + _tmp = true + else + self.pos = _save93 + end + if _tmp + text = get_text(_text_start) + end + unless _tmp + self.pos = _save92 + break + end + _tmp = _Sp() + unless _tmp + self.pos = _save92 + break + end + _tmp = apply(:_Ticks4) + unless _tmp + self.pos = _save92 + end + break + end # end sequence + + break if _tmp + self.pos = _save1 + + _save122 = self.pos + while true # sequence + _tmp = apply(:_Ticks5) + unless _tmp + self.pos = _save122 + break + end + _tmp = _Sp() + unless _tmp + self.pos = _save122 + break + end + _text_start = self.pos + _save123 = self.pos + + _save124 = self.pos + while true # choice + _save125 = self.pos + + _save126 = self.pos + while true # sequence + _save127 = self.pos + _tmp = match_string("`") + _tmp = _tmp ? nil : true + self.pos = _save127 + unless _tmp + self.pos = _save126 + break + end + _tmp = apply(:_Nonspacechar) + unless _tmp + self.pos = _save126 + end + break + end # end sequence + + if _tmp + while true + + _save128 = self.pos + while true # sequence + _save129 = self.pos + _tmp = match_string("`") + _tmp = _tmp ? nil : true + self.pos = _save129 + unless _tmp + self.pos = _save128 + break + end + _tmp = apply(:_Nonspacechar) + unless _tmp + self.pos = _save128 + end + break + end # end sequence + + break unless _tmp + end + _tmp = true + else + self.pos = _save125 + end + break if _tmp + self.pos = _save124 + + _save130 = self.pos + while true # sequence + _save131 = self.pos + _tmp = apply(:_Ticks5) + _tmp = _tmp ? nil : true + self.pos = _save131 + unless _tmp + self.pos = _save130 + break + end + _tmp = scan(/\A(?-mix:`+)/) + unless _tmp + self.pos = _save130 + end + break + end # end sequence + + break if _tmp + self.pos = _save124 + + _save132 = self.pos + while true # sequence + _save133 = self.pos + + _save134 = self.pos + while true # sequence + _tmp = _Sp() + unless _tmp + self.pos = _save134 + break + end + _tmp = apply(:_Ticks5) + unless _tmp + self.pos = _save134 + end + break + end # end sequence + + _tmp = _tmp ? nil : true + self.pos = _save133 + unless _tmp + self.pos = _save132 + break + end + + _save135 = self.pos + while true # choice + _tmp = _Spacechar() + break if _tmp + self.pos = _save135 + + _save136 = self.pos + while true # sequence + _tmp = _Newline() + unless _tmp + self.pos = _save136 + break + end + _save137 = self.pos + _tmp = _BlankLine() + _tmp = _tmp ? nil : true + self.pos = _save137 + unless _tmp + self.pos = _save136 + end + break + end # end sequence + + break if _tmp + self.pos = _save135 + break + end # end choice + + unless _tmp + self.pos = _save132 + end + break + end # end sequence + + break if _tmp + self.pos = _save124 + break + end # end choice + + if _tmp + while true + + _save138 = self.pos + while true # choice + _save139 = self.pos + + _save140 = self.pos + while true # sequence + _save141 = self.pos + _tmp = match_string("`") + _tmp = _tmp ? nil : true + self.pos = _save141 + unless _tmp + self.pos = _save140 + break + end + _tmp = apply(:_Nonspacechar) + unless _tmp + self.pos = _save140 + end + break + end # end sequence + + if _tmp + while true + + _save142 = self.pos + while true # sequence + _save143 = self.pos + _tmp = match_string("`") + _tmp = _tmp ? nil : true + self.pos = _save143 + unless _tmp + self.pos = _save142 + break + end + _tmp = apply(:_Nonspacechar) + unless _tmp + self.pos = _save142 + end + break + end # end sequence + + break unless _tmp + end + _tmp = true + else + self.pos = _save139 + end + break if _tmp + self.pos = _save138 + + _save144 = self.pos + while true # sequence + _save145 = self.pos + _tmp = apply(:_Ticks5) + _tmp = _tmp ? nil : true + self.pos = _save145 + unless _tmp + self.pos = _save144 + break + end + _tmp = scan(/\A(?-mix:`+)/) + unless _tmp + self.pos = _save144 + end + break + end # end sequence + + break if _tmp + self.pos = _save138 + + _save146 = self.pos + while true # sequence + _save147 = self.pos + + _save148 = self.pos + while true # sequence + _tmp = _Sp() + unless _tmp + self.pos = _save148 + break + end + _tmp = apply(:_Ticks5) + unless _tmp + self.pos = _save148 + end + break + end # end sequence + + _tmp = _tmp ? nil : true + self.pos = _save147 + unless _tmp + self.pos = _save146 + break + end + + _save149 = self.pos + while true # choice + _tmp = _Spacechar() + break if _tmp + self.pos = _save149 + + _save150 = self.pos + while true # sequence + _tmp = _Newline() + unless _tmp + self.pos = _save150 + break + end + _save151 = self.pos + _tmp = _BlankLine() + _tmp = _tmp ? nil : true + self.pos = _save151 + unless _tmp + self.pos = _save150 + end + break + end # end sequence + + break if _tmp + self.pos = _save149 + break + end # end choice + + unless _tmp + self.pos = _save146 + end + break + end # end sequence + + break if _tmp + self.pos = _save138 + break + end # end choice + + break unless _tmp + end + _tmp = true + else + self.pos = _save123 + end + if _tmp + text = get_text(_text_start) + end + unless _tmp + self.pos = _save122 + break + end + _tmp = _Sp() + unless _tmp + self.pos = _save122 + break + end + _tmp = apply(:_Ticks5) + unless _tmp + self.pos = _save122 + end + break + end # end sequence + + break if _tmp + self.pos = _save1 + break + end # end choice + + unless _tmp + self.pos = _save + break + end + @result = begin; "<code>#{text}</code>" ; end + _tmp = true + unless _tmp + self.pos = _save + end + break + end # end sequence + + set_failed_rule :_Code unless _tmp + return _tmp + end + + # RawHtml = < (HtmlComment | HtmlBlockScript | HtmlTag) > { if html? then text else '' end } + def _RawHtml + + _save = self.pos + while true # sequence + _text_start = self.pos + + _save1 = self.pos + while true # choice + _tmp = apply(:_HtmlComment) + break if _tmp + self.pos = _save1 + _tmp = apply(:_HtmlBlockScript) + break if _tmp + self.pos = _save1 + _tmp = apply(:_HtmlTag) + break if _tmp + self.pos = _save1 + break + end # end choice + + if _tmp + text = get_text(_text_start) + end + unless _tmp + self.pos = _save + break + end + @result = begin; if html? then text else '' end ; end + _tmp = true + unless _tmp + self.pos = _save + end + break + end # end sequence + + set_failed_rule :_RawHtml unless _tmp + return _tmp + end + + # BlankLine = @Sp @Newline { "\n" } + def _BlankLine + + _save = self.pos + while true # sequence + _tmp = _Sp() + unless _tmp + self.pos = _save + break + end + _tmp = _Newline() + unless _tmp + self.pos = _save + break + end + @result = begin; "\n" ; end + _tmp = true + unless _tmp + self.pos = _save + end + break + end # end sequence + + set_failed_rule :_BlankLine unless _tmp + return _tmp + end + + # Quoted = ("\"" (!"\"" .)* "\"" | "'" (!"'" .)* "'") + def _Quoted + + _save = self.pos + while true # choice + + _save1 = self.pos + while true # sequence + _tmp = match_string("\"") + unless _tmp + self.pos = _save1 + break + end + while true + + _save3 = self.pos + while true # sequence + _save4 = self.pos + _tmp = match_string("\"") + _tmp = _tmp ? nil : true + self.pos = _save4 + unless _tmp + self.pos = _save3 + break + end + _tmp = get_byte + unless _tmp + self.pos = _save3 + end + break + end # end sequence + + break unless _tmp + end + _tmp = true + unless _tmp + self.pos = _save1 + break + end + _tmp = match_string("\"") + unless _tmp + self.pos = _save1 + end + break + end # end sequence + + break if _tmp + self.pos = _save + + _save5 = self.pos + while true # sequence + _tmp = match_string("'") + unless _tmp + self.pos = _save5 + break + end + while true + + _save7 = self.pos + while true # sequence + _save8 = self.pos + _tmp = match_string("'") + _tmp = _tmp ? nil : true + self.pos = _save8 + unless _tmp + self.pos = _save7 + break + end + _tmp = get_byte + unless _tmp + self.pos = _save7 + end + break + end # end sequence + + break unless _tmp + end + _tmp = true + unless _tmp + self.pos = _save5 + break + end + _tmp = match_string("'") + unless _tmp + self.pos = _save5 + end + break + end # end sequence + + break if _tmp + self.pos = _save + break + end # end choice + + set_failed_rule :_Quoted unless _tmp + return _tmp + end + + # HtmlAttribute = (AlphanumericAscii | "-")+ Spnl ("=" Spnl (Quoted | (!">" Nonspacechar)+))? Spnl + def _HtmlAttribute + + _save = self.pos + while true # sequence + _save1 = self.pos + + _save2 = self.pos + while true # choice + _tmp = apply(:_AlphanumericAscii) + break if _tmp + self.pos = _save2 + _tmp = match_string("-") + break if _tmp + self.pos = _save2 + break + end # end choice + + if _tmp + while true + + _save3 = self.pos + while true # choice + _tmp = apply(:_AlphanumericAscii) + break if _tmp + self.pos = _save3 + _tmp = match_string("-") + break if _tmp + self.pos = _save3 + break + end # end choice + + break unless _tmp + end + _tmp = true + else + self.pos = _save1 + end + unless _tmp + self.pos = _save + break + end + _tmp = apply(:_Spnl) + unless _tmp + self.pos = _save + break + end + _save4 = self.pos + + _save5 = self.pos + while true # sequence + _tmp = match_string("=") + unless _tmp + self.pos = _save5 + break + end + _tmp = apply(:_Spnl) + unless _tmp + self.pos = _save5 + break + end + + _save6 = self.pos + while true # choice + _tmp = apply(:_Quoted) + break if _tmp + self.pos = _save6 + _save7 = self.pos + + _save8 = self.pos + while true # sequence + _save9 = self.pos + _tmp = match_string(">") + _tmp = _tmp ? nil : true + self.pos = _save9 + unless _tmp + self.pos = _save8 + break + end + _tmp = apply(:_Nonspacechar) + unless _tmp + self.pos = _save8 + end + break + end # end sequence + + if _tmp + while true + + _save10 = self.pos + while true # sequence + _save11 = self.pos + _tmp = match_string(">") + _tmp = _tmp ? nil : true + self.pos = _save11 + unless _tmp + self.pos = _save10 + break + end + _tmp = apply(:_Nonspacechar) + unless _tmp + self.pos = _save10 + end + break + end # end sequence + + break unless _tmp + end + _tmp = true + else + self.pos = _save7 + end + break if _tmp + self.pos = _save6 + break + end # end choice + + unless _tmp + self.pos = _save5 + end + break + end # end sequence + + unless _tmp + _tmp = true + self.pos = _save4 + end + unless _tmp + self.pos = _save + break + end + _tmp = apply(:_Spnl) + unless _tmp + self.pos = _save + end + break + end # end sequence + + set_failed_rule :_HtmlAttribute unless _tmp + return _tmp + end + + # HtmlComment = "<!--" (!"-->" .)* "-->" + def _HtmlComment + + _save = self.pos + while true # sequence + _tmp = match_string("<!--") + unless _tmp + self.pos = _save + break + end + while true + + _save2 = self.pos + while true # sequence + _save3 = self.pos + _tmp = match_string("-->") + _tmp = _tmp ? nil : true + self.pos = _save3 + unless _tmp + self.pos = _save2 + break + end + _tmp = get_byte + unless _tmp + self.pos = _save2 + end + break + end # end sequence + + break unless _tmp + end + _tmp = true + unless _tmp + self.pos = _save + break + end + _tmp = match_string("-->") + unless _tmp + self.pos = _save + end + break + end # end sequence + + set_failed_rule :_HtmlComment unless _tmp + return _tmp + end + + # HtmlTag = "<" Spnl "/"? AlphanumericAscii+ Spnl HtmlAttribute* "/"? Spnl ">" + def _HtmlTag + + _save = self.pos + while true # sequence + _tmp = match_string("<") + unless _tmp + self.pos = _save + break + end + _tmp = apply(:_Spnl) + unless _tmp + self.pos = _save + break + end + _save1 = self.pos + _tmp = match_string("/") + unless _tmp + _tmp = true + self.pos = _save1 + end + unless _tmp + self.pos = _save + break + end + _save2 = self.pos + _tmp = apply(:_AlphanumericAscii) + if _tmp + while true + _tmp = apply(:_AlphanumericAscii) + break unless _tmp + end + _tmp = true + else + self.pos = _save2 + end + unless _tmp + self.pos = _save + break + end + _tmp = apply(:_Spnl) + unless _tmp + self.pos = _save + break + end + while true + _tmp = apply(:_HtmlAttribute) + break unless _tmp + end + _tmp = true + unless _tmp + self.pos = _save + break + end + _save4 = self.pos + _tmp = match_string("/") + unless _tmp + _tmp = true + self.pos = _save4 + end + unless _tmp + self.pos = _save + break + end + _tmp = apply(:_Spnl) + unless _tmp + self.pos = _save + break + end + _tmp = match_string(">") + unless _tmp + self.pos = _save + end + break + end # end sequence + + set_failed_rule :_HtmlTag unless _tmp + return _tmp + end + + # Eof = !. + def _Eof + _save = self.pos + _tmp = get_byte + _tmp = _tmp ? nil : true + self.pos = _save + set_failed_rule :_Eof unless _tmp + return _tmp + end + + # Nonspacechar = !@Spacechar !@Newline . + def _Nonspacechar + + _save = self.pos + while true # sequence + _save1 = self.pos + _tmp = _Spacechar() + _tmp = _tmp ? nil : true + self.pos = _save1 + unless _tmp + self.pos = _save + break + end + _save2 = self.pos + _tmp = _Newline() + _tmp = _tmp ? nil : true + self.pos = _save2 + unless _tmp + self.pos = _save + break + end + _tmp = get_byte + unless _tmp + self.pos = _save + end + break + end # end sequence + + set_failed_rule :_Nonspacechar unless _tmp + return _tmp + end + + # Sp = @Spacechar* + def _Sp + while true + _tmp = _Spacechar() + break unless _tmp + end + _tmp = true + set_failed_rule :_Sp unless _tmp + return _tmp + end + + # Spnl = @Sp (@Newline @Sp)? + def _Spnl + + _save = self.pos + while true # sequence + _tmp = _Sp() + unless _tmp + self.pos = _save + break + end + _save1 = self.pos + + _save2 = self.pos + while true # sequence + _tmp = _Newline() + unless _tmp + self.pos = _save2 + break + end + _tmp = _Sp() + unless _tmp + self.pos = _save2 + end + break + end # end sequence + + unless _tmp + _tmp = true + self.pos = _save1 + end + unless _tmp + self.pos = _save + end + break + end # end sequence + + set_failed_rule :_Spnl unless _tmp + return _tmp + end + + # SpecialChar = (/[~*_`&\[\]()<!#\\'"]/ | @ExtendedSpecialChar) + def _SpecialChar + + _save = self.pos + while true # choice + _tmp = scan(/\A(?-mix:[~*_`&\[\]()<!#\\'"])/) + break if _tmp + self.pos = _save + _tmp = _ExtendedSpecialChar() + break if _tmp + self.pos = _save + break + end # end choice + + set_failed_rule :_SpecialChar unless _tmp + return _tmp + end + + # NormalChar = !(@SpecialChar | @Spacechar | @Newline) . + def _NormalChar + + _save = self.pos + while true # sequence + _save1 = self.pos + + _save2 = self.pos + while true # choice + _tmp = _SpecialChar() + break if _tmp + self.pos = _save2 + _tmp = _Spacechar() + break if _tmp + self.pos = _save2 + _tmp = _Newline() + break if _tmp + self.pos = _save2 + break + end # end choice + + _tmp = _tmp ? nil : true + self.pos = _save1 + unless _tmp + self.pos = _save + break + end + _tmp = get_byte + unless _tmp + self.pos = _save + end + break + end # end sequence + + set_failed_rule :_NormalChar unless _tmp + return _tmp + end + + # Digit = [0-9] + def _Digit + _save = self.pos + _tmp = get_byte + if _tmp + unless _tmp >= 48 and _tmp <= 57 + self.pos = _save + _tmp = nil + end + end + set_failed_rule :_Digit unless _tmp + return _tmp + end + + # Alphanumeric = %literals.Alphanumeric + def _Alphanumeric + _tmp = @_grammar_literals.external_invoke(self, :_Alphanumeric) + set_failed_rule :_Alphanumeric unless _tmp + return _tmp + end + + # AlphanumericAscii = %literals.AlphanumericAscii + def _AlphanumericAscii + _tmp = @_grammar_literals.external_invoke(self, :_AlphanumericAscii) + set_failed_rule :_AlphanumericAscii unless _tmp + return _tmp + end + + # BOM = %literals.BOM + def _BOM + _tmp = @_grammar_literals.external_invoke(self, :_BOM) + set_failed_rule :_BOM unless _tmp + return _tmp + end + + # Newline = %literals.Newline + def _Newline + _tmp = @_grammar_literals.external_invoke(self, :_Newline) + set_failed_rule :_Newline unless _tmp + return _tmp + end + + # Spacechar = %literals.Spacechar + def _Spacechar + _tmp = @_grammar_literals.external_invoke(self, :_Spacechar) + set_failed_rule :_Spacechar unless _tmp + return _tmp + end + + # HexEntity = /&#x/i < /[0-9a-fA-F]+/ > ";" { [text.to_i(16)].pack 'U' } + def _HexEntity + + _save = self.pos + while true # sequence + _tmp = scan(/\A(?i-mx:&#x)/) + unless _tmp + self.pos = _save + break + end + _text_start = self.pos + _tmp = scan(/\A(?-mix:[0-9a-fA-F]+)/) + if _tmp + text = get_text(_text_start) + end + unless _tmp + self.pos = _save + break + end + _tmp = match_string(";") + unless _tmp + self.pos = _save + break + end + @result = begin; [text.to_i(16)].pack 'U' ; end + _tmp = true + unless _tmp + self.pos = _save + end + break + end # end sequence + + set_failed_rule :_HexEntity unless _tmp + return _tmp + end + + # DecEntity = "&#" < /[0-9]+/ > ";" { [text.to_i].pack 'U' } + def _DecEntity + + _save = self.pos + while true # sequence + _tmp = match_string("&#") + unless _tmp + self.pos = _save + break + end + _text_start = self.pos + _tmp = scan(/\A(?-mix:[0-9]+)/) + if _tmp + text = get_text(_text_start) + end + unless _tmp + self.pos = _save + break + end + _tmp = match_string(";") + unless _tmp + self.pos = _save + break + end + @result = begin; [text.to_i].pack 'U' ; end + _tmp = true + unless _tmp + self.pos = _save + end + break + end # end sequence + + set_failed_rule :_DecEntity unless _tmp + return _tmp + end + + # CharEntity = "&" < /[A-Za-z0-9]+/ > ";" { if entity = HTML_ENTITIES[text] then entity.pack 'U*' else "&#{text};" end } + def _CharEntity + + _save = self.pos + while true # sequence + _tmp = match_string("&") + unless _tmp + self.pos = _save + break + end + _text_start = self.pos + _tmp = scan(/\A(?-mix:[A-Za-z0-9]+)/) + if _tmp + text = get_text(_text_start) + end + unless _tmp + self.pos = _save + break + end + _tmp = match_string(";") + unless _tmp + self.pos = _save + break + end + @result = begin; if entity = HTML_ENTITIES[text] then + entity.pack 'U*' + else + "&#{text};" + end + ; end + _tmp = true + unless _tmp + self.pos = _save + end + break + end # end sequence + + set_failed_rule :_CharEntity unless _tmp + return _tmp + end + + # NonindentSpace = / {0,3}/ + def _NonindentSpace + _tmp = scan(/\A(?-mix: {0,3})/) + set_failed_rule :_NonindentSpace unless _tmp + return _tmp + end + + # Indent = /\t| / + def _Indent + _tmp = scan(/\A(?-mix:\t| )/) + set_failed_rule :_Indent unless _tmp + return _tmp + end + + # IndentedLine = Indent Line + def _IndentedLine + + _save = self.pos + while true # sequence + _tmp = apply(:_Indent) + unless _tmp + self.pos = _save + break + end + _tmp = apply(:_Line) + unless _tmp + self.pos = _save + end + break + end # end sequence + + set_failed_rule :_IndentedLine unless _tmp + return _tmp + end + + # OptionallyIndentedLine = Indent? Line + def _OptionallyIndentedLine + + _save = self.pos + while true # sequence + _save1 = self.pos + _tmp = apply(:_Indent) + unless _tmp + _tmp = true + self.pos = _save1 + end + unless _tmp + self.pos = _save + break + end + _tmp = apply(:_Line) + unless _tmp + self.pos = _save + end + break + end # end sequence + + set_failed_rule :_OptionallyIndentedLine unless _tmp + return _tmp + end + + # StartList = &. { [] } + def _StartList + + _save = self.pos + while true # sequence + _save1 = self.pos + _tmp = get_byte + self.pos = _save1 + unless _tmp + self.pos = _save + break + end + @result = begin; [] ; end + _tmp = true + unless _tmp + self.pos = _save + end + break + end # end sequence + + set_failed_rule :_StartList unless _tmp + return _tmp + end + + # Line = @RawLine:a { a } + def _Line + + _save = self.pos + while true # sequence + _tmp = _RawLine() + a = @result + unless _tmp + self.pos = _save + break + end + @result = begin; a ; end + _tmp = true + unless _tmp + self.pos = _save + end + break + end # end sequence + + set_failed_rule :_Line unless _tmp + return _tmp + end + + # RawLine = (< (!"\r" !"\n" .)* @Newline > | < .+ > @Eof) { text } + def _RawLine + + _save = self.pos + while true # sequence + + _save1 = self.pos + while true # choice + _text_start = self.pos + + _save2 = self.pos + while true # sequence + while true + + _save4 = self.pos + while true # sequence + _save5 = self.pos + _tmp = match_string("\r") + _tmp = _tmp ? nil : true + self.pos = _save5 + unless _tmp + self.pos = _save4 + break + end + _save6 = self.pos + _tmp = match_string("\n") + _tmp = _tmp ? nil : true + self.pos = _save6 + unless _tmp + self.pos = _save4 + break + end + _tmp = get_byte + unless _tmp + self.pos = _save4 + end + break + end # end sequence + + break unless _tmp + end + _tmp = true + unless _tmp + self.pos = _save2 + break + end + _tmp = _Newline() + unless _tmp + self.pos = _save2 + end + break + end # end sequence + + if _tmp + text = get_text(_text_start) + end + break if _tmp + self.pos = _save1 + + _save7 = self.pos + while true # sequence + _text_start = self.pos + _save8 = self.pos + _tmp = get_byte + if _tmp + while true + _tmp = get_byte + break unless _tmp + end + _tmp = true + else + self.pos = _save8 + end + if _tmp + text = get_text(_text_start) + end + unless _tmp + self.pos = _save7 + break + end + _tmp = _Eof() + unless _tmp + self.pos = _save7 + end + break + end # end sequence + + break if _tmp + self.pos = _save1 + break + end # end choice + + unless _tmp + self.pos = _save + break + end + @result = begin; text ; end + _tmp = true + unless _tmp + self.pos = _save + end + break + end # end sequence + + set_failed_rule :_RawLine unless _tmp + return _tmp + end + + # SkipBlock = (HtmlBlock | (!"#" !SetextBottom1 !SetextBottom2 !@BlankLine @RawLine)+ @BlankLine* | @BlankLine+ | @RawLine) + def _SkipBlock + + _save = self.pos + while true # choice + _tmp = apply(:_HtmlBlock) + break if _tmp + self.pos = _save + + _save1 = self.pos + while true # sequence + _save2 = self.pos + + _save3 = self.pos + while true # sequence + _save4 = self.pos + _tmp = match_string("#") + _tmp = _tmp ? nil : true + self.pos = _save4 + unless _tmp + self.pos = _save3 + break + end + _save5 = self.pos + _tmp = apply(:_SetextBottom1) + _tmp = _tmp ? nil : true + self.pos = _save5 + unless _tmp + self.pos = _save3 + break + end + _save6 = self.pos + _tmp = apply(:_SetextBottom2) + _tmp = _tmp ? nil : true + self.pos = _save6 + unless _tmp + self.pos = _save3 + break + end + _save7 = self.pos + _tmp = _BlankLine() + _tmp = _tmp ? nil : true + self.pos = _save7 + unless _tmp + self.pos = _save3 + break + end + _tmp = _RawLine() + unless _tmp + self.pos = _save3 + end + break + end # end sequence + + if _tmp + while true + + _save8 = self.pos + while true # sequence + _save9 = self.pos + _tmp = match_string("#") + _tmp = _tmp ? nil : true + self.pos = _save9 + unless _tmp + self.pos = _save8 + break + end + _save10 = self.pos + _tmp = apply(:_SetextBottom1) + _tmp = _tmp ? nil : true + self.pos = _save10 + unless _tmp + self.pos = _save8 + break + end + _save11 = self.pos + _tmp = apply(:_SetextBottom2) + _tmp = _tmp ? nil : true + self.pos = _save11 + unless _tmp + self.pos = _save8 + break + end + _save12 = self.pos + _tmp = _BlankLine() + _tmp = _tmp ? nil : true + self.pos = _save12 + unless _tmp + self.pos = _save8 + break + end + _tmp = _RawLine() + unless _tmp + self.pos = _save8 + end + break + end # end sequence + + break unless _tmp + end + _tmp = true + else + self.pos = _save2 + end + unless _tmp + self.pos = _save1 + break + end + while true + _tmp = _BlankLine() + break unless _tmp + end + _tmp = true + unless _tmp + self.pos = _save1 + end + break + end # end sequence + + break if _tmp + self.pos = _save + _save14 = self.pos + _tmp = _BlankLine() + if _tmp + while true + _tmp = _BlankLine() + break unless _tmp + end + _tmp = true + else + self.pos = _save14 + end + break if _tmp + self.pos = _save + _tmp = _RawLine() + break if _tmp + self.pos = _save + break + end # end choice + + set_failed_rule :_SkipBlock unless _tmp + return _tmp + end + + # ExtendedSpecialChar = &{ notes? } "^" + def _ExtendedSpecialChar + + _save = self.pos + while true # sequence + _save1 = self.pos + _tmp = begin; notes? ; end + self.pos = _save1 + unless _tmp + self.pos = _save + break + end + _tmp = match_string("^") + unless _tmp + self.pos = _save + end + break + end # end sequence + + set_failed_rule :_ExtendedSpecialChar unless _tmp + return _tmp + end + + # NoteReference = &{ notes? } RawNoteReference:ref { note_for ref } + def _NoteReference + + _save = self.pos + while true # sequence + _save1 = self.pos + _tmp = begin; notes? ; end + self.pos = _save1 + unless _tmp + self.pos = _save + break + end + _tmp = apply(:_RawNoteReference) + ref = @result + unless _tmp + self.pos = _save + break + end + @result = begin; note_for ref ; end + _tmp = true + unless _tmp + self.pos = _save + end + break + end # end sequence + + set_failed_rule :_NoteReference unless _tmp + return _tmp + end + + # RawNoteReference = "[^" < (!@Newline !"]" .)+ > "]" { text } + def _RawNoteReference + + _save = self.pos + while true # sequence + _tmp = match_string("[^") + unless _tmp + self.pos = _save + break + end + _text_start = self.pos + _save1 = self.pos + + _save2 = self.pos + while true # sequence + _save3 = self.pos + _tmp = _Newline() + _tmp = _tmp ? nil : true + self.pos = _save3 + unless _tmp + self.pos = _save2 + break + end + _save4 = self.pos + _tmp = match_string("]") + _tmp = _tmp ? nil : true + self.pos = _save4 + unless _tmp + self.pos = _save2 + break + end + _tmp = get_byte + unless _tmp + self.pos = _save2 + end + break + end # end sequence + + if _tmp + while true + + _save5 = self.pos + while true # sequence + _save6 = self.pos + _tmp = _Newline() + _tmp = _tmp ? nil : true + self.pos = _save6 + unless _tmp + self.pos = _save5 + break + end + _save7 = self.pos + _tmp = match_string("]") + _tmp = _tmp ? nil : true + self.pos = _save7 + unless _tmp + self.pos = _save5 + break + end + _tmp = get_byte + unless _tmp + self.pos = _save5 + end + break + end # end sequence + + break unless _tmp + end + _tmp = true + else + self.pos = _save1 + end + if _tmp + text = get_text(_text_start) + end + unless _tmp + self.pos = _save + break + end + _tmp = match_string("]") + unless _tmp + self.pos = _save + break + end + @result = begin; text ; end + _tmp = true + unless _tmp + self.pos = _save + end + break + end # end sequence + + set_failed_rule :_RawNoteReference unless _tmp + return _tmp + end + + # Note = &{ notes? } @NonindentSpace RawNoteReference:ref ":" @Sp @StartList:a RawNoteBlock:i { a.concat i } (&Indent RawNoteBlock:i { a.concat i })* { @footnotes[ref] = paragraph a nil } + def _Note + + _save = self.pos + while true # sequence + _save1 = self.pos + _tmp = begin; notes? ; end + self.pos = _save1 + unless _tmp + self.pos = _save + break + end + _tmp = _NonindentSpace() + unless _tmp + self.pos = _save + break + end + _tmp = apply(:_RawNoteReference) + ref = @result + unless _tmp + self.pos = _save + break + end + _tmp = match_string(":") + unless _tmp + self.pos = _save + break + end + _tmp = _Sp() + unless _tmp + self.pos = _save + break + end + _tmp = _StartList() + a = @result + unless _tmp + self.pos = _save + break + end + _tmp = apply(:_RawNoteBlock) + i = @result + unless _tmp + self.pos = _save + break + end + @result = begin; a.concat i ; end + _tmp = true + unless _tmp + self.pos = _save + break + end + while true + + _save3 = self.pos + while true # sequence + _save4 = self.pos + _tmp = apply(:_Indent) + self.pos = _save4 + unless _tmp + self.pos = _save3 + break + end + _tmp = apply(:_RawNoteBlock) + i = @result + unless _tmp + self.pos = _save3 + break + end + @result = begin; a.concat i ; end + _tmp = true + unless _tmp + self.pos = _save3 + end + break + end # end sequence + + break unless _tmp + end + _tmp = true + unless _tmp + self.pos = _save + break + end + @result = begin; @footnotes[ref] = paragraph a + + nil + ; end + _tmp = true + unless _tmp + self.pos = _save + end + break + end # end sequence + + set_failed_rule :_Note unless _tmp + return _tmp + end + + # InlineNote = &{ notes? } "^[" @StartList:a (!"]" Inline:l { a << l })+ "]" { ref = [:inline, @note_order.length] @footnotes[ref] = paragraph a note_for ref } + def _InlineNote + + _save = self.pos + while true # sequence + _save1 = self.pos + _tmp = begin; notes? ; end + self.pos = _save1 + unless _tmp + self.pos = _save + break + end + _tmp = match_string("^[") + unless _tmp + self.pos = _save + break + end + _tmp = _StartList() + a = @result + unless _tmp + self.pos = _save + break + end + _save2 = self.pos + + _save3 = self.pos + while true # sequence + _save4 = self.pos + _tmp = match_string("]") + _tmp = _tmp ? nil : true + self.pos = _save4 + unless _tmp + self.pos = _save3 + break + end + _tmp = apply(:_Inline) + l = @result + unless _tmp + self.pos = _save3 + break + end + @result = begin; a << l ; end + _tmp = true + unless _tmp + self.pos = _save3 + end + break + end # end sequence + + if _tmp + while true + + _save5 = self.pos + while true # sequence + _save6 = self.pos + _tmp = match_string("]") + _tmp = _tmp ? nil : true + self.pos = _save6 + unless _tmp + self.pos = _save5 + break + end + _tmp = apply(:_Inline) + l = @result + unless _tmp + self.pos = _save5 + break + end + @result = begin; a << l ; end + _tmp = true + unless _tmp + self.pos = _save5 + end + break + end # end sequence + + break unless _tmp + end + _tmp = true + else + self.pos = _save2 + end + unless _tmp + self.pos = _save + break + end + _tmp = match_string("]") + unless _tmp + self.pos = _save + break + end + @result = begin; ref = [:inline, @note_order.length] + @footnotes[ref] = paragraph a + + note_for ref + ; end + _tmp = true + unless _tmp + self.pos = _save + end + break + end # end sequence + + set_failed_rule :_InlineNote unless _tmp + return _tmp + end + + # Notes = (Note | SkipBlock)* + def _Notes + while true + + _save1 = self.pos + while true # choice + _tmp = apply(:_Note) + break if _tmp + self.pos = _save1 + _tmp = apply(:_SkipBlock) + break if _tmp + self.pos = _save1 + break + end # end choice + + break unless _tmp + end + _tmp = true + set_failed_rule :_Notes unless _tmp + return _tmp + end + + # RawNoteBlock = @StartList:a (!@BlankLine OptionallyIndentedLine:l { a << l })+ < @BlankLine* > { a << text } { a } + def _RawNoteBlock + + _save = self.pos + while true # sequence + _tmp = _StartList() + a = @result + unless _tmp + self.pos = _save + break + end + _save1 = self.pos + + _save2 = self.pos + while true # sequence + _save3 = self.pos + _tmp = _BlankLine() + _tmp = _tmp ? nil : true + self.pos = _save3 + unless _tmp + self.pos = _save2 + break + end + _tmp = apply(:_OptionallyIndentedLine) + l = @result + unless _tmp + self.pos = _save2 + break + end + @result = begin; a << l ; end + _tmp = true + unless _tmp + self.pos = _save2 + end + break + end # end sequence + + if _tmp + while true + + _save4 = self.pos + while true # sequence + _save5 = self.pos + _tmp = _BlankLine() + _tmp = _tmp ? nil : true + self.pos = _save5 + unless _tmp + self.pos = _save4 + break + end + _tmp = apply(:_OptionallyIndentedLine) + l = @result + unless _tmp + self.pos = _save4 + break + end + @result = begin; a << l ; end + _tmp = true + unless _tmp + self.pos = _save4 + end + break + end # end sequence + + break unless _tmp + end + _tmp = true + else + self.pos = _save1 + end + unless _tmp + self.pos = _save + break + end + _text_start = self.pos + while true + _tmp = _BlankLine() + break unless _tmp + end + _tmp = true + if _tmp + text = get_text(_text_start) + end + unless _tmp + self.pos = _save + break + end + @result = begin; a << text ; end + _tmp = true + unless _tmp + self.pos = _save + break + end + @result = begin; a ; end + _tmp = true + unless _tmp + self.pos = _save + end + break + end # end sequence + + set_failed_rule :_RawNoteBlock unless _tmp + return _tmp + end + + # CodeFence = &{ github? } Ticks3 (@Sp StrChunk:format)? Spnl < ((!"`" Nonspacechar)+ | !Ticks3 /`+/ | Spacechar | @Newline)+ > Ticks3 @Sp @Newline* { verbatim = RDoc::Markup::Verbatim.new text verbatim.format = format.intern if format.instance_of?(String) verbatim } + def _CodeFence + + _save = self.pos + while true # sequence + _save1 = self.pos + _tmp = begin; github? ; end + self.pos = _save1 + unless _tmp + self.pos = _save + break + end + _tmp = apply(:_Ticks3) + unless _tmp + self.pos = _save + break + end + _save2 = self.pos + + _save3 = self.pos + while true # sequence + _tmp = _Sp() + unless _tmp + self.pos = _save3 + break + end + _tmp = apply(:_StrChunk) + format = @result + unless _tmp + self.pos = _save3 + end + break + end # end sequence + + unless _tmp + _tmp = true + self.pos = _save2 + end + unless _tmp + self.pos = _save + break + end + _tmp = apply(:_Spnl) + unless _tmp + self.pos = _save + break + end + _text_start = self.pos + _save4 = self.pos + + _save5 = self.pos + while true # choice + _save6 = self.pos + + _save7 = self.pos + while true # sequence + _save8 = self.pos + _tmp = match_string("`") + _tmp = _tmp ? nil : true + self.pos = _save8 + unless _tmp + self.pos = _save7 + break + end + _tmp = apply(:_Nonspacechar) + unless _tmp + self.pos = _save7 + end + break + end # end sequence + + if _tmp + while true + + _save9 = self.pos + while true # sequence + _save10 = self.pos + _tmp = match_string("`") + _tmp = _tmp ? nil : true + self.pos = _save10 + unless _tmp + self.pos = _save9 + break + end + _tmp = apply(:_Nonspacechar) + unless _tmp + self.pos = _save9 + end + break + end # end sequence + + break unless _tmp + end + _tmp = true + else + self.pos = _save6 + end + break if _tmp + self.pos = _save5 + + _save11 = self.pos + while true # sequence + _save12 = self.pos + _tmp = apply(:_Ticks3) + _tmp = _tmp ? nil : true + self.pos = _save12 + unless _tmp + self.pos = _save11 + break + end + _tmp = scan(/\A(?-mix:`+)/) + unless _tmp + self.pos = _save11 + end + break + end # end sequence + + break if _tmp + self.pos = _save5 + _tmp = apply(:_Spacechar) + break if _tmp + self.pos = _save5 + _tmp = _Newline() + break if _tmp + self.pos = _save5 + break + end # end choice + + if _tmp + while true + + _save13 = self.pos + while true # choice + _save14 = self.pos + + _save15 = self.pos + while true # sequence + _save16 = self.pos + _tmp = match_string("`") + _tmp = _tmp ? nil : true + self.pos = _save16 + unless _tmp + self.pos = _save15 + break + end + _tmp = apply(:_Nonspacechar) + unless _tmp + self.pos = _save15 + end + break + end # end sequence + + if _tmp + while true + + _save17 = self.pos + while true # sequence + _save18 = self.pos + _tmp = match_string("`") + _tmp = _tmp ? nil : true + self.pos = _save18 + unless _tmp + self.pos = _save17 + break + end + _tmp = apply(:_Nonspacechar) + unless _tmp + self.pos = _save17 + end + break + end # end sequence + + break unless _tmp + end + _tmp = true + else + self.pos = _save14 + end + break if _tmp + self.pos = _save13 + + _save19 = self.pos + while true # sequence + _save20 = self.pos + _tmp = apply(:_Ticks3) + _tmp = _tmp ? nil : true + self.pos = _save20 + unless _tmp + self.pos = _save19 + break + end + _tmp = scan(/\A(?-mix:`+)/) + unless _tmp + self.pos = _save19 + end + break + end # end sequence + + break if _tmp + self.pos = _save13 + _tmp = apply(:_Spacechar) + break if _tmp + self.pos = _save13 + _tmp = _Newline() + break if _tmp + self.pos = _save13 + break + end # end choice + + break unless _tmp + end + _tmp = true + else + self.pos = _save4 + end + if _tmp + text = get_text(_text_start) + end + unless _tmp + self.pos = _save + break + end + _tmp = apply(:_Ticks3) + unless _tmp + self.pos = _save + break + end + _tmp = _Sp() + unless _tmp + self.pos = _save + break + end + while true + _tmp = _Newline() + break unless _tmp + end + _tmp = true + unless _tmp + self.pos = _save + break + end + @result = begin; verbatim = RDoc::Markup::Verbatim.new text + verbatim.format = format.intern if format.instance_of?(String) + verbatim + ; end + _tmp = true + unless _tmp + self.pos = _save + end + break + end # end sequence + + set_failed_rule :_CodeFence unless _tmp + return _tmp + end + + # DefinitionList = &{ definition_lists? } DefinitionListItem+:list { RDoc::Markup::List.new :NOTE, *list.flatten } + def _DefinitionList + + _save = self.pos + while true # sequence + _save1 = self.pos + _tmp = begin; definition_lists? ; end + self.pos = _save1 + unless _tmp + self.pos = _save + break + end + _save2 = self.pos + _ary = [] + _tmp = apply(:_DefinitionListItem) + if _tmp + _ary << @result + while true + _tmp = apply(:_DefinitionListItem) + _ary << @result if _tmp + break unless _tmp + end + _tmp = true + @result = _ary + else + self.pos = _save2 + end + list = @result + unless _tmp + self.pos = _save + break + end + @result = begin; RDoc::Markup::List.new :NOTE, *list.flatten ; end + _tmp = true + unless _tmp + self.pos = _save + end + break + end # end sequence + + set_failed_rule :_DefinitionList unless _tmp + return _tmp + end + + # DefinitionListItem = DefinitionListLabel+:label DefinitionListDefinition+:defns { list_items = [] list_items << RDoc::Markup::ListItem.new(label, defns.shift) list_items.concat defns.map { |defn| RDoc::Markup::ListItem.new nil, defn } unless list_items.empty? list_items } + def _DefinitionListItem + + _save = self.pos + while true # sequence + _save1 = self.pos + _ary = [] + _tmp = apply(:_DefinitionListLabel) + if _tmp + _ary << @result + while true + _tmp = apply(:_DefinitionListLabel) + _ary << @result if _tmp + break unless _tmp + end + _tmp = true + @result = _ary + else + self.pos = _save1 + end + label = @result + unless _tmp + self.pos = _save + break + end + _save2 = self.pos + _ary = [] + _tmp = apply(:_DefinitionListDefinition) + if _tmp + _ary << @result + while true + _tmp = apply(:_DefinitionListDefinition) + _ary << @result if _tmp + break unless _tmp + end + _tmp = true + @result = _ary + else + self.pos = _save2 + end + defns = @result + unless _tmp + self.pos = _save + break + end + @result = begin; list_items = [] + list_items << + RDoc::Markup::ListItem.new(label, defns.shift) + + list_items.concat defns.map { |defn| + RDoc::Markup::ListItem.new nil, defn + } unless list_items.empty? + + list_items + ; end + _tmp = true + unless _tmp + self.pos = _save + end + break + end # end sequence + + set_failed_rule :_DefinitionListItem unless _tmp + return _tmp + end + + # DefinitionListLabel = StrChunk:label @Sp @Newline { label } + def _DefinitionListLabel + + _save = self.pos + while true # sequence + _tmp = apply(:_StrChunk) + label = @result + unless _tmp + self.pos = _save + break + end + _tmp = _Sp() + unless _tmp + self.pos = _save + break + end + _tmp = _Newline() + unless _tmp + self.pos = _save + break + end + @result = begin; label ; end + _tmp = true + unless _tmp + self.pos = _save + end + break + end # end sequence + + set_failed_rule :_DefinitionListLabel unless _tmp + return _tmp + end + + # DefinitionListDefinition = @NonindentSpace ":" @Space Inlines:a @BlankLine+ { paragraph a } + def _DefinitionListDefinition + + _save = self.pos + while true # sequence + _tmp = _NonindentSpace() + unless _tmp + self.pos = _save + break + end + _tmp = match_string(":") + unless _tmp + self.pos = _save + break + end + _tmp = _Space() + unless _tmp + self.pos = _save + break + end + _tmp = apply(:_Inlines) + a = @result + unless _tmp + self.pos = _save + break + end + _save1 = self.pos + _tmp = _BlankLine() + if _tmp + while true + _tmp = _BlankLine() + break unless _tmp + end + _tmp = true + else + self.pos = _save1 + end + unless _tmp + self.pos = _save + break + end + @result = begin; paragraph a ; end + _tmp = true + unless _tmp + self.pos = _save + end + break + end # end sequence + + set_failed_rule :_DefinitionListDefinition unless _tmp + return _tmp + end + + Rules = {} + Rules[:_root] = rule_info("root", "Doc") + Rules[:_Doc] = rule_info("Doc", "BOM? Block*:a { RDoc::Markup::Document.new(*a.compact) }") + Rules[:_Block] = rule_info("Block", "@BlankLine* (BlockQuote | Verbatim | CodeFence | Note | Reference | HorizontalRule | Heading | OrderedList | BulletList | DefinitionList | HtmlBlock | StyleBlock | Para | Plain)") + Rules[:_Para] = rule_info("Para", "@NonindentSpace Inlines:a @BlankLine+ { paragraph a }") + Rules[:_Plain] = rule_info("Plain", "Inlines:a { paragraph a }") + Rules[:_AtxInline] = rule_info("AtxInline", "!@Newline !(@Sp /\#*/ @Sp @Newline) Inline") + Rules[:_AtxStart] = rule_info("AtxStart", "< /\\\#{1,6}/ > { text.length }") + Rules[:_AtxHeading] = rule_info("AtxHeading", "AtxStart:s @Sp AtxInline+:a (@Sp /\#*/ @Sp)? @Newline { RDoc::Markup::Heading.new(s, a.join) }") + Rules[:_SetextHeading] = rule_info("SetextHeading", "(SetextHeading1 | SetextHeading2)") + Rules[:_SetextBottom1] = rule_info("SetextBottom1", "/={1,}/ @Newline") + Rules[:_SetextBottom2] = rule_info("SetextBottom2", "/-{1,}/ @Newline") + Rules[:_SetextHeading1] = rule_info("SetextHeading1", "&(@RawLine SetextBottom1) @StartList:a (!@Endline Inline:b { a << b })+ @Sp @Newline SetextBottom1 { RDoc::Markup::Heading.new(1, a.join) }") + Rules[:_SetextHeading2] = rule_info("SetextHeading2", "&(@RawLine SetextBottom2) @StartList:a (!@Endline Inline:b { a << b })+ @Sp @Newline SetextBottom2 { RDoc::Markup::Heading.new(2, a.join) }") + Rules[:_Heading] = rule_info("Heading", "(SetextHeading | AtxHeading)") + Rules[:_BlockQuote] = rule_info("BlockQuote", "BlockQuoteRaw:a { RDoc::Markup::BlockQuote.new(*a) }") + Rules[:_BlockQuoteRaw] = rule_info("BlockQuoteRaw", "@StartList:a (\">\" \" \"? Line:l { a << l } (!\">\" !@BlankLine Line:c { a << c })* (@BlankLine:n { a << n })*)+ { inner_parse a.join }") + Rules[:_NonblankIndentedLine] = rule_info("NonblankIndentedLine", "!@BlankLine IndentedLine") + Rules[:_VerbatimChunk] = rule_info("VerbatimChunk", "@BlankLine*:a NonblankIndentedLine+:b { a.concat b }") + Rules[:_Verbatim] = rule_info("Verbatim", "VerbatimChunk+:a { RDoc::Markup::Verbatim.new(*a.flatten) }") + Rules[:_HorizontalRule] = rule_info("HorizontalRule", "@NonindentSpace (\"*\" @Sp \"*\" @Sp \"*\" (@Sp \"*\")* | \"-\" @Sp \"-\" @Sp \"-\" (@Sp \"-\")* | \"_\" @Sp \"_\" @Sp \"_\" (@Sp \"_\")*) @Sp @Newline @BlankLine+ { RDoc::Markup::Rule.new 1 }") + Rules[:_Bullet] = rule_info("Bullet", "!HorizontalRule @NonindentSpace /[+*-]/ @Spacechar+") + Rules[:_BulletList] = rule_info("BulletList", "&Bullet (ListTight | ListLoose):a { RDoc::Markup::List.new(:BULLET, *a) }") + Rules[:_ListTight] = rule_info("ListTight", "ListItemTight+:a @BlankLine* !(Bullet | Enumerator) { a }") + Rules[:_ListLoose] = rule_info("ListLoose", "@StartList:a (ListItem:b @BlankLine* { a << b })+ { a }") + Rules[:_ListItem] = rule_info("ListItem", "(Bullet | Enumerator) @StartList:a ListBlock:b { a << b } (ListContinuationBlock:c { a.push(*c) })* { list_item_from a }") + Rules[:_ListItemTight] = rule_info("ListItemTight", "(Bullet | Enumerator) ListBlock:a (!@BlankLine ListContinuationBlock:b { a.push(*b) })* !ListContinuationBlock { list_item_from a }") + Rules[:_ListBlock] = rule_info("ListBlock", "!@BlankLine Line:a ListBlockLine*:c { [a, *c] }") + Rules[:_ListContinuationBlock] = rule_info("ListContinuationBlock", "@StartList:a @BlankLine* { a << \"\\n\" } (Indent ListBlock:b { a.concat b })+ { a }") + Rules[:_Enumerator] = rule_info("Enumerator", "@NonindentSpace [0-9]+ \".\" @Spacechar+") + Rules[:_OrderedList] = rule_info("OrderedList", "&Enumerator (ListTight | ListLoose):a { RDoc::Markup::List.new(:NUMBER, *a) }") + Rules[:_ListBlockLine] = rule_info("ListBlockLine", "!@BlankLine !(Indent? (Bullet | Enumerator)) !HorizontalRule OptionallyIndentedLine") + Rules[:_HtmlOpenAnchor] = rule_info("HtmlOpenAnchor", "\"<\" Spnl (\"a\" | \"A\") Spnl HtmlAttribute* \">\"") + Rules[:_HtmlCloseAnchor] = rule_info("HtmlCloseAnchor", "\"<\" Spnl \"/\" (\"a\" | \"A\") Spnl \">\"") + Rules[:_HtmlAnchor] = rule_info("HtmlAnchor", "HtmlOpenAnchor (HtmlAnchor | !HtmlCloseAnchor .)* HtmlCloseAnchor") + Rules[:_HtmlBlockOpenAddress] = rule_info("HtmlBlockOpenAddress", "\"<\" Spnl (\"address\" | \"ADDRESS\") Spnl HtmlAttribute* \">\"") + Rules[:_HtmlBlockCloseAddress] = rule_info("HtmlBlockCloseAddress", "\"<\" Spnl \"/\" (\"address\" | \"ADDRESS\") Spnl \">\"") + Rules[:_HtmlBlockAddress] = rule_info("HtmlBlockAddress", "HtmlBlockOpenAddress (HtmlBlockAddress | !HtmlBlockCloseAddress .)* HtmlBlockCloseAddress") + Rules[:_HtmlBlockOpenBlockquote] = rule_info("HtmlBlockOpenBlockquote", "\"<\" Spnl (\"blockquote\" | \"BLOCKQUOTE\") Spnl HtmlAttribute* \">\"") + Rules[:_HtmlBlockCloseBlockquote] = rule_info("HtmlBlockCloseBlockquote", "\"<\" Spnl \"/\" (\"blockquote\" | \"BLOCKQUOTE\") Spnl \">\"") + Rules[:_HtmlBlockBlockquote] = rule_info("HtmlBlockBlockquote", "HtmlBlockOpenBlockquote (HtmlBlockBlockquote | !HtmlBlockCloseBlockquote .)* HtmlBlockCloseBlockquote") + Rules[:_HtmlBlockOpenCenter] = rule_info("HtmlBlockOpenCenter", "\"<\" Spnl (\"center\" | \"CENTER\") Spnl HtmlAttribute* \">\"") + Rules[:_HtmlBlockCloseCenter] = rule_info("HtmlBlockCloseCenter", "\"<\" Spnl \"/\" (\"center\" | \"CENTER\") Spnl \">\"") + Rules[:_HtmlBlockCenter] = rule_info("HtmlBlockCenter", "HtmlBlockOpenCenter (HtmlBlockCenter | !HtmlBlockCloseCenter .)* HtmlBlockCloseCenter") + Rules[:_HtmlBlockOpenDir] = rule_info("HtmlBlockOpenDir", "\"<\" Spnl (\"dir\" | \"DIR\") Spnl HtmlAttribute* \">\"") + Rules[:_HtmlBlockCloseDir] = rule_info("HtmlBlockCloseDir", "\"<\" Spnl \"/\" (\"dir\" | \"DIR\") Spnl \">\"") + Rules[:_HtmlBlockDir] = rule_info("HtmlBlockDir", "HtmlBlockOpenDir (HtmlBlockDir | !HtmlBlockCloseDir .)* HtmlBlockCloseDir") + Rules[:_HtmlBlockOpenDiv] = rule_info("HtmlBlockOpenDiv", "\"<\" Spnl (\"div\" | \"DIV\") Spnl HtmlAttribute* \">\"") + Rules[:_HtmlBlockCloseDiv] = rule_info("HtmlBlockCloseDiv", "\"<\" Spnl \"/\" (\"div\" | \"DIV\") Spnl \">\"") + Rules[:_HtmlBlockDiv] = rule_info("HtmlBlockDiv", "HtmlBlockOpenDiv (HtmlBlockDiv | !HtmlBlockCloseDiv .)* HtmlBlockCloseDiv") + Rules[:_HtmlBlockOpenDl] = rule_info("HtmlBlockOpenDl", "\"<\" Spnl (\"dl\" | \"DL\") Spnl HtmlAttribute* \">\"") + Rules[:_HtmlBlockCloseDl] = rule_info("HtmlBlockCloseDl", "\"<\" Spnl \"/\" (\"dl\" | \"DL\") Spnl \">\"") + Rules[:_HtmlBlockDl] = rule_info("HtmlBlockDl", "HtmlBlockOpenDl (HtmlBlockDl | !HtmlBlockCloseDl .)* HtmlBlockCloseDl") + Rules[:_HtmlBlockOpenFieldset] = rule_info("HtmlBlockOpenFieldset", "\"<\" Spnl (\"fieldset\" | \"FIELDSET\") Spnl HtmlAttribute* \">\"") + Rules[:_HtmlBlockCloseFieldset] = rule_info("HtmlBlockCloseFieldset", "\"<\" Spnl \"/\" (\"fieldset\" | \"FIELDSET\") Spnl \">\"") + Rules[:_HtmlBlockFieldset] = rule_info("HtmlBlockFieldset", "HtmlBlockOpenFieldset (HtmlBlockFieldset | !HtmlBlockCloseFieldset .)* HtmlBlockCloseFieldset") + Rules[:_HtmlBlockOpenForm] = rule_info("HtmlBlockOpenForm", "\"<\" Spnl (\"form\" | \"FORM\") Spnl HtmlAttribute* \">\"") + Rules[:_HtmlBlockCloseForm] = rule_info("HtmlBlockCloseForm", "\"<\" Spnl \"/\" (\"form\" | \"FORM\") Spnl \">\"") + Rules[:_HtmlBlockForm] = rule_info("HtmlBlockForm", "HtmlBlockOpenForm (HtmlBlockForm | !HtmlBlockCloseForm .)* HtmlBlockCloseForm") + Rules[:_HtmlBlockOpenH1] = rule_info("HtmlBlockOpenH1", "\"<\" Spnl (\"h1\" | \"H1\") Spnl HtmlAttribute* \">\"") + Rules[:_HtmlBlockCloseH1] = rule_info("HtmlBlockCloseH1", "\"<\" Spnl \"/\" (\"h1\" | \"H1\") Spnl \">\"") + Rules[:_HtmlBlockH1] = rule_info("HtmlBlockH1", "HtmlBlockOpenH1 (HtmlBlockH1 | !HtmlBlockCloseH1 .)* HtmlBlockCloseH1") + Rules[:_HtmlBlockOpenH2] = rule_info("HtmlBlockOpenH2", "\"<\" Spnl (\"h2\" | \"H2\") Spnl HtmlAttribute* \">\"") + Rules[:_HtmlBlockCloseH2] = rule_info("HtmlBlockCloseH2", "\"<\" Spnl \"/\" (\"h2\" | \"H2\") Spnl \">\"") + Rules[:_HtmlBlockH2] = rule_info("HtmlBlockH2", "HtmlBlockOpenH2 (HtmlBlockH2 | !HtmlBlockCloseH2 .)* HtmlBlockCloseH2") + Rules[:_HtmlBlockOpenH3] = rule_info("HtmlBlockOpenH3", "\"<\" Spnl (\"h3\" | \"H3\") Spnl HtmlAttribute* \">\"") + Rules[:_HtmlBlockCloseH3] = rule_info("HtmlBlockCloseH3", "\"<\" Spnl \"/\" (\"h3\" | \"H3\") Spnl \">\"") + Rules[:_HtmlBlockH3] = rule_info("HtmlBlockH3", "HtmlBlockOpenH3 (HtmlBlockH3 | !HtmlBlockCloseH3 .)* HtmlBlockCloseH3") + Rules[:_HtmlBlockOpenH4] = rule_info("HtmlBlockOpenH4", "\"<\" Spnl (\"h4\" | \"H4\") Spnl HtmlAttribute* \">\"") + Rules[:_HtmlBlockCloseH4] = rule_info("HtmlBlockCloseH4", "\"<\" Spnl \"/\" (\"h4\" | \"H4\") Spnl \">\"") + Rules[:_HtmlBlockH4] = rule_info("HtmlBlockH4", "HtmlBlockOpenH4 (HtmlBlockH4 | !HtmlBlockCloseH4 .)* HtmlBlockCloseH4") + Rules[:_HtmlBlockOpenH5] = rule_info("HtmlBlockOpenH5", "\"<\" Spnl (\"h5\" | \"H5\") Spnl HtmlAttribute* \">\"") + Rules[:_HtmlBlockCloseH5] = rule_info("HtmlBlockCloseH5", "\"<\" Spnl \"/\" (\"h5\" | \"H5\") Spnl \">\"") + Rules[:_HtmlBlockH5] = rule_info("HtmlBlockH5", "HtmlBlockOpenH5 (HtmlBlockH5 | !HtmlBlockCloseH5 .)* HtmlBlockCloseH5") + Rules[:_HtmlBlockOpenH6] = rule_info("HtmlBlockOpenH6", "\"<\" Spnl (\"h6\" | \"H6\") Spnl HtmlAttribute* \">\"") + Rules[:_HtmlBlockCloseH6] = rule_info("HtmlBlockCloseH6", "\"<\" Spnl \"/\" (\"h6\" | \"H6\") Spnl \">\"") + Rules[:_HtmlBlockH6] = rule_info("HtmlBlockH6", "HtmlBlockOpenH6 (HtmlBlockH6 | !HtmlBlockCloseH6 .)* HtmlBlockCloseH6") + Rules[:_HtmlBlockOpenMenu] = rule_info("HtmlBlockOpenMenu", "\"<\" Spnl (\"menu\" | \"MENU\") Spnl HtmlAttribute* \">\"") + Rules[:_HtmlBlockCloseMenu] = rule_info("HtmlBlockCloseMenu", "\"<\" Spnl \"/\" (\"menu\" | \"MENU\") Spnl \">\"") + Rules[:_HtmlBlockMenu] = rule_info("HtmlBlockMenu", "HtmlBlockOpenMenu (HtmlBlockMenu | !HtmlBlockCloseMenu .)* HtmlBlockCloseMenu") + Rules[:_HtmlBlockOpenNoframes] = rule_info("HtmlBlockOpenNoframes", "\"<\" Spnl (\"noframes\" | \"NOFRAMES\") Spnl HtmlAttribute* \">\"") + Rules[:_HtmlBlockCloseNoframes] = rule_info("HtmlBlockCloseNoframes", "\"<\" Spnl \"/\" (\"noframes\" | \"NOFRAMES\") Spnl \">\"") + Rules[:_HtmlBlockNoframes] = rule_info("HtmlBlockNoframes", "HtmlBlockOpenNoframes (HtmlBlockNoframes | !HtmlBlockCloseNoframes .)* HtmlBlockCloseNoframes") + Rules[:_HtmlBlockOpenNoscript] = rule_info("HtmlBlockOpenNoscript", "\"<\" Spnl (\"noscript\" | \"NOSCRIPT\") Spnl HtmlAttribute* \">\"") + Rules[:_HtmlBlockCloseNoscript] = rule_info("HtmlBlockCloseNoscript", "\"<\" Spnl \"/\" (\"noscript\" | \"NOSCRIPT\") Spnl \">\"") + Rules[:_HtmlBlockNoscript] = rule_info("HtmlBlockNoscript", "HtmlBlockOpenNoscript (HtmlBlockNoscript | !HtmlBlockCloseNoscript .)* HtmlBlockCloseNoscript") + Rules[:_HtmlBlockOpenOl] = rule_info("HtmlBlockOpenOl", "\"<\" Spnl (\"ol\" | \"OL\") Spnl HtmlAttribute* \">\"") + Rules[:_HtmlBlockCloseOl] = rule_info("HtmlBlockCloseOl", "\"<\" Spnl \"/\" (\"ol\" | \"OL\") Spnl \">\"") + Rules[:_HtmlBlockOl] = rule_info("HtmlBlockOl", "HtmlBlockOpenOl (HtmlBlockOl | !HtmlBlockCloseOl .)* HtmlBlockCloseOl") + Rules[:_HtmlBlockOpenP] = rule_info("HtmlBlockOpenP", "\"<\" Spnl (\"p\" | \"P\") Spnl HtmlAttribute* \">\"") + Rules[:_HtmlBlockCloseP] = rule_info("HtmlBlockCloseP", "\"<\" Spnl \"/\" (\"p\" | \"P\") Spnl \">\"") + Rules[:_HtmlBlockP] = rule_info("HtmlBlockP", "HtmlBlockOpenP (HtmlBlockP | !HtmlBlockCloseP .)* HtmlBlockCloseP") + Rules[:_HtmlBlockOpenPre] = rule_info("HtmlBlockOpenPre", "\"<\" Spnl (\"pre\" | \"PRE\") Spnl HtmlAttribute* \">\"") + Rules[:_HtmlBlockClosePre] = rule_info("HtmlBlockClosePre", "\"<\" Spnl \"/\" (\"pre\" | \"PRE\") Spnl \">\"") + Rules[:_HtmlBlockPre] = rule_info("HtmlBlockPre", "HtmlBlockOpenPre (HtmlBlockPre | !HtmlBlockClosePre .)* HtmlBlockClosePre") + Rules[:_HtmlBlockOpenTable] = rule_info("HtmlBlockOpenTable", "\"<\" Spnl (\"table\" | \"TABLE\") Spnl HtmlAttribute* \">\"") + Rules[:_HtmlBlockCloseTable] = rule_info("HtmlBlockCloseTable", "\"<\" Spnl \"/\" (\"table\" | \"TABLE\") Spnl \">\"") + Rules[:_HtmlBlockTable] = rule_info("HtmlBlockTable", "HtmlBlockOpenTable (HtmlBlockTable | !HtmlBlockCloseTable .)* HtmlBlockCloseTable") + Rules[:_HtmlBlockOpenUl] = rule_info("HtmlBlockOpenUl", "\"<\" Spnl (\"ul\" | \"UL\") Spnl HtmlAttribute* \">\"") + Rules[:_HtmlBlockCloseUl] = rule_info("HtmlBlockCloseUl", "\"<\" Spnl \"/\" (\"ul\" | \"UL\") Spnl \">\"") + Rules[:_HtmlBlockUl] = rule_info("HtmlBlockUl", "HtmlBlockOpenUl (HtmlBlockUl | !HtmlBlockCloseUl .)* HtmlBlockCloseUl") + Rules[:_HtmlBlockOpenDd] = rule_info("HtmlBlockOpenDd", "\"<\" Spnl (\"dd\" | \"DD\") Spnl HtmlAttribute* \">\"") + Rules[:_HtmlBlockCloseDd] = rule_info("HtmlBlockCloseDd", "\"<\" Spnl \"/\" (\"dd\" | \"DD\") Spnl \">\"") + Rules[:_HtmlBlockDd] = rule_info("HtmlBlockDd", "HtmlBlockOpenDd (HtmlBlockDd | !HtmlBlockCloseDd .)* HtmlBlockCloseDd") + Rules[:_HtmlBlockOpenDt] = rule_info("HtmlBlockOpenDt", "\"<\" Spnl (\"dt\" | \"DT\") Spnl HtmlAttribute* \">\"") + Rules[:_HtmlBlockCloseDt] = rule_info("HtmlBlockCloseDt", "\"<\" Spnl \"/\" (\"dt\" | \"DT\") Spnl \">\"") + Rules[:_HtmlBlockDt] = rule_info("HtmlBlockDt", "HtmlBlockOpenDt (HtmlBlockDt | !HtmlBlockCloseDt .)* HtmlBlockCloseDt") + Rules[:_HtmlBlockOpenFrameset] = rule_info("HtmlBlockOpenFrameset", "\"<\" Spnl (\"frameset\" | \"FRAMESET\") Spnl HtmlAttribute* \">\"") + Rules[:_HtmlBlockCloseFrameset] = rule_info("HtmlBlockCloseFrameset", "\"<\" Spnl \"/\" (\"frameset\" | \"FRAMESET\") Spnl \">\"") + Rules[:_HtmlBlockFrameset] = rule_info("HtmlBlockFrameset", "HtmlBlockOpenFrameset (HtmlBlockFrameset | !HtmlBlockCloseFrameset .)* HtmlBlockCloseFrameset") + Rules[:_HtmlBlockOpenLi] = rule_info("HtmlBlockOpenLi", "\"<\" Spnl (\"li\" | \"LI\") Spnl HtmlAttribute* \">\"") + Rules[:_HtmlBlockCloseLi] = rule_info("HtmlBlockCloseLi", "\"<\" Spnl \"/\" (\"li\" | \"LI\") Spnl \">\"") + Rules[:_HtmlBlockLi] = rule_info("HtmlBlockLi", "HtmlBlockOpenLi (HtmlBlockLi | !HtmlBlockCloseLi .)* HtmlBlockCloseLi") + Rules[:_HtmlBlockOpenTbody] = rule_info("HtmlBlockOpenTbody", "\"<\" Spnl (\"tbody\" | \"TBODY\") Spnl HtmlAttribute* \">\"") + Rules[:_HtmlBlockCloseTbody] = rule_info("HtmlBlockCloseTbody", "\"<\" Spnl \"/\" (\"tbody\" | \"TBODY\") Spnl \">\"") + Rules[:_HtmlBlockTbody] = rule_info("HtmlBlockTbody", "HtmlBlockOpenTbody (HtmlBlockTbody | !HtmlBlockCloseTbody .)* HtmlBlockCloseTbody") + Rules[:_HtmlBlockOpenTd] = rule_info("HtmlBlockOpenTd", "\"<\" Spnl (\"td\" | \"TD\") Spnl HtmlAttribute* \">\"") + Rules[:_HtmlBlockCloseTd] = rule_info("HtmlBlockCloseTd", "\"<\" Spnl \"/\" (\"td\" | \"TD\") Spnl \">\"") + Rules[:_HtmlBlockTd] = rule_info("HtmlBlockTd", "HtmlBlockOpenTd (HtmlBlockTd | !HtmlBlockCloseTd .)* HtmlBlockCloseTd") + Rules[:_HtmlBlockOpenTfoot] = rule_info("HtmlBlockOpenTfoot", "\"<\" Spnl (\"tfoot\" | \"TFOOT\") Spnl HtmlAttribute* \">\"") + Rules[:_HtmlBlockCloseTfoot] = rule_info("HtmlBlockCloseTfoot", "\"<\" Spnl \"/\" (\"tfoot\" | \"TFOOT\") Spnl \">\"") + Rules[:_HtmlBlockTfoot] = rule_info("HtmlBlockTfoot", "HtmlBlockOpenTfoot (HtmlBlockTfoot | !HtmlBlockCloseTfoot .)* HtmlBlockCloseTfoot") + Rules[:_HtmlBlockOpenTh] = rule_info("HtmlBlockOpenTh", "\"<\" Spnl (\"th\" | \"TH\") Spnl HtmlAttribute* \">\"") + Rules[:_HtmlBlockCloseTh] = rule_info("HtmlBlockCloseTh", "\"<\" Spnl \"/\" (\"th\" | \"TH\") Spnl \">\"") + Rules[:_HtmlBlockTh] = rule_info("HtmlBlockTh", "HtmlBlockOpenTh (HtmlBlockTh | !HtmlBlockCloseTh .)* HtmlBlockCloseTh") + Rules[:_HtmlBlockOpenThead] = rule_info("HtmlBlockOpenThead", "\"<\" Spnl (\"thead\" | \"THEAD\") Spnl HtmlAttribute* \">\"") + Rules[:_HtmlBlockCloseThead] = rule_info("HtmlBlockCloseThead", "\"<\" Spnl \"/\" (\"thead\" | \"THEAD\") Spnl \">\"") + Rules[:_HtmlBlockThead] = rule_info("HtmlBlockThead", "HtmlBlockOpenThead (HtmlBlockThead | !HtmlBlockCloseThead .)* HtmlBlockCloseThead") + Rules[:_HtmlBlockOpenTr] = rule_info("HtmlBlockOpenTr", "\"<\" Spnl (\"tr\" | \"TR\") Spnl HtmlAttribute* \">\"") + Rules[:_HtmlBlockCloseTr] = rule_info("HtmlBlockCloseTr", "\"<\" Spnl \"/\" (\"tr\" | \"TR\") Spnl \">\"") + Rules[:_HtmlBlockTr] = rule_info("HtmlBlockTr", "HtmlBlockOpenTr (HtmlBlockTr | !HtmlBlockCloseTr .)* HtmlBlockCloseTr") + Rules[:_HtmlBlockOpenScript] = rule_info("HtmlBlockOpenScript", "\"<\" Spnl (\"script\" | \"SCRIPT\") Spnl HtmlAttribute* \">\"") + Rules[:_HtmlBlockCloseScript] = rule_info("HtmlBlockCloseScript", "\"<\" Spnl \"/\" (\"script\" | \"SCRIPT\") Spnl \">\"") + Rules[:_HtmlBlockScript] = rule_info("HtmlBlockScript", "HtmlBlockOpenScript (!HtmlBlockCloseScript .)* HtmlBlockCloseScript") + Rules[:_HtmlBlockOpenHead] = rule_info("HtmlBlockOpenHead", "\"<\" Spnl (\"head\" | \"HEAD\") Spnl HtmlAttribute* \">\"") + Rules[:_HtmlBlockCloseHead] = rule_info("HtmlBlockCloseHead", "\"<\" Spnl \"/\" (\"head\" | \"HEAD\") Spnl \">\"") + Rules[:_HtmlBlockHead] = rule_info("HtmlBlockHead", "HtmlBlockOpenHead (!HtmlBlockCloseHead .)* HtmlBlockCloseHead") + Rules[:_HtmlBlockInTags] = rule_info("HtmlBlockInTags", "(HtmlAnchor | HtmlBlockAddress | HtmlBlockBlockquote | HtmlBlockCenter | HtmlBlockDir | HtmlBlockDiv | HtmlBlockDl | HtmlBlockFieldset | HtmlBlockForm | HtmlBlockH1 | HtmlBlockH2 | HtmlBlockH3 | HtmlBlockH4 | HtmlBlockH5 | HtmlBlockH6 | HtmlBlockMenu | HtmlBlockNoframes | HtmlBlockNoscript | HtmlBlockOl | HtmlBlockP | HtmlBlockPre | HtmlBlockTable | HtmlBlockUl | HtmlBlockDd | HtmlBlockDt | HtmlBlockFrameset | HtmlBlockLi | HtmlBlockTbody | HtmlBlockTd | HtmlBlockTfoot | HtmlBlockTh | HtmlBlockThead | HtmlBlockTr | HtmlBlockScript | HtmlBlockHead)") + Rules[:_HtmlBlock] = rule_info("HtmlBlock", "< (HtmlBlockInTags | HtmlComment | HtmlBlockSelfClosing | HtmlUnclosed) > @BlankLine+ { if html? then RDoc::Markup::Raw.new text end }") + Rules[:_HtmlUnclosed] = rule_info("HtmlUnclosed", "\"<\" Spnl HtmlUnclosedType Spnl HtmlAttribute* Spnl \">\"") + Rules[:_HtmlUnclosedType] = rule_info("HtmlUnclosedType", "(\"HR\" | \"hr\")") + Rules[:_HtmlBlockSelfClosing] = rule_info("HtmlBlockSelfClosing", "\"<\" Spnl HtmlBlockType Spnl HtmlAttribute* \"/\" Spnl \">\"") + Rules[:_HtmlBlockType] = rule_info("HtmlBlockType", "(\"ADDRESS\" | \"BLOCKQUOTE\" | \"CENTER\" | \"DD\" | \"DIR\" | \"DIV\" | \"DL\" | \"DT\" | \"FIELDSET\" | \"FORM\" | \"FRAMESET\" | \"H1\" | \"H2\" | \"H3\" | \"H4\" | \"H5\" | \"H6\" | \"HR\" | \"ISINDEX\" | \"LI\" | \"MENU\" | \"NOFRAMES\" | \"NOSCRIPT\" | \"OL\" | \"P\" | \"PRE\" | \"SCRIPT\" | \"TABLE\" | \"TBODY\" | \"TD\" | \"TFOOT\" | \"TH\" | \"THEAD\" | \"TR\" | \"UL\" | \"address\" | \"blockquote\" | \"center\" | \"dd\" | \"dir\" | \"div\" | \"dl\" | \"dt\" | \"fieldset\" | \"form\" | \"frameset\" | \"h1\" | \"h2\" | \"h3\" | \"h4\" | \"h5\" | \"h6\" | \"hr\" | \"isindex\" | \"li\" | \"menu\" | \"noframes\" | \"noscript\" | \"ol\" | \"p\" | \"pre\" | \"script\" | \"table\" | \"tbody\" | \"td\" | \"tfoot\" | \"th\" | \"thead\" | \"tr\" | \"ul\")") + Rules[:_StyleOpen] = rule_info("StyleOpen", "\"<\" Spnl (\"style\" | \"STYLE\") Spnl HtmlAttribute* \">\"") + Rules[:_StyleClose] = rule_info("StyleClose", "\"<\" Spnl \"/\" (\"style\" | \"STYLE\") Spnl \">\"") + Rules[:_InStyleTags] = rule_info("InStyleTags", "StyleOpen (!StyleClose .)* StyleClose") + Rules[:_StyleBlock] = rule_info("StyleBlock", "< InStyleTags > @BlankLine* { if css? then RDoc::Markup::Raw.new text end }") + Rules[:_Inlines] = rule_info("Inlines", "(!@Endline Inline:i { i } | @Endline:c &Inline { c })+:chunks @Endline? { chunks }") + Rules[:_Inline] = rule_info("Inline", "(Str | @Endline | UlOrStarLine | @Space | Strong | Emph | Strike | Image | Link | NoteReference | InlineNote | Code | RawHtml | Entity | EscapedChar | Symbol)") + Rules[:_Space] = rule_info("Space", "@Spacechar+ { \" \" }") + Rules[:_Str] = rule_info("Str", "@StartList:a < @NormalChar+ > { a = text } (StrChunk:c { a << c })* { a }") + Rules[:_StrChunk] = rule_info("StrChunk", "< (@NormalChar | /_+/ &Alphanumeric)+ > { text }") + Rules[:_EscapedChar] = rule_info("EscapedChar", "\"\\\\\" !@Newline < /[:\\\\`|*_{}\\[\\]()\#+.!><-]/ > { text }") + Rules[:_Entity] = rule_info("Entity", "(HexEntity | DecEntity | CharEntity):a { a }") + Rules[:_Endline] = rule_info("Endline", "(@LineBreak | @TerminalEndline | @NormalEndline)") + Rules[:_NormalEndline] = rule_info("NormalEndline", "@Sp @Newline !@BlankLine !\">\" !AtxStart !(Line /={1,}|-{1,}/ @Newline) { \"\\n\" }") + Rules[:_TerminalEndline] = rule_info("TerminalEndline", "@Sp @Newline @Eof") + Rules[:_LineBreak] = rule_info("LineBreak", "\" \" @NormalEndline { RDoc::Markup::HardBreak.new }") + Rules[:_Symbol] = rule_info("Symbol", "< @SpecialChar > { text }") + Rules[:_UlOrStarLine] = rule_info("UlOrStarLine", "(UlLine | StarLine):a { a }") + Rules[:_StarLine] = rule_info("StarLine", "(< /\\*{4,}/ > { text } | < @Spacechar /\\*+/ &@Spacechar > { text })") + Rules[:_UlLine] = rule_info("UlLine", "(< /_{4,}/ > { text } | < @Spacechar /_+/ &@Spacechar > { text })") + Rules[:_Emph] = rule_info("Emph", "(EmphStar | EmphUl)") + Rules[:_Whitespace] = rule_info("Whitespace", "(@Spacechar | @Newline)") + Rules[:_EmphStar] = rule_info("EmphStar", "\"*\" !@Whitespace @StartList:a (!\"*\" Inline:b { a << b } | StrongStar:b { a << b })+ \"*\" { emphasis a.join }") + Rules[:_EmphUl] = rule_info("EmphUl", "\"_\" !@Whitespace @StartList:a (!\"_\" Inline:b { a << b } | StrongUl:b { a << b })+ \"_\" { emphasis a.join }") + Rules[:_Strong] = rule_info("Strong", "(StrongStar | StrongUl)") + Rules[:_StrongStar] = rule_info("StrongStar", "\"**\" !@Whitespace @StartList:a (!\"**\" Inline:b { a << b })+ \"**\" { strong a.join }") + Rules[:_StrongUl] = rule_info("StrongUl", "\"__\" !@Whitespace @StartList:a (!\"__\" Inline:b { a << b })+ \"__\" { strong a.join }") + Rules[:_Strike] = rule_info("Strike", "&{ strike? } \"~~\" !@Whitespace @StartList:a (!\"~~\" Inline:b { a << b })+ \"~~\" { strike a.join }") + Rules[:_Image] = rule_info("Image", "\"!\" (ExplicitLink | ReferenceLink):a { \"rdoc-image:\#{a[/\\[(.*)\\]/, 1]}\" }") + Rules[:_Link] = rule_info("Link", "(ExplicitLink | ReferenceLink | AutoLink)") + Rules[:_ReferenceLink] = rule_info("ReferenceLink", "(ReferenceLinkDouble | ReferenceLinkSingle)") + Rules[:_ReferenceLinkDouble] = rule_info("ReferenceLinkDouble", "Label:content < Spnl > !\"[]\" Label:label { link_to content, label, text }") + Rules[:_ReferenceLinkSingle] = rule_info("ReferenceLinkSingle", "Label:content < (Spnl \"[]\")? > { link_to content, content, text }") + Rules[:_ExplicitLink] = rule_info("ExplicitLink", "Label:l \"(\" @Sp Source:s Spnl Title @Sp \")\" { \"{\#{l}}[\#{s}]\" }") + Rules[:_Source] = rule_info("Source", "(\"<\" < SourceContents > \">\" | < SourceContents >) { text }") + Rules[:_SourceContents] = rule_info("SourceContents", "((!\"(\" !\")\" !\">\" Nonspacechar)+ | \"(\" SourceContents \")\")*") + Rules[:_Title] = rule_info("Title", "(TitleSingle | TitleDouble | \"\"):a { a }") + Rules[:_TitleSingle] = rule_info("TitleSingle", "\"'\" (!(\"'\" @Sp (\")\" | @Newline)) .)* \"'\"") + Rules[:_TitleDouble] = rule_info("TitleDouble", "\"\\\"\" (!(\"\\\"\" @Sp (\")\" | @Newline)) .)* \"\\\"\"") + Rules[:_AutoLink] = rule_info("AutoLink", "(AutoLinkUrl | AutoLinkEmail)") + Rules[:_AutoLinkUrl] = rule_info("AutoLinkUrl", "\"<\" < /[A-Za-z]+/ \"://\" (!@Newline !\">\" .)+ > \">\" { text }") + Rules[:_AutoLinkEmail] = rule_info("AutoLinkEmail", "\"<\" \"mailto:\"? < /[\\w+.\\/!%~$-]+/i \"@\" (!@Newline !\">\" .)+ > \">\" { \"mailto:\#{text}\" }") + Rules[:_Reference] = rule_info("Reference", "@NonindentSpace !\"[]\" Label:label \":\" Spnl RefSrc:link RefTitle @BlankLine+ { \# TODO use title reference label, link nil }") + Rules[:_Label] = rule_info("Label", "\"[\" (!\"^\" &{ notes? } | &. &{ !notes? }) @StartList:a (!\"]\" Inline:l { a << l })* \"]\" { a.join.gsub(/\\s+/, ' ') }") + Rules[:_RefSrc] = rule_info("RefSrc", "< Nonspacechar+ > { text }") + Rules[:_RefTitle] = rule_info("RefTitle", "(RefTitleSingle | RefTitleDouble | RefTitleParens | EmptyTitle)") + Rules[:_EmptyTitle] = rule_info("EmptyTitle", "\"\"") + Rules[:_RefTitleSingle] = rule_info("RefTitleSingle", "Spnl \"'\" < (!(\"'\" @Sp @Newline | @Newline) .)* > \"'\" { text }") + Rules[:_RefTitleDouble] = rule_info("RefTitleDouble", "Spnl \"\\\"\" < (!(\"\\\"\" @Sp @Newline | @Newline) .)* > \"\\\"\" { text }") + Rules[:_RefTitleParens] = rule_info("RefTitleParens", "Spnl \"(\" < (!(\")\" @Sp @Newline | @Newline) .)* > \")\" { text }") + Rules[:_References] = rule_info("References", "(Reference | SkipBlock)*") + Rules[:_Ticks1] = rule_info("Ticks1", "\"`\" !\"`\"") + Rules[:_Ticks2] = rule_info("Ticks2", "\"``\" !\"`\"") + Rules[:_Ticks3] = rule_info("Ticks3", "\"```\" !\"`\"") + Rules[:_Ticks4] = rule_info("Ticks4", "\"````\" !\"`\"") + Rules[:_Ticks5] = rule_info("Ticks5", "\"`````\" !\"`\"") + Rules[:_Code] = rule_info("Code", "(Ticks1 @Sp < ((!\"`\" Nonspacechar)+ | !Ticks1 /`+/ | !(@Sp Ticks1) (@Spacechar | @Newline !@BlankLine))+ > @Sp Ticks1 | Ticks2 @Sp < ((!\"`\" Nonspacechar)+ | !Ticks2 /`+/ | !(@Sp Ticks2) (@Spacechar | @Newline !@BlankLine))+ > @Sp Ticks2 | Ticks3 @Sp < ((!\"`\" Nonspacechar)+ | !Ticks3 /`+/ | !(@Sp Ticks3) (@Spacechar | @Newline !@BlankLine))+ > @Sp Ticks3 | Ticks4 @Sp < ((!\"`\" Nonspacechar)+ | !Ticks4 /`+/ | !(@Sp Ticks4) (@Spacechar | @Newline !@BlankLine))+ > @Sp Ticks4 | Ticks5 @Sp < ((!\"`\" Nonspacechar)+ | !Ticks5 /`+/ | !(@Sp Ticks5) (@Spacechar | @Newline !@BlankLine))+ > @Sp Ticks5) { \"<code>\#{text}</code>\" }") + Rules[:_RawHtml] = rule_info("RawHtml", "< (HtmlComment | HtmlBlockScript | HtmlTag) > { if html? then text else '' end }") + Rules[:_BlankLine] = rule_info("BlankLine", "@Sp @Newline { \"\\n\" }") + Rules[:_Quoted] = rule_info("Quoted", "(\"\\\"\" (!\"\\\"\" .)* \"\\\"\" | \"'\" (!\"'\" .)* \"'\")") + Rules[:_HtmlAttribute] = rule_info("HtmlAttribute", "(AlphanumericAscii | \"-\")+ Spnl (\"=\" Spnl (Quoted | (!\">\" Nonspacechar)+))? Spnl") + Rules[:_HtmlComment] = rule_info("HtmlComment", "\"<!--\" (!\"-->\" .)* \"-->\"") + Rules[:_HtmlTag] = rule_info("HtmlTag", "\"<\" Spnl \"/\"? AlphanumericAscii+ Spnl HtmlAttribute* \"/\"? Spnl \">\"") + Rules[:_Eof] = rule_info("Eof", "!.") + Rules[:_Nonspacechar] = rule_info("Nonspacechar", "!@Spacechar !@Newline .") + Rules[:_Sp] = rule_info("Sp", "@Spacechar*") + Rules[:_Spnl] = rule_info("Spnl", "@Sp (@Newline @Sp)?") + Rules[:_SpecialChar] = rule_info("SpecialChar", "(/[~*_`&\\[\\]()<!\#\\\\'\"]/ | @ExtendedSpecialChar)") + Rules[:_NormalChar] = rule_info("NormalChar", "!(@SpecialChar | @Spacechar | @Newline) .") + Rules[:_Digit] = rule_info("Digit", "[0-9]") + Rules[:_Alphanumeric] = rule_info("Alphanumeric", "%literals.Alphanumeric") + Rules[:_AlphanumericAscii] = rule_info("AlphanumericAscii", "%literals.AlphanumericAscii") + Rules[:_BOM] = rule_info("BOM", "%literals.BOM") + Rules[:_Newline] = rule_info("Newline", "%literals.Newline") + Rules[:_Spacechar] = rule_info("Spacechar", "%literals.Spacechar") + Rules[:_HexEntity] = rule_info("HexEntity", "/&\#x/i < /[0-9a-fA-F]+/ > \";\" { [text.to_i(16)].pack 'U' }") + Rules[:_DecEntity] = rule_info("DecEntity", "\"&\#\" < /[0-9]+/ > \";\" { [text.to_i].pack 'U' }") + Rules[:_CharEntity] = rule_info("CharEntity", "\"&\" < /[A-Za-z0-9]+/ > \";\" { if entity = HTML_ENTITIES[text] then entity.pack 'U*' else \"&\#{text};\" end }") + Rules[:_NonindentSpace] = rule_info("NonindentSpace", "/ {0,3}/") + Rules[:_Indent] = rule_info("Indent", "/\\t| /") + Rules[:_IndentedLine] = rule_info("IndentedLine", "Indent Line") + Rules[:_OptionallyIndentedLine] = rule_info("OptionallyIndentedLine", "Indent? Line") + Rules[:_StartList] = rule_info("StartList", "&. { [] }") + Rules[:_Line] = rule_info("Line", "@RawLine:a { a }") + Rules[:_RawLine] = rule_info("RawLine", "(< (!\"\\r\" !\"\\n\" .)* @Newline > | < .+ > @Eof) { text }") + Rules[:_SkipBlock] = rule_info("SkipBlock", "(HtmlBlock | (!\"\#\" !SetextBottom1 !SetextBottom2 !@BlankLine @RawLine)+ @BlankLine* | @BlankLine+ | @RawLine)") + Rules[:_ExtendedSpecialChar] = rule_info("ExtendedSpecialChar", "&{ notes? } \"^\"") + Rules[:_NoteReference] = rule_info("NoteReference", "&{ notes? } RawNoteReference:ref { note_for ref }") + Rules[:_RawNoteReference] = rule_info("RawNoteReference", "\"[^\" < (!@Newline !\"]\" .)+ > \"]\" { text }") + Rules[:_Note] = rule_info("Note", "&{ notes? } @NonindentSpace RawNoteReference:ref \":\" @Sp @StartList:a RawNoteBlock:i { a.concat i } (&Indent RawNoteBlock:i { a.concat i })* { @footnotes[ref] = paragraph a nil }") + Rules[:_InlineNote] = rule_info("InlineNote", "&{ notes? } \"^[\" @StartList:a (!\"]\" Inline:l { a << l })+ \"]\" { ref = [:inline, @note_order.length] @footnotes[ref] = paragraph a note_for ref }") + Rules[:_Notes] = rule_info("Notes", "(Note | SkipBlock)*") + Rules[:_RawNoteBlock] = rule_info("RawNoteBlock", "@StartList:a (!@BlankLine OptionallyIndentedLine:l { a << l })+ < @BlankLine* > { a << text } { a }") + Rules[:_CodeFence] = rule_info("CodeFence", "&{ github? } Ticks3 (@Sp StrChunk:format)? Spnl < ((!\"`\" Nonspacechar)+ | !Ticks3 /`+/ | Spacechar | @Newline)+ > Ticks3 @Sp @Newline* { verbatim = RDoc::Markup::Verbatim.new text verbatim.format = format.intern if format.instance_of?(String) verbatim }") + Rules[:_DefinitionList] = rule_info("DefinitionList", "&{ definition_lists? } DefinitionListItem+:list { RDoc::Markup::List.new :NOTE, *list.flatten }") + Rules[:_DefinitionListItem] = rule_info("DefinitionListItem", "DefinitionListLabel+:label DefinitionListDefinition+:defns { list_items = [] list_items << RDoc::Markup::ListItem.new(label, defns.shift) list_items.concat defns.map { |defn| RDoc::Markup::ListItem.new nil, defn } unless list_items.empty? list_items }") + Rules[:_DefinitionListLabel] = rule_info("DefinitionListLabel", "StrChunk:label @Sp @Newline { label }") + Rules[:_DefinitionListDefinition] = rule_info("DefinitionListDefinition", "@NonindentSpace \":\" @Space Inlines:a @BlankLine+ { paragraph a }") + # :startdoc: +end diff --git a/lib/rdoc/markdown/entities.rb b/lib/rdoc/markdown/entities.rb new file mode 100644 index 0000000000..d2cf610293 --- /dev/null +++ b/lib/rdoc/markdown/entities.rb @@ -0,0 +1,2132 @@ +# frozen_string_literal: true +## +# HTML entity name map for RDoc::Markdown + +RDoc::Markdown::HTML_ENTITIES = { + "AElig" => [0x000C6], + "AMP" => [0x00026], + "Aacute" => [0x000C1], + "Abreve" => [0x00102], + "Acirc" => [0x000C2], + "Acy" => [0x00410], + "Afr" => [0x1D504], + "Agrave" => [0x000C0], + "Alpha" => [0x00391], + "Amacr" => [0x00100], + "And" => [0x02A53], + "Aogon" => [0x00104], + "Aopf" => [0x1D538], + "ApplyFunction" => [0x02061], + "Aring" => [0x000C5], + "Ascr" => [0x1D49C], + "Assign" => [0x02254], + "Atilde" => [0x000C3], + "Auml" => [0x000C4], + "Backslash" => [0x02216], + "Barv" => [0x02AE7], + "Barwed" => [0x02306], + "Bcy" => [0x00411], + "Because" => [0x02235], + "Bernoullis" => [0x0212C], + "Beta" => [0x00392], + "Bfr" => [0x1D505], + "Bopf" => [0x1D539], + "Breve" => [0x002D8], + "Bscr" => [0x0212C], + "Bumpeq" => [0x0224E], + "CHcy" => [0x00427], + "COPY" => [0x000A9], + "Cacute" => [0x00106], + "Cap" => [0x022D2], + "CapitalDifferentialD" => [0x02145], + "Cayleys" => [0x0212D], + "Ccaron" => [0x0010C], + "Ccedil" => [0x000C7], + "Ccirc" => [0x00108], + "Cconint" => [0x02230], + "Cdot" => [0x0010A], + "Cedilla" => [0x000B8], + "CenterDot" => [0x000B7], + "Cfr" => [0x0212D], + "Chi" => [0x003A7], + "CircleDot" => [0x02299], + "CircleMinus" => [0x02296], + "CirclePlus" => [0x02295], + "CircleTimes" => [0x02297], + "ClockwiseContourIntegral" => [0x02232], + "CloseCurlyDoubleQuote" => [0x0201D], + "CloseCurlyQuote" => [0x02019], + "Colon" => [0x02237], + "Colone" => [0x02A74], + "Congruent" => [0x02261], + "Conint" => [0x0222F], + "ContourIntegral" => [0x0222E], + "Copf" => [0x02102], + "Coproduct" => [0x02210], + "CounterClockwiseContourIntegral" => [0x02233], + "Cross" => [0x02A2F], + "Cscr" => [0x1D49E], + "Cup" => [0x022D3], + "CupCap" => [0x0224D], + "DD" => [0x02145], + "DDotrahd" => [0x02911], + "DJcy" => [0x00402], + "DScy" => [0x00405], + "DZcy" => [0x0040F], + "Dagger" => [0x02021], + "Darr" => [0x021A1], + "Dashv" => [0x02AE4], + "Dcaron" => [0x0010E], + "Dcy" => [0x00414], + "Del" => [0x02207], + "Delta" => [0x00394], + "Dfr" => [0x1D507], + "DiacriticalAcute" => [0x000B4], + "DiacriticalDot" => [0x002D9], + "DiacriticalDoubleAcute" => [0x002DD], + "DiacriticalGrave" => [0x00060], + "DiacriticalTilde" => [0x002DC], + "Diamond" => [0x022C4], + "DifferentialD" => [0x02146], + "Dopf" => [0x1D53B], + "Dot" => [0x000A8], + "DotDot" => [0x020DC], + "DotEqual" => [0x02250], + "DoubleContourIntegral" => [0x0222F], + "DoubleDot" => [0x000A8], + "DoubleDownArrow" => [0x021D3], + "DoubleLeftArrow" => [0x021D0], + "DoubleLeftRightArrow" => [0x021D4], + "DoubleLeftTee" => [0x02AE4], + "DoubleLongLeftArrow" => [0x027F8], + "DoubleLongLeftRightArrow" => [0x027FA], + "DoubleLongRightArrow" => [0x027F9], + "DoubleRightArrow" => [0x021D2], + "DoubleRightTee" => [0x022A8], + "DoubleUpArrow" => [0x021D1], + "DoubleUpDownArrow" => [0x021D5], + "DoubleVerticalBar" => [0x02225], + "DownArrow" => [0x02193], + "DownArrowBar" => [0x02913], + "DownArrowUpArrow" => [0x021F5], + "DownBreve" => [0x00311], + "DownLeftRightVector" => [0x02950], + "DownLeftTeeVector" => [0x0295E], + "DownLeftVector" => [0x021BD], + "DownLeftVectorBar" => [0x02956], + "DownRightTeeVector" => [0x0295F], + "DownRightVector" => [0x021C1], + "DownRightVectorBar" => [0x02957], + "DownTee" => [0x022A4], + "DownTeeArrow" => [0x021A7], + "Downarrow" => [0x021D3], + "Dscr" => [0x1D49F], + "Dstrok" => [0x00110], + "ENG" => [0x0014A], + "ETH" => [0x000D0], + "Eacute" => [0x000C9], + "Ecaron" => [0x0011A], + "Ecirc" => [0x000CA], + "Ecy" => [0x0042D], + "Edot" => [0x00116], + "Efr" => [0x1D508], + "Egrave" => [0x000C8], + "Element" => [0x02208], + "Emacr" => [0x00112], + "EmptySmallSquare" => [0x025FB], + "EmptyVerySmallSquare" => [0x025AB], + "Eogon" => [0x00118], + "Eopf" => [0x1D53C], + "Epsilon" => [0x00395], + "Equal" => [0x02A75], + "EqualTilde" => [0x02242], + "Equilibrium" => [0x021CC], + "Escr" => [0x02130], + "Esim" => [0x02A73], + "Eta" => [0x00397], + "Euml" => [0x000CB], + "Exists" => [0x02203], + "ExponentialE" => [0x02147], + "Fcy" => [0x00424], + "Ffr" => [0x1D509], + "FilledSmallSquare" => [0x025FC], + "FilledVerySmallSquare" => [0x025AA], + "Fopf" => [0x1D53D], + "ForAll" => [0x02200], + "Fouriertrf" => [0x02131], + "Fscr" => [0x02131], + "GJcy" => [0x00403], + "GT" => [0x0003E], + "Gamma" => [0x00393], + "Gammad" => [0x003DC], + "Gbreve" => [0x0011E], + "Gcedil" => [0x00122], + "Gcirc" => [0x0011C], + "Gcy" => [0x00413], + "Gdot" => [0x00120], + "Gfr" => [0x1D50A], + "Gg" => [0x022D9], + "Gopf" => [0x1D53E], + "GreaterEqual" => [0x02265], + "GreaterEqualLess" => [0x022DB], + "GreaterFullEqual" => [0x02267], + "GreaterGreater" => [0x02AA2], + "GreaterLess" => [0x02277], + "GreaterSlantEqual" => [0x02A7E], + "GreaterTilde" => [0x02273], + "Gscr" => [0x1D4A2], + "Gt" => [0x0226B], + "HARDcy" => [0x0042A], + "Hacek" => [0x002C7], + "Hat" => [0x0005E], + "Hcirc" => [0x00124], + "Hfr" => [0x0210C], + "HilbertSpace" => [0x0210B], + "Hopf" => [0x0210D], + "HorizontalLine" => [0x02500], + "Hscr" => [0x0210B], + "Hstrok" => [0x00126], + "HumpDownHump" => [0x0224E], + "HumpEqual" => [0x0224F], + "IEcy" => [0x00415], + "IJlig" => [0x00132], + "IOcy" => [0x00401], + "Iacute" => [0x000CD], + "Icirc" => [0x000CE], + "Icy" => [0x00418], + "Idot" => [0x00130], + "Ifr" => [0x02111], + "Igrave" => [0x000CC], + "Im" => [0x02111], + "Imacr" => [0x0012A], + "ImaginaryI" => [0x02148], + "Implies" => [0x021D2], + "Int" => [0x0222C], + "Integral" => [0x0222B], + "Intersection" => [0x022C2], + "InvisibleComma" => [0x02063], + "InvisibleTimes" => [0x02062], + "Iogon" => [0x0012E], + "Iopf" => [0x1D540], + "Iota" => [0x00399], + "Iscr" => [0x02110], + "Itilde" => [0x00128], + "Iukcy" => [0x00406], + "Iuml" => [0x000CF], + "Jcirc" => [0x00134], + "Jcy" => [0x00419], + "Jfr" => [0x1D50D], + "Jopf" => [0x1D541], + "Jscr" => [0x1D4A5], + "Jsercy" => [0x00408], + "Jukcy" => [0x00404], + "KHcy" => [0x00425], + "KJcy" => [0x0040C], + "Kappa" => [0x0039A], + "Kcedil" => [0x00136], + "Kcy" => [0x0041A], + "Kfr" => [0x1D50E], + "Kopf" => [0x1D542], + "Kscr" => [0x1D4A6], + "LJcy" => [0x00409], + "LT" => [0x0003C], + "Lacute" => [0x00139], + "Lambda" => [0x0039B], + "Lang" => [0x027EA], + "Laplacetrf" => [0x02112], + "Larr" => [0x0219E], + "Lcaron" => [0x0013D], + "Lcedil" => [0x0013B], + "Lcy" => [0x0041B], + "LeftAngleBracket" => [0x027E8], + "LeftArrow" => [0x02190], + "LeftArrowBar" => [0x021E4], + "LeftArrowRightArrow" => [0x021C6], + "LeftCeiling" => [0x02308], + "LeftDoubleBracket" => [0x027E6], + "LeftDownTeeVector" => [0x02961], + "LeftDownVector" => [0x021C3], + "LeftDownVectorBar" => [0x02959], + "LeftFloor" => [0x0230A], + "LeftRightArrow" => [0x02194], + "LeftRightVector" => [0x0294E], + "LeftTee" => [0x022A3], + "LeftTeeArrow" => [0x021A4], + "LeftTeeVector" => [0x0295A], + "LeftTriangle" => [0x022B2], + "LeftTriangleBar" => [0x029CF], + "LeftTriangleEqual" => [0x022B4], + "LeftUpDownVector" => [0x02951], + "LeftUpTeeVector" => [0x02960], + "LeftUpVector" => [0x021BF], + "LeftUpVectorBar" => [0x02958], + "LeftVector" => [0x021BC], + "LeftVectorBar" => [0x02952], + "Leftarrow" => [0x021D0], + "Leftrightarrow" => [0x021D4], + "LessEqualGreater" => [0x022DA], + "LessFullEqual" => [0x02266], + "LessGreater" => [0x02276], + "LessLess" => [0x02AA1], + "LessSlantEqual" => [0x02A7D], + "LessTilde" => [0x02272], + "Lfr" => [0x1D50F], + "Ll" => [0x022D8], + "Lleftarrow" => [0x021DA], + "Lmidot" => [0x0013F], + "LongLeftArrow" => [0x027F5], + "LongLeftRightArrow" => [0x027F7], + "LongRightArrow" => [0x027F6], + "Longleftarrow" => [0x027F8], + "Longleftrightarrow" => [0x027FA], + "Longrightarrow" => [0x027F9], + "Lopf" => [0x1D543], + "LowerLeftArrow" => [0x02199], + "LowerRightArrow" => [0x02198], + "Lscr" => [0x02112], + "Lsh" => [0x021B0], + "Lstrok" => [0x00141], + "Lt" => [0x0226A], + "Map" => [0x02905], + "Mcy" => [0x0041C], + "MediumSpace" => [0x0205F], + "Mellintrf" => [0x02133], + "Mfr" => [0x1D510], + "MinusPlus" => [0x02213], + "Mopf" => [0x1D544], + "Mscr" => [0x02133], + "Mu" => [0x0039C], + "NJcy" => [0x0040A], + "Nacute" => [0x00143], + "Ncaron" => [0x00147], + "Ncedil" => [0x00145], + "Ncy" => [0x0041D], + "NegativeMediumSpace" => [0x0200B], + "NegativeThickSpace" => [0x0200B], + "NegativeThinSpace" => [0x0200B], + "NegativeVeryThinSpace" => [0x0200B], + "NestedGreaterGreater" => [0x0226B], + "NestedLessLess" => [0x0226A], + "NewLine" => [0x0000A], + "Nfr" => [0x1D511], + "NoBreak" => [0x02060], + "NonBreakingSpace" => [0x000A0], + "Nopf" => [0x02115], + "Not" => [0x02AEC], + "NotCongruent" => [0x02262], + "NotCupCap" => [0x0226D], + "NotDoubleVerticalBar" => [0x02226], + "NotElement" => [0x02209], + "NotEqual" => [0x02260], + "NotEqualTilde" => [0x02242, 0x00338], + "NotExists" => [0x02204], + "NotGreater" => [0x0226F], + "NotGreaterEqual" => [0x02271], + "NotGreaterFullEqual" => [0x02267, 0x00338], + "NotGreaterGreater" => [0x0226B, 0x00338], + "NotGreaterLess" => [0x02279], + "NotGreaterSlantEqual" => [0x02A7E, 0x00338], + "NotGreaterTilde" => [0x02275], + "NotHumpDownHump" => [0x0224E, 0x00338], + "NotHumpEqual" => [0x0224F, 0x00338], + "NotLeftTriangle" => [0x022EA], + "NotLeftTriangleBar" => [0x029CF, 0x00338], + "NotLeftTriangleEqual" => [0x022EC], + "NotLess" => [0x0226E], + "NotLessEqual" => [0x02270], + "NotLessGreater" => [0x02278], + "NotLessLess" => [0x0226A, 0x00338], + "NotLessSlantEqual" => [0x02A7D, 0x00338], + "NotLessTilde" => [0x02274], + "NotNestedGreaterGreater" => [0x02AA2, 0x00338], + "NotNestedLessLess" => [0x02AA1, 0x00338], + "NotPrecedes" => [0x02280], + "NotPrecedesEqual" => [0x02AAF, 0x00338], + "NotPrecedesSlantEqual" => [0x022E0], + "NotReverseElement" => [0x0220C], + "NotRightTriangle" => [0x022EB], + "NotRightTriangleBar" => [0x029D0, 0x00338], + "NotRightTriangleEqual" => [0x022ED], + "NotSquareSubset" => [0x0228F, 0x00338], + "NotSquareSubsetEqual" => [0x022E2], + "NotSquareSuperset" => [0x02290, 0x00338], + "NotSquareSupersetEqual" => [0x022E3], + "NotSubset" => [0x02282, 0x020D2], + "NotSubsetEqual" => [0x02288], + "NotSucceeds" => [0x02281], + "NotSucceedsEqual" => [0x02AB0, 0x00338], + "NotSucceedsSlantEqual" => [0x022E1], + "NotSucceedsTilde" => [0x0227F, 0x00338], + "NotSuperset" => [0x02283, 0x020D2], + "NotSupersetEqual" => [0x02289], + "NotTilde" => [0x02241], + "NotTildeEqual" => [0x02244], + "NotTildeFullEqual" => [0x02247], + "NotTildeTilde" => [0x02249], + "NotVerticalBar" => [0x02224], + "Nscr" => [0x1D4A9], + "Ntilde" => [0x000D1], + "Nu" => [0x0039D], + "OElig" => [0x00152], + "Oacute" => [0x000D3], + "Ocirc" => [0x000D4], + "Ocy" => [0x0041E], + "Odblac" => [0x00150], + "Ofr" => [0x1D512], + "Ograve" => [0x000D2], + "Omacr" => [0x0014C], + "Omega" => [0x003A9], + "Omicron" => [0x0039F], + "Oopf" => [0x1D546], + "OpenCurlyDoubleQuote" => [0x0201C], + "OpenCurlyQuote" => [0x02018], + "Or" => [0x02A54], + "Oscr" => [0x1D4AA], + "Oslash" => [0x000D8], + "Otilde" => [0x000D5], + "Otimes" => [0x02A37], + "Ouml" => [0x000D6], + "OverBar" => [0x0203E], + "OverBrace" => [0x023DE], + "OverBracket" => [0x023B4], + "OverParenthesis" => [0x023DC], + "PartialD" => [0x02202], + "Pcy" => [0x0041F], + "Pfr" => [0x1D513], + "Phi" => [0x003A6], + "Pi" => [0x003A0], + "PlusMinus" => [0x000B1], + "Poincareplane" => [0x0210C], + "Popf" => [0x02119], + "Pr" => [0x02ABB], + "Precedes" => [0x0227A], + "PrecedesEqual" => [0x02AAF], + "PrecedesSlantEqual" => [0x0227C], + "PrecedesTilde" => [0x0227E], + "Prime" => [0x02033], + "Product" => [0x0220F], + "Proportion" => [0x02237], + "Proportional" => [0x0221D], + "Pscr" => [0x1D4AB], + "Psi" => [0x003A8], + "QUOT" => [0x00022], + "Qfr" => [0x1D514], + "Qopf" => [0x0211A], + "Qscr" => [0x1D4AC], + "RBarr" => [0x02910], + "REG" => [0x000AE], + "Racute" => [0x00154], + "Rang" => [0x027EB], + "Rarr" => [0x021A0], + "Rarrtl" => [0x02916], + "Rcaron" => [0x00158], + "Rcedil" => [0x00156], + "Rcy" => [0x00420], + "Re" => [0x0211C], + "ReverseElement" => [0x0220B], + "ReverseEquilibrium" => [0x021CB], + "ReverseUpEquilibrium" => [0x0296F], + "Rfr" => [0x0211C], + "Rho" => [0x003A1], + "RightAngleBracket" => [0x027E9], + "RightArrow" => [0x02192], + "RightArrowBar" => [0x021E5], + "RightArrowLeftArrow" => [0x021C4], + "RightCeiling" => [0x02309], + "RightDoubleBracket" => [0x027E7], + "RightDownTeeVector" => [0x0295D], + "RightDownVector" => [0x021C2], + "RightDownVectorBar" => [0x02955], + "RightFloor" => [0x0230B], + "RightTee" => [0x022A2], + "RightTeeArrow" => [0x021A6], + "RightTeeVector" => [0x0295B], + "RightTriangle" => [0x022B3], + "RightTriangleBar" => [0x029D0], + "RightTriangleEqual" => [0x022B5], + "RightUpDownVector" => [0x0294F], + "RightUpTeeVector" => [0x0295C], + "RightUpVector" => [0x021BE], + "RightUpVectorBar" => [0x02954], + "RightVector" => [0x021C0], + "RightVectorBar" => [0x02953], + "Rightarrow" => [0x021D2], + "Ropf" => [0x0211D], + "RoundImplies" => [0x02970], + "Rrightarrow" => [0x021DB], + "Rscr" => [0x0211B], + "Rsh" => [0x021B1], + "RuleDelayed" => [0x029F4], + "SHCHcy" => [0x00429], + "SHcy" => [0x00428], + "SOFTcy" => [0x0042C], + "Sacute" => [0x0015A], + "Sc" => [0x02ABC], + "Scaron" => [0x00160], + "Scedil" => [0x0015E], + "Scirc" => [0x0015C], + "Scy" => [0x00421], + "Sfr" => [0x1D516], + "ShortDownArrow" => [0x02193], + "ShortLeftArrow" => [0x02190], + "ShortRightArrow" => [0x02192], + "ShortUpArrow" => [0x02191], + "Sigma" => [0x003A3], + "SmallCircle" => [0x02218], + "Sopf" => [0x1D54A], + "Sqrt" => [0x0221A], + "Square" => [0x025A1], + "SquareIntersection" => [0x02293], + "SquareSubset" => [0x0228F], + "SquareSubsetEqual" => [0x02291], + "SquareSuperset" => [0x02290], + "SquareSupersetEqual" => [0x02292], + "SquareUnion" => [0x02294], + "Sscr" => [0x1D4AE], + "Star" => [0x022C6], + "Sub" => [0x022D0], + "Subset" => [0x022D0], + "SubsetEqual" => [0x02286], + "Succeeds" => [0x0227B], + "SucceedsEqual" => [0x02AB0], + "SucceedsSlantEqual" => [0x0227D], + "SucceedsTilde" => [0x0227F], + "SuchThat" => [0x0220B], + "Sum" => [0x02211], + "Sup" => [0x022D1], + "Superset" => [0x02283], + "SupersetEqual" => [0x02287], + "Supset" => [0x022D1], + "THORN" => [0x000DE], + "TRADE" => [0x02122], + "TSHcy" => [0x0040B], + "TScy" => [0x00426], + "Tab" => [0x00009], + "Tau" => [0x003A4], + "Tcaron" => [0x00164], + "Tcedil" => [0x00162], + "Tcy" => [0x00422], + "Tfr" => [0x1D517], + "Therefore" => [0x02234], + "Theta" => [0x00398], + "ThickSpace" => [0x0205F, 0x0200A], + "ThinSpace" => [0x02009], + "Tilde" => [0x0223C], + "TildeEqual" => [0x02243], + "TildeFullEqual" => [0x02245], + "TildeTilde" => [0x02248], + "Topf" => [0x1D54B], + "TripleDot" => [0x020DB], + "Tscr" => [0x1D4AF], + "Tstrok" => [0x00166], + "Uacute" => [0x000DA], + "Uarr" => [0x0219F], + "Uarrocir" => [0x02949], + "Ubrcy" => [0x0040E], + "Ubreve" => [0x0016C], + "Ucirc" => [0x000DB], + "Ucy" => [0x00423], + "Udblac" => [0x00170], + "Ufr" => [0x1D518], + "Ugrave" => [0x000D9], + "Umacr" => [0x0016A], + "UnderBar" => [0x0005F], + "UnderBrace" => [0x023DF], + "UnderBracket" => [0x023B5], + "UnderParenthesis" => [0x023DD], + "Union" => [0x022C3], + "UnionPlus" => [0x0228E], + "Uogon" => [0x00172], + "Uopf" => [0x1D54C], + "UpArrow" => [0x02191], + "UpArrowBar" => [0x02912], + "UpArrowDownArrow" => [0x021C5], + "UpDownArrow" => [0x02195], + "UpEquilibrium" => [0x0296E], + "UpTee" => [0x022A5], + "UpTeeArrow" => [0x021A5], + "Uparrow" => [0x021D1], + "Updownarrow" => [0x021D5], + "UpperLeftArrow" => [0x02196], + "UpperRightArrow" => [0x02197], + "Upsi" => [0x003D2], + "Upsilon" => [0x003A5], + "Uring" => [0x0016E], + "Uscr" => [0x1D4B0], + "Utilde" => [0x00168], + "Uuml" => [0x000DC], + "VDash" => [0x022AB], + "Vbar" => [0x02AEB], + "Vcy" => [0x00412], + "Vdash" => [0x022A9], + "Vdashl" => [0x02AE6], + "Vee" => [0x022C1], + "Verbar" => [0x02016], + "Vert" => [0x02016], + "VerticalBar" => [0x02223], + "VerticalLine" => [0x0007C], + "VerticalSeparator" => [0x02758], + "VerticalTilde" => [0x02240], + "VeryThinSpace" => [0x0200A], + "Vfr" => [0x1D519], + "Vopf" => [0x1D54D], + "Vscr" => [0x1D4B1], + "Vvdash" => [0x022AA], + "Wcirc" => [0x00174], + "Wedge" => [0x022C0], + "Wfr" => [0x1D51A], + "Wopf" => [0x1D54E], + "Wscr" => [0x1D4B2], + "Xfr" => [0x1D51B], + "Xi" => [0x0039E], + "Xopf" => [0x1D54F], + "Xscr" => [0x1D4B3], + "YAcy" => [0x0042F], + "YIcy" => [0x00407], + "YUcy" => [0x0042E], + "Yacute" => [0x000DD], + "Ycirc" => [0x00176], + "Ycy" => [0x0042B], + "Yfr" => [0x1D51C], + "Yopf" => [0x1D550], + "Yscr" => [0x1D4B4], + "Yuml" => [0x00178], + "ZHcy" => [0x00416], + "Zacute" => [0x00179], + "Zcaron" => [0x0017D], + "Zcy" => [0x00417], + "Zdot" => [0x0017B], + "ZeroWidthSpace" => [0x0200B], + "Zeta" => [0x00396], + "Zfr" => [0x02128], + "Zopf" => [0x02124], + "Zscr" => [0x1D4B5], + "aacute" => [0x000E1], + "abreve" => [0x00103], + "ac" => [0x0223E], + "acE" => [0x0223E, 0x00333], + "acd" => [0x0223F], + "acirc" => [0x000E2], + "acute" => [0x000B4], + "acy" => [0x00430], + "aelig" => [0x000E6], + "af" => [0x02061], + "afr" => [0x1D51E], + "agrave" => [0x000E0], + "alefsym" => [0x02135], + "aleph" => [0x02135], + "alpha" => [0x003B1], + "amacr" => [0x00101], + "amalg" => [0x02A3F], + "amp" => [0x00026], + "and" => [0x02227], + "andand" => [0x02A55], + "andd" => [0x02A5C], + "andslope" => [0x02A58], + "andv" => [0x02A5A], + "ang" => [0x02220], + "ange" => [0x029A4], + "angle" => [0x02220], + "angmsd" => [0x02221], + "angmsdaa" => [0x029A8], + "angmsdab" => [0x029A9], + "angmsdac" => [0x029AA], + "angmsdad" => [0x029AB], + "angmsdae" => [0x029AC], + "angmsdaf" => [0x029AD], + "angmsdag" => [0x029AE], + "angmsdah" => [0x029AF], + "angrt" => [0x0221F], + "angrtvb" => [0x022BE], + "angrtvbd" => [0x0299D], + "angsph" => [0x02222], + "angst" => [0x000C5], + "angzarr" => [0x0237C], + "aogon" => [0x00105], + "aopf" => [0x1D552], + "ap" => [0x02248], + "apE" => [0x02A70], + "apacir" => [0x02A6F], + "ape" => [0x0224A], + "apid" => [0x0224B], + "apos" => [0x00027], + "approx" => [0x02248], + "approxeq" => [0x0224A], + "aring" => [0x000E5], + "ascr" => [0x1D4B6], + "ast" => [0x0002A], + "asymp" => [0x02248], + "asympeq" => [0x0224D], + "atilde" => [0x000E3], + "auml" => [0x000E4], + "awconint" => [0x02233], + "awint" => [0x02A11], + "bNot" => [0x02AED], + "backcong" => [0x0224C], + "backepsilon" => [0x003F6], + "backprime" => [0x02035], + "backsim" => [0x0223D], + "backsimeq" => [0x022CD], + "barvee" => [0x022BD], + "barwed" => [0x02305], + "barwedge" => [0x02305], + "bbrk" => [0x023B5], + "bbrktbrk" => [0x023B6], + "bcong" => [0x0224C], + "bcy" => [0x00431], + "bdquo" => [0x0201E], + "becaus" => [0x02235], + "because" => [0x02235], + "bemptyv" => [0x029B0], + "bepsi" => [0x003F6], + "bernou" => [0x0212C], + "beta" => [0x003B2], + "beth" => [0x02136], + "between" => [0x0226C], + "bfr" => [0x1D51F], + "bigcap" => [0x022C2], + "bigcirc" => [0x025EF], + "bigcup" => [0x022C3], + "bigodot" => [0x02A00], + "bigoplus" => [0x02A01], + "bigotimes" => [0x02A02], + "bigsqcup" => [0x02A06], + "bigstar" => [0x02605], + "bigtriangledown" => [0x025BD], + "bigtriangleup" => [0x025B3], + "biguplus" => [0x02A04], + "bigvee" => [0x022C1], + "bigwedge" => [0x022C0], + "bkarow" => [0x0290D], + "blacklozenge" => [0x029EB], + "blacksquare" => [0x025AA], + "blacktriangle" => [0x025B4], + "blacktriangledown" => [0x025BE], + "blacktriangleleft" => [0x025C2], + "blacktriangleright" => [0x025B8], + "blank" => [0x02423], + "blk12" => [0x02592], + "blk14" => [0x02591], + "blk34" => [0x02593], + "block" => [0x02588], + "bne" => [0x0003D, 0x020E5], + "bnequiv" => [0x02261, 0x020E5], + "bnot" => [0x02310], + "bopf" => [0x1D553], + "bot" => [0x022A5], + "bottom" => [0x022A5], + "bowtie" => [0x022C8], + "boxDL" => [0x02557], + "boxDR" => [0x02554], + "boxDl" => [0x02556], + "boxDr" => [0x02553], + "boxH" => [0x02550], + "boxHD" => [0x02566], + "boxHU" => [0x02569], + "boxHd" => [0x02564], + "boxHu" => [0x02567], + "boxUL" => [0x0255D], + "boxUR" => [0x0255A], + "boxUl" => [0x0255C], + "boxUr" => [0x02559], + "boxV" => [0x02551], + "boxVH" => [0x0256C], + "boxVL" => [0x02563], + "boxVR" => [0x02560], + "boxVh" => [0x0256B], + "boxVl" => [0x02562], + "boxVr" => [0x0255F], + "boxbox" => [0x029C9], + "boxdL" => [0x02555], + "boxdR" => [0x02552], + "boxdl" => [0x02510], + "boxdr" => [0x0250C], + "boxh" => [0x02500], + "boxhD" => [0x02565], + "boxhU" => [0x02568], + "boxhd" => [0x0252C], + "boxhu" => [0x02534], + "boxminus" => [0x0229F], + "boxplus" => [0x0229E], + "boxtimes" => [0x022A0], + "boxuL" => [0x0255B], + "boxuR" => [0x02558], + "boxul" => [0x02518], + "boxur" => [0x02514], + "boxv" => [0x02502], + "boxvH" => [0x0256A], + "boxvL" => [0x02561], + "boxvR" => [0x0255E], + "boxvh" => [0x0253C], + "boxvl" => [0x02524], + "boxvr" => [0x0251C], + "bprime" => [0x02035], + "breve" => [0x002D8], + "brvbar" => [0x000A6], + "bscr" => [0x1D4B7], + "bsemi" => [0x0204F], + "bsim" => [0x0223D], + "bsime" => [0x022CD], + "bsol" => [0x0005C], + "bsolb" => [0x029C5], + "bsolhsub" => [0x027C8], + "bull" => [0x02022], + "bullet" => [0x02022], + "bump" => [0x0224E], + "bumpE" => [0x02AAE], + "bumpe" => [0x0224F], + "bumpeq" => [0x0224F], + "cacute" => [0x00107], + "cap" => [0x02229], + "capand" => [0x02A44], + "capbrcup" => [0x02A49], + "capcap" => [0x02A4B], + "capcup" => [0x02A47], + "capdot" => [0x02A40], + "caps" => [0x02229, 0x0FE00], + "caret" => [0x02041], + "caron" => [0x002C7], + "ccaps" => [0x02A4D], + "ccaron" => [0x0010D], + "ccedil" => [0x000E7], + "ccirc" => [0x00109], + "ccups" => [0x02A4C], + "ccupssm" => [0x02A50], + "cdot" => [0x0010B], + "cedil" => [0x000B8], + "cemptyv" => [0x029B2], + "cent" => [0x000A2], + "centerdot" => [0x000B7], + "cfr" => [0x1D520], + "chcy" => [0x00447], + "check" => [0x02713], + "checkmark" => [0x02713], + "chi" => [0x003C7], + "cir" => [0x025CB], + "cirE" => [0x029C3], + "circ" => [0x002C6], + "circeq" => [0x02257], + "circlearrowleft" => [0x021BA], + "circlearrowright" => [0x021BB], + "circledR" => [0x000AE], + "circledS" => [0x024C8], + "circledast" => [0x0229B], + "circledcirc" => [0x0229A], + "circleddash" => [0x0229D], + "cire" => [0x02257], + "cirfnint" => [0x02A10], + "cirmid" => [0x02AEF], + "cirscir" => [0x029C2], + "clubs" => [0x02663], + "clubsuit" => [0x02663], + "colon" => [0x0003A], + "colone" => [0x02254], + "coloneq" => [0x02254], + "comma" => [0x0002C], + "commat" => [0x00040], + "comp" => [0x02201], + "compfn" => [0x02218], + "complement" => [0x02201], + "complexes" => [0x02102], + "cong" => [0x02245], + "congdot" => [0x02A6D], + "conint" => [0x0222E], + "copf" => [0x1D554], + "coprod" => [0x02210], + "copy" => [0x000A9], + "copysr" => [0x02117], + "crarr" => [0x021B5], + "cross" => [0x02717], + "cscr" => [0x1D4B8], + "csub" => [0x02ACF], + "csube" => [0x02AD1], + "csup" => [0x02AD0], + "csupe" => [0x02AD2], + "ctdot" => [0x022EF], + "cudarrl" => [0x02938], + "cudarrr" => [0x02935], + "cuepr" => [0x022DE], + "cuesc" => [0x022DF], + "cularr" => [0x021B6], + "cularrp" => [0x0293D], + "cup" => [0x0222A], + "cupbrcap" => [0x02A48], + "cupcap" => [0x02A46], + "cupcup" => [0x02A4A], + "cupdot" => [0x0228D], + "cupor" => [0x02A45], + "cups" => [0x0222A, 0x0FE00], + "curarr" => [0x021B7], + "curarrm" => [0x0293C], + "curlyeqprec" => [0x022DE], + "curlyeqsucc" => [0x022DF], + "curlyvee" => [0x022CE], + "curlywedge" => [0x022CF], + "curren" => [0x000A4], + "curvearrowleft" => [0x021B6], + "curvearrowright" => [0x021B7], + "cuvee" => [0x022CE], + "cuwed" => [0x022CF], + "cwconint" => [0x02232], + "cwint" => [0x02231], + "cylcty" => [0x0232D], + "dArr" => [0x021D3], + "dHar" => [0x02965], + "dagger" => [0x02020], + "daleth" => [0x02138], + "darr" => [0x02193], + "dash" => [0x02010], + "dashv" => [0x022A3], + "dbkarow" => [0x0290F], + "dblac" => [0x002DD], + "dcaron" => [0x0010F], + "dcy" => [0x00434], + "dd" => [0x02146], + "ddagger" => [0x02021], + "ddarr" => [0x021CA], + "ddotseq" => [0x02A77], + "deg" => [0x000B0], + "delta" => [0x003B4], + "demptyv" => [0x029B1], + "dfisht" => [0x0297F], + "dfr" => [0x1D521], + "dharl" => [0x021C3], + "dharr" => [0x021C2], + "diam" => [0x022C4], + "diamond" => [0x022C4], + "diamondsuit" => [0x02666], + "diams" => [0x02666], + "die" => [0x000A8], + "digamma" => [0x003DD], + "disin" => [0x022F2], + "div" => [0x000F7], + "divide" => [0x000F7], + "divideontimes" => [0x022C7], + "divonx" => [0x022C7], + "djcy" => [0x00452], + "dlcorn" => [0x0231E], + "dlcrop" => [0x0230D], + "dollar" => [0x00024], + "dopf" => [0x1D555], + "dot" => [0x002D9], + "doteq" => [0x02250], + "doteqdot" => [0x02251], + "dotminus" => [0x02238], + "dotplus" => [0x02214], + "dotsquare" => [0x022A1], + "doublebarwedge" => [0x02306], + "downarrow" => [0x02193], + "downdownarrows" => [0x021CA], + "downharpoonleft" => [0x021C3], + "downharpoonright" => [0x021C2], + "drbkarow" => [0x02910], + "drcorn" => [0x0231F], + "drcrop" => [0x0230C], + "dscr" => [0x1D4B9], + "dscy" => [0x00455], + "dsol" => [0x029F6], + "dstrok" => [0x00111], + "dtdot" => [0x022F1], + "dtri" => [0x025BF], + "dtrif" => [0x025BE], + "duarr" => [0x021F5], + "duhar" => [0x0296F], + "dwangle" => [0x029A6], + "dzcy" => [0x0045F], + "dzigrarr" => [0x027FF], + "eDDot" => [0x02A77], + "eDot" => [0x02251], + "eacute" => [0x000E9], + "easter" => [0x02A6E], + "ecaron" => [0x0011B], + "ecir" => [0x02256], + "ecirc" => [0x000EA], + "ecolon" => [0x02255], + "ecy" => [0x0044D], + "edot" => [0x00117], + "ee" => [0x02147], + "efDot" => [0x02252], + "efr" => [0x1D522], + "eg" => [0x02A9A], + "egrave" => [0x000E8], + "egs" => [0x02A96], + "egsdot" => [0x02A98], + "el" => [0x02A99], + "elinters" => [0x023E7], + "ell" => [0x02113], + "els" => [0x02A95], + "elsdot" => [0x02A97], + "emacr" => [0x00113], + "empty" => [0x02205], + "emptyset" => [0x02205], + "emptyv" => [0x02205], + "emsp" => [0x02003], + "emsp13" => [0x02004], + "emsp14" => [0x02005], + "eng" => [0x0014B], + "ensp" => [0x02002], + "eogon" => [0x00119], + "eopf" => [0x1D556], + "epar" => [0x022D5], + "eparsl" => [0x029E3], + "eplus" => [0x02A71], + "epsi" => [0x003B5], + "epsilon" => [0x003B5], + "epsiv" => [0x003F5], + "eqcirc" => [0x02256], + "eqcolon" => [0x02255], + "eqsim" => [0x02242], + "eqslantgtr" => [0x02A96], + "eqslantless" => [0x02A95], + "equals" => [0x0003D], + "equest" => [0x0225F], + "equiv" => [0x02261], + "equivDD" => [0x02A78], + "eqvparsl" => [0x029E5], + "erDot" => [0x02253], + "erarr" => [0x02971], + "escr" => [0x0212F], + "esdot" => [0x02250], + "esim" => [0x02242], + "eta" => [0x003B7], + "eth" => [0x000F0], + "euml" => [0x000EB], + "euro" => [0x020AC], + "excl" => [0x00021], + "exist" => [0x02203], + "expectation" => [0x02130], + "exponentiale" => [0x02147], + "fallingdotseq" => [0x02252], + "fcy" => [0x00444], + "female" => [0x02640], + "ffilig" => [0x0FB03], + "fflig" => [0x0FB00], + "ffllig" => [0x0FB04], + "ffr" => [0x1D523], + "filig" => [0x0FB01], + "fjlig" => [0x00066, 0x0006A], + "flat" => [0x0266D], + "fllig" => [0x0FB02], + "fltns" => [0x025B1], + "fnof" => [0x00192], + "fopf" => [0x1D557], + "forall" => [0x02200], + "fork" => [0x022D4], + "forkv" => [0x02AD9], + "fpartint" => [0x02A0D], + "frac12" => [0x000BD], + "frac13" => [0x02153], + "frac14" => [0x000BC], + "frac15" => [0x02155], + "frac16" => [0x02159], + "frac18" => [0x0215B], + "frac23" => [0x02154], + "frac25" => [0x02156], + "frac34" => [0x000BE], + "frac35" => [0x02157], + "frac38" => [0x0215C], + "frac45" => [0x02158], + "frac56" => [0x0215A], + "frac58" => [0x0215D], + "frac78" => [0x0215E], + "frasl" => [0x02044], + "frown" => [0x02322], + "fscr" => [0x1D4BB], + "gE" => [0x02267], + "gEl" => [0x02A8C], + "gacute" => [0x001F5], + "gamma" => [0x003B3], + "gammad" => [0x003DD], + "gap" => [0x02A86], + "gbreve" => [0x0011F], + "gcirc" => [0x0011D], + "gcy" => [0x00433], + "gdot" => [0x00121], + "ge" => [0x02265], + "gel" => [0x022DB], + "geq" => [0x02265], + "geqq" => [0x02267], + "geqslant" => [0x02A7E], + "ges" => [0x02A7E], + "gescc" => [0x02AA9], + "gesdot" => [0x02A80], + "gesdoto" => [0x02A82], + "gesdotol" => [0x02A84], + "gesl" => [0x022DB, 0x0FE00], + "gesles" => [0x02A94], + "gfr" => [0x1D524], + "gg" => [0x0226B], + "ggg" => [0x022D9], + "gimel" => [0x02137], + "gjcy" => [0x00453], + "gl" => [0x02277], + "glE" => [0x02A92], + "gla" => [0x02AA5], + "glj" => [0x02AA4], + "gnE" => [0x02269], + "gnap" => [0x02A8A], + "gnapprox" => [0x02A8A], + "gne" => [0x02A88], + "gneq" => [0x02A88], + "gneqq" => [0x02269], + "gnsim" => [0x022E7], + "gopf" => [0x1D558], + "grave" => [0x00060], + "gscr" => [0x0210A], + "gsim" => [0x02273], + "gsime" => [0x02A8E], + "gsiml" => [0x02A90], + "gt" => [0x0003E], + "gtcc" => [0x02AA7], + "gtcir" => [0x02A7A], + "gtdot" => [0x022D7], + "gtlPar" => [0x02995], + "gtquest" => [0x02A7C], + "gtrapprox" => [0x02A86], + "gtrarr" => [0x02978], + "gtrdot" => [0x022D7], + "gtreqless" => [0x022DB], + "gtreqqless" => [0x02A8C], + "gtrless" => [0x02277], + "gtrsim" => [0x02273], + "gvertneqq" => [0x02269, 0x0FE00], + "gvnE" => [0x02269, 0x0FE00], + "hArr" => [0x021D4], + "hairsp" => [0x0200A], + "half" => [0x000BD], + "hamilt" => [0x0210B], + "hardcy" => [0x0044A], + "harr" => [0x02194], + "harrcir" => [0x02948], + "harrw" => [0x021AD], + "hbar" => [0x0210F], + "hcirc" => [0x00125], + "hearts" => [0x02665], + "heartsuit" => [0x02665], + "hellip" => [0x02026], + "hercon" => [0x022B9], + "hfr" => [0x1D525], + "hksearow" => [0x02925], + "hkswarow" => [0x02926], + "hoarr" => [0x021FF], + "homtht" => [0x0223B], + "hookleftarrow" => [0x021A9], + "hookrightarrow" => [0x021AA], + "hopf" => [0x1D559], + "horbar" => [0x02015], + "hscr" => [0x1D4BD], + "hslash" => [0x0210F], + "hstrok" => [0x00127], + "hybull" => [0x02043], + "hyphen" => [0x02010], + "iacute" => [0x000ED], + "ic" => [0x02063], + "icirc" => [0x000EE], + "icy" => [0x00438], + "iecy" => [0x00435], + "iexcl" => [0x000A1], + "iff" => [0x021D4], + "ifr" => [0x1D526], + "igrave" => [0x000EC], + "ii" => [0x02148], + "iiiint" => [0x02A0C], + "iiint" => [0x0222D], + "iinfin" => [0x029DC], + "iiota" => [0x02129], + "ijlig" => [0x00133], + "imacr" => [0x0012B], + "image" => [0x02111], + "imagline" => [0x02110], + "imagpart" => [0x02111], + "imath" => [0x00131], + "imof" => [0x022B7], + "imped" => [0x001B5], + "in" => [0x02208], + "incare" => [0x02105], + "infin" => [0x0221E], + "infintie" => [0x029DD], + "inodot" => [0x00131], + "int" => [0x0222B], + "intcal" => [0x022BA], + "integers" => [0x02124], + "intercal" => [0x022BA], + "intlarhk" => [0x02A17], + "intprod" => [0x02A3C], + "iocy" => [0x00451], + "iogon" => [0x0012F], + "iopf" => [0x1D55A], + "iota" => [0x003B9], + "iprod" => [0x02A3C], + "iquest" => [0x000BF], + "iscr" => [0x1D4BE], + "isin" => [0x02208], + "isinE" => [0x022F9], + "isindot" => [0x022F5], + "isins" => [0x022F4], + "isinsv" => [0x022F3], + "isinv" => [0x02208], + "it" => [0x02062], + "itilde" => [0x00129], + "iukcy" => [0x00456], + "iuml" => [0x000EF], + "jcirc" => [0x00135], + "jcy" => [0x00439], + "jfr" => [0x1D527], + "jmath" => [0x00237], + "jopf" => [0x1D55B], + "jscr" => [0x1D4BF], + "jsercy" => [0x00458], + "jukcy" => [0x00454], + "kappa" => [0x003BA], + "kappav" => [0x003F0], + "kcedil" => [0x00137], + "kcy" => [0x0043A], + "kfr" => [0x1D528], + "kgreen" => [0x00138], + "khcy" => [0x00445], + "kjcy" => [0x0045C], + "kopf" => [0x1D55C], + "kscr" => [0x1D4C0], + "lAarr" => [0x021DA], + "lArr" => [0x021D0], + "lAtail" => [0x0291B], + "lBarr" => [0x0290E], + "lE" => [0x02266], + "lEg" => [0x02A8B], + "lHar" => [0x02962], + "lacute" => [0x0013A], + "laemptyv" => [0x029B4], + "lagran" => [0x02112], + "lambda" => [0x003BB], + "lang" => [0x027E8], + "langd" => [0x02991], + "langle" => [0x027E8], + "lap" => [0x02A85], + "laquo" => [0x000AB], + "larr" => [0x02190], + "larrb" => [0x021E4], + "larrbfs" => [0x0291F], + "larrfs" => [0x0291D], + "larrhk" => [0x021A9], + "larrlp" => [0x021AB], + "larrpl" => [0x02939], + "larrsim" => [0x02973], + "larrtl" => [0x021A2], + "lat" => [0x02AAB], + "latail" => [0x02919], + "late" => [0x02AAD], + "lates" => [0x02AAD, 0x0FE00], + "lbarr" => [0x0290C], + "lbbrk" => [0x02772], + "lbrace" => [0x0007B], + "lbrack" => [0x0005B], + "lbrke" => [0x0298B], + "lbrksld" => [0x0298F], + "lbrkslu" => [0x0298D], + "lcaron" => [0x0013E], + "lcedil" => [0x0013C], + "lceil" => [0x02308], + "lcub" => [0x0007B], + "lcy" => [0x0043B], + "ldca" => [0x02936], + "ldquo" => [0x0201C], + "ldquor" => [0x0201E], + "ldrdhar" => [0x02967], + "ldrushar" => [0x0294B], + "ldsh" => [0x021B2], + "le" => [0x02264], + "leftarrow" => [0x02190], + "leftarrowtail" => [0x021A2], + "leftharpoondown" => [0x021BD], + "leftharpoonup" => [0x021BC], + "leftleftarrows" => [0x021C7], + "leftrightarrow" => [0x02194], + "leftrightarrows" => [0x021C6], + "leftrightharpoons" => [0x021CB], + "leftrightsquigarrow" => [0x021AD], + "leftthreetimes" => [0x022CB], + "leg" => [0x022DA], + "leq" => [0x02264], + "leqq" => [0x02266], + "leqslant" => [0x02A7D], + "les" => [0x02A7D], + "lescc" => [0x02AA8], + "lesdot" => [0x02A7F], + "lesdoto" => [0x02A81], + "lesdotor" => [0x02A83], + "lesg" => [0x022DA, 0x0FE00], + "lesges" => [0x02A93], + "lessapprox" => [0x02A85], + "lessdot" => [0x022D6], + "lesseqgtr" => [0x022DA], + "lesseqqgtr" => [0x02A8B], + "lessgtr" => [0x02276], + "lesssim" => [0x02272], + "lfisht" => [0x0297C], + "lfloor" => [0x0230A], + "lfr" => [0x1D529], + "lg" => [0x02276], + "lgE" => [0x02A91], + "lhard" => [0x021BD], + "lharu" => [0x021BC], + "lharul" => [0x0296A], + "lhblk" => [0x02584], + "ljcy" => [0x00459], + "ll" => [0x0226A], + "llarr" => [0x021C7], + "llcorner" => [0x0231E], + "llhard" => [0x0296B], + "lltri" => [0x025FA], + "lmidot" => [0x00140], + "lmoust" => [0x023B0], + "lmoustache" => [0x023B0], + "lnE" => [0x02268], + "lnap" => [0x02A89], + "lnapprox" => [0x02A89], + "lne" => [0x02A87], + "lneq" => [0x02A87], + "lneqq" => [0x02268], + "lnsim" => [0x022E6], + "loang" => [0x027EC], + "loarr" => [0x021FD], + "lobrk" => [0x027E6], + "longleftarrow" => [0x027F5], + "longleftrightarrow" => [0x027F7], + "longmapsto" => [0x027FC], + "longrightarrow" => [0x027F6], + "looparrowleft" => [0x021AB], + "looparrowright" => [0x021AC], + "lopar" => [0x02985], + "lopf" => [0x1D55D], + "loplus" => [0x02A2D], + "lotimes" => [0x02A34], + "lowast" => [0x02217], + "lowbar" => [0x0005F], + "loz" => [0x025CA], + "lozenge" => [0x025CA], + "lozf" => [0x029EB], + "lpar" => [0x00028], + "lparlt" => [0x02993], + "lrarr" => [0x021C6], + "lrcorner" => [0x0231F], + "lrhar" => [0x021CB], + "lrhard" => [0x0296D], + "lrm" => [0x0200E], + "lrtri" => [0x022BF], + "lsaquo" => [0x02039], + "lscr" => [0x1D4C1], + "lsh" => [0x021B0], + "lsim" => [0x02272], + "lsime" => [0x02A8D], + "lsimg" => [0x02A8F], + "lsqb" => [0x0005B], + "lsquo" => [0x02018], + "lsquor" => [0x0201A], + "lstrok" => [0x00142], + "lt" => [0x0003C], + "ltcc" => [0x02AA6], + "ltcir" => [0x02A79], + "ltdot" => [0x022D6], + "lthree" => [0x022CB], + "ltimes" => [0x022C9], + "ltlarr" => [0x02976], + "ltquest" => [0x02A7B], + "ltrPar" => [0x02996], + "ltri" => [0x025C3], + "ltrie" => [0x022B4], + "ltrif" => [0x025C2], + "lurdshar" => [0x0294A], + "luruhar" => [0x02966], + "lvertneqq" => [0x02268, 0x0FE00], + "lvnE" => [0x02268, 0x0FE00], + "mDDot" => [0x0223A], + "macr" => [0x000AF], + "male" => [0x02642], + "malt" => [0x02720], + "maltese" => [0x02720], + "map" => [0x021A6], + "mapsto" => [0x021A6], + "mapstodown" => [0x021A7], + "mapstoleft" => [0x021A4], + "mapstoup" => [0x021A5], + "marker" => [0x025AE], + "mcomma" => [0x02A29], + "mcy" => [0x0043C], + "mdash" => [0x02014], + "measuredangle" => [0x02221], + "mfr" => [0x1D52A], + "mho" => [0x02127], + "micro" => [0x000B5], + "mid" => [0x02223], + "midast" => [0x0002A], + "midcir" => [0x02AF0], + "middot" => [0x000B7], + "minus" => [0x02212], + "minusb" => [0x0229F], + "minusd" => [0x02238], + "minusdu" => [0x02A2A], + "mlcp" => [0x02ADB], + "mldr" => [0x02026], + "mnplus" => [0x02213], + "models" => [0x022A7], + "mopf" => [0x1D55E], + "mp" => [0x02213], + "mscr" => [0x1D4C2], + "mstpos" => [0x0223E], + "mu" => [0x003BC], + "multimap" => [0x022B8], + "mumap" => [0x022B8], + "nGg" => [0x022D9, 0x00338], + "nGt" => [0x0226B, 0x020D2], + "nGtv" => [0x0226B, 0x00338], + "nLeftarrow" => [0x021CD], + "nLeftrightarrow" => [0x021CE], + "nLl" => [0x022D8, 0x00338], + "nLt" => [0x0226A, 0x020D2], + "nLtv" => [0x0226A, 0x00338], + "nRightarrow" => [0x021CF], + "nVDash" => [0x022AF], + "nVdash" => [0x022AE], + "nabla" => [0x02207], + "nacute" => [0x00144], + "nang" => [0x02220, 0x020D2], + "nap" => [0x02249], + "napE" => [0x02A70, 0x00338], + "napid" => [0x0224B, 0x00338], + "napos" => [0x00149], + "napprox" => [0x02249], + "natur" => [0x0266E], + "natural" => [0x0266E], + "naturals" => [0x02115], + "nbsp" => [0x000A0], + "nbump" => [0x0224E, 0x00338], + "nbumpe" => [0x0224F, 0x00338], + "ncap" => [0x02A43], + "ncaron" => [0x00148], + "ncedil" => [0x00146], + "ncong" => [0x02247], + "ncongdot" => [0x02A6D, 0x00338], + "ncup" => [0x02A42], + "ncy" => [0x0043D], + "ndash" => [0x02013], + "ne" => [0x02260], + "neArr" => [0x021D7], + "nearhk" => [0x02924], + "nearr" => [0x02197], + "nearrow" => [0x02197], + "nedot" => [0x02250, 0x00338], + "nequiv" => [0x02262], + "nesear" => [0x02928], + "nesim" => [0x02242, 0x00338], + "nexist" => [0x02204], + "nexists" => [0x02204], + "nfr" => [0x1D52B], + "ngE" => [0x02267, 0x00338], + "nge" => [0x02271], + "ngeq" => [0x02271], + "ngeqq" => [0x02267, 0x00338], + "ngeqslant" => [0x02A7E, 0x00338], + "nges" => [0x02A7E, 0x00338], + "ngsim" => [0x02275], + "ngt" => [0x0226F], + "ngtr" => [0x0226F], + "nhArr" => [0x021CE], + "nharr" => [0x021AE], + "nhpar" => [0x02AF2], + "ni" => [0x0220B], + "nis" => [0x022FC], + "nisd" => [0x022FA], + "niv" => [0x0220B], + "njcy" => [0x0045A], + "nlArr" => [0x021CD], + "nlE" => [0x02266, 0x00338], + "nlarr" => [0x0219A], + "nldr" => [0x02025], + "nle" => [0x02270], + "nleftarrow" => [0x0219A], + "nleftrightarrow" => [0x021AE], + "nleq" => [0x02270], + "nleqq" => [0x02266, 0x00338], + "nleqslant" => [0x02A7D, 0x00338], + "nles" => [0x02A7D, 0x00338], + "nless" => [0x0226E], + "nlsim" => [0x02274], + "nlt" => [0x0226E], + "nltri" => [0x022EA], + "nltrie" => [0x022EC], + "nmid" => [0x02224], + "nopf" => [0x1D55F], + "not" => [0x000AC], + "notin" => [0x02209], + "notinE" => [0x022F9, 0x00338], + "notindot" => [0x022F5, 0x00338], + "notinva" => [0x02209], + "notinvb" => [0x022F7], + "notinvc" => [0x022F6], + "notni" => [0x0220C], + "notniva" => [0x0220C], + "notnivb" => [0x022FE], + "notnivc" => [0x022FD], + "npar" => [0x02226], + "nparallel" => [0x02226], + "nparsl" => [0x02AFD, 0x020E5], + "npart" => [0x02202, 0x00338], + "npolint" => [0x02A14], + "npr" => [0x02280], + "nprcue" => [0x022E0], + "npre" => [0x02AAF, 0x00338], + "nprec" => [0x02280], + "npreceq" => [0x02AAF, 0x00338], + "nrArr" => [0x021CF], + "nrarr" => [0x0219B], + "nrarrc" => [0x02933, 0x00338], + "nrarrw" => [0x0219D, 0x00338], + "nrightarrow" => [0x0219B], + "nrtri" => [0x022EB], + "nrtrie" => [0x022ED], + "nsc" => [0x02281], + "nsccue" => [0x022E1], + "nsce" => [0x02AB0, 0x00338], + "nscr" => [0x1D4C3], + "nshortmid" => [0x02224], + "nshortparallel" => [0x02226], + "nsim" => [0x02241], + "nsime" => [0x02244], + "nsimeq" => [0x02244], + "nsmid" => [0x02224], + "nspar" => [0x02226], + "nsqsube" => [0x022E2], + "nsqsupe" => [0x022E3], + "nsub" => [0x02284], + "nsubE" => [0x02AC5, 0x00338], + "nsube" => [0x02288], + "nsubset" => [0x02282, 0x020D2], + "nsubseteq" => [0x02288], + "nsubseteqq" => [0x02AC5, 0x00338], + "nsucc" => [0x02281], + "nsucceq" => [0x02AB0, 0x00338], + "nsup" => [0x02285], + "nsupE" => [0x02AC6, 0x00338], + "nsupe" => [0x02289], + "nsupset" => [0x02283, 0x020D2], + "nsupseteq" => [0x02289], + "nsupseteqq" => [0x02AC6, 0x00338], + "ntgl" => [0x02279], + "ntilde" => [0x000F1], + "ntlg" => [0x02278], + "ntriangleleft" => [0x022EA], + "ntrianglelefteq" => [0x022EC], + "ntriangleright" => [0x022EB], + "ntrianglerighteq" => [0x022ED], + "nu" => [0x003BD], + "num" => [0x00023], + "numero" => [0x02116], + "numsp" => [0x02007], + "nvDash" => [0x022AD], + "nvHarr" => [0x02904], + "nvap" => [0x0224D, 0x020D2], + "nvdash" => [0x022AC], + "nvge" => [0x02265, 0x020D2], + "nvgt" => [0x0003E, 0x020D2], + "nvinfin" => [0x029DE], + "nvlArr" => [0x02902], + "nvle" => [0x02264, 0x020D2], + "nvlt" => [0x0003C, 0x020D2], + "nvltrie" => [0x022B4, 0x020D2], + "nvrArr" => [0x02903], + "nvrtrie" => [0x022B5, 0x020D2], + "nvsim" => [0x0223C, 0x020D2], + "nwArr" => [0x021D6], + "nwarhk" => [0x02923], + "nwarr" => [0x02196], + "nwarrow" => [0x02196], + "nwnear" => [0x02927], + "oS" => [0x024C8], + "oacute" => [0x000F3], + "oast" => [0x0229B], + "ocir" => [0x0229A], + "ocirc" => [0x000F4], + "ocy" => [0x0043E], + "odash" => [0x0229D], + "odblac" => [0x00151], + "odiv" => [0x02A38], + "odot" => [0x02299], + "odsold" => [0x029BC], + "oelig" => [0x00153], + "ofcir" => [0x029BF], + "ofr" => [0x1D52C], + "ogon" => [0x002DB], + "ograve" => [0x000F2], + "ogt" => [0x029C1], + "ohbar" => [0x029B5], + "ohm" => [0x003A9], + "oint" => [0x0222E], + "olarr" => [0x021BA], + "olcir" => [0x029BE], + "olcross" => [0x029BB], + "oline" => [0x0203E], + "olt" => [0x029C0], + "omacr" => [0x0014D], + "omega" => [0x003C9], + "omicron" => [0x003BF], + "omid" => [0x029B6], + "ominus" => [0x02296], + "oopf" => [0x1D560], + "opar" => [0x029B7], + "operp" => [0x029B9], + "oplus" => [0x02295], + "or" => [0x02228], + "orarr" => [0x021BB], + "ord" => [0x02A5D], + "order" => [0x02134], + "orderof" => [0x02134], + "ordf" => [0x000AA], + "ordm" => [0x000BA], + "origof" => [0x022B6], + "oror" => [0x02A56], + "orslope" => [0x02A57], + "orv" => [0x02A5B], + "oscr" => [0x02134], + "oslash" => [0x000F8], + "osol" => [0x02298], + "otilde" => [0x000F5], + "otimes" => [0x02297], + "otimesas" => [0x02A36], + "ouml" => [0x000F6], + "ovbar" => [0x0233D], + "par" => [0x02225], + "para" => [0x000B6], + "parallel" => [0x02225], + "parsim" => [0x02AF3], + "parsl" => [0x02AFD], + "part" => [0x02202], + "pcy" => [0x0043F], + "percnt" => [0x00025], + "period" => [0x0002E], + "permil" => [0x02030], + "perp" => [0x022A5], + "pertenk" => [0x02031], + "pfr" => [0x1D52D], + "phi" => [0x003C6], + "phiv" => [0x003D5], + "phmmat" => [0x02133], + "phone" => [0x0260E], + "pi" => [0x003C0], + "pitchfork" => [0x022D4], + "piv" => [0x003D6], + "planck" => [0x0210F], + "planckh" => [0x0210E], + "plankv" => [0x0210F], + "plus" => [0x0002B], + "plusacir" => [0x02A23], + "plusb" => [0x0229E], + "pluscir" => [0x02A22], + "plusdo" => [0x02214], + "plusdu" => [0x02A25], + "pluse" => [0x02A72], + "plusmn" => [0x000B1], + "plussim" => [0x02A26], + "plustwo" => [0x02A27], + "pm" => [0x000B1], + "pointint" => [0x02A15], + "popf" => [0x1D561], + "pound" => [0x000A3], + "pr" => [0x0227A], + "prE" => [0x02AB3], + "prap" => [0x02AB7], + "prcue" => [0x0227C], + "pre" => [0x02AAF], + "prec" => [0x0227A], + "precapprox" => [0x02AB7], + "preccurlyeq" => [0x0227C], + "preceq" => [0x02AAF], + "precnapprox" => [0x02AB9], + "precneqq" => [0x02AB5], + "precnsim" => [0x022E8], + "precsim" => [0x0227E], + "prime" => [0x02032], + "primes" => [0x02119], + "prnE" => [0x02AB5], + "prnap" => [0x02AB9], + "prnsim" => [0x022E8], + "prod" => [0x0220F], + "profalar" => [0x0232E], + "profline" => [0x02312], + "profsurf" => [0x02313], + "prop" => [0x0221D], + "propto" => [0x0221D], + "prsim" => [0x0227E], + "prurel" => [0x022B0], + "pscr" => [0x1D4C5], + "psi" => [0x003C8], + "puncsp" => [0x02008], + "qfr" => [0x1D52E], + "qint" => [0x02A0C], + "qopf" => [0x1D562], + "qprime" => [0x02057], + "qscr" => [0x1D4C6], + "quaternions" => [0x0210D], + "quatint" => [0x02A16], + "quest" => [0x0003F], + "questeq" => [0x0225F], + "quot" => [0x00022], + "rAarr" => [0x021DB], + "rArr" => [0x021D2], + "rAtail" => [0x0291C], + "rBarr" => [0x0290F], + "rHar" => [0x02964], + "race" => [0x0223D, 0x00331], + "racute" => [0x00155], + "radic" => [0x0221A], + "raemptyv" => [0x029B3], + "rang" => [0x027E9], + "rangd" => [0x02992], + "range" => [0x029A5], + "rangle" => [0x027E9], + "raquo" => [0x000BB], + "rarr" => [0x02192], + "rarrap" => [0x02975], + "rarrb" => [0x021E5], + "rarrbfs" => [0x02920], + "rarrc" => [0x02933], + "rarrfs" => [0x0291E], + "rarrhk" => [0x021AA], + "rarrlp" => [0x021AC], + "rarrpl" => [0x02945], + "rarrsim" => [0x02974], + "rarrtl" => [0x021A3], + "rarrw" => [0x0219D], + "ratail" => [0x0291A], + "ratio" => [0x02236], + "rationals" => [0x0211A], + "rbarr" => [0x0290D], + "rbbrk" => [0x02773], + "rbrace" => [0x0007D], + "rbrack" => [0x0005D], + "rbrke" => [0x0298C], + "rbrksld" => [0x0298E], + "rbrkslu" => [0x02990], + "rcaron" => [0x00159], + "rcedil" => [0x00157], + "rceil" => [0x02309], + "rcub" => [0x0007D], + "rcy" => [0x00440], + "rdca" => [0x02937], + "rdldhar" => [0x02969], + "rdquo" => [0x0201D], + "rdquor" => [0x0201D], + "rdsh" => [0x021B3], + "real" => [0x0211C], + "realine" => [0x0211B], + "realpart" => [0x0211C], + "reals" => [0x0211D], + "rect" => [0x025AD], + "reg" => [0x000AE], + "rfisht" => [0x0297D], + "rfloor" => [0x0230B], + "rfr" => [0x1D52F], + "rhard" => [0x021C1], + "rharu" => [0x021C0], + "rharul" => [0x0296C], + "rho" => [0x003C1], + "rhov" => [0x003F1], + "rightarrow" => [0x02192], + "rightarrowtail" => [0x021A3], + "rightharpoondown" => [0x021C1], + "rightharpoonup" => [0x021C0], + "rightleftarrows" => [0x021C4], + "rightleftharpoons" => [0x021CC], + "rightrightarrows" => [0x021C9], + "rightsquigarrow" => [0x0219D], + "rightthreetimes" => [0x022CC], + "ring" => [0x002DA], + "risingdotseq" => [0x02253], + "rlarr" => [0x021C4], + "rlhar" => [0x021CC], + "rlm" => [0x0200F], + "rmoust" => [0x023B1], + "rmoustache" => [0x023B1], + "rnmid" => [0x02AEE], + "roang" => [0x027ED], + "roarr" => [0x021FE], + "robrk" => [0x027E7], + "ropar" => [0x02986], + "ropf" => [0x1D563], + "roplus" => [0x02A2E], + "rotimes" => [0x02A35], + "rpar" => [0x00029], + "rpargt" => [0x02994], + "rppolint" => [0x02A12], + "rrarr" => [0x021C9], + "rsaquo" => [0x0203A], + "rscr" => [0x1D4C7], + "rsh" => [0x021B1], + "rsqb" => [0x0005D], + "rsquo" => [0x02019], + "rsquor" => [0x02019], + "rthree" => [0x022CC], + "rtimes" => [0x022CA], + "rtri" => [0x025B9], + "rtrie" => [0x022B5], + "rtrif" => [0x025B8], + "rtriltri" => [0x029CE], + "ruluhar" => [0x02968], + "rx" => [0x0211E], + "sacute" => [0x0015B], + "sbquo" => [0x0201A], + "sc" => [0x0227B], + "scE" => [0x02AB4], + "scap" => [0x02AB8], + "scaron" => [0x00161], + "sccue" => [0x0227D], + "sce" => [0x02AB0], + "scedil" => [0x0015F], + "scirc" => [0x0015D], + "scnE" => [0x02AB6], + "scnap" => [0x02ABA], + "scnsim" => [0x022E9], + "scpolint" => [0x02A13], + "scsim" => [0x0227F], + "scy" => [0x00441], + "sdot" => [0x022C5], + "sdotb" => [0x022A1], + "sdote" => [0x02A66], + "seArr" => [0x021D8], + "searhk" => [0x02925], + "searr" => [0x02198], + "searrow" => [0x02198], + "sect" => [0x000A7], + "semi" => [0x0003B], + "seswar" => [0x02929], + "setminus" => [0x02216], + "setmn" => [0x02216], + "sext" => [0x02736], + "sfr" => [0x1D530], + "sfrown" => [0x02322], + "sharp" => [0x0266F], + "shchcy" => [0x00449], + "shcy" => [0x00448], + "shortmid" => [0x02223], + "shortparallel" => [0x02225], + "shy" => [0x000AD], + "sigma" => [0x003C3], + "sigmaf" => [0x003C2], + "sigmav" => [0x003C2], + "sim" => [0x0223C], + "simdot" => [0x02A6A], + "sime" => [0x02243], + "simeq" => [0x02243], + "simg" => [0x02A9E], + "simgE" => [0x02AA0], + "siml" => [0x02A9D], + "simlE" => [0x02A9F], + "simne" => [0x02246], + "simplus" => [0x02A24], + "simrarr" => [0x02972], + "slarr" => [0x02190], + "smallsetminus" => [0x02216], + "smashp" => [0x02A33], + "smeparsl" => [0x029E4], + "smid" => [0x02223], + "smile" => [0x02323], + "smt" => [0x02AAA], + "smte" => [0x02AAC], + "smtes" => [0x02AAC, 0x0FE00], + "softcy" => [0x0044C], + "sol" => [0x0002F], + "solb" => [0x029C4], + "solbar" => [0x0233F], + "sopf" => [0x1D564], + "spades" => [0x02660], + "spadesuit" => [0x02660], + "spar" => [0x02225], + "sqcap" => [0x02293], + "sqcaps" => [0x02293, 0x0FE00], + "sqcup" => [0x02294], + "sqcups" => [0x02294, 0x0FE00], + "sqsub" => [0x0228F], + "sqsube" => [0x02291], + "sqsubset" => [0x0228F], + "sqsubseteq" => [0x02291], + "sqsup" => [0x02290], + "sqsupe" => [0x02292], + "sqsupset" => [0x02290], + "sqsupseteq" => [0x02292], + "squ" => [0x025A1], + "square" => [0x025A1], + "squarf" => [0x025AA], + "squf" => [0x025AA], + "srarr" => [0x02192], + "sscr" => [0x1D4C8], + "ssetmn" => [0x02216], + "ssmile" => [0x02323], + "sstarf" => [0x022C6], + "star" => [0x02606], + "starf" => [0x02605], + "straightepsilon" => [0x003F5], + "straightphi" => [0x003D5], + "strns" => [0x000AF], + "sub" => [0x02282], + "subE" => [0x02AC5], + "subdot" => [0x02ABD], + "sube" => [0x02286], + "subedot" => [0x02AC3], + "submult" => [0x02AC1], + "subnE" => [0x02ACB], + "subne" => [0x0228A], + "subplus" => [0x02ABF], + "subrarr" => [0x02979], + "subset" => [0x02282], + "subseteq" => [0x02286], + "subseteqq" => [0x02AC5], + "subsetneq" => [0x0228A], + "subsetneqq" => [0x02ACB], + "subsim" => [0x02AC7], + "subsub" => [0x02AD5], + "subsup" => [0x02AD3], + "succ" => [0x0227B], + "succapprox" => [0x02AB8], + "succcurlyeq" => [0x0227D], + "succeq" => [0x02AB0], + "succnapprox" => [0x02ABA], + "succneqq" => [0x02AB6], + "succnsim" => [0x022E9], + "succsim" => [0x0227F], + "sum" => [0x02211], + "sung" => [0x0266A], + "sup" => [0x02283], + "sup1" => [0x000B9], + "sup2" => [0x000B2], + "sup3" => [0x000B3], + "supE" => [0x02AC6], + "supdot" => [0x02ABE], + "supdsub" => [0x02AD8], + "supe" => [0x02287], + "supedot" => [0x02AC4], + "suphsol" => [0x027C9], + "suphsub" => [0x02AD7], + "suplarr" => [0x0297B], + "supmult" => [0x02AC2], + "supnE" => [0x02ACC], + "supne" => [0x0228B], + "supplus" => [0x02AC0], + "supset" => [0x02283], + "supseteq" => [0x02287], + "supseteqq" => [0x02AC6], + "supsetneq" => [0x0228B], + "supsetneqq" => [0x02ACC], + "supsim" => [0x02AC8], + "supsub" => [0x02AD4], + "supsup" => [0x02AD6], + "swArr" => [0x021D9], + "swarhk" => [0x02926], + "swarr" => [0x02199], + "swarrow" => [0x02199], + "swnwar" => [0x0292A], + "szlig" => [0x000DF], + "target" => [0x02316], + "tau" => [0x003C4], + "tbrk" => [0x023B4], + "tcaron" => [0x00165], + "tcedil" => [0x00163], + "tcy" => [0x00442], + "tdot" => [0x020DB], + "telrec" => [0x02315], + "tfr" => [0x1D531], + "there4" => [0x02234], + "therefore" => [0x02234], + "theta" => [0x003B8], + "thetasym" => [0x003D1], + "thetav" => [0x003D1], + "thickapprox" => [0x02248], + "thicksim" => [0x0223C], + "thinsp" => [0x02009], + "thkap" => [0x02248], + "thksim" => [0x0223C], + "thorn" => [0x000FE], + "tilde" => [0x002DC], + "times" => [0x000D7], + "timesb" => [0x022A0], + "timesbar" => [0x02A31], + "timesd" => [0x02A30], + "tint" => [0x0222D], + "toea" => [0x02928], + "top" => [0x022A4], + "topbot" => [0x02336], + "topcir" => [0x02AF1], + "topf" => [0x1D565], + "topfork" => [0x02ADA], + "tosa" => [0x02929], + "tprime" => [0x02034], + "trade" => [0x02122], + "triangle" => [0x025B5], + "triangledown" => [0x025BF], + "triangleleft" => [0x025C3], + "trianglelefteq" => [0x022B4], + "triangleq" => [0x0225C], + "triangleright" => [0x025B9], + "trianglerighteq" => [0x022B5], + "tridot" => [0x025EC], + "trie" => [0x0225C], + "triminus" => [0x02A3A], + "triplus" => [0x02A39], + "trisb" => [0x029CD], + "tritime" => [0x02A3B], + "trpezium" => [0x023E2], + "tscr" => [0x1D4C9], + "tscy" => [0x00446], + "tshcy" => [0x0045B], + "tstrok" => [0x00167], + "twixt" => [0x0226C], + "twoheadleftarrow" => [0x0219E], + "twoheadrightarrow" => [0x021A0], + "uArr" => [0x021D1], + "uHar" => [0x02963], + "uacute" => [0x000FA], + "uarr" => [0x02191], + "ubrcy" => [0x0045E], + "ubreve" => [0x0016D], + "ucirc" => [0x000FB], + "ucy" => [0x00443], + "udarr" => [0x021C5], + "udblac" => [0x00171], + "udhar" => [0x0296E], + "ufisht" => [0x0297E], + "ufr" => [0x1D532], + "ugrave" => [0x000F9], + "uharl" => [0x021BF], + "uharr" => [0x021BE], + "uhblk" => [0x02580], + "ulcorn" => [0x0231C], + "ulcorner" => [0x0231C], + "ulcrop" => [0x0230F], + "ultri" => [0x025F8], + "umacr" => [0x0016B], + "uml" => [0x000A8], + "uogon" => [0x00173], + "uopf" => [0x1D566], + "uparrow" => [0x02191], + "updownarrow" => [0x02195], + "upharpoonleft" => [0x021BF], + "upharpoonright" => [0x021BE], + "uplus" => [0x0228E], + "upsi" => [0x003C5], + "upsih" => [0x003D2], + "upsilon" => [0x003C5], + "upuparrows" => [0x021C8], + "urcorn" => [0x0231D], + "urcorner" => [0x0231D], + "urcrop" => [0x0230E], + "uring" => [0x0016F], + "urtri" => [0x025F9], + "uscr" => [0x1D4CA], + "utdot" => [0x022F0], + "utilde" => [0x00169], + "utri" => [0x025B5], + "utrif" => [0x025B4], + "uuarr" => [0x021C8], + "uuml" => [0x000FC], + "uwangle" => [0x029A7], + "vArr" => [0x021D5], + "vBar" => [0x02AE8], + "vBarv" => [0x02AE9], + "vDash" => [0x022A8], + "vangrt" => [0x0299C], + "varepsilon" => [0x003F5], + "varkappa" => [0x003F0], + "varnothing" => [0x02205], + "varphi" => [0x003D5], + "varpi" => [0x003D6], + "varpropto" => [0x0221D], + "varr" => [0x02195], + "varrho" => [0x003F1], + "varsigma" => [0x003C2], + "varsubsetneq" => [0x0228A, 0x0FE00], + "varsubsetneqq" => [0x02ACB, 0x0FE00], + "varsupsetneq" => [0x0228B, 0x0FE00], + "varsupsetneqq" => [0x02ACC, 0x0FE00], + "vartheta" => [0x003D1], + "vartriangleleft" => [0x022B2], + "vartriangleright" => [0x022B3], + "vcy" => [0x00432], + "vdash" => [0x022A2], + "vee" => [0x02228], + "veebar" => [0x022BB], + "veeeq" => [0x0225A], + "vellip" => [0x022EE], + "verbar" => [0x0007C], + "vert" => [0x0007C], + "vfr" => [0x1D533], + "vltri" => [0x022B2], + "vnsub" => [0x02282, 0x020D2], + "vnsup" => [0x02283, 0x020D2], + "vopf" => [0x1D567], + "vprop" => [0x0221D], + "vrtri" => [0x022B3], + "vscr" => [0x1D4CB], + "vsubnE" => [0x02ACB, 0x0FE00], + "vsubne" => [0x0228A, 0x0FE00], + "vsupnE" => [0x02ACC, 0x0FE00], + "vsupne" => [0x0228B, 0x0FE00], + "vzigzag" => [0x0299A], + "wcirc" => [0x00175], + "wedbar" => [0x02A5F], + "wedge" => [0x02227], + "wedgeq" => [0x02259], + "weierp" => [0x02118], + "wfr" => [0x1D534], + "wopf" => [0x1D568], + "wp" => [0x02118], + "wr" => [0x02240], + "wreath" => [0x02240], + "wscr" => [0x1D4CC], + "xcap" => [0x022C2], + "xcirc" => [0x025EF], + "xcup" => [0x022C3], + "xdtri" => [0x025BD], + "xfr" => [0x1D535], + "xhArr" => [0x027FA], + "xharr" => [0x027F7], + "xi" => [0x003BE], + "xlArr" => [0x027F8], + "xlarr" => [0x027F5], + "xmap" => [0x027FC], + "xnis" => [0x022FB], + "xodot" => [0x02A00], + "xopf" => [0x1D569], + "xoplus" => [0x02A01], + "xotime" => [0x02A02], + "xrArr" => [0x027F9], + "xrarr" => [0x027F6], + "xscr" => [0x1D4CD], + "xsqcup" => [0x02A06], + "xuplus" => [0x02A04], + "xutri" => [0x025B3], + "xvee" => [0x022C1], + "xwedge" => [0x022C0], + "yacute" => [0x000FD], + "yacy" => [0x0044F], + "ycirc" => [0x00177], + "ycy" => [0x0044B], + "yen" => [0x000A5], + "yfr" => [0x1D536], + "yicy" => [0x00457], + "yopf" => [0x1D56A], + "yscr" => [0x1D4CE], + "yucy" => [0x0044E], + "yuml" => [0x000FF], + "zacute" => [0x0017A], + "zcaron" => [0x0017E], + "zcy" => [0x00437], + "zdot" => [0x0017C], + "zeetrf" => [0x02128], + "zeta" => [0x003B6], + "zfr" => [0x1D537], + "zhcy" => [0x00436], + "zigrarr" => [0x021DD], + "zopf" => [0x1D56B], + "zscr" => [0x1D4CF], + "zwj" => [0x0200D], + "zwnj" => [0x0200C], +} + diff --git a/lib/rdoc/markdown/literals.rb b/lib/rdoc/markdown/literals.rb new file mode 100644 index 0000000000..cd4cb52335 --- /dev/null +++ b/lib/rdoc/markdown/literals.rb @@ -0,0 +1,416 @@ +# coding: UTF-8 +# :markup: markdown + +## +#-- +# This set of literals is for Ruby 1.9 regular expressions and gives full +# unicode support. +# +# Unlike peg-markdown, this set of literals recognizes Unicode alphanumeric +# characters, newlines and spaces. +class RDoc::Markdown::Literals + # :stopdoc: + + # This is distinct from setup_parser so that a standalone parser + # can redefine #initialize and still have access to the proper + # parser setup code. + def initialize(str, debug=false) + setup_parser(str, debug) + end + + + + # Prepares for parsing +str+. If you define a custom initialize you must + # call this method before #parse + def setup_parser(str, debug=false) + set_string str, 0 + @memoizations = Hash.new { |h,k| h[k] = {} } + @result = nil + @failed_rule = nil + @failing_rule_offset = -1 + + setup_foreign_grammar + end + + attr_reader :string + attr_reader :failing_rule_offset + attr_accessor :result, :pos + + def current_column(target=pos) + if c = string.rindex("\n", target-1) + return target - c - 1 + end + + target + 1 + end + + def current_line(target=pos) + cur_offset = 0 + cur_line = 0 + + string.each_line do |line| + cur_line += 1 + cur_offset += line.size + return cur_line if cur_offset >= target + end + + -1 + end + + def lines + lines = [] + string.each_line { |l| lines << l } + lines + end + + + + def get_text(start) + @string[start..@pos-1] + end + + # Sets the string and current parsing position for the parser. + def set_string string, pos + @string = string + @string_size = string ? string.size : 0 + @pos = pos + end + + def show_pos + width = 10 + if @pos < width + "#{@pos} (\"#{@string[0,@pos]}\" @ \"#{@string[@pos,width]}\")" + else + "#{@pos} (\"... #{@string[@pos - width, width]}\" @ \"#{@string[@pos,width]}\")" + end + end + + def failure_info + l = current_line @failing_rule_offset + c = current_column @failing_rule_offset + + if @failed_rule.kind_of? Symbol + info = self.class::Rules[@failed_rule] + "line #{l}, column #{c}: failed rule '#{info.name}' = '#{info.rendered}'" + else + "line #{l}, column #{c}: failed rule '#{@failed_rule}'" + end + end + + def failure_caret + l = current_line @failing_rule_offset + c = current_column @failing_rule_offset + + line = lines[l-1] + "#{line}\n#{' ' * (c - 1)}^" + end + + def failure_character + l = current_line @failing_rule_offset + c = current_column @failing_rule_offset + lines[l-1][c-1, 1] + end + + def failure_oneline + l = current_line @failing_rule_offset + c = current_column @failing_rule_offset + + char = lines[l-1][c-1, 1] + + if @failed_rule.kind_of? Symbol + info = self.class::Rules[@failed_rule] + "@#{l}:#{c} failed rule '#{info.name}', got '#{char}'" + else + "@#{l}:#{c} failed rule '#{@failed_rule}', got '#{char}'" + end + end + + class ParseError < RuntimeError + end + + def raise_error + raise ParseError, failure_oneline + end + + def show_error(io=STDOUT) + error_pos = @failing_rule_offset + line_no = current_line(error_pos) + col_no = current_column(error_pos) + + io.puts "On line #{line_no}, column #{col_no}:" + + if @failed_rule.kind_of? Symbol + info = self.class::Rules[@failed_rule] + io.puts "Failed to match '#{info.rendered}' (rule '#{info.name}')" + else + io.puts "Failed to match rule '#{@failed_rule}'" + end + + io.puts "Got: #{string[error_pos,1].inspect}" + line = lines[line_no-1] + io.puts "=> #{line}" + io.print(" " * (col_no + 3)) + io.puts "^" + end + + def set_failed_rule(name) + if @pos > @failing_rule_offset + @failed_rule = name + @failing_rule_offset = @pos + end + end + + attr_reader :failed_rule + + def match_string(str) + len = str.size + if @string[pos,len] == str + @pos += len + return str + end + + return nil + end + + def scan(reg) + if m = reg.match(@string[@pos..-1]) + width = m.end(0) + @pos += width + return true + end + + return nil + end + + if "".respond_to? :ord + def get_byte + if @pos >= @string_size + return nil + end + + s = @string[@pos].ord + @pos += 1 + s + end + else + def get_byte + if @pos >= @string_size + return nil + end + + s = @string[@pos] + @pos += 1 + s + end + end + + def parse(rule=nil) + # We invoke the rules indirectly via apply + # instead of by just calling them as methods because + # if the rules use left recursion, apply needs to + # manage that. + + if !rule + apply(:_root) + else + method = rule.gsub("-","_hyphen_") + apply :"_#{method}" + end + end + + class MemoEntry + def initialize(ans, pos) + @ans = ans + @pos = pos + @result = nil + @set = false + @left_rec = false + end + + attr_reader :ans, :pos, :result, :set + attr_accessor :left_rec + + def move!(ans, pos, result) + @ans = ans + @pos = pos + @result = result + @set = true + @left_rec = false + end + end + + def external_invoke(other, rule, *args) + old_pos = @pos + old_string = @string + + set_string other.string, other.pos + + begin + if val = __send__(rule, *args) + other.pos = @pos + other.result = @result + else + other.set_failed_rule "#{self.class}##{rule}" + end + val + ensure + set_string old_string, old_pos + end + end + + def apply_with_args(rule, *args) + memo_key = [rule, args] + if m = @memoizations[memo_key][@pos] + @pos = m.pos + if !m.set + m.left_rec = true + return nil + end + + @result = m.result + + return m.ans + else + m = MemoEntry.new(nil, @pos) + @memoizations[memo_key][@pos] = m + start_pos = @pos + + ans = __send__ rule, *args + + lr = m.left_rec + + m.move! ans, @pos, @result + + # Don't bother trying to grow the left recursion + # if it's failing straight away (thus there is no seed) + if ans and lr + return grow_lr(rule, args, start_pos, m) + else + return ans + end + end + end + + def apply(rule) + if m = @memoizations[rule][@pos] + @pos = m.pos + if !m.set + m.left_rec = true + return nil + end + + @result = m.result + + return m.ans + else + m = MemoEntry.new(nil, @pos) + @memoizations[rule][@pos] = m + start_pos = @pos + + ans = __send__ rule + + lr = m.left_rec + + m.move! ans, @pos, @result + + # Don't bother trying to grow the left recursion + # if it's failing straight away (thus there is no seed) + if ans and lr + return grow_lr(rule, nil, start_pos, m) + else + return ans + end + end + end + + def grow_lr(rule, args, start_pos, m) + while true + @pos = start_pos + @result = m.result + + if args + ans = __send__ rule, *args + else + ans = __send__ rule + end + return nil unless ans + + break if @pos <= m.pos + + m.move! ans, @pos, @result + end + + @result = m.result + @pos = m.pos + return m.ans + end + + class RuleInfo + def initialize(name, rendered) + @name = name + @rendered = rendered + end + + attr_reader :name, :rendered + end + + def self.rule_info(name, rendered) + RuleInfo.new(name, rendered) + end + + + # :startdoc: + # :stopdoc: + def setup_foreign_grammar; end + + # Alphanumeric = /\p{Word}/ + def _Alphanumeric + _tmp = scan(/\A(?-mix:\p{Word})/) + set_failed_rule :_Alphanumeric unless _tmp + return _tmp + end + + # AlphanumericAscii = /[A-Za-z0-9]/ + def _AlphanumericAscii + _tmp = scan(/\A(?-mix:[A-Za-z0-9])/) + set_failed_rule :_AlphanumericAscii unless _tmp + return _tmp + end + + # BOM = "uFEFF" + def _BOM + _tmp = match_string("uFEFF") + set_failed_rule :_BOM unless _tmp + return _tmp + end + + # Newline = /\n|\r\n?|\p{Zl}|\p{Zp}/ + def _Newline + _tmp = scan(/\A(?-mix:\n|\r\n?|\p{Zl}|\p{Zp})/) + set_failed_rule :_Newline unless _tmp + return _tmp + end + + # NonAlphanumeric = /\p{^Word}/ + def _NonAlphanumeric + _tmp = scan(/\A(?-mix:\p{^Word})/) + set_failed_rule :_NonAlphanumeric unless _tmp + return _tmp + end + + # Spacechar = /\t|\p{Zs}/ + def _Spacechar + _tmp = scan(/\A(?-mix:\t|\p{Zs})/) + set_failed_rule :_Spacechar unless _tmp + return _tmp + end + + Rules = {} + Rules[:_Alphanumeric] = rule_info("Alphanumeric", "/\\p{Word}/") + Rules[:_AlphanumericAscii] = rule_info("AlphanumericAscii", "/[A-Za-z0-9]/") + Rules[:_BOM] = rule_info("BOM", "\"uFEFF\"") + Rules[:_Newline] = rule_info("Newline", "/\\n|\\r\\n?|\\p{Zl}|\\p{Zp}/") + Rules[:_NonAlphanumeric] = rule_info("NonAlphanumeric", "/\\p{^Word}/") + Rules[:_Spacechar] = rule_info("Spacechar", "/\\t|\\p{Zs}/") + # :startdoc: +end diff --git a/lib/rdoc/markup.rb b/lib/rdoc/markup.rb new file mode 100644 index 0000000000..08ecc6f7df --- /dev/null +++ b/lib/rdoc/markup.rb @@ -0,0 +1,870 @@ +# frozen_string_literal: true +## +# 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 and other markup formats do no output formatting, this is +# handled by the RDoc::Markup::Formatter subclasses. +# +# = Supported Formats +# +# Besides the RDoc::Markup format, the following formats are built in to RDoc: +# +# markdown:: +# The markdown format as described by +# http://daringfireball.net/projects/markdown/. See RDoc::Markdown for +# details on the parser and supported extensions. +# rd:: +# The rdtool format. See RDoc::RD for details on the parser and format. +# tomdoc:: +# The TomDoc format as described by http://tomdoc.org/. See RDoc::TomDoc +# for details on the parser and supported extensions. +# +# You can choose a markup format using the following methods: +# +# per project:: +# If you build your documentation with rake use RDoc::Task#markup. +# +# If you build your documentation by hand run: +# +# rdoc --markup your_favorite_format --write-options +# +# and commit <tt>.rdoc_options</tt> and ship it with your packaged gem. +# per file:: +# At the top of the file use the <tt>:markup:</tt> directive to set the +# default format for the rest of the file. +# per comment:: +# Use the <tt>:markup:</tt> directive at the top of a comment you want +# to write in a different format. +# +# = RDoc::Markup +# +# RDoc::Markup is extensible at runtime: you can add \new markup elements to +# be recognized 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 formatting RDoc style comment blocks, +# Wiki entries, and online FAQs. +# +# == Synopsis +# +# This code converts +input_string+ to HTML. The conversion takes place in +# the +convert+ method, so you can use the same RDoc::Markup converter to +# convert multiple input strings. +# +# require 'rdoc' +# +# h = RDoc::Markup::ToHtml.new(RDoc::Options.new) +# +# puts h.convert(input_string) +# +# You can extend the RDoc::Markup parser to recognize new markup +# sequences, and to add special processing for text that matches a +# regular expression. Here we make WikiWords significant to the parser, +# and also make the sequences {word} and \<no>text...</no> signify +# strike-through text. We then subclass the HTML output class to deal +# with these: +# +# require 'rdoc' +# +# class WikiHtml < RDoc::Markup::ToHtml +# def handle_special_WIKIWORD(special) +# "<font color=red>" + special.text + "</font>" +# end +# end +# +# markup = RDoc::Markup.new +# markup.add_word_pair("{", "}", :STRIKE) +# markup.add_html("no", :STRIKE) +# +# markup.add_special(/\b([A-Z][a-z]+[A-Z]\w+)/, :WIKIWORD) +# +# wh = WikiHtml.new RDoc::Options.new, markup +# wh.add_tag(:STRIKE, "<strike>", "</strike>") +# +# puts "<body>#{wh.convert ARGF.read}</body>" +# +# == Encoding +# +# Where Encoding support is available, RDoc will automatically convert all +# documents to the same output encoding. The output encoding can be set via +# RDoc::Options#encoding and defaults to Encoding.default_external. +# +# = \RDoc Markup Reference +# +# == Block Markup +# +# === Paragraphs and Verbatim +# +# The markup engine 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. Empty lines separate paragraphs. +# +# Any line that starts to the right of the current margin is treated +# as verbatim text. This is useful for code listings: +# +# 3.times { puts "Ruby" } +# +# In verbatim text, two or more blank lines are collapsed into one, +# and trailing blank lines are removed: +# +# This is the first line +# +# +# This is the second non-blank line, +# after 2 blank lines in the source markup. +# +# +# There were two trailing blank lines right above this paragraph, that +# have been removed. In addition, the verbatim text has been shifted +# left, so the amount of indentation of verbatim text is unimportant. +# +# For HTML output RDoc makes a small effort to determine if a verbatim section +# contains Ruby source code. If so, the verbatim block will be marked up as +# HTML. Triggers include "def", "class", "module", "require", the "hash +# rocket"# (=>) or a block call with a parameter. +# +# === Headers +# +# A line starting with an equal sign (=) is treated as a +# heading. Level one headings have one equals sign, level two headings +# have two, and so on until level six, which is the maximum +# (seven hyphens or more result in a level six heading). +# +# For example, the above header was obtained with: +# +# === Headers +# +# In HTML output headers have an id matching their name. The above example's +# HTML is: +# +# <h3 id="label-Headers">Headers</h3> +# +# If a heading is inside a method body the id will be prefixed with the +# method's id. If the above header where in the documentation for a method +# such as: +# +# ## +# # This method does fun things +# # +# # = Example +# # +# # Example of fun things goes here ... +# +# def do_fun_things +# end +# +# The header's id would be: +# +# <h1 id="method-i-do_fun_things-label-Example">Example</h1> +# +# The label can be linked-to using <tt>SomeClass@Headers</tt>. See +# {Links}[RDoc::Markup@Links] for further details. +# +# === Rules +# +# A line starting with three or more hyphens (at the current indent) +# generates a horizontal rule. +# +# --- +# +# produces: +# +# --- +# +# === Simple Lists +# +# If a paragraph starts with a "*", "-", "<digit>." or "<letter>.", +# then it is taken to be the start of a list. The margin is 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 +# +# produces: +# +# * 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 +# +# === Labeled Lists +# +# 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 +# +# produces: +# +# [cat] a small furry mammal +# that seems to sleep a lot +# +# [ant] a little insect that is known +# to enjoy picnics +# +# If you want the list bodies to line up to the left of the labels, +# use two colons: +# +# cat:: a small furry mammal +# that seems to sleep a lot +# +# ant:: a little insect that is known +# to enjoy picnics +# +# produces: +# +# cat:: a small furry mammal +# that seems to sleep a lot +# +# ant:: a little insect that is known +# to enjoy picnics +# +# Notice that blank lines right after the label are ignored in labeled lists: +# +# [one] +# +# definition 1 +# +# [two] +# +# definition 2 +# +# produces the same output as +# +# [one] definition 1 +# [two] definition 2 +# +# +# === Lists and Verbatim +# +# If you want to introduce a verbatim section right after a list, it has to be +# less indented than the list item bodies, but more indented than the list +# label, letter, digit or bullet. For instance: +# +# * point 1 +# +# * point 2, first paragraph +# +# point 2, second paragraph +# verbatim text inside point 2 +# point 2, third paragraph +# verbatim text outside of the list (the list is therefore closed) +# regular paragraph after the list +# +# produces: +# +# * point 1 +# +# * point 2, first paragraph +# +# point 2, second paragraph +# verbatim text inside point 2 +# point 2, third paragraph +# verbatim text outside of the list (the list is therefore closed) +# regular paragraph after the list +# +# == Text Markup +# +# === Bold, Italic, Typewriter Text +# +# 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: +# +# <tt>\*_word_\*</tt>:: displays _word_ in a *bold* font +# <tt>\__word_\_</tt>:: displays _word_ in an _emphasized_ font +# <tt>\+_word_\+</tt>:: displays _word_ in a +code+ font +# +# General markup affects text between a start delimiter and an end +# delimiter. Not surprisingly, these delimiters look like HTML markup. +# +# <tt>\<b>_text_</b></tt>:: displays _text_ in a *bold* font +# <tt>\<em>_text_</em></tt>:: displays _text_ in an _emphasized_ font +# (alternate tag: <tt>\<i></tt>) +# <tt>\<tt>_text_\</tt></tt>:: displays _text_ in a +code+ font +# (alternate tag: <tt>\<code></tt>) +# +# 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 (see <i>Escaping +# Text Markup</i>, below). +# +# === Links +# +# Links to starting with +http:+, +https:+, +mailto:+, +ftp:+ or +www.+ +# are recognized. An HTTP url that references an external image is converted +# into an inline image element. +# +# Classes and methods will be automatically linked to their definition. For +# example, <tt>RDoc::Markup</tt> will link to this documentation. By default +# methods will only be automatically linked if they contain an <tt>_</tt> (all +# methods can be automatically linked through the <tt>--hyperlink-all</tt> +# command line option). +# +# Single-word methods can be linked by using the <tt>#</tt> character for +# instance methods or <tt>::</tt> for class methods. For example, +# <tt>#convert</tt> links to #convert. A class or method may be combined like +# <tt>RDoc::Markup#convert</tt>. +# +# A heading inside the documentation can be linked by following the class +# or method by an <tt>@</tt> then the heading name. +# <tt>RDoc::Markup@Links</tt> will link to this section like this: +# RDoc::Markup@Links. Spaces in headings with multiple words must be escaped +# with <tt>+</tt> like <tt>RDoc::Markup@Escaping+Text+Markup</tt>. +# Punctuation and other special characters must be escaped like CGI.escape. +# +# The <tt>@</tt> can also be used to link to sections. If a section and a +# heading share the same name the section is preferred for the link. +# +# Links can also be of the form <tt>label[url]</tt>, in which case +label+ is +# used in the displayed text, and +url+ is used as the target. If +label+ +# contains multiple words, put it in braces: <tt>{multi word label}[url]</tt>. +# The +url+ may be an +http:+-type link or a cross-reference to a class, +# module or method with a label. +# +# Links with the <code>rdoc-image:</code> scheme will create an image tag for +# HTML output. Only fully-qualified URLs are supported. +# +# Links with the <tt>rdoc-ref:</tt> scheme will link to the referenced class, +# module, method, file, etc. If the referenced item is does not exist +# no link will be generated and <tt>rdoc-ref:</tt> will be removed from the +# resulting text. +# +# Links starting with <tt>rdoc-label:label_name</tt> will link to the +# +label_name+. You can create a label for the current link (for +# bidirectional links) by supplying a name for the current link like +# <tt>rdoc-label:label-other:label-mine</tt>. +# +# Links starting with +link:+ refer to local files whose path is relative to +# the <tt>--op</tt> directory. Use <tt>rdoc-ref:</tt> instead of +# <tt>link:</tt> to link to files generated by RDoc as the link target may +# be different across RDoc generators. +# +# Example links: +# +# https://github.com/ruby/rdoc +# mailto:user@example.com +# {RDoc Documentation}[http://rdoc.rubyforge.org] +# {RDoc Markup}[rdoc-ref:RDoc::Markup] +# +# === Escaping Text Markup +# +# Text markup can be escaped with a backslash, as in \<tt>, which was obtained +# with <tt>\\<tt></tt>. Except in verbatim sections and between \<tt> tags, +# to produce a backslash you have to double it unless it is followed by a +# space, tab or newline. Otherwise, the HTML formatter will discard it, as it +# is used to escape potential links: +# +# * The \ must be doubled if not followed by white space: \\. +# * But not in \<tt> tags: in a Regexp, <tt>\S</tt> matches non-space. +# * This is a link to {ruby-lang}[www.ruby-lang.org]. +# * This is not a link, however: \{ruby-lang.org}[www.ruby-lang.org]. +# * This will not be linked to \RDoc::RDoc#document +# +# generates: +# +# * The \ must be doubled if not followed by white space: \\. +# * But not in \<tt> tags: in a Regexp, <tt>\S</tt> matches non-space. +# * This is a link to {ruby-lang}[www.ruby-lang.org] +# * This is not a link, however: \{ruby-lang.org}[www.ruby-lang.org] +# * This will not be linked to \RDoc::RDoc#document +# +# Inside \<tt> tags, more precisely, leading backslashes are removed only if +# followed by a markup character (<tt><*_+</tt>), a backslash, or a known link +# reference (a known class or method). So in the example above, the backslash +# of <tt>\S</tt> would be removed if there was a class or module named +S+ in +# the current context. +# +# This behavior is inherited from RDoc version 1, and has been kept for +# compatibility with existing RDoc documentation. +# +# === Conversion of characters +# +# HTML will convert two/three dashes to an em-dash. Other common characters are +# converted as well: +# +# em-dash:: -- or --- +# ellipsis:: ... +# +# single quotes:: 'text' or `text' +# double quotes:: "text" or ``text'' +# +# copyright:: (c) +# registered trademark:: (r) +# +# produces: +# +# em-dash:: -- or --- +# ellipsis:: ... +# +# single quotes:: 'text' or `text' +# double quotes:: "text" or ``text'' +# +# copyright:: (c) +# registered trademark:: (r) +# +# +# == Documenting Source Code +# +# Comment blocks can be written fairly naturally, either using <tt>#</tt> on +# successive lines of the comment, or by including the comment in +# a <tt>=begin</tt>/<tt>=end</tt> block. If you use the latter form, +# the <tt>=begin</tt> line _must_ be flagged with an +rdoc+ tag: +# +# =begin rdoc +# Documentation to be processed by RDoc. +# +# ... +# =end +# +# RDoc stops processing comments if it finds a comment line starting +# with <tt>--</tt> right after the <tt>#</tt> character (otherwise, +# it will be treated as a rule if it has three dashes or more). +# 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 with <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) +# # ... +# end +# +# Names of classes, files, and any method names containing an underscore or +# preceded by a hash character are automatically linked from comment text to +# their description. This linking works inside the current class or module, +# and with ancestor methods (in included modules or in the superclass). +# +# 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| ... } +# +# +:yields:+ is an example of a documentation directive. These appear +# immediately after the start of the document element they are modifying. +# +# RDoc automatically cross-references words with underscores or camel-case. +# To suppress cross-references, prefix the word with a \ character. To +# include special characters like "<tt>\n</tt>", you'll need to use +# two \ characters in normal text, but only one in \<tt> text: +# +# "\\n" or "<tt>\n</tt>" +# +# produces: +# +# "\\n" or "<tt>\n</tt>" +# +# == Directives +# +# Directives are keywords surrounded by ":" characters. +# +# === Controlling what is documented +# +# [+:nodoc:+ / <tt>:nodoc: all</tt>] +# This directive prevents documentation for the element from +# being generated. For classes and modules, methods, aliases, +# constants, and attributes directly within the affected class or +# module also will be omitted. By default, though, modules and +# classes within that class or 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 +# +# In the above code, only class <tt>MyModule::Input</tt> will be documented. +# +# The +:nodoc:+ directive, like +:enddoc:+, +:stopdoc:+ and +:startdoc:+ +# presented below, is local to the current file: if you do not want to +# document a module that appears in several files, specify +:nodoc:+ on each +# appearance, at least once per file. +# +# [+: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 specify a +:startdoc:+ by the end of the container, +# disables documentation for the rest of the current file. +# +# [+:doc:+] +# Forces a method or attribute to be documented even if it wouldn't be +# otherwise. Useful if, for example, you want to include documentation of a +# particular private method. +# +# [+:enddoc:+] +# Document nothing further at the current level: directives +:startdoc:+ and +# +:doc:+ that appear after this will not be honored for the current container +# (file, class or module), in the current file. +# +# [+:notnew:+ / +:not_new:+ / +:not-new:+ ] +# 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:+ directive stops this. Remember that +initialize+ is private, +# so you won't see the documentation unless you use the +-a+ command line +# option. +# +# === Method arguments +# +# [+:arg:+ or +:args:+ _parameters_] +# Overrides the default argument handling with exactly these parameters. +# +# ## +# # :args: a, b +# +# def some_method(*a) +# end +# +# [+:yield:+ or +:yields:+ _parameters_] +# Overrides the default yield discovery with these parameters. +# +# ## +# # :yields: key, value +# +# def each_thing &block +# @things.each(&block) +# end +# +# [+:call-seq:+] +# Lines up to the next blank line or lines with a common prefix in the +# comment are treated as the method's calling sequence, overriding the +# default parsing of method parameters and yield arguments. +# +# Multiple lines may be used. +# +# # :call-seq: +# # ARGF.readlines(sep=$/) -> array +# # ARGF.readlines(limit) -> array +# # ARGF.readlines(sep, limit) -> array +# # +# # ARGF.to_a(sep=$/) -> array +# # ARGF.to_a(limit) -> array +# # ARGF.to_a(sep, limit) -> array +# # +# # The remaining lines are documentation ... +# +# === Sections +# +# Sections allow you to group methods in a class into sensible containers. If +# you use the sections 'Public', 'Internal' and 'Deprecated' (the three +# allowed method statuses from TomDoc) the sections will be displayed in that +# order placing the most useful methods at the top. Otherwise, sections will +# be displayed in alphabetical order. +# +# [+:category:+ _section_] +# Adds this item to the named +section+ overriding the current section. Use +# this to group methods by section in RDoc output while maintaining a +# sensible ordering (like alphabetical). +# +# # :category: Utility Methods +# # +# # CGI escapes +text+ +# +# def convert_string text +# CGI.escapeHTML text +# end +# +# An empty category will place the item in the default category: +# +# # :category: +# # +# # This method is in the default category +# +# def some_method +# # ... +# end +# +# Unlike the :section: directive, :category: is not sticky. The category +# only applies to the item immediately following the comment. +# +# Use the :section: directive to provide introductory text for a section of +# documentation. +# +# [+:section:+ _title_] +# Provides section introductory text in RDoc output. The title following +# +:section:+ is used as the section name and the remainder of the comment +# containing the section is used as introductory text. A section's comment +# block must be separated from following comment blocks. Use an empty title +# to switch to the default section. +# +# The :section: directive is sticky, so subsequent methods, aliases, +# attributes, and classes will be contained in this section until the +# section is changed. The :category: directive will override the :section: +# directive. +# +# 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 to the +# section. +# +# Example: +# +# # ---------------------------------------- +# # :section: My Section +# # This is the section that I wrote. +# # See it glisten in the noon-day sun. +# # ---------------------------------------- +# +# ## +# # Comment for some_method +# +# def some_method +# # ... +# end +# +# === Other directives +# +# [+:markup:+ _type_] +# Overrides the default markup type for this comment with the specified +# markup type. For Ruby files, if the first comment contains this directive +# it is applied automatically to all comments in the file. +# +# Unless you are converting between markup formats you should use a +# <code>.rdoc_options</code> file to specify the default documentation +# format for your entire project. See RDoc::Options@Saved+Options for +# instructions. +# +# At the top of a file the +:markup:+ directive applies to the entire file: +# +# # coding: UTF-8 +# # :markup: TomDoc +# +# # TomDoc comment here ... +# +# class MyClass +# # ... +# +# For just one comment: +# +# # ... +# end +# +# # :markup: RDoc +# # +# # This is a comment in RDoc markup format ... +# +# def some_method +# # ... +# +# See Markup@CONTRIBUTING for instructions on adding a new markup format. +# +# [+:include:+ _filename_] +# Include the contents of the named file at this point. This directive +# must appear alone on one line, possibly preceded by spaces. In this +# position, it can be escaped with a \ in front of the first colon. +# +# 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 <tt>--title</tt> +# command line parameter. (The command line parameter overrides any :title: +# directive in the source). +# +# [+:main:+ _name_] +# Equivalent to the <tt>--main</tt> command line parameter. +# +#-- +# Original Author:: Dave Thomas, dave@pragmaticprogrammer.com +# License:: Ruby license + +class RDoc::Markup + + ## + # An AttributeManager which handles inline markup. + + attr_reader :attribute_manager + + ## + # Parses +str+ into an RDoc::Markup::Document. + + def self.parse str + RDoc::Markup::Parser.parse str + rescue RDoc::Markup::Parser::Error => e + $stderr.puts <<-EOF +While parsing markup, RDoc encountered a #{e.class}: + +#{e} +\tfrom #{e.backtrace.join "\n\tfrom "} + +---8<--- +#{text} +---8<--- + +RDoc #{RDoc::VERSION} + +Ruby #{RUBY_VERSION}-p#{RUBY_PATCHLEVEL} #{RUBY_RELEASE_DATE} + +Please file a bug report with the above information at: + +https://github.com/ruby/rdoc/issues + + EOF + raise + end + + ## + # Take a block of text and use various heuristics to determine its + # structure (paragraphs, lists, and so on). Invoke an event handler as we + # identify significant chunks. + + def initialize attribute_manager = nil + @attribute_manager = attribute_manager || RDoc::Markup::AttributeManager.new + @output = nil + end + + ## + # Add to the sequences used to add formatting to an individual word (such + # as *bold*). Matching entries will generate attributes that the output + # formatters can recognize by their +name+. + + def add_word_pair(start, stop, name) + @attribute_manager.add_word_pair(start, stop, name) + end + + ## + # Add to the sequences recognized as general markup. + + def add_html(tag, name) + @attribute_manager.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) + @attribute_manager.add_special(pattern, name) + end + + ## + # We take +input+, parse it if necessary, then invoke the output +formatter+ + # using a Visitor to render the result. + + def convert input, formatter + document = case input + when RDoc::Markup::Document then + input + else + RDoc::Markup::Parser.parse input + end + + document.accept formatter + end + + autoload :Parser, 'rdoc/markup/parser' + autoload :PreProcess, 'rdoc/markup/pre_process' + + # Inline markup classes + autoload :AttrChanger, 'rdoc/markup/attr_changer' + autoload :AttrSpan, 'rdoc/markup/attr_span' + autoload :Attributes, 'rdoc/markup/attributes' + autoload :AttributeManager, 'rdoc/markup/attribute_manager' + autoload :Special, 'rdoc/markup/special' + + # RDoc::Markup AST + autoload :BlankLine, 'rdoc/markup/blank_line' + autoload :BlockQuote, 'rdoc/markup/block_quote' + autoload :Document, 'rdoc/markup/document' + autoload :HardBreak, 'rdoc/markup/hard_break' + autoload :Heading, 'rdoc/markup/heading' + autoload :Include, 'rdoc/markup/include' + autoload :IndentedParagraph, 'rdoc/markup/indented_paragraph' + autoload :List, 'rdoc/markup/list' + autoload :ListItem, 'rdoc/markup/list_item' + autoload :Paragraph, 'rdoc/markup/paragraph' + autoload :Raw, 'rdoc/markup/raw' + autoload :Rule, 'rdoc/markup/rule' + autoload :Verbatim, 'rdoc/markup/verbatim' + + # Formatters + autoload :Formatter, 'rdoc/markup/formatter' + autoload :FormatterTestCase, 'rdoc/markup/formatter_test_case' + autoload :TextFormatterTestCase, 'rdoc/markup/text_formatter_test_case' + + autoload :ToAnsi, 'rdoc/markup/to_ansi' + autoload :ToBs, 'rdoc/markup/to_bs' + autoload :ToHtml, 'rdoc/markup/to_html' + autoload :ToHtmlCrossref, 'rdoc/markup/to_html_crossref' + autoload :ToHtmlSnippet, 'rdoc/markup/to_html_snippet' + autoload :ToLabel, 'rdoc/markup/to_label' + autoload :ToMarkdown, 'rdoc/markup/to_markdown' + autoload :ToRdoc, 'rdoc/markup/to_rdoc' + autoload :ToTableOfContents, 'rdoc/markup/to_table_of_contents' + autoload :ToTest, 'rdoc/markup/to_test' + autoload :ToTtOnly, 'rdoc/markup/to_tt_only' + +end + diff --git a/lib/rdoc/markup/.document b/lib/rdoc/markup/.document deleted file mode 100644 index 3cf4f21bd7..0000000000 --- a/lib/rdoc/markup/.document +++ /dev/null @@ -1,2 +0,0 @@ -simple_markup -simple_markup.rb diff --git a/lib/rdoc/markup/attr_changer.rb b/lib/rdoc/markup/attr_changer.rb new file mode 100644 index 0000000000..4c4bc6479e --- /dev/null +++ b/lib/rdoc/markup/attr_changer.rb @@ -0,0 +1,23 @@ +# frozen_string_literal: true +class RDoc::Markup + + AttrChanger = Struct.new :turn_on, :turn_off # :nodoc: + +end + +## +# An AttrChanger records a change in attributes. It contains a bitmap of the +# attributes to turn on, and a bitmap of those to turn off. + +class RDoc::Markup::AttrChanger + + def to_s # :nodoc: + "Attr: +#{turn_on}/-#{turn_off}" + end + + def inspect # :nodoc: + '+%d/-%d' % [turn_on, turn_off] + end + +end + diff --git a/lib/rdoc/markup/attr_span.rb b/lib/rdoc/markup/attr_span.rb new file mode 100644 index 0000000000..63aace60d2 --- /dev/null +++ b/lib/rdoc/markup/attr_span.rb @@ -0,0 +1,30 @@ +# frozen_string_literal: true +## +# An array of attributes which parallels the characters in a string. + +class RDoc::Markup::AttrSpan + + ## + # Creates a new AttrSpan for +length+ characters + + def initialize(length) + @attrs = Array.new(length, 0) + end + + ## + # Toggles +bits+ from +start+ to +length+ + def set_attrs(start, length, bits) + for i in start ... (start+length) + @attrs[i] |= bits + end + end + + ## + # Accesses flags for character +n+ + + def [](n) + @attrs[n] + end + +end + diff --git a/lib/rdoc/markup/attribute_manager.rb b/lib/rdoc/markup/attribute_manager.rb new file mode 100644 index 0000000000..a10f731615 --- /dev/null +++ b/lib/rdoc/markup/attribute_manager.rb @@ -0,0 +1,344 @@ +# frozen_string_literal: true +## +# Manages changes of attributes in a block of text + +class RDoc::Markup::AttributeManager + + ## + # The NUL character + + NULL = "\000".freeze + + #-- + # We work by substituting non-printing characters in to the text. For now + # I'm assuming that I can substitute a character in the range 0..8 for a 7 + # bit character without damaging the encoded string, but this might be + # optimistic + #++ + + A_PROTECT = 004 # :nodoc: + + ## + # Special mask character to prevent inline markup handling + + PROTECT_ATTR = A_PROTECT.chr # :nodoc: + + ## + # The attributes enabled for this markup object. + + attr_reader :attributes + + ## + # This maps delimiters that occur around words (such as *bold* or +tt+) + # where the start and end delimiters and the same. This lets us optimize + # the regexp + + attr_reader :matching_word_pairs + + ## + # And this is used when the delimiters aren't the same. In this case the + # hash maps a pattern to the attribute character + + attr_reader :word_pair_map + + ## + # This maps HTML tags to the corresponding attribute char + + attr_reader :html_tags + + ## + # A \ in front of a character that would normally be processed turns off + # processing. We do this by turning \< into <#{PROTECT} + + attr_reader :protectable + + ## + # And this maps _special_ sequences to a name. A special sequence is + # something like a WikiWord + + attr_reader :special + + ## + # Creates a new attribute manager that understands bold, emphasized and + # teletype text. + + def initialize + @html_tags = {} + @matching_word_pairs = {} + @protectable = %w[<] + @special = [] + @word_pair_map = {} + @attributes = RDoc::Markup::Attributes.new + + add_word_pair "*", "*", :BOLD + add_word_pair "_", "_", :EM + add_word_pair "+", "+", :TT + + add_html "em", :EM + add_html "i", :EM + add_html "b", :BOLD + add_html "tt", :TT + add_html "code", :TT + end + + ## + # Return an attribute object with the given turn_on and turn_off bits set + + def attribute(turn_on, turn_off) + RDoc::Markup::AttrChanger.new turn_on, turn_off + end + + ## + # Changes the current attribute from +current+ to +new+ + + def change_attribute current, new + diff = current ^ new + attribute(new & diff, current & diff) + end + + ## + # Used by the tests to change attributes by name from +current_set+ to + # +new_set+ + + def changed_attribute_by_name current_set, new_set + current = new = 0 + current_set.each do |name| + current |= @attributes.bitmap_for(name) + end + + new_set.each do |name| + new |= @attributes.bitmap_for(name) + end + + change_attribute(current, new) + end + + ## + # Copies +start_pos+ to +end_pos+ from the current string + + def copy_string(start_pos, end_pos) + res = @str[start_pos...end_pos] + res.gsub!(/\000/, '') + res + end + + ## + # Map attributes like <b>text</b>to the sequence + # \001\002<char>\001\003<char>, where <char> is a per-attribute specific + # character + + def convert_attrs(str, attrs) + # first do matching ones + tags = @matching_word_pairs.keys.join("") + + re = /(^|\W)([#{tags}])([#\\]?[\w:.\/-]+?\S?)\2(\W|$)/ + + 1 while str.gsub!(re) do + attr = @matching_word_pairs[$2] + attrs.set_attrs($`.length + $1.length + $2.length, $3.length, attr) + $1 + NULL * $2.length + $3 + NULL * $2.length + $4 + end + + # then non-matching + unless @word_pair_map.empty? then + @word_pair_map.each do |regexp, attr| + str.gsub!(regexp) { + attrs.set_attrs($`.length + $1.length, $2.length, attr) + NULL * $1.length + $2 + NULL * $3.length + } + end + end + end + + ## + # Converts HTML tags to RDoc attributes + + def convert_html(str, attrs) + tags = @html_tags.keys.join '|' + + 1 while str.gsub!(/<(#{tags})>(.*?)<\/\1>/i) { + attr = @html_tags[$1.downcase] + html_length = $1.length + 2 + seq = NULL * html_length + attrs.set_attrs($`.length + html_length, $2.length, attr) + seq + $2 + seq + NULL + } + end + + ## + # Converts special sequences to RDoc attributes + + def convert_specials str, attrs + @special.each do |regexp, attribute| + str.scan(regexp) do + capture = $~.size == 1 ? 0 : 1 + + s, e = $~.offset capture + + attrs.set_attrs s, e - s, attribute | @attributes.special + end + end + end + + ## + # Escapes special sequences of text to prevent conversion to RDoc + + def mask_protected_sequences + # protect __send__, __FILE__, etc. + @str.gsub!(/__([a-z]+)__/i, + "_#{PROTECT_ATTR}_#{PROTECT_ATTR}\\1_#{PROTECT_ATTR}_#{PROTECT_ATTR}") + @str.gsub!(/(\A|[^\\])\\([#{Regexp.escape @protectable.join}])/m, + "\\1\\2#{PROTECT_ATTR}") + @str.gsub!(/\\(\\[#{Regexp.escape @protectable.join}])/m, "\\1") + end + + ## + # Unescapes special sequences of text + + def unmask_protected_sequences + @str.gsub!(/(.)#{PROTECT_ATTR}/, "\\1\000") + end + + ## + # Adds a markup class with +name+ for words wrapped in the +start+ and + # +stop+ character. To make words wrapped with "*" bold: + # + # am.add_word_pair '*', '*', :BOLD + + def add_word_pair(start, stop, name) + raise ArgumentError, "Word flags may not start with '<'" if + start[0,1] == '<' + + bitmap = @attributes.bitmap_for name + + if start == stop then + @matching_word_pairs[start] = bitmap + else + pattern = /(#{Regexp.escape start})(\S+)(#{Regexp.escape stop})/ + @word_pair_map[pattern] = bitmap + end + + @protectable << start[0,1] + @protectable.uniq! + end + + ## + # Adds a markup class with +name+ for words surrounded by HTML tag +tag+. + # To process emphasis tags: + # + # am.add_html 'em', :EM + + def add_html(tag, name) + @html_tags[tag.downcase] = @attributes.bitmap_for name + end + + ## + # Adds a special handler for +pattern+ with +name+. A simple URL handler + # would be: + # + # @am.add_special(/((https?:)\S+\w)/, :HYPERLINK) + + def add_special pattern, name + @special << [pattern, @attributes.bitmap_for(name)] + end + + ## + # Processes +str+ converting attributes, HTML and specials + + def flow str + @str = str.dup + + mask_protected_sequences + + @attrs = RDoc::Markup::AttrSpan.new @str.length + + convert_attrs @str, @attrs + convert_html @str, @attrs + convert_specials @str, @attrs + + unmask_protected_sequences + + split_into_flow + end + + ## + # Debug method that prints a string along with its attributes + + def display_attributes + puts + puts @str.tr(NULL, "!") + bit = 1 + 16.times do |bno| + line = "" + @str.length.times do |i| + if (@attrs[i] & bit) == 0 + line << " " + else + if bno.zero? + line << "S" + else + line << ("%d" % (bno+1)) + end + end + end + puts(line) unless line =~ /^ *$/ + bit <<= 1 + end + end + + ## + # Splits the string into chunks by attribute change + + def split_into_flow + res = [] + current_attr = 0 + + str_len = @str.length + + # skip leading invisible text + i = 0 + i += 1 while i < str_len and @str[i].chr == "\0" + start_pos = i + + # then scan the string, chunking it on attribute changes + while i < str_len + new_attr = @attrs[i] + if new_attr != current_attr + if i > start_pos + res << copy_string(start_pos, i) + start_pos = i + end + + res << change_attribute(current_attr, new_attr) + current_attr = new_attr + + if (current_attr & @attributes.special) != 0 then + i += 1 while + i < str_len and (@attrs[i] & @attributes.special) != 0 + + res << RDoc::Markup::Special.new(current_attr, + copy_string(start_pos, i)) + start_pos = i + next + end + end + + # move on, skipping any invisible characters + begin + i += 1 + end while i < str_len and @str[i].chr == "\0" + end + + # tidy up trailing text + if start_pos < str_len + res << copy_string(start_pos, str_len) + end + + # and reset to all attributes off + res << change_attribute(current_attr, 0) if current_attr != 0 + + res + end + +end + diff --git a/lib/rdoc/markup/attributes.rb b/lib/rdoc/markup/attributes.rb new file mode 100644 index 0000000000..ec30160d3d --- /dev/null +++ b/lib/rdoc/markup/attributes.rb @@ -0,0 +1,71 @@ +# frozen_string_literal: true +## +# We manage a set of attributes. Each attribute has a symbol name and a bit +# value. + +class RDoc::Markup::Attributes + + ## + # The special attribute type. See RDoc::Markup#add_special + + attr_reader :special + + ## + # Creates a new attributes set. + + def initialize + @special = 1 + + @name_to_bitmap = [ + [:_SPECIAL_, @special], + ] + + @next_bitmap = @special << 1 + end + + ## + # Returns a unique bit for +name+ + + def bitmap_for name + bitmap = @name_to_bitmap.assoc name + + unless bitmap then + bitmap = @next_bitmap + @next_bitmap <<= 1 + @name_to_bitmap << [name, bitmap] + else + bitmap = bitmap.last + end + + bitmap + end + + ## + # Returns a string representation of +bitmap+ + + def as_string bitmap + return 'none' if bitmap.zero? + res = [] + + @name_to_bitmap.each do |name, bit| + res << name if (bitmap & bit) != 0 + end + + res.join ',' + end + + ## + # yields each attribute name in +bitmap+ + + def each_name_of bitmap + return enum_for __method__, bitmap unless block_given? + + @name_to_bitmap.each do |name, bit| + next if bit == @special + + yield name.to_s if (bitmap & bit) != 0 + end + end + +end + diff --git a/lib/rdoc/markup/blank_line.rb b/lib/rdoc/markup/blank_line.rb new file mode 100644 index 0000000000..3129ab5e7f --- /dev/null +++ b/lib/rdoc/markup/blank_line.rb @@ -0,0 +1,28 @@ +# frozen_string_literal: true +## +# An empty line. This class is a singleton. + +class RDoc::Markup::BlankLine + + @instance = new + + ## + # RDoc::Markup::BlankLine is a singleton + + def self.new + @instance + end + + ## + # Calls #accept_blank_line on +visitor+ + + def accept visitor + visitor.accept_blank_line self + end + + def pretty_print q # :nodoc: + q.text 'blankline' + end + +end + diff --git a/lib/rdoc/markup/block_quote.rb b/lib/rdoc/markup/block_quote.rb new file mode 100644 index 0000000000..7a4b3e36b0 --- /dev/null +++ b/lib/rdoc/markup/block_quote.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true +## +# A quoted section which contains markup items. + +class RDoc::Markup::BlockQuote < RDoc::Markup::Raw + + ## + # Calls #accept_block_quote on +visitor+ + + def accept visitor + visitor.accept_block_quote self + end + +end + diff --git a/lib/rdoc/markup/document.rb b/lib/rdoc/markup/document.rb new file mode 100644 index 0000000000..f3a5de1fc3 --- /dev/null +++ b/lib/rdoc/markup/document.rb @@ -0,0 +1,165 @@ +# frozen_string_literal: true +## +# A Document containing lists, headings, paragraphs, etc. + +class RDoc::Markup::Document + + include Enumerable + + ## + # The file this document was created from. See also + # RDoc::ClassModule#add_comment + + attr_reader :file + + ## + # If a heading is below the given level it will be omitted from the + # table_of_contents + + attr_accessor :omit_headings_below + + ## + # The parts of the Document + + attr_reader :parts + + ## + # Creates a new Document with +parts+ + + def initialize *parts + @parts = [] + @parts.concat parts + + @file = nil + @omit_headings_from_table_of_contents_below = nil + end + + ## + # Appends +part+ to the document + + def << part + case part + when RDoc::Markup::Document then + unless part.empty? then + parts.concat part.parts + parts << RDoc::Markup::BlankLine.new + end + when String then + raise ArgumentError, + "expected RDoc::Markup::Document and friends, got String" unless + part.empty? + else + parts << part + end + end + + def == other # :nodoc: + self.class == other.class and + @file == other.file and + @parts == other.parts + end + + ## + # Runs this document and all its #items through +visitor+ + + def accept visitor + visitor.start_accepting + + visitor.accept_document self + + visitor.end_accepting + end + + ## + # Concatenates the given +parts+ onto the document + + def concat parts + self.parts.concat parts + end + + ## + # Enumerator for the parts of this document + + def each &block + @parts.each(&block) + end + + ## + # Does this document have no parts? + + def empty? + @parts.empty? or (@parts.length == 1 and merged? and @parts.first.empty?) + end + + ## + # The file this Document was created from. + + def file= location + @file = case location + when RDoc::TopLevel then + location.relative_name + else + location + end + end + + ## + # When this is a collection of documents (#file is not set and this document + # contains only other documents as its direct children) #merge replaces + # documents in this class with documents from +other+ when the file matches + # and adds documents from +other+ when the files do not. + # + # The information in +other+ is preferred over the receiver + + def merge other + if empty? then + @parts = other.parts + return self + end + + other.parts.each do |other_part| + self.parts.delete_if do |self_part| + self_part.file and self_part.file == other_part.file + end + + self.parts << other_part + end + + self + end + + ## + # Does this Document contain other Documents? + + def merged? + RDoc::Markup::Document === @parts.first + end + + def pretty_print q # :nodoc: + start = @file ? "[doc (#{@file}): " : '[doc: ' + + q.group 2, start, ']' do + q.seplist @parts do |part| + q.pp part + end + end + end + + ## + # Appends +parts+ to the document + + def push *parts + self.parts.concat parts + end + + ## + # Returns an Array of headings in the document. + # + # Require 'rdoc/markup/formatter' before calling this method. + + def table_of_contents + accept RDoc::Markup::ToTableOfContents.to_toc + end + +end + diff --git a/lib/rdoc/markup/formatter.rb b/lib/rdoc/markup/formatter.rb new file mode 100644 index 0000000000..5dc71d2242 --- /dev/null +++ b/lib/rdoc/markup/formatter.rb @@ -0,0 +1,265 @@ +# frozen_string_literal: true +## +# Base class for RDoc markup formatters +# +# Formatters are a visitor that converts an RDoc::Markup tree (from a comment) +# into some kind of output. RDoc ships with formatters for converting back to +# rdoc, ANSI text, HTML, a Table of Contents and other formats. +# +# If you'd like to write your own Formatter use +# RDoc::Markup::FormatterTestCase. If you're writing a text-output formatter +# use RDoc::Markup::TextFormatterTestCase which provides extra test cases. + +class RDoc::Markup::Formatter + + ## + # Tag for inline markup containing a +bit+ for the bitmask and the +on+ and + # +off+ triggers. + + InlineTag = Struct.new(:bit, :on, :off) + + ## + # Converts a target url to one that is relative to a given path + + def self.gen_relative_url path, target + from = File.dirname path + to, to_file = File.split target + + from = from.split "/" + to = to.split "/" + + from.delete '.' + to.delete '.' + + while from.size > 0 and to.size > 0 and from[0] == to[0] do + from.shift + to.shift + end + + from.fill ".." + from.concat to + from << to_file + File.join(*from) + end + + ## + # Creates a new Formatter + + def initialize options, markup = nil + @options = options + + @markup = markup || RDoc::Markup.new + @am = @markup.attribute_manager + @am.add_special(/<br>/, :HARD_BREAK) + + @attributes = @am.attributes + + @attr_tags = [] + + @in_tt = 0 + @tt_bit = @attributes.bitmap_for :TT + + @hard_break = '' + @from_path = '.' + end + + ## + # Adds +document+ to the output + + def accept_document document + document.parts.each do |item| + case item + when RDoc::Markup::Document then # HACK + accept_document item + else + item.accept self + end + end + end + + ## + # Adds a special for links of the form rdoc-...: + + def add_special_RDOCLINK + @markup.add_special(/rdoc-[a-z]+:[^\s\]]+/, :RDOCLINK) + end + + ## + # Adds a special for links of the form {<text>}[<url>] and <word>[<url>] + + def add_special_TIDYLINK + @markup.add_special(/(?: + \{.*?\} | # multi-word label + \b[^\s{}]+? # single-word label + ) + + \[\S+?\] # link target + /x, :TIDYLINK) + end + + ## + # Add a new set of tags for an attribute. We allow separate start and end + # tags for flexibility + + def add_tag(name, start, stop) + attr = @attributes.bitmap_for name + @attr_tags << InlineTag.new(attr, start, stop) + end + + ## + # Allows +tag+ to be decorated with additional information. + + def annotate(tag) + tag + end + + ## + # Marks up +content+ + + def convert content + @markup.convert content, self + end + + ## + # Converts flow items +flow+ + + def convert_flow(flow) + res = [] + + flow.each do |item| + case item + when String then + res << convert_string(item) + when RDoc::Markup::AttrChanger then + off_tags res, item + on_tags res, item + when RDoc::Markup::Special then + res << convert_special(item) + else + raise "Unknown flow element: #{item.inspect}" + end + end + + res.join + end + + ## + # Converts added specials. See RDoc::Markup#add_special + + def convert_special special + return special.text if in_tt? + + handled = false + + @attributes.each_name_of special.type do |name| + method_name = "handle_special_#{name}" + + if respond_to? method_name then + special.text = send method_name, special + handled = true + end + end + + unless handled then + special_name = @attributes.as_string special.type + + raise RDoc::Error, "Unhandled special #{special_name}: #{special}" + end + + special.text + end + + ## + # Converts a string to be fancier if desired + + def convert_string string + string + end + + ## + # Use ignore in your subclass to ignore the content of a node. + # + # ## + # # We don't support raw nodes in ToNoRaw + # + # alias accept_raw ignore + + def ignore *node + end + + ## + # Are we currently inside tt tags? + + def in_tt? + @in_tt > 0 + end + + ## + # Turns on tags for +item+ on +res+ + + 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 then + res << annotate(tag.on) + @in_tt += 1 if tt? tag + end + end + end + + ## + # Turns off tags for +item+ on +res+ + + 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 then + @in_tt -= 1 if tt? tag + res << annotate(tag.off) + end + end + end + + ## + # Extracts and a scheme, url and an anchor id from +url+ and returns them. + + def parse_url url + case url + when /^rdoc-label:([^:]*)(?::(.*))?/ then + scheme = 'link' + path = "##{$1}" + id = " id=\"#{$2}\"" if $2 + when /([A-Za-z]+):(.*)/ then + scheme = $1.downcase + path = $2 + when /^#/ then + else + scheme = 'http' + path = url + url = url + end + + if scheme == 'link' then + url = if path[0, 1] == '#' then # is this meaningful? + path + else + self.class.gen_relative_url @from_path, path + end + end + + [scheme, url, id] + end + + ## + # Is +tag+ a tt tag? + + def tt? tag + tag.bit == @tt_bit + end + +end + diff --git a/lib/rdoc/markup/formatter_test_case.rb b/lib/rdoc/markup/formatter_test_case.rb new file mode 100644 index 0000000000..076b7c81bb --- /dev/null +++ b/lib/rdoc/markup/formatter_test_case.rb @@ -0,0 +1,764 @@ +# frozen_string_literal: true +require 'minitest/unit' + +## +# Test case for creating new RDoc::Markup formatters. See +# test/test_rdoc_markup_to_*.rb for examples. +# +# This test case adds a variety of tests to your subclass when +# #add_visitor_tests is called. Most tests set up a scenario then call a +# method you will provide to perform the assertion on the output. +# +# Your subclass must instantiate a visitor and assign it to <tt>@to</tt>. +# +# For example, test_accept_blank_line sets up a RDoc::Markup::BlockLine then +# calls accept_blank_line on your visitor. You are responsible for asserting +# that the output is correct. +# +# Example: +# +# class TestRDocMarkupToNewFormat < RDoc::Markup::FormatterTestCase +# +# add_visitor_tests +# +# def setup +# super +# +# @to = RDoc::Markup::ToNewFormat.new +# end +# +# def accept_blank_line +# assert_equal :junk, @to.res.join +# end +# +# # ... +# +# end + +class RDoc::Markup::FormatterTestCase < RDoc::TestCase + + ## + # Call #setup when inheriting from this test case. + # + # Provides the following instance variables: + # + # +@m+:: RDoc::Markup.new + # +@RM+:: RDoc::Markup # to reduce typing + # +@bullet_list+:: @RM::List.new :BULLET, # ... + # +@label_list+:: @RM::List.new :LABEL, # ... + # +@lalpha_list+:: @RM::List.new :LALPHA, # ... + # +@note_list+:: @RM::List.new :NOTE, # ... + # +@number_list+:: @RM::List.new :NUMBER, # ... + # +@ualpha_list+:: @RM::List.new :UALPHA, # ... + + def setup + super + + @options = RDoc::Options.new + + @m = @RM.new + + @bullet_list = @RM::List.new(:BULLET, + @RM::ListItem.new(nil, @RM::Paragraph.new('l1')), + @RM::ListItem.new(nil, @RM::Paragraph.new('l2'))) + + @label_list = @RM::List.new(:LABEL, + @RM::ListItem.new('cat', @RM::Paragraph.new('cats are cool')), + @RM::ListItem.new('dog', @RM::Paragraph.new('dogs are cool too'))) + + @lalpha_list = @RM::List.new(:LALPHA, + @RM::ListItem.new(nil, @RM::Paragraph.new('l1')), + @RM::ListItem.new(nil, @RM::Paragraph.new('l2'))) + + @note_list = @RM::List.new(:NOTE, + @RM::ListItem.new('cat', @RM::Paragraph.new('cats are cool')), + @RM::ListItem.new('dog', @RM::Paragraph.new('dogs are cool too'))) + + @number_list = @RM::List.new(:NUMBER, + @RM::ListItem.new(nil, @RM::Paragraph.new('l1')), + @RM::ListItem.new(nil, @RM::Paragraph.new('l2'))) + + @ualpha_list = @RM::List.new(:UALPHA, + @RM::ListItem.new(nil, @RM::Paragraph.new('l1')), + @RM::ListItem.new(nil, @RM::Paragraph.new('l2'))) + end + + ## + # Call to add the visitor tests to your test case + + def self.add_visitor_tests + class_eval do + + ## + # Calls start_accepting which needs to verify startup state + + def test_start_accepting + @to.start_accepting + + start_accepting + end + + ## + # Calls end_accepting on your test case which needs to call + # <tt>@to.end_accepting</tt> and verify document generation + + def test_end_accepting + @to.start_accepting + @to.res << 'hi' + + end_accepting + end + + ## + # Calls accept_blank_line + + def test_accept_blank_line + @to.start_accepting + + @to.accept_blank_line @RM::BlankLine.new + + accept_blank_line + end + + ## + # Calls accept_block_quote + + def test_accept_block_quote + @to.start_accepting + + @to.accept_block_quote block para 'quote' + + accept_block_quote + end + ## + # Test case that calls <tt>@to.accept_document</tt> + + def test_accept_document + @to.start_accepting + @to.accept_document @RM::Document.new @RM::Paragraph.new 'hello' + + accept_document + end + + ## + # Calls accept_heading with a level 5 RDoc::Markup::Heading + + def test_accept_heading + @to.start_accepting + + @to.accept_heading @RM::Heading.new(5, 'Hello') + + accept_heading + end + + ## + # Calls accept_heading_1 with a level 1 RDoc::Markup::Heading + + def test_accept_heading_1 + @to.start_accepting + + @to.accept_heading @RM::Heading.new(1, 'Hello') + + accept_heading_1 + end + + ## + # Calls accept_heading_2 with a level 2 RDoc::Markup::Heading + + def test_accept_heading_2 + @to.start_accepting + + @to.accept_heading @RM::Heading.new(2, 'Hello') + + accept_heading_2 + end + + ## + # Calls accept_heading_3 with a level 3 RDoc::Markup::Heading + + def test_accept_heading_3 + @to.start_accepting + + @to.accept_heading @RM::Heading.new(3, 'Hello') + + accept_heading_3 + end + + ## + # Calls accept_heading_4 with a level 4 RDoc::Markup::Heading + + def test_accept_heading_4 + @to.start_accepting + + @to.accept_heading @RM::Heading.new(4, 'Hello') + + accept_heading_4 + end + + ## + # Calls accept_heading_b with a bold level 1 RDoc::Markup::Heading + + def test_accept_heading_b + @to.start_accepting + + @to.accept_heading @RM::Heading.new(1, '*Hello*') + + accept_heading_b + end + + ## + # Calls accept_heading_suppressed_crossref with a level 1 + # RDoc::Markup::Heading containing a suppressed crossref + + def test_accept_heading_suppressed_crossref # HACK to_html_crossref test + @to.start_accepting + + @to.accept_heading @RM::Heading.new(1, '\\Hello') + + accept_heading_suppressed_crossref + end + + ## + # Calls accept_paragraph + + def test_accept_paragraph + @to.start_accepting + + @to.accept_paragraph @RM::Paragraph.new('hi') + + accept_paragraph + end + + ## + # Calls accept_paragraph_b with a RDoc::Markup::Paragraph containing + # bold words + + def test_accept_paragraph_b + @to.start_accepting + + @to.accept_paragraph @RM::Paragraph.new('reg <b>bold words</b> reg') + + accept_paragraph_b + end + + ## + # Calls accept_paragraph_br with a RDoc::Markup::Paragraph containing + # a \<br> + + def test_accept_paragraph_br + @to.start_accepting + + @to.accept_paragraph para 'one<br>two' + + accept_paragraph_br + end + + ## + # Calls accept_paragraph with a Paragraph containing a hard break + + def test_accept_paragraph_break + @to.start_accepting + + @to.accept_paragraph para('hello', hard_break, 'world') + + accept_paragraph_break + end + + ## + # Calls accept_paragraph_i with a RDoc::Markup::Paragraph containing + # emphasized words + + def test_accept_paragraph_i + @to.start_accepting + + @to.accept_paragraph @RM::Paragraph.new('reg <em>italic words</em> reg') + + accept_paragraph_i + end + + ## + # Calls accept_paragraph_plus with a RDoc::Markup::Paragraph containing + # teletype words + + def test_accept_paragraph_plus + @to.start_accepting + + @to.accept_paragraph @RM::Paragraph.new('reg +teletype+ reg') + + accept_paragraph_plus + end + + ## + # Calls accept_paragraph_star with a RDoc::Markup::Paragraph containing + # bold words + + def test_accept_paragraph_star + @to.start_accepting + + @to.accept_paragraph @RM::Paragraph.new('reg *bold* reg') + + accept_paragraph_star + end + + ## + # Calls accept_paragraph_underscore with a RDoc::Markup::Paragraph + # containing emphasized words + + def test_accept_paragraph_underscore + @to.start_accepting + + @to.accept_paragraph @RM::Paragraph.new('reg _italic_ reg') + + accept_paragraph_underscore + end + + ## + # Calls accept_verbatim with a RDoc::Markup::Verbatim + + def test_accept_verbatim + @to.start_accepting + + @to.accept_verbatim @RM::Verbatim.new("hi\n", " world\n") + + accept_verbatim + end + + ## + # Calls accept_raw with a RDoc::Markup::Raw + + def test_accept_raw + @to.start_accepting + + @to.accept_raw @RM::Raw.new("<table>", + "<tr><th>Name<th>Count", + "<tr><td>a<td>1", + "<tr><td>b<td>2", + "</table>") + + accept_raw + end + + ## + # Calls accept_rule with a RDoc::Markup::Rule + + def test_accept_rule + @to.start_accepting + + @to.accept_rule @RM::Rule.new(4) + + accept_rule + end + + ## + # Calls accept_list_item_start_bullet + + def test_accept_list_item_start_bullet + @to.start_accepting + + @to.accept_list_start @bullet_list + + @to.accept_list_item_start @bullet_list.items.first + + accept_list_item_start_bullet + end + + ## + # Calls accept_list_item_start_label + + def test_accept_list_item_start_label + @to.start_accepting + + @to.accept_list_start @label_list + + @to.accept_list_item_start @label_list.items.first + + accept_list_item_start_label + end + + ## + # Calls accept_list_item_start_lalpha + + def test_accept_list_item_start_lalpha + @to.start_accepting + + @to.accept_list_start @lalpha_list + + @to.accept_list_item_start @lalpha_list.items.first + + accept_list_item_start_lalpha + end + + ## + # Calls accept_list_item_start_note + + def test_accept_list_item_start_note + @to.start_accepting + + @to.accept_list_start @note_list + + @to.accept_list_item_start @note_list.items.first + + accept_list_item_start_note + end + + ## + # Calls accept_list_item_start_note_2 + + def test_accept_list_item_start_note_2 + list = list(:NOTE, + item('<tt>teletype</tt>', + para('teletype description'))) + + @to.start_accepting + + list.accept @to + + @to.end_accepting + + accept_list_item_start_note_2 + end + + ## + # Calls accept_list_item_start_note_multi_description + + def test_accept_list_item_start_note_multi_description + list = list(:NOTE, + item(%w[label], + para('description one')), + item(nil, para('description two'))) + + @to.start_accepting + + list.accept @to + + @to.end_accepting + + accept_list_item_start_note_multi_description + end + + ## + # Calls accept_list_item_start_note_multi_label + + def test_accept_list_item_start_note_multi_label + list = list(:NOTE, + item(%w[one two], + para('two headers'))) + + @to.start_accepting + + list.accept @to + + @to.end_accepting + + accept_list_item_start_note_multi_label + end + + ## + # Calls accept_list_item_start_number + + def test_accept_list_item_start_number + @to.start_accepting + + @to.accept_list_start @number_list + + @to.accept_list_item_start @number_list.items.first + + accept_list_item_start_number + end + + ## + # Calls accept_list_item_start_ualpha + + def test_accept_list_item_start_ualpha + @to.start_accepting + + @to.accept_list_start @ualpha_list + + @to.accept_list_item_start @ualpha_list.items.first + + accept_list_item_start_ualpha + end + + ## + # Calls accept_list_item_end_bullet + + def test_accept_list_item_end_bullet + @to.start_accepting + + @to.accept_list_start @bullet_list + + @to.accept_list_item_start @bullet_list.items.first + + @to.accept_list_item_end @bullet_list.items.first + + accept_list_item_end_bullet + end + + ## + # Calls accept_list_item_end_label + + def test_accept_list_item_end_label + @to.start_accepting + + @to.accept_list_start @label_list + + @to.accept_list_item_start @label_list.items.first + + @to.accept_list_item_end @label_list.items.first + + accept_list_item_end_label + end + + ## + # Calls accept_list_item_end_lalpha + + def test_accept_list_item_end_lalpha + @to.start_accepting + + @to.accept_list_start @lalpha_list + + @to.accept_list_item_start @lalpha_list.items.first + + @to.accept_list_item_end @lalpha_list.items.first + + accept_list_item_end_lalpha + end + + ## + # Calls accept_list_item_end_note + + def test_accept_list_item_end_note + @to.start_accepting + + @to.accept_list_start @note_list + + @to.accept_list_item_start @note_list.items.first + + @to.accept_list_item_end @note_list.items.first + + accept_list_item_end_note + end + + ## + # Calls accept_list_item_end_number + + def test_accept_list_item_end_number + @to.start_accepting + + @to.accept_list_start @number_list + + @to.accept_list_item_start @number_list.items.first + + @to.accept_list_item_end @number_list.items.first + + accept_list_item_end_number + end + + ## + # Calls accept_list_item_end_ualpha + + def test_accept_list_item_end_ualpha + @to.start_accepting + + @to.accept_list_start @ualpha_list + + @to.accept_list_item_start @ualpha_list.items.first + + @to.accept_list_item_end @ualpha_list.items.first + + accept_list_item_end_ualpha + end + + ## + # Calls accept_list_start_bullet + + def test_accept_list_start_bullet + @to.start_accepting + + @to.accept_list_start @bullet_list + + accept_list_start_bullet + end + + ## + # Calls accept_list_start_label + + def test_accept_list_start_label + @to.start_accepting + + @to.accept_list_start @label_list + + accept_list_start_label + end + + ## + # Calls accept_list_start_lalpha + + def test_accept_list_start_lalpha + @to.start_accepting + + @to.accept_list_start @lalpha_list + + accept_list_start_lalpha + end + + ## + # Calls accept_list_start_note + + def test_accept_list_start_note + @to.start_accepting + + @to.accept_list_start @note_list + + accept_list_start_note + end + + ## + # Calls accept_list_start_number + + def test_accept_list_start_number + @to.start_accepting + + @to.accept_list_start @number_list + + accept_list_start_number + end + + ## + # Calls accept_list_start_ualpha + + def test_accept_list_start_ualpha + @to.start_accepting + + @to.accept_list_start @ualpha_list + + accept_list_start_ualpha + end + + ## + # Calls accept_list_end_bullet + + def test_accept_list_end_bullet + @to.start_accepting + + @to.accept_list_start @bullet_list + + @to.accept_list_end @bullet_list + + accept_list_end_bullet + end + + ## + # Calls accept_list_end_label + + def test_accept_list_end_label + @to.start_accepting + + @to.accept_list_start @label_list + + @to.accept_list_end @label_list + + accept_list_end_label + end + + ## + # Calls accept_list_end_lalpha + + def test_accept_list_end_lalpha + @to.start_accepting + + @to.accept_list_start @lalpha_list + + @to.accept_list_end @lalpha_list + + accept_list_end_lalpha + end + + ## + # Calls accept_list_end_number + + def test_accept_list_end_number + @to.start_accepting + + @to.accept_list_start @number_list + + @to.accept_list_end @number_list + + accept_list_end_number + end + + ## + # Calls accept_list_end_note + + def test_accept_list_end_note + @to.start_accepting + + @to.accept_list_start @note_list + + @to.accept_list_end @note_list + + accept_list_end_note + end + + ## + # Calls accept_list_end_ualpha + + def test_accept_list_end_ualpha + @to.start_accepting + + @to.accept_list_start @ualpha_list + + @to.accept_list_end @ualpha_list + + accept_list_end_ualpha + end + + ## + # Calls list_nested with a two-level list + + def test_list_nested + doc = @RM::Document.new( + @RM::List.new(:BULLET, + @RM::ListItem.new(nil, + @RM::Paragraph.new('l1'), + @RM::List.new(:BULLET, + @RM::ListItem.new(nil, + @RM::Paragraph.new('l1.1')))), + @RM::ListItem.new(nil, + @RM::Paragraph.new('l2')))) + + doc.accept @to + + list_nested + end + + ## + # Calls list_verbatim with a list containing a verbatim block + + def test_list_verbatim # HACK overblown + doc = + doc( + list(:BULLET, + item(nil, + para('list stuff'), + blank_line, + verb("* list\n", + " with\n", + "\n", + " second\n", + "\n", + " 1. indented\n", + " 2. numbered\n", + "\n", + " third\n", + "\n", + "* second\n")))) + + doc.accept @to + + list_verbatim + end + end + end + +end diff --git a/lib/rdoc/markup/hard_break.rb b/lib/rdoc/markup/hard_break.rb new file mode 100644 index 0000000000..046068d5c2 --- /dev/null +++ b/lib/rdoc/markup/hard_break.rb @@ -0,0 +1,32 @@ +# frozen_string_literal: true +## +# A hard-break in the middle of a paragraph. + +class RDoc::Markup::HardBreak + + @instance = new + + ## + # RDoc::Markup::HardBreak is a singleton + + def self.new + @instance + end + + ## + # Calls #accept_hard_break on +visitor+ + + def accept visitor + visitor.accept_hard_break self + end + + def == other # :nodoc: + self.class === other + end + + def pretty_print q # :nodoc: + q.text "[break]" + end + +end + diff --git a/lib/rdoc/markup/heading.rb b/lib/rdoc/markup/heading.rb new file mode 100644 index 0000000000..233774c5c4 --- /dev/null +++ b/lib/rdoc/markup/heading.rb @@ -0,0 +1,79 @@ +# frozen_string_literal: true +## +# A heading with a level (1-6) and text + +RDoc::Markup::Heading = + Struct.new :level, :text do + + @to_html = nil + @to_label = nil + + ## + # A singleton RDoc::Markup::ToLabel formatter for headings. + + def self.to_label + @to_label ||= RDoc::Markup::ToLabel.new + end + + ## + # A singleton plain HTML formatter for headings. Used for creating labels + # for the Table of Contents + + def self.to_html + return @to_html if @to_html + + markup = RDoc::Markup.new + markup.add_special RDoc::CrossReference::CROSSREF_REGEXP, :CROSSREF + + @to_html = RDoc::Markup::ToHtml.new nil + + def @to_html.handle_special_CROSSREF special + special.text.sub(/^\\/, '') + end + + @to_html + end + + ## + # Calls #accept_heading on +visitor+ + + def accept visitor + visitor.accept_heading self + end + + ## + # An HTML-safe anchor reference for this header. + + def aref + "label-#{self.class.to_label.convert text.dup}" + end + + ## + # Creates a fully-qualified label which will include the label from + # +context+. This helps keep ids unique in HTML. + + def label context = nil + label = aref + + label = [context.aref, label].compact.join '-' if + context and context.respond_to? :aref + + label + end + + ## + # HTML markup of the text of this label without the surrounding header + # element. + + def plain_html + self.class.to_html.to_html(text.dup) + end + + def pretty_print q # :nodoc: + q.group 2, "[head: #{level} ", ']' do + q.pp text + end + end + +end + diff --git a/lib/rdoc/markup/include.rb b/lib/rdoc/markup/include.rb new file mode 100644 index 0000000000..ad7c4a9640 --- /dev/null +++ b/lib/rdoc/markup/include.rb @@ -0,0 +1,43 @@ +# frozen_string_literal: true +## +# A file included at generation time. Objects of this class are created by +# RDoc::RD for an extension-less include. +# +# This implementation in incomplete. + +class RDoc::Markup::Include + + ## + # The filename to be included, without extension + + attr_reader :file + + ## + # Directories to search for #file + + attr_reader :include_path + + ## + # Creates a new include that will import +file+ from +include_path+ + + def initialize file, include_path + @file = file + @include_path = include_path + end + + def == other # :nodoc: + self.class === other and + @file == other.file and @include_path == other.include_path + end + + def pretty_print q # :nodoc: + q.group 2, '[incl ', ']' do + q.text file + q.breakable + q.text 'from ' + q.pp include_path + end + end + +end + diff --git a/lib/rdoc/markup/indented_paragraph.rb b/lib/rdoc/markup/indented_paragraph.rb new file mode 100644 index 0000000000..d42b2e52b8 --- /dev/null +++ b/lib/rdoc/markup/indented_paragraph.rb @@ -0,0 +1,48 @@ +# frozen_string_literal: true +## +# An Indented Paragraph of text + +class RDoc::Markup::IndentedParagraph < RDoc::Markup::Raw + + ## + # The indent in number of spaces + + attr_reader :indent + + ## + # Creates a new IndentedParagraph containing +parts+ indented with +indent+ + # spaces + + def initialize indent, *parts + @indent = indent + + super(*parts) + end + + def == other # :nodoc: + super and indent == other.indent + end + + ## + # Calls #accept_indented_paragraph on +visitor+ + + def accept visitor + visitor.accept_indented_paragraph self + end + + ## + # Joins the raw paragraph text and converts inline HardBreaks to the + # +hard_break+ text followed by the indent. + + def text hard_break = nil + @parts.map do |part| + if RDoc::Markup::HardBreak === part then + '%1$s%3$*2$s' % [hard_break, @indent, ' '] if hard_break + else + part + end + end.join + end + +end + diff --git a/lib/rdoc/markup/inline.rb b/lib/rdoc/markup/inline.rb new file mode 100644 index 0000000000..aba7ec21ce --- /dev/null +++ b/lib/rdoc/markup/inline.rb @@ -0,0 +1,2 @@ +# frozen_string_literal: true +warn "requiring rdoc/markup/inline is deprecated and will be removed in RDoc 4." if $-w diff --git a/lib/rdoc/markup/list.rb b/lib/rdoc/markup/list.rb new file mode 100644 index 0000000000..05c3609202 --- /dev/null +++ b/lib/rdoc/markup/list.rb @@ -0,0 +1,102 @@ +# frozen_string_literal: true +## +# A List is a homogeneous set of ListItems. +# +# The supported list types include: +# +# :BULLET:: +# An unordered list +# :LABEL:: +# An unordered definition list, but using an alternate RDoc::Markup syntax +# :LALPHA:: +# An ordered list using increasing lowercase English letters +# :NOTE:: +# An unordered definition list +# :NUMBER:: +# An ordered list using increasing Arabic numerals +# :UALPHA:: +# An ordered list using increasing uppercase English letters +# +# Definition lists behave like HTML definition lists. Each list item can +# describe multiple terms. See RDoc::Markup::ListItem for how labels and +# definition are stored as list items. + +class RDoc::Markup::List + + ## + # The list's type + + attr_accessor :type + + ## + # Items in the list + + attr_reader :items + + ## + # Creates a new list of +type+ with +items+. Valid list types are: + # +:BULLET+, +:LABEL+, +:LALPHA+, +:NOTE+, +:NUMBER+, +:UALPHA+ + + def initialize type = nil, *items + @type = type + @items = [] + @items.concat items + end + + ## + # Appends +item+ to the list + + def << item + @items << item + end + + def == other # :nodoc: + self.class == other.class and + @type == other.type and + @items == other.items + end + + ## + # Runs this list and all its #items through +visitor+ + + def accept visitor + visitor.accept_list_start self + + @items.each do |item| + item.accept visitor + end + + visitor.accept_list_end self + end + + ## + # Is the list empty? + + def empty? + @items.empty? + end + + ## + # Returns the last item in the list + + def last + @items.last + end + + def pretty_print q # :nodoc: + q.group 2, "[list: #{@type} ", ']' do + q.seplist @items do |item| + q.pp item + end + end + end + + ## + # Appends +items+ to the list + + def push *items + @items.concat items + end + +end + diff --git a/lib/rdoc/markup/list_item.rb b/lib/rdoc/markup/list_item.rb new file mode 100644 index 0000000000..d22554ee73 --- /dev/null +++ b/lib/rdoc/markup/list_item.rb @@ -0,0 +1,100 @@ +# frozen_string_literal: true +## +# An item within a List that contains paragraphs, headings, etc. +# +# For BULLET, NUMBER, LALPHA and UALPHA lists, the label will always be nil. +# For NOTE and LABEL lists, the list label may contain: +# +# * a single String for a single label +# * an Array of Strings for a list item with multiple terms +# * nil for an extra description attached to a previously labeled list item + +class RDoc::Markup::ListItem + + ## + # The label for the ListItem + + attr_accessor :label + + ## + # Parts of the ListItem + + attr_reader :parts + + ## + # Creates a new ListItem with an optional +label+ containing +parts+ + + def initialize label = nil, *parts + @label = label + @parts = [] + @parts.concat parts + end + + ## + # Appends +part+ to the ListItem + + def << part + @parts << part + end + + def == other # :nodoc: + self.class == other.class and + @label == other.label and + @parts == other.parts + end + + ## + # Runs this list item and all its #parts through +visitor+ + + def accept visitor + visitor.accept_list_item_start self + + @parts.each do |part| + part.accept visitor + end + + visitor.accept_list_item_end self + end + + ## + # Is the ListItem empty? + + def empty? + @parts.empty? + end + + ## + # Length of parts in the ListItem + + def length + @parts.length + end + + def pretty_print q # :nodoc: + q.group 2, '[item: ', ']' do + case @label + when Array then + q.pp @label + q.text ';' + q.breakable + when String then + q.pp @label + q.text ';' + q.breakable + end + + q.seplist @parts do |part| + q.pp part + end + end + end + + ## + # Adds +parts+ to the ListItem + + def push *parts + @parts.concat parts + end + +end + diff --git a/lib/rdoc/markup/paragraph.rb b/lib/rdoc/markup/paragraph.rb new file mode 100644 index 0000000000..a2e45ef009 --- /dev/null +++ b/lib/rdoc/markup/paragraph.rb @@ -0,0 +1,29 @@ +# frozen_string_literal: true +## +# A Paragraph of text + +class RDoc::Markup::Paragraph < RDoc::Markup::Raw + + ## + # Calls #accept_paragraph on +visitor+ + + def accept visitor + visitor.accept_paragraph self + end + + ## + # Joins the raw paragraph text and converts inline HardBreaks to the + # +hard_break+ text. + + def text hard_break = '' + @parts.map do |part| + if RDoc::Markup::HardBreak === part then + hard_break + else + part + end + end.join + end + +end + diff --git a/lib/rdoc/markup/parser.rb b/lib/rdoc/markup/parser.rb new file mode 100644 index 0000000000..f08587e676 --- /dev/null +++ b/lib/rdoc/markup/parser.rb @@ -0,0 +1,543 @@ +# frozen_string_literal: true +require 'strscan' + +## +# A recursive-descent parser for RDoc markup. +# +# The parser tokenizes an input string then parses the tokens into a Document. +# Documents can be converted into output formats by writing a visitor like +# RDoc::Markup::ToHTML. +# +# The parser only handles the block-level constructs Paragraph, List, +# ListItem, Heading, Verbatim, BlankLine and Rule. Inline markup such as +# <tt>\+blah\+</tt> is handled separately by RDoc::Markup::AttributeManager. +# +# To see what markup the Parser implements read RDoc. To see how to use +# RDoc markup to format text in your program read RDoc::Markup. + +class RDoc::Markup::Parser + + include RDoc::Text + + ## + # List token types + + LIST_TOKENS = [ + :BULLET, + :LABEL, + :LALPHA, + :NOTE, + :NUMBER, + :UALPHA, + ] + + ## + # Parser error subclass + + class Error < RuntimeError; end + + ## + # Raised when the parser is unable to handle the given markup + + class ParseError < Error; end + + ## + # Enables display of debugging information + + attr_accessor :debug + + ## + # Token accessor + + attr_reader :tokens + + ## + # Parses +str+ into a Document. + # + # Use RDoc::Markup#parse instead of this method. + + def self.parse str + parser = new + parser.tokenize str + doc = RDoc::Markup::Document.new + parser.parse doc + end + + ## + # Returns a token stream for +str+, for testing + + def self.tokenize str + parser = new + parser.tokenize str + parser.tokens + end + + ## + # Creates a new Parser. See also ::parse + + def initialize + @binary_input = nil + @current_token = nil + @debug = false + @input = nil + @input_encoding = nil + @line = 0 + @line_pos = 0 + @s = nil + @tokens = [] + end + + ## + # Builds a Heading of +level+ + + def build_heading level + type, text, = get + + text = case type + when :TEXT then + skip :NEWLINE + text + else + unget + '' + end + + RDoc::Markup::Heading.new level, text + end + + ## + # Builds a List flush to +margin+ + + def build_list margin + p :list_start => margin if @debug + + list = RDoc::Markup::List.new + label = nil + + until @tokens.empty? do + type, data, column, = get + + case type + when *LIST_TOKENS then + if column < margin || (list.type && list.type != type) then + unget + break + end + + list.type = type + peek_type, _, column, = peek_token + + case type + when :NOTE, :LABEL then + label = [] unless label + + if peek_type == :NEWLINE then + # description not on the same line as LABEL/NOTE + # skip the trailing newline & any blank lines below + while peek_type == :NEWLINE + get + peek_type, _, column, = peek_token + end + + # we may be: + # - at end of stream + # - at a column < margin: + # [text] + # blah blah blah + # - at the same column, but with a different type of list item + # [text] + # * blah blah + # - at the same column, with the same type of list item + # [one] + # [two] + # In all cases, we have an empty description. + # In the last case only, we continue. + if peek_type.nil? || column < margin then + empty = true + elsif column == margin then + case peek_type + when type + empty = :continue + when *LIST_TOKENS + empty = true + else + empty = false + end + else + empty = false + end + + if empty then + label << data + next if empty == :continue + break + end + end + else + data = nil + end + + if label then + data = label << data + label = nil + end + + list_item = RDoc::Markup::ListItem.new data + parse list_item, column + list << list_item + + else + unget + break + end + end + + p :list_end => margin if @debug + + if list.empty? then + return nil unless label + return nil unless [:LABEL, :NOTE].include? list.type + + list_item = RDoc::Markup::ListItem.new label, RDoc::Markup::BlankLine.new + list << list_item + end + + list + end + + ## + # Builds a Paragraph that is flush to +margin+ + + def build_paragraph margin + p :paragraph_start => margin if @debug + + paragraph = RDoc::Markup::Paragraph.new + + until @tokens.empty? do + type, data, column, = get + + if type == :TEXT and column == margin then + paragraph << data + + break if peek_token.first == :BREAK + + data << ' ' if skip :NEWLINE + else + unget + break + end + end + + paragraph.parts.last.sub!(/ \z/, '') # cleanup + + p :paragraph_end => margin if @debug + + paragraph + end + + ## + # Builds a Verbatim that is indented from +margin+. + # + # The verbatim block is shifted left (the least indented lines start in + # column 0). Each part of the verbatim is one line of text, always + # terminated by a newline. Blank lines always consist of a single newline + # character, and there is never a single newline at the end of the verbatim. + + def build_verbatim margin + p :verbatim_begin => margin if @debug + verbatim = RDoc::Markup::Verbatim.new + + min_indent = nil + generate_leading_spaces = true + line = ''.dup + + until @tokens.empty? do + type, data, column, = get + + if type == :NEWLINE then + line << data + verbatim << line + line = ''.dup + generate_leading_spaces = true + next + end + + if column <= margin + unget + break + end + + if generate_leading_spaces then + indent = column - margin + line << ' ' * indent + min_indent = indent if min_indent.nil? || indent < min_indent + generate_leading_spaces = false + end + + case type + when :HEADER then + line << '=' * data + _, _, peek_column, = peek_token + peek_column ||= column + data + indent = peek_column - column - data + line << ' ' * indent + when :RULE then + width = 2 + data + line << '-' * width + _, _, peek_column, = peek_token + peek_column ||= column + width + indent = peek_column - column - width + line << ' ' * indent + when :BREAK, :TEXT then + line << data + else # *LIST_TOKENS + list_marker = case type + when :BULLET then data + when :LABEL then "[#{data}]" + when :NOTE then "#{data}::" + else # :LALPHA, :NUMBER, :UALPHA + "#{data}." + end + line << list_marker + peek_type, _, peek_column = peek_token + unless peek_type == :NEWLINE then + peek_column ||= column + list_marker.length + indent = peek_column - column - list_marker.length + line << ' ' * indent + end + end + + end + + verbatim << line << "\n" unless line.empty? + verbatim.parts.each { |p| p.slice!(0, min_indent) unless p == "\n" } if min_indent > 0 + verbatim.normalize + + p :verbatim_end => margin if @debug + + verbatim + end + + ## + # The character offset for the input string at the given +byte_offset+ + + def char_pos byte_offset + @input.byteslice(0, byte_offset).length + end + + ## + # Pulls the next token from the stream. + + def get + @current_token = @tokens.shift + p :get => @current_token if @debug + @current_token + end + + ## + # Parses the tokens into an array of RDoc::Markup::XXX objects, + # and appends them to the passed +parent+ RDoc::Markup::YYY object. + # + # Exits at the end of the token stream, or when it encounters a token + # in a column less than +indent+ (unless it is a NEWLINE). + # + # Returns +parent+. + + def parse parent, indent = 0 + p :parse_start => indent if @debug + + until @tokens.empty? do + type, data, column, = get + + case type + when :BREAK then + parent << RDoc::Markup::BlankLine.new + skip :NEWLINE, false + next + when :NEWLINE then + # trailing newlines are skipped below, so this is a blank line + parent << RDoc::Markup::BlankLine.new + skip :NEWLINE, false + next + end + + # indentation change: break or verbatim + if column < indent then + unget + break + elsif column > indent then + unget + parent << build_verbatim(indent) + next + end + + # indentation is the same + case type + when :HEADER then + parent << build_heading(data) + when :RULE then + parent << RDoc::Markup::Rule.new(data) + skip :NEWLINE + when :TEXT then + unget + parse_text parent, indent + when *LIST_TOKENS then + unget + parent << build_list(indent) + else + type, data, column, line = @current_token + raise ParseError, "Unhandled token #{type} (#{data.inspect}) at #{line}:#{column}" + end + end + + p :parse_end => indent if @debug + + parent + + end + + ## + # Small hook that is overridden by RDoc::TomDoc + + def parse_text parent, indent # :nodoc: + parent << build_paragraph(indent) + end + + ## + # Returns the next token on the stream without modifying the stream + + def peek_token + token = @tokens.first || [] + p :peek => token if @debug + token + end + + ## + # Creates the StringScanner + + def setup_scanner input + @line = 0 + @line_pos = 0 + @input = input.dup + + @s = StringScanner.new input + end + + ## + # Skips the next token if its type is +token_type+. + # + # Optionally raises an error if the next token is not of the expected type. + + def skip token_type, error = true + type, = get + return unless type # end of stream + return @current_token if token_type == type + unget + raise ParseError, "expected #{token_type} got #{@current_token.inspect}" if error + end + + ## + # Turns text +input+ into a stream of tokens + + def tokenize input + setup_scanner input + + until @s.eos? do + pos = @s.pos + + # leading spaces will be reflected by the column of the next token + # the only thing we loose are trailing spaces at the end of the file + next if @s.scan(/ +/) + + # note: after BULLET, LABEL, etc., + # indent will be the column of the next non-newline token + + @tokens << case + # [CR]LF => :NEWLINE + when @s.scan(/\r?\n/) then + token = [:NEWLINE, @s.matched, *token_pos(pos)] + @line_pos = char_pos @s.pos + @line += 1 + token + # === text => :HEADER then :TEXT + when @s.scan(/(=+)(\s*)/) then + level = @s[1].length + header = [:HEADER, level, *token_pos(pos)] + + if @s[2] =~ /^\r?\n/ then + @s.pos -= @s[2].length + header + else + pos = @s.pos + @s.scan(/.*/) + @tokens << header + [:TEXT, @s.matched.sub(/\r$/, ''), *token_pos(pos)] + end + # --- (at least 3) and nothing else on the line => :RULE + when @s.scan(/(-{3,}) *\r?$/) then + [:RULE, @s[1].length - 2, *token_pos(pos)] + # * or - followed by white space and text => :BULLET + when @s.scan(/([*-]) +(\S)/) then + @s.pos -= @s[2].bytesize # unget \S + [:BULLET, @s[1], *token_pos(pos)] + # A. text, a. text, 12. text => :UALPHA, :LALPHA, :NUMBER + when @s.scan(/([a-z]|\d+)\. +(\S)/i) then + # FIXME if tab(s), the column will be wrong + # either support tabs everywhere by first expanding them to + # spaces, or assume that they will have been replaced + # before (and provide a check for that at least in debug + # mode) + list_label = @s[1] + @s.pos -= @s[2].bytesize # unget \S + list_type = + case list_label + when /[a-z]/ then :LALPHA + when /[A-Z]/ then :UALPHA + when /\d/ then :NUMBER + else + raise ParseError, "BUG token #{list_label}" + end + [list_type, list_label, *token_pos(pos)] + # [text] followed by spaces or end of line => :LABEL + when @s.scan(/\[(.*?)\]( +|\r?$)/) then + [:LABEL, @s[1], *token_pos(pos)] + # text:: followed by spaces or end of line => :NOTE + when @s.scan(/(.*?)::( +|\r?$)/) then + [:NOTE, @s[1], *token_pos(pos)] + # anything else: :TEXT + else @s.scan(/(.*?)( )?\r?$/) + token = [:TEXT, @s[1], *token_pos(pos)] + + if @s[2] then + @tokens << token + [:BREAK, @s[2], *token_pos(pos + @s[1].length)] + else + token + end + end + end + + self + end + + ## + # Calculates the column (by character) and line of the current token based + # on +byte_offset+. + + def token_pos byte_offset + offset = char_pos byte_offset + + [offset - @line_pos, @line] + end + + ## + # Returns the current token to the token stream + + def unget + token = @current_token + p :unget => token if @debug + raise Error, 'too many #ungets' if token == @tokens.first + @tokens.unshift token if token + end + +end diff --git a/lib/rdoc/markup/pre_process.rb b/lib/rdoc/markup/pre_process.rb new file mode 100644 index 0000000000..0ac7a41934 --- /dev/null +++ b/lib/rdoc/markup/pre_process.rb @@ -0,0 +1,295 @@ +# frozen_string_literal: true +## +# Handle common directives that can occur in a block of text: +# +# \:include: filename +# +# Directives can be escaped by preceding them with a backslash. +# +# RDoc plugin authors can register additional directives to be handled by +# using RDoc::Markup::PreProcess::register. +# +# Any directive that is not built-in to RDoc (including those registered via +# plugins) will be stored in the metadata hash on the CodeObject the comment +# is attached to. See RDoc::Markup@Directives for the list of built-in +# directives. + +class RDoc::Markup::PreProcess + + ## + # An RDoc::Options instance that will be filled in with overrides from + # directives + + attr_accessor :options + + ## + # Adds a post-process handler for directives. The handler will be called + # with the result RDoc::Comment (or text String) and the code object for the + # comment (if any). + + def self.post_process &block + @post_processors << block + end + + ## + # Registered post-processors + + def self.post_processors + @post_processors + end + + ## + # Registers +directive+ as one handled by RDoc. If a block is given the + # directive will be replaced by the result of the block, otherwise the + # directive will be removed from the processed text. + # + # The block will be called with the directive name and the directive + # parameter: + # + # RDoc::Markup::PreProcess.register 'my-directive' do |directive, param| + # # replace text, etc. + # end + + def self.register directive, &block + @registered[directive] = block + end + + ## + # Registered directives + + def self.registered + @registered + end + + ## + # Clears all registered directives and post-processors + + def self.reset + @post_processors = [] + @registered = {} + end + + reset + + ## + # Creates a new pre-processor for +input_file_name+ that will look for + # included files in +include_path+ + + def initialize(input_file_name, include_path) + @input_file_name = input_file_name + @include_path = include_path + @options = nil + end + + ## + # Look for directives in the given +text+. + # + # Options that we don't handle are yielded. If the block returns false the + # directive is restored to the text. If the block returns nil or no block + # was given the directive is handled according to the registered directives. + # If a String was returned the directive is replaced with the string. + # + # If no matching directive was registered the directive is restored to the + # text. + # + # If +code_object+ is given and the directive is unknown then the + # directive's parameter is set as metadata on the +code_object+. See + # RDoc::CodeObject#metadata for details. + + def handle text, code_object = nil, &block + if RDoc::Comment === text then + comment = text + text = text.text + end + + # regexp helper (square brackets for optional) + # $1 $2 $3 $4 $5 + # [prefix][\]:directive:[spaces][param]newline + text = text.gsub(/^([ \t]*(?:#|\/?\*)?[ \t]*)(\\?):(\w+):([ \t]*)(.+)?(\r?\n|$)/) do + # skip something like ':toto::' + next $& if $4.empty? and $5 and $5[0, 1] == ':' + + # skip if escaped + next "#$1:#$3:#$4#$5\n" unless $2.empty? + + # This is not in handle_directive because I didn't want to pass another + # argument into it + if comment and $3 == 'markup' then + next "#{$1.strip}\n" unless $5 + comment.format = $5.downcase + next "#{$1.strip}\n" + end + + handle_directive $1, $3, $5, code_object, text.encoding, &block + end + + if comment then + comment.text = text + else + comment = text + end + + self.class.post_processors.each do |handler| + handler.call comment, code_object + end + + text + end + + ## + # Performs the actions described by +directive+ and its parameter +param+. + # + # +code_object+ is used for directives that operate on a class or module. + # +prefix+ is used to ensure the replacement for handled directives is + # correct. +encoding+ is used for the <tt>include</tt> directive. + # + # For a list of directives in RDoc see RDoc::Markup. + #-- + # When 1.8.7 support is ditched prefix can be defaulted to '' + + def handle_directive prefix, directive, param, code_object = nil, + encoding = nil + blankline = "#{prefix.strip}\n" + directive = directive.downcase + + case directive + when 'arg', 'args' then + return "#{prefix}:#{directive}: #{param}\n" unless code_object && code_object.kind_of?(RDoc::AnyMethod) + + code_object.params = param + + blankline + when 'category' then + if RDoc::Context === code_object then + section = code_object.add_section param + code_object.temporary_section = section + end + + blankline # ignore category if we're not on an RDoc::Context + when 'doc' then + return blankline unless code_object + code_object.document_self = true + code_object.force_documentation = true + + blankline + when 'enddoc' then + return blankline unless code_object + code_object.done_documenting = true + + blankline + when 'include' then + filename = param.split.first + include_file filename, prefix, encoding + when 'main' then + @options.main_page = param if @options.respond_to? :main_page + + blankline + when 'nodoc' then + return blankline unless code_object + code_object.document_self = nil # notify nodoc + code_object.document_children = param !~ /all/i + + blankline + when 'notnew', 'not_new', 'not-new' then + return blankline unless RDoc::AnyMethod === code_object + + code_object.dont_rename_initialize = true + + blankline + when 'startdoc' then + return blankline unless code_object + + code_object.start_doc + code_object.force_documentation = true + + blankline + when 'stopdoc' then + return blankline unless code_object + + code_object.stop_doc + + blankline + when 'title' then + @options.default_title = param if @options.respond_to? :default_title= + + blankline + when 'yield', 'yields' then + return blankline unless code_object + # remove parameter &block + code_object.params = code_object.params.sub(/,?\s*&\w+/, '') if code_object.params + + code_object.block_params = param + + blankline + else + result = yield directive, param if block_given? + + case result + when nil then + code_object.metadata[directive] = param if code_object + + if RDoc::Markup::PreProcess.registered.include? directive then + handler = RDoc::Markup::PreProcess.registered[directive] + result = handler.call directive, param if handler + else + result = "#{prefix}:#{directive}: #{param}\n" + end + when false then + result = "#{prefix}:#{directive}: #{param}\n" + end + + result + end + end + + ## + # Handles the <tt>:include: _filename_</tt> directive. + # + # If the first line of the included file starts with '#', and contains + # an encoding information in the form 'coding:' or 'coding=', it is + # removed. + # + # If all lines in the included file start with a '#', this leading '#' + # is removed before inclusion. The included content is indented like + # the <tt>:include:</tt> directive. + #-- + # so all content will be verbatim because of the likely space after '#'? + # TODO shift left the whole file content in that case + # TODO comment stop/start #-- and #++ in included file must be processed here + + def include_file name, indent, encoding + full_name = find_include_file name + + unless full_name then + warn "Couldn't find file to include '#{name}' from #{@input_file_name}" + return '' + end + + content = RDoc::Encoding.read_file full_name, encoding, true + + # strip magic comment + content = content.sub(/\A# .*coding[=:].*$/, '').lstrip + + # strip leading '#'s, but only if all lines start with them + if content =~ /^[^#]/ then + content.gsub(/^/, indent) + else + content.gsub(/^#?/, indent) + 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/raw.rb b/lib/rdoc/markup/raw.rb new file mode 100644 index 0000000000..85e2c8b825 --- /dev/null +++ b/lib/rdoc/markup/raw.rb @@ -0,0 +1,70 @@ +# frozen_string_literal: true +## +# A section of text that is added to the output document as-is + +class RDoc::Markup::Raw + + ## + # The component parts of the list + + attr_reader :parts + + ## + # Creates a new Raw containing +parts+ + + def initialize *parts + @parts = [] + @parts.concat parts + end + + ## + # Appends +text+ + + def << text + @parts << text + end + + def == other # :nodoc: + self.class == other.class and @parts == other.parts + end + + ## + # Calls #accept_raw+ on +visitor+ + + def accept visitor + visitor.accept_raw self + end + + ## + # Appends +other+'s parts + + def merge other + @parts.concat other.parts + end + + def pretty_print q # :nodoc: + self.class.name =~ /.*::(\w{1,4})/i + + q.group 2, "[#{$1.downcase}: ", ']' do + q.seplist @parts do |part| + q.pp part + end + end + end + + ## + # Appends +texts+ onto this Paragraph + + def push *texts + self.parts.concat texts + end + + ## + # The raw text + + def text + @parts.join ' ' + end + +end + diff --git a/lib/rdoc/markup/rule.rb b/lib/rdoc/markup/rule.rb new file mode 100644 index 0000000000..38c1dc7f56 --- /dev/null +++ b/lib/rdoc/markup/rule.rb @@ -0,0 +1,21 @@ +# frozen_string_literal: true +## +# A horizontal rule with a weight + +class RDoc::Markup::Rule < Struct.new :weight + + ## + # Calls #accept_rule on +visitor+ + + def accept visitor + visitor.accept_rule self + end + + def pretty_print q # :nodoc: + q.group 2, '[rule:', ']' do + q.pp weight + end + end + +end + diff --git a/lib/rdoc/markup/sample/rdoc2latex.rb b/lib/rdoc/markup/sample/rdoc2latex.rb deleted file mode 100644 index 26563b75da..0000000000 --- a/lib/rdoc/markup/sample/rdoc2latex.rb +++ /dev/null @@ -1,16 +0,0 @@ -#!/usr/local/bin/ruby -# Illustration of a script to convert an RDoc-style file to a LaTeX -# document - -require 'rdoc/markup/simple_markup' -require 'rdoc/markup/simple_markup/to_latex' - -p = SM::SimpleMarkup.new -h = SM::ToLaTeX.new - -#puts "\\documentclass{report}" -#puts "\\usepackage{tabularx}" -#puts "\\usepackage{parskip}" -#puts "\\begin{document}" -puts p.convert(ARGF.read, h) -#puts "\\end{document}" diff --git a/lib/rdoc/markup/sample/sample.rb b/lib/rdoc/markup/sample/sample.rb deleted file mode 100644 index a375b54564..0000000000 --- a/lib/rdoc/markup/sample/sample.rb +++ /dev/null @@ -1,42 +0,0 @@ -# This program illustrates the basic use of the SimpleMarkup -# class. It extracts the first comment block from the -# simple_markup.rb file and converts it into HTML on -# standard output. Run it using -# -# % ruby sample.rb -# -# You should be in the sample/ directory when you do this, -# as it hardwires the path to the files it needs to require. -# This isn't necessary in the code you write once you've -# installed the package. -# -# For a better way of formatting code comment blocks (and more) -# see the rdoc package. -# - -$:.unshift "../../.." - -require 'rdoc/markup/simple_markup' -require 'rdoc/markup/simple_markup/to_html' - -# Extract the comment block from the source file - -input_string = "" - -File.foreach("../simple_markup.rb") do |line| - break unless line.gsub!(/^\# ?/, '') - input_string << line -end - -# Create a markup object -markup = SM::SimpleMarkup.new - -# Attach it to an HTML formatter -h = SM::ToHtml.new - -# And convert out comment block to html. Wrap it a body -# tag pair to let browsers view it - -puts "<html><body>" -puts markup.convert(input_string, h) -puts "</body></html>" diff --git a/lib/rdoc/markup/simple_markup.rb b/lib/rdoc/markup/simple_markup.rb deleted file mode 100644 index 8193ca02d4..0000000000 --- a/lib/rdoc/markup/simple_markup.rb +++ /dev/null @@ -1,476 +0,0 @@ -# = Introduction -# -# 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 - - - -require 'rdoc/markup/simple_markup/fragments' -require 'rdoc/markup/simple_markup/lines.rb' - -module SM #:nodoc: - - # == 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>" - # - # == Output Formatters - # - # _missing_ - # - # - - 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 ab attached to the label. - # - - def handled_labeled_list(line, level, margin, offset, prefix) - prefix_length = prefix.length - text = line.text - flag = nil - case prefix - when /^\[/ - flag = ListBase::LABELED - prefix = prefix[1, prefix.length-2] - when /:$/ - flag = ListBase::NOTE - prefix.chop! - else raise "Invalid List Type: #{self.inspect}" - end - - # body is on the next line - - if text.length <= offset - original_line = line - line = @lines.next - return(false) unless line - text = line.text - - for i in 0..margin - if text[i] != SPACE - @lines.unget - return false - end - end - i = margin - i += 1 while text[i] == SPACE - if i >= text.length - @lines.unget - return false - else - offset = i - prefix_length = 0 - @lines.delete(original_line) - end - end - - line.stamp(Line::LIST, level+1, prefix, flag) - text[margin, prefix_length] = " " * prefix_length - assign_types_to_lines(offset, level + 1) - return true - end - - # Return a block consisting of fragments which are - # paragraphs, list entries or verbatim text. We merge consecutive - # lines of the same type and level together. We are also slightly - # tricky with lists: the lines following a list introduction - # look like paragraph lines at the next level, and we remap them - # into list entries instead - - def group_lines - @lines.rewind - - inList = false - wantedType = wantedLevel = nil - - block = LineCollection.new - group = nil - - while line = @lines.next - if line.level == wantedLevel and line.type == wantedType - group.add_text(line.text) - else - group = block.fragment_for(line) - block.add(group) - if line.type == Line::LIST - wantedType = Line::PARAGRAPH - else - wantedType = line.type - end - wantedLevel = line.type == Line::HEADING ? line.param : line.level - end - end - - block.normalize - block - end - - ## for debugging, we allow access to our line contents as text - def content - @lines.as_text - end - public :content - - ## for debugging, return the list of line types - def get_line_types - @lines.line_types - end - public :get_line_types - end - -end diff --git a/lib/rdoc/markup/simple_markup/fragments.rb b/lib/rdoc/markup/simple_markup/fragments.rb deleted file mode 100644 index 6ca06382ab..0000000000 --- a/lib/rdoc/markup/simple_markup/fragments.rb +++ /dev/null @@ -1,328 +0,0 @@ -require 'rdoc/markup/simple_markup/lines.rb' -#require 'rdoc/markup/simple_markup/to_flow.rb' - -module SM - - ## - # A Fragment is a chunk of text, subclassed as a paragraph, a list - # entry, or verbatim text - - class Fragment - attr_reader :level, :param, :txt - attr_accessor :type - - def initialize(level, param, type, txt) - @level = level - @param = param - @type = type - @txt = "" - add_text(txt) if txt - end - - def add_text(txt) - @txt << " " if @txt.length > 0 - @txt << txt.tr_s("\n ", " ").strip - end - - def to_s - "L#@level: #{self.class.name.split('::')[-1]}\n#@txt" - end - - ###### - # This is a simple factory system that lets us associate fragement - # types (a string) with a subclass of fragment - - TYPE_MAP = {} - - def Fragment.type_name(name) - TYPE_MAP[name] = self - end - - def Fragment.for(line) - klass = TYPE_MAP[line.type] || - raise("Unknown line type: '#{line.type.inspect}:' '#{line.text}'") - return klass.new(line.level, line.param, line.flag, line.text) - end - end - - ## - # A paragraph is a fragment which gets wrapped to fit. We remove all - # newlines when we're created, and have them put back on output - - class Paragraph < Fragment - type_name Line::PARAGRAPH - end - - class BlankLine < Paragraph - type_name Line::BLANK - end - - class Heading < Paragraph - type_name Line::HEADING - - def head_level - @param.to_i - end - end - - ## - # A List is a fragment with some kind of label - # - - class ListBase < Paragraph - # List types - BULLET = :BULLET - NUMBER = :NUMBER - UPPERALPHA = :UPPERALPHA - LOWERALPHA = :LOWERALPHA - LABELED = :LABELED - NOTE = :NOTE - end - - class ListItem < ListBase - type_name Line::LIST - - # def label - # am = AttributeManager.new(@param) - # am.flow - # end - end - - class ListStart < ListBase - def initialize(level, param, type) - super(level, param, type, nil) - end - end - - class ListEnd < ListBase - def initialize(level, type) - super(level, "", type, nil) - end - end - - ## - # Verbatim code contains lines that don't get wrapped. - - class Verbatim < Fragment - type_name Line::VERBATIM - - def add_text(txt) - @txt << txt.chomp << "\n" - end - - end - - ## - # A horizontal rule - class Rule < Fragment - type_name Line::RULE - end - - - # Collect groups of lines together. Each group - # will end up containing a flow of text - - class LineCollection - - def initialize - @fragments = [] - end - - def add(fragment) - @fragments << fragment - end - - def each(&b) - @fragments.each(&b) - end - - # For testing - def to_a - @fragments.map {|fragment| fragment.to_s} - end - - # Factory for different fragment types - def fragment_for(*args) - Fragment.for(*args) - end - - # tidy up at the end - def normalize - change_verbatim_blank_lines - add_list_start_and_ends - add_list_breaks - tidy_blank_lines - end - - def to_s - @fragments.join("\n----\n") - end - - def accept(am, visitor) - - visitor.start_accepting - - @fragments.each do |fragment| - case fragment - when Verbatim - visitor.accept_verbatim(am, fragment) - when Rule - visitor.accept_rule(am, fragment) - when ListStart - visitor.accept_list_start(am, fragment) - when ListEnd - visitor.accept_list_end(am, fragment) - when ListItem - visitor.accept_list_item(am, fragment) - when BlankLine - visitor.accept_blank_line(am, fragment) - when Heading - visitor.accept_heading(am, fragment) - when Paragraph - visitor.accept_paragraph(am, fragment) - end - end - - visitor.end_accepting - end - ####### - private - ####### - - # If you have: - # - # normal paragraph text. - # - # this is code - # - # and more code - # - # You'll end up with the fragments Paragraph, BlankLine, - # Verbatim, BlankLine, Verbatim, BlankLine, etc - # - # The BlankLine in the middle of the verbatim chunk needs to - # be changed to a real verbatim newline, and the two - # verbatim blocks merged - # - # - def change_verbatim_blank_lines - frag_block = nil - blank_count = 0 - @fragments.each_with_index do |frag, i| - if frag_block.nil? - frag_block = frag if Verbatim === frag - else - case frag - when Verbatim - blank_count.times { frag_block.add_text("\n") } - blank_count = 0 - frag_block.add_text(frag.txt) - @fragments[i] = nil # remove out current fragment - when BlankLine - if frag_block - blank_count += 1 - @fragments[i] = nil - end - else - frag_block = nil - blank_count = 0 - end - end - end - @fragments.compact! - end - - # List nesting is implicit given the level of - # Make it explicit, just to make life a tad - # easier for the output processors - - def add_list_start_and_ends - level = 0 - res = [] - type_stack = [] - - @fragments.each do |fragment| - # $stderr.puts "#{level} : #{fragment.class.name} : #{fragment.level}" - new_level = fragment.level - while (level < new_level) - level += 1 - type = fragment.type - res << ListStart.new(level, fragment.param, type) if type - type_stack.push type - # $stderr.puts "Start: #{level}" - end - - while level > new_level - type = type_stack.pop - res << ListEnd.new(level, type) if type - level -= 1 - # $stderr.puts "End: #{level}, #{type}" - end - - res << fragment - level = fragment.level - end - level.downto(1) do |i| - type = type_stack.pop - res << ListEnd.new(i, type) if type - end - - @fragments = res - end - - # now insert start/ends between list entries at the - # same level that have different element types - - def add_list_breaks - res = @fragments - - @fragments = [] - list_stack = [] - - res.each do |fragment| - case fragment - when ListStart - list_stack.push fragment - when ListEnd - start = list_stack.pop - fragment.type = start.type - when ListItem - l = list_stack.last - if fragment.type != l.type - @fragments << ListEnd.new(l.level, l.type) - start = ListStart.new(l.level, fragment.param, fragment.type) - @fragments << start - list_stack.pop - list_stack.push start - end - else - ; - end - @fragments << fragment - end - end - - # Finally tidy up the blank lines: - # * change Blank/ListEnd into ListEnd/Blank - # * remove blank lines at the front - - def tidy_blank_lines - (@fragments.size - 1).times do |i| - if @fragments[i].kind_of?(BlankLine) and - @fragments[i+1].kind_of?(ListEnd) - @fragments[i], @fragments[i+1] = @fragments[i+1], @fragments[i] - end - end - - # remove leading blanks - @fragments.each_with_index do |f, i| - break unless f.kind_of? BlankLine - @fragments[i] = nil - end - - @fragments.compact! - end - - end - -end diff --git a/lib/rdoc/markup/simple_markup/inline.rb b/lib/rdoc/markup/simple_markup/inline.rb deleted file mode 100644 index d54fe1e667..0000000000 --- a/lib/rdoc/markup/simple_markup/inline.rb +++ /dev/null @@ -1,340 +0,0 @@ -module SM - - # We manage a set of attributes. Each attribute has a symbol name - # and a bit value - - class Attribute - SPECIAL = 1 - - @@name_to_bitmap = { :_SPECIAL_ => SPECIAL } - @@next_bitmap = 2 - - def Attribute.bitmap_for(name) - bitmap = @@name_to_bitmap[name] - if !bitmap - bitmap = @@next_bitmap - @@next_bitmap <<= 1 - @@name_to_bitmap[name] = bitmap - end - bitmap - end - - def Attribute.as_string(bitmap) - return "none" if bitmap.zero? - res = [] - @@name_to_bitmap.each do |name, bit| - res << name if (bitmap & bit) != 0 - end - res.join(",") - end - - def Attribute.each_name_of(bitmap) - @@name_to_bitmap.each do |name, bit| - next if bit == SPECIAL - yield name.to_s if (bitmap & bit) != 0 - end - end - end - - - # An AttrChanger records a change in attributes. It contains - # a bitmap of the attributes to turn on, and a bitmap of those to - # turn off - - AttrChanger = Struct.new(:turn_on, :turn_off) - class AttrChanger - def to_s - "Attr: +#{Attribute.as_string(@turn_on)}/-#{Attribute.as_string(@turn_on)}" - end - end - - # An array of attributes which parallels the characters in a string - class AttrSpan - def initialize(length) - @attrs = Array.new(length, 0) - end - - def set_attrs(start, length, bits) - for i in start ... (start+length) - @attrs[i] |= bits - end - end - - def [](n) - @attrs[n] - end - end - - ## - # Hold details of a special sequence - - class Special - attr_reader :type - attr_accessor :text - - def initialize(type, text) - @type, @text = type, text - end - - def ==(o) - self.text == o.text && self.type == o.type - end - - def to_s - "Special: type=#{type}, text=#{text.dump}" - end - end - - class AttributeManager - - NULL = "\000".freeze - - ## - # We work by substituting non-printing characters in to the - # text. For now I'm assuming that I can substitute - # a character in the range 0..8 for a 7 bit character - # without damaging the encoded string, but this might - # be optimistic - # - - A_PROTECT = 004 - PROTECT_ATTR = A_PROTECT.chr - - # This maps delimiters that occur around words (such as - # *bold* or +tt+) where the start and end delimiters - # and the same. This lets us optimize the regexp - MATCHING_WORD_PAIRS = {} - - # And this is used when the delimiters aren't the same. In this - # case the hash maps a pattern to the attribute character - WORD_PAIR_MAP = {} - - # This maps HTML tags to the corresponding attribute char - HTML_TAGS = {} - - # And this maps _special_ sequences to a name. A special sequence - # is something like a WikiWord - SPECIAL = {} - - # Return an attribute object with the given turn_on - # and turn_off bits set - - def attribute(turn_on, turn_off) - AttrChanger.new(turn_on, turn_off) - end - - - def change_attribute(current, new) - diff = current ^ new - attribute(new & diff, current & diff) - end - - def changed_attribute_by_name(current_set, new_set) - current = new = 0 - current_set.each {|name| current |= Attribute.bitmap_for(name) } - new_set.each {|name| new |= Attribute.bitmap_for(name) } - change_attribute(current, new) - end - - def copy_string(start_pos, end_pos) - res = @str[start_pos...end_pos] - res.gsub!(/\000/, '') - res - end - - # Map attributes like <b>text</b>to the sequence \001\002<char>\001\003<char>, - # where <char> is a per-attribute specific character - - def convert_attrs(str, attrs) - # first do matching ones - tags = MATCHING_WORD_PAIRS.keys.join("") - re = "(^|\\W)([#{tags}])([A-Za-z_]+?)\\2(\\W|\$)" -# re = "(^|\\W)([#{tags}])(\\S+?)\\2(\\W|\$)" - 1 while str.gsub!(Regexp.new(re)) { - attr = MATCHING_WORD_PAIRS[$2]; - attrs.set_attrs($`.length + $1.length + $2.length, $3.length, attr) - $1 + NULL*$2.length + $3 + NULL*$2.length + $4 - } - - # then non-matching - unless WORD_PAIR_MAP.empty? - WORD_PAIR_MAP.each do |regexp, attr| - str.gsub!(regexp) { - attrs.set_attrs($`.length + $1.length, $2.length, attr) - NULL*$1.length + $2 + NULL*$3.length - } - end - end - end - - def convert_html(str, attrs) - tags = HTML_TAGS.keys.join("|") - re = "<(#{tags})>(.*?)</\\1>" - 1 while str.gsub!(Regexp.new(re, Regexp::IGNORECASE)) { - attr = HTML_TAGS[$1.downcase] - html_length = $1.length + 2 - seq = NULL * html_length - attrs.set_attrs($`.length + html_length, $2.length, attr) - seq + $2 + seq + NULL - } - end - - def convert_specials(str, attrs) - unless SPECIAL.empty? - SPECIAL.each do |regexp, attr| - str.scan(regexp) do - attrs.set_attrs($`.length, $&.length, attr | Attribute::SPECIAL) - end - end - end - end - - # A \ in front of a character that would normally be - # processed turns off processing. We do this by turning - # \< into <#{PROTECT} - - PROTECTABLE = [ "<" << "\\" ] #" - - - def mask_protected_sequences - protect_pattern = Regexp.new("\\\\([#{Regexp.escape(PROTECTABLE.join(''))}])") - @str.gsub!(protect_pattern, "\\1#{PROTECT_ATTR}") - end - - def unmask_protected_sequences - @str.gsub!(/(.)#{PROTECT_ATTR}/, "\\1\000") - end - - def initialize - add_word_pair("*", "*", :BOLD) - add_word_pair("_", "_", :EM) - add_word_pair("+", "+", :TT) - - add_html("em", :EM) - add_html("i", :EM) - add_html("b", :BOLD) - add_html("tt", :TT) - add_html("code", :TT) - - add_special(/<!--(.*?)-->/, :COMMENT) - end - - def add_word_pair(start, stop, name) - raise "Word flags may not start '<'" if start[0] == ?< - bitmap = Attribute.bitmap_for(name) - if start == stop - MATCHING_WORD_PAIRS[start] = bitmap - else - pattern = Regexp.new("(" + Regexp.escape(start) + ")" + -# "([A-Za-z]+)" + - "(\\S+)" + - "(" + Regexp.escape(stop) +")") - WORD_PAIR_MAP[pattern] = bitmap - end - PROTECTABLE << start[0,1] - PROTECTABLE.uniq! - end - - def add_html(tag, name) - HTML_TAGS[tag.downcase] = Attribute.bitmap_for(name) - end - - def add_special(pattern, name) - SPECIAL[pattern] = Attribute.bitmap_for(name) - end - - def flow(str) - @str = str - - puts("Before flow, str='#{@str.dump}'") if $DEBUG - mask_protected_sequences - - @attrs = AttrSpan.new(@str.length) - - puts("After protecting, str='#{@str.dump}'") if $DEBUG - convert_attrs(@str, @attrs) - convert_html(@str, @attrs) - convert_specials(str, @attrs) - unmask_protected_sequences - puts("After flow, str='#{@str.dump}'") if $DEBUG - return split_into_flow - end - - def display_attributes - puts - puts @str.tr(NULL, "!") - bit = 1 - 16.times do |bno| - line = "" - @str.length.times do |i| - if (@attrs[i] & bit) == 0 - line << " " - else - if bno.zero? - line << "S" - else - line << ("%d" % (bno+1)) - end - end - end - puts(line) unless line =~ /^ *$/ - bit <<= 1 - end - end - - def split_into_flow - - display_attributes if $DEBUG - - res = [] - current_attr = 0 - str = "" - - - str_len = @str.length - - # skip leading invisible text - i = 0 - i += 1 while i < str_len and @str[i].zero? - start_pos = i - - # then scan the string, chunking it on attribute changes - while i < str_len - new_attr = @attrs[i] - if new_attr != current_attr - if i > start_pos - res << copy_string(start_pos, i) - start_pos = i - end - - res << change_attribute(current_attr, new_attr) - current_attr = new_attr - - if (current_attr & Attribute::SPECIAL) != 0 - i += 1 while i < str_len and (@attrs[i] & Attribute::SPECIAL) != 0 - res << Special.new(current_attr, copy_string(start_pos, i)) - start_pos = i - next - end - end - - # move on, skipping any invisible characters - begin - i += 1 - end while i < str_len and @str[i].zero? - end - - # tidy up trailing text - if start_pos < str_len - res << copy_string(start_pos, str_len) - end - - # and reset to all attributes off - res << change_attribute(current_attr, 0) if current_attr != 0 - - return res - end - - end - -end diff --git a/lib/rdoc/markup/simple_markup/lines.rb b/lib/rdoc/markup/simple_markup/lines.rb deleted file mode 100644 index 4e294f27dc..0000000000 --- a/lib/rdoc/markup/simple_markup/lines.rb +++ /dev/null @@ -1,151 +0,0 @@ -########################################################################## -# -# We store the lines we're working on as objects of class Line. -# These contain the text of the line, along with a flag indicating the -# line type, and an indentation level - -module SM - - class Line - INFINITY = 9999 - - BLANK = :BLANK - HEADING = :HEADING - LIST = :LIST - RULE = :RULE - PARAGRAPH = :PARAGRAPH - VERBATIM = :VERBATIM - - # line type - attr_accessor :type - - # The indentation nesting level - attr_accessor :level - - # The contents - attr_accessor :text - - # A prefix or parameter. For LIST lines, this is - # the text that introduced the list item (the label) - attr_accessor :param - - # A flag. For list lines, this is the type of the list - attr_accessor :flag - - # the number of leading spaces - attr_accessor :leading_spaces - - # true if this line has been deleted from the list of lines - attr_accessor :deleted - - - def initialize(text) - @text = text.dup - @deleted = false - - # expand tabs - 1 while @text.gsub!(/\t+/) { ' ' * (8*$&.length - $`.length % 8)} && $~ #` - - # Strip trailing whitespace - @text.sub!(/\s+$/, '') - - # and look for leading whitespace - if @text.length > 0 - @text =~ /^(\s*)/ - @leading_spaces = $1.length - else - @leading_spaces = INFINITY - end - end - - # Return true if this line is blank - def isBlank? - @text.length.zero? - end - - # stamp a line with a type, a level, a prefix, and a flag - def stamp(type, level, param="", flag=nil) - @type, @level, @param, @flag = type, level, param, flag - end - - ## - # Strip off the leading margin - # - - def strip_leading(size) - if @text.size > size - @text[0,size] = "" - else - @text = "" - end - end - - def to_s - "#@type#@level: #@text" - end - end - - ############################################################################### - # - # A container for all the lines - # - - class Lines - include Enumerable - - attr_reader :lines # for debugging - - def initialize(lines) - @lines = lines - rewind - end - - def empty? - @lines.size.zero? - end - - def each - @lines.each do |line| - yield line unless line.deleted - end - end - -# def [](index) -# @lines[index] -# end - - def rewind - @nextline = 0 - end - - def next - begin - res = @lines[@nextline] - @nextline += 1 if @nextline < @lines.size - end while res and res.deleted and @nextline < @lines.size - res - end - - def unget - @nextline -= 1 - end - - def delete(a_line) - a_line.deleted = true - end - - def normalize - margin = @lines.collect{|l| l.leading_spaces}.min - margin = 0 if margin == Line::INFINITY - @lines.each {|line| line.strip_leading(margin) } if margin > 0 - end - - def as_text - @lines.map {|l| l.text}.join("\n") - end - - def line_types - @lines.map {|l| l.type } - end - end -end diff --git a/lib/rdoc/markup/simple_markup/preprocess.rb b/lib/rdoc/markup/simple_markup/preprocess.rb deleted file mode 100644 index 101c9bdeb1..0000000000 --- a/lib/rdoc/markup/simple_markup/preprocess.rb +++ /dev/null @@ -1,73 +0,0 @@ -module SM - - ## - # Handle common directives that can occur in a block of text: - # - # : include : filename - # - - class PreProcess - - def initialize(input_file_name, include_path) - @input_file_name = input_file_name - @include_path = include_path - end - - # Look for common options in a chunk of text. Options that - # we don't handle are passed back to our caller - # as |directive, param| - - def handle(text) - text.gsub!(/^([ \t#]*):(\w+):\s*(.+)?\n/) do - prefix = $1 - directive = $2.downcase - param = $3 - - case directive - when "include" - filename = param.split[0] - include_file(filename, prefix) - - else - yield(directive, param) - end - end - end - - ####### - private - ####### - - # Include a file, indenting it correctly - - def include_file(name, indent) - if (full_name = find_include_file(name)) - content = File.open(full_name) {|f| f.read} - # strip leading '#'s, but only if all lines start with them - if content =~ /^[^#]/ - content.gsub(/^/, indent) - else - content.gsub(/^#?/, indent) - end - else - $stderr.puts "Couldn't find file to include: '#{name}'" - '' - end - end - - # Look for the given file in the directory containing the current - # file, and then in each of the directories specified in the - # RDOC_INCLUDE path - - def find_include_file(name) - to_search = [ File.dirname(@input_file_name) ].concat @include_path - to_search.each do |dir| - full_name = File.join(dir, name) - stat = File.stat(full_name) rescue next - return full_name if stat.readable? - end - nil - end - - end -end diff --git a/lib/rdoc/markup/simple_markup/to_flow.rb b/lib/rdoc/markup/simple_markup/to_flow.rb deleted file mode 100644 index 048e71abce..0000000000 --- a/lib/rdoc/markup/simple_markup/to_flow.rb +++ /dev/null @@ -1,188 +0,0 @@ -require 'rdoc/markup/simple_markup/fragments' -require 'rdoc/markup/simple_markup/inline' -require 'cgi' - -module SM - - module Flow - P = Struct.new(:body) - VERB = Struct.new(:body) - RULE = Struct.new(:width) - class LIST - attr_reader :type, :contents - def initialize(type) - @type = type - @contents = [] - end - def <<(stuff) - @contents << stuff - end - end - LI = Struct.new(:label, :body) - H = Struct.new(:level, :text) - end - - class ToFlow - LIST_TYPE_TO_HTML = { - SM::ListBase::BULLET => [ "<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>" ], - } - - 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 = [] - @list_stack = [] - end - - def end_accepting - @res - end - - def accept_paragraph(am, fragment) - @res << Flow::P.new((convert_flow(am.flow(fragment.txt)))) - end - - def accept_verbatim(am, fragment) - @res << Flow::VERB.new((convert_flow(am.flow(fragment.txt)))) - end - - def accept_rule(am, fragment) - size = fragment.param - size = 10 if size > 10 - @res << Flow::RULE.new(size) - end - - def accept_list_start(am, fragment) - @list_stack.push(@res) - list = Flow::LIST.new(fragment.type) - @res << list - @res = list - end - - def accept_list_end(am, fragment) - @res = @list_stack.pop - end - - def accept_list_item(am, fragment) - @res << Flow::LI.new(fragment.param, convert_flow(am.flow(fragment.txt))) - end - - def accept_blank_line(am, fragment) - # @res << annotate("<p />") << "\n" - end - - def accept_heading(am, fragment) - @res << Flow::H.new(fragment.head_level, convert_flow(am.flow(fragment.txt))) - end - - - ####################################################################### - - private - - ####################################################################### - - def on_tags(res, item) - attr_mask = item.turn_on - return if attr_mask.zero? - - @attr_tags.each do |tag| - if attr_mask & tag.bit != 0 - res << annotate(tag.on) - end - end - end - - def off_tags(res, item) - attr_mask = item.turn_off - return if attr_mask.zero? - - @attr_tags.reverse_each do |tag| - if attr_mask & tag.bit != 0 - res << annotate(tag.off) - end - end - end - - def convert_flow(flow) - res = "" - flow.each do |item| - case item - when String - res << convert_string(item) - when AttrChanger - off_tags(res, item) - on_tags(res, item) - when Special - res << convert_special(item) - else - raise "Unknown flow element: #{item.inspect}" - end - end - res - end - - # some of these patterns are taken from SmartyPants... - - def convert_string(item) - CGI.escapeHTML(item) - end - - def convert_special(special) - handled = false - Attribute.each_name_of(special.type) do |name| - method_name = "handle_special_#{name}" - if self.respond_to? method_name - special.text = send(method_name, special) - handled = true - end - end - raise "Unhandled special: #{special}" unless handled - special.text - end - - - end - -end diff --git a/lib/rdoc/markup/simple_markup/to_html.rb b/lib/rdoc/markup/simple_markup/to_html.rb deleted file mode 100644 index 26b5f4ce70..0000000000 --- a/lib/rdoc/markup/simple_markup/to_html.rb +++ /dev/null @@ -1,289 +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 6c16278652..0000000000 --- a/lib/rdoc/markup/simple_markup/to_latex.rb +++ /dev/null @@ -1,333 +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/special.rb b/lib/rdoc/markup/special.rb new file mode 100644 index 0000000000..57261b44a7 --- /dev/null +++ b/lib/rdoc/markup/special.rb @@ -0,0 +1,41 @@ +# frozen_string_literal: true +## +# Hold details of a special sequence + +class RDoc::Markup::Special + + ## + # Special type + + attr_reader :type + + ## + # Special text + + attr_accessor :text + + ## + # Creates a new special sequence of +type+ with +text+ + + def initialize(type, text) + @type, @text = type, text + end + + ## + # Specials are equal when the have the same text and type + + def ==(o) + self.text == o.text && self.type == o.type + end + + def inspect # :nodoc: + "#<RDoc::Markup::Special:0x%x @type=%p, @text=%p>" % [ + object_id, @type, text.dump] + end + + def to_s # :nodoc: + "Special: type=#{type} text=#{text.dump}" + end + +end + diff --git a/lib/rdoc/markup/test/AllTests.rb b/lib/rdoc/markup/test/AllTests.rb deleted file mode 100644 index b9c8c9dfcc..0000000000 --- a/lib/rdoc/markup/test/AllTests.rb +++ /dev/null @@ -1,2 +0,0 @@ -require 'TestParse.rb' -require 'TestInline.rb' diff --git a/lib/rdoc/markup/test/TestInline.rb b/lib/rdoc/markup/test/TestInline.rb deleted file mode 100644 index a067d4c24c..0000000000 --- a/lib/rdoc/markup/test/TestInline.rb +++ /dev/null @@ -1,154 +0,0 @@ -require "test/unit" - -$:.unshift "../../.." - -require "rdoc/markup/simple_markup/inline" - -class TestInline < Test::Unit::TestCase - - - def setup - @am = SM::AttributeManager.new - - @bold_on = @am.changed_attribute_by_name([], [:BOLD]) - @bold_off = @am.changed_attribute_by_name([:BOLD], []) - - @tt_on = @am.changed_attribute_by_name([], [:TT]) - @tt_off = @am.changed_attribute_by_name([:TT], []) - - @em_on = @am.changed_attribute_by_name([], [:EM]) - @em_off = @am.changed_attribute_by_name([:EM], []) - - @bold_em_on = @am.changed_attribute_by_name([], [:BOLD] | [:EM]) - @bold_em_off = @am.changed_attribute_by_name([:BOLD] | [:EM], []) - - @em_then_bold = @am.changed_attribute_by_name([:EM], [:EM] | [:BOLD]) - - @em_to_bold = @am.changed_attribute_by_name([:EM], [:BOLD]) - - @am.add_word_pair("{", "}", :WOMBAT) - @wombat_on = @am.changed_attribute_by_name([], [:WOMBAT]) - @wombat_off = @am.changed_attribute_by_name([:WOMBAT], []) - end - - def crossref(text) - [ @am.changed_attribute_by_name([], [:CROSSREF] | [:_SPECIAL_]), - SM::Special.new(33, text), - @am.changed_attribute_by_name([:CROSSREF] | [:_SPECIAL_], []) - ] - end - - def test_special - # class names, variable names, file names, or instance variables - @am.add_special(/( - \b([A-Z]\w+(::\w+)*) - | \#\w+[!?=]? - | \b\w+([_\/\.]+\w+)+[!?=]? - )/x, - :CROSSREF) - - assert_equal(["cat"], @am.flow("cat")) - - assert_equal(["cat ", crossref("#fred"), " dog"].flatten, - @am.flow("cat #fred dog")) - - assert_equal([crossref("#fred"), " dog"].flatten, - @am.flow("#fred dog")) - - assert_equal(["cat ", crossref("#fred")].flatten, @am.flow("cat #fred")) - end - - def test_basic - assert_equal(["cat"], @am.flow("cat")) - - assert_equal(["cat ", @bold_on, "and", @bold_off, " dog"], - @am.flow("cat *and* dog")) - - assert_equal(["cat ", @bold_on, "AND", @bold_off, " dog"], - @am.flow("cat *AND* dog")) - - assert_equal(["cat ", @em_on, "And", @em_off, " dog"], - @am.flow("cat _And_ dog")) - - assert_equal(["cat *and dog*"], @am.flow("cat *and dog*")) - - assert_equal(["*cat and* dog"], @am.flow("*cat and* dog")) - - assert_equal(["cat *and ", @bold_on, "dog", @bold_off], - @am.flow("cat *and *dog*")) - - assert_equal(["cat ", @em_on, "and", @em_off, " dog"], - @am.flow("cat _and_ dog")) - - assert_equal(["cat_and_dog"], - @am.flow("cat_and_dog")) - - assert_equal(["cat ", @tt_on, "and", @tt_off, " dog"], - @am.flow("cat +and+ dog")) - - assert_equal(["cat ", @bold_on, "a_b_c", @bold_off, " dog"], - @am.flow("cat *a_b_c* dog")) - - assert_equal(["cat __ dog"], - @am.flow("cat __ dog")) - - assert_equal(["cat ", @em_on, "_", @em_off, " dog"], - @am.flow("cat ___ dog")) - - end - - def test_combined - assert_equal(["cat ", @em_on, "and", @em_off, " ", @bold_on, "dog", @bold_off], - @am.flow("cat _and_ *dog*")) - - assert_equal(["cat ", @em_on, "a__nd", @em_off, " ", @bold_on, "dog", @bold_off], - @am.flow("cat _a__nd_ *dog*")) - end - - def test_html_like - assert_equal(["cat ", @tt_on, "dog", @tt_off], @am.flow("cat <tt>dog</Tt>")) - - assert_equal(["cat ", @em_on, "and", @em_off, " ", @bold_on, "dog", @bold_off], - @am.flow("cat <i>and</i> <B>dog</b>")) - - assert_equal(["cat ", @em_on, "and ", @em_then_bold, "dog", @bold_em_off], - @am.flow("cat <i>and <B>dog</B></I>")) - - assert_equal(["cat ", @em_on, "and ", @em_to_bold, "dog", @bold_off], - @am.flow("cat <i>and </i><b>dog</b>")) - - assert_equal(["cat ", @em_on, "and ", @em_to_bold, "dog", @bold_off], - @am.flow("cat <i>and <b></i>dog</b>")) - - assert_equal([@tt_on, "cat", @tt_off, " ", @em_on, "and ", @em_to_bold, "dog", @bold_off], - @am.flow("<tt>cat</tt> <i>and <b></i>dog</b>")) - - assert_equal(["cat ", @em_on, "and ", @em_then_bold, "dog", @bold_em_off], - @am.flow("cat <i>and <b>dog</b></i>")) - - assert_equal(["cat ", @bold_em_on, "and", @bold_em_off, " dog"], - @am.flow("cat <i><b>and</b></i> dog")) - - - end - - def test_protect - assert_equal(['cat \\ dog'], @am.flow('cat \\ dog')) - - assert_equal(["cat <tt>dog</Tt>"], @am.flow("cat \\<tt>dog</Tt>")) - - assert_equal(["cat ", @em_on, "and", @em_off, " <B>dog</b>"], - @am.flow("cat <i>and</i> \\<B>dog</b>")) - - assert_equal(["*word* or <b>text</b>"], @am.flow("\\*word* or \\<b>text</b>")) - - assert_equal(["_cat_", @em_on, "dog", @em_off], - @am.flow("\\_cat_<i>dog</i>")) - end - - def test_adding - assert_equal(["cat ", @wombat_on, "and", @wombat_off, " dog" ], - @am.flow("cat {and} dog")) -# assert_equal(["cat {and} dog" ], @am.flow("cat \\{and} dog")) - end -end diff --git a/lib/rdoc/markup/test/TestParse.rb b/lib/rdoc/markup/test/TestParse.rb deleted file mode 100644 index 3ec541ce7a..0000000000 --- a/lib/rdoc/markup/test/TestParse.rb +++ /dev/null @@ -1,503 +0,0 @@ -require 'test/unit' - -$:.unshift "../../.." - -require 'rdoc/markup/simple_markup' - -include SM - -class TestParse < Test::Unit::TestCase - - class MockOutput - def start_accepting - @res = [] - end - - def end_accepting - @res - end - - def accept_paragraph(am, fragment) - @res << fragment.to_s - end - - def accept_verbatim(am, fragment) - @res << fragment.to_s - end - - def accept_list_start(am, fragment) - @res << fragment.to_s - end - - def accept_list_end(am, fragment) - @res << fragment.to_s - end - - def accept_list_item(am, fragment) - @res << fragment.to_s - end - - def accept_blank_line(am, fragment) - @res << fragment.to_s - end - - def accept_heading(am, fragment) - @res << fragment.to_s - end - - def accept_rule(am, fragment) - @res << fragment.to_s - end - - end - - def basic_conv(str) - sm = SimpleMarkup.new - mock = MockOutput.new - sm.convert(str, mock) - sm.content - end - - def line_types(str, expected) - p = SimpleMarkup.new - mock = MockOutput.new - p.convert(str, mock) - assert_equal(expected, p.get_line_types.map{|type| type.to_s[0,1]}.join('')) - end - - def line_groups(str, expected) - p = SimpleMarkup.new - mock = MockOutput.new - - block = p.convert(str, mock) - - if block != expected - rows = (0...([expected.size, block.size].max)).collect{|i| - [expected[i]||"nil", block[i]||"nil"] - } - printf "\n\n%35s %35s\n", "Expected", "Got" - rows.each {|e,g| printf "%35s %35s\n", e.dump, g.dump } - end - - assert_equal(expected, block) - end - - def test_tabs - str = "hello\n dave" - assert_equal(str, basic_conv(str)) - str = "hello\n\tdave" - assert_equal("hello\n dave", basic_conv(str)) - str = "hello\n \tdave" - assert_equal("hello\n dave", basic_conv(str)) - str = "hello\n \tdave" - assert_equal("hello\n dave", basic_conv(str)) - str = "hello\n \tdave" - assert_equal("hello\n dave", basic_conv(str)) - str = "hello\n \tdave" - assert_equal("hello\n dave", basic_conv(str)) - str = "hello\n \tdave" - assert_equal("hello\n dave", basic_conv(str)) - str = "hello\n \tdave" - assert_equal("hello\n dave", basic_conv(str)) - str = "hello\n \tdave" - assert_equal("hello\n dave", basic_conv(str)) - str = "hello\n \tdave" - assert_equal("hello\n dave", basic_conv(str)) - str = ".\t\t." - assert_equal(". .", basic_conv(str)) - end - - def test_whitespace - assert_equal("hello", basic_conv("hello")) - assert_equal("hello", basic_conv(" hello ")) - assert_equal("hello", basic_conv(" \t \t hello\t\t")) - - assert_equal("1\n 2\n 3", basic_conv("1\n 2\n 3")) - assert_equal("1\n 2\n 3", basic_conv(" 1\n 2\n 3")) - - assert_equal("1\n 2\n 3\n1\n 2", basic_conv("1\n 2\n 3\n1\n 2")) - assert_equal("1\n 2\n 3\n1\n 2", basic_conv(" 1\n 2\n 3\n 1\n 2")) - - assert_equal("1\n 2\n\n 3", basic_conv(" 1\n 2\n\n 3")) - end - - def test_types - str = "now is the time" - line_types(str, 'P') - - str = "now is the time\nfor all good men" - line_types(str, 'PP') - - str = "now is the time\n code\nfor all good men" - line_types(str, 'PVP') - - str = "now is the time\n code\n more code\nfor all good men" - line_types(str, 'PVVP') - - str = "now is\n---\nthe time" - line_types(str, 'PRP') - - str = %{\ - now is - * l1 - * l2 - the time} - line_types(str, 'PLLP') - - str = %{\ - now is - * l1 - l1+ - * l2 - the time} - line_types(str, 'PLPLP') - - str = %{\ - now is - * l1 - * l1.1 - * l2 - the time} - line_types(str, 'PLLLP') - - str = %{\ - now is - * l1 - * l1.1 - text - code - code - - text - * l2 - the time} - line_types(str, 'PLLPVVBPLP') - - str = %{\ - now is - 1. l1 - * l1.1 - 2. l2 - the time} - line_types(str, 'PLLLP') - - str = %{\ - now is - [cat] l1 - * l1.1 - [dog] l2 - the time} - line_types(str, 'PLLLP') - - str = %{\ - now is - [cat] l1 - continuation - [dog] l2 - the time} - line_types(str, 'PLPLP') - end - - def test_groups - str = "now is the time" - line_groups(str, ["L0: Paragraph\nnow is the time"] ) - - str = "now is the time\nfor all good men" - line_groups(str, ["L0: Paragraph\nnow is the time for all good men"] ) - - str = %{\ - now is the time - code _line_ here - for all good men} - - line_groups(str, - [ "L0: Paragraph\nnow is the time", - "L0: Verbatim\n code _line_ here\n", - "L0: Paragraph\nfor all good men" - ] ) - - str = "now is the time\n code\n more code\nfor all good men" - line_groups(str, - [ "L0: Paragraph\nnow is the time", - "L0: Verbatim\n code\n more code\n", - "L0: Paragraph\nfor all good men" - ] ) - - str = %{\ - now is - * l1 - * l2 - the time} - line_groups(str, - [ "L0: Paragraph\nnow is", - "L1: ListStart\n", - "L1: ListItem\nl1", - "L1: ListItem\nl2", - "L1: ListEnd\n", - "L0: Paragraph\nthe time" - ]) - - str = %{\ - now is - * l1 - l1+ - * l2 - the time} - line_groups(str, - [ "L0: Paragraph\nnow is", - "L1: ListStart\n", - "L1: ListItem\nl1 l1+", - "L1: ListItem\nl2", - "L1: ListEnd\n", - "L0: Paragraph\nthe time" - ]) - - str = %{\ - now is - * l1 - * l1.1 - * l2 - the time} - line_groups(str, - [ "L0: Paragraph\nnow is", - "L1: ListStart\n", - "L1: ListItem\nl1", - "L2: ListStart\n", - "L2: ListItem\nl1.1", - "L2: ListEnd\n", - "L1: ListItem\nl2", - "L1: ListEnd\n", - "L0: Paragraph\nthe time" - ]) - - - str = %{\ - now is - * l1 - * l1.1 - text - code - code - - text - * l2 - the time} - line_groups(str, - [ "L0: Paragraph\nnow is", - "L1: ListStart\n", - "L1: ListItem\nl1", - "L2: ListStart\n", - "L2: ListItem\nl1.1 text", - "L2: Verbatim\n code\n code\n", - "L2: Paragraph\ntext", - "L2: ListEnd\n", - "L1: ListItem\nl2", - "L1: ListEnd\n", - "L0: Paragraph\nthe time" - ]) - - - str = %{\ - now is - 1. l1 - * l1.1 - 2. l2 - the time} - line_groups(str, - [ "L0: Paragraph\nnow is", - "L1: ListStart\n", - "L1: ListItem\nl1", - "L2: ListStart\n", - "L2: ListItem\nl1.1", - "L2: ListEnd\n", - "L1: ListItem\nl2", - "L1: ListEnd\n", - "L0: Paragraph\nthe time" - ]) - - str = %{\ - now is - [cat] l1 - * l1.1 - [dog] l2 - the time} - line_groups(str, - [ "L0: Paragraph\nnow is", - "L1: ListStart\n", - "L1: ListItem\nl1", - "L2: ListStart\n", - "L2: ListItem\nl1.1", - "L2: ListEnd\n", - "L1: ListItem\nl2", - "L1: ListEnd\n", - "L0: Paragraph\nthe time" - ]) - - str = %{\ - now is - [cat] l1 - continuation - [dog] l2 - the time} - line_groups(str, - [ "L0: Paragraph\nnow is", - "L1: ListStart\n", - "L1: ListItem\nl1 continuation", - "L1: ListItem\nl2", - "L1: ListEnd\n", - "L0: Paragraph\nthe time" - ]) - - - end - - def test_verbatim_merge - str = %{\ - now is - code - the time} - - line_groups(str, - [ "L0: Paragraph\nnow is", - "L0: Verbatim\n code\n", - "L0: Paragraph\nthe time" - ]) - - - str = %{\ - now is - code - code1 - the time} - - line_groups(str, - [ "L0: Paragraph\nnow is", - "L0: Verbatim\n code\n code1\n", - "L0: Paragraph\nthe time" - ]) - - - str = %{\ - now is - code - - code1 - the time} - - line_groups(str, - [ "L0: Paragraph\nnow is", - "L0: Verbatim\n code\n\n code1\n", - "L0: Paragraph\nthe time" - ]) - - - str = %{\ - now is - code - - code1 - - the time} - - line_groups(str, - [ "L0: Paragraph\nnow is", - "L0: Verbatim\n code\n\n code1\n", - "L0: Paragraph\nthe time" - ]) - - - str = %{\ - now is - code - - code1 - - code2 - the time} - - line_groups(str, - [ "L0: Paragraph\nnow is", - "L0: Verbatim\n code\n\n code1\n\n code2\n", - "L0: Paragraph\nthe time" - ]) - - - # Folds multiple blank lines - str = %{\ - now is - code - - - code1 - - the time} - - line_groups(str, - [ "L0: Paragraph\nnow is", - "L0: Verbatim\n code\n\n code1\n", - "L0: Paragraph\nthe time" - ]) - - - end - - def test_list_split - str = %{\ - now is - * l1 - 1. n1 - 2. n2 - * l2 - the time} - line_groups(str, - [ "L0: Paragraph\nnow is", - "L1: ListStart\n", - "L1: ListItem\nl1", - "L1: ListEnd\n", - "L1: ListStart\n", - "L1: ListItem\nn1", - "L1: ListItem\nn2", - "L1: ListEnd\n", - "L1: ListStart\n", - "L1: ListItem\nl2", - "L1: ListEnd\n", - "L0: Paragraph\nthe time" - ]) - - end - - - def test_headings - str = "= heading one" - line_groups(str, - [ "L0: Heading\nheading one" - ]) - - str = "=== heading three" - line_groups(str, - [ "L0: Heading\nheading three" - ]) - - str = "text\n === heading three" - line_groups(str, - [ "L0: Paragraph\ntext", - "L0: Verbatim\n === heading three\n" - ]) - - str = "text\n code\n === heading three" - line_groups(str, - [ "L0: Paragraph\ntext", - "L0: Verbatim\n code\n === heading three\n" - ]) - - str = "text\n code\n=== heading three" - line_groups(str, - [ "L0: Paragraph\ntext", - "L0: Verbatim\n code\n", - "L0: Heading\nheading three" - ]) - - end - - -end diff --git a/lib/rdoc/markup/text_formatter_test_case.rb b/lib/rdoc/markup/text_formatter_test_case.rb new file mode 100644 index 0000000000..22a762b5f0 --- /dev/null +++ b/lib/rdoc/markup/text_formatter_test_case.rb @@ -0,0 +1,115 @@ +# frozen_string_literal: true +## +# Test case for creating new plain-text RDoc::Markup formatters. See also +# RDoc::Markup::FormatterTestCase +# +# See test_rdoc_markup_to_rdoc.rb for a complete example. +# +# Example: +# +# class TestRDocMarkupToNewTextFormat < RDoc::Markup::TextFormatterTestCase +# +# add_visitor_tests +# add_text_tests +# +# def setup +# super +# +# @to = RDoc::Markup::ToNewTextFormat.new +# end +# +# def accept_blank_line +# assert_equal :junk, @to.res.join +# end +# +# # ... +# +# end + +class RDoc::Markup::TextFormatterTestCase < RDoc::Markup::FormatterTestCase + + ## + # Adds test cases to the calling TestCase. + + def self.add_text_tests + self.class_eval do + + ## + # Test case that calls <tt>@to.accept_heading</tt> + + def test_accept_heading_indent + @to.start_accepting + @to.indent = 3 + @to.accept_heading @RM::Heading.new(1, 'Hello') + + accept_heading_indent + end + + ## + # Test case that calls <tt>@to.accept_rule</tt> + + def test_accept_rule_indent + @to.start_accepting + @to.indent = 3 + @to.accept_rule @RM::Rule.new(1) + + accept_rule_indent + end + + ## + # Test case that calls <tt>@to.accept_verbatim</tt> + + def test_accept_verbatim_indent + @to.start_accepting + @to.indent = 2 + @to.accept_verbatim @RM::Verbatim.new("hi\n", " world\n") + + accept_verbatim_indent + end + + ## + # Test case that calls <tt>@to.accept_verbatim</tt> with a big indent + + def test_accept_verbatim_big_indent + @to.start_accepting + @to.indent = 2 + @to.accept_verbatim @RM::Verbatim.new("hi\n", "world\n") + + accept_verbatim_big_indent + end + + ## + # Test case that calls <tt>@to.accept_paragraph</tt> with an indent + + def test_accept_paragraph_indent + @to.start_accepting + @to.indent = 3 + @to.accept_paragraph @RM::Paragraph.new(('words ' * 30).strip) + + accept_paragraph_indent + end + + ## + # Test case that calls <tt>@to.accept_paragraph</tt> with a long line + + def test_accept_paragraph_wrap + @to.start_accepting + @to.accept_paragraph @RM::Paragraph.new(('words ' * 30).strip) + + accept_paragraph_wrap + end + + ## + # Test case that calls <tt>@to.attributes</tt> with an escaped + # cross-reference. If this test doesn't pass something may be very + # wrong. + + def test_attributes + assert_equal 'Dog', @to.attributes("\\Dog") + end + + end + end + +end + diff --git a/lib/rdoc/markup/to_ansi.rb b/lib/rdoc/markup/to_ansi.rb new file mode 100644 index 0000000000..6cc3b70e93 --- /dev/null +++ b/lib/rdoc/markup/to_ansi.rb @@ -0,0 +1,94 @@ +# frozen_string_literal: true +## +# Outputs RDoc markup with vibrant ANSI color! + +class RDoc::Markup::ToAnsi < RDoc::Markup::ToRdoc + + ## + # Creates a new ToAnsi visitor that is ready to output vibrant ANSI color! + + def initialize markup = nil + super + + @headings.clear + @headings[1] = ["\e[1;32m", "\e[m"] # bold + @headings[2] = ["\e[4;32m", "\e[m"] # underline + @headings[3] = ["\e[32m", "\e[m"] # just green + end + + ## + # Maps attributes to ANSI sequences + + def init_tags + add_tag :BOLD, "\e[1m", "\e[m" + add_tag :TT, "\e[7m", "\e[m" + add_tag :EM, "\e[4m", "\e[m" + end + + ## + # Overrides indent width to ensure output lines up correctly. + + def accept_list_item_end list_item + width = case @list_type.last + when :BULLET then + 2 + when :NOTE, :LABEL then + if @prefix then + @res << @prefix.strip + @prefix = nil + end + + @res << "\n" unless res.length == 1 + 2 + else + bullet = @list_index.last.to_s + @list_index[-1] = @list_index.last.succ + bullet.length + 2 + end + + @indent -= width + end + + ## + # Adds coloring to note and label list items + + def accept_list_item_start list_item + bullet = case @list_type.last + when :BULLET then + '*' + when :NOTE, :LABEL then + labels = Array(list_item.label).map do |label| + attributes(label).strip + end.join "\n" + + labels << ":\n" unless labels.empty? + + labels + else + @list_index.last.to_s + '.' + end + + case @list_type.last + when :NOTE, :LABEL then + @indent += 2 + @prefix = bullet + (' ' * @indent) + else + @prefix = (' ' * @indent) + bullet.ljust(bullet.length + 1) + + width = bullet.gsub(/\e\[[\d;]*m/, '').length + 1 + + @indent += width + end + end + + ## + # Starts accepting with a reset screen + + def start_accepting + super + + @res = ["\e[0m"] + end + +end + diff --git a/lib/rdoc/markup/to_bs.rb b/lib/rdoc/markup/to_bs.rb new file mode 100644 index 0000000000..fea017e89d --- /dev/null +++ b/lib/rdoc/markup/to_bs.rb @@ -0,0 +1,77 @@ +# frozen_string_literal: true +## +# Outputs RDoc markup with hot backspace action! You will probably need a +# pager to use this output format. +# +# This formatter won't work on 1.8.6 because it lacks String#chars. + +class RDoc::Markup::ToBs < RDoc::Markup::ToRdoc + + ## + # Returns a new ToBs that is ready for hot backspace action! + + def initialize markup = nil + super + + @in_b = false + @in_em = false + end + + ## + # Sets a flag that is picked up by #annotate to do the right thing in + # #convert_string + + def init_tags + add_tag :BOLD, '+b', '-b' + add_tag :EM, '+_', '-_' + add_tag :TT, '' , '' # we need in_tt information maintained + end + + ## + # Makes heading text bold. + + def accept_heading heading + use_prefix or @res << ' ' * @indent + @res << @headings[heading.level][0] + @in_b = true + @res << attributes(heading.text) + @in_b = false + @res << @headings[heading.level][1] + @res << "\n" + end + + ## + # Turns on or off special handling for +convert_string+ + + def annotate tag + case tag + when '+b' then @in_b = true + when '-b' then @in_b = false + when '+_' then @in_em = true + when '-_' then @in_em = false + end + '' + end + + ## + # Calls convert_string on the result of convert_special + + def convert_special special + convert_string super + end + + ## + # Adds bold or underline mixed with backspaces + + def convert_string string + return string unless @in_b or @in_em + chars = if @in_b then + string.chars.map do |char| "#{char}\b#{char}" end + elsif @in_em then + string.chars.map do |char| "_\b#{char}" end + end + + chars.join + end + +end diff --git a/lib/rdoc/markup/to_html.rb b/lib/rdoc/markup/to_html.rb new file mode 100644 index 0000000000..c5e1f073f8 --- /dev/null +++ b/lib/rdoc/markup/to_html.rb @@ -0,0 +1,404 @@ +# frozen_string_literal: true +require 'cgi' + +## +# Outputs RDoc markup as HTML. + +class RDoc::Markup::ToHtml < RDoc::Markup::Formatter + + include RDoc::Text + + # :section: Utilities + + ## + # Maps RDoc::Markup::Parser::LIST_TOKENS types to HTML tags + + LIST_TYPE_TO_HTML = { + :BULLET => ['<ul>', '</ul>'], + :LABEL => ['<dl class="rdoc-list label-list">', '</dl>'], + :LALPHA => ['<ol style="list-style-type: lower-alpha">', '</ol>'], + :NOTE => ['<dl class="rdoc-list note-list">', '</dl>'], + :NUMBER => ['<ol>', '</ol>'], + :UALPHA => ['<ol style="list-style-type: upper-alpha">', '</ol>'], + } + + attr_reader :res # :nodoc: + attr_reader :in_list_entry # :nodoc: + attr_reader :list # :nodoc: + + ## + # The RDoc::CodeObject HTML is being generated for. This is used to + # generate namespaced URI fragments + + attr_accessor :code_object + + ## + # Path to this document for relative links + + attr_accessor :from_path + + # :section: + + ## + # Creates a new formatter that will output HTML + + def initialize options, markup = nil + super + + @code_object = nil + @from_path = '' + @in_list_entry = nil + @list = nil + @th = nil + @hard_break = "<br>\n" + + # external links + @markup.add_special(/(?:link:|https?:|mailto:|ftp:|irc:|www\.)\S+\w/, + :HYPERLINK) + + add_special_RDOCLINK + add_special_TIDYLINK + + init_tags + end + + # :section: Special Handling + # + # These methods handle special markup added by RDoc::Markup#add_special. + + def handle_RDOCLINK url # :nodoc: + case url + when /^rdoc-ref:/ + $' + when /^rdoc-label:/ + text = $' + + text = case text + when /\Alabel-/ then $' + when /\Afootmark-/ then $' + when /\Afoottext-/ then $' + else text + end + + gen_url url, text + when /^rdoc-image:/ + "<img src=\"#{$'}\">" + else + url =~ /\Ardoc-[a-z]+:/ + + $' + end + end + + ## + # +special+ is a <code><br></code> + + def handle_special_HARD_BREAK special + '<br>' + end + + ## + # +special+ is a potential link. The following schemes are handled: + # + # <tt>mailto:</tt>:: + # Inserted as-is. + # <tt>http:</tt>:: + # Links are checked to see if they reference an image. If so, that image + # gets inserted using an <tt><img></tt> tag. Otherwise a conventional + # <tt><a href></tt> is used. + # <tt>link:</tt>:: + # Reference to a local file relative to the output directory. + + def handle_special_HYPERLINK(special) + url = special.text + + gen_url url, url + end + + ## + # +special+ is an rdoc-schemed link that will be converted into a hyperlink. + # + # For the +rdoc-ref+ scheme the named reference will be returned without + # creating a link. + # + # For the +rdoc-label+ scheme the footnote and label prefixes are stripped + # when creating a link. All other contents will be linked verbatim. + + def handle_special_RDOCLINK special + handle_RDOCLINK special.text + end + + ## + # This +special+ is a link where the label is different from the URL + # <tt>label[url]</tt> or <tt>{long label}[url]</tt> + + def handle_special_TIDYLINK(special) + text = special.text + + return text unless + text =~ /^\{(.*)\}\[(.*?)\]$/ or text =~ /^(\S+)\[(.*?)\]$/ + + label = $1 + url = $2 + + label = handle_RDOCLINK label if /^rdoc-image:/ =~ label + + gen_url url, label + end + + # :section: Visitor + # + # These methods implement the HTML visitor. + + ## + # Prepares the visitor for HTML generation + + def start_accepting + @res = [] + @in_list_entry = [] + @list = [] + end + + ## + # Returns the generated output + + def end_accepting + @res.join + end + + ## + # Adds +block_quote+ to the output + + def accept_block_quote block_quote + @res << "\n<blockquote>" + + block_quote.parts.each do |part| + part.accept self + end + + @res << "</blockquote>\n" + end + + ## + # Adds +paragraph+ to the output + + def accept_paragraph paragraph + @res << "\n<p>" + text = paragraph.text @hard_break + text = text.gsub(/\r?\n/, ' ') + @res << wrap(to_html(text)) + @res << "</p>\n" + end + + ## + # Adds +verbatim+ to the output + + def accept_verbatim verbatim + text = verbatim.text.rstrip + + klass = nil + + content = if verbatim.ruby? or parseable? text then + begin + tokens = RDoc::RipperStateLex.parse text + klass = ' class="ruby"' + + result = RDoc::TokenStream.to_html tokens + result = result + "\n" unless "\n" == result[-1] + result + rescue + CGI.escapeHTML text + end + else + CGI.escapeHTML text + end + + if @options.pipe then + @res << "\n<pre><code>#{CGI.escapeHTML text}\n</code></pre>\n" + else + @res << "\n<pre#{klass}>#{content}</pre>\n" + end + end + + ## + # Adds +rule+ to the output + + def accept_rule rule + @res << "<hr>\n" + end + + ## + # Prepares the visitor for consuming +list+ + + def accept_list_start(list) + @list << list.type + @res << html_list_name(list.type, true) + @in_list_entry.push false + end + + ## + # Finishes consumption of +list+ + + def accept_list_end(list) + @list.pop + if tag = @in_list_entry.pop + @res << tag + end + @res << html_list_name(list.type, false) << "\n" + end + + ## + # Prepares the visitor for consuming +list_item+ + + def accept_list_item_start(list_item) + if tag = @in_list_entry.last + @res << tag + end + + @res << list_item_start(list_item, @list.last) + end + + ## + # Finishes consumption of +list_item+ + + def accept_list_item_end(list_item) + @in_list_entry[-1] = list_end_for(@list.last) + end + + ## + # Adds +blank_line+ to the output + + def accept_blank_line(blank_line) + # @res << annotate("<p />") << "\n" + end + + ## + # Adds +heading+ to the output. The headings greater than 6 are trimmed to + # level 6. + + def accept_heading heading + level = [6, heading.level].min + + label = heading.label @code_object + + @res << if @options.output_decoration + "\n<h#{level} id=\"#{label}\">" + else + "\n<h#{level}>" + end + @res << to_html(heading.text) + unless @options.pipe then + @res << "<span><a href=\"##{label}\">¶</a>" + @res << " <a href=\"#top\">↑</a></span>" + end + @res << "</h#{level}>\n" + end + + ## + # Adds +raw+ to the output + + def accept_raw raw + @res << raw.parts.join("\n") + end + + # :section: Utilities + + ## + # CGI-escapes +text+ + + def convert_string(text) + CGI.escapeHTML text + end + + ## + # Generate a link to +url+ with content +text+. Handles the special cases + # for img: and link: described under handle_special_HYPERLINK + + def gen_url url, text + scheme, url, id = parse_url url + + if %w[http https link].include?(scheme) and + url =~ /\.(gif|png|jpg|jpeg|bmp)$/ then + "<img src=\"#{url}\" />" + else + text = text.sub %r%^#{scheme}:/*%i, '' + text = text.sub %r%^[*\^](\d+)$%, '\1' + + link = "<a#{id} href=\"#{url}\">#{text}</a>" + + link = "<sup>#{link}</sup>" if /"foot/ =~ id + + link + end + end + + ## + # Determines the HTML list element for +list_type+ and +open_tag+ + + def html_list_name(list_type, open_tag) + tags = LIST_TYPE_TO_HTML[list_type] + raise RDoc::Error, "Invalid list type: #{list_type.inspect}" unless tags + tags[open_tag ? 0 : 1] + end + + ## + # Maps attributes to HTML tags + + def init_tags + add_tag :BOLD, "<strong>", "</strong>" + add_tag :TT, "<code>", "</code>" + add_tag :EM, "<em>", "</em>" + end + + ## + # Returns the HTML tag for +list_type+, possible using a label from + # +list_item+ + + def list_item_start(list_item, list_type) + case list_type + when :BULLET, :LALPHA, :NUMBER, :UALPHA then + "<li>" + when :LABEL, :NOTE then + Array(list_item.label).map do |label| + "<dt>#{to_html label}\n" + end.join << "<dd>" + else + raise RDoc::Error, "Invalid list type: #{list_type.inspect}" + end + end + + ## + # Returns the HTML end-tag for +list_type+ + + def list_end_for(list_type) + case list_type + when :BULLET, :LALPHA, :NUMBER, :UALPHA then + "</li>" + when :LABEL, :NOTE then + "</dd>" + else + raise RDoc::Error, "Invalid list type: #{list_type.inspect}" + end + end + + ## + # Returns true if text is valid ruby syntax + + def parseable? text + verbose, $VERBOSE = $VERBOSE, nil + eval("BEGIN {return true}\n#{text}") + rescue SyntaxError + false + ensure + $VERBOSE = verbose + end + + ## + # Converts +item+ to HTML using RDoc::Text#to_html + + def to_html item + super convert_flow @am.flow item + end + +end + diff --git a/lib/rdoc/markup/to_html_crossref.rb b/lib/rdoc/markup/to_html_crossref.rb new file mode 100644 index 0000000000..2911aee954 --- /dev/null +++ b/lib/rdoc/markup/to_html_crossref.rb @@ -0,0 +1,161 @@ +# frozen_string_literal: true +## +# Subclass of the RDoc::Markup::ToHtml class that supports looking up method +# names, classes, etc to create links. RDoc::CrossReference is used to +# generate those links based on the current context. + +class RDoc::Markup::ToHtmlCrossref < RDoc::Markup::ToHtml + + # :stopdoc: + ALL_CROSSREF_REGEXP = RDoc::CrossReference::ALL_CROSSREF_REGEXP + CLASS_REGEXP_STR = RDoc::CrossReference::CLASS_REGEXP_STR + CROSSREF_REGEXP = RDoc::CrossReference::CROSSREF_REGEXP + METHOD_REGEXP_STR = RDoc::CrossReference::METHOD_REGEXP_STR + # :startdoc: + + ## + # RDoc::CodeObject for generating references + + attr_accessor :context + + ## + # Should we show '#' characters on method references? + + attr_accessor :show_hash + + ## + # Creates a new crossref resolver that generates links relative to +context+ + # which lives at +from_path+ in the generated files. '#' characters on + # references are removed unless +show_hash+ is true. Only method names + # preceded by '#' or '::' are linked, unless +hyperlink_all+ is true. + + def initialize(options, from_path, context, markup = nil) + raise ArgumentError, 'from_path cannot be nil' if from_path.nil? + + super options, markup + + @context = context + @from_path = from_path + @hyperlink_all = @options.hyperlink_all + @show_hash = @options.show_hash + + crossref_re = @hyperlink_all ? ALL_CROSSREF_REGEXP : CROSSREF_REGEXP + @markup.add_special crossref_re, :CROSSREF + + @cross_reference = RDoc::CrossReference.new @context + end + + ## + # Creates a link to the reference +name+ if the name exists. If +text+ is + # given it is used as the link text, otherwise +name+ is used. + + def cross_reference name, text = nil + lookup = name + + name = name[1..-1] unless @show_hash if name[0, 1] == '#' + + name = "#{CGI.unescape $'} at #{$1}" if name =~ /(.*[^#:])@/ + + text = name unless text + + link lookup, text + end + + ## + # We're invoked when any text matches the CROSSREF pattern. If we find the + # corresponding reference, generate a link. If the name we're looking for + # contains no punctuation, we look for it up the module/class chain. For + # example, ToHtml is found, even without the <tt>RDoc::Markup::</tt> prefix, + # because we look for it in module Markup first. + + def handle_special_CROSSREF(special) + name = special.text + + return name if name =~ /@[\w-]+\.[\w-]/ # labels that look like emails + + unless @hyperlink_all then + # This ensures that words entirely consisting of lowercase letters will + # not have cross-references generated (to suppress lots of erroneous + # cross-references to "new" in text, for instance) + return name if name =~ /\A[a-z]*\z/ + end + + cross_reference name + end + + ## + # Handles <tt>rdoc-ref:</tt> scheme links and allows RDoc::Markup::ToHtml to + # handle other schemes. + + def handle_special_HYPERLINK special + return cross_reference $' if special.text =~ /\Ardoc-ref:/ + + super + end + + ## + # +special+ is an rdoc-schemed link that will be converted into a hyperlink. + # For the rdoc-ref scheme the cross-reference will be looked up and the + # given name will be used. + # + # All other contents are handled by + # {the superclass}[rdoc-ref:RDoc::Markup::ToHtml#handle_special_RDOCLINK] + + def handle_special_RDOCLINK special + url = special.text + + case url + when /\Ardoc-ref:/ then + cross_reference $' + else + super + end + end + + ## + # Generates links for <tt>rdoc-ref:</tt> scheme URLs and allows + # RDoc::Markup::ToHtml to handle other schemes. + + def gen_url url, text + return super unless url =~ /\Ardoc-ref:/ + + cross_reference $', text + end + + ## + # Creates an HTML link to +name+ with the given +text+. + + def link name, text + original_name = name + + if name =~ /(.*[^#:])@/ then + name = $1 + label = $' + end + + ref = @cross_reference.resolve name, text + + text = ref.output_name @context if + RDoc::MethodAttr === ref and text == original_name + + case ref + when String then + ref + else + path = ref.as_href @from_path + + if path =~ /#/ then + path << "-label-#{label}" + elsif ref.sections and + ref.sections.any? { |section| label == section.title } then + path << "##{label}" + else + path << "#label-#{label}" + end if label + + "<a href=\"#{path}\">#{text}</a>" + end + end + +end + diff --git a/lib/rdoc/markup/to_html_snippet.rb b/lib/rdoc/markup/to_html_snippet.rb new file mode 100644 index 0000000000..24aa1d32d9 --- /dev/null +++ b/lib/rdoc/markup/to_html_snippet.rb @@ -0,0 +1,285 @@ +# frozen_string_literal: true +## +# Outputs RDoc markup as paragraphs with inline markup only. + +class RDoc::Markup::ToHtmlSnippet < RDoc::Markup::ToHtml + + ## + # After this many characters the input will be cut off. + + attr_reader :character_limit + + ## + # The number of characters seen so far. + + attr_reader :characters # :nodoc: + + ## + # The attribute bitmask + + attr_reader :mask + + ## + # After this many paragraphs the input will be cut off. + + attr_reader :paragraph_limit + + ## + # Count of paragraphs found + + attr_reader :paragraphs + + ## + # Creates a new ToHtmlSnippet formatter that will cut off the input on the + # next word boundary after the given number of +characters+ or +paragraphs+ + # of text have been encountered. + + def initialize options, characters = 100, paragraphs = 3, markup = nil + super options, markup + + @character_limit = characters + @paragraph_limit = paragraphs + + @characters = 0 + @mask = 0 + @paragraphs = 0 + + @markup.add_special RDoc::CrossReference::CROSSREF_REGEXP, :CROSSREF + end + + ## + # Adds +heading+ to the output as a paragraph + + def accept_heading heading + @res << "<p>#{to_html heading.text}\n" + + add_paragraph + end + + ## + # Raw sections are untrusted and ignored + + alias accept_raw ignore + + ## + # Rules are ignored + + alias accept_rule ignore + + def accept_paragraph paragraph + para = @in_list_entry.last || "<p>" + + text = paragraph.text @hard_break + + @res << "#{para}#{wrap to_html text}\n" + + add_paragraph + end + + ## + # Finishes consumption of +list_item+ + + def accept_list_item_end list_item + end + + ## + # Prepares the visitor for consuming +list_item+ + + def accept_list_item_start list_item + @res << list_item_start(list_item, @list.last) + end + + ## + # Prepares the visitor for consuming +list+ + + def accept_list_start list + @list << list.type + @res << html_list_name(list.type, true) + @in_list_entry.push '' + end + + ## + # Adds +verbatim+ to the output + + def accept_verbatim verbatim + throw :done if @characters >= @character_limit + input = verbatim.text.rstrip + + text = truncate input + text << ' ...' unless text == input + + super RDoc::Markup::Verbatim.new text + + add_paragraph + end + + ## + # Prepares the visitor for HTML snippet generation + + def start_accepting + super + + @characters = 0 + end + + ## + # Removes escaping from the cross-references in +special+ + + def handle_special_CROSSREF special + special.text.sub(/\A\\/, '') + end + + ## + # +special+ is a <code><br></code> + + def handle_special_HARD_BREAK special + @characters -= 4 + '<br>' + end + + ## + # Lists are paragraphs, but notes and labels have a separator + + def list_item_start list_item, list_type + throw :done if @characters >= @character_limit + + case list_type + when :BULLET, :LALPHA, :NUMBER, :UALPHA then + "<p>" + when :LABEL, :NOTE then + labels = Array(list_item.label).map do |label| + to_html label + end.join ', ' + + labels << " — " unless labels.empty? + + start = "<p>#{labels}" + @characters += 1 # try to include the label + start + else + raise RDoc::Error, "Invalid list type: #{list_type.inspect}" + end + end + + ## + # Returns just the text of +link+, +url+ is only used to determine the link + # type. + + def gen_url url, text + if url =~ /^rdoc-label:([^:]*)(?::(.*))?/ then + type = "link" + elsif url =~ /([A-Za-z]+):(.*)/ then + type = $1 + else + type = "http" + end + + if (type == "http" or type == "https" or type == "link") and + url =~ /\.(gif|png|jpg|jpeg|bmp)$/ then + '' + else + text.sub(%r%^#{type}:/*%, '') + end + end + + ## + # In snippets, there are no lists + + def html_list_name list_type, open_tag + '' + end + + ## + # Throws +:done+ when paragraph_limit paragraphs have been encountered + + def add_paragraph + @paragraphs += 1 + + throw :done if @paragraphs >= @paragraph_limit + end + + ## + # Marks up +content+ + + def convert content + catch :done do + return super + end + + end_accepting + end + + ## + # Converts flow items +flow+ + + def convert_flow flow + throw :done if @characters >= @character_limit + + res = [] + @mask = 0 + + flow.each do |item| + case item + when RDoc::Markup::AttrChanger then + off_tags res, item + on_tags res, item + when String then + text = convert_string item + res << truncate(text) + when RDoc::Markup::Special then + text = convert_special item + res << truncate(text) + else + raise "Unknown flow element: #{item.inspect}" + end + + if @characters >= @character_limit then + off_tags res, RDoc::Markup::AttrChanger.new(0, @mask) + break + end + end + + res << ' ...' if @characters >= @character_limit + + res.join + end + + ## + # Maintains a bitmask to allow HTML elements to be closed properly. See + # RDoc::Markup::Formatter. + + def on_tags res, item + @mask ^= item.turn_on + + super + end + + ## + # Maintains a bitmask to allow HTML elements to be closed properly. See + # RDoc::Markup::Formatter. + + def off_tags res, item + @mask ^= item.turn_off + + super + end + + ## + # Truncates +text+ at the end of the first word after the character_limit. + + def truncate text + length = text.length + characters = @characters + @characters += length + + return text if @characters < @character_limit + + remaining = @character_limit - characters + + text =~ /\A(.{#{remaining},}?)(\s|$)/m # TODO word-break instead of \s? + + $1 + end + +end + diff --git a/lib/rdoc/markup/to_joined_paragraph.rb b/lib/rdoc/markup/to_joined_paragraph.rb new file mode 100644 index 0000000000..795f3f62ee --- /dev/null +++ b/lib/rdoc/markup/to_joined_paragraph.rb @@ -0,0 +1,46 @@ +# frozen_string_literal: true +## +# Joins the parts of an RDoc::Markup::Paragraph into a single String. +# +# This allows for easier maintenance and testing of Markdown support. +# +# This formatter only works on Paragraph instances. Attempting to process +# other markup syntax items will not work. + +class RDoc::Markup::ToJoinedParagraph < RDoc::Markup::Formatter + + def initialize # :nodoc: + super nil + end + + def start_accepting # :nodoc: + end + + def end_accepting # :nodoc: + end + + ## + # Converts the parts of +paragraph+ to a single entry. + + def accept_paragraph paragraph + parts = paragraph.parts.chunk do |part| + String === part + end.map do |string, chunk| + string ? chunk.join.rstrip : chunk + end.flatten + + paragraph.parts.replace parts + end + + alias accept_block_quote ignore + alias accept_heading ignore + alias accept_list_end ignore + alias accept_list_item_end ignore + alias accept_list_item_start ignore + alias accept_list_start ignore + alias accept_raw ignore + alias accept_rule ignore + alias accept_verbatim ignore + +end + diff --git a/lib/rdoc/markup/to_label.rb b/lib/rdoc/markup/to_label.rb new file mode 100644 index 0000000000..9f179013f2 --- /dev/null +++ b/lib/rdoc/markup/to_label.rb @@ -0,0 +1,75 @@ +# frozen_string_literal: true +require 'cgi' + +## +# Creates HTML-safe labels suitable for use in id attributes. Tidylinks are +# converted to their link part and cross-reference links have the suppression +# marks removed (\\SomeClass is converted to SomeClass). + +class RDoc::Markup::ToLabel < RDoc::Markup::Formatter + + attr_reader :res # :nodoc: + + ## + # Creates a new formatter that will output HTML-safe labels + + def initialize markup = nil + super nil, markup + + @markup.add_special RDoc::CrossReference::CROSSREF_REGEXP, :CROSSREF + @markup.add_special(/(((\{.*?\})|\b\S+?)\[\S+?\])/, :TIDYLINK) + + add_tag :BOLD, '', '' + add_tag :TT, '', '' + add_tag :EM, '', '' + + @res = [] + end + + ## + # Converts +text+ to an HTML-safe label + + def convert text + label = convert_flow @am.flow text + + CGI.escape(label).gsub('%', '-').sub(/^-/, '') + end + + ## + # Converts the CROSSREF +special+ to plain text, removing the suppression + # marker, if any + + def handle_special_CROSSREF special + text = special.text + + text.sub(/^\\/, '') + end + + ## + # Converts the TIDYLINK +special+ to just the text part + + def handle_special_TIDYLINK special + text = special.text + + return text unless text =~ /\{(.*?)\}\[(.*?)\]/ or text =~ /(\S+)\[(.*?)\]/ + + $1 + end + + alias accept_blank_line ignore + alias accept_block_quote ignore + alias accept_heading ignore + alias accept_list_end ignore + alias accept_list_item_end ignore + alias accept_list_item_start ignore + alias accept_list_start ignore + alias accept_paragraph ignore + alias accept_raw ignore + alias accept_rule ignore + alias accept_verbatim ignore + alias end_accepting ignore + alias handle_special_HARD_BREAK ignore + alias start_accepting ignore + +end + diff --git a/lib/rdoc/markup/to_markdown.rb b/lib/rdoc/markup/to_markdown.rb new file mode 100644 index 0000000000..d471032f9f --- /dev/null +++ b/lib/rdoc/markup/to_markdown.rb @@ -0,0 +1,192 @@ +# frozen_string_literal: true +# :markup: markdown + +## +# Outputs parsed markup as Markdown + +class RDoc::Markup::ToMarkdown < RDoc::Markup::ToRdoc + + ## + # Creates a new formatter that will output Markdown format text + + def initialize markup = nil + super + + @headings[1] = ['# ', ''] + @headings[2] = ['## ', ''] + @headings[3] = ['### ', ''] + @headings[4] = ['#### ', ''] + @headings[5] = ['##### ', ''] + @headings[6] = ['###### ', ''] + + add_special_RDOCLINK + add_special_TIDYLINK + + @hard_break = " \n" + end + + ## + # Maps attributes to HTML sequences + + def init_tags + add_tag :BOLD, '**', '**' + add_tag :EM, '*', '*' + add_tag :TT, '`', '`' + end + + ## + # Adds a newline to the output + + def handle_special_HARD_BREAK special + " \n" + end + + ## + # Finishes consumption of `list` + + def accept_list_end list + @res << "\n" + + super + end + + ## + # Finishes consumption of `list_item` + + def accept_list_item_end list_item + width = case @list_type.last + when :BULLET then + 4 + when :NOTE, :LABEL then + use_prefix + + 4 + else + @list_index[-1] = @list_index.last.succ + 4 + end + + @indent -= width + end + + ## + # Prepares the visitor for consuming `list_item` + + def accept_list_item_start list_item + type = @list_type.last + + case type + when :NOTE, :LABEL then + bullets = Array(list_item.label).map do |label| + attributes(label).strip + end.join "\n" + + bullets << "\n:" + + @prefix = ' ' * @indent + @indent += 4 + @prefix << bullets + (' ' * (@indent - 1)) + else + bullet = type == :BULLET ? '*' : @list_index.last.to_s + '.' + @prefix = (' ' * @indent) + bullet.ljust(4) + + @indent += 4 + end + end + + ## + # Prepares the visitor for consuming `list` + + def accept_list_start list + case list.type + when :BULLET, :LABEL, :NOTE then + @list_index << nil + when :LALPHA, :NUMBER, :UALPHA then + @list_index << 1 + else + raise RDoc::Error, "invalid list type #{list.type}" + end + + @list_width << 4 + @list_type << list.type + end + + ## + # Adds `rule` to the output + + def accept_rule rule + use_prefix or @res << ' ' * @indent + @res << '-' * 3 + @res << "\n" + end + + ## + # Outputs `verbatim` indented 4 columns + + def accept_verbatim verbatim + indent = ' ' * (@indent + 4) + + verbatim.parts.each do |part| + @res << indent unless part == "\n" + @res << part + end + + @res << "\n" unless @res =~ /\n\z/ + end + + ## + # Creates a Markdown-style URL from +url+ with +text+. + + def gen_url url, text + scheme, url, = parse_url url + + "[#{text.sub(%r{^#{scheme}:/*}i, '')}](#{url})" + end + + ## + # Handles <tt>rdoc-</tt> type links for footnotes. + + def handle_rdoc_link url + case url + when /^rdoc-ref:/ then + $' + when /^rdoc-label:footmark-(\d+)/ then + "[^#{$1}]:" + when /^rdoc-label:foottext-(\d+)/ then + "[^#{$1}]" + when /^rdoc-label:label-/ then + gen_url url, $' + when /^rdoc-image:/ then + "" + when /^rdoc-[a-z]+:/ then + $' + end + end + + ## + # Converts the RDoc markup tidylink into a Markdown.style link. + + def handle_special_TIDYLINK special + text = special.text + + return text unless text =~ /\{(.*?)\}\[(.*?)\]/ or text =~ /(\S+)\[(.*?)\]/ + + label = $1 + url = $2 + + if url =~ /^rdoc-label:foot/ then + handle_rdoc_link url + else + gen_url url, label + end + end + + ## + # Converts the rdoc-...: links into a Markdown.style links. + + def handle_special_RDOCLINK special + handle_rdoc_link special.text + end + +end + diff --git a/lib/rdoc/markup/to_rdoc.rb b/lib/rdoc/markup/to_rdoc.rb new file mode 100644 index 0000000000..1cb4d6bab2 --- /dev/null +++ b/lib/rdoc/markup/to_rdoc.rb @@ -0,0 +1,334 @@ +# frozen_string_literal: true +## +# Outputs RDoc markup as RDoc markup! (mostly) + +class RDoc::Markup::ToRdoc < RDoc::Markup::Formatter + + ## + # Current indent amount for output in characters + + attr_accessor :indent + + ## + # Output width in characters + + attr_accessor :width + + ## + # Stack of current list indexes for alphabetic and numeric lists + + attr_reader :list_index + + ## + # Stack of list types + + attr_reader :list_type + + ## + # Stack of list widths for indentation + + attr_reader :list_width + + ## + # Prefix for the next list item. See #use_prefix + + attr_reader :prefix + + ## + # Output accumulator + + attr_reader :res + + ## + # Creates a new formatter that will output (mostly) \RDoc markup + + def initialize markup = nil + super nil, markup + + @markup.add_special(/\\\S/, :SUPPRESSED_CROSSREF) + @width = 78 + init_tags + + @headings = {} + @headings.default = [] + + @headings[1] = ['= ', ''] + @headings[2] = ['== ', ''] + @headings[3] = ['=== ', ''] + @headings[4] = ['==== ', ''] + @headings[5] = ['===== ', ''] + @headings[6] = ['====== ', ''] + + @hard_break = "\n" + end + + ## + # Maps attributes to HTML sequences + + def init_tags + add_tag :BOLD, "<b>", "</b>" + add_tag :TT, "<tt>", "</tt>" + add_tag :EM, "<em>", "</em>" + end + + ## + # Adds +blank_line+ to the output + + def accept_blank_line blank_line + @res << "\n" + end + + ## + # Adds +paragraph+ to the output + + def accept_block_quote block_quote + @indent += 2 + + block_quote.parts.each do |part| + @prefix = '> ' + + part.accept self + end + + @indent -= 2 + end + + ## + # Adds +heading+ to the output + + def accept_heading heading + use_prefix or @res << ' ' * @indent + @res << @headings[heading.level][0] + @res << attributes(heading.text) + @res << @headings[heading.level][1] + @res << "\n" + end + + ## + # Finishes consumption of +list+ + + def accept_list_end list + @list_index.pop + @list_type.pop + @list_width.pop + end + + ## + # Finishes consumption of +list_item+ + + def accept_list_item_end list_item + width = case @list_type.last + when :BULLET then + 2 + when :NOTE, :LABEL then + if @prefix then + @res << @prefix.strip + @prefix = nil + end + + @res << "\n" + 2 + else + bullet = @list_index.last.to_s + @list_index[-1] = @list_index.last.succ + bullet.length + 2 + end + + @indent -= width + end + + ## + # Prepares the visitor for consuming +list_item+ + + def accept_list_item_start list_item + type = @list_type.last + + case type + when :NOTE, :LABEL then + bullets = Array(list_item.label).map do |label| + attributes(label).strip + end.join "\n" + + bullets << ":\n" unless bullets.empty? + + @prefix = ' ' * @indent + @indent += 2 + @prefix << bullets + (' ' * @indent) + else + bullet = type == :BULLET ? '*' : @list_index.last.to_s + '.' + @prefix = (' ' * @indent) + bullet.ljust(bullet.length + 1) + width = bullet.length + 1 + @indent += width + end + end + + ## + # Prepares the visitor for consuming +list+ + + def accept_list_start list + case list.type + when :BULLET then + @list_index << nil + @list_width << 1 + when :LABEL, :NOTE then + @list_index << nil + @list_width << 2 + when :LALPHA then + @list_index << 'a' + @list_width << list.items.length.to_s.length + when :NUMBER then + @list_index << 1 + @list_width << list.items.length.to_s.length + when :UALPHA then + @list_index << 'A' + @list_width << list.items.length.to_s.length + else + raise RDoc::Error, "invalid list type #{list.type}" + end + + @list_type << list.type + end + + ## + # Adds +paragraph+ to the output + + def accept_paragraph paragraph + text = paragraph.text @hard_break + wrap attributes text + end + + ## + # Adds +paragraph+ to the output + + def accept_indented_paragraph paragraph + @indent += paragraph.indent + text = paragraph.text @hard_break + wrap attributes text + @indent -= paragraph.indent + end + + ## + # Adds +raw+ to the output + + def accept_raw raw + @res << raw.parts.join("\n") + end + + ## + # Adds +rule+ to the output + + def accept_rule rule + use_prefix or @res << ' ' * @indent + @res << '-' * (@width - @indent) + @res << "\n" + end + + ## + # Outputs +verbatim+ indented 2 columns + + def accept_verbatim verbatim + indent = ' ' * (@indent + 2) + + verbatim.parts.each do |part| + @res << indent unless part == "\n" + @res << part + end + + @res << "\n" unless @res =~ /\n\z/ + end + + ## + # Applies attribute-specific markup to +text+ using RDoc::AttributeManager + + def attributes text + flow = @am.flow text.dup + convert_flow flow + end + + ## + # Returns the generated output + + def end_accepting + @res.join + end + + ## + # Removes preceding \\ from the suppressed crossref +special+ + + def handle_special_SUPPRESSED_CROSSREF special + text = special.text + text = text.sub('\\', '') unless in_tt? + text + end + + ## + # Adds a newline to the output + + def handle_special_HARD_BREAK special + "\n" + end + + ## + # Prepares the visitor for text generation + + def start_accepting + @res = [""] + @indent = 0 + @prefix = nil + + @list_index = [] + @list_type = [] + @list_width = [] + end + + ## + # Adds the stored #prefix to the output and clears it. Lists generate a + # prefix for later consumption. + + def use_prefix + prefix, @prefix = @prefix, nil + @res << prefix if prefix + + prefix + end + + ## + # Wraps +text+ to #width + + def wrap text + return unless text && !text.empty? + + text_len = @width - @indent + + text_len = 20 if text_len < 20 + + re = /^(.{0,#{text_len}})[ \n]/ + next_prefix = ' ' * @indent + + prefix = @prefix || next_prefix + @prefix = nil + + @res << prefix + + while text.length > text_len + if text =~ re then + @res << $1 + text.slice!(0, $&.length) + else + @res << text.slice!(0, text_len) + end + + @res << "\n" << next_prefix + end + + if text.empty? then + @res.pop + @res.pop + else + @res << text + @res << "\n" + end + end + +end + diff --git a/lib/rdoc/markup/to_table_of_contents.rb b/lib/rdoc/markup/to_table_of_contents.rb new file mode 100644 index 0000000000..f68b90bcf6 --- /dev/null +++ b/lib/rdoc/markup/to_table_of_contents.rb @@ -0,0 +1,88 @@ +# frozen_string_literal: true +## +# Extracts just the RDoc::Markup::Heading elements from a +# RDoc::Markup::Document to help build a table of contents + +class RDoc::Markup::ToTableOfContents < RDoc::Markup::Formatter + + @to_toc = nil + + ## + # Singleton for table-of-contents generation + + def self.to_toc + @to_toc ||= new + end + + ## + # Output accumulator + + attr_reader :res + + ## + # Omits headings with a level less than the given level. + + attr_accessor :omit_headings_below + + def initialize # :nodoc: + super nil + + @omit_headings_below = nil + end + + ## + # Adds +document+ to the output, using its heading cutoff if present + + def accept_document document + @omit_headings_below = document.omit_headings_below + + super + end + + ## + # Adds +heading+ to the table of contents + + def accept_heading heading + @res << heading unless suppressed? heading + end + + ## + # Returns the table of contents + + def end_accepting + @res + end + + ## + # Prepares the visitor for text generation + + def start_accepting + @omit_headings_below = nil + @res = [] + end + + ## + # Returns true if +heading+ is below the display threshold + + def suppressed? heading + return false unless @omit_headings_below + + heading.level > @omit_headings_below + end + + # :stopdoc: + alias accept_block_quote ignore + alias accept_raw ignore + alias accept_rule ignore + alias accept_blank_line ignore + alias accept_paragraph ignore + alias accept_verbatim ignore + alias accept_list_end ignore + alias accept_list_item_start ignore + alias accept_list_item_end ignore + alias accept_list_end_bullet ignore + alias accept_list_start ignore + # :startdoc: + +end + diff --git a/lib/rdoc/markup/to_test.rb b/lib/rdoc/markup/to_test.rb new file mode 100644 index 0000000000..61d3cffaf0 --- /dev/null +++ b/lib/rdoc/markup/to_test.rb @@ -0,0 +1,70 @@ +# frozen_string_literal: true +## +# This Markup outputter is used for testing purposes. + +class RDoc::Markup::ToTest < RDoc::Markup::Formatter + + # :stopdoc: + + ## + # :section: Visitor + + def start_accepting + @res = [] + @list = [] + end + + def end_accepting + @res + end + + def accept_paragraph(paragraph) + @res << convert_flow(@am.flow(paragraph.text)) + end + + def accept_raw raw + @res << raw.parts.join + end + + def accept_verbatim(verbatim) + @res << verbatim.text.gsub(/^(\S)/, ' \1') + end + + def accept_list_start(list) + @list << case list.type + when :BULLET then + '*' + when :NUMBER then + '1' + else + list.type + end + end + + def accept_list_end(list) + @list.pop + end + + def accept_list_item_start(list_item) + @res << "#{' ' * (@list.size - 1)}#{@list.last}: " + end + + def accept_list_item_end(list_item) + end + + def accept_blank_line(blank_line) + @res << "\n" + end + + def accept_heading(heading) + @res << "#{'=' * heading.level} #{heading.text}" + end + + def accept_rule(rule) + @res << '-' * rule.weight + end + + # :startdoc: + +end + diff --git a/lib/rdoc/markup/to_tt_only.rb b/lib/rdoc/markup/to_tt_only.rb new file mode 100644 index 0000000000..4f43546e3d --- /dev/null +++ b/lib/rdoc/markup/to_tt_only.rb @@ -0,0 +1,121 @@ +# frozen_string_literal: true +## +# Extracts sections of text enclosed in plus, tt or code. Used to discover +# undocumented parameters. + +class RDoc::Markup::ToTtOnly < RDoc::Markup::Formatter + + ## + # Stack of list types + + attr_reader :list_type + + ## + # Output accumulator + + attr_reader :res + + ## + # Creates a new tt-only formatter. + + def initialize markup = nil + super nil, markup + + add_tag :TT, nil, nil + end + + ## + # Adds tts from +block_quote+ to the output + + def accept_block_quote block_quote + tt_sections block_quote.text + end + + ## + # Pops the list type for +list+ from #list_type + + def accept_list_end list + @list_type.pop + end + + ## + # Pushes the list type for +list+ onto #list_type + + def accept_list_start list + @list_type << list.type + end + + ## + # Prepares the visitor for consuming +list_item+ + + def accept_list_item_start list_item + case @list_type.last + when :NOTE, :LABEL then + Array(list_item.label).map do |label| + tt_sections label + end.flatten + end + end + + ## + # Adds +paragraph+ to the output + + def accept_paragraph paragraph + tt_sections(paragraph.text) + end + + ## + # Does nothing to +markup_item+ because it doesn't have any user-built + # content + + def do_nothing markup_item + end + + alias accept_blank_line do_nothing # :nodoc: + alias accept_heading do_nothing # :nodoc: + alias accept_list_item_end do_nothing # :nodoc: + alias accept_raw do_nothing # :nodoc: + alias accept_rule do_nothing # :nodoc: + alias accept_verbatim do_nothing # :nodoc: + + ## + # Extracts tt sections from +text+ + + def tt_sections text + flow = @am.flow text.dup + + flow.each do |item| + case item + when String then + @res << item if in_tt? + when RDoc::Markup::AttrChanger then + off_tags res, item + on_tags res, item + when RDoc::Markup::Special then + @res << convert_special(item) if in_tt? # TODO can this happen? + else + raise "Unknown flow element: #{item.inspect}" + end + end + + res + end + + ## + # Returns an Array of items that were wrapped in plus, tt or code. + + def end_accepting + @res.compact + end + + ## + # Prepares the visitor for gathering tt sections + + def start_accepting + @res = [] + + @list_type = [] + end + +end + diff --git a/lib/rdoc/markup/verbatim.rb b/lib/rdoc/markup/verbatim.rb new file mode 100644 index 0000000000..7f1bc29a09 --- /dev/null +++ b/lib/rdoc/markup/verbatim.rb @@ -0,0 +1,84 @@ +# frozen_string_literal: true +## +# A section of verbatim text + +class RDoc::Markup::Verbatim < RDoc::Markup::Raw + + ## + # Format of this verbatim section + + attr_accessor :format + + def initialize *parts # :nodoc: + super + + @format = nil + end + + def == other # :nodoc: + super and @format == other.format + end + + ## + # Calls #accept_verbatim on +visitor+ + + def accept visitor + visitor.accept_verbatim self + end + + ## + # Collapses 3+ newlines into two newlines + + def normalize + parts = [] + + newlines = 0 + + @parts.each do |part| + case part + when /^\s*\n/ then + newlines += 1 + parts << part if newlines == 1 + else + newlines = 0 + parts << part + end + end + + parts.pop if parts.last =~ /\A\r?\n\z/ + + @parts = parts + end + + def pretty_print q # :nodoc: + self.class.name =~ /.*::(\w{1,4})/i + + q.group 2, "[#{$1.downcase}: ", ']' do + if @format then + q.text "format: #{@format}" + q.breakable + end + + q.seplist @parts do |part| + q.pp part + end + end + end + + ## + # Is this verbatim section Ruby code? + + def ruby? + @format ||= nil # TODO for older ri data, switch the tree to marshal_dump + @format == :ruby + end + + ## + # The text of the section + + def text + @parts.join + end + +end + diff --git a/lib/rdoc/meta_method.rb b/lib/rdoc/meta_method.rb new file mode 100644 index 0000000000..7927a9ce9c --- /dev/null +++ b/lib/rdoc/meta_method.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true +## +# MetaMethod represents a meta-programmed method + +class RDoc::MetaMethod < RDoc::AnyMethod +end + diff --git a/lib/rdoc/method_attr.rb b/lib/rdoc/method_attr.rb new file mode 100644 index 0000000000..3cef78c4a5 --- /dev/null +++ b/lib/rdoc/method_attr.rb @@ -0,0 +1,419 @@ +# frozen_string_literal: true +## +# Abstract class representing either a method or an attribute. + +class RDoc::MethodAttr < RDoc::CodeObject + + include Comparable + + ## + # Name of this method/attribute. + + attr_accessor :name + + ## + # public, protected, private + + attr_accessor :visibility + + ## + # Is this a singleton method/attribute? + + attr_accessor :singleton + + ## + # Source file token stream + + attr_reader :text + + ## + # Array of other names for this method/attribute + + attr_reader :aliases + + ## + # The method/attribute we're aliasing + + attr_accessor :is_alias_for + + #-- + # The attributes below are for AnyMethod only. + # They are left here for the time being to + # allow ri to operate. + # TODO modify ri to avoid calling these on attributes. + #++ + + ## + # Parameters yielded by the called block + + attr_reader :block_params + + ## + # Parameters for this method + + attr_accessor :params + + ## + # Different ways to call this method + + attr_accessor :call_seq + + ## + # The call_seq or the param_seq with method name, if there is no call_seq. + + attr_reader :arglists + + ## + # Pretty parameter list for this method + + attr_reader :param_seq + + + ## + # Creates a new MethodAttr from token stream +text+ and method or attribute + # name +name+. + # + # Usually this is called by super from a subclass. + + def initialize text, name + super() + + @text = text + @name = name + + @aliases = [] + @is_alias_for = nil + @parent_name = nil + @singleton = nil + @visibility = :public + @see = false + + @arglists = nil + @block_params = nil + @call_seq = nil + @param_seq = nil + @params = nil + end + + ## + # Resets cached data for the object so it can be rebuilt by accessor methods + + def initialize_copy other # :nodoc: + @full_name = nil + end + + def initialize_visibility # :nodoc: + super + @see = nil + end + + ## + # Order by #singleton then #name + + def <=>(other) + return unless other.respond_to?(:singleton) && + other.respond_to?(:name) + + [ @singleton ? 0 : 1, name] <=> + [other.singleton ? 0 : 1, other.name] + end + + def == other # :nodoc: + equal?(other) or self.class == other.class and full_name == other.full_name + end + + ## + # A method/attribute is documented if any of the following is true: + # - it was marked with :nodoc:; + # - it has a comment; + # - it is an alias for a documented method; + # - it has a +#see+ method that is documented. + + def documented? + super or + (is_alias_for and is_alias_for.documented?) or + (see and see.documented?) + end + + ## + # A method/attribute to look at, + # in particular if this method/attribute has no documentation. + # + # It can be a method/attribute of the superclass or of an included module, + # including the Kernel module, which is always appended to the included + # modules. + # + # Returns +nil+ if there is no such method/attribute. + # The +#is_alias_for+ method/attribute, if any, is not included. + # + # Templates may generate a "see also ..." if this method/attribute + # has documentation, and "see ..." if it does not. + + def see + @see = find_see if @see == false + @see + end + + ## + # Sets the store for this class or module and its contained code objects. + + def store= store + super + + @file = @store.add_file @file.full_name if @file + end + + def find_see # :nodoc: + return nil if singleton || is_alias_for + + # look for the method + other = find_method_or_attribute name + return other if other + + # if it is a setter, look for a getter + return nil unless name =~ /[a-z_]=$/i # avoid == or === + return find_method_or_attribute name[0..-2] + end + + def find_method_or_attribute name # :nodoc: + return nil unless parent.respond_to? :ancestors + + searched = parent.ancestors + kernel = @store.modules_hash['Kernel'] + + searched << kernel if kernel && + parent != kernel && !searched.include?(kernel) + + searched.each do |ancestor| + next if String === ancestor + next if parent == ancestor + + other = ancestor.find_method_named('#' + name) || + ancestor.find_attribute_named(name) + + return other if other + end + + nil + end + + ## + # Abstract method. Contexts in their building phase call this + # to register a new alias for this known method/attribute. + # + # - creates a new AnyMethod/Attribute named <tt>an_alias.new_name</tt>; + # - adds +self+ as an alias for the new method or attribute + # - adds the method or attribute to #aliases + # - adds the method or attribute to +context+. + + def add_alias(an_alias, context) + raise NotImplementedError + end + + ## + # HTML fragment reference for this method + + def aref + type = singleton ? 'c' : 'i' + # % characters are not allowed in html names => dash instead + "#{aref_prefix}-#{type}-#{html_name}" + end + + ## + # Prefix for +aref+, defined by subclasses. + + def aref_prefix + raise NotImplementedError + end + + ## + # Attempts to sanitize the content passed by the Ruby parser: + # remove outer parentheses, etc. + + def block_params=(value) + # 'yield.to_s' or 'assert yield, msg' + return @block_params = '' if value =~ /^[\.,]/ + + # remove trailing 'if/unless ...' + return @block_params = '' if value =~ /^(if|unless)\s/ + + value = $1.strip if value =~ /^(.+)\s(if|unless)\s/ + + # outer parentheses + value = $1 if value =~ /^\s*\((.*)\)\s*$/ + value = value.strip + + # proc/lambda + return @block_params = $1 if value =~ /^(proc|lambda)(\s*\{|\sdo)/ + + # surrounding +...+ or [...] + value = $1.strip if value =~ /^\+(.*)\+$/ + value = $1.strip if value =~ /^\[(.*)\]$/ + + return @block_params = '' if value.empty? + + # global variable + return @block_params = 'str' if value =~ /^\$[&0-9]$/ + + # wipe out array/hash indices + value.gsub!(/(\w)\[[^\[]+\]/, '\1') + + # remove @ from class/instance variables + value.gsub!(/@@?([a-z0-9_]+)/, '\1') + + # method calls => method name + value.gsub!(/([A-Z:a-z0-9_]+)\.([a-z0-9_]+)(\s*\(\s*[a-z0-9_.,\s]*\s*\)\s*)?/) do + case $2 + when 'to_s' then $1 + when 'const_get' then 'const' + when 'new' then + $1.split('::').last. # ClassName => class_name + gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2'). + gsub(/([a-z\d])([A-Z])/,'\1_\2'). + downcase + else + $2 + end + end + + # class prefixes + value.gsub!(/[A-Za-z0-9_:]+::/, '') + + # simple expressions + value = $1 if value =~ /^([a-z0-9_]+)\s*[-*+\/]/ + + @block_params = value.strip + end + + ## + # HTML id-friendly method/attribute name + + def html_name + require 'cgi' + + CGI.escape(@name.gsub('-', '-2D')).gsub('%','-').sub(/^-/, '') + end + + ## + # Full method/attribute name including namespace + + def full_name + @full_name ||= "#{parent_name}#{pretty_name}" + end + + def inspect # :nodoc: + alias_for = @is_alias_for ? " (alias for #{@is_alias_for.name})" : nil + visibility = self.visibility + visibility = "forced #{visibility}" if force_documentation + "#<%s:0x%x %s (%s)%s>" % [ + self.class, object_id, + full_name, + visibility, + alias_for, + ] + end + + ## + # '::' for a class method/attribute, '#' for an instance method. + + def name_prefix + @singleton ? '::' : '#' + end + + ## + # Name for output to HTML. For class methods the full name with a "." is + # used like +SomeClass.method_name+. For instance methods the class name is + # used if +context+ does not match the parent. + # + # This is to help prevent people from using :: to call class methods. + + def output_name context + return "#{name_prefix}#{@name}" if context == parent + + "#{parent_name}#{@singleton ? '.' : '#'}#{@name}" + end + + ## + # Method/attribute name with class/instance indicator + + def pretty_name + "#{name_prefix}#{@name}" + end + + ## + # Type of method/attribute (class or instance) + + def type + singleton ? 'class' : 'instance' + end + + ## + # Path to this method for use with HTML generator output. + + def path + "#{@parent.path}##{aref}" + end + + ## + # Name of our parent with special handling for un-marshaled methods + + def parent_name + @parent_name || super + end + + def pretty_print q # :nodoc: + alias_for = + if @is_alias_for.respond_to? :name then + "alias for #{@is_alias_for.name}" + elsif Array === @is_alias_for then + "alias for #{@is_alias_for.last}" + end + + q.group 2, "[#{self.class.name} #{full_name} #{visibility}", "]" do + if alias_for then + q.breakable + q.text alias_for + end + + if text then + q.breakable + q.text "text:" + q.breakable + q.pp @text + end + + unless comment.empty? then + q.breakable + q.text "comment:" + q.breakable + q.pp @comment + end + end + end + + ## + # Used by RDoc::Generator::JsonIndex to create a record for the search + # engine. + + def search_record + [ + @name, + full_name, + @name, + @parent.full_name, + path, + params, + snippet(@comment), + ] + end + + def to_s # :nodoc: + if @is_alias_for + "#{self.class.name}: #{full_name} -> #{is_alias_for}" + else + "#{self.class.name}: #{full_name}" + end + end + +end + diff --git a/lib/rdoc/mixin.rb b/lib/rdoc/mixin.rb new file mode 100644 index 0000000000..379d7cc526 --- /dev/null +++ b/lib/rdoc/mixin.rb @@ -0,0 +1,121 @@ +# frozen_string_literal: true +## +# A Mixin adds features from a module into another context. RDoc::Include and +# RDoc::Extend are both mixins. + +class RDoc::Mixin < RDoc::CodeObject + + ## + # Name of included module + + attr_accessor :name + + ## + # Creates a new Mixin for +name+ with +comment+ + + def initialize(name, comment) + super() + @name = name + self.comment = comment + @module = nil # cache for module if found + end + + ## + # Mixins are sorted by name + + def <=> other + return unless self.class === other + + name <=> other.name + end + + def == other # :nodoc: + self.class === other and @name == other.name + end + + alias eql? == # :nodoc: + + ## + # Full name based on #module + + def full_name + m = self.module + RDoc::ClassModule === m ? m.full_name : @name + end + + def hash # :nodoc: + [@name, self.module].hash + end + + def inspect # :nodoc: + "#<%s:0x%x %s.%s %s>" % [ + self.class, + object_id, + parent_name, self.class.name.downcase, @name, + ] + end + + ## + # Attempts to locate the included module object. Returns the name if not + # known. + # + # The scoping rules of Ruby to resolve the name of an included module are: + # - first look into the children of the current context; + # - if not found, look into the children of included modules, + # in reverse inclusion order; + # - if still not found, go up the hierarchy of names. + # + # This method has <code>O(n!)</code> behavior when the module calling + # include is referencing nonexistent modules. Avoid calling #module until + # after all the files are parsed. This behavior is due to ruby's constant + # lookup behavior. + # + # As of the beginning of October, 2011, no gem includes nonexistent modules. + + def module + return @module if @module + + # search the current context + return @name unless parent + full_name = parent.child_name(@name) + @module = @store.modules_hash[full_name] + return @module if @module + return @name if @name =~ /^::/ + + # search the includes before this one, in reverse order + searched = parent.includes.take_while { |i| i != self }.reverse + searched.each do |i| + inc = i.module + next if String === inc + full_name = inc.child_name(@name) + @module = @store.modules_hash[full_name] + return @module if @module + end + + # go up the hierarchy of names + up = parent.parent + while up + full_name = up.child_name(@name) + @module = @store.modules_hash[full_name] + return @module if @module + up = up.parent + end + + @name + end + + ## + # Sets the store for this class or module and its contained code objects. + + def store= store + super + + @file = @store.add_file @file.full_name if @file + end + + def to_s # :nodoc: + "#{self.class.name.downcase} #@name in: #{parent}" + end + +end + diff --git a/lib/rdoc/normal_class.rb b/lib/rdoc/normal_class.rb new file mode 100644 index 0000000000..6729b18448 --- /dev/null +++ b/lib/rdoc/normal_class.rb @@ -0,0 +1,93 @@ +# frozen_string_literal: true +## +# A normal class, neither singleton nor anonymous + +class RDoc::NormalClass < RDoc::ClassModule + + ## + # The ancestors of this class including modules. Unlike Module#ancestors, + # this class is not included in the result. The result will contain both + # RDoc::ClassModules and Strings. + + def ancestors + if String === superclass then + super << superclass + elsif superclass then + ancestors = super + ancestors << superclass + ancestors.concat superclass.ancestors + else + super + end + end + + def aref_prefix # :nodoc: + 'class' + end + + ## + # The definition of this class, <tt>class MyClassName</tt> + + def definition + "class #{full_name}" + end + + def direct_ancestors + superclass ? super + [superclass] : super + end + + def inspect # :nodoc: + superclass = @superclass ? " < #{@superclass}" : nil + "<%s:0x%x class %s%s includes: %p extends: %p attributes: %p methods: %p aliases: %p>" % [ + self.class, object_id, + full_name, superclass, @includes, @extends, @attributes, @method_list, @aliases + ] + end + + def to_s # :nodoc: + display = "#{self.class.name} #{self.full_name}" + if superclass + display += ' < ' + (superclass.is_a?(String) ? superclass : superclass.full_name) + end + display += ' -> ' + is_alias_for.to_s if is_alias_for + display + end + + def pretty_print q # :nodoc: + superclass = @superclass ? " < #{@superclass}" : nil + + q.group 2, "[class #{full_name}#{superclass} ", "]" do + q.breakable + q.text "includes:" + q.breakable + q.seplist @includes do |inc| q.pp inc end + + q.breakable + q.text "constants:" + q.breakable + q.seplist @constants do |const| q.pp const end + + q.breakable + q.text "attributes:" + q.breakable + q.seplist @attributes do |attr| q.pp attr end + + q.breakable + q.text "methods:" + q.breakable + q.seplist @method_list do |meth| q.pp meth end + + q.breakable + q.text "aliases:" + q.breakable + q.seplist @aliases do |aliaz| q.pp aliaz end + + q.breakable + q.text "comment:" + q.breakable + q.pp comment + end + end + +end + diff --git a/lib/rdoc/normal_module.rb b/lib/rdoc/normal_module.rb new file mode 100644 index 0000000000..8f364be41c --- /dev/null +++ b/lib/rdoc/normal_module.rb @@ -0,0 +1,74 @@ +# frozen_string_literal: true +## +# A normal module, like NormalClass + +class RDoc::NormalModule < RDoc::ClassModule + + def aref_prefix # :nodoc: + 'module' + end + + def inspect # :nodoc: + "#<%s:0x%x module %s includes: %p extends: %p attributes: %p methods: %p aliases: %p>" % [ + self.class, object_id, + full_name, @includes, @extends, @attributes, @method_list, @aliases + ] + end + + ## + # The definition of this module, <tt>module MyModuleName</tt> + + def definition + "module #{full_name}" + end + + ## + # This is a module, returns true + + def module? + true + end + + def pretty_print q # :nodoc: + q.group 2, "[module #{full_name}: ", "]" do + q.breakable + q.text "includes:" + q.breakable + q.seplist @includes do |inc| q.pp inc end + q.breakable + + q.breakable + q.text "constants:" + q.breakable + q.seplist @constants do |const| q.pp const end + + q.text "attributes:" + q.breakable + q.seplist @attributes do |attr| q.pp attr end + q.breakable + + q.text "methods:" + q.breakable + q.seplist @method_list do |meth| q.pp meth end + q.breakable + + q.text "aliases:" + q.breakable + q.seplist @aliases do |aliaz| q.pp aliaz end + q.breakable + + q.text "comment:" + q.breakable + q.pp comment + end + end + + ## + # Modules don't have one, raises NoMethodError + + def superclass + raise NoMethodError, "#{full_name} is a module" + end + +end + diff --git a/lib/rdoc/options.rb b/lib/rdoc/options.rb index 31441793c4..17bbca81fe 100644 --- a/lib/rdoc/options.rb +++ b/lib/rdoc/options.rb @@ -1,575 +1,1227 @@ -# We handle the parsing of options, and subsequently as a singleton -# object to be queried for option values +# frozen_string_literal: true +require 'optparse' +require 'pathname' + +## +# RDoc::Options handles the parsing and storage of options +# +# == Saved Options +# +# You can save some options like the markup format in the +# <tt>.rdoc_options</tt> file in your gem. The easiest way to do this is: +# +# rdoc --markup tomdoc --write-options +# +# Which will automatically create the file and fill it with the options you +# specified. +# +# The following options will not be saved since they interfere with the user's +# preferences or with the normal operation of RDoc: +# +# * +--coverage-report+ +# * +--dry-run+ +# * +--encoding+ +# * +--force-update+ +# * +--format+ +# * +--pipe+ +# * +--quiet+ +# * +--template+ +# * +--verbose+ +# +# == Custom Options +# +# Generators can hook into RDoc::Options to add generator-specific command +# line options. +# +# When <tt>--format</tt> is encountered in ARGV, RDoc calls ::setup_options on +# the generator class to add extra options to the option parser. Options for +# custom generators must occur after <tt>--format</tt>. <tt>rdoc --help</tt> +# will list options for all installed generators. +# +# Example: +# +# class RDoc::Generator::Spellcheck +# RDoc::RDoc.add_generator self +# +# def self.setup_options rdoc_options +# op = rdoc_options.option_parser +# +# op.on('--spell-dictionary DICTIONARY', +# RDoc::Options::Path) do |dictionary| +# rdoc_options.spell_dictionary = dictionary +# end +# end +# end +# +# Of course, RDoc::Options does not respond to +spell_dictionary+ by default +# so you will need to add it: +# +# class RDoc::Options +# +# ## +# # The spell dictionary used by the spell-checking plugin. +# +# attr_accessor :spell_dictionary +# +# end +# +# == Option Validators +# +# OptionParser validators will validate and cast user input values. In +# addition to the validators that ship with OptionParser (String, Integer, +# Float, TrueClass, FalseClass, Array, Regexp, Date, Time, URI, etc.), +# RDoc::Options adds Path, PathArray and Template. + +class RDoc::Options + + ## + # The deprecated options. + + DEPRECATED = { + '--accessor' => 'support discontinued', + '--diagram' => 'support discontinued', + '--help-output' => 'support discontinued', + '--image-format' => 'was an option for --diagram', + '--inline-source' => 'source code is now always inlined', + '--merge' => 'ri now always merges class information', + '--one-file' => 'support discontinued', + '--op-name' => 'support discontinued', + '--opname' => 'support discontinued', + '--promiscuous' => 'files always only document their content', + '--ri-system' => 'Ruby installers use other techniques', + } + + ## + # RDoc options ignored (or handled specially) by --write-options + + SPECIAL = %w[ + coverage_report + dry_run + encoding + files + force_output + force_update + generator + generator_name + generator_options + generators + op_dir + option_parser + pipe + rdoc_include + root + static_path + stylesheet_url + template + template_dir + update_output_dir + verbosity + write_options + ] + + ## + # Option validator for OptionParser that matches a directory that exists on + # the filesystem. + + Directory = Object.new + + ## + # Option validator for OptionParser that matches a file or directory that + # exists on the filesystem. + + Path = Object.new + + ## + # Option validator for OptionParser that matches a comma-separated list of + # files or directories that exist on the filesystem. + + PathArray = Object.new + + ## + # Option validator for OptionParser that matches a template directory for an + # installed generator that lives in + # <tt>"rdoc/generator/template/#{template_name}"</tt> + + Template = Object.new + + ## + # Character-set for HTML output. #encoding is preferred over #charset + + attr_accessor :charset + + ## + # If true, RDoc will not write any files. + + attr_accessor :dry_run + + ## + # The output encoding. All input files will be transcoded to this encoding. + # + # The default encoding is UTF-8. This is set via --encoding. + + attr_accessor :encoding + + ## + # Files matching this pattern will be excluded -require "rdoc/ri/ri_paths" + attr_accessor :exclude -class Options + ## + # The list of files to be processed - require 'singleton' - require 'getoptlong' + attr_accessor :files - include Singleton + ## + # Create the output even if the output directory does not look + # like an rdoc output directory - # files matching this pattern will be excluded - attr_accessor :exclude + attr_accessor :force_output - # the name of the output directory - attr_accessor :op_dir - - # the name to use for the output - attr_reader :op_name - - # include private and protected methods in the - # output - attr_accessor :show_all - - # name of the file, class or module to display in - # the initial index page (if not specified - # the first file we encounter is used) - attr_accessor :main_page + ## + # Scan newer sources than the flag file if true. - # merge into classes of the name name when generating ri - attr_reader :merge + attr_accessor :force_update - # Don't display progress as we process the files - attr_reader :quiet + ## + # Formatter to mark up text with + + attr_accessor :formatter + + ## + # Description of the output generator (set with the <tt>--format</tt> option) - # description of the output generator (set with the <tt>-fmt</tt> - # option attr_accessor :generator - # and the list of files to be processed - attr_reader :files + ## + # For #== + + attr_reader :generator_name # :nodoc: + + ## + # Loaded generator options. Used to prevent --help from loading the same + # options multiple times. + + attr_accessor :generator_options + + ## + # Old rdoc behavior: hyperlink all words that match a method name, + # even if not preceded by '#' or '::' + + attr_accessor :hyperlink_all + + ## + # Include line numbers in the source code + + attr_accessor :line_numbers + + ## + # The output locale. + + attr_accessor :locale + + ## + # The directory where locale data live. + + attr_accessor :locale_dir + + ## + # Name of the file, class or module to display in the initial index page (if + # not specified the first file we encounter is used) + + attr_accessor :main_page + + ## + # The default markup format. The default is 'rdoc'. 'markdown', 'tomdoc' + # and 'rd' are also built-in. + + attr_accessor :markup + + ## + # If true, only report on undocumented files + + attr_accessor :coverage_report + + ## + # The name of the output directory + + attr_accessor :op_dir + + ## + # The OptionParser for this instance + + attr_accessor :option_parser + + ## + # Output heading decorations? + attr_accessor :output_decoration + + ## + # Directory where guides, FAQ, and other pages not associated with a class + # live. You may leave this unset if these are at the root of your project. + + attr_accessor :page_dir + + ## + # Is RDoc in pipe mode? + + attr_accessor :pipe + + ## + # Array of directories to search for files to satisfy an :include: + + attr_accessor :rdoc_include + + ## + # Root of the source documentation will be generated for. Set this when + # building documentation outside the source directory. Defaults to the + # current directory. - # array of directories to search for files to satisfy an :include: - attr_reader :rdoc_include + attr_accessor :root - # title to be used out the output - #attr_writer :title + ## + # Include the '#' at the front of hyperlinked instance method names - # template to be used when generating output - attr_reader :template + attr_accessor :show_hash - # should diagrams be drawn - attr_reader :diagram + ## + # Directory to copy static files from - # should we draw fileboxes in diagrams - attr_reader :fileboxes + attr_accessor :static_path - # include the '#' at the front of hyperlinked instance method names - attr_reader :show_hash + ## + # The number of columns in a tab - # image format for diagrams - attr_reader :image_format + attr_accessor :tab_width - # character-set - attr_reader :charset + ## + # Template to be used when generating output - # should source code be included inline, or displayed in a popup - attr_reader :inline_source + attr_accessor :template - # should the output be placed into a single file - attr_reader :all_one_file + ## + # Directory the template lives in - # the number of columns in a tab - attr_reader :tab_width + attr_accessor :template_dir - # include line numbers in the source listings - attr_reader :include_line_numbers + ## + # Additional template stylesheets - # pattern for additional attr_... style methods - attr_reader :extra_accessors - attr_reader :extra_accessor_flags + attr_accessor :template_stylesheets - # URL of stylesheet - attr_reader :css + ## + # Documentation title + attr_accessor :title + + ## + # Should RDoc update the timestamps in the output dir? + + attr_accessor :update_output_dir + + ## + # Verbosity, zero means quiet + + attr_accessor :verbosity + + ## # URL of web cvs frontend - attr_reader :webcvs - - # Are we promiscuous about showing module contents across - # multiple files - attr_reader :promiscuous - - module OptionList - - OPTION_LIST = [ - [ "--accessor", "-A", "accessorname[,..]", - "comma separated list of additional class methods\n" + - "that should be treated like 'attr_reader' and\n" + - "friends. Option may be repeated. Each accessorname\n" + - "may have '=text' appended, in which case that text\n" + - "appears where the r/w/rw appears for normal accessors."], - - [ "--all", "-a", nil, - "include all methods (not just public)\nin the output" ], - - [ "--charset", "-c", "charset", - "specifies HTML character-set" ], - - [ "--debug", "-D", nil, - "displays lots on internal stuff" ], - - [ "--diagram", "-d", nil, - "Generate diagrams showing modules and classes.\n" + - "You need dot V1.8.6 or later to use the --diagram\n" + - "option correctly. Dot is available from\n"+ - "http://www.research.att.com/sw/tools/graphviz/" ], - - [ "--exclude", "-x", "pattern", - "do not process files or directories matching\n" + - "pattern. Files given explicitly on the command\n" + - "line will never be excluded." ], - - [ "--extension", "-E", "new=old", - "Treat files ending with .new as if they ended with\n" + - ".old. Using '-E cgi=rb' will cause xxx.cgi to be\n" + - "parsed as a Ruby file"], - - [ "--fileboxes", "-F", nil, - "classes are put in boxes which represents\n" + - "files, where these classes reside. Classes\n" + - "shared between more than one file are\n" + - "shown with list of files that sharing them.\n" + - "Silently discarded if --diagram is not given\n" + - "Experimental." ], - - [ "--fmt", "-f", "format name", - "set the output formatter (see below)" ], - - [ "--help", "-h", nil, - "you're looking at it" ], - - [ "--help-output", "-O", nil, - "explain the various output options" ], - - [ "--image-format", "-I", "gif/png/jpg/jpeg", - "Sets output image format for diagrams. Can\n" + - "be png, gif, jpeg, jpg. If this option is\n" + - "omitted, png is used. Requires --diagram." ], - - [ "--include", "-i", "dir[,dir...]", - "set (or add to) the list of directories\n" + - "to be searched when satisfying :include:\n" + - "requests. Can be used more than once." ], - - [ "--inline-source", "-S", nil, - "Show method source code inline, rather\n" + - "than via a popup link" ], - - [ "--line-numbers", "-N", nil, - "Include line numbers in the source code" ], - - [ "--main", "-m", "name", - "'name' will be the initial page displayed" ], - - [ "--merge", "-M", nil, - "when creating ri output, merge processed classes\n" + - "into previously documented classes of the name name"], - - [ "--one-file", "-1", nil, - "put all the output into a single file" ], - - [ "--op", "-o", "dir", - "set the output directory" ], - - [ "--opname", "-n", "name", - "Set the 'name' of the output. Has no\n" + - "effect for HTML." ], - - [ "--promiscuous", "-p", nil, - "When documenting a file that contains a module\n" + - "or class also defined in other files, show\n" + - "all stuff for that module/class in each files\n" + - "page. By default, only show stuff defined in\n" + - "that particular file." ], - - [ "--quiet", "-q", nil, - "don't show progress as we parse" ], - - [ "--ri", "-r", nil, - "generate output for use by 'ri.' The files are\n" + - "stored in the '.rdoc' directory under your home\n"+ - "directory unless overridden by a subsequent\n" + - "--op parameter, so no special privileges are needed." ], - - [ "--ri-site", "-R", nil, - "generate output for use by 'ri.' The files are\n" + - "stored in a site-wide directory, making them accessible\n"+ - "to others, so special privileges are needed." ], - - [ "--ri-system", "-Y", nil, - "generate output for use by 'ri.' The files are\n" + - "stored in a system-level directory, making them accessible\n"+ - "to others, so special privileges are needed. This option\n"+ - "is intended to be used during Ruby installations" ], - - [ "--show-hash", "-H", nil, - "A name of the form #name in a comment\n" + - "is a possible hyperlink to an instance\n" + - "method name. When displayed, the '#' is\n" + - "removed unless this option is specified" ], - - [ "--style", "-s", "stylesheet url", - "specifies the URL of a separate stylesheet." ], - - [ "--tab-width", "-w", "n", - "Set the width of tab characters (default 8)"], - - [ "--template", "-T", "template name", - "Set the template used when generating output" ], - - [ "--title", "-t", "text", - "Set 'txt' as the title for the output" ], - - [ "--version", "-v", nil, - "display RDoc's version" ], - - [ "--webcvs", "-W", "url", - "Specify a URL for linking to a web frontend\n" + - "to CVS. If the URL contains a '\%s', the\n" + - "name of the current file will be substituted;\n" + - "if the URL doesn't contain a '\%s', the\n" + - "filename will be appended to it." ], - ] - - def OptionList.options - OPTION_LIST.map do |long, short, arg,| - [ long, - short, - arg ? GetoptLong::REQUIRED_ARGUMENT : GetoptLong::NO_ARGUMENT - ] + + attr_accessor :webcvs + + ## + # Minimum visibility of a documented method. One of +:public+, +:protected+, + # +:private+ or +:nodoc+. + # + # The +:nodoc+ visibility ignores all directives related to visibility. The + # other visibilities may be overridden on a per-method basis with the :doc: + # directive. + + attr_reader :visibility + + def initialize # :nodoc: + init_ivars + end + + def init_ivars # :nodoc: + @dry_run = false + @exclude = [] + @files = nil + @force_output = false + @force_update = true + @generator = nil + @generator_name = nil + @generator_options = [] + @generators = RDoc::RDoc::GENERATORS + @hyperlink_all = false + @line_numbers = false + @locale = nil + @locale_name = nil + @locale_dir = 'locale' + @main_page = nil + @markup = 'rdoc' + @coverage_report = false + @op_dir = nil + @page_dir = nil + @pipe = false + @output_decoration = true + @rdoc_include = [] + @root = Pathname(Dir.pwd) + @show_hash = false + @static_path = [] + @stylesheet_url = nil # TODO remove in RDoc 4 + @tab_width = 8 + @template = nil + @template_dir = nil + @template_stylesheets = [] + @title = nil + @update_output_dir = true + @verbosity = 1 + @visibility = :protected + @webcvs = nil + @write_options = false + @encoding = Encoding::UTF_8 + @charset = @encoding.name + end + + def init_with map # :nodoc: + init_ivars + + encoding = map['encoding'] + @encoding = encoding ? Encoding.find(encoding) : encoding + + @charset = map['charset'] + @exclude = map['exclude'] + @generator_name = map['generator_name'] + @hyperlink_all = map['hyperlink_all'] + @line_numbers = map['line_numbers'] + @locale_name = map['locale_name'] + @locale_dir = map['locale_dir'] + @main_page = map['main_page'] + @markup = map['markup'] + @op_dir = map['op_dir'] + @show_hash = map['show_hash'] + @tab_width = map['tab_width'] + @template_dir = map['template_dir'] + @title = map['title'] + @visibility = map['visibility'] + @webcvs = map['webcvs'] + + @rdoc_include = sanitize_path map['rdoc_include'] + @static_path = sanitize_path map['static_path'] + end + + def yaml_initialize tag, map # :nodoc: + init_with map + end + + def == other # :nodoc: + self.class === other and + @encoding == other.encoding and + @generator_name == other.generator_name and + @hyperlink_all == other.hyperlink_all and + @line_numbers == other.line_numbers and + @locale == other.locale and + @locale_dir == other.locale_dir and + @main_page == other.main_page and + @markup == other.markup and + @op_dir == other.op_dir and + @rdoc_include == other.rdoc_include and + @show_hash == other.show_hash and + @static_path == other.static_path and + @tab_width == other.tab_width and + @template == other.template and + @title == other.title and + @visibility == other.visibility and + @webcvs == other.webcvs + end + + ## + # Check that the files on the command line exist + + def check_files + @files.delete_if do |file| + if File.exist? file then + if File.readable? file then + false + else + warn "file '#{file}' not readable" + + true + end + else + warn "file '#{file}' not found" + + true end end + end + + ## + # Ensure only one generator is loaded + + def check_generator + if @generator then + raise OptionParser::InvalidOption, + "generator already set to #{@generator_name}" + end + end + + ## + # Set the title, but only if not already set. Used to set the title + # from a source file, so that a title set from the command line + # will have the priority. + + def default_title=(string) + @title ||= string + end + + ## + # For dumping YAML + + def encode_with coder # :nodoc: + encoding = @encoding ? @encoding.name : nil + + coder.add 'encoding', encoding + coder.add 'static_path', sanitize_path(@static_path) + coder.add 'rdoc_include', sanitize_path(@rdoc_include) + + ivars = instance_variables.map { |ivar| ivar.to_s[1..-1] } + ivars -= SPECIAL + + ivars.sort.each do |ivar| + coder.add ivar, instance_variable_get("@#{ivar}") + end + end + ## + # Completes any unfinished option setup business such as filtering for + # existent files, creating a regexp for #exclude and setting a default + # #template. - def OptionList.strip_output(text) - text =~ /^\s+/ - leading_spaces = $& - text.gsub!(/^#{leading_spaces}/, '') - $stdout.puts text + def finish + @op_dir ||= 'doc' + + @rdoc_include << "." if @rdoc_include.empty? + root = @root.to_s + @rdoc_include << root unless @rdoc_include.include?(root) + + if @exclude.nil? or Regexp === @exclude then + # done, #finish is being re-run + elsif @exclude.empty? then + @exclude = nil + else + @exclude = Regexp.new(@exclude.join("|")) end + finish_page_dir + + check_files + + # If no template was specified, use the default template for the output + # formatter + + unless @template then + @template = @generator_name + @template_dir = template_dir_for @template + end + + if @locale_name + @locale = RDoc::I18n::Locale[@locale_name] + @locale.load(@locale_dir) + else + @locale = nil + end + + self + end + + ## + # Fixes the page_dir to be relative to the root_dir and adds the page_dir to + # the files list. + + def finish_page_dir + return unless @page_dir + + @files << @page_dir.to_s + + page_dir = @page_dir.expand_path.relative_path_from @root - # Show an error and exit + @page_dir = page_dir + end + + ## + # Returns a properly-space list of generators and their descriptions. + + def generator_descriptions + lengths = [] + + generators = RDoc::RDoc::GENERATORS.map do |name, generator| + lengths << name.length + + description = generator::DESCRIPTION if + generator.const_defined? :DESCRIPTION - def OptionList.error(msg) - $stderr.puts - $stderr.puts msg - $stderr.puts "\nFor help on options, try 'rdoc --help'\n\n" - exit 1 + [name, description] end - # Show usage and exit - - def OptionList.usage(generator_names) - - puts - puts(VERSION_STRING) - puts - - name = File.basename($0) - OptionList.strip_output(<<-EOT) - Usage: - - #{name} [options] [names...] - - 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. - - Options: - - EOT - - OPTION_LIST.each do |long, short, arg, desc| - opt = sprintf("%20s", "#{long}, #{short}") - oparg = sprintf("%-7s", arg) - print "#{opt} #{oparg}" - desc = desc.split("\n") - if arg.nil? || arg.length < 7 - puts desc.shift + longest = lengths.max + + generators.sort.map do |name, description| + if description then + " %-*s - %s" % [longest, name, description] + else + " #{name}" + end + end.join "\n" + end + + ## + # Parses command line options. + + def parse argv + ignore_invalid = true + + argv.insert(0, *ENV['RDOCOPT'].split) if ENV['RDOCOPT'] + + opts = OptionParser.new do |opt| + @option_parser = opt + opt.program_name = File.basename $0 + opt.version = RDoc::VERSION + opt.release = nil + opt.summary_indent = ' ' * 4 + opt.banner = <<-EOF +Usage: #{opt.program_name} [options] [names...] + + 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. + + How RDoc generates output depends on the output formatter being used, and on + the options you give. + + Options can be specified via the RDOCOPT environment variable, which + functions similar to the RUBYOPT environment variable for ruby. + + $ export RDOCOPT="--show-hash" + + will make rdoc show hashes in method links by default. Command-line options + always will override those in RDOCOPT. + + Available formatters: + +#{generator_descriptions} + + RDoc understands the following file formats: + + EOF + + parsers = Hash.new { |h,parser| h[parser] = [] } + + RDoc::Parser.parsers.each do |regexp, parser| + parsers[parser.name.sub('RDoc::Parser::', '')] << regexp.source + end + + parsers.sort.each do |parser, regexp| + opt.banner += " - #{parser}: #{regexp.join ', '}\n" + end + opt.banner += " - TomDoc: Only in ruby files\n" + + opt.banner += "\n The following options are deprecated:\n\n" + + name_length = DEPRECATED.keys.sort_by { |k| k.length }.last.length + + DEPRECATED.sort_by { |k,| k }.each do |name, reason| + opt.banner += " %*1$2$s %3$s\n" % [-name_length, name, reason] + end + + opt.accept Template do |template| + template_dir = template_dir_for template + + unless template_dir then + $stderr.puts "could not find template #{template}" + nil else - puts + [template, template_dir] end - desc.each do |line| - puts(" "*28 + line) + end + + opt.accept Directory do |directory| + directory = File.expand_path directory + + raise OptionParser::InvalidArgument unless File.directory? directory + + directory + end + + opt.accept Path do |path| + path = File.expand_path path + + raise OptionParser::InvalidArgument unless File.exist? path + + path + end + + opt.accept PathArray do |paths,| + paths = if paths then + paths.split(',').map { |d| d unless d.empty? } + end + + paths.map do |path| + path = File.expand_path path + + raise OptionParser::InvalidArgument unless File.exist? path + + path end - puts end - puts "\nAvailable output formatters: " + - generator_names.sort.join(', ') + "\n\n" + opt.separator nil + opt.separator "Parsing options:" + opt.separator nil - puts "For information on where the output goes, use\n\n" - puts " rdoc --help-output\n\n" + opt.on("--encoding=ENCODING", "-e", Encoding.list.map { |e| e.name }, + "Specifies the output encoding. All files", + "read will be converted to this encoding.", + "The default encoding is UTF-8.", + "--encoding is preferred over --charset") do |value| + @encoding = Encoding.find value + @charset = @encoding.name # may not be valid value + end - exit 0 - end + opt.separator nil - def OptionList.help_output - OptionList.strip_output(<<-EOT) - How RDoc generates output depends on the output formatter being - used, and on the options you give. + opt.on("--locale=NAME", + "Specifies the output locale.") do |value| + @locale_name = value + end - - HTML output is normally produced into a number of separate files - (one per class, module, and file, along with various indices). - These files will appear in the directory given by the --op - option (doc/ by default). + opt.on("--locale-data-dir=DIR", + "Specifies the directory where locale data live.") do |value| + @locale_dir = value + end + + opt.separator nil - - XML output by default is written to standard output. If a - --opname option is given, the output will instead be written - to a file with that name in the output directory. + opt.on("--all", "-a", + "Synonym for --visibility=private.") do |value| + @visibility = :private + end - - .chm files (Windows help files) are written in the --op directory. - If an --opname parameter is present, that name is used, otherwise - the file will be called rdoc.chm. + opt.separator nil + + opt.on("--exclude=PATTERN", "-x", Regexp, + "Do not process files or directories", + "matching PATTERN.") do |value| + @exclude << value + end + + opt.separator nil + + opt.on("--extension=NEW=OLD", "-E", + "Treat files ending with .new as if they", + "ended with .old. Using '-E cgi=rb' will", + "cause xxx.cgi to be parsed as a Ruby file.") do |value| + new, old = value.split(/=/, 2) + + unless new and old then + raise OptionParser::InvalidArgument, "Invalid parameter to '-E'" + end - For information on other RDoc options, use "rdoc --help". - EOT - exit 0 + unless RDoc::Parser.alias_extension old, new then + raise OptionParser::InvalidArgument, "Unknown extension .#{old} to -E" + end + end + + opt.separator nil + + opt.on("--[no-]force-update", "-U", + "Forces rdoc to scan all sources even if", + "newer than the flag file.") do |value| + @force_update = value + end + + opt.separator nil + + opt.on("--pipe", "-p", + "Convert RDoc on stdin to HTML") do + @pipe = true + end + + opt.separator nil + + opt.on("--tab-width=WIDTH", "-w", Integer, + "Set the width of tab characters.") do |value| + raise OptionParser::InvalidArgument, + "#{value} is an invalid tab width" if value <= 0 + @tab_width = value + end + + opt.separator nil + + opt.on("--visibility=VISIBILITY", "-V", RDoc::VISIBILITIES + [:nodoc], + "Minimum visibility to document a method.", + "One of 'public', 'protected' (the default),", + "'private' or 'nodoc' (show everything)") do |value| + @visibility = value + end + + opt.separator nil + + markup_formats = RDoc::Text::MARKUP_FORMAT.keys.sort + + opt.on("--markup=MARKUP", markup_formats, + "The markup format for the named files.", + "The default is rdoc. Valid values are:", + markup_formats.join(', ')) do |value| + @markup = value + end + + opt.separator nil + + opt.on("--root=ROOT", Directory, + "Root of the source tree documentation", + "will be generated for. Set this when", + "building documentation outside the", + "source directory. Default is the", + "current directory.") do |root| + @root = Pathname(root) + end + + opt.separator nil + + opt.on("--page-dir=DIR", Directory, + "Directory where guides, your FAQ or", + "other pages not associated with a class", + "live. Set this when you don't store", + "such files at your project root.", + "NOTE: Do not use the same file name in", + "the page dir and the root of your project") do |page_dir| + @page_dir = Pathname(page_dir) + end + + opt.separator nil + opt.separator "Common generator options:" + opt.separator nil + + opt.on("--force-output", "-O", + "Forces rdoc to write the output files,", + "even if the output directory exists", + "and does not seem to have been created", + "by rdoc.") do |value| + @force_output = value + end + + opt.separator nil + + generator_text = @generators.keys.map { |name| " #{name}" }.sort + + opt.on("-f", "--fmt=FORMAT", "--format=FORMAT", @generators.keys, + "Set the output formatter. One of:", *generator_text) do |value| + check_generator + + @generator_name = value.downcase + setup_generator + end + + opt.separator nil + + opt.on("--include=DIRECTORIES", "-i", PathArray, + "Set (or add to) the list of directories to", + "be searched when satisfying :include:", + "requests. Can be used more than once.") do |value| + @rdoc_include.concat value.map { |dir| dir.strip } + end + + opt.separator nil + + opt.on("--[no-]coverage-report=[LEVEL]", "--[no-]dcov", "-C", Integer, + "Prints a report on undocumented items.", + "Does not generate files.") do |value| + value = 0 if value.nil? # Integer converts -C to nil + + @coverage_report = value + @force_update = true if value + end + + opt.separator nil + + opt.on("--output=DIR", "--op", "-o", + "Set the output directory.") do |value| + @op_dir = value + end + + opt.separator nil + + opt.on("-d", + "Deprecated --diagram option.", + "Prevents firing debug mode", + "with legacy invocation.") do |value| + end + + opt.separator nil + opt.separator 'HTML generator options:' + opt.separator nil + + opt.on("--charset=CHARSET", "-c", + "Specifies the output HTML character-set.", + "Use --encoding instead of --charset if", + "available.") do |value| + @charset = value + end + + opt.separator nil + + opt.on("--hyperlink-all", "-A", + "Generate hyperlinks for all words that", + "correspond to known methods, even if they", + "do not start with '#' or '::' (legacy", + "behavior).") do |value| + @hyperlink_all = value + end + + opt.separator nil + + opt.on("--main=NAME", "-m", + "NAME will be the initial page displayed.") do |value| + @main_page = value + end + + opt.separator nil + + opt.on("--[no-]line-numbers", "-N", + "Include line numbers in the source code.", + "By default, only the number of the first", + "line is displayed, in a leading comment.") do |value| + @line_numbers = value + end + + opt.separator nil + + opt.on("--show-hash", "-H", + "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.") do |value| + @show_hash = value + end + + opt.separator nil + + opt.on("--template=NAME", "-T", Template, + "Set the template used when generating", + "output. The default depends on the", + "formatter used.") do |(template, template_dir)| + @template = template + @template_dir = template_dir + end + + opt.separator nil + + opt.on("--template-stylesheets=FILES", PathArray, + "Set (or add to) the list of files to", + "include with the html template.") do |value| + @template_stylesheets << value + end + + opt.separator nil + + opt.on("--title=TITLE", "-t", + "Set TITLE as the title for HTML output.") do |value| + @title = value + end + + opt.separator nil + + opt.on("--copy-files=PATH", Path, + "Specify a file or directory to copy static", + "files from.", + "If a file is given it will be copied into", + "the output dir. If a directory is given the", + "entire directory will be copied.", + "You can use this multiple times") do |value| + @static_path << value + end + + opt.separator nil + + opt.on("--webcvs=URL", "-W", + "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.") do |value| + @webcvs = value + end + + opt.separator nil + opt.separator "ri generator options:" + opt.separator nil + + opt.on("--ri", "-r", + "Generate output for use by `ri`. The files", + "are stored in the '.rdoc' directory under", + "your home directory unless overridden by a", + "subsequent --op parameter, so no special", + "privileges are needed.") do |value| + check_generator + + @generator_name = "ri" + @op_dir ||= RDoc::RI::Paths::HOMEDIR + setup_generator + end + + opt.separator nil + + opt.on("--ri-site", "-R", + "Generate output for use by `ri`. The files", + "are stored in a site-wide directory,", + "making them accessible to others, so", + "special privileges are needed.") do |value| + check_generator + + @generator_name = "ri" + @op_dir = RDoc::RI::Paths.site_dir + setup_generator + end + + opt.separator nil + opt.separator "Generic options:" + opt.separator nil + + opt.on("--write-options", + "Write .rdoc_options to the current", + "directory with the given options. Not all", + "options will be used. See RDoc::Options", + "for details.") do |value| + @write_options = true + end + + opt.separator nil + + opt.on("--[no-]dry-run", + "Don't write any files") do |value| + @dry_run = value + end + + opt.separator nil + + opt.on("-D", "--[no-]debug", + "Displays lots on internal stuff.") do |value| + $DEBUG_RDOC = value + end + + opt.separator nil + + opt.on("--[no-]ignore-invalid", + "Ignore invalid options and continue", + "(default true).") do |value| + ignore_invalid = value + end + + opt.separator nil + + opt.on("--quiet", "-q", + "Don't show progress as we parse.") do |value| + @verbosity = 0 + end + + opt.separator nil + + opt.on("--verbose", "-V", + "Display extra progress as RDoc parses") do |value| + @verbosity = 2 + end + + opt.separator nil + + opt.on("--version", "-v", "print the version") do + puts opt.version + exit + end + + opt.separator nil + + opt.on("--help", "-h", "Display this help") do + RDoc::RDoc::GENERATORS.each_key do |generator| + setup_generator generator + end + + puts opt.help + exit + end + + opt.separator nil end - end - # Parse command line options. We're passed a hash containing - # output generators, keyed by the generator name + setup_generator 'darkfish' if + argv.grep(/\A(-f|--fmt|--format|-r|-R|--ri|--ri-site)\b/).empty? + + deprecated = [] + invalid = [] - def parse(argv, generators) - old_argv = ARGV.dup begin - ARGV.replace(argv) - @op_dir = "doc" - @op_name = nil - @show_all = false - @main_page = nil - @marge = false - @exclude = [] - @quiet = false - @generator_name = 'html' - @generator = generators[@generator_name] - @rdoc_include = [] - @title = nil - @template = nil - @diagram = false - @fileboxes = false - @show_hash = false - @image_format = 'png' - @inline_source = false - @all_one_file = false - @tab_width = 8 - @include_line_numbers = false - @extra_accessor_flags = {} - @promiscuous = false - - @css = nil - @webcvs = nil - - @charset = case $KCODE - when /^S/i - 'Shift_JIS' - when /^E/i - 'EUC-JP' - else - 'iso-8859-1' - end - - accessors = [] - - go = GetoptLong.new(*OptionList.options) - go.quiet = true - - go.each do |opt, arg| - case opt - when "--all" then @show_all = true - when "--charset" then @charset = arg - when "--debug" then $DEBUG = true - when "--exclude" then @exclude << Regexp.new(arg) - when "--inline-source" then @inline_source = true - when "--line-numbers" then @include_line_numbers = true - when "--main" then @main_page = arg - when "--merge" then @merge = true - when "--one-file" then @all_one_file = @inline_source = true - when "--op" then @op_dir = arg - when "--opname" then @op_name = arg - when "--promiscuous" then @promiscuous = true - when "--quiet" then @quiet = true - when "--show-hash" then @show_hash = true - when "--style" then @css = arg - when "--template" then @template = arg - when "--title" then @title = arg - when "--webcvs" then @webcvs = arg - - when "--accessor" - arg.split(/,/).each do |accessor| - if accessor =~ /^(\w+)(=(.*))?$/ - accessors << $1 - @extra_accessor_flags[$1] = $3 - end - end - - when "--diagram" - check_diagram - @diagram = true - - when "--fileboxes" - @fileboxes = true if @diagram - - when "--fmt" - @generator_name = arg.downcase - setup_generator(generators) - - when "--help" - OptionList.usage(generators.keys) - - when "--help-output" - OptionList.help_output - - when "--image-format" - if ['gif', 'png', 'jpeg', 'jpg'].include?(arg) - @image_format = arg - else - raise GetoptLong::InvalidOption.new("unknown image format: #{arg}") - end - - when "--include" - @rdoc_include.concat arg.split(/\s*,\s*/) - - when "--ri", "--ri-site", "--ri-system" - @generator_name = "ri" - @op_dir = case opt - when "--ri" then RI::Paths::HOMEDIR - when "--ri-site" then RI::Paths::SITEDIR - when "--ri-system" then RI::Paths::SYSDIR - else fail opt - end - setup_generator(generators) - - when "--tab-width" - begin - @tab_width = Integer(arg) - rescue - $stderr.puts "Invalid tab width: '#{arg}'" - exit 1 - end - - when "--extension" - new, old = arg.split(/=/, 2) - OptionList.error("Invalid parameter to '-E'") unless new && old - unless RDoc::ParserFactory.alias_extension(old, new) - OptionList.error("Unknown extension .#{old} to -E") - end - - when "--version" - puts VERSION_STRING - exit - end - - end - - @files = ARGV.dup - - @rdoc_include << "." if @rdoc_include.empty? - - if @exclude.empty? - @exclude = nil + opts.parse! argv + rescue OptionParser::ParseError => e + if DEPRECATED[e.args.first] then + deprecated << e.args.first + elsif %w[--format --ri -r --ri-site -R].include? e.args.first then + raise else - @exclude = Regexp.new(@exclude.join("|")) + invalid << e.args.join(' ') end - check_files + retry + end - # If no template was specified, use the default - # template for the output formatter + unless @generator then + @generator = RDoc::Generator::Darkfish + @generator_name = 'darkfish' + end - @template ||= @generator_name + if @pipe and not argv.empty? then + @pipe = false + invalid << '-p (with files)' + end + + unless quiet then + deprecated.each do |opt| + $stderr.puts 'option ' + opt + ' is deprecated: ' + DEPRECATED[opt] + end + end + + unless invalid.empty? then + invalid = "invalid options: #{invalid.join ', '}" - # Generate a regexp from the accessors - unless accessors.empty? - re = '^(' + accessors.map{|a| Regexp.quote(a)}.join('|') + ')$' - @extra_accessors = Regexp.new(re) + if ignore_invalid then + unless quiet then + $stderr.puts invalid + $stderr.puts '(invalid options are ignored)' + end + else + unless quiet then + $stderr.puts opts + end + $stderr.puts invalid + exit 1 end + end + + @files = argv.dup - rescue GetoptLong::InvalidOption, GetoptLong::MissingArgument => error - OptionList.error(error.message) + finish - ensure - ARGV.replace(old_argv) + if @write_options then + write_options + exit end + + self end + ## + # Don't display progress as we process the files - def title - @title ||= "RDoc Documentation" + def quiet + @verbosity.zero? end - - # Set the title, but only if not already set. This means that a title set from - # the command line trumps one set in a source file - def title=(string) - @title ||= string + ## + # Set quietness to +bool+ + + def quiet= bool + @verbosity = bool ? 0 : 1 end + ## + # Removes directories from +path+ that are outside the current directory - private + def sanitize_path path + require 'pathname' + dot = Pathname.new('.').expand_path - # Set up an output generator for the format in @generator_name - def setup_generator(generators) - @generator = generators[@generator_name] - if !@generator - OptionList.error("Invalid output formatter") - end - - if @generator_name == "xml" - @all_one_file = true - @inline_source = true + path.reject do |item| + path = Pathname.new(item).expand_path + relative = path.relative_path_from(dot).to_s + relative.start_with? '..' end end - # Check that the right version of 'dot' is available. - # Unfortuately this doesn't work correctly under Windows NT, - # so we'll bypass the test under Windows + ## + # Set up an output generator for the named +generator_name+. + # + # If the found generator responds to :setup_options it will be called with + # the options instance. This allows generators to add custom options or set + # default options. - def check_diagram - return if RUBY_PLATFORM =~ /win/ + def setup_generator generator_name = @generator_name + @generator = @generators[generator_name] - ok = false - ver = nil - IO.popen("dot -V 2>&1") do |io| - ver = io.read - if ver =~ /dot.+version(?:\s+gviz)?\s+(\d+)\.(\d+)/ - ok = ($1.to_i > 1) || ($1.to_i == 1 && $2.to_i >= 8) - end + unless @generator then + raise OptionParser::InvalidArgument, + "Invalid output formatter #{generator_name}" end - unless ok - if ver =~ /^dot.+version/ - $stderr.puts "Warning: You may need dot V1.8.6 or later to use\n", - "the --diagram option correctly. You have:\n\n ", - ver, - "\nDiagrams might have strange background colors.\n\n" - else - $stderr.puts "You need the 'dot' program to produce diagrams.", - "(see http://www.research.att.com/sw/tools/graphviz/)\n\n" - exit - end -# exit + + return if @generator_options.include? @generator + + @generator_name = generator_name + @generator_options << @generator + + if @generator.respond_to? :setup_options then + @option_parser ||= OptionParser.new + @generator.setup_options self end end - - # Check that the files on the command line exist - - def check_files - @files.each do |f| - stat = File.stat f rescue error("File not found: #{f}") - error("File '#{f}' not readable") unless stat.readable? + + ## + # Finds the template dir for +template+ + + def template_dir_for template + template_path = File.join 'rdoc', 'generator', 'template', template + + $LOAD_PATH.map do |path| + File.join File.expand_path(path), template_path + end.find do |dir| + File.directory? dir + end + end + + # Sets the minimum visibility of a documented method. + # + # Accepts +:public+, +:protected+, +:private+, +:nodoc+, or +:all+. + # + # When +:all+ is passed, visibility is set to +:private+, similarly to + # RDOCOPT="--all", see #visibility for more information. + + def visibility= visibility + case visibility + when :all + @visibility = :private + else + @visibility = visibility end end - def error(str) - $stderr.puts str - exit(1) + ## + # Displays a warning using Kernel#warn if we're being verbose + + def warn message + super message if @verbosity > 1 + end + + ## + # Writes the YAML file .rdoc_options to the current directory containing the + # parsed options. + + def write_options + RDoc.load_yaml + + open '.rdoc_options', 'w' do |io| + io.set_encoding Encoding::UTF_8 + + YAML.dump self, io + end end end diff --git a/lib/rdoc/parser.rb b/lib/rdoc/parser.rb new file mode 100644 index 0000000000..2b826d9284 --- /dev/null +++ b/lib/rdoc/parser.rb @@ -0,0 +1,277 @@ +# -*- coding: us-ascii -*- +# frozen_string_literal: true + +## +# A parser is simple a class that subclasses RDoc::Parser and implements #scan +# to fill in an RDoc::TopLevel with parsed data. +# +# The initialize method takes an RDoc::TopLevel to fill with parsed content, +# the name of the file to be parsed, the content of the file, an RDoc::Options +# object and an RDoc::Stats object to inform the user of parsed items. The +# scan method is then called to parse the file and must return the +# RDoc::TopLevel object. By calling super these items will be set for you. +# +# In order to be used by RDoc the parser needs to register the file extensions +# it can parse. Use ::parse_files_matching to register extensions. +# +# require 'rdoc' +# +# class RDoc::Parser::Xyz < RDoc::Parser +# parse_files_matching /\.xyz$/ +# +# def initialize top_level, file_name, content, options, stats +# super +# +# # extra initialization if needed +# end +# +# def scan +# # parse file and fill in @top_level +# end +# end + +class RDoc::Parser + + @parsers = [] + + class << self + + ## + # An Array of arrays that maps file extension (or name) regular + # expressions to parser classes that will parse matching filenames. + # + # Use parse_files_matching to register a parser's file extensions. + + attr_reader :parsers + + end + + ## + # The name of the file being parsed + + attr_reader :file_name + + ## + # Alias an extension to another extension. After this call, files ending + # "new_ext" will be parsed using the same parser as "old_ext" + + def self.alias_extension(old_ext, new_ext) + old_ext = old_ext.sub(/^\.(.*)/, '\1') + new_ext = new_ext.sub(/^\.(.*)/, '\1') + + parser = can_parse_by_name "xxx.#{old_ext}" + return false unless parser + + RDoc::Parser.parsers.unshift [/\.#{new_ext}$/, parser] + + true + end + + ## + # Determines if the file is a "binary" file which basically means it has + # content that an RDoc parser shouldn't try to consume. + + def self.binary?(file) + return false if file =~ /\.(rdoc|txt)$/ + + s = File.read(file, 1024) or return false + + return true if s[0, 2] == Marshal.dump('')[0, 2] or s.index("\x00") + + mode = 'r:utf-8' # default source encoding has been chagened to utf-8 + s.sub!(/\A#!.*\n/, '') # assume shebang line isn't longer than 1024. + encoding = s[/^\s*\#\s*(?:-\*-\s*)?(?:en)?coding:\s*([^\s;]+?)(?:-\*-|[\s;])/, 1] + mode = "rb:#{encoding}" if encoding + s = File.open(file, mode) {|f| f.gets(nil, 1024)} + + not s.valid_encoding? + end + + ## + # Checks if +file+ is a zip file in disguise. Signatures from + # http://www.garykessler.net/library/file_sigs.html + + def self.zip? file + zip_signature = File.read file, 4 + + zip_signature == "PK\x03\x04" or + zip_signature == "PK\x05\x06" or + zip_signature == "PK\x07\x08" + rescue + false + end + + ## + # Return a parser that can handle a particular extension + + def self.can_parse file_name + parser = can_parse_by_name file_name + + # HACK Selenium hides a jar file using a .txt extension + return if parser == RDoc::Parser::Simple and zip? file_name + + parser + end + + ## + # Returns a parser that can handle the extension for +file_name+. This does + # not depend upon the file being readable. + + def self.can_parse_by_name file_name + _, parser = RDoc::Parser.parsers.find { |regexp,| regexp =~ file_name } + + # The default parser must not parse binary files + ext_name = File.extname file_name + return parser if ext_name.empty? + + if parser == RDoc::Parser::Simple and ext_name !~ /txt|rdoc/ then + case check_modeline file_name + when nil, 'rdoc' then # continue + else return nil + end + end + + parser + rescue Errno::EACCES + end + + ## + # Returns the file type from the modeline in +file_name+ + + def self.check_modeline file_name + line = open file_name do |io| + io.gets + end + + /-\*-\s*(.*?\S)\s*-\*-/ =~ line + + return nil unless type = $1 + + if /;/ =~ type then + return nil unless /(?:\s|\A)mode:\s*([^\s;]+)/i =~ type + type = $1 + end + + return nil if /coding:/i =~ type + + type.downcase + rescue ArgumentError + rescue Encoding::InvalidByteSequenceError # invalid byte sequence + + end + + ## + # Finds and instantiates the correct parser for the given +file_name+ and + # +content+. + + def self.for top_level, file_name, content, options, stats + return if binary? file_name + + parser = use_markup content + + unless parser then + parse_name = file_name + + # If no extension, look for shebang + if file_name !~ /\.\w+$/ && content =~ %r{\A#!(.+)} then + shebang = $1 + case shebang + when %r{env\s+ruby}, %r{/ruby} + parse_name = 'dummy.rb' + end + end + + parser = can_parse parse_name + end + + return unless parser + + content = remove_modeline content + + parser.new top_level, file_name, content, options, stats + rescue SystemCallError + nil + end + + ## + # Record which file types this parser can understand. + # + # It is ok to call this multiple times. + + def self.parse_files_matching(regexp) + RDoc::Parser.parsers.unshift [regexp, self] + end + + ## + # Removes an emacs-style modeline from the first line of the document + + def self.remove_modeline content + content.sub(/\A.*-\*-\s*(.*?\S)\s*-\*-.*\r?\n/, '') + end + + ## + # If there is a <tt>markup: parser_name</tt> comment at the front of the + # file, use it to determine the parser. For example: + # + # # markup: rdoc + # # Class comment can go here + # + # class C + # end + # + # The comment should appear as the first line of the +content+. + # + # If the content contains a shebang or editor modeline the comment may + # appear on the second or third line. + # + # Any comment style may be used to hide the markup comment. + + def self.use_markup content + markup = content.lines.first(3).grep(/markup:\s+(\w+)/) { $1 }.first + + return unless markup + + # TODO Ruby should be returned only when the filename is correct + return RDoc::Parser::Ruby if %w[tomdoc markdown].include? markup + + markup = Regexp.escape markup + + _, selected = RDoc::Parser.parsers.find do |_, parser| + /^#{markup}$/i =~ parser.name.sub(/.*:/, '') + end + + selected + end + + ## + # Creates a new Parser storing +top_level+, +file_name+, +content+, + # +options+ and +stats+ in instance variables. In +@preprocess+ an + # RDoc::Markup::PreProcess object is created which allows processing of + # directives. + + def initialize top_level, file_name, content, options, stats + @top_level = top_level + @top_level.parser = self.class + @store = @top_level.store + + @file_name = file_name + @content = content + @options = options + @stats = stats + + @preprocess = RDoc::Markup::PreProcess.new @file_name, @options.rdoc_include + @preprocess.options = @options + end + + autoload :RubyTools, 'rdoc/parser/ruby_tools' + autoload :Text, 'rdoc/parser/text' + +end + +# simple must come first in order to show up last in the parsers list +require 'rdoc/parser/simple' +require 'rdoc/parser/c' +require 'rdoc/parser/changelog' +require 'rdoc/parser/markdown' +require 'rdoc/parser/rd' +require 'rdoc/parser/ruby' diff --git a/lib/rdoc/parser/c.rb b/lib/rdoc/parser/c.rb new file mode 100644 index 0000000000..183538d54b --- /dev/null +++ b/lib/rdoc/parser/c.rb @@ -0,0 +1,1268 @@ +# frozen_string_literal: true +require 'tsort' + +## +# RDoc::Parser::C attempts to parse C extension files. It looks for +# the standard patterns that you find in extensions: <tt>rb_define_class, +# rb_define_method</tt> and so on. It tries to find the corresponding +# C source for the methods and extract comments, but if we fail +# we don't worry too much. +# +# The comments associated with a Ruby method are extracted from the C +# comment block associated with the routine that _implements_ that +# method, that is to say the method whose name is given in the +# <tt>rb_define_method</tt> call. For example, you might write: +# +# /* +# * Returns a new array that is a one-dimensional flattening of this +# * array (recursively). That is, for every element that is an array, +# * extract its elements into the new array. +# * +# * s = [ 1, 2, 3 ] #=> [1, 2, 3] +# * t = [ 4, 5, 6, [7, 8] ] #=> [4, 5, 6, [7, 8]] +# * a = [ s, t, 9, 10 ] #=> [[1, 2, 3], [4, 5, 6, [7, 8]], 9, 10] +# * a.flatten #=> [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] +# */ +# static VALUE +# rb_ary_flatten(ary) +# VALUE ary; +# { +# ary = rb_obj_dup(ary); +# rb_ary_flatten_bang(ary); +# return ary; +# } +# +# ... +# +# void +# Init_Array() +# { +# ... +# rb_define_method(rb_cArray, "flatten", rb_ary_flatten, 0); +# +# Here RDoc will determine from the rb_define_method line that there's a +# method called "flatten" in class Array, and will look for the implementation +# in the method rb_ary_flatten. It will then use the comment from that +# method in the HTML output. This method must be in the same source file +# as the rb_define_method. +# +# The comment blocks may include special directives: +# +# [Document-class: +name+] +# Documentation for the named class. +# +# [Document-module: +name+] +# Documentation for the named module. +# +# [Document-const: +name+] +# Documentation for the named +rb_define_const+. +# +# Constant values can be supplied on the first line of the comment like so: +# +# /* 300: The highest possible score in bowling */ +# rb_define_const(cFoo, "PERFECT", INT2FIX(300)); +# +# The value can contain internal colons so long as they are escaped with a \ +# +# [Document-global: +name+] +# Documentation for the named +rb_define_global_const+ +# +# [Document-variable: +name+] +# Documentation for the named +rb_define_variable+ +# +# [Document-method: +method_name+] +# Documentation for the named method. Use this when the method name is +# unambiguous. +# +# [Document-method: <tt>ClassName::method_name<tt>] +# Documentation for a singleton method in the given class. Use this when +# the method name alone is ambiguous. +# +# [Document-method: <tt>ClassName#method_name<tt>] +# Documentation for a instance method in the given class. Use this when the +# method name alone is ambiguous. +# +# [Document-attr: +name+] +# Documentation for the named attribute. +# +# [call-seq: <i>text up to an empty line</i>] +# Because C source doesn't give descriptive names to Ruby-level parameters, +# you need to document the calling sequence explicitly +# +# In addition, RDoc assumes by default that the C method implementing a +# Ruby function is in the same source file as the rb_define_method call. +# If this isn't the case, add the comment: +# +# rb_define_method(....); // in filename +# +# As an example, we might have an extension that defines multiple classes +# in its Init_xxx method. We could document them using +# +# /* +# * Document-class: MyClass +# * +# * Encapsulate the writing and reading of the configuration +# * file. ... +# */ +# +# /* +# * Document-method: read_value +# * +# * call-seq: +# * cfg.read_value(key) -> value +# * cfg.read_value(key} { |key| } -> value +# * +# * Return the value corresponding to +key+ from the configuration. +# * In the second form, if the key isn't found, invoke the +# * block and return its value. +# */ + +class RDoc::Parser::C < RDoc::Parser + + parse_files_matching(/\.(?:([CcHh])\1?|c([+xp])\2|y)\z/) + + include RDoc::Text + + ## + # Maps C variable names to names of Ruby classes or modules + + attr_reader :classes + + ## + # C file the parser is parsing + + attr_accessor :content + + ## + # Dependencies from a missing enclosing class to the classes in + # missing_dependencies that depend upon it. + + attr_reader :enclosure_dependencies + + ## + # Maps C variable names to names of Ruby classes (and singleton classes) + + attr_reader :known_classes + + ## + # Classes found while parsing the C file that were not yet registered due to + # a missing enclosing class. These are processed by do_missing + + attr_reader :missing_dependencies + + ## + # Maps C variable names to names of Ruby singleton classes + + attr_reader :singleton_classes + + ## + # The TopLevel items in the parsed file belong to + + attr_reader :top_level + + ## + # Prepares for parsing a C file. See RDoc::Parser#initialize for details on + # the arguments. + + def initialize top_level, file_name, content, options, stats + super + + @known_classes = RDoc::KNOWN_CLASSES.dup + @content = handle_tab_width handle_ifdefs_in @content + @file_dir = File.dirname @file_name + + @classes = load_variable_map :c_class_variables + @singleton_classes = load_variable_map :c_singleton_class_variables + + # class_variable => { function => [method, ...] } + @methods = Hash.new { |h, f| h[f] = Hash.new { |i, m| i[m] = [] } } + + # missing variable => [handle_class_module arguments] + @missing_dependencies = {} + + # missing enclosure variable => [dependent handle_class_module arguments] + @enclosure_dependencies = Hash.new { |h, k| h[k] = [] } + @enclosure_dependencies.instance_variable_set :@missing_dependencies, + @missing_dependencies + + @enclosure_dependencies.extend TSort + + def @enclosure_dependencies.tsort_each_node &block + each_key(&block) + rescue TSort::Cyclic => e + cycle_vars = e.message.scan(/"(.*?)"/).flatten + + cycle = cycle_vars.sort.map do |var_name| + delete var_name + + var_name, type, mod_name, = @missing_dependencies[var_name] + + "#{type} #{mod_name} (#{var_name})" + end.join ', ' + + warn "Unable to create #{cycle} due to a cyclic class or module creation" + + retry + end + + def @enclosure_dependencies.tsort_each_child node, &block + fetch(node, []).each(&block) + end + end + + ## + # Removes duplicate call-seq entries for methods using the same + # implementation. + + def deduplicate_call_seq + @methods.each do |var_name, functions| + class_name = @known_classes[var_name] + class_obj = find_class var_name, class_name + + functions.each_value do |method_names| + next if method_names.length == 1 + + method_names.each do |method_name| + deduplicate_method_name class_obj, method_name + end + end + end + end + + ## + # If two ruby methods share a C implementation (and comment) this + # deduplicates the examples in the call_seq for the method to reduce + # confusion in the output. + + def deduplicate_method_name class_obj, method_name # :nodoc: + return unless + method = class_obj.method_list.find { |m| m.name == method_name } + return unless call_seq = method.call_seq + + method_name = method_name[0, 1] if method_name =~ /\A\[/ + + entries = call_seq.split "\n" + + matching = entries.select do |entry| + entry =~ /^\w*\.?#{Regexp.escape method_name}/ or + entry =~ /\s#{Regexp.escape method_name}\s/ + end + + method.call_seq = matching.join "\n" + end + + ## + # Scans #content for rb_define_alias + + def do_aliases + @content.scan(/rb_define_alias\s*\( + \s*(\w+), + \s*"(.+?)", + \s*"(.+?)" + \s*\)/xm) do |var_name, new_name, old_name| + class_name = @known_classes[var_name] + + unless class_name then + @options.warn "Enclosing class or module %p for alias %s %s is not known" % [ + var_name, new_name, old_name] + next + end + + class_obj = find_class var_name, class_name + + al = RDoc::Alias.new '', old_name, new_name, '' + al.singleton = @singleton_classes.key? var_name + + comment = find_alias_comment var_name, new_name, old_name + + comment.normalize + + al.comment = comment + + al.record_location @top_level + + class_obj.add_alias al + @stats.add_alias al + end + end + + ## + # Scans #content for rb_attr and rb_define_attr + + def do_attrs + @content.scan(/rb_attr\s*\( + \s*(\w+), + \s*([\w"()]+), + \s*([01]), + \s*([01]), + \s*\w+\);/xm) do |var_name, attr_name, read, write| + handle_attr var_name, attr_name, read, write + end + + @content.scan(%r%rb_define_attr\( + \s*([\w\.]+), + \s*"([^"]+)", + \s*(\d+), + \s*(\d+)\s*\); + %xm) do |var_name, attr_name, read, write| + handle_attr var_name, attr_name, read, write + end + end + + ## + # Scans #content for boot_defclass + + def do_boot_defclass + @content.scan(/(\w+)\s*=\s*boot_defclass\s*\(\s*"(\w+?)",\s*(\w+?)\s*\)/) do + |var_name, class_name, parent| + parent = nil if parent == "0" + handle_class_module(var_name, :class, class_name, parent, nil) + end + end + + ## + # Scans #content for rb_define_class, boot_defclass, rb_define_class_under + # and rb_singleton_class + + def do_classes + do_boot_defclass + do_define_class + do_define_class_under + do_singleton_class + do_struct_define_without_accessor + end + + ## + # Scans #content for rb_define_variable, rb_define_readonly_variable, + # rb_define_const and rb_define_global_const + + def do_constants + @content.scan(%r%\Wrb_define_ + ( variable | + readonly_variable | + const | + global_const ) + \s*\( + (?:\s*(\w+),)? + \s*"(\w+)", + \s*(.*?)\s*\)\s*; + %xm) do |type, var_name, const_name, definition| + var_name = "rb_cObject" if !var_name or var_name == "rb_mKernel" + handle_constants type, var_name, const_name, definition + end + + @content.scan(%r% + \Wrb_curses_define_const + \s*\( + \s* + (\w+) + \s* + \) + \s*;%xm) do |consts| + const = consts.first + + handle_constants 'const', 'mCurses', const, "UINT2NUM(#{const})" + end + + @content.scan(%r% + \Wrb_file_const + \s*\( + \s* + "([^"]+)", + \s* + (.*?) + \s* + \) + \s*;%xm) do |name, value| + handle_constants 'const', 'rb_mFConst', name, value + end + end + + ## + # Scans #content for rb_define_class + + def do_define_class + # The '.' lets us handle SWIG-generated files + @content.scan(/([\w\.]+)\s* = \s*rb_define_class\s* + \( + \s*"(\w+)", + \s*(\w+)\s* + \)/mx) do |var_name, class_name, parent| + handle_class_module(var_name, :class, class_name, parent, nil) + end + end + + ## + # Scans #content for rb_define_class_under + + def do_define_class_under + @content.scan(/([\w\.]+)\s* = # var_name + \s*rb_define_class_under\s* + \( + \s* (\w+), # under + \s* "(\w+)", # class_name + \s* + (?: + ([\w\*\s\(\)\.\->]+) | # parent_name + rb_path2class\("([\w:]+)"\) # path + ) + \s* + \) + /mx) do |var_name, under, class_name, parent_name, path| + parent = path || parent_name + + handle_class_module var_name, :class, class_name, parent, under + end + end + + ## + # Scans #content for rb_define_module + + def do_define_module + @content.scan(/(\w+)\s* = \s*rb_define_module\s*\(\s*"(\w+)"\s*\)/mx) do + |var_name, class_name| + handle_class_module(var_name, :module, class_name, nil, nil) + end + end + + ## + # Scans #content for rb_define_module_under + + def do_define_module_under + @content.scan(/(\w+)\s* = \s*rb_define_module_under\s* + \( + \s*(\w+), + \s*"(\w+)" + \s*\)/mx) do |var_name, in_module, class_name| + handle_class_module(var_name, :module, class_name, nil, in_module) + end + end + + ## + # Scans #content for rb_include_module + + def do_includes + @content.scan(/rb_include_module\s*\(\s*(\w+?),\s*(\w+?)\s*\)/) do |c,m| + next unless cls = @classes[c] + m = @known_classes[m] || m + + comment = RDoc::Comment.new '', @top_level + incl = cls.add_include RDoc::Include.new(m, comment) + incl.record_location @top_level + end + end + + ## + # Scans #content for rb_define_method, rb_define_singleton_method, + # rb_define_module_function, rb_define_private_method, + # rb_define_global_function and define_filetest_function + + def do_methods + @content.scan(%r%rb_define_ + ( + singleton_method | + method | + module_function | + private_method + ) + \s*\(\s*([\w\.]+), + \s*"([^"]+)", + \s*(?:RUBY_METHOD_FUNC\(|VALUEFUNC\(|\(METHOD\))?(\w+)\)?, + \s*(-?\w+)\s*\) + (?:;\s*/[*/]\s+in\s+(\w+?\.(?:cpp|c|y)))? + %xm) do |type, var_name, meth_name, function, param_count, source_file| + + # Ignore top-object and weird struct.c dynamic stuff + next if var_name == "ruby_top_self" + next if var_name == "nstr" + + var_name = "rb_cObject" if var_name == "rb_mKernel" + handle_method(type, var_name, meth_name, function, param_count, + source_file) + end + + @content.scan(%r%rb_define_global_function\s*\( + \s*"([^"]+)", + \s*(?:RUBY_METHOD_FUNC\(|VALUEFUNC\()?(\w+)\)?, + \s*(-?\w+)\s*\) + (?:;\s*/[*/]\s+in\s+(\w+?\.[cy]))? + %xm) do |meth_name, function, param_count, source_file| + handle_method("method", "rb_mKernel", meth_name, function, param_count, + source_file) + end + + @content.scan(/define_filetest_function\s*\( + \s*"([^"]+)", + \s*(?:RUBY_METHOD_FUNC\(|VALUEFUNC\()?(\w+)\)?, + \s*(-?\w+)\s*\)/xm) do |meth_name, function, param_count| + + handle_method("method", "rb_mFileTest", meth_name, function, param_count) + handle_method("singleton_method", "rb_cFile", meth_name, function, + param_count) + end + end + + ## + # Creates classes and module that were missing were defined due to the file + # order being different than the declaration order. + + def do_missing + return if @missing_dependencies.empty? + + @enclosure_dependencies.tsort.each do |in_module| + arguments = @missing_dependencies.delete in_module + + next unless arguments # dependency on existing class + + handle_class_module(*arguments) + end + end + + ## + # Scans #content for rb_define_module and rb_define_module_under + + def do_modules + do_define_module + do_define_module_under + end + + ## + # Scans #content for rb_singleton_class + + def do_singleton_class + @content.scan(/([\w\.]+)\s* = \s*rb_singleton_class\s* + \( + \s*(\w+) + \s*\)/mx) do |sclass_var, class_var| + handle_singleton sclass_var, class_var + end + end + + ## + # Scans #content for struct_define_without_accessor + + def do_struct_define_without_accessor + @content.scan(/([\w\.]+)\s* = \s*rb_struct_define_without_accessor\s* + \( + \s*"(\w+)", # Class name + \s*(\w+), # Parent class + \s*\w+, # Allocation function + (\s*"\w+",)* # Attributes + \s*NULL + \)/mx) do |var_name, class_name, parent| + handle_class_module(var_name, :class, class_name, parent, nil) + end + end + + ## + # Finds the comment for an alias on +class_name+ from +new_name+ to + # +old_name+ + + def find_alias_comment class_name, new_name, old_name + content =~ %r%((?>/\*.*?\*/\s+)) + rb_define_alias\(\s*#{Regexp.escape class_name}\s*, + \s*"#{Regexp.escape new_name}"\s*, + \s*"#{Regexp.escape old_name}"\s*\);%xm + + RDoc::Comment.new($1 || '', @top_level) + end + + ## + # Finds a comment for rb_define_attr, rb_attr or Document-attr. + # + # +var_name+ is the C class variable the attribute is defined on. + # +attr_name+ is the attribute's name. + # + # +read+ and +write+ are the read/write flags ('1' or '0'). Either both or + # neither must be provided. + + def find_attr_comment var_name, attr_name, read = nil, write = nil + attr_name = Regexp.escape attr_name + + rw = if read and write then + /\s*#{read}\s*,\s*#{write}\s*/xm + else + /.*?/m + end + + comment = if @content =~ %r%((?>/\*.*?\*/\s+)) + rb_define_attr\((?:\s*#{var_name},)?\s* + "#{attr_name}"\s*, + #{rw}\)\s*;%xm then + $1 + elsif @content =~ %r%((?>/\*.*?\*/\s+)) + rb_attr\(\s*#{var_name}\s*, + \s*#{attr_name}\s*, + #{rw},.*?\)\s*;%xm then + $1 + elsif @content =~ %r%(/\*.*?(?:\s*\*\s*)?) + Document-attr:\s#{attr_name}\s*?\n + ((?>(.|\n)*?\*/))%x then + "#{$1}\n#{$2}" + else + '' + end + + RDoc::Comment.new comment, @top_level + end + + ## + # Generate a Ruby-method table + + def gen_body_table file_content + table = {} + file_content.scan(%r{ + ((?>/\*.*?\*/\s*)?) + ((?:(?:\w+)\s+)? + (?:intern\s+)?VALUE\s+(\w+) + \s*(?:\([^)]*\))(?:[^;]|$)) + | ((?>/\*.*?\*/\s*))^\s*(\#\s*define\s+(\w+)\s+(\w+)) + | ^\s*\#\s*define\s+(\w+)\s+(\w+) + }xm) do + case + when $1 + table[$3] = [:func_def, $1, $2, $~.offset(2)] if !table[$3] || table[$3][0] != :func_def + when $4 + table[$6] = [:macro_def, $4, $5, $~.offset(5), $7] if !table[$6] || table[$6][0] == :macro_alias + when $8 + table[$8] ||= [:macro_alias, $9] + end + end + table + end + + ## + # Find the C code corresponding to a Ruby method + + def find_body class_name, meth_name, meth_obj, file_content, quiet = false + if file_content + @body_table ||= {} + @body_table[file_content] ||= gen_body_table file_content + type, *args = @body_table[file_content][meth_name] + end + + case type + when :func_def + comment = RDoc::Comment.new args[0], @top_level + body = args[1] + offset, = args[2] + + comment.remove_private if comment + + # try to find the whole body + body = $& if /#{Regexp.escape body}[^(]*?\{.*?^\}/m =~ file_content + + # The comment block may have been overridden with a 'Document-method' + # block. This happens in the interpreter when multiple methods are + # vectored through to the same C method but those methods are logically + # distinct (for example Kernel.hash and Kernel.object_id share the same + # implementation + + override_comment = find_override_comment class_name, meth_obj + comment = override_comment if override_comment + + comment.normalize + find_modifiers comment, meth_obj if comment + + #meth_obj.params = params + meth_obj.start_collecting_tokens + tk = { :line_no => 1, :char_no => 1, :text => body } + meth_obj.add_token tk + meth_obj.comment = comment + meth_obj.line = file_content[0, offset].count("\n") + 1 + + body + when :macro_def + comment = RDoc::Comment.new args[0], @top_level + body = args[1] + offset, = args[2] + + find_body class_name, args[3], meth_obj, file_content, true + + comment.normalize + find_modifiers comment, meth_obj + + meth_obj.start_collecting_tokens + tk = { :line_no => 1, :char_no => 1, :text => body } + meth_obj.add_token tk + meth_obj.comment = comment + meth_obj.line = file_content[0, offset].count("\n") + 1 + + body + when :macro_alias + # with no comment we hope the aliased definition has it and use it's + # definition + + body = find_body(class_name, args[0], meth_obj, file_content, true) + + return body if body + + @options.warn "No definition for #{meth_name}" + false + else # No body, but might still have an override comment + comment = find_override_comment class_name, meth_obj + + if comment then + comment.normalize + find_modifiers comment, meth_obj + meth_obj.comment = comment + + '' + else + @options.warn "No definition for #{meth_name}" + false + end + end + end + + ## + # Finds a RDoc::NormalClass or RDoc::NormalModule for +raw_name+ + + def find_class(raw_name, name) + unless @classes[raw_name] + if raw_name =~ /^rb_m/ + container = @top_level.add_module RDoc::NormalModule, name + else + container = @top_level.add_class RDoc::NormalClass, name + end + + container.record_location @top_level + @classes[raw_name] = container + end + @classes[raw_name] + end + + ## + # Look for class or module documentation above Init_+class_name+(void), + # in a Document-class +class_name+ (or module) comment or above an + # rb_define_class (or module). If a comment is supplied above a matching + # Init_ and a rb_define_class the Init_ comment is used. + # + # /* + # * This is a comment for Foo + # */ + # Init_Foo(void) { + # VALUE cFoo = rb_define_class("Foo", rb_cObject); + # } + # + # /* + # * Document-class: Foo + # * This is a comment for Foo + # */ + # Init_foo(void) { + # VALUE cFoo = rb_define_class("Foo", rb_cObject); + # } + # + # /* + # * This is a comment for Foo + # */ + # VALUE cFoo = rb_define_class("Foo", rb_cObject); + + def find_class_comment class_name, class_mod + comment = nil + + if @content =~ %r% + ((?>/\*.*?\*/\s+)) + (static\s+)? + void\s+ + Init_#{class_name}\s*(?:_\(\s*)?\(\s*(?:void\s*)?\)%xmi then + comment = $1.sub(%r%Document-(?:class|module):\s+#{class_name}%, '') + elsif @content =~ %r%Document-(?:class|module):\s+#{class_name}\s*? + (?:<\s+[:,\w]+)?\n((?>.*?\*/))%xm then + comment = "/*\n#{$1}" + elsif @content =~ %r%((?>/\*.*?\*/\s+)) + ([\w\.\s]+\s* = \s+)?rb_define_(class|module)[\t (]*?"(#{class_name})"%xm then + comment = $1 + elsif @content =~ %r%((?>/\*.*?\*/\s+)) + ([\w\. \t]+ = \s+)?rb_define_(class|module)_under[\t\w, (]*?"(#{class_name.split('::').last})"%xm then + comment = $1 + else + comment = '' + end + + comment = RDoc::Comment.new comment, @top_level + comment.normalize + + look_for_directives_in class_mod, comment + + class_mod.add_comment comment, @top_level + end + + ## + # Generate a const table + + def gen_const_table file_content + table = {} + @content.scan(%r{ + ((?>^\s*/\*.*?\*/\s+)) + rb_define_(\w+)\((?:\s*(?:\w+),)?\s* + "(\w+)"\s*, + .*?\)\s*; + | Document-(?:const|global|variable):\s + ((?:\w+::)*\w+) + \s*?\n((?>.*?\*/)) + }mxi) do + case + when $1 then table[[$2, $3]] = $1 + when $4 then table[$4] = "/*\n" + $5 + end + end + table + end + + ## + # Finds a comment matching +type+ and +const_name+ either above the + # comment or in the matching Document- section. + + def find_const_comment(type, const_name, class_name = nil) + @const_table ||= {} + @const_table[@content] ||= gen_const_table @content + table = @const_table[@content] + + comment = + table[[type, const_name]] || + (class_name && table[class_name + "::" + const_name]) || + table[const_name] || + '' + + RDoc::Comment.new comment, @top_level + end + + ## + # Handles modifiers in +comment+ and updates +meth_obj+ as appropriate. + + def find_modifiers comment, meth_obj + comment.normalize + comment.extract_call_seq meth_obj + + look_for_directives_in meth_obj, comment + end + + ## + # Finds a <tt>Document-method</tt> override for +meth_obj+ on +class_name+ + + def find_override_comment class_name, meth_obj + name = Regexp.escape meth_obj.name + prefix = Regexp.escape meth_obj.name_prefix + + comment = if @content =~ %r%Document-method: + \s+#{class_name}#{prefix}#{name} + \s*?\n((?>.*?\*/))%xm then + "/*#{$1}" + elsif @content =~ %r%Document-method: + \s#{name}\s*?\n((?>.*?\*/))%xm then + "/*#{$1}" + end + + return unless comment + + RDoc::Comment.new comment, @top_level + end + + ## + # Creates a new RDoc::Attr +attr_name+ on class +var_name+ that is either + # +read+, +write+ or both + + def handle_attr(var_name, attr_name, read, write) + rw = '' + rw += 'R' if '1' == read + rw += 'W' if '1' == write + + class_name = @known_classes[var_name] + + return unless class_name + + class_obj = find_class var_name, class_name + + return unless class_obj + + comment = find_attr_comment var_name, attr_name + comment.normalize + + name = attr_name.gsub(/rb_intern(?:_const)?\("([^"]+)"\)/, '\1') + + attr = RDoc::Attr.new '', name, rw, comment + + attr.record_location @top_level + class_obj.add_attribute attr + @stats.add_attribute attr + end + + ## + # Creates a new RDoc::NormalClass or RDoc::NormalModule based on +type+ + # named +class_name+ in +parent+ which was assigned to the C +var_name+. + + def handle_class_module(var_name, type, class_name, parent, in_module) + parent_name = @known_classes[parent] || parent + + if in_module then + enclosure = @classes[in_module] || @store.find_c_enclosure(in_module) + + if enclosure.nil? and enclosure = @known_classes[in_module] then + enc_type = /^rb_m/ =~ in_module ? :module : :class + handle_class_module in_module, enc_type, enclosure, nil, nil + enclosure = @classes[in_module] + end + + unless enclosure then + @enclosure_dependencies[in_module] << var_name + @missing_dependencies[var_name] = + [var_name, type, class_name, parent, in_module] + + return + end + else + enclosure = @top_level + end + + if type == :class then + full_name = if RDoc::ClassModule === enclosure then + enclosure.full_name + "::#{class_name}" + else + class_name + end + + if @content =~ %r%Document-class:\s+#{full_name}\s*<\s+([:,\w]+)% then + parent_name = $1 + end + + cm = enclosure.add_class RDoc::NormalClass, class_name, parent_name + else + cm = enclosure.add_module RDoc::NormalModule, class_name + end + + cm.record_location enclosure.top_level + + find_class_comment cm.full_name, cm + + case cm + when RDoc::NormalClass + @stats.add_class cm + when RDoc::NormalModule + @stats.add_module cm + end + + @classes[var_name] = cm + @known_classes[var_name] = cm.full_name + @store.add_c_enclosure var_name, cm + end + + ## + # Adds constants. By providing some_value: at the start of the comment you + # can override the C value of the comment to give a friendly definition. + # + # /* 300: The perfect score in bowling */ + # rb_define_const(cFoo, "PERFECT", INT2FIX(300); + # + # Will override <tt>INT2FIX(300)</tt> with the value +300+ in the output + # RDoc. Values may include quotes and escaped colons (\:). + + def handle_constants(type, var_name, const_name, definition) + class_name = @known_classes[var_name] + + return unless class_name + + class_obj = find_class var_name, class_name + + unless class_obj then + @options.warn 'Enclosing class or module %p is not known' % [const_name] + return + end + + comment = find_const_comment type, const_name, class_name + comment.normalize + + # In the case of rb_define_const, the definition and comment are in + # "/* definition: comment */" form. The literal ':' and '\' characters + # can be escaped with a backslash. + if type.downcase == 'const' then + no_match, new_definition, new_comment = comment.text.split(/(\A.*):/) + + if no_match and no_match.empty? then + if new_definition.empty? then # Default to literal C definition + new_definition = definition + else + new_definition = new_definition.gsub("\:", ":") + new_definition = new_definition.gsub("\\", '\\') + end + + new_definition.sub!(/\A(\s+)/, '') + + new_comment = "#{$1}#{new_comment.lstrip}" + + new_comment = RDoc::Comment.new new_comment, @top_level + + con = RDoc::Constant.new const_name, new_definition, new_comment + else + con = RDoc::Constant.new const_name, definition, comment + end + else + con = RDoc::Constant.new const_name, definition, comment + end + + con.record_location @top_level + @stats.add_constant con + class_obj.add_constant con + end + + ## + # Removes #ifdefs that would otherwise confuse us + + def handle_ifdefs_in(body) + body.gsub(/^#ifdef HAVE_PROTOTYPES.*?#else.*?\n(.*?)#endif.*?\n/m, '\1') + end + + ## + # Adds an RDoc::AnyMethod +meth_name+ defined on a class or module assigned + # to +var_name+. +type+ is the type of method definition function used. + # +singleton_method+ and +module_function+ create a singleton method. + + def handle_method(type, var_name, meth_name, function, param_count, + source_file = nil) + class_name = @known_classes[var_name] + singleton = @singleton_classes.key? var_name + + @methods[var_name][function] << meth_name + + return unless class_name + + class_obj = find_class var_name, class_name + + if class_obj then + if meth_name == 'initialize' then + meth_name = 'new' + singleton = true + type = 'method' # force public + end + + meth_obj = RDoc::AnyMethod.new '', meth_name + meth_obj.c_function = function + meth_obj.singleton = + singleton || %w[singleton_method module_function].include?(type) + + p_count = Integer(param_count) rescue -1 + + if source_file then + file_name = File.join @file_dir, source_file + + if File.exist? file_name then + file_content = File.read file_name + else + @options.warn "unknown source #{source_file} for #{meth_name} in #{@file_name}" + end + else + file_content = @content + end + + body = find_body class_name, function, meth_obj, file_content + + if body and meth_obj.document_self then + meth_obj.params = if p_count < -1 then # -2 is Array + '(*args)' + elsif p_count == -1 then # argc, argv + rb_scan_args body + else + "(#{(1..p_count).map { |i| "p#{i}" }.join ', '})" + end + + + meth_obj.record_location @top_level + class_obj.add_method meth_obj + @stats.add_method meth_obj + meth_obj.visibility = :private if 'private_method' == type + end + end + end + + ## + # Registers a singleton class +sclass_var+ as a singleton of +class_var+ + + def handle_singleton sclass_var, class_var + class_name = @known_classes[class_var] + + @known_classes[sclass_var] = class_name + @singleton_classes[sclass_var] = class_name + end + + ## + # Normalizes tabs in +body+ + + def handle_tab_width(body) + if /\t/ =~ body + tab_width = @options.tab_width + body.split(/\n/).map do |line| + 1 while line.gsub!(/\t+/) do + ' ' * (tab_width * $&.length - $`.length % tab_width) + end && $~ + line + end.join "\n" + else + body + end + end + + ## + # Loads the variable map with the given +name+ from the RDoc::Store, if + # present. + + def load_variable_map map_name + return {} unless files = @store.cache[map_name] + return {} unless name_map = files[@file_name] + + class_map = {} + + name_map.each do |variable, name| + next unless mod = @store.find_class_or_module(name) + + class_map[variable] = if map_name == :c_class_variables then + mod + else + name + end + @known_classes[variable] = name + end + + class_map + end + + ## + # Look for directives in a normal comment block: + # + # /* + # * :title: My Awesome Project + # */ + # + # This method modifies the +comment+ + + def look_for_directives_in context, comment + @preprocess.handle comment, context do |directive, param| + case directive + when 'main' then + @options.main_page = param + '' + when 'title' then + @options.default_title = param if @options.respond_to? :default_title= + '' + end + end + + comment + end + + ## + # Extracts parameters from the +method_body+ and returns a method + # parameter string. Follows 1.9.3dev's scan-arg-spec, see README.EXT + + def rb_scan_args method_body + method_body =~ /rb_scan_args\((.*?)\)/m + return '(*args)' unless $1 + + $1.split(/,/)[2] =~ /"(.*?)"/ # format argument + format = $1.split(//) + + lead = opt = trail = 0 + + if format.first =~ /\d/ then + lead = $&.to_i + format.shift + if format.first =~ /\d/ then + opt = $&.to_i + format.shift + if format.first =~ /\d/ then + trail = $&.to_i + format.shift + block_arg = true + end + end + end + + if format.first == '*' and not block_arg then + var = true + format.shift + if format.first =~ /\d/ then + trail = $&.to_i + format.shift + end + end + + if format.first == ':' then + hash = true + format.shift + end + + if format.first == '&' then + block = true + format.shift + end + + # if the format string is not empty there's a bug in the C code, ignore it + + args = [] + position = 1 + + (1...(position + lead)).each do |index| + args << "p#{index}" + end + + position += lead + + (position...(position + opt)).each do |index| + args << "p#{index} = v#{index}" + end + + position += opt + + if var then + args << '*args' + position += 1 + end + + (position...(position + trail)).each do |index| + args << "p#{index}" + end + + position += trail + + if hash then + args << "p#{position} = {}" + end + + args << '&block' if block + + "(#{args.join ', '})" + end + + ## + # Removes lines that are commented out that might otherwise get picked up + # when scanning for classes and methods + + def remove_commented_out_lines + @content = @content.gsub(%r%//.*rb_define_%, '//') + end + + ## + # Extracts the classes, modules, methods, attributes, constants and aliases + # from a C file and returns an RDoc::TopLevel for this file + + def scan + remove_commented_out_lines + + do_modules + do_classes + do_missing + + do_constants + do_methods + do_includes + do_aliases + do_attrs + + deduplicate_call_seq + + @store.add_c_variables self + + @top_level + end + +end + diff --git a/lib/rdoc/parser/changelog.rb b/lib/rdoc/parser/changelog.rb new file mode 100644 index 0000000000..167892f543 --- /dev/null +++ b/lib/rdoc/parser/changelog.rb @@ -0,0 +1,204 @@ +# frozen_string_literal: true +require 'time' + +## +# A ChangeLog file parser. +# +# This parser converts a ChangeLog into an RDoc::Markup::Document. When +# viewed as HTML a ChangeLog page will have an entry for each day's entries in +# the sidebar table of contents. +# +# This parser is meant to parse the MRI ChangeLog, but can be used to parse any +# {GNU style Change +# Log}[http://www.gnu.org/prep/standards/html_node/Style-of-Change-Logs.html]. + +class RDoc::Parser::ChangeLog < RDoc::Parser + + include RDoc::Parser::Text + + parse_files_matching(/(\/|\\|\A)ChangeLog[^\/\\]*\z/) + + ## + # Attaches the +continuation+ of the previous line to the +entry_body+. + # + # Continued function listings are joined together as a single entry. + # Continued descriptions are joined to make a single paragraph. + + def continue_entry_body entry_body, continuation + return unless last = entry_body.last + + if last =~ /\)\s*\z/ and continuation =~ /\A\(/ then + last.sub!(/\)\s*\z/, ',') + continuation = continuation.sub(/\A\(/, '') + end + + if last =~ /\s\z/ then + last << continuation + else + last << ' ' + continuation + end + end + + ## + # Creates an RDoc::Markup::Document given the +groups+ of ChangeLog entries. + + def create_document groups + doc = RDoc::Markup::Document.new + doc.omit_headings_below = 2 + doc.file = @top_level + + doc << RDoc::Markup::Heading.new(1, File.basename(@file_name)) + doc << RDoc::Markup::BlankLine.new + + groups.sort_by do |day,| day end.reverse_each do |day, entries| + doc << RDoc::Markup::Heading.new(2, day.dup) + doc << RDoc::Markup::BlankLine.new + + doc.concat create_entries entries + end + + doc + end + + ## + # Returns a list of ChangeLog entries an RDoc::Markup nodes for the given + # +entries+. + + def create_entries entries + out = [] + + entries.each do |entry, items| + out << RDoc::Markup::Heading.new(3, entry) + out << RDoc::Markup::BlankLine.new + + out << create_items(items) + end + + out + end + + ## + # Returns an RDoc::Markup::List containing the given +items+ in the + # ChangeLog + + def create_items items + list = RDoc::Markup::List.new :NOTE + + items.each do |item| + item =~ /\A(.*?(?:\([^)]+\))?):\s*/ + + title = $1 + body = $' + + paragraph = RDoc::Markup::Paragraph.new body + list_item = RDoc::Markup::ListItem.new title, paragraph + list << list_item + end + + list + end + + ## + # Groups +entries+ by date. + + def group_entries entries + @time_cache ||= {} + entries.group_by do |title, _| + begin + time = @time_cache[title] + (time || Time.parse(title)).strftime '%Y-%m-%d' + rescue NoMethodError, ArgumentError + time, = title.split ' ', 2 + Time.parse(time).strftime '%Y-%m-%d' + end + end + end + + ## + # Parses the entries in the ChangeLog. + # + # Returns an Array of each ChangeLog entry in order of parsing. + # + # A ChangeLog entry is an Array containing the ChangeLog title (date and + # committer) and an Array of ChangeLog items (file and function changed with + # description). + # + # An example result would be: + # + # [ 'Tue Dec 4 08:33:46 2012 Eric Hodel <drbrain@segment7.net>', + # [ 'README.EXT: Converted to RDoc format', + # 'README.EXT.ja: ditto']] + + def parse_entries + @time_cache ||= {} + entries = [] + entry_name = nil + entry_body = [] + + @content.each_line do |line| + case line + when /^\s*$/ then + next + when /^\w.*/ then + entries << [entry_name, entry_body] if entry_name + + entry_name = $& + + begin + time = Time.parse entry_name + @time_cache[entry_name] = time + # HACK Ruby 1.8 does not raise ArgumentError for Time.parse "Other" + entry_name = nil unless entry_name =~ /#{time.year}/ + rescue NoMethodError + # HACK Ruby 2.1.2 and earlier raises NoMethodError if time part is absent + entry_name.split ' ', 2 + rescue ArgumentError + if /out of range/ =~ $!.message + Time.parse(entry_name.split(' ', 2)[0]) rescue entry_name = nil + else + entry_name = nil + end + end + + entry_body = [] + when /^(\t| {8})?\*\s*(.*)/ then # "\t* file.c (func): ..." + entry_body << $2.dup + when /^(\t| {8})?\s*(\(.*)/ then # "\t(func): ..." + entry = $2 + + if entry_body.last =~ /:/ then + entry_body << entry.dup + else + continue_entry_body entry_body, entry + end + when /^(\t| {8})?\s*(.*)/ then + continue_entry_body entry_body, $2 + end + end + + entries << [entry_name, entry_body] if entry_name + + entries.reject! do |(entry,_)| + entry == nil + end + + entries + end + + ## + # Converts the ChangeLog into an RDoc::Markup::Document + + def scan + @time_cache = {} + entries = parse_entries + grouped_entries = group_entries entries + + doc = create_document grouped_entries + + @top_level.comment = doc + + @top_level + end + +end + diff --git a/lib/rdoc/parser/markdown.rb b/lib/rdoc/parser/markdown.rb new file mode 100644 index 0000000000..9ff478f872 --- /dev/null +++ b/lib/rdoc/parser/markdown.rb @@ -0,0 +1,24 @@ +# frozen_string_literal: true +## +# Parse a Markdown format file. The parsed RDoc::Markup::Document is attached +# as a file comment. + +class RDoc::Parser::Markdown < RDoc::Parser + + include RDoc::Parser::Text + + parse_files_matching(/\.(md|markdown)(?:\.[^.]+)?$/) + + ## + # Creates an Markdown-format TopLevel for the given file. + + def scan + comment = RDoc::Comment.new @content, @top_level + comment.format = 'markdown' + + @top_level.comment = comment + end + +end + + diff --git a/lib/rdoc/parser/rd.rb b/lib/rdoc/parser/rd.rb new file mode 100644 index 0000000000..25f5711731 --- /dev/null +++ b/lib/rdoc/parser/rd.rb @@ -0,0 +1,23 @@ +# frozen_string_literal: true +## +# Parse a RD format file. The parsed RDoc::Markup::Document is attached as a +# file comment. + +class RDoc::Parser::RD < RDoc::Parser + + include RDoc::Parser::Text + + parse_files_matching(/\.rd(?:\.[^.]+)?$/) + + ## + # Creates an rd-format TopLevel for the given file. + + def scan + comment = RDoc::Comment.new @content, @top_level + comment.format = 'rd' + + @top_level.comment = comment + end + +end + diff --git a/lib/rdoc/parser/ripper_state_lex.rb b/lib/rdoc/parser/ripper_state_lex.rb new file mode 100644 index 0000000000..b7cec84bfc --- /dev/null +++ b/lib/rdoc/parser/ripper_state_lex.rb @@ -0,0 +1,605 @@ +require 'ripper' + +class RDoc::RipperStateLex + # TODO: Remove this constants after Ruby 2.4 EOL + RIPPER_HAS_LEX_STATE = Ripper::Filter.method_defined?(:state) + + EXPR_NONE = 0 + EXPR_BEG = 1 + EXPR_END = 2 + EXPR_ENDARG = 4 + EXPR_ENDFN = 8 + EXPR_ARG = 16 + EXPR_CMDARG = 32 + EXPR_MID = 64 + EXPR_FNAME = 128 + EXPR_DOT = 256 + EXPR_CLASS = 512 + EXPR_LABEL = 1024 + EXPR_LABELED = 2048 + EXPR_FITEM = 4096 + EXPR_VALUE = EXPR_BEG + EXPR_BEG_ANY = (EXPR_BEG | EXPR_MID | EXPR_CLASS) + EXPR_ARG_ANY = (EXPR_ARG | EXPR_CMDARG) + EXPR_END_ANY = (EXPR_END | EXPR_ENDARG | EXPR_ENDFN) + + class InnerStateLex < Ripper::Filter + attr_accessor :lex_state + + def initialize(code) + @lex_state = EXPR_BEG + @in_fname = false + @continue = false + reset + super(code) + end + + def reset + @command_start = false + @cmd_state = @command_start + end + + def on_nl(tok, data) + case @lex_state + when EXPR_FNAME, EXPR_DOT + @continue = true + else + @continue = false + @lex_state = EXPR_BEG unless (EXPR_LABEL & @lex_state) != 0 + end + @callback.call({ :line_no => lineno, :char_no => column, :kind => __method__, :text => tok, :state => @lex_state}) + end + + def on_ignored_nl(tok, data) + case @lex_state + when EXPR_FNAME, EXPR_DOT + @continue = true + else + @continue = false + @lex_state = EXPR_BEG unless (EXPR_LABEL & @lex_state) != 0 + end + @callback.call({ :line_no => lineno, :char_no => column, :kind => __method__, :text => tok, :state => @lex_state}) + end + + def on_op(tok, data) + case tok + when '&', '|', '!', '!=', '!~' + case @lex_state + when EXPR_FNAME, EXPR_DOT + @lex_state = EXPR_ARG + else + @lex_state = EXPR_BEG + end + when '<<' + # TODO next token? + case @lex_state + when EXPR_FNAME, EXPR_DOT + @lex_state = EXPR_ARG + else + @lex_state = EXPR_BEG + end + when '?' + @lex_state = EXPR_BEG + when '&&', '||', '+=', '-=', '*=', '**=', + '&=', '|=', '^=', '<<=', '>>=', '||=', '&&=' + @lex_state = EXPR_BEG + else + case @lex_state + when EXPR_FNAME, EXPR_DOT + @lex_state = EXPR_ARG + else + @lex_state = EXPR_BEG + end + end + @callback.call({ :line_no => lineno, :char_no => column, :kind => __method__, :text => tok, :state => @lex_state}) + end + + def on_kw(tok, data) + case tok + when 'class' + @lex_state = EXPR_CLASS + @in_fname = true + when 'def' + @lex_state = EXPR_FNAME + @continue = true + @in_fname = true + when 'if', 'unless', 'while', 'until' + if ((EXPR_END | EXPR_ENDARG | EXPR_ENDFN | EXPR_ARG | EXPR_CMDARG) & @lex_state) != 0 # postfix if + @lex_state = EXPR_BEG | EXPR_LABEL + else + @lex_state = EXPR_BEG + end + when 'begin' + @lex_state = EXPR_BEG + else + if @lex_state == EXPR_FNAME + @lex_state = EXPR_END + else + @lex_state = EXPR_END + end + end + @callback.call({ :line_no => lineno, :char_no => column, :kind => __method__, :text => tok, :state => @lex_state}) + end + + def on_tstring_beg(tok, data) + @lex_state = EXPR_BEG + @callback.call({ :line_no => lineno, :char_no => column, :kind => __method__, :text => tok, :state => @lex_state}) + end + + def on_tstring_end(tok, data) + @lex_state = EXPR_END | EXPR_ENDARG + @callback.call({ :line_no => lineno, :char_no => column, :kind => __method__, :text => tok, :state => @lex_state}) + end + + def on_CHAR(tok, data) + @lex_state = EXPR_END + @callback.call({ :line_no => lineno, :char_no => column, :kind => __method__, :text => tok, :state => @lex_state}) + end + + def on_period(tok, data) + @lex_state = EXPR_DOT + @callback.call({ :line_no => lineno, :char_no => column, :kind => __method__, :text => tok, :state => @lex_state}) + end + + def on_int(tok, data) + @lex_state = EXPR_END | EXPR_ENDARG + @callback.call({ :line_no => lineno, :char_no => column, :kind => __method__, :text => tok, :state => @lex_state}) + end + + def on_float(tok, data) + @lex_state = EXPR_END | EXPR_ENDARG + @callback.call({ :line_no => lineno, :char_no => column, :kind => __method__, :text => tok, :state => @lex_state}) + end + + def on_rational(tok, data) + @lex_state = EXPR_END | EXPR_ENDARG + @callback.call({ :line_no => lineno, :char_no => column, :kind => __method__, :text => tok, :state => @lex_state}) + end + + def on_imaginary(tok, data) + @lex_state = EXPR_END | EXPR_ENDARG + @callback.call({ :line_no => lineno, :char_no => column, :kind => __method__, :text => tok, :state => @lex_state}) + end + + def on_symbeg(tok, data) + @lex_state = EXPR_FNAME + @continue = true + @in_fname = true + @callback.call({ :line_no => lineno, :char_no => column, :kind => __method__, :text => tok, :state => @lex_state}) + end + + private def on_variables(event, tok, data) + if @in_fname + @lex_state = EXPR_ENDFN + @in_fname = false + @continue = false + elsif @continue + case @lex_state + when EXPR_DOT + @lex_state = EXPR_ARG + else + @lex_state = EXPR_ENDFN + @continue = false + end + else + @lex_state = EXPR_CMDARG + end + @callback.call({ :line_no => lineno, :char_no => column, :kind => event, :text => tok, :state => @lex_state}) + end + + def on_ident(tok, data) + on_variables(__method__, tok, data) + end + + def on_ivar(tok, data) + @lex_state = EXPR_END + on_variables(__method__, tok, data) + end + + def on_cvar(tok, data) + @lex_state = EXPR_END + on_variables(__method__, tok, data) + end + + def on_gvar(tok, data) + @lex_state = EXPR_END + on_variables(__method__, tok, data) + end + + def on_backref(tok, data) + @lex_state = EXPR_END + on_variables(__method__, tok, data) + end + + def on_lparen(tok, data) + @lex_state = EXPR_LABEL | EXPR_BEG + @callback.call({ :line_no => lineno, :char_no => column, :kind => __method__, :text => tok, :state => @lex_state}) + end + + def on_rparen(tok, data) + @lex_state = EXPR_ENDFN + @callback.call({ :line_no => lineno, :char_no => column, :kind => __method__, :text => tok, :state => @lex_state}) + end + + def on_lbrace(tok, data) + @lex_state = EXPR_LABEL | EXPR_BEG + @callback.call({ :line_no => lineno, :char_no => column, :kind => __method__, :text => tok, :state => @lex_state}) + end + + def on_rbrace(tok, data) + @lex_state = EXPR_ENDARG + @callback.call({ :line_no => lineno, :char_no => column, :kind => __method__, :text => tok, :state => @lex_state}) + end + + def on_lbracket(tok, data) + @lex_state = EXPR_LABEL | EXPR_BEG + @callback.call({ :line_no => lineno, :char_no => column, :kind => __method__, :text => tok, :state => @lex_state}) + end + + def on_rbracket(tok, data) + @lex_state = EXPR_ENDARG + @callback.call({ :line_no => lineno, :char_no => column, :kind => __method__, :text => tok, :state => @lex_state}) + end + + def on_const(tok, data) + case @lex_state + when EXPR_FNAME + @lex_state = EXPR_ENDFN + when EXPR_CLASS + @lex_state = EXPR_ARG + else + @lex_state = EXPR_CMDARG + end + @callback.call({ :line_no => lineno, :char_no => column, :kind => __method__, :text => tok, :state => @lex_state}) + end + + def on_sp(tok, data) + @callback.call({ :line_no => lineno, :char_no => column, :kind => __method__, :text => tok, :state => @lex_state}) + end + + def on_comma(tok, data) + @lex_state = EXPR_BEG | EXPR_LABEL if (EXPR_ARG_ANY & @lex_state) != 0 + @callback.call({ :line_no => lineno, :char_no => column, :kind => __method__, :text => tok, :state => @lex_state}) + end + + def on_comment(tok, data) + @lex_state = EXPR_BEG unless (EXPR_LABEL & @lex_state) != 0 + @callback.call({ :line_no => lineno, :char_no => column, :kind => __method__, :text => tok, :state => @lex_state}) + end + + def on_ignored_sp(tok, data) + @lex_state = EXPR_BEG unless (EXPR_LABEL & @lex_state) != 0 + @callback.call({ :line_no => lineno, :char_no => column, :kind => __method__, :text => tok, :state => @lex_state}) + end + + def on_heredoc_end(tok, data) + @callback.call({ :line_no => lineno, :char_no => column, :kind => __method__, :text => tok, :state => @lex_state}) + @lex_state = EXPR_BEG + end + + def on_default(event, tok, data) + reset + @callback.call({ :line_no => lineno, :char_no => column, :kind => event, :text => tok, :state => @lex_state}) + end + + def each(&block) + @callback = block + parse + end + end unless RIPPER_HAS_LEX_STATE + + class InnerStateLex < Ripper::Filter + def initialize(code) + super(code) + end + + def on_default(event, tok, data) + @callback.call({ :line_no => lineno, :char_no => column, :kind => event, :text => tok, :state => state}) + end + + def each(&block) + @callback = block + parse + end + end if RIPPER_HAS_LEX_STATE + + def get_squashed_tk + if @buf.empty? + tk = @inner_lex_enumerator.next + else + tk = @buf.shift + end + case tk[:kind] + when :on_symbeg then + tk = get_symbol_tk(tk) + when :on_tstring_beg then + tk = get_string_tk(tk) + when :on_backtick then + if (tk[:state] & (EXPR_FNAME | EXPR_ENDFN)) != 0 + @inner_lex.lex_state = EXPR_ARG unless RIPPER_HAS_LEX_STATE + tk[:kind] = :on_ident + tk[:state] = Ripper::Lexer.const_defined?(:State) ? Ripper::Lexer::State.new(EXPR_ARG) : EXPR_ARG + else + tk = get_string_tk(tk) + end + when :on_regexp_beg then + tk = get_regexp_tk(tk) + when :on_embdoc_beg then + tk = get_embdoc_tk(tk) + when :on_heredoc_beg then + @heredoc_queue << retrieve_heredoc_info(tk) + @inner_lex.lex_state = EXPR_END unless RIPPER_HAS_LEX_STATE + when :on_nl, :on_ignored_nl, :on_comment, :on_heredoc_end then + unless @heredoc_queue.empty? + get_heredoc_tk(*@heredoc_queue.shift) + end + when :on_words_beg then + tk = get_words_tk(tk) + when :on_qwords_beg then + tk = get_words_tk(tk) + when :on_symbols_beg then + tk = get_words_tk(tk) + when :on_qsymbols_beg then + tk = get_words_tk(tk) + when :on_op then + if '&.' == tk[:text] + tk[:kind] = :on_period + else + tk = get_op_tk(tk) + end + end + tk + end + + private def get_symbol_tk(tk) + is_symbol = true + symbol_tk = { :line_no => tk[:line_no], :char_no => tk[:char_no], :kind => :on_symbol } + if ":'" == tk[:text] or ':"' == tk[:text] + tk1 = get_string_tk(tk) + symbol_tk[:text] = tk1[:text] + symbol_tk[:state] = tk1[:state] + else + case (tk1 = get_squashed_tk)[:kind] + when :on_ident + symbol_tk[:text] = ":#{tk1[:text]}" + symbol_tk[:state] = tk1[:state] + when :on_tstring_content + symbol_tk[:text] = ":#{tk1[:text]}" + symbol_tk[:state] = get_squashed_tk[:state] # skip :on_tstring_end + when :on_tstring_end + symbol_tk[:text] = ":#{tk1[:text]}" + symbol_tk[:state] = tk1[:state] + when :on_op + symbol_tk[:text] = ":#{tk1[:text]}" + symbol_tk[:state] = tk1[:state] + when :on_ivar + symbol_tk[:text] = ":#{tk1[:text]}" + symbol_tk[:state] = tk1[:state] + when :on_cvar + symbol_tk[:text] = ":#{tk1[:text]}" + symbol_tk[:state] = tk1[:state] + when :on_gvar + symbol_tk[:text] = ":#{tk1[:text]}" + symbol_tk[:state] = tk1[:state] + when :on_const + symbol_tk[:text] = ":#{tk1[:text]}" + symbol_tk[:state] = tk1[:state] + when :on_kw + symbol_tk[:text] = ":#{tk1[:text]}" + symbol_tk[:state] = tk1[:state] + else + is_symbol = false + tk = tk1 + end + end + if is_symbol + tk = symbol_tk + end + tk + end + + private def get_string_tk(tk) + string = tk[:text] + state = nil + kind = :on_tstring + loop do + inner_str_tk = get_squashed_tk + if inner_str_tk.nil? + break + elsif :on_tstring_end == inner_str_tk[:kind] + string = string + inner_str_tk[:text] + state = inner_str_tk[:state] + break + elsif :on_label_end == inner_str_tk[:kind] + string = string + inner_str_tk[:text] + state = inner_str_tk[:state] + kind = :on_symbol + break + else + string = string + inner_str_tk[:text] + if :on_embexpr_beg == inner_str_tk[:kind] then + kind = :on_dstring if :on_tstring == kind + end + end + end + { + :line_no => tk[:line_no], + :char_no => tk[:char_no], + :kind => kind, + :text => string, + :state => state + } + end + + private def get_regexp_tk(tk) + string = tk[:text] + state = nil + loop do + inner_str_tk = get_squashed_tk + if inner_str_tk.nil? + break + elsif :on_regexp_end == inner_str_tk[:kind] + string = string + inner_str_tk[:text] + state = inner_str_tk[:state] + break + else + string = string + inner_str_tk[:text] + end + end + { + :line_no => tk[:line_no], + :char_no => tk[:char_no], + :kind => :on_regexp, + :text => string, + :state => state + } + end + + private def get_embdoc_tk(tk) + string = tk[:text] + until :on_embdoc_end == (embdoc_tk = get_squashed_tk)[:kind] do + string = string + embdoc_tk[:text] + end + string = string + embdoc_tk[:text] + { + :line_no => tk[:line_no], + :char_no => tk[:char_no], + :kind => :on_embdoc, + :text => string, + :state => embdoc_tk[:state] + } + end + + private def get_heredoc_tk(heredoc_name, indent) + string = '' + start_tk = nil + prev_tk = nil + until heredoc_end?(heredoc_name, indent, tk = @inner_lex_enumerator.next) do + start_tk = tk unless start_tk + if (prev_tk.nil? or "\n" == prev_tk[:text][-1]) and 0 != tk[:char_no] + string = string + (' ' * tk[:char_no]) + end + string = string + tk[:text] + prev_tk = tk + end + start_tk = tk unless start_tk + prev_tk = tk unless prev_tk + @buf.unshift tk # closing heredoc + heredoc_tk = { + :line_no => start_tk[:line_no], + :char_no => start_tk[:char_no], + :kind => :on_heredoc, + :text => string, + :state => prev_tk[:state] + } + @buf.unshift heredoc_tk + end + + private def retrieve_heredoc_info(tk) + name = tk[:text].gsub(/\A<<[-~]?(['"`]?)(.+)\1\z/, '\2') + indent = tk[:text] =~ /\A<<[-~]/ + [name, indent] + end + + private def heredoc_end?(name, indent, tk) + result = false + if :on_heredoc_end == tk[:kind] then + tk_name = (indent ? tk[:text].gsub(/^ *(.+)\n?$/, '\1') : tk[:text].gsub(/\n\z/, '')) + if name == tk_name + result = true + end + end + result + end + + private def get_words_tk(tk) + string = '' + start_token = tk[:text] + start_quote = tk[:text].rstrip[-1] + line_no = tk[:line_no] + char_no = tk[:char_no] + state = tk[:state] + end_quote = + case start_quote + when ?( then ?) + when ?[ then ?] + when ?{ then ?} + when ?< then ?> + else start_quote + end + end_token = nil + loop do + tk = get_squashed_tk + if tk.nil? + end_token = end_quote + break + elsif :on_tstring_content == tk[:kind] then + string += tk[:text] + elsif :on_words_sep == tk[:kind] or :on_tstring_end == tk[:kind] then + if end_quote == tk[:text].strip then + end_token = tk[:text] + break + else + string += tk[:text] + end + else + string += tk[:text] + end + end + text = "#{start_token}#{string}#{end_token}" + { + :line_no => line_no, + :char_no => char_no, + :kind => :on_dstring, + :text => text, + :state => state + } + end + + private def get_op_tk(tk) + redefinable_operators = %w[! != !~ % & * ** + +@ - -@ / < << <= <=> == === =~ > >= >> [] []= ^ ` | ~] + if redefinable_operators.include?(tk[:text]) and tk[:state] == EXPR_ARG then + @inner_lex.lex_state = EXPR_ARG unless RIPPER_HAS_LEX_STATE + tk[:state] = Ripper::Lexer.const_defined?(:State) ? Ripper::Lexer::State.new(EXPR_ARG) : EXPR_ARG + tk[:kind] = :on_ident + elsif tk[:text] =~ /^[-+]$/ then + tk_ahead = get_squashed_tk + case tk_ahead[:kind] + when :on_int, :on_float, :on_rational, :on_imaginary then + tk[:text] += tk_ahead[:text] + tk[:kind] = tk_ahead[:kind] + tk[:state] = tk_ahead[:state] + else + @buf.unshift tk_ahead + end + end + tk + end + + def initialize(code) + @buf = [] + @heredoc_queue = [] + @inner_lex = InnerStateLex.new(code) + @inner_lex_enumerator = Enumerator.new do |y| + @inner_lex.each do |tk| + y << tk + end + end + end + + def self.parse(code) + lex = self.new(code) + tokens = [] + begin + while tk = lex.get_squashed_tk + tokens.push tk + end + rescue StopIteration + end + tokens + end + + def self.end?(token) + (token[:state] & EXPR_END) + end +end diff --git a/lib/rdoc/parser/ruby.rb b/lib/rdoc/parser/ruby.rb new file mode 100644 index 0000000000..8599f655ad --- /dev/null +++ b/lib/rdoc/parser/ruby.rb @@ -0,0 +1,2232 @@ +# frozen_string_literal: true +## +# This file contains stuff stolen outright from: +# +# rtags.rb - +# ruby-lex.rb - ruby lexcal analyzer +# ruby-token.rb - ruby tokens +# by Keiju ISHITSUKA (Nippon Rational Inc.) +# + +$TOKEN_DEBUG ||= nil + +## +# Extracts code elements from a source file returning a TopLevel object +# containing the constituent file elements. +# +# This file is based on rtags +# +# RubyParser understands how to document: +# * classes +# * modules +# * methods +# * constants +# * aliases +# * private, public, protected +# * private_class_function, public_class_function +# * private_constant, public_constant +# * module_function +# * attr, attr_reader, attr_writer, attr_accessor +# * extra accessors given on the command line +# * metaprogrammed methods +# * require +# * include +# +# == Method Arguments +# +#-- +# NOTE: I don't think this works, needs tests, remove the paragraph following +# this block when known to work +# +# The parser extracts the arguments from the method definition. You can +# override this with a custom argument definition using the :args: directive: +# +# ## +# # This method tries over and over until it is tired +# +# def go_go_go(thing_to_try, tries = 10) # :args: thing_to_try +# puts thing_to_try +# go_go_go thing_to_try, tries - 1 +# end +# +# If you have a more-complex set of overrides you can use the :call-seq: +# directive: +#++ +# +# The parser extracts the arguments from the method definition. You can +# override this with a custom argument definition using the :call-seq: +# directive: +# +# ## +# # This method can be called with a range or an offset and length +# # +# # :call-seq: +# # my_method(Range) +# # my_method(offset, length) +# +# def my_method(*args) +# end +# +# The parser extracts +yield+ expressions from method bodies to gather the +# yielded argument names. If your method manually calls a block instead of +# yielding or you want to override the discovered argument names use +# the :yields: directive: +# +# ## +# # My method is awesome +# +# def my_method(&block) # :yields: happy, times +# block.call 1, 2 +# end +# +# == Metaprogrammed Methods +# +# To pick up a metaprogrammed method, the parser looks for a comment starting +# with '##' before an identifier: +# +# ## +# # This is a meta-programmed method! +# +# add_my_method :meta_method, :arg1, :arg2 +# +# The parser looks at the token after the identifier to determine the name, in +# this example, :meta_method. If a name cannot be found, a warning is printed +# and 'unknown is used. +# +# You can force the name of a method using the :method: directive: +# +# ## +# # :method: some_method! +# +# By default, meta-methods are instance methods. To indicate that a method is +# a singleton method instead use the :singleton-method: directive: +# +# ## +# # :singleton-method: +# +# You can also use the :singleton-method: directive with a name: +# +# ## +# # :singleton-method: some_method! +# +# You can define arguments for metaprogrammed methods via either the +# :call-seq:, :arg: or :args: directives. +# +# Additionally you can mark a method as an attribute by +# using :attr:, :attr_reader:, :attr_writer: or :attr_accessor:. Just like +# for :method:, the name is optional. +# +# ## +# # :attr_reader: my_attr_name +# +# == Hidden methods and attributes +# +# You can provide documentation for methods that don't appear using +# the :method:, :singleton-method: and :attr: directives: +# +# ## +# # :attr_writer: ghost_writer +# # There is an attribute here, but you can't see it! +# +# ## +# # :method: ghost_method +# # There is a method here, but you can't see it! +# +# ## +# # this is a comment for a regular method +# +# def regular_method() end +# +# Note that by default, the :method: directive will be ignored if there is a +# standard rdocable item following it. + +require 'ripper' + +class RDoc::Parser::Ruby < RDoc::Parser + + parse_files_matching(/\.rbw?$/) + + include RDoc::TokenStream + include RDoc::Parser::RubyTools + + ## + # RDoc::NormalClass type + + NORMAL = "::" + + ## + # RDoc::SingleClass type + + SINGLE = "<<" + + ## + # Creates a new Ruby parser. + + def initialize(top_level, file_name, content, options, stats) + super + + if /\t/ =~ content then + tab_width = @options.tab_width + content = content.split(/\n/).map do |line| + 1 while line.gsub!(/\t+/) { + ' ' * (tab_width*$&.length - $`.length % tab_width) + } && $~ + line + end.join("\n") + end + + @size = 0 + @token_listeners = nil + @scanner = RDoc::RipperStateLex.parse(content) + @content = content + @scanner_point = 0 + @prev_seek = nil + @markup = @options.markup + @track_visibility = :nodoc != @options.visibility + @encoding = @options.encoding + + reset + end + + def tk_nl?(tk) + :on_nl == tk[:kind] or :on_ignored_nl == tk[:kind] + end + + ## + # Retrieves the read token stream and replaces +pattern+ with +replacement+ + # using gsub. If the result is only a ";" returns an empty string. + + def get_tkread_clean pattern, replacement # :nodoc: + read = get_tkread.gsub(pattern, replacement).strip + return '' if read == ';' + read + end + + ## + # Extracts the visibility information for the visibility token +tk+ + # and +single+ class type identifier. + # + # Returns the visibility type (a string), the visibility (a symbol) and + # +singleton+ if the methods following should be converted to singleton + # methods. + + def get_visibility_information tk, single # :nodoc: + vis_type = tk[:text] + singleton = single == SINGLE + + vis = + case vis_type + when 'private' then :private + when 'protected' then :protected + when 'public' then :public + when 'private_class_method' then + singleton = true + :private + when 'public_class_method' then + singleton = true + :public + when 'module_function' then + singleton = true + :public + else + raise RDoc::Error, "Invalid visibility: #{tk.name}" + end + + return vis_type, vis, singleton + end + + ## + # Look for the first comment in a file that isn't a shebang line. + + def collect_first_comment + skip_tkspace + comment = ''.dup + comment = RDoc::Encoding.change_encoding comment, @encoding if @encoding + first_line = true + first_comment_tk_kind = nil + + tk = get_tk + + while tk && (:on_comment == tk[:kind] or :on_embdoc == tk[:kind]) + if first_line and tk[:text] =~ /\A#!/ then + skip_tkspace + tk = get_tk + elsif first_line and tk[:text] =~ /\A#\s*-\*-/ then + first_line = false + skip_tkspace + tk = get_tk + else + break if first_comment_tk_kind and not first_comment_tk_kind === tk[:kind] + first_comment_tk_kind = tk[:kind] + + first_line = false + comment << tk[:text] + tk = get_tk + + if :on_nl === tk then + skip_tkspace false + tk = get_tk + end + end + end + + unget_tk tk + + new_comment comment + end + + ## + # Consumes trailing whitespace from the token stream + + def consume_trailing_spaces # :nodoc: + skip_tkspace false + end + + ## + # Creates a new attribute in +container+ with +name+. + + def create_attr container, single, name, rw, comment # :nodoc: + att = RDoc::Attr.new get_tkread, name, rw, comment, single == SINGLE + record_location att + + container.add_attribute att + @stats.add_attribute att + + att + end + + ## + # Creates a module alias in +container+ at +rhs_name+ (or at the top-level + # for "::") with the name from +constant+. + + def create_module_alias container, constant, rhs_name # :nodoc: + mod = if rhs_name =~ /^::/ then + @store.find_class_or_module rhs_name + else + container.find_module_named rhs_name + end + + container.add_module_alias mod, constant.name, @top_level if mod + end + + ## + # Aborts with +msg+ + + def error(msg) + msg = make_message msg + + abort msg + end + + ## + # Looks for a true or false token. + + def get_bool + skip_tkspace + tk = get_tk + if :on_kw == tk[:kind] && 'true' == tk[:text] + true + elsif :on_kw == tk[:kind] && ('false' == tk[:text] || 'nil' == tk[:text]) + false + else + unget_tk tk + true + end + end + + ## + # Look for the name of a class of module (optionally with a leading :: or + # with :: separated named) and return the ultimate name, the associated + # container, and the given name (with the ::). + + def get_class_or_module container, ignore_constants = false + skip_tkspace + name_t = get_tk + given_name = ''.dup + + # class ::A -> A is in the top level + if :on_op == name_t[:kind] and '::' == name_t[:text] then # bug + name_t = get_tk + container = @top_level + given_name << '::' + end + + skip_tkspace false + given_name << name_t[:text] + + is_self = name_t[:kind] == :on_op && name_t[:text] == '<<' + while !is_self && (tk = peek_tk) and :on_op == tk[:kind] and '::' == tk[:text] do + prev_container = container + container = container.find_module_named name_t[:text] + container ||= + if ignore_constants then + RDoc::Context.new + else + c = prev_container.add_module RDoc::NormalModule, name_t[:text] + c.ignore unless prev_container.document_children + @top_level.add_to_classes_or_modules c + c + end + + record_location container + + get_tk + skip_tkspace false + name_t = get_tk + unless :on_const == name_t[:kind] || :on_ident == name_t[:kind] + raise RDoc::Error, "Invalid class or module definition: #{given_name}" + end + if prev_container == container and !ignore_constants + given_name = name_t[:text] + else + given_name << '::' + name_t[:text] + end + end + + skip_tkspace false + + return [container, name_t, given_name] + end + + ## + # Return a superclass, which can be either a constant of an expression + + def get_class_specification + tk = peek_tk + if tk.nil? + return '' + elsif :on_kw == tk[:kind] && 'self' == tk[:text] + return 'self' + elsif :on_gvar == tk[:kind] + return '' + end + + res = get_constant + + skip_tkspace false + + get_tkread # empty out read buffer + + tk = get_tk + return res unless tk + + case tk[:kind] + when :on_nl, :on_comment, :on_embdoc, :on_semicolon then + unget_tk(tk) + return res + end + + res += parse_call_parameters(tk) + res + end + + ## + # 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 + + while tk && ((:on_op == tk[:kind] && '::' == tk[:text]) || :on_const == tk[:kind]) do + res += tk[:text] + tk = get_tk + 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 :on_lparen == (tk = peek_tk)[:kind] do + get_tk + skip_tkspace + nest += 1 + end + + name = get_constant + + while nest > 0 + skip_tkspace + tk = get_tk + nest -= 1 if :on_rparen == tk[:kind] + end + + name + 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" + + def get_end_token tk # :nodoc: + case tk[:kind] + when :on_lparen + { + :kind => :on_rparen, + :text => ')' + } + when :on_rparen + nil + else + { + :kind => :on_nl, + :text => "\n" + } + end + end + + ## + # Retrieves the method container for a singleton method. + + def get_method_container container, name_t # :nodoc: + prev_container = container + container = container.find_module_named(name_t[:text]) + + unless container then + constant = prev_container.constants.find do |const| + const.name == name_t[:text] + end + + if constant then + parse_method_dummy prev_container + return + end + end + + unless container then + # TODO seems broken, should starting at Object in @store + obj = name_t[:text].split("::").inject(Object) do |state, item| + state.const_get(item) + end rescue nil + + type = obj.class == Class ? RDoc::NormalClass : RDoc::NormalModule + + unless [Class, Module].include?(obj.class) then + warn("Couldn't find #{name_t[:text]}. Assuming it's a module") + end + + if type == RDoc::NormalClass then + sclass = obj.superclass ? obj.superclass.name : nil + container = prev_container.add_class type, name_t[:text], sclass + else + container = prev_container.add_module type, name_t[:text] + end + + record_location container + end + + container + end + + ## + # Extracts a name or symbol from the token stream. + + def get_symbol_or_name + tk = get_tk + case tk[:kind] + when :on_symbol then + text = tk[:text].sub(/^:/, '') + + next_tk = peek_tk + if next_tk && :on_op == next_tk[:kind] && '=' == next_tk[:text] then + get_tk + text << '=' + end + + text + when :on_ident, :on_const, :on_gvar, :on_cvar, :on_ivar, :on_op, :on_kw then + tk[:text] + when :on_tstring, :on_dstring then + tk[:text][1..-2] + else + raise RDoc::Error, "Name or symbol expected (got #{tk})" + end + end + + ## + # Marks containers between +container+ and +ancestor+ as ignored + + def suppress_parents container, ancestor # :nodoc: + while container and container != ancestor do + container.suppress unless container.documented? + container = container.parent + end + end + + ## + # Look for directives in a normal comment block: + # + # # :stopdoc: + # # Don't display comment from this point forward + # + # This routine modifies its +comment+ parameter. + + def look_for_directives_in container, comment + @preprocess.handle comment, container do |directive, param| + case directive + when 'method', 'singleton-method', + 'attr', 'attr_accessor', 'attr_reader', 'attr_writer' then + false # handled elsewhere + when 'section' then + break unless container.kind_of?(RDoc::Context) + container.set_current_section param, comment.dup + comment.text = '' + break + end + end + + comment.remove_private + end + + ## + # Adds useful info about the parser to +message+ + + def make_message message + prefix = "#{@file_name}:".dup + + tk = peek_tk + prefix << "#{tk[:line_no]}:#{tk[:char_no]}:" if tk + + "#{prefix} #{message}" + end + + ## + # Creates a comment with the correct format + + def new_comment comment + c = RDoc::Comment.new comment, @top_level + c.format = @markup + c + end + + ## + # Creates an RDoc::Attr for the name following +tk+, setting the comment to + # +comment+. + + def parse_attr(context, single, tk, comment) + line_no = tk[:line_no] + + args = parse_symbol_arg 1 + if args.size > 0 then + name = args[0] + rw = "R" + skip_tkspace false + tk = get_tk + + if :on_comma == tk[:kind] then + rw = "RW" if get_bool + else + unget_tk tk + end + + att = create_attr context, single, name, rw, comment + att.line = line_no + + read_documentation_modifiers att, RDoc::ATTR_MODIFIERS + else + warn "'attr' ignored - looks like a variable" + end + end + + ## + # Creates an RDoc::Attr for each attribute listed after +tk+, setting the + # comment for each to +comment+. + + def parse_attr_accessor(context, single, tk, comment) + line_no = tk[:line_no] + + args = parse_symbol_arg + rw = "?" + + tmp = RDoc::CodeObject.new + read_documentation_modifiers tmp, RDoc::ATTR_MODIFIERS + # TODO In most other places we let the context keep track of document_self + # and add found items appropriately but here we do not. I'm not sure why. + return if @track_visibility and not tmp.document_self + + case tk[:text] + when "attr_reader" then rw = "R" + when "attr_writer" then rw = "W" + when "attr_accessor" then rw = "RW" + else + rw = '?' + end + + for name in args + att = create_attr context, single, name, rw, comment + att.line = line_no + end + end + + ## + # Parses an +alias+ in +context+ with +comment+ + + def parse_alias(context, single, tk, comment) + line_no = tk[:line_no] + + skip_tkspace + + if :on_lparen === peek_tk[:kind] then + get_tk + skip_tkspace + end + + new_name = get_symbol_or_name + + skip_tkspace + if :on_comma === peek_tk[:kind] then + get_tk + skip_tkspace + end + + begin + old_name = get_symbol_or_name + rescue RDoc::Error + return + end + + al = RDoc::Alias.new(get_tkread, old_name, new_name, comment, + single == SINGLE) + record_location al + al.line = line_no + + read_documentation_modifiers al, RDoc::ATTR_MODIFIERS + context.add_alias al + @stats.add_alias al + + al + end + + ## + # Extracts call parameters from the token stream. + + def parse_call_parameters(tk) + end_token = case tk[:kind] + when :on_lparen + :on_rparen + when :on_rparen + return "" + else + :on_nl + end + nest = 0 + + loop do + break if tk.nil? + case tk[:kind] + when :on_semicolon + break + when :on_lparen + nest += 1 + when end_token + if end_token == :on_rparen + nest -= 1 + break if RDoc::RipperStateLex.end?(tk) and nest <= 0 + else + break if RDoc::RipperStateLex.end?(tk) + end + when :on_comment, :on_embdoc + unget_tk(tk) + break + when :on_op + if tk[:text] =~ /^(.{1,2})?=$/ + unget_tk(tk) + break + end + end + tk = get_tk + end + + get_tkread_clean "\n", " " + end + + ## + # Parses a class in +context+ with +comment+ + + def parse_class container, single, tk, comment + line_no = tk[:line_no] + + declaration_context = container + container, name_t, given_name = get_class_or_module container + + if name_t[:kind] == :on_const + cls = parse_class_regular container, declaration_context, single, + name_t, given_name, comment + elsif name_t[:kind] == :on_op && name_t[:text] == '<<' + case name = get_class_specification + when 'self', container.name + read_documentation_modifiers cls, RDoc::CLASS_MODIFIERS + parse_statements container, SINGLE + return # don't update line + else + cls = parse_class_singleton container, name, comment + end + else + warn "Expected class name or '<<'. Got #{name_t[:kind]}: #{name_t[:text].inspect}" + return + end + + cls.line = line_no + + # after end modifiers + read_documentation_modifiers cls, RDoc::CLASS_MODIFIERS + + cls + end + + ## + # Parses and creates a regular class + + def parse_class_regular container, declaration_context, single, # :nodoc: + name_t, given_name, comment + superclass = '::Object' + + if given_name =~ /^::/ then + declaration_context = @top_level + given_name = $' + end + + tk = peek_tk + if tk[:kind] == :on_op && tk[:text] == '<' then + get_tk + skip_tkspace + superclass = get_class_specification + superclass = '(unknown)' if superclass.empty? + end + + cls_type = single == SINGLE ? RDoc::SingleClass : RDoc::NormalClass + cls = declaration_context.add_class cls_type, given_name, superclass + cls.ignore unless container.document_children + + read_documentation_modifiers cls, RDoc::CLASS_MODIFIERS + record_location cls + + cls.add_comment comment, @top_level + + @top_level.add_to_classes_or_modules cls + @stats.add_class cls + + suppress_parents container, declaration_context unless cls.document_self + + parse_statements cls + + cls + end + + ## + # Parses a singleton class in +container+ with the given +name+ and + # +comment+. + + def parse_class_singleton container, name, comment # :nodoc: + other = @store.find_class_named name + + unless other then + if name =~ /^::/ then + name = $' + container = @top_level + end + + other = container.add_module RDoc::NormalModule, name + record_location other + + # class << $gvar + other.ignore if name.empty? + + other.add_comment comment, @top_level + end + + # notify :nodoc: all if not a constant-named class/module + # (and remove any comment) + unless name =~ /\A(::)?[A-Z]/ then + other.document_self = nil + other.document_children = false + other.clear_comment + end + + @top_level.add_to_classes_or_modules other + @stats.add_class other + + read_documentation_modifiers other, RDoc::CLASS_MODIFIERS + parse_statements(other, SINGLE) + + other + end + + ## + # Parses a constant in +context+ with +comment+. If +ignore_constants+ is + # true, no found constants will be added to RDoc. + + def parse_constant container, tk, comment, ignore_constants = false + line_no = tk[:line_no] + + name = tk[:text] + skip_tkspace false + + return unless name =~ /^\w+$/ + + if :on_op == peek_tk[:kind] && '::' == peek_tk[:text] then + unget_tk tk + + container, name_t, = get_class_or_module container, ignore_constants + + name = name_t[:text] + end + + is_array_or_hash = false + if peek_tk && :on_lbracket == peek_tk[:kind] + get_tk + nest = 1 + while bracket_tk = get_tk + case bracket_tk[:kind] + when :on_lbracket + nest += 1 + when :on_rbracket + nest -= 1 + break if nest == 0 + end + end + skip_tkspace false + is_array_or_hash = true + end + + unless peek_tk && :on_op == peek_tk[:kind] && '=' == peek_tk[:text] then + return false + end + get_tk + + value = '' + con = RDoc::Constant.new name, value, comment + + body = parse_constant_body container, con, is_array_or_hash + + return unless body + + con.value = body + record_location con + con.line = line_no + read_documentation_modifiers con, RDoc::CONSTANT_MODIFIERS + + return if is_array_or_hash + + @stats.add_constant con + container.add_constant con + + true + end + + def parse_constant_body container, constant, is_array_or_hash # :nodoc: + nest = 0 + rhs_name = ''.dup + + get_tkread + + tk = get_tk + + body = nil + loop do + break if tk.nil? + if :on_semicolon == tk[:kind] then + break if nest <= 0 + elsif [:on_tlambeg, :on_lparen, :on_lbrace, :on_lbracket].include?(tk[:kind]) then + nest += 1 + elsif (:on_kw == tk[:kind] && 'def' == tk[:text]) then + nest += 1 + elsif (:on_kw == tk[:kind] && %w{do if unless case begin}.include?(tk[:text])) then + if (tk[:state] & RDoc::RipperStateLex::EXPR_LABEL) == 0 + nest += 1 + end + elsif [:on_rparen, :on_rbrace, :on_rbracket].include?(tk[:kind]) || + (:on_kw == tk[:kind] && 'end' == tk[:text]) then + nest -= 1 + elsif (:on_comment == tk[:kind] or :on_embdoc == tk[:kind]) then + unget_tk tk + if nest <= 0 and RDoc::RipperStateLex.end?(tk) then + body = get_tkread_clean(/^[ \t]+/, '') + read_documentation_modifiers constant, RDoc::CONSTANT_MODIFIERS + break + else + read_documentation_modifiers constant, RDoc::CONSTANT_MODIFIERS + end + elsif :on_const == tk[:kind] then + rhs_name << tk[:text] + + next_tk = peek_tk + if nest <= 0 and (next_tk.nil? || :on_nl == next_tk[:kind]) then + create_module_alias container, constant, rhs_name unless is_array_or_hash + break + end + elsif :on_nl == tk[:kind] then + if nest <= 0 and RDoc::RipperStateLex.end?(tk) then + unget_tk tk + break + end + elsif :on_op == tk[:kind] && '::' == tk[:text] + rhs_name << '::' + end + tk = get_tk + end + + body ? body : get_tkread_clean(/^[ \t]+/, '') + end + + ## + # Generates an RDoc::Method or RDoc::Attr from +comment+ by looking for + # :method: or :attr: directives in +comment+. + + def parse_comment container, tk, comment + return parse_comment_tomdoc container, tk, comment if @markup == 'tomdoc' + column = tk[:char_no] + line_no = tk[:line_no] + + comment.text = comment.text.sub(/(^# +:?)(singleton-)(method:)/, '\1\3') + singleton = !!$~ + + co = + if (comment.text = comment.text.sub(/^# +:?method: *(\S*).*?\n/i, '')) && !!$~ then + parse_comment_ghost container, comment.text, $1, column, line_no, comment + elsif (comment.text = comment.text.sub(/# +:?(attr(_reader|_writer|_accessor)?): *(\S*).*?\n/i, '')) && !!$~ then + parse_comment_attr container, $1, $3, comment + end + + if co then + co.singleton = singleton + co.line = line_no + end + + true + end + + ## + # Parse a comment that is describing an attribute in +container+ with the + # given +name+ and +comment+. + + def parse_comment_attr container, type, name, comment # :nodoc: + return if name.empty? + + rw = case type + when 'attr_reader' then 'R' + when 'attr_writer' then 'W' + else 'RW' + end + + create_attr container, NORMAL, name, rw, comment + end + + def parse_comment_ghost container, text, name, column, line_no, # :nodoc: + comment + name = nil if name.empty? + + meth = RDoc::GhostMethod.new get_tkread, name + record_location meth + + meth.start_collecting_tokens + indent = { :line_no => 1, :char_no => 1, :kind => :on_sp, :text => ' ' * column } + position_comment = { :line_no => line_no, :char_no => 1, :kind => :on_comment } + position_comment[:text] = "# File #{@top_level.relative_name}, line #{line_no}" + newline = { :line_no => 0, :char_no => 0, :kind => :on_nl, :text => "\n" } + meth.add_tokens [position_comment, newline, indent] + + meth.params = + if text.sub!(/^#\s+:?args?:\s*(.*?)\s*$/i, '') then + $1 + else + '' + end + + comment.normalize + comment.extract_call_seq meth + + return unless meth.name + + container.add_method meth + + meth.comment = comment + + @stats.add_method meth + + meth + end + + ## + # Creates an RDoc::Method on +container+ from +comment+ if there is a + # Signature section in the comment + + def parse_comment_tomdoc container, tk, comment + return unless signature = RDoc::TomDoc.signature(comment) + column = tk[:char_no] + line_no = tk[:line_no] + + name, = signature.split %r%[ \(]%, 2 + + meth = RDoc::GhostMethod.new get_tkread, name + record_location meth + meth.line = line_no + + meth.start_collecting_tokens + indent = { :line_no => 1, :char_no => 1, :kind => :on_sp, :text => ' ' * column } + position_comment = { :line_no => line_no, :char_no => 1, :kind => :on_comment } + position_comment[:text] = "# File #{@top_level.relative_name}, line #{line_no}" + newline = { :line_no => 0, :char_no => 0, :kind => :on_nl, :text => "\n" } + meth.add_tokens [position_comment, newline, indent] + + meth.call_seq = signature + + comment.normalize + + return unless meth.name + + container.add_method meth + + meth.comment = comment + + @stats.add_method meth + end + + ## + # Parses an +include+ or +extend+, indicated by the +klass+ and adds it to + # +container+ # with +comment+ + + def parse_extend_or_include klass, container, comment # :nodoc: + loop do + skip_tkspace_comment + + name = get_constant_with_optional_parens + + unless name.empty? then + obj = container.add klass, name, comment + record_location obj + end + + return if peek_tk.nil? || :on_comma != peek_tk[:kind] + + get_tk + end + end + + ## + # Parses identifiers that can create new methods or change visibility. + # + # Returns true if the comment was not consumed. + + def parse_identifier container, single, tk, comment # :nodoc: + case tk[:text] + when 'private', 'protected', 'public', 'private_class_method', + 'public_class_method', 'module_function' then + parse_visibility container, single, tk + return true + when 'private_constant', 'public_constant' + parse_constant_visibility container, single, tk + return true + when 'attr' then + parse_attr container, single, tk, comment + when /^attr_(reader|writer|accessor)$/ then + parse_attr_accessor container, single, tk, comment + when 'alias_method' then + parse_alias container, single, tk, comment + when 'require', 'include' then + # ignore + else + if comment.text =~ /\A#\#$/ then + case comment.text + when /^# +:?attr(_reader|_writer|_accessor)?:/ then + parse_meta_attr container, single, tk, comment + else + method = parse_meta_method container, single, tk, comment + method.params = container.params if + container.params + method.block_params = container.block_params if + container.block_params + end + end + end + + false + end + + ## + # Parses a meta-programmed attribute and creates an RDoc::Attr. + # + # To create foo and bar attributes on class C with comment "My attributes": + # + # class C + # + # ## + # # :attr: + # # + # # My attributes + # + # my_attr :foo, :bar + # + # end + # + # To create a foo attribute on class C with comment "My attribute": + # + # class C + # + # ## + # # :attr: foo + # # + # # My attribute + # + # my_attr :foo, :bar + # + # end + + def parse_meta_attr(context, single, tk, comment) + args = parse_symbol_arg + rw = "?" + + # If nodoc is given, don't document any of them + + tmp = RDoc::CodeObject.new + read_documentation_modifiers tmp, RDoc::ATTR_MODIFIERS + + regexp = /^# +:?(attr(_reader|_writer|_accessor)?): *(\S*).*?\n/i + if regexp =~ comment.text then + comment.text = comment.text.sub(regexp, '') + rw = case $1 + when 'attr_reader' then 'R' + when 'attr_writer' then 'W' + else 'RW' + end + name = $3 unless $3.empty? + end + + if name then + att = create_attr context, single, name, rw, comment + else + args.each do |attr_name| + att = create_attr context, single, attr_name, rw, comment + end + end + + att + end + + ## + # Parses a meta-programmed method + + def parse_meta_method(container, single, tk, comment) + column = tk[:char_no] + line_no = tk[:line_no] + + start_collecting_tokens + add_token tk + add_token_listener self + + skip_tkspace false + + comment.text = comment.text.sub(/(^# +:?)(singleton-)(method:)/, '\1\3') + singleton = !!$~ + + name = parse_meta_method_name comment, tk + + return unless name + + meth = RDoc::MetaMethod.new get_tkread, name + record_location meth + meth.line = line_no + meth.singleton = singleton + + remove_token_listener self + + meth.start_collecting_tokens + indent = { :line_no => 1, :char_no => 1, :kind => :on_sp, :text => ' ' * column } + position_comment = { :line_no => line_no, :char_no => 1, :kind => :on_comment } + position_comment[:text] = "# File #{@top_level.relative_name}, line #{line_no}" + newline = { :line_no => 0, :char_no => 0, :kind => :on_nl, :text => "\n" } + meth.add_tokens [position_comment, newline, indent] + meth.add_tokens @token_stream + + parse_meta_method_params container, single, meth, tk, comment + + meth.comment = comment + + @stats.add_method meth + + meth + end + + ## + # Parses the name of a metaprogrammed method. +comment+ is used to + # determine the name while +tk+ is used in an error message if the name + # cannot be determined. + + def parse_meta_method_name comment, tk # :nodoc: + if comment.text.sub!(/^# +:?method: *(\S*).*?\n/i, '') then + return $1 unless $1.empty? + end + + name_t = get_tk + + if :on_symbol == name_t[:kind] then + name_t[:text][1..-1] + elsif :on_tstring == name_t[:kind] then + name_t[:text][1..-2] + elsif :on_op == name_t[:kind] && '=' == name_t[:text] then # ignore + remove_token_listener self + + nil + else + warn "unknown name token #{name_t.inspect} for meta-method '#{tk[:text]}'" + 'unknown' + end + end + + ## + # Parses the parameters and block for a meta-programmed method. + + def parse_meta_method_params container, single, meth, tk, comment # :nodoc: + token_listener meth do + meth.params = '' + + look_for_directives_in meth, comment + comment.normalize + comment.extract_call_seq meth + + container.add_method meth + + last_tk = tk + + while tk = get_tk do + if :on_semicolon == tk[:kind] then + break + elsif :on_nl == tk[:kind] then + break unless last_tk and :on_comma == last_tk[:kind] + elsif :on_sp == tk[:kind] then + # expression continues + elsif :on_kw == tk[:kind] && 'do' == tk[:text] then + parse_statements container, single, meth + break + else + last_tk = tk + end + end + end + end + + ## + # Parses a normal method defined by +def+ + + def parse_method(container, single, tk, comment) + singleton = nil + added_container = false + name = nil + column = tk[:char_no] + line_no = tk[:line_no] + + start_collecting_tokens + add_token tk + + token_listener self do + prev_container = container + name, container, singleton = parse_method_name container + added_container = container != prev_container + end + + return unless name + + meth = RDoc::AnyMethod.new get_tkread, name + look_for_directives_in meth, comment + meth.singleton = single == SINGLE ? true : singleton + + record_location meth + meth.line = line_no + + meth.start_collecting_tokens + indent = { :line_no => 1, :char_no => 1, :kind => :on_sp, :text => ' ' * column } + token = { :line_no => line_no, :char_no => 1, :kind => :on_comment } + token[:text] = "# File #{@top_level.relative_name}, line #{line_no}" + newline = { :line_no => 0, :char_no => 0, :kind => :on_nl, :text => "\n" } + meth.add_tokens [token, newline, indent] + meth.add_tokens @token_stream + + parse_method_params_and_body container, single, meth, added_container + + comment.normalize + comment.extract_call_seq meth + + meth.comment = comment + + # after end modifiers + read_documentation_modifiers meth, RDoc::METHOD_MODIFIERS + + @stats.add_method meth + end + + ## + # Parses the parameters and body of +meth+ + + def parse_method_params_and_body container, single, meth, added_container + token_listener meth do + parse_method_parameters meth + + if meth.document_self or not @track_visibility then + container.add_method meth + elsif added_container then + container.document_self = false + end + + # Having now read the method parameters and documentation modifiers, we + # now know whether we have to rename #initialize to ::new + + if meth.name == "initialize" && !meth.singleton then + if meth.dont_rename_initialize then + meth.visibility = :protected + else + meth.singleton = true + meth.name = "new" + meth.visibility = :public + end + end + + parse_statements container, single, meth + end + end + + ## + # Parses a method that needs to be ignored. + + def parse_method_dummy container + dummy = RDoc::Context.new + dummy.parent = container + dummy.store = container.store + skip_method dummy + end + + ## + # Parses the name of a method in +container+. + # + # Returns the method name, the container it is in (for def Foo.name) and if + # it is a singleton or regular method. + + def parse_method_name container # :nodoc: + skip_tkspace + name_t = get_tk + back_tk = skip_tkspace(false) + singleton = false + + dot = get_tk + if dot[:kind] == :on_period || (dot[:kind] == :on_op && dot[:text] == '::') then + singleton = true + + name, container = parse_method_name_singleton container, name_t + else + unget_tk dot + back_tk.reverse_each do |token| + unget_tk token + end + + name = parse_method_name_regular container, name_t + end + + return name, container, singleton + end + + ## + # For the given +container+ and initial name token +name_t+ the method name + # is parsed from the token stream for a regular method. + + def parse_method_name_regular container, name_t # :nodoc: + if :on_op == name_t[:kind] && (%w{* & [] []= <<}.include?(name_t[:text])) then + name_t[:text] + else + unless [:on_kw, :on_const, :on_ident].include?(name_t[:kind]) then + warn "expected method name token, . or ::, got #{name_t.inspect}" + skip_method container + return + end + name_t[:text] + end + end + + ## + # For the given +container+ and initial name token +name_t+ the method name + # and the new +container+ (if necessary) are parsed from the token stream + # for a singleton method. + + def parse_method_name_singleton container, name_t # :nodoc: + skip_tkspace + name_t2 = get_tk + + if (:on_kw == name_t[:kind] && 'self' == name_t[:text]) || (:on_op == name_t[:kind] && '%' == name_t[:text]) then + # NOTE: work around '[' being consumed early + if :on_lbracket == name_t2[:kind] + get_tk + name = '[]' + else + name = name_t2[:text] + end + elsif :on_const == name_t[:kind] then + name = name_t2[:text] + + container = get_method_container container, name_t + + return unless container + + name + elsif :on_ident == name_t[:kind] || :on_ivar == name_t[:kind] || :on_gvar == name_t[:kind] then + parse_method_dummy container + + name = nil + elsif (:on_kw == name_t[:kind]) && ('true' == name_t[:text] || 'false' == name_t[:text] || 'nil' == name_t[:text]) then + klass_name = "#{name_t[:text].capitalize}Class" + container = @store.find_class_named klass_name + container ||= @top_level.add_class RDoc::NormalClass, klass_name + + name = name_t2[:text] + else + warn "unexpected method name token #{name_t.inspect}" + # break + skip_method container + + name = nil + end + + return name, container + end + + ## + # Extracts +yield+ parameters from +method+ + + def parse_method_or_yield_parameters(method = nil, + modifiers = RDoc::METHOD_MODIFIERS) + skip_tkspace false + tk = get_tk + end_token = get_end_token tk + return '' unless end_token + + nest = 0 + continue = false + + while tk != nil do + case tk[:kind] + when :on_semicolon then + break if nest == 0 + when :on_lbrace then + nest += 1 + when :on_rbrace then + nest -= 1 + if nest <= 0 + # we might have a.each { |i| yield i } + unget_tk(tk) if nest < 0 + break + end + when :on_lparen then + nest += 1 + when end_token[:kind] then + if end_token[:kind] == :on_rparen + nest -= 1 + break if nest <= 0 + else + break + end + when :on_rparen then + nest -= 1 + when :on_comment, :on_embdoc then + @read.pop + if :on_nl == end_token[:kind] and "\n" == tk[:text][-1] and + (!continue or (tk[:state] & RDoc::RipperStateLex::EXPR_LABEL) != 0) then + if method && method.block_params.nil? then + unget_tk tk + read_documentation_modifiers method, modifiers + end + break if !continue and nest <= 0 + end + when :on_comma then + continue = true + when :on_ident then + continue = false if continue + end + tk = get_tk + end + + get_tkread_clean(/\s+/, ' ') + 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 =~ /\A\(/ + method.params = res unless method.params + + return if method.block_params + + skip_tkspace false + read_documentation_modifiers method, RDoc::METHOD_MODIFIERS + end + + ## + # Parses an RDoc::NormalModule in +container+ with +comment+ + + def parse_module container, single, tk, comment + container, name_t, = get_class_or_module container + + name = name_t[:text] + + mod = container.add_module RDoc::NormalModule, name + mod.ignore unless container.document_children + record_location mod + + read_documentation_modifiers mod, RDoc::CLASS_MODIFIERS + mod.add_comment comment, @top_level + parse_statements mod + + # after end modifiers + read_documentation_modifiers mod, RDoc::CLASS_MODIFIERS + + @stats.add_module mod + end + + ## + # Parses an RDoc::Require in +context+ containing +comment+ + + def parse_require(context, comment) + skip_tkspace_comment + tk = get_tk + + if :on_lparen == tk[:kind] then + skip_tkspace_comment + tk = get_tk + end + + name = tk[:text][1..-2] if :on_tstring == tk[:kind] + + if name then + @top_level.add_require RDoc::Require.new(name, comment) + else + unget_tk tk + end + end + + ## + # Parses a rescue + + def parse_rescue + skip_tkspace false + + while tk = get_tk + case tk[:kind] + when :on_nl, :on_semicolon, :on_comment then + break + when :on_comma then + skip_tkspace false + + get_tk if :on_nl == peek_tk[:kind] + end + + skip_tkspace false + end + end + + ## + # The core of the Ruby parser. + + def parse_statements(container, single = NORMAL, current_method = nil, + comment = new_comment('')) + raise 'no' unless RDoc::Comment === comment + comment = RDoc::Encoding.change_encoding comment, @encoding if @encoding + + nest = 1 + save_visibility = container.visibility + + non_comment_seen = true + + while tk = get_tk do + keep_comment = false + try_parse_comment = false + + non_comment_seen = true unless (:on_comment == tk[:kind] or :on_embdoc == tk[:kind]) + + case tk[:kind] + when :on_nl, :on_ignored_nl, :on_comment, :on_embdoc then + if :on_nl == tk[:kind] or :on_ignored_nl == tk[:kind] + skip_tkspace + tk = get_tk + else + past_tokens = @read.size > 1 ? @read[0..-2] : [] + nl_position = 0 + past_tokens.reverse.each_with_index do |read_tk, i| + if read_tk =~ /^\n$/ then + nl_position = (past_tokens.size - 1) - i + break + elsif read_tk =~ /^#.*\n$/ then + nl_position = ((past_tokens.size - 1) - i) + 1 + break + end + end + comment_only_line = past_tokens[nl_position..-1].all?{ |c| c =~ /^\s+$/ } + unless comment_only_line then + tk = get_tk + end + end + + if tk and (:on_comment == tk[:kind] or :on_embdoc == tk[:kind]) then + if non_comment_seen then + # Look for RDoc in a comment about to be thrown away + non_comment_seen = parse_comment container, tk, comment unless + comment.empty? + + comment = '' + comment = RDoc::Encoding.change_encoding comment, @encoding if @encoding + end + + while tk and (:on_comment == tk[:kind] or :on_embdoc == tk[:kind]) do + comment += tk[:text] + comment += "\n" unless "\n" == tk[:text].chars.to_a.last + + if tk[:text].size > 1 && "\n" == tk[:text].chars.to_a.last then + skip_tkspace false # leading spaces + end + tk = get_tk + end + + comment = new_comment comment + + unless comment.empty? then + look_for_directives_in container, comment + + if container.done_documenting then + throw :eof if RDoc::TopLevel === container + container.ongoing_visibility = save_visibility + end + end + + keep_comment = true + else + non_comment_seen = true + end + + unget_tk tk + keep_comment = true + container.current_line_visibility = nil + + when :on_kw then + case tk[:text] + when 'class' then + parse_class container, single, tk, comment + + when 'module' then + parse_module container, single, tk, comment + + when 'def' then + parse_method container, single, tk, comment + + when 'alias' then + parse_alias container, single, tk, comment unless current_method + + when 'yield' then + if current_method.nil? then + warn "Warning: yield outside of method" if container.document_self + else + parse_yield container, single, tk, current_method + end + + when 'until', 'while' then + if (tk[:state] & RDoc::RipperStateLex::EXPR_LABEL) == 0 + nest += 1 + skip_optional_do_after_expression + end + + # Until and While can have a 'do', which shouldn't increase 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. + + # 'for' is trickier + when 'for' then + nest += 1 + skip_for_variable + skip_optional_do_after_expression + + when 'case', 'do', 'if', 'unless', 'begin' then + if (tk[:state] & RDoc::RipperStateLex::EXPR_LABEL) == 0 + nest += 1 + end + + when 'super' then + current_method.calls_super = true if current_method + + when 'rescue' then + parse_rescue + + when 'end' then + nest -= 1 + if nest == 0 then + container.ongoing_visibility = save_visibility + + parse_comment container, tk, comment unless comment.empty? + + return + end + end + + when :on_const then + unless parse_constant container, tk, comment, current_method then + try_parse_comment = true + end + + when :on_ident then + if nest == 1 and current_method.nil? then + keep_comment = parse_identifier container, single, tk, comment + end + + case tk[:text] + when "require" then + parse_require container, comment + when "include" then + parse_extend_or_include RDoc::Include, container, comment + when "extend" then + parse_extend_or_include RDoc::Extend, container, comment + end + + else + try_parse_comment = nest == 1 + end + + if try_parse_comment then + non_comment_seen = parse_comment container, tk, comment unless + comment.empty? + + keep_comment = false + end + + unless keep_comment then + comment = new_comment '' + comment = RDoc::Encoding.change_encoding comment, @encoding if @encoding + container.params = nil + container.block_params = nil + end + + consume_trailing_spaces + end + + container.params = nil + container.block_params = nil + end + + ## + # Parse up to +no+ symbol arguments + + def parse_symbol_arg(no = nil) + skip_tkspace_comment + + tk = get_tk + if tk[:kind] == :on_lparen + parse_symbol_arg_paren no + else + parse_symbol_arg_space no, tk + end + end + + ## + # Parses up to +no+ symbol arguments surrounded by () and places them in + # +args+. + + def parse_symbol_arg_paren no # :nodoc: + args = [] + + 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)[:kind] + when :on_rparen + break + when :on_comma + else + warn("unexpected token: '#{tk2.inspect}'") if $DEBUG_RDOC + break + end + end + + args + end + + ## + # Parses up to +no+ symbol arguments separated by spaces and places them in + # +args+. + + def parse_symbol_arg_space no, tk # :nodoc: + args = [] + + unget_tk tk + if tk = parse_symbol_in_arg + args.push tk + return args if no and args.size >= no + end + + loop do + skip_tkspace false + + tk1 = get_tk + if tk1.nil? || :on_comma != tk1[:kind] then + 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 + + ## + # Returns symbol text from the next token + + def parse_symbol_in_arg + tk = get_tk + if :on_symbol == tk[:kind] then + tk[:text].sub(/^:/, '') + elsif :on_tstring == tk[:kind] then + tk[:text][1..-2] + elsif :on_dstring == tk[:kind] or :on_ident == tk[:kind] then + nil # ignore + else + warn("Expected symbol or string, got #{tk.inspect}") if $DEBUG_RDOC + nil + end + end + + ## + # Parses statements in the top-level +container+ + + def parse_top_level_statements container + comment = collect_first_comment + + look_for_directives_in container, comment + + throw :eof if container.done_documenting + + @markup = comment.format + + # HACK move if to RDoc::Context#comment= + container.comment = comment if container.document_self unless comment.empty? + + parse_statements container, NORMAL, nil, comment + end + + ## + # Determines the visibility in +container+ from +tk+ + + def parse_visibility(container, single, tk) + vis_type, vis, singleton = get_visibility_information tk, single + + skip_tkspace_comment false + + ptk = peek_tk + # Ryan Davis suggested the extension to ignore modifiers, because he + # often writes + # + # protected unless $TESTING + # + if [:on_nl, :on_semicolon].include?(ptk[:kind]) || (:on_kw == ptk[:kind] && (['if', 'unless'].include?(ptk[:text]))) then + container.ongoing_visibility = vis + elsif :on_kw == ptk[:kind] && 'def' == ptk[:text] + container.current_line_visibility = vis + else + update_visibility container, vis_type, vis, singleton + end + end + + ## + # Parses a Module#private_constant or Module#public_constant call from +tk+. + + def parse_constant_visibility(container, single, tk) + args = parse_symbol_arg + case tk[:text] + when 'private_constant' + vis = :private + when 'public_constant' + vis = :public + else + raise RDoc::Error, 'Unreachable' + end + container.set_constant_visibility_for args, vis + end + + ## + # Determines the block parameter for +context+ + + def parse_yield(context, single, tk, method) + return if method.block_params + + get_tkread + method.block_params = parse_method_or_yield_parameters + 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 if + # the name is in +allowed+. A directive can be found anywhere up to the end + # of the current line. + + def read_directive allowed + tokens = [] + + while tk = get_tk do + tokens << tk + + if :on_nl == tk[:kind] or (:on_kw == tk[:kind] && 'def' == tk[:text]) then + return + elsif :on_comment == tk[:kind] or :on_embdoc == tk[:kind] then + return unless tk[:text] =~ /\s*:?([\w-]+):\s*(.*)/ + + directive = $1.downcase + + return [directive, $2] if allowed.include? directive + + return + end + end + ensure + unless tokens.length == 1 and (:on_comment == tokens.first[:kind] or :on_embdoc == tokens.first[:kind]) then + tokens.reverse_each do |token| + unget_tk token + end + end + end + + ## + # Handles directives following the definition for +context+ (any + # RDoc::CodeObject) if the directives are +allowed+ at this point. + # + # See also RDoc::Markup::PreProcess#handle_directive + + def read_documentation_modifiers context, allowed + skip_tkspace(false) + directive, value = read_directive allowed + + return unless directive + + @preprocess.handle_directive '', directive, value, context do |dir, param| + if %w[notnew not_new not-new].include? dir then + context.dont_rename_initialize = true + + true + end + end + end + + ## + # Records the location of this +container+ in the file for this parser and + # adds it to the list of classes and modules in the file. + + def record_location container # :nodoc: + case container + when RDoc::ClassModule then + @top_level.add_to_classes_or_modules container + end + + container.record_location @top_level + end + + ## + # Scans this Ruby file for Ruby constructs + + def scan + reset + + catch :eof do + begin + parse_top_level_statements @top_level + + rescue StandardError => e + if @content.include?('<%') and @content.include?('%>') then + # Maybe, this is ERB. + $stderr.puts "\033[2KRDoc detects ERB file. Skips it for compatibility:" + $stderr.puts @file_name + return + end + bytes = '' + + if @scanner_point >= @scanner.size + now_line_no = @scanner[@scanner.size - 1][:line_no] + else + now_line_no = peek_tk[:line_no] + end + + $stderr.puts <<-EOF + +#{self.class} failure around line #{now_line_no} of +#{@file_name} + + EOF + + unless bytes.empty? then + $stderr.puts + now_line_no = peek_tk[:line_no] + start_index = @scanner.find_index { |tk| tk[:line_no] == now_line_no } + end_index = @scanner.find_index { |tk| tk[:line_no] == now_line_no + 1 } - 1 + $stderr.puts @scanner[start_index..end_index].join + end + + raise e + end + end + + @top_level + end + + ## + # while, until, and for have an optional do + + def skip_optional_do_after_expression + skip_tkspace false + tk = get_tk + + b_nest = 0 + nest = 0 + + loop do + break unless tk + case tk[:kind] + when :on_semicolon, :on_nl, :on_ignored_nl then + break if b_nest.zero? + when :on_lparen then + nest += 1 + when :on_rparen then + nest -= 1 + when :on_kw then + case tk[:text] + when 'begin' + b_nest += 1 + when 'end' + b_nest -= 1 + when 'do' + break if nest.zero? + end + when :on_comment, :on_embdoc then + if b_nest.zero? and "\n" == tk[:text][-1] then + break + end + end + tk = get_tk + end + + skip_tkspace false + + get_tk if peek_tk && :on_kw == peek_tk[:kind] && 'do' == peek_tk[:text] + end + + ## + # skip the var [in] part of a 'for' statement + + def skip_for_variable + skip_tkspace false + get_tk + skip_tkspace false + tk = get_tk + unget_tk(tk) unless :on_kw == tk[:kind] and 'in' == tk[:text] + end + + ## + # Skips the next method in +container+ + + def skip_method container + meth = RDoc::AnyMethod.new "", "anon" + parse_method_parameters meth + parse_statements container, false, meth + end + + ## + # Skip spaces until a comment is found + + def skip_tkspace_comment(skip_nl = true) + loop do + skip_tkspace skip_nl + next_tk = peek_tk + return if next_tk.nil? || (:on_comment != next_tk[:kind] and :on_embdoc != next_tk[:kind]) + get_tk + end + end + + ## + # Updates visibility in +container+ from +vis_type+ and +vis+. + + def update_visibility container, vis_type, vis, singleton # :nodoc: + new_methods = [] + + case vis_type + when 'module_function' then + args = parse_symbol_arg + container.set_visibility_for args, :private, false + + container.methods_matching args do |m| + s_m = m.dup + record_location s_m + s_m.singleton = true + new_methods << s_m + end + when 'public_class_method', 'private_class_method' then + args = parse_symbol_arg + + container.methods_matching args, true do |m| + if m.parent != container then + m = m.dup + record_location m + new_methods << m + end + + m.visibility = vis + end + else + args = parse_symbol_arg + container.set_visibility_for args, vis, singleton + end + + new_methods.each do |method| + case method + when RDoc::AnyMethod then + container.add_method method + when RDoc::Attr then + container.add_attribute method + end + method.visibility = vis + end + end + + ## + # Prints +message+ to +$stderr+ unless we're being quiet + + def warn message + @options.warn make_message message + end + +end diff --git a/lib/rdoc/parser/ruby_tools.rb b/lib/rdoc/parser/ruby_tools.rb new file mode 100644 index 0000000000..1f621cd32e --- /dev/null +++ b/lib/rdoc/parser/ruby_tools.rb @@ -0,0 +1,159 @@ +# frozen_string_literal: true +## +# Collection of methods for writing parsers + +module RDoc::Parser::RubyTools + + ## + # Adds a token listener +obj+, but you should probably use token_listener + + def add_token_listener(obj) + @token_listeners ||= [] + @token_listeners << obj + end + + ## + # Fetches the next token from the scanner + + def get_tk + tk = nil + + if @tokens.empty? then + if @scanner_point >= @scanner.size + return nil + else + tk = @scanner[@scanner_point] + @scanner_point += 1 + @read.push tk[:text] + puts "get_tk1 => #{tk.inspect}" if $TOKEN_DEBUG + end + else + @read.push @unget_read.shift + tk = @tokens.shift + puts "get_tk2 => #{tk.inspect}" if $TOKEN_DEBUG + end + + if tk == nil || :on___end__ == tk[:kind] + tk = nil + end + + return nil unless tk + + # inform any listeners of our shiny new token + @token_listeners.each do |obj| + obj.add_token(tk) + end if @token_listeners + + tk + end + + ## + # Reads and returns all tokens up to one of +tokens+. Leaves the matched + # token in the token list. + + def get_tk_until(*tokens) + read = [] + + loop do + tk = get_tk + + case tk + when *tokens then + unget_tk tk + break + end + + read << tk + end + + read + end + + ## + # Retrieves a String representation of the read tokens + + def get_tkread + read = @read.join("") + @read = [] + read + end + + ## + # Peek equivalent for get_tkread + + def peek_read + @read.join('') + end + + ## + # Peek at the next token, but don't remove it from the stream + + def peek_tk + unget_tk(tk = get_tk) + tk + end + + ## + # Removes the token listener +obj+ + + def remove_token_listener(obj) + @token_listeners.delete(obj) + end + + ## + # Resets the tools + + def reset + @read = [] + @tokens = [] + @unget_read = [] + @nest = 0 + @scanner_point = 0 + end + + def tk_nl?(tk) + :on_nl == tk[:kind] or :on_ignored_nl == tk[:kind] + end + + ## + # Skips whitespace tokens including newlines if +skip_nl+ is true + + def skip_tkspace(skip_nl = true) + tokens = [] + + while (tk = get_tk) and (:on_sp == tk[:kind] or (skip_nl and tk_nl?(tk))) do + tokens.push(tk) + end + + unget_tk(tk) + tokens + end + + ## + # Has +obj+ listen to tokens + + def token_listener(obj) + add_token_listener obj + yield + ensure + remove_token_listener obj + end + + ## + # Returns +tk+ to the scanner + + 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 + + nil + end + +end + + diff --git a/lib/rdoc/parser/simple.rb b/lib/rdoc/parser/simple.rb new file mode 100644 index 0000000000..b1dabad0f8 --- /dev/null +++ b/lib/rdoc/parser/simple.rb @@ -0,0 +1,61 @@ +# frozen_string_literal: true +## +# Parse a non-source file. We basically take the whole thing as one big +# comment. + +class RDoc::Parser::Simple < RDoc::Parser + + include RDoc::Parser::Text + + parse_files_matching(//) + + attr_reader :content # :nodoc: + + ## + # Prepare to parse a plain file + + def initialize(top_level, file_name, content, options, stats) + super + + preprocess = RDoc::Markup::PreProcess.new @file_name, @options.rdoc_include + + @content = preprocess.handle @content, @top_level + end + + ## + # Extract the file contents and attach them to the TopLevel as a comment + + def scan + comment = remove_coding_comment @content + comment = remove_private_comment comment + + comment = RDoc::Comment.new comment, @top_level + + @top_level.comment = comment + @top_level + end + + ## + # Removes the encoding magic comment from +text+ + + def remove_coding_comment text + text.sub(/\A# .*coding[=:].*$/, '') + end + + ## + # Removes private comments. + # + # Unlike RDoc::Comment#remove_private this implementation only looks for two + # dashes at the beginning of the line. Three or more dashes are considered + # to be a rule and ignored. + + def remove_private_comment comment + # Workaround for gsub encoding for Ruby 1.9.2 and earlier + empty = '' + empty = RDoc::Encoding.change_encoding empty, comment.encoding + + comment = comment.gsub(%r%^--\n.*?^\+\+\n?%m, empty) + comment.sub(%r%^--\n.*%m, empty) + end + +end diff --git a/lib/rdoc/parser/text.rb b/lib/rdoc/parser/text.rb new file mode 100644 index 0000000000..01de0cc595 --- /dev/null +++ b/lib/rdoc/parser/text.rb @@ -0,0 +1,12 @@ +# frozen_string_literal: true +## +# Indicates this parser is text and doesn't contain code constructs. +# +# Include this module in a RDoc::Parser subclass to make it show up as a file, +# not as part of a class or module. +#-- +# This is not named File to avoid overriding ::File + +module RDoc::Parser::Text +end + diff --git a/lib/rdoc/parsers/parse_c.rb b/lib/rdoc/parsers/parse_c.rb deleted file mode 100644 index fdec9c6b23..0000000000 --- a/lib/rdoc/parsers/parse_c.rb +++ /dev/null @@ -1,697 +0,0 @@ - # We attempt to parse C extension files. Basically we look for - # the standard patterns that you find in extensions: <tt>rb_define_class, - # rb_define_method</tt> and so on. We also try to find the corresponding - # C source for the methods and extract comments, but if we fail - # we don't worry too much. - # - # The comments associated with a Ruby method are extracted from the C - # comment block associated with the routine that _implements_ that - # method, that is to say the method whose name is given in the - # <tt>rb_define_method</tt> call. For example, you might write: - # - # /* - # * Returns a new array that is a one-dimensional flattening of this - # * array (recursively). That is, for every element that is an array, - # * extract its elements into the new array. - # * - # * s = [ 1, 2, 3 ] #=> [1, 2, 3] - # * t = [ 4, 5, 6, [7, 8] ] #=> [4, 5, 6, [7, 8]] - # * a = [ s, t, 9, 10 ] #=> [[1, 2, 3], [4, 5, 6, [7, 8]], 9, 10] - # * a.flatten #=> [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] - # */ - # static VALUE - # rb_ary_flatten(ary) - # VALUE ary; - # { - # ary = rb_obj_dup(ary); - # rb_ary_flatten_bang(ary); - # return ary; - # } - # - # ... - # - # void - # Init_Array() - # { - # ... - # rb_define_method(rb_cArray, "flatten", rb_ary_flatten, 0); - # - # Here RDoc will determine from the rb_define_method line that there's a - # method called "flatten" in class Array, and will look for the implementation - # in the method rb_ary_flatten. It will then use the comment from that - # method in the HTML output. This method must be in the same source file - # as the rb_define_method. - # - # C classes can be diagramed (see /tc/dl/ruby/ruby/error.c), and RDoc - # integrates C and Ruby source into one tree - # - # The comment blocks may include special direcives: - # - # [Document-class: <i>name</i>] - # This comment block is documentation for the given class. Use this - # when the <tt>Init_xxx</tt> method is not named after the class. - # - # [Document-method: <i>name</i>] - # This comment documents the named method. Use when RDoc cannot outomatically - # find the method from it's declaration - # - # [call-seq: <i>text up to an empty line</i>] - # Because C source doesn't give descripive names to Ruby-level parameters, - # you need to document the calling sequence explicitly - # - # In additon, RDoc assumes by default that the C method implementing a - # Ruby function is in the same source file as the rb_define_method call. - # If this isn't the case, add the comment - # - # rb_define_method(....); // in: filename - # - # As an example, we might have an extension that defines multiple classes - # in its Init_xxx method. We could document them using - # - # - # /* - # * Document-class: MyClass - # * - # * Encapsulate the writing and reading of the configuration - # * file. ... - # */ - # - # /* - # * Document-method: read_value - # * - # * call-seq: - # * cfg.read_value(key) -> value - # * cfg.read_value(key} { |key| } -> value - # * - # * Return the value corresponding to +key+ from the configuration. - # * In the second form, if the key isn't found, invoke the - # * block and return its value. - # */ - # - - - # Classes and modules built in to the interpreter. We need - # these to define superclasses of user objects - -require "rdoc/code_objects" -require "rdoc/parsers/parserfactory" - - -module RDoc - - KNOWN_CLASSES = { - "rb_cObject" => "Object", - "rb_cArray" => "Array", - "rb_cBignum" => "Bignum", - "rb_cClass" => "Class", - "rb_cDir" => "Dir", - "rb_cData" => "Data", - "rb_cFalseClass" => "FalseClass", - "rb_cFile" => "File", - "rb_cFixnum" => "Fixnum", - "rb_cFloat" => "Float", - "rb_cHash" => "Hash", - "rb_cInteger" => "Integer", - "rb_cIO" => "IO", - "rb_cModule" => "Module", - "rb_cNilClass" => "NilClass", - "rb_cNumeric" => "Numeric", - "rb_cProc" => "Proc", - "rb_cRange" => "Range", - "rb_cRegexp" => "Regexp", - "rb_cString" => "String", - "rb_cSymbol" => "Symbol", - "rb_cThread" => "Thread", - "rb_cTime" => "Time", - "rb_cTrueClass" => "TrueClass", - "rb_cStruct" => "Struct", - "rb_eException" => "Exception", - "rb_eStandardError" => "StandardError", - "rb_eSystemExit" => "SystemExit", - "rb_eInterrupt" => "Interrupt", - "rb_eSignal" => "Signal", - "rb_eFatal" => "Fatal", - "rb_eArgError" => "ArgError", - "rb_eEOFError" => "EOFError", - "rb_eIndexError" => "IndexError", - "rb_eRangeError" => "RangeError", - "rb_eIOError" => "IOError", - "rb_eRuntimeError" => "RuntimeError", - "rb_eSecurityError" => "SecurityError", - "rb_eSystemCallError" => "SystemCallError", - "rb_eTypeError" => "TypeError", - "rb_eZeroDivError" => "ZeroDivError", - "rb_eNotImpError" => "NotImpError", - "rb_eNoMemError" => "NoMemError", - "rb_eFloatDomainError" => "FloatDomainError", - "rb_eScriptError" => "ScriptError", - "rb_eNameError" => "NameError", - "rb_eSyntaxError" => "SyntaxError", - "rb_eLoadError" => "LoadError", - - "rb_mKernel" => "Kernel", - "rb_mComparable" => "Comparable", - "rb_mEnumerable" => "Enumerable", - "rb_mPrecision" => "Precision", - "rb_mErrno" => "Errno", - "rb_mFileTest" => "FileTest", - "rb_mGC" => "GC", - "rb_mMath" => "Math", - "rb_mProcess" => "Process" - - } - - # See rdoc/c_parse.rb - - class C_Parser - - - extend ParserFactory - parse_files_matching(/\.(?:([CcHh])\1?|c([+xp])\2|y)\z/) - - @@known_bodies = {} - - # prepare to parse a C file - def initialize(top_level, file_name, body, options, stats) - @known_classes = KNOWN_CLASSES.dup - @body = handle_tab_width(handle_ifdefs_in(body)) - @options = options - @stats = stats - @top_level = top_level - @classes = Hash.new - @file_dir = File.dirname(file_name) - @progress = $stderr unless options.quiet - end - - # Extract the classes/modules and methods from a C file - # and return the corresponding top-level object - def scan - remove_commented_out_lines - do_classes - do_constants - do_methods - do_includes - do_aliases - @top_level - end - - ####### - private - ####### - - def progress(char) - unless @options.quiet - @progress.print(char) - @progress.flush - end - end - - def warn(msg) - $stderr.puts - $stderr.puts msg - $stderr.flush - end - - def remove_private_comments(comment) - comment.gsub!(/\/?\*--(.*?)\/?\*\+\+/m, '') - comment.sub!(/\/?\*--.*/m, '') - end - - # remove lines that are commented out that might otherwise get - # picked up when scanning for classes and methods - - def remove_commented_out_lines - @body.gsub!(%r{//.*rb_define_}, '//') - end - - def handle_class_module(var_name, class_mod, class_name, parent, in_module) - progress(class_mod[0, 1]) - - parent_name = @known_classes[parent] || parent - - if in_module - enclosure = @classes[in_module] - unless enclosure - if enclosure = @known_classes[in_module] - handle_class_module(in_module, (/^rb_m/ =~ in_module ? "module" : "class"), - enclosure, nil, nil) - enclosure = @classes[in_module] - end - end - unless enclosure - warn("Enclosing class/module '#{in_module}' for " + - "#{class_mod} #{class_name} not known") - return - end - else - enclosure = @top_level - end - - if class_mod == "class" - cm = enclosure.add_class(NormalClass, class_name, parent_name) - @stats.num_classes += 1 - else - cm = enclosure.add_module(NormalModule, class_name) - @stats.num_modules += 1 - end - cm.record_location(enclosure.toplevel) - - find_class_comment(cm.full_name, cm) - @classes[var_name] = cm - @known_classes[var_name] = cm.full_name - end - - - ############################################################ - - def find_class_comment(class_name, class_meth) - comment = nil - if @body =~ %r{((?>/\*.*?\*/\s+)) - (static\s+)?void\s+Init_#{class_name}\s*(?:_\(\s*)?\(\s*(?:void\s*)?\)}xmi - comment = $1 - elsif @body =~ %r{Document-(class|module):\s#{class_name}\s*?\n((?>.*?\*/))}m - comment = $2 - end - class_meth.comment = mangle_comment(comment) if comment - end - - ############################################################ - - def do_classes - @body.scan(/(\w+)\s* = \s*rb_define_module\s*\(\s*"(\w+)"\s*\)/mx) do - |var_name, class_name| - handle_class_module(var_name, "module", class_name, nil, nil) - end - - # The '.' lets us handle SWIG-generated files - @body.scan(/([\w\.]+)\s* = \s*rb_define_class\s* - \( - \s*"(\w+)", - \s*(\w+)\s* - \)/mx) do - - |var_name, class_name, parent| - handle_class_module(var_name, "class", class_name, parent, nil) - end - - @body.scan(/(\w+)\s*=\s*boot_defclass\s*\(\s*"(\w+?)",\s*(\w+?)\s*\)/) do - |var_name, class_name, parent| - parent = nil if parent == "0" - handle_class_module(var_name, "class", class_name, parent, nil) - end - - @body.scan(/(\w+)\s* = \s*rb_define_module_under\s* - \( - \s*(\w+), - \s*"(\w+)" - \s*\)/mx) do - - |var_name, in_module, class_name| - handle_class_module(var_name, "module", class_name, nil, in_module) - end - - @body.scan(/([\w\.]+)\s* = \s*rb_define_class_under\s* - \( - \s*(\w+), - \s*"(\w+)", - \s*(\w+)\s* - \s*\)/mx) do - - |var_name, in_module, class_name, parent| - handle_class_module(var_name, "class", class_name, parent, in_module) - end - - end - - ########################################################### - - def do_constants - @body.scan(%r{\Wrb_define_ - ( - variable | - readonly_variable | - const | - global_const | - ) - \s*\( - (?:\s*(\w+),)? - \s*"(\w+)", - \s*(.*?)\s*\)\s*; - }xm) do - - |type, var_name, const_name, definition| - var_name = "rb_cObject" if !var_name or var_name == "rb_mKernel" - handle_constants(type, var_name, const_name, definition) - end - end - - ############################################################ - - def do_methods - - @body.scan(%r{rb_define_ - ( - singleton_method | - method | - module_function | - private_method - ) - \s*\(\s*([\w\.]+), - \s*"([^"]+)", - \s*(?:RUBY_METHOD_FUNC\(|VALUEFUNC\()?(\w+)\)?, - \s*(-?\w+)\s*\) - (?:;\s*/[*/]\s+in\s+(\w+?\.[cy]))? - }xm) do - |type, var_name, meth_name, meth_body, param_count, source_file| - #" - - # Ignore top-object and weird struct.c dynamic stuff - next if var_name == "ruby_top_self" - next if var_name == "nstr" - next if var_name == "envtbl" - next if var_name == "argf" # it'd be nice to handle this one - - var_name = "rb_cObject" if var_name == "rb_mKernel" - handle_method(type, var_name, meth_name, - meth_body, param_count, source_file) - end - - @body.scan(%r{rb_define_attr\( - \s*([\w\.]+), - \s*"([^"]+)", - \s*(\d+), - \s*(\d+)\s*\); - }xm) do #" - |var_name, attr_name, attr_reader, attr_writer| - - #var_name = "rb_cObject" if var_name == "rb_mKernel" - handle_attr(var_name, attr_name, - attr_reader.to_i != 0, - attr_writer.to_i != 0) - end - - @body.scan(%r{rb_define_global_function\s*\( - \s*"([^"]+)", - \s*(?:RUBY_METHOD_FUNC\(|VALUEFUNC\()?(\w+)\)?, - \s*(-?\w+)\s*\) - (?:;\s*/[*/]\s+in\s+(\w+?\.[cy]))? - }xm) do #" - |meth_name, meth_body, param_count, source_file| - handle_method("method", "rb_mKernel", meth_name, - meth_body, param_count, source_file) - end - - @body.scan(/define_filetest_function\s*\( - \s*"([^"]+)", - \s*(?:RUBY_METHOD_FUNC\(|VALUEFUNC\()?(\w+)\)?, - \s*(-?\w+)\s*\)/xm) do #" - |meth_name, meth_body, param_count| - - handle_method("method", "rb_mFileTest", meth_name, meth_body, param_count) - handle_method("singleton_method", "rb_cFile", meth_name, meth_body, param_count) - end - end - - ############################################################ - - def do_aliases - @body.scan(%r{rb_define_alias\s*\(\s*(\w+),\s*"([^"]+)",\s*"([^"]+)"\s*\)}m) do - |var_name, new_name, old_name| - @stats.num_methods += 1 - class_name = @known_classes[var_name] || var_name - class_obj = find_class(var_name, class_name) - - class_obj.add_alias(Alias.new("", old_name, new_name, "")) - end - end - - ############################################################ - - def handle_constants(type, var_name, const_name, definition) - #@stats.num_constants += 1 - class_name = @known_classes[var_name] - - return unless class_name - - class_obj = find_class(var_name, class_name) - - unless class_obj - warn("Enclosing class/module '#{const_name}' for not known") - return - end - - comment = find_const_comment(type, const_name) - - con = Constant.new(const_name, definition, mangle_comment(comment)) - class_obj.add_constant(con) - end - - ########################################################### - - def find_const_comment(type, const_name) - if @body =~ %r{((?>/\*.*?\*/\s+)) - rb_define_#{type}\((?:\s*(\w+),)?\s*"#{const_name}"\s*,.*?\)\s*;}xmi - $1 - elsif @body =~ %r{Document-(?:const|global|variable):\s#{const_name}\s*?\n((?>.*?\*/))}m - $1 - else - '' - end - end - - ########################################################### - - def handle_attr(var_name, attr_name, reader, writer) - rw = '' - if reader - #@stats.num_methods += 1 - rw << 'R' - end - if writer - #@stats.num_methods += 1 - rw << 'W' - end - - class_name = @known_classes[var_name] - - return unless class_name - - class_obj = find_class(var_name, class_name) - - if class_obj - comment = find_attr_comment(attr_name) - unless comment.empty? - comment = mangle_comment(comment) - end - att = Attr.new('', attr_name, rw, comment) - class_obj.add_attribute(att) - end - - end - - ########################################################### - - def find_attr_comment(attr_name) - if @body =~ %r{((?>/\*.*?\*/\s+)) - rb_define_attr\((?:\s*(\w+),)?\s*"#{attr_name}"\s*,.*?\)\s*;}xmi - $1 - elsif @body =~ %r{Document-attr:\s#{attr_name}\s*?\n((?>.*?\*/))}m - $1 - else - '' - end - end - - ########################################################### - - def handle_method(type, var_name, meth_name, - meth_body, param_count, source_file = nil) - progress(".") - - @stats.num_methods += 1 - class_name = @known_classes[var_name] - - return unless class_name - - class_obj = find_class(var_name, class_name) - - if class_obj - if meth_name == "initialize" - meth_name = "new" - type = "singleton_method" - end - meth_obj = AnyMethod.new("", meth_name) - meth_obj.singleton = - %w{singleton_method module_function}.include?(type) - - p_count = (Integer(param_count) rescue -1) - - if p_count < 0 - meth_obj.params = "(...)" - elsif p_count == 0 - meth_obj.params = "()" - else - meth_obj.params = "(" + - (1..p_count).map{|i| "p#{i}"}.join(", ") + - ")" - end - - if source_file - file_name = File.join(@file_dir, source_file) - body = (@@known_bodies[source_file] ||= File.read(file_name)) - else - body = @body - end - if find_body(meth_body, meth_obj, body) and meth_obj.document_self - class_obj.add_method(meth_obj) - end - end - end - - ############################################################ - - # Find the C code corresponding to a Ruby method - def find_body(meth_name, meth_obj, body, quiet = false) - case body - when %r{((?>/\*.*?\*/\s*))(?:static\s+)?VALUE\s+#{meth_name} - \s*(\(.*?\)).*?^}xm - comment, params = $1, $2 - body_text = $& - - remove_private_comments(comment) if comment - - # see if we can find the whole body - - re = Regexp.escape(body_text) + '[^(]*^\{.*?^\}' - if Regexp.new(re, Regexp::MULTILINE).match(body) - body_text = $& - end - - # The comment block may have been overridden with a - # 'Document-method' block. This happens in the interpreter - # when multiple methods are vectored through to the same - # C method but those methods are logically distinct (for - # example Kernel.hash and Kernel.object_id share the same - # implementation - - override_comment = find_override_comment(meth_obj.name) - comment = override_comment if override_comment - - find_modifiers(comment, meth_obj) if comment - -# meth_obj.params = params - meth_obj.start_collecting_tokens - meth_obj.add_token(RubyToken::Token.new(1,1).set_text(body_text)) - meth_obj.comment = mangle_comment(comment) - when %r{((?>/\*.*?\*/\s*))^\s*\#\s*define\s+#{meth_name}\s+(\w+)}m - comment = $1 - find_body($2, meth_obj, body, true) - find_modifiers(comment, meth_obj) - meth_obj.comment = mangle_comment(comment) + meth_obj.comment - when %r{^\s*\#\s*define\s+#{meth_name}\s+(\w+)}m - unless find_body($1, meth_obj, body, true) - warn "No definition for #{meth_name}" unless quiet - return false - end - else - - # No body, but might still have an override comment - comment = find_override_comment(meth_obj.name) - - if comment - find_modifiers(comment, meth_obj) - meth_obj.comment = mangle_comment(comment) - else - warn "No definition for #{meth_name}" unless quiet - return false - end - end - true - end - - - ################################################## - # - # If the comment block contains a section that looks like - # call-seq: - # Array.new - # Array.new(10) - # use it for the parameters - def find_modifiers(comment, meth_obj) - if comment.sub!(/:nodoc:\s*^\s*\*?\s*$/m, '') or - comment.sub!(/\A\/\*\s*:nodoc:\s*\*\/\Z/, '') - meth_obj.document_self = false - end - if comment.sub!(/call-seq:(.*?)^\s*\*?\s*$/m, '') or - comment.sub!(/\A\/\*\s*call-seq:(.*?)\*\/\Z/, '') - seq = $1 - seq.gsub!(/^\s*\*\s*/, '') - meth_obj.call_seq = seq - end - end - - ############################################################ - - def find_override_comment(meth_name) - name = Regexp.escape(meth_name) - if @body =~ %r{Document-method:\s#{name}\s*?\n((?>.*?\*/))}m - $1 - end - end - - ############################################################ - - # Look for includes of the form - # rb_include_module(rb_cArray, rb_mEnumerable); - def do_includes - @body.scan(/rb_include_module\s*\(\s*(\w+?),\s*(\w+?)\s*\)/) do |c,m| - if cls = @classes[c] - m = @known_classes[m] || m - cls.add_include(Include.new(m, "")) - end - end - end - - ############################################################ - - # Remove the /*'s and leading asterisks from C comments - - def mangle_comment(comment) - comment.sub!(%r{/\*+}) { " " * $&.length } - comment.sub!(%r{\*+/}) { " " * $&.length } - comment.gsub!(/^[ \t]*\*/m) { " " * $&.length } - comment - end - - def find_class(raw_name, name) - unless @classes[raw_name] - if raw_name =~ /^rb_m/ - @classes[raw_name] = @top_level.add_module(NormalModule, name) - else - @classes[raw_name] = @top_level.add_class(NormalClass, name, nil) - end - end - @classes[raw_name] - end - - def handle_tab_width(body) - if /\t/ =~ body - tab_width = Options.instance.tab_width - body.split(/\n/).map do |line| - 1 while line.gsub!(/\t+/) { ' ' * (tab_width*$&.length - $`.length % tab_width)} && $~ #` - line - end .join("\n") - else - body - end - end - - # Remove #ifdefs that would otherwise confuse us - - def handle_ifdefs_in(body) - body.gsub(/^#ifdef HAVE_PROTOTYPES.*?#else.*?\n(.*?)#endif.*?\n/m) { $1 } - end - - end - -end diff --git a/lib/rdoc/parsers/parse_f95.rb b/lib/rdoc/parsers/parse_f95.rb deleted file mode 100644 index f3f6d76103..0000000000 --- a/lib/rdoc/parsers/parse_f95.rb +++ /dev/null @@ -1,1841 +0,0 @@ -#= parse_f95.rb - Fortran95 Parser -# -#== Overview -# -#"parse_f95.rb" parses Fortran95 files with suffixes "f90", "F90", "f95" -#and "F95". Fortran95 files are expected to be conformed to Fortran95 -#standards. -# -#== Rules -# -#Fundamental rules are same as that of the Ruby parser. -#But comment markers are '!' not '#'. -# -#=== Correspondence between RDoc documentation and Fortran95 programs -# -#"parse_f95.rb" parses main programs, modules, subroutines, functions, -#derived-types, public variables, public constants, -#defined operators and defined assignments. -#These components are described in items of RDoc documentation, as follows. -# -#Files :: Files (same as Ruby) -#Classes :: Modules -#Methods :: Subroutines, functions, variables, constants, derived-types, defined operators, defined assignments -#Required files :: Files in which imported modules, external subroutines and external functions are defined. -#Included Modules :: List of imported modules -#Attributes :: List of derived-types, List of imported modules all of whose components are published again -# -#Components listed in 'Methods' (subroutines, functions, ...) -#defined in modules are described in the item of 'Classes'. -#On the other hand, components defined in main programs or -#as external procedures are described in the item of 'Files'. -# -#=== Components parsed by default -# -#By default, documentation on public components (subroutines, functions, -#variables, constants, derived-types, defined operators, -#defined assignments) are generated. -#With "--all" option, documentation on all components -#are generated (almost same as the Ruby parser). -# -#=== Information parsed automatically -# -#The following information is automatically parsed. -# -#* Types of arguments -#* Types of variables and constants -#* Types of variables in the derived types, and initial values -#* NAMELISTs and types of variables in them, and initial values -# -#Aliases by interface statement are described in the item of 'Methods'. -# -#Components which are imported from other modules and published again -#are described in the item of 'Methods'. -# -#=== Format of comment blocks -# -#Comment blocks should be written as follows. -#Comment blocks are considered to be ended when the line without '!' -#appears. -#The indentation is not necessary. -# -# ! (Top of file) -# ! -# ! Comment blocks for the files. -# ! -# !-- -# ! The comment described in the part enclosed by -# ! "!--" and "!++" is ignored. -# !++ -# ! -# module hogehoge -# ! -# ! Comment blocks for the modules (or the programs). -# ! -# -# private -# -# logical :: a ! a private variable -# real, public :: b ! a public variable -# integer, parameter :: c = 0 ! a public constant -# -# public :: c -# public :: MULTI_ARRAY -# public :: hoge, foo -# -# type MULTI_ARRAY -# ! -# ! Comment blocks for the derived-types. -# ! -# real, pointer :: var(:) =>null() ! Comments block for the variables. -# integer :: num = 0 -# end type MULTI_ARRAY -# -# contains -# -# subroutine hoge( in, & ! Comment blocks between continuation lines are ignored. -# & out ) -# ! -# ! Comment blocks for the subroutines or functions -# ! -# character(*),intent(in):: in ! Comment blocks for the arguments. -# character(*),intent(out),allocatable,target :: in -# ! Comment blocks can be -# ! written under Fortran statements. -# -# character(32) :: file ! This comment parsed as a variable in below NAMELIST. -# integer :: id -# -# namelist /varinfo_nml/ file, id -# ! -# ! Comment blocks for the NAMELISTs. -# ! Information about variables are described above. -# ! -# -# .... -# -# end subroutine hoge -# -# integer function foo( in ) -# ! -# ! This part is considered as comment block. -# -# ! Comment blocks under blank lines are ignored. -# ! -# integer, intent(in):: inA ! This part is considered as comment block. -# -# ! This part is ignored. -# -# end function foo -# -# subroutine hide( in, & -# & out ) !:nodoc: -# ! -# ! If "!:nodoc:" is described at end-of-line in subroutine -# ! statement as above, the subroutine is ignored. -# ! This assignment can be used to modules, subroutines, -# ! functions, variables, constants, derived-types, -# ! defined operators, defined assignments, -# ! list of imported modules ("use" statement). -# ! -# -# .... -# -# end subroutine hide -# -# end module hogehoge -# - - -require "rdoc/code_objects" - -module RDoc - - class Token - - NO_TEXT = "??".freeze - - def initialize(line_no, char_no) - @line_no = line_no - @char_no = char_no - @text = NO_TEXT - end - # Because we're used in contexts that expect to return a token, - # we set the text string and then return ourselves - def set_text(text) - @text = text - self - end - - attr_reader :line_no, :char_no, :text - - end - - # See rdoc/parsers/parse_f95.rb - - class Fortran95parser - - extend ParserFactory - parse_files_matching(/\.((f|F)9(0|5)|F)$/) - - @@external_aliases = [] - @@public_methods = [] - - # "false":: Comments are below source code - # "true" :: Comments are upper source code - COMMENTS_ARE_UPPER = false - - # Internal alias message - INTERNAL_ALIAS_MES = "Alias for" - - # External alias message - EXTERNAL_ALIAS_MES = "The entity is" - - # prepare to parse a Fortran 95 file - def initialize(top_level, file_name, body, options, stats) - @body = body - @stats = stats - @file_name = file_name - @options = options - @top_level = top_level - @progress = $stderr unless options.quiet - end - - # devine code constructs - def scan - - # remove private comment - remaining_code = remove_private_comments(@body) - - # continuation lines are united to one line - remaining_code = united_to_one_line(remaining_code) - - # semicolons are replaced to line feed - remaining_code = semicolon_to_linefeed(remaining_code) - - # collect comment for file entity - whole_comment, remaining_code = collect_first_comment(remaining_code) - @top_level.comment = whole_comment - - # String "remaining_code" is converted to Array "remaining_lines" - remaining_lines = remaining_code.split("\n") - - # "module" or "program" parts are parsed (new) - # - level_depth = 0 - block_searching_flag = nil - block_searching_lines = [] - pre_comment = [] - module_program_trailing = "" - module_program_name = "" - other_block_level_depth = 0 - other_block_searching_flag = nil - remaining_lines.collect!{|line| - if !block_searching_flag && !other_block_searching_flag - if line =~ /^\s*?module\s+(\w+)\s*?(!.*?)?$/i - block_searching_flag = :module - block_searching_lines << line - module_program_name = $1 - module_program_trailing = find_comments($2) - next false - elsif line =~ /^\s*?program\s+(\w+)\s*?(!.*?)?$/i || - line =~ /^\s*?\w/ && !block_start?(line) - block_searching_flag = :program - block_searching_lines << line - module_program_name = $1 || "" - module_program_trailing = find_comments($2) - next false - - elsif block_start?(line) - other_block_searching_flag = true - next line - - elsif line =~ /^\s*?!\s?(.*)/ - pre_comment << line - next line - else - pre_comment = [] - next line - end - elsif other_block_searching_flag - other_block_level_depth += 1 if block_start?(line) - other_block_level_depth -= 1 if block_end?(line) - if other_block_level_depth < 0 - other_block_level_depth = 0 - other_block_searching_flag = nil - end - next line - end - - block_searching_lines << line - level_depth += 1 if block_start?(line) - level_depth -= 1 if block_end?(line) - if level_depth >= 0 - next false - end - - # "module_program_code" is formatted. - # ":nodoc:" flag is checked. - # - module_program_code = block_searching_lines.join("\n") - module_program_code = remove_empty_head_lines(module_program_code) - if module_program_trailing =~ /^:nodoc:/ - # next loop to search next block - level_depth = 0 - block_searching_flag = false - block_searching_lines = [] - pre_comment = [] - next false - end - - # NormalClass is created, and added to @top_level - # - if block_searching_flag == :module - module_name = module_program_name - module_code = module_program_code - module_trailing = module_program_trailing - progress "m" - @stats.num_modules += 1 - f9x_module = @top_level.add_module NormalClass, module_name - f9x_module.record_location @top_level - - f9x_comment = COMMENTS_ARE_UPPER ? - find_comments(pre_comment.join("\n")) + "\n" + module_trailing : - module_trailing + "\n" + find_comments(module_code.sub(/^.*$\n/i, '')) - f9x_module.comment = f9x_comment - parse_program_or_module(f9x_module, module_code) - - TopLevel.all_files.each do |name, toplevel| - if toplevel.include_includes?(module_name, @options.ignore_case) - if !toplevel.include_requires?(@file_name, @options.ignore_case) - toplevel.add_require(Require.new(@file_name, "")) - end - end - toplevel.each_classmodule{|m| - if m.include_includes?(module_name, @options.ignore_case) - if !m.include_requires?(@file_name, @options.ignore_case) - m.add_require(Require.new(@file_name, "")) - end - end - } - end - elsif block_searching_flag == :program - program_name = module_program_name - program_code = module_program_code - program_trailing = module_program_trailing - progress "p" - program_comment = COMMENTS_ARE_UPPER ? - find_comments(pre_comment.join("\n")) + "\n" + program_trailing : - program_trailing + "\n" + find_comments(program_code.sub(/^.*$\n/i, '')) - program_comment = "\n\n= <i>Program</i> <tt>#{program_name}</tt>\n\n" \ - + program_comment - @top_level.comment << program_comment - parse_program_or_module(@top_level, program_code, :private) - end - - # next loop to search next block - level_depth = 0 - block_searching_flag = false - block_searching_lines = [] - pre_comment = [] - next false - } - - remaining_lines.delete_if{ |line| - line == false - } - - # External subprograms and functions are parsed - # - parse_program_or_module(@top_level, remaining_lines.join("\n"), - :public, true) - - @top_level - end # End of scan - - private - - def parse_program_or_module(container, code, - visibility=:public, external=nil) - return unless container - return unless code - remaining_lines = code.split("\n") - remaining_code = "#{code}" - - # - # Parse variables before "contains" in module - # - level_depth = 0 - before_contains_lines = [] - before_contains_code = nil - before_contains_flag = nil - remaining_lines.each{ |line| - if !before_contains_flag - if line =~ /^\s*?module\s+\w+\s*?(!.*?)?$/i - before_contains_flag = true - end - else - break if line =~ /^\s*?contains\s*?(!.*?)?$/i - level_depth += 1 if block_start?(line) - level_depth -= 1 if block_end?(line) - break if level_depth < 0 - before_contains_lines << line - end - } - before_contains_code = before_contains_lines.join("\n") - if before_contains_code - before_contains_code.gsub!(/^\s*?interface\s+.*?\s+end\s+interface.*?$/im, "") - before_contains_code.gsub!(/^\s*?type[\s\,]+.*?\s+end\s+type.*?$/im, "") - end - - # - # Parse global "use" - # - use_check_code = "#{before_contains_code}" - cascaded_modules_list = [] - while use_check_code =~ /^\s*?use\s+(\w+)(.*?)(!.*?)?$/i - use_check_code = $~.pre_match - use_check_code << $~.post_match - used_mod_name = $1.strip.chomp - used_list = $2 || "" - used_trailing = $3 || "" - next if used_trailing =~ /!:nodoc:/ - if !container.include_includes?(used_mod_name, @options.ignore_case) - progress "." - container.add_include Include.new(used_mod_name, "") - end - if ! (used_list =~ /\,\s*?only\s*?:/i ) - cascaded_modules_list << "\#" + used_mod_name - end - end - - # - # Parse public and private, and store information. - # This information is used when "add_method" and - # "set_visibility_for" are called. - # - visibility_default, visibility_info = - parse_visibility(remaining_lines.join("\n"), visibility, container) - @@public_methods.concat visibility_info - if visibility_default == :public - if !cascaded_modules_list.empty? - cascaded_modules = - Attr.new("Cascaded Modules", - "Imported modules all of whose components are published again", - "", - cascaded_modules_list.join(", ")) - container.add_attribute(cascaded_modules) - end - end - - # - # Check rename elements - # - use_check_code = "#{before_contains_code}" - while use_check_code =~ /^\s*?use\s+(\w+)\s*?\,(.+)$/i - use_check_code = $~.pre_match - use_check_code << $~.post_match - used_mod_name = $1.strip.chomp - used_elements = $2.sub(/\s*?only\s*?:\s*?/i, '') - used_elements.split(",").each{ |used| - if /\s*?(\w+)\s*?=>\s*?(\w+)\s*?/ =~ used - local = $1 - org = $2 - @@public_methods.collect!{ |pub_meth| - if local == pub_meth["name"] || - local.upcase == pub_meth["name"].upcase && - @options.ignore_case - pub_meth["name"] = org - pub_meth["local_name"] = local - end - pub_meth - } - end - } - end - - # - # Parse private "use" - # - use_check_code = remaining_lines.join("\n") - while use_check_code =~ /^\s*?use\s+(\w+)(.*?)(!.*?)?$/i - use_check_code = $~.pre_match - use_check_code << $~.post_match - used_mod_name = $1.strip.chomp - used_trailing = $3 || "" - next if used_trailing =~ /!:nodoc:/ - if !container.include_includes?(used_mod_name, @options.ignore_case) - progress "." - container.add_include Include.new(used_mod_name, "") - end - end - - container.each_includes{ |inc| - TopLevel.all_files.each do |name, toplevel| - indicated_mod = toplevel.find_symbol(inc.name, - nil, @options.ignore_case) - if indicated_mod - indicated_name = indicated_mod.parent.file_relative_name - if !container.include_requires?(indicated_name, @options.ignore_case) - container.add_require(Require.new(indicated_name, "")) - end - break - end - end - } - - # - # Parse derived-types definitions - # - derived_types_comment = "" - remaining_code = remaining_lines.join("\n") - while remaining_code =~ /^\s*? - type[\s\,]+(public|private)?\s*?(::)?\s*? - (\w+)\s*?(!.*?)?$ - (.*?) - ^\s*?end\s+type.*?$ - /imx - remaining_code = $~.pre_match - remaining_code << $~.post_match - typename = $3.chomp.strip - type_elements = $5 || "" - type_code = remove_empty_head_lines($&) - type_trailing = find_comments($4) - next if type_trailing =~ /^:nodoc:/ - type_visibility = $1 - type_comment = COMMENTS_ARE_UPPER ? - find_comments($~.pre_match) + "\n" + type_trailing : - type_trailing + "\n" + find_comments(type_code.sub(/^.*$\n/i, '')) - type_element_visibility_public = true - type_code.split("\n").each{ |line| - if /^\s*?private\s*?$/ =~ line - type_element_visibility_public = nil - break - end - } if type_code - - args_comment = "" - type_args_info = nil - - if @options.show_all - args_comment = find_arguments(nil, type_code, true) - else - type_public_args_list = [] - type_args_info = definition_info(type_code) - type_args_info.each{ |arg| - arg_is_public = type_element_visibility_public - arg_is_public = true if arg.include_attr?("public") - arg_is_public = nil if arg.include_attr?("private") - type_public_args_list << arg.varname if arg_is_public - } - args_comment = find_arguments(type_public_args_list, type_code) - end - - type = AnyMethod.new("type #{typename}", typename) - type.singleton = false - type.params = "" - type.comment = "<b><em> Derived Type </em></b> :: <tt></tt>\n" - type.comment << args_comment if args_comment - type.comment << type_comment if type_comment - progress "t" - @stats.num_methods += 1 - container.add_method type - - set_visibility(container, typename, visibility_default, @@public_methods) - - if type_visibility - type_visibility.gsub!(/\s/,'') - type_visibility.gsub!(/\,/,'') - type_visibility.gsub!(/:/,'') - type_visibility.downcase! - if type_visibility == "public" - container.set_visibility_for([typename], :public) - elsif type_visibility == "private" - container.set_visibility_for([typename], :private) - end - end - - check_public_methods(type, container.name) - - if @options.show_all - derived_types_comment << ", " unless derived_types_comment.empty? - derived_types_comment << typename - else - if type.visibility == :public - derived_types_comment << ", " unless derived_types_comment.empty? - derived_types_comment << typename - end - end - - end - - if !derived_types_comment.empty? - derived_types_table = - Attr.new("Derived Types", "Derived_Types", "", - derived_types_comment) - container.add_attribute(derived_types_table) - end - - # - # move interface scope - # - interface_code = "" - while remaining_code =~ /^\s*? - interface( - \s+\w+ | - \s+operator\s*?\(.*?\) | - \s+assignment\s*?\(\s*?=\s*?\) - )?\s*?$ - (.*?) - ^\s*?end\s+interface.*?$ - /imx - interface_code << remove_empty_head_lines($&) + "\n" - remaining_code = $~.pre_match - remaining_code << $~.post_match - end - - # - # Parse global constants or variables in modules - # - const_var_defs = definition_info(before_contains_code) - const_var_defs.each{|defitem| - next if defitem.nodoc - const_or_var_type = "Variable" - const_or_var_progress = "v" - if defitem.include_attr?("parameter") - const_or_var_type = "Constant" - const_or_var_progress = "c" - end - const_or_var = AnyMethod.new(const_or_var_type, defitem.varname) - const_or_var.singleton = false - const_or_var.params = "" - self_comment = find_arguments([defitem.varname], before_contains_code) - const_or_var.comment = "<b><em>" + const_or_var_type + "</em></b> :: <tt></tt>\n" - const_or_var.comment << self_comment if self_comment - progress const_or_var_progress - @stats.num_methods += 1 - container.add_method const_or_var - - set_visibility(container, defitem.varname, visibility_default, @@public_methods) - - if defitem.include_attr?("public") - container.set_visibility_for([defitem.varname], :public) - elsif defitem.include_attr?("private") - container.set_visibility_for([defitem.varname], :private) - end - - check_public_methods(const_or_var, container.name) - - } if const_var_defs - - remaining_lines = remaining_code.split("\n") - - # "subroutine" or "function" parts are parsed (new) - # - level_depth = 0 - block_searching_flag = nil - block_searching_lines = [] - pre_comment = [] - procedure_trailing = "" - procedure_name = "" - procedure_params = "" - procedure_prefix = "" - procedure_result_arg = "" - procedure_type = "" - contains_lines = [] - contains_flag = nil - remaining_lines.collect!{|line| - if !block_searching_flag - # subroutine - if line =~ /^\s*? - (recursive|pure|elemental)?\s*? - subroutine\s+(\w+)\s*?(\(.*?\))?\s*?(!.*?)?$ - /ix - block_searching_flag = :subroutine - block_searching_lines << line - - procedure_name = $2.chomp.strip - procedure_params = $3 || "" - procedure_prefix = $1 || "" - procedure_trailing = $4 || "!" - next false - - # function - elsif line =~ /^\s*? - (recursive|pure|elemental)?\s*? - ( - character\s*?(\([\w\s\=\(\)\*]+?\))?\s+ - | type\s*?\([\w\s]+?\)\s+ - | integer\s*?(\([\w\s\=\(\)\*]+?\))?\s+ - | real\s*?(\([\w\s\=\(\)\*]+?\))?\s+ - | double\s+precision\s+ - | logical\s*?(\([\w\s\=\(\)\*]+?\))?\s+ - | complex\s*?(\([\w\s\=\(\)\*]+?\))?\s+ - )? - function\s+(\w+)\s*? - (\(.*?\))?(\s+result\((.*?)\))?\s*?(!.*?)?$ - /ix - block_searching_flag = :function - block_searching_lines << line - - procedure_prefix = $1 || "" - procedure_type = $2 ? $2.chomp.strip : nil - procedure_name = $8.chomp.strip - procedure_params = $9 || "" - procedure_result_arg = $11 ? $11.chomp.strip : procedure_name - procedure_trailing = $12 || "!" - next false - elsif line =~ /^\s*?!\s?(.*)/ - pre_comment << line - next line - else - pre_comment = [] - next line - end - end - contains_flag = true if line =~ /^\s*?contains\s*?(!.*?)?$/ - block_searching_lines << line - contains_lines << line if contains_flag - - level_depth += 1 if block_start?(line) - level_depth -= 1 if block_end?(line) - if level_depth >= 0 - next false - end - - # "procedure_code" is formatted. - # ":nodoc:" flag is checked. - # - procedure_code = block_searching_lines.join("\n") - procedure_code = remove_empty_head_lines(procedure_code) - if procedure_trailing =~ /^!:nodoc:/ - # next loop to search next block - level_depth = 0 - block_searching_flag = nil - block_searching_lines = [] - pre_comment = [] - procedure_trailing = "" - procedure_name = "" - procedure_params = "" - procedure_prefix = "" - procedure_result_arg = "" - procedure_type = "" - contains_lines = [] - contains_flag = nil - next false - end - - # AnyMethod is created, and added to container - # - subroutine_function = nil - if block_searching_flag == :subroutine - subroutine_prefix = procedure_prefix - subroutine_name = procedure_name - subroutine_params = procedure_params - subroutine_trailing = procedure_trailing - subroutine_code = procedure_code - - subroutine_comment = COMMENTS_ARE_UPPER ? - pre_comment.join("\n") + "\n" + subroutine_trailing : - subroutine_trailing + "\n" + subroutine_code.sub(/^.*$\n/i, '') - subroutine = AnyMethod.new("subroutine", subroutine_name) - parse_subprogram(subroutine, subroutine_params, - subroutine_comment, subroutine_code, - before_contains_code, nil, subroutine_prefix) - progress "s" - @stats.num_methods += 1 - container.add_method subroutine - subroutine_function = subroutine - - elsif block_searching_flag == :function - function_prefix = procedure_prefix - function_type = procedure_type - function_name = procedure_name - function_params_org = procedure_params - function_result_arg = procedure_result_arg - function_trailing = procedure_trailing - function_code_org = procedure_code - - function_comment = COMMENTS_ARE_UPPER ? - pre_comment.join("\n") + "\n" + function_trailing : - function_trailing + "\n " + function_code_org.sub(/^.*$\n/i, '') - - function_code = "#{function_code_org}" - if function_type - function_code << "\n" + function_type + " :: " + function_result_arg - end - - function_params = - function_params_org.sub(/^\(/, "\(#{function_result_arg}, ") - - function = AnyMethod.new("function", function_name) - parse_subprogram(function, function_params, - function_comment, function_code, - before_contains_code, true, function_prefix) - - # Specific modification due to function - function.params.sub!(/\(\s*?#{function_result_arg}\s*?,\s*?/, "\( ") - function.params << " result(" + function_result_arg + ")" - function.start_collecting_tokens - function.add_token Token.new(1,1).set_text(function_code_org) - - progress "f" - @stats.num_methods += 1 - container.add_method function - subroutine_function = function - - end - - # The visibility of procedure is specified - # - set_visibility(container, procedure_name, - visibility_default, @@public_methods) - - # The alias for this procedure from external modules - # - check_external_aliases(procedure_name, - subroutine_function.params, - subroutine_function.comment, subroutine_function) if external - check_public_methods(subroutine_function, container.name) - - - # contains_lines are parsed as private procedures - if contains_flag - parse_program_or_module(container, - contains_lines.join("\n"), :private) - end - - # next loop to search next block - level_depth = 0 - block_searching_flag = nil - block_searching_lines = [] - pre_comment = [] - procedure_trailing = "" - procedure_name = "" - procedure_params = "" - procedure_prefix = "" - procedure_result_arg = "" - contains_lines = [] - contains_flag = nil - next false - } # End of remaining_lines.collect!{|line| - - # Array remains_lines is converted to String remains_code again - # - remaining_code = remaining_lines.join("\n") - - # - # Parse interface - # - interface_scope = false - generic_name = "" - interface_code.split("\n").each{ |line| - if /^\s*? - interface( - \s+\w+| - \s+operator\s*?\(.*?\)| - \s+assignment\s*?\(\s*?=\s*?\) - )? - \s*?(!.*?)?$ - /ix =~ line - generic_name = $1 ? $1.strip.chomp : nil - interface_trailing = $2 || "!" - interface_scope = true - interface_scope = false if interface_trailing =~ /!:nodoc:/ -# if generic_name =~ /operator\s*?\((.*?)\)/i -# operator_name = $1 -# if operator_name && !operator_name.empty? -# generic_name = "#{operator_name}" -# end -# end -# if generic_name =~ /assignment\s*?\((.*?)\)/i -# assignment_name = $1 -# if assignment_name && !assignment_name.empty? -# generic_name = "#{assignment_name}" -# end -# end - end - if /^\s*?end\s+interface/i =~ line - interface_scope = false - generic_name = nil - end - # internal alias - if interface_scope && /^\s*?module\s+procedure\s+(.*?)(!.*?)?$/i =~ line - procedures = $1.strip.chomp - procedures_trailing = $2 || "!" - next if procedures_trailing =~ /!:nodoc:/ - procedures.split(",").each{ |proc| - proc.strip! - proc.chomp! - next if generic_name == proc || !generic_name - old_meth = container.find_symbol(proc, nil, @options.ignore_case) - next if !old_meth - nolink = old_meth.visibility == :private ? true : nil - nolink = nil if @options.show_all - new_meth = - initialize_external_method(generic_name, proc, - old_meth.params, nil, - old_meth.comment, - old_meth.clone.token_stream[0].text, - true, nolink) - new_meth.singleton = old_meth.singleton - - progress "i" - @stats.num_methods += 1 - container.add_method new_meth - - set_visibility(container, generic_name, visibility_default, @@public_methods) - - check_public_methods(new_meth, container.name) - - } - end - - # external aliases - if interface_scope - # subroutine - proc = nil - params = nil - procedures_trailing = nil - if line =~ /^\s*? - (recursive|pure|elemental)?\s*? - subroutine\s+(\w+)\s*?(\(.*?\))?\s*?(!.*?)?$ - /ix - proc = $2.chomp.strip - generic_name = proc unless generic_name - params = $3 || "" - procedures_trailing = $4 || "!" - - # function - elsif line =~ /^\s*? - (recursive|pure|elemental)?\s*? - ( - character\s*?(\([\w\s\=\(\)\*]+?\))?\s+ - | type\s*?\([\w\s]+?\)\s+ - | integer\s*?(\([\w\s\=\(\)\*]+?\))?\s+ - | real\s*?(\([\w\s\=\(\)\*]+?\))?\s+ - | double\s+precision\s+ - | logical\s*?(\([\w\s\=\(\)\*]+?\))?\s+ - | complex\s*?(\([\w\s\=\(\)\*]+?\))?\s+ - )? - function\s+(\w+)\s*? - (\(.*?\))?(\s+result\((.*?)\))?\s*?(!.*?)?$ - /ix - proc = $8.chomp.strip - generic_name = proc unless generic_name - params = $9 || "" - procedures_trailing = $12 || "!" - else - next - end - next if procedures_trailing =~ /!:nodoc:/ - indicated_method = nil - indicated_file = nil - TopLevel.all_files.each do |name, toplevel| - indicated_method = toplevel.find_local_symbol(proc, @options.ignore_case) - indicated_file = name - break if indicated_method - end - - if indicated_method - external_method = - initialize_external_method(generic_name, proc, - indicated_method.params, - indicated_file, - indicated_method.comment) - - progress "e" - @stats.num_methods += 1 - container.add_method external_method - set_visibility(container, generic_name, visibility_default, @@public_methods) - if !container.include_requires?(indicated_file, @options.ignore_case) - container.add_require(Require.new(indicated_file, "")) - end - check_public_methods(external_method, container.name) - - else - @@external_aliases << { - "new_name" => generic_name, - "old_name" => proc, - "file_or_module" => container, - "visibility" => find_visibility(container, generic_name, @@public_methods) || visibility_default - } - end - end - - } if interface_code # End of interface_code.split("\n").each ... - - # - # Already imported methods are removed from @@public_methods. - # Remainders are assumed to be imported from other modules. - # - @@public_methods.delete_if{ |method| method["entity_is_discovered"]} - - @@public_methods.each{ |pub_meth| - next unless pub_meth["file_or_module"].name == container.name - pub_meth["used_modules"].each{ |used_mod| - TopLevel.all_classes_and_modules.each{ |modules| - if modules.name == used_mod || - modules.name.upcase == used_mod.upcase && - @options.ignore_case - modules.method_list.each{ |meth| - if meth.name == pub_meth["name"] || - meth.name.upcase == pub_meth["name"].upcase && - @options.ignore_case - new_meth = initialize_public_method(meth, - modules.name) - if pub_meth["local_name"] - new_meth.name = pub_meth["local_name"] - end - progress "e" - @stats.num_methods += 1 - container.add_method new_meth - end - } - end - } - } - } - - container - end # End of parse_program_or_module - - # - # Parse arguments, comment, code of subroutine and function. - # Return AnyMethod object. - # - def parse_subprogram(subprogram, params, comment, code, - before_contains=nil, function=nil, prefix=nil) - subprogram.singleton = false - prefix = "" if !prefix - arguments = params.sub(/\(/, "").sub(/\)/, "").split(",") if params - args_comment, params_opt = - find_arguments(arguments, code.sub(/^s*?contains\s*?(!.*?)?$.*/im, ""), - nil, nil, true) - params_opt = "( " + params_opt + " ) " if params_opt - subprogram.params = params_opt || "" - namelist_comment = find_namelists(code, before_contains) - - block_comment = find_comments comment - if function - subprogram.comment = "<b><em> Function </em></b> :: <em>#{prefix}</em>\n" - else - subprogram.comment = "<b><em> Subroutine </em></b> :: <em>#{prefix}</em>\n" - end - subprogram.comment << args_comment if args_comment - subprogram.comment << block_comment if block_comment - subprogram.comment << namelist_comment if namelist_comment - - # For output source code - subprogram.start_collecting_tokens - subprogram.add_token Token.new(1,1).set_text(code) - - subprogram - end - - # - # Collect comment for file entity - # - def collect_first_comment(body) - comment = "" - not_comment = "" - comment_start = false - comment_end = false - body.split("\n").each{ |line| - if comment_end - not_comment << line - not_comment << "\n" - elsif /^\s*?!\s?(.*)$/i =~ line - comment_start = true - comment << $1 - comment << "\n" - elsif /^\s*?$/i =~ line - comment_end = true if comment_start && COMMENTS_ARE_UPPER - else - comment_end = true - not_comment << line - not_comment << "\n" - end - } - return comment, not_comment - end - - - # Return comments of definitions of arguments - # - # If "all" argument is true, information of all arguments are returned. - # If "modified_params" is true, list of arguments are decorated, - # for exameple, optional arguments are parenthetic as "[arg]". - # - def find_arguments(args, text, all=nil, indent=nil, modified_params=nil) - return unless args || all - indent = "" unless indent - args = ["all"] if all - params = "" if modified_params - comma = "" - return unless text - args_rdocforms = "\n" - remaining_lines = "#{text}" - definitions = definition_info(remaining_lines) - args.each{ |arg| - arg.strip! - arg.chomp! - definitions.each { |defitem| - if arg == defitem.varname.strip.chomp || all - args_rdocforms << <<-"EOF" - -#{indent}<tt><b>#{defitem.varname.chomp.strip}#{defitem.arraysuffix}</b> #{defitem.inivalue}</tt> :: -#{indent} <tt>#{defitem.types.chomp.strip}</tt> -EOF - if !defitem.comment.chomp.strip.empty? - comment = "" - defitem.comment.split("\n").each{ |line| - comment << " " + line + "\n" - } - args_rdocforms << <<-"EOF" - -#{indent} <tt></tt> :: -#{indent} <tt></tt> -#{indent} #{comment.chomp.strip} -EOF - end - - if modified_params - if defitem.include_attr?("optional") - params << "#{comma}[#{arg}]" - else - params << "#{comma}#{arg}" - end - comma = ", " - end - end - } - } - if modified_params - return args_rdocforms, params - else - return args_rdocforms - end - end - - # Return comments of definitions of namelists - # - def find_namelists(text, before_contains=nil) - return nil if !text - result = "" - lines = "#{text}" - before_contains = "" if !before_contains - while lines =~ /^\s*?namelist\s+\/\s*?(\w+)\s*?\/([\s\w\,]+)$/i - lines = $~.post_match - nml_comment = COMMENTS_ARE_UPPER ? - find_comments($~.pre_match) : find_comments($~.post_match) - nml_name = $1 - nml_args = $2.split(",") - result << "\n\n=== NAMELIST <tt><b>" + nml_name + "</tt></b>\n\n" - result << nml_comment + "\n" if nml_comment - if lines.split("\n")[0] =~ /^\//i - lines = "namelist " + lines - end - result << find_arguments(nml_args, "#{text}" + "\n" + before_contains) - end - return result - end - - # - # Comments just after module or subprogram, or arguments are - # returnd. If "COMMENTS_ARE_UPPER" is true, comments just before - # modules or subprograms are returnd - # - def find_comments text - return "" unless text - lines = text.split("\n") - lines.reverse! if COMMENTS_ARE_UPPER - comment_block = Array.new - lines.each do |line| - break if line =~ /^\s*?\w/ || line =~ /^\s*?$/ - if COMMENTS_ARE_UPPER - comment_block.unshift line.sub(/^\s*?!\s?/,"") - else - comment_block.push line.sub(/^\s*?!\s?/,"") - end - end - nice_lines = comment_block.join("\n").split "\n\s*?\n" - nice_lines[0] ||= "" - nice_lines.shift - end - - def progress(char) - unless @options.quiet - @progress.print(char) - @progress.flush - end - end - - # - # Create method for internal alias - # - def initialize_public_method(method, parent) - return if !method || !parent - - new_meth = AnyMethod.new("External Alias for module", method.name) - new_meth.singleton = method.singleton - new_meth.params = method.params.clone - new_meth.comment = remove_trailing_alias(method.comment.clone) - new_meth.comment << "\n\n#{EXTERNAL_ALIAS_MES} #{parent.strip.chomp}\##{method.name}" - - return new_meth - end - - # - # Create method for external alias - # - # If argument "internal" is true, file is ignored. - # - def initialize_external_method(new, old, params, file, comment, token=nil, - internal=nil, nolink=nil) - return nil unless new || old - - if internal - external_alias_header = "#{INTERNAL_ALIAS_MES} " - external_alias_text = external_alias_header + old - elsif file - external_alias_header = "#{EXTERNAL_ALIAS_MES} " - external_alias_text = external_alias_header + file + "#" + old - else - return nil - end - external_meth = AnyMethod.new(external_alias_text, new) - external_meth.singleton = false - external_meth.params = params - external_comment = remove_trailing_alias(comment) + "\n\n" if comment - external_meth.comment = external_comment || "" - if nolink && token - external_meth.start_collecting_tokens - external_meth.add_token Token.new(1,1).set_text(token) - else - external_meth.comment << external_alias_text - end - - return external_meth - end - - - - # - # Parse visibility - # - def parse_visibility(code, default, container) - result = [] - visibility_default = default || :public - - used_modules = [] - container.includes.each{|i| used_modules << i.name} if container - - remaining_code = code.gsub(/^\s*?type[\s\,]+.*?\s+end\s+type.*?$/im, "") - remaining_code.split("\n").each{ |line| - if /^\s*?private\s*?$/ =~ line - visibility_default = :private - break - end - } if remaining_code - - remaining_code.split("\n").each{ |line| - if /^\s*?private\s*?(::)?\s+(.*)\s*?(!.*?)?/i =~ line - methods = $2.sub(/!.*$/, '') - methods.split(",").each{ |meth| - meth.sub!(/!.*$/, '') - meth.gsub!(/:/, '') - result << { - "name" => meth.chomp.strip, - "visibility" => :private, - "used_modules" => used_modules.clone, - "file_or_module" => container, - "entity_is_discovered" => nil, - "local_name" => nil - } - } - elsif /^\s*?public\s*?(::)?\s+(.*)\s*?(!.*?)?/i =~ line - methods = $2.sub(/!.*$/, '') - methods.split(",").each{ |meth| - meth.sub!(/!.*$/, '') - meth.gsub!(/:/, '') - result << { - "name" => meth.chomp.strip, - "visibility" => :public, - "used_modules" => used_modules.clone, - "file_or_module" => container, - "entity_is_discovered" => nil, - "local_name" => nil - } - } - end - } if remaining_code - - if container - result.each{ |vis_info| - vis_info["parent"] = container.name - } - end - - return visibility_default, result - end - - # - # Set visibility - # - # "subname" element of "visibility_info" is deleted. - # - def set_visibility(container, subname, visibility_default, visibility_info) - return unless container || subname || visibility_default || visibility_info - not_found = true - visibility_info.collect!{ |info| - if info["name"] == subname || - @options.ignore_case && info["name"].upcase == subname.upcase - if info["file_or_module"].name == container.name - container.set_visibility_for([subname], info["visibility"]) - info["entity_is_discovered"] = true - not_found = false - end - end - info - } - if not_found - return container.set_visibility_for([subname], visibility_default) - else - return container - end - end - - # - # Find visibility - # - def find_visibility(container, subname, visibility_info) - return nil if !subname || !visibility_info - visibility_info.each{ |info| - if info["name"] == subname || - @options.ignore_case && info["name"].upcase == subname.upcase - if info["parent"] == container.name - return info["visibility"] - end - end - } - return nil - end - - # - # Check external aliases - # - def check_external_aliases(subname, params, comment, test=nil) - @@external_aliases.each{ |alias_item| - if subname == alias_item["old_name"] || - subname.upcase == alias_item["old_name"].upcase && - @options.ignore_case - - new_meth = initialize_external_method(alias_item["new_name"], - subname, params, @file_name, - comment) - new_meth.visibility = alias_item["visibility"] - - progress "e" - @stats.num_methods += 1 - alias_item["file_or_module"].add_method(new_meth) - - if !alias_item["file_or_module"].include_requires?(@file_name, @options.ignore_case) - alias_item["file_or_module"].add_require(Require.new(@file_name, "")) - end - end - } - end - - # - # Check public_methods - # - def check_public_methods(method, parent) - return if !method || !parent - @@public_methods.each{ |alias_item| - parent_is_used_module = nil - alias_item["used_modules"].each{ |used_module| - if used_module == parent || - used_module.upcase == parent.upcase && - @options.ignore_case - parent_is_used_module = true - end - } - next if !parent_is_used_module - - if method.name == alias_item["name"] || - method.name.upcase == alias_item["name"].upcase && - @options.ignore_case - - new_meth = initialize_public_method(method, parent) - if alias_item["local_name"] - new_meth.name = alias_item["local_name"] - end - - progress "e" - @stats.num_methods += 1 - alias_item["file_or_module"].add_method new_meth - end - } - end - - # - # Continuous lines are united. - # - # Comments in continuous lines are removed. - # - def united_to_one_line(f90src) - return "" unless f90src - lines = f90src.split("\n") - previous_continuing = false - now_continuing = false - body = "" - lines.each{ |line| - words = line.split("") - next if words.empty? && previous_continuing - commentout = false - brank_flag = true ; brank_char = "" - squote = false ; dquote = false - ignore = false - words.collect! { |char| - if previous_continuing && brank_flag - now_continuing = true - ignore = true - case char - when "!" ; break - when " " ; brank_char << char ; next "" - when "&" - brank_flag = false - now_continuing = false - next "" - else - brank_flag = false - now_continuing = false - ignore = false - next brank_char + char - end - end - ignore = false - - if now_continuing - next "" - elsif !(squote) && !(dquote) && !(commentout) - case char - when "!" ; commentout = true ; next char - when "\""; dquote = true ; next char - when "\'"; squote = true ; next char - when "&" ; now_continuing = true ; next "" - else next char - end - elsif commentout - next char - elsif squote - case char - when "\'"; squote = false ; next char - else next char - end - elsif dquote - case char - when "\""; dquote = false ; next char - else next char - end - end - } - if !ignore && !previous_continuing || !brank_flag - if previous_continuing - body << words.join("") - else - body << "\n" + words.join("") - end - end - previous_continuing = now_continuing ? true : nil - now_continuing = nil - } - return body - end - - - # - # Continuous line checker - # - def continuous_line?(line) - continuous = false - if /&\s*?(!.*)?$/ =~ line - continuous = true - if comment_out?($~.pre_match) - continuous = false - end - end - return continuous - end - - # - # Comment out checker - # - def comment_out?(line) - return nil unless line - commentout = false - squote = false ; dquote = false - line.split("").each { |char| - if !(squote) && !(dquote) - case char - when "!" ; commentout = true ; break - when "\""; dquote = true - when "\'"; squote = true - else next - end - elsif squote - case char - when "\'"; squote = false - else next - end - elsif dquote - case char - when "\""; dquote = false - else next - end - end - } - return commentout - end - - # - # Semicolons are replaced to line feed. - # - def semicolon_to_linefeed(text) - return "" unless text - lines = text.split("\n") - lines.collect!{ |line| - words = line.split("") - commentout = false - squote = false ; dquote = false - words.collect! { |char| - if !(squote) && !(dquote) && !(commentout) - case char - when "!" ; commentout = true ; next char - when "\""; dquote = true ; next char - when "\'"; squote = true ; next char - when ";" ; "\n" - else next char - end - elsif commentout - next char - elsif squote - case char - when "\'"; squote = false ; next char - else next char - end - elsif dquote - case char - when "\""; dquote = false ; next char - else next char - end - end - } - words.join("") - } - return lines.join("\n") - end - - # - # Which "line" is start of block (module, program, block data, - # subroutine, function) statement ? - # - def block_start?(line) - return nil if !line - - if line =~ /^\s*?module\s+(\w+)\s*?(!.*?)?$/i || - line =~ /^\s*?program\s+(\w+)\s*?(!.*?)?$/i || - line =~ /^\s*?block\s+data(\s+\w+)?\s*?(!.*?)?$/i || - line =~ \ - /^\s*? - (recursive|pure|elemental)?\s*? - subroutine\s+(\w+)\s*?(\(.*?\))?\s*?(!.*?)?$ - /ix || - line =~ \ - /^\s*? - (recursive|pure|elemental)?\s*? - ( - character\s*?(\([\w\s\=\(\)\*]+?\))?\s+ - | type\s*?\([\w\s]+?\)\s+ - | integer\s*?(\([\w\s\=\(\)\*]+?\))?\s+ - | real\s*?(\([\w\s\=\(\)\*]+?\))?\s+ - | double\s+precision\s+ - | logical\s*?(\([\w\s\=\(\)\*]+?\))?\s+ - | complex\s*?(\([\w\s\=\(\)\*]+?\))?\s+ - )? - function\s+(\w+)\s*? - (\(.*?\))?(\s+result\((.*?)\))?\s*?(!.*?)?$ - /ix - return true - end - - return nil - end - - # - # Which "line" is end of block (module, program, block data, - # subroutine, function) statement ? - # - def block_end?(line) - return nil if !line - - if line =~ /^\s*?end\s*?(!.*?)?$/i || - line =~ /^\s*?end\s+module(\s+\w+)?\s*?(!.*?)?$/i || - line =~ /^\s*?end\s+program(\s+\w+)?\s*?(!.*?)?$/i || - line =~ /^\s*?end\s+block\s+data(\s+\w+)?\s*?(!.*?)?$/i || - line =~ /^\s*?end\s+subroutine(\s+\w+)?\s*?(!.*?)?$/i || - line =~ /^\s*?end\s+function(\s+\w+)?\s*?(!.*?)?$/i - return true - end - - return nil - end - - # - # Remove "Alias for" in end of comments - # - def remove_trailing_alias(text) - return "" if !text - lines = text.split("\n").reverse - comment_block = Array.new - checked = false - lines.each do |line| - if !checked - if /^\s?#{INTERNAL_ALIAS_MES}/ =~ line || - /^\s?#{EXTERNAL_ALIAS_MES}/ =~ line - checked = true - next - end - end - comment_block.unshift line - end - nice_lines = comment_block.join("\n") - nice_lines ||= "" - return nice_lines - end - - # Empty lines in header are removed - def remove_empty_head_lines(text) - return "" unless text - lines = text.split("\n") - header = true - lines.delete_if{ |line| - header = false if /\S/ =~ line - header && /^\s*?$/ =~ line - } - lines.join("\n") - end - - - # header marker "=", "==", ... are removed - def remove_header_marker(text) - return text.gsub(/^\s?(=+)/, '<tt></tt>\1') - end - - def remove_private_comments(body) - body.gsub!(/^\s*!--\s*?$.*?^\s*!\+\+\s*?$/m, '') - return body - end - - - # - # Information of arguments of subroutines and functions in Fortran95 - # - class Fortran95Definition - - # Name of variable - # - attr_reader :varname - - # Types of variable - # - attr_reader :types - - # Initial Value - # - attr_reader :inivalue - - # Suffix of array - # - attr_reader :arraysuffix - - # Comments - # - attr_accessor :comment - - # Flag of non documentation - # - attr_accessor :nodoc - - def initialize(varname, types, inivalue, arraysuffix, comment, - nodoc=false) - @varname = varname - @types = types - @inivalue = inivalue - @arraysuffix = arraysuffix - @comment = comment - @nodoc = nodoc - end - - def to_s - return <<-EOF -<Fortran95Definition: - varname=#{@varname}, types=#{types}, - inivalue=#{@inivalue}, arraysuffix=#{@arraysuffix}, nodoc=#{@nodoc}, - comment= -#{@comment} -> -EOF - end - - # - # If attr is included, true is returned - # - def include_attr?(attr) - return if !attr - @types.split(",").each{ |type| - return true if type.strip.chomp.upcase == attr.strip.chomp.upcase - } - return nil - end - - end # End of Fortran95Definition - - # - # Parse string argument "text", and Return Array of - # Fortran95Definition object - # - def definition_info(text) - return nil unless text - lines = "#{text}" - defs = Array.new - comment = "" - trailing_comment = "" - under_comment_valid = false - lines.split("\n").each{ |line| - if /^\s*?!\s?(.*)/ =~ line - if COMMENTS_ARE_UPPER - comment << remove_header_marker($1) - comment << "\n" - elsif defs[-1] && under_comment_valid - defs[-1].comment << "\n" - defs[-1].comment << remove_header_marker($1) - end - next - elsif /^\s*?$/ =~ line - comment = "" - under_comment_valid = false - next - end - type = "" - characters = "" - if line =~ /^\s*? - ( - character\s*?(\([\w\s\=\(\)\*]+?\))?[\s\,]* - | type\s*?\([\w\s]+?\)[\s\,]* - | integer\s*?(\([\w\s\=\(\)\*]+?\))?[\s\,]* - | real\s*?(\([\w\s\=\(\)\*]+?\))?[\s\,]* - | double\s+precision[\s\,]* - | logical\s*?(\([\w\s\=\(\)\*]+?\))?[\s\,]* - | complex\s*?(\([\w\s\=\(\)\*]+?\))?[\s\,]* - ) - (.*?::)? - (.+)$ - /ix - characters = $8 - type = $1 - type << $7.gsub(/::/, '').gsub(/^\s*?\,/, '') if $7 - else - under_comment_valid = false - next - end - squote = false ; dquote = false ; bracket = 0 - iniflag = false; commentflag = false - varname = "" ; arraysuffix = "" ; inivalue = "" - start_pos = defs.size - characters.split("").each { |char| - if !(squote) && !(dquote) && bracket <= 0 && !(iniflag) && !(commentflag) - case char - when "!" ; commentflag = true - when "(" ; bracket += 1 ; arraysuffix = char - when "\""; dquote = true - when "\'"; squote = true - when "=" ; iniflag = true ; inivalue << char - when "," - defs << Fortran95Definition.new(varname, type, inivalue, arraysuffix, comment) - varname = "" ; arraysuffix = "" ; inivalue = "" - under_comment_valid = true - when " " ; next - else ; varname << char - end - elsif commentflag - comment << remove_header_marker(char) - trailing_comment << remove_header_marker(char) - elsif iniflag - if dquote - case char - when "\"" ; dquote = false ; inivalue << char - else ; inivalue << char - end - elsif squote - case char - when "\'" ; squote = false ; inivalue << char - else ; inivalue << char - end - elsif bracket > 0 - case char - when "(" ; bracket += 1 ; inivalue << char - when ")" ; bracket -= 1 ; inivalue << char - else ; inivalue << char - end - else - case char - when "," - defs << Fortran95Definition.new(varname, type, inivalue, arraysuffix, comment) - varname = "" ; arraysuffix = "" ; inivalue = "" - iniflag = false - under_comment_valid = true - when "(" ; bracket += 1 ; inivalue << char - when "\""; dquote = true ; inivalue << char - when "\'"; squote = true ; inivalue << char - when "!" ; commentflag = true - else ; inivalue << char - end - end - elsif !(squote) && !(dquote) && bracket > 0 - case char - when "(" ; bracket += 1 ; arraysuffix << char - when ")" ; bracket -= 1 ; arraysuffix << char - else ; arraysuffix << char - end - elsif squote - case char - when "\'"; squote = false ; inivalue << char - else ; inivalue << char - end - elsif dquote - case char - when "\""; dquote = false ; inivalue << char - else ; inivalue << char - end - end - } - defs << Fortran95Definition.new(varname, type, inivalue, arraysuffix, comment) - if trailing_comment =~ /^:nodoc:/ - defs[start_pos..-1].collect!{ |defitem| - defitem.nodoc = true - } - end - varname = "" ; arraysuffix = "" ; inivalue = "" - comment = "" - under_comment_valid = true - trailing_comment = "" - } - return defs - end - - - end # class Fortran95parser - -end # module RDoc diff --git a/lib/rdoc/parsers/parse_rb.rb b/lib/rdoc/parsers/parse_rb.rb deleted file mode 100644 index dde017be7d..0000000000 --- a/lib/rdoc/parsers/parse_rb.rb +++ /dev/null @@ -1,2605 +0,0 @@ -#!/usr/local/bin/ruby - -# Parse a Ruby source file, building a set of objects -# representing the modules, classes, methods, -# requires, and includes we find (these classes -# are defined in code_objects.rb). - -# This file contains stuff stolen outright from: -# -# rtags.rb - -# ruby-lex.rb - ruby lexcal analizer -# ruby-token.rb - ruby tokens -# by Keiju ISHITSUKA (Nippon Rational Inc.) -# - -require "e2mmap" -require "irb/slex" - -require "rdoc/code_objects" -require "rdoc/tokenstream" - -require "rdoc/markup/simple_markup/preprocess" - -require "rdoc/parsers/parserfactory" - -$TOKEN_DEBUG = $DEBUG - -# Definitions of all tokens involved in the lexical analysis - -module RubyToken - EXPR_BEG = :EXPR_BEG - EXPR_MID = :EXPR_MID - EXPR_END = :EXPR_END - EXPR_ARG = :EXPR_ARG - EXPR_FNAME = :EXPR_FNAME - EXPR_DOT = :EXPR_DOT - EXPR_CLASS = :EXPR_CLASS - - class Token - NO_TEXT = "??".freeze - attr :text - - def initialize(line_no, char_no) - @line_no = line_no - @char_no = char_no - @text = NO_TEXT - end - - # Because we're used in contexts that expect to return a token, - # we set the text string and then return ourselves - def set_text(text) - @text = text - self - end - - attr_reader :line_no, :char_no, :text - end - - class TkNode < Token - attr :node - end - - class TkId < Token - def initialize(line_no, char_no, name) - super(line_no, char_no) - @name = name - end - attr :name - end - - class TkKW < TkId - end - - class TkVal < Token - def initialize(line_no, char_no, value = nil) - super(line_no, char_no) - set_text(value) - end - end - - class TkOp < Token - def name - self.class.op_name - end - end - - class TkOPASGN < TkOp - def initialize(line_no, char_no, op) - super(line_no, char_no) - op = TkReading2Token[op] unless op.kind_of?(Symbol) - @op = op - end - attr :op - end - - class TkUnknownChar < Token - def initialize(line_no, char_no, id) - super(line_no, char_no) - @name = char_no.chr - end - attr :name - end - - class TkError < Token - end - - def set_token_position(line, char) - @prev_line_no = line - @prev_char_no = char - end - - def Token(token, value = nil) - tk = nil - case token - when String, Symbol - source = token.kind_of?(String) ? TkReading2Token : TkSymbol2Token - if (tk = source[token]).nil? - IRB.fail TkReading2TokenNoKey, token - end - tk = Token(tk[0], value) - else - tk = if (token.ancestors & [TkId, TkVal, TkOPASGN, TkUnknownChar]).empty? - token.new(@prev_line_no, @prev_char_no) - else - token.new(@prev_line_no, @prev_char_no, value) - end - end - tk - end - - TokenDefinitions = [ - [:TkCLASS, TkKW, "class", EXPR_CLASS], - [:TkMODULE, TkKW, "module", EXPR_BEG], - [:TkDEF, TkKW, "def", EXPR_FNAME], - [:TkUNDEF, TkKW, "undef", EXPR_FNAME], - [:TkBEGIN, TkKW, "begin", EXPR_BEG], - [:TkRESCUE, TkKW, "rescue", EXPR_MID], - [:TkENSURE, TkKW, "ensure", EXPR_BEG], - [:TkEND, TkKW, "end", EXPR_END], - [:TkIF, TkKW, "if", EXPR_BEG, :TkIF_MOD], - [:TkUNLESS, TkKW, "unless", EXPR_BEG, :TkUNLESS_MOD], - [:TkTHEN, TkKW, "then", EXPR_BEG], - [:TkELSIF, TkKW, "elsif", EXPR_BEG], - [:TkELSE, TkKW, "else", EXPR_BEG], - [:TkCASE, TkKW, "case", EXPR_BEG], - [:TkWHEN, TkKW, "when", EXPR_BEG], - [:TkWHILE, TkKW, "while", EXPR_BEG, :TkWHILE_MOD], - [:TkUNTIL, TkKW, "until", EXPR_BEG, :TkUNTIL_MOD], - [:TkFOR, TkKW, "for", EXPR_BEG], - [:TkBREAK, TkKW, "break", EXPR_END], - [:TkNEXT, TkKW, "next", EXPR_END], - [:TkREDO, TkKW, "redo", EXPR_END], - [:TkRETRY, TkKW, "retry", EXPR_END], - [:TkIN, TkKW, "in", EXPR_BEG], - [:TkDO, TkKW, "do", EXPR_BEG], - [:TkRETURN, TkKW, "return", EXPR_MID], - [:TkYIELD, TkKW, "yield", EXPR_END], - [:TkSUPER, TkKW, "super", EXPR_END], - [:TkSELF, TkKW, "self", EXPR_END], - [:TkNIL, TkKW, "nil", EXPR_END], - [:TkTRUE, TkKW, "true", EXPR_END], - [:TkFALSE, TkKW, "false", EXPR_END], - [:TkAND, TkKW, "and", EXPR_BEG], - [:TkOR, TkKW, "or", EXPR_BEG], - [:TkNOT, TkKW, "not", EXPR_BEG], - [:TkIF_MOD, TkKW], - [:TkUNLESS_MOD, TkKW], - [:TkWHILE_MOD, TkKW], - [:TkUNTIL_MOD, TkKW], - [:TkALIAS, TkKW, "alias", EXPR_FNAME], - [:TkDEFINED, TkKW, "defined?", EXPR_END], - [:TklBEGIN, TkKW, "BEGIN", EXPR_END], - [:TklEND, TkKW, "END", EXPR_END], - [:Tk__LINE__, TkKW, "__LINE__", EXPR_END], - [:Tk__FILE__, TkKW, "__FILE__", EXPR_END], - - [:TkIDENTIFIER, TkId], - [:TkFID, TkId], - [:TkGVAR, TkId], - [:TkIVAR, TkId], - [:TkCONSTANT, TkId], - - [:TkINTEGER, TkVal], - [:TkFLOAT, TkVal], - [:TkSTRING, TkVal], - [:TkXSTRING, TkVal], - [:TkREGEXP, TkVal], - [:TkCOMMENT, TkVal], - - [:TkDSTRING, TkNode], - [:TkDXSTRING, TkNode], - [:TkDREGEXP, TkNode], - [:TkNTH_REF, TkId], - [:TkBACK_REF, TkId], - - [:TkUPLUS, TkOp, "+@"], - [:TkUMINUS, TkOp, "-@"], - [:TkPOW, TkOp, "**"], - [:TkCMP, TkOp, "<=>"], - [:TkEQ, TkOp, "=="], - [:TkEQQ, TkOp, "==="], - [:TkNEQ, TkOp, "!="], - [:TkGEQ, TkOp, ">="], - [:TkLEQ, TkOp, "<="], - [:TkANDOP, TkOp, "&&"], - [:TkOROP, TkOp, "||"], - [:TkMATCH, TkOp, "=~"], - [:TkNMATCH, TkOp, "!~"], - [:TkDOT2, TkOp, ".."], - [:TkDOT3, TkOp, "..."], - [:TkAREF, TkOp, "[]"], - [:TkASET, TkOp, "[]="], - [:TkLSHFT, TkOp, "<<"], - [:TkRSHFT, TkOp, ">>"], - [:TkCOLON2, TkOp], - [:TkCOLON3, TkOp], -# [:OPASGN, TkOp], # +=, -= etc. # - [:TkASSOC, TkOp, "=>"], - [:TkQUESTION, TkOp, "?"], #? - [:TkCOLON, TkOp, ":"], #: - - [:TkfLPAREN], # func( # - [:TkfLBRACK], # func[ # - [:TkfLBRACE], # func{ # - [:TkSTAR], # *arg - [:TkAMPER], # &arg # - [:TkSYMBOL, TkId], # :SYMBOL - [:TkSYMBEG, TkId], - [:TkGT, TkOp, ">"], - [:TkLT, TkOp, "<"], - [:TkPLUS, TkOp, "+"], - [:TkMINUS, TkOp, "-"], - [:TkMULT, TkOp, "*"], - [:TkDIV, TkOp, "/"], - [:TkMOD, TkOp, "%"], - [:TkBITOR, TkOp, "|"], - [:TkBITXOR, TkOp, "^"], - [:TkBITAND, TkOp, "&"], - [:TkBITNOT, TkOp, "~"], - [:TkNOTOP, TkOp, "!"], - - [:TkBACKQUOTE, TkOp, "`"], - - [:TkASSIGN, Token, "="], - [:TkDOT, Token, "."], - [:TkLPAREN, Token, "("], #(exp) - [:TkLBRACK, Token, "["], #[arry] - [:TkLBRACE, Token, "{"], #{hash} - [:TkRPAREN, Token, ")"], - [:TkRBRACK, Token, "]"], - [:TkRBRACE, Token, "}"], - [:TkCOMMA, Token, ","], - [:TkSEMICOLON, Token, ";"], - - [:TkRD_COMMENT], - [:TkSPACE], - [:TkNL], - [:TkEND_OF_SCRIPT], - - [:TkBACKSLASH, TkUnknownChar, "\\"], - [:TkAT, TkUnknownChar, "@"], - [:TkDOLLAR, TkUnknownChar, "\$"], #" - ] - - # {reading => token_class} - # {reading => [token_class, *opt]} - TkReading2Token = {} - TkSymbol2Token = {} - - def RubyToken.def_token(token_n, super_token = Token, reading = nil, *opts) - token_n = token_n.id2name unless token_n.kind_of?(String) - if RubyToken.const_defined?(token_n) - IRB.fail AlreadyDefinedToken, token_n - end - - token_c = Class.new super_token - RubyToken.const_set token_n, token_c -# token_c.inspect - - if reading - if TkReading2Token[reading] - IRB.fail TkReading2TokenDuplicateError, token_n, reading - end - if opts.empty? - TkReading2Token[reading] = [token_c] - else - TkReading2Token[reading] = [token_c].concat(opts) - end - end - TkSymbol2Token[token_n.intern] = token_c - - if token_c <= TkOp - token_c.class_eval %{ - def self.op_name; "#{reading}"; end - } - end - end - - for defs in TokenDefinitions - def_token(*defs) - end - - NEWLINE_TOKEN = TkNL.new(0,0) - NEWLINE_TOKEN.set_text("\n") - -end - - - -# Lexical analyzer for Ruby source - -class RubyLex - - ###################################################################### - # - # Read an input stream character by character. We allow for unlimited - # ungetting of characters just read. - # - # We simplify the implementation greatly by reading the entire input - # into a buffer initially, and then simply traversing it using - # pointers. - # - # We also have to allow for the <i>here document diversion</i>. This - # little gem comes about when the lexer encounters a here - # document. At this point we effectively need to split the input - # stream into two parts: one to read the body of the here document, - # the other to read the rest of the input line where the here - # document was initially encountered. For example, we might have - # - # do_something(<<-A, <<-B) - # stuff - # for - # A - # stuff - # for - # B - # - # When the lexer encounters the <<A, it reads until the end of the - # line, and keeps it around for later. It then reads the body of the - # 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) - if /\t/ =~ content - tab_width = Options.instance.tab_width - content = content.split(/\n/).map do |line| - 1 while line.gsub!(/\t+/) { ' ' * (tab_width*$&.length - $`.length % tab_width)} && $~ #` - line - end .join("\n") - end - @content = content - @content << "\n" unless @content[-1,1] == "\n" - @size = @content.size - @offset = 0 - @hwm = 0 - @line_num = 1 - @read_back_offset = 0 - @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 - if @content[@offset] == ?\n - @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 - nil - else - @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 - end - end - - # end of nested class BufferedReader - - extend Exception2MessageMapper - def_exception(:AlreadyDefinedToken, "Already defined token(%s)") - def_exception(:TkReading2TokenNoKey, "key nothing(key='%s')") - def_exception(:TkSymbol2TokenNoKey, "key nothing(key='%s')") - def_exception(:TkReading2TokenDuplicateError, - "key duplicate(token_n='%s', key='%s')") - def_exception(:SyntaxError, "%s") - - include RubyToken - include IRB - - attr_reader :continue - attr_reader :lex_state - - def RubyLex.debug? - false - end - - def initialize(content) - lex_init - - @reader = BufferedReader.new(content) - - @exp_line_no = @line_no = 1 - @base_char_no = 0 - @indent = 0 - - @ltype = nil - @quoted = nil - @lex_state = EXPR_BEG - @space_seen = false - - @continue = false - @line = "" - - @skip_space = false - @read_auto_clean_up = false - @exception_on_syntax_error = true - end - - attr :skip_space, true - attr :read_auto_clean_up, true - attr :exception_on_syntax_error, true - - attr :indent - - # io functions - def line_no - @reader.line_num - end - - def char_no - @reader.column - end - - def get_read - @reader.get_read - end - - def getc - @reader.getc - end - - def getc_of_rests - @reader.getc_already_read - end - - def gets - c = getc or return - l = "" - begin - l.concat c unless c == "\r" - break if c == "\n" - end while c = getc - l - end - - - def ungetc(c = nil) - @reader.ungetc(c) - end - - def peek_equal?(str) - @reader.peek_equal(str) - end - - def peek(i = 0) - @reader.peek(i) - end - - def lex - until (((tk = token).kind_of?(TkNL) || tk.kind_of?(TkEND_OF_SCRIPT)) && - !@continue or - tk.nil?) - end - line = get_read - - if line == "" and tk.kind_of?(TkEND_OF_SCRIPT) || tk.nil? - nil - else - line - end - end - - def token - set_token_position(line_no, char_no) - begin - begin - tk = @OP.match(self) - @space_seen = tk.kind_of?(TkSPACE) - rescue SyntaxError - abort if @exception_on_syntax_error - tk = TkError.new(line_no, char_no) - end - end while @skip_space and tk.kind_of?(TkSPACE) - if @read_auto_clean_up - get_read - end -# throw :eof unless tk - p tk if $DEBUG - tk - end - - ENINDENT_CLAUSE = [ - "case", "class", "def", "do", "for", "if", - "module", "unless", "until", "while", "begin" #, "when" - ] - DEINDENT_CLAUSE = ["end" #, "when" - ] - - PERCENT_LTYPE = { - "q" => "\'", - "Q" => "\"", - "x" => "\`", - "r" => "/", - "w" => "]" - } - - PERCENT_PAREN = { - "{" => "}", - "[" => "]", - "<" => ">", - "(" => ")" - } - - Ltype2Token = { - "\'" => TkSTRING, - "\"" => TkSTRING, - "\`" => TkXSTRING, - "/" => TkREGEXP, - "]" => TkDSTRING - } - Ltype2Token.default = TkSTRING - - DLtype2Token = { - "\"" => TkDSTRING, - "\`" => TkDXSTRING, - "/" => TkDREGEXP, - } - - def lex_init() - @OP = SLex.new - @OP.def_rules("\0", "\004", "\032") do |chars, io| - Token(TkEND_OF_SCRIPT).set_text(chars) - end - - @OP.def_rules(" ", "\t", "\f", "\r", "\13") do |chars, io| - @space_seen = TRUE - while (ch = getc) =~ /[ \t\f\r\13]/ - chars << ch - end - ungetc - Token(TkSPACE).set_text(chars) - end - - @OP.def_rule("#") do - |op, io| - identify_comment - end - - @OP.def_rule("=begin", proc{@prev_char_no == 0 && peek(0) =~ /\s/}) do - |op, io| - str = op - @ltype = "=" - - - begin - line = "" - begin - ch = getc - line << ch - end until ch == "\n" - str << line - end until line =~ /^=end/ - - ungetc - - @ltype = nil - - if str =~ /\A=begin\s+rdoc/i - str.sub!(/\A=begin.*\n/, '') - str.sub!(/^=end.*/m, '') - Token(TkCOMMENT).set_text(str) - else - Token(TkRD_COMMENT)#.set_text(str) - end - end - - @OP.def_rule("\n") do - print "\\n\n" if RubyLex.debug? - case @lex_state - when EXPR_BEG, EXPR_FNAME, EXPR_DOT - @continue = TRUE - else - @continue = FALSE - @lex_state = EXPR_BEG - end - Token(TkNL).set_text("\n") - end - - @OP.def_rules("*", "**", - "!", "!=", "!~", - "=", "==", "===", - "=~", "<=>", - "<", "<=", - ">", ">=", ">>") do - |op, io| - @lex_state = EXPR_BEG - Token(op).set_text(op) - end - - @OP.def_rules("<<") do - |op, io| - tk = nil - if @lex_state != EXPR_END && @lex_state != EXPR_CLASS && - (@lex_state != EXPR_ARG || @space_seen) - c = peek(0) - if /[-\w_\"\'\`]/ =~ c - tk = identify_here_document - end - end - if !tk - @lex_state = EXPR_BEG - tk = Token(op).set_text(op) - end - tk - end - - @OP.def_rules("'", '"') do - |op, io| - identify_string(op) - end - - @OP.def_rules("`") do - |op, io| - if @lex_state == EXPR_FNAME - Token(op).set_text(op) - else - identify_string(op) - end - end - - @OP.def_rules('?') do - |op, io| - if @lex_state == EXPR_END - @lex_state = EXPR_BEG - Token(TkQUESTION).set_text(op) - else - ch = getc - if @lex_state == EXPR_ARG && ch !~ /\s/ - ungetc - @lex_state = EXPR_BEG; - Token(TkQUESTION).set_text(op) - else - str = op - str << ch - if (ch == '\\') #' - str << read_escape - end - @lex_state = EXPR_END - Token(TkINTEGER).set_text(str) - end - end - end - - @OP.def_rules("&", "&&", "|", "||") do - |op, io| - @lex_state = EXPR_BEG - Token(op).set_text(op) - end - - @OP.def_rules("+=", "-=", "*=", "**=", - "&=", "|=", "^=", "<<=", ">>=", "||=", "&&=") do - |op, io| - @lex_state = EXPR_BEG - op =~ /^(.*)=$/ - Token(TkOPASGN, $1).set_text(op) - end - - @OP.def_rule("+@", proc{@lex_state == EXPR_FNAME}) do |op, io| - Token(TkUPLUS).set_text(op) - end - - @OP.def_rule("-@", proc{@lex_state == EXPR_FNAME}) do |op, io| - Token(TkUMINUS).set_text(op) - end - - @OP.def_rules("+", "-") do - |op, io| - catch(:RET) do - if @lex_state == EXPR_ARG - if @space_seen and peek(0) =~ /[0-9]/ - throw :RET, identify_number(op) - else - @lex_state = EXPR_BEG - end - elsif @lex_state != EXPR_END and peek(0) =~ /[0-9]/ - throw :RET, identify_number(op) - else - @lex_state = EXPR_BEG - end - Token(op).set_text(op) - end - end - - @OP.def_rule(".") do - @lex_state = EXPR_BEG - if peek(0) =~ /[0-9]/ - ungetc - identify_number("") - else - # for obj.if - @lex_state = EXPR_DOT - Token(TkDOT).set_text(".") - end - end - - @OP.def_rules("..", "...") do - |op, io| - @lex_state = EXPR_BEG - Token(op).set_text(op) - end - - lex_int2 - end - - def lex_int2 - @OP.def_rules("]", "}", ")") do - |op, io| - @lex_state = EXPR_END - @indent -= 1 - Token(op).set_text(op) - end - - @OP.def_rule(":") do - if @lex_state == EXPR_END || peek(0) =~ /\s/ - @lex_state = EXPR_BEG - tk = Token(TkCOLON) - else - @lex_state = EXPR_FNAME; - tk = Token(TkSYMBEG) - end - tk.set_text(":") - end - - @OP.def_rule("::") do -# p @lex_state.id2name, @space_seen - if @lex_state == EXPR_BEG or @lex_state == EXPR_ARG && @space_seen - @lex_state = EXPR_BEG - tk = Token(TkCOLON3) - else - @lex_state = EXPR_DOT - tk = Token(TkCOLON2) - end - tk.set_text("::") - end - - @OP.def_rule("/") do - |op, io| - if @lex_state == EXPR_BEG || @lex_state == EXPR_MID - identify_string(op) - elsif peek(0) == '=' - getc - @lex_state = EXPR_BEG - Token(TkOPASGN, :/).set_text("/=") #") - elsif @lex_state == EXPR_ARG and @space_seen and peek(0) !~ /\s/ - identify_string(op) - else - @lex_state = EXPR_BEG - Token("/").set_text(op) - end - end - - @OP.def_rules("^") do - @lex_state = EXPR_BEG - Token("^").set_text("^") - end - - # @OP.def_rules("^=") do - # @lex_state = EXPR_BEG - # Token(TkOPASGN, :^) - # end - - @OP.def_rules(",", ";") do - |op, io| - @lex_state = EXPR_BEG - Token(op).set_text(op) - end - - @OP.def_rule("~") do - @lex_state = EXPR_BEG - Token("~").set_text("~") - end - - @OP.def_rule("~@", proc{@lex_state = EXPR_FNAME}) do - @lex_state = EXPR_BEG - Token("~").set_text("~@") - end - - @OP.def_rule("(") do - @indent += 1 - if @lex_state == EXPR_BEG || @lex_state == EXPR_MID - @lex_state = EXPR_BEG - tk = Token(TkfLPAREN) - else - @lex_state = EXPR_BEG - tk = Token(TkLPAREN) - end - tk.set_text("(") - end - - @OP.def_rule("[]", proc{@lex_state == EXPR_FNAME}) do - Token("[]").set_text("[]") - end - - @OP.def_rule("[]=", proc{@lex_state == EXPR_FNAME}) do - Token("[]=").set_text("[]=") - end - - @OP.def_rule("[") do - @indent += 1 - if @lex_state == EXPR_FNAME - t = Token(TkfLBRACK) - else - if @lex_state == EXPR_BEG || @lex_state == EXPR_MID - t = Token(TkLBRACK) - elsif @lex_state == EXPR_ARG && @space_seen - t = Token(TkLBRACK) - else - t = Token(TkfLBRACK) - end - @lex_state = EXPR_BEG - end - t.set_text("[") - end - - @OP.def_rule("{") do - @indent += 1 - if @lex_state != EXPR_END && @lex_state != EXPR_ARG - t = Token(TkLBRACE) - else - t = Token(TkfLBRACE) - end - @lex_state = EXPR_BEG - t.set_text("{") - end - - @OP.def_rule('\\') do #' - if getc == "\n" - @space_seen = true - @continue = true - Token(TkSPACE).set_text("\\\n") - else - ungetc - Token("\\").set_text("\\") #" - end - end - - @OP.def_rule('%') do - |op, io| - if @lex_state == EXPR_BEG || @lex_state == EXPR_MID - identify_quotation('%') - elsif peek(0) == '=' - getc - Token(TkOPASGN, "%").set_text("%=") - elsif @lex_state == EXPR_ARG and @space_seen and peek(0) !~ /\s/ - identify_quotation('%') - else - @lex_state = EXPR_BEG - Token("%").set_text("%") - end - end - - @OP.def_rule('$') do #' - identify_gvar - end - - @OP.def_rule('@') do - if peek(0) =~ /[@\w_]/ - ungetc - identify_identifier - else - Token("@").set_text("@") - end - end - - # @OP.def_rule("def", proc{|op, io| /\s/ =~ io.peek(0)}) do - # |op, io| - # @indent += 1 - # @lex_state = EXPR_FNAME - # # @lex_state = EXPR_END - # # until @rests[0] == "\n" or @rests[0] == ";" - # # rests.shift - # # end - # end - - @OP.def_rule("__END__", proc{@prev_char_no == 0 && peek(0) =~ /[\r\n]/}) do - throw :eof - end - - @OP.def_rule("") do - |op, io| - printf "MATCH: start %s: %s\n", op, io.inspect if RubyLex.debug? - if peek(0) =~ /[0-9]/ - t = identify_number("") - elsif peek(0) =~ /[\w_]/ - t = identify_identifier - end - 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 = "$" - - tk = case ch = getc - 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]/ - str << ch - end - ungetc - Token(TkNTH_REF) - when /\w/ - ungetc - ungetc - return identify_identifier - else - ungetc - Token("$") - end - tk.set_text(str) - end - - def identify_identifier - token = "" - token.concat getc if peek(0) =~ /[$@]/ - token.concat getc if peek(0) == "@" - - while (ch = getc) =~ /\w|_/ - print ":", ch, ":" if RubyLex.debug? - token.concat ch - end - ungetc - - if ch == "!" or ch == "?" - token.concat getc - end - # fix token - - # $stderr.puts "identifier - #{token}, state = #@lex_state" - - case token - when /^\$/ - return Token(TkGVAR, token).set_text(token) - when /^\@/ - @lex_state = EXPR_END - return Token(TkIVAR, token).set_text(token) - end - - if @lex_state != EXPR_DOT - print token, "\n" if RubyLex.debug? - - token_c, *trans = TkReading2Token[token] - if token_c - # reserved word? - - if (@lex_state != EXPR_BEG && - @lex_state != EXPR_FNAME && - trans[1]) - # modifiers - token_c = TkSymbol2Token[trans[1]] - @lex_state = trans[0] - else - if @lex_state != EXPR_FNAME - if ENINDENT_CLAUSE.include?(token) - @indent += 1 - elsif DEINDENT_CLAUSE.include?(token) - @indent -= 1 - end - @lex_state = trans[0] - else - @lex_state = EXPR_END - end - end - return Token(token_c, token).set_text(token) - end - end - - if @lex_state == EXPR_FNAME - @lex_state = EXPR_END - if peek(0) == '=' - token.concat getc - end - elsif @lex_state == EXPR_BEG || @lex_state == EXPR_DOT - @lex_state = EXPR_ARG - else - @lex_state = EXPR_END - end - - if token[0, 1] =~ /[A-Z]/ - return Token(TkCONSTANT, token).set_text(token) - elsif token[token.size - 1, 1] =~ /[!?]/ - return Token(TkFID, token).set_text(token) - else - return Token(TkIDENTIFIER, token).set_text(token) - end - end - - def identify_here_document - ch = getc - if ch == "-" - ch = getc - indent = true - end - if /['"`]/ =~ ch # ' - lt = ch - quoted = "" - while (c = getc) && c != lt - quoted.concat c - end - else - lt = '"' - quoted = ch.dup - while (c = getc) && c =~ /\w/ - quoted.concat c - end - ungetc - end - - ltback, @ltype = @ltype, lt - reserve = "" - - while ch = getc - reserve << ch - if ch == "\\" #" - ch = getc - reserve << ch - elsif ch == "\n" - break - end - end - - str = "" - while (l = gets) - l.chomp! - l.strip! if indent - break if l == quoted - str << l.chomp << "\n" - end - - @reader.divert_read_from(reserve) - - @ltype = ltback - @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] - initial_char += ch - ch = getc - elsif ch =~ /\W/ - lt = "\"" - else - RubyLex.fail SyntaxError, "unknown type of %string ('#{ch}')" - end -# if ch !~ /\W/ -# ungetc -# next -# end - #@ltype = lt - @quoted = ch unless @quoted = PERCENT_PAREN[ch] - identify_string(lt, @quoted, ch, initial_char) - end - - def identify_number(start) - str = start.dup - - if start == "+" or start == "-" or start == "" - start = getc - str << start - end - - @lex_state = EXPR_END - - if start == "0" - if peek(0) == "x" - ch = getc - str << ch - match = /[0-9a-f_]/ - else - match = /[0-7_]/ - end - while ch = getc - if ch !~ match - ungetc - break - else - str << ch - end - end - return Token(TkINTEGER).set_text(str) - end - - type = TkINTEGER - allow_point = TRUE - allow_e = TRUE - while ch = getc - case ch - when /[0-9_]/ - str << ch - - when allow_point && "." - type = TkFLOAT - if peek(0) !~ /[0-9]/ - ungetc - break - end - str << ch - allow_point = false - - when allow_e && "e", allow_e && "E" - str << ch - type = TkFLOAT - if peek(0) =~ /[+-]/ - str << getc - end - allow_e = false - allow_point = false - else - ungetc - break - end - end - Token(type).set_text(str) - end - - def identify_string(ltype, quoted = ltype, opener=nil, initial_char = nil) - @ltype = ltype - @quoted = quoted - subtype = nil - - str = "" - str << initial_char if initial_char - str << (opener||quoted) - - nest = 0 - begin - while ch = getc - str << ch - if @quoted == ch - if nest == 0 - break - else - nest -= 1 - end - elsif opener == ch - nest += 1 - elsif @ltype != "'" && @ltype != "]" and ch == "#" - ch = getc - if ch == "{" - subtype = true - str << ch << skip_inner_expression - else - ungetc(ch) - end - elsif ch == '\\' #' - str << read_escape - end - end - if @ltype == "/" - if peek(0) =~ /i|o|n|e|s/ - str << getc - end - end - if subtype - Token(DLtype2Token[ltype], str) - else - Token(Ltype2Token[ltype], str) - end.set_text(str) - ensure - @ltype = nil - @quoted = nil - @lex_state = EXPR_END - end - end - - def skip_inner_expression - res = "" - nest = 0 - while (ch = getc) - res << ch - if ch == '}' - break if nest.zero? - nest -= 1 - elsif ch == '{' - nest += 1 - end - end - res - end - - def identify_comment - @ltype = "#" - comment = "#" - while ch = getc - if ch == "\\" - ch = getc - if ch == "\n" - ch = " " - else - comment << "\\" - end - else - if ch == "\n" - @ltype = nil - ungetc - break - end - end - comment << ch - end - return Token(TkCOMMENT).set_text(comment) - end - - def read_escape - res = "" - case ch = getc - when /[0-7]/ - ungetc ch - 3.times do - case ch = getc - when /[0-7]/ - when nil - break - else - ungetc - break - end - res << ch - end - - when "x" - res << ch - 2.times do - case ch = getc - when /[0-9a-fA-F]/ - when nil - break - else - ungetc - break - end - res << ch - end - - when "M" - res << ch - if (ch = getc) != '-' - ungetc - else - res << ch - if (ch = getc) == "\\" #" - res << ch - res << read_escape - else - res << ch - end - end - - when "C", "c" #, "^" - res << ch - if ch == "C" and (ch = getc) != "-" - ungetc - else - res << ch - if (ch = getc) == "\\" #" - res << ch - res << read_escape - else - res << ch - end - end - else - res << ch - end - res - end -end - - - -# Extract code elements from a source file, returning a TopLevel -# object containing the constituent file elements. -# -# This file is based on rtags - -module RDoc - - GENERAL_MODIFIERS = [ 'nodoc' ].freeze - - CLASS_MODIFIERS = GENERAL_MODIFIERS - - ATTR_MODIFIERS = GENERAL_MODIFIERS - - CONSTANT_MODIFIERS = GENERAL_MODIFIERS - - METHOD_MODIFIERS = GENERAL_MODIFIERS + - [ 'arg', 'args', 'yield', 'yields', 'notnew', 'not-new', 'not_new', 'doc' ] - - - class RubyParser - include RubyToken - include TokenStream - - extend ParserFactory - - 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) - @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 - end - end - @top_level - end - - private - - 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 error(msg) - msg = make_message msg - $stderr.puts msg - exit(1) - 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 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 - - 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) - tk = Token(TkSYMBOL).set_text(":" + tk1.name) - # 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 identified 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 - - tk - end - - def peek_tk - unget_tk(tk = get_tk) - tk - end - - 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 - - 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 peek_read - @read.join('') - end - - NORMAL = "::" - SINGLE = "<<" - - # Look for the first comment in a file that isn't - # a shebang line. - - 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 - if tk.kind_of? TkNL - skip_tkspace(false) - tk = get_tk - end - end - 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 - -# 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 - - 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 - end - 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 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 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 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 - - when TkUNTIL, TkWHILE - nest += 1 - puts "FOUND #{tk.class} in #{container.name}, nest = #{nest}, " + - "line #{tk.line_no}" if $DEBUG - 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 - 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 - - 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 TkEND - nest -= 1 - puts "Found 'end' in #{container.name}, nest = #{nest}, line #{tk.line_no}" if $DEBUG - puts "Method = #{current_method.name}" if $DEBUG and current_method - if nest == 0 - read_documentation_modifiers(container, 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 - end - - def parse_class(container, single, tk, comment, &block) - progress("c") - - @stats.num_classes += 1 - - container, name_t = get_class_or_module(container) - - 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 - - if single == SINGLE - cls_type = SingleClass - else - cls_type = NormalClass - end - - cls = container.add_class(cls_type, name, superclass) - read_documentation_modifiers(cls, CLASS_MODIFIERS) - cls.record_location(@top_level) - parse_statements(cls) - cls.comment = comment - - 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 - - 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) -# 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 - - # 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 - - # 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) - - 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 - 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 - - unless eq_tk.kind_of?(TkASSIGN) - unget_tk(eq_tk) - return - end - - - nest = 0 - get_tkread - - 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 - - case tk - when TkSEMICOLON - break - when TkLPAREN, TkfLPAREN - nest += 1 - when TkRPAREN - nest -= 1 - when TkCOMMENT - if nest <= 0 && @scanner.lex_state == EXPR_END - unget_tk(tk) - break - end - when TkNL - if (@scanner.lex_state == EXPR_END and nest <= 0) || !@scanner.continue - unget_tk(tk) - break - end - end - tk = get_tk - 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 - 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) - - @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 - - 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 - end - else - # warn("Unexpected token '#{name_t2.inspect}'") - # break - skip_method(container) - return - end - meth = AnyMethod.new(get_tkread, name) - meth.singleton = true - else - unget_tk dot - back_tk.reverse_each do - |tk| - unget_tk tk - end - name = name_t.name - - meth = AnyMethod.new(get_tkread, name) - meth.singleton = (single == SINGLE) - end - - remove_token_listener(self) - - meth.start_collecting_tokens - indent = TkSPACE.new(1,1) - indent.set_text(" " * column) - - meth.add_tokens([TkCOMMENT.new(line_no, - 1, - "# File #{@top_level.file_absolute_name}, line #{line_no}"), - NEWLINE_TOKEN, - indent]) - - meth.add_tokens(@token_stream) - - add_token_listener(meth) - - @scanner.instance_eval{@continue = false} - parse_method_parameters(meth) - - if meth.document_self - container.add_method(meth) - elsif added_container - container.document_self = false - end - - # Having now read the method parameters and documentation modifiers, we - # now know whether we have to rename #initialize to ::new - - if name == "initialize" && !meth.singleton - if meth.dont_rename_initialize - meth.visibility = :protected - else - meth.singleton = true - meth.name = "new" - meth.visibility = :public - end - end - - parse_statements(container, single, meth) - - remove_token_listener(meth) - - # Look for a 'call-seq' in the comment, and override the - # normal parameter stuff - - if comment.sub!(/:?call-seq:(.*?)^\s*\#?\s*$/m, '') - seq = $1 - seq.gsub!(/^\s*\#\s*/, '') - meth.call_seq = seq - end - - meth.comment = comment - - end - - def skip_method(container) - meth = AnyMethod.new("", "anon") - parse_method_parameters(meth) - parse_statements(container, false, meth) - end - - # Capture the method's parameters. Along the way, - # look for a comment containing - # - # # yields: .... - # - # and add this as the block_params for the method - - def parse_method_parameters(method) - res = parse_method_or_yield_parameters(method) - res = "(" + res + ")" unless res[0] == ?( - method.params = res unless method.params - if method.block_params.nil? - skip_tkspace(false) - read_documentation_modifiers(method, METHOD_MODIFIERS) - end - end - - def parse_method_or_yield_parameters(method=nil, modifiers=METHOD_MODIFIERS) - skip_tkspace(false) - tk = get_tk - - # 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: #{tk.inspect}, #{@scanner.continue} " + - "#{@scanner.lex_state} #{nest}") if $DEBUG - case tk - when TkSEMICOLON - break - when TkLBRACE - nest += 1 - when TkRBRACE - # we might have a.each {|i| yield i } - unget_tk(tk) if nest.zero? - nest -= 1 - break if nest <= 0 - when TkLPAREN, TkfLPAREN - nest += 1 - when end_token - if end_token == TkRPAREN - nest -= 1 - break if @scanner.lex_state == EXPR_END and nest <= 0 - else - break unless @scanner.continue - end - when method && method.block_params.nil? && TkCOMMENT - unget_tk(tk) - read_documentation_modifiers(method, modifiers) - end - 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) - 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 - - nest = 0 - @scanner.instance_eval{@continue = false} - - loop do - puts("\nWhile: #{tk}, #{@scanner.continue} " + - "#{@scanner.lex_state} #{nest}") if $DEBUG - 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 - end - tk = get_tk - end - skip_tkspace(false) - if peek_tk.kind_of? TkDO - get_tk - end - end - - # Return a superclass, which can be either a constant - # of an expression - - 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 - - unget_tk(tk) - skip_tkspace(false) - - get_tkread # empty out read buffer - - tk = get_tk - - case tk - when TkNL, TkCOMMENT, TkSEMICOLON - unget_tk(tk) - return res - end - - 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 - - loop do - puts("Call param: #{tk}, #{@scanner.continue} " + - "#{@scanner.lex_state} #{nest}") if $DEBUG - case tk - when TkSEMICOLON - break - when TkLPAREN, TkfLPAREN - nest += 1 - when end_token - if end_token == TkRPAREN - nest -= 1 - break if @scanner.lex_state == EXPR_END and nest <= 0 - else - break unless @scanner.continue - end - when TkCOMMENT - unget_tk(tk) - break - end - tk = get_tk - 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 - - def get_constant - res = "" - skip_tkspace(false) - tk = get_tk - - while tk.kind_of?(TkCOLON2) || - tk.kind_of?(TkCOLON3) || - tk.kind_of?(TkCONSTANT) - - res += tk.text - tk = get_tk - end - -# if res.empty? -# warn("Unexpected token #{tk} in constant") -# 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 - - 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) - tk = get_tk - puts "directive: #{tk.inspect}" if $DEBUG - result = nil - if tk.kind_of?(TkCOMMENT) - if tk.text =~ /\s*:?(\w+):\s*(.*)/ - directive = $1.downcase - if allowed.include?(directive) - result = [directive, $2] - end - end - else - unget_tk(tk) - end - result - end - - - def read_documentation_modifiers(context, allow) - dir = read_directive(allow) - - case dir[0] - - 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 "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 = Options.instance - options.main_page = param - "" - - when "title" - options = Options.instance - options.title = param - "" - - when "section" - context.set_current_section(param, comment) - comment.replace("") # 1.8 doesn't support #clear - break - else - warn "Unrecognized directive '#{directive}'" - break - end - end - - 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 - 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 - 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 - - def parse_yield_parameters - parse_method_or_yield_parameters - end - - def parse_yield(context, single, tk, method) - if method.block_params.nil? - get_tkread - @scanner.instance_eval{@continue = false} - method.block_params = parse_yield_parameters - end - end - - def parse_require(context, comment) - skip_tkspace_comment - tk = get_tk - if tk.kind_of? TkLPAREN - skip_tkspace_comment - tk = get_tk - end - - name = nil - case tk - when TkSTRING - name = tk.text -# when TkCONSTANT, TkIDENTIFIER, TkIVAR, TkGVAR -# name = tk.name - when TkDSTRING - warn "Skipping require of dynamic string: #{tk.text}" - # else - # warn "'require' used as variable" - end - if name - context.add_require(Require.new(name, comment)) - else - unget_tk(tk) - end - end - - def parse_include(context, comment) - loop do - skip_tkspace_comment - name = get_constant_with_optional_parens - unless name.empty? - context.add_include(Include.new(name, comment)) - end - return unless peek_tk.kind_of?(TkCOMMA) - get_tk - end - end - - 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 - 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) - end - end - - def parse_attr_accessor(context, single, tk, comment) - args = parse_symbol_arg - read = get_tkread - rw = "?" - - # If nodoc is given, don't document any of them - - tmp = CodeObject.new - read_documentation_modifiers(tmp, 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 - - for name in args - att = Attr.new(get_tkread, name, rw, comment) - context.add_attribute(att) - end - end - - def skip_tkspace_comment(skip_nl = true) - loop do - skip_tkspace(skip_nl) - return unless peek_tk.kind_of? TkCOMMENT - get_tk - end - end - - def parse_symbol_arg(no = nil) - - 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 - break - end - end - else - unget_tk tk - if tk = parse_symbol_in_arg - args.push tk - return args if no and args.size >= no - end - - loop do -# skip_tkspace_comment(false) - skip_tkspace(false) - - 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 - 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 - nil - end - end - end - -end diff --git a/lib/rdoc/parsers/parse_simple.rb b/lib/rdoc/parsers/parse_simple.rb deleted file mode 100644 index 3f1a546964..0000000000 --- a/lib/rdoc/parsers/parse_simple.rb +++ /dev/null @@ -1,41 +0,0 @@ -# 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 - - def remove_private_comments(comment) - comment.gsub(/^--.*?^\+\+/m, '').sub(/^--.*/m, '') - end - end -end diff --git a/lib/rdoc/parsers/parserfactory.rb b/lib/rdoc/parsers/parserfactory.rb deleted file mode 100644 index 00a82cf4b1..0000000000 --- a/lib/rdoc/parsers/parserfactory.rb +++ /dev/null @@ -1,99 +0,0 @@ -require "rdoc/parsers/parse_simple" - -module RDoc - - # A parser is simple a class that implements - # - # #initialize(file_name, body, options) - # - # and - # - # #scan - # - # The initialize method takes a file name to be used, the body of the - # file, and an RDoc::Options object. The scan method is then called - # to return an appropriately parsed TopLevel code object. - # - # The ParseFactory is used to redirect to the correct parser given a filename - # extension. This magic works because individual parsers have to register - # themselves with us as they are loaded in. The do this using the following - # incantation - # - # - # require "rdoc/parsers/parsefactory" - # - # module RDoc - # - # class XyzParser - # extend ParseFactory <<<< - # parse_files_matching /\.xyz$/ <<<< - # - # def initialize(file_name, body, options) - # ... - # end - # - # def scan - # ... - # end - # end - # end - # - # Just to make life interesting, if we suspect a plain text file, we - # also look for a shebang line just in case it's a potential - # shell script - - - - module ParserFactory - - @@parsers = [] - - Parsers = Struct.new(:regexp, :parser) - - # Record the fact that a particular class parses files that - # match a given extension - - def parse_files_matching(regexp) - @@parsers.unshift Parsers.new(regexp, self) - end - - # Return a parser that can handle a particular extension - - def ParserFactory.can_parse(file_name) - @@parsers.find {|p| p.regexp.match(file_name) } - end - - # Alias an extension to another extension. After this call, - # files ending "new_ext" will be parsed using the same parser - # as "old_ext" - - def ParserFactory.alias_extension(old_ext, new_ext) - parser = ParserFactory.can_parse("xxx.#{old_ext}") - return false unless parser - @@parsers.unshift Parsers.new(Regexp.new("\\.#{new_ext}$"), parser.parser) - true - end - - # Find the correct parser for a particular file name. Return a - # SimpleParser for ones that we don't know - - def ParserFactory.parser_for(top_level, file_name, body, options, stats) - # If no extension, look for shebang - if file_name !~ /\.\w+$/ && body =~ %r{\A#!(.+)} - shebang = $1 - case shebang - when %r{env\s+ruby}, %r{/ruby} - file_name = "dummy.rb" - end - end - parser_description = can_parse(file_name) - if parser_description - parser = parser_description.parser - else - parser = SimpleParser - end - - parser.new(top_level, file_name, body, options, stats) - end - end -end diff --git a/lib/rdoc/rd.rb b/lib/rdoc/rd.rb new file mode 100644 index 0000000000..0d3d3cea85 --- /dev/null +++ b/lib/rdoc/rd.rb @@ -0,0 +1,100 @@ +# frozen_string_literal: true +## +# RDoc::RD implements the RD format from the rdtool gem. +# +# To choose RD as your only default format see +# RDoc::Options@Saved+Options for instructions on setting up a +# <code>.doc_options</code> file to store your project default. +# +# == LICENSE +# +# The grammar that produces RDoc::RD::BlockParser and RDoc::RD::InlineParser +# is included in RDoc under the Ruby License. +# +# You can find the original source for rdtool at +# https://github.com/uwabami/rdtool/ +# +# You can use, re-distribute or change these files under Ruby's License or GPL. +# +# 1. You may make and give away verbatim copies of the source form of the +# software without restriction, provided that you duplicate all of the +# original copyright notices and associated disclaimers. +# +# 2. You may modify your copy of the software in any way, provided that +# you do at least ONE of the following: +# +# a. place your modifications in the Public Domain or otherwise +# make them Freely Available, such as by posting said +# modifications to Usenet or an equivalent medium, or by allowing +# the author to include your modifications in the software. +# +# b. use the modified software only within your corporation or +# organization. +# +# c. give non-standard binaries non-standard names, with +# instructions on where to get the original software distribution. +# +# d. make other distribution arrangements with the author. +# +# 3. You may distribute the software in object code or binary form, +# provided that you do at least ONE of the following: +# +# a. distribute the binaries and library files of the software, +# together with instructions (in the manual page or equivalent) +# on where to get the original distribution. +# +# b. accompany the distribution with the machine-readable source of +# the software. +# +# c. give non-standard binaries non-standard names, with +# instructions on where to get the original software distribution. +# +# d. make other distribution arrangements with the author. +# +# 4. You may modify and include the part of the software into any other +# software (possibly commercial). But some files in the distribution +# are not written by the author, so that they are not under these terms. +# +# For the list of those files and their copying conditions, see the +# file LEGAL. +# +# 5. The scripts and library files supplied as input to or produced as +# output from the software do not automatically fall under the +# copyright of the software, but belong to whomever generated them, +# and may be sold commercially, and may be aggregated with this +# software. +# +# 6. THIS SOFTWARE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR +# IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED +# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +# PURPOSE. + +class RDoc::RD + + ## + # Parses +rd+ source and returns an RDoc::Markup::Document. If the + # <tt>=begin</tt> or <tt>=end</tt> lines are missing they will be added. + + def self.parse rd + rd = rd.lines.to_a + + if rd.find { |i| /\S/ === i } and !rd.find{|i| /^=begin\b/ === i } then + rd.unshift("=begin\n").push("=end\n") + end + + parser = RDoc::RD::BlockParser.new + document = parser.parse rd + + # isn't this always true? + document.parts.shift if RDoc::Markup::BlankLine === document.parts.first + document.parts.pop if RDoc::Markup::BlankLine === document.parts.last + + document + end + + autoload :BlockParser, 'rdoc/rd/block_parser' + autoload :InlineParser, 'rdoc/rd/inline_parser' + autoload :Inline, 'rdoc/rd/inline' + +end + diff --git a/lib/rdoc/rd/block_parser.rb b/lib/rdoc/rd/block_parser.rb new file mode 100644 index 0000000000..3f4941168f --- /dev/null +++ b/lib/rdoc/rd/block_parser.rb @@ -0,0 +1,1055 @@ +# +# DO NOT MODIFY!!!! +# This file is automatically generated by Racc 1.4.14 +# from Racc grammer file "". +# + +require 'racc/parser.rb' + +class RDoc::RD + +## +# RD format parser for headings, paragraphs, lists, verbatim sections that +# exist as blocks. + +class BlockParser < Racc::Parser + + +# :stopdoc: + +TMPFILE = ["rdtmp", $$, 0] + +MARK_TO_LEVEL = { + '=' => 1, + '==' => 2, + '===' => 3, + '====' => 4, + '+' => 5, + '++' => 6, +} + +# :startdoc: + +## +# Footnotes for this document + +attr_reader :footnotes + +## +# Labels for items in this document + +attr_reader :labels + +## +# Path to find included files in + +attr_accessor :include_path + +## +# Creates a new RDoc::RD::BlockParser. Use #parse to parse an rd-format +# document. + +def initialize + @inline_parser = RDoc::RD::InlineParser.new self + @include_path = [] + + # for testing + @footnotes = [] + @labels = {} +end + +## +# Parses +src+ and returns an RDoc::Markup::Document. + +def parse src + @src = src + @src.push false + + @footnotes = [] + @labels = {} + + # @i: index(line no.) of src + @i = 0 + + # stack for current indentation + @indent_stack = [] + + # how indented. + @current_indent = @indent_stack.join("") + + # RDoc::RD::BlockParser for tmp src + @subparser = nil + + # which part is in now + @in_part = nil + @part_content = [] + + @in_verbatim = false + + @yydebug = true + + document = do_parse + + unless @footnotes.empty? then + blankline = document.parts.pop + + document.parts << RDoc::Markup::Rule.new(1) + document.parts.concat @footnotes + + document.parts.push blankline + end + + document +end + +## +# Returns the next token from the document + +def next_token # :nodoc: + # preprocessing + # if it is not in RD part + # => method + while @in_part != "rd" + line = @src[@i] + @i += 1 # next line + + case line + # src end + when false + return [false, false] + # RD part begin + when /^=begin\s*(?:\bRD\b.*)?\s*$/ + if @in_part # if in non-RD part + @part_content.push(line) + else + @in_part = "rd" + return [:WHITELINE, "=begin\n"] # <= for textblockand + end + # non-RD part begin + when /^=begin\s+(\w+)/ + part = $1 + if @in_part # if in non-RD part + @part_content.push(line) + else + @in_part = part if @tree.filter[part] # if filter exists +# p "BEGIN_PART: #{@in_part}" # DEBUG + end + # non-RD part end + when /^=end/ + if @in_part # if in non-RD part +# p "END_PART: #{@in_part}" # DEBUG + # make Part-in object + part = RDoc::RD::Part.new(@part_content.join(""), @tree, "r") + @part_content.clear + # call filter, part_out is output(Part object) + part_out = @tree.filter[@in_part].call(part) + + if @tree.filter[@in_part].mode == :rd # if output is RD formatted + subtree = parse_subtree(part_out.to_a) + else # if output is target formatted + basename = TMPFILE.join('.') + TMPFILE[-1] += 1 + tmpfile = open(@tree.tmp_dir + "/" + basename + ".#{@in_part}", "w") + tmpfile.print(part_out) + tmpfile.close + subtree = parse_subtree(["=begin\n", "<<< #{basename}\n", "=end\n"]) + end + @in_part = nil + return [:SUBTREE, subtree] + end + else + if @in_part # if in non-RD part + @part_content.push(line) + end + end + end + + @current_indent = @indent_stack.join("") + line = @src[@i] + case line + when false + if_current_indent_equal("") do + [false, false] + end + when /^=end/ + if_current_indent_equal("") do + @in_part = nil + [:WHITELINE, "=end"] # MUST CHANGE?? + end + when /^\s*$/ + @i += 1 # next line + return [:WHITELINE, ':WHITELINE'] + when /^\#/ # comment line + @i += 1 # next line + self.next_token() + when /^(={1,4})(?!=)\s*(?=\S)/, /^(\+{1,2})(?!\+)\s*(?=\S)/ + rest = $' # ' + rest.strip! + mark = $1 + if_current_indent_equal("") do + return [:HEADLINE, [MARK_TO_LEVEL[mark], rest]] + end + when /^<<<\s*(\S+)/ + file = $1 + if_current_indent_equal("") do + suffix = file[-3 .. -1] + if suffix == ".rd" or suffix == ".rb" + subtree = parse_subtree(get_included(file)) + [:SUBTREE, subtree] + else + [:INCLUDE, file] + end + end + when /^(\s*)\*(\s*)/ + rest = $' # ' + newIndent = $2 + if_current_indent_equal($1) do + if @in_verbatim + [:STRINGLINE, line] + else + @indent_stack.push("\s" << newIndent) + [:ITEMLISTLINE, rest] + end + end + when /^(\s*)(\(\d+\))(\s*)/ + rest = $' # ' + mark = $2 + newIndent = $3 + if_current_indent_equal($1) do + if @in_verbatim + [:STRINGLINE, line] + else + @indent_stack.push("\s" * mark.size << newIndent) + [:ENUMLISTLINE, rest] + end + end + when /^(\s*):(\s*)/ + rest = $' # ' + newIndent = $2 + if_current_indent_equal($1) do + if @in_verbatim + [:STRINGLINE, line] + else + @indent_stack.push("\s#{$2}") + [:DESCLISTLINE, rest] + end + end + when /^(\s*)---(?!-|\s*$)/ + indent = $1 + rest = $' + /\s*/ === rest + term = $' + new_indent = $& + if_current_indent_equal(indent) do + if @in_verbatim + [:STRINGLINE, line] + else + @indent_stack.push("\s\s\s" + new_indent) + [:METHODLISTLINE, term] + end + end + when /^(\s*)/ + if_current_indent_equal($1) do + [:STRINGLINE, line] + end + else + raise "[BUG] parsing error may occurred." + end +end + +## +# Yields to the given block if +indent+ matches the current indent, otherwise +# an indentation token is processed. + +def if_current_indent_equal(indent) + indent = indent.sub(/\t/, "\s" * 8) + if @current_indent == indent + @i += 1 # next line + yield + elsif indent.index(@current_indent) == 0 + @indent_stack.push(indent[@current_indent.size .. -1]) + [:INDENT, ":INDENT"] + else + @indent_stack.pop + [:DEDENT, ":DEDENT"] + end +end +private :if_current_indent_equal + +## +# Cuts off excess whitespace in +src+ + +def cut_off(src) + ret = [] + whiteline_buf = [] + + line = src.shift + /^\s*/ =~ line + + indent = Regexp.quote($&) + ret.push($') + + while line = src.shift + if /^(\s*)$/ =~ line + whiteline_buf.push(line) + elsif /^#{indent}/ =~ line + unless whiteline_buf.empty? + ret.concat(whiteline_buf) + whiteline_buf.clear + end + ret.push($') + else + raise "[BUG]: probably Parser Error while cutting off.\n" + end + end + ret +end +private :cut_off + +def set_term_to_element(parent, term) +# parent.set_term_under_document_struct(term, @tree.document_struct) + parent.set_term_without_document_struct(term) +end +private :set_term_to_element + +## +# Raises a ParseError when invalid formatting is found + +def on_error(et, ev, _values) + prv, cur, nxt = format_line_num(@i, @i+1, @i+2) + + raise ParseError, <<Msg + +RD syntax error: line #{@i+1}: + #{prv} |#{@src[@i-1].chomp} + #{cur}=>|#{@src[@i].chomp} + #{nxt} |#{@src[@i+1].chomp} + +Msg +end + +## +# Current line number + +def line_index + @i +end + +## +# Parses subtree +src+ + +def parse_subtree src + @subparser ||= RDoc::RD::BlockParser.new + + @subparser.parse src +end +private :parse_subtree + +## +# Retrieves the content for +file+ from the include_path + +def get_included(file) + included = [] + + @include_path.each do |dir| + file_name = File.join dir, file + + if File.exist? file_name then + included = IO.readlines file_name + break + end + end + + included +end +private :get_included + +## +# Formats line numbers +line_numbers+ prettily + +def format_line_num(*line_numbers) + width = line_numbers.collect{|i| i.to_s.length }.max + line_numbers.collect{|i| sprintf("%#{width}d", i) } +end +private :format_line_num + +## +# Retrieves the content of +values+ as a single String + +def content values + values.map { |value| value.content }.join +end + +## +# Creates a paragraph for +value+ + +def paragraph value + content = cut_off(value).join(' ').rstrip + contents = @inline_parser.parse content + + RDoc::Markup::Paragraph.new(*contents) +end + +## +# Adds footnote +content+ to the document + +def add_footnote content + index = @footnotes.length / 2 + 1 + + footmark_link = "{^#{index}}[rdoc-label:footmark-#{index}:foottext-#{index}]" + + @footnotes << RDoc::Markup::Paragraph.new(footmark_link, ' ', *content) + @footnotes << RDoc::Markup::BlankLine.new + + index +end + +## +# Adds label +label+ to the document + +def add_label label + @labels[label] = true + + label +end + +# :stopdoc: + +##### State transition tables begin ### + +racc_action_table = [ + 34, 35, 30, 33, 40, 34, 35, 30, 33, 40, + 65, 34, 35, 30, 33, 14, 73, 14, 54, 76, + 15, 88, 34, 35, 30, 33, 14, 73, 77, 33, + 54, 15, 34, 35, 30, 33, 14, 73, 81, 38, + 38, 15, 34, 35, 30, 33, 14, 73, 40, 36, + 83, 15, 34, 35, 30, 33, 54, 47, 30, 35, + 34, 15, 34, 35, 30, 33, 14, 73, 38, 67, + 59, 15, 34, 35, 30, 33, 14, 9, 10, 11, + 12, 15, 34, 35, 30, 33, 14, 73, 14, nil, + nil, 15, 34, 35, 30, 33, 14, 73, nil, nil, + nil, 15, 34, 35, 30, 33, nil, 47, nil, nil, + nil, 15, 34, 35, 30, 33, 14, 73, nil, nil, + nil, 15, 34, 35, 30, 33, 14, 73, nil, nil, + nil, 15, 34, 35, 30, 33, 14, 9, 10, 11, + 12, 15, 34, 35, 30, 33, 14, 73, 61, 63, + nil, 15, 14, 62, 60, 61, 63, 79, 61, 63, + 62, 87, nil, 62, 34, 35, 30, 33 ] + +racc_action_check = [ + 41, 41, 41, 41, 41, 15, 15, 15, 15, 15, + 41, 86, 86, 86, 86, 86, 86, 34, 33, 49, + 86, 86, 85, 85, 85, 85, 85, 85, 51, 31, + 54, 85, 79, 79, 79, 79, 79, 79, 56, 57, + 58, 79, 78, 78, 78, 78, 78, 78, 62, 1, + 66, 78, 24, 24, 24, 24, 30, 24, 28, 25, + 22, 24, 75, 75, 75, 75, 75, 75, 13, 44, + 36, 75, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 46, 46, 46, 46, 46, 46, 35, nil, + nil, 46, 45, 45, 45, 45, 45, 45, nil, nil, + nil, 45, 27, 27, 27, 27, nil, 27, nil, nil, + nil, 27, 74, 74, 74, 74, 74, 74, nil, nil, + nil, 74, 68, 68, 68, 68, 68, 68, nil, nil, + nil, 68, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 47, 47, 47, 47, 47, 47, 39, 39, + nil, 47, 52, 39, 39, 82, 82, 52, 64, 64, + 82, 82, nil, 64, 20, 20, 20, 20 ] + +racc_action_pointer = [ + 129, 49, 69, nil, nil, nil, nil, nil, nil, nil, + nil, nil, nil, 61, nil, 2, nil, nil, nil, nil, + 161, nil, 57, nil, 49, 55, nil, 99, 53, nil, + 48, 23, nil, 10, 10, 81, 70, nil, nil, 141, + nil, -3, nil, nil, 56, 89, 79, 139, nil, 6, + nil, 15, 145, nil, 22, nil, 25, 32, 33, nil, + nil, nil, 41, nil, 151, nil, 37, nil, 119, nil, + nil, nil, nil, nil, 109, 59, nil, nil, 39, 29, + nil, nil, 148, nil, nil, 19, 8, nil, nil ] + +racc_action_default = [ + -2, -73, -1, -4, -5, -6, -7, -8, -9, -10, + -11, -12, -13, -14, -16, -73, -23, -24, -25, -26, + -27, -31, -32, -34, -72, -36, -38, -72, -40, -42, + -59, -44, -46, -59, -63, -65, -73, -3, -15, -73, + -22, -73, -30, -33, -73, -69, -70, -71, -37, -73, + -41, -73, -51, -58, -61, -45, -73, -62, -64, 89, + -17, -19, -73, -21, -18, -28, -73, -35, -66, -53, + -54, -55, -56, -57, -67, -68, -39, -43, -49, -73, + -60, -47, -73, -29, -52, -48, -73, -20, -50 ] + +racc_goto_table = [ + 4, 39, 4, 68, 74, 75, 6, 5, 6, 5, + 44, 42, 51, 49, 3, 56, 37, 57, 58, 80, + 2, 66, 84, 41, 43, 48, 50, 64, 84, 84, + 46, 45, 42, 46, 45, 55, 85, 86, 1, 84, + 84, nil, nil, nil, nil, nil, nil, nil, 82, nil, + nil, nil, 78 ] + +racc_goto_check = [ + 4, 10, 4, 31, 31, 31, 6, 5, 6, 5, + 21, 12, 27, 21, 3, 27, 3, 9, 9, 33, + 2, 11, 32, 17, 19, 23, 26, 10, 32, 32, + 6, 5, 12, 6, 5, 29, 31, 31, 1, 32, + 32, nil, nil, nil, nil, nil, nil, nil, 10, nil, + nil, nil, 4 ] + +racc_goto_pointer = [ + nil, 38, 20, 14, 0, 7, 6, nil, nil, -17, + -14, -20, -9, nil, nil, nil, nil, 8, nil, 2, + nil, -14, nil, 0, nil, nil, -2, -18, nil, 4, + nil, -42, -46, -35 ] + +racc_goto_default = [ + nil, nil, nil, nil, 70, 71, 72, 7, 8, 13, + nil, nil, 21, 16, 17, 18, 19, 20, 22, 23, + 24, nil, 25, 26, 27, 28, 29, nil, 31, 32, + 52, nil, 69, 53 ] + +racc_reduce_table = [ + 0, 0, :racc_error, + 1, 15, :_reduce_1, + 0, 15, :_reduce_2, + 2, 16, :_reduce_3, + 1, 16, :_reduce_4, + 1, 17, :_reduce_5, + 1, 17, :_reduce_6, + 1, 17, :_reduce_none, + 1, 17, :_reduce_8, + 1, 17, :_reduce_9, + 1, 17, :_reduce_10, + 1, 17, :_reduce_11, + 1, 21, :_reduce_12, + 1, 22, :_reduce_13, + 1, 18, :_reduce_14, + 2, 23, :_reduce_15, + 1, 23, :_reduce_16, + 3, 19, :_reduce_17, + 1, 25, :_reduce_18, + 2, 24, :_reduce_19, + 4, 24, :_reduce_20, + 2, 24, :_reduce_21, + 1, 24, :_reduce_22, + 1, 26, :_reduce_none, + 1, 26, :_reduce_none, + 1, 26, :_reduce_none, + 1, 26, :_reduce_none, + 1, 20, :_reduce_27, + 3, 20, :_reduce_28, + 4, 20, :_reduce_29, + 2, 31, :_reduce_30, + 1, 31, :_reduce_31, + 1, 27, :_reduce_32, + 2, 32, :_reduce_33, + 1, 32, :_reduce_34, + 3, 33, :_reduce_35, + 1, 28, :_reduce_36, + 2, 36, :_reduce_37, + 1, 36, :_reduce_38, + 3, 37, :_reduce_39, + 1, 29, :_reduce_40, + 2, 39, :_reduce_41, + 1, 39, :_reduce_42, + 3, 40, :_reduce_43, + 1, 30, :_reduce_44, + 2, 42, :_reduce_45, + 1, 42, :_reduce_46, + 3, 43, :_reduce_47, + 3, 41, :_reduce_48, + 2, 41, :_reduce_49, + 4, 41, :_reduce_50, + 1, 41, :_reduce_51, + 2, 45, :_reduce_52, + 1, 45, :_reduce_none, + 1, 46, :_reduce_54, + 1, 46, :_reduce_55, + 1, 46, :_reduce_none, + 1, 46, :_reduce_57, + 1, 44, :_reduce_none, + 0, 44, :_reduce_none, + 2, 47, :_reduce_none, + 1, 47, :_reduce_none, + 2, 34, :_reduce_62, + 1, 34, :_reduce_63, + 2, 38, :_reduce_64, + 1, 38, :_reduce_65, + 2, 35, :_reduce_66, + 2, 35, :_reduce_67, + 2, 35, :_reduce_68, + 1, 35, :_reduce_69, + 1, 35, :_reduce_none, + 1, 35, :_reduce_71, + 0, 35, :_reduce_72 ] + +racc_reduce_n = 73 + +racc_shift_n = 89 + +racc_token_table = { + false => 0, + :error => 1, + :DUMMY => 2, + :ITEMLISTLINE => 3, + :ENUMLISTLINE => 4, + :DESCLISTLINE => 5, + :METHODLISTLINE => 6, + :STRINGLINE => 7, + :WHITELINE => 8, + :SUBTREE => 9, + :HEADLINE => 10, + :INCLUDE => 11, + :INDENT => 12, + :DEDENT => 13 } + +racc_nt_base = 14 + +racc_use_result_var = true + +Racc_arg = [ + racc_action_table, + racc_action_check, + racc_action_default, + racc_action_pointer, + racc_goto_table, + racc_goto_check, + racc_goto_default, + racc_goto_pointer, + racc_nt_base, + racc_reduce_table, + racc_token_table, + racc_shift_n, + racc_reduce_n, + racc_use_result_var ] + +Racc_token_to_s_table = [ + "$end", + "error", + "DUMMY", + "ITEMLISTLINE", + "ENUMLISTLINE", + "DESCLISTLINE", + "METHODLISTLINE", + "STRINGLINE", + "WHITELINE", + "SUBTREE", + "HEADLINE", + "INCLUDE", + "INDENT", + "DEDENT", + "$start", + "document", + "blocks", + "block", + "textblock", + "verbatim", + "lists", + "headline", + "include", + "textblockcontent", + "verbatimcontent", + "verbatim_after_lists", + "list", + "itemlist", + "enumlist", + "desclist", + "methodlist", + "lists2", + "itemlistitems", + "itemlistitem", + "first_textblock_in_itemlist", + "other_blocks_in_list", + "enumlistitems", + "enumlistitem", + "first_textblock_in_enumlist", + "desclistitems", + "desclistitem", + "description_part", + "methodlistitems", + "methodlistitem", + "whitelines", + "blocks_in_list", + "block_in_list", + "whitelines2" ] + +Racc_debug_parser = false + +##### State transition tables end ##### + +# reduce 0 omitted + +def _reduce_1(val, _values, result) + result = RDoc::Markup::Document.new(*val[0]) + result +end + +def _reduce_2(val, _values, result) + raise ParseError, "file empty" + result +end + +def _reduce_3(val, _values, result) + result = val[0].concat val[1] + result +end + +def _reduce_4(val, _values, result) + result = val[0] + result +end + +def _reduce_5(val, _values, result) + result = val + result +end + +def _reduce_6(val, _values, result) + result = val + result +end + +# reduce 7 omitted + +def _reduce_8(val, _values, result) + result = val + result +end + +def _reduce_9(val, _values, result) + result = val + result +end + +def _reduce_10(val, _values, result) + result = [RDoc::Markup::BlankLine.new] + result +end + +def _reduce_11(val, _values, result) + result = val[0].parts + result +end + +def _reduce_12(val, _values, result) + # val[0] is like [level, title] + title = @inline_parser.parse(val[0][1]) + result = RDoc::Markup::Heading.new(val[0][0], title) + + result +end + +def _reduce_13(val, _values, result) + result = RDoc::Markup::Include.new val[0], @include_path + + result +end + +def _reduce_14(val, _values, result) + # val[0] is Array of String + result = paragraph val[0] + + result +end + +def _reduce_15(val, _values, result) + result << val[1].rstrip + result +end + +def _reduce_16(val, _values, result) + result = [val[0].rstrip] + result +end + +def _reduce_17(val, _values, result) + # val[1] is Array of String + content = cut_off val[1] + result = RDoc::Markup::Verbatim.new(*content) + + # imform to lexer. + @in_verbatim = false + + result +end + +def _reduce_18(val, _values, result) + # val[0] is Array of String + content = cut_off val[0] + result = RDoc::Markup::Verbatim.new(*content) + + # imform to lexer. + @in_verbatim = false + + result +end + +def _reduce_19(val, _values, result) + result << val[1] + + result +end + +def _reduce_20(val, _values, result) + result.concat val[2] + + result +end + +def _reduce_21(val, _values, result) + result << "\n" + + result +end + +def _reduce_22(val, _values, result) + result = val + # inform to lexer. + @in_verbatim = true + + result +end + +# reduce 23 omitted + +# reduce 24 omitted + +# reduce 25 omitted + +# reduce 26 omitted + +def _reduce_27(val, _values, result) + result = val[0] + + result +end + +def _reduce_28(val, _values, result) + result = val[1] + + result +end + +def _reduce_29(val, _values, result) + result = val[1].push(val[2]) + + result +end + +def _reduce_30(val, _values, result) + result = val[0] << val[1] + result +end + +def _reduce_31(val, _values, result) + result = [val[0]] + result +end + +def _reduce_32(val, _values, result) + result = RDoc::Markup::List.new :BULLET, *val[0] + + result +end + +def _reduce_33(val, _values, result) + result.push(val[1]) + result +end + +def _reduce_34(val, _values, result) + result = val + result +end + +def _reduce_35(val, _values, result) + result = RDoc::Markup::ListItem.new nil, val[0], *val[1] + + result +end + +def _reduce_36(val, _values, result) + result = RDoc::Markup::List.new :NUMBER, *val[0] + + result +end + +def _reduce_37(val, _values, result) + result.push(val[1]) + result +end + +def _reduce_38(val, _values, result) + result = val + result +end + +def _reduce_39(val, _values, result) + result = RDoc::Markup::ListItem.new nil, val[0], *val[1] + + result +end + +def _reduce_40(val, _values, result) + result = RDoc::Markup::List.new :NOTE, *val[0] + + result +end + +def _reduce_41(val, _values, result) + result.push(val[1]) + result +end + +def _reduce_42(val, _values, result) + result = val + result +end + +def _reduce_43(val, _values, result) + term = @inline_parser.parse val[0].strip + + result = RDoc::Markup::ListItem.new term, *val[1] + + result +end + +def _reduce_44(val, _values, result) + result = RDoc::Markup::List.new :LABEL, *val[0] + + result +end + +def _reduce_45(val, _values, result) + result.push(val[1]) + result +end + +def _reduce_46(val, _values, result) + result = val + result +end + +def _reduce_47(val, _values, result) + result = RDoc::Markup::ListItem.new "<tt>#{val[0].strip}</tt>", *val[1] + + result +end + +def _reduce_48(val, _values, result) + result = [val[1]].concat(val[2]) + + result +end + +def _reduce_49(val, _values, result) + result = [val[1]] + + result +end + +def _reduce_50(val, _values, result) + result = val[2] + + result +end + +def _reduce_51(val, _values, result) + result = [] + + result +end + +def _reduce_52(val, _values, result) + result.concat val[1] + result +end + +# reduce 53 omitted + +def _reduce_54(val, _values, result) + result = val + result +end + +def _reduce_55(val, _values, result) + result = val + result +end + +# reduce 56 omitted + +def _reduce_57(val, _values, result) + result = [] + result +end + +# reduce 58 omitted + +# reduce 59 omitted + +# reduce 60 omitted + +# reduce 61 omitted + +def _reduce_62(val, _values, result) + result = paragraph [val[0]].concat(val[1]) + + result +end + +def _reduce_63(val, _values, result) + result = paragraph [val[0]] + + result +end + +def _reduce_64(val, _values, result) + result = paragraph [val[0]].concat(val[1]) + + result +end + +def _reduce_65(val, _values, result) + result = paragraph [val[0]] + + result +end + +def _reduce_66(val, _values, result) + result = [val[0]].concat(val[1]) + + result +end + +def _reduce_67(val, _values, result) + result.concat val[1] + result +end + +def _reduce_68(val, _values, result) + result = val[1] + result +end + +def _reduce_69(val, _values, result) + result = val + result +end + +# reduce 70 omitted + +def _reduce_71(val, _values, result) + result = [] + result +end + +def _reduce_72(val, _values, result) + result = [] + result +end + +def _reduce_none(val, _values, result) + val[0] +end + +end # class BlockParser + +end diff --git a/lib/rdoc/rd/inline.rb b/lib/rdoc/rd/inline.rb new file mode 100644 index 0000000000..e5cb545728 --- /dev/null +++ b/lib/rdoc/rd/inline.rb @@ -0,0 +1,72 @@ +# frozen_string_literal: true +## +# Inline keeps track of markup and labels to create proper links. + +class RDoc::RD::Inline + + ## + # The text of the reference + + attr_reader :reference + + ## + # The markup of this reference in RDoc format + + attr_reader :rdoc + + ## + # Creates a new Inline for +rdoc+ and +reference+. + # + # +rdoc+ may be another Inline or a String. If +reference+ is not given it + # will use the text from +rdoc+. + + def self.new rdoc, reference = rdoc + if self === rdoc and reference.equal? rdoc then + rdoc + else + super + end + end + + ## + # Initializes the Inline with +rdoc+ and +inline+ + + def initialize rdoc, reference # :not-new: + @reference = reference.equal?(rdoc) ? reference.dup : reference + + # unpack + @reference = @reference.reference if self.class === @reference + @rdoc = rdoc + end + + def == other # :nodoc: + self.class === other and + @reference == other.reference and @rdoc == other.rdoc + end + + ## + # Appends +more+ to this inline. +more+ may be a String or another Inline. + + def append more + case more + when String then + @reference += more + @rdoc += more + when RDoc::RD::Inline then + @reference += more.reference + @rdoc += more.rdoc + else + raise "unknown thingy #{more}" + end + + self + end + + def inspect # :nodoc: + "(inline: #{self})" + end + + alias to_s rdoc # :nodoc: + +end + diff --git a/lib/rdoc/rd/inline_parser.rb b/lib/rdoc/rd/inline_parser.rb new file mode 100644 index 0000000000..783a5a7c7e --- /dev/null +++ b/lib/rdoc/rd/inline_parser.rb @@ -0,0 +1,1207 @@ +# +# DO NOT MODIFY!!!! +# This file is automatically generated by Racc 1.4.14 +# from Racc grammer file "". +# + +require 'racc/parser.rb' + +require 'strscan' + +class RDoc::RD + +## +# RD format parser for inline markup such as emphasis, links, footnotes, etc. + +class InlineParser < Racc::Parser + + +# :stopdoc: + +EM_OPEN = '((*' +EM_OPEN_RE = /\A#{Regexp.quote(EM_OPEN)}/ +EM_CLOSE = '*))' +EM_CLOSE_RE = /\A#{Regexp.quote(EM_CLOSE)}/ +CODE_OPEN = '(({' +CODE_OPEN_RE = /\A#{Regexp.quote(CODE_OPEN)}/ +CODE_CLOSE = '}))' +CODE_CLOSE_RE = /\A#{Regexp.quote(CODE_CLOSE)}/ +VAR_OPEN = '((|' +VAR_OPEN_RE = /\A#{Regexp.quote(VAR_OPEN)}/ +VAR_CLOSE = '|))' +VAR_CLOSE_RE = /\A#{Regexp.quote(VAR_CLOSE)}/ +KBD_OPEN = '((%' +KBD_OPEN_RE = /\A#{Regexp.quote(KBD_OPEN)}/ +KBD_CLOSE = '%))' +KBD_CLOSE_RE = /\A#{Regexp.quote(KBD_CLOSE)}/ +INDEX_OPEN = '((:' +INDEX_OPEN_RE = /\A#{Regexp.quote(INDEX_OPEN)}/ +INDEX_CLOSE = ':))' +INDEX_CLOSE_RE = /\A#{Regexp.quote(INDEX_CLOSE)}/ +REF_OPEN = '((<' +REF_OPEN_RE = /\A#{Regexp.quote(REF_OPEN)}/ +REF_CLOSE = '>))' +REF_CLOSE_RE = /\A#{Regexp.quote(REF_CLOSE)}/ +FOOTNOTE_OPEN = '((-' +FOOTNOTE_OPEN_RE = /\A#{Regexp.quote(FOOTNOTE_OPEN)}/ +FOOTNOTE_CLOSE = '-))' +FOOTNOTE_CLOSE_RE = /\A#{Regexp.quote(FOOTNOTE_CLOSE)}/ +VERB_OPEN = "(('" +VERB_OPEN_RE = /\A#{Regexp.quote(VERB_OPEN)}/ +VERB_CLOSE = "'))" +VERB_CLOSE_RE = /\A#{Regexp.quote(VERB_CLOSE)}/ + +BAR = "|" +BAR_RE = /\A#{Regexp.quote(BAR)}/ +QUOTE = '"' +QUOTE_RE = /\A#{Regexp.quote(QUOTE)}/ +SLASH = "/" +SLASH_RE = /\A#{Regexp.quote(SLASH)}/ +BACK_SLASH = "\\" +BACK_SLASH_RE = /\A#{Regexp.quote(BACK_SLASH)}/ +URL = "URL:" +URL_RE = /\A#{Regexp.quote(URL)}/ + +other_re_mode = Regexp::EXTENDED +other_re_mode |= Regexp::MULTILINE + +OTHER_RE = Regexp.new( + "\\A.+?(?=#{Regexp.quote(EM_OPEN)}|#{Regexp.quote(EM_CLOSE)}| + #{Regexp.quote(CODE_OPEN)}|#{Regexp.quote(CODE_CLOSE)}| + #{Regexp.quote(VAR_OPEN)}|#{Regexp.quote(VAR_CLOSE)}| + #{Regexp.quote(KBD_OPEN)}|#{Regexp.quote(KBD_CLOSE)}| + #{Regexp.quote(INDEX_OPEN)}|#{Regexp.quote(INDEX_CLOSE)}| + #{Regexp.quote(REF_OPEN)}|#{Regexp.quote(REF_CLOSE)}| + #{Regexp.quote(FOOTNOTE_OPEN)}|#{Regexp.quote(FOOTNOTE_CLOSE)}| + #{Regexp.quote(VERB_OPEN)}|#{Regexp.quote(VERB_CLOSE)}| + #{Regexp.quote(BAR)}| + #{Regexp.quote(QUOTE)}| + #{Regexp.quote(SLASH)}| + #{Regexp.quote(BACK_SLASH)}| + #{Regexp.quote(URL)})", other_re_mode) + +# :startdoc: + +## +# Creates a new parser for inline markup in the rd format. The +block_parser+ +# is used to for footnotes and labels in the inline text. + +def initialize block_parser + @block_parser = block_parser +end + +## +# Parses the +inline+ text from RD format into RDoc format. + +def parse inline + @inline = inline + @src = StringScanner.new inline + @pre = "" + @yydebug = true + do_parse.to_s +end + +## +# Returns the next token from the inline text + +def next_token + return [false, false] if @src.eos? +# p @src.rest if @yydebug + if ret = @src.scan(EM_OPEN_RE) + @pre << ret + [:EM_OPEN, ret] + elsif ret = @src.scan(EM_CLOSE_RE) + @pre << ret + [:EM_CLOSE, ret] + elsif ret = @src.scan(CODE_OPEN_RE) + @pre << ret + [:CODE_OPEN, ret] + elsif ret = @src.scan(CODE_CLOSE_RE) + @pre << ret + [:CODE_CLOSE, ret] + elsif ret = @src.scan(VAR_OPEN_RE) + @pre << ret + [:VAR_OPEN, ret] + elsif ret = @src.scan(VAR_CLOSE_RE) + @pre << ret + [:VAR_CLOSE, ret] + elsif ret = @src.scan(KBD_OPEN_RE) + @pre << ret + [:KBD_OPEN, ret] + elsif ret = @src.scan(KBD_CLOSE_RE) + @pre << ret + [:KBD_CLOSE, ret] + elsif ret = @src.scan(INDEX_OPEN_RE) + @pre << ret + [:INDEX_OPEN, ret] + elsif ret = @src.scan(INDEX_CLOSE_RE) + @pre << ret + [:INDEX_CLOSE, ret] + elsif ret = @src.scan(REF_OPEN_RE) + @pre << ret + [:REF_OPEN, ret] + elsif ret = @src.scan(REF_CLOSE_RE) + @pre << ret + [:REF_CLOSE, ret] + elsif ret = @src.scan(FOOTNOTE_OPEN_RE) + @pre << ret + [:FOOTNOTE_OPEN, ret] + elsif ret = @src.scan(FOOTNOTE_CLOSE_RE) + @pre << ret + [:FOOTNOTE_CLOSE, ret] + elsif ret = @src.scan(VERB_OPEN_RE) + @pre << ret + [:VERB_OPEN, ret] + elsif ret = @src.scan(VERB_CLOSE_RE) + @pre << ret + [:VERB_CLOSE, ret] + elsif ret = @src.scan(BAR_RE) + @pre << ret + [:BAR, ret] + elsif ret = @src.scan(QUOTE_RE) + @pre << ret + [:QUOTE, ret] + elsif ret = @src.scan(SLASH_RE) + @pre << ret + [:SLASH, ret] + elsif ret = @src.scan(BACK_SLASH_RE) + @pre << ret + [:BACK_SLASH, ret] + elsif ret = @src.scan(URL_RE) + @pre << ret + [:URL, ret] + elsif ret = @src.scan(OTHER_RE) + @pre << ret + [:OTHER, ret] + else + ret = @src.rest + @pre << ret + @src.terminate + [:OTHER, ret] + end +end + +## +# Raises a ParseError when invalid formatting is found + +def on_error(et, ev, values) + lines_of_rest = @src.rest.lines.to_a.length + prev_words = prev_words_on_error(ev) + at = 4 + prev_words.length + + message = <<-MSG +RD syntax error: line #{@block_parser.line_index - lines_of_rest}: +...#{prev_words} #{(ev||'')} #{next_words_on_error()} ... + MSG + + message << " " * at + "^" * (ev ? ev.length : 0) + "\n" + raise ParseError, message +end + +## +# Returns words before the error + +def prev_words_on_error(ev) + pre = @pre + if ev and /#{Regexp.quote(ev)}$/ =~ pre + pre = $` + end + last_line(pre) +end + +## +# Returns the last line of +src+ + +def last_line(src) + if n = src.rindex("\n") + src[(n+1) .. -1] + else + src + end +end +private :last_line + +## +# Returns words following an error + +def next_words_on_error + if n = @src.rest.index("\n") + @src.rest[0 .. (n-1)] + else + @src.rest + end +end + +## +# Creates a new RDoc::RD::Inline for the +rdoc+ markup and the raw +reference+ + +def inline rdoc, reference = rdoc + RDoc::RD::Inline.new rdoc, reference +end + +# :stopdoc: +##### State transition tables begin ### + +racc_action_table = [ + 104, 103, 102, 100, 101, 99, 115, 116, 117, 86, + 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, + 164, 118, 119, 104, 103, 102, 100, 101, 99, 115, + 116, 117, 175, 105, 106, 107, 108, 109, 110, 111, + 112, 113, 114, 85, 118, 119, 63, 64, 65, 61, + 81, 62, 76, 78, 79, 84, 66, 67, 68, 69, + 70, 71, 72, 73, 74, 75, 77, 80, 149, 104, + 103, 102, 100, 101, 99, 115, 116, 117, 29, 105, + 106, 107, 108, 109, 110, 111, 112, 113, 114, 173, + 118, 119, 104, 103, 102, 100, 101, 99, 115, 116, + 117, 137, 105, 106, 107, 108, 109, 110, 111, 112, + 113, 114, 177, 118, 119, 63, 64, 65, 153, 81, + 62, 76, 78, 79, 148, 66, 67, 68, 69, 70, + 71, 72, 73, 74, 75, 77, 80, 152, 22, 23, + 24, 25, 26, 21, 18, 19, 176, 177, 13, 124, + 14, 96, 15, 89, 16, 154, 17, 88, 137, 20, + 22, 23, 24, 25, 26, 21, 18, 19, 87, 161, + 13, nil, 14, nil, 15, nil, 16, nil, 17, 42, + nil, 20, 54, 38, 53, 55, 56, 57, nil, 13, + nil, 14, nil, 15, nil, 16, nil, 17, nil, nil, + 20, 22, 23, 24, 25, 26, 21, 18, 19, nil, + nil, 13, nil, 14, nil, 15, nil, 16, nil, 17, + nil, nil, 20, 63, 64, 65, 61, 81, 62, 76, + 78, 79, nil, 66, 67, 68, 69, 70, 71, 72, + 73, 74, 75, 77, 80, 145, nil, nil, 54, 133, + 53, 55, 56, 57, nil, 13, nil, 14, nil, 15, + nil, 16, nil, 17, 145, nil, 20, 54, 133, 53, + 55, 56, 57, nil, 13, nil, 14, nil, 15, nil, + 16, nil, 17, nil, nil, 20, 22, 23, 24, 25, + 26, 21, 18, 19, nil, nil, 13, nil, 14, nil, + 15, nil, 16, nil, 17, 145, nil, 20, 54, 133, + 53, 55, 56, 57, nil, 13, nil, 14, nil, 15, + nil, 16, nil, 17, 145, nil, 20, 54, 133, 53, + 55, 56, 57, nil, 13, nil, 14, nil, 15, nil, + 16, nil, 17, nil, nil, 20, 22, 23, 24, 25, + 26, 21, 18, 19, nil, nil, 13, nil, 14, nil, + 15, nil, 16, nil, 17, nil, nil, 20, 22, 23, + 24, 25, 26, 21, 18, 19, nil, nil, 13, nil, + 14, nil, 15, nil, 16, nil, 17, nil, nil, 20, + 22, 23, 24, 25, 26, 21, 18, 19, nil, nil, + 13, nil, 14, nil, 15, nil, 16, nil, 17, nil, + nil, 20, 22, 23, 24, 25, 26, 21, 18, 19, + nil, nil, 13, nil, 14, nil, 15, nil, 16, 122, + 17, nil, 54, 20, 53, 55, 56, 57, nil, 13, + nil, 14, nil, 15, nil, 16, nil, 17, nil, nil, + 20, 135, 136, 54, 133, 53, 55, 56, 57, nil, + 13, nil, 14, nil, 15, nil, 16, nil, 17, nil, + nil, 20, 135, 136, 54, 133, 53, 55, 56, 57, + nil, 13, nil, 14, nil, 15, nil, 16, nil, 17, + nil, nil, 20, 135, 136, 54, 133, 53, 55, 56, + 57, nil, 13, nil, 14, nil, 15, nil, 16, 158, + 17, nil, 54, 20, 53, 55, 56, 57, 95, nil, + nil, 54, 91, 53, 55, 56, 57, 145, nil, nil, + 54, 133, 53, 55, 56, 57, 165, 135, 136, 54, + 133, 53, 55, 56, 57, 145, nil, nil, 54, 133, + 53, 55, 56, 57, 172, 135, 136, 54, 133, 53, + 55, 56, 57, 174, 135, 136, 54, 133, 53, 55, + 56, 57, 178, 135, 136, 54, 133, 53, 55, 56, + 57, 135, 136, 54, 133, 53, 55, 56, 57, 135, + 136, 54, 133, 53, 55, 56, 57, 135, 136, 54, + 133, 53, 55, 56, 57, 22, 23, 24, 25, 26, + 21 ] + +racc_action_check = [ + 38, 38, 38, 38, 38, 38, 38, 38, 38, 32, + 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, + 125, 38, 38, 97, 97, 97, 97, 97, 97, 97, + 97, 97, 164, 97, 97, 97, 97, 97, 97, 97, + 97, 97, 97, 31, 97, 97, 59, 59, 59, 59, + 59, 59, 59, 59, 59, 29, 59, 59, 59, 59, + 59, 59, 59, 59, 59, 59, 59, 59, 59, 91, + 91, 91, 91, 91, 91, 91, 91, 91, 1, 91, + 91, 91, 91, 91, 91, 91, 91, 91, 91, 162, + 91, 91, 155, 155, 155, 155, 155, 155, 155, 155, + 155, 43, 155, 155, 155, 155, 155, 155, 155, 155, + 155, 155, 172, 155, 155, 61, 61, 61, 61, 61, + 61, 61, 61, 61, 58, 61, 61, 61, 61, 61, + 61, 61, 61, 61, 61, 61, 61, 61, 16, 16, + 16, 16, 16, 16, 16, 16, 165, 165, 16, 41, + 16, 37, 16, 35, 16, 90, 16, 34, 94, 16, + 17, 17, 17, 17, 17, 17, 17, 17, 33, 100, + 17, nil, 17, nil, 17, nil, 17, nil, 17, 18, + nil, 17, 18, 18, 18, 18, 18, 18, nil, 18, + nil, 18, nil, 18, nil, 18, nil, 18, nil, nil, + 18, 19, 19, 19, 19, 19, 19, 19, 19, nil, + nil, 19, nil, 19, nil, 19, nil, 19, nil, 19, + nil, nil, 19, 20, 20, 20, 20, 20, 20, 20, + 20, 20, nil, 20, 20, 20, 20, 20, 20, 20, + 20, 20, 20, 20, 20, 146, nil, nil, 146, 146, + 146, 146, 146, 146, nil, 146, nil, 146, nil, 146, + nil, 146, nil, 146, 138, nil, 146, 138, 138, 138, + 138, 138, 138, nil, 138, nil, 138, nil, 138, nil, + 138, nil, 138, nil, nil, 138, 0, 0, 0, 0, + 0, 0, 0, 0, nil, nil, 0, nil, 0, nil, + 0, nil, 0, nil, 0, 45, nil, 0, 45, 45, + 45, 45, 45, 45, nil, 45, nil, 45, nil, 45, + nil, 45, nil, 45, 44, nil, 45, 44, 44, 44, + 44, 44, 44, nil, 44, nil, 44, nil, 44, nil, + 44, nil, 44, nil, nil, 44, 2, 2, 2, 2, + 2, 2, 2, 2, nil, nil, 2, nil, 2, nil, + 2, nil, 2, nil, 2, nil, nil, 2, 13, 13, + 13, 13, 13, 13, 13, 13, nil, nil, 13, nil, + 13, nil, 13, nil, 13, nil, 13, nil, nil, 13, + 14, 14, 14, 14, 14, 14, 14, 14, nil, nil, + 14, nil, 14, nil, 14, nil, 14, nil, 14, nil, + nil, 14, 15, 15, 15, 15, 15, 15, 15, 15, + nil, nil, 15, nil, 15, nil, 15, nil, 15, 39, + 15, nil, 39, 15, 39, 39, 39, 39, nil, 39, + nil, 39, nil, 39, nil, 39, nil, 39, nil, nil, + 39, 42, 42, 42, 42, 42, 42, 42, 42, nil, + 42, nil, 42, nil, 42, nil, 42, nil, 42, nil, + nil, 42, 127, 127, 127, 127, 127, 127, 127, 127, + nil, 127, nil, 127, nil, 127, nil, 127, nil, 127, + nil, nil, 127, 122, 122, 122, 122, 122, 122, 122, + 122, nil, 122, nil, 122, nil, 122, nil, 122, 92, + 122, nil, 92, 122, 92, 92, 92, 92, 36, nil, + nil, 36, 36, 36, 36, 36, 36, 52, nil, nil, + 52, 52, 52, 52, 52, 52, 126, 126, 126, 126, + 126, 126, 126, 126, 126, 142, nil, nil, 142, 142, + 142, 142, 142, 142, 159, 159, 159, 159, 159, 159, + 159, 159, 159, 163, 163, 163, 163, 163, 163, 163, + 163, 163, 171, 171, 171, 171, 171, 171, 171, 171, + 171, 95, 95, 95, 95, 95, 95, 95, 95, 158, + 158, 158, 158, 158, 158, 158, 158, 168, 168, 168, + 168, 168, 168, 168, 168, 27, 27, 27, 27, 27, + 27 ] + +racc_action_pointer = [ + 283, 78, 343, nil, nil, nil, nil, nil, nil, nil, + nil, nil, nil, 365, 387, 409, 135, 157, 176, 198, + 220, nil, nil, nil, nil, nil, nil, 602, nil, 55, + nil, 29, -7, 150, 137, 131, 515, 128, -3, 426, + nil, 145, 447, 96, 321, 302, nil, nil, nil, nil, + nil, nil, 524, nil, nil, nil, nil, nil, 113, 43, + nil, 112, nil, nil, nil, nil, nil, nil, nil, nil, + nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, + nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, + 132, 66, 506, nil, 153, 577, nil, 20, nil, nil, + 163, nil, nil, nil, nil, nil, nil, nil, nil, nil, + nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, + nil, nil, 489, nil, nil, 17, 533, 468, nil, nil, + nil, nil, nil, nil, nil, nil, nil, nil, 261, nil, + nil, nil, 542, nil, nil, nil, 242, nil, nil, nil, + nil, nil, nil, nil, nil, 89, nil, nil, 585, 551, + nil, nil, 86, 560, 28, 142, nil, nil, 593, nil, + nil, 569, 107, nil, nil, nil, nil, nil, nil ] + +racc_action_default = [ + -138, -138, -1, -3, -4, -5, -6, -7, -8, -9, + -10, -11, -12, -138, -138, -138, -138, -138, -138, -138, + -138, -103, -104, -105, -106, -107, -108, -111, -110, -138, + -2, -138, -138, -138, -138, -138, -138, -138, -138, -27, + -26, -35, -138, -58, -41, -40, -47, -48, -49, -50, + -51, -52, -63, -66, -67, -68, -69, -70, -138, -138, + -112, -138, -116, -117, -118, -119, -120, -121, -122, -123, + -124, -125, -126, -127, -128, -129, -130, -131, -132, -133, + -134, -135, -137, -109, 179, -13, -14, -15, -16, -17, + -138, -138, -23, -22, -33, -138, -19, -24, -79, -80, + -138, -82, -83, -84, -85, -86, -87, -88, -89, -90, + -91, -92, -93, -94, -95, -96, -97, -98, -99, -100, + -25, -35, -138, -58, -28, -138, -59, -42, -46, -55, + -56, -65, -71, -72, -75, -76, -77, -31, -38, -44, + -53, -54, -57, -61, -73, -74, -39, -62, -101, -102, + -136, -113, -114, -115, -18, -20, -21, -33, -138, -138, + -78, -81, -138, -59, -36, -37, -64, -45, -59, -43, + -60, -138, -34, -36, -37, -29, -30, -32, -34 ] + +racc_goto_table = [ + 126, 44, 125, 52, 144, 144, 160, 93, 97, 43, + 166, 82, 144, 41, 40, 39, 138, 146, 169, 90, + 36, 52, 44, 1, 52, 129, 169, 94, 59, 83, + 123, 30, 151, 92, 121, 120, 31, 32, 33, 34, + 35, 170, 58, 166, 167, 147, 170, 166, 37, nil, + 150, nil, 166, 159, 4, 166, 4, nil, nil, nil, + nil, 155, nil, 156, 160, nil, nil, 4, 4, 4, + 4, 4, nil, 4, 5, nil, 5, 52, nil, nil, + 163, nil, 162, 157, nil, 168, nil, 5, 5, 5, + 5, 5, nil, 5, nil, nil, nil, nil, 144, nil, + nil, nil, 144, nil, nil, 129, 144, 144, nil, 6, + 129, 6, nil, nil, nil, nil, 171, 7, nil, 7, + nil, nil, 6, 6, 6, 6, 6, 8, 6, 8, + 7, 7, 7, 7, 7, 11, 7, 11, nil, nil, + 8, 8, 8, 8, 8, nil, 8, nil, 11, 11, + 11, 11, 11, nil, 11 ] + +racc_goto_check = [ + 22, 24, 21, 34, 36, 36, 37, 18, 16, 23, + 35, 41, 36, 20, 19, 17, 25, 25, 28, 14, + 13, 34, 24, 1, 34, 24, 28, 23, 38, 39, + 23, 3, 42, 17, 20, 19, 1, 1, 1, 1, + 1, 33, 1, 35, 29, 32, 33, 35, 15, nil, + 41, nil, 35, 22, 4, 35, 4, nil, nil, nil, + nil, 16, nil, 18, 37, nil, nil, 4, 4, 4, + 4, 4, nil, 4, 5, nil, 5, 34, nil, nil, + 22, nil, 21, 23, nil, 22, nil, 5, 5, 5, + 5, 5, nil, 5, nil, nil, nil, nil, 36, nil, + nil, nil, 36, nil, nil, 24, 36, 36, nil, 6, + 24, 6, nil, nil, nil, nil, 22, 7, nil, 7, + nil, nil, 6, 6, 6, 6, 6, 8, 6, 8, + 7, 7, 7, 7, 7, 11, 7, 11, nil, nil, + 8, 8, 8, 8, 8, nil, 8, nil, 11, 11, + 11, 11, 11, nil, 11 ] + +racc_goto_pointer = [ + nil, 23, nil, 29, 54, 74, 109, 117, 127, nil, + nil, 135, nil, 2, -17, 30, -30, -3, -29, -4, + -5, -40, -42, -9, -17, -28, nil, nil, -120, -83, + nil, nil, -7, -101, -15, -116, -40, -91, 8, 2, + nil, -9, -29 ] + +racc_goto_default = [ + nil, nil, 2, 3, 46, 47, 48, 49, 50, 9, + 10, 51, 12, nil, nil, nil, nil, nil, nil, nil, + nil, nil, nil, nil, 140, nil, 45, 127, 139, 128, + 141, 130, 142, 143, 132, 131, 134, 98, nil, 28, + 27, nil, 60 ] + +racc_reduce_table = [ + 0, 0, :racc_error, + 1, 27, :_reduce_none, + 2, 28, :_reduce_2, + 1, 28, :_reduce_3, + 1, 29, :_reduce_none, + 1, 29, :_reduce_none, + 1, 29, :_reduce_none, + 1, 29, :_reduce_none, + 1, 29, :_reduce_none, + 1, 29, :_reduce_none, + 1, 29, :_reduce_none, + 1, 29, :_reduce_none, + 1, 29, :_reduce_none, + 3, 30, :_reduce_13, + 3, 31, :_reduce_14, + 3, 32, :_reduce_15, + 3, 33, :_reduce_16, + 3, 34, :_reduce_17, + 4, 35, :_reduce_18, + 3, 35, :_reduce_19, + 2, 40, :_reduce_20, + 2, 40, :_reduce_21, + 1, 40, :_reduce_22, + 1, 40, :_reduce_23, + 2, 41, :_reduce_24, + 2, 41, :_reduce_25, + 1, 41, :_reduce_26, + 1, 41, :_reduce_27, + 2, 39, :_reduce_none, + 4, 39, :_reduce_29, + 4, 39, :_reduce_30, + 2, 43, :_reduce_31, + 4, 43, :_reduce_32, + 1, 44, :_reduce_33, + 3, 44, :_reduce_34, + 1, 45, :_reduce_none, + 3, 45, :_reduce_36, + 3, 45, :_reduce_37, + 2, 46, :_reduce_38, + 2, 46, :_reduce_39, + 1, 46, :_reduce_40, + 1, 46, :_reduce_41, + 1, 47, :_reduce_none, + 2, 51, :_reduce_43, + 1, 51, :_reduce_44, + 2, 53, :_reduce_45, + 1, 53, :_reduce_46, + 1, 50, :_reduce_none, + 1, 50, :_reduce_none, + 1, 50, :_reduce_none, + 1, 50, :_reduce_none, + 1, 50, :_reduce_none, + 1, 50, :_reduce_none, + 1, 54, :_reduce_none, + 1, 54, :_reduce_none, + 1, 55, :_reduce_none, + 1, 55, :_reduce_none, + 1, 56, :_reduce_57, + 1, 52, :_reduce_58, + 1, 57, :_reduce_59, + 2, 58, :_reduce_60, + 1, 58, :_reduce_none, + 2, 49, :_reduce_62, + 1, 49, :_reduce_none, + 2, 48, :_reduce_64, + 1, 48, :_reduce_none, + 1, 60, :_reduce_none, + 1, 60, :_reduce_none, + 1, 60, :_reduce_none, + 1, 60, :_reduce_none, + 1, 60, :_reduce_none, + 1, 62, :_reduce_none, + 1, 62, :_reduce_none, + 1, 59, :_reduce_none, + 1, 59, :_reduce_none, + 1, 61, :_reduce_none, + 1, 61, :_reduce_none, + 1, 61, :_reduce_none, + 2, 42, :_reduce_78, + 1, 42, :_reduce_none, + 1, 63, :_reduce_none, + 2, 63, :_reduce_none, + 1, 63, :_reduce_none, + 1, 63, :_reduce_none, + 1, 63, :_reduce_none, + 1, 63, :_reduce_none, + 1, 63, :_reduce_none, + 1, 63, :_reduce_none, + 1, 63, :_reduce_none, + 1, 63, :_reduce_none, + 1, 63, :_reduce_none, + 1, 63, :_reduce_none, + 1, 63, :_reduce_none, + 1, 63, :_reduce_none, + 1, 63, :_reduce_none, + 1, 63, :_reduce_none, + 1, 63, :_reduce_none, + 1, 63, :_reduce_none, + 1, 63, :_reduce_none, + 1, 63, :_reduce_none, + 1, 63, :_reduce_none, + 3, 36, :_reduce_101, + 3, 37, :_reduce_102, + 1, 65, :_reduce_none, + 1, 65, :_reduce_none, + 1, 65, :_reduce_none, + 1, 65, :_reduce_none, + 1, 65, :_reduce_none, + 1, 65, :_reduce_none, + 2, 66, :_reduce_109, + 1, 66, :_reduce_none, + 1, 38, :_reduce_111, + 1, 67, :_reduce_none, + 2, 67, :_reduce_113, + 2, 67, :_reduce_114, + 2, 67, :_reduce_115, + 1, 68, :_reduce_none, + 1, 68, :_reduce_none, + 1, 68, :_reduce_none, + 1, 68, :_reduce_none, + 1, 68, :_reduce_none, + 1, 68, :_reduce_none, + 1, 68, :_reduce_none, + 1, 68, :_reduce_none, + 1, 68, :_reduce_none, + 1, 68, :_reduce_none, + 1, 68, :_reduce_none, + 1, 68, :_reduce_none, + 1, 68, :_reduce_none, + 1, 68, :_reduce_none, + 1, 68, :_reduce_none, + 1, 68, :_reduce_none, + 1, 68, :_reduce_none, + 1, 68, :_reduce_none, + 1, 68, :_reduce_none, + 1, 68, :_reduce_none, + 2, 64, :_reduce_136, + 1, 64, :_reduce_none ] + +racc_reduce_n = 138 + +racc_shift_n = 179 + +racc_token_table = { + false => 0, + :error => 1, + :EX_LOW => 2, + :QUOTE => 3, + :BAR => 4, + :SLASH => 5, + :BACK_SLASH => 6, + :URL => 7, + :OTHER => 8, + :REF_OPEN => 9, + :FOOTNOTE_OPEN => 10, + :FOOTNOTE_CLOSE => 11, + :EX_HIGH => 12, + :EM_OPEN => 13, + :EM_CLOSE => 14, + :CODE_OPEN => 15, + :CODE_CLOSE => 16, + :VAR_OPEN => 17, + :VAR_CLOSE => 18, + :KBD_OPEN => 19, + :KBD_CLOSE => 20, + :INDEX_OPEN => 21, + :INDEX_CLOSE => 22, + :REF_CLOSE => 23, + :VERB_OPEN => 24, + :VERB_CLOSE => 25 } + +racc_nt_base = 26 + +racc_use_result_var = true + +Racc_arg = [ + racc_action_table, + racc_action_check, + racc_action_default, + racc_action_pointer, + racc_goto_table, + racc_goto_check, + racc_goto_default, + racc_goto_pointer, + racc_nt_base, + racc_reduce_table, + racc_token_table, + racc_shift_n, + racc_reduce_n, + racc_use_result_var ] + +Racc_token_to_s_table = [ + "$end", + "error", + "EX_LOW", + "QUOTE", + "BAR", + "SLASH", + "BACK_SLASH", + "URL", + "OTHER", + "REF_OPEN", + "FOOTNOTE_OPEN", + "FOOTNOTE_CLOSE", + "EX_HIGH", + "EM_OPEN", + "EM_CLOSE", + "CODE_OPEN", + "CODE_CLOSE", + "VAR_OPEN", + "VAR_CLOSE", + "KBD_OPEN", + "KBD_CLOSE", + "INDEX_OPEN", + "INDEX_CLOSE", + "REF_CLOSE", + "VERB_OPEN", + "VERB_CLOSE", + "$start", + "content", + "elements", + "element", + "emphasis", + "code", + "var", + "keyboard", + "index", + "reference", + "footnote", + "verb", + "normal_str_ele", + "substitute", + "ref_label", + "ref_label2", + "ref_url_strings", + "filename", + "element_label", + "element_label2", + "ref_subst_content", + "ref_subst_content_q", + "ref_subst_strings_q", + "ref_subst_strings_first", + "ref_subst_ele2", + "ref_subst_eles", + "ref_subst_str_ele_first", + "ref_subst_eles_q", + "ref_subst_ele", + "ref_subst_ele_q", + "ref_subst_str_ele", + "ref_subst_str_ele_q", + "ref_subst_strings", + "ref_subst_string3", + "ref_subst_string", + "ref_subst_string_q", + "ref_subst_string2", + "ref_url_string", + "verb_strings", + "normal_string", + "normal_strings", + "verb_string", + "verb_normal_string" ] + +Racc_debug_parser = false + +##### State transition tables end ##### + +# reduce 0 omitted + +# reduce 1 omitted + +def _reduce_2(val, _values, result) + result.append val[1] + result +end + +def _reduce_3(val, _values, result) + result = val[0] + result +end + +# reduce 4 omitted + +# reduce 5 omitted + +# reduce 6 omitted + +# reduce 7 omitted + +# reduce 8 omitted + +# reduce 9 omitted + +# reduce 10 omitted + +# reduce 11 omitted + +# reduce 12 omitted + +def _reduce_13(val, _values, result) + content = val[1] + result = inline "<em>#{content}</em>", content + + result +end + +def _reduce_14(val, _values, result) + content = val[1] + result = inline "<code>#{content}</code>", content + + result +end + +def _reduce_15(val, _values, result) + content = val[1] + result = inline "+#{content}+", content + + result +end + +def _reduce_16(val, _values, result) + content = val[1] + result = inline "<tt>#{content}</tt>", content + + result +end + +def _reduce_17(val, _values, result) + label = val[1] + @block_parser.add_label label.reference + result = "<span id=\"label-#{label}\">#{label}</span>" + + result +end + +def _reduce_18(val, _values, result) + result = "{#{val[1]}}[#{val[2].join}]" + + result +end + +def _reduce_19(val, _values, result) + scheme, inline = val[1] + + result = "{#{inline}}[#{scheme}#{inline.reference}]" + + result +end + +def _reduce_20(val, _values, result) + result = [nil, inline(val[1])] + + result +end + +def _reduce_21(val, _values, result) + result = [ + 'rdoc-label:', + inline("#{val[0].reference}/#{val[1].reference}") + ] + + result +end + +def _reduce_22(val, _values, result) + result = ['rdoc-label:', val[0].reference] + + result +end + +def _reduce_23(val, _values, result) + result = ['rdoc-label:', "#{val[0].reference}/"] + + result +end + +def _reduce_24(val, _values, result) + result = [nil, inline(val[1])] + + result +end + +def _reduce_25(val, _values, result) + result = [ + 'rdoc-label:', + inline("#{val[0].reference}/#{val[1].reference}") + ] + + result +end + +def _reduce_26(val, _values, result) + result = ['rdoc-label:', val[0]] + + result +end + +def _reduce_27(val, _values, result) + ref = val[0].reference + result = ['rdoc-label:', inline(ref, "#{ref}/")] + + result +end + +# reduce 28 omitted + +def _reduce_29(val, _values, result) + result = val[1] + result +end + +def _reduce_30(val, _values, result) + result = val[1] + result +end + +def _reduce_31(val, _values, result) + result = inline val[0] + + result +end + +def _reduce_32(val, _values, result) + result = inline "\"#{val[1]}\"" + + result +end + +def _reduce_33(val, _values, result) + result = inline val[0] + + result +end + +def _reduce_34(val, _values, result) + result = inline "\"#{val[1]}\"" + + result +end + +# reduce 35 omitted + +def _reduce_36(val, _values, result) + result = val[1] + result +end + +def _reduce_37(val, _values, result) + result = inline val[1] + result +end + +def _reduce_38(val, _values, result) + result = val[0].append val[1] + + result +end + +def _reduce_39(val, _values, result) + result = val[0].append val[1] + + result +end + +def _reduce_40(val, _values, result) + result = val[0] + + result +end + +def _reduce_41(val, _values, result) + result = inline val[0] + + result +end + +# reduce 42 omitted + +def _reduce_43(val, _values, result) + result = val[0].append val[1] + + result +end + +def _reduce_44(val, _values, result) + result = inline val[0] + + result +end + +def _reduce_45(val, _values, result) + result = val[0].append val[1] + + result +end + +def _reduce_46(val, _values, result) + result = val[0] + + result +end + +# reduce 47 omitted + +# reduce 48 omitted + +# reduce 49 omitted + +# reduce 50 omitted + +# reduce 51 omitted + +# reduce 52 omitted + +# reduce 53 omitted + +# reduce 54 omitted + +# reduce 55 omitted + +# reduce 56 omitted + +def _reduce_57(val, _values, result) + result = val[0] + + result +end + +def _reduce_58(val, _values, result) + result = inline val[0] + + result +end + +def _reduce_59(val, _values, result) + result = inline val[0] + + result +end + +def _reduce_60(val, _values, result) + result << val[1] + result +end + +# reduce 61 omitted + +def _reduce_62(val, _values, result) + result << val[1] + + result +end + +# reduce 63 omitted + +def _reduce_64(val, _values, result) + result << val[1] + + result +end + +# reduce 65 omitted + +# reduce 66 omitted + +# reduce 67 omitted + +# reduce 68 omitted + +# reduce 69 omitted + +# reduce 70 omitted + +# reduce 71 omitted + +# reduce 72 omitted + +# reduce 73 omitted + +# reduce 74 omitted + +# reduce 75 omitted + +# reduce 76 omitted + +# reduce 77 omitted + +def _reduce_78(val, _values, result) + result << val[1] + result +end + +# reduce 79 omitted + +# reduce 80 omitted + +# reduce 81 omitted + +# reduce 82 omitted + +# reduce 83 omitted + +# reduce 84 omitted + +# reduce 85 omitted + +# reduce 86 omitted + +# reduce 87 omitted + +# reduce 88 omitted + +# reduce 89 omitted + +# reduce 90 omitted + +# reduce 91 omitted + +# reduce 92 omitted + +# reduce 93 omitted + +# reduce 94 omitted + +# reduce 95 omitted + +# reduce 96 omitted + +# reduce 97 omitted + +# reduce 98 omitted + +# reduce 99 omitted + +# reduce 100 omitted + +def _reduce_101(val, _values, result) + index = @block_parser.add_footnote val[1].rdoc + result = "{*#{index}}[rdoc-label:foottext-#{index}:footmark-#{index}]" + + result +end + +def _reduce_102(val, _values, result) + result = inline "<tt>#{val[1]}</tt>", val[1] + + result +end + +# reduce 103 omitted + +# reduce 104 omitted + +# reduce 105 omitted + +# reduce 106 omitted + +# reduce 107 omitted + +# reduce 108 omitted + +def _reduce_109(val, _values, result) + result << val[1] + result +end + +# reduce 110 omitted + +def _reduce_111(val, _values, result) + result = inline val[0] + + result +end + +# reduce 112 omitted + +def _reduce_113(val, _values, result) + result = val[1] + result +end + +def _reduce_114(val, _values, result) + result = val[1] + result +end + +def _reduce_115(val, _values, result) + result = val[1] + result +end + +# reduce 116 omitted + +# reduce 117 omitted + +# reduce 118 omitted + +# reduce 119 omitted + +# reduce 120 omitted + +# reduce 121 omitted + +# reduce 122 omitted + +# reduce 123 omitted + +# reduce 124 omitted + +# reduce 125 omitted + +# reduce 126 omitted + +# reduce 127 omitted + +# reduce 128 omitted + +# reduce 129 omitted + +# reduce 130 omitted + +# reduce 131 omitted + +# reduce 132 omitted + +# reduce 133 omitted + +# reduce 134 omitted + +# reduce 135 omitted + +def _reduce_136(val, _values, result) + result << val[1] + result +end + +# reduce 137 omitted + +def _reduce_none(val, _values, result) + val[0] +end + +end # class InlineParser + +end diff --git a/lib/rdoc/rdoc.gemspec b/lib/rdoc/rdoc.gemspec new file mode 100644 index 0000000000..8c92908a66 --- /dev/null +++ b/lib/rdoc/rdoc.gemspec @@ -0,0 +1,63 @@ +begin + require_relative "lib/rdoc" +rescue LoadError + # for Ruby repository + require_relative "../rdoc" +end + +Gem::Specification.new do |s| + s.name = "rdoc" + s.version = RDoc::VERSION + s.date = "2017-12-24" + + s.authors = [ + "Eric Hodel", + "Dave Thomas", + "Phil Hagelberg", + "Tony Strauss", + "Zachary Scott", + "Hiroshi SHIBATA", + "ITOYANAGI Sakura" + ] + s.email = ["drbrain@segment7.net", "", "", "", "mail@zzak.io", "hsbt@ruby-lang.org", "aycabta@gmail.com"] + + s.summary = "RDoc produces HTML and command-line documentation for Ruby projects" + s.description = <<-DESCRIPTION +RDoc produces HTML and command-line documentation for Ruby projects. +RDoc includes the +rdoc+ and +ri+ tools for generating and displaying documentation from the command-line. + DESCRIPTION + s.homepage = "https://ruby.github.io/rdoc" + s.licenses = ["Ruby"] + + s.bindir = "exe" + s.executables = ["rdoc", "ri"] + s.require_paths = ["lib"] + # for ruby core repository. It was generated by `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) } + s.files = [".document", ".gitignore", ".travis.yml", "CONTRIBUTING.rdoc", "CVE-2013-0256.rdoc", "ExampleMarkdown.md", "ExampleRDoc.rdoc", "Gemfile", "History.rdoc", "LEGAL.rdoc", "LICENSE.rdoc", "README.rdoc", "RI.rdoc", "Rakefile", "TODO.rdoc", "appveyor.yml", "bin/console", "bin/setup", "exe/rdoc", "exe/ri", "lib/rdoc.rb", "lib/rdoc/alias.rb", "lib/rdoc/anon_class.rb", "lib/rdoc/any_method.rb", "lib/rdoc/attr.rb", "lib/rdoc/class_module.rb", "lib/rdoc/code_object.rb", "lib/rdoc/code_objects.rb", "lib/rdoc/comment.rb", "lib/rdoc/constant.rb", "lib/rdoc/context.rb", "lib/rdoc/context/section.rb", "lib/rdoc/cross_reference.rb", "lib/rdoc/encoding.rb", "lib/rdoc/erb_partial.rb", "lib/rdoc/erbio.rb", "lib/rdoc/extend.rb", "lib/rdoc/generator.rb", "lib/rdoc/generator/darkfish.rb", "lib/rdoc/generator/json_index.rb", "lib/rdoc/generator/markup.rb", "lib/rdoc/generator/pot.rb", "lib/rdoc/generator/pot/message_extractor.rb", "lib/rdoc/generator/pot/po.rb", "lib/rdoc/generator/pot/po_entry.rb", "lib/rdoc/generator/ri.rb", "lib/rdoc/generator/template/darkfish/.document", "lib/rdoc/generator/template/darkfish/_footer.rhtml", "lib/rdoc/generator/template/darkfish/_head.rhtml", "lib/rdoc/generator/template/darkfish/_sidebar_VCS_info.rhtml", "lib/rdoc/generator/template/darkfish/_sidebar_classes.rhtml", "lib/rdoc/generator/template/darkfish/_sidebar_extends.rhtml", "lib/rdoc/generator/template/darkfish/_sidebar_in_files.rhtml", "lib/rdoc/generator/template/darkfish/_sidebar_includes.rhtml", "lib/rdoc/generator/template/darkfish/_sidebar_installed.rhtml", "lib/rdoc/generator/template/darkfish/_sidebar_methods.rhtml", "lib/rdoc/generator/template/darkfish/_sidebar_navigation.rhtml", "lib/rdoc/generator/template/darkfish/_sidebar_pages.rhtml", "lib/rdoc/generator/template/darkfish/_sidebar_parent.rhtml", "lib/rdoc/generator/template/darkfish/_sidebar_search.rhtml", "lib/rdoc/generator/template/darkfish/_sidebar_sections.rhtml", "lib/rdoc/generator/template/darkfish/_sidebar_table_of_contents.rhtml", "lib/rdoc/generator/template/darkfish/class.rhtml", "lib/rdoc/generator/template/darkfish/css/fonts.css", "lib/rdoc/generator/template/darkfish/css/rdoc.css", "lib/rdoc/generator/template/darkfish/fonts/Lato-Light.ttf", "lib/rdoc/generator/template/darkfish/fonts/Lato-LightItalic.ttf", "lib/rdoc/generator/template/darkfish/fonts/Lato-Regular.ttf", "lib/rdoc/generator/template/darkfish/fonts/Lato-RegularItalic.ttf", "lib/rdoc/generator/template/darkfish/fonts/SourceCodePro-Bold.ttf", "lib/rdoc/generator/template/darkfish/fonts/SourceCodePro-Regular.ttf", "lib/rdoc/generator/template/darkfish/images/add.png", "lib/rdoc/generator/template/darkfish/images/arrow_up.png", "lib/rdoc/generator/template/darkfish/images/brick.png", "lib/rdoc/generator/template/darkfish/images/brick_link.png", "lib/rdoc/generator/template/darkfish/images/bug.png", "lib/rdoc/generator/template/darkfish/images/bullet_black.png", "lib/rdoc/generator/template/darkfish/images/bullet_toggle_minus.png", "lib/rdoc/generator/template/darkfish/images/bullet_toggle_plus.png", "lib/rdoc/generator/template/darkfish/images/date.png", "lib/rdoc/generator/template/darkfish/images/delete.png", "lib/rdoc/generator/template/darkfish/images/find.png", "lib/rdoc/generator/template/darkfish/images/loadingAnimation.gif", "lib/rdoc/generator/template/darkfish/images/macFFBgHack.png", "lib/rdoc/generator/template/darkfish/images/package.png", "lib/rdoc/generator/template/darkfish/images/page_green.png", "lib/rdoc/generator/template/darkfish/images/page_white_text.png", "lib/rdoc/generator/template/darkfish/images/page_white_width.png", "lib/rdoc/generator/template/darkfish/images/plugin.png", "lib/rdoc/generator/template/darkfish/images/ruby.png", "lib/rdoc/generator/template/darkfish/images/tag_blue.png", "lib/rdoc/generator/template/darkfish/images/tag_green.png", "lib/rdoc/generator/template/darkfish/images/transparent.png", "lib/rdoc/generator/template/darkfish/images/wrench.png", "lib/rdoc/generator/template/darkfish/images/wrench_orange.png", "lib/rdoc/generator/template/darkfish/images/zoom.png", "lib/rdoc/generator/template/darkfish/index.rhtml", "lib/rdoc/generator/template/darkfish/js/darkfish.js", "lib/rdoc/generator/template/darkfish/js/jquery.js", "lib/rdoc/generator/template/darkfish/js/search.js", "lib/rdoc/generator/template/darkfish/page.rhtml", "lib/rdoc/generator/template/darkfish/servlet_not_found.rhtml", "lib/rdoc/generator/template/darkfish/servlet_root.rhtml", "lib/rdoc/generator/template/darkfish/table_of_contents.rhtml", "lib/rdoc/generator/template/json_index/.document", "lib/rdoc/generator/template/json_index/js/navigation.js", "lib/rdoc/generator/template/json_index/js/searcher.js", "lib/rdoc/ghost_method.rb", "lib/rdoc/i18n.rb", "lib/rdoc/i18n/locale.rb", "lib/rdoc/i18n/text.rb", "lib/rdoc/include.rb", "lib/rdoc/known_classes.rb", "lib/rdoc/markdown.kpeg", "lib/rdoc/markdown/entities.rb", "lib/rdoc/markdown/literals.kpeg", "lib/rdoc/markup.rb", "lib/rdoc/markup/attr_changer.rb", "lib/rdoc/markup/attr_span.rb", "lib/rdoc/markup/attribute_manager.rb", "lib/rdoc/markup/attributes.rb", "lib/rdoc/markup/blank_line.rb", "lib/rdoc/markup/block_quote.rb", "lib/rdoc/markup/document.rb", "lib/rdoc/markup/formatter.rb", "lib/rdoc/markup/formatter_test_case.rb", "lib/rdoc/markup/hard_break.rb", "lib/rdoc/markup/heading.rb", "lib/rdoc/markup/include.rb", "lib/rdoc/markup/indented_paragraph.rb", "lib/rdoc/markup/inline.rb", "lib/rdoc/markup/list.rb", "lib/rdoc/markup/list_item.rb", "lib/rdoc/markup/paragraph.rb", "lib/rdoc/markup/parser.rb", "lib/rdoc/markup/pre_process.rb", "lib/rdoc/markup/raw.rb", "lib/rdoc/markup/rule.rb", "lib/rdoc/markup/special.rb", "lib/rdoc/markup/text_formatter_test_case.rb", "lib/rdoc/markup/to_ansi.rb", "lib/rdoc/markup/to_bs.rb", "lib/rdoc/markup/to_html.rb", "lib/rdoc/markup/to_html_crossref.rb", "lib/rdoc/markup/to_html_snippet.rb", "lib/rdoc/markup/to_joined_paragraph.rb", "lib/rdoc/markup/to_label.rb", "lib/rdoc/markup/to_markdown.rb", "lib/rdoc/markup/to_rdoc.rb", "lib/rdoc/markup/to_table_of_contents.rb", "lib/rdoc/markup/to_test.rb", "lib/rdoc/markup/to_tt_only.rb", "lib/rdoc/markup/verbatim.rb", "lib/rdoc/meta_method.rb", "lib/rdoc/method_attr.rb", "lib/rdoc/mixin.rb", "lib/rdoc/normal_class.rb", "lib/rdoc/normal_module.rb", "lib/rdoc/options.rb", "lib/rdoc/parser.rb", "lib/rdoc/parser/c.rb", "lib/rdoc/parser/changelog.rb", "lib/rdoc/parser/markdown.rb", "lib/rdoc/parser/rd.rb", "lib/rdoc/parser/ripper_state_lex.rb", "lib/rdoc/parser/ruby.rb", "lib/rdoc/parser/ruby_tools.rb", "lib/rdoc/parser/simple.rb", "lib/rdoc/parser/text.rb", "lib/rdoc/rd.rb", "lib/rdoc/rd/block_parser.ry", "lib/rdoc/rd/inline.rb", "lib/rdoc/rd/inline_parser.ry", "lib/rdoc/rdoc.rb", "lib/rdoc/require.rb", "lib/rdoc/ri.rb", "lib/rdoc/ri/driver.rb", "lib/rdoc/ri/formatter.rb", "lib/rdoc/ri/paths.rb", "lib/rdoc/ri/store.rb", "lib/rdoc/ri/task.rb", "lib/rdoc/rubygems_hook.rb", "lib/rdoc/servlet.rb", "lib/rdoc/single_class.rb", "lib/rdoc/stats.rb", "lib/rdoc/stats/normal.rb", "lib/rdoc/stats/quiet.rb", "lib/rdoc/stats/verbose.rb", "lib/rdoc/store.rb", "lib/rdoc/task.rb", "lib/rdoc/test_case.rb", "lib/rdoc/text.rb", "lib/rdoc/token_stream.rb", "lib/rdoc/tom_doc.rb", "lib/rdoc/top_level.rb", "rdoc.gemspec"] + # files from .gitignore + s.files << "lib/rdoc/rd/block_parser.rb" << "lib/rdoc/rd/inline_parser.rb" << "lib/rdoc/markdown.rb" << "lib/rdoc/markdown/literals.rb" + + s.rdoc_options = ["--main", "README.rdoc"] + s.extra_rdoc_files += %w[ + CVE-2013-0256.rdoc + CONTRIBUTING.rdoc + ExampleMarkdown.md + ExampleRDoc.rdoc + History.rdoc + LEGAL.rdoc + LICENSE.rdoc + README.rdoc + RI.rdoc + TODO.rdoc + ] + + s.required_ruby_version = Gem::Requirement.new(">= 2.2.2") + s.rubygems_version = "2.5.2" + s.required_rubygems_version = Gem::Requirement.new(">= 2.2") + + s.add_development_dependency("rake") + s.add_development_dependency("racc", "> 1.4.10") + s.add_development_dependency("kpeg") + s.add_development_dependency("minitest", "~> 4") + s.add_development_dependency("json") +end diff --git a/lib/rdoc/rdoc.rb b/lib/rdoc/rdoc.rb index cb5d6501d8..68775c8be1 100644 --- a/lib/rdoc/rdoc.rb +++ b/lib/rdoc/rdoc.rb @@ -1,278 +1,571 @@ -# See README. +# frozen_string_literal: true +require 'rdoc' + +require 'find' +require 'fileutils' +require 'pathname' +require 'time' + +## +# This is the driver for generating RDoc output. It handles file parsing and +# generation of output. +# +# To use this class to generate RDoc output via the API, the recommended way +# is: +# +# rdoc = RDoc::RDoc.new +# options = rdoc.load_options # returns an RDoc::Options instance +# # set extra options +# rdoc.document options # - +# You can also generate output like the +rdoc+ executable: +# +# rdoc = RDoc::RDoc.new +# rdoc.document argv +# +# Where +argv+ is an array of strings, each corresponding to an argument you'd +# give rdoc on the command line. See <tt>rdoc --help<tt> for details. -VERSION_STRING = %{RDoc V1.0.1 - 20041108} +class RDoc::RDoc + @current = nil -require 'rdoc/parsers/parse_rb.rb' -require 'rdoc/parsers/parse_c.rb' -require 'rdoc/parsers/parse_f95.rb' + ## + # This is the list of supported output generators -require 'rdoc/parsers/parse_simple.rb' -require 'rdoc/options' + GENERATORS = {} -require 'rdoc/diagram' + ## + # File pattern to exclude -require 'find' -require 'ftools' + attr_accessor :exclude -# We put rdoc stuff in the RDoc module to avoid namespace -# clutter. -# -# ToDo: This isn't universally true. -# -# :include: README + ## + # Generator instance used for creating output -module RDoc + attr_accessor :generator - # Name of the dotfile that contains the description of files to be - # processed in the current directory - DOT_DOC_FILENAME = ".document" + ## + # Hash of files and their last modified times. - # 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) + attr_reader :last_modified + + ## + # RDoc options + + attr_accessor :options + + ## + # Accessor for statistics. Available after each call to parse_files + + attr_reader :stats + + ## + # The current documentation store + + attr_reader :store + + ## + # Add +klass+ that can generate output after parsing + + def self.add_generator(klass) + name = klass.name.sub(/^RDoc::Generator::/, '').downcase + GENERATORS[name] = klass + end + + ## + # Active RDoc::RDoc instance + + def self.current + @current + end + + ## + # Sets the active RDoc::RDoc instance + + def self.current= rdoc + @current = rdoc + end + + ## + # Creates a new RDoc::RDoc instance. Call #document to parse files and + # generate documentation. + + def initialize + @current = nil + @exclude = nil + @generator = nil + @last_modified = {} + @old_siginfo = nil + @options = nil + @stats = nil + @store = nil + end + + ## + # Report an error message and exit + + def error(msg) + raise RDoc::Error, msg + end + + ## + # Gathers a set of parseable files from the files and directories listed in + # +files+. + + def gather_files files + files = ["."] if files.empty? + + file_list = normalized_file_list files, true, @exclude + + file_list = file_list.uniq + + file_list = remove_unparseable file_list + + file_list.sort + end + + ## + # Turns RDoc from stdin into HTML + + def handle_pipe + @html = RDoc::Markup::ToHtml.new @options + + parser = RDoc::Text::MARKUP_FORMAT[@options.markup] + + document = parser.parse $stdin.read + + out = @html.convert document + + $stdout.write out + end + + ## + # Installs a siginfo handler that prints the current filename. + + def install_siginfo_handler + return unless Signal.list.include? 'INFO' + + @old_siginfo = trap 'INFO' do + puts @current if @current end end + ## + # Loads options from .rdoc_options if the file exists, otherwise creates a + # new RDoc::Options instance. - # Exception thrown by any rdoc error. Only the #message part is - # of use externally. + def load_options + options_file = File.expand_path '.rdoc_options' + return RDoc::Options.new unless File.exist? options_file - class RDocError < Exception + RDoc.load_yaml + + begin + options = YAML.load_file '.rdoc_options' + rescue Psych::SyntaxError + end + + raise RDoc::Error, "#{options_file} is not a valid rdoc options file" unless + RDoc::Options === options + + options end - # Encapsulate the production of rdoc documentation. Basically - # you can use this as you would invoke rdoc from the command - # line: - # - # rdoc = RDoc::RDoc.new - # rdoc.document(args) - # - # where _args_ is an array of strings, each corresponding to - # an argument you'd give rdoc on the command line. See rdoc/rdoc.rb - # for details. - - class RDoc - - ## - # This is the list of output generators that we - # support - - Generator = Struct.new(:file_name, :class_name, :key) - - GENERATORS = {} - $:.collect {|d| - File::expand_path(d) - }.find_all {|d| - File::directory?("#{d}/rdoc/generators") - }.each {|dir| - Dir::entries("#{dir}/rdoc/generators").each {|gen| - next unless /(\w+)_generator.rb$/ =~ gen - type = $1 - unless GENERATORS.has_key? type - GENERATORS[type] = Generator.new("rdoc/generators/#{gen}", - "#{type.upcase}Generator".intern, - type) + ## + # Create an output dir if it doesn't exist. If it does exist, but doesn't + # contain the flag file <tt>created.rid</tt> then we refuse to use it, as + # we may clobber some manually generated documentation + + def setup_output_dir(dir, force) + flag_file = output_flag_file dir + + last = {} + + if @options.dry_run then + # do nothing + elsif File.exist? dir then + error "#{dir} exists and is not a directory" unless File.directory? dir + + begin + open flag_file do |io| + unless force then + Time.parse io.gets + + io.each do |line| + file, time = line.split "\t", 2 + time = Time.parse(time) rescue next + last[file] = time + end + end end - } - } - - ####### - private - ####### - - ## - # Report an error message and exit - - def error(msg) - raise RDocError.new(msg) + rescue SystemCallError, TypeError + error <<-ERROR + +Directory #{dir} already exists, but it looks like it isn't an RDoc directory. + +Because RDoc doesn't want to risk destroying any of your existing files, +you'll need to specify a different output directory name (using the --op <dir> +option) + + ERROR + end unless @options.force_output + else + FileUtils.mkdir_p dir + FileUtils.touch flag_file end - - ## - # Create an output dir if it doesn't exist. If it does - # exist, but doesn't contain the flag file <tt>created.rid</tt> - # then we refuse to use it, as we may clobber some - # manually generated documentation - - def setup_output_dir(op_dir) - flag_file = File.join(op_dir, "created.rid") - if File.exist?(op_dir) - unless File.directory?(op_dir) - error "'#{op_dir}' exists, and is not a directory" - end - unless File.file?(flag_file) - error "\nDirectory #{op_dir} already exists, but it looks like it\n" + - "isn't an RDoc directory. Because RDoc doesn't want to risk\n" + - "destroying any of your existing files, you'll need to\n" + - "specify a different output directory name (using the\n" + - "--op <dir> option).\n\n" - end - else - File.makedirs(op_dir) + + last + end + + ## + # Sets the current documentation tree to +store+ and sets the store's rdoc + # driver to this instance. + + def store= store + @store = store + @store.rdoc = self + end + + ## + # Update the flag file in an output directory. + + def update_output_dir(op_dir, time, last = {}) + return if @options.dry_run or not @options.update_output_dir + + open output_flag_file(op_dir), "w" do |f| + f.puts time.rfc2822 + last.each do |n, t| + f.puts "#{n}\t#{t.rfc2822}" end - File.open(flag_file, "w") {|f| f.puts Time.now } end - + end + + ## + # Return the path name of the flag file in an output directory. - # The .document file contains a list of file and directory name - # patterns, representing candidates for documentation. It may - # also contain comments (starting with '#') - def parse_dot_doc_file(in_dir, filename, options) - # read and strip comments - patterns = File.read(filename).gsub(/#.*/, '') + def output_flag_file(op_dir) + File.join op_dir, "created.rid" + end - result = [] + ## + # The .document file contains a list of file and directory name patterns, + # representing candidates for documentation. It may also contain comments + # (starting with '#') - patterns.split.each do |patt| - candidates = Dir.glob(File.join(in_dir, patt)) - result.concat(normalized_file_list(options, candidates)) - end - result + def parse_dot_doc_file in_dir, filename + # read and strip comments + patterns = File.read(filename).gsub(/#.*/, '') + + result = [] + + patterns.split.each do |patt| + candidates = Dir.glob(File.join(in_dir, patt)) + result.concat normalized_file_list(candidates) end + result + end + + ## + # Given a list of files and directories, create a list of all the Ruby + # files they contain. + # + # If +force_doc+ is true we always add the given files, if false, only + # add files that we guarantee we can parse. It is true when looking at + # files given on the command line, false when recursing through + # subdirectories. + # + # The effect of this is that if you want a file with a non-standard + # extension parsed, you must name it explicitly. + + def normalized_file_list(relative_files, force_doc = false, + exclude_pattern = nil) + file_list = [] + + relative_files.each do |rel_file_name| + next if rel_file_name.end_with? 'created.rid' + next if exclude_pattern && exclude_pattern =~ rel_file_name + stat = File.stat rel_file_name rescue next + + case type = stat.ftype + when "file" then + next if last_modified = @last_modified[rel_file_name] and + stat.mtime.to_i <= last_modified.to_i + + if force_doc or RDoc::Parser.can_parse(rel_file_name) then + file_list << rel_file_name.sub(/^\.\//, '') + @last_modified[rel_file_name] = stat.mtime + end + when "directory" then + next if rel_file_name == "CVS" || rel_file_name == ".svn" - # Given a list of files and directories, create a list - # of all the Ruby files they contain. - # - # If +force_doc+ is true, we always add the given files. - # If false, only add files that we guarantee we can parse - # It is true when looking at files given on the command line, - # false when recursing through subdirectories. - # - # The effect of this is that if you want a file with a non- - # standard extension parsed, you must name it explicity. - # - - def normalized_file_list(options, relative_files, force_doc = false, exclude_pattern=nil) - file_list = [] - - relative_files.each do |rel_file_name| - next if exclude_pattern && exclude_pattern =~ rel_file_name - case type = File.stat(rel_file_name).ftype - when "file" - file_list << rel_file_name.sub(/^\.\//, '') if force_doc || ParserFactory.can_parse(rel_file_name) - when "directory" - next if rel_file_name == "CVS" || rel_file_name == ".svn" - dot_doc = File.join(rel_file_name, DOT_DOC_FILENAME) - if File.file?(dot_doc) - file_list.concat(parse_dot_doc_file(rel_file_name, dot_doc, options)) - else - file_list.concat(list_files_in_directory(rel_file_name, options)) - end + created_rid = File.join rel_file_name, "created.rid" + next if File.file? created_rid + + dot_doc = File.join rel_file_name, RDoc::DOT_DOC_FILENAME + + if File.file? dot_doc then + file_list << parse_dot_doc_file(rel_file_name, dot_doc) else - raise RDocError.new("I can't deal with a #{type} #{rel_file_name}") + file_list << list_files_in_directory(rel_file_name) end + else + warn "rdoc can't parse the #{type} #{rel_file_name}" end - file_list end - # Return a list of the files to be processed in - # a directory. We know that this directory doesn't have - # a .document file, so we're looking for real files. However - # we may well contain subdirectories which must - # be tested for .document files - def list_files_in_directory(dir, options) - normalized_file_list(options, Dir.glob(File.join(dir, "*")), false, options.exclude) + file_list.flatten + end + + ## + # Return a list of the files to be processed in a directory. We know that + # this directory doesn't have a .document file, so we're looking for real + # files. However we may well contain subdirectories which must be tested + # for .document files. + + def list_files_in_directory dir + files = Dir.glob File.join(dir, "*") + + normalized_file_list files, false, @options.exclude + end + + ## + # Parses +filename+ and returns an RDoc::TopLevel + + def parse_file filename + encoding = @options.encoding + filename = filename.encode encoding + + @stats.add_file filename + + return if RDoc::Parser.binary? filename + + content = RDoc::Encoding.read_file filename, encoding + + return unless content + + filename_path = Pathname(filename).expand_path + begin + relative_path = filename_path.relative_path_from @options.root + rescue ArgumentError + relative_path = filename_path + end + + if @options.page_dir and + relative_path.to_s.start_with? @options.page_dir.to_s then + relative_path = + relative_path.relative_path_from @options.page_dir end + top_level = @store.add_file filename, relative_path.to_s - # Parse each file on the command line, recursively entering - # directories + parser = RDoc::Parser.for top_level, filename, content, @options, @stats - def parse_files(options) - - file_info = [] + return unless parser - files = options.files - files = ["."] if files.empty? + parser.scan - file_list = normalized_file_list(options, files, true) + # restart documentation for the classes & modules found + top_level.classes_or_modules.each do |cm| + cm.done_documenting = false + end - file_list.each do |fn| - $stderr.printf("\n%35s: ", File.basename(fn)) unless options.quiet - - content = File.open(fn, "r") {|f| f.read} + top_level - top_level = TopLevel.new(fn) - parser = ParserFactory.parser_for(top_level, fn, content, options, @stats) - file_info << parser.scan - @stats.num_files += 1 - end + rescue Errno::EACCES => e + $stderr.puts <<-EOF +Unable to read #{filename}, #{e.message} + +Please check the permissions for this file. Perhaps you do not have access to +it or perhaps the original author's permissions are to restrictive. If the +this is not your library please report a bug to the author. + EOF + rescue => e + $stderr.puts <<-EOF +Before reporting this, could you check that the file you're documenting +has proper syntax: + + #{Gem.ruby} -c #{filename} + +RDoc is not a full Ruby parser and will fail when fed invalid ruby programs. + +The internal error was: + +\t(#{e.class}) #{e.message} + + EOF + + $stderr.puts e.backtrace.join("\n\t") if $DEBUG_RDOC + + raise e + nil + end - file_info + ## + # Parse each file on the command line, recursively entering directories. + + def parse_files files + file_list = gather_files files + @stats = RDoc::Stats.new @store, file_list.length, @options.verbosity + + return [] if file_list.empty? + + original_options = @options.dup + @stats.begin_adding + + file_info = file_list.map do |filename| + @current = filename + parse_file filename + end.compact + + @stats.done_adding + @options = original_options + + file_info + end + + ## + # Removes file extensions known to be unparseable from +files+ and TAGS + # files for emacs and vim. + + def remove_unparseable files + files.reject do |file| + file =~ /\.(?:class|eps|erb|scpt\.txt|svg|ttf|yml)$/i or + (file =~ /tags$/i and + open(file, 'rb') { |io| + io.read(100) =~ /\A(\f\n[^,]+,\d+$|!_TAG_)/ + }) end + end + ## + # Generates documentation or a coverage report depending upon the settings + # in +options+. + # + # +options+ can be either an RDoc::Options instance or an array of strings + # equivalent to the strings that would be passed on the command line like + # <tt>%w[-q -o doc -t My\ Doc\ Title]</tt>. #document will automatically + # call RDoc::Options#finish if an options instance was given. + # + # For a list of options, see either RDoc::Options or <tt>rdoc --help</tt>. + # + # By default, output will be stored in a directory called "doc" below the + # current directory, so make sure you're somewhere writable before invoking. + + def document options + self.store = RDoc::Store.new + + if RDoc::Options === options then + @options = options + @options.finish + else + @options = load_options + @options.parse options + end - public + if @options.pipe then + handle_pipe + exit + end - ################################################################### - # - # Format up one or more files according to the given arguments. - # For simplicity, _argv_ is an array of strings, equivalent to the - # strings that would be passed on the command line. (This isn't a - # coincidence, as we _do_ pass in ARGV when running - # interactively). For a list of options, see rdoc/rdoc.rb. By - # default, output will be stored in a directory called +doc+ below - # the current directory, so make sure you're somewhere writable - # before invoking. - # - # Throws: RDocError on error + @exclude = @options.exclude - def document(argv) + unless @options.coverage_report then + @last_modified = setup_output_dir @options.op_dir, @options.force_update + end - TopLevel::reset + @store.encoding = @options.encoding + @store.dry_run = @options.dry_run + @store.main = @options.main_page + @store.title = @options.title + @store.path = @options.op_dir - @stats = Stats.new + @start_time = Time.now - options = Options.instance - options.parse(argv, GENERATORS) - - unless options.all_one_file - setup_output_dir(options.op_dir) - end + @store.load_cache + + file_info = parse_files @options.files + + @options.default_title = "RDoc Documentation" + + @store.complete @options.visibility - file_info = parse_files(options) + @stats.coverage_level = @options.coverage_report - gen = options.generator - - $stderr.puts "\nGenerating #{gen.key.upcase}..." unless options.quiet - - require gen.file_name - - gen_class = Generators.const_get(gen.class_name) - - unless file_info.empty? - gen = gen_class.for(options) + if @options.coverage_report then + puts - pwd = Dir.pwd + puts @stats.report.accept RDoc::Markup::ToRdoc.new + elsif file_info.empty? then + $stderr.puts "\nNo newer files." unless @options.quiet + else + gen_klass = @options.generator - Dir.chdir(options.op_dir) unless options.all_one_file + @generator = gen_klass.new @store, @options - begin - Diagram.new(file_info, options).draw if options.diagram - gen.generate(file_info) - ensure - Dir.chdir(pwd) + generate + end + + if @stats and (@options.coverage_report or not @options.quiet) then + puts + puts @stats.summary.accept RDoc::Markup::ToRdoc.new + end + + exit @stats.fully_documented? if @options.coverage_report + end + + ## + # Generates documentation for +file_info+ (from #parse_files) into the + # output dir using the generator selected + # by the RDoc options + + def generate + if @options.dry_run then + # do nothing + @generator.generate + else + Dir.chdir @options.op_dir do + unless @options.quiet then + $stderr.puts "\nGenerating #{@generator.class.name.sub(/^.*::/, '')} format into #{Dir.pwd}..." end - end - unless options.quiet - puts - @stats.print + @generator.generate + update_output_dir '.', @start_time, @last_modified end end end + + ## + # Removes a siginfo handler and replaces the previous + + def remove_siginfo_handler + return unless Signal.list.key? 'INFO' + + handler = @old_siginfo || 'DEFAULT' + + trap 'INFO', handler + end + +end + +begin + require 'rubygems' + + rdoc_extensions = Gem.find_files 'rdoc/discover' + + rdoc_extensions.each do |extension| + begin + load extension + rescue => e + warn "error loading #{extension.inspect}: #{e.message} (#{e.class})" + warn "\t#{e.backtrace.join "\n\t"}" if $DEBUG + end + end +rescue LoadError end +# require built-in generators after discovery in case they've been replaced +require 'rdoc/generator/darkfish' +require 'rdoc/generator/ri' +require 'rdoc/generator/pot' diff --git a/lib/rdoc/require.rb b/lib/rdoc/require.rb new file mode 100644 index 0000000000..91f9c24e5d --- /dev/null +++ b/lib/rdoc/require.rb @@ -0,0 +1,52 @@ +# frozen_string_literal: true +## +# A file loaded by \#require + +class RDoc::Require < RDoc::CodeObject + + ## + # Name of the required file + + attr_accessor :name + + ## + # Creates a new Require that loads +name+ with +comment+ + + def initialize(name, comment) + super() + @name = name.gsub(/'|"/, "") #' + @top_level = nil + self.comment = comment + end + + def inspect # :nodoc: + "#<%s:0x%x require '%s' in %s>" % [ + self.class, + object_id, + @name, + parent_file_name, + ] + end + + def to_s # :nodoc: + "require #{name} in: #{parent}" + end + + ## + # The RDoc::TopLevel corresponding to this require, or +nil+ if not found. + + def top_level + @top_level ||= begin + tl = RDoc::TopLevel.all_files_hash[name + '.rb'] + + if tl.nil? and RDoc::TopLevel.all_files.first.full_name =~ %r(^lib/) then + # second chance + tl = RDoc::TopLevel.all_files_hash['lib/' + name + '.rb'] + end + + tl + end + end + +end + diff --git a/lib/rdoc/ri.rb b/lib/rdoc/ri.rb new file mode 100644 index 0000000000..c798c1fc49 --- /dev/null +++ b/lib/rdoc/ri.rb @@ -0,0 +1,21 @@ +# frozen_string_literal: true +require 'rdoc' + +## +# Namespace for the ri command line tool's implementation. +# +# See <tt>ri --help</tt> for details. + +module RDoc::RI + + ## + # Base RI error class + + class Error < RDoc::Error; end + + autoload :Driver, 'rdoc/ri/driver' + autoload :Paths, 'rdoc/ri/paths' + autoload :Store, 'rdoc/ri/store' + +end + diff --git a/lib/rdoc/ri/driver.rb b/lib/rdoc/ri/driver.rb new file mode 100644 index 0000000000..fa0e040a42 --- /dev/null +++ b/lib/rdoc/ri/driver.rb @@ -0,0 +1,1547 @@ +# frozen_string_literal: true +require 'abbrev' +require 'optparse' + +begin + require 'readline' +rescue LoadError +end + +begin + require 'win32console' +rescue LoadError +end + +require 'rdoc' + +## +# For RubyGems backwards compatibility + +require 'rdoc/ri/formatter' + +## +# The RI driver implements the command-line ri tool. +# +# The driver supports: +# * loading RI data from: +# * Ruby's standard library +# * RubyGems +# * ~/.rdoc +# * A user-supplied directory +# * Paging output (uses RI_PAGER environment variable, PAGER environment +# variable or the less, more and pager programs) +# * Interactive mode with tab-completion +# * Abbreviated names (ri Zl shows Zlib documentation) +# * Colorized output +# * Merging output from multiple RI data sources + +class RDoc::RI::Driver + + ## + # Base Driver error class + + class Error < RDoc::RI::Error; end + + ## + # Raised when a name isn't found in the ri data stores + + class NotFoundError < Error + + def initialize(klass, suggestions = nil) # :nodoc: + @klass = klass + @suggestions = suggestions + end + + ## + # Name that wasn't found + + def name + @klass + end + + def message # :nodoc: + str = "Nothing known about #{@klass}" + if @suggestions and !@suggestions.empty? + str += "\nDid you mean? #{@suggestions.join("\n ")}" + end + str + end + end + + ## + # Show all method documentation following a class or module + + attr_accessor :show_all + + ## + # An RDoc::RI::Store for each entry in the RI path + + attr_accessor :stores + + ## + # Controls the user of the pager vs $stdout + + attr_accessor :use_stdout + + ## + # Default options for ri + + def self.default_options + options = {} + options[:interactive] = false + options[:profile] = false + options[:show_all] = false + options[:use_stdout] = !$stdout.tty? + options[:width] = 72 + + # By default all standard paths are used. + options[:use_system] = true + options[:use_site] = true + options[:use_home] = true + options[:use_gems] = true + options[:extra_doc_dirs] = [] + + return options + end + + ## + # Dump +data_path+ using pp + + def self.dump data_path + require 'pp' + + open data_path, 'rb' do |io| + pp Marshal.load(io.read) + end + end + + ## + # Parses +argv+ and returns a Hash of options + + def self.process_args argv + options = default_options + + opts = OptionParser.new do |opt| + opt.accept File do |file,| + File.readable?(file) and not File.directory?(file) and file + end + + opt.program_name = File.basename $0 + opt.version = RDoc::VERSION + opt.release = nil + opt.summary_indent = ' ' * 4 + + opt.banner = <<-EOT +Usage: #{opt.program_name} [options] [name ...] + +Where name can be: + + Class | Module | Module::Class + + Class::method | Class#method | Class.method | method + + gem_name: | gem_name:README | gem_name:History + +All class names may be abbreviated to their minimum unambiguous form. +If a name is ambiguous, all valid options will be listed. + +A '.' matches either class or instance methods, while #method +matches only instance and ::method matches only class methods. + +README and other files may be displayed by prefixing them with the gem name +they're contained in. If the gem name is followed by a ':' all files in the +gem will be shown. The file name extension may be omitted where it is +unambiguous. + +For example: + + #{opt.program_name} Fil + #{opt.program_name} File + #{opt.program_name} File.new + #{opt.program_name} zip + #{opt.program_name} rdoc:README + +Note that shell quoting or escaping may be required for method names +containing punctuation: + + #{opt.program_name} 'Array.[]' + #{opt.program_name} compact\\! + +To see the default directories #{opt.program_name} will search, run: + + #{opt.program_name} --list-doc-dirs + +Specifying the --system, --site, --home, --gems, or --doc-dir options +will limit ri to searching only the specified directories. + +ri options may be set in the RI environment variable. + +The ri pager can be set with the RI_PAGER environment variable +or the PAGER environment variable. + EOT + + opt.separator nil + opt.separator "Options:" + + opt.separator nil + + opt.on("--[no-]interactive", "-i", + "In interactive mode you can repeatedly", + "look up methods with autocomplete.") do |interactive| + options[:interactive] = interactive + end + + opt.separator nil + + opt.on("--[no-]all", "-a", + "Show all documentation for a class or", + "module.") do |show_all| + options[:show_all] = show_all + end + + opt.separator nil + + opt.on("--[no-]list", "-l", + "List classes ri knows about.") do |list| + options[:list] = list + end + + opt.separator nil + + opt.on("--[no-]pager", + "Send output to a pager,", + "rather than directly to stdout.") do |use_pager| + options[:use_stdout] = !use_pager + end + + opt.separator nil + + opt.on("-T", + "Synonym for --no-pager.") do + options[:use_stdout] = true + end + + opt.separator nil + + opt.on("--width=WIDTH", "-w", OptionParser::DecimalInteger, + "Set the width of the output.") do |width| + options[:width] = width + end + + opt.separator nil + + opt.on("--server[=PORT]", Integer, + "Run RDoc server on the given port.", + "The default port is 8214.") do |port| + options[:server] = port || 8214 + end + + opt.separator nil + + formatters = RDoc::Markup.constants.grep(/^To[A-Z][a-z]+$/).sort + formatters = formatters.sort.map do |formatter| + formatter.to_s.sub('To', '').downcase + end + formatters -= %w[html label test] # remove useless output formats + + opt.on("--format=NAME", "-f", + "Use the selected formatter. The default", + "formatter is bs for paged output and ansi", + "otherwise. Valid formatters are:", + "#{formatters.join(', ')}.", formatters) do |value| + options[:formatter] = RDoc::Markup.const_get "To#{value.capitalize}" + end + + opt.separator nil + + opt.on("--help", "-h", + "Show help and exit.") do + puts opts + exit + end + + opt.separator nil + + opt.on("--version", "-v", + "Output version information and exit.") do + puts "#{opts.program_name} #{opts.version}" + exit + end + + opt.separator nil + opt.separator "Data source options:" + opt.separator nil + + opt.on("--[no-]list-doc-dirs", + "List the directories from which ri will", + "source documentation on stdout and exit.") do |list_doc_dirs| + options[:list_doc_dirs] = list_doc_dirs + end + + opt.separator nil + + opt.on("--doc-dir=DIRNAME", "-d", Array, + "List of directories from which to source", + "documentation in addition to the standard", + "directories. May be repeated.") do |value| + value.each do |dir| + unless File.directory? dir then + raise OptionParser::InvalidArgument, "#{dir} is not a directory" + end + + options[:extra_doc_dirs] << File.expand_path(dir) + end + end + + opt.separator nil + + opt.on("--no-standard-docs", + "Do not include documentation from", + "the Ruby standard library, site_lib,", + "installed gems, or ~/.rdoc.", + "Use with --doc-dir.") do + options[:use_system] = false + options[:use_site] = false + options[:use_gems] = false + options[:use_home] = false + end + + opt.separator nil + + opt.on("--[no-]system", + "Include documentation from Ruby's", + "standard library. Defaults to true.") do |value| + options[:use_system] = value + end + + opt.separator nil + + opt.on("--[no-]site", + "Include documentation from libraries", + "installed in site_lib.", + "Defaults to true.") do |value| + options[:use_site] = value + end + + opt.separator nil + + opt.on("--[no-]gems", + "Include documentation from RubyGems.", + "Defaults to true.") do |value| + options[:use_gems] = value + end + + opt.separator nil + + opt.on("--[no-]home", + "Include documentation stored in ~/.rdoc.", + "Defaults to true.") do |value| + options[:use_home] = value + end + + opt.separator nil + opt.separator "Debug options:" + opt.separator nil + + opt.on("--[no-]profile", + "Run with the ruby profiler.") do |value| + options[:profile] = value + end + + opt.separator nil + + opt.on("--dump=CACHE", File, + "Dump data from an ri cache or data file.") do |value| + options[:dump_path] = value + end + end + + argv = ENV['RI'].to_s.split.concat argv + + opts.parse! argv + + options[:names] = argv + + options[:use_stdout] ||= !$stdout.tty? + options[:use_stdout] ||= options[:interactive] + options[:width] ||= 72 + + options + + rescue OptionParser::InvalidArgument, OptionParser::InvalidOption => e + puts opts + puts + puts e + exit 1 + end + + ## + # Runs the ri command line executable using +argv+ + + def self.run argv = ARGV + options = process_args argv + + if options[:dump_path] then + dump options[:dump_path] + return + end + + ri = new options + ri.run + end + + ## + # Creates a new driver using +initial_options+ from ::process_args + + def initialize initial_options = {} + @paging = false + @classes = nil + + options = self.class.default_options.update(initial_options) + + @formatter_klass = options[:formatter] + + require 'profile' if options[:profile] + + @names = options[:names] + @list = options[:list] + + @doc_dirs = [] + @stores = [] + + RDoc::RI::Paths.each(options[:use_system], options[:use_site], + options[:use_home], options[:use_gems], + *options[:extra_doc_dirs]) do |path, type| + @doc_dirs << path + + store = RDoc::RI::Store.new path, type + store.load_cache + @stores << store + end + + @list_doc_dirs = options[:list_doc_dirs] + + @interactive = options[:interactive] + @server = options[:server] + @use_stdout = options[:use_stdout] + @show_all = options[:show_all] + + # pager process for jruby + @jruby_pager_process = nil + end + + ## + # Adds paths for undocumented classes +also_in+ to +out+ + + def add_also_in out, also_in + return if also_in.empty? + + out << RDoc::Markup::Rule.new(1) + out << RDoc::Markup::Paragraph.new("Also found in:") + + paths = RDoc::Markup::Verbatim.new + also_in.each do |store| + paths.parts.push store.friendly_path, "\n" + end + out << paths + end + + ## + # Adds a class header to +out+ for class +name+ which is described in + # +classes+. + + def add_class out, name, classes + heading = if classes.all? { |klass| klass.module? } then + name + else + superclass = classes.map do |klass| + klass.superclass unless klass.module? + end.compact.shift || 'Object' + + superclass = superclass.full_name unless String === superclass + + "#{name} < #{superclass}" + end + + out << RDoc::Markup::Heading.new(1, heading) + out << RDoc::Markup::BlankLine.new + end + + ## + # Adds "(from ...)" to +out+ for +store+ + + def add_from out, store + out << RDoc::Markup::Paragraph.new("(from #{store.friendly_path})") + end + + ## + # Adds +extends+ to +out+ + + def add_extends out, extends + add_extension_modules out, 'Extended by', extends + end + + ## + # Adds a list of +extensions+ to this module of the given +type+ to +out+. + # add_includes and add_extends call this, so you should use those directly. + + def add_extension_modules out, type, extensions + return if extensions.empty? + + out << RDoc::Markup::Rule.new(1) + out << RDoc::Markup::Heading.new(1, "#{type}:") + + extensions.each do |modules, store| + if modules.length == 1 then + add_extension_modules_single out, store, modules.first + else + add_extension_modules_multiple out, store, modules + end + end + end + + ## + # Renders multiple included +modules+ from +store+ to +out+. + + def add_extension_modules_multiple out, store, modules # :nodoc: + out << RDoc::Markup::Paragraph.new("(from #{store.friendly_path})") + + wout, with = modules.partition { |incl| incl.comment.empty? } + + out << RDoc::Markup::BlankLine.new unless with.empty? + + with.each do |incl| + out << RDoc::Markup::Paragraph.new(incl.name) + out << RDoc::Markup::BlankLine.new + out << incl.comment + end + + unless wout.empty? then + verb = RDoc::Markup::Verbatim.new + + wout.each do |incl| + verb.push incl.name, "\n" + end + + out << verb + end + end + + ## + # Adds a single extension module +include+ from +store+ to +out+ + + def add_extension_modules_single out, store, include # :nodoc: + name = include.name + path = store.friendly_path + out << RDoc::Markup::Paragraph.new("#{name} (from #{path})") + + if include.comment then + out << RDoc::Markup::BlankLine.new + out << include.comment + end + end + + ## + # Adds +includes+ to +out+ + + def add_includes out, includes + add_extension_modules out, 'Includes', includes + end + + ## + # Looks up the method +name+ and adds it to +out+ + + def add_method out, name + filtered = lookup_method name + + method_out = method_document name, filtered + + out.concat method_out.parts + end + + ## + # Adds documentation for all methods in +klass+ to +out+ + + def add_method_documentation out, klass + klass.method_list.each do |method| + begin + add_method out, method.full_name + rescue NotFoundError + next + end + end + end + + ## + # Adds a list of +methods+ to +out+ with a heading of +name+ + + def add_method_list out, methods, name + return if methods.empty? + + out << RDoc::Markup::Heading.new(1, "#{name}:") + out << RDoc::Markup::BlankLine.new + + if @use_stdout and !@interactive then + out.concat methods.map { |method| + RDoc::Markup::Verbatim.new method + } + else + out << RDoc::Markup::IndentedParagraph.new(2, methods.join(', ')) + end + + out << RDoc::Markup::BlankLine.new + end + + ## + # Returns ancestor classes of +klass+ + + def ancestors_of klass + ancestors = [] + + unexamined = [klass] + seen = [] + + loop do + break if unexamined.empty? + current = unexamined.shift + seen << current + + stores = classes[current] + + break unless stores and not stores.empty? + + klasses = stores.map do |store| + store.ancestors[current] + end.flatten.uniq + + klasses = klasses - seen + + ancestors.concat klasses + unexamined.concat klasses + end + + ancestors.reverse + end + + ## + # For RubyGems backwards compatibility + + def class_cache # :nodoc: + end + + ## + # Builds a RDoc::Markup::Document from +found+, +klasess+ and +includes+ + + def class_document name, found, klasses, includes, extends + also_in = [] + + out = RDoc::Markup::Document.new + + add_class out, name, klasses + + add_includes out, includes + add_extends out, extends + + found.each do |store, klass| + render_class out, store, klass, also_in + end + + add_also_in out, also_in + + out + end + + ## + # Adds the class +comment+ to +out+. + + def class_document_comment out, comment # :nodoc: + unless comment.empty? then + out << RDoc::Markup::Rule.new(1) + + if comment.merged? then + parts = comment.parts + parts = parts.zip [RDoc::Markup::BlankLine.new] * parts.length + parts.flatten! + parts.pop + + out.concat parts + else + out << comment + end + end + end + + ## + # Adds the constants from +klass+ to the Document +out+. + + def class_document_constants out, klass # :nodoc: + return if klass.constants.empty? + + out << RDoc::Markup::Heading.new(1, "Constants:") + out << RDoc::Markup::BlankLine.new + list = RDoc::Markup::List.new :NOTE + + constants = klass.constants.sort_by { |constant| constant.name } + + list.items.concat constants.map { |constant| + parts = constant.comment.parts if constant.comment + parts << RDoc::Markup::Paragraph.new('[not documented]') if + parts.empty? + + RDoc::Markup::ListItem.new(constant.name, *parts) + } + + out << list + out << RDoc::Markup::BlankLine.new + end + + ## + # Hash mapping a known class or module to the stores it can be loaded from + + def classes + return @classes if @classes + + @classes = {} + + @stores.each do |store| + store.cache[:modules].each do |mod| + # using default block causes searched-for modules to be added + @classes[mod] ||= [] + @classes[mod] << store + end + end + + @classes + end + + ## + # Returns the stores wherein +name+ is found along with the classes, + # extends and includes that match it + + def classes_and_includes_and_extends_for name + klasses = [] + extends = [] + includes = [] + + found = @stores.map do |store| + begin + klass = store.load_class name + klasses << klass + extends << [klass.extends, store] if klass.extends + includes << [klass.includes, store] if klass.includes + [store, klass] + rescue RDoc::Store::MissingFileError + end + end.compact + + extends.reject! do |modules,| modules.empty? end + includes.reject! do |modules,| modules.empty? end + + [found, klasses, includes, extends] + end + + ## + # Completes +name+ based on the caches. For Readline + + def complete name + completions = [] + + klass, selector, method = parse_name name + + complete_klass name, klass, selector, method, completions + complete_method name, klass, selector, completions + + completions.sort.uniq + end + + def complete_klass name, klass, selector, method, completions # :nodoc: + klasses = classes.keys + + # may need to include Foo when given Foo:: + klass_name = method ? name : klass + + if name !~ /#|\./ then + completions.replace klasses.grep(/^#{Regexp.escape klass_name}[^:]*$/) + completions.concat klasses.grep(/^#{Regexp.escape name}[^:]*$/) if + name =~ /::$/ + + completions << klass if classes.key? klass # to complete a method name + elsif selector then + completions << klass if classes.key? klass + elsif classes.key? klass_name then + completions << klass_name + end + end + + def complete_method name, klass, selector, completions # :nodoc: + if completions.include? klass and name =~ /#|\.|::/ then + methods = list_methods_matching name + + if not methods.empty? then + # remove Foo if given Foo:: and a method was found + completions.delete klass + elsif selector then + # replace Foo with Foo:: as given + completions.delete klass + completions << "#{klass}#{selector}" + end + + completions.concat methods + end + end + + ## + # Converts +document+ to text and writes it to the pager + + def display document + page do |io| + text = document.accept formatter(io) + + io.write text + end + end + + ## + # Outputs formatted RI data for class +name+. Groups undocumented classes + + def display_class name + return if name =~ /#|\./ + + found, klasses, includes, extends = + classes_and_includes_and_extends_for name + + return if found.empty? + + out = class_document name, found, klasses, includes, extends + + display out + end + + ## + # Outputs formatted RI data for method +name+ + + def display_method name + out = RDoc::Markup::Document.new + + add_method out, name + + display out + end + + ## + # Outputs formatted RI data for the class or method +name+. + # + # Returns true if +name+ was found, false if it was not an alternative could + # be guessed, raises an error if +name+ couldn't be guessed. + + def display_name name + if name =~ /\w:(\w|$)/ then + display_page name + return true + end + + return true if display_class name + + display_method name if name =~ /::|#|\./ + + true + rescue NotFoundError + matches = list_methods_matching name if name =~ /::|#|\./ + matches = classes.keys.grep(/^#{Regexp.escape name}/) if matches.empty? + + raise if matches.empty? + + page do |io| + io.puts "#{name} not found, maybe you meant:" + io.puts + io.puts matches.sort.join("\n") + end + + false + end + + ## + # Displays each name in +name+ + + def display_names names + names.each do |name| + name = expand_name name + + display_name name + end + end + + ## + # Outputs formatted RI data for page +name+. + + def display_page name + store_name, page_name = name.split ':', 2 + + store = @stores.find { |s| s.source == store_name } + + return display_page_list store if page_name.empty? + + pages = store.cache[:pages] + + unless pages.include? page_name then + found_names = pages.select do |n| + n =~ /#{Regexp.escape page_name}\.[^.]+$/ + end + + if found_names.length.zero? then + return display_page_list store, pages + elsif found_names.length > 1 then + return display_page_list store, found_names, page_name + end + + page_name = found_names.first + end + + page = store.load_page page_name + + display page.comment + end + + ## + # Outputs a formatted RI page list for the pages in +store+. + + def display_page_list store, pages = store.cache[:pages], search = nil + out = RDoc::Markup::Document.new + + title = if search then + "#{search} pages" + else + 'Pages' + end + + out << RDoc::Markup::Heading.new(1, "#{title} in #{store.friendly_path}") + out << RDoc::Markup::BlankLine.new + + list = RDoc::Markup::List.new(:BULLET) + + pages.each do |page| + list << RDoc::Markup::Paragraph.new(page) + end + + out << list + + display out + end + + def check_did_you_mean # :nodoc: + if defined? DidYouMean::SpellChecker + true + else + begin + require 'did_you_mean' + if defined? DidYouMean::SpellChecker + true + else + false + end + rescue LoadError + false + end + end + end + + ## + # Expands abbreviated klass +klass+ into a fully-qualified class. "Zl::Da" + # will be expanded to Zlib::DataError. + + def expand_class klass + class_names = classes.keys + ary = class_names.grep(Regexp.new("\\A#{klass.gsub(/(?=::|\z)/, '[^:]*')}\\z")) + if ary.length != 1 && ary.first != klass + if check_did_you_mean + suggestions = DidYouMean::SpellChecker.new(dictionary: class_names).correct(klass) + raise NotFoundError.new(klass, suggestions) + else + raise NotFoundError, klass + end + end + ary.first + end + + ## + # Expands the class portion of +name+ into a fully-qualified class. See + # #expand_class. + + def expand_name name + klass, selector, method = parse_name name + + return [selector, method].join if klass.empty? + + case selector + when ':' then + [find_store(klass), selector, method] + else + [expand_class(klass), selector, method] + end.join + end + + ## + # Filters the methods in +found+ trying to find a match for +name+. + + def filter_methods found, name + regexp = name_regexp name + + filtered = found.find_all do |store, methods| + methods.any? { |method| method.full_name =~ regexp } + end + + return filtered unless filtered.empty? + + found + end + + ## + # Yields items matching +name+ including the store they were found in, the + # class being searched for, the class they were found in (an ancestor) the + # types of methods to look up (from #method_type), and the method name being + # searched for + + def find_methods name + klass, selector, method = parse_name name + + types = method_type selector + + klasses = nil + ambiguous = klass.empty? + + if ambiguous then + klasses = classes.keys + else + klasses = ancestors_of klass + klasses.unshift klass + end + + methods = [] + + klasses.each do |ancestor| + ancestors = classes[ancestor] + + next unless ancestors + + klass = ancestor if ambiguous + + ancestors.each do |store| + methods << [store, klass, ancestor, types, method] + end + end + + methods = methods.sort_by do |_, k, a, _, m| + [k, a, m].compact + end + + methods.each do |item| + yield(*item) # :yields: store, klass, ancestor, types, method + end + + self + end + + ## + # Finds the given +pager+ for jruby. Returns an IO if +pager+ was found. + # + # Returns false if +pager+ does not exist. + # + # Returns nil if the jruby JVM doesn't support ProcessBuilder redirection + # (1.6 and older). + + def find_pager_jruby pager + require 'java' + require 'shellwords' + + return nil unless java.lang.ProcessBuilder.constants.include? :Redirect + + pager = Shellwords.split pager + + pb = java.lang.ProcessBuilder.new(*pager) + pb = pb.redirect_output java.lang.ProcessBuilder::Redirect::INHERIT + + @jruby_pager_process = pb.start + + input = @jruby_pager_process.output_stream + + io = input.to_io + io.sync = true + io + rescue java.io.IOException + false + end + + ## + # Finds a store that matches +name+ which can be the name of a gem, "ruby", + # "home" or "site". + # + # See also RDoc::Store#source + + def find_store name + @stores.each do |store| + source = store.source + + return source if source == name + + return source if + store.type == :gem and source =~ /^#{Regexp.escape name}-\d/ + end + + raise RDoc::RI::Driver::NotFoundError, name + end + + ## + # Creates a new RDoc::Markup::Formatter. If a formatter is given with -f, + # use it. If we're outputting to a pager, use bs, otherwise ansi. + + def formatter(io) + if @formatter_klass then + @formatter_klass.new + elsif paging? or !io.tty? then + RDoc::Markup::ToBs.new + else + RDoc::Markup::ToAnsi.new + end + end + + ## + # Runs ri interactively using Readline if it is available. + + def interactive + puts "\nEnter the method name you want to look up." + + if defined? Readline then + Readline.completion_proc = method :complete + puts "You can use tab to autocomplete." + end + + puts "Enter a blank line to exit.\n\n" + + loop do + name = if defined? Readline then + Readline.readline ">> " + else + print ">> " + $stdin.gets + end + + return if name.nil? or name.empty? + + begin + display_name expand_name(name.strip) + rescue NotFoundError => e + puts e.message + end + end + + rescue Interrupt + exit + end + + ## + # Is +file+ in ENV['PATH']? + + def in_path? file + return true if file =~ %r%\A/% and File.exist? file + + ENV['PATH'].split(File::PATH_SEPARATOR).any? do |path| + File.exist? File.join(path, file) + end + end + + ## + # Lists classes known to ri starting with +names+. If +names+ is empty all + # known classes are shown. + + def list_known_classes names = [] + classes = [] + + stores.each do |store| + classes << store.module_names + end + + classes = classes.flatten.uniq.sort + + unless names.empty? then + filter = Regexp.union names.map { |name| /^#{name}/ } + + classes = classes.grep filter + end + + page do |io| + if paging? or io.tty? then + if names.empty? then + io.puts "Classes and Modules known to ri:" + else + io.puts "Classes and Modules starting with #{names.join ', '}:" + end + io.puts + end + + io.puts classes.join("\n") + end + end + + ## + # Returns an Array of methods matching +name+ + + def list_methods_matching name + found = [] + + find_methods name do |store, klass, ancestor, types, method| + if types == :instance or types == :both then + methods = store.instance_methods[ancestor] + + if methods then + matches = methods.grep(/^#{Regexp.escape method.to_s}/) + + matches = matches.map do |match| + "#{klass}##{match}" + end + + found.concat matches + end + end + + if types == :class or types == :both then + methods = store.class_methods[ancestor] + + next unless methods + matches = methods.grep(/^#{Regexp.escape method.to_s}/) + + matches = matches.map do |match| + "#{klass}::#{match}" + end + + found.concat matches + end + end + + found.uniq + end + + ## + # Loads RI data for method +name+ on +klass+ from +store+. +type+ and + # +cache+ indicate if it is a class or instance method. + + def load_method store, cache, klass, type, name + methods = store.send(cache)[klass] + + return unless methods + + method = methods.find do |method_name| + method_name == name + end + + return unless method + + store.load_method klass, "#{type}#{method}" + rescue RDoc::Store::MissingFileError => e + comment = RDoc::Comment.new("missing documentation at #{e.file}").parse + + method = RDoc::AnyMethod.new nil, name + method.comment = comment + method + end + + ## + # Returns an Array of RI data for methods matching +name+ + + def load_methods_matching name + found = [] + + find_methods name do |store, klass, ancestor, types, method| + methods = [] + + methods << load_method(store, :class_methods, ancestor, '::', method) if + [:class, :both].include? types + + methods << load_method(store, :instance_methods, ancestor, '#', method) if + [:instance, :both].include? types + + found << [store, methods.compact] + end + + found.reject do |path, methods| methods.empty? end + end + + ## + # Returns a filtered list of methods matching +name+ + + def lookup_method name + found = load_methods_matching name + + if found.empty? + if check_did_you_mean + methods = [] + _, _, method_name = parse_name name + find_methods name do |store, klass, ancestor, types, method| + methods.push(*store.class_methods[klass]) if [:class, :both].include? types + methods.push(*store.instance_methods[klass]) if [:instance, :both].include? types + end + methods = methods.uniq + suggestions = DidYouMean::SpellChecker.new(dictionary: methods).correct(method_name) + raise NotFoundError.new(name, suggestions) + else + raise NotFoundError, name + end + end + + filter_methods found, name + end + + ## + # Builds a RDoc::Markup::Document from +found+, +klasses+ and +includes+ + + def method_document name, filtered + out = RDoc::Markup::Document.new + + out << RDoc::Markup::Heading.new(1, name) + out << RDoc::Markup::BlankLine.new + + filtered.each do |store, methods| + methods.each do |method| + render_method out, store, method, name + end + end + + out + end + + ## + # Returns the type of method (:both, :instance, :class) for +selector+ + + def method_type selector + case selector + when '.', nil then :both + when '#' then :instance + else :class + end + end + + ## + # Returns a regular expression for +name+ that will match an + # RDoc::AnyMethod's name. + + def name_regexp name + klass, type, name = parse_name name + + case type + when '#', '::' then + /^#{klass}#{type}#{Regexp.escape name}$/ + else + /^#{klass}(#|::)#{Regexp.escape name}$/ + end + end + + ## + # Paginates output through a pager program. + + def page + if pager = setup_pager then + begin + yield pager + ensure + pager.close + @jruby_pager_process.wait_for if @jruby_pager_process + end + else + yield $stdout + end + rescue Errno::EPIPE + ensure + @paging = false + end + + ## + # Are we using a pager? + + def paging? + @paging + end + + ## + # Extracts the class, selector and method name parts from +name+ like + # Foo::Bar#baz. + # + # NOTE: Given Foo::Bar, Bar is considered a class even though it may be a + # method + + def parse_name name + parts = name.split(/(::?|#|\.)/) + + if parts.length == 1 then + if parts.first =~ /^[a-z]|^([%&*+\/<>^`|~-]|\+@|-@|<<|<=>?|===?|=>|=~|>>|\[\]=?|~@)$/ then + type = '.' + meth = parts.pop + else + type = nil + meth = nil + end + elsif parts.length == 2 or parts.last =~ /::|#|\./ then + type = parts.pop + meth = nil + elsif parts[1] == ':' then + klass = parts.shift + type = parts.shift + meth = parts.join + elsif parts[-2] != '::' or parts.last !~ /^[A-Z]/ then + meth = parts.pop + type = parts.pop + end + + klass ||= parts.join + + [klass, type, meth] + end + + ## + # Renders the +klass+ from +store+ to +out+. If the klass has no + # documentable items the class is added to +also_in+ instead. + + def render_class out, store, klass, also_in # :nodoc: + comment = klass.comment + # TODO the store's cache should always return an empty Array + class_methods = store.class_methods[klass.full_name] || [] + instance_methods = store.instance_methods[klass.full_name] || [] + attributes = store.attributes[klass.full_name] || [] + + if comment.empty? and + instance_methods.empty? and class_methods.empty? then + also_in << store + return + end + + add_from out, store + + class_document_comment out, comment + + if class_methods or instance_methods or not klass.constants.empty? then + out << RDoc::Markup::Rule.new(1) + end + + class_document_constants out, klass + + add_method_list out, class_methods, 'Class methods' + add_method_list out, instance_methods, 'Instance methods' + add_method_list out, attributes, 'Attributes' + + add_method_documentation out, klass if @show_all + end + + def render_method out, store, method, name # :nodoc: + out << RDoc::Markup::Paragraph.new("(from #{store.friendly_path})") + + unless name =~ /^#{Regexp.escape method.parent_name}/ then + out << RDoc::Markup::Heading.new(3, "Implementation from #{method.parent_name}") + end + + out << RDoc::Markup::Rule.new(1) + + render_method_arguments out, method.arglists + render_method_superclass out, method + render_method_comment out, method + end + + def render_method_arguments out, arglists # :nodoc: + return unless arglists + + arglists = arglists.chomp.split "\n" + arglists = arglists.map { |line| line + "\n" } + out << RDoc::Markup::Verbatim.new(*arglists) + out << RDoc::Markup::Rule.new(1) + end + + def render_method_comment out, method # :nodoc: + out << RDoc::Markup::BlankLine.new + out << method.comment + out << RDoc::Markup::BlankLine.new + end + + def render_method_superclass out, method # :nodoc: + return unless + method.respond_to?(:superclass_method) and method.superclass_method + + out << RDoc::Markup::BlankLine.new + out << RDoc::Markup::Heading.new(4, "(Uses superclass method #{method.superclass_method})") + out << RDoc::Markup::Rule.new(1) + end + + ## + # Looks up and displays ri data according to the options given. + + def run + if @list_doc_dirs then + puts @doc_dirs + elsif @list then + list_known_classes @names + elsif @server then + start_server + elsif @interactive or @names.empty? then + interactive + else + display_names @names + end + rescue NotFoundError => e + abort e.message + end + + ## + # Sets up a pager program to pass output through. Tries the RI_PAGER and + # PAGER environment variables followed by pager, less then more. + + def setup_pager + return if @use_stdout + + jruby = RUBY_ENGINE == 'jruby' + + pagers = [ENV['RI_PAGER'], ENV['PAGER'], 'pager', 'less', 'more'] + + pagers.compact.uniq.each do |pager| + next unless pager + + pager_cmd = pager.split.first + + next unless in_path? pager_cmd + + if jruby then + case io = find_pager_jruby(pager) + when nil then break + when false then next + else io + end + else + io = IO.popen(pager, 'w') rescue next + end + + next if $? and $?.pid == io.pid and $?.exited? # pager didn't work + + @paging = true + + return io + end + + @use_stdout = true + + nil + end + + ## + # Starts a WEBrick server for ri. + + def start_server + require 'webrick' + + server = WEBrick::HTTPServer.new :Port => @server + + extra_doc_dirs = @stores.map {|s| s.type == :extra ? s.path : nil}.compact + + server.mount '/', RDoc::Servlet, nil, extra_doc_dirs + + trap 'INT' do server.shutdown end + trap 'TERM' do server.shutdown end + + server.start + end + +end diff --git a/lib/rdoc/ri/formatter.rb b/lib/rdoc/ri/formatter.rb new file mode 100644 index 0000000000..832a101e6c --- /dev/null +++ b/lib/rdoc/ri/formatter.rb @@ -0,0 +1,6 @@ +# frozen_string_literal: true +## +# For RubyGems backwards compatibility + +module RDoc::RI::Formatter # :nodoc: +end diff --git a/lib/rdoc/ri/paths.rb b/lib/rdoc/ri/paths.rb new file mode 100644 index 0000000000..d41e610591 --- /dev/null +++ b/lib/rdoc/ri/paths.rb @@ -0,0 +1,185 @@ +# frozen_string_literal: true +require 'rdoc/ri' + +## +# The directories where ri data lives. Paths can be enumerated via ::each, or +# queried individually via ::system_dir, ::site_dir, ::home_dir and ::gem_dir. + +module RDoc::RI::Paths + + #:stopdoc: + require 'rbconfig' + + version = RbConfig::CONFIG['ruby_version'] + + BASE = if RbConfig::CONFIG.key? 'ridir' then + File.join RbConfig::CONFIG['ridir'], version + else + File.join RbConfig::CONFIG['datadir'], 'ri', version + end + + homedir = begin + File.expand_path('~') + rescue ArgumentError + end + + homedir ||= ENV['HOME'] || + ENV['USERPROFILE'] || ENV['HOMEPATH'] # for 1.8 compatibility + + HOMEDIR = if homedir then + File.join homedir, ".rdoc" + end + #:startdoc: + + ## + # Iterates over each selected path yielding the directory and type. + # + # Yielded types: + # :system:: Where Ruby's ri data is stored. Yielded when +system+ is + # true + # :site:: Where ri for installed libraries are stored. Yielded when + # +site+ is true. Normally no ri data is stored here. + # :home:: ~/.rdoc. Yielded when +home+ is true. + # :gem:: ri data for an installed gem. Yielded when +gems+ is true. + # :extra:: ri data directory from the command line. Yielded for each + # entry in +extra_dirs+ + + def self.each system = true, site = true, home = true, gems = :latest, *extra_dirs # :yields: directory, type + return enum_for __method__, system, site, home, gems, *extra_dirs unless + block_given? + + extra_dirs.each do |dir| + yield dir, :extra + end + + yield system_dir, :system if system + yield site_dir, :site if site + yield home_dir, :home if home and HOMEDIR + + gemdirs(gems).each do |dir| + yield dir, :gem + end if gems + + nil + end + + ## + # The ri directory for the gem with +gem_name+. + + def self.gem_dir name, version + req = Gem::Requirement.new "= #{version}" + + spec = Gem::Specification.find_by_name name, req + + File.join spec.doc_dir, 'ri' + end + + ## + # The latest installed gems' ri directories. +filter+ can be :all or + # :latest. + # + # A +filter+ :all includes all versions of gems and includes gems without + # ri documentation. + + def self.gemdirs filter = :latest + ri_paths = {} + + all = Gem::Specification.map do |spec| + [File.join(spec.doc_dir, 'ri'), spec.name, spec.version] + end + + if filter == :all then + gemdirs = [] + + all.group_by do |_, name, _| + name + end.sort_by do |group, _| + group + end.map do |group, items| + items.sort_by do |_, _, version| + version + end.reverse_each do |dir,| + gemdirs << dir + end + end + + return gemdirs + end + + all.each do |dir, name, ver| + next unless File.exist? dir + + if ri_paths[name].nil? or ver > ri_paths[name].first then + ri_paths[name] = [ver, name, dir] + end + end + + ri_paths.sort_by { |_, (_, name, _)| name }.map { |k, v| v.last } + rescue LoadError + [] + end + + ## + # The location of the rdoc data in the user's home directory. + # + # Like ::system, ri data in the user's home directory is rare and predates + # libraries distributed via RubyGems. ri data is rarely generated into this + # directory. + + def self.home_dir + HOMEDIR + end + + ## + # Returns existing directories from the selected documentation directories + # as an Array. + # + # See also ::each + + def self.path(system = true, site = true, home = true, gems = :latest, *extra_dirs) + path = raw_path system, site, home, gems, *extra_dirs + + path.select { |directory| File.directory? directory } + end + + ## + # Returns selected documentation directories including nonexistent + # directories. + # + # See also ::each + + def self.raw_path(system, site, home, gems, *extra_dirs) + path = [] + + each(system, site, home, gems, *extra_dirs) do |dir, type| + path << dir + end + + path.compact + end + + ## + # The location of ri data installed into the site dir. + # + # Historically this was available for documentation installed by Ruby + # libraries predating RubyGems. It is unlikely to contain any content for + # modern Ruby installations. + + def self.site_dir + File.join BASE, 'site' + end + + ## + # The location of the built-in ri data. + # + # This data is built automatically when `make` is run when Ruby is + # installed. If you did not install Ruby by hand you may need to install + # the documentation yourself. Please consult the documentation for your + # package manager or Ruby installer for details. You can also use the + # rdoc-data gem to install system ri data for common versions of Ruby. + + def self.system_dir + File.join BASE, 'system' + end + +end diff --git a/lib/rdoc/ri/ri_cache.rb b/lib/rdoc/ri/ri_cache.rb deleted file mode 100644 index 1844ac969e..0000000000 --- a/lib/rdoc/ri/ri_cache.rb +++ /dev/null @@ -1,187 +0,0 @@ -module RI - - class ClassEntry - - attr_reader :name - attr_reader :path_names - - def initialize(path_name, name, in_class) - @path_names = [ path_name ] - @name = name - @in_class = in_class - @class_methods = [] - @instance_methods = [] - @inferior_classes = [] - end - - # We found this class in more tha one place, so add - # in the name from there. - def add_path(path) - @path_names << path - end - - # read in our methods and any classes - # and modules in our namespace. Methods are - # stored in files called name-c|i.yaml, - # where the 'name' portion is the external - # form of the method name and the c|i is a class|instance - # flag - - def load_from(dir) - Dir.foreach(dir) do |name| - next if name =~ /^\./ - - # convert from external to internal form, and - # extract the instance/class flag - - if name =~ /^(.*?)-(c|i).yaml$/ - external_name = $1 - is_class_method = $2 == "c" - internal_name = RiWriter.external_to_internal(external_name) - list = is_class_method ? @class_methods : @instance_methods - path = File.join(dir, name) - list << MethodEntry.new(path, internal_name, is_class_method, self) - else - full_name = File.join(dir, name) - if File.directory?(full_name) - inf_class = @inferior_classes.find {|c| c.name == name } - if inf_class - inf_class.add_path(full_name) - else - inf_class = ClassEntry.new(full_name, name, self) - @inferior_classes << inf_class - end - inf_class.load_from(full_name) - end - end - end - end - - # Return a list of any classes or modules that we contain - # that match a given string - - def contained_modules_matching(name) - @inferior_classes.find_all {|c| c.name[name]} - end - - def classes_and_modules - @inferior_classes - end - - # Return an exact match to a particular name - def contained_class_named(name) - @inferior_classes.find {|c| c.name == name} - end - - # return the list of local methods matching name - # We're split into two because we need distinct behavior - # when called from the _toplevel_ - def methods_matching(name, is_class_method) - local_methods_matching(name, is_class_method) - end - - # Find methods matching 'name' in ourselves and in - # any classes we contain - def recursively_find_methods_matching(name, is_class_method) - res = local_methods_matching(name, is_class_method) - @inferior_classes.each do |c| - res.concat(c.recursively_find_methods_matching(name, is_class_method)) - end - res - end - - - # Return our full name - def full_name - res = @in_class.full_name - res << "::" unless res.empty? - res << @name - end - - # Return a list of all out method names - def all_method_names - res = @class_methods.map {|m| m.full_name } - @instance_methods.each {|m| res << m.full_name} - res - end - - private - - # Return a list of all our methods matching a given string. - # Is +is_class_methods+ if 'nil', we don't care if the method - # is a class method or not, otherwise we only return - # those methods that match - def local_methods_matching(name, is_class_method) - - list = case is_class_method - when nil then @class_methods + @instance_methods - when true then @class_methods - when false then @instance_methods - else fail "Unknown is_class_method: #{is_class_method.inspect}" - end - - list.find_all {|m| m.name; m.name[name]} - end - end - - # A TopLevelEntry is like a class entry, but when asked to search - # for methods searches all classes, not just itself - - class TopLevelEntry < ClassEntry - def methods_matching(name, is_class_method) - res = recursively_find_methods_matching(name, is_class_method) - end - - def full_name - "" - end - - def module_named(name) - - end - - end - - class MethodEntry - attr_reader :name - attr_reader :path_name - - def initialize(path_name, name, is_class_method, in_class) - @path_name = path_name - @name = name - @is_class_method = is_class_method - @in_class = in_class - end - - def full_name - res = @in_class.full_name - unless res.empty? - if @is_class_method - res << "::" - else - res << "#" - end - end - res << @name - end - end - - # We represent everything know about all 'ri' files - # accessible to this program - - class RiCache - - attr_reader :toplevel - - def initialize(dirs) - # At the top level we have a dummy module holding the - # overall namespace - @toplevel = TopLevelEntry.new('', '::', nil) - - dirs.each do |dir| - @toplevel.load_from(dir) - end - end - - end -end diff --git a/lib/rdoc/ri/ri_descriptions.rb b/lib/rdoc/ri/ri_descriptions.rb deleted file mode 100644 index e5ea9f2fbf..0000000000 --- a/lib/rdoc/ri/ri_descriptions.rb +++ /dev/null @@ -1,154 +0,0 @@ -require 'yaml' -require 'rdoc/markup/simple_markup/fragments' - -# Descriptions are created by RDoc (in ri_generator) and -# written out in serialized form into the documentation -# tree. ri then reads these to generate the documentation - -module RI - class NamedThing - attr_reader :name - def initialize(name) - @name = name - end - def <=>(other) - @name <=> other.name - end - - def hash - @name.hash - end - - def eql?(other) - @name.eql?(other) - end - end - -# Alias = Struct.new(:old_name, :new_name) - - class AliasName < NamedThing - end - - class Attribute < NamedThing - attr_reader :rw, :comment - def initialize(name, rw, comment) - super(name) - @rw = rw - @comment = comment - end - end - - class Constant < NamedThing - attr_reader :value, :comment - def initialize(name, value, comment) - super(name) - @value = value - @comment = comment - end - end - - class IncludedModule < NamedThing - end - - - class MethodSummary < NamedThing - def initialize(name="") - super - end - end - - - - class Description - attr_accessor :name - attr_accessor :full_name - attr_accessor :comment - - def serialize - self.to_yaml - end - - def Description.deserialize(from) - YAML.load(from) - end - - def <=>(other) - @name <=> other.name - end - end - - class ModuleDescription < Description - - attr_accessor :class_methods - attr_accessor :instance_methods - attr_accessor :attributes - attr_accessor :constants - attr_accessor :includes - - # merge in another class desscription into this one - def merge_in(old) - merge(@class_methods, old.class_methods) - merge(@instance_methods, old.instance_methods) - merge(@attributes, old.attributes) - merge(@constants, old.constants) - merge(@includes, old.includes) - if @comment.nil? || @comment.empty? - @comment = old.comment - else - unless old.comment.nil? or old.comment.empty? then - @comment << SM::Flow::RULE.new - @comment.concat old.comment - end - end - end - - def display_name - "Module" - end - - # the 'ClassDescription' subclass overrides this - # to format up the name of a parent - def superclass_string - nil - end - - private - - def merge(into, from) - names = {} - into.each {|i| names[i.name] = i } - from.each {|i| names[i.name] = i } - into.replace(names.keys.sort.map {|n| names[n]}) - end - end - - class ClassDescription < ModuleDescription - attr_accessor :superclass - - def display_name - "Class" - end - - def superclass_string - if @superclass && @superclass != "Object" - @superclass - else - nil - end - end - end - - - class MethodDescription < Description - - attr_accessor :is_class_method - attr_accessor :visibility - attr_accessor :block_params - attr_accessor :is_singleton - attr_accessor :aliases - attr_accessor :is_alias_for - attr_accessor :params - - end - -end diff --git a/lib/rdoc/ri/ri_display.rb b/lib/rdoc/ri/ri_display.rb deleted file mode 100644 index 67962fc2c1..0000000000 --- a/lib/rdoc/ri/ri_display.rb +++ /dev/null @@ -1,255 +0,0 @@ -require 'rdoc/ri/ri_util' -require 'rdoc/ri/ri_formatter' -require 'rdoc/ri/ri_options' - - -# This is a kind of 'flag' module. If you want to write your -# own 'ri' display module (perhaps because you'r writing -# an IDE or somesuch beast), you simply write a class -# which implements the various 'display' methods in 'DefaultDisplay', -# and include the 'RiDisplay' module in that class. -# -# To access your class from the command line, you can do -# -# ruby -r <your source file> ../ri .... -# -# If folks _really_ want to do this from the command line, -# I'll build an option in - -module RiDisplay - @@display_class = nil - - def RiDisplay.append_features(display_class) - @@display_class = display_class - end - - def RiDisplay.new(*args) - @@display_class.new(*args) - end -end - -###################################################################### -# -# A paging display module. Uses the ri_formatter class to do the -# actual presentation -# - -class DefaultDisplay - - include RiDisplay - - def initialize(options) - @options = options - @formatter = @options.formatter.new(@options, " ") - end - - - ###################################################################### - - def display_usage - page do - RI::Options::OptionList.usage(short_form=true) - end - end - - - ###################################################################### - - def display_method_info(method) - page do - @formatter.draw_line(method.full_name) - display_params(method) - @formatter.draw_line - display_flow(method.comment) - if method.aliases && !method.aliases.empty? - @formatter.blankline - aka = "(also known as " - aka << method.aliases.map {|a| a.name }.join(", ") - aka << ")" - @formatter.wrap(aka) - end - end - end - - ###################################################################### - - def display_class_info(klass, ri_reader) - page do - superclass = klass.superclass_string - - if superclass - superclass = " < " + superclass - else - superclass = "" - end - - @formatter.draw_line(klass.display_name + ": " + - klass.full_name + superclass) - - display_flow(klass.comment) - @formatter.draw_line - - unless klass.includes.empty? - @formatter.blankline - @formatter.display_heading("Includes:", 2, "") - incs = [] - klass.includes.each do |inc| - inc_desc = ri_reader.find_class_by_name(inc.name) - if inc_desc - str = inc.name + "(" - str << inc_desc.instance_methods.map{|m| m.name}.join(", ") - str << ")" - incs << str - else - incs << inc.name - end - end - @formatter.wrap(incs.sort.join(', ')) - end - - unless klass.constants.empty? - @formatter.blankline - @formatter.display_heading("Constants:", 2, "") - len = 0 - klass.constants.each { |c| len = c.name.length if c.name.length > len } - len += 2 - klass.constants.each do |c| - @formatter.wrap(c.value, - @formatter.indent+((c.name+":").ljust(len))) - end - end - - unless klass.class_methods.empty? - @formatter.blankline - @formatter.display_heading("Class methods:", 2, "") - @formatter.wrap(klass.class_methods.map{|m| m.name}.sort.join(', ')) - end - - unless klass.instance_methods.empty? - @formatter.blankline - @formatter.display_heading("Instance methods:", 2, "") - @formatter.wrap(klass.instance_methods.map{|m| m.name}.sort.join(', ')) - end - - unless klass.attributes.empty? - @formatter.blankline - @formatter.wrap("Attributes:", "") - @formatter.wrap(klass.attributes.map{|a| a.name}.sort.join(', ')) - end - end - end - - ###################################################################### - - # Display a list of method names - - def display_method_list(methods) - page do - puts "More than one method matched your request. You can refine" - puts "your search by asking for information on one of:\n\n" - @formatter.wrap(methods.map {|m| m.full_name} .join(", ")) - end - end - - ###################################################################### - - def display_class_list(namespaces) - page do - puts "More than one class or module matched your request. You can refine" - puts "your search by asking for information on one of:\n\n" - @formatter.wrap(namespaces.map {|m| m.full_name}.join(", ")) - end - end - - ###################################################################### - - def list_known_classes(classes) - if classes.empty? - warn_no_database - else - page do - @formatter.draw_line("Known classes and modules") - @formatter.blankline - @formatter.wrap(classes.sort.join(", ")) - end - end - end - - ###################################################################### - - def list_known_names(names) - if names.empty? - warn_no_database - else - page do - names.each {|n| @formatter.raw_print_line(n)} - end - end - end - - ###################################################################### - - private - - ###################################################################### - - def page - return yield unless pager = setup_pager - begin - save_stdout = STDOUT.clone - STDOUT.reopen(pager) - yield - ensure - STDOUT.reopen(save_stdout) - save_stdout.close - pager.close - end - end - - ###################################################################### - - def setup_pager - unless @options.use_stdout - for pager in [ ENV['PAGER'], "less", "more", 'pager' ].compact.uniq - return IO.popen(pager, "w") rescue nil - end - @options.use_stdout = true - nil - end - end - - ###################################################################### - - def display_params(method) - - params = method.params - - if params[0,1] == "(" - if method.is_singleton - params = method.full_name + params - else - params = method.name + params - end - end - params.split(/\n/).each do |p| - @formatter.wrap(p) - @formatter.break_to_newline - end - end - ###################################################################### - - def display_flow(flow) - if !flow || flow.empty? - @formatter.wrap("(no description...)") - else - @formatter.display_flow(flow) - end - end - - ###################################################################### - - def warn_no_database - puts "Before using ri, you need to generate documentation" - puts "using 'rdoc' with the --ri option" - end -end # class RiDisplay diff --git a/lib/rdoc/ri/ri_driver.rb b/lib/rdoc/ri/ri_driver.rb deleted file mode 100644 index a00f20ee3b..0000000000 --- a/lib/rdoc/ri/ri_driver.rb +++ /dev/null @@ -1,143 +0,0 @@ -require 'rdoc/ri/ri_paths' -require 'rdoc/usage' -require 'rdoc/ri/ri_cache' -require 'rdoc/ri/ri_util' -require 'rdoc/ri/ri_reader' -require 'rdoc/ri/ri_formatter' -require 'rdoc/ri/ri_options' - - -###################################################################### - -class RiDriver - - def initialize - @options = RI::Options.instance - - args = ARGV - if ENV["RI"] - args = ENV["RI"].split.concat(ARGV) - end - - @options.parse(args) - - path = @options.path - report_missing_documentation @options.raw_path if path.empty? - - @ri_reader = RI::RiReader.new(RI::RiCache.new(path)) - @display = @options.displayer - end - - # Couldn't find documentation in +path+, so tell the user what to do - - def report_missing_documentation(path) - STDERR.puts "No ri documentation found in:" - path.each do |d| - STDERR.puts " #{d}" - end - STDERR.puts "\nWas rdoc run to create documentation?\n\n" - RDoc::usage("Installing Documentation") - end - - ###################################################################### - - # If the list of matching methods contains exactly one entry, or - # if it contains an entry that exactly matches the requested method, - # then display that entry, otherwise display the list of - # matching method names - - def report_method_stuff(requested_method_name, methods) - if methods.size == 1 - method = @ri_reader.get_method(methods[0]) - @display.display_method_info(method) - else - entries = methods.find_all {|m| m.name == requested_method_name} - if entries.size == 1 - method = @ri_reader.get_method(entries[0]) - @display.display_method_info(method) - else - @display.display_method_list(methods) - end - end - end - - ###################################################################### - - def report_class_stuff(namespaces) - if namespaces.size == 1 - klass = @ri_reader.get_class(namespaces[0]) - @display.display_class_info(klass, @ri_reader) - else -# entries = namespaces.find_all {|m| m.full_name == requested_class_name} -# if entries.size == 1 -# klass = @ri_reader.get_class(entries[0]) -# @display.display_class_info(klass, @ri_reader) -# else - @display.display_class_list(namespaces) -# end - end - end - - ###################################################################### - - - def get_info_for(arg) - desc = NameDescriptor.new(arg) - - namespaces = @ri_reader.top_level_namespace - - for class_name in desc.class_names - namespaces = @ri_reader.lookup_namespace_in(class_name, namespaces) - if namespaces.empty? - raise RiError.new("Nothing known about #{arg}") - end - end - - # at this point, if we have multiple possible namespaces, but one - # is an exact match for our requested class, prune down to just it - - full_class_name = desc.full_class_name - entries = namespaces.find_all {|m| m.full_name == full_class_name} - namespaces = entries if entries.size == 1 - - if desc.method_name.nil? - report_class_stuff(namespaces) - else - methods = @ri_reader.find_methods(desc.method_name, - desc.is_class_method, - namespaces) - - if methods.empty? - raise RiError.new("Nothing known about #{arg}") - else - report_method_stuff(desc.method_name, methods) - end - end - end - - ###################################################################### - - def process_args - if @options.list_classes - classes = @ri_reader.full_class_names - @display.list_known_classes(classes) - elsif @options.list_names - names = @ri_reader.all_names - @display.list_known_names(names) - else - if ARGV.size.zero? - @display.display_usage - else - begin - ARGV.each do |arg| - get_info_for(arg) - end - rescue RiError => e - STDERR.puts(e.message) - exit(1) - end - end - end - end - -end # class RiDriver diff --git a/lib/rdoc/ri/ri_formatter.rb b/lib/rdoc/ri/ri_formatter.rb deleted file mode 100644 index 56a1fb4665..0000000000 --- a/lib/rdoc/ri/ri_formatter.rb +++ /dev/null @@ -1,674 +0,0 @@ -module RI - class TextFormatter - - attr_reader :indent - - def initialize(options, indent) - @options = options - @width = options.width - @indent = indent - end - - - ###################################################################### - - def draw_line(label=nil) - len = @width - len -= (label.size+1) if label - print "-"*len - if label - print(" ") - bold_print(label) - end - puts - end - - ###################################################################### - - def wrap(txt, prefix=@indent, linelen=@width) - return unless txt && !txt.empty? - work = conv_markup(txt) - textLen = linelen - prefix.length - patt = Regexp.new("^(.{0,#{textLen}})[ \n]") - next_prefix = prefix.tr("^ ", " ") - - res = [] - - while work.length > textLen - if work =~ patt - res << $1 - work.slice!(0, $&.length) - else - res << work.slice!(0, textLen) - end - end - res << work if work.length.nonzero? - puts(prefix + res.join("\n" + next_prefix)) - end - - ###################################################################### - - def blankline - puts - end - - ###################################################################### - - # called when we want to ensure a nbew 'wrap' starts on a newline - # Only needed for HtmlFormatter, because the rest do their - # own line breaking - - def break_to_newline - end - - ###################################################################### - - def bold_print(txt) - print txt - end - - ###################################################################### - - def raw_print_line(txt) - puts txt - end - - ###################################################################### - - # convert HTML entities back to ASCII - def conv_html(txt) - txt. - gsub(/>/, '>'). - gsub(/</, '<'). - gsub(/"/, '"'). - gsub(/&/, '&') - - end - - # convert markup into display form - def conv_markup(txt) - txt. - gsub(%r{<tt>(.*?)</tt>}) { "+#$1+" } . - gsub(%r{<code>(.*?)</code>}) { "+#$1+" } . - gsub(%r{<b>(.*?)</b>}) { "*#$1*" } . - gsub(%r{<em>(.*?)</em>}) { "_#$1_" } - end - - ###################################################################### - - def display_list(list) - case list.type - - when SM::ListBase::BULLET - prefixer = proc { |ignored| @indent + "* " } - - when SM::ListBase::NUMBER, - SM::ListBase::UPPERALPHA, - SM::ListBase::LOWERALPHA - - start = case list.type - when SM::ListBase::NUMBER then 1 - when SM::ListBase::UPPERALPHA then 'A' - when SM::ListBase::LOWERALPHA then 'a' - end - prefixer = proc do |ignored| - res = @indent + "#{start}.".ljust(4) - start = start.succ - res - end - - when SM::ListBase::LABELED - prefixer = proc do |li| - li.label - end - - when SM::ListBase::NOTE - longest = 0 - list.contents.each do |item| - if item.kind_of?(SM::Flow::LI) && item.label.length > longest - longest = item.label.length - end - end - - prefixer = proc do |li| - @indent + li.label.ljust(longest+1) - end - - else - fail "unknown list type" - - end - - list.contents.each do |item| - if item.kind_of? SM::Flow::LI - prefix = prefixer.call(item) - display_flow_item(item, prefix) - else - display_flow_item(item) - end - end - end - - ###################################################################### - - def display_flow_item(item, prefix=@indent) - case item - when SM::Flow::P, SM::Flow::LI - wrap(conv_html(item.body), prefix) - blankline - - when SM::Flow::LIST - display_list(item) - - when SM::Flow::VERB - display_verbatim_flow_item(item, @indent) - - when SM::Flow::H - display_heading(conv_html(item.text), item.level, @indent) - - when SM::Flow::RULE - draw_line - - else - fail "Unknown flow element: #{item.class}" - end - end - - ###################################################################### - - def display_verbatim_flow_item(item, prefix=@indent) - item.body.split(/\n/).each do |line| - print @indent, conv_html(line), "\n" - end - blankline - end - - ###################################################################### - - def display_heading(text, level, indent) - text = strip_attributes(text) - case level - when 1 - ul = "=" * text.length - puts - puts text.upcase - puts ul -# puts - - when 2 - ul = "-" * text.length - puts - puts text - puts ul -# puts - else - print indent, text, "\n" - end - end - - - def display_flow(flow) - flow.each do |f| - display_flow_item(f) - end - end - - def strip_attributes(txt) - tokens = txt.split(%r{(</?(?:b|code|em|i|tt)>)}) - text = [] - attributes = 0 - tokens.each do |tok| - case tok - when %r{^</(\w+)>$}, %r{^<(\w+)>$} - ; - else - text << tok - end - end - text.join - end - - - end - - - ###################################################################### - # Handle text with attributes. We're a base class: there are - # different presentation classes (one, for example, uses overstrikes - # to handle bold and underlining, while another using ANSI escape - # sequences - - class AttributeFormatter < TextFormatter - - BOLD = 1 - ITALIC = 2 - CODE = 4 - - ATTR_MAP = { - "b" => BOLD, - "code" => CODE, - "em" => ITALIC, - "i" => ITALIC, - "tt" => CODE - } - - # TODO: struct? - class AttrChar - attr_reader :char - attr_reader :attr - - def initialize(char, attr) - @char = char - @attr = attr - end - end - - - class AttributeString - attr_reader :txt - - def initialize - @txt = [] - @optr = 0 - end - - def <<(char) - @txt << char - end - - def empty? - @optr >= @txt.length - end - - # accept non space, then all following spaces - def next_word - start = @optr - len = @txt.length - - while @optr < len && @txt[@optr].char != " " - @optr += 1 - end - - while @optr < len && @txt[@optr].char == " " - @optr += 1 - end - - @txt[start...@optr] - end - end - - ###################################################################### - # overrides base class. Looks for <tt>...</tt> etc sequences - # and generates an array of AttrChars. This array is then used - # as the basis for the split - - def wrap(txt, prefix=@indent, linelen=@width) - return unless txt && !txt.empty? - - txt = add_attributes_to(txt) - next_prefix = prefix.tr("^ ", " ") - linelen -= prefix.size - - line = [] - - until txt.empty? - word = txt.next_word - if word.size + line.size > linelen - write_attribute_text(prefix, line) - prefix = next_prefix - line = [] - end - line.concat(word) - end - - write_attribute_text(prefix, line) if line.length > 0 - end - - protected - - # overridden in specific formatters - - def write_attribute_text(prefix, line) - print prefix - line.each do |achar| - print achar.char - end - puts - end - - # again, overridden - - def bold_print(txt) - print txt - end - - private - - def add_attributes_to(txt) - tokens = txt.split(%r{(</?(?:b|code|em|i|tt)>)}) - text = AttributeString.new - attributes = 0 - tokens.each do |tok| - case tok - when %r{^</(\w+)>$} then attributes &= ~(ATTR_MAP[$1]||0) - when %r{^<(\w+)>$} then attributes |= (ATTR_MAP[$1]||0) - else - tok.split(//).each {|ch| text << AttrChar.new(ch, attributes)} - end - end - text - end - - end - - - ################################################## - - # This formatter generates overstrike-style formatting, which - # works with pagers such as man and less. - - class OverstrikeFormatter < AttributeFormatter - - BS = "\C-h" - - def write_attribute_text(prefix, line) - print prefix - line.each do |achar| - attr = achar.attr - if (attr & (ITALIC+CODE)) != 0 - print "_", BS - end - if (attr & BOLD) != 0 - print achar.char, BS - end - print achar.char - end - puts - end - - # draw a string in bold - def bold_print(text) - text.split(//).each do |ch| - print ch, BS, ch - end - end - end - - ################################################## - - # This formatter uses ANSI escape sequences - # to colorize stuff - # works with pages such as man and less. - - class AnsiFormatter < AttributeFormatter - - def initialize(*args) - print "\033[0m" - super - end - - def write_attribute_text(prefix, line) - print prefix - curr_attr = 0 - line.each do |achar| - attr = achar.attr - if achar.attr != curr_attr - update_attributes(achar.attr) - curr_attr = achar.attr - end - print achar.char - end - update_attributes(0) unless curr_attr.zero? - puts - end - - - def bold_print(txt) - print "\033[1m#{txt}\033[m" - end - - HEADINGS = { - 1 => [ "\033[1;32m", "\033[m" ] , - 2 => ["\033[4;32m", "\033[m" ], - 3 => ["\033[32m", "\033[m" ] - } - - def display_heading(text, level, indent) - level = 3 if level > 3 - heading = HEADINGS[level] - print indent - print heading[0] - print strip_attributes(text) - puts heading[1] - end - - private - - ATTR_MAP = { - BOLD => "1", - ITALIC => "33", - CODE => "36" - } - - def update_attributes(attr) - str = "\033[" - for quality in [ BOLD, ITALIC, CODE] - unless (attr & quality).zero? - str << ATTR_MAP[quality] - end - end - print str, "m" - end - end - - ################################################## - - # This formatter uses HTML. - - class HtmlFormatter < AttributeFormatter - - def initialize(*args) - super - end - - def write_attribute_text(prefix, line) - curr_attr = 0 - line.each do |achar| - attr = achar.attr - if achar.attr != curr_attr - update_attributes(curr_attr, achar.attr) - curr_attr = achar.attr - end - print(escape(achar.char)) - end - update_attributes(curr_attr, 0) unless curr_attr.zero? - end - - def draw_line(label=nil) - if label != nil - bold_print(label) - end - puts("<hr>") - end - - def bold_print(txt) - tag("b") { txt } - end - - def blankline() - puts("<p>") - end - - def break_to_newline - puts("<br>") - end - - def display_heading(text, level, indent) - level = 4 if level > 4 - tag("h#{level}") { text } - puts - end - - ###################################################################### - - def display_list(list) - - case list.type - when SM::ListBase::BULLET - list_type = "ul" - prefixer = proc { |ignored| "<li>" } - - when SM::ListBase::NUMBER, - SM::ListBase::UPPERALPHA, - SM::ListBase::LOWERALPHA - list_type = "ol" - prefixer = proc { |ignored| "<li>" } - - when SM::ListBase::LABELED - list_type = "dl" - prefixer = proc do |li| - "<dt><b>" + escape(li.label) + "</b><dd>" - end - - when SM::ListBase::NOTE - list_type = "table" - prefixer = proc do |li| - %{<tr valign="top"><td>#{li.label.gsub(/ /, ' ')}</td><td>} - end - else - fail "unknown list type" - end - - print "<#{list_type}>" - list.contents.each do |item| - if item.kind_of? SM::Flow::LI - prefix = prefixer.call(item) - print prefix - display_flow_item(item, prefix) - else - display_flow_item(item) - end - end - print "</#{list_type}>" - end - - def display_verbatim_flow_item(item, prefix=@indent) - print("<pre>") - item.body.split(/\n/).each do |line| - puts conv_html(line) - end - puts("</pre>") - end - - private - - ATTR_MAP = { - BOLD => "b>", - ITALIC => "i>", - CODE => "tt>" - } - - def update_attributes(current, wanted) - str = "" - # first turn off unwanted ones - off = current & ~wanted - for quality in [ BOLD, ITALIC, CODE] - if (off & quality) > 0 - str << "</" + ATTR_MAP[quality] - end - end - - # now turn on wanted - for quality in [ BOLD, ITALIC, CODE] - unless (wanted & quality).zero? - str << "<" << ATTR_MAP[quality] - end - end - print str - end - - def tag(code) - print("<#{code}>") - print(yield) - print("</#{code}>") - end - - def escape(str) - str. - gsub(/&/n, '&'). - gsub(/\"/n, '"'). - gsub(/>/n, '>'). - gsub(/</n, '<') - end - - end - - ################################################## - - # This formatter reduces extra lines for a simpler output. - # It improves way output looks for tools like IRC bots. - - class SimpleFormatter < TextFormatter - - ###################################################################### - - # No extra blank lines - - def blankline - end - - ###################################################################### - - # Display labels only, no lines - - def draw_line(label=nil) - unless label.nil? then - bold_print(label) - puts - end - end - - ###################################################################### - - # Place heading level indicators inline with heading. - - def display_heading(text, level, indent) - text = strip_attributes(text) - case level - when 1 - puts "= " + text.upcase - when 2 - puts "-- " + text - else - print indent, text, "\n" - end - end - - end - - - # Finally, fill in the list of known formatters - - class TextFormatter - - FORMATTERS = { - "ansi" => AnsiFormatter, - "bs" => OverstrikeFormatter, - "html" => HtmlFormatter, - "plain" => TextFormatter, - "simple" => SimpleFormatter, - } - - def TextFormatter.list - FORMATTERS.keys.sort.join(", ") - end - - def TextFormatter.for(name) - FORMATTERS[name.downcase] - end - - end - -end - - diff --git a/lib/rdoc/ri/ri_options.rb b/lib/rdoc/ri/ri_options.rb deleted file mode 100644 index db9f4afecf..0000000000 --- a/lib/rdoc/ri/ri_options.rb +++ /dev/null @@ -1,313 +0,0 @@ -# We handle the parsing of options, and subsequently as a singleton -# object to be queried for option values - -module RI - - require 'rdoc/ri/ri_paths' - require 'rdoc/ri/ri_display' - - VERSION_STRING = "ri v1.0.1 - 20041108" - - class Options - - require 'singleton' - require 'getoptlong' - - include Singleton - - # No not use a pager. Writable, because ri sets it if it - # can't find a pager - attr_accessor :use_stdout - - # should we just display a class list and exit - attr_reader :list_classes - - # should we display a list of all names - attr_reader :list_names - - # The width of the output line - attr_reader :width - - # the formatting we apply to the output - attr_reader :formatter - - # the directory we search for original documentation - attr_reader :doc_dir - - module OptionList - - OPTION_LIST = [ - [ "--help", "-h", nil, - "you're looking at it" ], - - [ "--classes", "-c", nil, - "Display the names of classes and modules we\n" + - "know about"], - - [ "--doc-dir", "-d", "<dirname>", - "A directory to search for documentation. If not\n" + - "specified, we search the standard rdoc/ri directories.\n" + - "May be repeated."], - - [ "--system", nil, nil, - "Include documentation from Ruby's standard library:\n " + - RI::Paths::SYSDIR ], - - [ "--site", nil, nil, - "Include documentation from libraries installed in site_lib:\n " + - RI::Paths::SITEDIR ], - - [ "--home", nil, nil, - "Include documentation stored in ~/.rdoc:\n " + - (RI::Paths::HOMEDIR || "No ~/.rdoc found") ], - - [ "--gems", nil, nil, - "Include documentation from Rubygems:\n " + - (RI::Paths::GEMDIRS ? "#{Gem.path}/doc/*/ri" : - "No Rubygems ri found.") ], - - [ "--format", "-f", "<name>", - "Format to use when displaying output:\n" + - " " + RI::TextFormatter.list + "\n" + - "Use 'bs' (backspace) with most pager programs.\n" + - "To use ANSI, either also use the -T option, or\n" + - "tell your pager to allow control characters\n" + - "(for example using the -R option to less)"], - - [ "--list-names", "-l", nil, - "List all the names known to RDoc, one per line" - ], - - [ "--no-pager", "-T", nil, - "Send output directly to stdout." - ], - - [ "--width", "-w", "output width", - "Set the width of the output" ], - - [ "--version", "-v", nil, - "Display the version of ri" - ], - - ] - - def OptionList.options - OPTION_LIST.map do |long, short, arg,| - option = [] - option << long - option << short unless short.nil? - option << (arg ? GetoptLong::REQUIRED_ARGUMENT : - GetoptLong::NO_ARGUMENT) - option - end - end - - - def OptionList.strip_output(text) - text =~ /^\s+/ - leading_spaces = $& - text.gsub!(/^#{leading_spaces}/, '') - $stdout.puts text - end - - - # Show an error and exit - - def OptionList.error(msg) - $stderr.puts - $stderr.puts msg - $stderr.puts "\nFor help on options, try 'ri --help'\n\n" - exit 1 - end - - # Show usage and exit - - def OptionList.usage(short_form=false) - - puts - puts(RI::VERSION_STRING) - puts - - name = File.basename($0) - - directories = [ - RI::Paths::SYSDIR, - RI::Paths::SITEDIR, - RI::Paths::HOMEDIR - ] - - directories << "#{Gem.path}/doc/*/ri" if RI::Paths::GEMDIRS - - directories = directories.join("\n ") - - OptionList.strip_output(<<-EOT) - Usage: - - #{name} [options] [names...] - - Display information on Ruby classes, modules, and methods. - Give the names of classes or methods to see their documentation. - Partial names may be given: if the names match more than - one entity, a list will be shown, otherwise details on - that entity will be displayed. - - Nested classes and modules can be specified using the normal - Name::Name notation, and instance methods can be distinguished - from class methods using "." (or "#") instead of "::". - - For example: - - ri File - ri File.new - ri F.n - ri zip - - Note that shell quoting may be required for method names - containing punctuation: - - ri 'Array.[]' - ri compact\\! - - By default ri searches for documentation in the following - directories: - - #{directories} - - Specifying the --system, --site, --home, --gems or --doc-dir - options will limit ri to searching only the specified - directories. - - EOT - - if short_form - puts "For help on options, type 'ri -h'" - puts "For a list of classes I know about, type 'ri -c'" - else - puts "Options:\n\n" - OPTION_LIST.each do|long, short, arg, desc| - opt = '' - opt << (short ? sprintf("%15s", "#{long}, #{short}") : - sprintf("%15s", long)) - if arg - opt << " " << arg - end - print opt - desc = desc.split("\n") - if opt.size < 17 - print " "*(18-opt.size) - puts desc.shift - else - puts - end - desc.each do |line| - puts(" "*18 + line) - end - puts - end - puts "Options may also be passed in the 'RI' environment variable" - exit 0 - end - end - end - - # Show the version and exit - def show_version - puts VERSION_STRING - exit(0) - end - - def initialize - @use_stdout = !STDOUT.tty? - @width = 72 - @formatter = RI::TextFormatter.for("plain") - @list_classes = false - @list_names = false - - # By default all paths are used. If any of these are true, only those - # directories are used. - @use_system = false - @use_site = false - @use_home = false - @use_gems = false - @doc_dirs = [] - end - - # Parse command line options. - - def parse(args) - - old_argv = ARGV.dup - - ARGV.replace(args) - - begin - - go = GetoptLong.new(*OptionList.options) - go.quiet = true - - go.each do |opt, arg| - case opt - when "--help" then OptionList.usage - when "--version" then show_version - when "--list-names" then @list_names = true - when "--no-pager" then @use_stdout = true - when "--classes" then @list_classes = true - - when "--system" then @use_system = true - when "--site" then @use_site = true - when "--home" then @use_home = true - when "--gems" then @use_gems = true - - when "--doc-dir" - if File.directory?(arg) - @doc_dirs << arg - else - $stderr.puts "Invalid directory: #{arg}" - exit 1 - end - - when "--format" - @formatter = RI::TextFormatter.for(arg) - unless @formatter - $stderr.print "Invalid formatter (should be one of " - $stderr.puts RI::TextFormatter.list + ")" - exit 1 - end - when "--width" - begin - @width = Integer(arg) - rescue - $stderr.puts "Invalid width: '#{arg}'" - exit 1 - end - end - end - - rescue GetoptLong::InvalidOption, GetoptLong::MissingArgument => error - OptionList.error(error.message) - - end - end - - # Return the selected documentation directories. - - def path - RI::Paths.path(@use_system, @use_site, @use_home, @use_gems, *@doc_dirs) - end - - def raw_path - RI::Paths.raw_path(@use_system, @use_site, @use_home, @use_gems, - *@doc_dirs) - end - - # Return an instance of the displayer (the thing that actually writes - # the information). This allows us to load in new displayer classes - # at runtime (for example to help with IDE integration) - - def displayer - ::RiDisplay.new(self) - end - end - -end - diff --git a/lib/rdoc/ri/ri_paths.rb b/lib/rdoc/ri/ri_paths.rb deleted file mode 100644 index 32363bf70a..0000000000 --- a/lib/rdoc/ri/ri_paths.rb +++ /dev/null @@ -1,80 +0,0 @@ -module RI - - # Encapsulate all the strangeness to do with finding out - # where to find RDoc files - # - # We basically deal with three directories: - # - # 1. The 'system' documentation directory, which holds - # the documentation distributed with Ruby, and which - # is managed by the Ruby install process - # 2. The 'site' directory, which contains site-wide - # documentation added locally. - # 3. The 'user' documentation directory, stored under the - # user's own home directory. - # - # There's contention about all this, but for now: - # - # system:: $datadir/ri/<ver>/system/... - # site:: $datadir/ri/<ver>/site/... - # user:: ~/.rdoc - - module Paths - - #:stopdoc: - require 'rbconfig' - - DOC_DIR = "doc/rdoc" - - version = Config::CONFIG['ruby_version'] - - base = File.join(Config::CONFIG['datadir'], "ri", version) - SYSDIR = File.join(base, "system") - SITEDIR = File.join(base, "site") - homedir = ENV['HOME'] || ENV['USERPROFILE'] || ENV['HOMEPATH'] - - if homedir - HOMEDIR = File.join(homedir, ".rdoc") - else - HOMEDIR = nil - end - - # This is the search path for 'ri' - PATH = [ SYSDIR, SITEDIR, HOMEDIR ].find_all {|p| p && File.directory?(p)} - - begin - require 'rubygems' - GEMDIRS = Dir["#{Gem.path}/doc/*/ri"] - GEMDIRS.each { |path| RI::Paths::PATH << path } - rescue LoadError - GEMDIRS = nil - end - - # Returns the selected documentation directories as an Array, or PATH if no - # overriding directories were given. - - def self.path(use_system, use_site, use_home, use_gems, *extra_dirs) - path = raw_path(use_system, use_site, use_home, use_gems, *extra_dirs) - return path.select { |path| File.directory? path } - end - - # Returns the selected documentation directories including nonexistent - # directories. Used to print out what paths were searched if no ri was - # found. - - def self.raw_path(use_system, use_site, use_home, use_gems, *extra_dirs) - return PATH unless use_system or use_site or use_home or use_gems or - not extra_dirs.empty? - - path = [] - path << extra_dirs unless extra_dirs.empty? - path << RI::Paths::SYSDIR if use_system - path << RI::Paths::SITEDIR if use_site - path << RI::Paths::HOMEDIR if use_home - path << RI::Paths::GEMDIRS if use_gems - - return path.flatten.compact - end - - end -end diff --git a/lib/rdoc/ri/ri_reader.rb b/lib/rdoc/ri/ri_reader.rb deleted file mode 100644 index fb2c373e38..0000000000 --- a/lib/rdoc/ri/ri_reader.rb +++ /dev/null @@ -1,100 +0,0 @@ -require 'rdoc/ri/ri_descriptions' -require 'rdoc/ri/ri_writer' -require 'rdoc/markup/simple_markup/to_flow' - -module RI - class RiReader - - def initialize(ri_cache) - @cache = ri_cache - end - - def top_level_namespace - [ @cache.toplevel ] - end - - def lookup_namespace_in(target, namespaces) - result = [] - for n in namespaces - result.concat(n.contained_modules_matching(target)) - end - result - end - - def find_class_by_name(full_name) - names = full_name.split(/::/) - ns = @cache.toplevel - for name in names - ns = ns.contained_class_named(name) - return nil if ns.nil? - end - get_class(ns) - end - - def find_methods(name, is_class_method, namespaces) - result = [] - namespaces.each do |ns| - result.concat ns.methods_matching(name, is_class_method) - end - result - end - - # return the MethodDescription for a given MethodEntry - # by deserializing the YAML - def get_method(method_entry) - path = method_entry.path_name - File.open(path) { |f| RI::Description.deserialize(f) } - end - - # Return a class description - def get_class(class_entry) - result = nil - for path in class_entry.path_names - path = RiWriter.class_desc_path(path, class_entry) - desc = File.open(path) {|f| RI::Description.deserialize(f) } - if result - result.merge_in(desc) - else - result = desc - end - end - result - end - - # return the names of all classes and modules - def full_class_names - res = [] - find_classes_in(res, @cache.toplevel) - end - - # return a list of all classes, modules, and methods - def all_names - res = [] - find_names_in(res, @cache.toplevel) - end - - # ---- - private - # ---- - - def find_classes_in(res, klass) - classes = klass.classes_and_modules - for c in classes - res << c.full_name - find_classes_in(res, c) - end - res - end - - def find_names_in(res, klass) - classes = klass.classes_and_modules - for c in classes - res << c.full_name - res.concat c.all_method_names - find_names_in(res, c) - end - res - end - - end -end diff --git a/lib/rdoc/ri/ri_util.rb b/lib/rdoc/ri/ri_util.rb deleted file mode 100644 index 8a01255897..0000000000 --- a/lib/rdoc/ri/ri_util.rb +++ /dev/null @@ -1,75 +0,0 @@ -###################################################################### - -class RiError < Exception; end -# -# Break argument into its constituent class or module names, an -# optional method type, and a method name - -class NameDescriptor - - attr_reader :class_names - attr_reader :method_name - - # true and false have the obvious meaning. nil means we don't care - attr_reader :is_class_method - - # arg may be - # 1. a class or module name (optionally qualified with other class - # or module names (Kernel, File::Stat etc) - # 2. a method name - # 3. a method name qualified by a optionally fully qualified class - # or module name - # - # We're fairly casual about delimiters: folks can say Kernel::puts, - # Kernel.puts, or Kernel\#puts for example. There's one exception: - # if you say IO::read, we look for a class method, but if you - # say IO.read, we look for an instance method - - def initialize(arg) - @class_names = [] - separator = nil - - tokens = arg.split(/(\.|::|#)/) - - # Skip leading '::', '#' or '.', but remember it might - # be a method name qualifier - separator = tokens.shift if tokens[0] =~ /^(\.|::|#)/ - - # Skip leading '::', but remember we potentially have an inst - - # leading stuff must be class names - - while tokens[0] =~ /^[A-Z]/ - @class_names << tokens.shift - unless tokens.empty? - separator = tokens.shift - break unless separator == "::" - end - end - - # Now must have a single token, the method name, or an empty - # array - unless tokens.empty? - @method_name = tokens.shift - # We may now have a trailing !, ?, or = to roll into - # the method name - if !tokens.empty? && tokens[0] =~ /^[!?=]$/ - @method_name << tokens.shift - end - - if @method_name =~ /::|\.|#/ or !tokens.empty? - raise RiError.new("Bad argument: #{arg}") - end - if separator && separator != '.' - @is_class_method = separator == "::" - end - end - end - - # Return the full class name (with '::' between the components) - # or "" if there's no class name - - def full_class_name - @class_names.join("::") - end -end diff --git a/lib/rdoc/ri/ri_writer.rb b/lib/rdoc/ri/ri_writer.rb deleted file mode 100644 index 78c68e8409..0000000000 --- a/lib/rdoc/ri/ri_writer.rb +++ /dev/null @@ -1,62 +0,0 @@ -require 'fileutils' - -module RI - class RiWriter - - def RiWriter.class_desc_path(dir, class_desc) - File.join(dir, "cdesc-" + class_desc.name + ".yaml") - end - - - # Convert a name from internal form (containing punctuation) - # to an external form (where punctuation is replaced - # by %xx) - - def RiWriter.internal_to_external(name) - name.gsub(/\W/) { sprintf("%%%02x", $&[0]) } - end - - # And the reverse operation - def RiWriter.external_to_internal(name) - name.gsub(/%([0-9a-f]{2,2})/) { $1.to_i(16).chr } - end - - def initialize(base_dir) - @base_dir = base_dir - end - - def remove_class(class_desc) - FileUtils.rm_rf(path_to_dir(class_desc.full_name)) - end - - def add_class(class_desc) - dir = path_to_dir(class_desc.full_name) - FileUtils.mkdir_p(dir) - class_file_name = RiWriter.class_desc_path(dir, class_desc) - File.open(class_file_name, "w") do |f| - f.write(class_desc.serialize) - end - end - - def add_method(class_desc, method_desc) - dir = path_to_dir(class_desc.full_name) - file_name = RiWriter.internal_to_external(method_desc.name) - meth_file_name = File.join(dir, file_name) - if method_desc.is_singleton - meth_file_name += "-c.yaml" - else - meth_file_name += "-i.yaml" - end - - File.open(meth_file_name, "w") do |f| - f.write(method_desc.serialize) - end - end - - private - - def path_to_dir(class_name) - File.join(@base_dir, *class_name.split('::')) - end - end -end diff --git a/lib/rdoc/ri/store.rb b/lib/rdoc/ri/store.rb new file mode 100644 index 0000000000..9f4b03734a --- /dev/null +++ b/lib/rdoc/ri/store.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true +module RDoc::RI + + Store = RDoc::Store # :nodoc: + +end + diff --git a/lib/rdoc/ri/task.rb b/lib/rdoc/ri/task.rb new file mode 100644 index 0000000000..6a6ea572bf --- /dev/null +++ b/lib/rdoc/ri/task.rb @@ -0,0 +1,71 @@ +# frozen_string_literal: true +begin + gem 'rdoc' +rescue Gem::LoadError +end unless defined?(RDoc) + +require 'rdoc/task' + +## +# RDoc::RI::Task creates ri data in <code>./.rdoc</code> for your project. +# +# It contains the following tasks: +# +# [ri] +# Build ri data +# +# [clobber_ri] +# Delete ri data files. This target is automatically added to the main +# clobber target. +# +# [reri] +# Rebuild the ri data from scratch even if they are not out of date. +# +# Simple example: +# +# require 'rdoc/ri/task' +# +# RDoc::RI::Task.new do |ri| +# ri.main = 'README.rdoc' +# ri.rdoc_files.include 'README.rdoc', 'lib/**/*.rb' +# end +# +# For further configuration details see RDoc::Task. + +class RDoc::RI::Task < RDoc::Task + + DEFAULT_NAMES = { # :nodoc: + :clobber_rdoc => :clobber_ri, + :rdoc => :ri, + :rerdoc => :reri, + } + + ## + # Create an ri task with the given name. See RDoc::Task for documentation on + # setting names. + + def initialize name = DEFAULT_NAMES # :yield: self + super + end + + def clobber_task_description # :nodoc: + "Remove RI data files" + end + + ## + # Sets default task values + + def defaults + super + + @rdoc_dir = '.rdoc' + end + + def rdoc_task_description # :nodoc: + 'Build RI data files' + end + + def rerdoc_task_description # :nodoc: + 'Rebuild RI data files' + end +end diff --git a/lib/rdoc/rubygems_hook.rb b/lib/rdoc/rubygems_hook.rb new file mode 100644 index 0000000000..90b0541fcf --- /dev/null +++ b/lib/rdoc/rubygems_hook.rb @@ -0,0 +1,246 @@ +# frozen_string_literal: true +require 'rubygems/user_interaction' +require 'fileutils' +require 'rdoc' + +## +# Gem::RDoc provides methods to generate RDoc and ri data for installed gems +# upon gem installation. +# +# This file is automatically required by RubyGems 1.9 and newer. + +class RDoc::RubygemsHook + + include Gem::UserInteraction + extend Gem::UserInteraction + + @rdoc_version = nil + @specs = [] + + ## + # Force installation of documentation? + + attr_accessor :force + + ## + # Generate rdoc? + + attr_accessor :generate_rdoc + + ## + # Generate ri data? + + attr_accessor :generate_ri + + class << self + + ## + # Loaded version of RDoc. Set by ::load_rdoc + + attr_reader :rdoc_version + + end + + ## + # Post installs hook that generates documentation for each specification in + # +specs+ + + def self.generation_hook installer, specs + start = Time.now + types = installer.document + + generate_rdoc = types.include? 'rdoc' + generate_ri = types.include? 'ri' + + specs.each do |spec| + new(spec, generate_rdoc, generate_ri).generate + end + + return unless generate_rdoc or generate_ri + + duration = (Time.now - start).to_i + names = specs.map(&:name).join ', ' + + say "Done installing documentation for #{names} after #{duration} seconds" + end + + ## + # Loads the RDoc generator + + def self.load_rdoc + return if @rdoc_version + + require 'rdoc/rdoc' + + @rdoc_version = Gem::Version.new ::RDoc::VERSION + end + + ## + # Creates a new documentation generator for +spec+. RDoc and ri data + # generation can be enabled or disabled through +generate_rdoc+ and + # +generate_ri+ respectively. + # + # Only +generate_ri+ is enabled by default. + + def initialize spec, generate_rdoc = false, generate_ri = true + @doc_dir = spec.doc_dir + @force = false + @rdoc = nil + @spec = spec + + @generate_rdoc = generate_rdoc + @generate_ri = generate_ri + + @rdoc_dir = spec.doc_dir 'rdoc' + @ri_dir = spec.doc_dir 'ri' + end + + ## + # Removes legacy rdoc arguments from +args+ + #-- + # TODO move to RDoc::Options + + def delete_legacy_args args + args.delete '--inline-source' + args.delete '--promiscuous' + args.delete '-p' + args.delete '--one-file' + end + + ## + # Generates documentation using the named +generator+ ("darkfish" or "ri") + # and following the given +options+. + # + # Documentation will be generated into +destination+ + + def document generator, options, destination + generator_name = generator + + options = options.dup + options.exclude ||= [] # TODO maybe move to RDoc::Options#finish + options.setup_generator generator + options.op_dir = destination + options.finish + + generator = options.generator.new @rdoc.store, options + + @rdoc.options = options + @rdoc.generator = generator + + say "Installing #{generator_name} documentation for #{@spec.full_name}" + + FileUtils.mkdir_p options.op_dir + + Dir.chdir options.op_dir do + begin + @rdoc.class.current = @rdoc + @rdoc.generator.generate + ensure + @rdoc.class.current = nil + end + end + end + + ## + # Generates RDoc and ri data + + def generate + return if @spec.default_gem? + return unless @generate_ri or @generate_rdoc + + setup + + options = nil + + args = @spec.rdoc_options + args.concat @spec.source_paths + args.concat @spec.extra_rdoc_files + + case config_args = Gem.configuration[:rdoc] + when String then + args = args.concat config_args.split + when Array then + args = args.concat config_args + end + + delete_legacy_args args + + Dir.chdir @spec.full_gem_path do + options = ::RDoc::Options.new + options.default_title = "#{@spec.full_name} Documentation" + options.parse args + end + + options.quiet = !Gem.configuration.really_verbose + + @rdoc = new_rdoc + @rdoc.options = options + + store = RDoc::Store.new + store.encoding = options.encoding + store.dry_run = options.dry_run + store.main = options.main_page + store.title = options.title + + @rdoc.store = store + + say "Parsing documentation for #{@spec.full_name}" + + Dir.chdir @spec.full_gem_path do + @rdoc.parse_files options.files + end + + document 'ri', options, @ri_dir if + @generate_ri and (@force or not File.exist? @ri_dir) + + document 'darkfish', options, @rdoc_dir if + @generate_rdoc and (@force or not File.exist? @rdoc_dir) + end + + ## + # #new_rdoc creates a new RDoc instance. This method is provided only to + # make testing easier. + + def new_rdoc # :nodoc: + ::RDoc::RDoc.new + end + + ## + # Is rdoc documentation installed? + + def rdoc_installed? + File.exist? @rdoc_dir + end + + ## + # Removes generated RDoc and ri data + + def remove + base_dir = @spec.base_dir + + raise Gem::FilePermissionError, base_dir unless File.writable? base_dir + + FileUtils.rm_rf @rdoc_dir + FileUtils.rm_rf @ri_dir + end + + ## + # Is ri data installed? + + def ri_installed? + File.exist? @ri_dir + end + + ## + # Prepares the spec for documentation generation + + def setup + self.class.load_rdoc + + raise Gem::FilePermissionError, @doc_dir if + File.exist?(@doc_dir) and not File.writable?(@doc_dir) + + FileUtils.mkdir_p @doc_dir unless File.exist? @doc_dir + end + +end diff --git a/lib/rdoc/servlet.rb b/lib/rdoc/servlet.rb new file mode 100644 index 0000000000..f2d6dd5adc --- /dev/null +++ b/lib/rdoc/servlet.rb @@ -0,0 +1,442 @@ +# frozen_string_literal: true +require 'rdoc' +require 'time' +require 'json' +require 'webrick' + +## +# This is a WEBrick servlet that allows you to browse ri documentation. +# +# You can show documentation through either `ri --server` or, with RubyGems +# 2.0 or newer, `gem server`. For ri, the server runs on port 8214 by +# default. For RubyGems the server runs on port 8808 by default. +# +# You can use this servlet in your own project by mounting it on a WEBrick +# server: +# +# require 'webrick' +# +# server = WEBrick::HTTPServer.new Port: 8000 +# +# server.mount '/', RDoc::Servlet +# +# If you want to mount the servlet some other place than the root, provide the +# base path when mounting: +# +# server.mount '/rdoc', RDoc::Servlet, '/rdoc' + +class RDoc::Servlet < WEBrick::HTTPServlet::AbstractServlet + + @server_stores = Hash.new { |hash, server| hash[server] = {} } + @cache = Hash.new { |hash, store| hash[store] = {} } + + ## + # Maps an asset type to its path on the filesystem + + attr_reader :asset_dirs + + ## + # An RDoc::Options instance used for rendering options + + attr_reader :options + + ## + # Creates an instance of this servlet that shares cached data between + # requests. + + def self.get_instance server, *options # :nodoc: + stores = @server_stores[server] + + new server, stores, @cache, *options + end + + ## + # Creates a new WEBrick servlet. + # + # Use +mount_path+ when mounting the servlet somewhere other than /. + # + # Use +extra_doc_dirs+ for additional documentation directories. + # + # +server+ is provided automatically by WEBrick when mounting. +stores+ and + # +cache+ are provided automatically by the servlet. + + def initialize server, stores, cache, mount_path = nil, extra_doc_dirs = [] + super server + + @cache = cache + @mount_path = mount_path + @extra_doc_dirs = extra_doc_dirs + @stores = stores + + @options = RDoc::Options.new + @options.op_dir = '.' + + darkfish_dir = nil + + # HACK dup + $LOAD_PATH.each do |path| + darkfish_dir = File.join path, 'rdoc/generator/template/darkfish/' + next unless File.directory? darkfish_dir + @options.template_dir = darkfish_dir + break + end + + @asset_dirs = { + :darkfish => darkfish_dir, + :json_index => + File.expand_path('../generator/template/json_index/', __FILE__), + } + end + + ## + # Serves the asset at the path in +req+ for +generator_name+ via +res+. + + def asset generator_name, req, res + asset_dir = @asset_dirs[generator_name] + + asset_path = File.join asset_dir, req.path + + if_modified_since req, res, asset_path + + res.body = File.read asset_path + + res.content_type = case req.path + when /css$/ then 'text/css' + when /js$/ then 'application/javascript' + else 'application/octet-stream' + end + end + + ## + # GET request entry point. Fills in +res+ for the path, etc. in +req+. + + def do_GET req, res + req.path = req.path.sub(/^#{Regexp.escape @mount_path}/o, '') if @mount_path + + case req.path + when '/' then + root req, res + when '/js/darkfish.js', '/js/jquery.js', '/js/search.js', + %r%^/css/%, %r%^/images/%, %r%^/fonts/% then + asset :darkfish, req, res + when '/js/navigation.js', '/js/searcher.js' then + asset :json_index, req, res + when '/js/search_index.js' then + root_search req, res + else + show_documentation req, res + end + rescue WEBrick::HTTPStatus::NotFound => e + generator = generator_for RDoc::Store.new + + not_found generator, req, res, e.message + rescue WEBrick::HTTPStatus::Status + raise + rescue => e + error e, req, res + end + + ## + # Fills in +res+ with the class, module or page for +req+ from +store+. + # + # +path+ is relative to the mount_path and is used to determine the class, + # module or page name (/RDoc/Servlet.html becomes RDoc::Servlet). + # +generator+ is used to create the page. + + def documentation_page store, generator, path, req, res + name = path.sub(/.html$/, '').gsub '/', '::' + + if klass = store.find_class_or_module(name) then + res.body = generator.generate_class klass + elsif page = store.find_text_page(name.sub(/_([^_]*)$/, '.\1')) then + res.body = generator.generate_page page + else + not_found generator, req, res + end + end + + ## + # Creates the JSON search index on +res+ for the given +store+. +generator+ + # must respond to \#json_index to build. +req+ is ignored. + + def documentation_search store, generator, req, res + json_index = @cache[store].fetch :json_index do + @cache[store][:json_index] = + JSON.dump generator.json_index.build_index + end + + res.content_type = 'application/javascript' + res.body = "var search_data = #{json_index}" + end + + ## + # Returns the RDoc::Store and path relative to +mount_path+ for + # documentation at +path+. + + def documentation_source path + _, source_name, path = path.split '/', 3 + + store = @stores[source_name] + return store, path if store + + store = store_for source_name + + store.load_all + + @stores[source_name] = store + + return store, path + end + + ## + # Generates an error page for the +exception+ while handling +req+ on +res+. + + def error exception, req, res + backtrace = exception.backtrace.join "\n" + + res.content_type = 'text/html' + res.status = 500 + res.body = <<-BODY +<!DOCTYPE html> +<html> +<head> +<meta content="text/html; charset=UTF-8" http-equiv="Content-Type"> + +<title>Error - #{ERB::Util.html_escape exception.class}</title> + +<link type="text/css" media="screen" href="#{@mount_path}/css/rdoc.css" rel="stylesheet"> +</head> +<body> +<h1>Error</h1> + +<p>While processing <code>#{ERB::Util.html_escape req.request_uri}</code> the +RDoc (#{ERB::Util.html_escape RDoc::VERSION}) server has encountered a +<code>#{ERB::Util.html_escape exception.class}</code> +exception: + +<pre>#{ERB::Util.html_escape exception.message}</pre> + +<p>Please report this to the +<a href="https://github.com/ruby/rdoc/issues">RDoc issues tracker</a>. Please +include the RDoc version, the URI above and exception class, message and +backtrace. If you're viewing a gem's documentation, include the gem name and +version. If you're viewing Ruby's documentation, include the version of ruby. + +<p>Backtrace: + +<pre>#{ERB::Util.html_escape backtrace}</pre> + +</body> +</html> + BODY + end + + ## + # Instantiates a Darkfish generator for +store+ + + def generator_for store + generator = RDoc::Generator::Darkfish.new store, @options + generator.file_output = false + generator.asset_rel_path = '..' + + rdoc = RDoc::RDoc.new + rdoc.store = store + rdoc.generator = generator + rdoc.options = @options + + @options.main_page = store.main + @options.title = store.title + + generator + end + + ## + # Handles the If-Modified-Since HTTP header on +req+ for +path+. If the + # file has not been modified a Not Modified response is returned. If the + # file has been modified a Last-Modified header is added to +res+. + + def if_modified_since req, res, path = nil + last_modified = File.stat(path).mtime if path + + res['last-modified'] = last_modified.httpdate + + return unless ims = req['if-modified-since'] + + ims = Time.parse ims + + unless ims < last_modified then + res.body = '' + raise WEBrick::HTTPStatus::NotModified + end + end + + ## + # Returns an Array of installed documentation. + # + # Each entry contains the documentation name (gem name, 'Ruby + # Documentation', etc.), the path relative to the mount point, whether the + # documentation exists, the type of documentation (See RDoc::RI::Paths#each) + # and the filesystem to the RDoc::Store for the documentation. + + def installed_docs + extra_counter = 0 + ri_paths.map do |path, type| + store = RDoc::Store.new path, type + exists = File.exist? store.cache_path + + case type + when :gem then + gem_path = path[%r%/([^/]*)/ri$%, 1] + [gem_path, "#{gem_path}/", exists, type, path] + when :system then + ['Ruby Documentation', 'ruby/', exists, type, path] + when :site then + ['Site Documentation', 'site/', exists, type, path] + when :home then + ['Home Documentation', 'home/', exists, type, path] + when :extra then + extra_counter += 1 + store.load_cache if exists + title = store.title || "Extra Documentation" + [title, "extra-#{extra_counter}/", exists, type, path] + end + end + end + + ## + # Returns a 404 page built by +generator+ for +req+ on +res+. + + def not_found generator, req, res, message = nil + message ||= "The page <kbd>#{ERB::Util.h req.path}</kbd> was not found" + res.body = generator.generate_servlet_not_found message + res.status = 404 + end + + ## + # Enumerates the ri paths. See RDoc::RI::Paths#each + + def ri_paths &block + RDoc::RI::Paths.each true, true, true, :all, *@extra_doc_dirs, &block #TODO: pass extra_dirs + end + + ## + # Generates the root page on +res+. +req+ is ignored. + + def root req, res + generator = RDoc::Generator::Darkfish.new nil, @options + + res.body = generator.generate_servlet_root installed_docs + + res.content_type = 'text/html' + end + + ## + # Generates a search index for the root page on +res+. +req+ is ignored. + + def root_search req, res + search_index = [] + info = [] + + installed_docs.map do |name, href, exists, type, path| + next unless exists + + search_index << name + + case type + when :gem + gemspec = path.gsub(%r%/doc/([^/]*?)/ri$%, + '/specifications/\1.gemspec') + + spec = Gem::Specification.load gemspec + + path = spec.full_name + comment = spec.summary + when :system then + path = 'ruby' + comment = 'Documentation for the Ruby standard library' + when :site then + path = 'site' + comment = 'Documentation for non-gem libraries' + when :home then + path = 'home' + comment = 'Documentation from your home directory' + when :extra + comment = name + end + + info << [name, '', path, '', comment] + end + + index = { + :index => { + :searchIndex => search_index, + :longSearchIndex => search_index, + :info => info, + } + } + + res.body = "var search_data = #{JSON.dump index};" + res.content_type = 'application/javascript' + end + + ## + # Displays documentation for +req+ on +res+, whether that be HTML or some + # asset. + + def show_documentation req, res + store, path = documentation_source req.path + + if_modified_since req, res, store.cache_path + + generator = generator_for store + + case path + when nil, '', 'index.html' then + res.body = generator.generate_index + when 'table_of_contents.html' then + res.body = generator.generate_table_of_contents + when 'js/search_index.js' then + documentation_search store, generator, req, res + else + documentation_page store, generator, path, req, res + end + ensure + res.content_type ||= 'text/html' + end + + ## + # Returns an RDoc::Store for the given +source_name+ ('ruby' or a gem name). + + def store_for source_name + case source_name + when 'home' then + RDoc::Store.new RDoc::RI::Paths.home_dir, :home + when 'ruby' then + RDoc::Store.new RDoc::RI::Paths.system_dir, :system + when 'site' then + RDoc::Store.new RDoc::RI::Paths.site_dir, :site + when /^extra-(\d+)$/ then + index = $1.to_i - 1 + ri_dir = installed_docs[index][4] + RDoc::Store.new ri_dir, :extra + else + ri_dir, type = ri_paths.find do |dir, dir_type| + next unless dir_type == :gem + + source_name == dir[%r%/([^/]*)/ri$%, 1] + end + + raise WEBrick::HTTPStatus::NotFound, + "Could not find gem \"#{source_name}\". Are you sure you installed it?" unless ri_dir + + store = RDoc::Store.new ri_dir, type + + return store if File.exist? store.cache_path + + raise WEBrick::HTTPStatus::NotFound, + "Could not find documentation for \"#{source_name}\". Please run `gem rdoc --ri gem_name`" + + end + end + +end diff --git a/lib/rdoc/single_class.rb b/lib/rdoc/single_class.rb new file mode 100644 index 0000000000..6a7b67deb3 --- /dev/null +++ b/lib/rdoc/single_class.rb @@ -0,0 +1,26 @@ +# frozen_string_literal: true +## +# A singleton class + +class RDoc::SingleClass < RDoc::ClassModule + + ## + # Adds the superclass to the included modules. + + def ancestors + superclass ? super + [superclass] : super + end + + def aref_prefix # :nodoc: + 'sclass' + end + + ## + # The definition of this singleton class, <tt>class << MyClassName</tt> + + def definition + "class << #{full_name}" + end + +end + diff --git a/lib/rdoc/stats.rb b/lib/rdoc/stats.rb new file mode 100644 index 0000000000..bd6c0ef23a --- /dev/null +++ b/lib/rdoc/stats.rb @@ -0,0 +1,462 @@ +# frozen_string_literal: true +## +# RDoc statistics collector which prints a summary and report of a project's +# documentation totals. + +class RDoc::Stats + + include RDoc::Text + + ## + # Output level for the coverage report + + attr_reader :coverage_level + + ## + # Count of files parsed during parsing + + attr_reader :files_so_far + + ## + # Total number of files found + + attr_reader :num_files + + ## + # Creates a new Stats that will have +num_files+. +verbosity+ defaults to 1 + # which will create an RDoc::Stats::Normal outputter. + + def initialize store, num_files, verbosity = 1 + @num_files = num_files + @store = store + + @coverage_level = 0 + @doc_items = nil + @files_so_far = 0 + @fully_documented = false + @num_params = 0 + @percent_doc = nil + @start = Time.now + @undoc_params = 0 + + @display = case verbosity + when 0 then Quiet.new num_files + when 1 then Normal.new num_files + else Verbose.new num_files + end + end + + ## + # Records the parsing of an alias +as+. + + def add_alias as + @display.print_alias as + end + + ## + # Records the parsing of an attribute +attribute+ + + def add_attribute attribute + @display.print_attribute attribute + end + + ## + # Records the parsing of a class +klass+ + + def add_class klass + @display.print_class klass + end + + ## + # Records the parsing of +constant+ + + def add_constant constant + @display.print_constant constant + end + + ## + # Records the parsing of +file+ + + def add_file(file) + @files_so_far += 1 + @display.print_file @files_so_far, file + end + + ## + # Records the parsing of +method+ + + def add_method(method) + @display.print_method method + end + + ## + # Records the parsing of a module +mod+ + + def add_module(mod) + @display.print_module mod + end + + ## + # Call this to mark the beginning of parsing for display purposes + + def begin_adding + @display.begin_adding + end + + ## + # Calculates documentation totals and percentages for classes, modules, + # constants, attributes and methods. + + def calculate + return if @doc_items + + ucm = @store.unique_classes_and_modules + + classes = @store.unique_classes.reject { |cm| cm.full_name == 'Object' } + + constants = [] + ucm.each { |cm| constants.concat cm.constants } + + methods = [] + ucm.each { |cm| methods.concat cm.method_list } + + attributes = [] + ucm.each { |cm| attributes.concat cm.attributes } + + @num_attributes, @undoc_attributes = doc_stats attributes + @num_classes, @undoc_classes = doc_stats classes + @num_constants, @undoc_constants = doc_stats constants + @num_methods, @undoc_methods = doc_stats methods + @num_modules, @undoc_modules = doc_stats @store.unique_modules + + @num_items = + @num_attributes + + @num_classes + + @num_constants + + @num_methods + + @num_modules + + @num_params + + @undoc_items = + @undoc_attributes + + @undoc_classes + + @undoc_constants + + @undoc_methods + + @undoc_modules + + @undoc_params + + @doc_items = @num_items - @undoc_items + end + + ## + # Sets coverage report level. Accepted values are: + # + # false or nil:: No report + # 0:: Classes, modules, constants, attributes, methods + # 1:: Level 0 + method parameters + + def coverage_level= level + level = -1 unless level + + @coverage_level = level + end + + ## + # Returns the length and number of undocumented items in +collection+. + + def doc_stats collection + visible = collection.select { |item| item.display? } + [visible.length, visible.count { |item| not item.documented? }] + end + + ## + # Call this to mark the end of parsing for display purposes + + def done_adding + @display.done_adding + end + + ## + # The documentation status of this project. +true+ when 100%, +false+ when + # less than 100% and +nil+ when unknown. + # + # Set by calling #calculate + + def fully_documented? + @fully_documented + end + + ## + # A report that says you did a great job! + + def great_job + report = RDoc::Markup::Document.new + + report << RDoc::Markup::Paragraph.new('100% documentation!') + report << RDoc::Markup::Paragraph.new('Great Job!') + + report + end + + ## + # Calculates the percentage of items documented. + + def percent_doc + return @percent_doc if @percent_doc + + @fully_documented = (@num_items - @doc_items) == 0 + + @percent_doc = @doc_items.to_f / @num_items * 100 if @num_items.nonzero? + @percent_doc ||= 0 + + @percent_doc + end + + ## + # Returns a report on which items are not documented + + def report + if @coverage_level > 0 then + extend RDoc::Text + end + + if @coverage_level.zero? then + calculate + + return great_job if @num_items == @doc_items + end + + ucm = @store.unique_classes_and_modules + + report = RDoc::Markup::Document.new + report << RDoc::Markup::Paragraph.new('The following items are not documented:') + report << RDoc::Markup::BlankLine.new + + ucm.sort.each do |cm| + body = report_class_module(cm) { + [ + report_constants(cm), + report_attributes(cm), + report_methods(cm), + ].compact + } + + report << body if body + end + + if @coverage_level > 0 then + calculate + + return great_job if @num_items == @doc_items + end + + report + end + + ## + # Returns a report on undocumented attributes in ClassModule +cm+ + + def report_attributes cm + return if cm.attributes.empty? + + report = [] + + cm.each_attribute do |attr| + next if attr.documented? + line = attr.line ? ":#{attr.line}" : nil + report << " #{attr.definition} :#{attr.name} # in file #{attr.file.full_name}#{line}\n" + report << "\n" + end + + report + end + + ## + # Returns a report on undocumented items in ClassModule +cm+ + + def report_class_module cm + return if cm.fully_documented? and @coverage_level.zero? + return unless cm.display? + + report = RDoc::Markup::Document.new + + if cm.in_files.empty? then + report << RDoc::Markup::Paragraph.new("#{cm.definition} is referenced but empty.") + report << RDoc::Markup::Paragraph.new("It probably came from another project. I'm sorry I'm holding it against you.") + + return report + elsif cm.documented? then + documented = true + klass = RDoc::Markup::Verbatim.new("#{cm.definition} # is documented\n") + else + report << RDoc::Markup::Paragraph.new('In files:') + + list = RDoc::Markup::List.new :BULLET + + cm.in_files.each do |file| + para = RDoc::Markup::Paragraph.new file.full_name + list << RDoc::Markup::ListItem.new(nil, para) + end + + report << list + report << RDoc::Markup::BlankLine.new + + klass = RDoc::Markup::Verbatim.new("#{cm.definition}\n") + end + + klass << "\n" + + body = yield.flatten # HACK remove #flatten + + if body.empty? then + return if documented + + klass.parts.pop + else + klass.parts.concat body + end + + klass << "end\n" + + report << klass + + report + end + + ## + # Returns a report on undocumented constants in ClassModule +cm+ + + def report_constants cm + return if cm.constants.empty? + + report = [] + + cm.each_constant do |constant| + # TODO constant aliases are listed in the summary but not reported + # figure out what to do here + next if constant.documented? || constant.is_alias_for + + line = constant.line ? ":#{constant.line}" : line + report << " # in file #{constant.file.full_name}#{line}\n" + report << " #{constant.name} = nil\n" + report << "\n" + end + + report + end + + ## + # Returns a report on undocumented methods in ClassModule +cm+ + + def report_methods cm + return if cm.method_list.empty? + + report = [] + + cm.each_method do |method| + next if method.documented? and @coverage_level.zero? + + if @coverage_level > 0 then + params, undoc = undoc_params method + + @num_params += params + + unless undoc.empty? then + @undoc_params += undoc.length + + undoc = undoc.map do |param| "+#{param}+" end + param_report = " # #{undoc.join ', '} is not documented\n" + end + end + + next if method.documented? and not param_report + + line = method.line ? ":#{method.line}" : nil + scope = method.singleton ? 'self.' : nil + + report << " # in file #{method.file.full_name}#{line}\n" + report << param_report if param_report + report << " def #{scope}#{method.name}#{method.params}; end\n" + report << "\n" + end + + report + end + + ## + # Returns a summary of the collected statistics. + + def summary + calculate + + num_width = [@num_files, @num_items].max.to_s.length + undoc_width = [ + @undoc_attributes, + @undoc_classes, + @undoc_constants, + @undoc_items, + @undoc_methods, + @undoc_modules, + @undoc_params, + ].max.to_s.length + + report = RDoc::Markup::Verbatim.new + + report << "Files: %*d\n" % [num_width, @num_files] + + report << "\n" + + report << "Classes: %*d (%*d undocumented)\n" % [ + num_width, @num_classes, undoc_width, @undoc_classes] + report << "Modules: %*d (%*d undocumented)\n" % [ + num_width, @num_modules, undoc_width, @undoc_modules] + report << "Constants: %*d (%*d undocumented)\n" % [ + num_width, @num_constants, undoc_width, @undoc_constants] + report << "Attributes: %*d (%*d undocumented)\n" % [ + num_width, @num_attributes, undoc_width, @undoc_attributes] + report << "Methods: %*d (%*d undocumented)\n" % [ + num_width, @num_methods, undoc_width, @undoc_methods] + report << "Parameters: %*d (%*d undocumented)\n" % [ + num_width, @num_params, undoc_width, @undoc_params] if + @coverage_level > 0 + + report << "\n" + + report << "Total: %*d (%*d undocumented)\n" % [ + num_width, @num_items, undoc_width, @undoc_items] + + report << "%6.2f%% documented\n" % percent_doc + report << "\n" + report << "Elapsed: %0.1fs\n" % (Time.now - @start) + + RDoc::Markup::Document.new report + end + + ## + # Determines which parameters in +method+ were not documented. Returns a + # total parameter count and an Array of undocumented methods. + + def undoc_params method + @formatter ||= RDoc::Markup::ToTtOnly.new + + params = method.param_list + + params = params.map { |param| param.gsub(/^\*\*?/, '') } + + return 0, [] if params.empty? + + document = parse method.comment + + tts = document.accept @formatter + + undoc = params - tts + + [params.length, undoc] + end + + autoload :Quiet, 'rdoc/stats/quiet' + autoload :Normal, 'rdoc/stats/normal' + autoload :Verbose, 'rdoc/stats/verbose' + +end + diff --git a/lib/rdoc/stats/normal.rb b/lib/rdoc/stats/normal.rb new file mode 100644 index 0000000000..a3a6ff377e --- /dev/null +++ b/lib/rdoc/stats/normal.rb @@ -0,0 +1,58 @@ +# frozen_string_literal: true +begin + require 'io/console/size' +rescue LoadError + # for JRuby + require 'io/console' +end + +## +# Stats printer that prints just the files being documented with a progress +# bar + +class RDoc::Stats::Normal < RDoc::Stats::Quiet + + def begin_adding # :nodoc: + puts "Parsing sources..." + @last_width = 0 + end + + ## + # Prints a file with a progress bar + + def print_file files_so_far, filename + progress_bar = sprintf("%3d%% [%2d/%2d] ", + 100 * files_so_far / @num_files, + files_so_far, + @num_files) + + # Print a progress bar, but make sure it fits on a single line. Filename + # will be truncated if necessary. + size = IO.respond_to?(:console_size) ? IO.console_size : IO.console.winsize + terminal_width = size[1].to_i.nonzero? || 80 + max_filename_size = terminal_width - progress_bar.size + + if filename.size > max_filename_size then + # Turn "some_long_filename.rb" to "...ong_filename.rb" + filename = filename[(filename.size - max_filename_size) .. -1] + filename[0..2] = "..." + end + + line = "#{progress_bar}#{filename}" + if $stdout.tty? + # Clean the line with whitespaces so that leftover output from the + # previous line doesn't show up. + $stdout.print("\r" + (" " * @last_width) + ("\b" * @last_width) + "\r") if @last_width && @last_width > 0 + @last_width = line.size + $stdout.print("#{line}\r") + else + $stdout.puts(line) + end + $stdout.flush + end + + def done_adding # :nodoc: + puts + end + +end diff --git a/lib/rdoc/stats/quiet.rb b/lib/rdoc/stats/quiet.rb new file mode 100644 index 0000000000..bc4161b2d4 --- /dev/null +++ b/lib/rdoc/stats/quiet.rb @@ -0,0 +1,60 @@ +# frozen_string_literal: true +## +# Stats printer that prints nothing + +class RDoc::Stats::Quiet + + ## + # Creates a new Quiet that will print nothing + + def initialize num_files + @num_files = num_files + end + + ## + # Prints a message at the beginning of parsing + + def begin_adding(*) end + + ## + # Prints when an alias is added + + def print_alias(*) end + + ## + # Prints when an attribute is added + + def print_attribute(*) end + + ## + # Prints when a class is added + + def print_class(*) end + + ## + # Prints when a constant is added + + def print_constant(*) end + + ## + # Prints when a file is added + + def print_file(*) end + + ## + # Prints when a method is added + + def print_method(*) end + + ## + # Prints when a module is added + + def print_module(*) end + + ## + # Prints when RDoc is done + + def done_adding(*) end + +end + diff --git a/lib/rdoc/stats/verbose.rb b/lib/rdoc/stats/verbose.rb new file mode 100644 index 0000000000..6ace8937a2 --- /dev/null +++ b/lib/rdoc/stats/verbose.rb @@ -0,0 +1,46 @@ +# frozen_string_literal: true +## +# Stats printer that prints everything documented, including the documented +# status + +class RDoc::Stats::Verbose < RDoc::Stats::Normal + + ## + # Returns a marker for RDoc::CodeObject +co+ being undocumented + + def nodoc co + " (undocumented)" unless co.documented? + end + + def print_alias as # :nodoc: + puts " alias #{as.new_name} #{as.old_name}#{nodoc as}" + end + + def print_attribute attribute # :nodoc: + puts " #{attribute.definition} #{attribute.name}#{nodoc attribute}" + end + + def print_class(klass) # :nodoc: + puts " class #{klass.full_name}#{nodoc klass}" + end + + def print_constant(constant) # :nodoc: + puts " #{constant.name}#{nodoc constant}" + end + + def print_file(files_so_far, file) # :nodoc: + super + puts + end + + def print_method(method) # :nodoc: + puts " #{method.singleton ? '::' : '#'}#{method.name}#{nodoc method}" + end + + def print_module(mod) # :nodoc: + puts " module #{mod.full_name}#{nodoc mod}" + end + +end + + diff --git a/lib/rdoc/store.rb b/lib/rdoc/store.rb new file mode 100644 index 0000000000..999aa76f92 --- /dev/null +++ b/lib/rdoc/store.rb @@ -0,0 +1,968 @@ +# frozen_string_literal: true +require 'fileutils' + +## +# A set of rdoc data for a single project (gem, path, etc.). +# +# The store manages reading and writing ri data for a project and maintains a +# cache of methods, classes and ancestors in the store. +# +# The store maintains a #cache of its contents for faster lookup. After +# adding items to the store it must be flushed using #save_cache. The cache +# contains the following structures: +# +# @cache = { +# :ancestors => {}, # class name => ancestor names +# :attributes => {}, # class name => attributes +# :class_methods => {}, # class name => class methods +# :instance_methods => {}, # class name => instance methods +# :modules => [], # classes and modules in this store +# :pages => [], # page names +# } +#-- +# TODO need to prune classes + +class RDoc::Store + + ## + # Errors raised from loading or saving the store + + class Error < RDoc::Error + end + + ## + # Raised when a stored file for a class, module, page or method is missing. + + class MissingFileError < Error + + ## + # The store the file should exist in + + attr_reader :store + + ## + # The file the #name should be saved as + + attr_reader :file + + ## + # The name of the object the #file would be loaded from + + attr_reader :name + + ## + # Creates a new MissingFileError for the missing +file+ for the given + # +name+ that should have been in the +store+. + + def initialize store, file, name + @store = store + @file = file + @name = name + end + + def message # :nodoc: + "store at #{@store.path} missing file #{@file} for #{@name}" + end + + end + + ## + # Stores the name of the C variable a class belongs to. This helps wire up + # classes defined from C across files. + + attr_reader :c_enclosure_classes # :nodoc: + + attr_reader :c_enclosure_names # :nodoc: + + ## + # Maps C variables to class or module names for each parsed C file. + + attr_reader :c_class_variables + + ## + # Maps C variables to singleton class names for each parsed C file. + + attr_reader :c_singleton_class_variables + + ## + # If true this Store will not write any files + + attr_accessor :dry_run + + ## + # Path this store reads or writes + + attr_accessor :path + + ## + # The RDoc::RDoc driver for this parse tree. This allows classes consulting + # the documentation tree to access user-set options, for example. + + attr_accessor :rdoc + + ## + # Type of ri datastore this was loaded from. See RDoc::RI::Driver, + # RDoc::RI::Paths. + + attr_accessor :type + + ## + # The contents of the Store + + attr_reader :cache + + ## + # The encoding of the contents in the Store + + attr_accessor :encoding + + ## + # Creates a new Store of +type+ that will load or save to +path+ + + def initialize path = nil, type = nil + @dry_run = false + @encoding = nil + @path = path + @rdoc = nil + @type = type + + @cache = { + :ancestors => {}, + :attributes => {}, + :class_methods => {}, + :c_class_variables => {}, + :c_singleton_class_variables => {}, + :encoding => @encoding, + :instance_methods => {}, + :main => nil, + :modules => [], + :pages => [], + :title => nil, + } + + @classes_hash = {} + @modules_hash = {} + @files_hash = {} + + @c_enclosure_classes = {} + @c_enclosure_names = {} + + @c_class_variables = {} + @c_singleton_class_variables = {} + + @unique_classes = nil + @unique_modules = nil + end + + ## + # Adds +module+ as an enclosure (namespace) for the given +variable+ for C + # files. + + def add_c_enclosure variable, namespace + @c_enclosure_classes[variable] = namespace + end + + ## + # Adds C variables from an RDoc::Parser::C + + def add_c_variables c_parser + filename = c_parser.top_level.relative_name + + @c_class_variables[filename] = make_variable_map c_parser.classes + + @c_singleton_class_variables[filename] = c_parser.singleton_classes + end + + ## + # Adds the file with +name+ as an RDoc::TopLevel to the store. Returns the + # created RDoc::TopLevel. + + def add_file absolute_name, relative_name = absolute_name + unless top_level = @files_hash[relative_name] then + top_level = RDoc::TopLevel.new absolute_name, relative_name + top_level.store = self + @files_hash[relative_name] = top_level + end + + top_level + end + + ## + # Returns all classes discovered by RDoc + + def all_classes + @classes_hash.values + end + + ## + # Returns all classes and modules discovered by RDoc + + def all_classes_and_modules + @classes_hash.values + @modules_hash.values + end + + ## + # All TopLevels known to RDoc + + def all_files + @files_hash.values + end + + ## + # Returns all modules discovered by RDoc + + def all_modules + modules_hash.values + end + + ## + # Ancestors cache accessor. Maps a klass name to an Array of its ancestors + # in this store. If Foo in this store inherits from Object, Kernel won't be + # listed (it will be included from ruby's ri store). + + def ancestors + @cache[:ancestors] + end + + ## + # Attributes cache accessor. Maps a class to an Array of its attributes. + + def attributes + @cache[:attributes] + end + + ## + # Path to the cache file + + def cache_path + File.join @path, 'cache.ri' + end + + ## + # Path to the ri data for +klass_name+ + + def class_file klass_name + name = klass_name.split('::').last + File.join class_path(klass_name), "cdesc-#{name}.ri" + end + + ## + # Class methods cache accessor. Maps a class to an Array of its class + # methods (not full name). + + def class_methods + @cache[:class_methods] + end + + ## + # Path where data for +klass_name+ will be stored (methods or class data) + + def class_path klass_name + File.join @path, *klass_name.split('::') + end + + ## + # Hash of all classes known to RDoc + + def classes_hash + @classes_hash + end + + ## + # Removes empty items and ensures item in each collection are unique and + # sorted + + def clean_cache_collection collection # :nodoc: + collection.each do |name, item| + if item.empty? then + collection.delete name + else + # HACK mongrel-1.1.5 documents its files twice + item.uniq! + item.sort! + end + end + end + + ## + # Prepares the RDoc code object tree for use by a generator. + # + # It finds unique classes/modules defined, and replaces classes/modules that + # are aliases for another one by a copy with RDoc::ClassModule#is_alias_for + # set. + # + # It updates the RDoc::ClassModule#constant_aliases attribute of "real" + # classes or modules. + # + # It also completely removes the classes and modules that should be removed + # from the documentation and the methods that have a visibility below + # +min_visibility+, which is the <tt>--visibility</tt> option. + # + # See also RDoc::Context#remove_from_documentation? + + def complete min_visibility + fix_basic_object_inheritance + + # cache included modules before they are removed from the documentation + all_classes_and_modules.each { |cm| cm.ancestors } + + unless min_visibility == :nodoc then + remove_nodoc @classes_hash + remove_nodoc @modules_hash + end + + @unique_classes = find_unique @classes_hash + @unique_modules = find_unique @modules_hash + + unique_classes_and_modules.each do |cm| + cm.complete min_visibility + end + + @files_hash.each_key do |file_name| + tl = @files_hash[file_name] + + unless tl.text? then + tl.modules_hash.clear + tl.classes_hash.clear + + tl.classes_or_modules.each do |cm| + name = cm.full_name + if cm.type == 'class' then + tl.classes_hash[name] = cm if @classes_hash[name] + else + tl.modules_hash[name] = cm if @modules_hash[name] + end + end + end + end + end + + ## + # Hash of all files known to RDoc + + def files_hash + @files_hash + end + + ## + # Finds the enclosure (namespace) for the given C +variable+. + + def find_c_enclosure variable + @c_enclosure_classes.fetch variable do + break unless name = @c_enclosure_names[variable] + + mod = find_class_or_module name + + unless mod then + loaded_mod = load_class_data name + + file = loaded_mod.in_files.first + + return unless file # legacy data source + + file.store = self + + mod = file.add_module RDoc::NormalModule, name + end + + @c_enclosure_classes[variable] = mod + end + end + + ## + # Finds the class with +name+ in all discovered classes + + def find_class_named name + @classes_hash[name] + end + + ## + # Finds the class with +name+ starting in namespace +from+ + + def find_class_named_from name, from + from = find_class_named from unless RDoc::Context === from + + until RDoc::TopLevel === from do + return nil unless from + + klass = from.find_class_named name + return klass if klass + + from = from.parent + end + + find_class_named name + end + + ## + # Finds the class or module with +name+ + + def find_class_or_module name + name = $' if name =~ /^::/ + @classes_hash[name] || @modules_hash[name] + end + + ## + # Finds the file with +name+ in all discovered files + + def find_file_named name + @files_hash[name] + end + + ## + # Finds the module with +name+ in all discovered modules + + def find_module_named name + @modules_hash[name] + end + + ## + # Returns the RDoc::TopLevel that is a text file and has the given + # +file_name+ + + def find_text_page file_name + @files_hash.each_value.find do |file| + file.text? and file.full_name == file_name + end + end + + ## + # Finds unique classes/modules defined in +all_hash+, + # and returns them as an array. Performs the alias + # updates in +all_hash+: see ::complete. + #-- + # TODO aliases should be registered by Context#add_module_alias + + def find_unique all_hash + unique = [] + + all_hash.each_pair do |full_name, cm| + unique << cm if full_name == cm.full_name + end + + unique + end + + ## + # Fixes the erroneous <tt>BasicObject < Object</tt> in 1.9. + # + # Because we assumed all classes without a stated superclass + # inherit from Object, we have the above wrong inheritance. + # + # We fix BasicObject right away if we are running in a Ruby + # version >= 1.9. + + def fix_basic_object_inheritance + basic = classes_hash['BasicObject'] + return unless basic + basic.superclass = nil + end + + ## + # Friendly rendition of #path + + def friendly_path + case type + when :gem then + parent = File.expand_path '..', @path + "gem #{File.basename parent}" + when :home then '~/.rdoc' + when :site then 'ruby site' + when :system then 'ruby core' + else @path + end + end + + def inspect # :nodoc: + "#<%s:0x%x %s %p>" % [self.class, object_id, @path, module_names.sort] + end + + ## + # Instance methods cache accessor. Maps a class to an Array of its + # instance methods (not full name). + + def instance_methods + @cache[:instance_methods] + end + + ## + # Loads all items from this store into memory. This recreates a + # documentation tree for use by a generator + + def load_all + load_cache + + module_names.each do |module_name| + mod = find_class_or_module(module_name) || load_class(module_name) + + # load method documentation since the loaded class/module does not have + # it + loaded_methods = mod.method_list.map do |method| + load_method module_name, method.full_name + end + + mod.method_list.replace loaded_methods + + loaded_attributes = mod.attributes.map do |attribute| + load_method module_name, attribute.full_name + end + + mod.attributes.replace loaded_attributes + end + + all_classes_and_modules.each do |mod| + descendent_re = /^#{mod.full_name}::[^:]+$/ + + module_names.each do |name| + next unless name =~ descendent_re + + descendent = find_class_or_module name + + case descendent + when RDoc::NormalClass then + mod.classes_hash[name] = descendent + when RDoc::NormalModule then + mod.modules_hash[name] = descendent + end + end + end + + @cache[:pages].each do |page_name| + page = load_page page_name + @files_hash[page_name] = page + end + end + + ## + # Loads cache file for this store + + def load_cache + #orig_enc = @encoding + + open cache_path, 'rb' do |io| + @cache = Marshal.load io.read + end + + load_enc = @cache[:encoding] + + # TODO this feature will be time-consuming to add: + # a) Encodings may be incompatible but transcodeable + # b) Need to warn in the appropriate spots, wherever they may be + # c) Need to handle cross-cache differences in encodings + # d) Need to warn when generating into a cache with different encodings + # + #if orig_enc and load_enc != orig_enc then + # warn "Cached encoding #{load_enc} is incompatible with #{orig_enc}\n" \ + # "from #{path}/cache.ri" unless + # Encoding.compatible? orig_enc, load_enc + #end + + @encoding = load_enc unless @encoding + + @cache[:pages] ||= [] + @cache[:main] ||= nil + @cache[:c_class_variables] ||= {} + @cache[:c_singleton_class_variables] ||= {} + + @cache[:c_class_variables].each do |_, map| + map.each do |variable, name| + @c_enclosure_names[variable] = name + end + end + + @cache + rescue Errno::ENOENT + end + + ## + # Loads ri data for +klass_name+ and hooks it up to this store. + + def load_class klass_name + obj = load_class_data klass_name + + obj.store = self + + case obj + when RDoc::NormalClass then + @classes_hash[klass_name] = obj + when RDoc::NormalModule then + @modules_hash[klass_name] = obj + end + end + + ## + # Loads ri data for +klass_name+ + + def load_class_data klass_name + file = class_file klass_name + + open file, 'rb' do |io| + Marshal.load io.read + end + rescue Errno::ENOENT => e + error = MissingFileError.new(self, file, klass_name) + error.set_backtrace e.backtrace + raise error + end + + ## + # Loads ri data for +method_name+ in +klass_name+ + + def load_method klass_name, method_name + file = method_file klass_name, method_name + + open file, 'rb' do |io| + obj = Marshal.load io.read + obj.store = self + obj.parent = + find_class_or_module(klass_name) || load_class(klass_name) unless + obj.parent + obj + end + rescue Errno::ENOENT => e + error = MissingFileError.new(self, file, klass_name + method_name) + error.set_backtrace e.backtrace + raise error + end + + ## + # Loads ri data for +page_name+ + + def load_page page_name + file = page_file page_name + + open file, 'rb' do |io| + obj = Marshal.load io.read + obj.store = self + obj + end + rescue Errno::ENOENT => e + error = MissingFileError.new(self, file, page_name) + error.set_backtrace e.backtrace + raise error + end + + ## + # Gets the main page for this RDoc store. This page is used as the root of + # the RDoc server. + + def main + @cache[:main] + end + + ## + # Sets the main page for this RDoc store. + + def main= page + @cache[:main] = page + end + + ## + # Converts the variable => ClassModule map +variables+ from a C parser into + # a variable => class name map. + + def make_variable_map variables + map = {} + + variables.each { |variable, class_module| + map[variable] = class_module.full_name + } + + map + end + + ## + # Path to the ri data for +method_name+ in +klass_name+ + + def method_file klass_name, method_name + method_name = method_name.split('::').last + method_name =~ /#(.*)/ + method_type = $1 ? 'i' : 'c' + method_name = $1 if $1 + method_name = method_name.gsub(/\W/) { "%%%02x" % $&[0].ord } + + File.join class_path(klass_name), "#{method_name}-#{method_type}.ri" + end + + ## + # Modules cache accessor. An Array of all the module (and class) names in + # the store. + + def module_names + @cache[:modules] + end + + ## + # Hash of all modules known to RDoc + + def modules_hash + @modules_hash + end + + ## + # Returns the RDoc::TopLevel that is a text file and has the given +name+ + + def page name + @files_hash.each_value.find do |file| + file.text? and file.page_name == name + end + end + + ## + # Path to the ri data for +page_name+ + + def page_file page_name + file_name = File.basename(page_name).gsub('.', '_') + + File.join @path, File.dirname(page_name), "page-#{file_name}.ri" + end + + ## + # Removes from +all_hash+ the contexts that are nodoc or have no content. + # + # See RDoc::Context#remove_from_documentation? + + def remove_nodoc all_hash + all_hash.keys.each do |name| + context = all_hash[name] + all_hash.delete(name) if context.remove_from_documentation? + end + end + + ## + # Saves all entries in the store + + def save + load_cache + + all_classes_and_modules.each do |klass| + save_class klass + + klass.each_method do |method| + save_method klass, method + end + + klass.each_attribute do |attribute| + save_method klass, attribute + end + end + + all_files.each do |file| + save_page file + end + + save_cache + end + + ## + # Writes the cache file for this store + + def save_cache + clean_cache_collection @cache[:ancestors] + clean_cache_collection @cache[:attributes] + clean_cache_collection @cache[:class_methods] + clean_cache_collection @cache[:instance_methods] + + @cache[:modules].uniq! + @cache[:modules].sort! + + @cache[:pages].uniq! + @cache[:pages].sort! + + @cache[:encoding] = @encoding # this gets set twice due to assert_cache + + @cache[:c_class_variables].merge! @c_class_variables + @cache[:c_singleton_class_variables].merge! @c_singleton_class_variables + + return if @dry_run + + marshal = Marshal.dump @cache + + open cache_path, 'wb' do |io| + io.write marshal + end + end + + ## + # Writes the ri data for +klass+ (or module) + + def save_class klass + full_name = klass.full_name + + FileUtils.mkdir_p class_path(full_name) unless @dry_run + + @cache[:modules] << full_name + + path = class_file full_name + + begin + disk_klass = load_class full_name + + klass = disk_klass.merge klass + rescue MissingFileError + end + + # BasicObject has no ancestors + ancestors = klass.direct_ancestors.compact.map do |ancestor| + # HACK for classes we don't know about (class X < RuntimeError) + String === ancestor ? ancestor : ancestor.full_name + end + + @cache[:ancestors][full_name] ||= [] + @cache[:ancestors][full_name].concat ancestors + + attribute_definitions = klass.attributes.map do |attribute| + "#{attribute.definition} #{attribute.name}" + end + + unless attribute_definitions.empty? then + @cache[:attributes][full_name] ||= [] + @cache[:attributes][full_name].concat attribute_definitions + end + + to_delete = [] + + unless klass.method_list.empty? then + @cache[:class_methods][full_name] ||= [] + @cache[:instance_methods][full_name] ||= [] + + class_methods, instance_methods = + klass.method_list.partition { |meth| meth.singleton } + + class_methods = class_methods. map { |method| method.name } + instance_methods = instance_methods.map { |method| method.name } + attribute_names = klass.attributes.map { |attr| attr.name } + + old = @cache[:class_methods][full_name] - class_methods + to_delete.concat old.map { |method| + method_file full_name, "#{full_name}::#{method}" + } + + old = @cache[:instance_methods][full_name] - + instance_methods - attribute_names + to_delete.concat old.map { |method| + method_file full_name, "#{full_name}##{method}" + } + + @cache[:class_methods][full_name] = class_methods + @cache[:instance_methods][full_name] = instance_methods + end + + return if @dry_run + + FileUtils.rm_f to_delete + + marshal = Marshal.dump klass + + open path, 'wb' do |io| + io.write marshal + end + end + + ## + # Writes the ri data for +method+ on +klass+ + + def save_method klass, method + full_name = klass.full_name + + FileUtils.mkdir_p class_path(full_name) unless @dry_run + + cache = if method.singleton then + @cache[:class_methods] + else + @cache[:instance_methods] + end + cache[full_name] ||= [] + cache[full_name] << method.name + + return if @dry_run + + marshal = Marshal.dump method + + open method_file(full_name, method.full_name), 'wb' do |io| + io.write marshal + end + end + + ## + # Writes the ri data for +page+ + + def save_page page + return unless page.text? + + path = page_file page.full_name + + FileUtils.mkdir_p File.dirname(path) unless @dry_run + + cache[:pages] ||= [] + cache[:pages] << page.full_name + + return if @dry_run + + marshal = Marshal.dump page + + open path, 'wb' do |io| + io.write marshal + end + end + + ## + # Source of the contents of this store. + # + # For a store from a gem the source is the gem name. For a store from the + # home directory the source is "home". For system ri store (the standard + # library documentation) the source is"ruby". For a store from the site + # ri directory the store is "site". For other stores the source is the + # #path. + + def source + case type + when :gem then File.basename File.expand_path '..', @path + when :home then 'home' + when :site then 'site' + when :system then 'ruby' + else @path + end + end + + ## + # Gets the title for this RDoc store. This is used as the title in each + # page on the RDoc server + + def title + @cache[:title] + end + + ## + # Sets the title page for this RDoc store. + + def title= title + @cache[:title] = title + end + + ## + # Returns the unique classes discovered by RDoc. + # + # ::complete must have been called prior to using this method. + + def unique_classes + @unique_classes + end + + ## + # Returns the unique classes and modules discovered by RDoc. + # ::complete must have been called prior to using this method. + + def unique_classes_and_modules + @unique_classes + @unique_modules + end + + ## + # Returns the unique modules discovered by RDoc. + # ::complete must have been called prior to using this method. + + def unique_modules + @unique_modules + end + +end diff --git a/lib/rdoc/task.rb b/lib/rdoc/task.rb new file mode 100644 index 0000000000..323d00eabc --- /dev/null +++ b/lib/rdoc/task.rb @@ -0,0 +1,329 @@ +# frozen_string_literal: true +#-- +# Copyright (c) 2003, 2004 Jim Weirich, 2009 Eric Hodel +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +#++ + +begin + gem 'rdoc' +rescue Gem::LoadError +end unless defined?(RDoc) + +begin + gem 'rake' +rescue Gem::LoadError +end unless defined?(Rake) + +require 'rdoc' +require 'rake' +require 'rake/tasklib' + +## +# RDoc::Task creates the following rake tasks to generate and clean up RDoc +# output: +# +# [rdoc] +# Main task for this RDoc task. +# +# [clobber_rdoc] +# Delete all the rdoc files. This target is automatically added to the main +# clobber target. +# +# [rerdoc] +# Rebuild the rdoc files from scratch, even if they are not out of date. +# +# Simple Example: +# +# require 'rdoc/task' +# +# RDoc::Task.new do |rdoc| +# rdoc.main = "README.rdoc" +# rdoc.rdoc_files.include("README.rdoc", "lib/**/*.rb") +# end +# +# The +rdoc+ object passed to the block is an RDoc::Task object. See the +# attributes list for the RDoc::Task class for available customization options. +# +# == Specifying different task names +# +# You may wish to give the task a different name, such as if you are +# generating two sets of documentation. For instance, if you want to have a +# development set of documentation including private methods: +# +# require 'rdoc/task' +# +# RDoc::Task.new :rdoc_dev do |rdoc| +# rdoc.main = "README.doc" +# rdoc.rdoc_files.include("README.rdoc", "lib/**/*.rb") +# rdoc.options << "--all" +# end +# +# The tasks would then be named :<em>rdoc_dev</em>, +# :clobber_<em>rdoc_dev</em>, and :re<em>rdoc_dev</em>. +# +# If you wish to have completely different task names, then pass a Hash as +# first argument. With the <tt>:rdoc</tt>, <tt>:clobber_rdoc</tt> and +# <tt>:rerdoc</tt> options, you can customize the task names to your liking. +# +# For example: +# +# require 'rdoc/task' +# +# RDoc::Task.new(:rdoc => "rdoc", :clobber_rdoc => "rdoc:clean", +# :rerdoc => "rdoc:force") +# +# This will create the tasks <tt>:rdoc</tt>, <tt>:rdoc:clean</tt> and +# <tt>:rdoc:force</tt>. + +class RDoc::Task < Rake::TaskLib + + ## + # Name of the main, top level task. (default is :rdoc) + + attr_accessor :name + + ## + # Comment markup format. rdoc, rd and tomdoc are supported. (default is + # 'rdoc') + + attr_accessor :markup + + ## + # Name of directory to receive the html output files. (default is "html") + + attr_accessor :rdoc_dir + + ## + # Title of RDoc documentation. (defaults to rdoc's default) + + attr_accessor :title + + ## + # Name of file to be used as the main, top level file of the RDoc. (default + # is none) + + attr_accessor :main + + ## + # Name of template to be used by rdoc. (defaults to rdoc's default) + + attr_accessor :template + + ## + # Name of format generator (<tt>--format<tt>) used by rdoc. (defaults to + # rdoc's default) + + attr_accessor :generator + + ## + # List of files to be included in the rdoc generation. (default is []) + + attr_accessor :rdoc_files + + ## + # Additional list of options to be passed rdoc. (default is []) + + attr_accessor :options + + ## + # Whether to run the rdoc process as an external shell (default is false) + + attr_accessor :external + + ## + # Create an RDoc task with the given name. See the RDoc::Task class overview + # for documentation. + + def initialize name = :rdoc # :yield: self + defaults + + check_names name + + @name = name + + yield self if block_given? + + define + end + + ## + # Ensures that +names+ only includes names for the :rdoc, :clobber_rdoc and + # :rerdoc. If other names are given an ArgumentError is raised. + + def check_names names + return unless Hash === names + + invalid_options = + names.keys.map { |k| k.to_sym } - [:rdoc, :clobber_rdoc, :rerdoc] + + unless invalid_options.empty? then + raise ArgumentError, "invalid options: #{invalid_options.join ', '}" + end + end + + ## + # Task description for the clobber rdoc task or its renamed equivalent + + def clobber_task_description + "Remove RDoc HTML files" + end + + ## + # Sets default task values + + def defaults + @name = :rdoc + @rdoc_files = Rake::FileList.new + @rdoc_dir = 'html' + @main = nil + @title = nil + @template = nil + @generator = nil + @options = [] + end + + ## + # All source is inline now. This method is deprecated + + def inline_source # :nodoc: + warn "RDoc::Task#inline_source is deprecated" + true + end + + ## + # All source is inline now. This method is deprecated + + def inline_source=(value) # :nodoc: + warn "RDoc::Task#inline_source is deprecated" + end + + ## + # Create the tasks defined by this task lib. + + def define + desc rdoc_task_description + task rdoc_task_name + + desc rerdoc_task_description + task rerdoc_task_name => [clobber_task_name, rdoc_task_name] + + desc clobber_task_description + task clobber_task_name do + rm_r @rdoc_dir rescue nil + end + + task :clobber => [clobber_task_name] + + directory @rdoc_dir + + rdoc_target_deps = [ + @rdoc_files, + Rake.application.rakefile + ].flatten.compact + + task rdoc_task_name => [rdoc_target] + file rdoc_target => rdoc_target_deps do + @before_running_rdoc.call if @before_running_rdoc + args = option_list + @rdoc_files + + $stderr.puts "rdoc #{args.join ' '}" if Rake.application.options.trace + RDoc::RDoc.new.document args + end + + self + end + + ## + # List of options that will be supplied to RDoc + + def option_list + result = @options.dup + result << "-o" << @rdoc_dir + result << "--main" << main if main + result << "--markup" << markup if markup + result << "--title" << title if title + result << "-T" << template if template + result << '-f' << generator if generator + result + end + + ## + # The block passed to this method will be called just before running the + # RDoc generator. It is allowed to modify RDoc::Task attributes inside the + # block. + + def before_running_rdoc(&block) + @before_running_rdoc = block + end + + ## + # Task description for the rdoc task or its renamed equivalent + + def rdoc_task_description + 'Build RDoc HTML files' + end + + ## + # Task description for the rerdoc task or its renamed description + + def rerdoc_task_description + "Rebuild RDoc HTML files" + end + + private + + def rdoc_target + "#{rdoc_dir}/created.rid" + end + + def rdoc_task_name + case name + when Hash then (name[:rdoc] || "rdoc").to_s + else name.to_s + end + end + + def clobber_task_name + case name + when Hash then (name[:clobber_rdoc] || "clobber_rdoc").to_s + else "clobber_#{name}" + end + end + + def rerdoc_task_name + case name + when Hash then (name[:rerdoc] || "rerdoc").to_s + else "re#{name}" + end + end + +end + +# :stopdoc: +module Rake + + ## + # For backwards compatibility + + RDocTask = RDoc::Task + +end +# :startdoc: diff --git a/lib/rdoc/template.rb b/lib/rdoc/template.rb deleted file mode 100644 index 469e10fb4b..0000000000 --- a/lib/rdoc/template.rb +++ /dev/null @@ -1,234 +0,0 @@ -# Cheap-n-cheerful HTML page template system. You create a -# template containing: -# -# * variable names between percent signs (<tt>%fred%</tt>) -# * blocks of repeating stuff: -# -# START:key -# ... stuff -# END:key -# -# You feed the code a hash. For simple variables, the values -# are resolved directly from the hash. For blocks, the hash entry -# corresponding to +key+ will be an array of hashes. The block will -# be generated once for each entry. Blocks can be nested arbitrarily -# deeply. -# -# The template may also contain -# -# IF:key -# ... stuff -# ENDIF:key -# -# _stuff_ will only be included in the output if the corresponding -# key is set in the value hash. -# -# Usage: Given a set of templates <tt>T1, T2,</tt> etc -# -# values = { "name" => "Dave", state => "TX" } -# -# t = TemplatePage.new(T1, T2, T3) -# File.open(name, "w") {|f| t.write_html_on(f, values)} -# or -# res = '' -# t.write_html_on(res, values) -# -# - -class TemplatePage - - ########## - # A context holds a stack of key/value pairs (like a symbol - # table). When asked to resolve a key, it first searches the top of - # the stack, then the next level, and so on until it finds a match - # (or runs out of entries) - - class Context - def initialize - @stack = [] - end - - def push(hash) - @stack.push(hash) - end - - def pop - @stack.pop - end - - # Find a scalar value, throwing an exception if not found. This - # method is used when substituting the %xxx% constructs - - def find_scalar(key) - @stack.reverse_each do |level| - if val = level[key] - return val unless val.kind_of? Array - end - end - raise "Template error: can't find variable '#{key}'" - end - - # Lookup any key in the stack of hashes - - def lookup(key) - @stack.reverse_each do |level| - val = level[key] - return val if val - end - nil - end - end - - ######### - # Simple class to read lines out of a string - - class LineReader - # we're initialized with an array of lines - def initialize(lines) - @lines = lines - end - - # read the next line - def read - @lines.shift - end - - # Return a list of lines up to the line that matches - # a pattern. That last line is discarded. - def read_up_to(pattern) - res = [] - while line = read - if pattern.match(line) - return LineReader.new(res) - else - res << line - end - end - raise "Missing end tag in template: #{pattern.source}" - end - - # Return a copy of ourselves that can be modified without - # affecting us - def dup - LineReader.new(@lines.dup) - end - end - - - - # +templates+ is an array of strings containing the templates. - # We start at the first, and substitute in subsequent ones - # where the string <tt>!INCLUDE!</tt> occurs. For example, - # we could have the overall page template containing - # - # <html><body> - # <h1>Master</h1> - # !INCLUDE! - # </bost></html> - # - # and substitute subpages in to it by passing [master, sub_page]. - # This gives us a cheap way of framing pages - - def initialize(*templates) - result = "!INCLUDE!" - templates.each do |content| - result.sub!(/!INCLUDE!/, content) - end - @lines = LineReader.new(result.split($/)) - end - - # Render the templates into HTML, storing the result on +op+ - # using the method <tt><<</tt>. The <tt>value_hash</tt> contains - # key/value pairs used to drive the substitution (as described above) - - def write_html_on(op, value_hash) - @context = Context.new - op << substitute_into(@lines, value_hash).tr("\000", '\\') - end - - - # Substitute a set of key/value pairs into the given template. - # Keys with scalar values have them substituted directly into - # the page. Those with array values invoke <tt>substitute_array</tt> - # (below), which examples a block of the template once for each - # row in the array. - # - # This routine also copes with the <tt>IF:</tt>_key_ directive, - # removing chunks of the template if the corresponding key - # does not appear in the hash, and the START: directive, which - # loops its contents for each value in an array - - def substitute_into(lines, values) - @context.push(values) - skip_to = nil - result = [] - - while line = lines.read - - case line - - when /^IF:(\w+)/ - lines.read_up_to(/^ENDIF:#$1/) unless @context.lookup($1) - - when /^IFNOT:(\w+)/ - lines.read_up_to(/^ENDIF:#$1/) if @context.lookup($1) - - when /^ENDIF:/ - ; - - when /^START:(\w+)/ - tag = $1 - body = lines.read_up_to(/^END:#{tag}/) - inner_values = @context.lookup(tag) - raise "unknown tag: #{tag}" unless inner_values - raise "not array: #{tag}" unless inner_values.kind_of?(Array) - inner_values.each do |vals| - result << substitute_into(body.dup, vals) - end - else - result << expand_line(line.dup) - end - end - - @context.pop - - result.join("\n") - end - - # Given an individual line, we look for %xxx% constructs and - # HREF:ref:name: constructs, substituting for each. - - def expand_line(line) - # Generate a cross reference if a reference is given, - # otherwise just fill in the name part - - line.gsub!(/HREF:(\w+?):(\w+?):/) { - ref = @context.lookup($1) - name = @context.find_scalar($2) - - if ref and !ref.kind_of?(Array) - "<a href=\"#{ref}\">#{name}</a>" - else - name - end - } - - # Substitute in values for %xxx% constructs. This is made complex - # because the replacement string may contain characters that are - # meaningful to the regexp (like \1) - - line = line.gsub(/%([a-zA-Z]\w*)%/) { - val = @context.find_scalar($1) - val.tr('\\', "\000") - } - - - line - rescue Exception => e - $stderr.puts "Error in template: #{e}" - $stderr.puts "Original line: #{line}" - exit - end - -end - diff --git a/lib/rdoc/test_case.rb b/lib/rdoc/test_case.rb new file mode 100644 index 0000000000..22d3f14219 --- /dev/null +++ b/lib/rdoc/test_case.rb @@ -0,0 +1,203 @@ +# frozen_string_literal: true +begin + gem 'minitest', '~> 4.0' unless defined?(Test::Unit) +rescue NoMethodError, Gem::LoadError + # for ruby tests +end + +require 'minitest/autorun' +require 'minitest/benchmark' unless ENV['NOBENCHMARK'] + +require 'fileutils' +require 'pp' +require 'tempfile' +require 'tmpdir' +require 'stringio' + +require 'rdoc' + +## +# RDoc::TestCase is an abstract TestCase to provide common setup and teardown +# across all RDoc tests. The test case uses minitest, so all the assertions +# of minitest may be used. +# +# The testcase provides the following: +# +# * A reset code-object tree +# * A reset markup preprocessor (RDoc::Markup::PreProcess) +# * The <code>@RM</code> alias of RDoc::Markup (for less typing) +# * <code>@pwd</code> containing the current working directory +# * FileUtils, pp, Tempfile, Dir.tmpdir and StringIO + +class RDoc::TestCase < MiniTest::Unit::TestCase + + ## + # Abstract test-case setup + + def setup + super + + @top_level = nil + + @RM = RDoc::Markup + + RDoc::Markup::PreProcess.reset + + @pwd = Dir.pwd + + @store = RDoc::Store.new + + @rdoc = RDoc::RDoc.new + @rdoc.store = @store + @rdoc.options = RDoc::Options.new + + g = Object.new + def g.class_dir() end + def g.file_dir() end + @rdoc.generator = g + end + + ## + # Asserts +path+ is a file + + def assert_file path + assert File.file?(path), "#{path} is not a file" + end + + ## + # Asserts +path+ is a directory + + def assert_directory path + assert File.directory?(path), "#{path} is not a directory" + end + + ## + # Refutes +path+ exists + + def refute_file path + refute File.exist?(path), "#{path} exists" + end + + ## + # Shortcut for RDoc::Markup::BlankLine.new + + def blank_line + @RM::BlankLine.new + end + + ## + # Shortcut for RDoc::Markup::BlockQuote.new with +contents+ + + def block *contents + @RM::BlockQuote.new(*contents) + end + + ## + # Creates an RDoc::Comment with +text+ which was defined on +top_level+. + # By default the comment has the 'rdoc' format. + + def comment text, top_level = @top_level + RDoc::Comment.new text, top_level + end + + ## + # Shortcut for RDoc::Markup::Document.new with +contents+ + + def doc *contents + @RM::Document.new(*contents) + end + + ## + # Shortcut for RDoc::Markup::HardBreak.new + + def hard_break + @RM::HardBreak.new + end + + ## + # Shortcut for RDoc::Markup::Heading.new with +level+ and +text+ + + def head level, text + @RM::Heading.new level, text + end + + ## + # Shortcut for RDoc::Markup::ListItem.new with +label+ and +parts+ + + def item label = nil, *parts + @RM::ListItem.new label, *parts + end + + ## + # Shortcut for RDoc::Markup::List.new with +type+ and +items+ + + def list type = nil, *items + @RM::List.new type, *items + end + + ## + # Enables pretty-print output + + def mu_pp obj # :nodoc: + s = obj.pretty_inspect + s = RDoc::Encoding.change_encoding s, Encoding.default_external + s.chomp + end + + ## + # Shortcut for RDoc::Markup::Paragraph.new with +contents+ + + def para *a + @RM::Paragraph.new(*a) + end + + ## + # Shortcut for RDoc::Markup::Rule.new with +weight+ + + def rule weight + @RM::Rule.new weight + end + + ## + # Shortcut for RDoc::Markup::Raw.new with +contents+ + + def raw *contents + @RM::Raw.new(*contents) + end + + ## + # Creates a temporary directory changes the current directory to it for the + # duration of the block. + # + # Depends upon Dir.mktmpdir + + def temp_dir + Dir.mktmpdir do |temp_dir| + Dir.chdir temp_dir do + yield temp_dir + end + end + end + + ## + # Shortcut for RDoc::Markup::Verbatim.new with +parts+ + + def verb *parts + @RM::Verbatim.new(*parts) + end + + ## + # run capture_io with setting $VERBOSE = true + + def verbose_capture_io + capture_io do + begin + orig_verbose = $VERBOSE + $VERBOSE = true + yield + ensure + $VERBOSE = orig_verbose + end + end + end +end diff --git a/lib/rdoc/text.rb b/lib/rdoc/text.rb new file mode 100644 index 0000000000..7e714be0ad --- /dev/null +++ b/lib/rdoc/text.rb @@ -0,0 +1,298 @@ +# frozen_string_literal: true + +## +# For RDoc::Text#to_html + +require 'strscan' + +## +# Methods for manipulating comment text + +module RDoc::Text + + ## + # Maps markup formats to classes that can parse them. If the format is + # unknown, "rdoc" format is used. + + MARKUP_FORMAT = { + 'markdown' => RDoc::Markdown, + 'rdoc' => RDoc::Markup, + 'rd' => RDoc::RD, + 'tomdoc' => RDoc::TomDoc, + } + + MARKUP_FORMAT.default = RDoc::Markup + + ## + # Maps an encoding to a Hash of characters properly transcoded for that + # encoding. + # + # See also encode_fallback. + + TO_HTML_CHARACTERS = Hash.new do |h, encoding| + h[encoding] = { + :close_dquote => encode_fallback('”', encoding, '"'), + :close_squote => encode_fallback('’', encoding, '\''), + :copyright => encode_fallback('©', encoding, '(c)'), + :ellipsis => encode_fallback('…', encoding, '...'), + :em_dash => encode_fallback('—', encoding, '---'), + :en_dash => encode_fallback('–', encoding, '--'), + :open_dquote => encode_fallback('“', encoding, '"'), + :open_squote => encode_fallback('‘', encoding, '\''), + :trademark => encode_fallback('®', encoding, '(r)'), + } + end + + ## + # Transcodes +character+ to +encoding+ with a +fallback+ character. + + def self.encode_fallback character, encoding, fallback + character.encode(encoding, :fallback => { character => fallback }, + :undef => :replace, :replace => fallback) + end + + ## + # Expands tab characters in +text+ to eight spaces + + def expand_tabs text + expanded = [] + + text.each_line do |line| + nil while line.gsub!(/(?:\G|\r)((?:.{8})*?)([^\t\r\n]{0,7})\t/) do + r = "#{$1}#{$2}#{' ' * (8 - $2.size)}" + r = RDoc::Encoding.change_encoding r, text.encoding + r + end + + expanded << line + end + + expanded.join + end + + ## + # Flush +text+ left based on the shortest line + + def flush_left text + indent = 9999 + + text.each_line do |line| + line_indent = line =~ /\S/ || 9999 + indent = line_indent if indent > line_indent + end + + empty = '' + empty = RDoc::Encoding.change_encoding empty, text.encoding + + text.gsub(/^ {0,#{indent}}/, empty) + end + + ## + # Convert a string in markup format into HTML. + # + # Requires the including class to implement #formatter + + def markup text + if @store.rdoc.options + locale = @store.rdoc.options.locale + else + locale = nil + end + if locale + i18n_text = RDoc::I18n::Text.new(text) + text = i18n_text.translate(locale) + end + parse(text).accept formatter + end + + ## + # Strips hashes, expands tabs then flushes +text+ to the left + + def normalize_comment text + return text if text.empty? + + text = strip_stars text + text = strip_hashes text + text = expand_tabs text + text = flush_left text + text = strip_newlines text + text + end + + ## + # Normalizes +text+ then builds a RDoc::Markup::Document from it + + def parse text, format = 'rdoc' + return text if RDoc::Markup::Document === text + return text.parse if RDoc::Comment === text + + text = normalize_comment text # TODO remove, should not be necessary + + return RDoc::Markup::Document.new if text =~ /\A\n*\z/ + + MARKUP_FORMAT[format].parse text + end + + ## + # The first +limit+ characters of +text+ as HTML + + def snippet text, limit = 100 + document = parse text + + RDoc::Markup::ToHtmlSnippet.new(options, limit).convert document + end + + ## + # Strips leading # characters from +text+ + + def strip_hashes text + return text if text =~ /^(?>\s*)[^\#]/ + + empty = '' + empty = RDoc::Encoding.change_encoding empty, text.encoding + + text.gsub(/^\s*(#+)/) { $1.tr '#', ' ' }.gsub(/^\s+$/, empty) + end + + ## + # Strips leading and trailing \n characters from +text+ + + def strip_newlines text + text.gsub(/\A\n*(.*?)\n*\z/m) do $1 end # block preserves String encoding + end + + ## + # Strips /* */ style comments + + def strip_stars text + return text unless text =~ %r%/\*.*\*/%m + + encoding = text.encoding + + text = text.gsub %r%Document-method:\s+[\w:.#=!?]+%, '' + + space = ' ' + space = RDoc::Encoding.change_encoding space, encoding if encoding + + text.sub! %r%/\*+% do space * $&.length end + text.sub! %r%\*+/% do space * $&.length end + text.gsub! %r%^[ \t]*\*%m do space * $&.length end + + empty = '' + empty = RDoc::Encoding.change_encoding empty, encoding if encoding + text.gsub(/^\s+$/, empty) + end + + ## + # Converts ampersand, dashes, ellipsis, quotes, copyright and registered + # trademark symbols in +text+ to properly encoded characters. + + def to_html text + html = (''.encode text.encoding).dup + + encoded = RDoc::Text::TO_HTML_CHARACTERS[text.encoding] + + s = StringScanner.new text + insquotes = false + indquotes = false + after_word = nil + + until s.eos? do + case + when s.scan(/<(tt|code)>.*?<\/\1>/) then # skip contents of tt + html << s.matched.gsub('\\\\', '\\') + when s.scan(/<(tt|code)>.*?/) then + warn "mismatched <#{s[1]}> tag" # TODO signal file/line + html << s.matched + when s.scan(/<[^>]+\/?s*>/) then # skip HTML tags + html << s.matched + when s.scan(/\\(\S)/) then # unhandled suppressed crossref + html << s[1] + after_word = nil + when s.scan(/\.\.\.(\.?)/) then + html << s[1] << encoded[:ellipsis] + after_word = nil + when s.scan(/\(c\)/) then + html << encoded[:copyright] + after_word = nil + when s.scan(/\(r\)/) then + html << encoded[:trademark] + after_word = nil + when s.scan(/---/) then + html << encoded[:em_dash] + after_word = nil + when s.scan(/--/) then + html << encoded[:en_dash] + after_word = nil + when s.scan(/"|"/) then + html << encoded[indquotes ? :close_dquote : :open_dquote] + indquotes = !indquotes + after_word = nil + when s.scan(/``/) then # backtick double quote + html << encoded[:open_dquote] + after_word = nil + when s.scan(/''/) then # tick double quote + html << encoded[:close_dquote] + after_word = nil + when s.scan(/'/) then # single quote + if insquotes + html << encoded[:close_squote] + insquotes = false + elsif after_word + # Mary's dog, my parents' house: do not start paired quotes + html << encoded[:close_squote] + else + html << encoded[:open_squote] + insquotes = true + end + + after_word = nil + else # advance to the next potentially significant character + match = s.scan(/.+?(?=[<\\.("'`&-])/) #" + + if match then + html << match + after_word = match =~ /\w$/ + else + html << s.rest + break + end + end + end + + html + end + + ## + # Wraps +txt+ to +line_len+ + + 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.join.strip + end + +end diff --git a/lib/rdoc/token_stream.rb b/lib/rdoc/token_stream.rb new file mode 100644 index 0000000000..05fb46e89a --- /dev/null +++ b/lib/rdoc/token_stream.rb @@ -0,0 +1,114 @@ +# frozen_string_literal: true +## +# A TokenStream is a list of tokens, gathered during the parse of some entity +# (say a method). Entities populate these streams by being registered with the +# lexer. Any class can collect tokens by including TokenStream. From the +# outside, you use such an object by calling the start_collecting_tokens +# method, followed by calls to add_token and pop_token. + +module RDoc::TokenStream + + ## + # Converts +token_stream+ to HTML wrapping various tokens with + # <tt><span></tt> elements. Some tokens types are wrapped in spans + # with the given class names. Other token types are not wrapped in spans. + + def self.to_html token_stream + starting_title = false + + token_stream.map do |t| + next unless t + + style = case t[:kind] + when :on_const then 'ruby-constant' + when :on_kw then 'ruby-keyword' + when :on_ivar then 'ruby-ivar' + when :on_cvar then 'ruby-identifier' + when :on_gvar then 'ruby-identifier' + when '=' != t[:text] && :on_op + then 'ruby-operator' + when :on_tlambda then 'ruby-operator' + when :on_ident then 'ruby-identifier' + when :on_label then 'ruby-value' + when :on_backref, :on_dstring + then 'ruby-node' + when :on_comment then 'ruby-comment' + when :on_embdoc then 'ruby-comment' + when :on_regexp then 'ruby-regexp' + when :on_tstring then 'ruby-string' + when :on_int, :on_float, + :on_rational, :on_imaginary, + :on_heredoc, + :on_symbol, :on_CHAR then 'ruby-value' + when :on_heredoc_beg, :on_heredoc_end + then 'ruby-identifier' + end + + comment_with_nl = false + if :on_comment == t[:kind] or :on_embdoc == t[:kind] or :on_heredoc_end == t[:kind] + comment_with_nl = true if "\n" == t[:text][-1] + text = t[:text].rstrip + else + text = t[:text] + end + + if :on_ident == t[:kind] && starting_title + starting_title = false + style = 'ruby-identifier ruby-title' + end + + if :on_kw == t[:kind] and 'def' == t[:text] + starting_title = true + end + + text = CGI.escapeHTML text + + if style then + "<span class=\"#{style}\">#{text}</span>#{"\n" if comment_with_nl}" + else + text + end + end.join + end + + ## + # Adds +tokens+ to the collected tokens + + def add_tokens(*tokens) + tokens.flatten.each { |token| @token_stream << token } + end + + alias add_token add_tokens + + ## + # Starts collecting tokens + + def collect_tokens + @token_stream = [] + end + + alias start_collecting_tokens collect_tokens + + ## + # Remove the last token from the collected tokens + + def pop_token + @token_stream.pop + end + + ## + # Current token stream + + def token_stream + @token_stream + end + + ## + # Returns a string representation of the token stream + + def tokens_to_s + token_stream.compact.map { |token| token.text }.join '' + end + +end + diff --git a/lib/rdoc/tokenstream.rb b/lib/rdoc/tokenstream.rb deleted file mode 100644 index 0a0720d8a9..0000000000 --- a/lib/rdoc/tokenstream.rb +++ /dev/null @@ -1,25 +0,0 @@ -# A TokenStream is a list of tokens, gathered during the parse -# of some entity (say a method). Entities populate these streams -# by being registered with the lexer. Any class can collect tokens -# by including TokenStream. From the outside, you use such an object -# by calling the start_collecting_tokens method, followed by calls -# to add_token and pop_token - -module TokenStream - def token_stream - @token_stream - end - - def start_collecting_tokens - @token_stream = [] - end - def add_token(tk) - @token_stream << tk - end - def add_tokens(tks) - tks.each {|tk| add_token(tk)} - end - def pop_token - @token_stream.pop - end -end diff --git a/lib/rdoc/tom_doc.rb b/lib/rdoc/tom_doc.rb new file mode 100644 index 0000000000..2b594b7d84 --- /dev/null +++ b/lib/rdoc/tom_doc.rb @@ -0,0 +1,258 @@ +# frozen_string_literal: true +# :markup: tomdoc + +# A parser for TomDoc based on TomDoc 1.0.0-rc1 (02adef9b5a) +# +# The TomDoc specification can be found at: +# +# http://tomdoc.org +# +# The latest version of the TomDoc specification can be found at: +# +# https://github.com/mojombo/tomdoc/blob/master/tomdoc.md +# +# To choose TomDoc as your only default format see RDoc::Options@Saved+Options +# for instructions on setting up a <code>.rdoc_options</code> file to store +# your project default. +# +# There are a few differences between this parser and the specification. A +# best-effort was made to follow the specification as closely as possible but +# some choices to deviate were made. +# +# A future version of RDoc will warn when a MUST or MUST NOT is violated and +# may warn when a SHOULD or SHOULD NOT is violated. RDoc will always try +# to emit documentation even if given invalid TomDoc. +# +# Here are some implementation choices this parser currently makes: +# +# This parser allows rdoc-style inline markup but you should not depended on +# it. +# +# This parser allows a space between the comment and the method body. +# +# This parser does not require the default value to be described for an +# optional argument. +# +# This parser does not examine the order of sections. An Examples section may +# precede the Arguments section. +# +# This class is documented in TomDoc format. Since this is a subclass of the +# RDoc markup parser there isn't much to see here, unfortunately. + +class RDoc::TomDoc < RDoc::Markup::Parser + + # Internal: Token accessor + + attr_reader :tokens + + # Internal: Adds a post-processor which sets the RDoc section based on the + # comment's status. + # + # Returns nothing. + + def self.add_post_processor # :nodoc: + RDoc::Markup::PreProcess.post_process do |comment, code_object| + next unless code_object and + RDoc::Comment === comment and comment.format == 'tomdoc' + + comment.text.gsub!(/(\A\s*# )(Public|Internal|Deprecated):\s+/) do + section = code_object.add_section $2 + code_object.temporary_section = section + + $1 + end + end + end + + add_post_processor + + # Public: Parses TomDoc from text + # + # text - A String containing TomDoc-format text. + # + # Examples + # + # RDoc::TomDoc.parse <<-TOMDOC + # This method does some things + # + # Returns nothing. + # TOMDOC + # # => #<RDoc::Markup::Document:0xXXX @parts=[...], @file=nil> + # + # Returns an RDoc::Markup::Document representing the TomDoc format. + + def self.parse text + parser = new + + parser.tokenize text + doc = RDoc::Markup::Document.new + parser.parse doc + doc + end + + # Internal: Extracts the Signature section's method signature + # + # comment - An RDoc::Comment that will be parsed and have the signature + # extracted + # + # Returns a String containing the signature and nil if not + + def self.signature comment + return unless comment.tomdoc? + + document = comment.parse + + signature = nil + found_heading = false + found_signature = false + + document.parts.delete_if do |part| + next false if found_signature + + found_heading ||= + RDoc::Markup::Heading === part && part.text == 'Signature' + + next false unless found_heading + + next true if RDoc::Markup::BlankLine === part + + if RDoc::Markup::Verbatim === part then + signature = part + found_signature = true + end + end + + signature and signature.text + end + + # Public: Creates a new TomDoc parser. See also RDoc::Markup::parse + + def initialize + super + + @section = nil + @seen_returns = false + end + + # Internal: Builds a heading from the token stream + # + # level - The level of heading to create + # + # Returns an RDoc::Markup::Heading + + def build_heading level + heading = super + + @section = heading.text + + heading + end + + # Internal: Builds a verbatim from the token stream. A verbatim in the + # Examples section will be marked as in Ruby format. + # + # margin - The indentation from the margin for lines that belong to this + # verbatim section. + # + # Returns an RDoc::Markup::Verbatim + + def build_verbatim margin + verbatim = super + + verbatim.format = :ruby if @section == 'Examples' + + verbatim + end + + # Internal: Builds a paragraph from the token stream + # + # margin - Unused + # + # Returns an RDoc::Markup::Paragraph. + + def build_paragraph margin + p :paragraph_start => margin if @debug + + paragraph = RDoc::Markup::Paragraph.new + + until @tokens.empty? do + type, data, = get + + case type + when :TEXT then + @section = 'Returns' if data =~ /\AReturns/ + + paragraph << data + when :NEWLINE then + if :TEXT == peek_token[0] then + paragraph << ' ' + else + break + end + else + unget + break + end + end + + p :paragraph_end => margin if @debug + + paragraph + end + + ## + # Detects a section change to "Returns" and adds a heading + + def parse_text parent, indent # :nodoc: + paragraph = build_paragraph indent + + if false == @seen_returns and 'Returns' == @section then + @seen_returns = true + parent << RDoc::Markup::Heading.new(3, 'Returns') + parent << RDoc::Markup::BlankLine.new + end + + parent << paragraph + end + + # Internal: Turns text into an Array of tokens + # + # text - A String containing TomDoc-format text. + # + # Returns self. + + def tokenize text + text = text.sub(/\A(Public|Internal|Deprecated):\s+/, '') + + setup_scanner text + + until @s.eos? do + pos = @s.pos + + # leading spaces will be reflected by the column of the next token + # the only thing we loose are trailing spaces at the end of the file + next if @s.scan(/ +/) + + @tokens << case + when @s.scan(/\r?\n/) then + token = [:NEWLINE, @s.matched, *token_pos(pos)] + @line_pos = char_pos @s.pos + @line += 1 + token + when @s.scan(/(Examples|Signature)$/) then + @tokens << [:HEADER, 3, *token_pos(pos)] + + [:TEXT, @s[1], *token_pos(pos)] + when @s.scan(/([:\w][\w\[\]]*)[ ]+- /) then + [:NOTE, @s[1], *token_pos(pos)] + else + @s.scan(/.*/) + [:TEXT, @s.matched.sub(/\r$/, ''), *token_pos(pos)] + end + end + + self + end + +end + diff --git a/lib/rdoc/top_level.rb b/lib/rdoc/top_level.rb new file mode 100644 index 0000000000..6186722772 --- /dev/null +++ b/lib/rdoc/top_level.rb @@ -0,0 +1,283 @@ +# frozen_string_literal: true +## +# A TopLevel context is a representation of the contents of a single file + +class RDoc::TopLevel < RDoc::Context + + MARSHAL_VERSION = 0 # :nodoc: + + ## + # This TopLevel's File::Stat struct + + attr_accessor :file_stat + + ## + # Relative name of this file + + attr_accessor :relative_name + + ## + # Absolute name of this file + + attr_accessor :absolute_name + + ## + # All the classes or modules that were declared in + # this file. These are assigned to either +#classes_hash+ + # or +#modules_hash+ once we know what they really are. + + attr_reader :classes_or_modules + + attr_accessor :diagram # :nodoc: + + ## + # The parser class that processed this file + + attr_accessor :parser + + ## + # Creates a new TopLevel for the file at +absolute_name+. If documentation + # is being generated outside the source dir +relative_name+ is relative to + # the source directory. + + def initialize absolute_name, relative_name = absolute_name + super() + @name = nil + @absolute_name = absolute_name + @relative_name = relative_name + @file_stat = File.stat(absolute_name) rescue nil # HACK for testing + @diagram = nil + @parser = nil + + @classes_or_modules = [] + end + + ## + # An RDoc::TopLevel is equal to another with the same relative_name + + def == other + self.class === other and @relative_name == other.relative_name + end + + alias eql? == + + ## + # Adds +an_alias+ to +Object+ instead of +self+. + + def add_alias(an_alias) + object_class.record_location self + return an_alias unless @document_self + object_class.add_alias an_alias + end + + ## + # Adds +constant+ to +Object+ instead of +self+. + + def add_constant constant + object_class.record_location self + return constant unless @document_self + object_class.add_constant constant + end + + ## + # Adds +include+ to +Object+ instead of +self+. + + def add_include(include) + object_class.record_location self + return include unless @document_self + object_class.add_include include + end + + ## + # Adds +method+ to +Object+ instead of +self+. + + def add_method(method) + object_class.record_location self + return method unless @document_self + object_class.add_method method + end + + ## + # Adds class or module +mod+. Used in the building phase + # by the Ruby parser. + + def add_to_classes_or_modules mod + @classes_or_modules << mod + end + + ## + # Base name of this file + + def base_name + File.basename @relative_name + end + + alias name base_name + + ## + # Only a TopLevel that contains text file) will be displayed. See also + # RDoc::CodeObject#display? + + def display? + text? and super + end + + ## + # See RDoc::TopLevel::find_class_or_module + #-- + # TODO Why do we search through all classes/modules found, not just the + # ones of this instance? + + def find_class_or_module name + @store.find_class_or_module name + end + + ## + # Finds a class or module named +symbol+ + + def find_local_symbol(symbol) + find_class_or_module(symbol) || super + end + + ## + # Finds a module or class with +name+ + + def find_module_named(name) + find_class_or_module(name) + end + + ## + # Returns the relative name of this file + + def full_name + @relative_name + end + + ## + # An RDoc::TopLevel has the same hash as another with the same + # relative_name + + def hash + @relative_name.hash + end + + ## + # URL for this with a +prefix+ + + def http_url(prefix) + path = [prefix, @relative_name.tr('.', '_')] + + File.join(*path.compact) + '.html' + end + + def inspect # :nodoc: + "#<%s:0x%x %p modules: %p classes: %p>" % [ + self.class, object_id, + base_name, + @modules.map { |n,m| m }, + @classes.map { |n,c| c } + ] + end + + ## + # Time this file was last modified, if known + + def last_modified + @file_stat ? file_stat.mtime : nil + end + + ## + # Dumps this TopLevel for use by ri. See also #marshal_load + + def marshal_dump + [ + MARSHAL_VERSION, + @relative_name, + @parser, + parse(@comment), + ] + end + + ## + # Loads this TopLevel from +array+. + + def marshal_load array # :nodoc: + initialize array[1] + + @parser = array[2] + @comment = array[3] + + @file_stat = nil + end + + ## + # Returns the NormalClass "Object", creating it if not found. + # + # Records +self+ as a location in "Object". + + def object_class + @object_class ||= begin + oc = @store.find_class_named('Object') || add_class(RDoc::NormalClass, 'Object') + oc.record_location self + oc + end + end + + ## + # Base name of this file without the extension + + def page_name + basename = File.basename @relative_name + basename =~ /\.(rb|rdoc|txt|md)$/i + + $` || basename + end + + ## + # Path to this file for use with HTML generator output. + + def path + http_url @store.rdoc.generator.file_dir + end + + def pretty_print q # :nodoc: + q.group 2, "[#{self.class}: ", "]" do + q.text "base name: #{base_name.inspect}" + q.breakable + + items = @modules.map { |n,m| m } + items.concat @modules.map { |n,c| c } + q.seplist items do |mod| q.pp mod end + end + end + + ## + # Search record used by RDoc::Generator::JsonIndex + + def search_record + return unless @parser < RDoc::Parser::Text + + [ + page_name, + '', + page_name, + '', + path, + '', + snippet(@comment), + ] + end + + ## + # Is this TopLevel from a text file instead of a source code file? + + def text? + @parser and @parser.ancestors.include? RDoc::Parser::Text + end + + def to_s # :nodoc: + "file #{full_name}" + end + +end + diff --git a/lib/rdoc/usage.rb b/lib/rdoc/usage.rb deleted file mode 100644 index def516b3d7..0000000000 --- a/lib/rdoc/usage.rb +++ /dev/null @@ -1,210 +0,0 @@ -# = Synopsis -# -# This library allows command-line tools to encapsulate their usage -# as a comment at the top of the main file. Calling <tt>RDoc::usage</tt> -# then displays some or all of that comment, and optionally exits -# the program with an exit status. We always look for the comment -# in the main program file, so it is safe to call this method -# from anywhere in the executing program. -# -# = Usage -# -# RDoc::usage( [ exit_status ], [ section, ...]) -# RDoc::usage_no_exit( [ section, ...]) -# -# where: -# -# exit_status:: -# the integer exit code (default zero). RDoc::usage will exit -# the calling program with this status. -# -# section:: -# an optional list of section names. If specified, only the -# sections with the given names as headings will be output. -# For example, this section is named 'Usage', and the next -# section is named 'Examples'. The section names are case -# insensitive. -# -# = Examples -# -# # Comment block describing usage -# # with (optional) section headings -# # . . . -# -# require 'rdoc/usage' -# -# # Display all usage and exit with a status of 0 -# -# RDoc::usage -# -# # Display all usage and exit with a status of 99 -# -# RDoc::usage(99) -# -# # Display usage in the 'Summary' section only, then -# # exit with a status of 99 -# -# RDoc::usage(99, 'Summary') -# -# # Display information in the Author and Copyright -# # sections, then exit 0. -# -# RDoc::usage('Author', 'Copyright') -# -# # Display information in the Author and Copyright -# # sections, but don't exit -# -# RDoc::usage_no_exit('Author', 'Copyright') -# -# = Author -# -# Dave Thomas, The Pragmatic Programmers, LLC -# -# = Copyright -# -# Copyright (c) 2004 Dave Thomas. -# Licensed under the same terms as Ruby -# - -require 'rdoc/markup/simple_markup' -require 'rdoc/markup/simple_markup/to_flow' -require 'rdoc/ri/ri_formatter' -require 'rdoc/ri/ri_options' - -module RDoc - - # Display usage information from the comment at the top of - # the file. String arguments identify specific sections of the - # comment to display. An optional integer first argument - # specifies the exit status (defaults to 0) - - def RDoc.usage(*args) - exit_code = 0 - - if args.size > 0 - status = args[0] - if status.respond_to?(:to_int) - exit_code = status.to_int - args.shift - end - end - - # display the usage and exit with the given code - usage_no_exit(*args) - exit(exit_code) - end - - # Display usage - def RDoc.usage_no_exit(*args) - main_program_file = caller[-1].sub(/:\d+$/, '') - comment = File.open(main_program_file) do |file| - find_comment(file) - end - - comment = comment.gsub(/^\s*#/, '') - - markup = SM::SimpleMarkup.new - flow_convertor = SM::ToFlow.new - - flow = markup.convert(comment, flow_convertor) - - format = "plain" - - unless args.empty? - flow = extract_sections(flow, args) - end - - options = RI::Options.instance - if args = ENV["RI"] - options.parse(args.split) - end - formatter = options.formatter.new(options, "") - formatter.display_flow(flow) - end - - ###################################################################### - - private - - # Find the first comment in the file (that isn't a shebang line) - # If the file doesn't start with a comment, report the fact - # and return empty string - - def RDoc.gets(file) - if (line = file.gets) && (line =~ /^#!/) # shebang - throw :exit, find_comment(file) - else - line - end - end - - def RDoc.find_comment(file) - catch(:exit) do - # skip leading blank lines - 0 while (line = gets(file)) && (line =~ /^\s*$/) - - comment = [] - while line && line =~ /^\s*#/ - comment << line - line = gets(file) - end - - 0 while line && (line = gets(file)) - return no_comment if comment.empty? - return comment.join - end - end - - - ##### - # Given an array of flow items and an array of section names, extract those - # sections from the flow which have headings corresponding to - # a section name in the list. Return them in the order - # of names in the +sections+ array. - - def RDoc.extract_sections(flow, sections) - result = [] - sections.each do |name| - name = name.downcase - copy_upto_level = nil - - flow.each do |item| - case item - when SM::Flow::H - if copy_upto_level && item.level >= copy_upto_level - copy_upto_level = nil - else - if item.text.downcase == name - result << item - copy_upto_level = item.level - end - end - else - if copy_upto_level - result << item - end - end - end - end - if result.empty? - puts "Note to developer: requested section(s) [#{sections.join(', ')}] " + - "not found" - result = flow - end - result - end - - ##### - # Report the fact that no doc comment count be found - def RDoc.no_comment - $stderr.puts "No usage information available for this program" - "" - end -end - - -if $0 == __FILE__ - - RDoc::usage(*ARGV) - -end |
