;;; -*- Scheme -*- ;; eggdoc-svnwiki ;; Limitations ;; Nested lists are not handled ;; Elements within lists may not be expanded (untested) ;; Nested definition lists won't render correctly (limitation of svnwiki) ;; #xxx at beginning of line or within is rendered as list (svnwiki bug) ;; Certain eggdocs may contain extraneous whitespace; if at beginning of line, ;; may result in inadvertent PRE. ;; Poor escaping will no doubt cause problems. ;; is not a valid svnwiki tag. ;; not handled. Should use alt= if present. (module eggdoc-svnwiki (eggdoc:make-svnwiki-stylesheet eggdoc->svnwiki eggdoc:svnwiki-override!) (import scheme chicken) (use eggdoc) (use sxml-transforms sxpath) (use regex data-structures srfi-1) ;;; tag helpers (define (eggdoc:warning . rest) ; Maybe move this to eggdoc. (when (eggdoc:warnings) (apply warning rest))) (define (eat-tag . expr) (if (pair? expr) (cdr expr) '() )) (define (eat-tag-and-warn tag . body) (eggdoc:warning (conc "eggdoc-svnwiki: ate " tag " tag")) (eat-tag tag body)) (define (alias-tag alias) (lambda (tag . body) (cons alias body))) (define (discard . args) '()) (define (discard-and-warn . args) (eggdoc:warning (conc "eggdoc-svnwiki: discarded " args)) '()) (define (make-section prefix) (lambda (tag name . contents) (list #\newline prefix name #\newline #\newline contents))) (define make-defsig eggdoc:make-defsig) ;;; main ;; The only character worth escaping is < ; everything else such as { ' etc. ;; occurs in pairs and can't be detected here. Even < is overloaded; ;; <-> is converted to an entity but is a tag. (define string->good-svnwiki ; (make-char-quotator '((#\< . "<"))) ; incredibly ugly identity) ;; Pull certain headers out and place them in an About section. ;; No reordering is done so they will occur in the same order as in the source. (define (partition-about doc) (receive (headers body) (partition (lambda (x) (memq (car x) '(requires history author license))) (cdar doc)) `((eggdoc:begin ,@body (eggdoc:about "About this egg" ,@headers) )))) (define (latest-version doc) (let ((path ((sxpath '(history (version 1))) doc))) (if (null? path) #f (cadar path)))) (define (eggdoc:make-svnwiki-stylesheet Doc) (let* ((egg-name (eggdoc:derive-egg-name Doc)) ;; (egg-version (latest-version Doc)) ) `( (eggdoc:about ((history *macro* . ,(lambda (tag . versions) `(subsection "Version history" (dl ,versions)))) (author *macro* . ,(lambda (tag name) `(subsection "Author" ,name))) (requires *macro* . ,(lambda (tag . requires) (if (null? requires) '() `(subsection "Requirements" (p ,(intersperse (map (lambda (x) `(tt ,x)) requires) ", ")))))) ;; If bare license, assume pre tag, otherwise verbatim (license *macro* . ,(lambda (tag contents) `(subsection "License" ,(if (pair? contents) contents `(pre (@ (id "license")) ,contents)))))) . ,(make-section "== ")) (name *macro* . ,discard) ;; eat this; advisory element for title and page header ;; [though, this will eat ALL "name" elements] (version *macro* . ,(lambda (tag number . desc) `((dt ,number) (dd ,desc)))) (history . ,discard) (author . ,discard) (license . ,discard) ;; Sections (section . ,(make-section "== ")) (subsection . ,(make-section "=== ")) (subsubsection . ,(make-section "==== ")) (subsubsubsection . ,(make-section "===== ")) (description *macro* . ,(lambda (tag . contents) `(section ,egg-name (p ,contents) (toc)))) (toc . ,(lambda (tag . body) '("[[toc:]]\n"))) (eggdoc:begin . ,(lambda (tag . body) `( "[[tags:egg]]" ,@body #\newline))) ;; If arglist is empty, download section is not shown (use chicken-setup). ;; Otherwise, insert verbatim. (download *macro* . ,(lambda (tag name) (if (pair? name) `(section "Download" ,name) '() ; Default download is omitted ))) ;; ;; (requires elt_1 .. elt_n) -- creates unordered list ;; (requires *macro* . ,(lambda (tag . reqs) ;; `(section "Requires" ;; ,(unordered-list reqs)))) ;; (usage) or (usage elt_1 .. elt_n) ;; If arglist is empty, section is skipped, as (require-extension eggname) ;; should be obvious by now. ;; Otherwise, inserts elements verbatim. (usage *macro* . ,(lambda (tag . reqs) (if (null? reqs) '() ; `(section "Usage" (tt "(require-extension " ,egg-name ")")) `(section "Usage" ,reqs #\newline)))) (documentation *macro* . ,(lambda (tag . elts) `(section "Documentation" ,elts))) ;; should be more descriptive than "group" (group . ,eat-tag) ;;; Signatures ;; Syntax: (definition (signatures (signature type sig) ...) def) ;; Not enforced, though. (signature *macro* . ,(lambda (tag type sig) `(#\newline ("<" ,type ">") ,sig ("")))) (signatures . ,eat-tag ; (lambda (tag . sigs) (intersperse sigs '((br)) )) ) (definition . ,(lambda (tag term . def) (list term #\newline def #\newline #\newline))) ;; Redundant -- should generate these programatically -- or change to pass type in an attribute (macro *macro* . ,make-defsig) (record *macro* . ,make-defsig) (procedure *macro* . ,make-defsig) (parameter *macro* . ,make-defsig) (read-syntax *macro* . ,make-defsig) (with-default-param *macro* . ,(lambda (tag param default) (conc "(" param ") [default: " default "]"))) ;; This works by calling pre-post-order on symbol-table's children ;; with the 'describe binding prepended to global bindings (so it's effectively local). ;; This means symbol-table can't be a macro so HTML conversion must be ;; performed manually. It may be possible to call post-order using universal-conversion-rules. ;; "class" is ignored under svnwiki, but we include it anyway. (symbol-table ((describe *macro* . ,(lambda (tag symbol description) `(tr (td (@ (class "symbol")) ,symbol) (td ,description))))) . ,(lambda (tag . contents) `(#\newline "
" ,contents "
" #\newline))) ;; the same as license (examples *macro* . ,(lambda (tag . contents) `(section "Examples" ,contents))) ;; (url *macro* . ,(lambda (tag href . contents) ;; `(a (@ (href ,href)) ;; ,(if (pair? contents) ;; contents ;; href)))) ;; Since svnwiki ignores class/id attributes we could probably pass through ;; any table attributes specified by the user for future use; ;; but oh well. (@ *macro* . ,discard-and-warn) ;;; svnwiki-format output (*text* . ,(lambda (trigger str) (if (string? str) (string->good-svnwiki str) str))) (*default* . ,(lambda (tag . elts) (error 'eggdoc-svnwiki (conc "Illegal tag '" tag "'")))) ;; When commmented out, do NOT accept unknown eggdoc SXML (p . ,(lambda (tag . elts) (list #\newline elts #\newline))) (tt . ,(lambda (tag . elts) (list "{{" elts "}}"))) (code . ,(lambda (tag . elts) (list "{{" elts "}}"))) (strong . ,(lambda (tag . elts) (list "'''" elts "'''"))) (emph . ,(lambda (tag . elts) (list "''" elts "''"))) (br . ,(lambda (tag) #\newline)) (b *macro* . ,(alias-tag 'strong)) (i *macro* . ,(alias-tag 'emph)) (em *macro* . ,(alias-tag 'emph)) (& . ,(lambda (tag . elts) (map (lambda (e) (case (string->symbol e) ((copyright) "(C)") ((lambda) "lambda") ((mdash) "---") ((ndash) "--") (else (eggdoc:warning (conc "unhandled & element: " e))))) elts))) ;; svnwiki supports basic tables using HTML-like tags (table . ,(lambda (tag . elts) (list "" elts "
" #\newline))) (th . ,(lambda (tag . elts) ; Probably not accepted anyway. (list "" elts ""))) (tr . ,(lambda (tag . elts) (list "" elts "" #\newline))) (td . ,(lambda (tag . elts) (list "" elts ""))) ;; Lists (dl . ,(lambda (tag . elts) (list #\newline elts))) ; must ensure blank line at beginning! (dt . ,(lambda (tag . body) (list "; " body " "))) (dd . ,(lambda (tag . body) (list ": " body #\newline))) ;; Nested lists are not handled. (ul ((li . ,(lambda (tag . body) (list "* " body #\newline)))) . ,(lambda (tag . body) (list #\newline body))) (ol ((li . ,(lambda (tag . body) (list "# " body #\newline)))) . ,(lambda (tag . body) (list #\newline body))) ;; div/span (div . ,eat-tag-and-warn) (span . ,eat-tag-and-warn) ;; quoting (q . ,(lambda (tag . elts) (list #\" elts #\"))) (blockquote . ,(lambda (tag . body) (list #\newline "
" body "
" #\newline))) ;; Pre tags have space before each line; we change the default ;; text handler to replace newlines with newline + space. Also, ;; we prepend a single newline. Note that text of inline elements ;; will also be replaced, which may be illegal under svnwiki. ;; (Note: we do not escape. svnwiki usually ignores escapes ;; but in some cases this may cause problems.) (pre ((*text* . ,(lambda (trigger str) (if (string? str) (string-substitute "\n" "\n " str ; (string->good-svnwiki str) #:global) str)))) . ,(lambda (tag . elts) ;; Prepending newline to
 is a recent change because people are
            ;; embedding 
 in 

. This could cause some extraneous space in ;; proper documents. (list #\newline #\space elts #\newline))) ;; Links ;; name attributes are ignored (as svnwiki has no name anchors). ;; However, links to such anchors (#name) will be passed through ;; (and render as expected, but with no functioning target). (a *macro* . ,(lambda elems (let ((link ((sxpath '(@ href *text*)) elems)) (contents ((sxpath '((*not* @))) elems))) ;; get all text and elts (non-@) (if (null? link) ;; Not an href= -- a name=, for example contents (if (pair? contents) `(url ,link ,contents) `(url ,link)))))) (url . ,(lambda (tag link . contents) (if (pair? contents) (list "[[" link "|" contents "]]") ;; contents is not spliced in (list "[[" link "]]")) )) ;; so it remains one argument. ;; Images -- use alt text, else discard. (img *macro* . ,(lambda elems (let ((alt ((sxpath '(@ alt *text*)) elems))) (if (null? alt) (discard-and-warn elems) alt)))) ))) ;; (eggdoc->svnwiki DOC [XML-STYLESHEET]) ;; Default stylesheet is (eggdoc:make-svnwiki-stylesheet doc), which is user-visible ;; so you can append to or otherwise modify it, and pass it in. (define (eggdoc->svnwiki doc . rest) (let-optionals rest ((ss (eggdoc:make-svnwiki-stylesheet doc))) (SRV:send-reply (pre-post-order (partition-about doc) ss)) (eggdoc:result))) ;; Set default transformer and stylesheet to svnwiki. (define (eggdoc:svnwiki-override!) (eggdoc:default-stylesheet-maker eggdoc:make-svnwiki-stylesheet) (eggdoc:default-transformer eggdoc->svnwiki)) )