Browse Source

Merge pull request #51 from redline6561/experimental

Release: 0.9.5!
Brit Butler 11 years ago
parent
commit
4896f31596
12 changed files with 259 additions and 52 deletions
  1. 7 3
      NEWS.md
  2. 9 6
      README.md
  3. 1 1
      coleslaw.asd
  4. 8 2
      docs/hacking.md
  5. 89 30
      docs/plugin-use.md
  6. 17 0
      examples/dump-db.lisp
  7. 9 0
      examples/dump_db.sh
  8. 85 0
      plugins/incremental.lisp
  9. 16 0
      plugins/parallel.lisp
  10. 4 0
      src/documents.lisp
  11. 4 3
      src/packages.lisp
  12. 10 7
      src/util.lisp

+ 7 - 3
NEWS.md

1
-## Changes for 0.9.5-dev (20xx):
1
+## Changes for 0.9.5 (2014-06-04):
2
 
2
 
3
-* A Twitter plugin to tweet your new posts. Thanks to @PuercoPop!
3
+* A plugin for Incremental builds, cutting runtime for generating
4
+  medium to large sites roughly in half!
5
+* A Twitter plugin to tweet about your new posts. Thanks to @PuercoPop!
4
 * Coleslaw now exports a `get-updated-files` function which can be
6
 * Coleslaw now exports a `get-updated-files` function which can be
5
   used to get a list of file-status/file-name pairs that were changed
7
   used to get a list of file-status/file-name pairs that were changed
6
   in the last git push. There is also an exported `find-content-by-path`
8
   in the last git push. There is also an exported `find-content-by-path`
7
-  function to retrieve content objects from the above file-name.
9
+  function to retrieve content objects from the above file-name. These
10
+  were used by both the Twitter and Incremental plugins.
11
+* The usual bugfixes, performance improvements, and documentation tweaks.
8
 
12
 
9
 ## Changes for 0.9.4 (2014-05-05):
13
 ## Changes for 0.9.4 (2014-05-05):
10
 
14
 

+ 9 - 6
README.md

7
 > drinking coffee, reading, writing, eating chips and salsa. I remember a gentleness
7
 > drinking coffee, reading, writing, eating chips and salsa. I remember a gentleness
8
 > behind the enormous bushy eyebrows and that we called him Coleslaw. - anon
8
 > behind the enormous bushy eyebrows and that we called him Coleslaw. - anon
9
 
9
 
10
-Coleslaw aims to be flexible blog software suitable for replacing a single-user static site compiler such as Jekyll.
10
+Coleslaw aims to be flexible blog software suitable for replacing a single-user static site generator such as [Jekyll](http://jekyllrb.com/).
11
 
11
 
12
 ## Features
12
 ## Features
13
 * Git for storage
13
 * Git for storage
14
-* RSS and Atom feeds!
15
-* Markdown Support with Code Highlighting provided by [colorize](http://www.cliki.net/colorize).
14
+* RSS and Atom feeds
15
+* Markdown Support with Code Highlighting provided by [colorize](http://www.cliki.net/colorize)
16
   * Currently supports: Common Lisp, Emacs Lisp, Scheme, C, C++, Java, Python, Erlang, Haskell, Obj-C, Diff.
16
   * Currently supports: Common Lisp, Emacs Lisp, Scheme, C, C++, Java, Python, Erlang, Haskell, Obj-C, Diff.
17
 
17
 
18
 * A [Plugin API](http://github.com/redline6561/coleslaw/blob/master/docs/plugin-api.md) and [**plugins**](http://github.com/redline6561/coleslaw/blob/master/docs/plugin-use.md) for...
18
 * A [Plugin API](http://github.com/redline6561/coleslaw/blob/master/docs/plugin-api.md) and [**plugins**](http://github.com/redline6561/coleslaw/blob/master/docs/plugin-use.md) for...
19
   * Static Pages
19
   * Static Pages
20
+  * Sitemap generation
21
+  * Incremental builds
20
   * Analytics via Google
22
   * Analytics via Google
21
   * Comments via [Disqus](http://disqus.com/)
23
   * Comments via [Disqus](http://disqus.com/)
22
   * Hosting via [Github Pages](https://pages.github.com/), [Heroku](http://heroku.com/), or [Amazon S3](http://aws.amazon.com/s3/)
24
   * Hosting via [Github Pages](https://pages.github.com/), [Heroku](http://heroku.com/), or [Amazon S3](http://aws.amazon.com/s3/)
25
+  * [Tweeting](http://twitter.com/) about new posts
23
   * Using LaTeX via [Mathjax](http://mathjax.org/)
26
   * Using LaTeX via [Mathjax](http://mathjax.org/)
24
-  * Using ReStructured Text
27
+  * Writing posts in ReStructured Text
25
   * Importing posts from [Wordpress](http://wordpress.org/)
28
   * Importing posts from [Wordpress](http://wordpress.org/)
26
-  * Sitemap generation
27
 
29
 
28
 * There is also a [Heroku buildpack](https://github.com/jsmpereira/coleslaw-heroku) maintained by Jose Pereira.
30
 * There is also a [Heroku buildpack](https://github.com/jsmpereira/coleslaw-heroku) maintained by Jose Pereira.
29
-* Example sites: 
31
+
32
+## Example Sites
30
   * [redlinernotes](http://redlinernotes.com/blog/)
33
   * [redlinernotes](http://redlinernotes.com/blog/)
31
   * [kenan-bolukbasi.log](http://kenanb.com/)
34
   * [kenan-bolukbasi.log](http://kenanb.com/)
32
   * [Nothing Really Matters](http://ironhead.xs4all.nl/)
35
   * [Nothing Really Matters](http://ironhead.xs4all.nl/)

+ 1 - 1
coleslaw.asd

1
 (defsystem #:coleslaw
1
 (defsystem #:coleslaw
2
   :name "coleslaw"
2
   :name "coleslaw"
3
   :description "Flexible Lisp Blogware"
3
   :description "Flexible Lisp Blogware"
4
-  :version "0.9.5-dev"
4
+  :version "0.9.5"
5
   :license "BSD"
5
   :license "BSD"
6
   :author "Brit Butler <redline6561@gmail.com>"
6
   :author "Brit Butler <redline6561@gmail.com>"
7
   :pathname "src/"
7
   :pathname "src/"

+ 8 - 2
docs/hacking.md

33
 would be worthwhile to see how well [cl-markdown][clmd] performs as
33
 would be worthwhile to see how well [cl-markdown][clmd] performs as
34
 a replacement if this becomes an issue for users though we would lose
34
 a replacement if this becomes an issue for users though we would lose
35
 source highlighting from [colorize][clrz] and should also investigate
35
 source highlighting from [colorize][clrz] and should also investigate
36
-[pygments][pyg] as a replacement.
36
+[pygments][pyg] as a replacement. Using the new [incremental][incf] plugin
37
+reduced runtime to 1.36 seconds, almost cutting it in half.
37
 
38
 
38
 ## Core Concepts
39
 ## Core Concepts
39
 
40
 
134
 All current Content Types and Indexes implement the protocol faithfully.
135
 All current Content Types and Indexes implement the protocol faithfully.
135
 It consists of 2 "class" methods, 2 instance methods, and an invariant.
136
 It consists of 2 "class" methods, 2 instance methods, and an invariant.
136
 
137
 
137
-There are also 4 helper functions provided that should prove useful in
138
+There are also 5 helper functions provided that should prove useful in
138
 implementing new content types.
139
 implementing new content types.
139
 
140
 
140
 
141
 
191
   unique. Such a hash collision represents content on the site being
192
   unique. Such a hash collision represents content on the site being
192
   shadowed/overwritten. This should be used in your `discover` method.
193
   shadowed/overwritten. This should be used in your `discover` method.
193
 
194
 
195
+- `delete-document`: Remove a document from *coleslaw*'s in-memory
196
+  database. This is currently only used by the incremental compilation
197
+  plugin.
198
+
194
 - `write-document`: Write the document out to disk as HTML. It takes
199
 - `write-document`: Write the document out to disk as HTML. It takes
195
   an optional template name and render-args to pass to the template.
200
   an optional template name and render-args to pass to the template.
196
   This should be used in your `publish` method.
201
   This should be used in your `publish` method.
268
 [clmd]: https://github.com/gwkkwg/cl-markdown
273
 [clmd]: https://github.com/gwkkwg/cl-markdown
269
 [clrz]: https://github.com/redline6561/colorize
274
 [clrz]: https://github.com/redline6561/colorize
270
 [pyg]: http://pygments.org/
275
 [pyg]: http://pygments.org/
276
+[incf]: https://github.com/redline6561/coleslaw/blob/master/plugins/incremental.lisp

+ 89 - 30
docs/plugin-use.md

1
 # General Use
1
 # General Use
2
 
2
 
3
 * Add a list with the plugin name and settings to the ```:plugins```
3
 * Add a list with the plugin name and settings to the ```:plugins```
4
-  section of your [.coleslawrc][config_file]. Plugin settings are described below.
4
+  section of your [.coleslawrc][config_file]. Plugin settings are
5
+  described below.
5
 
6
 
6
-* Available plugins are listed below with usage descriptions and config examples.
7
+* Available plugins are listed below with usage descriptions and
8
+  config examples.
7
 
9
 
8
 ## Analytics via Google
10
 ## Analytics via Google
9
 
11
 
10
-**Description**: Provides traffic analysis through [Google Analytics](http://www.google.com/analytics/).
12
+**Description**: Provides traffic analysis through
13
+  [Google Analytics](http://www.google.com/analytics/).
11
 
14
 
12
 **Example**: `(analytics :tracking-code "google-provided-unique-id")`
15
 **Example**: `(analytics :tracking-code "google-provided-unique-id")`
13
 
16
 
14
 ## Comments via Disqus
17
 ## Comments via Disqus
15
 
18
 
16
-**Description**: Provides comment support through [Disqus](http://www.disqus.com/).
19
+**Description**: Provides comment support through
20
+  [Disqus](http://www.disqus.com/).
17
 
21
 
18
 **Example**: `(disqus :shortname "disqus-provided-unique-id")`
22
 **Example**: `(disqus :shortname "disqus-provided-unique-id")`
19
 
23
 
20
 ## Hosting via Github Pages
24
 ## Hosting via Github Pages
21
 
25
 
22
-**Description**: Allows hosting with CNAMEs via [github-pages](http://pages.github.com/). Parses the host from the `:domain` section of your config by default. Pass in a string to override.
26
+**Description**: Allows hosting with CNAMEs via
27
+  [github-pages](http://pages.github.com/). Parses the host from the
28
+  `:domain` section of your config by default. Pass in a string to
29
+  override.
23
 
30
 
24
 **Example**: `(gh-pages :cname t)`
31
 **Example**: `(gh-pages :cname t)`
25
 
32
 
33
+## Incremental Builds
34
+
35
+**Description**: Primarily a performance enhancement. Caches the
36
+  content database between builds with
37
+  [cl-store][http://common-lisp.net/project/cl-store/] to avoid
38
+  parsing the whole git repo every time. May become default
39
+  functionality instead of a plugin at some point. Substantially
40
+  reduces runtime for medium to large sites.
41
+
42
+**Example**: `(incremental)`
43
+
44
+**Setup**:
45
+- You must run the `examples/dump_db.sh` script to generate a database dump
46
+  for your site before enabling the incremental plugin.
47
+
26
 ## LaTeX via Mathjax
48
 ## LaTeX via Mathjax
27
 
49
 
28
-**Description**: Provides LaTeX support through [Mathjax](http://www.mathjax.org/) for posts tagged with "math" and indexes containing such posts. Any text enclosed in $$ will be rendered, for example, ```$$ \lambda \scriptstyle{f}. (\lambda x. (\scriptstyle{f} (x x)) \lambda x. (\scriptstyle{f} (x x))) $$```.
50
+**Description**: Provides LaTeX support through
51
+  [Mathjax](http://www.mathjax.org/) for posts tagged with "math" and
52
+  indexes containing such posts. Any text enclosed in $$ will be
53
+  rendered, for example, ```$$ \lambda \scriptstyle{f}. (\lambda
54
+  x. (\scriptstyle{f} (x x)) \lambda x. (\scriptstyle{f} (x x)))
55
+  $$```.
29
 
56
 
30
 **Example**: ```(mathjax)```
57
 **Example**: ```(mathjax)```
31
 
58
 
32
 **Options**:
59
 **Options**:
33
 
60
 
34
-- `:force`, when non-nil, will force the inclusion of MathJax on all posts.  Default value is `nil`.
61
+- `:force`, when non-nil, will force the inclusion of MathJax on all
62
+  posts.  Default value is `nil`.
35
 
63
 
36
-- `:location` specifies the location of the `MathJax.js` file.  The default value is `"http://cdn.mathjax.org/mathjax/latest/MathJax.js"`.  This is useful if you have a local copy of MathJax and want to use that version.
64
+- `:location` specifies the location of the `MathJax.js` file.  The
65
+  default value is `"http://cdn.mathjax.org/mathjax/latest/MathJax.js"`.
66
+  This is useful if you have a local copy of MathJax and want to use that
67
+  version.
37
 
68
 
38
-- `:preset` allows the specification of the config parameter of `MathJax.js`.  The default value is `"TeX-AMS-MML_HTMLorMML"`.
69
+- `:preset` allows the specification of the config parameter of
70
+  `MathJax.js`.  The default value is `"TeX-AMS-MML_HTMLorMML"`.
39
 
71
 
40
-- `:config` is used as supplementary inline configuration to the `MathJax.Hub.Config ({ ... });`. It is unused by default.
72
+- `:config` is used as supplementary inline configuration to the
73
+  `MathJax.Hub.Config ({ ... });`. It is unused by default.
41
 
74
 
42
 ## ReStructuredText
75
 ## ReStructuredText
43
 
76
 
44
-**Description**: Some people really like [ReStructuredText](http://docutils.sourceforge.net/rst.html). Who knows why? But it only took one method to add, so yeah! Just create a post with `format: rst` and the plugin will do the rest.
77
+**Description**: Some people really like
78
+  [ReStructuredText](http://docutils.sourceforge.net/rst.html). Who
79
+  knows why? But it only took one method to add, so yeah! Just create
80
+  a post with `format: rst` and the plugin will do the rest.
45
 
81
 
46
 **Example**: `(rst)`
82
 **Example**: `(rst)`
47
 
83
 
48
 ## S3 Hosting
84
 ## S3 Hosting
49
 
85
 
50
-**Description**: Allows hosting your blog entirely via [Amazon S3](http://aws.amazon.com/s3/). It is suggested you closely follow the relevant [AWS guide](http://docs.aws.amazon.com/AmazonS3/latest/dev/website-hosting-custom-domain-walkthrough.html) to get the DNS setup correctly. Your `:auth-file` should match that described in the [ZS3 docs](http://www.xach.com/lisp/zs3/#file-credentials).
86
+**Description**: Allows hosting your blog entirely via
87
+  [Amazon S3](http://aws.amazon.com/s3/). It is suggested you closely
88
+  follow the relevant
89
+  [AWS guide](http://docs.aws.amazon.com/AmazonS3/latest/dev/website-hosting-custom-domain-walkthrough.html)
90
+  to get the DNS setup correctly. Your `:auth-file` should match that
91
+  described in the
92
+  [ZS3 docs](http://www.xach.com/lisp/zs3/#file-credentials).
51
 
93
 
52
-**Example**: `(s3 :auth-file "/home/redline/.aws_creds" :bucket "blog.redlinernotes.com")`
94
+**Example**: `(s3 :auth-file "/home/redline/.aws_creds" :bucket
95
+  "blog.redlinernotes.com")`
53
 
96
 
54
 ## Sitemap generator
97
 ## Sitemap generator
55
 
98
 
56
-**Description**: This plugin generates a sitemap.xml under the page root, which is useful if you want google to crawl your site.
99
+**Description**: This plugin generates a sitemap.xml under the page
100
+  root, which is useful if you want google to crawl your site.
57
 
101
 
58
 **Example**: `(sitemap)`
102
 **Example**: `(sitemap)`
59
 
103
 
60
 ## Static Pages
104
 ## Static Pages
61
 
105
 
62
-**Description**: This plugin allows you to add `.page` files to your repo, that will be rendered to static pages at a designated URL.
106
+**Description**: This plugin allows you to add `.page` files to your
107
+  repo, that will be rendered to static pages at a designated URL.
63
 
108
 
64
 **Example**: `(static-pages)`
109
 **Example**: `(static-pages)`
65
 
110
 
66
-## Wordpress Importer
67
-
68
-**NOTE**: This plugin really should be rewritten to act as a standalone script. It is designed for one time use and using it through a site config is pretty silly.
69
-
70
-**Description**: Import blog posts from Wordpress using their export tool. Blog entries will be read from the XML and converted into .post files. Afterwards the XML file will be deleted to prevent reimporting. Optionally an `:output` argument may be supplied to the plugin. If provided, it should be a directory in which to store the .post files. Otherwise, the value of `:repo` in your .coleslawrc will be used.
71
-
72
-**Example**: `(import :filepath "/home/redline/redlinernotes-export.timestamp.xml" :output "/home/redlinernotes/blog/")`
73
-
74
-[config_file]: http://github.com/redline6561/coleslaw/blob/master/examples/single-site.coleslawrc
75
-
76
 ## Twitter
111
 ## Twitter
77
 
112
 
78
-**Description**: This plugin tweets every time a new post is added to your repo. See Setup for an example of how to get your access token & secret.
113
+**Description**: This plugin tweets every time a new post is added to
114
+  your repo. See Setup for an example of how to get your access token
115
+  & secret.
79
 
116
 
80
-**Example**: `(twitter :api-key "<api-key>" :api-secret "<api-seret" :access-token "<access-token>" :access-secret "<access-secret>")`
117
+**Example**: `(twitter :api-key "<api-key>"
118
+                       :api-secret "<api-secret>"
119
+                       :access-token "<access-token>"
120
+                       :access-secret "<access-secret>")`
81
 
121
 
82
 **Setup**:
122
 **Setup**:
83
 - Create a new [twitter app](https://apps.twitter.com/). Take note of the api key & secret.
123
 - Create a new [twitter app](https://apps.twitter.com/). Take note of the api key & secret.
89
 
129
 
90
 ;; Use the api key & secret to get a URL where a pin code will be handled to you.
130
 ;; Use the api key & secret to get a URL where a pin code will be handled to you.
91
 (chirp:initiate-authentication
131
 (chirp:initiate-authentication
92
- :api-key "D1pMCK17gI10bQ6orBPS0w"
93
- :api-secret "BfkvKNRRMoBPkEtDYAAOPW4s2G9U8Z7u3KAf0dBUA")
132
+  :api-key "D1pMCK17gI10bQ6orBPS0w"
133
+  :api-secret "BfkvKNRRMoBPkEtDYAAOPW4s2G9U8Z7u3KAf0dBUA")
94
 ;; => "https://api.twitter.com/oauth/authorize?oauth_token=cJIw9MJM5HEtQqZKahkj1cPn3m3kMb0BYEp6qhaRxfk"
134
 ;; => "https://api.twitter.com/oauth/authorize?oauth_token=cJIw9MJM5HEtQqZKahkj1cPn3m3kMb0BYEp6qhaRxfk"
95
 
135
 
96
 ;; Exchange the pin code for an access token and and access secret. Take note
136
 ;; Exchange the pin code for an access token and and access secret. Take note
99
 ;; => "18403733-bXtuum6qbab1O23ltUcwIk2w9NS3RusUFiuum4D3w"
139
 ;; => "18403733-bXtuum6qbab1O23ltUcwIk2w9NS3RusUFiuum4D3w"
100
 ;;    "zDFsFSaLerRz9PEXqhfB0h0FNfUIDgbEe59NIHpRWQbWk"
140
 ;;    "zDFsFSaLerRz9PEXqhfB0h0FNfUIDgbEe59NIHpRWQbWk"
101
 
141
 
102
-;; Finally verify the credentials 
142
+;; Finally verify the credentials
103
 (chirp:account/verify-credentials)
143
 (chirp:account/verify-credentials)
104
 #<CHIRP-OBJECTS:USER PuercoPop #18405433>
144
 #<CHIRP-OBJECTS:USER PuercoPop #18405433>
105
 ```
145
 ```
146
+
147
+## Wordpress Importer
148
+
149
+**NOTE**: This plugin really should be rewritten to act as a
150
+  standalone script. It is designed for one time use and using it
151
+  through a site config is pretty silly.
152
+
153
+**Description**: Import blog posts from Wordpress using their export
154
+  tool. Blog entries will be read from the XML and converted into
155
+  .post files. Afterwards the XML file will be deleted to prevent
156
+  reimporting. Optionally an `:output` argument may be supplied to the
157
+  plugin. If provided, it should be a directory in which to store the
158
+  .post files. Otherwise, the value of `:repo` in your .coleslawrc
159
+  will be used.
160
+
161
+**Example**: `(import :filepath "/home/redline/redlinernotes-export.timestamp.xml"
162
+                      :output "/home/redlinernotes/blog/")`
163
+
164
+[config_file]: http://github.com/redline6561/coleslaw/blob/master/examples/example.coleslawrc

+ 17 - 0
examples/dump-db.lisp

1
+(eval-when (:compile-toplevel :load-toplevel :execute)
2
+  (ql:quickload '(coleslaw cl-store)))
3
+
4
+(in-package :coleslaw)
5
+
6
+(defun main ()
7
+  (let ((db-file (rel-path (user-homedir-pathname) ".coleslaw.db")))
8
+    (format t "~%~%Coleslaw loaded. Attempting to load config file.~%")
9
+    (load-config "")
10
+    (format t "~%Config loaded. Attempting to load blog content.~%")
11
+    (load-content)
12
+    (format t "~%Content loaded. Attempting to dump content database.~%")
13
+    (cl-store:store *site* db-file)
14
+    (format t "~%Content database saved to ~s!~%~%" (namestring db-file))))
15
+
16
+(main)
17
+(exit)

+ 9 - 0
examples/dump_db.sh

1
+#!/bin/sh
2
+
3
+LISP=sbcl
4
+
5
+## Disclaimer:
6
+## I have not tested that all lisps take the "--load" flag.
7
+## This code might spontaneously combust your whole everything.
8
+
9
+$LISP --load "dump-db.lisp"

+ 85 - 0
plugins/incremental.lisp

1
+(eval-when (:compile-toplevel :load-toplevel)
2
+  (ql:quickload 'cl-store))
3
+
4
+(defpackage :coleslaw-incremental
5
+  (:use :cl)
6
+  (:import-from :alexandria #:when-let)
7
+  (:import-from :coleslaw #:*config*
8
+                          #:content
9
+                          #:index
10
+                          #:discover
11
+                          #:get-updated-files
12
+                          #:find-content-by-path
13
+                          #:add-document
14
+                          #:delete-document
15
+                          ;; Private
16
+                          #:all-subclasses
17
+                          #:do-subclasses
18
+                          #:read-content
19
+                          #:construct
20
+                          #:rel-path
21
+                          #:repo
22
+                          #:update-content-metadata)
23
+  (:export #:enable))
24
+
25
+(in-package :coleslaw-incremental)
26
+
27
+;; In contrast to the original incremental plans, full of shoving state into
28
+;; the right place by hand and avoiding writing pages to disk that hadn't
29
+;; changed, the new plan is to only avoid redundant parsing of content in
30
+;; the git repo. The rest of coleslaw's operation is "fast enough".
31
+;;
32
+;;   Prior to enabling the plugin a user must have a cl-store dump of the
33
+;;   database at ~/.coleslaw.db. There is a dump_db shell script in
34
+;;   examples to generate the database dump.
35
+;;
36
+;; We're gonna be a bit dirty here and monkey patch. The compilation model
37
+;; still isn't an "exposed" part of Coleslaw. After some experimentation maybe
38
+;; we'll settle on an interface.
39
+
40
+(defun coleslaw::load-content ()
41
+  (let ((db-file (rel-path (user-homedir-pathname) ".coleslaw.db")))
42
+    (setf coleslaw::*site* (cl-store:restore db-file))
43
+    (loop for (status path) in (get-updated-files)
44
+       for file-path = (rel-path (repo *config*) path)
45
+       do (update-content status file-path))
46
+    (update-content-metadata)
47
+    ;; Discover's :before method will delete any possibly outdated indexes.
48
+    (do-subclasses (itype index)
49
+      (discover itype))
50
+    (cl-store:store coleslaw::*site* db-file)))
51
+
52
+(defun update-content (status path)
53
+  (cond ((string= "D" status) (process-change :deleted path))
54
+        ((string= "M" status) (process-change :modified path))
55
+        ((string= "A" status) (process-change :added path))))
56
+
57
+(defgeneric process-change (status path &key &allow-other-keys)
58
+  (:documentation "Updates the database as needed for the STATUS change to PATH.")
59
+  (:method :around (status path &key)
60
+    (let ((extension (pathname-type path))
61
+          (ctypes (all-subclasses (find-class 'content))))
62
+      ;; This feels way too clever. I wish I could think of a better option.
63
+      (flet ((class-name-p (x class)
64
+               (string-equal x (symbol-name (class-name class)))))
65
+        ;; If the updated file's extension doesn't match one of our content types,
66
+        ;; we don't need to mess with it at all. Otherwise, since the class is
67
+        ;; annoyingly tricky to determine, pass it along.
68
+        (when-let (ctype (find extension ctypes :test #'class-name-p))
69
+          (call-next-method status path :ctype ctype))))))
70
+
71
+(defmethod process-change ((status (eql :deleted)) path &key)
72
+  (let ((old (find-content-by-path path)))
73
+    (delete-document old)))
74
+
75
+(defmethod process-change ((status (eql :modified)) path &key ctype)
76
+  (let ((old (find-content-by-path path))
77
+        (new (construct ctype (read-content path))))
78
+    (delete-document old)
79
+    (add-document new)))
80
+
81
+(defmethod process-change ((status (eql :added)) path &key ctype)
82
+  (let ((new (construct ctype (read-content path))))
83
+    (add-document new)))
84
+
85
+(defun enable ())

+ 16 - 0
plugins/parallel.lisp

1
+(eval-when (:compile-toplevel :load-toplevel)
2
+  (ql:quickload 'lparallel))
3
+
4
+(defpackage :coleslaw-parallel
5
+  (:use :cl)
6
+  (:export #:enable))
7
+
8
+(in-package :coleslaw-parallel)
9
+
10
+;; TODO: The bulk of the speedup here should come from parallelizing discover.
11
+;; Publish will also benefit. Whether it's better to spin off threads for each
12
+;; content type/index type or the operations *within* discover/publish is not
13
+;; known, the higher granularity of doing it at the iterating over types level
14
+;; is certainly easier to prototype though.
15
+
16
+(defun enable ())

+ 4 - 0
src/documents.lisp

51
         (error "There is already an existing document with the url ~a" url)
51
         (error "There is already an existing document with the url ~a" url)
52
         (setf (gethash url *site*) document))))
52
         (setf (gethash url *site*) document))))
53
 
53
 
54
+(defun delete-document (document)
55
+  "Given a DOCUMENT, delete it from the in-memory database."
56
+  (remhash (page-url document) *site*))
57
+
54
 (defun write-document (document &optional theme-fn &rest render-args)
58
 (defun write-document (document &optional theme-fn &rest render-args)
55
   "Write the given DOCUMENT to disk as HTML. If THEME-FN is present,
59
   "Write the given DOCUMENT to disk as HTML. If THEME-FN is present,
56
 use it as the template passing any RENDER-ARGS."
60
 use it as the template passing any RENDER-ARGS."

+ 4 - 3
src/packages.lisp

25
            #:get-updated-files
25
            #:get-updated-files
26
            #:theme-fn
26
            #:theme-fn
27
            ;; The Document Protocol
27
            ;; The Document Protocol
28
-           #:add-document
29
-           #:find-all
30
-           #:purge-all
31
            #:discover
28
            #:discover
32
            #:publish
29
            #:publish
33
            #:page-url
30
            #:page-url
34
            #:render
31
            #:render
32
+           #:find-all
33
+           #:purge-all
34
+           #:add-document
35
+           #:delete-document
35
            #:write-document))
36
            #:write-document))

+ 10 - 7
src/util.lisp

4
   "Create an instance of CLASS-NAME with the given ARGS."
4
   "Create an instance of CLASS-NAME with the given ARGS."
5
   (apply 'make-instance class-name args))
5
   (apply 'make-instance class-name args))
6
 
6
 
7
+;; Thanks to bknr-web for this bit of code.
8
+(defun all-subclasses (class)
9
+  "Return a list of all the subclasses of CLASS."
10
+  (let ((subclasses (closer-mop:class-direct-subclasses class)))
11
+    (append subclasses (loop for subclass in subclasses
12
+                          nconc (all-subclasses subclass)))))
13
+
7
 (defmacro do-subclasses ((var class) &body body)
14
 (defmacro do-subclasses ((var class) &body body)
8
   "Iterate over the subclasses of CLASS performing BODY with VAR
15
   "Iterate over the subclasses of CLASS performing BODY with VAR
9
 lexically bound to the current subclass."
16
 lexically bound to the current subclass."
10
-  (alexandria:with-gensyms (klasses all-subclasses)
11
-    `(labels ((,all-subclasses (class)
12
-                (let ((subclasses (closer-mop:class-direct-subclasses class)))
13
-                  (append subclasses (loop for subclass in subclasses
14
-                                        nconc (,all-subclasses subclass))))))
15
-       (let ((,klasses (,all-subclasses (find-class ',class))))
16
-         (loop for ,var in ,klasses do ,@body)))))
17
+  (alexandria:with-gensyms (klasses)
18
+    `(let ((,klasses (all-subclasses (find-class ',class))))
19
+       (loop for ,var in ,klasses do ,@body))))
17
 
20
 
18
 (defmacro do-files ((var path &optional extension) &body body)
21
 (defmacro do-files ((var path &optional extension) &body body)
19
   "For each file under PATH, run BODY. If EXTENSION is provided, only run
22
   "For each file under PATH, run BODY. If EXTENSION is provided, only run