1 ! Copyright (C) 2008, 2009 Doug Coleman, Daniel Ehrenberg.
2 ! See http://factorcode.org/license.txt for BSD license.
3 USING: sequences kernel splitting lists fry accessors assocs math.order
4 math combinators namespaces urls.encoding xml.syntax xmode.code2html
5 xml.data arrays strings vectors xml.writer io.streams.string locals
9 SYMBOL: relative-link-prefix
10 SYMBOL: disable-images?
11 SYMBOL: link-no-follow?
14 TUPLE: heading1 child ;
15 TUPLE: heading2 child ;
16 TUPLE: heading3 child ;
17 TUPLE: heading4 child ;
19 TUPLE: emphasis child ;
20 TUPLE: superscript child ;
21 TUPLE: subscript child ;
22 TUPLE: inline-code child ;
23 TUPLE: paragraph child ;
24 TUPLE: list-item child ;
25 TUPLE: unordered-list child ;
26 TUPLE: ordered-list child ;
28 TUPLE: table-row child ;
29 TUPLE: link href text ;
30 TUPLE: image href text ;
31 TUPLE: code mode string ;
35 : absolute-url? ( string -- ? )
36 { "http://" "https://" "ftp://" } [ head? ] with any? ;
38 : simple-link-title ( string -- string' )
39 dup absolute-url? [ "/" split1-last swap or ] unless ;
41 ! _foo*bar_baz*bing works like <i>foo*bar</i>baz<b>bing</b>
42 ! I could support overlapping, but there's not a good use case for it.
44 DEFER: (parse-paragraph)
46 : parse-paragraph ( string -- seq )
47 (parse-paragraph) list>array ;
49 : make-paragraph ( string -- paragraph )
50 parse-paragraph paragraph boa ;
52 : cut-half-slice ( string i -- before after-slice )
53 [ head ] [ 1+ short tail-slice ] 2bi ;
55 : find-cut ( string quot -- before after delimiter )
57 [ [ cut-half-slice ] [ f ] if* ] dip ; inline
59 : parse-delimiter ( string delimiter class -- paragraph )
60 [ '[ _ = ] find-cut drop ] dip
61 '[ parse-paragraph _ new swap >>child ]
62 [ (parse-paragraph) ] bi* cons ;
64 : delimiter-class ( delimiter -- class )
68 { CHAR: ^ superscript }
70 { CHAR: % inline-code }
73 : parse-link ( string -- paragraph-list )
74 rest-slice "]]" split1-slice [
76 [ "" like dup simple-link-title ] unless*
77 [ "image:" ?head ] dip swap [ image boa ] [ parse-paragraph link boa ] if
78 ] dip [ (parse-paragraph) cons ] when* ;
80 : ?first ( seq -- elt ) 0 swap ?nth ;
82 : parse-big-link ( before after -- link rest )
85 [ [ CHAR: [ suffix ] [ (parse-paragraph) ] bi* ]
88 : escape ( before after -- before' after' )
89 [ nil ] [ unclip-slice swap [ suffix ] dip (parse-paragraph) ] if-empty ;
91 : (parse-paragraph) ( string -- list )
93 [ "*_^~%[\\" member? ] find-cut [
95 { CHAR: [ [ parse-big-link ] }
96 { CHAR: \\ [ escape ] }
97 [ dup delimiter-class parse-delimiter ]
99 ] [ drop "" like 1list ] if*
102 : <farkup-state> ( string -- state ) string-lines ;
103 : look ( state i -- char ) swap first ?nth ;
104 : done? ( state -- ? ) empty? ;
105 : take-line ( state -- state' line ) unclip-slice ;
107 : take-lines ( state char -- state' lines )
108 dupd '[ ?first _ = not ] find drop
109 [ cut-slice ] [ f ] if* swap ;
111 :: (take-until) ( state delimiter accum -- string/f state' )
112 state empty? [ accum "\n" join f ] [
113 state unclip-slice :> first :> rest
114 first delimiter split1 :> after :> before
120 rest delimiter accum (take-until)
124 : take-until ( state delimiter -- string/f state' )
125 V{ } clone (take-until) ;
127 : count= ( string -- n )
128 dup <reversed> [ [ CHAR: = = not ] find drop 0 or ] bi@ min ;
130 : trim= ( string -- string' )
133 : make-heading ( string class -- heading )
134 [ trim= parse-paragraph ] dip boa ; inline
136 : parse-heading ( state -- state' heading )
137 take-line dup count= {
138 { 0 [ make-paragraph ] }
139 { 1 [ heading1 make-heading ] }
140 { 2 [ heading2 make-heading ] }
141 { 3 [ heading3 make-heading ] }
142 { 4 [ heading4 make-heading ] }
143 [ drop heading4 make-heading ]
146 : trim-row ( seq -- seq' )
148 dup peek empty? [ but-last ] when ;
150 : ?peek ( seq -- elt/f )
151 [ f ] [ peek ] if-empty ;
153 : coalesce ( rows -- rows' )
156 _ dup ?peek ?peek CHAR: \\ =
157 [ [ pop "|" rot 3append ] keep ] when
162 : parse-table ( state -- state' table )
167 [ parse-paragraph ] map
171 : parse-line ( state -- state' item )
172 take-line dup "___" =
173 [ drop line new ] [ make-paragraph ] if ;
175 : parse-list ( state char class -- state' list )
178 [ rest parse-paragraph list-item boa ] map
181 : parse-ul ( state -- state' ul )
182 CHAR: - unordered-list parse-list ;
184 : parse-ol ( state -- state' ul )
185 CHAR: # ordered-list parse-list ;
187 : parse-code ( state -- state' item )
189 [ unclip-slice make-paragraph ] [
190 "{" take-until [ rest ] dip
192 [ code boa ] dip swap
195 : parse-item ( state -- state' item )
197 { CHAR: = [ parse-heading ] }
198 { CHAR: | [ parse-table ] }
199 { CHAR: _ [ parse-line ] }
200 { CHAR: - [ parse-ul ] }
201 { CHAR: # [ parse-ol ] }
202 { CHAR: [ [ parse-code ] }
203 { f [ rest-slice f ] }
204 [ drop take-line make-paragraph ]
207 : parse-farkup ( string -- farkup )
208 <farkup-state> [ dup done? not ] [ parse-item ] produce nip sift ;
210 CONSTANT: invalid-url "javascript:alert('Invalid URL in farkup');"
212 : check-url ( href -- href' )
214 { [ dup empty? ] [ drop invalid-url ] }
215 { [ dup [ 127 > ] any? ] [ drop invalid-url ] }
216 { [ dup first "/\\" member? ] [ drop invalid-url ] }
217 { [ CHAR: : over member? ] [ dup absolute-url? [ drop invalid-url ] unless ] }
218 [ relative-link-prefix get prepend "" like url-encode ]
221 : render-code ( string mode -- xml )
222 [ string-lines ] dip htmlize-lines
223 [XML <pre><-></pre> XML] ;
225 GENERIC: (write-farkup) ( farkup -- xml )
227 : farkup-inside ( farkup name -- xml )
228 <simple-name> swap T{ attrs } swap
229 child>> (write-farkup) 1array <tag> ;
231 M: heading1 (write-farkup) "h1" farkup-inside ;
232 M: heading2 (write-farkup) "h2" farkup-inside ;
233 M: heading3 (write-farkup) "h3" farkup-inside ;
234 M: heading4 (write-farkup) "h4" farkup-inside ;
235 M: strong (write-farkup) "strong" farkup-inside ;
236 M: emphasis (write-farkup) "em" farkup-inside ;
237 M: superscript (write-farkup) "sup" farkup-inside ;
238 M: subscript (write-farkup) "sub" farkup-inside ;
239 M: inline-code (write-farkup) "code" farkup-inside ;
240 M: list-item (write-farkup) "li" farkup-inside ;
241 M: unordered-list (write-farkup) "ul" farkup-inside ;
242 M: ordered-list (write-farkup) "ol" farkup-inside ;
243 M: paragraph (write-farkup) "p" farkup-inside ;
244 M: table (write-farkup) "table" farkup-inside ;
246 : write-link ( href text -- xml )
247 [ check-url link-no-follow? get "nofollow" and ] dip
248 [XML <a href=<-> rel=<->><-></a> XML] ;
250 : write-image-link ( href text -- xml )
251 disable-images? get [
253 [XML <strong>Images are not allowed</strong> XML]
255 [ check-url ] [ f like ] bi*
256 [XML <img src=<-> alt=<->/> XML]
259 : open-link ( link -- href text )
260 [ href>> ] [ text>> (write-farkup) ] bi ;
262 M: link (write-farkup)
263 open-link write-link ;
265 M: image (write-farkup)
266 open-link write-image-link ;
268 M: code (write-farkup)
269 [ string>> ] [ mode>> ] bi render-code ;
271 M: line (write-farkup)
272 drop [XML <hr/> XML] ;
274 M: line-break (write-farkup)
275 drop [XML <br/> XML] ;
277 M: table-row (write-farkup)
279 [ (write-farkup) [XML <td><-></td> XML] ] map
280 [XML <tr><-></tr> XML] ;
282 M: string (write-farkup) ;
284 M: array (write-farkup) [ (write-farkup) ] map ;
286 : farkup>xml ( string -- xml )
287 parse-farkup (write-farkup) ;
289 : write-farkup ( string -- )
290 farkup>xml write-xml ;
292 : convert-farkup ( string -- string' )
293 [ write-farkup ] with-string-writer ;