Add variable documentation 0.2
authorFrank Duncan <frank@kank.net>
Thu, 21 Apr 2016 23:57:32 +0000 (18:57 -0500)
committerFrank Duncan <frank@kank.net>
Fri, 22 Apr 2016 00:00:28 +0000 (19:00 -0500)
13 files changed:
README.md
resources/emptydocs.lisp
resources/success1.lisp
resources/success1.md
src/main/docgen.asd
src/main/docgen.lisp
src/main/func.lisp
src/main/package.lisp
src/main/var.lisp [new file with mode: 0644]
src/test/docgen-test.asd
src/test/failures.lisp
src/test/main.lisp
wiki

index 039c15d3285d15767fb74b5caef86debe83000f7..43f0db22ceb6b4fb504d6bb3ccb96bce79176a89 100644 (file)
--- a/README.md
+++ b/README.md
@@ -4,7 +4,7 @@ Enforcement of documentation guidelines for my Common Lisp Projects, as well as
 
 I wish I had aspirations for this being some standard that someone else might follow, but realistically I'm just irritated at my own laziness with regard to documentation, so I wrote a solution.  The forceful nature of the validator is really just because I didn't want to write a smarter parser.  As an added bonus, all the docs now look the same when I look at them in the repl, so that's kind of nice.
 
-If you like, you can [download it](https://github.com/frankduncan/docgen/releases/download/0.1/docgen_0.1.tar.gz)
+If you like, you can [download it](https://github.com/frankduncan/docgen/releases/download/0.2/docgen_0.2.tar.gz)
 
 ## Usage
 
@@ -18,7 +18,36 @@ Packages are documented by sections broken up by one empty line, with the first
 
 ## Structure/Condition documentation
 
-Requirements are the same as the package
+Requirements are the same as the package documentation.
+
+## Variable documentation
+
+Variables should follow the template:
+
+```
+*VARIABLE*
+
+VALUE TYPE:
+
+  a generalized boolean
+
+INITIAL VALUE:
+
+  NIL
+
+DESCRIPTION:
+
+  *VARIABLE* is expected to be a boolean, but
+  most anything can be it.
+
+EXAMPLES:
+
+  (let ((*variable* t)) (go)) => let-it-go
+````
+
+There are three required sections and one optional section.  ```VALUE TYPE```, ```INITIAL VALUE```, and ```DESCRIPTION``` are freeform, with the ```EXAMPLES``` section following the same rules as below in the function documentation.
+
+### 
 
 ## Function documentation
 
index 608383136f2b8ce185ef2759dc73d8051e803dc5..7d4cca4f24224e1475f6d4079f4b6c0fd5552576 100644 (file)
@@ -1,6 +1,8 @@
 (defpackage #:emptydocs (:use :cl)
  (:export #:no-doc-condition #:no-doc-func))
 
+(defvar *special-variable* nil)
+
 (in-package #:emptydocs)
 
 (define-condition no-doc-condition nil nil)
index 716dda6e8429c872f37aca487185fc3537b5a433..3e33e13298c4f1359678d21b5bbebeea2300d77b 100644 (file)
@@ -5,12 +5,35 @@
 This is should all get pulled in and the markdown.md should be equal
 to success1.md.")
  (:export
+  #:*special-variable*
   #:test-condition
   #:func-that-does-stuff #:noargs #:result-list #:has-no-examples
   #:values-result #:has-optional #:has-keywords #:has-rest))
 
 (in-package #:success1)
 
+(defvar *special-variable* nil
+ "*SPECIAL-VARIABLE*
+
+VALUE TYPE:
+
+  a generalized boolean
+
+INITIAL VALUE:
+
+  NIL
+
+DESCRIPTION:
+
+  It is special, and a boolean.
+
+  When true, it satisfies if coniditions.  When NIL, it does not.
+  That may make it seem like it's not very special, but it is.
+
+EXAMPLES:
+
+  (let ((*special-variable* t)) (go)) => 'let-it-go")
+
 (define-condition test-condition nil nil
  (:documentation
   "Simple documentation.
index f0bf81d29f40ac795351dba5c0d97445644a0639..80c3c1ce406030bd17769fecaf1a694b8371375e 100644 (file)
@@ -6,6 +6,7 @@ This is should all get pulled in and the markdown.md should be equal to success1
 
 ## Contents
 
+* **variable [\*special\-variable\*](#variable-special-variable)** - It is special, and a boolean.
 * **function [func-that-does-stuff](#function-func-that-does-stuff)** - _func-that-does-stuff_ runs all the things against a file and returns as soon as the first func error is found.
 * **function [has-keywords](#function-has-keywords)** - _has-keywords_ runs all the things against a file and returns as soon as the first func error is found.
 * **function [has-no-examples](#function-has-no-examples)** - _has-no-examples_ runs all the things against a file and returns as soon as the first func error is found.
@@ -16,6 +17,26 @@ This is should all get pulled in and the markdown.md should be equal to success1
 * **condition [test-condition](#condition-test-condition)** - Simple documentation.
 * **function [values-result](#function-values-result)** - _values-result_ runs all the things against a file and returns as soon as the first func error is found.
 
+## Variable \*SPECIAL\-VARIABLE\*
+
+#### Value Type:
+
+a generalized boolean
+
+#### Initial Value:
+
+NIL
+
+#### Description:
+
+It is special, and a boolean.
+
+When true, it satisfies if coniditions.  When NIL, it does not. That may make it seem like it's not very special, but it is.
+
+#### Examples:
+
+```(let ((*special-variable* t)) (go))``` => ```'let-it-go```  
+
 ## Function **FUNC-THAT-DOES-STUFF**
 
 #### Syntax:
index 5e33b624268bc87872406790350ff2163212f844..3c63c458c6aacc007efde51c047a12fd25d9e66e 100644 (file)
@@ -1,8 +1,8 @@
 (asdf:defsystem docgen
  :name "Documentation Generator"
- :version "0.1"
+ :version "0.2"
  :maintainer "Frank Duncan (frank@kank.com)"
  :author "Frank Duncan (frank@kank.com)"
  :serial t
- :components ((:file "package") (:file "func") (:file "pkg") (:file "struc") (:file "docgen"))
+ :components ((:file "package") (:file "func") (:file "var") (:file "pkg") (:file "struc") (:file "docgen"))
  :depends-on (#-travis :cl-ppcre)) ; Don't load libraries in travis
index 8d27051be7e66ea6cfc6025f432c509693063695..f873f994857d07e2699e00a4ea797498db32db64 100644 (file)
@@ -5,7 +5,7 @@
 
 (defun get-symb-type (symb)
  (cond
-  ;((documentation symb 'variable) :variable)
+  ((documentation symb 'variable) :variable)
   ((documentation symb 'structure) :structure)
   ((documentation symb 'function) :function)))
 
@@ -47,6 +47,7 @@ DESCRIPTION:
         (case (get-symb-type symb)
          (:function (docgen-func:doc->ast symb))
          (:structure (docgen-struc:doc->ast symb))
+         (:variable (docgen-var:doc->ast symb))
          (t (error (make-condition 'validation-failure :msg (format nil "Symbol ~A has no documentation" symb)))))))
       symbs))))))
 
@@ -105,7 +106,13 @@ EXAMPLES:
         (docgen-struc:ast->category-name (docgen-struc:doc->ast symb))
         (docgen-struc:ast->short-name (docgen-struc:doc->ast symb))
         (docgen-struc:ast->link (docgen-struc:doc->ast symb))
-        (docgen-struc:ast->short-desc (docgen-struc:doc->ast symb))))))
+        (docgen-struc:ast->short-desc (docgen-struc:doc->ast symb))))
+      (:variable
+       (list
+        (docgen-var:ast->category-name (docgen-var:doc->ast symb))
+        (docgen-var:ast->short-name (docgen-var:doc->ast symb))
+        (docgen-var:ast->link (docgen-var:doc->ast symb))
+        (docgen-var:ast->short-desc (docgen-var:doc->ast symb))))))
     symbs))))
 
 (defun export-package (pkg)
@@ -134,6 +141,7 @@ DESCRIPTION:
     (mapcar
      (lambda (symb)
       (case (get-symb-type symb)
+       (:variable (docgen-var:ast->md (docgen-var:doc->ast symb)))
        (:function (docgen-func:ast->md (docgen-func:doc->ast symb)))
        (:structure (docgen-struc:ast->md (docgen-struc:doc->ast symb)))))
      symbs)))))
index b3926a12be7156be199689ff4c4dad9cbe969313..68b05dcf7b473985df52b5c56450ea0f973f856e 100644 (file)
         (text-item)
         (cond
          ((not (stringp text-item)) (list text-item))
-         ((not (cl-ppcre:scan (car remaining-keywords) text-item)) (list text-item))
+         ((not (cl-ppcre:scan (cl-ppcre:quote-meta-chars (car remaining-keywords)) text-item)) (list text-item))
          (t
           (let
-           ((split-text (cl-ppcre:split (car remaining-keywords) text-item :limit 1000)))
+           ((split-text (cl-ppcre:split (cl-ppcre:quote-meta-chars (car remaining-keywords)) text-item :limit 1000)))
            (apply #'append
             (list (car split-text))
             (mapcar (lambda (ti) (list (list :keyword (car remaining-keywords)) ti)) (cdr split-text)))))))
index 119922bc26a4f5849963add7f22d077b71fdd746..c834cbd649ba4e03ce7b05e665edb85fd18be7a6 100644 (file)
@@ -9,6 +9,9 @@ looking decent when used within a common lisp process."))
 (defpackage #:docgen-func (:use :cl)
  (:export #:doc->ast #:ast->md #:ast->link #:ast->short-name #:ast->short-desc #:ast->category-name))
 
+(defpackage #:docgen-var (:use :cl)
+ (:export #:doc->ast #:ast->md #:ast->link #:ast->short-name #:ast->short-desc #:ast->category-name))
+
 (defpackage #:docgen-pkg (:use :cl)
  (:export #:doc->ast #:ast->md))
 
diff --git a/src/main/var.lisp b/src/main/var.lisp
new file mode 100644 (file)
index 0000000..a3910f2
--- /dev/null
@@ -0,0 +1,177 @@
+(in-package #:docgen-var)
+
+(defvar *doc*)
+(defvar *prev-line*)
+(defun peek () (car *doc*))
+(defun next () (setf *prev-line* (pop *doc*)))
+(defun more () (not (not *doc*)))
+(defun prev-line () *prev-line*)
+
+(defvar *keywords*)
+
+(defun add-keyword (type)
+ (setf *keywords* (remove-duplicates (cons type *keywords*) :test #'string=)))
+
+(defun fire-error (msg) (error (make-instance 'docgen:validation-failure :msg msg)))
+
+(defun expect-blank-line ()
+ (let
+  ((prev (prev-line)))
+  (when (string/= "" (next)) (fire-error (format nil "Expected blank line after: ~A" prev)))))
+
+(defun verify-next-line (&key optional)
+ (cond
+  ((and optional (not (more))) t)
+  ((not (more)) (fire-error (format nil "Expected line after: ~A" (prev-line))))
+  ((cl-ppcre:scan " $" (peek)) (fire-error (format nil "Can't end line with a space: ~A" (peek))))
+  ((< 120 (length (peek))) (fire-error (format nil "Longer than 120 chars: ~A" (peek))))))
+
+(defun freeform->paragraphs (next next-optional)
+ (verify-next-line :optional t)
+ (let
+  ((next-line (next)))
+  (cond
+   ((and next-optional (not next-line)) (list ""))
+   ((and (string= "" next-line) (not (more))) (fire-error "Can't end with empty line"))
+   ((cl-ppcre:scan "^  [^ ].+" next-line)
+    (let
+     ((rest-of-freeform (freeform->paragraphs next next-optional)))
+     (cons
+      (format nil "~A~A~A"
+       (subseq next-line 2 (length next-line))
+       (if (and (car rest-of-freeform) (string/= "" (car rest-of-freeform))) " " "")
+       (car rest-of-freeform))
+      (cdr rest-of-freeform))))
+   ((string= "" next-line)
+    (if (string= next (peek))
+     (list "")
+     (cons "" (freeform->paragraphs next next-optional))))
+   (t (fire-error (format nil "Got unexpected line, requires blank lines or start with two spaces: ~S" next-line))))))
+
+(defun parse-freeform (start section next next-optional)
+ (when (string/= start (next)) (fire-error (format nil "Expected ~A instead of: ~A" start (prev-line))))
+ (expect-blank-line)
+ (let
+  ((paragraphs (freeform->paragraphs next next-optional)))
+  (list section (mapcar #'handle-text paragraphs))))
+
+(defun process-examples ()
+ (when (more)
+  (verify-next-line :optional t)
+  (cons
+   (let
+    ((example-scanner (cl-ppcre:create-scanner "^  ([^ ].+) => (.+)$"))
+     (next-line (next)))
+    (if (not (cl-ppcre:scan example-scanner next-line))
+     (fire-error (format nil "Example line does not match \"  example => result\": ~A" next-line))
+     (cl-ppcre:register-groups-bind (example result) (example-scanner next-line)
+      (list example result))))
+   (process-examples))))
+
+(defun parse-examples ()
+ (when (string/= "EXAMPLES:" (next)) (fire-error (format nil "Expected EXAMPLES: instead of: ~A" (prev-line))))
+ (expect-blank-line)
+ (list :examples (process-examples)))
+
+; For formatting of things like types in there
+(defun handle-text (text)
+ (labels
+  ((inject-keywords (text remaining-keywords)
+    (if
+     (not remaining-keywords)
+     (list text)
+     (apply #'append
+      (mapcar
+       (lambda
+        (text-item)
+        (cond
+         ((not (stringp text-item)) (list text-item))
+         ((not (cl-ppcre:scan (cl-ppcre:quote-meta-chars (car remaining-keywords)) text-item)) (list text-item))
+         (t
+          (let
+           ((split-text (cl-ppcre:split (cl-ppcre:quote-meta-chars (car remaining-keywords)) text-item :limit 1000)))
+           (apply #'append
+            (list (car split-text))
+            (mapcar (lambda (ti) (list (list :keyword (car remaining-keywords)) ti)) (cdr split-text)))))))
+       (inject-keywords text (cdr remaining-keywords)))))))
+  (list :text (inject-keywords text *keywords*))))
+; (map
+; (list :text text))
+
+(defun parse-header (var)
+ (verify-next-line)
+ (let*
+  ((var-name (symbol-name var)))
+  (when (not (string= var-name (peek)))
+   (fire-error (format nil "First line of ~A did not match: ~A, ~A" var var-name (peek))))
+  (when (cl-ppcre:scan "[a-z]" var-name)
+   (fire-error (format nil "Variable name should be all uppercase: ~A" var-name)))
+  (add-keyword var-name)
+  (next)
+  (expect-blank-line)
+  (list :variable var-name)))
+
+(defun internal-doc->ast (var doc)
+ (let
+  ((*doc* (cl-ppcre:split "\\n" doc :limit 1000))
+   (*prev-line* nil)
+   (*keywords* nil))
+  (cons (parse-header var)
+   (append
+    (list
+     (parse-freeform "VALUE TYPE:" :value-type "INITIAL VALUE:" nil)
+     (parse-freeform "INITIAL VALUE:" :initial-value "DESCRIPTION:" nil)
+     (parse-freeform "DESCRIPTION:" :description "EXAMPLES:" t))
+    (when (more) (list (parse-examples)))))))
+
+(defun doc->ast (var) (internal-doc->ast var (documentation var 'variable)))
+
+(defun format-text (text)
+ (format nil "~{~A~}"
+  (mapcar
+   (lambda (text)
+    (cond
+     ((stringp text) text)
+     ((and (listp text) (eql :keyword (car text))) (format nil "_~(~A~)_" (cadr text)))
+     (t (fire-error (format nil "Don't know how to convert text: ~S" text)))))
+   (cadr text))))
+
+(defun format-header (header)
+ (format nil "## Variable ~A
+
+"
+  (cl-ppcre:quote-meta-chars (second header))))
+
+(defun format-freeform (heading text)
+ (format nil "#### ~A:~%~%~{~A~%~^~%~}" heading (mapcar #'format-text (cadr text))))
+
+(defun format-examples (examples)
+ (if (not examples)
+  ""
+  (format nil "~%#### Examples:~%~%~{~A~%~}"
+   (mapcar
+    (lambda (example) (format nil "```~A``` => ```~A```  " (car example) (cadr example)))
+    (cadr examples)))))
+
+(defun ast->md (ast)
+ (flet
+  ((get-section (name) (find name ast :key #'car)))
+  (format nil "~A~A~%~A~%~A~A"
+   (format-header (get-section :variable))
+   (format-freeform "Value Type" (get-section :value-type))
+   (format-freeform "Initial Value" (get-section :initial-value))
+   (format-freeform "Description" (get-section :description))
+   (format-examples (get-section :examples)))))
+
+(defun ast->category-name (ast)
+ (declare (ignore ast))
+ "variable")
+
+(defun ast->short-name (ast)
+ (format nil "~(~A~)" (cl-ppcre:quote-meta-chars (second (find :variable ast :key #'car)))))
+
+(defun ast->link (ast)
+ (format nil "variable-~(~A~)" (cl-ppcre:regex-replace-all "\\*" (second (find :variable ast :key #'car)) "")))
+
+(defun ast->short-desc (ast)
+ (format-text (car (cadr (find :description ast :key #'car)))))
index 33c093032192553a40709563b4630f46c79746be..94eeceeb6b4f46c094b4aa7c49599236258a35a1 100644 (file)
@@ -1,6 +1,5 @@
 (asdf:defsystem docgen-test
  :name "Document Generator Tests"
- :version "0.1"
  :maintainer "Frank Duncan (frank@kank.com)"
  :author "Frank Duncan (frank@kank.com)"
  :serial t
index b264dbb154568a90b56664b9ddf55b2141d7535a..88311c474f9b13290501aa464f372b104a50880b 100644 (file)
@@ -376,3 +376,298 @@ DESCRIPTION:
 
 "
  "Result in UNUSED should be all upper case: REsULT")
+
+(deffailure-var-test
+ "Blank line - after value type"
+ "*UNUSED*
+
+VALUE TYPE:
+  generalized boolean
+
+INITIAL VALUE:
+
+  NIL
+
+DESCRIPTION:
+
+"
+ "Expected blank line after: VALUE TYPE:")
+
+(deffailure-var-test
+ "Blank line - after description"
+ "*UNUSED*
+
+VALUE TYPE:
+
+  generalized boolean
+
+INITIAL VALUE:
+
+  NIL
+
+DESCRIPTION:
+  Fail here
+
+"
+ "Expected blank line after: DESCRIPTION:")
+
+(deffailure-var-test
+ "Blank line - after examples"
+ "*UNUSED*
+
+VALUE TYPE:
+
+  generalized boolean
+
+INITIAL VALUE:
+
+  NIL
+
+DESCRIPTION:
+
+  Fail here
+
+EXAMPLES:
+  Fail here
+
+"
+ "Expected blank line after: EXAMPLES:")
+
+(deffailure-var-test
+ "Blank line - after header"
+ "*UNUSED*
+ Fail here
+"
+ "Expected blank line after: *UNUSED*")
+
+(deffailure-var-test
+ "Two spaces - beginning of value type"
+ "*UNUSED*
+
+VALUE TYPE:
+
+   a generalized boolean
+"
+ "Got unexpected line, requires blank lines or start with two spaces: \"   a generalized boolean\"")
+
+(deffailure-var-test
+ "Two spaces - beginning of initial value"
+ "*UNUSED*
+
+VALUE TYPE:
+
+  a generalized boolean
+
+INITIAL VALUE:
+
+   RESULT: fail here
+"
+ "Got unexpected line, requires blank lines or start with two spaces: \"   RESULT: fail here\"")
+
+(deffailure-var-test
+ "Two spaces - in description"
+ "*UNUSED*
+
+VALUE TYPE:
+
+  a generalized boolean
+
+INITIAL VALUE:
+
+  RESULT: a result
+
+DESCRIPTION:
+
+  This is a description
+
+  About some
+   things"
+ "Got unexpected line, requires blank lines or start with two spaces: \"   things\"")
+
+(deffailure-var-test
+ "Two spaces - in examples"
+ "*UNUSED*
+
+VALUE TYPE:
+
+  a generalized boolean
+
+INITIAL VALUE:
+
+  RESULT: a result
+
+DESCRIPTION:
+
+  This is a description
+
+EXAMPLES:
+
+  (example1) => (yo)
+   (example2) => (yoyo)"
+ "Example line does not match \"  example => result\":    (example2) => (yoyo)")
+
+(deffailure-var-test
+ "Two spaces - in examples"
+ "*UNUSED*
+
+VALUE TYPE:
+
+  a generalized boolean
+
+INITIAL VALUE:
+
+  RESULT: a result
+
+DESCRIPTION:
+
+  This is a description
+
+EXAMPLES:
+
+  (example1) => (yo)
+   (example2) => (yoyo)"
+ "Example line does not match \"  example => result\":    (example2) => (yoyo)")
+
+(deffailure-var-test
+ "Description - ends with empty line when last thing"
+ "*UNUSED*
+
+VALUE TYPE:
+
+  a generalized boolean
+
+INITIAL VALUE:
+
+  NIL
+
+DESCRIPTION:
+
+  Hello world
+
+"
+ "Can't end with empty line")
+
+(deffailure-var-test
+ "Description - malformed line"
+ "*UNUSED*
+
+VALUE TYPE:
+
+  a generalized boolean
+
+INITIAL VALUE:
+
+  NIL
+
+DESCRIPTION:
+
+ A mistake"
+ "Got unexpected line, requires blank lines or start with two spaces: \" A mistake\"")
+
+(deffailure-var-test
+ "Description - section doesn't start with description"
+ "*UNUSED*
+
+VALUE TYPE:
+
+  a generalized boolean
+
+INITIAL VALUE:
+
+  NIL
+
+DESCRIPTAION:
+
+"
+ "Got unexpected line, requires blank lines or start with two spaces: \"DESCRIPTAION:\"")
+
+(deffailure-var-test
+ "Examples - doesn't have arrow"
+ "*UNUSED*
+
+VALUE TYPE:
+
+  a generalized boolean
+
+INITIAL VALUE:
+
+  NIL
+
+DESCRIPTION:
+
+  This is a mock description.
+
+EXAMPLES:
+
+  *unused* - :success
+"
+ "Example line does not match \"  example => result\":   *unused* - :success")
+
+(deffailure-var-test
+ "Examples - doesn't start with EXAMPLES"
+ "*UNUSED*
+
+VALUE TYPE:
+
+  a generalized boolean
+
+INITIAL VALUE:
+
+  NIL
+
+DESCRIPTION:
+
+  This is a mock description.
+
+EXAAMPLES:
+
+  *unused* => :success"
+ "Got unexpected line, requires blank lines or start with two spaces: \"EXAAMPLES:\"")
+
+(deffailure-var-test
+ "Header - first line doesn't start with var-name (naturally all in upper case)"
+ "*UNUUSED*
+
+INITIAL VALUE:
+
+  RESULT: a pathname
+
+DESCRIPTION:
+
+"
+ "First line of *UNUSED* did not match: *UNUSED*, *UNUUSED*")
+
+(deffailure-var-test
+ "General - No value type"
+ "*UNUSED*
+
+INITIAL VALUE:
+
+  NIL
+
+DESCRIPTAION:
+
+"
+ "Expected VALUE TYPE: instead of: INITIAL VALUE:")
+
+(deffailure-var-test
+ "General - No initial value"
+ "*UNUSED*
+
+VALUE TYPE:
+
+  a generalized boolean
+
+DESCRIPTION:
+
+"
+ "Got unexpected line, requires blank lines or start with two spaces: \"DESCRIPTION:\"")
+
+(deffailure-var-test
+ "General - Ends early"
+ "*UNUSED*
+
+VALUE TYPE:
+
+  a generalized boolean"
+ "Got unexpected line, requires blank lines or start with two spaces: NIL")
index 572636ef8441419a4781a0698a7c53ca35609e8b..9bae683d13ec013175a444b8b52120f5def15fed 100644 (file)
@@ -52,7 +52,7 @@
 
 (defmacro deffailure-func-test (name doc expected)
  `(deftest
-   ,name
+   ,(format nil "Func - ~A" name)
    (lambda ()
     (handler-case
      (progn
         (string= ,expected result)
         (format t "  Got error:~%~S~%  but expected~%~S~%" result ,expected))))))))
 
+(defmacro deffailure-var-test (name doc expected)
+ `(deftest
+   ,(format nil "Var - ~A" name)
+   (lambda ()
+    (handler-case
+     (progn
+      (funcall
+       (symbol-function (find-symbol "INTERNAL-DOC->AST" :docgen-var))
+       '*unused*
+       ,doc)
+      nil)
+     (docgen:validation-failure (vf)
+      (let
+       ((result (funcall (symbol-function (find-symbol "VALIDATION-FAILURE-MSG" :docgen)) vf)))
+       (or
+        (string= ,expected result)
+        (format t "  Got error:~%~S~%  but expected~%~S~%" result ,expected))))))))
+
 (defsuccesstest :success1 "resources/success1.lisp" "resources/success1.md")
 (deffailuretest :emptydocs "resources/emptydocs.lisp"
  `((:failure :emptydocs "Package EMPTYDOCS has no documentation")
diff --git a/wiki b/wiki
index 06c150fca744ae5052741ca676e09d7081d0eefe..8c039edd1301fc95e5d4e0570ae7a5d1256d176a 160000 (submodule)
--- a/wiki
+++ b/wiki
@@ -1 +1 @@
-Subproject commit 06c150fca744ae5052741ca676e09d7081d0eefe
+Subproject commit 8c039edd1301fc95e5d4e0570ae7a5d1256d176a