]> gitweb.factorcode.org Git - factor.git/blob - basis/farkup/farkup.factor
6a3192c6d1dc4b0fbe2b78bdf708db314bc048df
[factor.git] / basis / farkup / farkup.factor
1 ! Copyright (C) 2008, 2009 Doug Coleman, Daniel Ehrenberg.
2 ! See http://factorcode.org/license.txt for BSD license.
3 USING: accessors arrays assocs combinators io.streams.string
4 kernel lists math math.order namespaces sequences splitting
5 strings urls urls.encoding xml.data xml.syntax xml.writer
6 xmode.code2html ;
7 IN: farkup
8
9 SYMBOL: relative-link-prefix
10 SYMBOL: disable-images?
11 SYMBOL: link-no-follow?
12 SYMBOL: line-breaks?
13
14 TUPLE: heading1 child ;
15 TUPLE: heading2 child ;
16 TUPLE: heading3 child ;
17 TUPLE: heading4 child ;
18 TUPLE: strong 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 ;
27 TUPLE: table child ;
28 TUPLE: table-row child ;
29 TUPLE: link href text ;
30 TUPLE: image href text ;
31 TUPLE: code mode string ;
32 TUPLE: line ;
33 TUPLE: line-break ;
34
35 : absolute-url? ( string -- ? )
36     >url protocol>> >boolean ;
37
38 : simple-link-title ( string -- string' )
39     dup absolute-url? [ "/" split1-last swap or ] unless ;
40
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.
43
44 DEFER: (parse-paragraph)
45
46 : parse-paragraph ( string -- seq )
47     (parse-paragraph) list>array ;
48
49 : make-paragraph ( string -- paragraph )
50     parse-paragraph paragraph boa ;
51
52 : cut-half-slice ( string i -- before after-slice )
53     [ head ] [ 1 + cramp tail-slice ] 2bi ;
54
55 : find-cut ( string quot -- before after delimiter )
56     dupd find
57     [ [ cut-half-slice ] [ f ] if* ] dip ; inline
58
59 : parse-delimiter ( string delimiter class -- paragraph )
60     [ '[ _ = ] find-cut drop ] dip
61     '[ parse-paragraph _ new swap >>child ]
62     [ (parse-paragraph) ] bi* cons ;
63
64 : delimiter-class ( delimiter -- class )
65     H{
66         { CHAR: * strong }
67         { CHAR: _ emphasis }
68         { CHAR: ^ superscript }
69         { CHAR: ~ subscript }
70         { CHAR: % inline-code }
71     } at ;
72
73 : or-simple-title ( ... url title/f quot: ( ... title -- ... title' ) -- ... url title' )
74     [ "" like dup simple-link-title ] if* ; inline
75
76 : parse-link ( string -- paragraph-list )
77     rest-slice "]]" split1-slice [
78         "|" split1
79         [ "image:" ?head ] dip swap
80         [ [ ] or-simple-title image boa ]
81         [ [ parse-paragraph ] or-simple-title link boa ] if
82     ] dip [ (parse-paragraph) cons ] [ 1list ] if* ;
83
84 : parse-big-link ( before after -- link rest )
85     dup ?first CHAR: [ =
86     [ parse-link ]
87     [ [ CHAR: [ suffix ] [ (parse-paragraph) ] bi* ]
88     if ;
89
90 : escape ( before after -- before' after' )
91     [ nil ] [ unclip-slice swap [ suffix ] dip (parse-paragraph) ] if-empty ;
92
93 : (parse-paragraph) ( string -- list )
94     [ nil ] [
95         [ "*_^~%[\\" member? ] find-cut [
96             {
97                 { CHAR: [ [ parse-big-link ] }
98                 { CHAR: \\ [ escape ] }
99                 [ dup delimiter-class parse-delimiter ]
100             } case cons
101         ] [ drop "" like 1list ] if*
102     ] if-empty ;
103
104 : look ( state i -- char ) swap first ?nth ;
105
106 : take-lines ( state char -- state' lines )
107     dupd '[ ?first _ = not ] find drop
108     [ cut-slice ] [ f ] if* swap ;
109
110 :: (take-until) ( state delimiter accum -- string/f state' )
111     state empty? [ accum join-lines f ] [
112         state unclip-slice :> ( rest first )
113         first delimiter split1 :> ( before after )
114         before accum push
115         after [
116             accum join-lines
117             rest after prefix
118         ] [
119             rest delimiter accum (take-until)
120         ] if
121     ] if ;
122
123 : take-until ( state delimiter -- string state'/f )
124     V{ } clone (take-until) ;
125
126 : count= ( string -- n )
127     dup <reversed> [ [ CHAR: = = not ] find drop 0 or ] bi@ min ;
128
129 : trim= ( string -- string' )
130     [ CHAR: = = ] trim ;
131
132 : make-heading ( string class -- heading )
133     [ trim= parse-paragraph ] dip boa ; inline
134
135 : parse-heading ( state -- state' heading )
136     unclip-slice dup count= {
137         { 0 [ make-paragraph ] }
138         { 1 [ heading1 make-heading ] }
139         { 2 [ heading2 make-heading ] }
140         { 3 [ heading3 make-heading ] }
141         { 4 [ heading4 make-heading ] }
142         [ drop heading4 make-heading ]
143     } case ;
144
145 : trim-row ( seq -- seq' )
146     rest
147     dup last empty? [ but-last ] when ;
148
149 : coalesce ( rows -- rows' )
150     V{ } clone [
151         '[
152             _ dup ?last ?last CHAR: \\ =
153             [ [ pop "|" rot 3append ] keep ] when
154             push
155         ] each
156     ] keep ;
157
158 : parse-table ( state -- state' table )
159     CHAR: | take-lines [
160         "|" split
161         trim-row
162         coalesce
163         [ parse-paragraph ] map
164         table-row boa
165     ] map table boa ;
166
167 : parse-line ( state -- state' item )
168     unclip-slice dup "___" =
169     [ drop line new ] [ make-paragraph ] if ;
170
171 : parse-list ( state char class -- state' list )
172     [
173         take-lines
174         [ rest parse-paragraph list-item boa ] map
175     ] dip boa ; inline
176
177 : parse-ul ( state -- state' ul )
178     CHAR: - unordered-list parse-list ;
179
180 : parse-ol ( state -- state' ul )
181     CHAR: # ordered-list parse-list ;
182
183 : parse-code ( state -- state' item )
184     dup 1 look CHAR: [ =
185     [ unclip-slice make-paragraph ] [
186         dup "{" take-until [
187             [ nip rest ] dip
188             "}]" take-until
189             [ code boa ] dip swap
190         ] [ drop unclip-slice make-paragraph ] if*
191     ] if ;
192
193 : parse-item ( state -- state' item )
194     dup 0 look {
195         { CHAR: = [ parse-heading ] }
196         { CHAR: | [ parse-table ] }
197         { CHAR: _ [ parse-line ] }
198         { CHAR: - [ parse-ul ] }
199         { CHAR: # [ parse-ol ] }
200         { CHAR: [ [ parse-code ] }
201         { f [ rest-slice f ] }
202         [ drop unclip-slice make-paragraph ]
203     } case ;
204
205 : parse-farkup ( string -- farkup )
206     split-lines [ dup empty? not ] [ parse-item ] produce nip sift ;
207
208 CONSTANT: invalid-url "javascript:alert('Invalid URL in farkup');"
209
210 : check-url ( href -- href' absolute? )
211     {
212         { [ dup empty? ] [ drop invalid-url f ] }
213         { [ dup [ 127 > ] any? ] [ drop invalid-url f ] }
214         { [ dup first "/\\" member? ] [ drop invalid-url f ] }
215         { [ CHAR: : over member? ] [ dup absolute-url? [ t ] [ drop invalid-url f ] if ] }
216         [ relative-link-prefix get prepend "" like url-encode f ]
217     } cond ;
218
219 : render-code ( string mode -- xml )
220     [ split-lines ] dip htmlize-lines
221     [XML <pre><-></pre> XML] ;
222
223 GENERIC: (write-farkup) ( farkup -- xml )
224
225 : farkup-inside ( farkup name -- xml )
226     <simple-name> swap T{ attrs } swap
227     child>> (write-farkup) 1array <tag> ;
228
229 M: heading1 (write-farkup) "h1" farkup-inside ;
230 M: heading2 (write-farkup) "h2" farkup-inside ;
231 M: heading3 (write-farkup) "h3" farkup-inside ;
232 M: heading4 (write-farkup) "h4" farkup-inside ;
233 M: strong (write-farkup) "strong" farkup-inside ;
234 M: emphasis (write-farkup) "em" farkup-inside ;
235 M: superscript (write-farkup) "sup" farkup-inside ;
236 M: subscript (write-farkup) "sub" farkup-inside ;
237 M: inline-code (write-farkup) "code" farkup-inside ;
238 M: list-item (write-farkup) "li" farkup-inside ;
239 M: unordered-list (write-farkup) "ul" farkup-inside ;
240 M: ordered-list (write-farkup) "ol" farkup-inside ;
241 M: paragraph (write-farkup) "p" farkup-inside ;
242 M: table (write-farkup) "table" farkup-inside ;
243
244 : write-link ( href text -- xml )
245     [ check-url link-no-follow? get "nofollow" and and ] dip
246     [XML <a href=<-> rel=<->><-></a> XML] ;
247
248 : write-image-link ( href text -- xml )
249     disable-images? get [
250         2drop
251         [XML <strong>Images are not allowed</strong> XML]
252     ] [
253         [ check-url drop ] [ f like ] bi*
254         [XML <img src=<-> alt=<->/> XML]
255     ] if ;
256
257 : open-link ( link -- href text )
258     [ href>> ] [ text>> (write-farkup) ] bi ;
259
260 M: link (write-farkup)
261     open-link write-link ;
262
263 M: image (write-farkup)
264     open-link write-image-link ;
265
266 M: code (write-farkup)
267     [ string>> ] [ mode>> ] bi render-code ;
268
269 M: line (write-farkup)
270     drop [XML <hr/> XML] ;
271
272 M: line-break (write-farkup)
273     drop [XML <br/> XML] ;
274
275 M: table-row (write-farkup)
276     child>>
277     [ (write-farkup) [XML <td><-></td> XML] ] map
278     [XML <tr><-></tr> XML] ;
279
280 M: string (write-farkup) ;
281
282 M: array (write-farkup) [ (write-farkup) ] map ;
283
284 : farkup>xml ( string -- xml )
285     parse-farkup (write-farkup) ;
286
287 : write-farkup ( string -- )
288     farkup>xml write-xml ;
289
290 : convert-farkup ( string -- string' )
291     [ write-farkup ] with-string-writer ;