(library (lambda wiki) (export wiki-main wiki-data-direcoty wiki-top-url spam-block-question spam-block-answer) (import (rnrs) (rnrs mutable-pairs) (only (system) get-environment-variable make-parameter) (only (srfi :1) first second third alist-cons) (only (srfi :2) and-let*) (only (mosh) assoc-ref read-line string-split format call-with-string-input-port string->regexp rxmatch) (only (mosh file) directory-list file->string write-to-file) (prefix (mosh cgi) cgi:)) ;; Configuration (define wiki-data-direcoty (make-parameter #f)) (define wiki-top-url (make-parameter #f)) (define spam-block-question (make-parameter #f (lambda (x) (when (and x (not (string? x))) (error 'spam-block-question "should be string")) x))) (define spam-block-answer (make-parameter #f (lambda (x) (when (and x (not (string? x))) (error 'spam-block-answer "should be string")) x))) (define (print msg) (display msg) (newline)) (define (add-to-list lst a) (append lst (list a))) ;;; reader for unread-line (define (make-reader port) (cons port '())) (define (reader-port r) (car r)) (define (reader-buffer-empty? r) (null? (cdr r))) (define (pop-reader-buffer! r) (let ([ret (car (cdr r))] [rest (cdr (cdr r))]) (set-cdr! r rest) ret)) (define (unread-line-reader! r line) (if (eof-object? line) '() (set-cdr! r (cons line (cdr r))))) (define (read-line-reader r) (if (reader-buffer-empty? r) (read-line (reader-port r)) (pop-reader-buffer! r))) ;;; Wiki plugin (define *plugins* '()) (define (define-plugin name inline-proc url-proc) (list name inline-proc url-proc)) (define (plugin-inline-proc plugin) (second plugin)) (define (plugin-name plugin) (first plugin)) (define (plugin-url-proc plugin) (third plugin)) (define (register-plugin plugin) (set! *plugins* (alist-cons (plugin-name plugin) plugin *plugins*))) (define (get-plugin name) (assoc-ref *plugins* name)) ;;; Wiki parser (define (wiki-parse r) (define (parse-list match reader) (define (iter ulp reader nest-level) (let loop ([l (read-line-reader reader)] [li-lst '()]) (cond [(eof-object? l) (list (if ulp 'ul 'ol) nest-level (reverse li-lst))] [else (let* ([reg (if ulp #/^(-+)/ #/^(\++)/)] [o-reg (if ulp #/^(\++)/ #/^(\-+)/)] [match (reg l)] [o-match (o-reg l)] [level (if match (string-length (match)) (if (and o-match (> (string-length (o-match)) nest-level)) (string-length (o-match)) 0))]) (cond [(zero? level) ;; next is not list syntax (unread-line-reader! reader l) (list (if ulp 'ul 'ol) nest-level (reverse li-lst))] [(= level nest-level) ;; same nest-level list (loop (read-line-reader reader) (cons (make-li (wiki-parse-inline (match 'after))) li-lst))] [(< level nest-level) ;; nest-level is shallower, so close. (unread-line-reader! reader l) (list (if ulp 'ul 'ol) nest-level (reverse li-lst))] [else ;; nest-level is deeper, so include. (unread-line-reader! reader l) (let ([deeper-lst (parse-list (if match match o-match) reader)]) (loop (read-line-reader reader) (cons (add-li-body (car li-lst) deeper-lst) (cdr li-lst))) )]))]))) (iter (#/-+/ (match)) reader (string-length (match)))) (define (parse-pre reader) (let loop ([line (read-line-reader reader)] [pre-lines '()]) (cond [(eof-object? line) (list 'pre pre-lines)] [(rxmatch #/^ / line) (loop (read-line-reader reader) (append pre-lines (list (cgi:escape line))))] [else (unread-line-reader! reader line) (list 'pre pre-lines)]))) (let loop ([parsed '()] [line (read-line-reader r)] [text '()]) (cond [(eof-object? line) (add-to-list parsed (list 'p text))] [else (let ([line (cgi:escape line)]) (cond [(#/^(-+)/ line) => (lambda (match) ;; list syntax (unread-line-reader! r line) (loop (add-to-list (add-to-list parsed (list 'p text)) (parse-list match r)) (read-line-reader r) '()))] [(#/^(\++)/ line) => (lambda (match) ;; list syntax (unread-line-reader! r line) (loop (add-to-list (add-to-list parsed (list 'p text)) (parse-list match r)) (read-line-reader r) '()))] [(#/^(\*+)/ line) => (lambda (match) ;; h1/h2/h3 syntax (loop (add-to-list (add-to-list parsed (list 'p text)) (make-head match)) (read-line-reader r) '()))] [(rxmatch #/^ / line) => (lambda (match) ;; pre syntax (unread-line-reader! r line) (loop (add-to-list (add-to-list parsed (list 'p text)) (parse-pre r)) (read-line-reader r) '()))] [(#/^#([^(^)^\s]+)(?:\(([^\)]+)\))?/ line) => (lambda (match) ;; plugin syntax (let ([plugin (if (match 2) (list 'plugin (match 1) (string-split (match 2) #\,)) (list 'plugin (match 1)))]) (loop (add-to-list (add-to-list parsed (list 'p text)) plugin) (read-line-reader r) '())))] [(#/^\r$/ line) (loop (add-to-list parsed (list 'p text)) (read-line-reader r) '())] [else (loop parsed (read-line-reader r) (append text (list (wiki-parse-inline line))))]) )]))) (define (wiki-parse-inline content) (cond [(#/\[\[([^>^\]]+)>(https?:\/\/[^\]^\s]+)\]\]/ content) => make-alias-link] ;; [[alias>http://example.com]] [(#/\[\[([^\]]+)\]\]/ content) => make-wiki-name] ;; [[wiki-name]] [(#/https?:\/\/[^\s\)]+/ content) => make-link] ;; http://example.com [(#/&new\{([^\}]+)\}\;/ content) => make-amp] [else (if (equal? "" content) "" (make-inline content))])) (define (make-li body) (list 'li (list body))) (define (add-li-body li body) (list 'li (append (cadr li) (list body)))) (define (make-head match) (list 'head (string-length (match)) (wiki-parse-inline (match 'after)))) (define (make-wiki-name m) (make-inline (wiki-parse-inline (m 'before)) (list 'wiki-name (m 1)) (wiki-parse-inline (m 'after)))) (define make-inline (lambda args (list 'inline (remp (lambda (s) (and (string? s) (= (string-length s) 0))) args)))) (define (make-alias-link m) (make-inline (wiki-parse-inline (m 'before)) (list 'link (m 1) (m 2)) (wiki-parse-inline (m 'after)))) (define (make-amp match) (make-inline (wiki-parse-inline (match 'before)) (list 'new (match 1)) (wiki-parse-inline (match 'after)))) (define (make-link m) (make-inline (wiki-parse-inline (m 'before)) (list 'link (m) (m)) (wiki-parse-inline (m 'after)))) (define (page-exist? page-name) (find (lambda (s) (equal? page-name s)) (wiki-enum-pages))) (define (wiki->html get-parameter page-name wiki) (define (iter wiki) (if (string? wiki) (format #t "~a" wiki) (case (first wiki) [(p) (unless (null? (second wiki)) (display "
") (for-each iter (second wiki)) (print "
"))] [(head) (format #t "") (for-each display (second wiki)) (print "") ] [else (cond [(string? (first wiki)) (format #t "
~a
" (first wiki))] [else (format #t "unknown element wiki [[~a]]" (car wiki))])]))) (for-each iter wiki)) (define (page-name->path page-name) (string-append (wiki-data-direcoty) "/" (cgi:encode page-name) ".dat")) (define (wiki-enum-pages) (map cgi:decode (map (lambda (f) ((#/\.dat$/ f) 'before)) (filter (lambda (f) (#/\.dat$/ f)) (directory-list (wiki-data-direcoty)))))) (define (print-a uri text) (format #t "~a" uri text)) (define (read-raw-page page-name) (file->string (page-name->path page-name))) ;; page-name should be not encoded (define (print-edit-form page-name) (format #t "~s doesn't exist. Please create with following form.
" page-name) (print-edit-form page-name))))) (define (print-header . args) (define (top-menu url label) (format #t "