#!/usr/bin/env cgi-factor USING: arrays assocs byte-arrays calendar checksums checksums.md5 classes.tuple continuations csv environment formatting html.entities io io.encodings.string io.encodings.utf8 io.monitors kernel math.parser multiline namespaces peg.ebnf prettyprint sequences present sequences.generalizations sorting splitting strings system threads tools.annotations xml xml.syntax xml.writer ; FROM: sets => diff ; IN: thread-cgi : send-chunk ( string -- ) [ utf8 encode length >hex print ] keep print flush ; : terminate-chunks ( -- ) "0\r\n\r\n" write ; : posts-root ( -- path ) ! !!! Modify as needed. "resource:www/boards/" ; : parse-cgi ( string -- assoc ) "&" split [ "=" split ] map ; : board-master ( -- path ) posts-root "QUERY_STRING" os-env parse-cgi "board" of ".csv" 3append ; : ?thread ( -- id/f ) "QUERY_STRING" os-env parse-cgi "thread" of ; :: thread-path ( id -- path ) board-master "." id "#" "." surround replace ; : thread-master ( -- path ) ?thread thread-path ; ! TUPLE: post id timestamp name hash reply image sig ; ! C: post ! : load-posts ( -- posts ) ! Useless? ! thread-master utf8 file>csv [ post slots>tuple ] map ; : special-user? ( hashcode -- string/f ) "resource:www/tripfags.csv" utf8 file>csv at ; DEFER: parse-post : parse-nest ( string -- string ) parse-post concat ; SYMBOL: this-post : parse-reply ( ast -- string ) [ second >string ] [ concat >string ] bi over [ this-post get dup ] dip "%s" sprintf ; : parse-yen ( ast -- string ) 2 over [ parse-nest ] change-nth concat "%s
" sprintf ; : parse-spoiler ( ast -- string ) >string parse-nest "%s" sprintf ; : parse-code ( ast -- string ) >string "
%s
" sprintf ; : parse-quot ( ast -- string ) >string parse-nest "
%s
" sprintf ; : spoil-img ( string -- string ) [ ] [ 1 head ] [ 1 tail* ] tri [ "(" = ] [ ")" = ] bi* and [ rest but-last "" sprintf ] [ "" sprintf ] if ; : parse-imgs ( ast -- string ) >string string>csv [ "" ] [ first 3 index-or-length head [ spoil-img "%s" sprintf ] map concat "
%s
" sprintf ] if-empty ; : parse-link ( ast -- string ) concat dup "%s" sprintf ; EBNF: parse-post [=[ newln = ( "\n" | "\r" ) space = ( " " | newln ) newlnx = newln newln => [[ drop "
" ]] reply = ">>" [0-9]+ &(space) => [[ parse-reply ]] yen = newlnx "¥" (!("\n") .)+ &("\n") => [[ parse-yen ]] yen1 = newln "¥" (!("\n") .)+ &("\n") => [[ parse-yen ]] spoiler = "**"~ (!("**") .)+ "**"~ => [[ parse-spoiler ]] code = "[code]"~ (!("[/code]") .)+ "[/code]"~ => [[ parse-code ]] quot = "[quot]"~ (!("[/quot]") .)+ "[/quot]"~ => [[ parse-quot ]] imgs = "[images{"~ (!("}]") .)+ "}]"~ => [[ parse-imgs ]] link = ("http://" | "https://") (!(space) .)+ &(space) => [[ parse-link ]] special = ( code | quot | imgs | spoiler | reply | yen | yen1 | newlnx | link ) else = (!(special) .)+ => [[ >string ]] main = ( special | else )* ]=] : apply-markup ( string -- xml ) html-escape "\n" 1surround parse-post concat "
" "
" surround string>xml ; :: normalize-post ( id time name topic hash body img sig -- id time name topic hash body img sig ) ! Lot of runtime effort, cache/memoize? id [ this-post set ] keep time string>number seconds since-1970 >gmt present " GMT" append name [ "Anonymous" ] when-empty topic [ "★" ] when-empty hash [ f ] [ md5 checksum-bytes >array [ >hex ] map concat dup special-user? [ nip #<-> XML> ] [ 8 head #<-> XML> ] ! 10 cut ! <-><-> ! XML> ] if* ] if-empty body apply-markup img [ f ] [ spoil-img string>xml ] ! [ align="left"/> XML> ] if-empty sig [ f ] [ html-escape "\n" 1surround parse-post concat "
%s
" sprintf string>xml ] if-empty ; :: ( id time name topic hash reply img sig -- xml ) id "#" prepend :> id-href id "-replies" append :> id-replies >
class="post-id"><-id-href-> <-topic->
<-name-> <-hash->
<-img-> <-reply->
<-time-> <-sig->
class="replies">
XML> ; : render-reply ( seq -- xml ) 8 ?firstn normalize-post ; INITIALIZED-SYMBOL: loaded-posts [ ?thread [ thread-master utf8 file>csv ] [ f ] if ] : render-posts ( -- string ) loaded-posts get-global [ render-reply ] map [ xml>string ] map concat "\n
\n" prepend ; : load-new-replies ( monitor -- ) dup next-change drop thread-master utf8 file>csv [ loaded-posts get-global diff [ render-reply ] map ] [ loaded-posts set-global ] bi xml>string send-chunk load-new-replies ; : sort-ops ( -- posts ) board-master utf8 file>csv [ third "" = ] filter [ second string>number ] inv-sort-by ; : catalog-op ( post -- catalog-xml ) [ first "/cgi-bin/board.cgi?" "QUERY_STRING" os-env "&thread=" 3array concat prepend ] [ first thread-path utf8 file>csv [ length ] [ first ] [ last ] tri pick 1 = [ drop render-reply f ] [ [ render-reply ] bi@ ] if ] bi
target="_parent" style="position:absolute;top:0;left:0; height:100%;width:95%;display:block; z-index:999;">

(<-> Posts)

<->
<->
XML> ; : render-ops ( -- string ) sort-ops [ catalog-op ] map <-> XML> pprint-xml>string ; : reload-catalog ( monitor -- ) dup next-change 100 milliseconds sleep drop render-ops send-chunk reload-catalog ; : heartbeat-loop ( -- ) 30 seconds sleep "" [ send-chunk ] [ quit ] recover heartbeat-loop ; ! == Script Proper: "Content-type: text/html; charset=utf8" print "X-Content-Type-Options: nosniff" print "Connection: keep-alive" print "Transfer-Encoding: chunked\r\n" print XML> xml>string send-chunk [ heartbeat-loop ] "Heartbeat" spawn drop ?thread [ render-posts send-chunk [ thread-master f [ load-new-replies ] with-monitor ] with-monitors ] [ "\n" send-chunk render-ops send-chunk [ board-master f [ reload-catalog ] with-monitor ] with-monitors ] if