Changeset 86 for trunk/lib/bluecloth.rb

Show
Ignore:
Timestamp:
08/20/08 14:46:31 (5 months ago)
Author:
deveiant
Message:

Adding MarkdownTest? tests, new build system. [broken build]

Location:
trunk
Files:
2 modified

Legend:

Unmodified
Added
Removed
  • trunk

    • Property svn:externals set to
  • trunk/lib/bluecloth.rb

    r69 r86  
    66# == Synopsis 
    77#  
    8 #   doc = BlueCloth::new " 
     8#   doc = BlueCloth.new " 
    99#     ## Test document ## 
    1010# 
     
    2626# 
    2727# Original version: 
    28 #   Copyright (c) 2003-2004 John Gruber 
     28#   Copyright (c) 2004, 2005, John Gruber   
    2929#   <http://daringfireball.net/>   
    3030#   All rights reserved. 
    3131# 
     32#   Redistribution and use in source and binary forms, with or without 
     33#   modification, are permitted provided that the following conditions are 
     34#   met: 
     35#    
     36#   * Redistributions of source code must retain the above copyright notice, 
     37#     this list of conditions and the following disclaimer. 
     38#    
     39#   * Redistributions in binary form must reproduce the above copyright 
     40#     notice, this list of conditions and the following disclaimer in the 
     41#     documentation and/or other materials provided with the distribution. 
     42#    
     43#   * Neither the name "Markdown" nor the names of its contributors may 
     44#     be used to endorse or promote products derived from this software 
     45#     without specific prior written permission. 
     46#    
     47#   This software is provided by the copyright holders and contributors "as 
     48#   is" and any express or implied warranties, including, but not limited 
     49#   to, the implied warranties of merchantability and fitness for a 
     50#   particular purpose are disclaimed. In no event shall the copyright owner 
     51#   or contributors be liable for any direct, indirect, incidental, special, 
     52#   exemplary, or consequential damages (including, but not limited to, 
     53#   procurement of substitute goods or services; loss of use, data, or 
     54#   profits; or business interruption) however caused and on any theory of 
     55#   liability, whether in contract, strict liability, or tort (including 
     56#   negligence or otherwise) arising in any way out of the use of this 
     57#   software, even if advised of the possibility of such damage. 
     58# 
    3259# Ruby port: 
    33 #   Copyright (c) 2004 The FaerieMUD Consortium. 
     60#   Copyright (c) 2004, 2005 The FaerieMUD Consortium. 
    3461#  
    35 # BlueCloth is free software; you can redistribute it and/or modify it under the 
    36 # terms of the GNU General Public License as published by the Free Software 
    37 # Foundation; either version 2 of the License, or (at your option) any later 
    38 # version. 
    39 #  
    40 # BlueCloth is distributed in the hope that it will be useful, but WITHOUT ANY 
    41 # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR 
    42 # A PARTICULAR PURPOSE.  See the GNU General Public License for more details. 
     62#   You may use, modify, and/or redistribute this software under the same terms 
     63#   as Ruby itself. A copy of Ruby's license should be included in this package; 
     64#   if not, it can be obtained online at: 
     65#     http://www.ruby-lang.org/en/LICENSE.txt. 
     66#    
     67#   THIS PACKAGE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR IMPLIED 
     68#   WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF 
     69#   MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE. 
    4370#  
    4471# == To-do 
     
    83110 
    84111    # Release Version 
    85     Version = '0.0.3' 
     112    VERSION = '1.1.0' 
    86113 
    87114    # SVN Revision 
    88     SvnRev = %q$Rev$ 
     115    SVNREV = %q$Rev$ 
    89116 
    90117    # SVN Id tag 
    91     SvnId = %q$Id$ 
    92  
    93     # SVN URL 
    94     SvnUrl = %q$URL$ 
     118    SVNID = %q$Id$ 
     119 
    95120 
    96121 
     
    98123    # midway through a render. I prefer this to the globals of the Perl version 
    99124    # because globals make me break out in hives. Or something. 
    100     RenderState = Struct::new( "RenderState", :urls, :titles, :html_blocks, :log ) 
     125    RenderState = Struct.new( "RenderState", :urls, :titles, :html_blocks, :list_level, :log ) 
    101126 
    102127    # Tab width for #detab! if none is specified 
    103     TabWidth = 4 
     128    # :TODO: Make this DEFAULT_TAB_WIDTH and make tab width a per-instance setting instead. 
     129    TAB_WIDTH = 4 
     130    LESS_THAN_TAB_WIDTH = TAB_WIDTH - 1 
    104131 
    105132    # The tag-closing string -- set to '>' for HTML 
    106     EmptyElementSuffix = "/>"; 
     133    EMPTY_ELEMENT_SUFFIX = "/>"; 
    107134 
    108135    # Table of MD5 sums for escaped characters 
    109     EscapeTable = {} 
    110     '\\`*_{}[]()#.!'.split(//).each {|char| 
    111         hash = Digest::MD5::hexdigest( char ) 
    112  
    113         EscapeTable[ char ] = { 
     136    ESCAPE_TABLE = {} 
     137    '\\`*_{}[]()>#+-.!'.split(//).each {|char| 
     138        hash = Digest::MD5.hexdigest( char ) 
     139 
     140        ESCAPE_TABLE[ char ] = { 
    114141            :md5 => hash, 
    115             :md5re => Regexp::new( hash ), 
    116             :re  => Regexp::new( '\\\\' + Regexp::escape(char) ), 
     142            :md5re => Regexp.new( hash ), 
     143            :re  => Regexp.new( '\\\\' + Regexp.escape(char) ), 
    117144        } 
    118145    } 
     
    125152    ### Create a new BlueCloth string. 
    126153    def initialize( content="", *restrictions ) 
    127         @log = Logger::new( $deferr ) 
     154        @log = Logger.new( $deferr ) 
    128155        @log.level = $DEBUG ? 
    129156            Logger::DEBUG : 
     
    164191 
    165192        # Create a StringScanner we can reuse for various lexing tasks 
    166         @scanner = StringScanner::new( '' ) 
     193        @scanner = StringScanner.new( '' ) 
    167194 
    168195        # Make a structure to carry around stuff that gets placeholdered out of 
    169196        # the source. 
    170         rs = RenderState::new( {}, {}, {} ) 
     197        rs = RenderState.new( {}, {}, {}, 0 ) 
    171198 
    172199        # Make a copy of the string with normalized line endings, tabs turned to 
     
    214241 
    215242    ### Convert tabs in +str+ to spaces. 
    216     def detab( tabwidth=TabWidth ) 
     243    def detab( tabwidth=TAB_WIDTH ) 
    217244        copy = self.dup 
    218245        copy.detab!( tabwidth ) 
     
    222249 
    223250    ### Convert tabs to spaces in place and return self if any were converted. 
    224     def detab!( tabwidth=TabWidth ) 
     251    def detab!( tabwidth=TAB_WIDTH ) 
    225252        newstr = self.split( /\n/ ).collect {|line| 
    226253            line.gsub( /(.*?)\t/ ) do 
     
    247274        text = transform_code_blocks( text, rs ) 
    248275        text = transform_block_quotes( text, rs ) 
    249         text = transform_auto_links( text, rs ) 
    250276        text = hide_html_blocks( text, rs ) 
    251277 
     
    263289 
    264290        str = transform_code_spans( str, rs ) 
    265         str = encode_html( str ) 
     291        str = escape_special_chars( str ) 
    266292        str = transform_images( str, rs ) 
    267293        str = transform_anchors( str, rs ) 
     294        str = transform_auto_links( str, rs ) 
     295        str = encode_html( str ) 
    268296        str = transform_italic_and_bold( str, rs ) 
    269297 
    270298        # Hard breaks 
    271         str.gsub!( / {2,}\n/, "<br#{EmptyElementSuffix}\n" ) 
     299        str.gsub!( / {2,}\n/, "<br#{EMPTY_ELEMENT_SUFFIX}\n" ) 
    272300 
    273301        @log.debug "Done with span transforms:\n  %p" % str 
     
    320348        ) 
    321349        (                       # save in $2 
    322             [ ]*                # Any spaces 
     350            [ ]{0,#{LESS_THAN_TAB_WIDTH}}               # Any spaces 
    323351            <hr                 # Tag open 
    324352            \b                  # Word break 
     
    329357      }ix 
    330358 
     359    # Special case for standalone HTML comments 
     360    CommentBlockRegex = %r{ 
     361        (                       # $1 
     362            \A\n?               # Start of doc + optional \n 
     363            |                   # or 
     364            .*\n\n              # anything + blank line 
     365        ) 
     366        (                       # save in $2 
     367            [ ]{0,#{LESS_THAN_TAB_WIDTH}}               # Any spaces 
     368            (?: 
     369                <! 
     370                (--.*?--\s*)+ 
     371                > 
     372            ) 
     373            $                   # followed by a blank line or end of document 
     374        ) 
     375      }ix 
     376 
    331377    ### Replace all blocks of HTML in +str+ that start in the left margin with 
    332378    ### tokens. 
     
    336382        # Tokenizer proc to pass to gsub 
    337383        tokenize = lambda {|match| 
    338             key = Digest::MD5::hexdigest( match ) 
     384            key = Digest::MD5.hexdigest( match ) 
    339385            rs.html_blocks[ key ] = match 
    340386            @log.debug "Replacing %p with %p" % [ match, key ] 
     
    353399        rval.gsub!( HruleBlockRegex ) {|match| $1 + tokenize[$2] } 
    354400 
     401        @log.debug "Finding comments..." 
     402        rval.gsub!( CommentBlockRegex ) {|match| $1 + tokenize[$2] } 
     403 
    355404        return rval 
    356405    end 
     
    359408    # Link defs are in the form: ^[id]: url "optional title" 
    360409    LinkRegex = %r{ 
    361         ^[ ]*\[(.+)\]:      # id = $1 
     410        ^[ ]{0,#{LESS_THAN_TAB_WIDTH}}\[(.+)\]:     # id = $1 
    362411          [ ]* 
    363412          \n?               # maybe *one* newline 
     
    397446        text = '' 
    398447 
    399         # The original Markdown source has something called '$tags_to_skip' 
    400         # declared here, but it's never used, so I don't define it. 
    401  
     448        # Split the HTML into tags and text, calling back into this block for 
     449        # each chunk. 
    402450        tokenize_html( str ) {|token, str| 
    403451            @log.debug "   Adding %p token %p" % [ token, str ] 
     
    407455            when :tag 
    408456                text += str. 
    409                     gsub( /\*/, EscapeTable['*'][:md5] ). 
    410                     gsub( /_/, EscapeTable['_'][:md5] ) 
     457                    gsub( /\*/, ESCAPE_TABLE['*'][:md5] ). 
     458                    gsub( /_/, ESCAPE_TABLE['_'][:md5] ) 
    411459 
    412460            # Encode backslashed stuff in regular text 
     
    426474    ### it. 
    427475    def unescape_special_chars( str ) 
    428         EscapeTable.each {|char, hash| 
     476        ESCAPE_TABLE.each {|char, hash| 
    429477            @log.debug "Unescaping escaped %p with %p" % [ char, hash[:md5re] ] 
    430478            str.gsub!( hash[:md5re], char ) 
     
    439487    def encode_backslash_escapes( str ) 
    440488        # Make a copy with any double-escaped backslashes encoded 
    441         text = str.gsub( /\\\\/, EscapeTable['\\'][:md5] ) 
     489        text = str.gsub( /\\\\/, ESCAPE_TABLE['\\'][:md5] ) 
    442490         
    443         EscapeTable.each_pair {|char, esc| 
     491        ESCAPE_TABLE.each_pair {|char, esc| 
    444492            next if char == '\\' 
    445493            text.gsub!( esc[:re], esc[:md5] ) 
     
    454502    def transform_hrules( str, rs ) 
    455503        @log.debug " Transforming horizontal rules" 
    456         str.gsub( /^( ?[\-\*_] ?){3,}$/, "\n<hr#{EmptyElementSuffix}\n" ) 
     504        str.gsub( /^[ ]{0,2}( ?[\-\*_] ?){3,} *$/, "\n<hr#{EMPTY_ELEMENT_SUFFIX}\n" ) 
    457505    end 
    458506 
     
    462510    ListMarkerOl = %r{\d+\.} 
    463511    ListMarkerUl = %r{[*+-]} 
    464     ListMarkerAny = Regexp::union( ListMarkerOl, ListMarkerUl ) 
    465  
    466     ListRegexp = %r{ 
    467           (?: 
    468             ^[ ]{0,#{TabWidth - 1}}     # Indent < tab width 
    469             (#{ListMarkerAny})          # unordered or ordered ($1) 
    470             [ ]+                        # At least one space 
    471           ) 
    472           (?m:.+?)                      # item content (include newlines) 
    473           (?: 
    474               \z                        # Either EOF 
    475             |                           #  or 
    476               \n{2,}                    # Blank line... 
    477               (?=\S)                    # ...followed by non-space 
    478               (?![ ]*                   # ...but not another item 
    479                 (#{ListMarkerAny}) 
    480                [ ]+) 
    481           ) 
     512    ListMarkerAny = Regexp.union( ListMarkerOl, ListMarkerUl ) 
     513 
     514    # Part of list-pattern common to both first-level and n-level lists 
     515    ListBodyPattern = %Q{ 
     516        (?: 
     517          [ ]{0,#{LESS_THAN_TAB_WIDTH}} # Indent < tab width 
     518          (#{ListMarkerAny})            # $3 (see below): unordered or ordered 
     519          [ ]+                          # At least one space 
     520        ) 
     521        (?m:.+?)                        # item content (include newlines) 
     522        (?: 
     523            \\z                         # Either EOF 
     524          |                             #  or 
     525            \\n{2,}                     # Blank line... 
     526            (?=\S)                      # ...followed by non-space 
     527            (?![ ]*                     # ...but not another item 
     528              (#{ListMarkerAny}) 
     529             [ ]+) 
     530        ) 
     531      } 
     532 
     533    # Regexp to match first-level lists 
     534    OuterListRegexp = %r{ 
     535        (                               # $1 
     536            \A\n?                       # Start of doc + optional \n 
     537            |                           # or 
     538            .*\n\n                      # anything + blank line 
     539        ) 
     540        (#{ListBodyPattern})            # $2 
     541      }x 
     542 
     543    # Regexp to match n-level lists 
     544    InnerListRegexp = %r{ 
     545        (^)                             # $1 
     546        (#{ListBodyPattern})            # $2 
    482547      }x 
    483548 
     
    487552        @log.debug " Transforming lists at %p" % (str[0,100] + '...') 
    488553 
    489         str.gsub( ListRegexp ) {|list| 
    490             @log.debug "  Found list %p" % list 
    491             bullet = $1 
     554        # Choose a regexp based on whether we're already in a list or not 
     555        re = if rs.list_level.zero? then OuterListRegexp else InnerListRegexp end 
     556 
     557        # Use the chosen regexp to find lists 
     558        str.gsub( re ) { 
     559            pre, list, bullet = $1, $2, $3 
     560            @log.debug "  Found list bullet %p after %p: %p" % 
     561                [ bullet, pre, list ] 
     562 
    492563            list_type = (ListMarkerUl.match(bullet) ? "ul" : "ol") 
    493564            list.gsub!( /\n{2,}/, "\n\n\n" ) 
    494565 
    495             %{<%s>\n%s</%s>\n} % [ 
     566            %{%s<%s>\n%s</%s>\n} % [ 
     567                pre, 
    496568                list_type, 
    497569                transform_list_items( list, rs ), 
     
    516588        @log.debug " Transforming list items" 
    517589 
     590        # Increment the marker for parsing sublists 
     591        rs.list_level += 1 
     592 
    518593        # Trim trailing blank lines 
    519594        str = str.sub( /\n{2,}\z/, "\n" ) 
     
    535610            %{<li>%s</li>\n} % item 
    536611        } 
     612    ensure 
     613        # Decrement the list-level counter 
     614        rs.list_level -= 1 
    537615    end 
    538616 
     
    543621        (                                   # $1 = the code block 
    544622          (?: 
    545             (?:[ ]{#{TabWidth}} | \t)       # a tab or tab-width of spaces 
     623            (?:[ ]{#{TAB_WIDTH}} | \t)      # a tab or tab-width of spaces 
    546624            .*\n+ 
    547625          )+ 
    548626        ) 
    549         (^[ ]{0,#{TabWidth - 1}}\S|\Z)      # Lookahead for non-space at 
     627        (^[ ]{0,#{TAB_WIDTH - 1}}\S|\Z)     # Lookahead for non-space at 
    550628                                            # line-start, or end of doc 
    551629      }x 
     
    589667            quote.gsub!( /^ +$/, '' )   # Trim whitespace-only lines 
    590668 
    591             indent = " " * TabWidth 
     669            indent = " " * TAB_WIDTH 
    592670            quoted = %{<blockquote>\n%s\n</blockquote>\n\n} % 
    593671                apply_block_transforms( quote, rs ). 
     
    770848 
    771849        # Scan the whole string 
    772         until @scanner.empty? 
     850        until @scanner.eos? 
    773851         
    774852            if @scanner.scan( /\[/ ) 
     
    857935            end 
    858936 
    859         end # until @scanner.empty? 
     937        end # until @scanner.eos? 
    860938 
    861939        return text 
     
    899977 
    900978        # Scan to the end of the string 
    901         until @scanner.empty? 
     979        until @scanner.eos? 
    902980 
    903981            # Scan up to an opening backtick 
     
    909987                opener = @scanner.scan( /`+/ ) 
    910988                len = opener.length 
    911                 closer = Regexp::new( opener ) 
     989                closer = Regexp.new( opener ) 
    912990                @log.debug "Scanning for end of code span with %p" % closer 
    913991 
     
    916994                # whitespace, and encode any enitites contained in it. 
    917995                codespan = @scanner.scan_until( closer ) or 
    918                     raise FormatError::new( @scanner.rest[0,20], 
     996                    raise FormatError.new( @scanner.rest[0,20], 
    919997                        "No %p found before end" % opener ) 
    920998 
     
    9671045    ### Turn image markup into image tags. 
    9681046    def transform_images( str, rs ) 
    969         @log.debug " Transforming images" % str 
     1047        @log.debug " Transforming images (%p)" % [str] 
    9701048 
    9711049        # Handle reference-style labeled images: ![alt text][id] 
     
    9891067                        result += %{ title="%s"} % escape_md( rs.titles[linkid] ) 
    9901068                    end 
    991                     result += EmptyElementSuffix 
     1069                    result += EMPTY_ELEMENT_SUFFIX 
    9921070 
    9931071                else 
     
    10121090                    result += %{ title="%s"} % escape_md( title ) 
    10131091                end 
    1014                 result += EmptyElementSuffix 
     1092                result += EMPTY_ELEMENT_SUFFIX 
    10151093 
    10161094                @log.debug "Replacing %p with %p" % [ match, result ] 
     
    10291107            gsub( %r{<}, '&lt;' ). 
    10301108            gsub( %r{>}, '&gt;' ). 
    1031             gsub( CodeEscapeRegexp ) {|match| EscapeTable[match][:md5]} 
     1109            gsub( CodeEscapeRegexp ) {|match| ESCAPE_TABLE[match][:md5]} 
    10321110    end 
    10331111                 
     
    10421120    def escape_md( str ) 
    10431121        str. 
    1044             gsub( /\*/, EscapeTable['*'][:md5] ). 
    1045             gsub( /_/,  EscapeTable['_'][:md5] ) 
     1122            gsub( /\*/, ESCAPE_TABLE['*'][:md5] ). 
     1123            gsub( /_/,  ESCAPE_TABLE['_'][:md5] ) 
    10461124    end 
    10471125 
     
    10501128    HTMLCommentRegexp  = %r{ <! ( -- .*? -- \s* )+ > }mx 
    10511129    XMLProcInstRegexp  = %r{ <\? .*? \?> }mx 
    1052     MetaTag = Regexp::union( HTMLCommentRegexp, XMLProcInstRegexp ) 
     1130    MetaTag = Regexp.union( HTMLCommentRegexp, XMLProcInstRegexp ) 
    10531131 
    10541132    HTMLTagOpenRegexp  = %r{ < [a-z/!$] [^<>]* }imx 
    10551133    HTMLTagCloseRegexp = %r{ > }x 
    1056     HTMLTagPart = Regexp::union( HTMLTagOpenRegexp, HTMLTagCloseRegexp ) 
     1134    HTMLTagPart = Regexp.union( HTMLTagOpenRegexp, HTMLTagCloseRegexp ) 
    10571135 
    10581136    ### Break the HTML source in +str+ into a series of tokens and return 
     
    10671145        type, token = nil, nil 
    10681146 
    1069         until @scanner.empty? 
     1147        until @scanner.eos? 
    10701148            @log.debug "Scanning from %p" % @scanner.rest 
    10711149 
     
    11381216    ### return it. 
    11391217    def outdent( str ) 
    1140         str.gsub( /^(\t|[ ]{1,#{TabWidth}})/, '') 
     1218        str.gsub( /^(\t|[ ]{1,#{TAB_WIDTH}})/, '') 
    11411219    end 
    11421220