b0a70cc49faf

Commit docs from forever ago
[view raw] [browse files]
author Steve Losh <steve@stevelosh.com>
date Mon, 13 Apr 2026 15:20:35 -0400
parents 22da133eca64
children e455c60718db
branches/tags (none)
files Makefile README.markdown conserve.asd docs/01-usage.markdown docs/02-reference.markdown docs/03-changelog.markdown docs/api.lisp docs/footer.markdown docs/index.markdown docs/title test/data/.placeholder test/tests.lisp

Changes

--- a/Makefile	Tue Jan 14 20:06:56 2020 -0500
+++ b/Makefile	Mon Apr 13 15:20:35 2026 -0400
@@ -10,31 +10,31 @@
 
 test-sbcl:
 	$(heading_printer) computer 'SBCL'
-	sbcl --load test/run.lisp
+	time sbcl --load test/run.lisp
 
 test-ccl:
 	$(heading_printer) slant 'CCL'
-	ccl --load test/run.lisp
+	time ccl --load test/run.lisp
 
 test-ecl:
 	$(heading_printer) roman 'ECL'
-	ecl --load test/run.lisp
+	time ecl --load test/run.lisp
 
 test-abcl:
 	$(heading_printer) broadway 'ABCL'
-	abcl --load test/run.lisp
+	time abcl --load test/run.lisp
 
 # Documentation ---------------------------------------------------------------
-# $(apidocs): $(sourcefiles)
-# 	sbcl --noinform --load docs/api.lisp  --eval '(quit)'
+$(apidocs): $(sourcefiles)
+	sbcl --noinform --load docs/api.lisp  --eval '(quit)'
 
-# docs/build/index.html: $(docfiles) $(apidocs) docs/title
-# 	cd docs && ~/.virtualenvs/d/bin/d
+docs/build/index.html: $(docfiles) $(apidocs) docs/title
+	cd docs && ~/bin/venvs/tools/bin/d
 
-# docs: docs/build/index.html
+docs: docs/build/index.html
 
-# pubdocs: docs
-# 	hg -R ~/src/docs.stevelosh.com pull -u
-# 	rsync --delete -a ./docs/build/ ~/src/docs.stevelosh.com/conserve
-# 	hg -R ~/src/docs.stevelosh.com commit -Am 'conserve: Update site.'
-# 	hg -R ~/src/docs.stevelosh.com push
+pubdocs: docs
+	hg -R ~/src/docs.stevelosh.com pull -u
+	rsync --delete -a ./docs/build/ ~/src/docs.stevelosh.com/conserve
+	hg -R ~/src/docs.stevelosh.com commit -Am 'conserve: Update site.'
+	hg -R ~/src/docs.stevelosh.com push
--- a/README.markdown	Tue Jan 14 20:06:56 2020 -0500
+++ b/README.markdown	Mon Apr 13 15:20:35 2026 -0400
@@ -1,10 +1,7 @@
-Conserve is a Common Lisp library for reading and writing [RFC
+Conserve is a small Common Lisp library for reading and writing [RFC
 4180](https://tools.ietf.org/html/rfc4180) CSV data.
 
-**Not ready yet, clone at your own risk.**
-
-The test suite passes in SBCL, CCL, ECL, ABCL, Allegro, and LispWorks on Ubuntu
-18.04.
+It is about 200 lines of code and has no dependencies.
 
 * **License:** MIT/X11
 * **Documentation:** <https://docs.stevelosh.com/conserve/>
--- a/conserve.asd	Tue Jan 14 20:06:56 2020 -0500
+++ b/conserve.asd	Mon Apr 13 15:20:35 2026 -0400
@@ -27,7 +27,7 @@
 
   :serial t
   :components ((:module "test" :serial t :components
-                ((:file "package.test")
+                ((:file "package")
                  (:file "tests"))))
 
   :perform (asdf:test-op (op system)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/docs/01-usage.markdown	Mon Apr 13 15:20:35 2026 -0400
@@ -0,0 +1,142 @@
+Usage
+=====
+
+Conserve is a small Common Lisp library for reading and writing [RFC
+4180](https://tools.ietf.org/html/rfc4180) CSV data.
+
+It was made because I was tired of dealing with complicated libraries with many
+dependencies for parsing simple CSV data files.
+
+[TOC]
+
+## Example Data
+
+In the following documentation we'll read from the following `example.csv`:
+
+    id,name,score
+    1,foo,88.8
+    2,bar,100
+    3,baz,77
+
+## Reading
+
+CSV files can be read row-by-row or all at once, from a stream or a string.  If
+you want to read from a file it's up to you to open the file (with the
+appropriate external format) yourself.
+
+To read all rows from a stream, use `conserve:read-rows`:
+
+    (with-open-file (f "example.csv" :direction :input)
+      (conserve:read-rows f))
+    ; =>
+    ; (("id" "name" "score")
+    ;  ("1"  "foo"  "88.8")
+    ;  ("2"  "bar"  "100")
+    ;  ("3"  "baz"  "77"))
+
+Conserve does not process headers in any special way, nor does it parse values
+at all.  Rows are returned as lists of strings — what you do with that is up to
+you.
+
+    (defun parse-row (row)
+      (destructuring-bind (id name score) row
+        (list (parse-integer id)
+              name
+              (parse-float:parse-float score))))
+
+    (with-open-file (f "example.csv" :direction :input)
+      (destructuring-bind (header . rows)
+          (conserve:read-rows f)
+      (values header (map-into rows #'parse-row rows))))
+
+    ; =>
+    ; ("id" "name" "score")
+    ; ((1   "foo"  88.8)
+    ;  (2   "bar"  100.0)
+    ;  (3   "baz"  77.0))
+
+Use `conserve:read-row` to read a single row at a time:
+
+    (with-open-file (f "example.csv" :direction :input)
+      (conserve:read-row f))
+    ; =>
+    ; ("id" "name" "score")
+
+Note that `conserve:read-row` has the same interface as most Common Lisp
+`read-*` style functions, so it can often be used in places that expect that
+interface:
+
+    (iterate
+      (for (id name nil) :in-file "example.csv" :using #'conserve:read-row)
+      (finding (parse-integer id) :such-that (string= name "bar")))
+    ; => 2
+
+Both reading functions support reading from a string instead of a stream:
+
+    (conserve:read-row "foo,\"a,b,c\",bar")
+    ; => ("foo" "a,b,c" "bar")
+
+## Writing
+
+Much like reading, Conserve supports writing one or many rows at a time.
+
+Use `conserve:write-rows` to write a list of rows:
+
+    (with-open-file (f "out1.csv" :direction :output)
+      (conserve:write-rows '(("id" "name" "score")
+                             ("1" "foo" "88.8")
+                             ("2" "bar" "100.0")
+                             ("3" "baz" "77.0"))
+                           f))
+
+Use `conserve:write-row` to write a single row at a time:
+
+    (with-open-file (f "out2.csv" :direction :output)
+      (conserve:write-row '("id" "name" "score") f)
+      (conserve:write-row '("1" "foo" "88.8") f)
+      (conserve:write-row '("2" "bar" "100.0") f)
+      (conserve:write-row '("3" "baz" "77.0") f))
+
+Rows must be a list of *strings* — Conserve does not attempt to guess how you
+would like to serialize other objects to strings.
+
+If `nil` is passed as a stream, Conserve will return the resulting CSV as
+a string:
+
+    (conserve:write-row '("foo"
+                          "some \"quoted\" field"
+                          "comma,field")
+                        nil)
+    ; =>
+    "foo,\"some \"\"quoted\"\" field\",\"comma,field\"
+    "
+
+## Delimiter
+
+Conserve allows one single piece of customization: the choice of delimiter to
+use.  You can change the delimiter by binding `conserve:*delimiter*`:
+
+    (let ((conserve:*delimiter* #\,))
+      (conserve:write-row '("a" "b") nil))
+    ; =>
+    "a,b
+    "
+
+    (let ((conserve:*delimiter* #\|))
+      (conserve:write-row '("a" "b") nil))
+    ; =>
+    "a|b
+    "
+
+    (let ((conserve:*delimiter* #\tab))
+      (conserve:write-row '("foo,bar" "foo|bar") nil))
+    ; =>
+    "foo,bar        foo|bar
+    "
+
+## Test Suite
+
+The test suite include both hardcoded tests against particular edge cases, as
+well as round-trip fuzz testing against `cl-csv` and `fare-csv` to make sure it
+produces similar results.  You will need to Quickload those other CSV parsers to
+run the test suite (but not to use Conserve itself, of course).
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/docs/02-reference.markdown	Mon Apr 13 15:20:35 2026 -0400
@@ -0,0 +1,64 @@
+# API Reference
+
+The following is a list of all user-facing parts of Conserve.
+
+If there are backwards-incompatible changes to anything listed here, they will
+be noted in the changelog and the author will feel bad.
+
+Anything not listed here is subject to change at any time with no warning, so
+don't touch it.
+
+[TOC]
+
+## Package `CONSERVE`
+
+### `*DELIMITER*` (variable)
+
+### `READ-ROW` (function)
+
+    (READ-ROW &OPTIONAL (STREAM-OR-STRING *STANDARD-INPUT*) (EOF-ERROR-P T) EOF-VALUE)
+
+Read and return a row of fields from the CSV data in `stream-or-string`.
+
+  The result will be a fresh list.
+
+  If the end of file for the stream is encountered immediately, an error is
+  signaled unless `eof-error-p` is false, in which case `eof-value` is returned.
+
+  
+
+### `READ-ROWS` (function)
+
+    (READ-ROWS &OPTIONAL (STREAM-OR-STRING *STANDARD-INPUT*))
+
+Read and return all CSV rows from the CSV data in `stream-or-string`.
+
+  The result will be a completely fresh list of lists.
+
+  
+
+### `WRITE-ROW` (function)
+
+    (WRITE-ROW ROW &OPTIONAL (STREAM *STANDARD-OUTPUT*))
+
+Write `row` to `stream` as CSV data.
+
+  `row` must be a list of strings.
+
+  If `stream` is `nil`, the data will be returned as a fresh string instead.
+
+  
+
+### `WRITE-ROWS` (function)
+
+    (WRITE-ROWS ROWS &OPTIONAL (STREAM *STANDARD-OUTPUT*))
+
+Write `rows` to `stream` as CSV data.
+
+  `rows` must be a list of lists of strings.  The consequences are undefined if
+  all the rows do not have the same number of fields.
+
+  If `stream` is `nil`, the data will be returned as a fresh string instead.
+
+  
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/docs/03-changelog.markdown	Mon Apr 13 15:20:35 2026 -0400
@@ -0,0 +1,12 @@
+Changelog
+=========
+
+Here's the list of changes in each released version.
+
+[TOC]
+
+1.0.0
+-----
+
+Initial version.
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/docs/api.lisp	Mon Apr 13 15:20:35 2026 -0400
@@ -0,0 +1,20 @@
+(ql:quickload "cl-d-api")
+
+(defparameter *header*
+  "The following is a list of all user-facing parts of Conserve.
+
+If there are backwards-incompatible changes to anything listed here, they will
+be noted in the changelog and the author will feel bad.
+
+Anything not listed here is subject to change at any time with no warning, so
+don't touch it.
+
+")
+
+(d-api:generate-documentation
+  :conserve
+  #p"docs/02-reference.markdown"
+  (list "CONSERVE")
+  *header*
+  :title "API Reference")
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/docs/footer.markdown	Mon Apr 13 15:20:35 2026 -0400
@@ -0,0 +1,3 @@
+<i>Made with Lisp and love by [Steve Losh][].</i>
+
+[Steve Losh]: http://stevelosh.com/
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/docs/index.markdown	Mon Apr 13 15:20:35 2026 -0400
@@ -0,0 +1,9 @@
+Conserve is a small Common Lisp library for reading and writing [RFC
+4180](https://tools.ietf.org/html/rfc4180) CSV data.
+
+It is about 200 lines of code and has no dependencies.
+
+* **License:** MIT/X11
+* **Documentation:** <https://docs.stevelosh.com/conserve/>
+* **Mercurial:** <https://hg.stevelosh.com/conserve/>
+* **Git:** <https://github.com/sjl/conserve/>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/docs/title	Mon Apr 13 15:20:35 2026 -0400
@@ -0,0 +1,1 @@
+Conserve
--- a/test/tests.lisp	Tue Jan 14 20:06:56 2020 -0500
+++ b/test/tests.lisp	Mon Apr 13 15:20:35 2026 -0400
@@ -271,16 +271,17 @@
 
 
 (defun read-file/conserve ()
-  (with-open-file (s "test/data/large-conserve.csv")
-    (if *verify-large-file-reads*
-      (loop
-        :for original :in *data*
-        :for row = (conserve:read-row s nil :eof)
-        :until (eql :eof row)
-        :do (assert (equal original row)))
-      (loop
-        :for row = (conserve:read-row s nil :eof)
-        :until (eql :eof row)))))
+  (losh:profile
+    (with-open-file (s "test/data/large-conserve.csv")
+      (if *verify-large-file-reads*
+        (loop
+          :for original :in *data*
+          :for row = (conserve:read-row s nil :eof)
+          :until (eql :eof row)
+          :do (assert (equal original row)))
+        (loop
+          :for row = (conserve:read-row s nil :eof)
+          :until (eql :eof row))))))
 
 (defun read-file/fare-csv ()
   (with-open-file (s "test/data/large-fare-csv.csv")