Simple LISP library to parse a maildir file into a usable structure. It can then be used to move messages around based on these rules. Developed to fulfill my personal itch, pull requests welcome!

mailfilter-parse.lisp 5.0KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128
  1. ;;;;Copyright (c) 2015, Lily Carpenter
  2. ;;;;All rights reserved.
  3. ;;;;
  4. ;;;;Redistribution and use in source and binary forms, with or without modification,
  5. ;;;;are permitted provided that the following conditions are met:
  6. ;;;;
  7. ;;;;* Redistributions of source code must retain the above copyright notice, this
  8. ;;;; list of conditions and the following disclaimer.
  9. ;;;;
  10. ;;;;* Redistributions in binary form must reproduce the above copyright notice, this
  11. ;;;; list of conditions and the following disclaimer in the documentation and/or
  12. ;;;; other materials provided with the distribution.
  13. ;;;;
  14. ;;;;* Neither the name of the Lily Carpenter nor the names of its
  15. ;;;; contributors may be used to endorse or promote products derived from
  16. ;;;; this software without specific prior written permission.
  17. ;;;;
  18. ;;;;THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
  19. ;;;;ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
  20. ;;;;WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
  21. ;;;;DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
  22. ;;;;ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
  23. ;;;;(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
  24. ;;;;LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
  25. ;;;;ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  26. ;;;;(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
  27. ;;;;SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  28. (defvar *commands* '("to"))
  29. (defvar *maildir* nil)
  30. ;; This function is pretty ugly but I was tired when I wrote it and just wanted it to work.
  31. ;; TODO: Clean this up
  32. (defun split-quoted (str)
  33. (let ((my-list nil)
  34. (my-string (make-array 0 :element-type 'character :fill-pointer 0 :adjustable t))
  35. (encountered-quote nil))
  36. (loop for c across str do
  37. (if (eq c #\")
  38. (setf encountered-quote (not encountered-quote)))
  39. (if (or (not (eq c #\Space)) encountered-quote)
  40. (vector-push-extend c my-string)
  41. (progn (setf my-list (append my-list (list my-string)))
  42. (setf my-string (make-array 0 :element-type 'character :fill-pointer 0 :adjustable t)))))
  43. (setf my-list (append my-list (list my-string)))))
  44. (defun empty-string? (str)
  45. (string= "" str))
  46. (defun check-start (line expected seperator)
  47. (if (or (empty-string? line) (empty-string? seperator))
  48. nil
  49. (string= expected (first (split-sequence:split-sequence seperator line)))))
  50. (defun test-for-maildir (line)
  51. (check-start line "MAILDIR" "="))
  52. (defun test-for-if (line)
  53. (check-start line "if" "("))
  54. (defun test-for-end-if (line)
  55. (check-start line "}" "\n"))
  56. (defun loop-file-until (s test-func worker-func hash)
  57. (loop for line = (read-line s nil 'eof) do
  58. (if (eq line 'eof)
  59. nil
  60. (funcall worker-func s line hash))
  61. until (or (eq line 'eof) (funcall test-func line))))
  62. (defun convert-maildir-value (value)
  63. (remove #\" (cl-ppcre:regex-replace-all "${HOME}" value (user-homedir-pathname))))
  64. (defun set-maildir (line hash)
  65. (let ((maildir (convert-maildir-value (car (last (split-sequence:split-sequence "=" line))))))
  66. (setf (gethash "maildir" hash) maildir)
  67. (setf *maildir* maildir)))
  68. (defun parse-line (s line hash)
  69. (if (test-for-if line)
  70. (read-mailfilter-if s line hash)
  71. (if (test-for-maildir line)
  72. (set-maildir line hash))))
  73. (defun test-for-command (line)
  74. (member (first(split-quoted (string-trim '(#\Space #\Tab) line))) *commands* :test #'equal))
  75. (defun clean-line (line)
  76. (let ((cleaned-line (string-trim '(#\Space #\Tab) line)))
  77. (cl-ppcre:regex-replace-all "${MAILDIR}" cleaned-line *maildir*)))
  78. (defun hash-add-command (line hash)
  79. (let* ((cleaned-line (clean-line line))
  80. (line-list (split-quoted cleaned-line)))
  81. (setf (gethash "command" hash) (first line-list))
  82. (setf (gethash "args" hash) (subseq line-list 1))
  83. hash))
  84. (defun get-regex (line)
  85. (second (split-sequence:split-sequence "/" line)))
  86. (defun make-regex-hash (regex)
  87. (let ((regex-hash (make-hash-table :test 'equal)))
  88. (setf (gethash "regex" regex-hash) regex)
  89. regex-hash))
  90. (defun parse-if-line (line hash)
  91. (if (test-for-if line)
  92. (let* ((regex (get-regex line))
  93. (regex-hash (make-regex-hash regex)))
  94. (setf (gethash "patterns" hash) (append (gethash "patterns" hash) (list regex-hash)))
  95. regex-hash)))
  96. (defun parse-inner-if-line (s line hash)
  97. (declare (ignore s) )
  98. (if (test-for-command line)
  99. (hash-add-command line hash)))
  100. (defun read-mailfilter-if (s line hash)
  101. (let ((if-hash (parse-if-line line hash)))
  102. (loop-file-until s #'test-for-end-if #'parse-inner-if-line if-hash)))
  103. (defun read-mailfilter-file (file)
  104. (let ((hash (make-hash-table :test 'equal)))
  105. (with-open-file (s file)
  106. (loop-file-until s (lambda (x) (declare (ignore x))) #'parse-line hash))
  107. hash))