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!

parse-mailfilter.lisp 5.6KB

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