gmail-britta 0.1.4 → 0.1.5
Sign up to get free protection for your applications and to get access to all the features.
- data/.yardopts +1 -0
- data/Gemfile +2 -0
- data/Gemfile.lock +4 -0
- data/README.md +186 -0
- data/Rakefile +2 -8
- data/VERSION +1 -1
- data/gmail-britta.gemspec +12 -5
- data/lib/gmail-britta.rb +11 -39
- data/lib/gmail-britta/filter.rb +157 -66
- data/lib/gmail-britta/filter_set.rb +82 -0
- data/lib/gmail-britta/single_write_accessors.rb +1 -0
- metadata +49 -16
- data/README.rdoc +0 -24
- data/lib/gmail-britta/delegate.rb +0 -17
data/.yardopts
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
--markup markdown
|
data/Gemfile
CHANGED
data/Gemfile.lock
CHANGED
@@ -20,12 +20,14 @@ GEM
|
|
20
20
|
rcov (1.0.0)
|
21
21
|
rdoc (3.12)
|
22
22
|
json (~> 1.4)
|
23
|
+
redcarpet (2.2.2)
|
23
24
|
shoulda (3.1.1)
|
24
25
|
shoulda-context (~> 1.0)
|
25
26
|
shoulda-matchers (~> 1.2)
|
26
27
|
shoulda-context (1.0.0)
|
27
28
|
shoulda-matchers (1.3.0)
|
28
29
|
activesupport (>= 3.0.0)
|
30
|
+
yard (0.8.3)
|
29
31
|
|
30
32
|
PLATFORMS
|
31
33
|
ruby
|
@@ -38,4 +40,6 @@ DEPENDENCIES
|
|
38
40
|
nokogiri
|
39
41
|
rcov
|
40
42
|
rdoc (~> 3.12)
|
43
|
+
redcarpet (~> 2.2.2)
|
41
44
|
shoulda
|
45
|
+
yard (~> 0.8.3)
|
data/README.md
ADDED
@@ -0,0 +1,186 @@
|
|
1
|
+
# gmail-britta
|
2
|
+
|
3
|
+
This library helps you generate XML that you can import into gmail's
|
4
|
+
filter settings. It makes it more pleasant to write simple (and even
|
5
|
+
complex) filter chains.
|
6
|
+
|
7
|
+
## Motivation
|
8
|
+
|
9
|
+
As a user of the internet, you probably use gmail, and you probably
|
10
|
+
get a lot of Email - I do. Just like me, you're also likely to have a
|
11
|
+
lot of filters that were once supposed to keep your inbox
|
12
|
+
clean. But every time you click "Filter messages like this", you
|
13
|
+
remember the enormous list of filters you have and how it is only ever
|
14
|
+
going to make things worse in the long run.
|
15
|
+
|
16
|
+
### But how do you make them better?
|
17
|
+
|
18
|
+
As a programmer, I write code. As a metaprogrammer, I write code that
|
19
|
+
writes code. If only gmail filters could be written by a program. Oh
|
20
|
+
wait, [they
|
21
|
+
can](http://gmailblog.blogspot.com/2009/03/new-in-labs-filter-importexport.html).
|
22
|
+
There's an import & export function for gmail filters. These filters
|
23
|
+
are exported as XML, and the import in turn takes XML files. What this
|
24
|
+
library does is generate XML that you can import.
|
25
|
+
|
26
|
+
## Examples
|
27
|
+
|
28
|
+
Here are some very simple examples of how gmail-britta can make your
|
29
|
+
life easier:
|
30
|
+
|
31
|
+
### Archiving (or not) semi-important email
|
32
|
+
|
33
|
+
Imagine you're on a discussion mailing list that you skim from time to
|
34
|
+
time - so you want to label email to that list, and archive email from
|
35
|
+
it so it doesn't clog up your inbox. Now, from time to time someone
|
36
|
+
will Cc: you an important message, and you don't want to miss these -
|
37
|
+
what to do?
|
38
|
+
|
39
|
+
Naively, you define a filter like this:
|
40
|
+
|
41
|
+
```
|
42
|
+
list:discuss@lists.some-side-project.org !to:thisisme@mydomain.com
|
43
|
+
-> Archive, Label "some-side-project"
|
44
|
+
```
|
45
|
+
|
46
|
+
But this also means email addressed to you but also sent to that list
|
47
|
+
will be missing the "some-side-project" label. This means you need two
|
48
|
+
filters!
|
49
|
+
|
50
|
+
```
|
51
|
+
list:discuss@lists.some-side-project.org !to:thisisme@mydomain.com
|
52
|
+
-> Archive
|
53
|
+
list:discuss@lists.some-side-project.org
|
54
|
+
-> Label "some-side-project"
|
55
|
+
```
|
56
|
+
|
57
|
+
Now this can get pretty hairy pretty quickly; and if you ever have to
|
58
|
+
change the filter (e.g., the list changes email addresses), you have
|
59
|
+
to change two filters! Ugh.
|
60
|
+
|
61
|
+
With gmail-britta, it's just this:
|
62
|
+
|
63
|
+
``` ruby
|
64
|
+
GmailBritta.filterset do
|
65
|
+
filter {
|
66
|
+
has %w{list:discuss@lists.some-side-project.org}
|
67
|
+
}.archive_unless_directed
|
68
|
+
end
|
69
|
+
```
|
70
|
+
|
71
|
+
That's it - this little piece of ruby code will generate these two
|
72
|
+
filters; and if the list address changes, you just re-import these
|
73
|
+
filters. Easy.
|
74
|
+
|
75
|
+
### Better detection of your own email address
|
76
|
+
|
77
|
+
If you have issued a search for `to:me` on gmail in the last few
|
78
|
+
months (and you get Email from iCloud nee MobileMe users), you'll
|
79
|
+
notice that `to:me` also matches the "me" in `example@me.com`. This is
|
80
|
+
very frustrating mostly because any filter as above that matches on
|
81
|
+
`to:me` will now also not archive email sent from a me.com
|
82
|
+
account. With gmail-britta, you can define a set of emails that are
|
83
|
+
yours, and `archive_unless_directed` will generate a filter that
|
84
|
+
matches on those:
|
85
|
+
|
86
|
+
``` ruby
|
87
|
+
GmailBritta.filterset(:me => ['thisisme@my-private.org', 'this-is-me@bigco.example.com']) do
|
88
|
+
filter {
|
89
|
+
has %w{list:discuss@lists.some-side-project.org}
|
90
|
+
}.archive_unless_directed
|
91
|
+
end
|
92
|
+
```
|
93
|
+
|
94
|
+
### If/else chains on filters
|
95
|
+
|
96
|
+
There are instances where you can't only match on the mailing list -
|
97
|
+
sometimes interesting stuff will be sent to one list (that you want to
|
98
|
+
read), and lots of unnecessary silliness will be sent there too. So,
|
99
|
+
to put email with a certain subject into one label, and to put all the
|
100
|
+
rest into another, you have to make multiple filters. But gmail lacks
|
101
|
+
if/else in filters - you have to duplicate and correctly negate each
|
102
|
+
filter's condition in the next one. Luckily, you have gmail-britta to
|
103
|
+
help you out there:
|
104
|
+
|
105
|
+
``` ruby
|
106
|
+
GmailBritta.filterset do
|
107
|
+
filter {
|
108
|
+
has %w{list:robots@bigco.com subject:Important}
|
109
|
+
label 'work/robots/important'
|
110
|
+
# Do not archive
|
111
|
+
}.otherwise {
|
112
|
+
has %w{list:robots@bigco.com subject:Chunder}
|
113
|
+
label 'work/robots/irrelevant'
|
114
|
+
}.archive_unless_directed(:mark_read => true).otherwise {
|
115
|
+
has %w{list:robots@bigco.com subject:Semirelevant}
|
116
|
+
label 'work/robots/meh'
|
117
|
+
}.archive_unless_directed
|
118
|
+
end
|
119
|
+
```
|
120
|
+
|
121
|
+
A lot of things going on here! First, I hope you notice that this
|
122
|
+
looks similar to an if/else-if statement. Second, you may notice the
|
123
|
+
liberal application of `.archive_unless_directed`, with an optional
|
124
|
+
`:mark_read` parameter. Third, do check out the XML generated by this,
|
125
|
+
it's pretty horriffic - but the filters do exactly what the code says:
|
126
|
+
|
127
|
+
1. If the email is sent to that list and has "Important" in the subject, it gets labeled with the work/robots/important label, and won't be archived (unless another filter further down does archive that email).
|
128
|
+
2. If the email does not match all the above criteria, but is sent to that list and has "Chunder" in the subject, label it irrelevant, archive it and mark the email as read.
|
129
|
+
3. If the email does not match all the above criteria, but is sent to that list and has "Semirelevant" in the subject, label it "meh" and archive it, but don't mark it as read.
|
130
|
+
|
131
|
+
## Installation & usage
|
132
|
+
|
133
|
+
Best to use gmail-britta from a ruby script; but install the gem first:
|
134
|
+
|
135
|
+
gem install gmail-britta
|
136
|
+
|
137
|
+
Or include it in your Gemfile:
|
138
|
+
|
139
|
+
gem 'gmail-britta'
|
140
|
+
|
141
|
+
This is a simple script that generates filters for you:
|
142
|
+
|
143
|
+
``` ruby
|
144
|
+
#!/bin/env ruby
|
145
|
+
|
146
|
+
require 'rubygems'
|
147
|
+
require 'gmail-britta'
|
148
|
+
|
149
|
+
fs = GmailBritta.filterset(:me => ['thisisme@my-private.org']) do
|
150
|
+
filter {
|
151
|
+
has %w{from:asf@boinkor.net}
|
152
|
+
label 'from-the-author-of-gmail-britta'
|
153
|
+
never_spam
|
154
|
+
}
|
155
|
+
end
|
156
|
+
puts fs.generate
|
157
|
+
```
|
158
|
+
|
159
|
+
Running this script will write to stdout the filter XML that you
|
160
|
+
need. Redirect that to a file and upload it on the bottom of your
|
161
|
+
gmail filter settings, and you'll label email from me and archive it
|
162
|
+
unless I email you specifically. Not that useful, but with your own
|
163
|
+
filters and the recipes above, you should be able to make it work (-:
|
164
|
+
|
165
|
+
## A short apology to you, dear code-diver
|
166
|
+
|
167
|
+
A lot of this is a bit hacky (particularly the filter condition merge
|
168
|
+
logic), as it started as a oneoff ruby script - very sorry for any
|
169
|
+
amateurish code you may encounter. There are few tests, but so far, it
|
170
|
+
does work for me. Let me know if this made your life harder or easier!
|
171
|
+
(-:
|
172
|
+
|
173
|
+
## Contributing to gmail-britta
|
174
|
+
|
175
|
+
* Check out the latest master to make sure the feature hasn't been implemented or the bug hasn't been fixed yet.
|
176
|
+
* Check out the issue tracker to make sure someone already hasn't requested it and/or contributed it.
|
177
|
+
* Fork the project.
|
178
|
+
* Start a feature/bugfix branch.
|
179
|
+
* Commit and push until you are happy with your contribution.
|
180
|
+
* Make sure to add tests for it. (Note that are only very few tests yet. I would totally appreciate if you started fleshing out this suite, but I wouldn't expect you to add much of anything.)
|
181
|
+
* Please try not to mess with the Rakefile, version, or history. If you want to have your own version, or is otherwise necessary, that is fine, but please isolate to its own commit so I can cherry-pick around it.
|
182
|
+
|
183
|
+
## Copyright
|
184
|
+
|
185
|
+
Copyright (c) 2012 Andreas Fuchs. See LICENSE.txt for
|
186
|
+
further details.
|
data/Rakefile
CHANGED
@@ -42,12 +42,6 @@ end
|
|
42
42
|
|
43
43
|
task :default => :test
|
44
44
|
|
45
|
-
require '
|
46
|
-
Rake::RDocTask.new do |rdoc|
|
47
|
-
version = File.exist?('VERSION') ? File.read('VERSION') : ""
|
45
|
+
require 'yard'
|
48
46
|
|
49
|
-
|
50
|
-
rdoc.title = "gmail-britta #{version}"
|
51
|
-
rdoc.rdoc_files.include('README*')
|
52
|
-
rdoc.rdoc_files.include('lib/**/*.rb')
|
53
|
-
end
|
47
|
+
YARD::Rake::YardocTask.new
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.1.
|
1
|
+
0.1.5
|
data/gmail-britta.gemspec
CHANGED
@@ -5,29 +5,30 @@
|
|
5
5
|
|
6
6
|
Gem::Specification.new do |s|
|
7
7
|
s.name = "gmail-britta"
|
8
|
-
s.version = "0.1.
|
8
|
+
s.version = "0.1.5"
|
9
9
|
|
10
10
|
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
11
|
s.authors = ["Andreas Fuchs"]
|
12
|
-
s.date = "2012-12-
|
12
|
+
s.date = "2012-12-23"
|
13
13
|
s.description = "This gem helps create large (>50) gmail filter chains by writing xml compatible with gmail's \"import/export filters\" feature."
|
14
14
|
s.email = "asf@boinkor.net"
|
15
15
|
s.extra_rdoc_files = [
|
16
16
|
"LICENSE.txt",
|
17
|
-
"README.
|
17
|
+
"README.md"
|
18
18
|
]
|
19
19
|
s.files = [
|
20
20
|
".document",
|
21
|
+
".yardopts",
|
21
22
|
"Gemfile",
|
22
23
|
"Gemfile.lock",
|
23
24
|
"LICENSE.txt",
|
24
|
-
"README.
|
25
|
+
"README.md",
|
25
26
|
"Rakefile",
|
26
27
|
"VERSION",
|
27
28
|
"gmail-britta.gemspec",
|
28
29
|
"lib/gmail-britta.rb",
|
29
|
-
"lib/gmail-britta/delegate.rb",
|
30
30
|
"lib/gmail-britta/filter.rb",
|
31
|
+
"lib/gmail-britta/filter_set.rb",
|
31
32
|
"lib/gmail-britta/single_write_accessors.rb",
|
32
33
|
"test/test_gmail-britta.rb"
|
33
34
|
]
|
@@ -44,6 +45,8 @@ Gem::Specification.new do |s|
|
|
44
45
|
s.add_runtime_dependency(%q<haml>, ["~> 3.1.6"])
|
45
46
|
s.add_development_dependency(%q<shoulda>, [">= 0"])
|
46
47
|
s.add_development_dependency(%q<rdoc>, ["~> 3.12"])
|
48
|
+
s.add_development_dependency(%q<yard>, ["~> 0.8.3"])
|
49
|
+
s.add_development_dependency(%q<redcarpet>, ["~> 2.2.2"])
|
47
50
|
s.add_development_dependency(%q<bundler>, ["~> 1.2.0"])
|
48
51
|
s.add_development_dependency(%q<jeweler>, ["~> 1.8.4"])
|
49
52
|
s.add_development_dependency(%q<rcov>, [">= 0"])
|
@@ -53,6 +56,8 @@ Gem::Specification.new do |s|
|
|
53
56
|
s.add_dependency(%q<haml>, ["~> 3.1.6"])
|
54
57
|
s.add_dependency(%q<shoulda>, [">= 0"])
|
55
58
|
s.add_dependency(%q<rdoc>, ["~> 3.12"])
|
59
|
+
s.add_dependency(%q<yard>, ["~> 0.8.3"])
|
60
|
+
s.add_dependency(%q<redcarpet>, ["~> 2.2.2"])
|
56
61
|
s.add_dependency(%q<bundler>, ["~> 1.2.0"])
|
57
62
|
s.add_dependency(%q<jeweler>, ["~> 1.8.4"])
|
58
63
|
s.add_dependency(%q<rcov>, [">= 0"])
|
@@ -63,6 +68,8 @@ Gem::Specification.new do |s|
|
|
63
68
|
s.add_dependency(%q<haml>, ["~> 3.1.6"])
|
64
69
|
s.add_dependency(%q<shoulda>, [">= 0"])
|
65
70
|
s.add_dependency(%q<rdoc>, ["~> 3.12"])
|
71
|
+
s.add_dependency(%q<yard>, ["~> 0.8.3"])
|
72
|
+
s.add_dependency(%q<redcarpet>, ["~> 2.2.2"])
|
66
73
|
s.add_dependency(%q<bundler>, ["~> 1.2.0"])
|
67
74
|
s.add_dependency(%q<jeweler>, ["~> 1.8.4"])
|
68
75
|
s.add_dependency(%q<rcov>, [">= 0"])
|
data/lib/gmail-britta.rb
CHANGED
@@ -11,50 +11,22 @@ require 'haml'
|
|
11
11
|
require 'logger'
|
12
12
|
|
13
13
|
require 'gmail-britta/single_write_accessors'
|
14
|
-
require 'gmail-britta/
|
14
|
+
require 'gmail-britta/filter_set'
|
15
15
|
require 'gmail-britta/filter'
|
16
16
|
|
17
|
+
# # A generator DSL for importable gmail filter specifications.
|
18
|
+
#
|
19
|
+
# This is the main entry point for defining a filter set (multiple filters). See {.filterset} for details.
|
17
20
|
module GmailBritta
|
18
|
-
class Britta
|
19
|
-
def initialize(opts={})
|
20
|
-
@filters = []
|
21
|
-
@me = opts[:me] || 'me'
|
22
|
-
@logger = opts[:logger] || allocate_logger
|
23
|
-
end
|
24
|
-
|
25
|
-
def allocate_logger
|
26
|
-
logger = Logger.new(STDERR)
|
27
|
-
logger.level = Logger::WARN
|
28
|
-
logger
|
29
|
-
end
|
30
|
-
|
31
|
-
attr_accessor :filters
|
32
|
-
attr_accessor :me
|
33
|
-
attr_accessor :logger
|
34
|
-
|
35
|
-
def rules(&block)
|
36
|
-
GmailBritta::Delegate.new(self, :logger => @logger).perform(&block)
|
37
|
-
end
|
38
|
-
|
39
|
-
def generate
|
40
|
-
engine = Haml::Engine.new(<<-ATOM)
|
41
|
-
!!! XML
|
42
|
-
%feed{:xmlns => 'http://www.w3.org/2005/Atom', 'xmlns:apps' => 'http://schemas.google.com/apps/2006'}
|
43
|
-
%title Mail Filters
|
44
|
-
%id tag:mail.google.com,2008:filters:
|
45
|
-
%updated #{Time.now.utc.iso8601}
|
46
|
-
%author
|
47
|
-
%name Andreas Fuchs
|
48
|
-
%email asf@boinkor.net
|
49
|
-
- filters.each do |filter|
|
50
|
-
!= filter.generate_xml
|
51
|
-
ATOM
|
52
|
-
engine.render(self)
|
53
|
-
end
|
54
|
-
end
|
55
21
|
|
22
|
+
# Create a {FilterSet} and run the filter set definition in the block.
|
23
|
+
# This is the main entry point for GmailBritta.
|
24
|
+
# @option opts :me [Array<String>] A list of email addresses that should be considered as belonging to "you", effectively those email addresses you would expect `to:me` to match.
|
25
|
+
# @option opts :logger [Logger] (Logger.new()) An initialized logger instance.
|
26
|
+
# @yield the filterset definition block. `self` inside the block is the {FilterSet} instance.
|
27
|
+
# @return [FilterSet] the constructed filterset
|
56
28
|
def self.filterset(opts={}, &block)
|
57
|
-
(britta =
|
29
|
+
(britta = FilterSet.new(opts)).rules(&block)
|
58
30
|
britta
|
59
31
|
end
|
60
32
|
end
|
data/lib/gmail-britta/filter.rb
CHANGED
@@ -1,27 +1,137 @@
|
|
1
1
|
module GmailBritta
|
2
|
+
# This class specifies the behavior of a single filter
|
3
|
+
# definition. Create a filter object in the DSL via
|
4
|
+
# {FilterSet::Delegator#filter} or use {Filter#also},
|
5
|
+
# {Filter#otherwise} or {Filter#archive_unless_directed} to make a
|
6
|
+
# new filter based on another one.
|
7
|
+
# @todo this probably needs some explanatory docs to make it understandable.
|
8
|
+
|
2
9
|
class Filter
|
3
10
|
include SingleWriteAccessors
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
11
|
+
|
12
|
+
# @!group Methods for use in a filter definition block
|
13
|
+
# Archive the message.
|
14
|
+
# @!macro [new] bool_dsl_method
|
15
|
+
# @return [void]
|
16
|
+
# @!method $1()
|
10
17
|
single_write_boolean_accessor :archive, 'shouldArchive'
|
18
|
+
|
19
|
+
# Move the message to the trash.
|
20
|
+
# @macro bool_dsl_method
|
11
21
|
single_write_boolean_accessor :delete_it, 'shouldTrash'
|
22
|
+
|
23
|
+
# Mark the message as read.
|
24
|
+
# @macro bool_dsl_method
|
12
25
|
single_write_boolean_accessor :mark_read, 'shouldMarkAsRead'
|
26
|
+
|
27
|
+
# Mark the message as important.
|
28
|
+
# @macro bool_dsl_method
|
13
29
|
single_write_boolean_accessor :mark_important, 'shouldAlwaysMarkAsImportant'
|
30
|
+
|
31
|
+
# Do not mark the message as important.
|
32
|
+
# @macro bool_dsl_method
|
14
33
|
single_write_boolean_accessor :mark_unimportant, 'shouldNeverMarkAsImportant'
|
34
|
+
|
35
|
+
# Star the message
|
36
|
+
# @macro bool_dsl_method
|
15
37
|
single_write_boolean_accessor :star, 'shouldStar'
|
38
|
+
|
39
|
+
# Never mark the message as spam
|
40
|
+
# @macro bool_dsl_method
|
16
41
|
single_write_boolean_accessor :never_spam, 'shouldNeverSpam'
|
42
|
+
|
43
|
+
# Assign the given label to the message
|
44
|
+
# @return [void]
|
45
|
+
# @!method label(label)
|
46
|
+
# @param [String] label the label to assign the message
|
17
47
|
single_write_accessor :label, 'label'
|
48
|
+
|
49
|
+
# Forward the message to the given label.
|
50
|
+
# @return [void]
|
51
|
+
# @!method forward_to(email)
|
52
|
+
# @param [String] email an email address to forward the message to
|
18
53
|
single_write_accessor :forward_to, 'forwardTo'
|
19
54
|
|
55
|
+
# @!method has(conditions)
|
56
|
+
# @return [void]
|
57
|
+
# Defines the positive conditions for the filter to match.
|
58
|
+
# @overload has([conditions])
|
59
|
+
# Conditions ANDed together that an incoming email must match.
|
60
|
+
# @param [Array<conditions>] conditions a list of gmail search terms, all of which must match
|
61
|
+
# @overload has({:or => [conditions]})
|
62
|
+
# Conditions ORed together for the filter to match
|
63
|
+
# @param [{:or => conditions}] conditions a hash of the form `{:or => [condition1, condition2]}` - either of these conditions must match to match the filter.
|
64
|
+
single_write_accessor :has, 'hasTheWord' do |list|
|
65
|
+
emit_filter_spec(list)
|
66
|
+
end
|
67
|
+
|
68
|
+
# @!method has_not(conditions)
|
69
|
+
# @return [void]
|
70
|
+
# Defines the negative conditions that must not match for the filter to be allowed to match.
|
71
|
+
single_write_accessor :has_not, 'doesNotHaveTheWord' do |list|
|
72
|
+
emit_filter_spec(list)
|
73
|
+
end
|
74
|
+
# @!endgroup
|
75
|
+
|
76
|
+
# Register and return a new filter that matches only if this
|
77
|
+
# filter's conditions (those that are not duplicated on the new
|
78
|
+
# filter's `has` clause) do not match.
|
79
|
+
# @yield The filter definition block
|
80
|
+
# @return [Filter] the new filter
|
81
|
+
def otherwise(&block)
|
82
|
+
filter = Filter.new(@britta, :log => @log).perform(&block)
|
83
|
+
filter.merge_negated_criteria(self)
|
84
|
+
filter.log_definition
|
85
|
+
filter
|
86
|
+
end
|
87
|
+
|
88
|
+
# Register and return a new filter that matches a message only if
|
89
|
+
# this filter's conditions *and* the previous filter's condition
|
90
|
+
# match.
|
91
|
+
# @yield The filter definition block
|
92
|
+
# @return [Filter] the new filter
|
93
|
+
def also(&block)
|
94
|
+
filter = Filter.new(@britta, :log => @log).perform(&block)
|
95
|
+
filter.merge_positive_criteria(self)
|
96
|
+
filter.log_definition
|
97
|
+
filter
|
98
|
+
end
|
99
|
+
|
100
|
+
# Register (but don't return) a filter that archives the message
|
101
|
+
# unless it matches the `:to` email addresses. Optionally, mark
|
102
|
+
# the message as read if this filter matches.
|
103
|
+
#
|
104
|
+
# @note This method returns the previous filter to make it easier to construct filter chains with {#otherwise} and {#also}.
|
105
|
+
#
|
106
|
+
# @option options [true, false] :mark_read If true, mark the message as read
|
107
|
+
# @option options [Array<String>] :to a list of addresses that the message may be addressed to in order to prevent this filter from matching. Defaults to the value given to :me on {GmailBritta.filterset}.
|
108
|
+
# @return [Filter] the current (not the newly-constructed filter)
|
109
|
+
def archive_unless_directed(options={})
|
110
|
+
mark_as_read=options[:mark_read]
|
111
|
+
tos=(options[:to] || me).to_a
|
112
|
+
filter = Filter.new(@britta, :log => @log).perform do
|
113
|
+
has_not [{:or => tos.map {|to| "to:#{to}"}}]
|
114
|
+
archive
|
115
|
+
if mark_as_read
|
116
|
+
mark_read
|
117
|
+
end
|
118
|
+
end
|
119
|
+
filter.merge_positive_criteria(self)
|
120
|
+
filter.log_definition
|
121
|
+
self
|
122
|
+
end
|
123
|
+
|
124
|
+
# Create a new filter object
|
125
|
+
# @note Over the lifetime of {GmailBritta}, new {Filter}s usually get created only by the {Delegate}.
|
126
|
+
# @param [GmailBritta::Britta] britta the filterset object
|
127
|
+
# @option options :log [Logger] a logger for debug messages
|
20
128
|
def initialize(britta, options={})
|
21
129
|
@britta = britta
|
22
130
|
@log = options[:log]
|
23
131
|
end
|
24
132
|
|
133
|
+
# Return the filter's value as XML text.
|
134
|
+
# @return [String] the Atom XML representation of this filter
|
25
135
|
def generate_xml
|
26
136
|
engine = Haml::Engine.new(<<-ATOM)
|
27
137
|
%entry
|
@@ -36,49 +146,19 @@ ATOM
|
|
36
146
|
engine.render(self)
|
37
147
|
end
|
38
148
|
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
when Hash
|
45
|
-
filter.keys.each do |key|
|
46
|
-
case key
|
47
|
-
when :or
|
48
|
-
str << '('
|
49
|
-
str << emit_filter_spec(filter[key], ' OR ')
|
50
|
-
str << ')'
|
51
|
-
when :not
|
52
|
-
str << '-('
|
53
|
-
str << emit_filter_spec(filter[key], ' ')
|
54
|
-
str << ')'
|
55
|
-
end
|
56
|
-
end
|
57
|
-
when Array
|
58
|
-
str << filter.map {|elt| emit_filter_spec(elt, ' ')}.join(infix)
|
59
|
-
end
|
60
|
-
str
|
61
|
-
end
|
62
|
-
|
63
|
-
def me
|
64
|
-
@britta.me
|
65
|
-
end
|
66
|
-
|
67
|
-
def log_definition
|
68
|
-
@log.debug "Filter: #{self}"
|
69
|
-
Filter.single_write_accessors.each do |name|
|
70
|
-
val = instance_variable_get(Filter.ivar_name(name))
|
71
|
-
@log.debug " #{name}: #{val}" if val
|
72
|
-
end
|
73
|
-
self
|
74
|
-
end
|
75
|
-
|
149
|
+
# Evaluate block as a filter definition block and register `self` as a filter on the containing {FilterSet}
|
150
|
+
# @note this method gets called by {Delegate#filter} to create and register a new filter object
|
151
|
+
# @yield The filter definition. `self` in the block is the new filter object.
|
152
|
+
# @api private
|
153
|
+
# @return [Filter] the filter that
|
76
154
|
def perform(&block)
|
77
155
|
instance_eval(&block)
|
78
156
|
@britta.filters << self
|
79
157
|
self
|
80
158
|
end
|
81
159
|
|
160
|
+
protected
|
161
|
+
|
82
162
|
def merge_negated_criteria(filter)
|
83
163
|
old_has_not = Marshal.load(Marshal.dump((filter.get_has_not || []).reject { |elt|
|
84
164
|
@has.member?(elt)
|
@@ -103,13 +183,6 @@ ATOM
|
|
103
183
|
@log.debug(" M: nhn #{@has_not.inspect}")
|
104
184
|
end
|
105
185
|
|
106
|
-
def otherwise(&block)
|
107
|
-
filter = Filter.new(@britta, :log => @log).perform(&block)
|
108
|
-
filter.merge_negated_criteria(self)
|
109
|
-
filter.log_definition
|
110
|
-
filter
|
111
|
-
end
|
112
|
-
|
113
186
|
def merge_positive_criteria(filter)
|
114
187
|
new_has = (@has || []) + (filter.get_has || [])
|
115
188
|
new_has_not = (@has_not || []) + (filter.get_has_not || [])
|
@@ -117,26 +190,44 @@ ATOM
|
|
117
190
|
@has_not = new_has_not
|
118
191
|
end
|
119
192
|
|
120
|
-
def
|
121
|
-
|
122
|
-
filter
|
123
|
-
|
124
|
-
|
193
|
+
def self.emit_filter_spec(filter, infix=' ')
|
194
|
+
str = ''
|
195
|
+
case filter
|
196
|
+
when String
|
197
|
+
str << filter
|
198
|
+
when Hash
|
199
|
+
filter.keys.each do |key|
|
200
|
+
case key
|
201
|
+
when :or
|
202
|
+
str << '('
|
203
|
+
str << emit_filter_spec(filter[key], ' OR ')
|
204
|
+
str << ')'
|
205
|
+
when :not
|
206
|
+
str << '-('
|
207
|
+
str << emit_filter_spec(filter[key], ' ')
|
208
|
+
str << ')'
|
209
|
+
end
|
210
|
+
end
|
211
|
+
when Array
|
212
|
+
str << filter.map {|elt| emit_filter_spec(elt, ' ')}.join(infix)
|
213
|
+
end
|
214
|
+
str
|
125
215
|
end
|
126
216
|
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
if
|
134
|
-
mark_read
|
135
|
-
end
|
217
|
+
# Note a filter definition on the logger.
|
218
|
+
# @note for debugging only.
|
219
|
+
def log_definition
|
220
|
+
@log.debug "Filter: #{self}"
|
221
|
+
Filter.single_write_accessors.each do |name|
|
222
|
+
val = instance_variable_get(Filter.ivar_name(name))
|
223
|
+
@log.debug " #{name}: #{val}" if val
|
136
224
|
end
|
137
|
-
filter.merge_positive_criteria(self)
|
138
|
-
filter.log_definition
|
139
225
|
self
|
140
226
|
end
|
227
|
+
|
228
|
+
# Return the list of emails that the filterset has configured as "me".
|
229
|
+
def me
|
230
|
+
@britta.me
|
231
|
+
end
|
141
232
|
end
|
142
233
|
end
|
@@ -0,0 +1,82 @@
|
|
1
|
+
module GmailBritta
|
2
|
+
class FilterSet
|
3
|
+
def initialize(opts={})
|
4
|
+
@filters = []
|
5
|
+
@me = opts[:me] || 'me'
|
6
|
+
@logger = opts[:logger] || allocate_logger
|
7
|
+
end
|
8
|
+
|
9
|
+
# Currently defined filters
|
10
|
+
# @see Delegate#filter
|
11
|
+
# @see GmailBritta::Filter#otherwise
|
12
|
+
# @see GmailBritta::Filter#also
|
13
|
+
# @see GmailBritta::Filter#archive_unless_directed
|
14
|
+
attr_accessor :filters
|
15
|
+
|
16
|
+
# The list of emails that belong to the user running this {FilterSet} definition
|
17
|
+
# @see GmailBritta.filterset
|
18
|
+
attr_accessor :me
|
19
|
+
|
20
|
+
# The logger currently being used for debug output
|
21
|
+
# @see GmailBritta.filterset
|
22
|
+
attr_accessor :logger
|
23
|
+
|
24
|
+
# Run the block that defines the filters in {Delegate}'s `instance_eval`. This method will typically only be called by {GmailBritta.filterset}.
|
25
|
+
# @api private
|
26
|
+
# @yield the filter definition block in {Delegate}'s instance_eval.
|
27
|
+
def rules(&block)
|
28
|
+
Delegate.new(self, :logger => @logger).perform(&block)
|
29
|
+
end
|
30
|
+
|
31
|
+
# Generate ATOM XML for the defined filter set and return it as a String.
|
32
|
+
# @return [String] the generated XML, ready for importing into Gmail.
|
33
|
+
def generate
|
34
|
+
engine = Haml::Engine.new(<<-ATOM)
|
35
|
+
!!! XML
|
36
|
+
%feed{:xmlns => 'http://www.w3.org/2005/Atom', 'xmlns:apps' => 'http://schemas.google.com/apps/2006'}
|
37
|
+
%title Mail Filters
|
38
|
+
%id tag:mail.google.com,2008:filters:
|
39
|
+
%updated #{Time.now.utc.iso8601}
|
40
|
+
%author
|
41
|
+
%name Andreas Fuchs
|
42
|
+
%email asf@boinkor.net
|
43
|
+
- filters.each do |filter|
|
44
|
+
!= filter.generate_xml
|
45
|
+
ATOM
|
46
|
+
engine.render(self)
|
47
|
+
end
|
48
|
+
|
49
|
+
# A class whose sole purpose it is to be the `self` in a {FilterSet} definition block.
|
50
|
+
class Delegate
|
51
|
+
|
52
|
+
# @api private
|
53
|
+
def initialize(britta, options={})
|
54
|
+
@britta = britta
|
55
|
+
@log = options[:logger]
|
56
|
+
@filter = nil
|
57
|
+
end
|
58
|
+
|
59
|
+
# Create, register and return a new {Filter} without any merged conditions
|
60
|
+
# @yield [] the {Filter} definition block, with the new {Filter} instance as `self`.
|
61
|
+
# @return [Filtere] the new filter.
|
62
|
+
def filter(&block)
|
63
|
+
GmailBritta::Filter.new(@britta, :log => @log).perform(&block)
|
64
|
+
end
|
65
|
+
|
66
|
+
# Evaluate the {FilterSet} definition block with the {Delegate} object as `self`
|
67
|
+
# @api private
|
68
|
+
# @note this method will typically only be called by {FilterSet#rules}
|
69
|
+
# @yield [ ] that filterset definition block
|
70
|
+
def perform(&block)
|
71
|
+
instance_eval(&block)
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
private
|
76
|
+
def allocate_logger
|
77
|
+
logger = Logger.new(STDERR)
|
78
|
+
logger.level = Logger::WARN
|
79
|
+
logger
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: gmail-britta
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
hash:
|
4
|
+
hash: 17
|
5
5
|
prerelease:
|
6
6
|
segments:
|
7
7
|
- 0
|
8
8
|
- 1
|
9
|
-
-
|
10
|
-
version: 0.1.
|
9
|
+
- 5
|
10
|
+
version: 0.1.5
|
11
11
|
platform: ruby
|
12
12
|
authors:
|
13
13
|
- Andreas Fuchs
|
@@ -15,7 +15,7 @@ autorequire:
|
|
15
15
|
bindir: bin
|
16
16
|
cert_chain: []
|
17
17
|
|
18
|
-
date: 2012-12-
|
18
|
+
date: 2012-12-23 00:00:00 Z
|
19
19
|
dependencies:
|
20
20
|
- !ruby/object:Gem::Dependency
|
21
21
|
version_requirements: &id001 !ruby/object:Gem::Requirement
|
@@ -64,6 +64,38 @@ dependencies:
|
|
64
64
|
requirement: *id003
|
65
65
|
- !ruby/object:Gem::Dependency
|
66
66
|
version_requirements: &id004 !ruby/object:Gem::Requirement
|
67
|
+
none: false
|
68
|
+
requirements:
|
69
|
+
- - ~>
|
70
|
+
- !ruby/object:Gem::Version
|
71
|
+
hash: 57
|
72
|
+
segments:
|
73
|
+
- 0
|
74
|
+
- 8
|
75
|
+
- 3
|
76
|
+
version: 0.8.3
|
77
|
+
prerelease: false
|
78
|
+
type: :development
|
79
|
+
name: yard
|
80
|
+
requirement: *id004
|
81
|
+
- !ruby/object:Gem::Dependency
|
82
|
+
version_requirements: &id005 !ruby/object:Gem::Requirement
|
83
|
+
none: false
|
84
|
+
requirements:
|
85
|
+
- - ~>
|
86
|
+
- !ruby/object:Gem::Version
|
87
|
+
hash: 3
|
88
|
+
segments:
|
89
|
+
- 2
|
90
|
+
- 2
|
91
|
+
- 2
|
92
|
+
version: 2.2.2
|
93
|
+
prerelease: false
|
94
|
+
type: :development
|
95
|
+
name: redcarpet
|
96
|
+
requirement: *id005
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
version_requirements: &id006 !ruby/object:Gem::Requirement
|
67
99
|
none: false
|
68
100
|
requirements:
|
69
101
|
- - ~>
|
@@ -77,9 +109,9 @@ dependencies:
|
|
77
109
|
prerelease: false
|
78
110
|
type: :development
|
79
111
|
name: bundler
|
80
|
-
requirement: *
|
112
|
+
requirement: *id006
|
81
113
|
- !ruby/object:Gem::Dependency
|
82
|
-
version_requirements: &
|
114
|
+
version_requirements: &id007 !ruby/object:Gem::Requirement
|
83
115
|
none: false
|
84
116
|
requirements:
|
85
117
|
- - ~>
|
@@ -93,9 +125,9 @@ dependencies:
|
|
93
125
|
prerelease: false
|
94
126
|
type: :development
|
95
127
|
name: jeweler
|
96
|
-
requirement: *
|
128
|
+
requirement: *id007
|
97
129
|
- !ruby/object:Gem::Dependency
|
98
|
-
version_requirements: &
|
130
|
+
version_requirements: &id008 !ruby/object:Gem::Requirement
|
99
131
|
none: false
|
100
132
|
requirements:
|
101
133
|
- - ">="
|
@@ -107,9 +139,9 @@ dependencies:
|
|
107
139
|
prerelease: false
|
108
140
|
type: :development
|
109
141
|
name: rcov
|
110
|
-
requirement: *
|
142
|
+
requirement: *id008
|
111
143
|
- !ruby/object:Gem::Dependency
|
112
|
-
version_requirements: &
|
144
|
+
version_requirements: &id009 !ruby/object:Gem::Requirement
|
113
145
|
none: false
|
114
146
|
requirements:
|
115
147
|
- - ">="
|
@@ -121,9 +153,9 @@ dependencies:
|
|
121
153
|
prerelease: false
|
122
154
|
type: :development
|
123
155
|
name: minitest
|
124
|
-
requirement: *
|
156
|
+
requirement: *id009
|
125
157
|
- !ruby/object:Gem::Dependency
|
126
|
-
version_requirements: &
|
158
|
+
version_requirements: &id010 !ruby/object:Gem::Requirement
|
127
159
|
none: false
|
128
160
|
requirements:
|
129
161
|
- - ">="
|
@@ -135,7 +167,7 @@ dependencies:
|
|
135
167
|
prerelease: false
|
136
168
|
type: :development
|
137
169
|
name: nokogiri
|
138
|
-
requirement: *
|
170
|
+
requirement: *id010
|
139
171
|
description: This gem helps create large (>50) gmail filter chains by writing xml compatible with gmail's "import/export filters" feature.
|
140
172
|
email: asf@boinkor.net
|
141
173
|
executables: []
|
@@ -144,19 +176,20 @@ extensions: []
|
|
144
176
|
|
145
177
|
extra_rdoc_files:
|
146
178
|
- LICENSE.txt
|
147
|
-
- README.
|
179
|
+
- README.md
|
148
180
|
files:
|
149
181
|
- .document
|
182
|
+
- .yardopts
|
150
183
|
- Gemfile
|
151
184
|
- Gemfile.lock
|
152
185
|
- LICENSE.txt
|
153
|
-
- README.
|
186
|
+
- README.md
|
154
187
|
- Rakefile
|
155
188
|
- VERSION
|
156
189
|
- gmail-britta.gemspec
|
157
190
|
- lib/gmail-britta.rb
|
158
|
-
- lib/gmail-britta/delegate.rb
|
159
191
|
- lib/gmail-britta/filter.rb
|
192
|
+
- lib/gmail-britta/filter_set.rb
|
160
193
|
- lib/gmail-britta/single_write_accessors.rb
|
161
194
|
- test/test_gmail-britta.rb
|
162
195
|
homepage: http://github.com/antifuchs/gmail-britta
|
data/README.rdoc
DELETED
@@ -1,24 +0,0 @@
|
|
1
|
-
= gmail-britta
|
2
|
-
|
3
|
-
This gem assists in writing complex gmail filters. You probably have a
|
4
|
-
lot of questions, and I'm sorry this README currently answers so few
|
5
|
-
of them )-:
|
6
|
-
|
7
|
-
A lot of this is very hacky, as it started as a oneoff ruby script -
|
8
|
-
very sorry for any amateurish code you may encounter. All I can say
|
9
|
-
is, it works for me [-:
|
10
|
-
|
11
|
-
== Contributing to gmail-britta
|
12
|
-
|
13
|
-
* Check out the latest master to make sure the feature hasn't been implemented or the bug hasn't been fixed yet.
|
14
|
-
* Check out the issue tracker to make sure someone already hasn't requested it and/or contributed it.
|
15
|
-
* Fork the project.
|
16
|
-
* Start a feature/bugfix branch.
|
17
|
-
* Commit and push until you are happy with your contribution.
|
18
|
-
* Make sure to add tests for it. (Note that are only very few tests yet. I would totally appreciate if you started fleshing out this suite, but I wouldn't expect you to add much of anything.)
|
19
|
-
* Please try not to mess with the Rakefile, version, or history. If you want to have your own version, or is otherwise necessary, that is fine, but please isolate to its own commit so I can cherry-pick around it.
|
20
|
-
|
21
|
-
== Copyright
|
22
|
-
|
23
|
-
Copyright (c) 2012 Andreas Fuchs. See LICENSE.txt for
|
24
|
-
further details.
|
@@ -1,17 +0,0 @@
|
|
1
|
-
module GmailBritta
|
2
|
-
class Delegate
|
3
|
-
def initialize(britta, options={})
|
4
|
-
@britta = britta
|
5
|
-
@log = options[:logger]
|
6
|
-
@filter = nil
|
7
|
-
end
|
8
|
-
|
9
|
-
def filter(&block)
|
10
|
-
GmailBritta::Filter.new(@britta, :log => @log).perform(&block)
|
11
|
-
end
|
12
|
-
|
13
|
-
def perform(&block)
|
14
|
-
instance_eval(&block)
|
15
|
-
end
|
16
|
-
end
|
17
|
-
end
|