Compare commits

...
Sign in to create a new pull request.

564 commits

Author SHA1 Message Date
Cthulhux
ef76a42e4a
Merge pull request #215 from nohillside/patch-1
Add :oldrev key to call to coleslaw:main
2024-12-12 14:42:37 +01:00
nohillside
61f054b602
Add :oldrev key to call to coleslaw:main
coleslaw:main got changed to take the old revision as a &key argument, but the example code was not updated.
2024-12-11 17:19:40 +01:00
Cthulhux
73d7c6b753
Update plugin-use.md 2024-05-12 22:16:42 +02:00
Cthulhux
f3afb9ec15
Update README.md 2024-05-12 22:15:40 +02:00
Cthulhux
a9de710b0f
Update and rename piwik.lisp to matomo.lisp 2024-05-12 22:14:51 +02:00
Cthulhux
55525f5c15
Merge pull request #212 from equwal/master
Unmagic the number 10 for index posts
2024-05-11 22:39:19 +02:00
Spenser Truex
47e643a9ee
Unmagic the number 10 for index posts 2024-05-11 03:08:44 -04:00
Cthulhux
58cf3eebcc
Merge pull request #209 from khinsen/fix-recent-posts 2024-05-10 14:48:25 +02:00
Cthulhux
b70b6d8c11
Merge pull request #210 from khinsen/fix-wordpress-importer 2024-05-10 14:48:11 +02:00
Konrad Hinsen
bcc81664e5 Wordpress import plugin: add missing symbols to import 2023-11-22 16:46:16 +01:00
Konrad Hinsen
96ae19b146 Ensure that index.html points to the most recent index page 2023-11-15 09:48:52 +01:00
Cthulhux
cc0271b319
Merge pull request #207 from coleslaw-org/fix-extensionless-routing
Fix routing issues
2023-07-01 16:25:00 +02:00
Brit Butler
7ba1e0d16a Support extensionless sites via nil page-ext rather than index-ext.
Index-ext was a bit of a kludge used to support symlinking the index.html rather
than due to any real need for indexes to have different extensions than other
documents. Instead of an empty string to indicate no extension, we will use nil.
We hardcode "index.html" and use the first numeric-index's page-url to not make
assumptions about the user's routing scheme.
2023-06-30 16:28:24 -04:00
Cthulhux
e7e68ce602
Merge pull request #187 from equwal/url-gen-control
Control of URL generation -- extensions and name-fn
2022-08-08 16:51:12 +02:00
Brit Butler
c5b368aa8a
Merge pull request #199 from shukryzablah/fix-sitemap-plugin
make sitemap subclass index, add to routing
2022-04-19 11:15:23 -04:00
Shukry Zablah
b5d5076aa8 make sitemap subclass index, add to routing 2021-09-19 23:00:35 -04:00
Cthulhux
105d3a5df0
Merge pull request #196 from shukryzablah/patch-1
Update pygments instructions with setup
2021-05-26 16:05:49 +02:00
Cthulhux
891bfd52de
Merge pull request #197 from shukryzablah/update-analytics-plugin
Update google analytics integration: create gtag plugin
2021-05-26 16:05:23 +02:00
Cthulhux
878edba3df
Merge pull request #190 from equwal/more-versioning
Git versioned plugin
2021-05-26 16:03:47 +02:00
Cthulhux
bea699ebeb
Merge pull request #78 from cmstrickland/document-find-class
find-all matching adjusted
2021-05-26 16:01:47 +02:00
Shukry Zablah
b4ed7bdb49 Update plugin-use docs with gtag plugin usage 2021-03-02 20:40:36 -05:00
Shukry Zablah
7aa2c1f904 create gtag plugin, same as analytics, but updated 2021-02-28 20:29:58 -05:00
shukryzablah
647bb1ddf1
Update pygments instructions with setup
Setup now indicates that the program has to be installed to work.
2021-02-25 23:34:32 -05:00
Cthulhux
0b9f027a36
Merge pull request #191 from egao1980/windows-blogging
Use robocopy on Windows
2021-01-01 21:56:20 +01:00
egao1980
c2a107cb3e Use robocopy on Windows
Rsync treats C:/ type path as remote and cowardly refuses to copy directories. Robocopy knows no fear.
2020-04-14 18:34:47 +01:00
Spenser Truex
49288bc135 Add git-versioned plugin alternative versioning
More powerful and modern than using symlink versioning. Automatically stages,
commits, and/or uploads the source dir.
2020-03-25 18:02:29 -07:00
Spenser Truex
9a3cd70d4c Document URL generator config keywords
:index-ext
:page-ext
:name-fn

Change how URLs are generated by coleslaw.
2020-03-25 16:10:41 -07:00
Spenser Truex
efc0bfb738 Support name-fn for modifying URL strings.
Once the title has been converted to a URL (by `slugify`), name-fn changes it.
It is available in the .coleslawrc as `:namefn 'fn`, with the default
`:name-fn 'identity`
2020-03-24 15:56:48 -07:00
Spenser Truex
656401df71 Optional extension file extensions for posts and index
I wanted posts to have no extension (via :page-ext), so I had to allow for
:page-ext "" to work, and without inserting a dot at the end of my files. To
allow for index.html when :page-ext is not "html", :index-ext "html" in the
.coleslawrc is supported.
2020-03-24 15:44:53 -07:00
Masataro Asai
d1e30f150d fix the installation readme 2019-11-24 08:18:31 -05:00
Masataro Asai
daab8d63fd
Merge pull request #181 from guicho271828/simple-gh-pages
Simple gh pages
2019-10-28 22:54:35 -04:00
Masataro Asai
559171a34f report the state/deploy directory verbosely 2019-10-27 19:10:18 -04:00
Masataro Asai
7acd0eb951 added gh-pages plugin to the default coleslawrc 2019-10-27 19:10:18 -04:00
Masataro Asai
4978458966 documentation 2019-10-27 19:10:18 -04:00
Masataro Asai
beb627c482 a simple approach using shell commands 2019-10-27 19:09:26 -04:00
Masataro Asai
21411d37d9 reinitialize gh-pages 2019-10-27 17:05:30 -04:00
Masataro Asai
48b1122419
Merge pull request #168 from equwal/load-file
Added support for other lisp implementations based on research.
2019-10-27 16:33:46 -04:00
Masataro Asai
4ab837508b
Merge pull request #180 from guicho271828/coleslaw-conf-uiop
Simplify the setting of COLESLAW-CONF:*BASEDIR*
2019-10-27 15:44:42 -04:00
Masataro Asai
5fd748be8b *load-truename* does not work, it may point to the cached fasl file. 2019-10-27 15:36:20 -04:00
Javier Olaechea
35c09f45bd Simplify the setting of COLESLAW-CONF:*BASEDIR*
No need to use a before method specializing in the LOAD-OP. Use uiop's
pathname-parent-directory-pathname instead
2019-10-27 15:17:59 -04:00
Masataro Asai
0de6478caf [ci skip] cli-tests/basic.sh: can optionally run the preview server test 2019-10-27 15:16:50 -04:00
Masataro Asai
390af72d44
Merge pull request #152 from guicho271828/rsync-deploy
Removed the default deploy method and added RSYNC plugin
2019-10-27 15:07:29 -04:00
Masataro Asai
a91dcc8075 added rsync to the initial coleslawrc written by setup 2019-10-27 14:59:54 -04:00
Masataro Asai
dd53a77595 move all options to the default-initargs, for maximum customizability 2019-10-27 14:58:20 -04:00
Masataro Asai
eacc598930 as requested (Im not sure) 2019-10-27 14:55:20 -04:00
Masataro Asai
774a29a372 Removed the default deploy method and added RSYNC plugin 2019-10-27 14:54:54 -04:00
Masataro Asai
82702d25d3
Merge pull request #179 from guicho271828/cli-preview-done-in-staging
Cli preview done in staging
2019-10-27 14:35:22 -04:00
Masataro Asai
8b399b964a fix test 2019-10-27 14:31:17 -04:00
Masataro Asai
b6c35f0331
Merge pull request #178 from guicho271828/master
Fixing travis issue re: CLI
2019-10-27 14:29:13 -04:00
Masataro Asai
ea423d72e1 [ci skip] [README] slightly clarified the default deployment method in the tutorial 2019-10-27 14:28:05 -04:00
Masataro Asai
8a3b05d2af preview should happen in the staging directory 2019-10-27 14:07:42 -04:00
Masataro Asai
38a6c2d475 [minor api change] coleslaw:main takes keywords, can now skip the deploy 2019-10-27 14:07:42 -04:00
Masataro Asai
5f32be57f7 Revert "[travis] trying sudo to test the preview feature"
This reverts commit e7f0d111fc.
2019-10-27 14:07:05 -04:00
Masataro Asai
fbf7bb3c5c [travis] remove wget/curl test 2019-10-27 14:06:44 -04:00
Masataro Asai
e7f0d111fc [travis] trying sudo to test the preview feature 2019-10-27 14:06:27 -04:00
Masataro Asai
0172253002 [travis] trying curl to test the preview feature 2019-10-27 14:06:19 -04:00
Masataro Asai
0e35b72fc2 [travis] trying IPv4 to test the preview feature 2019-10-27 14:06:13 -04:00
Masataro Asai
0e4b8d81b0 [travis] install requirements 2019-10-27 14:05:10 -04:00
Masataro Asai
6e0684b128 [ci skip] minor help message 2019-10-27 14:04:18 -04:00
Masataro Asai
6fdeb58f63 [ci skip] [README] fix travis badge 2019-10-27 13:53:14 -04:00
Masataro Asai
6902af29ef [ci skip] [README] simplified / remove absolute url 2019-10-27 13:53:07 -04:00
Masataro Asai
d7a4433523
Merge pull request #146 from guicho271828/roswell
roswell script
2019-10-27 08:57:54 -04:00
Masataro Asai
d9fa597d79 fix: DEPLOY method does not respect *default-pathname-defaults* 2019-10-26 19:45:52 -04:00
Masataro Asai
0e325ee56e testing repl 2019-10-26 19:45:46 -04:00
Masataro Asai
919d047349 export functions 2019-10-26 19:45:46 -04:00
Masataro Asai
17e526317c absolute link prevents the preview from loading css/js 2019-10-26 19:45:46 -04:00
Masataro Asai
89db40a552 [README] tutorial for CLI / REPL commands 2019-10-26 19:45:46 -04:00
Masataro Asai
86fefef6fe roswell commands 2019-10-26 19:45:46 -04:00
Masataro Asai
5304158c0a basic cli tests 2019-10-26 19:45:46 -04:00
Masataro Asai
5dd66dcf62 [CLI] coleslaw-cli: system for CLI command 2019-10-26 19:45:46 -04:00
Masataro Asai
be933edf28 [README] minor mod 2019-10-26 12:09:11 -04:00
Masataro Asai
ca4aae8f66
Merge pull request #177 from guicho271828/readme
Readme
2019-10-26 10:43:46 -04:00
Masataro Asai
d56429a7f8 [README] fix Wiki URL 2019-10-20 13:31:30 -04:00
Masataro Asai
a63c168e86 [README] less mental steps before reaching Installation 2019-10-20 13:30:01 -04:00
Masataro Asai
a0457666a5 [README] move the orders; hacking is less important for new users 2019-10-20 13:30:01 -04:00
Masataro Asai
cc54e8031e [README] make plug-ins vertically shorter for accessibility 2019-10-20 13:29:40 -04:00
Shinmera
bcc2a5ecec Fix markless plugin problems 2019-09-29 07:53:20 -05:00
Javier Olaechea
d61a3a2adc
Merge pull request #174 from Shinmera/markless
Markless plugin through cl-markless-plump
2019-08-31 13:37:36 -05:00
Shinmera
3e803e7f03
Add a short plugin use description for Markless. 2019-08-31 11:13:05 +02:00
Shinmera
95e24bbc2d
Markless plugin through cl-markless-plump 2019-08-30 13:09:01 +02:00
Javier Olaechea
82f7a7f309
Merge pull request #173 from equwal/mailing-list
Add mailing list from common-lisp.net
2019-07-24 02:15:54 -05:00
Spenser Truex
1dea803651 Add mailing list. 2019-07-23 07:19:45 -07:00
Javier Olaechea
992f3aba7b
Merge pull request #164 from equwal/patch-1
Adding missing " to meta tag in Hyde's base.tmpl
2019-02-23 14:12:48 -05:00
Spenser Truex
48099d55ce Adding missing " to meta tag in Hyde's base.tmpl 2019-02-23 14:10:29 -05:00
Javier Olaechea
03415834c6
Merge pull request #167 from equwal/coleslaw-mode
Plug coleslaw-mode from @equwal
2019-02-23 13:36:18 -05:00
Spenser Truex
ce1c4f3054 Plug coleslaw-mode from Equwal 2019-02-23 13:33:42 -05:00
Spenser Truex
fbb9c034eb Added support for other lisp implementations based on research. 2018-12-01 22:56:28 -08:00
Muhammad Attiyah
1aefd3e520 Remove call to truename to fix CNAME generation. 2018-05-30 22:23:07 -05:00
Masataro Asai
7b8c5bb2ae Update plugin-api.md 2017-11-12 20:47:40 -05:00
Javier Olaechea
a447df807b Closes #145
Before calling RENDER-TEXT, we have to ensure that the FORMAT slot is a
keyword. This bug was introduced in
a6164f0bc0

Thanks to Masataro Asai (Guicho) for reporting the bug.
2017-11-12 20:36:08 -05:00
Javier Olaechea
a6164f0bc0 Avoid RENDERing the text of a Post twice
Besides avoiding duplicate work the order in which we assign the values to the
slots no longer matters as excerpt doesn't read the contents from the TEXT slot
anymore.

cf. e1ab552e8f
2017-10-29 01:12:42 -05:00
Alfredo Beaumont
d0178a7b0b Update documentation and config example for excerpt support 2017-10-29 01:12:42 -05:00
Alfredo Beaumont
11e234f9e9 Modify existing themes' index to make use of posts' excerpt 2017-10-29 01:12:42 -05:00
Alfredo Beaumont
806c003f98 Include :excerpt metadata in test 2017-10-29 01:12:42 -05:00
Alfredo Beaumont
12f174533f Add support for either an automatic or manual excerpt for index 2017-10-29 01:12:42 -05:00
Brit Butler
f4a66dc523 Merge pull request #141 from dertuxmalwieder/master
Not fscking up is sooo 90s
2017-08-03 15:58:31 -04:00
Cthulhux
ca65274456 Not fscking up is sooo 90s 2017-08-03 21:44:39 +02:00
Javier Olaechea
c2cf64a027 Merge pull request #140 from dertuxmalwieder/master
Added Piwik analytics
2017-07-31 18:12:31 -05:00
Cthulhux
f9c62cfc53 Added Piwik analytics 2017-07-31 16:54:54 +02:00
Johan Sjolen
d397b32328 Report when a required field is missing in the static-pages plugin
Add ASSERT-FIELD to the API as the entry point for plugins to report to the
user when a content field is missing.
2017-07-16 13:34:16 -05:00
Javier Olaechea
7d87d483a0 Merge pull request #133 from abeaumont/mathjax-cdn-update
Mathjax cdn update
2017-04-10 10:52:10 -05:00
Alfredo Beaumont
ee74b1a323 Update default value in MathJax plugin's documentation 2017-04-10 17:26:10 +02:00
Alfredo Beaumont
3695fd875b Update MathJax's CDN 2017-04-10 16:56:45 +02:00
Javier Olaechea
9ff2aa0944 Merge pull request #132 from abeaumont/pygments-plugin
Add a plugin to enable pygments in 3bmd
2017-03-30 07:59:53 -05:00
Alfredo Beaumont
5681f5724c Update documentation with info about Pygments plugin 2017-03-30 14:48:20 +02:00
Javier Olaechea
dee99e91b5 Merge pull request #131 from abeaumont/import-separator
Use separator defined by config instead of hardcoding the default.
2017-03-29 15:12:51 -05:00
Alfredo Beaumont
9a87ae81f3 Add a plugin to enable pygments in 3bmd 2017-03-29 21:50:19 +02:00
Alfredo Beaumont
67ef0dc71e Use separator defined by config instead of hardcoding the default. 2017-03-29 21:39:27 +02:00
Javier Olaechea
ec2eb2464c Merge pull request #130 from abeaumont/fix-all-months
Get the month from content-date, if exists, instead of whole date.
2017-03-24 00:49:52 -05:00
Alfredo Beaumont
ca4ba22ef9 Get the month from content-date, if exists, instead of whole date. 2017-03-24 00:10:33 +01:00
Javier Olaechea
9cc9eaf241 Merge pull request #128 from PuercoPop/127
PARSE-METADATA now handles CR-LF line endings
2016-11-15 10:06:13 -05:00
Javier Olaechea
b90c0cc6fe PARSE-METADATA now handles CR-LF line endings
Closes #127
Thanks @chuntaro!
2016-11-15 10:01:41 -05:00
Javier Olaechea
694c5a9290 Merge pull request #125 from PuercoPop/3bmd-youtube-plugin
Add 3bmd youtube plugin
2016-10-27 17:02:15 -05:00
Javier Olaechea
7b84f5b670 Add 3bmd youtube plugin
This plugin allows users to use a shorthand syntax to embed youtube
videos in markdown

Closes #124
2016-10-27 16:55:46 -05:00
Javier Olaechea
e60ec1abfb Merge pull request #123 from dertuxmalwieder/master
Fix and document the isso plug-in
2016-10-27 11:03:08 -05:00
Cthulhux
08a66b2f8b Fixed and finally documented the isso plug-in 2016-10-27 12:37:51 +02:00
Javier Olaechea
2ee2609020 Merge pull request #122 from PuercoPop/ignore-missing-date
Close #121: ALL-MONTHS: Don't assume that content has a date
2016-10-26 20:35:48 -05:00
Javier Olaechea
c2ff1badf8 ALL-MONTHS: Don't assume that content has a date 2016-10-26 03:52:38 -05:00
Javier Olaechea
7f69c93b6a Merge pull request #119 from PuercoPop/ignore-ccl-fasls
Ignore Linux CCL FASL files
2016-10-17 12:27:51 -05:00
Javier Olaechea
2d3d966b94 Ignore Linux CCL FASL files
CCL has different prefixes for different platforms[0].

[0]: http://ccl.clozure.com/manual/chapter3.1.html
2016-10-17 12:22:37 -05:00
Javier Olaechea
808bdb50c3 Merge pull request #118 from PuercoPop/rst-plugin-fix
Closes #113: Miscellaneous bugfixes for the ReST plugin
2016-10-16 17:05:40 -05:00
Alexander Artemenko
a856f0284d rst.lisp: Generate the BODY of the HTML document 2016-10-16 17:02:57 -05:00
Alexander Artemenko
678ea7fe4a RST.lisp: Remove call to VISIT-NODE
WRITE-DOCUMENT method for all writers already calls VISIT-NODE.
2016-10-16 12:28:00 -05:00
Alexander Artemenko
9e8bf5e4c9 rst.lisp: Fix conflicting symbol imports
WRITE-PART should be imported from docutils where it is defined.
2016-10-16 12:20:01 -05:00
Javier Olaechea
d654c17b2c Merge pull request #114 from PuercoPop/improve-basedir-tests
Improve COLESLAW-CONF:*BASEDIR* tests
2016-10-02 09:22:26 -05:00
Javier Olaechea
08f32f27c8 Improve COLESLAW-CONF:*BASEDIR* tests
Check for the presence of plugins and themes sub-directories. This is to
ensure that COLESLAW-CONF:*BASEDIR* is not bound the directory of the
FASL cache.
2016-10-02 09:19:58 -05:00
Brit Butler
5404e88c0b Merge pull request #111 from kingcons/coleslaw-conf-asdf-system
Move coleslaw-conf to its own ASDF system
2016-09-23 17:39:39 -04:00
Bruno Cichon
a5c6e365d9 Add CL-WHO plugin 2016-09-23 14:22:06 -05:00
Javier Olaechea
fc71373709 Test COLESLAW-CONF:*BASEDIR*
It should point to coleslaw's top level directory.
2016-09-20 23:38:03 -05:00
Javier Olaechea
9311c2244f Move coleslaw-conf to its own ASDF system
Ideally the package coleslaw would only have symbols that refer to
configuration variables. However to ease setting *BASEDIR* to the right
value using UIOP:SYMBOL-CALL we provide a setter, SET-BASEDIR.

- Split each ASDF system into its own file

- All system definitions are made in the ASDF-USER package, as
recommended by ASDF.

Fixes #110
2016-09-20 23:15:57 -05:00
Javier Olaechea
444698f987 Merge pull request #105 from juszczakn/master
#104 - mobile CSS updates for hyde
2016-08-18 11:50:47 -05:00
Nick
d1ff684c89 Update hyde with mobile css
Max-width is pretty arbitrary, could be adjusted
in the future.
2016-08-17 22:13:51 -05:00
Nick
90be8952cd hyde theme: on mobile, attempt to scale to width 2016-08-17 20:42:53 -05:00
Brit Butler
3d2bb56a3f Merge pull request #103 from dkochmanski/master
themes: add relative tag support for sitenav url
2016-05-07 10:01:29 -04:00
Daniel Kochmański
9877aecf5e themes: add relative tag support for sitenav url
This allows us providing not fully qualified url but to attach it to our
domain. This is helpful for static-pages when we navigate from inside a
post (which is nested deeper, like www.turtleware.eu/posts/xxx.html).
2016-05-07 09:45:20 +02:00
Javier Olaechea
078e5ff316 Merge pull request #102 from dkochmanski/master
plugin: static-pages: allow specifying content format
2016-04-21 13:19:38 -05:00
Daniel Kochmański
843bbb652c plugins: static-pages: use keyword instead of string
As suggested by a project maintainer.
2016-04-21 20:10:21 +02:00
Daniel Kochmański
1c585326ab plugin: static-pages: allow specifying format
`format' in class `page' defaults to markdown to ensure backward
compatibility.
2016-03-20 23:21:29 +01:00
Brit Butler
9819c279bb Merge pull request #101 from ljanyst/user-plugins
Allow plug-ins to be defined in the user repo
2015-12-03 12:55:29 -05:00
Lukasz Janyst
414c8dd742 Export *config* accessors useful in user plug-ins 2015-12-03 12:13:17 +01:00
Lukasz Janyst
075477b9ce Allow for plug-ins to be defined in the user repo 2015-12-03 12:12:59 +01:00
Brit Butler
44eb77da2c Merge pull request #98 from ljanyst/user-themes
Allow for themes defines in the user repo
2015-11-21 10:07:52 -05:00
Brit Butler
8b4aac2686 Merge pull request #99 from mawis/xhtml_blogging
Supporting other file extensions for the index page as well.
2015-11-21 10:06:07 -05:00
Matthias Wimmer
4d24884863 Supporting other file extensions for the index page as well.
Coleslaw is able to write the blog content to xhtml file as well, using the
configuration setting :page-ext. But it did ignore the file extension when
creating the index page link responsible for the default landing page. With this
fix the file extension gets honored. E.g. when "xhtml" is configured, coleslaw
will generate a link from "index.xhtml" to "1.xhtml".
2015-11-20 23:56:39 +01:00
Lukasz Janyst
bd17b6c039 Allow for themes defines in the user repo 2015-11-15 20:23:53 +01:00
Brit Butler
96512011a0 Merge branch 'pr/89' 2015-09-09 16:20:42 -04:00
Cthulhux
fe84645a0f added info 2015-09-09 16:15:31 -04:00
Cthulhux
b94267a6f6 added support for isso: http://posativ.org/isso/ 2015-09-09 16:15:31 -04:00
Brit Butler
0a1be15179 Merge pull request #88 from kingcons/tests
Tests
2015-09-02 16:14:00 -05:00
Brit Butler
7bde4c1781 Tweak travis.yml so coleslaw-test is found. 2015-09-02 15:42:59 -05:00
Brit Butler
b925ab5583 Tweaks to travis.yml since I keep all my systems in one asd file. 2015-09-02 14:03:52 -05:00
Brit Butler
64c917b53f Initial switch to prove, travis and quicklisp badges. 2015-09-02 13:41:39 -05:00
Brit Butler
a45254b1fc Merge pull request #83 from PuercoPop/contributing-docs
Add a contributing section to the hacking document
2015-06-19 00:07:10 -04:00
Javier Olaechea
71b535648f (hacking.md): Add a contributing section 2015-06-12 21:53:21 -05:00
Brit Butler
c45d7be575 Merge pull request #81 from digiorgi/master
coleslaw-s3::*credentials* was useless and not setting the important …
2015-06-07 21:53:49 -04:00
digiorgi
c625aa1de6 coleslaw-s3::*credentials* was useless and not setting the important variable zs3:*credentials*. Now seting correctly in the correct package zs3. 2015-06-07 18:12:04 -04:00
Brit Butler
5f3100604c Add metadata for quicklisp. 2015-05-26 23:38:50 -04:00
Brit Butler
5727578b26 Minor indentation tweak. 2015-05-26 23:38:49 -04:00
Brit Butler
132b235aee Merge pull request #80 from douglarek/patch-1
Fix http link reference when site enables https
2015-05-24 14:51:49 -04:00
Lingchao Xin
100fa30578 Fix http link reference when site enables https 2015-05-25 00:34:43 +08:00
Colin M. Strickland
4b0819c288 refactored class name matching code
added class-name-p function to util
refactored incremental plugin process-change and documents purge-all
to use class-name-p when matching against exact class names
2015-01-04 15:25:39 +00:00
Colin M. Strickland
7d26e2bb5a Renamed find-all matching parameter to matches-p
In response to feedback on PR-78
  https://github.com/redline6561/coleslaw/pull/78
2015-01-04 14:13:21 +00:00
Colin M. Strickland
7facd55afb find-all matching adjusted
find-all implementation returns false positives for subclasses
added parameter of a predicate to implement matching, with a default
value that preserves original behaviour

added an exact class matcher to purge-all invocation of find-all to stop
it purging subclasses
2015-01-02 10:47:32 +00:00
Javier Olaechea
144cb03294 Style fixes and missing closing paren 2014-12-10 12:16:31 -05:00
Javier Olaechea
66928295e3 Merge pull request #75 from lukasepple/master
error handling in slugify
2014-12-10 12:15:57 -05:00
lukasepple
1e1c64b245 using zerop instead of (= 0 2014-12-10 15:07:09 +01:00
lukasepple
d7ffd1b7d7 error handling in slugify 2014-12-09 20:55:57 +01:00
Brit Butler
deb2e556a4 Formatting tweaks. 2014-12-08 15:23:09 -05:00
Brit Butler
0fdac81d5e Merge pull request #74 from lukasepple/master
add first idea for the cli command set
2014-12-08 15:18:57 -05:00
lukasepple
1251db17fd mentioning IRC channel in README 2014-12-08 20:59:37 +01:00
lukasepple
86133fef8f simplified and clarified the cli 2014-12-08 20:04:45 +01:00
lukasepple
e52a9765a5 added some more commands 2014-12-08 16:18:46 +01:00
lukasepple
78ec9c37eb add first idea for the cli command set
Please help with commands for git-deployment I haven't used that feature
yet
2014-12-07 21:34:10 +01:00
Javier Olaechea
515f93cbd3 Merge pull request #73 from lukasepple/master
using char instead of aref for getting a char by index
2014-12-07 13:53:21 -05:00
lukasepple
d5e5aed0d9 using char instead of aref
for getting a char by index
2014-12-07 13:03:27 +01:00
Brit Butler
3671178a35 Minor indentation fixes. 2014-12-06 22:00:07 -05:00
Javier Olaechea
b272c7880b Merge pull request #71 from lukasepple/master
made slugs unicode-safe
2014-12-06 21:46:54 -05:00
lukasepple
5aa89f4497 cleanup of the code belonging to slugify 2014-12-06 22:55:50 +01:00
lukasepple
fbacd3b81f made slugs unicode-safe like
used mozilla's slugify implementation as reference:
https://github.com/mozilla/unicode-slugify/blob/master/slugify/__init__.py
2014-12-06 19:51:06 +01:00
Brit Butler
1d5b9117e2 Merge pull request #70 from Ferada/no-plugin-compilation
Don't compile plugin files.
2014-12-04 11:32:20 -05:00
Olof-Joachim Frahm
68ee3c942d Catch errors while trying to compile plugins.
A warning with the full error message will be printed and the original
file `LOAD`ed instead.  If the error isn't related to the `fasl` file
output, then the `LOAD` should also throw an error, at which point the
normal error handling would take place.
2014-12-02 21:29:29 +01:00
Brit Butler
9c4dd4162b Add gfycat note to readme. 2014-11-25 22:49:09 -05:00
Brit Butler
a8326f8870 A final news tweak. 2014-11-25 22:47:23 -05:00
Brit Butler
b6f1834239 Merge pull request #68 from redline6561/dev
Release: 0.9.7!
2014-11-25 22:43:58 -05:00
Brit Butler
8d44e8ac3a Minor news tweak. 2014-11-25 22:40:40 -05:00
Brit Butler
45523dcc35 Release: 0.9.7! 2014-11-25 22:39:54 -05:00
Brit Butler
3490af0287 Add gfycat plugin. 2014-11-22 23:06:53 -05:00
Brit Butler
4fd195db19 Merge pull request #67 from PuercoPop/patch-3
content.lisp (parse-metadata): add filename to the error message
2014-11-11 23:09:15 -05:00
Javier Olaechea
1ff10b9b63 conent.lisp (parse-medatada): add filename to the error message 2014-11-11 22:59:17 -05:00
Brit Butler
8273efe00e Fix symbols in static pages plugin some more. 2014-11-09 20:30:52 -05:00
Brit Butler
558e2d1e0d Fix a symbol-scoping bug in the static pages plugin. 2014-11-09 20:16:16 -05:00
Brit Butler
29e0fe0f58 Use bootstrap for the pager in the readable template. 2014-11-03 17:08:50 -05:00
Brit Butler
762ad5e44d Handle trailing slashes more gracefully. Kick off 0.9.7-dev. 2014-11-02 23:04:45 -05:00
Brit Butler
a6a5b0b86b Force staging-dir to be a directory-pathname in compile-blog. 2014-11-02 21:00:40 -05:00
Brit Butler
913023e377 Fix directory-does-not-exist initarg buglet. 2014-11-02 20:45:25 -05:00
Brit Butler
52f9cfa63c Use separate eval statements for ccl in post-receive script. 2014-11-02 20:25:26 -05:00
Brit Butler
6073122961 Readable theme should include links to prev/next page. 2014-11-02 19:18:56 -05:00
Brit Butler
8a50b6c4e4 Merge pull request #64 from cmstrickland/unicode-import
utf-8 output when writing wordpress imported posts
2014-11-01 09:51:52 -04:00
Colin M. Strickland
3a37b8339b utf-8 output when writing wordpress imported files
the file writer in wordpress import did not set external-format
2014-11-01 11:20:14 +00:00
Brit Butler
81fcf3a47a Cleanup wordpress since handling a little more. 2014-10-26 19:23:31 -04:00
Brit Butler
8e464ed762 Merge pull request #63 from cmstrickland/wp-import-since
optional 'since' handling in WP import plugin
2014-10-26 19:16:00 -04:00
Colin M. Strickland
054621ab13 fix import-posts pass through 'since' as nil
adds a conditional to the invocation of import-post in import-posts loop

there needs to be a guard around 'since' in the loop:
if import posts tries to pass it's optional 'since' parameter through to
import-post it will prevent any posts from being output , because every
post will be compared against a timestamp of nil
2014-10-26 21:52:44 +00:00
Brit Butler
7cd51a1334 Merge pull request #62 from cmstrickland/ccl-fixes
A couple of tiny ccl fixes, external-format and gitgnore for ccl fsl files.
2014-10-26 09:51:25 -04:00
Colin M. Strickland
b38b07e83a gitignore 32 and 64 bit ccl fasl files
they have different file extensions
2014-10-26 09:25:22 +00:00
Colin M. Strickland
bcd9051b14 use :utf8 symbol directly in external-format
sbcl can use :utf8 as a direct symbol also
2014-10-25 22:16:53 +01:00
Colin M. Strickland
c6cf6ee943 exclude x64fsl files 2014-10-25 22:15:30 +01:00
Brit Butler
1c587f8616 Merge pull request #61 from redline6561/basic-deploy
Release: 0.9.6!
2014-09-26 10:30:00 -04:00
Brit Butler
62cc5e91f4 Release: 0.9.6! 2014-09-23 17:39:40 -04:00
Brit Butler
1b3acd107e Fix feed handling bug backwards compatibly. No name changes. 2014-09-23 17:39:40 -04:00
Brit Butler
1289706b46 Quick bugfix to add-injection. 2014-09-23 17:33:55 -04:00
Brit Butler
f27172a1f7 Last minute backwards compatibility fix for people on very old SBCLs. 2014-09-23 17:28:11 -04:00
Brit Butler
c7fc38b73c Some last tweaks for Summary Cards before 0.9.6. 2014-09-23 11:52:52 -04:00
Javier Olaechea
c286fc1e33 Add tests as a PoC. Do not for merge. 2014-09-23 11:52:50 -04:00
Javier Olaechea
a9d73e3b26 Bind content-text & add quotes to HTML attributes 2014-09-23 11:52:33 -04:00
Javier Olaechea
47a17508e8 Add twitter meta-data to posts
This depends on a pending change to add-injection
2014-09-23 11:52:33 -04:00
Brit Butler
0640ebc5cb Some more NEWS updates. 2014-09-22 15:50:48 -04:00
Brit Butler
8bea5f1927 TODO tweaks for 0.9.7. 2014-09-22 14:32:27 -04:00
Brit Butler
e44d0bde9e Make add-injection support more complex injections. Thanks @PuercoPop.
In short, an injection is now a FUNCTION that takes a
document and returns either a STRING to insert in the
page (possibly with data from the document) or NIL.
2014-09-22 14:31:08 -04:00
Brit Butler
f474db77b2 Factor parse-initarg out of parse-metadata. 2014-09-22 12:56:00 -04:00
Brit Butler
33e81104b8 Tweaks to NEWS, README. 2014-09-16 11:30:14 -04:00
Brit Butler
3ae3c208f7 Remove the unused fiveam dependency, get started with stefil! 2014-09-12 16:36:59 -04:00
Brit Butler
81cfcc469e New release target. 2014-09-12 16:22:34 -04:00
Brit Butler
b2082175e3 Little more doc work, fix versioned plugin thinko. 2014-09-12 16:21:50 -04:00
Brit Butler
60302d5f47 Fix blogroll link. 2014-09-05 17:11:33 -04:00
Brit Butler
bb2b6e49a4 README markdown tweaks. 2014-09-05 17:10:51 -04:00
Brit Butler
41186fb41c Fixes to theming docs. 2014-09-05 17:08:13 -04:00
Brit Butler
439f3f35f7 Push closer to 0.9.6. Just tests and a merge left now... 2014-09-05 17:08:13 -04:00
Brit Butler
46e6915ba2 Make parse-metadata suck a little less. Many docs updates. 2014-09-05 17:08:12 -04:00
Brit Butler
78e0f9ecea Split out some 0.9.7 items. 2014-09-05 17:07:22 -04:00
Brit Butler
2ed12c5bce Factor out parse-metadata in preparation for real error-handling. 2014-09-05 17:07:22 -04:00
Brit Butler
2bd647207a Some 0.9.6 preparations. 2014-09-05 17:07:22 -04:00
Brit Butler
7b516ccc9c A few notes tweaks. 2014-09-05 17:07:22 -04:00
Brit Butler
148c1a0c52 Switch back to using rsync --delete -avz in deploy. 2014-09-05 17:07:22 -04:00
Brit Butler
b23a398dd5 Assorted bugfixes and cleanups. 2014-09-05 17:07:22 -04:00
Brit Butler
b3acf878e1 Update TODO notes in Hacking. 2014-09-05 17:07:22 -04:00
Brit Butler
78f1fc9fa0 Fix sitemap plugin breaking in INDEX :after method. It's a one off. 2014-09-05 17:07:22 -04:00
Brit Butler
702aaf5b4b Closure Template/Esrap's error reporting leaves something to be desired. 2014-09-05 17:07:22 -04:00
Brit Butler
ef378b9b56 Finish the routing/slugs overhaul. 2014-09-05 17:07:22 -04:00
Brit Butler
ab34400307 Use with-slots for initializers, readers only please! 2014-09-05 17:07:22 -04:00
Brit Butler
ed886b57e7 Set blog repo in "the right place". 2014-09-05 17:07:22 -04:00
Brit Butler
f512ab7a48 More TODO notes. 2014-09-05 17:07:22 -04:00
Brit Butler
ab03544600 Tracking TODOs in the Hacking file now. 2014-09-05 17:07:22 -04:00
Brit Butler
ae96c069c4 Half-way commit to getting hardcoded paths out of templates. 2014-09-05 17:07:21 -04:00
Brit Butler
04c1ed9aee Bugfixes. 2014-09-05 17:06:41 -04:00
Brit Butler
ab12f8ed6d Some TODO notes. 2014-09-05 17:06:41 -04:00
Brit Butler
c2920901fa More notes. 2014-09-05 17:06:41 -04:00
Brit Butler
99e87c061f Add some error-handling notes. 2014-09-05 17:06:41 -04:00
Brit Butler
c82fe20382 Incremental compilation is done, note some new goals. 2014-09-05 17:06:41 -04:00
Brit Butler
567c4473bb Tentatively factor deploy method into git-hook plugin. TODO follows...
* Deploy :after plugins probably need revision now, and coleslaw-heroku.
* README, HACKING need updates. Plugin-api.md too.
* NEWS needs a carefully worded entry.

Is that what we want?
2014-09-05 17:06:41 -04:00
Brit Butler
4630a7a224 Remove scaffolded parallel plugin. 2014-09-05 17:06:41 -04:00
Brit Butler
a176f38537 Bugfix: Feed should be an abstract class. 2014-09-05 17:06:41 -04:00
Brit Butler
0dae5d249a Fix buglet in readable theme's post template.
Thanks to Matthias Wimmer for the report and fix.
2014-09-04 09:09:36 -04:00
Brit Butler
2286814509 Merge pull request #60 from nathanielksmith/fix-link
fix link to chiptheglasses
2014-08-22 14:26:45 -04:00
Nathaniel Smith
caaac152cc fix link to chiptheglasses 2014-08-22 14:01:29 -04:00
Brit Butler
375c25d5f5 Merge pull request #59 from Ferada/validation-fixes
Fix validation errors.
2014-08-21 09:52:08 -04:00
Olof-Joachim Frahm
b29d45e3a4 Fix validation errors. 2014-08-20 23:11:49 +01:00
Brit Butler
c7d418b896 Merge pull request #56 from PuercoPop/misc-fixes
Misc fixes
2014-08-20 10:08:23 -04:00
Javier Olaechea
8304e6b74b More robust directory changing
- Doesn't fail when the directory is missing a trailing '/'
     - When directory doesn't exist it signals an informative
       condition.
2014-08-17 16:51:17 -05:00
Javier Olaechea
221a9cbe86 Make tags indices URL absolute 2014-08-16 02:23:48 -05:00
Javier Olaechea
c2e83dd729 current-directory not needed, use uiop instead
Add setf expansion for getcwd
2014-08-16 02:23:48 -05:00
Brit Butler
81fe5a72d3 Merge pull request #53 from PuercoPop/patch-1
replace deprecated sb-ext:quit with sb-ext:exit
2014-08-15 15:35:14 -04:00
Javier Olaechea
a4b92a05ef replace deprecated sb-ext:quit with sb-ext:exit
As of 1.0.57[1]

[1]: http://sbcl.org/all-news.html#1.0.57
2014-08-15 14:05:03 -05:00
Brit Butler
def471c917 Minor README tweak. 2014-06-27 13:48:42 -04:00
Brit Butler
138c27c5d6 Slight tweak to README language. 2014-06-13 10:52:23 -04:00
Brit Butler
e4f3ee06ec Last minute NEWS tweak to sneak this into 0.9.5/Quicklisp June 2014. 2014-06-13 09:51:02 -04:00
Brit Butler
98f6d0f8c8 Use lang and charset in the hyde theme, not just readable theme. 2014-06-13 09:49:13 -04:00
Brit Butler
6a4b26c9bb Add default lang and charset values to the config.
Any time we can avoid forcing people to update their configs it's a good thing.
2014-06-13 09:48:34 -04:00
Brit Butler
137d465ba8 Merge pull request #52 from ryumei/master
Add config values for l10n
2014-06-13 09:38:06 -04:00
NAKAJIMA Takaaki
6a0d01494e Apply l10n to readable theme 2014-06-13 19:34:28 +09:00
NAKAJIMA Takaaki
1e7f927aa4 Apply l10n in readable theme 2014-06-13 19:31:51 +09:00
NAKAJIMA Takaaki
2ac0d43877 Add charset of HTML 2014-06-13 19:16:11 +09:00
NAKAJIMA Takaaki
99ddaa0ca2 Apply :lang 2014-06-13 19:10:56 +09:00
NAKAJIMA Takaaki
fc980eacf0 Fix typo 2014-06-13 19:02:43 +09:00
NAKAJIMA Takaaki
1bafadc279 Enable specify lang of html 2014-06-13 19:00:11 +09:00
Brit Butler
4896f31596 Merge pull request #51 from redline6561/experimental
Release: 0.9.5!
2014-06-04 11:42:37 -04:00
Brit Butler
733a8a82ad Add one more docs note, call it 0.9.5. 2014-06-04 11:40:14 -04:00
Brit Butler
70cad7c7d1 More docs, README updates. 2014-06-04 11:28:43 -04:00
Brit Butler
37a1d7ad6a Comment tweaks. 2014-06-03 18:13:12 -04:00
Brit Butler
06a1f73ad2 Add a naive performance evaluation to the docs. 2014-06-03 17:24:17 -04:00
Brit Butler
6daa930366 Fix some bugs in the incremental plugin. 2014-06-03 17:18:24 -04:00
Brit Butler
1d18a32454 Add basic dump_db.sh. We fight for the user! (or hacker, sysadmin, whatever) 2014-06-03 17:01:23 -04:00
Brit Butler
3109a988c4 Tiny tweak to incremental plugin. 2014-06-03 16:41:12 -04:00
Brit Butler
bed63d2156 New pass at "incremental" compilation. See now, isn't that better? 2014-06-03 16:37:56 -04:00
Brit Butler
3467157805 Add and export DELETE-DOCUMENT. 2014-06-03 16:32:43 -04:00
Brit Butler
62c940bde9 Finish addition, push modification uphill a bit. 2014-06-03 14:51:24 -04:00
Brit Butler
6ea3166a8b Fix outdated themes documentation for indexes.
The one thing I miss in the docs sweep before the last release
and my friend trips all over it. God dammit software.
2014-06-03 11:43:23 -04:00
Brit Butler
8e8e3231ec Finish deletion, push addition uphill a bit. 2014-06-03 11:15:13 -04:00
Brit Butler
331cc94b2c Merge branch 'master' into experimental 2014-06-01 17:22:55 -04:00
Brit Butler
fd0e1007db There's no such thing as single-site.rc anymore. 2014-05-31 15:51:41 -04:00
Brit Butler
669655930b Simplify PAGE-URL's around method. 2014-05-28 11:50:44 -04:00
Brit Butler
7d9b0aa011 Flesh out the sketch a bit more and add some constraints. 2014-05-26 16:27:02 -04:00
Brit Butler
75c30c5844 Push sketch slightly further up hill. 2014-05-26 16:27:02 -04:00
Brit Butler
7c12a9670b Sketch out incremental plugin. 2014-05-26 16:27:02 -04:00
Brit Butler
70427b81d0 Ensure that sitemap includes all content.
I had introduced a bug by depending on sitemap being the last subclass
in the ALL-SUBCLASSES list. Since there's only one instance of sitemap,
it's not *too* hackish to do everything in the publish step.
2014-05-26 16:19:42 -04:00
Brit Butler
235b727017 PAGE-URL should always return a pathname. 2014-05-19 15:27:51 -04:00
Brit Butler
2397b70abb Extract setfs into UPDATE-CONTENT-METADATA. 2014-05-19 12:01:46 -04:00
Brit Butler
2268516412 Add performance note to hacking docs. 2014-05-16 11:35:05 -04:00
Brit Butler
6283d63331 A little performance hack for faster compiles.
We were generating the complete list of tags and months on every
index render. Now the data is computed once during content load
and cached in a global for the remainder of the compilation process.
20% reduction in compile-time for my 430-post blog.
2014-05-16 11:09:47 -04:00
Brit Butler
f58c265fb7 Bugfixes to sitemaps, mathjax, and posts. 2014-05-16 10:42:08 -04:00
Brit Butler
414b221e6b Use default-initargs, switch some accessors to readers. 2014-05-15 10:43:39 -04:00
Brit Butler
c0447fd7d0 I forgot to remove the conditions file from the defsystem. 2014-05-09 16:30:52 -04:00
Brit Butler
947ad9023b Cleanup Twitter plugin and update docs a bit. 2014-05-09 11:54:22 -04:00
Brit Butler
a4d7b8505c Mention twitter plugin in NEWS. 2014-05-08 11:56:24 -04:00
Brit Butler
7326e31479 Export title-of, author-of, and find-content-by-path. Cleanup twitter plugin. 2014-05-08 11:47:59 -04:00
Brit Butler
3264848cde Cleanup sitemap plugin a bit. 2014-05-08 11:37:10 -04:00
Brit Butler
57f9aeb696 Keep plugin-conf-error condition with near the plugin handling code. 2014-05-08 11:02:51 -04:00
Brit Butler
b2ffed2b17 Simplify TAG and INDEX definitions. 2014-05-08 10:51:45 -04:00
Brit Butler
7912037f2e Merge pull request #50 from PuercoPop/twitter-plugin
Twitter plugin
2014-05-08 10:38:26 -04:00
PuercoPop
03b1cb9a0b Use *tweet-format-fn* in render-tweet 2014-05-08 04:48:14 -05:00
PuercoPop
3b59ca1321 *tweet-format* DSL 2014-05-08 04:45:51 -05:00
PuercoPop
643e56cd6b Add plugin documentation 2014-05-08 00:43:43 -05:00
PuercoPop
d4ffbe0c5c bugfix 2014-05-07 21:19:08 -05:00
PuercoPop
0192fb648d Hook to deploy instead of publish. 2014-05-07 21:02:10 -05:00
PuercoPop
72f0000a82 Add and export plugin-conf-error condition 2014-05-07 21:00:33 -05:00
PuercoPop
ed60a94be8 typo 2014-05-07 20:05:57 -05:00
PuercoPop
f95a3c27a2 Hook to publish instead of render 2014-05-07 20:05:56 -05:00
PuercoPop
7cb6461cb2 post url should include domain 2014-05-07 20:05:56 -05:00
PuercoPop
d7ef24664d Fix render-tweet 2014-05-07 20:05:56 -05:00
PuercoPop
0be9a17cfa Implementation sans test 2014-05-07 20:05:56 -05:00
Brit Butler
5e236e6ef1 Bump version to 0.95-dev, tweak exports, NEWS entries. 2014-05-07 18:04:30 -04:00
Brit Butler
52b32f459b Add support for looking up content by relative path. 2014-05-07 17:54:23 -04:00
Brit Butler
0760459e8f Add *last-revision* and export it and GET-UPDATED-FILES. 2014-05-07 17:51:26 -04:00
Brit Butler
795f568aea Track the original filepath for all content. 2014-05-07 17:36:05 -04:00
Brit Butler
a67a08d54a Use some more format recipes. 2014-05-06 15:25:28 -04:00
Brit Butler
975be4236e Fix style-warnings for static-pages plugin. 2014-05-06 15:25:28 -04:00
Brit Butler
1ea73feab7 Cleanup plugin loading. 2014-05-06 15:25:28 -04:00
Brit Butler
0912413f11 Go ahead and actually link to Coleslaw in the base template. 2014-05-05 17:13:27 -04:00
Brit Butler
a10089856f Merge pull request #49 from sjas/master
example sites update
2014-05-04 16:16:23 -04:00
sjas
4599305acf example sites update 2014-05-04 21:05:32 +02:00
Brit Butler
e17d2a27df Update the post-receive script to pass the previous revision to Coleslaw. 2014-05-03 22:16:26 -04:00
Brit Butler
35afff4ed9 Add GET-UPDATED-FILES to find files changed in last push. Update HACKING doc. 2014-05-03 13:55:09 -04:00
Brit Butler
16272cc6a6 Slight tweak to READ-CONTENT's metadata parsing. 2014-05-03 13:53:13 -04:00
Brit Butler
16d7656421 Import content-text for the static pages plugin. 2014-05-03 13:53:13 -04:00
Brit Butler
162df3593d One more NEWS note. 2014-05-02 17:11:04 -04:00
Brit Butler
9ad47016fb Tweak Theming docs. 2014-05-02 17:02:31 -04:00
Brit Butler
43bad78452 Updates to the Plugin docs. 2014-05-02 16:38:53 -04:00
Brit Butler
96d85c8e69 Go through HACKING docs with a fine tooth comb. 2014-05-02 16:25:39 -04:00
Brit Butler
4ae9634c52 Fixup example files and README. 2014-05-02 15:22:36 -04:00
Brit Butler
6573fd54dd Remove old HTML doc generation.
Quickdocs or the Markdown documentation are preferrable.
2014-05-02 15:19:54 -04:00
Brit Butler
3399a30b19 README tweaks. 2014-05-02 15:15:27 -04:00
Brit Butler
a9740474eb Simplify the support for multi-site configs. 2014-05-02 15:03:15 -04:00
Brit Butler
a03962f7f8 Add todo item to simplify config handling. 2014-05-02 14:06:52 -04:00
Brit Butler
58b37dc630 I really need to write some more tests. 2014-05-01 18:53:06 -04:00
Brit Butler
e96f7f58b9 Assorted cleanups. 2014-05-01 18:51:47 -04:00
Brit Butler
f602c371ea Split feeds back out into their own file. 2014-05-01 17:16:50 -04:00
Brit Butler
b703477ed8 Fix some missing imports in the static-pages plugin. 2014-04-30 14:31:04 -04:00
Brit Butler
3465441b58 I'm dumb. What year is this? Can I go back to bed now? 2014-04-30 10:08:44 -04:00
Brit Butler
4e6efa9d9e Merge pull request #48 from redline6561/static-pages
Static pages
2014-04-29 14:06:18 -04:00
Brit Butler
0446b5f919 Add some NEWS entries and remove the TODO item from hacking.md. 2014-04-29 14:03:44 -04:00
Brit Butler
f1abdd5410 Add a title and treat static-pages as markdown. 2014-04-29 13:56:01 -04:00
Brit Butler
3bf0bcf337 Tweak hyde/post indentation a bit. 2014-04-29 13:56:00 -04:00
Brit Butler
0e70d8661e Minimal changes to support tagless, dateless static-pages using post template.
Note that tagless or dateless posts might not behave as expected in
indexes. Filtering by tag still works but sorting by date doesn't
drop the nil values.
2014-04-29 13:55:58 -04:00
Brit Butler
a8a2a391ca Add minor comment. 2014-04-29 13:55:00 -04:00
Brit Butler
cba6776afd Cleanup imports and use write-document. 2014-04-29 13:55:00 -04:00
Brit Butler
4f6006cfcf First pass at static-pages as a plugin. 2014-04-29 13:55:00 -04:00
Brit Butler
85ed427969 Minor bugfix to PAGE-URL. Never use the wrong data in testing. 2014-04-29 13:48:52 -04:00
Brit Butler
40d6e5bb2c Merge pull request #47 from redline6561/user-defined-routing
User defined routing
2014-04-29 13:39:56 -04:00
Brit Butler
4433acfce4 Bump version to 0.94-dev. 2014-04-29 13:33:30 -04:00
Brit Butler
e26ed4398c Update NEWS and remove user-defined routing item from docs. 2014-04-29 13:31:45 -04:00
Brit Butler
514761e1cd Remove page-url methods now stored in the config.
We probably don't want to merge this in until users have had an
adjustment period or without putting a *big* warning in the NEWS.
Users will need to know to update their configs/steal the :routing
block from the example config.
2014-04-29 13:28:12 -04:00
Brit Butler
f56270430f Hey, look! User-defined routes are easy. Slot-value is gross though. 2014-04-29 13:28:12 -04:00
Brit Butler
e5a40b67a7 Update all page-url calls to only use the slug. 2014-04-29 13:28:12 -04:00
Brit Butler
98be18c1f8 Update docs for static-pages branch. 2014-04-29 00:27:46 -04:00
Brit Butler
6a2e35b125 Remove unused sitemap imports. 2014-04-28 17:59:03 -04:00
Brit Butler
40c00b9708 Allow arbitrary layout of blog repos. 2014-04-28 17:49:37 -04:00
Brit Butler
04619d57ad Minor tweak to the RSS template. 2014-04-28 14:17:41 -04:00
Brit Butler
5f75afde21 Fix thinko in write-document. 2014-04-28 14:13:52 -04:00
Brit Butler
0b7795270f Rearrange source a bit. 2014-04-28 14:10:27 -04:00
Brit Butler
44e82382f3 Fix write-document to work for feeds. 2014-04-28 14:09:27 -04:00
Brit Butler
7e028d354e Use write-document throughout. Render-function cleanup done. 2014-04-28 13:59:50 -04:00
Brit Butler
dc2a2cc92e First pass at WRITE-DOCUMENT, document exported protocol helpers. 2014-04-28 13:55:55 -04:00
Brit Butler
e0fc5be8e3 Add some TODOs to hacking.md 2014-04-28 13:28:02 -04:00
Brit Butler
2011da83ad Fix embarrassing escaping in write-file. 2014-04-28 12:40:42 -04:00
Brit Butler
7fc472bb7f Fix sitemap package imports. 2014-04-28 12:31:33 -04:00
Brit Butler
cb868f0852 Rename write-page -> write-file. Clean up some docstrings. 2014-04-18 12:12:57 -04:00
Brit Butler
069188b2b4 Fix NEWS typo. 2014-04-16 13:31:42 -04:00
Brit Butler
7620b7a87d Move Page content type to "Areas for Improvement", arrange by rough difficulty. 2014-04-16 11:29:16 -04:00
Brit Butler
a8cad00da7 Note user-defined routing branch. 2014-04-16 11:23:24 -04:00
Brit Butler
7489a6f769 Cleaner implementation of DO-FILES. 2014-04-16 10:46:39 -04:00
Brit Butler
13648020c4 Remove docs TODO, add small Plugins section. 2014-04-16 10:06:56 -04:00
Brit Butler
3029c7e774 Minor comments and doc cleanups. Rearrange util. 2014-04-16 00:04:50 -04:00
Brit Butler
0ea460529a Update HTML docs on redlinernotes.
Should I remove these? At least add a note at some point directing
people to Quickdocs.
2014-04-15 23:46:25 -04:00
Brit Butler
5e9a256a91 Rename render-content to render-text for clarity. Retroactively add to 0.9.3. 2014-04-15 23:44:41 -04:00
Brit Butler
af23e1bfbb Tweak some formatting in the hacking docs. 2014-04-15 22:29:23 -04:00
Brit Butler
8a507e33ec Add reference to hacking docs to README. 2014-04-15 22:27:35 -04:00
Brit Butler
5705e3a7dc Merge pull request #46 from redline6561/better-indexes
Release: 0.9.3!
2014-04-15 22:11:02 -04:00
Brit Butler
6c2145d0d9 Release: 0.9.3! 2014-04-15 22:09:17 -04:00
Brit Butler
eb0f1e23ab Update hacking docs. 2014-04-15 22:05:26 -04:00
Brit Butler
c17bcdd385 Factor purge-all calls in discover to a before method. 2014-04-15 20:43:56 -04:00
Brit Butler
862d7ad066 Update templates to match posts->content. 2014-04-15 20:39:13 -04:00
Brit Butler
fb224744e7 Rename index-posts -> index-content. 2014-04-15 20:32:24 -04:00
Brit Butler
c66703ed19 Template linking fix. 2014-04-15 19:33:25 -04:00
Brit Butler
f2bd0ff0ef More comments and docs tweaks. 2014-04-15 19:25:19 -04:00
Brit Butler
e44dcbf05d Sitemap plugin tweaks. 2014-04-15 19:05:52 -04:00
Brit Butler
d0059ed69e Miscellaneous fixes. 2014-04-15 16:46:04 -04:00
Brit Butler
7af3462d99 Massive indexes rewrite. 2014-04-15 15:27:46 -04:00
Brit Butler
3693e10cbf Initial pass at the Document Protocol. 2014-04-15 11:48:38 -04:00
Brit Butler
05ea52813a Remove feeds from defsystem. 2014-04-14 22:15:03 -04:00
Brit Butler
7b4a795142 Fix FEEDS definition thinko and tweak some more docs. 2014-04-14 22:12:52 -04:00
Brit Butler
dcf2db3ff4 Update theming docs and README a bit. 2014-04-14 17:10:03 -04:00
Brit Butler
9a86d48314 Alphabetize config slots, docs TODO about content types. 2014-04-10 16:46:07 -04:00
Brit Butler
39687805f5 Generalize DO-CTYPES to DO-SUBCLASSES, update callsites. 2014-04-10 16:44:51 -04:00
Brit Butler
74ac87d4e5 Move CONSTRUCT into util, it's more general than CONTENT. 2014-04-08 17:49:13 -04:00
Brit Butler
3ee8f43325 Remove dead code in PAGE-URL.
We partially support 'pretty URLs'. They can be achieved by setting
:page-ext in the *config* to "/" and configuring the web server to
ignore html extensions.
2014-04-08 16:59:56 -04:00
Brit Butler
e429d94f3f Make feeds a subclass of indexes. Cleanup! 2014-04-08 16:51:53 -04:00
Brit Butler
adfcadf36b Missed feeds in mass renaming. 2014-04-07 21:00:44 -04:00
Brit Butler
bb66adb1d4 Rename indices file. 2014-04-07 20:56:33 -04:00
Brit Butler
c75e62e724 Mass rename indices->indexes. 2014-04-07 20:54:45 -04:00
Brit Butler
b11b6fbf94 We only want to include POSTs in an INDEX for now. 2014-04-06 17:20:05 -04:00
Brit Butler
3ccc0fb5d0 Add some content type details to hacking.md. Minor renaming. 2014-04-06 17:16:25 -04:00
Clement Gerouville
e4ec9bd1ae Enabled UTF-8 when reading the configuration file 2014-04-06 16:55:16 -04:00
Clement Gerouville
0b645902a7 Adding UTF-8 support. 2014-04-06 16:53:48 -04:00
Brit Butler
e2e2704a26 Add HACKING docs and minor tweaks. 2014-03-25 18:06:18 -04:00
Brit Butler
e1cb39618d Minor README tweak. 2014-03-23 13:11:42 -04:00
Brit Butler
3692c57123 Merge pull request #45 from redline6561/minor-cleanups
Minor cleanups
2014-03-23 13:05:49 -04:00
Brit Butler
9a3fc1ee32 Use REL-PATH instead of make-pathname in DISCOVER-CONFIG-PATH. 2014-03-23 13:04:22 -04:00
Brit Butler
5ce88c48f5 Fix dangling parentheses. 2014-03-22 18:09:52 -04:00
Brit Butler
387e47bde6 Use OR to guard instead of IF. 2014-03-22 18:08:47 -04:00
Brit Butler
5a32bb19cf Merge pull request #44 from tychoish/more-data
adding additional configurable options
2014-03-22 18:06:23 -04:00
tycho garen
795ddf5f1b revisions to configuration option patch in response to code review 2014-03-22 12:54:45 -04:00
Brit Butler
f9348807f3 Merge pull request #43 from tychoish/rst-fixes
Clean up Restructured Text support.
2014-03-07 16:12:58 -05:00
tycho garen
25891fb7ca adding additional data to the environment, making more build factors configurable 2014-03-02 11:54:39 -05:00
tycho garen
1b72440a61 make output from restructured text generator useable 2014-03-01 22:27:17 -05:00
Brit Butler
ea81fddd96 Merge pull request #41 from rmoritz/master
Fix broken link in README.md
2013-09-02 05:46:05 -07:00
Ralph Moritz
ad581d38e8 Fix broken link in README.md 2013-09-02 09:13:35 +02:00
Brit Butler
6f2eb86f6a Tweak README.md 2013-08-28 11:10:34 -04:00
Brit Butler
d93a529e41 Merge pull request #40 from ralph-moeritz/master
New theme based on bootswatch readable.
2013-07-21 11:07:12 -07:00
Ralph Moritz
d30cbc7d7b New theme based on bootswatch readable. 2013-07-20 23:18:01 +02:00
Brit Butler
38795ad77b Minor TODO update. 2013-06-08 17:22:41 -04:00
Brit Butler
210c0aff76 Merge pull request #38 from woudshoo/sitemap-timestamp
Changed timestamp format of the sitemap so google does not complain (and...
2013-05-18 07:20:03 -07:00
Brit Butler
4d64cc1708 Merge pull request #39 from woudshoo/doc-fix
Small doc fix in template example
2013-05-18 06:24:11 -07:00
Willem Rein Oudshoorn
bd3bdad9b1 Small doc fix in template example 2013-05-18 12:24:06 +02:00
Willem Rein Oudshoorn
f753debbba Changed timestamp format of the sitemap so google does not complain (and it conforms to the specs.) 2013-05-18 12:12:25 +02:00
Brit Butler
51ce961bae Minor deploy fix. 2013-05-11 21:45:24 -04:00
Brit Butler
1db94e8dd3 Release: 0.9.2! 2013-05-11 20:37:58 -04:00
Brit Butler
cb1a82fc67 Use sitemap plugin in the default config. 2013-05-01 15:42:05 -04:00
Brit Butler
c2c45356d2 Update TODO. Minor sitemap plugin cleanup. 2013-04-29 17:01:22 -04:00
Brit Butler
2fb3ed1157 Factor render-index out of render-indices. 2013-04-29 10:17:19 -04:00
Brit Butler
58fe27aa54 Generalize DISCOVER for all content-types. 2013-04-29 09:22:00 -04:00
Brit Butler
5f76d94e42 Clean up theme docs and add overview. 2013-04-28 16:13:08 -04:00
Willem Rein Oudshoorn
77d1881927 Added first draft of theme documentation 2013-04-28 16:13:08 -04:00
Brit Butler
361316f7b3 Minor cleanups to MAIN and LOAD-CONFIG. 2013-04-28 16:12:37 -04:00
Brit Butler
4e7a0d4c90 README tweaks. 2013-04-28 12:21:24 -04:00
Brit Butler
dd027db409 Minor cleanups to sitemap plugin. 2013-04-28 10:11:38 -04:00
Brit Butler
e6c6bcbe26 Merge pull request #33 from mrordinaire/sitemap
Added sitemap generation.
2013-04-28 07:00:11 -07:00
Do Nhat Minh
dfcb669d68 added doc for sitemap plugin 2013-04-27 02:40:53 +08:00
Do Nhat Minh
5dac19c696 consistency 2013-04-27 02:20:08 +08:00
Do Nhat Minh
e28729b0de add sitemap.lisp in plugins 2013-04-27 02:17:59 +08:00
Do Nhat Minh
488191b237 move sitemap to plugins 2013-04-27 02:13:56 +08:00
Brit Butler
6eab23f8b7 Bugfix to FMT refactor. 2013-04-26 12:53:51 -04:00
Do Nhat Minh
edcd1022ab add documentation 2013-04-26 22:26:41 +08:00
Do Nhat Minh
a3ed5b7719 added robots.txt generation 2013-04-26 22:14:57 +08:00
Brit Butler
25c8bc11ea Minor cleanups. 2013-04-26 10:14:14 -04:00
Do Nhat Minh
99f57f3513 added lastmod for each url in sitemap.xml 2013-04-26 10:29:32 +08:00
Do Nhat Minh
67594d4bca fix missing slash in url in sitemap.tmpl 2013-04-26 10:15:41 +08:00
Do Nhat Minh
273d4ad6a7 added sitemap generation 2013-04-26 10:03:04 +08:00
Brit Butler
a30405de7c Merge pull request #32 from mrordinaire/fix-atom-feed
Fix atom feed generation
2013-04-25 07:47:59 -07:00
Do Nhat Minh
6e4082c7cb fix atom feed generation 2013-04-25 16:40:55 +08:00
Brit Butler
e399b6063a Update docs. 2013-04-22 09:59:26 -04:00
Brit Butler
e12f5edc79 Fix gh-pages recursive call by renaming deploy->deploy-dir. 2013-04-22 09:49:31 -04:00
Brit Butler
c7b36f65c3 Assorted cleanups. 2013-04-21 20:07:59 -04:00
Brit Butler
9a3a7d7f50 Add a sanity test to prevent future embarrassment. 2013-04-21 16:05:32 -04:00
Brit Butler
a7754c5246 Update mathjax plugin for new tags. Fixes Issue #29. 2013-04-21 16:05:32 -04:00
Brit Butler
0e6edb7211 Cleanups to feeds. 2013-04-21 14:11:24 -04:00
Brit Butler
96be3254ab Bugfix to github-pages plugin. Base should be the current deploy, not
the deploy directory itself.
2013-04-21 12:43:34 -04:00
Brit Butler
135295b073 Add gh-pages plugin. Fixes Issue #28. Thanks mrordinaire! 2013-04-21 12:32:20 -04:00
Do Nhat Minh
b97bd2b99d removed printing cname path 2013-04-21 12:24:55 -04:00
Do Nhat Minh
55613d717f change slot github to github-pages, add github-pages to single-site.coleslawrc 2013-04-21 12:24:22 -04:00
Do Nhat Minh
bf6ba24434 finished adding github's pages support 2013-04-21 12:23:12 -04:00
Do Nhat Minh
6396e136d3 first pass in adding github integration 2013-04-21 12:20:42 -04:00
Brit Butler
0aef123193 NEWS updates. 2013-04-20 08:14:32 -04:00
Brit Butler
08ac715884 Cleanups to new-tags. Fixes Issue #27. 2013-04-20 07:48:42 -04:00
Willem Rein Oudshoorn
dbdb7e29af Allow arbitrary tag names. However if two tags have the same slug they will be merged.
The name of the merged tag will be one of the original names.
However this should not happen in practice.
2013-04-19 12:53:25 +02:00
Brit Butler
3a5d2d1c68 Minor deploy cleanup. 2013-04-18 16:24:02 -04:00
Brit Butler
f6e78dd47c Slugify tags. Fixes issue #25. 2013-04-18 15:18:14 -04:00
Brit Butler
ad2a05fa3a Fix thinko. 2013-04-18 14:43:45 -04:00
Brit Butler
eb1c5d8f83 Minor cleanups to theme-package error handling. 2013-04-18 14:41:43 -04:00
Brit Butler
e7caa267a3 Merge pull request #26 from woudshoo/theme-not-exist-condition
Raise condition if the theme package cannot be found.
2013-04-18 11:31:18 -07:00
Willem Rein Oudshoorn
2c1c398581 Raise condition if the theme package cannot be found. 2013-04-12 15:45:53 +02:00
Brit Butler
8dc43ae256 Release: 0.9.1! 2013-04-10 23:21:50 -04:00
Brit Butler
930e8bb04d Make WRITE-PAGE overwrite existing pages by default. 2013-04-09 16:45:04 -04:00
Brit Butler
da219abe4f Minor tweaks to PREVIEW. 2013-04-09 16:41:09 -04:00
Brit Butler
553030428d Tweaks to injections handling. NEWS updates. 2013-04-09 16:36:07 -04:00
Brit Butler
6082cb46f5 Add and export PREVIEW. 2013-04-09 16:35:43 -04:00
Brit Butler
dfc5be16aa Minor spacing tweak to navigation. 2013-04-09 12:08:10 -04:00
Brit Butler
ffc8fd3c60 Merge pull request #22 from woudshoo/mathjax-improvements
Tiny improvement, do not emit text/x-math-config script section if not n...
2013-04-04 11:36:35 -07:00
Willem Rein Oudshoorn
94420c9d4e Tiny improvement, do not emit text/x-math-config script section if not needed 2013-04-04 20:18:18 +02:00
Brit Butler
09d54e4863 Mathjax plugin cleanups. 2013-04-04 10:06:02 -04:00
Brit Butler
cd8b9f8d2c Merge pull request #20 from woudshoo/mathjax-improvements
Mathjax improvements
2013-04-04 06:03:26 -07:00
Willem Rein Oudshoorn
aedaf625ff Also allow for nil argument to mathjax-config 2013-04-03 20:10:10 +02:00
Willem Rein Oudshoorn
2e5144996b Added mathjax-config argument. 2013-04-03 20:05:04 +02:00
Willem Rein Oudshoorn
3afb6e29e4 Added options to MathJax plugin, also updated documentation 2013-04-03 18:27:30 +02:00
Brit Butler
23f2cb16e3 Merge pull request #19 from woudshoo/injections-growth
*injections* was never set to nil, so kept growing indefinitely
2013-04-02 11:25:10 -07:00
Willem Rein Oudshoorn
6569342f2a *injections* was never set to nil, so kept growing indefinetely 2013-04-02 18:29:39 +02:00
Brit Butler
3a21bd10e1 Merge pull request #18 from woudshoo/comma-space-issue
Fixed issue with " ," rendered instead of ", "
2013-04-01 13:06:10 -07:00
Willem Rein Oudshoorn
67b39fff12 Fixed issue with " ," rendered instead of ", " 2013-04-01 20:22:39 +02:00
Brit Butler
9bc2e55d58 Make RSS and ATOM templates theme-independent. 2013-04-01 11:23:10 -04:00
Brit Butler
b72eb5b03a Packaging cleanups to S3 plugin. 2013-03-06 13:05:57 -05:00
Brit Butler
6be80e8d5c Minor tweaks. 2013-03-04 17:23:11 -05:00
Brit Butler
13a7a7dc65 Cleanup make-pubdate based on information from cl-test-grid. Thanks Fare! 2013-02-21 09:18:09 -05:00
Brit Butler
149c384dce Fix docs thinko. Don't hack so late. 2013-02-20 13:44:57 -05:00
Brit Butler
818def3629 Release: 0.9! 2013-02-20 01:06:32 -05:00
Brit Butler
60ad551523 Simplify plugin loader. 2013-02-19 19:14:14 -05:00
Brit Butler
40a61d0615 Move rst plugin to the right place. 2013-02-19 17:26:55 -05:00
Brit Butler
a2cd3c1cda Minor cleanups. 2013-02-01 12:24:10 -05:00
Brit Butler
c706964880 Add exit function and use it in post-receive script. Fixes Issue #13. 2013-02-01 10:47:37 -05:00
Brit Butler
9f72bff710 Slight README tweaks. 2013-01-31 00:23:50 -05:00
Brit Butler
c169581485 TODO updates. 2013-01-30 23:32:55 -05:00
Brit Butler
e1a1ca75cf Minor cleanup to S3 plugin. 2013-01-30 23:24:57 -05:00
Brit Butler
7fd7954c5d Overhaul S3 plugin and update NEWS. 2013-01-30 23:18:25 -05:00
Brit Butler
9a46023d76 Package fix to rst plugin. 2013-01-29 20:38:55 -05:00
Brit Butler
d256db6bbc Add draft restructured-text plugin, minor heroku cleanup. 2013-01-29 20:38:16 -05:00
Brit Butler
87b85b1d29 This use of dynamic rebinding is more confusing than clever. 2013-01-29 17:49:55 -05:00
Brit Butler
93ba4e45fc Heroku is a read-only FS so we can't use /app as a deploy target.
This reverts commit e893ac5447.
2013-01-29 16:59:30 -05:00
Brit Butler
e893ac5447 Export *config* and use it in heroku plugin. 2013-01-29 16:15:41 -05:00
Brit Butler
a703bcd984 Remove old hunchentoot plugin. Ensure hunchentoot is loaded for heroku even when not using jsmpereira's cl-buildpack. 2013-01-29 16:11:55 -05:00
Brit Butler
26749ee1d3 Merge pull request #12 from jsmpereira/master
Add initial version of Heroku plugin.
2013-01-29 12:27:22 -08:00
Jose Santos
a3feed45fd Heroku plugin. 2013-01-29 20:24:56 +00:00
Brit Butler
499453f622 Add BSD License file. 2013-01-29 10:18:26 -05:00
Brit Butler
9be3a7f769 NEWS update and minor style tweak. 2013-01-29 09:59:14 -05:00
Brit Butler
24a7a11ccd Merge pull request #11 from jsmpereira/master
Fix call to flet function on indices.lisp
2013-01-29 06:45:07 -08:00
Jose Santos
ce12c56644 Fix function call. 2013-01-29 14:42:48 +00:00
Brit Butler
70fbaafe40 Export read-content and assorted minor cleanups. 2013-01-22 15:13:37 -05:00
Brit Butler
a1b39bfa33 Disallow periods in slugs. Fixes Issue #10. 2013-01-15 11:56:58 -05:00
Brit Butler
4930387924 Minor import fix. 2013-01-14 12:35:48 -05:00
Brit Butler
f2fa130527 Add Analytics plugin, extend example coleslawrc. 2013-01-14 12:32:58 -05:00
Brit Butler
7024377cad Merge pull request #9 from danieldickison/master
Fix typo in readme
2013-01-12 20:25:42 -08:00
Daniel Dickison
a117529516 Fix typo in readme. 2013-01-12 10:58:26 -05:00
Brit Butler
ba4e4d9496 Merge pull request #8 from redline6561/content-types
Extensible Content types.
2013-01-06 12:37:27 -08:00
Brit Butler
88c0adc0af DO-CTYPES bugfix. 2013-01-06 15:34:52 -05:00
Brit Butler
1ee5402179 Docs update and package tweaks. 2013-01-06 14:36:40 -05:00
Brit Butler
8592cad324 Release: Version 0.8! NEWS tells the story. 2013-01-06 14:31:04 -05:00
Brit Butler
1d4312f01f Update export list to expose new API. 2013-01-06 13:55:02 -05:00
Brit Butler
686aaa2941 Stick with indices rendering all content rather than having content types control their inclusion at this time. 2013-01-04 16:18:03 -05:00
Brit Butler
5a7ac0a3c9 Fix README examples/ paths. 2013-01-03 11:12:42 -05:00
Brit Butler
f2ccd8829f Tweak the language in indices. 2013-01-02 14:23:56 -05:00
Brit Butler
a1c894c49d Fix Mathjax plugin. 2013-01-02 13:04:20 -05:00
Brit Butler
bc79620b3a TODO notes and shout content-type thoughts. 2013-01-01 20:22:58 -05:00
Brit Butler
cc2700b817 Text is the universal interface. 2013-01-01 20:16:28 -05:00
Brit Butler
04937e88bf Minor thinko/bugfix. 2013-01-01 20:07:31 -05:00
Brit Butler
3d1301dc6d Fix feeds and indices. 2013-01-01 19:41:22 -05:00
Brit Butler
6d47244eac First pass at support for multiple content-types. WARNING: Indices and feeds are broken. 2013-01-01 18:18:43 -05:00
Brit Butler
497935af91 Update README to mention support for multi-site publishing. 2013-01-01 18:18:11 -05:00
Brit Butler
19a63f2cdd Another minor load-config cleanup. 2013-01-01 18:15:55 -05:00
Brit Butler
794f08a7be Minor cleanups to load-config. 2013-01-01 16:45:33 -05:00
Brit Butler
ef77026c0c Merge pull request #7 from ralph-moeritz/master
Added support for multiple blogs
2012-12-31 14:11:49 -08:00
Ralph Moritz
04d9d523d8 Multi-site support 2012-12-31 14:00:54 +02:00
Brit Butler
8463a33b62 Stick with pathnames throughout. 2012-12-14 15:04:21 -05:00
Brit Butler
d7747ea0b2 Move the filepath stuff into an :around method. 2012-12-14 14:55:36 -05:00
Brit Butler
6dc400c91c Fix oversight from page-path refactor. Thanks to Xach for the report. 2012-12-14 14:38:03 -05:00
Brit Butler
d8e79c7a0a Merge pull request #6 from ralph-moeritz/master
Fix typo.
2012-12-11 15:31:02 -08:00
Brit Butler
067f523293 Minor readme tweaks. 2012-12-11 10:11:39 -05:00
Brit Butler
3bdcf123a0 Add formerly transitive dependency, cl-ppcre. Thanks Xach! 2012-12-11 10:10:00 -05:00
Ralph Moritz
7d6324aaf7 Fix typo. 2012-12-11 12:38:54 +02:00
Brit Butler
65933f731f Fix indices and feeds. 2012-11-28 17:37:19 -05:00
Brit Butler
47e2ca02c1 README updates. 2012-11-28 16:56:42 -05:00
Brit Butler
4b8145a7ca Remove DATE-TO-TIMESTAMP since it was unused. 2012-11-28 16:35:41 -05:00
Brit Butler
3ddd50bf7f WARNING: This commit breaks indices and feeds.
Factor write-page from render-page. Reorganization to allow for new content types.
2012-11-28 16:11:02 -05:00
Brit Butler
2a248f96e9 Rename rss->rss-feed for template name consistency. 2012-11-28 15:56:09 -05:00
Brit Butler
5e46e6a771 Merge pull request #4 from ralph-moeritz/master
Switch from trivial-shell to inferior-shell, bugfixes on CCL.
2012-11-27 06:29:56 -08:00
Ralph Moeritz
f52ef504fd Got it working with CCL. 2012-11-27 16:11:53 +02:00
Brit Butler
1978c6a62f Fix symbol collision in theme package. 2012-11-26 23:03:22 -05:00
Brit Butler
2240dea8ba Add TODO note. 2012-09-25 19:29:10 -04:00
Brit Butler
ed98a5bd69 Fix destructuring and other minor thinkos. 2012-09-20 19:14:10 -04:00
91 changed files with 3791 additions and 653 deletions

5
.gitignore vendored
View file

@ -1,6 +1,11 @@
*~
*.fasl
*.dx32fsl
*.dx64fsl
*.lx32fsl
*.lx64fsl
ignore/
generated/
.curr
.prev
build/

24
.travis.yml Normal file
View file

@ -0,0 +1,24 @@
language: common-lisp
sudo: false
env:
global:
- PATH=~/.roswell/bin:$PATH
- ROSWELL_INSTALL_DIR=$HOME/.roswell
matrix:
- LISP=sbcl-bin
- LISP=ccl-bin
install:
- curl -L https://raw.githubusercontent.com/snmsts/roswell/release/scripts/install-for-ci.sh | sh
- ros install coleslaw
cache:
directories:
- $HOME/.roswell
- $HOME/.config/common-lisp
script:
- ros -s prove -e "(ql:quickload '(coleslaw coleslaw-test))"
-e '(or (prove:run :coleslaw-test) (uiop:quit -1))'
- cli-tests/basic.sh false

9
LICENSE Normal file
View file

@ -0,0 +1,9 @@
Copyright (c) 2013, Brit Butler
All rights reserved.
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

163
NEWS.md Normal file
View file

@ -0,0 +1,163 @@
Legend:
* Site-Breaking Change:
A change that will break most config files or coleslaw installations.
It is expected to effect all users but should require only minor
user effort to resolve.
* Incompatible Change:
A change to Coleslaw's exported interface. Plugins or Themes that have
not been upstreamed are effected and may require minor effort to fix.
## Changes for 0.9.7 (2014-11-25):
* **New Plugin**: Support for [embedded gfycats](http://gfycat.com/)
has been added. See the [plugin use][plg-use] docs for further details.
* **Enhancement**: UTF-8 support has been made more portable and
added to the Wordpress import plugin. (Thanks @cmstrickland!)
* **Enhancement**: Filenames are now included in errors from the
content loader. (via @PuercoPop)
* **Enhancement**: Coleslaw now handles **deploy-dir**, **repo**,
and **staging-dir** config options more gracefully. Previously,
various errors could be encountered if directory options lacked
a trailing slash.
* Several portability fixes were made to CCL's encoding handling
and usage in the post-receive script.
* An initarg bug was fixed in the directory-does-not-exist condition.
* Some namespacing bugs in the Static Pages plugin have been fixed.
## Changes for 0.9.6 (2014-09-27):
* **SITE-BREAKING CHANGE**: Coleslaw now defaults to a "basic" deploy
instead of the previous symlinked, timestamped deploy strategy.
To retain the previous behavior, add `(versioned)` to your config's
`:plugins` list.
* **Incompatible Change**: Custom themes will be broken by a change
to URL handling. Previously, we were hand-constructing URLs in the
templates. All site objects now store their URL in an instance slot.
In general, hrefs should be of the form `<a href="{$config.domain}/{$obj.url}"> ...</a>`.
* **Incompatible Change**: The interface of the `add-injection` function
has changed. If you have written a plugin which uses `add-injection`
you should update it to conform to the [new interface][plg-api].
* **New Plugin**: Support for [twitter summary cards][ts-cards] on blog
posts has been added thanks to @PuercoPop.
* **Docs**: Improved README and Theming docs. New Config File docs.
* Changes to `:routing` would previously break links in the templates
but now work seamlessly due to the updated URL handling.
* Loading content is more robust when empty lines or metadata are passed.
Thanks to @PuercoPop for the bug report and preliminary fix.
* The config `:repo` option is now deprecated as its value has become
a required argument to `coleslaw:main`. The value passed to `main`
will override the config value going forward.
* Improved handling of directories and error-reporting when they
don't exist is available thanks to @PuercoPop.
* The templates are now HTML5 valid thanks to @Ferada.
* Fixed a bug where RSS/Atom tag feeds were being published multiple times.
## Changes for 0.9.5 (2014-06-13):
* **New Plugin**: Incremental builds, cutting runtime for generating
medium to large sites roughly in half!
* **New Plugin**: A Twitter plugin to tweet about your new posts. Thanks to @PuercoPop!
* Config options for the HTML lang and charset attributes. Thanks to @ryumei!
* Coleslaw now exports a `get-updated-files` function which can be
used to get a list of file-status/file-name pairs that were changed
in the last git push. There is also an exported `find-content-by-path`
function to retrieve content objects from the above file-name. These
were used by both the Twitter and Incremental plugins.
* The usual bugfixes, performance improvements, and documentation tweaks.
## Changes for 0.9.4 (2014-05-05):
* **SITE-BREAKING CHANGE**: Coleslaw now supports user-defined routing.
Instead of hard-coding the paths various content types are stored at,
they **must** be specified in the configuration file (.coleslawrc).
Just copy the `:routing` key from the [example][example.rc] to
get the old behavior.
* **SITE-BREAKING CHANGE**: Coleslaw's multi-site support has changed.
Instead of having a single .coleslawrc in the user's home directory
that has sections for multiple repos, a .coleslawrc may be included
*in* the blog repo itself. If no .coleslawrc is found in the repo,
it is loaded from the user's home directory instead.
* Coleslaw no longer expects a particular repo layout. Use whatever
directory hierarchy you like.
* New Content Type Plugin: Static Pages, accepting a title, url, and
optionally tags and a date. All files with a `.page` extension are
compiled as static pages and reuse the POST template.
To enable Static Pages, add `(static-pages)` to the `:plugins`
section of your config.
* Coleslaw now allows content without a date or tags. Note that POSTs
without a date will still show up in the reverse chronological
indexes at the very end.
* Fixed an embarrassing escaping bug in our last quicklisp release.
## Changes for 0.9.3 (2014-04-16):
* **INCOMPATIBLE CHANGE**: `page-path` and the `blog` config class are no longer exported.
* **INCOMPATIBLE CHANGE**: `render-content` has been renamed `render-text` for clarity.
* New Docs: [A Hacker's Guide to Coleslaw][hacking_guide] and [Themes][theming_guide]!
* A new theme *readable* based on bootswatch readable, courtesy of @rmoritz!
* Posts may have an author to support multi-user blogs courtesy of @tychoish.
* Fixes to the ReStructuredText plugin courtesy of @tychoish.
* UTF-8 fixes for config files and site content courtesy of @cl-ment.
* Fix timestamps in the sitemap plugin courtesy of @woudshoo.
## Changes for 0.9.2 (2013-05-11):
* **INCOMPATIBLE CHANGE**: Renamed staging, deploy config options staging-dir, deploy-dir.
* A plugin for Github Pages support. (thanks @mrordinaire!)
* A new and improved implementation of tags. (thanks @woudshoo!)
* A THEME-DOES-NOT-EXIST error is raised when the theme can't be found.
## Changes for 0.9.1 (2013-04-10):
* Added a PREVIEW function for REPL use.
* Make ATOM and RSS templates a separate "generic" theme. (thanks @woudshoo!)
* Fixed bug where repeatedly loading plugins caused them to appear in the page more than once. (thanks @woudshoo!)
* Fixes to spacing in navigation and tagsoup. (thanks @woudshoo!)
## Changes for 0.9 (2013-02-20):
* **INCOMPATIBLE CHANGE**: All :plugins in .coleslawrc must be lists. (i.e. (mathjax) not mathjax)
* Add support for analytics via Google.
* Add support for Restructured Text via cl-docutils.
* Add support for deploying to Amazon S3.
* Add a heroku plugin to ease hunchentoot deployments. (thanks @jsmpereira!)
* Ensure coleslaw exits after MAIN. Fixes issue #13.
* Greatly improved docs for the various plugins and plugin API.
## Changes for 0.8 (2013-01-06):
* Add support for new [content types](http://blog.redlinernotes.com/posts/Lessons-from-Coleslaw.html).
* Support for [Multi-site Publishing](http://blub.co.za/posts/Adding-multi-site-support-to-Coleslaw.html).
* CCL and Atom feed bugfixes.
* Major code refactor and docs update.
## Changes for 0.7 (2012-09-20):
* Add commenting support via Disqus plugin.
* Add formal plugin API with per-page predicate support. (aka "injections")
* Note jsmpereira's [coleslaw heroku package](https://github.com/jsmpereira/coleslaw-heroku) in README.
* Support for RSS feeds of arbitrary tags, e.g. "lisp" posts.
## Changes for 0.6.5 (2012-09-12):
* Add support for ATOM feeds.
* Add support for a sitenav in coleslawrc configs.
* Template and rendering cleanup.
* Miscellaneous deployment improvements.
## Changes for 0.6 (2012-08-29):
* Support Markdown in core rather than as a plugin.
* Improve documentation + README.
* Copious bugfixes and code cleanups.
## Changes for 0.5 (2012-08-22):
* Initial release.
[hacking_guide]: https://github.com/redline6561/coleslaw/blob/master/docs/hacking.md
[theming_guide]: https://github.com/redline6561/coleslaw/blob/master/docs/themes.md
[example.rc]: https://github.com/redline6561/coleslaw/blob/master/examples/example.coleslawrc
[plg-use]: https://github.com/redline6561/coleslaw/blob/master/docs/plugin-use.md
[plg-api]: https://github.com/redline6561/coleslaw/blob/master/docs/plugin-api.md#extension-points
[ts-cards]: https://dev.twitter.com/cards/types/summary

149
README.md
View file

@ -1,5 +1,8 @@
# coleslaw
[![Build Status](https://travis-ci.org/coleslaw-org/coleslaw.svg?branch=master)](https://travis-ci.org/kingcons/coleslaw)
[![Quicklisp](http://quickdocs.org/badge/coleslaw.svg)](http://quickdocs.org/coleslaw/)
<img src="https://raw.github.com/redline6561/coleslaw/master/themes/hyde/css/logo_medium.jpg" alt="coleslaw logo" align="right"/>
> [Czeslaw Milosz](http://blog.redlinernotes.com/tag/milosz.html) was the writer-in-residence at UNC c. 1992.
@ -7,59 +10,121 @@
> drinking coffee, reading, writing, eating chips and salsa. I remember a gentleness
> behind the enormous bushy eyebrows and that we called him Coleslaw. - anon
Coleslaw aims to be flexible blog software suitable for replacing a single-user static site compiler such as Jekyll.
Coleslaw is Flexible Lisp Blogware similar to [Frog](https://github.com/greghendershott/frog), [Jekyll](http://jekyllrb.com/), or [Hakyll](http://jaspervdj.be/hakyll/).
Have questions?
- IRC in **#coleslaw** on Freenode!
- Subscribe to the mailing list [**coleslaw@common-lisp.net**](https://mailman.common-lisp.net/listinfo/coleslaw).
See the [wiki](https://github.com/redline6561/coleslaw/wiki/Example-sites) for a list of coleslaw-powered blogs.
Coleslaw should run on any conforming Common Lisp implementations but
testing is primarily done on [SBCL](http://www.sbcl.org/) and [CCL](http://ccl.clozure.com/).
## Features
* Git for storage
* RSS feeds!
* Markdown Support with Code Highlighting provided by [colorize](http://www.cliki.net/colorize).
* Currently supports: Common Lisp, Emacs Lisp, Scheme, Clojure, C, C++, Java, Python, Erlang, Haskell, Obj-C, Diff.
* Plugins to...
* Use LaTeX (inside pairs of $$) via Mathjax
* Import from wordpress
* There is also a [Heroku buildpack](https://github.com/jsmpereira/coleslaw-heroku) maintained by Jose Pereira.
* RSS/Atom feeds
* Themes
* A [Plugin API](docs/plugin-api.md) and [**plugins**](docs/plugin-use.md) for...
## Installation
This software should be portable to any conforming Common Lisp implementation but this guide will assume SBCL is installed. Testing has also been done on CCL.
Server side setup:
| plugins | plugins | plugins |
|--------------------------------------------------------|----------------------------------------------|----------------------------------------------------------|
| Sitemap generation | Incremental builds | Analytics via Google or [Matomo](https://www.matomo.org) |
| Comments via [Disqus](http://disqus.com/) | Comments via [isso](http://posativ.org/isso) | Hosting via [Amazon S3](http://aws.amazon.com/s3/) |
| Hosting via [Github Pages](https://pages.github.com/) | Embedding [gfycats](http://gfycat.com/) | [Tweeting](http://twitter.com/) about new posts |
| [Mathjax](http://mathjax.org/) | Posts in ReStructured Text | [Wordpress](http://wordpress.org/) import |
| [Pygments](http://pygments.org/) | [colorize](http://www.cliki.net/colorize) | |
1. Setup git and create a bare repo as shown [here](http://git-scm.com/book/en/Git-on-the-Server-Setting-Up-the-Server).
2. Install Lisp and [Quicklisp](http://quicklisp.org/).
3. ```wget -c https://raw.github.com/redline6561/coleslaw/master/example.coleslawrc -O ~/.coleslawrc``` # and edit as necessary
4. ```wget -c https://raw.github.com/redline6561/coleslaw/master/example.post-receieve -O your-blog.git/hooks/post-receive``` # and edit as necessary
5. ```chmod +x your-blog/.git/hooks/post-receive```
6. Create or clone your blog repo locally. Add your server as a remote with ```git remote add prod git@my-host.com:path/to/repo.git```
7. Point the web server of your choice at the symlink /path/to/deploy-dir/.curr/
Now whenever you push a new commit to the server, coleslaw will update your blog automatically! You may need to git push -u prod master the first time.
## Installation/Tutorial
<!-- Don't let the first user select from multiple choises -->
Step 1: Install this library.
## The Post Format
Coleslaw expects post files to be formatted as follows:
```
;;;;;
title: foo
tags: bar, baz
date: yyyy-mm-dd hh:mm:ss
format: html (for raw html) or md (for markdown)
;;;;;
your post
$ ros install coleslaw-org/coleslaw # With [Roswell](https://roswell.github.io/)
$ export PATH="$HOME/.roswell/bin:$PATH" # If you haven't done this before for Roswell
or
CL-USER> (ql:quickload :coleslaw-cli)
```
## Importing from Wordpress
There is a "plugin" to import from wordpress. At some point, it should be turned into a standalone script. Until then...
Step 2: Initialize your blog repository.
1. Export your posts from wordpress.
2. In your lisp of choice, do the following:
1. ```(ql:quickload 'coleslaw)```
2. ```(in-package :coleslaw)```
3. ```(load-plugins '(import))```
4. ```(coleslaw-import::import-posts "/path/to/export.xml")```
```
$ mkdir yourblog ; cd yourblog
$ git init
$ coleslaw setup # or
CL-USER> (coleslaw-cli:setup)
```
The XML will be read and placed into .post files in the :repo location specified in your [.coleslawrc](http://github.com/redline6561/coleslaw/blob/master/example.coleslawrc).
`coleslaw setup` / `(coleslaw-cli:setup)` will generate a `.coleslawrc` file in
the current directory, which contains the configuration of the static website.
## Writing your own plugins
For now, see the [API](http://redlinernotes.com/docs/coleslaw.html) and the [mathjax plugin](https://github.com/redline6561/coleslaw/blob/master/plugins/mathjax.lisp) for an example.
A proper guide about this will be written later.
Step 3: Write a post file in the current directory.
The file should contain a certain metadata, so use the `coleslaw new` command,
which instantiates a correct file for you.
## Theming
A default theme, hyde, is provided. Themes are made using Google's closure-template and the source for [hyde](https://github.com/redline6561/coleslaw/tree/master/themes/hyde) should be simple and instructive until I can provide better docs.
```
$ coleslaw new
Created a post 2017-11-06.post .
# or
CL-USER> (coleslaw-cli:new "post")
Created a post 2017-11-06.post .
```
Step 4: Generate the site from those post files.
The result goes to the *staging directory* specified in the `.coleslawrc` file.
The staging directory is `/tmp/coleslaw/` by default.
```
$ coleslaw # or
$ coleslaw generate # or
$ coleslaw stage # or
CL-USER> (coleslaw-cli:generate) ; or
CL-USER> (coleslaw-cli:stage) ; --- these are all aliases
```
Step 5: You can launch a web server to check the result on a browser.
(Running a webserver sometimes has a benefit over just opening an html file,
e.g. the relative links behaves differently on a file:/// protocol)
```
$ coleslaw preview # or
CL-USER> (coleslaw-cli:preview)
```
Step 6: and watch the file system to automatically regenerate the site!
```
$ coleslaw watch # or even better,
$ coleslaw watch-preview # or, on REPL,
CL-USER> (coleslaw-cli:watch) ;; watch-preview does not work on REPL right now
```
Step 7: When you think your article is publishable, run
```
$ coleslaw deploy # or
CL-USER> (coleslaw-cli:deploy)
```
To move the contents in the staging dir to the deploy dir.
By default, this deploy command uses `rsync` to sync the directories,
where the deploy dir could be a remote directory on the server which is running your website.
By using a plugin, you can customize this behavior e.g. running the deploy on gh-pages.
For further customization, e.g. adding a new plugin, developing a new plugin, changing the deploy option, or creating a new theme,
see the [config docs](docs).
We provide three default themes: hyde, the default, and readable (based on
[bootswatch readable](http://bootswatch.com/readable/)).
A core goal of *coleslaw* is to be both pleasant to read and easy to
hack on and extend. If you want to understand the internals and bend
*coleslaw* to do new and interesting things, I strongly encourage you
to read the [Hacker's Guide to Coleslaw][hackers]. You'll find some
current **TODO** items towards the bottom.
[hackers]: docs/hacking.md

10
TODO
View file

@ -1,10 +0,0 @@
What about themes? Templates are themes. DUH.
BUGS:
; Slugs aren't unicode safe. See [reddit discussion](http://www.reddit.com/r/lisp/comments/yvh6g/coleslaw_jekylllike_static_blogware_in_500_lines/) and [mozilla code](https://github.com/mozilla/unicode-slugify/blob/master/slugify/__init__.py).
TODO:
; doc themes and plugins, s3+hunchentoot. -> 0.8
; unit tests -> 0.9
; Incremental compilation: only "touched" posts+tags+months and by-n. -> 1.0
;; possible plugins: analytics, logging/monitoring, crossposting

45
cli-tests/basic.sh Executable file
View file

@ -0,0 +1,45 @@
#!/bin/bash -x
set -e
dir=$(mktemp -d)
cd $dir
coleslaw setup
cat .coleslawrc
post=$(coleslaw new post "my first blog")
echo "my firrrrrrst text!!!!" >> "$post"
cat "$post"
coleslaw generate
coleslaw preview &
pid=$!
trap "kill $pid; rm -rf $dir" EXIT
sleep 3
try_query=${1:-true}
if $try_query
then
# Doesn't run on Travis!
curl --fail 127.0.0.1:5000
! curl --fail 127.0.0.1:5000/nosuchurl
echo success!
fi
# (
# wget 127.0.0.1:5000/nosuchurl -O-
# echo $?
# true
# )

321
cli/cli.lisp Normal file
View file

@ -0,0 +1,321 @@
(defpackage :coleslaw-cli
(:use :cl :trivia)
(:export
#:copy-theme
#:setup
#:new
#:generate
#:preview
#:watch
#:watch-preview
#:help
#:stage
#:deploy))
(in-package :coleslaw-cli)
(defun setup-coleslawrc (user &aux (path (merge-pathnames ".coleslawrc")))
"Set up the default .coleslawrc file in the current directory."
(with-open-file (s path :direction :output :if-exists :supersede :if-does-not-exist :create)
(format t "~&Generating ~a ...~%" path)
;; odd formatting in this source code because emacs has problem detecting the parenthesis inside a string
(format s ";;; -*- mode : lisp -*-~%(~
;; Required information
:author \"~a\" ;; to be placed on post pages and in the copyright/CC-BY-SA notice
:deploy-dir \"deploy/\" ;; for Coleslaw's generated HTML to go in
:domain \"\" ;; to generate absolute links to the site content. Note: with :cname option of gh-pages, this requires a url scheme, e.g. https://fake.org
:routing ((:post \"posts/~~a\") ;; to determine the URL scheme of content on the site
(:tag-index \"tag/~~a\")
(:month-index \"date/~~a\")
(:numeric-index \"~~d\")
(:feed \"~~a.xml\")
(:tag-feed \"tag/~~a.xml\")
(:sitemap \"~~a.xml\"))
:title \"Improved Means for Achieving Deteriorated Ends\" ;; a site title
:theme \"hyde\" ;; to select one of the themes in \"coleslaw/themes/\"
;; Optional information
:excerpt-sep \"<!--more-->\" ;; to set the separator for excerpt in content
:feeds (\"lisp\")
:plugins ((analytics :tracking-code \"foo\")
(disqus :shortname \"my-site-name\")
; (incremental) ;; *Remove comment to enable incremental builds.
(mathjax)
(sitemap)
(static-pages)
;; deployment plugins
;; deployment to github pages
; (gh-pages :url \"git@github.com:myaccount/myrepo.git\"
; ; :cname t ;; if you want to use the custom domain --- see http://pages.github.com/
; )
;; versioned deployment. Remove comment to enable symlinked, timestamped deploys.
; (versioned)
;; default deploy method is rsync
(rsync \"-avz\" \"--delete\" \"--exclude\" \".git/\" \"--exclude\" \".gitignore\" \"--copy-links\")
)
:sitenav ((:url \"http://~a.github.com/\" :name \"Home\")
(:url \"http://twitter.com/~a\" :name \"Twitter\")
(:url \"http://github.com/~a\" :name \"Code\")
(:url \"http://soundcloud.com/~a\" :name \"Music\")
(:url \"http://redlinernotes.com/docs/talks/\" :name \"Talks\"))
:staging-dir \"/tmp/coleslaw/\" ;; for Coleslaw to do intermediate work, default: \"/tmp/coleslaw\"
)
;; * Prerequisites described in plugin docs."
user
user
user
user
user)))
(defun copy-theme (which &optional (target which))
"Copy the theme named WHICH into the blog directory and rename it into TARGET"
(format t "~&Copying themes/~a ...~%" which)
(if (probe-file (format nil "themes/~a" which))
(format t "~& themes/~a already exists.~%" which)
(progn
(ensure-directories-exist "themes/" :verbose t)
(uiop:run-program `("cp" "-v" "-r"
,(namestring (coleslaw::app-path "themes/~a/" which))
,(namestring (merge-pathnames (format nil "themes/~a" target))))))))
(defun setup (&optional (user (uiop:getenv "USER")))
(setup-coleslawrc user)
(copy-theme "hyde" "default"))
(defun read-rc (&aux (path (merge-pathnames ".coleslawrc")))
(with-open-file (s (if (probe-file path)
path
(merge-pathnames #p".coleslawrc" (user-homedir-pathname))))
(read s)))
(defun new (&optional (type "post") name)
(let ((sep (getf (read-rc) :separator ";;;;;")))
(multiple-value-match (get-decoded-time)
((second minute hour date month year _ _ _)
(let* ((name (or name
(format nil "~a-~2,,,'0@a-~2,,,'0@a" year month date)))
(path (merge-pathnames (make-pathname :name name :type type))))
(with-open-file (s path
:direction :output :if-exists :error :if-does-not-exist :create)
(format s "~
~a
title: ~a
tags: bar, baz
date: ~a-~2,,,'0@a-~2,,,'0@a ~2,,,'0@a:~2,,,'0@a:~2,,,'0@a
format: md
~:[~*~;URL: pages/~a.html~%~]~
~a
<!-- **** your post here (remove this line) **** -->
<!-- format: could be 'html' (for raw html) or 'md' (for markdown). -->
Here is my content.
<!--more-->
Excerpt separator can also be extracted from content.
Add `excerpt: <string>` to the above metadata.
Excerpt separator is `<!--more-->` by default.
"
sep
name
year month date hour minute second
(string= type "page") name
sep)
(format *error-output* "~&Created a ~a \"~a\".~%" type name)
(format t "~&~a~%" path)
path))))))
(defun generate ()
(stage))
(defun stage ()
(prog1 (coleslaw:main *default-pathname-defaults* :deploy nil)
(format t "~&Page generated at the staging dir ~a~%" (getf (read-rc) :staging-dir))))
(defun deploy ()
(prog1 (coleslaw:main *default-pathname-defaults* :deploy t)
(format t "~&Page deployed at the deploy dir ~a~%" (getf (read-rc) :deploy-dir))))
(defun preview (&optional (path (getf (read-rc) :staging-dir)))
;; clack depends on the global binding of *default-pathname-defaults*.
(let ((oldpath *default-pathname-defaults*))
(unwind-protect
(progn
(when path
(setf *default-pathname-defaults* (truename path)))
(format t "~%Starting a Clack server at ~a. Press C-c to stop it~%" path)
(clack:clackup
(lack:builder
:accesslog
(:static :path (lambda (p)
(if (char= #\/ (alexandria:last-elt p))
(concatenate 'string p "index.html")
p)))
#'identity)
:use-thread nil))
(setf *default-pathname-defaults* oldpath))))
;; code from fs-watcher
(defun mtime (pathname)
"Returns the mtime of a pathname"
(when (ignore-errors (probe-file pathname))
(file-write-date pathname)))
(defun dir-contents (pathnames test)
(remove-if-not test
;; uiop:slurp-input-stream
(uiop:run-program `("find" ,@(mapcar #'namestring pathnames))
:output :lines)))
(defun run-loop (pathnames mtimes callback delay)
"The main loop constantly polling the filesystem"
(loop
(sleep delay)
(map nil
#'(lambda (pathname)
(let ((mtime (mtime pathname)))
(unless (eql mtime (gethash pathname mtimes))
(funcall callback pathname)
(if mtime
(setf (gethash pathname mtimes) mtime)
(remhash pathname mtimes)))))
pathnames)))
(defun watch (&optional (source-path *default-pathname-defaults*))
(format t "~&Start watching! : ~a~%" source-path)
(let ((pathnames
(dir-contents (list source-path)
(lambda (p) (not (equal "fasl" (pathname-type p))))))
(mtimes (make-hash-table)))
(dolist (pathname pathnames)
(setf (gethash pathname mtimes) (mtime pathname)))
(ignore-errors
(run-loop pathnames
mtimes
(lambda (pathname)
(format t "~&Changes detected! : ~a~%" pathname)
(finish-output)
(handler-case
(coleslaw:main source-path)
(error (c)
(format *error-output* "something happened... ~a" c))))
1))))
(defun watch-preview (&optional (source-path *default-pathname-defaults*))
(when (member :swank *features*)
(warn "FIXME: This command does not do what you intend from a SLIME session."))
(ignore-errors
(uiop:run-program
;; The hackiness here is because clack fails? to handle? SIGINT correctly when run in a threaded mode
`("sh" "-c" ,(format nil "coleslaw watch ~a &~
coleslaw preview &~
jobs -p;~
trap \"kill $(jobs -p)\" EXIT;~
wait" source-path))
:output :interactive
:error-output :interactive)))
(defun help ()
(format *error-output* "
Coleslaw, a Flexible Lisp Blogware.
Written by: Brit Butler <redline6561@gmail.com>.
Distributed by BSD license.
Command Line Syntax:
coleslaw setup [NAME] --- Sets up a new .coleslawrc file in the current directory.
coleslaw copy-theme THEME [TARGET] --- Copies the installed THEME in coleslaw to the current directory with a different name TARGET.
coleslaw new [TYPE] [NAME] --- Creates a new content file with the correct format. TYPE defaults to 'post', NAME defaults to the current date.
coleslaw stage --- Generates the static html in the staging dir.
coleslaw generate --- Alias to `coleslaw stage`.
coleslaw deploy --- Generates the static html in the staging dir, then publish it to the deploy dir.
coleslaw preview [DIRECTORY] --- Runs a preview server at port 5000. DIRECTORY defaults to the staging directory.
coleslaw watch [DIRECTORY] --- Watches the given directory and generates the site when changes are detected. Defaults to the current directory.
coleslaw --- Alias to `coleslaw stage`.
coleslaw -h --- Show this help
Corresponding REPL commands are available in coleslaw-cli package.
```lisp
(ql:quickload :coleslaw-cli)
(coleslaw-cli:setup &optional name)
(coleslaw-cli:copy-theme theme &optional target)
(coleslaw-cli:new &optional type name)
(coleslaw-cli:stage)
(coleslaw-cli:generate)
(coleslaw-cli:deploy)
(coleslaw-cli:preview &optional directory)
(coleslaw-cli:watch &optional directory)
```
Examples:
* set up a blog
mkdir yourblog ; cd yourblog
git init
coleslaw setup
git commit -a -m 'initial repo'
* Copy the base theme to the current directory for modification
coleslaw copy-theme hyde mytheme
* Create a post
coleslaw new
* Create a page (static page)
coleslaw new page
* Generate a site
coleslaw generate
# or just:
coleslaw
* Preview a site
coleslaw preview
# or
coleslaw preview .
"
))
(defun main (&rest argv)
(declare (ignorable argv))
(match argv
((list* "setup" rest)
(apply #'setup rest))
((list* "preview" rest)
(apply #'preview rest))
((list* "watch" rest)
(apply #'watch rest))
((list* "watch-preview" rest)
(apply #'watch-preview rest))
((list* "new" rest)
(apply #'new rest))
((list* "generate" rest)
(apply #'generate rest))
((list* "stage" rest)
(apply #'stage rest))
((list* "deploy" rest)
(apply #'deploy rest))
(nil
(generate))
((list* "copy-theme" rest)
(apply #'copy-theme rest))
((list* (or "-v" "--version") _)
)
((list* (or "-h" "--help") _)
(help))))
(when (member :swank *features*)
(help))

14
coleslaw-cli.asd Normal file
View file

@ -0,0 +1,14 @@
(defsystem #:coleslaw-cli
:name "coleslaw"
:description "Flexible Lisp Blogware"
:version "0.9.7"
:license "BSD"
:author "Brit Butler <redline6561@gmail.com>"
:pathname "cli/"
:depends-on (:coleslaw
:clack
:trivia
:uiop)
:serial t
:components ((:file "cli")))

14
coleslaw-test.asd Normal file
View file

@ -0,0 +1,14 @@
(in-package #:asdf-user)
(defsystem #:coleslaw-test
:description "A test suite for coleslaw."
:license "BSD"
:author "Brit Butler <redline6561@gmail.com>"
:depends-on (:coleslaw :coleslaw-cli :prove)
:defsystem-depends-on (:prove-asdf)
:components ((:module "tests"
:components
((:test-file "tests")
(:test-file "cli"))))
:perform (test-op :after (op c)
(uiop:symbol-call :prove 'run c)))

View file

@ -1,37 +1,33 @@
(in-package #:asdf-user)
(defsystem #:coleslaw
:name "coleslaw-core"
:name "coleslaw"
:description "Flexible Lisp Blogware"
:version "0.7"
:version "0.9.7"
:license "BSD"
:author "Brit Butler <redline6561@gmail.com>"
:pathname "src/"
:depends-on (:closure-template :3bmd :3bmd-ext-code-blocks
:alexandria :local-time :trivial-shell :cl-fad)
:depends-on (:closure-template
:3bmd
:3bmd-ext-code-blocks
:alexandria
:local-time
:inferior-shell
:cl-fad
:cl-ppcre
:closer-mop
:cl-unicode
:uiop)
:serial t
:components ((:file "packages")
:components ((:file "coleslaw-conf")
(:file "packages")
(:file "util")
(:file "config")
(:file "themes")
(:file "documents")
(:file "content")
(:file "posts")
(:file "indices")
(:file "indexes")
(:file "feeds")
(:file "coleslaw"))
:in-order-to ((test-op (load-op coleslaw-tests)))
:perform (test-op :after (op c)
(funcall (intern "RUN!" :coleslaw-tests)
(intern "COLESLAW-TESTS" :coleslaw-tests))))
(defsystem #:coleslaw-tests
:depends-on (coleslaw fiveam)
:pathname "tests/"
:serial t
:components ((:file "packages")
(:file "tests")))
(defmethod operation-done-p ((op test-op)
(c (eql (find-system :coleslaw))))
(values nil))
(defpackage #:coleslaw-conf (:export #:*basedir*))
(defparameter coleslaw-conf:*basedir*
(make-pathname :name nil :type nil :defaults *load-truename*))
:in-order-to ((test-op (test-op coleslaw-test))))

View file

@ -1,106 +0,0 @@
<html lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>API for COLESLAW</title>
<style type="text/css" media="all">
body {margin: 0 2em .5em 2em;font-family: Verdana,Arial,sans-serif;}
.package {background: #efefef;
padding: 1.5em 0 1em 0;
text-align: center;
font-size: x-large;}
.definition {background: #efefef; padding: .3em 1em;}
a.symbolname, a:visited.symbolname {font-weight: bold;}
.initargs {font-size: small;}
.slots {font-size: small;}
div.label {border-bottom: 1px solid #efefef; margin-bottom: .5em}
.symboldecl, .footer {margin: 0 2em 2em 2em;}
.symbolname {font-weight: bold; color: gray;}
.symboltype {font-style: italic;margin-left: 1.5em; font-size: smaller;}
.documentation {color: gray; font-family: Fixed,monospace;margin: 0 0 1.5em 0.5em;}
.packagedocumentation {color: gray;
font-family: Fixed,monospace;
margin: 0 0 1.5em 0;
border: 1px solid #efefef;
padding-left: 1.5em;}
.symbolcomments span {font-weight: bold;}
.footer {font-size: x-small; text-align: right; margin-top: 2em; padding-top: 1em; border-top: 1px solid gray;}
.undocumented {color: red; font-weight: bold;}
a, a:visited {font-weight: bold; color: gray; text-decoration: none; font-weight: normal;}
a:hover {border-bottom: 1px solid gray; }
.label {font-weight: bold; font-style: italic;color: gray;}
.labeltitle {font-weight: bold; font-style: italic;color: gray; border: 1px solid #efefef; padding: .25em;margin-bottom: .5em}
.frame {marin-top: 1.5em}
.expander {border: 2px solid #efefef; color: gray;
font-weight: bold;
font-family: Fixed,monospace;
margin-right: .25em; padding: 0 .25em;cursor: pointer;}
</style>
<script type="text/javascript">
function expand (expander, id) {
var text = expander.innerHTML;
if (text == '-')
{
expander.innerHTML = '+';
document.getElementById(id).style.display = 'none';
}
else
{
expander.innerHTML = '-';
document.getElementById(id).style.display = '';
}
}</script></head>
<body>
<div class="package">
<div class="definition">API for package:
<a class="symbolname" name="coleslaw_package" href="#coleslaw_package">coleslaw</a></div></div>
<div class="packagedocumentation">
<pre>Homepage: <a href="http://github.com/redline6561/coleslaw">Github</a></pre></div>
<div class="frame">
<div class="labeltitle">
<span class="expander" onclick="expand(this, 'functions');">-</span>Functions</div>
<div id="functions">
<div class="symboldecl">
<div class="definition">
<a class="symbolname" name="add-injection_func" href="#add-injection_func">add-injection</a>
<span class="lambdalist">str location</span>
<span class="symboltype">standard-generic-function</span></div>
<div class="documentation">
<pre>Add STR to the list of elements injected in LOCATION.</pre></div></div>
<div class="symboldecl">
<div class="definition">
<a class="symbolname" name="deploy_func" href="#deploy_func">deploy</a>
<span class="lambdalist">staging</span>
<span class="symboltype">standard-generic-function</span></div>
<div class="documentation">
<pre>Deploy the STAGING dir, updating the .prev and .curr symlinks.</pre></div></div>
<div class="symboldecl">
<div class="definition">
<a class="symbolname" name="(setf deploy)_func" href="#(setf deploy)_func">(setf deploy)</a>
<span class="lambdalist">new-value object</span>
<span class="symboltype">standard-generic-function</span></div>
<div class="documentation">
<pre>:undocumented</pre></div></div>
<div class="symboldecl">
<div class="definition">
<a class="symbolname" name="main_func" href="#main_func">main</a>
<span class="lambdalist"></span>
<span class="symboltype">function</span></div>
<div class="documentation">
<pre>Load the user's config, then compile and deploy the blog.</pre></div></div>
<div class="symboldecl">
<div class="definition">
<a class="symbolname" name="render_func" href="#render_func">render</a>
<span class="lambdalist">content &key next prev &allow-other-keys</span>
<span class="symboltype">standard-generic-function</span></div>
<div class="documentation">
<pre>Render the given CONTENT to HTML.</pre></div></div>
<div class="symboldecl">
<div class="definition">
<a class="symbolname" name="render-content_func" href="#render-content_func">render-content</a>
<span class="lambdalist">text format</span>
<span class="symboltype">standard-generic-function</span></div>
<div class="documentation">
<pre>Compile TEXT from the given FORMAT to HTML for display.</pre></div></div></div></div>
<div class="footer">Generated by:
<a href="http://common-lisp.net/project/cl-api">CL-API</a></div></body></html>

37
docs/config.md Normal file
View file

@ -0,0 +1,37 @@
# Configuration
## Where
Coleslaw needs a `.coleslawrc` file to operate properly. That file is usually located at
$HOME/.coleslawrc but may also be placed in the blog repo itself.
## What
The only *required* information in the config is:
* `:author` => to be placed on post pages and in the copyright/CC-BY-SA notice
* `:deploy-dir` => for Coleslaw's generated HTML to go in
* `:domain` => to generate absolute links to the site content
* `:routing` => to determine the URL scheme of content on the site
* `:title` => to provide a site title
* `:theme` => to select one of the themes in "coleslaw/themes/"
It is usually recommend to start from the [example config][ex_config] and pare down from there.
[ex_config]: https://github.com/redline6561/coleslaw/blob/master/examples/example.coleslawrc
## Extras
There are also many *optional* config parameters such as:
* `:charset` => to set HTML attributes for international characters, default: "UTF-8"
* `:feeds` => to generate RSS and Atom feeds for certain tagged content
* `:excerpt-sep` => to set the separator for excerpt in content, default: `<!--more-->`
* `:lang` => to set HTML attributes indicating the site language, default: "en"
* `:license` => to override the displayed content license, the default is CC-BY-SA
* `:page-ext` => to set the suffix of generated files, default: "html". "" for no extension
* `:plugins` => to configure and enable coleslaw's [various plugins][plugin-use]
* `:separator` => to set the separator for content metadata, default: ";;;;;"
* `:sitenav` => to provide relevant links and ease navigation
* `:staging-dir` => for Coleslaw to do intermediate work, default: "/tmp/coleslaw"
* `:title-fn` => to modify document slugs after they are generated, default: `'identity`
[plugin-use]: https://github.com/redline6561/coleslaw/blob/master/docs/plugin-use.md

26
docs/content-format.md Normal file
View file

@ -0,0 +1,26 @@
# The Content Format
Coleslaw expects content to have a file extension matching the class
of the content. (I.e. `.post` for blog posts, `.page` for static pages, etc.)
There should also be a metadata header on all files
starting and ending with the config-specified `:separator`, ";;;;;" by
default. Example:
```
;;;;;
title: foo
tags: bar, baz
date: yyyy-mm-dd hh:mm:ss
format: html (for raw html) or md (for markdown)
excerpt: Can also be extracted from content (see :excerpt-sep config param)
;;;;;
your post
```
Posts require the `title:` and `format:` fields.
Pages require the `title:` and `url:` fields.
To omit a field, simply do not have the line present, empty lines and
fields (e.g. "tags:" followed by whitespace) will be ignored.

13
docs/deploy.md Normal file
View file

@ -0,0 +1,13 @@
# Deploying on a standalone server
Coleslaw can deploy to a standalone server.
If you want this server installation, initialize a bare git repo and
set up the post-receive hook on that repo.
* First initialize a [git bare repo](http://git-scm.com/book/en/Git-on-the-Server-Setting-Up-the-Server) on the server.
* Copy [example post-receive hook][post_hook] to your blog's bare repo and set the executable bit (`chmod +x`).
* Point the web server at `:deploy-dir` attribute on the config file.
Or "deploy-dir/.curr" if the `versioned` plugin is enabled.
[post_hook]: https://github.com/redline6561/coleslaw/blob/master/examples/example.post-receive

404
docs/hacking.md Normal file
View file

@ -0,0 +1,404 @@
## Coleslaw: A Hacker's Guide
Here we'll provide an overview of key concepts and technical decisions
in *coleslaw* and a few suggestions about future directions. Please
keep in mind that *coleslaw* was written on a lark when 3 friends had
the idea to each complete their half-dreamed wordpress replacement in
a week. Though it has evolved considerably since it's inception, like
any software some mess remains.
## Overall Structure
Conceptually, coleslaw processes a blog as follows:
1. Coleslaw loads the user's config, then reads the blog repo loading
any `.post` files or other content. CONTENT and INDEX objects are
created from those files.
2. The CONTENT and INDEX objects are then fed to the templating engine
to produce HTML files in a config-specified staging directory,
usually under `/tmp`.
3. A deploy method (possibly customized via plugins) is called with the
staging directory. It does whatever work is needed to make the
generated HTML files (and any static content) visible to the web.
## A Note on Performance
A recent test on my Core i5 touting Thinkpad generated my 430-post blog
in 2.7 seconds. This averages out to about 6-7 milliseconds per piece of
content. However, about 400 of those 430 items were HTML posts from a
wordpress export, not markdown posts that require parsing with 3bmd.
I expect that 3bmd would be the main bottleneck on a larger site. It
would be worthwhile to see how well [cl-markdown][clmd] performs as
a replacement if this becomes an issue for users though we would lose
source highlighting from [colorize][clrz] and should also investigate
[pygments][pyg] as a replacement. Using the new [incremental][incf] plugin
reduced runtime to 1.36 seconds, almost cutting it in half.
## Core Concepts
### Plugins
**Coleslaw** strongly encourages extending functionality via plugins.
The Plugin API is well-documented and flexible enough for many use
cases. Do check the [API docs][api_docs] when contemplating a new
feature and see if a plugin would be appropriate.
### Templates and Theming
User configs are allowed to specify a theme. A theme consists of a
directory under "themes/" containing css, images, and at least
3 templates: Base, Index, and Post.
**Coleslaw** uses [cl-closure-template][closure_template]
exclusively for templating. **cl-closure-template** is a well
documented CL implementation of Google's Closure Templates. Each
template file should contain a namespace like
`coleslaw.theme.theme-name`.
Each template creates a lisp function in the theme's package when
loaded. These functions take a property list (or plist) as an argument
and return rendered HTML. **Coleslaw** defines a helper called
`theme-fn` for easy access to the template functions. Additionally,
there are RSS, ATOM, and sitemap templates *coleslaw* uses automatically.
No need for individual themes to reimplement a standard, after all!
Unfortunately, it is not very pleasant to debug broken templates.
Efforts to remedy this are being pursued for the next release.
Two particular issues to note are transposed Closure commands,
e.g. "${foo}" instead of "{$foo}", and trying to use nonexistent
keys or slots which fails silently instead of producing an error.
### The Lifecycle of a Page
- `(progn
(load-config "/my/blog/repo/path")
(compile-theme (theme *config*)))`
Coleslaw first needs the config loaded and theme compiled,
as neither the blog location, the theme to use, and other
crucial information are not yet known.
- `(load-content)`
A page starts, obviously, with a file. When *coleslaw* loads your
content, it iterates over a list of content types (i.e. subclasses of
CONTENT). For each content type, it iterates over all files in the
repo with a matching extension, e.g. ".post" for POSTs. Objects of the
appropriate class are created from each matching file and inserted
into the an in-memory data store. Then the INDEXes are created from
the loaded content and added to the data store.
- `(compile-blog dir)`
Compilation starts by ensuring the staging directory (`/tmp/coleslaw/`
by default) exists, cd'ing there, and copying over any necessary theme
assets. Then *coleslaw* iterates over all the content types and index
classes, rendering all of their instances and writing the HTML to disk.
After this, an 'index.html' symlink is created pointing to the first
reverse-chronological index.
- `(deploy dir)`
Finally, we move the staging directory to a path under the config's
`:deploy-dir`. If the versioned plugin is enabled, it is a timestamped
path and we delete the directory pointed to by the old '.prev' symlink,
point '.curr' at '.prev', and point '.curr' at our freshly built site.
### Blogs vs Sites
**Coleslaw** is blogware. When I designed it, I only cared that it
could replace my server's wordpress install. As a result, the code
until very recently was structured in terms of POSTs and
INDEXes. Roughly speaking, a POST is a blog entry and an INDEX is a
collection of POSTs or other content. An INDEX really only serves to
group a set of content objects on a page, it isn't content itself.
Content Types were added in 0.8 as a step towards making *coleslaw*
suitable for more use cases. Any subclass of CONTENT that implements
the *document protocol* counts as a content type. However, only POSTs
are currently included in the bundled INDEXes since there isn't yet a
formal relationship to determine which content types should be
included on which indexes. It is straightforward for users to implement
their own dedicated INDEX for new Content Types.
### The Document Protocol
The *document protocol* was born during a giant refactoring in 0.9.3.
Any object that will be rendered to HTML should adhere to the protocol.
Subclasses of CONTENT (content types) that implement the protocol will
be seamlessly picked up by *coleslaw* and included on the rendered site.
All current Content Types and Indexes implement the protocol faithfully.
It consists of 2 "class" methods, 2 instance methods, and an invariant.
There are also 5 helper functions provided that should prove useful in
implementing new content types.
**Class Methods**:
Class Methods don't *really* exist in Common Lisp, as methods are
defined on generic functions and not on the class itself. But since
it's useful to think about a Class as being responsible for its
instances in the case of a blog, we implement class methods by
eql-specializing on the class, e.g.
```lisp
(defmethod foo ((doc-type (eql (find-class 'bar))))
... )
```
- `discover`: Create instances for documents of the class and put them
in the in-memory database with `add-document`.
For CONTENT, this means checking the blog repo for any files with a
matching extension and loading them from disk. If your class is a
subclass of CONTENT, it inherits a pleasant default method for this.
For INDEXes, this means iterating over any relevant CONTENT in the
database, and creating INDEXes in the database that include that
content.
- `publish`: Iterate over all instances of the class, rendering each
one to HTML and writing it out to the staging directory on disk.
**Instance Methods**:
- `page-url`: Retrieve the relative path for the object on the site.
The implementation of `page-url` is not fully specified. For most
content types, we compute and store the path on the instance at
initialization time making `page-url` just a reader method.
- `render`: A method that calls the appropriate template with `theme-fn`,
passing it any needed arguments and returning rendered HTML.
**Invariants**:
- Any Content Types (subclasses of CONTENT) are expected to be stored in
the site's git repo with the lowercased class-name as a file extension,
i.e. (".post" for POST files).
**Protocol Helpers**:
- `add-document`: Add the document to *coleslaw*'s in-memory
database. It will error if the `page-url` of the document is not
unique. Such a hash collision represents content on the site being
shadowed/overwritten. This should be used in your `discover` method.
- `delete-document`: Remove a document from *coleslaw*'s in-memory
database. This is currently only used by the incremental compilation
plugin.
- `write-document`: Write the document out to disk as HTML. It takes
an optional template name and render-args to pass to the template.
This should be used in your `publish` method.
- `find-all`: Return a list of all documents of the requested class.
This is often used in the `publish` method to iterate over documents
of a given type.
- `purge-all`: Remove all instances of the requested class from the DB.
This is primarily used at the REPL or for debugging but it is also
used in a `:before` method on `discover` to keep it idempotent.
### Current Content Types & Indexes
There are 5 INDEX subclasses at present: TAG-INDEX, MONTH-INDEX,
NUMERIC-INDEX, FEED, and TAG-FEED. Respectively, they support
grouping content by tags, publishing date, and reverse chronological
order. Feeds exist to special case RSS and ATOM generation.
Currently, there is only 1 content type: POST, for blog entries.
PAGE, a content type for static page support, is available as a plugin.
## Areas for Improvement (i.e. The Roadmap)
### TODO for 0.9.7
* Test suite improvements:
* `load-content`/`read-content`/parsing
* Content Discovery
* Theme Compilation
* Content Publishing
* Common Plugins including Injections
* Add proper errors to read-content/load-content? Not just ignoring bad data. Line info, etc.
* Improved template debugging? "${" instead of "{$", static checks for valid slots, etc.
At least a serious investigation into how such things might be provided.
* Some minor scripting conveniences with cl-launch? (Scaffold a post/page, Enable incremental, Build, etc).
### Assorted Cleanups
* Try to get tag-index urls out of the tags. Post templates use them.
* Profile/memoize find-all calls in **INDEX** `render` method.
### Real Error Handling
One reason Coleslaw's code base is so small is probably the
omission of any serious error handling. Trying to debug
coleslaw if there's a problem during a build is unpleasant
at best, especially for anyone not coming from the lisp world.
We need to start handling errors and reporting errors in ways
that are useful to the user. Example errors users have encountered:
1. Loading of Content. If `read-content` fails to parse a file, we
should tell the user what file failed and why. We also should
probably enforce more constraints about metadata. E.g. Empty
metadata is not allowed/meaningful. Trailing space after separator, etc.
2. Trying to load content from the bare repo instead of the clone.
i.e. Specifying the `:repo` in .coleslawrc as the bare repo.
The README should clarify this point and the need for posts to be
".post" files.
3. Custom themes that try to access non-existent properties of content
do not currently error. They just wind up returning whitespace.
When the theme compiles, we should alert the user to any obvious
issues with it.
4. Dear Lord it was miserable even debugging a transposed character error
in one of the templates. "${foo}" instead of "{$foo}". But fuck supporting
multiple templating backends I have enough problems. What can we do?
### Scripting Conveniences
It would be convenient to add command-line tools/scripts to run coleslaw,
set up the db for incremental builds, scaffold a new post, etc. for new users.
Fukamachi's Shelly, Xach's buildapp or Fare's cl-launch would be useful here. frog and hakyll are
reasonable points of inspiration for commands to offer.
#### Commands
This is a initial set of commands which will be used to implement the first coleslaw cli, feel free to contribute!
Imagine a executable `coleslaw`, the commands would be invoked like this: `coleslaw <commandname> <args>`
* `build` generates the site. Takes:
* `--repo-dir`: first defaults to `~/.coleslawrc`'s `repo-dir` then to `.` and otherwise fails
* `clean` removes the files from `output-dir` and `staging-dir`. Takes:
* `--repo-dir`: See above
* `rebuild` is a shortcut for `clean` and then `build`. Takes:
* `--repo-dir`: See above
* `post` creates a new empty `.post` file. Takes:
* `--repo-dir`: See above
* `--title`: title (also used for generating file name)
* `--date`: same as the header-key. If not given, current time is used.
* `--format`: same as the header-key (optional, defaults to `md`)
* …
* `serve` starts a hunchentoot serving the blog locally
* maybe `page` which is `post` for static sites.
Ideas for later:
* Deployment. It would be nice to have but user needs vary greatly
and there are multiple deployment methods to support (heroku, s3,
gh-pages, git, etc). This will require some careful thought.
### Plugin Constraints
There is no system for determining what plugins work together or
enforcing the requirements or constraints of any particular
plugin. That is to say, the plugins are not actually modular. They are
closer to controlled monkey-patching.
While adding a [real module system to common lisp][asdf3] is probably
out of scope, we might be able to add some kind of [contract library][qpq]
for implementing this functionality. At the very least, a way to check
some assertions and error out at plugin load time if they fail should be
doable. I might not be able to [make illegal states unrepresentable][misu],
but I can sure as hell make them harder to construct than they are now.
@PuercoPop has suggested looking into how [wookie does plugins][wookie].
It's much more heavyweight but might be worth looking into. If we go that
route, the plugin support code will be almost half the coleslaw core.
Weigh the tradeoffs carefully.
### New Content Type: Shouts!
I've also toyed with the idea of a content type called a SHOUT, which
would be used primarily to reference or embed other content, sort of a
mix between a retweet and a del.icio.us bookmark. We encounter plenty
of great things on the web. Most of mine winds up forgotten in browser
tabs or stored on twitter's servers. It would be cool to see SHOUTs as
a plugin, probably with a dedicated SHOUT-INDEX, and some sort of
oEmbed/embed.ly/noembed support.
### Better Content Types
Creating a new content type is both straightforward and doable as a
plugin. All that is really required is a subclass of CONTENT with
any needed slots, a template, a `render` method to call the template
with any needed options, a `page-url` method for layout, and a
`publish` method.
Unfortunately, this does not solve:
1. The issue of compiling the template at load-time and making sure it
was installed in the theme package. The plugin would need to do
this itself or the template would need to be included in 'core'.
Thankfully, this should be easy with *cl-closure-template*.
2. More seriously, there is no formal relationship between content
types and indexes. Consequentially, INDEXes include only POST
objects at the moment. Whether the INDEX should specify what
Content Types it includes or the CONTENT which indexes it appears
on is not yet clear.
### Contributing
The preferred workflow is more or less:
- fork and clone
- make a branch
- commit your changes
- push your branch to your github fork
- open a Pull Request
#### Fork and clone
You may clone the main github repository or your fork, whichever you cloned
will be known as origin in your git repository. You have to add the other git
repository to your remotes, so if you cloned from your fork execute:
```bash
git remote add upstream git@github.com:redline6561/coleslaw.git
```
If you cloned from the main github repository execute:
```bash
git remote add fork git@github.com:<YourUsername>/coleslaw.git
```
For the rest of the steps we will assume you cloned from your fork and that the main github repository has the remote name of upstream.
#### Make a branch
```bash
git checkout -b <branch_name>
```
It is important to work always on branch so one can track changes in upstream by simply executing ```git pull upstream master:master``` from the master branch. If one can't come up with a suitable branch name just name it patch-n.
2
#### Commit your changes
Make the changes you want to coleslaw, add the files with that changes (```git add <path/to/file>```) and commit them (```git commit```). Your commit message should strive to sum up what has changes and why.
#### Push your branch to your github fork
```bash
git push origin branch
```
#### Open a Pull Request
After pushing the branch to your fork, on github you should see a button to open a pull request. In the PR message give the rationale for your changes.
[closure_template]: https://github.com/archimag/cl-closure-template
[api_docs]: https://github.com/redline6561/coleslaw/blob/master/docs/plugin-api.md
[clmd]: https://github.com/gwkkwg/cl-markdown
[clrz]: https://github.com/redline6561/colorize
[pyg]: http://pygments.org/
[incf]: https://github.com/redline6561/coleslaw/blob/master/plugins/incremental.lisp
[asdf3]: https://github.com/fare/asdf3-2013
[qpq]: https://github.com/sellout/quid-pro-quo
[misu]: https://blogs.janestreet.com/effective-ml-revisited/
[wookie]: https://github.com/orthecreedence/wookie/blob/master/plugin.lisp#L181

55
docs/plugin-api.md Normal file
View file

@ -0,0 +1,55 @@
# General Use
1. A lisp file should be created in coleslaw's ```plugins``` directory or the local `plugins` directory of the blog.
2. Any necessary lisp libraries not loaded by coleslaw should be included like so:
```(eval-when (:compile-toplevel :load-toplevel) (ql:quickload '(foo bar)))```
3. A package should be created for the plugin code like so:
```(defpackage :coleslaw-$NAME (:use :cl) (:export #:enable) ...)```
where $NAME is the pathname-name of the lisp file. (eg. `:coleslaw-disqus` for `disqus.lisp`)
4. An enable function should be present even if it's a no-op. Any work to enable the plugin is done there.
# Extension Points
* **New functionality via JS**, for example the Disqus and Mathjax plugins.
In this case, the plugin's `enable` function should call
[`add-injection`](http://redlinernotes.com/docs/coleslaw.html#add-injection_func)
with an injection and a keyword. The injection is a function that takes a
*Document* and returns a string to insert in the page or nil.
The keyword specifies whether the injected text goes in the HEAD or BODY element. The
[Disqus plugin](http://github.com/redline6561/coleslaw/blob/master/plugins/disqus.lisp)
is a good example of this.
* **New markup formats**, for example the
[ReStructuredText plugin](http://github.com/redline6561/coleslaw/blob/master/plugins/rst.lisp),
can be created by definining an appropriate `render-text`
method. The method takes `text` and `format` arguments and is
[EQL-specialized](http://www.gigamonkeys.com/book/object-reorientation-generic-functions.html#defmethod)
on the format. Format should be a keyword matching the file
extension (or `pathname-type`) of the markup format.
(eg. `:rst` for ReStructuredText)
* **New hosting options**, for example the
[Amazon S3 plugin](http://github.com/redline6561/coleslaw/blob/master/plugins/s3.lisp),
can be created by definining a `deploy :after` method. The method
takes a staging directory, likely uninteresting in the `:after`
stage. But by importing `*config*` from the coleslaw package and
getting its deploy location with `(deploy-dir *config*)` a number of
interesting options become possible.
* **New content types**, for example the
[static page content type](http://github.com/redline6561/coleslaw/blob/master/plugins/static-pages.lisp),
can be created by definining a subclass of CONTENT along with a
template, and `render`, `page-url`, and `publish` methods.
The PAGE content type cheats a bit by reusing the existing POST template.
* **New service integrations**, for example crossposting to
twitter/facebook/tumblr/livejournal/etc, is also possible by
adding an :after hook to the deploy method. The hook can iterate
over the results of the `get-updated-files` to crosspost any new content.
The [Twitter plugin](https://github.com/redline6561/coleslaw/blob/master/plugins/twitter.lisp)
is a good example of this.

283
docs/plugin-use.md Normal file
View file

@ -0,0 +1,283 @@
# General Use
* To enable a plugin, add its name and settings to your
[.coleslawrc][config_file]. Plugin settings are described
below. Note that some plugins require additional setup.
* Available plugins are listed below with usage descriptions and
config examples.
## Direct deployment via rsync
**Description**: This directly sends the contents of the staging dir to the deployed directory.
The former default deployment method.
**Example**: `(rsync "--exclude" ".git/" "--exclude" ".gitignore" "--copy-links")`
## Analytics via Google
**Description**: Provides traffic analysis through
[Google Analytics](http://www.google.com/analytics/).
**Example**: `(gtag :tracking-code "google-provided-unique-id")`
**Note**: You can use `(analytics :tracking-code "google-provided-unique-id")` for the legacy integration with Google Analytics.
## Analytics via Piwik
**Description**: Provides traffic analysis through
[Matomo](https://www.matomo.org).
**Example**: `(matomo :matomo-url "matomo.example.com" :matomo-site "example-site")`
## CL-WHO
**Description**: Allows the user to write posts cl-who markup. Just create a
post with `format: cl-who` and the plugin will do the rest.
**Example**: (cl-who)
## Comments via Disqus
**Description**: Provides comment support through
[Disqus](http://www.disqus.com/).
**Example**: `(disqus :shortname "disqus-provided-unique-id")`
## Comments via isso
**Description**: Provides comment support through
[isso](https://posativ.org/isso/).
**Example**: `(isso :isso-url "your-isso-url")`
## HTML5 Gifs via Gfycat
**Description**: Provides support for embedding [gfycat](http://gfycat.com/) gifs.
Any content tagged 'gfycat' containing an IMG element of the form
`<img class="gfyitem" data-id="your-gfy-slug" />` will embed the
corresponding gfy.
**Example**: `(gfycat)`
## Deploying / Hosting via Github Pages
**Description**:
Coleslaw deploys the blog to the specified branch of the given url.
* `url` -- a string, git repository url that you already have a push access.
* `branch` -- a string, the branch to publish, either `"gh-pages"` or `"master"` can be used.
* `remote` -- a string, the remote name that we use in the deploy directory. defaulted to `"origin"`.
* `cname` -- a string denoting the custom domain name, or `t`. If `cname` is `t`, the value is inferred
from the domain name specified in the `.coleslawrc`.
The value is written into `CNAME` file in the repository root.
For details, see [github-pages](http://pages.github.com/).
**Example**:
``` lisp
(gh-pages :url "git@github.com:myaccount/myrepo.git"
:branch "gh-pages"
:remote "origin"
:cname t)
```
## Incremental Builds
**Description**: Primarily a performance enhancement. Caches the
content database between builds with
[cl-store][http://common-lisp.net/project/cl-store/] to avoid
parsing the whole git repo every time. May become default
functionality instead of a plugin at some point. Substantially
reduces runtime for medium to large sites.
**Example**: `(incremental)`
**Setup**: You must run the `examples/dump_db.sh` script to
generate a database dump for your site before enabling the
incremental plugin.
## LaTeX via Mathjax
**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)))
$$```.
**Example**: ```(mathjax)```
**Options**:
- `:force`, when non-nil, will force the inclusion of MathJax on all
posts. Default value is `nil`.
- `:location` specifies the location of the `MathJax.js` file. The
default value is `"https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.0/MathJax.js"`.
This is useful if you have a local copy of MathJax and want to use that
version.
- `:preset` allows the specification of the config parameter of
`MathJax.js`. The default value is `"TeX-AMS-MML_HTMLorMML"`.
- `:config` is used as supplementary inline configuration to the
`MathJax.Hub.Config ({ ... });`. It is unused by default.
## Markless
**Description**: [Markless](https://shirakumo.github.io/markless) is a
new document markup standard. To use it in your posts, create the
posts with `format: markless`. The output is generated using
[cl-markless-plump](https://shirakumo.github.io/cl-markless/cl-markless-plump/),
meaning any syntax extensions that work with it should also be
available in Coleslaw.
**Example**: `(mess)`
## ReStructuredText
**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.
**Example**: `(rst)`
## S3 Hosting
**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).
**Example**: `(s3 :auth-file "/home/redline/.aws_creds" :bucket
"blog.redlinernotes.com")`
## Sitemap generator
**Description**: This plugin generates a sitemap.xml under the page
root, which is useful if you want google to crawl your site.
**Example**: `(sitemap)`
## Static Pages
**Description**: This plugin allows you to add `.page` files to your
repo, that will be rendered to static pages at a designated URL.
**Example**: `(static-pages)`
## Twitter
**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.
**Example**: `(twitter :api-key "<api-key>"
:api-secret "<api-secret>"
:access-token "<access-token>"
:access-secret "<access-secret>")`
**Setup**:
- Create a new [twitter app](https://apps.twitter.com/). Take note of the api key & secret.
- In the repl do the following:
```lisp
;; Load Chirp
(ql:quickload :chirp)
;; Use the api key & secret to get a URL where a pin code will be handled to you.
(chirp:initiate-authentication
:api-key "D1pMCK17gI10bQ6orBPS0w"
:api-secret "BfkvKNRRMoBPkEtDYAAOPW4s2G9U8Z7u3KAf0dBUA")
;; => "https://api.twitter.com/oauth/authorize?oauth_token=cJIw9MJM5HEtQqZKahkj1cPn3m3kMb0BYEp6qhaRxfk"
;; Exchange the pin code for an access token and and access secret. Take note
;; of them.
CL-USER> (chirp:complete-authentication "4173325")
;; => "18403733-bXtuum6qbab1O23ltUcwIk2w9NS3RusUFiuum4D3w"
;; "zDFsFSaLerRz9PEXqhfB0h0FNfUIDgbEe59NIHpRWQbWk"
;; Finally verify the credentials
(chirp:account/verify-credentials)
#<CHIRP-OBJECTS:USER PuercoPop #18405433>
```
## Twitter Summary Cards
**Description**: Add Summary Card metadata to blog posts
to enhance twitter links to that content.
**Example**: `(twitter-summary-card :twitter-handle "@redline6561")
## Versioning Deploys
Either [automatic git interaction](#git-versioned) or [double
versioning](#double-versioning)
### Git Versioned
**Description**: Automatically stages, commits, and/or pushes the server's
sources. Assumes that a git repository exists in the server's directory. Pushing
is optional.
**Examples**:
`(git-versioned "~/src/dir/" 'stage 'commit 'push)`
`(git-versioned "~/src/dir/" 'stage 'commit)`
### Double Versioning
**Description**: Originally, this was Coleslaw's only deploy behavior.
Instead of deploying directly to `:deploy-dir`, creates `.curr` and
`.prev` symlinks in the *deploy-dir*, which point to timestamped
directories of the last two deploys of the site. Deploys prior to the
last two are automatically cleaned up.
**Example**: `(versioned)`
## Wordpress Importer
**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.
**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.
**Example**: `(import :filepath "/home/redline/redlinernotes-export.timestamp.xml"
:output "/home/redlinernotes/blog/")`
[config_file]: http://github.com/redline6561/coleslaw/blob/master/examples/example.coleslawrc
## Markdown Embeding youtube Youtube
**Description**: Embed youtube videos in markdown using the shorthand syntax
`!yt[<video-id>(|options*)*]`. Options can be *width*, *height* or any of the
[player parameters](https://developers.google.com/youtube/player_parameters).
For example `!yt[oeul8fTG9dM|width=480,allowfullscreen]`.
**Example**: `(3bmd-youtube)`
## Code Highlighting via Pygments
**Description**: Provides code highlighting with [Pygments](http://pygments.org/)
instead of [colorize](http://www.cliki.net/colorize). Pygments supports over
300 languages and text formats. Look at
[3bmd](https://github.com/3b/3bmd/blob/master/README.md) for more info.
**Example**: `(pygments)`
**Setup**: Install `Pygments` and verify that the `pygmentize` command works (`pygmentize -V` should print the version number). You also need to verify that your theme includes an appropriate css file.

219
docs/themes.md Normal file
View file

@ -0,0 +1,219 @@
# Themes
The theming support in coleslaw is very flexible and relatively easy
to use. However it does require some knowledge of HTML, CSS, and how
coleslaw processes content.
To understand how coleslaw works, a look at the [hacking][hck]
documentation will prove useful. This document focuses mainly on the
template engine and how you can influence the resulting HTML.
## High-Level Overview
Themes are written using [Closure Templates][clt]. Those templates are
then compiled into functions that Lisp calls with the blog data to get
HTML. Since the Lisp code to use theme functions is already written,
your theme must follow a few rules.
Every theme **must** be in a folder under "themes/" named after the
theme. The theme's templates must start with a namespace declaration
like so: `{namespace coleslaw.theme.$MY-THEME-NAME}`.
A theme must have three templates which take *specific arguments*
(to be described later).
1. Base
2. Post
3. Index
## Two types of pages
Coleslaw generates two types of pages: `index` pages and `post` pages.
Every page other than those in the `posts/` directory is an `index`.
**Every** page uses the `base.tmpl` and fills in the content using
either the `post` or `index` templates. No important logic should be
in *any* template, they are only used to provide a consistent layout.
* `base.tmpl` This template generates the outer shell of the HTML.
It keeps a consistent look and feel for all pages in the blog. The
actual content (i.e., not header/footer/css) comes from other templates.
* `index.tmpl` This template generates the content of the `index` pages.
That is, any page with more than one content object, e.g. the homepage.
* `post.tmpl` This templates generates content for the individual posts.
Here's a visual example to make things clearer:
```
INDEX HTML FILES INDIVIDUAL POST HTML FILES
|-------------------------| |-------------------------|
| base.tmpl | | base.tmpl |
| | | |
| |-------------------| | | |------------------| |
| | index.tmpl | | | | post.tmpl | |
| | | | | | | |
| |-------------------| | | |------------------| |
| | | |
|-------------------------| |-------------------------|
```
## Note on Style Sheets (css)
If you only want to change the way the blog is styled, it is probably
simplest to either modify the existing default theme, `hyde`, or copy
it in entirety and then tweak only the CSS of your new theme. A large
amount of visual difference can be had with a minimum of (or no)
template hacking. There is plenty of advice on CSS styling on the web.
I'm no expert but feel free to send pull requests modifying a theme's
CSS or improving this section, perhaps by recommending a CSS resource.
## Creating a Theme from Scratch (with code)
### Step 1. Create the directory.
A theme name must be a valid lisp symbol. For this example, we'll use
`trivial`, so create a `themes/trivial` directory in the *coleslaw* repo.
### Step 2. Create the templates.
As described above, we need 3 template files `base.tmpl`, `post.tmpl`
and `index.tmpl`. Initially, let's just create the simplest theme that
compiles correctly.
base.tmpl:
```
{namespace coleslaw.theme.trivial}
{template base}
{/template}
```
post.tmpl:
```
{namespace coleslaw.theme.trivial}
{template post}
{/template}
```
index.tmpl:
```
{namespace coleslaw.theme.trivial}
{template index}
{/template}
```
This will create three template functions that coleslaw can find, named
`base`, `post`, and `index`.
### Step 3. Use it in your config.
At this point, you can change the `:theme` in your `.coleslawrc` to
`trivial` and then generate your blog with `(coleslaw:main)`. However,
all the HTML files will be empty because our templates are empty!
### Intermezzo I, The Templating Language
The templating language is documented [elsewhere][clt].
However as a short primer:
* Everything is output literally, except template commands.
* Template commands are enclosed in `{` and `}`.
* Variables, which are provided by coleslaw, can be referenced
inside a template command. So to use a variable you have to say
`{$variable}` or `{$variable.key}`.
**WARNING**: At present, cl-closure-template does not have great debugging.
If you typo this, e.g. `${variable}`, you will receive an *uninformative*
and apparently unrelated error. Also, attempted access of non-existent keys
fails silently. We are exploring options for making debugging easier in a
future release.
* If statements are written as `{if ...} ... {else} ... {/if}`.
Typical examples are: `{if $injections.body} ... {/if}` or
`{if not isLast($link)} ... {/if}`.
* Loops can be written as `{foreach $var in $sequence} ... {/foreach}`.
### Intermezzo II, Variables provided by Coleslaw
The variable that should be available to all templates is:
- **config** This contains the `.coleslawrc` content.
#### Base Template Variables
- **raw** HTML generated by a sub template, `index` or `post`.
- **content** The object which was used to generate **raw**.
- **pubdate** A string containing the publication date.
- **injections** A list containing the injections. Injections are used
by plugins mostly to add Javascript to the page.
#### Index Template Variables
- **tags** A list containing all the tags, each with keys
`name` and `url`.
- **months** A list of all the content months, each with keys
`name` and `url`.
- **index** This is the meat of the content. This variable has
the following keys:
- `content`, a list of content (see below)
- `name`, a name to use in links or href tags
- `title`, a title to use in H1 or header tags
- **prev** Nil or the previous index with keys: `url` and `title`.
- **next** Nil or the next index with keys: `url` and `title`.
#### Post Template Variable
- **prev**
- **next**
- **post** All these variables are post objects. **prev** and
**next** are the adjacent posts when put in
chronological order. Each post has the following keys:
- `url`, the relative url of the post
- `tags`, a list of tags (each with keys `name` and `url`)
- `date`, the date of posting
- `text`, the HTML of the post's body
- `title`, the title of the post
- `excerpt`, the excerpt of the post, same as `text` by default
### Step 4. Include the content
*NOTE*: We can keep the template engine from escaping raw HTML by
adding a `|noAutoescape` clause to commands, like so: `{$raw |noAutoescape}`.
Let's now rewrite `base.tmpl` like this:
```
{namespace coleslaw.theme.trivial}
{template base}
<html>
<head><title>Trivial Theme For Coleslaw</title></head>
<body>
<h1>All my pages have this title</h1>
{$raw |noAutoescape}
</body>
</html>
{/template}
```
A simple `index.tmpl` looks like this:
```
{namespace coleslaw.theme.trivial}
{template index}
{foreach $obj in $index.content}
<h1>{$object.title}</h1>
{$object.excerpt |noAutoescape}
{/foreach}
{/template}
```
And a simple `post.tmpl` is similarly:
```
{namespace coleslaw.theme.trivial}
{template post}
<h1>{$post.title}</h1>
{$post.text |noAutoescape}
{/template}
```
### Conclusion
All of the files are now populated with content. There are still no links
between the pages so navigation is cumbersome but adding links is simple.
Just do: `<a href="{$config.domain}/{$object.url}">{$object.name}</a>`.
[clt]: https://developers.google.com/closure/templates/
[ovr]: https://github.com/redline6561/coleslaw/blob/master/docs/overview.md
[hck]: https://github.com/redline6561/coleslaw/blob/master/docs/hacking.md

View file

@ -1,13 +0,0 @@
(:author "Brit Butler"
:deploy "/home/git/blog/"
:domain "http://blog.redlinernotes.com"
:feeds ("lisp")
:plugins (mathjax)
:repo "/home/git/tmp/improvedmeans/"
:sitenav ((:url "http://redlinernotes.com/" :name "Home")
(:url "http://twitter.com/redline6561" :name "Twitter")
(:url "http://github.com/redline6561" :name "Code")
(:url "http://soundcloud.com/redlinernotes" :name "Music")
(:url "http://redlinernotes.com/docs/talks/" :name "Talks"))
:title "Improved Means for Achieving Deteriorated Ends"
:theme "hyde")

View file

@ -1,10 +0,0 @@
GIT_REPO=$HOME/improvedmeans.git
# TMP_GIT_CLONE _must_ match the :repo arg in coleslawrc excluding trailing slash
TMP_GIT_CLONE=$HOME/tmp/improvedmeans
LISP=sbcl
git clone $GIT_REPO $TMP_GIT_CLONE
# Only ccl and sbcl support the eval switch, other lisps require a patch here
$LISP --eval "(ql:quickload 'coleslaw)" --eval "(coleslaw:main)" --eval "(trivial-shell:exit)"
rm -Rf $TMP_GIT_CLONE
exit

17
examples/dump-db.lisp Normal file
View file

@ -0,0 +1,17 @@
(eval-when (:compile-toplevel :load-toplevel :execute)
(ql:quickload '(coleslaw cl-store)))
(in-package :coleslaw)
(defun main ()
(let ((db-file (rel-path (user-homedir-pathname) ".coleslaw.db")))
(format t "~%~%Coleslaw loaded. Attempting to load config file.~%")
(load-config "")
(format t "~%Config loaded. Attempting to load blog content.~%")
(load-content)
(format t "~%Content loaded. Attempting to dump content database.~%")
(cl-store:store *site* db-file)
(format t "~%Content database saved to ~s!~%~%" (namestring db-file))))
(main)
(exit)

18
examples/dump_db.sh Executable file
View file

@ -0,0 +1,18 @@
#!/bin/sh
LISP=sbcl
### DON'T EDIT BELOW THIS LINE UNLESS YOU KNOW WHAT YOU ARE DOING. ###
DUMP="dump-db.lisp"
if [ "$LISP"="cmucl" ] || [ "$LISP"="lispworks" ] || [ "$LISP"="gcl" ] || [ "$LISP"="abcl" ];
then $LISP -load $DUMP
else
if [ "$LISP"="clisp" ];
then $LISP -i $DUMP
else
if [ "$LISP"="allegro" ];
then $LISP -l $DUMP
else $LISP --load $DUMP #SBCL CCL ECL
fi
fi
fi

View file

@ -0,0 +1,29 @@
(:author "Brit Butler"
:deploy-dir "/home/git/blog/"
:domain "http://blog.redlinernotes.com"
:excerpt-sep "<!--more-->"
:feeds ("lisp")
:plugins ((analytics :tracking-code "foo")
(disqus :shortname "my-site-name")
; (incremental) ;; *Remove comment to enable incremental builds.
(mathjax)
(sitemap)
(static-pages)
; (versioned) ;; *Remove comment to enable symlinked, timestamped deploys.
)
:routing ((:post "posts/~a")
(:tag-index "tag/~a")
(:month-index "date/~a")
(:numeric-index "~d")
(:feed "~a.xml")
(:tag-feed "tag/~a.xml"))
:sitenav ((:url "http://redlinernotes.com/" :name "Home")
(:url "http://twitter.com/redline6561" :name "Twitter")
(:url "http://github.com/redline6561" :name "Code")
(:url "http://soundcloud.com/redlinernotes" :name "Music")
(:url "http://redlinernotes.com/docs/talks/" :name "Talks"))
:staging-dir "/tmp/coleslaw/"
:title "Improved Means for Achieving Deteriorated Ends"
:theme "hyde")
;; * Prerequisites described in plugin docs.

View file

@ -0,0 +1,41 @@
########## CONFIGURATION VALUES ##########
TMP_GIT_CLONE=$HOME/tmp/improvedmeans/
# Set LISP to your preferred implementation. The following
# implementations are currently supported:
# * sbcl
# * ccl
LISP=sbcl
########## DON'T EDIT ANYTHING BELOW THIS LINE ##########
if cd `dirname "$0"`/..; then
GIT_REPO=`pwd`
cd $OLDPWD || exit 1
else
exit 1
fi
git clone $GIT_REPO $TMP_GIT_CLONE || exit 1
while read oldrev newrev refname; do
if [ $refname = "refs/heads/master" ]; then
echo -e "\n Master updated. Running coleslaw...\n"
if [ $LISP = sbcl ]; then
sbcl --eval "(ql:quickload 'coleslaw)" \
--eval "(coleslaw:main \"$TMP_GIT_CLONE\" :oldrev \"$oldrev\")" \
--eval "(uiop:quit)"
elif [ $LISP = ccl ]; then
ccl -e "(ql:quickload 'coleslaw)" \
-e "(coleslaw:main \"$TMP_GIT_CLONE\" :oldrev \"$oldrev\")" \
-e "(uiop:quit)"
else
echo -e "$LISP is not a supported lisp dialect at this time. Exiting.\n"
exit 1
fi
fi
done
rm -rf $TMP_GIT_CLONE
exit

View file

@ -1,4 +0,0 @@
#!/bin/sh
sbcl --eval "(ql:quickload '(coleslaw sb-introspect cl-api))" \
--eval "(cl-api:api-gen :coleslaw \"docs/coleslaw.html\")" \
--eval "(progn (terpri) (sb-ext:quit))"

12
plugins/3bmd-youtube.lisp Normal file
View file

@ -0,0 +1,12 @@
(eval-when (:compile-toplevel :load-toplevel)
(ql:quickload :3bmd-youtube))
(defpackage #:coleslaw-3bmd-youtube
(:use #:cl)
(:export
#:enable))
(in-package #:coleslaw-3bmd-youtube)
(defun enable ()
(setf 3bmd-youtube:*youtube-embeds* t))

View file

@ -1,2 +1,25 @@
(in-package :coleslaw)
(defpackage :coleslaw-analytics
(:use :cl)
(:export #:enable)
(:import-from :coleslaw #:add-injection))
(in-package :coleslaw-analytics)
(defvar *analytics-js*
"<script type=\"text/javascript\">
var _gaq = _gaq || [];
_gaq.push(['_setAccount', '~a']);
_gaq.push(['_trackPageview']);
(function() {
var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
})();
</script>")
(defun enable (&key tracking-code)
(let ((snippet (format nil *analytics-js* tracking-code)))
(add-injection (constantly snippet) :head)))

23
plugins/cl-who.lisp Normal file
View file

@ -0,0 +1,23 @@
(eval-when (:compile-toplevel :load-toplevel)
(ql:quickload 'cl-who))
(defpackage :coleslaw-cl-who
(:use #:cl #:cl-who)
(:import-from #:coleslaw :render-text)
(:export #:enable))
(in-package :coleslaw-cl-who)
(defmethod render-text (text (format (eql :cl-who)))
(let* ((*package* (find-package "COLESLAW-CL-WHO"))
(sexps (with-input-from-string (v text)
(do* ((line (read v)
(read v nil 'done))
(acc (list line)
(cons line acc)))
((eql line 'done)
(nreverse (cdr acc)))))))
(eval `(with-html-output-to-string (v) ,@sexps))))
(defun enable ()
)

View file

@ -1,7 +1,8 @@
(defpackage :coleslaw-disqus
(:use :cl)
(:export #:enable)
(:import-from :coleslaw #:add-injection))
(:import-from :coleslaw #:add-injection
#:post))
(in-package :coleslaw-disqus)
@ -23,5 +24,7 @@
<a href=\"http://disqus.com\" class=\"dsq-brlink\">comments powered by <span class=\"logo-disqus\">Disqus</span></a>")
(defun enable (&key shortname)
(add-injection (list (format nil *disqus-header* shortname)
(lambda (x) (typep x post))) :head))
(flet ((inject-p (x)
(when (typep x 'post)
(format nil *disqus-header* shortname))))
(add-injection #'inject-p :body)))

25
plugins/gfycat.lisp Normal file
View file

@ -0,0 +1,25 @@
(defpackage :coleslaw-gfycat
(:use :cl)
(:export #:enable)
(:import-from :coleslaw #:add-injection
#:content
#:tag-p))
(in-package :coleslaw-gfycat)
(defvar *gfycat-header*
"<script>
(function(d, t) {
var g = d.createElement(t),
s = d.getElementsByTagName(t)[0];
g.src = 'http://assets.gfycat.com/js/gfyajax-0.517d.js';
s.parentNode.insertBefore(g, s);
}(document, 'script'));
</script>")
(defun enable ()
(flet ((inject-p (x)
(when (and (typep x 'content)
(tag-p "gfycat" x))
*gfycat-header*)))
(add-injection #'inject-p :head)))

38
plugins/gh-pages.lisp Normal file
View file

@ -0,0 +1,38 @@
(eval-when (:compile-toplevel :load-toplevel)
(ql:quickload 'puri :silent t))
(defpackage :coleslaw-gh-pages
(:use :cl)
(:import-from :coleslaw
#:*config*
#:domain
#:deploy
#:staging-dir
#:deploy-dir)
(:export #:enable))
(in-package :coleslaw-gh-pages)
(defvar *options* nil)
(defmethod deploy (staging)
(uiop:run-program (list* (namestring
(merge-pathnames "plugins/publish-gh-pages.sh"
coleslaw-conf:*basedir*))
(namestring
(merge-pathnames (staging-dir *config*)))
(namestring
(merge-pathnames (deploy-dir *config*)))
*options*)
:output t
:error-output t))
(defun enable (&key url (branch "gh-pages") (remote "origin") cname)
(check-type url string)
(check-type remote string)
(check-type branch string)
(if (eq t cname)
(progn
(setf cname (puri:uri-host (puri:parse-uri (domain *config*))))
(check-type cname string)
(setf *options* (list url branch remote cname)))
(setf *options* (list url branch remote))))

View file

@ -0,0 +1,46 @@
(defpackage :coleslaw-git-versioned
(:use :cl)
(:import-from :coleslaw
#:*config*
#:run-lines)
(:import-from :uiop #:ensure-directory-pathname))
(in-package :coleslaw-git-versioned)
(defconstant +nothing-to-commit+ 1
"Error code when git-commit has nothing staged to commit.")
;; These have their symbol-functions set in order to close over the src-dir
;; variable.
(defun git-versioned ()
"Run all git commands as specified in the .coleslawrc.")
(defun command (args)
"Automatically git commit and push the blog to remote."
(declare (ignore args)))
(defun enable (src-dir &rest commands)
"Define git-versioned functions at runtime."
(setf (symbol-function 'git-versioned)
(lambda ()
(loop for fsym in commands
do (funcall (symbol-function (intern (symbol-name fsym)
:coleslaw-git-versioned))))))
(setf (symbol-function 'command)
(lambda (args)
(run-lines src-dir
(format nil "git ~A" args)))))
(defmethod coleslaw:deploy :before (staging)
(declare (ignore staging))
(git-versioned))
(defun stage () (command "stage -A"))
(defun commit (&optional (commit-message "Automatic commit."))
(handler-case (command (format nil "commit -m '~A'" commit-message))
(uiop/run-program:subprocess-error (error)
(case (uiop/run-program:subprocess-error-code error)
(+nothing-to-commit+ (format t "Nothing to commit. Error ~d"
+nothing-to-commit+))
(otherwise (error error))))))
(defun upload ()
(command "push"))

21
plugins/gtag.lisp Normal file
View file

@ -0,0 +1,21 @@
(defpackage :coleslaw-gtag
(:use :cl)
(:export #:enable)
(:import-from :coleslaw #:add-injection))
(in-package :coleslaw-gtag)
(defvar *analytics-js*
"<!-- Global site tag (gtag.js) - Google Analytics -->
<script async src='https://www.googletagmanager.com/gtag/js?id=~a'></script>
<script>
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', '~a');
</script>")
(defun enable (&key tracking-code)
(let ((snippet (format nil *analytics-js* tracking-code tracking-code)))
(add-injection (constantly snippet) :head)))

20
plugins/heroku.lisp Normal file
View file

@ -0,0 +1,20 @@
(eval-when (:compile-toplevel :load-toplevel)
(ql:quickload 'hunchentoot))
(defpackage :coleslaw-heroku
(:use :cl)
(:import-from #:hunchentoot :create-folder-dispatcher-and-handler
:create-static-file-dispatcher-and-handler
:*dispatch-table*)
(:import-from #:coleslaw :deploy)
(:export #:enable))
(in-package :coleslaw-heroku)
(defmethod deploy :after (staging)
(push (create-folder-dispatcher-and-handler "/" "/app/.curr/")
*dispatch-table*)
(push (create-static-file-dispatcher-and-handler "/" "/app/.curr/index.html")
*dispatch-table*))
(defun enable ())

View file

@ -1,4 +0,0 @@
(ql:quickload '(hunchentoot))
(in-package :coleslaw)

View file

@ -7,7 +7,9 @@
(:import-from :coleslaw #:slugify
#:load-config
#:*config*
#:repo)
#:repo
#:repo-dir
#:separator)
(:import-from :local-time #:+short-month-names+)
(:import-from :cl-ppcre #:regex-replace-all))
@ -28,10 +30,10 @@
(format nil "~a-~2,'0d-~2,'0d ~a" year (position month +short-month-names+
:test #'string=) date time)))
(defun import-post (post output &optional (since nil since-supplied-p))
(defun import-post (post output &optional since)
(when (and (string= "publish" (node-val "wp:status" post)) ; is it public?
(string= "post" (node-val "wp:post_type" post)) ; is it a post?
(or (not since-supplied-p) (string>= (get-timestamp post) since)))
(or (null since) (string>= (get-timestamp post) since)))
(let ((slug (slugify (node-val "title" post))))
(when (string= "" slug)
(error "No valid slug-title for post ~a." (get-timestamp post)))
@ -40,22 +42,23 @@
(format nil "~a.post" slug) output))))
(defun export-post (title tags date content path output)
(with-open-file (out (merge-pathnames path (or output (repo *config*)))
(with-open-file (out (merge-pathnames path (or output (repo-dir *config*)))
:direction :output
:if-exists :supersede
:if-does-not-exist :create)
:if-does-not-exist :create
:external-format :utf-8)
;; TODO: What other data/metadata should we write out?
(format out ";;;;;~%")
(format out "~A~%" (separator *config*))
(format out "title: ~A~%" title)
(format out "tags: ~A~%" (format nil "~{~A~^, ~}" tags))
(format out "date: ~A~%" date)
(format out "format: html~%") ; post format: html, md, rst, etc
(format out ";;;;;~%")
(format out "~A~%" (separator *config*))
(format out "~A~%" (regex-replace-all (string #\Newline) content "<br>"))))
(defun import-posts (filepath output &optional since)
(when (probe-file filepath)
(ensure-directories-exist (repo *config*))
(ensure-directories-exist (or output (repo-dir *config*)))
(let* ((xml (cxml:parse-file filepath (cxml-dom:make-dom-builder)))
(posts (dom:get-elements-by-tag-name xml "item")))
(loop for post across posts do (import-post post output since))

82
plugins/incremental.lisp Normal file
View file

@ -0,0 +1,82 @@
(eval-when (:compile-toplevel :load-toplevel)
(ql:quickload 'cl-store))
(defpackage :coleslaw-incremental
(:use :cl)
(:import-from :alexandria #:when-let)
(:import-from :coleslaw #:*config*
#:content
#:index
#:discover
#:get-updated-files
#:find-content-by-path
#:add-document
#:delete-document
;; Private
#:all-subclasses
#:do-subclasses
#:read-content
#:construct
#:rel-path
#:repo
#:update-content-metadata)
(:export #:enable))
(in-package :coleslaw-incremental)
;; In contrast to the original incremental plans, full of shoving state into
;; the right place by hand and avoiding writing pages to disk that hadn't
;; changed, the new plan is to only avoid redundant parsing of content in
;; the git repo. The rest of coleslaw's operation is "fast enough".
;;
;; Prior to enabling the plugin a user must have a cl-store dump of the
;; database at ~/.coleslaw.db. There is a dump_db shell script in
;; examples to generate the database dump.
;;
;; We're gonna be a bit dirty here and monkey patch. The compilation model
;; still isn't an "exposed" part of Coleslaw. After some experimentation maybe
;; we'll settle on an interface.
(defun coleslaw::load-content ()
(let ((db-file (rel-path (user-homedir-pathname) ".coleslaw.db")))
(setf coleslaw::*site* (cl-store:restore db-file))
(loop for (status path) in (get-updated-files)
for file-path = (rel-path (repo-dir *config*) path)
do (update-content status file-path))
(update-content-metadata)
;; Discover's :before method will delete any possibly outdated indexes.
(do-subclasses (itype index)
(discover itype))
(cl-store:store coleslaw::*site* db-file)))
(defun update-content (status path)
(cond ((string= "D" status) (process-change :deleted path))
((string= "M" status) (process-change :modified path))
((string= "A" status) (process-change :added path))))
(defgeneric process-change (status path &key &allow-other-keys)
(:documentation "Updates the database as needed for the STATUS change to PATH.")
(:method :around (status path &key)
(let ((extension (pathname-type path))
(ctypes (all-subclasses (find-class 'content))))
;; If the updated file's extension doesn't match one of our content types,
;; we don't need to mess with it at all. Otherwise, since the class is
;; annoyingly tricky to determine, pass it along.
(when-let (ctype (find extension ctypes :test #'class-name-p))
(call-next-method status path :ctype ctype)))))
(defmethod process-change ((status (eql :deleted)) path &key)
(let ((old (find-content-by-path path)))
(delete-document old)))
(defmethod process-change ((status (eql :modified)) path &key ctype)
(let ((old (find-content-by-path path))
(new (construct ctype (read-content path))))
(delete-document old)
(add-document new)))
(defmethod process-change ((status (eql :added)) path &key ctype)
(let ((new (construct ctype (read-content path))))
(add-document new)))
(defun enable ())

20
plugins/isso.lisp Normal file
View file

@ -0,0 +1,20 @@
(defpackage :coleslaw-isso
(:use :cl)
(:export #:enable)
(:import-from :coleslaw #:add-injection
#:post))
(in-package :coleslaw-isso)
(defvar *isso-header*
"<div class=\"comments\">
<section id=\"isso-thread\"></section>
<script data-isso=\"~a/\"
src=\"~a/js/embed.min.js\"></script>
</div>")
(defun enable (&key isso-url)
(flet ((inject-p (x)
(when (typep x 'post)
(format nil *isso-header* isso-url isso-url))))
(add-injection #'inject-p :body)))

12
plugins/markless.lisp Normal file
View file

@ -0,0 +1,12 @@
(eval-when (:compile-toplevel :load-toplevel)
(ql:quickload 'cl-markless-plump))
(defpackage #:coleslaw-markless
(:use #:cl)
(:export #:enable))
(in-package :coleslaw-markless)
(defmethod coleslaw:render-text (text (format (eql :markless)))
(cl-markless:output text :target NIL :format 'cl-markless-plump:plump))
(defun enable ())

View file

@ -2,29 +2,29 @@
(:use :cl)
(:export #:enable)
(:import-from :coleslaw #:add-injection
#:post
#:content
#:index
#:post-tags
#:index-posts))
#:tag-p
#:index-content))
(in-package :coleslaw-mathjax)
(defvar *mathjax-header* "<script type=\"text/x-mathjax-config\">
MathJax.Hub.Config({
tex2jax: {
inlineMath: [['$$','$$']]
}
});
</script>
<script type=\"text/javascript\"
src=\"http://cdn.mathjax.org/mathjax/latest/MathJax.js?config=TeX-AMS-MML_HTMLorMML\">
</script>")
(defvar *mathjax-header* "~@[<script type=\"text/x-mathjax-config\">
MathJax.Hub.Config({~A});
</script>~]
<script type=\"text/javascript\" src=\"~A~@[?config=~A~]\"></script>")
(defun enable ()
(labels ((math-post-p (post)
(member "math" (post-tags post) :test #'string=))
(mathjax-p (content)
(etypecase content
(post (math-post-p content))
(index (some #'math-post-p (index-posts content))))))
(add-injection (list *mathjax-header* #'mathjax-p) :head)))
(defgeneric mathjax-p (document)
(:documentation "Test if DOCUMENT requires contains any math-tagged content.")
(:method ((content content))
(tag-p "math" content))
(:method ((index index))
(and (slot-boundp index 'content)
(some #'mathjax-p (index-content index)))))
(defun enable (&key force config (preset "TeX-AMS-MML_HTMLorMML")
(location "https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.0/MathJax.js"))
(flet ((inject-p (x)
(when (or force (mathjax-p x))
(format nil *mathjax-header* config location preset))))
(add-injection #'inject-p :head)))

25
plugins/matomo.lisp Normal file
View file

@ -0,0 +1,25 @@
(defpackage :coleslaw-matomo
(:use :cl)
(:export #:enable)
(:import-from :coleslaw #:add-injection))
(in-package :coleslaw-matomo)
(defvar *matomo-js*
"<script type="text/javascript">
var _paq = _paq || [];
_paq.push(['trackPageView']);
_paq.push(['enableLinkTracking']);
(function() {
var u='//~a/';
_paq.push(['setTrackerUrl', u+'matomo.php']);
_paq.push(['setSiteId', '~a']);
var d=document, g=d.createElement('script'), s=d.getElementsByTagName('script')[0];
g.type='text/javascript'; g.async=true; g.defer=true; g.src=u+'matomo.js'; s.parentNode.insertBefore(g,s);
})();
</script>")
(defun enable (&key matomo-url matomo-site)
(let ((snippet (format nil *matomo-js* matomo-url matomo-site)))
(add-injection (constantly snippet) :head)))

55
plugins/publish-gh-pages.sh Executable file
View file

@ -0,0 +1,55 @@
#!/bin/bash -x
set -e
staging=$1
deploy=$2
url=$3
branch=$4
remote=$5
cname=$6
if [[ -d $deploy && ! -d $deploy/.git ]]
then
echo "Target directory $deploy exists and is not a git repository. Aborting" >&2
exit 1
fi
if [[ ! -d $deploy ]]
then
git clone --no-checkout --origin $remote $url $deploy
fi
cd $deploy
# safe and most reliable way to check if the branch exist
if git show-ref --verify --quiet refs/heads/$branch
then
# if the branch exists locally
git checkout $branch
elif git show-ref --verify --quiet refs/remotes/$remote/$branch
then
# if the branch does not exist locally but exist in the specified remote ---
# Note, git checkout $branch will search the branch with the same name with
# ALL remotes, and set it as the tracking branch if there is a single such
# remote, but does not allow the user to necessarily specify which.
git checkout -b $branch --track $remote/$branch
else
# if there is no matching branch, make an orphan branch
git checkout --orphan $branch
fi
rsync -avz --delete --exclude .git/ --copy-links $staging $deploy
if [[ ! -z "$cname" ]]
then
echo $cname > CNAME
fi
git add -A # add all changes in the worktree
git add $(git ls-files -o ) # add all untracked files in the worktree
git commit -m "Deployed on $(date)"
git push $remote $branch

View file

@ -1,2 +1,8 @@
(in-package :coleslaw)
(defpackage #:coleslaw-pygments
(:use #:cl)
(:export #:enable))
(in-package #:coleslaw-pygments)
(defun enable ()
(setf 3bmd-code-blocks:*renderer* :pygments))

View file

@ -1,7 +0,0 @@
(eval-when (:compile-toplevel :load-toplevel)
(ql:quickload 'docutils))
(defpackage :coleslaw-rst
(:use :cl :coleslaw))
(in-package :coleslaw-rst)

21
plugins/robocopy.lisp Normal file
View file

@ -0,0 +1,21 @@
(defpackage :coleslaw-robocopy
(:use :cl)
(:import-from :coleslaw #:*config*
#:deploy
#:deploy-dir)
(:export #:enable))
(in-package :coleslaw-robocopy)
(defvar *args* nil)
(defmethod deploy (staging)
(coleslaw::run-program
"(robocopy ~A ~A ~{~A~^ ~}) ^& IF %ERRORLEVEL% LEQ 1 exit 0"
(merge-pathnames staging)
(merge-pathnames (deploy-dir *config*))
*args*))
(defun enable (&rest args)
(setf *args* args))

32
plugins/rst.lisp Normal file
View file

@ -0,0 +1,32 @@
(eval-when (:compile-toplevel :load-toplevel)
(ql:quickload 'docutils))
(defpackage :coleslaw-rst
(:use :cl)
(:import-from :coleslaw #:render-text)
(:import-from :docutils #:read-rst #:write-part #:register-settings-spec
#:visit-node #:write-document #:document)
(:import-from :docutils.writer.html #:html-writer
#:body-pre-docinfo
#:body
#:parts)
(:export #:enable))
(in-package :coleslaw-rst)
;; XXX: This is not an ideal solution as it affects other uses of docutils in
;; the same lisp image.
(defmethod visit-node :after ((writer html-writer) (document document))
"This method removes unnecessary HTML elements, such as html, head, body
and make docutils output only html fragment with document itself."
(setf (slot-value writer 'parts) '(body-pre-docinfo
body)))
(defmethod render-text (text (format (eql :rst)))
(register-settings-spec '((:generator nil)
(:datestamp nil)))
(let ((writer (make-instance 'html-writer))
(document (read-rst text)))
(write-document writer document 'string)))
(defun enable ())

19
plugins/rsync.lisp Normal file
View file

@ -0,0 +1,19 @@
(defpackage :coleslaw-rsync
(:use :cl)
(:import-from :coleslaw #:*config*
#:deploy
#:deploy-dir)
(:export #:enable))
(in-package :coleslaw-rsync)
(defvar *args* nil)
(defmethod deploy (staging)
(coleslaw::run-program "rsync ~{~A~^ ~} ~A ~A" *args*
(merge-pathnames staging)
(merge-pathnames (deploy-dir *config*))))
(defun enable (&rest args)
(setf *args* args))

View file

@ -1,16 +1,15 @@
(eval-when (:compile-toplevel)
(ql:quickload '(zs3)))
(eval-when (:compile-toplevel :load-toplevel)
(ql:quickload 'zs3))
(defpackage :coleslaw-s3
(:use :cl :coleslaw :zs3))
(:use :cl)
(:import-from :coleslaw #:deploy
#:deploy-dir
#:*config*)
(:export #:enable))
(in-package :coleslaw-s3)
(defparameter *credentials* (get-credentials :s3)
"The credentials to authenticate with Amazon Web Services.
Stored in a file with the access key on the first line
and the secret key on the second.")
(defparameter *content-type-map* '(("html" . "text/html")
("css" . "text/css")
("png" . "image/png")
@ -26,34 +25,30 @@ and the secret key on the second.")
(defun content-type (extension)
(cdr (assoc extension *content-type-map* :test #'equal)))
(defun init ()
(unless *credentials*
(set-credentials :s3 (file-credentials "~/.aws"))
(setf *credentials* (get-credentials :s3))))
(defun stale-keys ()
(loop for key being the hash-values in *cache* collecting key))
(defun stale-keys (&key cache)
(loop for key being the hash-values in cache collecting key))
(defun s3-sync (filepath dir)
(let ((etag (zs3:file-etag filepath))
(key (enough-namestring filepath dir)))
(if (gethash etag *cache*)
(remhash etag *cache*)
(zs3:put-file filepath *bucket* key :public t
:content-type (content-type (pathname-type filepath))))))
(defun s3-sync (filepath &key bucket dir public-p cache)
(flet ((compute-key (namestring)
(subseq namestring (length (namestring (truename dir))))))
(let* ((etag (file-etag filepath))
(namestring (namestring filepath))
(key (compute-key namestring)))
(if (gethash etag cache)
(remhash etag cache)
(put-file filepath bucket key :public public-p
:content-type (content-type (pathname-type filepath)))))))
(defun dir->s3 (dir)
(flet ((upload (file) (s3-sync file dir)))
(cl-fad:walk-directory dir #'upload)))
(defun dir->s3 (dir &key bucket cache public-p)
(cl-fad:walk-directory dir (lambda (file)
(s3-sync file :cache cache :dir dir
:bucket bucket :public-p public-p))))
(defmethod deploy :after (staging)
(let ((blog (deploy-dir *config*)))
(loop for key across (zs3:all-keys *bucket*)
do (setf (gethash (zs3:etag key) *cache*) key))
(dir->s3 blog)
(zs3:delete-objects (stale-keys) *bucket*)))
(defmethod coleslaw::render-site :after ()
(init)
(let* ((keys (all-keys *bucket*)))
(loop for key across keys do (setf (gethash (etag key) *cache*) key))
(dir->s3 coleslaw::*output-dir* :bucket *bucket* :cache *cache* :public-p t)
(when (stale-keys :cache *cache*)
(delete-objects (stale-keys) *bucket*))))
(defun enable (&key auth-file bucket)
"AUTH-FILE: Path to file with the access key on the first line and the secret
key on the second."
(setf zs3:*credentials* (zs3:file-credentials auth-file)
*bucket* bucket))

30
plugins/sitemap.lisp Normal file
View file

@ -0,0 +1,30 @@
(defpackage :coleslaw-sitemap
(:use :cl)
(:import-from :coleslaw #:*config*
#:index
#:page-url
#:find-all
#:publish
#:theme-fn
#:add-document
#:write-document)
(:import-from :alexandria #:hash-table-values)
(:export #:enable))
(in-package :coleslaw-sitemap)
(defclass sitemap (index)
((urls :initarg :urls :reader urls)))
(defmethod page-url ((object sitemap)) "sitemap.xml")
;; We do 'discovery' in the publish method here because we can't ensure the
;; sitemap discover method is called last. Need all other content to be
;; discovered/in the DB.
(defmethod publish ((doc-type (eql (find-class 'sitemap))))
(let* ((base-urls '("" "sitemap.xml"))
(urls (mapcar #'page-url (hash-table-values coleslaw::*site*)))
(sitemap (make-instance 'sitemap :urls (append base-urls urls))))
(write-document sitemap (theme-fn 'sitemap "sitemap"))))
(defun enable ())

40
plugins/static-pages.lisp Normal file
View file

@ -0,0 +1,40 @@
(defpackage :coleslaw-static-pages
(:use :cl)
(:export #:enable)
(:import-from :coleslaw #:*config*
#:assert-field
#:content
#:find-all
#:render
#:publish
#:theme-fn
#:render-text
#:write-document))
(in-package :coleslaw-static-pages)
(defclass page (content)
((title :initarg :title :reader coleslaw::title-of)
(format :initarg :format :reader coleslaw::page-format))
;; default format is markdown (for backward compatibility)
(:default-initargs :format :md))
(defmethod initialize-instance :after ((object page) &key)
(assert-field 'title object)
(assert-field 'coleslaw::url object)
(with-slots (coleslaw::url coleslaw::text format title) object
(setf coleslaw::url (make-pathname :defaults coleslaw::url)
format (alexandria:make-keyword (string-upcase format))
coleslaw::text (render-text coleslaw::text format))))
(defmethod render ((object page) &key next prev)
;; For the time being, we'll re-use the normal post theme.
(declare (ignore next prev))
(funcall (theme-fn 'post) (list :config *config*
:post object)))
(defmethod publish ((doc-type (eql (find-class 'page))))
(dolist (page (find-all 'page))
(write-document page)))
(defun enable ())

View file

@ -0,0 +1,25 @@
(defpackage :coleslaw-twitter-summary-card
(:use :cl :coleslaw)
(:export #:enable))
(in-package :coleslaw-twitter-summary-card)
(defun summary-card (post twitter-handle)
"TODO: Figure if and how to include twitter:url meta property."
(format nil "<meta property=\"twitter:card\" content=\"summary\" />
~@[<meta property=\"twitter:author\" content=\"~A\" />~]
<meta property=\"twitter:title\" content=\"~A\" />
<meta property=\"twitter:description\" content=\"~A\" />"
twitter-handle
(title-of post)
(let ((text (content-text post)))
(if (< 200 (length text))
(subseq text 0 199)
text))))
(defun enable (&key twitter-handle)
(add-injection
(lambda (x)
(when (typep x 'post)
(summary-card x twitter-handle)))
:head))

88
plugins/twitter.lisp Normal file
View file

@ -0,0 +1,88 @@
(:eval-when (:compile-toplevel :load-toplevel)
(ql:quickload :chirp))
(defpackage :coleslaw-twitter
(:use :cl)
(:import-from :coleslaw #:*config*
#:deploy
#:get-updated-files
#:find-content-by-path
#:title-of
#:author-of
#:page-url
#:plugin-conf-error)
(:export #:enable))
(in-package :coleslaw-twitter)
(defvar *tweet-format* '(:title "by" :author)
"Controls what the tweet annoucing the post looks like.")
(defvar *tweet-format-fn* nil "Function that expects an instance of
coleslaw:post and returns the tweet content.")
(defvar *tweet-format-dsl-mapping*
'((:title title-of)
(:author author-of)))
(define-condition malformed-tweet-format (error)
((item :initarg :item :reader item))
(:report
(lambda (condition stream)
(format stream "Malformed tweet format. Can't proccess: ~A"
(item condition)))))
(defun compile-tweet-format (tweet-format)
(flet ((accessor-for (x)
(rest (assoc x *tweet-format-dsl-mapping*))))
(lambda (post)
(apply #'format nil "~{~A~^ ~}"
(loop for item in *tweet-format*
unless (or (keywordp item) (stringp item))
(error 'malformed-tweet-format :item item)
when (keywordp item)
collect (funcall (accessor-for item) post)
when (stringp item)
collect item)))))
(defun enable (&key api-key api-secret access-token access-secret tweet-format)
(if (and api-key api-secret access-token access-secret)
(setf chirp:*oauth-api-key* api-key
chirp:*oauth-api-secret* api-secret
chirp:*oauth-access-token* access-token
chirp:*oauth-access-secret* access-secret)
(error 'plugin-conf-error :plugin "twitter"
:message "Credentials missing."))
;; fallback to chirp for credential erros
(chirp:account/verify-credentials)
(when tweet-format
(setf *tweet-format* tweet-format))
(setf *tweet-format-fn* (compile-tweet-format *tweet-format*)))
(defmethod deploy :after (staging)
(declare (ignore staging))
(loop :for (state file) :in (get-updated-files)
:when (and (string= "A" state) (string= "post" (pathname-type file)))
:do (tweet-new-post file)))
(defun tweet-new-post (file)
"Retrieve content matching FILE from in memory DB and publish it."
(let ((post (find-content-by-path file)))
(chirp:statuses/update (%format-post 0 post))))
(defun %format-post (offset post)
"Guarantee that the tweet content is 140 chars at most. The 117 comes from
the spaxe needed for a space and the url."
(let* ((content-prefix (subseq (render-tweet post) 0 (- 117 offset)))
(content (format nil "~A ~A/~A" content-prefix
(coleslaw::domain *config*)
(page-url post)))
(content-length (chirp:compute-status-length content)))
(cond
((>= 140 content-length) content)
((< 140 content-length) (%format-post (1- offset) post)))))
(defun render-tweet (post)
"Sans the url, which is a must."
(funcall *tweet-format-fn* post))

24
plugins/versioned.lisp Normal file
View file

@ -0,0 +1,24 @@
(defpackage :coleslaw-versioned
(:use :cl)
(:import-from :coleslaw #:*config*
#:deploy-dir
#:rel-path
#:run-program
#:update-symlink))
(in-package :coleslaw-versioned)
(defmethod coleslaw:deploy (staging)
(let* ((dest (deploy-dir *config*))
(new-build (rel-path dest "generated/~a" (get-universal-time)))
(prev (rel-path dest ".prev"))
(curr (rel-path dest ".curr")))
(ensure-directories-exist new-build)
(run-program "mv ~a ~a" staging new-build)
(when (and (probe-file prev) (truename prev))
(run-program "rm -r ~a" (truename prev)))
(when (probe-file curr)
(update-symlink prev (truename curr)))
(update-symlink curr new-build)))
(defun enable ())

10
roswell/coleslaw.ros Executable file
View file

@ -0,0 +1,10 @@
#!/bin/sh
#|-*- mode:lisp -*-|#
#|
exec ros -Q -L sbcl-bin -m coleslaw -- $0 "$@"
|#
(progn ;;init forms
(ros:ensure-asdf)
#+quicklisp (ql:quickload '(:coleslaw-cli) :silent t))
(in-package :coleslaw-cli)

10
src/coleslaw-conf.lisp Normal file
View file

@ -0,0 +1,10 @@
(defpackage #:coleslaw-conf
(:use #:cl)
(:export #:*basedir*))
(in-package #:coleslaw-conf)
(defparameter *basedir*
(uiop/pathname:pathname-parent-directory-pathname
#.(or *compile-file-truename* *load-truename*))
"A pathname pointing to Coleslaw's top level directory.")

View file

@ -1,64 +1,80 @@
(in-package :coleslaw)
(defgeneric render (content &key &allow-other-keys)
(:documentation "Render the given CONTENT to HTML."))
(defvar *last-revision* nil
"The git revision prior to the last push. For use with GET-UPDATED-FILES.")
(defun render-page (content &optional theme-fn &rest render-args)
"Render the given CONTENT to disk using THEME-FN if supplied.
Additional args to render CONTENT can be passed via RENDER-ARGS."
(let* ((path (etypecase content
(post (format nil "posts/~a.html" (post-slug content)))
(index (index-path content))))
(filepath (merge-pathnames path (staging *config*)))
(page (funcall (theme-fn (or theme-fn 'base))
(list :config *config*
:content content
:raw (apply 'render content render-args)
:pubdate (make-pubdate)
:injections (find-injections content)))))
(ensure-directories-exist filepath)
(with-open-file (out filepath
:direction :output
:if-does-not-exist :create)
(write-line page out))))
(defun main (repo-dir &key oldrev (deploy t))
"Load the user's config file, compile the blog in REPO-DIR into STAGING-DIR,
and optionally deploy the blog to DEPLOY-DIR.
OLDREV -- the git revision prior to the last push.
DEPLOY -- when non-nil, perform the deploy. (default: t)"
(load-config repo-dir)
(setf *last-revision* oldrev)
(load-content)
(compile-theme (theme *config*))
(let ((dir (staging-dir *config*)))
(compile-blog dir)
(when deploy
(deploy dir))))
(defun load-content ()
"Load all content stored in the blog's repo."
(do-subclasses (ctype content)
(discover ctype))
(update-content-metadata)
(do-subclasses (itype index)
(discover itype)))
(defun compile-blog (staging)
"Compile the blog to a STAGING directory as specified in .coleslawrc."
(when (probe-file staging)
(run-program "rm -R ~a" staging))
(ensure-directories-exist staging)
(with-current-directory staging
(dolist (dir (list (app-path "themes/~a/css" (theme *config*))
(merge-pathnames "static" (repo *config*))))
(when (probe-file dir)
(run-program "cp -R ~a ." dir)))
(render-posts)
(render-indices)
(render-feeds (feeds *config*))))
(let ((theme-dir (find-theme (theme *config*))))
(dolist (dir (list (merge-pathnames "css" theme-dir)
(merge-pathnames "img" theme-dir)
(merge-pathnames "js" theme-dir)
(repo-path "static")))
(when (probe-file dir)
(if (uiop:os-windows-p)
(run-program "(robocopy ~a ~a /MIR /IS) ^& IF %ERRORLEVEL% LEQ 1 exit 0" dir (path:basename dir))
(run-program "rsync --delete -raz ~a ." dir)))))
(do-subclasses (ctype content)
(publish ctype))
(do-subclasses (itype index)
(publish itype))
(let ((recent-posts (reduce #'(lambda (a b)
(if (< (index-name a) (index-name b))
a b))
(find-all 'numeric-index))))
(update-symlink "index.html" (page-url recent-posts)))))
(defgeneric deploy (staging)
(:documentation "Deploy the STAGING dir, updating the .prev and .curr symlinks.")
(:documentation "Deploy the STAGING build to the directory specified in the config.")
(:method (staging)
(with-current-directory coleslaw-conf:*basedir*
(let* ((coleslaw-conf:*basedir* (deploy *config*))
(new-build (app-path "generated/~a" (get-universal-time)))
(prev (app-path ".prev"))
(curr (app-path ".curr")))
(ensure-directories-exist new-build)
(run-program "mv ~a ~a" staging new-build)
(when (probe-file prev)
(let ((dest (truename prev)))
(if (equal prev dest)
(delete-file prev)
(run-program "rm -R ~a" dest))))
(when (probe-file curr)
(update-symlink prev (truename curr)))
(update-symlink curr new-build)))))
"By default, do nothing"
(declare)))
(defun main ()
"Load the user's config, then compile and deploy the blog."
(load-config)
(load-posts)
(compile-theme (theme *config*))
(compile-blog (staging *config*))
(deploy (staging *config*)))
(defun update-symlink (path target)
"Update the symlink at PATH to point to TARGET."
(run-program "ln -sfn ~a ~a" target path))
(defun preview (path &optional (content-type 'post))
"Render the content at PATH under user's configured repo and save it to
~/tmp.html. Load the user's config and theme if necessary."
(let ((current-working-directory (cl-fad:pathname-directory-pathname path)))
(unless *config*
(load-config (namestring current-working-directory))
(compile-theme (theme *config*)))
(let* ((file (rel-path (repo-dir *config*) path))
(content (construct content-type (read-content file))))
(write-file "tmp.html" (render-page content)))))
(defun render-page (content &optional theme-fn &rest render-args)
"Render the given CONTENT to HTML using THEME-FN if supplied.
Additional args to render CONTENT can be passed via RENDER-ARGS."
(funcall (or theme-fn (theme-fn 'base))
(list :config *config*
:content content
:raw (apply 'render content render-args)
:pubdate (format-rfc1123-timestring nil (local-time:now))
:injections (find-injections content))))

View file

@ -1,42 +1,93 @@
(in-package :coleslaw)
(defclass blog ()
((author :initarg :author :initform "" :accessor author)
(deploy :initarg :deploy :initform nil :accessor deploy)
(domain :initarg :domain :initform "" :accessor domain)
(feeds :initarg :feeds :initform nil :accessor feeds)
(license :initarg :license :initform nil :accessor license)
(plugins :initarg :plugins :initform nil :accessor plugins)
(repo :initarg :repo :initform #p"/" :accessor repo)
(sitenav :initarg :sitenav :initform nil :accessor sitenav)
(staging :initarg :staging :initform #p"/tmp/coleslaw/" :accessor staging)
(title :initarg :title :initform "" :accessor title)
(theme :initarg :theme :initform "hyde" :accessor theme)))
((author :initarg :author :reader author)
(charset :initarg :charset :reader charset)
(deploy-dir :initarg :deploy-dir :reader deploy-dir)
(domain :initarg :domain :reader domain)
(excerpt-sep :initarg :excerpt-sep :reader excerpt-sep)
(feeds :initarg :feeds :reader feeds)
(name-fn :initarg :name-fn :reader name-fn)
(lang :initarg :lang :reader lang)
(license :initarg :license :reader license)
(page-ext :initarg :page-ext :reader page-ext)
(plugins :initarg :plugins :reader plugins)
(repo :initarg :repo :accessor repo-dir)
(routing :initarg :routing :reader routing)
(separator :initarg :separator :reader separator)
(sitenav :initarg :sitenav :reader sitenav)
(staging-dir :initarg :staging-dir :reader staging-dir)
(theme :initarg :theme :reader theme)
(title :initarg :title :reader title))
(:default-initargs
:feeds nil
:license nil
:plugins '((rsync "-avz" "--delete" "--exclude" ".git/" "--exclude" ".gitignore" "--copy-links"))
:sitenav nil
:excerpt-sep "<!--more-->"
:name-fn 'identity
:charset "UTF-8"
:lang "en"
:page-ext "html"
:separator ";;;;;"
:staging-dir "/tmp/coleslaw"))
(defun dir-slot-reader (config name)
"Take CONFIG and NAME, and return a directory pathname for the matching SLOT."
(ensure-directory-pathname (slot-value config name)))
(defmethod deploy-dir ((config blog)) (dir-slot-reader config 'deploy-dir))
(defmethod repo-dir ((config blog)) (dir-slot-reader config 'repo))
(defmethod staging-dir ((config blog)) (dir-slot-reader config 'staging-dir))
(defparameter *config* nil
"A variable to store the blog configuration and plugin settings.")
(defun enable-plugin (file &rest args)
"Given a path to a plugin, FILE, compile+load it, then call its ENABLE function."
(compile-file file)
(load file)
(let* ((pkgname (format nil "coleslaw-~a" (pathname-name file)))
(plugin-pkg (find-package (string-upcase pkgname))))
(apply (find-symbol "ENABLE" plugin-pkg) args)))
(define-condition plugin-conf-error ()
((plugin :initarg :plugin :reader plugin)
(message :initarg :message :reader message))
(:report (lambda (condition stream)
(format stream "~A: ~A" (plugin condition) (message condition))))
(:documentation "Condition to signal when the plugin is misconfigured."))
(defun enable-plugin (name args)
"Given a plugin, NAME, compile+load it and call its ENABLE function with ARGS."
(flet ((plugin-path (sym)
(if (probe-file (repo-path "plugins/~(~A~).lisp" sym))
(repo-path "plugins/~(~A~)" sym)
(app-path "plugins/~(~A~)" sym)))
(plugin-package (sym)
(format nil "~:@(coleslaw-~A~)" sym)))
(let ((file (plugin-path name)))
(multiple-value-bind (output-file error)
(ignore-errors (compile-file file :verbose nil :print nil))
(when error
(warn "Error while compiling plugin ~A: ~A.~%" name error))
(load (or output-file file) :verbose t)))
(let ((package (find-package (plugin-package name))))
(apply (find-symbol "ENABLE" package) args))))
(defun load-plugins (plugins)
"Compile and load the listed PLUGINS. It is expected that matching *.lisp files
are in the plugins folder in coleslaw's source directory."
(flet ((plugin-path (name)
(app-path "plugins/~a" (string-downcase (symbol-name sym)))))
(dolist (plugin plugins)
(etypecase plugin
(list (destructuring-bind (name &rest args) plugin
(apply 'enable-plugin (plugin-path name) args)))
(symbol (enable-plugin (plugin-path plugin)))))))
(setf *injections* nil)
(dolist (plugin plugins)
(destructuring-bind (name &rest args) plugin
(enable-plugin name args))))
(defun load-config (&optional (dir (user-homedir-pathname)))
"Load the coleslaw configuration from DIR/.coleslawrc. DIR is ~ by default."
(with-open-file (in (merge-pathnames ".coleslawrc" dir))
(setf *config* (apply #'make-instance 'blog (read in))))
(defun discover-config-path (repo-path)
"Check the supplied REPO-PATH for a .coleslawrc and if one
doesn't exist, use the .coleslawrc in the home directory."
(let ((repo-config (rel-path repo-path ".coleslawrc")))
(if (file-exists-p repo-config)
repo-config
(rel-path (user-homedir-pathname) ".coleslawrc"))))
(defun load-config (&optional (repo-dir ""))
"Find and load the coleslaw configuration from .coleslawrc. REPO-DIR will be
preferred over the home directory if provided."
(with-open-file (in (discover-config-path repo-dir) :external-format :utf-8)
(let ((config-form (read in)))
(setf *config* (construct 'blog config-form)
(repo-dir *config*) repo-dir)))
(load-plugins (plugins *config*)))

119
src/content.lisp Normal file
View file

@ -0,0 +1,119 @@
(in-package :coleslaw)
;; Tagging
(defclass tag ()
((name :initarg :name :reader tag-name)
(slug :initarg :slug :reader tag-slug)
(url :initarg :url)))
(defmethod initialize-instance :after ((tag tag) &key)
(with-slots (url slug) tag
(setf url (compute-url nil slug 'tag-index))))
(defun make-tag (str)
"Takes a string and returns a TAG instance with a name and slug."
(let ((trimmed (string-trim " " str)))
(make-instance 'tag :name trimmed :slug (slugify trimmed))))
(defun tag-slug= (a b)
"Test if the slugs for tag A and B are equal."
(string= (tag-slug a) (tag-slug b)))
;; Slugs
(defun slug-char-p (char &key (allowed-chars (list #\- #\~)))
"Determine if CHAR is a valid slug (i.e. URL) character."
;; use the first char of the general unicode category as kind of
;; hyper general category
(let ((cat (char (cl-unicode:general-category char) 0))
(allowed-cats (list #\L #\N))) ; allowed Unicode categories in URLs
(cond
((member cat allowed-cats) t)
((member char allowed-chars) t)
(t nil))))
(defun unicode-space-p (char)
"Determine if CHAR is a kind of whitespace by unicode category means."
(char= (char (cl-unicode:general-category char) 0) #\Z))
(defun slugify (string)
"Return a version of STRING suitable for use as a URL."
(let ((slugified (remove-if-not #'slug-char-p
(substitute-if #\- #'unicode-space-p string))))
(if (zerop (length slugified))
(error "Post title '~a' does not contain characters suitable for a slug!" string )
slugified)))
;; Content Types
(defclass content ()
((url :initarg :url :reader page-url)
(date :initarg :date :reader content-date)
(file :initarg :file :reader content-file)
(tags :initarg :tags :reader content-tags)
(text :initarg :text :reader content-text))
(:default-initargs :tags nil :date nil))
(defmethod initialize-instance :after ((object content) &key)
(with-slots (tags) object
(when (stringp tags)
(setf tags (mapcar #'make-tag (cl-ppcre:split "," tags))))))
(defun parse-initarg (line)
"Given a metadata header, LINE, parse an initarg name/value pair from it."
(let ((name (string-upcase (subseq line 0 (position #\: line))))
(match (nth-value 1 (scan-to-strings "[a-zA-Z]+:\\s+(.*)" line))))
(when match
(list (make-keyword name) (aref match 0)))))
(defun parse-metadata (stream)
"Given a STREAM, parse metadata from it or signal an appropriate condition."
(flet ((get-next-line (input)
(string-trim '(#\Space #\Return #\Newline #\Tab) (read-line input nil))))
(unless (string= (get-next-line stream) (separator *config*))
(error "The file, ~a, lacks the expected header: ~a" (file-namestring stream) (separator *config*)))
(loop for line = (get-next-line stream)
until (string= line (separator *config*))
appending (parse-initarg line))))
(defun read-content (file)
"Returns a plist of metadata from FILE with :text holding the content."
(flet ((slurp-remainder (stream)
(let ((seq (make-string (- (file-length stream)
(file-position stream)))))
(read-sequence seq stream)
(remove #\Nul seq))))
(with-open-file (in file :external-format :utf-8)
(let ((metadata (parse-metadata in))
(content (slurp-remainder in))
(filepath (enough-namestring file (repo-dir *config*))))
(append metadata (list :text content :file filepath))))))
;; Helper Functions
(defun tag-p (tag obj)
"Test if OBJ is tagged with TAG."
(let ((tag (if (typep tag 'tag) tag (make-tag tag))))
(member tag (content-tags obj) :test #'tag-slug=)))
(defun month-p (month obj)
"Test if OBJ was written in MONTH."
(search month (content-date obj)))
(defun by-date (content)
"Sort CONTENT in reverse chronological order."
(sort content #'string> :key #'content-date))
(defun find-content-by-path (path)
"Find the CONTENT corresponding to the file at PATH."
(find path (find-all 'content) :key #'content-file :test #'string=))
(defgeneric render-text (text format)
(:documentation "Render TEXT of the given FORMAT to HTML for display.")
(:method (text (format (eql :html)))
text)
(:method (text (format (eql :md)))
(let ((3bmd-code-blocks:*code-blocks* t))
(with-output-to-string (str)
(3bmd:parse-string-and-print-to-stream text str)))))

84
src/documents.lisp Normal file
View file

@ -0,0 +1,84 @@
(in-package :coleslaw)
;;;; The Document Protocol
;; Data Storage
(defvar *site* (make-hash-table :test #'equal)
"An in-memory database to hold all site documents, keyed on relative URLs.")
;; Class Methods
(defgeneric publish (doc-type)
(:documentation "Write pages to disk for all documents of the given DOC-TYPE."))
(defgeneric discover (doc-type)
(:documentation "Load all documents of the given DOC-TYPE into memory.")
(:method (doc-type)
(let ((file-type (format nil "~(~A~)" (class-name doc-type))))
(do-files (file (repo-dir *config*) file-type)
(let ((obj (construct (class-name doc-type) (read-content file))))
(add-document obj))))))
(defmethod discover :before (doc-type)
(purge-all (class-name doc-type)))
;; Instance Methods
(defgeneric page-url (document)
(:documentation "The relative URL to the DOCUMENT."))
(defgeneric render (document &key &allow-other-keys)
(:documentation "Render the given DOCUMENT to HTML."))
;; Helper Functions
(defun compute-url (document unique-id &optional class)
"Compute the relative URL for a DOCUMENT based on its UNIQUE-ID. If CLASS
is provided, it overrides the route used."
(let* ((class-name (or class (class-name (class-of document))))
(route (get-route class-name)))
(unless route
(error "No routing method found for: ~A" class-name))
(let* ((result (format nil route unique-id))
(type (or (pathname-type result) (page-ext *config*))))
(make-pathname :name (funcall (name-fn *config*) (pathname-name result))
:type type
:defaults result))))
(defun get-route (doc-type)
"Return the route format string for DOC-TYPE."
(second (assoc (make-keyword doc-type) (routing *config*))))
(defun add-document (document)
"Add DOCUMENT to the in-memory database. Error if a matching entry is present."
(let ((url (page-url document)))
(if (gethash url *site*)
(error "There is already an existing document with the url ~a" url)
(setf (gethash url *site*) document))))
(defun delete-document (document)
"Given a DOCUMENT, delete it from the in-memory database."
(remhash (page-url document) *site*))
(defun write-document (document &optional theme-fn &rest render-args)
"Write the given DOCUMENT to disk as HTML. If THEME-FN is present,
use it as the template passing any RENDER-ARGS."
(let ((html (if (or theme-fn render-args)
(apply #'render-page document theme-fn render-args)
(render-page document nil)))
(url (namestring (page-url document))))
(write-file (rel-path (staging-dir *config*) url) html)))
(defun find-all (doc-type &optional (matches-p (lambda (x) (typep x doc-type))))
"Return a list of all instances of a given DOC-TYPE."
(loop for val being the hash-values in *site*
when (funcall matches-p val) collect val))
(defun purge-all (doc-type)
"Remove all instances of DOC-TYPE from memory."
(flet ((matches-class-name-p (x)
(class-name-p (symbol-name doc-type)
(class-of x))))
(dolist (obj (find-all doc-type #'matches-class-name-p))
(remhash (page-url obj) *site*))))

View file

@ -1,30 +1,37 @@
(in-package :coleslaw)
(defun date-to-timestamp (date)
"Convert a post DATE to a local-time timestamp."
(destructuring-bind (date time) (cl-ppcre:split " " date)
(apply 'local-time:encode-timestamp 0
(mapcar #'parse-integer
(append (reverse (cl-ppcre:split ":" time))
(reverse (cl-ppcre:split "-" date)))))))
;;; Atom and RSS Feeds
(defun make-pubdate (&optional date)
"Make a RFC1123 pubdate representing the current time or DATE, when supplied."
(let ((timestamp (if date
(date-to-timestamp date)
(local-time:now))))
(local-time:format-rfc1123-timestring nil timestamp)))
(defclass base-feed () ((format :initarg :format :reader feed-format)))
(defun render-feeds (feeds)
"Render and write the given FEEDS for the site."
(flet ((first-10 (list)
(subseq list 0 (min (length list) 10))))
(let* ((by-date (by-date (hash-table-values *posts*)))
(posts (first-10 by-date)))
(render-page (make-instance 'index :path "rss.xml" :posts posts) :rss)
(render-page (make-instance 'index :path "feed.atom" :posts posts) :atom)
(dolist (feed feeds)
(let ((index (index-by-tag feed by-date)))
(setf (index-path index) (format nil "tag/~a-rss.xml" feed)
(index-posts index) (first-10 (index-posts index)))
(render-page index :rss))))))
(defclass feed (index base-feed) ())
(defmethod discover ((doc-type (eql (find-class 'feed))))
(let ((content (by-date (find-all 'post))))
(dolist (format '(rss atom))
(let ((feed (make-instance 'feed :format format
:content (take-up-to 10 content)
:slug (format nil "~(~a~)" format))))
(add-document feed)))))
(defmethod publish ((doc-type (eql (find-class 'feed))))
(dolist (feed (find-all 'feed))
(write-document feed (theme-fn (feed-format feed) "feeds"))))
;;; Tag Feeds
(defclass tag-feed (index base-feed) ())
(defmethod discover ((doc-type (eql (find-class 'tag-feed))))
(let ((content (by-date (find-all 'post))))
(dolist (tag (feeds *config*))
(let ((tagged (remove-if-not (lambda (x) (tag-p tag x)) content)))
(dolist (format '(rss atom))
(let ((feed (make-instance 'tag-feed :format format
:content (take-up-to 10 tagged)
:slug (format nil "~a-~(~a~)" tag format))))
(add-document feed)))))))
(defmethod publish ((doc-type (eql (find-class 'tag-feed))))
(dolist (feed (find-all 'tag-feed))
(write-document feed (theme-fn (feed-format feed) "feeds"))))

109
src/indexes.lisp Normal file
View file

@ -0,0 +1,109 @@
(in-package :coleslaw)
(defvar *all-months* nil
"The list of months in which content was authored.")
(defvar *all-tags* nil
"The list of tags which content has been tagged with.")
(defvar *pages-listings-on-index* 10
"Page listings in the index per page.")
(defclass index ()
((url :initarg :url :reader page-url)
(name :initarg :name :reader index-name)
(title :initarg :title :reader title-of)
(content :initarg :content :reader index-content)))
(defmethod initialize-instance :after ((object index) &key slug)
(with-slots (url) object
(setf url (compute-url object slug))))
(defmethod render ((object index) &key prev next)
(funcall (theme-fn 'index) (list :tags (find-all 'tag-index)
:months (find-all 'month-index)
:config *config*
:index object
:prev prev
:next next)))
;;; Index by Tag
(defclass tag-index (index) ())
(defmethod discover ((doc-type (eql (find-class 'tag-index))))
(let ((content (by-date (find-all 'post))))
(dolist (tag *all-tags*)
(add-document (index-by-tag tag content)))))
(defun index-by-tag (tag content)
"Return an index of all CONTENT matching the given TAG."
(make-instance 'tag-index :slug (tag-slug tag) :name (tag-name tag)
:content (remove-if-not (lambda (x) (tag-p tag x)) content)
:title (format nil "Content tagged ~a" (tag-name tag))))
(defmethod publish ((doc-type (eql (find-class 'tag-index))))
(dolist (index (find-all 'tag-index))
(write-document index)))
;;; Index by Month
(defclass month-index (index) ())
(defmethod discover ((doc-type (eql (find-class 'month-index))))
(let ((content (by-date (find-all 'post))))
(dolist (month *all-months*)
(add-document (index-by-month month content)))))
(defun index-by-month (month content)
"Return an index of all CONTENT matching the given MONTH."
(make-instance 'month-index :slug month :name month
:content (remove-if-not (lambda (x) (month-p month x)) content)
:title (format nil "Content from ~a" month)))
(defmethod publish ((doc-type (eql (find-class 'month-index))))
(dolist (index (find-all 'month-index))
(write-document index)))
;;; Reverse Chronological Index
(defclass numeric-index (index) ())
(defmethod discover ((doc-type (eql (find-class 'numeric-index))))
(let ((content (by-date (find-all 'post))))
(dotimes (i (ceiling (length content) *pages-listings-on-index*))
(add-document (index-by-n i content)))))
(defun index-by-n (i content)
"Return the index for the Ith page of CONTENT in reverse chronological order."
(let ((content (subseq content (* *pages-listings-on-index* i))))
(make-instance 'numeric-index :slug (1+ i) :name (1+ i)
:content (take-up-to *pages-listings-on-index* content)
:title "Recent Content")))
(defmethod publish ((doc-type (eql (find-class 'numeric-index))))
(let ((indexes (sort (find-all 'numeric-index) #'< :key #'index-name)))
(loop for (next index prev) on (append '(nil) indexes)
while index do (write-document index nil :prev prev :next next))))
;;; Helper Functions
(defun update-content-metadata ()
"Set *ALL-TAGS* and *ALL-MONTHS* to the union of all tags and months
of content loaded in the DB."
(setf *all-tags* (all-tags))
(setf *all-months* (all-months)))
(defun all-months ()
"Retrieve a list of all months with published content."
(let ((months (loop :for post :in (find-all 'post)
:for content-date := (content-date post)
:when content-date
:collect (subseq content-date 0
(min 7 (length content-date))))))
(sort (remove-duplicates months :test #'string=) #'string>)))
(defun all-tags ()
"Retrieve a list of all tags used in content."
(let* ((dupes (mappend #'content-tags (find-all 'post)))
(tags (remove-duplicates dupes :test #'tag-slug=)))
(sort tags #'string< :key #'tag-name)))

View file

@ -1,70 +0,0 @@
(in-package :coleslaw)
(defclass index ()
((path :initform nil :initarg :path :accessor index-path)
(posts :initform nil :initarg :posts :accessor index-posts)
(title :initform nil :initarg :title :accessor index-title)))
(defmethod render ((content index) &key prev next)
(funcall (theme-fn 'index) (list :tags (all-tags)
:months (all-months)
:config *config*
:index content
:prev prev
:next next)))
(defun all-months ()
"Retrieve a list of all months with published posts."
(sort (remove-duplicates (mapcar (lambda (x) (get-month (post-date x)))
(hash-table-values *posts*)) :test #'string=)
#'string>))
(defun all-tags ()
"Retrieve a list of all tags used in posts."
(sort (remove-duplicates (mappend 'post-tags (hash-table-values *posts*))
:test #'string=) #'string<))
(defun get-month (timestamp)
"Extract the YYYY-MM portion of TIMESTAMP."
(subseq timestamp 0 7))
(defun by-date (posts)
"Sort POSTS in reverse chronological order."
(sort posts #'string> :key #'post-date))
(defun index-by-tag (tag posts)
"Return an index of all POSTS matching the given TAG."
(let ((content (remove-if-not (lambda (post) (member tag (post-tags post)
:test #'string=)) posts)))
(make-instance 'index :path (format nil "tag/~a.html" tag)
:posts content
:title (format nil "Posts tagged ~a" tag))))
(defun index-by-month (month posts)
"Return an index of all POSTS matching the given MONTH."
(let ((content (remove-if-not (lambda (post) (search month (post-date post)))
posts)))
(make-instance 'index :path (format nil "date/~a.html" month)
:posts content
:title (format nil "Posts from ~a" month))))
(defun index-by-n (i posts &optional (step 10))
"Return the index for the Ith page of POSTS in reverse chronological order."
(make-instance 'index :path (format nil "~d.html" (1+ i))
:posts (let ((index (* step i)))
(subseq posts index (min (length posts)
(+ index step))))
:title "Recent Posts"))
(defun render-indices ()
"Render the indices to view posts in groups of size N, by month, and by tag."
(let ((posts (by-date (hash-table-values *posts*))))
(dolist (tag (all-tags))
(render-page (index-by-tag tag posts)))
(dolist (month (all-months))
(render-page (index-by-month month posts)))
(dotimes (i (ceiling (length posts) 10))
(render-page (index-by-n i posts) nil
:prev (and (plusp i) i)
:next (and (< (* (1+ i) 10) (length posts)) (+ 2 i)))))
(update-symlink "index.html" "1.html"))

View file

@ -4,11 +4,49 @@
(:import-from :alexandria #:hash-table-values
#:make-keyword
#:mappend)
(:import-from :cl-fad #:file-exists-p)
(:import-from :cl-ppcre #:scan-to-strings #:split)
(:import-from :closure-template #:compile-template)
(:import-from :local-time #:format-rfc1123-timestring)
(:import-from :uiop #:getcwd
#:ensure-directory-pathname)
(:export #:main
#:blog
#:preview
#:*config*
;; Config Accessors
#:author
#:deploy-dir
#:domain
#:page-ext
#:repo-dir
#:staging-dir
#:title
;; Core Classes
#:content
#:post
#:index
;; Content Helpers
#:title-of
#:author-of
#:find-content-by-path
;; Theming + Plugin API
#:theme-fn
#:plugin-conf-error
#:render-text
#:add-injection
#:render-content
#:deploy))
#:get-updated-files
#:deploy
;; The Document Protocol
#:discover
#:publish
#:page-url
#:render
#:find-all
#:purge-all
#:add-document
#:delete-document
#:write-document
#:content-text
;; Error reporting
#:assert-field))

View file

@ -1,82 +1,31 @@
(in-package :coleslaw)
(defparameter *posts* (make-hash-table :test #'equal)
"A hash table to store all the posts and their metadata.")
(defclass post (content)
((title :initarg :title :reader title-of)
(author :initarg :author :reader author-of)
(excerpt :initarg :excerpt :reader excerpt-of)
(format :initarg :format :reader post-format))
(:default-initargs :author nil :excerpt nil))
(defclass post ()
((slug :initform nil :initarg :slug :accessor post-slug)
(title :initform nil :initarg :title :accessor post-title)
(tags :initform nil :initarg :tags :accessor post-tags)
(date :initform nil :initarg :date :accessor post-date)
(format :initform nil :initarg :format :accessor post-format)
(content :initform nil :initarg :content :accessor post-content)))
(defmethod initialize-instance :after ((object post) &key)
(with-slots (url title author excerpt format text) object
(let (post-content)
(setf url (compute-url object (slugify title))
format (make-keyword (string-upcase format))
post-content (render-text text format)
excerpt (or excerpt
(first (split (excerpt-sep *config*)
post-content
:limit 2)))
text post-content
author (or author (author *config*))))))
(defmethod render ((content post) &key prev next)
(defmethod render ((object post) &key prev next)
(funcall (theme-fn 'post) (list :config *config*
:post content
:post object
:prev prev
:next next)))
(defun load-posts ()
"Read the stored .post files from the repo."
(clrhash *posts*)
(do-files (file (repo *config*) "post")
(with-open-file (in file)
(let ((post (read-post in)))
(if (gethash (post-slug post) *posts*)
(error "There is already an existing post with the slug ~a."
(post-slug post))
(setf (gethash (post-slug post) *posts*) post))))))
(defun render-posts ()
"Iterate through the files in the repo to render+write the posts out to disk."
(loop for (prev post next) on (append '(nil) (sort (hash-table-values *posts*)
#'string< :key #'post-date))
while post do (render-page post nil :prev prev :next next)))
(defgeneric render-content (text format)
(:documentation "Compile TEXT from the given FORMAT to HTML for display.")
(:method (text (format (eql :html)))
text))
(defmethod render-content (text (format (eql :md)))
(let ((3bmd-code-blocks:*code-blocks* t))
(with-output-to-string (str)
(3bmd:parse-string-and-print-to-stream text str))))
(defun read-post (in)
"Make a POST instance based on the data from the stream IN."
(flet ((check-header ()
(unless (string= (read-line in) ";;;;;")
(error "The provided file lacks the expected header.")))
(parse-field (str)
(nth-value 1 (cl-ppcre:scan-to-strings "[a-zA-Z]+: (.*)" str)))
(read-tags (str)
(mapcar #'string-downcase (cl-ppcre:split ", " str)))
(slurp-remainder ()
(let ((seq (make-string (- (file-length in) (file-position in)))))
(read-sequence seq in)
(remove #\Nul seq))))
(check-header)
(let ((args (loop for field in '("title" "tags" "date" "format")
for line = (read-line in nil)
appending (list (make-keyword (string-upcase field))
(aref (parse-field line) 0)))))
(check-header)
(setf (getf args :tags) (read-tags (getf args :tags))
(getf args :format) (make-keyword (string-upcase (getf args :format))))
(apply 'make-instance 'post
(append args (list :content (render-content (slurp-remainder)
(getf args :format))
:slug (slugify (getf args :title))))))))
(defun slug-char-p (char)
"Determine if CHAR is a valid slug (i.e. URL) character."
(or (char<= #\0 char #\9)
(char<= #\a char #\z)
(char<= #\A char #\Z)
(member char '(#\_ #\- #\.))))
(defun slugify (string)
"Return a version of STRING suitable for use as a URL."
(remove-if-not #'slug-char-p (substitute #\- #\Space string)))
(defmethod publish ((doc-type (eql (find-class 'post))))
(loop for (next post prev) on (append '(nil) (by-date (find-all 'post)))
while post do (write-document post nil :prev prev :next next)))

View file

@ -1,45 +1,45 @@
(in-package :coleslaw)
(defparameter *injections* ()
(defparameter *injections* '()
"A list that stores pairs of (string . predicate) to inject in the page.")
(defun add-injection (injection location)
"Adds an INJECTION to a given LOCATION for rendering. The INJECTION should be
a string which will always be added or a (string . lambda). In the latter case,
the lambda takes a single argument, a content object, i.e. a POST or INDEX, and
any return value other than nil indicates the injection should be added."
(let ((result (etypecase injection
(string (list injection #'identity))
(list injection))))
(push result (getf *injections* location))))
"Adds an INJECTION to a given LOCATION for rendering. The INJECTION should be a
function that takes a DOCUMENT and returns NIL or a STRING for template insertion."
(push injection (getf *injections* location)))
(defun find-injections (content)
"Iterate over *INJECTIONS* collecting any that should be added to CONTENT."
(flet ((injections-for (location)
(loop for (injection . predicate) in (getf *injections* location)
when (funcall predicate content)
collect injection)))
(loop for injection in (getf *injections* location)
collecting (funcall injection content))))
(list :head (injections-for :head)
:body (injections-for :body))))
(defun theme-package (&key (name (theme *config*)))
"Find the package matching the theme NAME."
(find-package (string-upcase (concatenate 'string "coleslaw.theme." name))))
(define-condition theme-does-not-exist (error)
((theme :initarg :theme :reader theme))
(:report (lambda (c stream)
(format stream "Cannot find the theme: '~A'" (theme c)))))
(defun theme-fn (name)
"Find the symbol NAME inside the current theme's package."
(find-symbol (princ-to-string name) (theme-package)))
(defun theme-package (name)
"Find the package matching the theme NAME or signal THEME-DOES-NOT-EXIST."
(or (find-package (format nil "~:@(coleslaw.theme.~A~)" name))
(error 'theme-does-not-exist :theme name)))
(defun theme-fn (name &optional (package (theme *config*)))
"Find the symbol NAME inside PACKAGE which defaults to the theme package."
(find-symbol (princ-to-string name) (theme-package package)))
(defun find-theme (theme)
"Find the theme prefering themes in the local repo."
(let ((local-theme (repo-path "themes/~a/" theme)))
(if (probe-file local-theme)
local-theme
(app-path "themes/~a/" theme))))
(defun compile-theme (theme)
"Locate and compile the templates for the given THEME."
(do-files (file (app-path "themes/~a/" theme) "tmpl")
(do-files (file (find-theme theme) "tmpl")
(compile-template :common-lisp-backend file))
(do-files (file (app-path "themes/") "tmpl")
(compile-template :common-lisp-backend file)))
;; DOCUMENTATION
;; A theme directory should be named after the theme and contain *.tmpl files
;; that define the following functions in a coleslaw.theme.$NAME namespace.
;; Required templates:
;; {template base}
;; {template post}
;; {template index}

View file

@ -1,54 +1,135 @@
(in-package :coleslaw)
(define-condition coleslaw-condition ()
())
(define-condition field-missing (error coleslaw-condition)
((field-name :initarg :field-name :reader missing-field-field-name)
(file :initarg :file :reader missing-field-file
:documentation "The path of the file where the field is missing."))
(:report
(lambda (c s)
(format s "~A: The required field ~A is missing."
(missing-field-file c)
(missing-field-field-name c)))))
(defmacro assert-field (field-name content)
`(when (not (slot-boundp ,content ,field-name))
(error 'field-missing
:field-name ,field-name
:file (content-file ,content))))
(defun construct (class-name args)
"Create an instance of CLASS-NAME with the given ARGS."
(apply 'make-instance class-name args))
;; Thanks to bknr-web for this bit of code.
(defun all-subclasses (class)
"Return a list of all the subclasses of CLASS."
(let ((subclasses (closer-mop:class-direct-subclasses class)))
(append subclasses (loop for subclass in subclasses
nconc (all-subclasses subclass)))))
(defmacro do-subclasses ((var class) &body body)
"Iterate over the subclasses of CLASS performing BODY with VAR
lexically bound to the current subclass."
(alexandria:with-gensyms (klasses)
`(let ((,klasses (all-subclasses (find-class ',class))))
(loop for ,var in ,klasses do ,@body))))
(defmacro do-files ((var path &optional extension) &body body)
"For each file under PATH, run BODY. If EXTENSION is provided, only run
BODY on files that match the given extension."
(alexandria:with-gensyms (extension-p)
`(flet ((,extension-p (file)
(string= (pathname-type file) ,extension)))
(cl-fad:walk-directory ,path (lambda (,var) ,@body)
:follow-symlinks nil
:test (if ,extension
#',extension-p
(constantly t))))))
(define-condition directory-does-not-exist (error)
((directory :initarg :dir :reader dir))
(:report (lambda (c stream)
(format stream "The directory '~A' does not exist" (dir c)))))
(defun (setf getcwd) (path)
"Change the operating system's current directory to PATH."
(setf path (ensure-directory-pathname path))
(unless (and (uiop:directory-exists-p path)
(uiop:chdir path))
(error 'directory-does-not-exist :dir path))
path)
(defmacro with-current-directory (path &body body)
"Change the current directory to PATH and execute BODY in
an UNWIND-PROTECT, then change back to the current directory."
(alexandria:with-gensyms (old)
`(let ((,old (getcwd)))
(unwind-protect (progn
(setf (getcwd) ,path)
,@body)
(setf (getcwd) ,old)))))
(defun exit ()
;; KLUDGE: Just call UIOP for now. Don't want users updating scripts.
"Exit the lisp system returning a 0 status code."
(uiop:quit))
(defun fmt (fmt-str args)
"A convenient FORMAT interface for string building."
(apply 'format nil fmt-str args))
(defun rel-path (base path &rest args)
"Take a relative PATH and return the corresponding pathname beneath BASE.
If ARGS is provided, use (fmt path args) as the value of PATH."
(merge-pathnames (fmt path args) base))
(defun app-path (path &rest args)
"Take a relative PATH and return the corresponding pathname beneath coleslaw.
If ARGS is provided, use (apply 'format nil PATH ARGS) as the value of PATH."
(merge-pathnames (apply 'format nil path args) coleslaw-conf:*basedir*))
"Return a relative path beneath coleslaw."
(apply 'rel-path coleslaw-conf:*basedir* path args))
(defun repo-path (path &rest args)
"Return a relative path beneath the repo being processed."
(apply 'rel-path (repo-dir *config*) path args))
(defun run-program (program &rest args)
"Take a PROGRAM and execute the corresponding shell command. If ARGS is provided,
use (apply 'format nil PROGRAM ARGS) as the value of PROGRAM."
(trivial-shell:shell-command (apply 'format nil program args)))
use (fmt program args) as the value of PROGRAM."
(inferior-shell:run (fmt program args) :show t))
(defun update-symlink (path target)
"Update the symlink at PATH to point to TARGET."
(run-program "ln -sfn ~a ~a" target path))
(defun run-lines (dir &rest programs)
"Runs some programs, in a directory."
(mapc (lambda (line)
(run-program "cd ~A && ~A" dir line))
programs))
(defmacro do-files ((var path &optional extension) &body body)
"For each file on PATH, run BODY. If EXTENSION is provided, only run BODY
on files that match the given extension."
(alexandria:with-gensyms (ext)
`(dolist (,var (cl-fad:list-directory ,path))
,@(if extension
`((let ((,ext (pathname-type ,var)))
(when (and ,ext (string= ,ext ,extension))
,@body)))
`,body))))
(defun take-up-to (n seq)
"Take elements from SEQ until all elements or N have been taken."
(subseq seq 0 (min (length seq) n)))
(defun current-directory ()
"Return the operating system's current directory."
#+sbcl (sb-posix:getcwd)
#+ccl (current-directory)
#+ecl (si:getcwd)
#+cmucl (unix:unix-current-directory)
#+clisp (ext:cd)
#-(or sbcl ccl ecl cmucl clisp) (error "Not implemented yet."))
(defun write-file (path text)
"Write the given TEXT to PATH. PATH is overwritten if it exists and created
along with any missing parent directories otherwise."
(ensure-directories-exist path)
(with-open-file (out path
:direction :output
:if-exists :supersede
:if-does-not-exist :create
:external-format :utf-8)
(write text :stream out :escape nil)))
(defun (setf current-directory) (path)
"Change the operating system's current directory to PATH."
#+sbcl (sb-posix:chdir path)
#+ccl (setf (current-directory) path)
#+ecl (si:chdir path)
#+cmucl (unix:unix-chdir (namestring path))
#+clisp (ext:cd path)
#-(or sbcl ccl ecl cmucl clisp) (error "Not implemented yet."))
(defun get-updated-files (&optional (revision *last-revision*))
"Return a plist of (file-status file-name) for files that were changed
in the git repo since REVISION."
(flet ((split-on-whitespace (str)
(cl-ppcre:split "\\s+" str)))
(let ((cmd (format nil "git diff --name-status ~A HEAD" revision)))
(mapcar #'split-on-whitespace (inferior-shell:run/lines cmd)))))
(defmacro with-current-directory (path &body body)
"Change the current OS directory to PATH and execute BODY in
an UNWIND-PROTECT, then change back to the current directory."
(alexandria:with-gensyms (old)
`(let ((,old (current-directory)))
(unwind-protect (progn
(setf (current-directory) ,path)
,@body)
(setf (current-directory) ,old)))))
(defun class-name-p (name class)
"True if the specified string is the name of the class provided"
;; This feels way too clever. I wish I could think of a better option.
(string-equal name (symbol-name (class-name class))))

23
tests/cli.lisp Normal file
View file

@ -0,0 +1,23 @@
(in-package :coleslaw-tests)
(plan 2)
(let ((*default-pathname-defaults*
(pathname
(format nil "~a/"
(uiop:run-program `("mktemp" "-d")
:output `(:string :stripped t))))))
(coleslaw-cli:setup)
(let ((file (coleslaw-cli:new)))
(ok (probe-file file)))
(coleslaw-cli:deploy)
(print (format nil "~adeploy/index.html" *default-pathname-defaults*))
(ok (probe-file (format nil "~adeploy/index.html" *default-pathname-defaults*))))
(finalize)

2
tests/files/.coleslawrc Normal file
View file

@ -0,0 +1,2 @@
;; -*- mode: lisp -*-
()

7
tests/files/127.txt Normal file
View file

@ -0,0 +1,7 @@
;;;;;
title: We should handle CR-LF
tags: fixtures
date: 2014-12-16
format: md
excerpt: An excerpt
;;;;;

View file

@ -0,0 +1,15 @@
(in-package #:cl-user)
;; Code for generating some files in tests/files/
(defun write-with-cr-lf (line stream)
(format stream line)
(format stream "~A~A" #\Return #\Linefeed))
(with-open-file (out (asdf:system-relative-pathname :coleslaw-test "tests/files/127.txt") :direction :output :if-exists :overwrite)
(write-with-cr-lf ";;;;;" out)
(write-with-cr-lf "title: We should handle CR-LF" out)
(write-with-cr-lf "tags: fixtures" out)
(write-with-cr-lf "date: 2014-12-16" out)
(write-with-cr-lf "format: md" out)
(write-with-cr-lf ";;;;;" out))

View file

@ -1,3 +0,0 @@
(defpackage :coleslaw-tests
(:use :cl :fiveam)
(:export #:run!))

View file

@ -0,0 +1,61 @@
(defpackage :summary-card-tests
(:use :cl :coleslaw :stefil))
(in-package :summary-card-tests)
(defsuite summary-cards)
(in-suite summary-cards)
;; TODO: Create a fixture to either load a mocked config or load set of plugins.
;; Then wrap these tests to use that fixture. Then add these to defsystem, setup
;; general test run with other packages.
(coleslaw::enable-plugin :twitter-summary-card)
(defvar *short-post*
(make-instance 'post :title "hai" :text "very greetings" :format "html"))
(defvar *long-post*
(make-instance 'post :title "What a Wonderful World"
:text "I see trees of green, red roses too. I see them
bloom, for me and you. And I think to myself, what a wonderful world.
I see skies of blue,
And clouds of white.
The bright blessed day,
The dark sacred night.
And I think to myself,
What a wonderful world.
The colors of the rainbow,
So pretty in the sky.
Are also on the faces,
Of people going by,
I see friends shaking hands.
Saying, \"How do you do?\"
They're really saying,
\"I love you\".
I hear babies cry,
I watch them grow,
They'll learn much more,
Than I'll ever know.
And I think to myself,
What a wonderful world.
Yes, I think to myself,
What a wonderful world. " :format "html"))
(deftest summary-card-sans-twitter-handle ()
(let ((summary-card (summary-card *short-post* nil)))
(is (null (cl-ppcre:scan "twitter:author" summary-card)))))
(deftest summary-card-with-twitter-handle ()
(let ((summary-card (summary-card *short-post* "@PuercoPop")))
(is (cl-ppcre:scan "twitter:author" summary-card))))
(deftest summary-card-trims-long-post ()
(let ((summary-card (summary-card *long-post* nil)))
(multiple-value-bind ())
;; (scan "twitter:description\" content=\"(.*)\"" summary-card)
summary-card))

View file

@ -1 +1,24 @@
(defpackage :coleslaw-tests
(:use :cl :prove))
(in-package :coleslaw-tests)
(plan 4)
(diag "COLESLAW-CONF:*BASEDIR* points to Coleslaw's top level directory")
(is (car (last (pathname-directory coleslaw-conf:*basedir*)))
"coleslaw" :test #'string=)
(ok (probe-file (merge-pathnames #P"plugins" coleslaw-conf:*basedir*))
"COLESLAW-CONF:*BASEDIR* has a plugins sub-directory")
(ok (probe-file (merge-pathnames #P"themes" coleslaw-conf:*basedir*))
"COLESLAW-CONF:*BASEDIR* has a themes sub-directory")
(coleslaw::load-config (asdf:system-relative-pathname :coleslaw-test "tests/files/"))
(with-open-file (in (asdf:system-relative-pathname :coleslaw-test "tests/files/127.txt"))
(diag "PARSE-METADATA should handle files with CR-LF line endings.")
(is (coleslaw::parse-metadata in) '(:TITLE "We should handle CR-LF" :TAGS "fixtures" :DATE "2014-12-16" :FORMAT
"md" :EXCERPT "An excerpt") :test 'equalp))
(finalize)

View file

@ -1,4 +1,4 @@
{namespace coleslaw.theme.hyde}
{namespace coleslaw.theme.feeds}
{template atom}
<?xml version="1.0"?>{\n}
@ -12,9 +12,9 @@
<name>{$config.author}</name>
</author>
{foreach $post in $content.posts}
{foreach $post in $content.content}
<entry>
<link type="text/html" rel="alternate" href="{$config.domain}/posts/{$post.slug}.html"/>
<link type="text/html" rel="alternate" href="{$config.domain}/{$post.url}"/>
<title>{$post.title}</title>
<published>{$post.date}</published>
<updated>{$post.date}</updated>
@ -22,7 +22,7 @@
<name>{$config.author}</name>
<uri>{$config.domain}</uri>
</author>
<content type="html">{$post.content |noAutoescape}</content>
<content type="html">{$post.text |escapeHtml}</content>
</entry>
{/foreach}

View file

@ -2,12 +2,13 @@
{template base}
<!doctype html>{\n}
<html>
<html lang="{$config.lang}">
<head>
<title>{$config.title}</title>
<meta http-equiv="content-type" content="application/xhtml+xml; charset=UTF-8" />
<link href="http://fonts.googleapis.com/css?family=Vollkorn:regular,italic,bold,bolditalic" rel="stylesheet" type="text/css" />
<link href="http://fonts.googleapis.com/css?family=Inconsolata" rel="stylesheet" type="text/css" />
<meta http-equiv="content-type" content="text/html;" charset="{$config.charset}" />
<meta name="viewport" content="width=device-width, initial-scale=1">
<link href="//fonts.googleapis.com/css?family=Vollkorn:regular,italic,bold,bolditalic" rel="stylesheet" type="text/css" />
<link href="//fonts.googleapis.com/css?family=Inconsolata" rel="stylesheet" type="text/css" />
<link href= "{$config.domain}/css/style.css" rel="stylesheet" type="text/css" />
<link rel="alternate" href="{$config.domain}/rss.xml" type="application/rss+xml" />
{if $injections.head}
@ -20,8 +21,12 @@
<div class="navigation">
<a href="{$config.domain}">{$config.title}</a> |
{foreach $link in $config.sitenav}
<a href="{$link.url}">{$link.name}</a>
{if not isLast($link)} | {/if}
{if $link.relative}
<a href="{$config.domain}/{$link.url}">{$link.name}</a>
{else}
<a href="{$link.url}">{$link.name}</a>
{/if}
{if not isLast($link)} {sp}|{sp} {/if}
{/foreach}
</div>
<div id="content">
@ -43,7 +48,9 @@
</a>
{/if}
by {$config.author}
<img align="right" src="{$config.domain}/css/logo_small.jpg" />
<a id="coleslaw-logo" href="https://github.com/redline6561/coleslaw">
<img src="{$config.domain}/css/logo_small.jpg" alt="Coleslaw logo" />
</a>
</div>
</body>
</html>

View file

@ -1,5 +1,6 @@
#content { background: #fff; padding-top: 1em }
#header { float: right; margin-left: 1em; margin-bottom: 1em }
#coleslaw-logo { float: right; }
a { text-decoration: none; color: #992900 }
a.anchor { color: black }
.date { font-style: italic }
@ -46,3 +47,54 @@ span.paren5 { background-color : inherit; -webkit-transition: background-color 0
span.paren5:hover { color : inherit; background-color : #CAFFCA; }
span.paren6 { background-color : inherit; -webkit-transition: background-color 0.2s linear; }
span.paren6:hover { color : inherit; background-color : #FFBAFF; }
@media (max-width: 680px) {
#content {
padding-top: 0em
}
.title {
margin-left: 0.1em;
}
.article-meta {
margin-bottom: 1em;
}
.article-meta, .article-content {
float: left;
margin-left: 0em;
margin-right: 0em;
width: 90%;
padding-left: 5%;
}
.article, .article-content {
margin-left: 0em;
text-align: justify;
}
img {
display: block;
margin-left: auto;
margin-right: auto;
width: 100%;
}
#coleslaw-logo {
float: none;
}
#coleslaw-logo img {
margin-left: auto;
margin-right: auto;
float: none;
width: auto;
}
.fineprint a img {
width: auto;
clear: both;
}
}

View file

@ -2,32 +2,32 @@
{template index}
<h1 class="title">{$index.title}</h1>
{foreach $post in $index.posts}
{foreach $obj in $index.content}
<div class="article-meta">
<a class="article-title" href="{$config.domain}/posts/{$post.slug}.html">{$post.title}</a>
<div class="date"> posted on {$post.date}</div>
<div class="article">{$post.content |noAutoescape}</div>
<a class="article-title" href="{$config.domain}/{$obj.url}">{$obj.title}</a>
<div class="date"> posted on {$obj.date}</div>
<div class="article">{$obj.excerpt |noAutoescape}</div>
</div>
{/foreach}
<div id="relative-nav">
{if $prev} <a href="{$prev}.html">Previous</a> {/if}
{if $next} <a href="{$next}.html">Next</a> {/if}
{if $prev} <a href="{$config.domain}/{$prev.url}">Previous</a> {/if}
{if $next} <a href="{$config.domain}/{$next.url}">Next</a> {/if}
</div>
{if $tags}
<div id="tagsoup">
<p>This blog covers
{foreach $tag in $tags}
<a href="{$config.domain}/tag/{$tag}.html">{$tag}</a>
{if not isLast($tag)}, {/if}
<a href="{$config.domain}/{$tag.url}">{$tag.name}</a>{nil}
{if not isLast($tag)},{sp}{/if}
{/foreach}
</div>
{/if}
{if $months}
<div id="monthsoup">
<p>View posts from
<p>View content from
{foreach $month in $months}
<a href="{$config.domain}/date/{$month}.html">{$month}</a>
{if not isLast($month)}, {/if}
<a href="{$config.domain}/{$month.url}">{$month.name}</a>{nil}
{if not isLast($month)},{sp}{/if}
{/foreach}
</div>
{/if}

View file

@ -4,20 +4,24 @@
<div class="article-meta">{\n}
<h1 class="title">{$post.title}</h1>{\n}
<div class="tags">{\n}
Tagged as {foreach $tag in $post.tags}
<a href="../tag/{$tag}.html">{$tag}</a>
{if not isLast($tag)}, {/if}
{/foreach}
{if $post.tags}
Tagged as {foreach $tag in $post.tags}
<a href="{$config.domain}/{$tag.url}">{$tag.name}</a>{nil}
{if not isLast($tag)},{sp}{/if}
{/foreach}
{/if}
</div>{\n}
<div class="date">{\n}
Written on {$post.date}
{if $post.date}
Written on {$post.date}
{/if}
</div>{\n}
</div>{\n}
<div class="article-content">{\n}
{$post.content |noAutoescape}
{$post.text |noAutoescape}
</div>{\n}
<div class="relative-nav">{\n}
{if $prev} <a href="{$config.domain}/posts/{$prev.slug}.html">Previous</a><br> {/if}{\n}
{if $next} <a href="{$config.domain}/posts/{$next.slug}.html">Next</a><br> {/if}{\n}
{if $prev} <a href="{$config.domain}/{$prev.url}">Previous</a><br> {/if}{\n}
{if $next} <a href="{$config.domain}/{$next.url}">Next</a><br> {/if}{\n}
</div>{\n}
{/template}

76
themes/readable/base.tmpl Normal file
View file

@ -0,0 +1,76 @@
{namespace coleslaw.theme.readable}
{template base}
<!DOCTYPE html>{\n}
<html lang="{$config.lang}">
<head>
<meta http-equiv="Content-Type" content="text/html; charset={$config.charset}">
<title>{$config.title}</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link href="{$config.domain}/css/bootstrap.min.css" rel="stylesheet" media="screen">
<link href="{$config.domain}/css/custom.css" rel="stylesheet" media="screen">
<link rel="alternate" href="{$config.domain}/rss.xml" type="application/rss+xml" />
{if $injections.head}
{foreach $injection in $injections.head}
{$injection |noAutoescape}
{/foreach}
{/if}
</head>
<body>
<div class="container-fluid">
<div class="row-fluid">
<div class="offset2 span8">
<div class="row-fluid">
<div class="navbar navbar-inverse">
<div class="navbar-inner">
<a class="brand" href="{$config.domain}">{$config.title}</a>
<ul class="nav">
{foreach $link in $config.sitenav}
<li>
{if $link.relative}
<a href="{$config.domain}/{$link.url}">{$link.name}</a>
{else}
<a href="{$link.url}">{$link.name}</a>
{/if}
</li>
{/foreach}
</ul>
</div>
</div>
</div>
<div class="row-fluid">
{$raw |noAutoescape}
</div>
{if $injections.body}
{foreach $injection in $injections.body}
<div class="row-fluid">
{$injection |noAutoescape}
</div>
{/foreach}
{/if}
<div class="row-fluid">
<hr>
<p class="fineprint">Unless otherwise credited all material
{if $config.license}
{$config.license}
{else}
<a rel="license" href="http://creativecommons.org/licenses/by-sa/3.0/deed.en_US">
<img alt="Creative Commons License" style="border-width:0" src="{$config.domain}/img/cc-by-sa.png" />
</a>
{/if}
by {$config.author}
<a id="coleslaw-logo" href="https://github.com/redline6561/coleslaw">
<img src="{$config.domain}/img/logo_small.jpg" alt="Coleslaw logo" /></p>
</a>
</div>
</div>
</div>
</div>
<script src="{$config.domain}/js/bootstrap.min.js"></script>
</body>
</html>
{/template}

9
themes/readable/css/bootstrap.min.css vendored Normal file

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,42 @@
#coleslaw-logo { float: right; }
hr {
border: 0;
height: 1px;
background-image: -webkit-linear-gradient(left, rgba(0,0,0,0), rgba(0,0,0,0.75), rgba(0,0,0,0));
background-image: -moz-linear-gradient(left, rgba(0,0,0,0), rgba(0,0,0,0.75), rgba(0,0,0,0));
background-image: -ms-linear-gradient(left, rgba(0,0,0,0), rgba(0,0,0,0.75), rgba(0,0,0,0));
background-image: -o-linear-gradient(left, rgba(0,0,0,0), rgba(0,0,0,0.75), rgba(0,0,0,0));
}
pre { overflow: auto; }
p.date-posted { font-style: italic; }
p.fineprint { font-size: smaller; }
/* Stolen from lisppaste for the colorize output of 3bmd */
.paste { background-color: #F4F4F4; color: black; }
.paste:hover { background-color: #F4F4F4; color: black; }
.symbol { color : #770055; background-color : transparent; border: 0px; margin: 0px;}
.special { color : #FF5000; background-color : inherit; }
.keyword { color : #770000; background-color : inherit; }
.comment { color : #007777; background-color : inherit; }
.string { color : #777777; background-color : inherit; }
.atom { color : #314F4F; background-color : inherit; }
.macro { color : #FF5000; background-color : inherit; }
.variable { color : #36648B; background-color : inherit; }
.function { color : #8B4789; background-color : inherit; }
.attribute { color : #FF5000; background-color : inherit; }
.character { color : #0055AA; background-color : inherit; }
.syntaxerror { color : #FF0000; background-color : inherit; }
.diff-deleted { color : #5F2121; background-color : inherit; }
.diff-added { color : #215F21; background-color : inherit; }
span.paren1 { background-color : inherit; -webkit-transition: background-color 0.2s linear; }
span.paren1:hover { color : inherit; background-color : #BAFFFF; }
span.paren2 { background-color : inherit; -webkit-transition: background-color 0.2s linear; }
span.paren2:hover { color : inherit; background-color : #FFCACA; }
span.paren3 { background-color : inherit; -webkit-transition: background-color 0.2s linear; }
span.paren3:hover { color : inherit; background-color : #FFFFBA; }
span.paren4 { background-color : inherit; -webkit-transition: background-color 0.2s linear; }
span.paren4:hover { color : inherit; background-color : #CACAFF; }
span.paren5 { background-color : inherit; -webkit-transition: background-color 0.2s linear; }
span.paren5:hover { color : inherit; background-color : #CAFFCA; }
span.paren6 { background-color : inherit; -webkit-transition: background-color 0.2s linear; }
span.paren6:hover { color : inherit; background-color : #FFBAFF; }

Binary file not shown.

After

Width:  |  Height:  |  Size: 672 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

View file

@ -0,0 +1,38 @@
{namespace coleslaw.theme.readable}
{template index}
<h1 class="page-header">{$index.title}</h1>
{foreach $obj in $index.content}
<div class="row-fluid">
<h1><a href="{$config.domain}/{$obj.url}">{$obj.title}</a></h1>
<p class="date-posted">posted on {$obj.date}</p>
{$obj.excerpt |noAutoescape}
</div>
{/foreach}
<div id="relative-nav">
<ul class="pager">
{if $prev} <li class="previous"><a href="{$config.domain}/{$prev.url}">Previous</a></li> {/if}
{if $next} <li class="next"><a href="{$config.domain}/{$next.url}">Next</a></li> {/if}
</ul>
</div>
{if $tags}
<div class="row-fluid">
<p>This blog covers
{foreach $tag in $tags}
<a href="{$config.domain}/{$tag.url}">{$tag.name}</a>{nil}
{if not isLast($tag)},{sp}{/if}
{/foreach}
</p>
</div>
{/if}
{if $months}
<div class="row-fluid">
<p>View content from
{foreach $month in $months}
<a href="{$config.domain}/{$month.url}">{$month.name}</a>{nil}
{if not isLast($month)},{sp}{/if}
{/foreach}
</p>
</div>
{/if}
{/template}

6
themes/readable/js/bootstrap.min.js vendored Normal file
View file

@ -0,0 +1,6 @@
/*!
* Bootstrap without jQuery v0.3.1
* By Daniel Davis under MIT License
* https://github.com/tagawa/bootstrap-without-jquery
*/
;(function(){"use strict";function o(n){for(var f=n.children,r=0,i,t=0,u=f.length;t<u;t++)i=f[t],r+=Math.max(i.clientHeight,i.offsetHeight,i.scrollHeight);return r}function e(n){n=n||window.event;var f=n.currentTarget||n.srcElement,r=f.getAttribute("data-target"),t=document.querySelector(r),u=o(t),i=" "+t.className+" ";return i.indexOf(" in ")>-1?(i=i.replace(" in "," "),t.className=i,t.style.height="0"):(t.className+=" in ",t.style.height=u+"px"),!1}function c(n){n=n||window.event;var r=n.currentTarget||n.srcElement,i=r.parentElement,t=" "+i.className+" ";return t.indexOf(" open ")>-1?(t=t.replace(" open "," "),i.className=t):i.className+=" open ",!1}function h(n){n=n||window.event;var i=n.currentTarget||n.srcElement,t=i.parentElement;return t.className=(" "+t.className+" ").replace(" open "," "),!1}function s(n){n=n||window.event;var i=n.currentTarget||n.srcElement,t=i.parentElement;return t.parentElement.removeChild(t),!1}var f,u,i,r,n,t;for(document.querySelectorAll||(document.querySelectorAll=function(n){var f=document.styleSheets[0]||document.createStyleSheet(),r,i,t,u;for(f.addRule(n,"foo:bar"),r=document.all,i=[],t=0,u=r.length;t<u;t++)r[t].currentStyle.foo==="bar"&&(i[i.length]=r[t]);return f.removeRule(0),i}),f=document.querySelectorAll("[data-toggle=collapse]"),n=0,t=f.length;n<t;n++)f[n].onclick=e;for(u=document.querySelectorAll("[data-toggle=dropdown]"),n=0,t=u.length;n<t;n++)i=u[n],i.setAttribute("tabindex","0"),i.onclick=c,i.onblur=h;for(r=document.querySelectorAll("[data-dismiss=alert]"),n=0,t=r.length;n<t;n++)r[n].onclick=s})();

27
themes/readable/post.tmpl Normal file
View file

@ -0,0 +1,27 @@
{namespace coleslaw.theme.readable}
{template post}
<div class="row-fluid">{\n}
<h1 class="page-header">{$post.title}</h1>{\n}
<p>
{if $post.tags}
Tagged as {foreach $tag in $post.tags}
<a href="{$config.domain}/{$tag.url}">{$tag.name}</a>{nil}
{if not isLast($tag)},{sp}{/if}
{/foreach}
{/if}
</p>
<p class="date-posted">
{if $post.date}
Written on {$post.date}
{/if}
</p>
{$post.text |noAutoescape}
<ul class="pager">
{if $prev}<li class="previous"><a href="{$config.domain}/{$prev.url}">&larr; Previous</a></li>{/if}{\n}
{if $next}<li class="next"><a href="{$config.domain}/{$next.url}">Next &rarr;</a></li>{/if}{\n}
</ul>
</div>{\n}
{/template}

View file

@ -1,4 +1,4 @@
{namespace coleslaw.theme.hyde}
{namespace coleslaw.theme.feeds}
{template rss}
<?xml version="1.0"?>{\n}
@ -10,17 +10,17 @@
<language>en-us</language>
<pubDate>{$pubdate}</pubDate>
{foreach $post in $content.posts}
{foreach $post in $content.content}
<item>
<title>{$post.title}</title>
<link>{$config.domain}/posts/{$post.slug}.html</link>
<link>{$config.domain}/{$post.url}</link>
<pubDate>{$post.date}</pubDate>
<author>{$config.author}</author>
<guid isPermaLink="true">{$config.domain}/posts/{$post.slug}.html</guid>
<guid isPermaLink="true">{$config.domain}/{$post.url}</guid>
{foreach $tag in $post.tags}
<category><![CDATA[ {$tag} ]]></category>
<category><![CDATA[ {$tag.name |noAutoescape} ]]></category>
{/foreach}
<description><![CDATA[ {$post.content |noAutoescape} ]]></description>
<description><![CDATA[ {$post.text |noAutoescape} ]]></description>
</item>
{/foreach}

13
themes/sitemap.tmpl Normal file
View file

@ -0,0 +1,13 @@
{namespace coleslaw.theme.sitemap}
{template sitemap}
<?xml version="1.0"?>{\n}
<urlset xmlns='http://www.sitemaps.org/schemas/sitemap/0.9'>
{foreach $url in $content.urls}
<url>
<loc>{$config.domain}/{$url}</loc>
<lastmod>{$pubdate}</lastmod>
</url>
{/foreach}
</urlset>
{/template}