/lib/gaa_mbox.tcl

http://github.com/maxcom/tklor · TCL · 254 lines · 212 code · 25 blank · 17 comment · 44 complexity · 891fae102d7d2075e47795dcbc379b5e MD5 · raw file

  1. ############################################################################
  2. # Copyright (C) 2008 Alexander Galanin <gaa.nnov@mail.ru> #
  3. # #
  4. # This program is free software: you can redistribute it and/or modify #
  5. # it under the terms of the GNU Lesser General Public License as #
  6. # published by the Free Software Foundation, either version 3 of the #
  7. # License, or (at your option) any later version. #
  8. # #
  9. # This program is distributed in the hope that it will be useful, #
  10. # but WITHOUT ANY WARRANTY; without even the implied warranty of #
  11. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
  12. # GNU General Public License for more details. #
  13. # #
  14. # You should have received a copy of the GNU Lesser GNU General Public #
  15. # License along with this program. #
  16. # If not, see <http://www.gnu.org/licenses/>. #
  17. ############################################################################
  18. package provide gaa_mbox 1.1
  19. package require Tcl 8.4
  20. package require cmdline 1.2.5
  21. namespace eval mbox {
  22. namespace export \
  23. initParser \
  24. closeParser \
  25. parseLine \
  26. parseFile \
  27. parseStream \
  28. writeToFile \
  29. writeToStream
  30. set id 0
  31. proc parseLine {id line} {
  32. variable letter$id
  33. variable state$id
  34. variable command$id
  35. set letter [ set letter$id ]
  36. set state [ set state$id ]
  37. array unset lt
  38. array set lt $letter
  39. if { $state == "EOF" ||
  40. ( [ regexp {^From:{0,1} (.+)$} $line dummy nick ] &&
  41. ( $state == "BODYSPACE" || $state == "BEGIN" ) ) } {
  42. regsub {(\n)+$} $lt(body) "" lt(body)
  43. set letter [ array get lt ]
  44. if [ info exists lt(From) ] {
  45. uplevel #0 [ concat [ set command$id ] [ list $letter ] ]
  46. }
  47. if { $state != "EOF" } {
  48. set state$id HEAD
  49. set letter$id [ list "From" $nick ]
  50. }
  51. return
  52. }
  53. if { $line == "" } {
  54. switch -exact $state {
  55. HEAD {
  56. set state$id BODY
  57. set lt(body) ""
  58. }
  59. BODY {
  60. set state$id BODYSPACE
  61. set lt(body) "$lt(body)\n"
  62. }
  63. BODYSPACE {
  64. set lt(body) "$lt(body)\n"
  65. }
  66. }
  67. set letter$id [ array get lt ]
  68. return
  69. }
  70. if { $state == "HEAD" || $state == "BEGIN" } {
  71. if [ regexp {^([\w-]+):\s*(.*)$} $line dummy tag val ] {
  72. set lt($tag) $val
  73. } else {
  74. error "Invalid data in the header section: $line"
  75. }
  76. } else {
  77. if [ regexp {^>(>*From:{0,1} .*)$} $line dummy ss ] {
  78. set line $ss
  79. }
  80. append lt(body) "$line\n"
  81. }
  82. set letter$id [ array get lt ]
  83. }
  84. proc outputHandler {id stream onoutput onerror oncomplete} {
  85. if { [ gets $stream s ] < 0 } {
  86. if [ eof $stream ] {
  87. if [ catch {
  88. fconfigure $stream -blocking 1
  89. close $stream
  90. } err ] {
  91. lappend onerror $err
  92. uplevel #0 $onerror
  93. } else {
  94. closeParser $id
  95. uplevel #0 $oncomplete
  96. }
  97. }
  98. } else {
  99. lappend onoutput $s
  100. uplevel #0 $onoutput
  101. }
  102. }
  103. proc parseFile {fileName command args} {
  104. array set p [ ::cmdline::getoptions args {
  105. {mode.arg "r" "Stream open mode"}
  106. {encoding.arg "utf-8" "Encoding"}
  107. {sync.arg "0" "Parse stream in synchronous mode"}
  108. {oncomplete.arg "" "Script to execute on complete(async mode)"}
  109. {onerror.arg "error" "Script to execute on error(async mode)"}
  110. } ]
  111. set f [ open $fileName $p(mode) ]
  112. fconfigure $f -encoding $p(encoding)
  113. parseStream $f $command \
  114. -oncomplete [ join [ list $p(oncomplete) [ list close $f ] ] ";" ] \
  115. -onerror $p(onerror) \
  116. -sync $p(sync)
  117. }
  118. proc parseStream {stream command args} {
  119. array set p [ ::cmdline::getoptions args {
  120. {sync.arg "0" "Parse stream in synchronous mode"}
  121. {oncomplete.arg "" "Script to execute on complete(async mode)"}
  122. {onerror.arg "error" "Script to execute on error(async mode)"}
  123. } ]
  124. set id [ initParser $command ]
  125. if $p(sync) {
  126. fconfigure $stream -blocking 1
  127. if [ catch {
  128. while { [ gets $stream s ] >= 0 } {
  129. parseLine $id $s
  130. }
  131. } err ] {
  132. set errInfo $::errorInfo
  133. catch {closeParser $id}
  134. error $err $errInfo
  135. }
  136. closeParser $id
  137. close $stream
  138. } else {
  139. fconfigure $stream \
  140. -buffering line \
  141. -blocking 0
  142. fileevent $stream readable [ list \
  143. [ namespace current ]::outputHandler \
  144. $id \
  145. $stream \
  146. [ list [ namespace current ]::parseLine $id ] \
  147. $p(onerror) \
  148. $p(oncomplete) \
  149. ]
  150. }
  151. }
  152. proc initParser {command} {
  153. variable id
  154. incr id
  155. variable letter$id
  156. variable state$id
  157. variable command$id
  158. set letter$id {body ""}
  159. set state$id "BEGIN"
  160. set command$id $command
  161. return $id
  162. }
  163. proc closeParser {id} {
  164. variable letter$id
  165. variable state$id
  166. variable command$id
  167. set state$id EOF
  168. if [ catch {parseLine $id ""} err ] {
  169. unset letter$id state$id command$id
  170. error $err $::errorInfo
  171. }
  172. unset letter$id state$id command$id
  173. }
  174. proc writeToFile {fileName letter args} {
  175. array set param [ ::cmdline::getoptions args {
  176. {encoding.arg "" "Encoding"}
  177. {append "Append to file instead of overwriting"}
  178. } ]
  179. if $param(append) {
  180. set mode "a"
  181. } else {
  182. set mode "w"
  183. }
  184. set f [ open $fileName $mode ]
  185. if {$param(encoding) != ""} {
  186. fconfigure $f -encoding $param(encoding)
  187. }
  188. if [ catch {
  189. foreach letter $letter {
  190. writeToStream $f $letter
  191. }
  192. } err ] {
  193. close $f
  194. error $err $::errorInfo
  195. }
  196. close $f
  197. }
  198. proc writeToStream {stream letter} {
  199. set body ""
  200. set fromExists 0
  201. foreach {header value} $letter {
  202. if { $header == "From"} {
  203. puts $stream "From $value"
  204. set fromExists 1
  205. break
  206. }
  207. }
  208. if { $fromExists == 0 } {
  209. error "No From header"
  210. }
  211. foreach {header value} $letter {
  212. if {$header != "body"} {
  213. if {$header != "From"} {
  214. puts $stream "$header: $value"
  215. }
  216. } else {
  217. set body $value
  218. }
  219. }
  220. puts $stream ""
  221. foreach line [ split $body "\n" ] {
  222. if [ regexp {^>*From } $line ] {
  223. puts -nonewline $stream ">"
  224. }
  225. puts $stream $line
  226. }
  227. puts $stream ""
  228. }
  229. }