wiki wiki with versioning added by C-Keen on Fri Apr 10 12:40:02 2015

(use awful
     irregex
     posix
     utils
     lowdown
     (prefix fancypants fancypants-)
     lowdown-lolevel
     srfi-13
     (prefix comparse c-)
     clojurian-syntax
     uri-common)

(define wiki-dir "wiki")

(create-directory wiki-dir 'recursively)

(define start-page "WelcomeVisitor")
(define all-pages "AllPages")

(enable-sxml #t)

;; stolen form lowdown-extra
(define inline-wiki-link
  (c-enclosed-by (c-char-seq "{")
               (->> (c-none-of* (c-is #\}) c-item)
                    (c-one-or-more)
                    (node 'wiki-link))
               (c-char-seq "}")))

(define fancypants
  (c-any-of
   (map (lambda (mapping)
          (c-preceded-by (c-char-seq (car mapping))
                                (c-result (cdr mapping))))
        fancypants-default-map)))

(define wiki-html-conversion-rules*
  `((wiki-link . ,(lambda (_ page-name)
                    `(a (@ (href ,page-name)) . ,page-name)))))

(inline-hook (cons* fancypants inline-wiki-link (inline-hook)))
(markdown-html-conversion-rules* (append wiki-html-conversion-rules*
                                         (markdown-html-conversion-rules*)))

(define (link page #!optional description)
  `(a (@ (href ,page)) ,(or description page)))

(define (all-versions page-name)
  (sort (find-files wiki-dir test: (lambda (f) (equal? (pathname-file f) page-name)))
               (lambda (a b)
                 (> (->> a pathname-extension string->number)
                    (->> b pathname-extension string->number)))))

(define (latest-file page-name)
  (let ((candidates (all-versions page-name)))
    (and (not (null? candidates)) (car candidates))))

(define (new-version wiki-file)
  (let loop ((n 0))
    (let (( f  (pathname-replace-extension wiki-file (->string n))))
      (if (file-exists? f)
          (loop (add1 n))
          f))))

(define (version-links page-name path #!key edit?)
  (let ((pages (all-versions page-name)))
    (if (or (= (length pages) 1) (null? pages))
        '()
        `(small "Versions "
          ,(link (string-append page-name (if edit? "?edit=1" "")) "(latest)") " > "
          ,@(intersperse (map (lambda (f)
                               (link (string-append path "?version=" (pathname-extension f) (if edit? ";edit=1" ""))
                                     (pathname-extension f)))  (cdr pages)) " > ")))))

(define (show-all-pages)
  `((ul ,@(map (lambda (f)
                 `(li ,(link (pathname-file (car f)))))
               (fold  (lambda (f l)
                        (let-values (((dir file ext) (decompose-pathname f)))
                          (if (string> ext (alist-ref file l equal? "") )
                              (alist-update file ext l equal?)
                              l)))
                      '()
                      (find-files wiki-dir))))
    (hr)))

(define-page (irregex "/[^/]*")
  (lambda (path)
    (with-request-variables ((edit (nonempty as-boolean))
                             (content (nonempty as-string))
                             (search (nonempty as-string))
                             (version (nonempty as-string)))
      (when (equal? path "/")
            (redirect-to start-page))
      (let* ((edit-path (string-append path "?edit=1"))
             (wiki-file (make-pathname wiki-dir path))
             (page-name (string-trim path #\/))
             (display-or-start
              (lambda (page-name . page-version)
                `(,(let ((f (if (not (null? page-version))
                                (pathname-replace-extension wiki-file (car page-version))
                                (latest-file page-name))))
                  (if f
                      `(,(with-input-from-file f markdown->sxml)
                        (hr)
                        ,(version-links page-name path)
                        (hr)
                        (a (@ (href ,(if (not (null? page-version))
                                         (string-append edit-path ";version=" (car page-version))
                                         edit-path))) "Edit"))
                      (redirect-to edit-path)))
                  " | "))))
        `((h1 (a (@ (href ,(string-append path "?search=" page-name))) ,page-name))
          (hr)
          ,(cond
            ((equal? page-name all-pages)
             (show-all-pages))
            (edit
             (let ((content (let ((f (if version
                                         (pathname-replace-extension wiki-file version)
                                         (latest-file page-name))))
                              (if f
                                  (read-all f)
                                  "This is a new page, please edit"))))
               `((form (@ (action ,path)
                          (method post))
                       (textarea (@
                                  (name "content")
                                  (cols 80)
                                  (rows 25)
                                  (autofocus))
                                 ,content)
                       (hr)
                       ,(version-links page-name path edit?: #t)
                       (hr)
                       (input (@ (type "submit") (value "Save")))))))
            (version
             (display-or-start page-name version))
            (content
             (cond ((equal? content "[delete]")
                    (let ((file (latest-file page-name)))
                      (when (file-read-access? file)
                            (delete-file file))
                      (redirect-to start-page)))
                   (else
                    (with-output-to-file (new-version wiki-file)
                      (cut print content))
                    (redirect-to path))))
            (search
             `((h2 "Search results for " ,search)
               (ul
                ,(with-input-from-pipe
                  (sprintf "grep -ri ~a ~s" (qs search) wiki-dir)
                  (lambda ()
                    (map (lambda (l) (let* ((r (string-split l ":"))
                                            (p (pathname-file (car r)))
                                            (c (cdr r)))
                                       `(li (a (@ (href ,p)) ,p) " : " ,c))) (read-lines)))))))
            (else
             (display-or-start page-name)))
          (a (@ (href ,start-page)) ,start-page) " | "
          (a (@ (href ,all-pages)) ,all-pages) " | "
          (form (@ (action ,path) (method post))
                (input (@ (type "text") (name "search")))
                (input (@ (type "submit") (value "Search"))))))))
  method: '(get post))