14 //import "github.com/jteeuwen/go-pkg-ini/ini"
15 import "github.com/kless/goconfig/config"
17 type mail_addr_freq struct {
22 type frequencies map[string]uint
24 /* Used to sort the email addresses from most to least used */
25 func sort_by_freq(m1, m2 *mail_addr_freq) int {
26 if (m1.count[0] == m2.count[0] &&
27 m1.count[1] == m2.count[1] &&
28 m1.count[2] == m2.count[2]) {
32 if (m1.count[0] > m2.count[0] ||
33 m1.count[0] == m2.count[0] &&
34 m1.count[1] > m2.count[1] ||
35 m1.count[0] == m2.count[0] &&
36 m1.count[1] == m2.count[1] &&
37 m1.count[2] > m2.count[2]) {
44 type maddresses []*mail_addr_freq
46 func (self *maddresses) Len() int {
50 func (self *maddresses) Less(i,j int) bool {
53 v := sort_by_freq(m1, m2)
60 func (self *maddresses) Swap(i,j int) {
61 (*self)[i], (*self)[j] = (*self)[j], (*self)[i]
64 // find most frequent real name for each mail address
65 func frequent_fullname(freqs frequencies) string {
68 freqs_sz := len(freqs)
70 for mail,freq := range freqs {
71 if (freq > maxfreq && mail != "") || freqs_sz == 1 {
72 // only use the entry if it has a real name
73 // or if this is the only entry
81 func addresses_by_frequency(msgs *notmuch.Messages, name string, pass uint, addr_to_realname *map[string]*frequencies) *frequencies {
83 freqs := make(frequencies)
85 pattern := `\s*(("(\.|[^"])*"|[^,])*<?(?mail\b\w+([-+.]\w+)*\@\w+[-\.\w]*\.([-\.\w]+)*\w\b)>?)`
86 // pattern := "\\s*((\\\"(\\\\.|[^\\\\\"])*\\\"|[^,])*" +
87 // "<?(?P<mail>\\b\\w+([-+.]\\w+)*\\@\\w+[-\\.\\w]*\\.([-\\.\\w]+)*\\w\\b)>?)"
88 pattern = `.*` + strings.ToLower(name) + `.*`
89 var re *regexp.Regexp = nil
90 var err os.Error = nil
91 if re,err = regexp.Compile(pattern); err != nil {
92 log.Printf("error: %v\n", err)
96 headers := []string{"from"}
98 headers = append(headers, "to", "cc", "bcc")
101 for ;msgs.Valid();msgs.MoveToNext() {
103 //println("==> msg [", msg.GetMessageId(), "]")
104 for _,header := range headers {
105 froms := strings.ToLower(msg.GetHeader(header))
106 //println(" froms: ["+froms+"]")
107 for _,from := range strings.Split(froms, ",", -1) {
108 from = strings.Trim(from, " ")
109 match := re.FindString(from)
110 //println(" -> match: ["+match+"]")
111 occ,ok := freqs[match]
123 func search_address_passes(queries [3]*notmuch.Query, name string) []string {
125 addr_freq := make(map[string]*mail_addr_freq)
126 addr_to_realname := make(map[string]*frequencies)
128 var pass uint = 0 // 0-based
129 for _,query := range queries {
131 //println("**warning: idx [",idx,"] contains a nil query")
134 msgs := query.SearchMessages()
135 ht := addresses_by_frequency(msgs, name, pass, &addr_to_realname)
136 for addr, count := range *ht {
137 freq,ok := addr_freq[addr]
139 freq = &mail_addr_freq{addr:addr, count:[3]uint{0,0,0}}
141 freq.count[pass] = count
142 addr_freq[addr] = freq
148 addrs := make(maddresses, len(addr_freq))
151 for _, freq := range addr_freq {
158 for _,addr := range addrs {
159 freqs,ok := addr_to_realname[addr.addr]
161 val = append(val, frequent_fullname(*freqs))
163 val = append(val, addr.addr)
166 //println("val:",val)
170 type address_matcher struct {
171 // the notmuch database
173 // full path of the notmuch database
175 // user primary email
176 user_primary_email string
177 // user tag to mark from addresses as in the address book
178 user_addrbook_tag string
181 func new_address_matcher() *address_matcher {
182 var cfg *config.Config
185 // honor NOTMUCH_CONFIG
186 home := os.Getenv("NOTMUCH_CONFIG")
188 home = os.Getenv("HOME")
191 if cfg,err = config.ReadDefault(path.Join(home, ".notmuch-config")); err != nil {
192 log.Exitf("error loading config file:",err)
195 db_path,_ := cfg.String("database", "path")
196 primary_email,_ := cfg.String("user", "primary_email")
197 addrbook_tag,err := cfg.String("user", "addrbook_tag")
199 addrbook_tag = "addressbook"
202 self := &address_matcher{db:nil,
203 user_db_path:db_path,
204 user_primary_email:primary_email,
205 user_addrbook_tag:addrbook_tag}
209 func (self *address_matcher) run(name string) {
210 queries := [3]*notmuch.Query{}
213 self.db = notmuch.OpenDatabase(self.user_db_path,
214 notmuch.DATABASE_MODE_READ_ONLY)
216 // pass 1: look at all from: addresses with the address book tag
217 query := "tag:" + self.user_addrbook_tag
219 query = query + " and from:" + name + "*"
221 queries[0] = self.db.CreateQuery(query)
223 // pass 2: look at all to: addresses sent from our primary mail
226 query = "to:"+name+"*"
228 if self.user_primary_email != "" {
229 query = query + " from:" + self.user_primary_email
231 queries[1] = self.db.CreateQuery(query)
233 // if that leads only to a few hits, we check every from too
234 if queries[0].CountMessages() + queries[1].CountMessages() < 10 {
237 query = "from:"+name+"*"
239 queries[2] = self.db.CreateQuery(query)
242 // actually retrieve and sort addresses
243 results := search_address_passes(queries, name)
244 for _,v := range results {
245 if v != "" && v != "\n" {
253 //fmt.Println("args:",os.Args)
254 app := new_address_matcher()
256 if len(os.Args) > 1 {