From ec27b4764845777ac7b18390e18a3fa3fbd0e2f5 Mon Sep 17 00:00:00 2001 From: Magnar Sveen Date: Sun, 30 Sep 2012 08:54:14 +0200 Subject: [PATCH] Turn tests into examples that can both be tested and turned into docs --- bang.el | 61 +++++++++--------- create-docs.sh | 7 +++ docs.md | 144 +++++++++++++++++++++++++++++++++++++++++++ examples-to-docs.el | 31 ++++++++++ examples-to-tests.el | 19 ++++++ examples.el | 69 +++++++++++++++++++++ run-tests.sh | 2 +- tests.el | 94 ---------------------------- 8 files changed, 303 insertions(+), 124 deletions(-) create mode 100755 create-docs.sh create mode 100644 docs.md create mode 100644 examples-to-docs.el create mode 100644 examples-to-tests.el create mode 100644 examples.el delete mode 100644 tests.el diff --git a/bang.el b/bang.el index 1094f4e..91c9f87 100644 --- a/bang.el +++ b/bang.el @@ -27,7 +27,12 @@ (defalias '!map 'mapcar) +(defun !map (fn list) + "Returns a new list consisting of the result of applying FN to the items in list." + (mapcar fn list)) + (defmacro !!map (form list) + "Anaphoric form of `!map'." `(!map (lambda (it) ,form) ,list)) (defun !reduce-from (fn initial-value list) @@ -36,16 +41,13 @@ first item in LIST, then applying FN to that result and the 2nd item, etc. If LIST contains no items, returns INITIAL-VALUE and FN is not called." (let ((acc initial-value)) - (while list - (setq acc (funcall fn acc (car list))) - (setq list (cdr list))) - acc)) + (while list + (setq acc (funcall fn acc (car list))) + (setq list (cdr list))) + acc)) (defmacro !!reduce-from (form initial-value list) - "Anaphoric form of `!reduce'. Returns the result of applying -FORM to INITIAL-VALUE and the first item in LIST, then applying -FORM to that result and the 2nd item, etc. If INITIAL-VALUE -contains no items, returns INITIAL-VALUE and FORM is not called." + "Anaphoric form of `!reduce-from'." `(let ((!--list ,list) (!--acc ,initial-value)) (while !--list @@ -66,12 +68,7 @@ LIST has only 1 item, it is returned and FN is not called." (funcall fn))) (defmacro !!reduce (form list) - "Returns the result of applying FORM to the first 2 items in LIST, -then applying FORM to that result and the 3rd item, etc. If -LIST contains no items, FORM must accept no arguments as -well, and reduce returns the result of calling FORM with no -arguments. If LIST has only 1 item, it is returned and FORM -is not called." + "Anaphoric form of `!reduce'." (if (eval list) `(!!reduce-from ,form ,(car (eval list)) ',(cdr (eval list))) `(let (acc it) ,form))) @@ -86,7 +83,7 @@ is not called." (nreverse result))) (defmacro !!filter (form list) - "Returns a new list of the items in LIST for which FORM returns a non-nil value." + "Anaphoric form of `!filter'." `(let ((!--list ,list) (!--result '())) (while !--list @@ -101,7 +98,7 @@ is not called." (!!filter (not (funcall fn it)) list)) (defmacro !!remove (form list) - "Returns a new list of the items in LIST for which FORM returns nil." + "Anaphoric form of `!remove'." `(!!filter (not ,form) ,list)) (defun !concat (&rest lists) @@ -115,11 +112,15 @@ Thus function FN should return a collection." (apply '!concat (!map fn list))) (defmacro !!mapcat (form list) - "Returns the result of applying concat to the result of applying map to FORM and LIST. -Thus function FORM should return a collection." + "Anaphoric form of `!mapcat'." `(apply '!concat (!!map ,form ,list))) -(defalias '!partial 'apply-partially) +(defun !partial (fn &rest args) + "Takes a function FN and fewer than the normal arguments to FN, +and returns a fn that takes a variable number of additional ARGS. +When called, the returned function calls FN with args + +additional args." + (apply 'apply-partially fn args)) (defun !uniq (list) "Return a new list with all duplicates removed. @@ -143,16 +144,18 @@ or with `!compare-fn' if that's non-nil." "Return whether LIST contains ELEMENT. The test for equality is done with `equal', or with `!compare-fn' if that's non-nil." - (cond - ((null !compare-fn) (member element list)) - ((eq !compare-fn 'eq) (memq element list)) - ((eq !compare-fn 'eql) (memql element list)) - (t - (let ((lst list)) - (while (and lst - (not (funcall !compare-fn element (car lst)))) - (setq lst (cdr lst))) - lst)))) + (not + (null + (cond + ((null !compare-fn) (member element list)) + ((eq !compare-fn 'eq) (memq element list)) + ((eq !compare-fn 'eql) (memql element list)) + (t + (let ((lst list)) + (while (and lst + (not (funcall !compare-fn element (car lst)))) + (setq lst (cdr lst))) + lst)))))) (defvar !compare-fn nil "Tests for equality use this function or `equal' if this is nil. diff --git a/create-docs.sh b/create-docs.sh new file mode 100755 index 0000000..462a312 --- /dev/null +++ b/create-docs.sh @@ -0,0 +1,7 @@ +#!/usr/bin/env bash + +if [ -z "$EMACS" ] ; then + EMACS="emacs" +fi + +$EMACS -batch -l examples-to-docs.el -l bang.el -l examples.el -f create-docs-file diff --git a/docs.md b/docs.md new file mode 100644 index 0000000..47cd06a --- /dev/null +++ b/docs.md @@ -0,0 +1,144 @@ +## !map `(fn list)` + +Returns a new list consisting of the result of applying FN to the items in list. + +```cl +(!map (lambda (num) (* num num)) (quote (1 2 3 4))) ;; => (quote (1 4 9 16)) +(!map (quote square) (quote (1 2 3 4))) ;; => (quote (1 4 9 16)) +(!!map (* it it) (quote (1 2 3 4))) ;; => (quote (1 4 9 16)) +``` + +## !reduce-from `(fn initial-value list)` + +Returns the result of applying FN to INITIAL-VALUE and the +first item in LIST, then applying FN to that result and the 2nd +item, etc. If LIST contains no items, returns INITIAL-VALUE and +FN is not called. + +```cl +(!reduce-from (quote +) 7 (quote nil)) ;; => 7 +(!reduce-from (quote +) 7 (quote (1))) ;; => 8 +(!reduce-from (quote +) 7 (quote (1 2))) ;; => 10 +(!!reduce-from (+ acc it) 7 (quote (1 2 3))) ;; => 13 +``` + +## !reduce `(fn list)` + +Returns the result of applying FN to the first 2 items in LIST, +then applying FN to that result and the 3rd item, etc. If LIST +contains no items, FN must accept no arguments as well, and +reduce returns the result of calling FN with no arguments. If +LIST has only 1 item, it is returned and FN is not called. + +```cl +(!reduce (quote +) (quote nil)) ;; => 0 +(!reduce (quote +) (quote (1))) ;; => 1 +(!reduce (quote +) (quote (1 2))) ;; => 3 +(!reduce (lambda (memo item) (format %s-%s memo item)) (quote (1 2 3))) ;; => 1-2-3 +(!!reduce (format %s-%s acc it) (quote (1 2 3))) ;; => 1-2-3 +(!!reduce (format %s-%s acc it) (quote nil)) ;; => nil-nil +``` + +## !filter `(fn list)` + +Returns a new list of the items in LIST for which FN returns a non-nil value. + +```cl +(!filter (lambda (num) (= 0 (% num 2))) (quote (1 2 3 4))) ;; => (quote (2 4)) +(!filter (quote even?) (quote (1 2 3 4))) ;; => (quote (2 4)) +(!!filter (= 0 (% it 2)) (quote (1 2 3 4))) ;; => (quote (2 4)) +``` + +## !remove `(fn list)` + +Returns a new list of the items in LIST for which FN returns nil. + +```cl +(!remove (lambda (num) (= 0 (% num 2))) (quote (1 2 3 4))) ;; => (quote (1 3)) +(!remove (quote even?) (quote (1 2 3 4))) ;; => (quote (1 3)) +(!!remove (= 0 (% it 2)) (quote (1 2 3 4))) ;; => (quote (1 3)) +``` + +## !concat `(&rest lists)` + +Returns a new list with the concatenation of the elements in +the supplied LISTS. + +```cl +(!concat) ;; => nil +(!concat (quote (1))) ;; => (quote (1)) +(!concat (quote (1)) (quote (2))) ;; => (quote (1 2)) +(!concat (quote (1)) (quote (2 3)) (quote (4))) ;; => (quote (1 2 3 4)) +``` + +## !mapcat `(fn list)` + +Returns the result of applying concat to the result of applying map to FN and LIST. +Thus function FN should return a collection. + +```cl +(!mapcat (quote list) (quote (1 2 3))) ;; => (quote (1 2 3)) +(!mapcat (lambda (item) (list 0 item)) (quote (1 2 3))) ;; => (quote (0 1 0 2 0 3)) +(!!mapcat (list 0 it) (quote (1 2 3))) ;; => (quote (0 1 0 2 0 3)) +``` + +## !partial `(fn &rest args)` + +Takes a function FN and fewer than the normal arguments to FN, +and returns a fn that takes a variable number of additional ARGS. +When called, the returned function calls FN with args + +additional args. + +```cl +(funcall (!partial (quote +) 5) 3) ;; => 8 +(funcall (!partial (quote +) 5 2) 3) ;; => 10 +``` + +## !difference `(list list2)` + +Return a new list with only the members of LIST that are not in LIST2. +The test for equality is done with `equal', +or with `!compare-fn' if that's non-nil. + +```cl +(!difference (quote nil) (quote nil)) ;; => (quote nil) +(!difference (quote (1 2 3)) (quote (4 5 6))) ;; => (quote (1 2 3)) +(!difference (quote (1 2 3 4)) (quote (3 4 5 6))) ;; => (quote (1 2)) +``` + +## !intersection `(list list2)` + +Return a new list containing only the elements that are members of both LIST and LIST2. +The test for equality is done with `equal', +or with `!compare-fn' if that's non-nil. + +```cl +(!intersection (quote nil) (quote nil)) ;; => (quote nil) +(!intersection (quote (1 2 3)) (quote (4 5 6))) ;; => (quote nil) +(!intersection (quote (1 2 3 4)) (quote (3 4 5 6))) ;; => (quote (3 4)) +``` + +## !uniq `(list)` + +Return a new list with all duplicates removed. +The test for equality is done with `equal', +or with `!compare-fn' if that's non-nil. + +```cl +(!uniq (quote nil)) ;; => (quote nil) +(!uniq (quote (1 2 2 4))) ;; => (quote (1 2 4)) +``` + +## !contains? `(list element)` + +Return whether LIST contains ELEMENT. +The test for equality is done with `equal', +or with `!compare-fn' if that's non-nil. + +```cl +(!contains? (quote (1 2 3)) 1) ;; => t +(!contains? (quote (1 2 3)) 2) ;; => t +(!contains? (quote nil) (quote nil)) ;; => nil +(!contains? (quote nil) 1) ;; => nil +(!contains? (quote (1 2 4)) 3) ;; => nil +``` diff --git a/examples-to-docs.el b/examples-to-docs.el new file mode 100644 index 0000000..4aa2b65 --- /dev/null +++ b/examples-to-docs.el @@ -0,0 +1,31 @@ +(defvar functions '()) + +(defun example-to-string (example) + (let ((actual (car example)) + (expected (cadr (cdr example)))) + (format "%s ;; => %s" actual expected))) + +(defun examples-to-strings (examples) + (let (result) + (while examples + (setq result (cons (example-to-string examples) result)) + (setq examples (cddr (cdr examples)))) + (nreverse result))) + +(defmacro defexamples (cmd &rest examples) + `(add-to-list 'functions (list + ',cmd ;; command name + (cadr (symbol-function ',cmd)) ;; signature + (car (cddr (symbol-function ',cmd))) ;; docstring + (examples-to-strings ',examples)))) ;; examples + +(defun function-to-md (function) + (let ((command-name (car function)) + (signature (cadr function)) + (docstring (cadr (cdr function))) + (examples (mapconcat 'identity (cadr (cddr function)) "\n"))) + (format "## %s `%s`\n\n%s\n\n```cl\n%s\n```\n" command-name signature docstring examples))) + +(defun create-docs-file () + (with-temp-file "./docs.md" + (insert (mapconcat 'function-to-md (nreverse functions) "\n")))) diff --git a/examples-to-tests.el b/examples-to-tests.el new file mode 100644 index 0000000..080a65f --- /dev/null +++ b/examples-to-tests.el @@ -0,0 +1,19 @@ +(require 'ert) + +(defun examples-to-should-1 (examples) + (let ((actual (car examples)) + (expected (cadr (cdr examples)))) + `(should (equal ,actual ,expected)))) + +(defun examples-to-should (examples) + (let (result) + (while examples + (setq result (cons (examples-to-should-1 examples) result)) + (setq examples (cddr (cdr examples)))) + (nreverse result))) + +(defmacro defexamples (cmd &rest examples) + `(ert-deftest ,cmd () + ,@(examples-to-should examples))) + +(provide 'examples-to-tests) diff --git a/examples.el b/examples.el new file mode 100644 index 0000000..aba8781 --- /dev/null +++ b/examples.el @@ -0,0 +1,69 @@ +(require 'bang) + +(defun even? (num) (= 0 (% num 2))) +(defun square (num) (* num num)) + +(defexamples !map + (!map (lambda (num) (* num num)) '(1 2 3 4)) => '(1 4 9 16) + (!map 'square '(1 2 3 4)) => '(1 4 9 16) + (!!map (* it it) '(1 2 3 4)) => '(1 4 9 16)) + +(defexamples !reduce-from + (!reduce-from '+ 7 '()) => 7 + (!reduce-from '+ 7 '(1)) => 8 + (!reduce-from '+ 7 '(1 2)) => 10 + (!!reduce-from (+ acc it) 7 '(1 2 3)) => 13) + +(defexamples !reduce + (!reduce '+ '()) => 0 + (!reduce '+ '(1)) => 1 + (!reduce '+ '(1 2)) => 3 + (!reduce (lambda (memo item) (format "%s-%s" memo item)) '(1 2 3)) => "1-2-3" + (!!reduce (format "%s-%s" acc it) '(1 2 3)) => "1-2-3" + (!!reduce (format "%s-%s" acc it) '()) => "nil-nil") + +(defexamples !filter + (!filter (lambda (num) (= 0 (% num 2))) '(1 2 3 4)) => '(2 4) + (!filter 'even? '(1 2 3 4)) => '(2 4) + (!!filter (= 0 (% it 2)) '(1 2 3 4)) => '(2 4)) + +(defexamples !remove + (!remove (lambda (num) (= 0 (% num 2))) '(1 2 3 4)) => '(1 3) + (!remove 'even? '(1 2 3 4)) => '(1 3) + (!!remove (= 0 (% it 2)) '(1 2 3 4)) => '(1 3)) + +(defexamples !concat + (!concat) => nil + (!concat '(1)) => '(1) + (!concat '(1) '(2)) => '(1 2) + (!concat '(1) '(2 3) '(4)) => '(1 2 3 4)) + +(defexamples !mapcat + (!mapcat 'list '(1 2 3)) => '(1 2 3) + (!mapcat (lambda (item) (list 0 item)) '(1 2 3)) => '(0 1 0 2 0 3) + (!!mapcat (list 0 it) '(1 2 3)) => '(0 1 0 2 0 3)) + +(defexamples !partial + (funcall (!partial '+ 5) 3) => 8 + (funcall (!partial '+ 5 2) 3) => 10) + +(defexamples !difference + (!difference '() '()) => '() + (!difference '(1 2 3) '(4 5 6)) => '(1 2 3) + (!difference '(1 2 3 4) '(3 4 5 6)) => '(1 2)) + +(defexamples !intersection + (!intersection '() '()) => '() + (!intersection '(1 2 3) '(4 5 6)) => '() + (!intersection '(1 2 3 4) '(3 4 5 6)) => '(3 4)) + +(defexamples !uniq + (!uniq '()) => '() + (!uniq '(1 2 2 4)) => '(1 2 4)) + +(defexamples !contains? + (!contains? '(1 2 3) 1) => t + (!contains? '(1 2 3) 2) => t + (!contains? '() '()) => nil + (!contains? '() 1) => nil + (!contains? '(1 2 4) 3) => nil) diff --git a/run-tests.sh b/run-tests.sh index 32cda47..5043480 100755 --- a/run-tests.sh +++ b/run-tests.sh @@ -4,4 +4,4 @@ if [ -z "$EMACS" ] ; then EMACS="emacs" fi -$EMACS -batch -l ert.el -l bang.el -l tests.el -f ert-run-tests-batch-and-exit +$EMACS -batch -l ert.el -l examples-to-tests.el -l bang.el -l examples.el -f ert-run-tests-batch-and-exit diff --git a/tests.el b/tests.el deleted file mode 100644 index a197849..0000000 --- a/tests.el +++ /dev/null @@ -1,94 +0,0 @@ -(require 'ert) -(require 'bang) - -(defun even? (num) (= 0 (% num 2))) -(defun square (num) (* num num)) - -(ert-deftest map () - "`!map' returns a new list with the results of calling the function on each element." - (should (equal (!map (lambda (num) (* num num)) '(1 2 3 4)) '(1 4 9 16))) - (should (equal (!map 'square '(1 2 3 4)) '(1 4 9 16))) - (should (equal (!!map (* it it) '(1 2 3 4)) '(1 4 9 16))) - ) - -(ert-deftest reduce-from () - "`!reduce-from' takes a list and an initial value, and applies the function over them to create one result" - (should (equal (!reduce '+ '()) 0)) - (should (equal (!reduce '+ '(1)) 1)) - (should (equal (!reduce '+ '(1 2)) 3)) - (should (equal (!reduce-from '+ 7 '()) 7)) - (should (equal (!reduce-from '+ 7 '(1)) 8)) - (should (equal (!reduce-from '+ 7 '(1 2)) 10)) - (should (equal (!!reduce-from (+ acc it) 7 '(1 2 3)) 13)) - ) - -(ert-deftest reduce () - "`!reduce' takes a list and applies the function over the elements to create one result" - (should (equal (!reduce (lambda (memo item) (format "%s-%s" memo item)) '(1 2 3)) "1-2-3")) - (should (equal (!!reduce (format "%s-%s" acc it) '(1 2 3)) "1-2-3")) - (should (equal (!!reduce (format "%s-%s" acc it) '()) "nil-nil")) - ) - -(ert-deftest filter () - "`!filter' returns a new list of only those elements where the predicate was non-nil." - (should (equal (!filter (lambda (num) (= 0 (% num 2))) '(1 2 3 4)) '(2 4))) - (should (equal (!filter 'even? '(1 2 3 4)) '(2 4))) - (should (equal (!!filter (= 0 (% it 2)) '(1 2 3 4)) '(2 4))) - ) - -(ert-deftest remove () - "`!remove' returns a new list of only those elements where the predicate was nil." - (should (equal (!remove (lambda (num) (= 0 (% num 2))) '(1 2 3 4)) '(1 3))) - (should (equal (!remove 'even? '(1 2 3 4)) '(1 3))) - (should (equal (!!remove (= 0 (% it 2)) '(1 2 3 4)) '(1 3))) - ) - -(ert-deftest concat () - "`!concat' returns the concatenation of the elements in the supplied lists" - (should (equal (!concat) nil)) - (should (equal (!concat '(1)) '(1))) - (should (equal (!concat '(1) '(2)) '(1 2))) - (should (equal (!concat '(1) '(2 3) '(4)) '(1 2 3 4))) - ) - -(ert-deftest mapcat () - "`!mapcat' applies the function to all elements of the list and then concatenates the result" - (should (equal (!mapcat 'list '(1 2 3)) '(1 2 3))) - (should (equal (!mapcat (lambda (item) (list 0 item)) '(1 2 3)) '(0 1 0 2 0 3))) - (should (equal (!!mapcat (list 0 it) '(1 2 3)) '(0 1 0 2 0 3))) - ) - -(ert-deftest partial () - "`!partial' returns a function like fn where the first arguments are filled in" - (should (equal (funcall (!partial '+ 5) 3) 8)) - (should (equal (funcall (!partial '+ 5 2) 3) 10)) - ) - -(ert-deftest difference () - "`!difference' returns a new list of only elements in list1 that are not in list2." - (should (equal (!difference '() '()) '())) - (should (equal (!difference '(1 2 3) '(4 5 6)) '(1 2 3))) - (should (equal (!difference '(1 2 3 4) '(3 4 5 6)) '(1 2))) - ) - -(ert-deftest intersection () - "`!intersection' returns a new list of only elements that are in both given lists." - (should (equal (!intersection '() '()) '())) - (should (equal (!intersection '(1 2 3) '(4 5 6)) '())) - (should (equal (!intersection '(1 2 3 4) '(3 4 5 6)) '(3 4))) - ) - -(ert-deftest uniq () - "`!uniq' returns a new list of only unique elements." - (should (equal (!uniq '()) '())) - (should (equal (!uniq '(1 2 2 4)) '(1 2 4))) - ) - -(ert-deftest contains? () - "`!contains?' returns t if the list contains the element." - (should (!contains? '(1 2 3) 1)) - (should (!contains? '(1 2 3) 2)) - (should (not (!contains? '() '()))) - (should (not (!contains? '() 1))) - (should (not (!contains? '(1 2 4) 3))) - )