Flexible Lisp Blogware. Fork for personal use. Mirrored from https://github.com/kingcons/coleslaw originally.

incremental.lisp 3.5KB

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