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.4KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143
  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. ; Thanks to http://cl-cookbook.sourceforge.net/strings.html#manip
  31. (defun replace-all (string part replacement &key (test #'char=))
  32. "Returns a new string in which all the occurences of the part
  33. is replaced with replacement."
  34. (with-output-to-string (out)
  35. (loop with part-length = (length part)
  36. for old-pos = 0 then (+ pos part-length)
  37. for pos = (search part string
  38. :start2 old-pos
  39. :test test)
  40. do (write-string string out
  41. :start old-pos
  42. :end (or pos (length string)))
  43. when pos do (write-string replacement out)
  44. while pos)))
  45. ; This function is pretty ugly but I was tired when I wrote it and just wanted it to work.
  46. ; TODO: Clean this up
  47. (defun split-quoted (str)
  48. (let ((my-list nil)
  49. (my-string (make-array 0 :element-type 'character :fill-pointer 0 :adjustable t))
  50. (encountered-quote nil))
  51. (loop for c across str do
  52. (if (eq c #\")
  53. (setf encountered-quote (not encountered-quote)))
  54. (if (or (not (eq c #\Space)) encountered-quote)
  55. (vector-push-extend c my-string)
  56. (progn (setf my-list (append my-list (list my-string)))
  57. (setf my-string (make-array 0 :element-type 'character :fill-pointer 0 :adjustable t)))))
  58. (setf my-list (append my-list (list my-string)))))
  59. (defun empty-string? (str)
  60. (string= "" str))
  61. (defun check-start (line expected seperator)
  62. (if (or (empty-string? line) (empty-string? seperator))
  63. nil
  64. (string= expected (first (split line seperator)))))
  65. (defun test-for-maildir (line)
  66. (check-start line "MAILDIR" "="))
  67. (defun test-for-if (line)
  68. (check-start line "if" "("))
  69. (defun test-for-end-if (line)
  70. (check-start line "}" "\n"))
  71. (defun loop-file-until (s test-func worker-func hash)
  72. (loop for line = (read-line s nil 'eof) do
  73. (if (eq line 'eof)
  74. nil
  75. (funcall worker-func s line hash))
  76. until (or (eq line 'eof) (funcall test-func line))))
  77. (defun convert-maildir-value (value)
  78. (remove #\" (replace-all value "${HOME}" (to-string (user-homedir-pathname)))))
  79. (defun set-maildir (line hash)
  80. (let ((maildir (convert-maildir-value (car (last (split line "="))))))
  81. (sethash "maildir" hash maildir)
  82. (setf *maildir* maildir)))
  83. (defun parse-line (s line hash)
  84. (if (test-for-if line)
  85. (read-mailfilter-if s line hash)
  86. (if (test-for-maildir line)
  87. (set-maildir line hash))))
  88. (defun test-for-command (line)
  89. (member (first(split-quoted (string-trim '(#\Space #\Tab) line))) *commands* :test #'equal))
  90. (defun clean-line (line)
  91. (let ((cleaned-line (string-trim '(#\Space #\Tab) line)))
  92. (replace-all cleaned-line "${MAILDIR}" *maildir*)))
  93. (defun hash-add-command (line hash)
  94. (let* ((cleaned-line (clean-line line))
  95. (line-list (split-quoted cleaned-line)))
  96. (sethash "command" hash (first line-list))
  97. (sethash "args" hash (subseq line-list 1))
  98. hash))
  99. (defun get-regex (line)
  100. (second (split line "/")))
  101. (defun make-regex-hash (regex)
  102. (let ((regex-hash (make-hash-table :test 'equal)))
  103. (sethash "regex" regex-hash regex)
  104. regex-hash))
  105. (defun parse-if-line (line hash)
  106. (if (test-for-if line)
  107. (let* ((regex (get-regex line))
  108. (regex-hash (make-regex-hash regex)))
  109. (sethash "patterns" hash (append (gethash "patterns" hash) (list regex-hash)))
  110. regex-hash)))
  111. (defun parse-inner-if-line (s line hash)
  112. (if (test-for-command line)
  113. (hash-add-command line hash)))
  114. (defun read-mailfilter-if (s line hash)
  115. (let ((if-hash (parse-if-line line hash)))
  116. (loop-file-until s #'test-for-end-if #'parse-inner-if-line if-hash)))
  117. (defun read-mailfilter-file (file)
  118. (let ((hash (make-hash-table :test 'equal)))
  119. (with-open-file (s file)
  120. (loop-file-until s (lambda (x) nil) #'parse-line hash))
  121. hash))