292 lines
		
	
	
		
			8.2 KiB
		
	
	
	
		
			Factor
		
	
	
			
		
		
	
	
			292 lines
		
	
	
		
			8.2 KiB
		
	
	
	
		
			Factor
		
	
	
! Copyright (C) 2008, 2009 Doug Coleman, Daniel Ehrenberg.
 | 
						|
! See http://factorcode.org/license.txt for BSD license.
 | 
						|
USING: accessors arrays assocs combinators fry io.streams.string
 | 
						|
kernel lists locals math math.order namespaces sequences splitting
 | 
						|
strings urls urls.encoding xml.data xml.syntax xml.writer
 | 
						|
xmode.code2html ;
 | 
						|
IN: farkup
 | 
						|
 | 
						|
SYMBOL: relative-link-prefix
 | 
						|
SYMBOL: disable-images?
 | 
						|
SYMBOL: link-no-follow?
 | 
						|
SYMBOL: line-breaks?
 | 
						|
 | 
						|
TUPLE: heading1 child ;
 | 
						|
TUPLE: heading2 child ;
 | 
						|
TUPLE: heading3 child ;
 | 
						|
TUPLE: heading4 child ;
 | 
						|
TUPLE: strong child ;
 | 
						|
TUPLE: emphasis child ;
 | 
						|
TUPLE: superscript child ;
 | 
						|
TUPLE: subscript child ;
 | 
						|
TUPLE: inline-code child ;
 | 
						|
TUPLE: paragraph child ;
 | 
						|
TUPLE: list-item child ;
 | 
						|
TUPLE: unordered-list child ;
 | 
						|
TUPLE: ordered-list child ;
 | 
						|
TUPLE: table child ;
 | 
						|
TUPLE: table-row child ;
 | 
						|
TUPLE: link href text ;
 | 
						|
TUPLE: image href text ;
 | 
						|
TUPLE: code mode string ;
 | 
						|
TUPLE: line ;
 | 
						|
TUPLE: line-break ;
 | 
						|
 | 
						|
: absolute-url? ( string -- ? )
 | 
						|
    >url protocol>> >boolean ;
 | 
						|
 | 
						|
: simple-link-title ( string -- string' )
 | 
						|
    dup absolute-url? [ "/" split1-last swap or ] unless ;
 | 
						|
 | 
						|
! _foo*bar_baz*bing works like <i>foo*bar</i>baz<b>bing</b>
 | 
						|
! I could support overlapping, but there's not a good use case for it.
 | 
						|
 | 
						|
DEFER: (parse-paragraph)
 | 
						|
 | 
						|
: parse-paragraph ( string -- seq )
 | 
						|
    (parse-paragraph) list>array ;
 | 
						|
 | 
						|
: make-paragraph ( string -- paragraph )
 | 
						|
    parse-paragraph paragraph boa ;
 | 
						|
 | 
						|
: cut-half-slice ( string i -- before after-slice )
 | 
						|
    [ head ] [ 1 + short tail-slice ] 2bi ;
 | 
						|
 | 
						|
: find-cut ( string quot -- before after delimiter )
 | 
						|
    dupd find
 | 
						|
    [ [ cut-half-slice ] [ f ] if* ] dip ; inline
 | 
						|
 | 
						|
: parse-delimiter ( string delimiter class -- paragraph )
 | 
						|
    [ '[ _ = ] find-cut drop ] dip
 | 
						|
    '[ parse-paragraph _ new swap >>child ]
 | 
						|
    [ (parse-paragraph) ] bi* cons ;
 | 
						|
 | 
						|
: delimiter-class ( delimiter -- class )
 | 
						|
    H{
 | 
						|
        { CHAR: * strong }
 | 
						|
        { CHAR: _ emphasis }
 | 
						|
        { CHAR: ^ superscript }
 | 
						|
        { CHAR: ~ subscript }
 | 
						|
        { CHAR: % inline-code }
 | 
						|
    } at ;
 | 
						|
 | 
						|
: or-simple-title ( ... url title/f quot: ( ... title -- ... title' ) -- ... url title' )
 | 
						|
    [ "" like dup simple-link-title ] if* ; inline
 | 
						|
 | 
						|
: parse-link ( string -- paragraph-list )
 | 
						|
    rest-slice "]]" split1-slice [
 | 
						|
        "|" split1
 | 
						|
        [ "image:" ?head ] dip swap
 | 
						|
        [ [ ] or-simple-title image boa ]
 | 
						|
        [ [ parse-paragraph ] or-simple-title link boa ] if
 | 
						|
    ] dip [ (parse-paragraph) cons ] [ 1list ] if* ;
 | 
						|
 | 
						|
: parse-big-link ( before after -- link rest )
 | 
						|
    dup ?first CHAR: [ =
 | 
						|
    [ parse-link ]
 | 
						|
    [ [ CHAR: [ suffix ] [ (parse-paragraph) ] bi* ]
 | 
						|
    if ;
 | 
						|
 | 
						|
: escape ( before after -- before' after' )
 | 
						|
    [ nil ] [ unclip-slice swap [ suffix ] dip (parse-paragraph) ] if-empty ;
 | 
						|
 | 
						|
: (parse-paragraph) ( string -- list )
 | 
						|
    [ nil ] [
 | 
						|
        [ "*_^~%[\\" member? ] find-cut [
 | 
						|
            {
 | 
						|
                { CHAR: [ [ parse-big-link ] }
 | 
						|
                { CHAR: \\ [ escape ] }
 | 
						|
                [ dup delimiter-class parse-delimiter ]
 | 
						|
            } case cons
 | 
						|
        ] [ drop "" like 1list ] if*
 | 
						|
    ] if-empty ;
 | 
						|
 | 
						|
: look ( state i -- char ) swap first ?nth ;
 | 
						|
 | 
						|
: take-lines ( state char -- state' lines )
 | 
						|
    dupd '[ ?first _ = not ] find drop
 | 
						|
    [ cut-slice ] [ f ] if* swap ;
 | 
						|
 | 
						|
:: (take-until) ( state delimiter accum -- string/f state' )
 | 
						|
    state empty? [ accum "\n" join f ] [
 | 
						|
        state unclip-slice :> ( rest first )
 | 
						|
        first delimiter split1 :> ( before after )
 | 
						|
        before accum push
 | 
						|
        after [
 | 
						|
            accum "\n" join
 | 
						|
            rest after prefix
 | 
						|
        ] [
 | 
						|
            rest delimiter accum (take-until)
 | 
						|
        ] if
 | 
						|
    ] if ;
 | 
						|
 | 
						|
: take-until ( state delimiter -- string state'/f )
 | 
						|
    V{ } clone (take-until) ;
 | 
						|
 | 
						|
: count= ( string -- n )
 | 
						|
    dup <reversed> [ [ CHAR: = = not ] find drop 0 or ] bi@ min ;
 | 
						|
 | 
						|
: trim= ( string -- string' )
 | 
						|
    [ CHAR: = = ] trim ;
 | 
						|
 | 
						|
: make-heading ( string class -- heading )
 | 
						|
    [ trim= parse-paragraph ] dip boa ; inline
 | 
						|
 | 
						|
: parse-heading ( state -- state' heading )
 | 
						|
    unclip-slice dup count= {
 | 
						|
        { 0 [ make-paragraph ] }
 | 
						|
        { 1 [ heading1 make-heading ] }
 | 
						|
        { 2 [ heading2 make-heading ] }
 | 
						|
        { 3 [ heading3 make-heading ] }
 | 
						|
        { 4 [ heading4 make-heading ] }
 | 
						|
        [ drop heading4 make-heading ]
 | 
						|
    } case ;
 | 
						|
 | 
						|
: trim-row ( seq -- seq' )
 | 
						|
    rest
 | 
						|
    dup last empty? [ but-last ] when ;
 | 
						|
 | 
						|
: coalesce ( rows -- rows' )
 | 
						|
    V{ } clone [
 | 
						|
        '[
 | 
						|
            _ dup ?last ?last CHAR: \\ =
 | 
						|
            [ [ pop "|" rot 3append ] keep ] when
 | 
						|
            push
 | 
						|
        ] each
 | 
						|
    ] keep ;
 | 
						|
 | 
						|
: parse-table ( state -- state' table )
 | 
						|
    CHAR: | take-lines [
 | 
						|
        "|" split
 | 
						|
        trim-row
 | 
						|
        coalesce
 | 
						|
        [ parse-paragraph ] map
 | 
						|
        table-row boa
 | 
						|
    ] map table boa ;
 | 
						|
 | 
						|
: parse-line ( state -- state' item )
 | 
						|
    unclip-slice dup "___" =
 | 
						|
    [ drop line new ] [ make-paragraph ] if ;
 | 
						|
 | 
						|
: parse-list ( state char class -- state' list )
 | 
						|
    [
 | 
						|
        take-lines
 | 
						|
        [ rest parse-paragraph list-item boa ] map
 | 
						|
    ] dip boa ; inline
 | 
						|
 | 
						|
: parse-ul ( state -- state' ul )
 | 
						|
    CHAR: - unordered-list parse-list ;
 | 
						|
 | 
						|
: parse-ol ( state -- state' ul )
 | 
						|
    CHAR: # ordered-list parse-list ;
 | 
						|
 | 
						|
: parse-code ( state -- state' item )
 | 
						|
    dup 1 look CHAR: [ =
 | 
						|
    [ unclip-slice make-paragraph ] [
 | 
						|
        dup "{" take-until [
 | 
						|
            [ nip rest ] dip
 | 
						|
            "}]" take-until
 | 
						|
            [ code boa ] dip swap
 | 
						|
        ] [ drop unclip-slice make-paragraph ] if*
 | 
						|
    ] if ;
 | 
						|
 | 
						|
: parse-item ( state -- state' item )
 | 
						|
    dup 0 look {
 | 
						|
        { CHAR: = [ parse-heading ] }
 | 
						|
        { CHAR: | [ parse-table ] }
 | 
						|
        { CHAR: _ [ parse-line ] }
 | 
						|
        { CHAR: - [ parse-ul ] }
 | 
						|
        { CHAR: # [ parse-ol ] }
 | 
						|
        { CHAR: [ [ parse-code ] }
 | 
						|
        { f [ rest-slice f ] }
 | 
						|
        [ drop unclip-slice make-paragraph ]
 | 
						|
    } case ;
 | 
						|
 | 
						|
: parse-farkup ( string -- farkup )
 | 
						|
    string-lines [ dup empty? not ] [ parse-item ] produce nip sift ;
 | 
						|
 | 
						|
CONSTANT: invalid-url "javascript:alert('Invalid URL in farkup');"
 | 
						|
 | 
						|
: check-url ( href -- href' )
 | 
						|
    {
 | 
						|
        { [ dup empty? ] [ drop invalid-url ] }
 | 
						|
        { [ dup [ 127 > ] any? ] [ drop invalid-url ] }
 | 
						|
        { [ dup first "/\\" member? ] [ drop invalid-url ] }
 | 
						|
        { [ CHAR: : over member? ] [ dup absolute-url? [ drop invalid-url ] unless ] }
 | 
						|
        [ relative-link-prefix get prepend "" like url-encode ]
 | 
						|
    } cond ;
 | 
						|
 | 
						|
: render-code ( string mode -- xml )
 | 
						|
    [ string-lines ] dip htmlize-lines
 | 
						|
    [XML <pre><-></pre> XML] ;
 | 
						|
 | 
						|
GENERIC: (write-farkup) ( farkup -- xml )
 | 
						|
 | 
						|
: farkup-inside ( farkup name -- xml )
 | 
						|
    <simple-name> swap T{ attrs } swap
 | 
						|
    child>> (write-farkup) 1array <tag> ;
 | 
						|
 | 
						|
M: heading1 (write-farkup) "h1" farkup-inside ;
 | 
						|
M: heading2 (write-farkup) "h2" farkup-inside ;
 | 
						|
M: heading3 (write-farkup) "h3" farkup-inside ;
 | 
						|
M: heading4 (write-farkup) "h4" farkup-inside ;
 | 
						|
M: strong (write-farkup) "strong" farkup-inside ;
 | 
						|
M: emphasis (write-farkup) "em" farkup-inside ;
 | 
						|
M: superscript (write-farkup) "sup" farkup-inside ;
 | 
						|
M: subscript (write-farkup) "sub" farkup-inside ;
 | 
						|
M: inline-code (write-farkup) "code" farkup-inside ;
 | 
						|
M: list-item (write-farkup) "li" farkup-inside ;
 | 
						|
M: unordered-list (write-farkup) "ul" farkup-inside ;
 | 
						|
M: ordered-list (write-farkup) "ol" farkup-inside ;
 | 
						|
M: paragraph (write-farkup) "p" farkup-inside ;
 | 
						|
M: table (write-farkup) "table" farkup-inside ;
 | 
						|
 | 
						|
: write-link ( href text -- xml )
 | 
						|
    [ check-url link-no-follow? get "nofollow" and ] dip
 | 
						|
    [XML <a href=<-> rel=<->><-></a> XML] ;
 | 
						|
 | 
						|
: write-image-link ( href text -- xml )
 | 
						|
    disable-images? get [
 | 
						|
        2drop
 | 
						|
        [XML <strong>Images are not allowed</strong> XML]
 | 
						|
    ] [
 | 
						|
        [ check-url ] [ f like ] bi*
 | 
						|
        [XML <img src=<-> alt=<->/> XML]
 | 
						|
    ] if ;
 | 
						|
 | 
						|
: open-link ( link -- href text )
 | 
						|
    [ href>> ] [ text>> (write-farkup) ] bi ;
 | 
						|
 | 
						|
M: link (write-farkup)
 | 
						|
    open-link write-link ;
 | 
						|
 | 
						|
M: image (write-farkup)
 | 
						|
    open-link write-image-link ;
 | 
						|
 | 
						|
M: code (write-farkup)
 | 
						|
    [ string>> ] [ mode>> ] bi render-code ;
 | 
						|
 | 
						|
M: line (write-farkup)
 | 
						|
    drop [XML <hr/> XML] ;
 | 
						|
 | 
						|
M: line-break (write-farkup)
 | 
						|
    drop [XML <br/> XML] ;
 | 
						|
 | 
						|
M: table-row (write-farkup)
 | 
						|
    child>>
 | 
						|
    [ (write-farkup) [XML <td><-></td> XML] ] map
 | 
						|
    [XML <tr><-></tr> XML] ;
 | 
						|
 | 
						|
M: string (write-farkup) ;
 | 
						|
 | 
						|
M: array (write-farkup) [ (write-farkup) ] map ;
 | 
						|
 | 
						|
: farkup>xml ( string -- xml )
 | 
						|
    parse-farkup (write-farkup) ;
 | 
						|
 | 
						|
: write-farkup ( string -- )
 | 
						|
    farkup>xml write-xml ;
 | 
						|
 | 
						|
: convert-farkup ( string -- string' )
 | 
						|
    [ write-farkup ] with-string-writer ;
 |