MHå½¢å¼ãã©ã«ããMaildirã«å¤æ
å¿ è¦ã«ãããï¼MHå½¢å¼ã®ãã©ã«ããMaildirã«å¤æããã¹ã¯ãªãããæ¸ãã¾ããï¼Rubyã§ï¼
MHããMaildirã«ç§»è¡ããå ´åã«ä¾¿å©ã«ä½¿ããã¨æãã¾ãï¼
Googleãªã©ã®æ¤ç´¢ã¨ã³ã¸ã³çµç±ã§è¦ã¤ããmh2maildirã¨ãããã¼ã«ãããããã§ããï¼ä»¥ä¸ã®ç¹ããã¾ãã¡ã«æãã¾ããã®ã§ï¼èªåã§ä½ã£ã¦ãã¾ãã¾ããï¼
- Maildirå½¢å¼ã®ã¡ã¼ã«ãæ¸ãè¾¼ãããã ãã«procmailã使ã£ã¦ãã
- æªèªãã©ã°ãè¿ä¿¡æ¸ã¿ãã©ã°ãä¿åãããªã(Maildirå½¢å¼ã«å¤æ´ãã¦ãããªã)
ãã¡ã¤ã«æ§æ
- mh.rb
- mh2maildir.rbã使ãã¯ã©ã¹ "Mail::Reader::MH", "Mail::Message::MH" ãå®ç¾©
- maildir.rb
- mh2maildir.rbã使ãã¯ã©ã¹ "Mail::Writer::Maildir" ãå®ç¾©
- mh2maildir.rb
- å¤æã¹ã¯ãªããæ¬ä½ï¼
使ãæ¹
(1) mh2maildir.rb
Maildirãã©ã«ãã¯èªåçã«ä½æããã¾ãï¼æå®ãããMaildirãã©ã«ããæ¢ã«åå¨ããå ´åã¯åä½ãåæ¢ãã¾ã(ã½ãããã®çº)
% ~/bin/mh2maildir.rb Mail Maildir-fromMH
(2) å¿
è¦ã«å¿ãï¼å¤æå¾ã®Maildirãã©ã«ããï¼æ¢åã®Maildirã¨çµ±å
æ¨æ¥ç´¹ä»ããmddigest, mdmergeã使ãã¾ãï¼
% ~/bin/mddigest.rb Maildir-fromMH % ~/bin/mddigest.rb Maildir % ~/bin/mdmerge.rb Maildir Maildir-fromMH
å®è¡åã«MTAãæ¢ãã¦ãããã¨ï¼
注æç¹
- å£ããã¡ã¼ã«ã¯å¦çãã¾ãã
- ãå£ããã¡ã¼ã«ãã®å®ç¾©: ããããä¸è¡ããªãMHå½¢å¼ã®ãã¡ã¤ã«
- Courier IMAPå°ç¨ã§ã
- ä»ã®IMAPãµã¼ãç¨ã®Maildirãä½ãããã«ã¯ï¼ããã¤ãä¿®æ£ããªãã¨ãããªããã
- MHå½¢å¼ãã©ã«ãã«ï¼æ¥æ¬èªãã©ã«ãåãããå ´åã¯ãã¾ãå¦çã§ãã¾ãã
- MHå½¢å¼ãã©ã«ãåã« "." (ããªãªã) ãå«ãå ´åã¯ï¼JIS X 0208ãï¼ã(ããããå
¨è§ã®ããªãªã)ã«å¤æãã¾ãï¼
- Courier IMAPã®å ´åï¼ããªãªãã¯IMAPãã©ã«ãã®åºåãã«ä½¿ãæåãªã®ã§ï¼å¤å½¢Base64ã¨ã³ã³ã¼ãã£ã³ã°ããã®ãçã§ããï¼ãããããã©ã«ãåãããå ´åï¼Courier IMAP + Thunderbirdã®çµã¿åããã§æ£ããåããªãããï¼ãããªããããã¦ããã¾ãï¼
ã½ã¼ã¹
mh.rb
#!/usr/local/bin/ruby # Mail::Reader::MH class # Mail::Message::MH class # $Id: mh.rb 39 2008-01-08 06:37:09Z genta $ require 'time' require 'find' class Mail class Reader class MH attr_reader :basedir def initialize(basedir) @basedir = basedir end def traverse Find.find(@basedir) do |file| next if File.directory?(file) rpath = file.sub(%r|^#{Regexp.escape(@basedir)}/?|, '') path = rpath.split(File::SEPARATOR) next if path.size < 2 folder = File.join(path[0..-2]) filename = path[-1] next unless filename =~ /^\d+$/ msg = open(folder, filename) next if ! msg.sanity? # skip broken mail yield(msg) end end alias :each :traverse def open(folder, filename) file = File.join(@basedir, folder, filename) message = Mail::Message::MH.new(file) message.folder = folder return message end end # MH end # Reader class Message class MH attr_reader :file, :from, :time, :header, :flag attr_accessor :folder def initialize(file) @header = Hash.new {[]} @flag = {} @file = file open() end def open(file = @file) File.open(file) do |fd| key, key_h, value = '', '', '' fd.each do |line| line.strip! break if line =~ /^$/ # End of the header if /^From (\S+) *(.+)$/ === line then _, @from, time = $~.to_a @time = Time.rfc2822(time) rescue Time.parse(time) next end if not /^([^: \000-\037\177]+): *(.*)/ === line then # line is the tailer of above line @header[key_h][-1] += ' ' + line next end _, key, value = $~.to_a key_h = key.downcase.capitalize @header[key_h] = [] unless @header.key?(key_h) @header[key_h] <<= value end end %w!Seen Replied Forwarded!. select {|x| @header[x].size > 0 }. map {|x| @flag[x] = true } if @time.nil? then retryq = [ lambda { @header['Date'][0] }, lambda { @header['Received'].first.match(/.*;(.*)/).to_a[1] }, lambda { File.mtime(file).to_s } ] begin time = retryq.shift.call @time = Time.rfc2822(time) rescue Time.parse(time) rescue if retryq.empty? then raise "Mail::Message::MH#open: Can't get the date. (file: #{file})" end retry end end end def each fd = File.open(@file) fd.each do |buf| _, rs = /(\r?\n)\Z/.match(buf).to_a next if /^(Seen|Replied|Forwarded): *$/ === buf # cut MH flags if /^From / === buf then if not @from.nil? and not @header.key?('Return-path') yield("Return-Path: <#{@from}>" + rs) end next end yield(buf) break if /^$/ === buf end fd.each do |body| yield(body) end fd.close end def sanity? return false if @header.empty? return true end end # MH end # Message end # Mail
maildir.rb
#!/usr/local/bin/ruby # Mail::Writer::Maildir class # $Id: maildir.rb 38 2008-01-07 18:20:21Z genta $ require 'time' class Mail class Writer class Maildir attr_reader :maildir, :logger @@host = `hostname`.chomp @@pid = $$ def initialize(maildir, logger = nil) @maildir = maildir if ! logger.nil? then @logger = lambda do o = logger dir = nil lambda do |tmpfile| if tmpfile.nil? then o.puts return end if dir.nil? or dir != tmpfile[:dir] then dir = tmpfile[:dir] o.puts o.print "#{dir}: " end o.print "." end end.call() else @logger = lambda {|x| x} # do nothing end create_maildir(@maildir) end def write(reader) reader.traverse do |mh| tmpfile = tmpfile(mh) raise 'Internal error' if test(?e, tmpfile[:file]) @logger.call(tmpfile) create_folder(tmpfile[:dir]) File.open(tmpfile[:file], 'w') do |fd| mh.each {|buf| fd.print buf } end curfile = curfile(tmpfile, mh) File.rename(tmpfile[:file], curfile) File.utime(mh.time, mh.time, curfile) end @logger.call(nil) end private def create_maildir(maildir) if test(?e, maildir) then raise "#{maildir}: Maildir already exists" end system(*%W<maildirmake #{maildir}>) or raise "maildirmake" end def tmpfile(mh) t = Time.now time, usec = t.to_i, t.usec pid = @@pid host = @@host dir = mh2maildir_rpath(mh.folder) file = File.join(@maildir, dir, "tmp", "#{time}.M#{usec}P#{pid}_0.#{host}") return({:time => time, :usec => usec, :pid => pid, :host => host, :dir => dir, :file => file}) end def curfile(t, mh) s = File.stat(t[:file]) info = '' [['Seen', 'S'], ['Replied', 'R']].each do |(label, tag)| info += tag if mh.flag[label] end info = ',' + info if info.size > 0 file = "%d.M%dP%dV%016XI%08X_0.%s,S=%d:2%s" % [t[:time], t[:usec], t[:pid], s.dev, s.ino, t[:host], s.size, info] curfile = File.join(@maildir, t[:dir], 'cur', file) return curfile end def mh2maildir_rpath(rpath_mh) rpath = rpath_mh.gsub('.', '&,w4-') # XXX: replace '.' -> JISX0208 '.' # due to Thunderbird + Courier IMAP # can't handle '.' in folder name. mpath = rpath.split(File::SEPARATOR) if mpath.size == 1 and mpath[0] == 'inbox' then return '' end # Convert MH specific folders to IMAP folders. [['drafts', 'Drafts'], ['trash', 'Trash']].each do |(mh, imap)| mpath[0] = imap if mpath[0] == mh end '.' + mpath.join('.') end def create_folder(dir) return nil if test(?e, File.join(@maildir, dir)) _, folder = /^\.(.*)$/.match(dir).to_a # strip '.' at head of dirname path = folder.split('.') path.size.times do |i| folder = path[0..i].join('.') next if test(?e, File.join(@maildir, '.' + folder)) system(*%W<maildirmake -f #{folder} #{@maildir}>) end end end # Maildir end # Writer end # Mail
mh2maildir.rb
#!/usr/local/bin/ruby # $Id: mh2maildir.rb 37 2008-01-07 16:35:10Z genta $ $LOAD_PATH << File.dirname($0) require 'mh' # Mail::Reader::MH, Mail::Messgae::MH class require 'maildir' # Mail::Writer::Maildir require 'pp' def usage puts "usage: #{File.basename($0)} <MH directory> <Maildir>" puts "Warning: Maildir will be overwritten." end begin $mhdir = ARGV[0] or raise $maildir = ARGV[1] or raise rescue usage() exit(1) end mh = Mail::Reader::MH.new($mhdir) maildir = Mail::Writer::Maildir.new($maildir, $stderr) maildir.write(mh)