# HG changeset patch # User Steve Losh # Date 1776108035 14400 # Node ID b0a70cc49faf2a410bec825699ba30023af77332 # Parent 22da133eca64eb841c55d1344394c0da960c1171 Commit docs from forever ago diff -r 22da133eca64 -r b0a70cc49faf Makefile --- 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 diff -r 22da133eca64 -r b0a70cc49faf README.markdown --- 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:** diff -r 22da133eca64 -r b0a70cc49faf conserve.asd --- 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) diff -r 22da133eca64 -r b0a70cc49faf docs/01-usage.markdown --- /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). diff -r 22da133eca64 -r b0a70cc49faf docs/02-reference.markdown --- /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. + + + diff -r 22da133eca64 -r b0a70cc49faf docs/03-changelog.markdown --- /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. + diff -r 22da133eca64 -r b0a70cc49faf docs/api.lisp --- /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") + diff -r 22da133eca64 -r b0a70cc49faf docs/footer.markdown --- /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 @@ +Made with Lisp and love by [Steve Losh][]. + +[Steve Losh]: http://stevelosh.com/ diff -r 22da133eca64 -r b0a70cc49faf docs/index.markdown --- /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:** +* **Mercurial:** +* **Git:** diff -r 22da133eca64 -r b0a70cc49faf docs/title --- /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 diff -r 22da133eca64 -r b0a70cc49faf test/data/.placeholder diff -r 22da133eca64 -r b0a70cc49faf test/tests.lisp --- 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")