sortah 0.5.0 → 0.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/CHANGELOG.md +7 -0
- data/FUTURE_PLANS.md +59 -0
- data/KNOWN_BUGS.md +2 -0
- data/bin/sortah +1 -0
- data/lib/sortah/cleanroom.rb +3 -5
- data/lib/sortah/version.rb +1 -1
- data/spec/fixtures/semantic_acceptance/contact.rb +99 -0
- data/spec/fixtures/semantic_acceptance/contacts.yml +10 -0
- data/spec/fixtures/semantic_acceptance/coworkers.yml +4 -0
- data/spec/fixtures/semantic_acceptance/rc +43 -0
- data/spec/parser_spec.rb +19 -3
- data/spec/semantic_spec.rb +98 -33
- metadata +15 -6
data/CHANGELOG.md
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
##11/02/11
|
2
|
+
|
3
|
+
- fixed the "sortah hangs when redirecting to another router" bug
|
4
|
+
- this required changing cleanroom to be a normal Object subclass, instead of
|
5
|
+
a BasicObject subclass, I need to investigate re-adding certain methods to
|
6
|
+
Object so that the throw/catch machinery will be in place for BasicObject
|
7
|
+
- made bin/sortah more verbose
|
data/FUTURE_PLANS.md
CHANGED
@@ -1,5 +1,64 @@
|
|
1
1
|
#Ideas
|
2
2
|
|
3
|
+
- nested destinations --- overhaul of destinations
|
4
|
+
|
5
|
+
two things, first, change the way destinations are defined, eg:
|
6
|
+
|
7
|
+
maildir "/path/to/root/mail/dir/" do
|
8
|
+
destination :personal do
|
9
|
+
dir "gmail/"
|
10
|
+
type :maildir
|
11
|
+
|
12
|
+
destination :contact do
|
13
|
+
dir "contacts/"
|
14
|
+
type :maildir
|
15
|
+
|
16
|
+
destination :joe
|
17
|
+
destination :jenn
|
18
|
+
destination :tim
|
19
|
+
|
20
|
+
end
|
21
|
+
|
22
|
+
destination :mailing_lists do
|
23
|
+
type :mbox
|
24
|
+
#... snip ...
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
#... snip ...
|
29
|
+
|
30
|
+
abs :bitbucket, "/dev/null"
|
31
|
+
|
32
|
+
proxy :search_index do
|
33
|
+
#code to inject into search index
|
34
|
+
end
|
35
|
+
|
36
|
+
#... snip ...
|
37
|
+
end
|
38
|
+
|
39
|
+
This would make defining destinations a bit nicer, in addition to providing a
|
40
|
+
way for multi-type mailboxen.
|
41
|
+
|
42
|
+
- 'scaffold' functionality
|
43
|
+
|
44
|
+
should build all the scaffolding described by the destinations given in the
|
45
|
+
sortah configuration
|
46
|
+
|
47
|
+
- 'resource' type
|
48
|
+
|
49
|
+
A resource is some block of code which returns an object which is cached, like
|
50
|
+
lenses, a router, lens, or resource may depend on other resources. Resources
|
51
|
+
differ from lenses in that they are not bound to an email, and -- in the event
|
52
|
+
of multiple incoming emails, are executed only once across the whole session
|
53
|
+
|
54
|
+
- speed
|
55
|
+
|
56
|
+
it's pretty slow, as it stands, could definitely use some speed up. Major
|
57
|
+
improvement would be a sortah server, which could run persistently, so that
|
58
|
+
starting/stopping a ruby vm wouldn't be necessary. Also, parallizing sortah
|
59
|
+
would be nice, potential for that could be batch execution or the server idea
|
60
|
+
from above.
|
61
|
+
|
3
62
|
- proxy destinations
|
4
63
|
|
5
64
|
Difficulty: middling
|
data/KNOWN_BUGS.md
CHANGED
data/bin/sortah
CHANGED
data/lib/sortah/cleanroom.rb
CHANGED
@@ -4,15 +4,13 @@ module Sortah
|
|
4
4
|
end
|
5
5
|
|
6
6
|
|
7
|
-
class CleanRoom <
|
7
|
+
class CleanRoom < Object
|
8
8
|
def self.sort(email, context)
|
9
9
|
new(email, context).sort
|
10
10
|
end
|
11
11
|
|
12
12
|
def sort
|
13
|
-
until @pointer.is_a?(Destination)
|
14
|
-
run!(@pointer) rescue FinishedExecution
|
15
|
-
end
|
13
|
+
catch(:finished_execution) { run!(@pointer) } until @pointer.is_a?(Destination)
|
16
14
|
self
|
17
15
|
end
|
18
16
|
|
@@ -35,7 +33,7 @@ module Sortah
|
|
35
33
|
|
36
34
|
def send_to(dest)
|
37
35
|
@pointer = @__context__.routers[dest] || @__context__.destinations[dest]
|
38
|
-
throw
|
36
|
+
throw :finished_execution
|
39
37
|
end
|
40
38
|
|
41
39
|
def initialize(email, context)
|
data/lib/sortah/version.rb
CHANGED
@@ -0,0 +1,99 @@
|
|
1
|
+
require 'yaml'
|
2
|
+
require 'sortah'
|
3
|
+
|
4
|
+
class Contact
|
5
|
+
def want?(email)
|
6
|
+
@emails.include?(email.sender) ||
|
7
|
+
email.from.any? { |me| is?(me) }
|
8
|
+
end
|
9
|
+
|
10
|
+
def build_destination(prefix)
|
11
|
+
local_dest = @destination #this gets around a weirdness -- the ivar doesn't end up in the right scope
|
12
|
+
#otherwise
|
13
|
+
sortah { destination local_dest, "#{prefix}/#{local_dest}/new/" }
|
14
|
+
end
|
15
|
+
|
16
|
+
def is?(contact)
|
17
|
+
case contact
|
18
|
+
when Contact
|
19
|
+
@emails.any? { |e| contact.is? e }
|
20
|
+
when String
|
21
|
+
@emails.include? contact
|
22
|
+
else
|
23
|
+
raise "Tried to compare a contact to something that wasn't an email address or contact object"
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def <<(email)
|
28
|
+
@emails << email
|
29
|
+
update_in_contacts
|
30
|
+
end
|
31
|
+
|
32
|
+
attr_reader :destination
|
33
|
+
|
34
|
+
def initialize(emails, destination, path = '~/.sortah/contacts.yml')
|
35
|
+
@emails = emails
|
36
|
+
@destination = destination
|
37
|
+
@path = path
|
38
|
+
register_with_contacts
|
39
|
+
end
|
40
|
+
|
41
|
+
def register_with_contacts
|
42
|
+
Contacts(@path).add_contact(self)
|
43
|
+
end
|
44
|
+
|
45
|
+
def update_in_contacts
|
46
|
+
Contacts(@path).update(self)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
class Contacts
|
51
|
+
def build_destinations(prefix)
|
52
|
+
@contacts.map { |c| c.build_destination(prefix) }
|
53
|
+
end
|
54
|
+
|
55
|
+
def destination_for(email)
|
56
|
+
find(email).first.destination
|
57
|
+
end
|
58
|
+
|
59
|
+
def want?(email)
|
60
|
+
@contacts.any? { |c| c.want? email }
|
61
|
+
end
|
62
|
+
|
63
|
+
def find(email)
|
64
|
+
@contacts.select { |c| c.want? email }
|
65
|
+
end
|
66
|
+
|
67
|
+
def add_contact(c)
|
68
|
+
@contacts << c
|
69
|
+
end
|
70
|
+
|
71
|
+
def update(c)
|
72
|
+
@contacts = @contacts.delete_if { |k| k.is? c } #compares by email
|
73
|
+
add_contact(c)
|
74
|
+
end
|
75
|
+
|
76
|
+
def save
|
77
|
+
File.open(@path, 'w') { |f| f << @contacts.to_yaml }
|
78
|
+
end
|
79
|
+
|
80
|
+
private
|
81
|
+
|
82
|
+
def load_contacts
|
83
|
+
@contacts = []
|
84
|
+
@contacts = YAML.load(File.read(@path)) if File.exists?(@path)
|
85
|
+
end
|
86
|
+
|
87
|
+
def initialize(path = nil)
|
88
|
+
if path
|
89
|
+
@path = File.expand_path(path)
|
90
|
+
else
|
91
|
+
@path = "#{ENV["HOME"]}/.sortah/contacts.yml"
|
92
|
+
end
|
93
|
+
load_contacts
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
def Contacts(path = nil)
|
98
|
+
Contacts.new(path)
|
99
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
#^^ just to make syntax highlighting stick
|
3
|
+
|
4
|
+
require './spec/fixtures/semantic_acceptance/contact.rb'
|
5
|
+
|
6
|
+
#destinations and maildir
|
7
|
+
sortah do
|
8
|
+
maildir "/Users/jfredett/.mutt/mail/"
|
9
|
+
|
10
|
+
$contacts = {}
|
11
|
+
$contacts[:work] = Contacts("./spec/fixtures/semantic_acceptance/coworkers.yml")
|
12
|
+
$contacts[:personal] = Contacts("./spec/fixtures/semantic_acceptance/contacts.yml")
|
13
|
+
|
14
|
+
$contacts[:work].build_destinations(:work)
|
15
|
+
$contacts[:personal].build_destinations(:personal)
|
16
|
+
|
17
|
+
destination :unknown, 'new/'
|
18
|
+
destination :unknown_personal, 'personal/unknown/new'
|
19
|
+
destination :unknown_work, 'work/unknown/new'
|
20
|
+
destination :unknown_coworker, 'work/coworkers/unknown/new'
|
21
|
+
end
|
22
|
+
|
23
|
+
#routers
|
24
|
+
sortah do
|
25
|
+
router :personal do
|
26
|
+
contact = $contacts[:personal]
|
27
|
+
send_to contact.destination_for(email) if contact.want? email
|
28
|
+
send_to :unknown_personal
|
29
|
+
end
|
30
|
+
|
31
|
+
router :work do
|
32
|
+
contact = $contacts[:work]
|
33
|
+
send_to contact.destination_for(email) if contact.want? email
|
34
|
+
send_to :unknown_work
|
35
|
+
end
|
36
|
+
|
37
|
+
router do
|
38
|
+
send_to :personal if email.to.any? { |r| r =~ /jfredett@place.com/ }
|
39
|
+
send_to :work if email.to.any? { |r| r =~ /joe@work.com/ }
|
40
|
+
send_to :unknown
|
41
|
+
end
|
42
|
+
|
43
|
+
end
|
data/spec/parser_spec.rb
CHANGED
@@ -15,6 +15,7 @@ describe Sortah::Parser do
|
|
15
15
|
sortah.should_not be_nil
|
16
16
|
end
|
17
17
|
|
18
|
+
|
18
19
|
it "should parse defined 'simple' destinations" do
|
19
20
|
expect {
|
20
21
|
sortah do
|
@@ -230,15 +231,30 @@ describe Sortah::Parser do
|
|
230
231
|
sortah.maildir.should == "/home/user/.mail/personal"
|
231
232
|
end
|
232
233
|
|
234
|
+
it "should pass over a nested #sortah block" do
|
235
|
+
expect {
|
236
|
+
sortah do
|
237
|
+
sortah do
|
238
|
+
destination :foo, "foo/"
|
239
|
+
|
240
|
+
sortah do
|
241
|
+
end
|
242
|
+
end
|
243
|
+
end
|
244
|
+
}.should_not raise_error
|
245
|
+
sortah.destinations[:foo].should == 'foo/'
|
246
|
+
end
|
233
247
|
end
|
234
248
|
|
235
249
|
#acceptance criteria
|
236
250
|
it "should parse an example sortah file, which contains all of the language elements" do
|
237
251
|
expect {
|
238
252
|
sortah do
|
239
|
-
|
240
|
-
|
241
|
-
|
253
|
+
sortah do
|
254
|
+
destination :place, "somewhere"
|
255
|
+
destination :devnull, :abs => "/dev/null"
|
256
|
+
destination :bitbucket, :devnull
|
257
|
+
end
|
242
258
|
|
243
259
|
lens :random_value do
|
244
260
|
rand
|
data/spec/semantic_spec.rb
CHANGED
@@ -18,45 +18,46 @@ def basic_sortah_definition
|
|
18
18
|
end
|
19
19
|
|
20
20
|
describe Sortah do
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
end
|
21
|
+
before :all do
|
22
|
+
Mail.defaults do
|
23
|
+
delivery_method :test
|
24
|
+
end
|
26
25
|
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
26
|
+
@email = Mail.new do
|
27
|
+
to 'testa@example.com'
|
28
|
+
from 'chuck@nope.com'
|
29
|
+
subject "Taximerdizin'"
|
30
|
+
body <<-TXT
|
31
|
+
OJAI VALLEY TAXIDERMY
|
33
32
|
|
34
|
-
|
33
|
+
BET YOU THOUGHT THIS EMAIL WAS REAL
|
35
34
|
|
36
|
-
|
37
|
-
|
38
|
-
end
|
39
|
-
|
40
|
-
@reply_email = Mail.new do
|
41
|
-
to 'chuck@nope.com'
|
42
|
-
from 'jgf@somewhere.com'
|
43
|
-
subject "Re: Taximerdizin'"
|
44
|
-
reply_to 'chuck@nope.com'
|
45
|
-
body <<-TXT
|
46
|
-
> OJAI VALLEY TAXIDERMY
|
47
|
-
>
|
48
|
-
> BET YOU THOUGHT THIS EMAIL WAS REAL
|
49
|
-
>
|
50
|
-
> NOPE. CHUCK TESTA
|
51
|
-
|
52
|
-
Do you taxidermize pets?
|
53
|
-
TXT
|
54
|
-
end
|
35
|
+
NOPE. CHUCK TESTA
|
36
|
+
TXT
|
55
37
|
end
|
56
38
|
|
57
|
-
|
58
|
-
|
39
|
+
@reply_email = Mail.new do
|
40
|
+
to 'chuck@nope.com'
|
41
|
+
from 'jgf@somewhere.com'
|
42
|
+
subject "Re: Taximerdizin'"
|
43
|
+
reply_to 'chuck@nope.com'
|
44
|
+
body <<-TXT
|
45
|
+
> OJAI VALLEY TAXIDERMY
|
46
|
+
>
|
47
|
+
> BET YOU THOUGHT THIS EMAIL WAS REAL
|
48
|
+
>
|
49
|
+
> NOPE. CHUCK TESTA
|
50
|
+
|
51
|
+
Do you taxidermize pets?
|
52
|
+
TXT
|
59
53
|
end
|
54
|
+
end
|
55
|
+
|
56
|
+
before :each do
|
57
|
+
Sortah::Parser.clear!
|
58
|
+
end
|
59
|
+
|
60
|
+
context "when sorting an email" do
|
60
61
|
|
61
62
|
it "should provide a way to sort a single email" do
|
62
63
|
sortah.should respond_to :sort
|
@@ -111,6 +112,44 @@ describe Sortah do
|
|
111
112
|
sortah.sort(@reply_email).destination.should == "bar/"
|
112
113
|
end
|
113
114
|
|
115
|
+
it "should defer to a second router using the more idiomatic ruby syntax" do
|
116
|
+
sortah do
|
117
|
+
destination :foo, "foo/"
|
118
|
+
destination :bar, "bar/"
|
119
|
+
|
120
|
+
router do
|
121
|
+
send_to :foo if email.from.any? { |sender| sender =~ /chuck/ }
|
122
|
+
send_to :secondary_router
|
123
|
+
end
|
124
|
+
|
125
|
+
router :secondary_router do
|
126
|
+
send_to :bar
|
127
|
+
end
|
128
|
+
end
|
129
|
+
sortah.sort(@email).destination.should == "foo/"
|
130
|
+
sortah.sort(@reply_email).destination.should == "bar/"
|
131
|
+
end
|
132
|
+
|
133
|
+
it "should allow me to set local variables in a router block" do
|
134
|
+
sortah do
|
135
|
+
destination :foo, "foo/"
|
136
|
+
destination :bar, "bar/"
|
137
|
+
|
138
|
+
router do
|
139
|
+
senders = email.from
|
140
|
+
|
141
|
+
send_to :foo if senders.any? { |sender| sender =~ /chuck/ }
|
142
|
+
send_to :secondary_router
|
143
|
+
end
|
144
|
+
|
145
|
+
router :secondary_router do
|
146
|
+
send_to :bar
|
147
|
+
end
|
148
|
+
end
|
149
|
+
sortah.sort(@email).destination.should == "foo/"
|
150
|
+
sortah.sort(@reply_email).destination.should == "bar/"
|
151
|
+
end
|
152
|
+
|
114
153
|
it "should run dependent lenses for the root router" do
|
115
154
|
sortah do
|
116
155
|
destination :foo, "foo/"
|
@@ -305,6 +344,32 @@ describe Sortah do
|
|
305
344
|
|
306
345
|
end
|
307
346
|
end
|
347
|
+
|
348
|
+
end
|
349
|
+
|
350
|
+
end
|
351
|
+
|
352
|
+
describe "acceptance" do
|
353
|
+
it "should be able to parse and sort using a real live example sortah definition" do
|
354
|
+
|
355
|
+
sortah do
|
356
|
+
load './spec/fixtures/semantic_acceptance/rc'
|
357
|
+
end
|
358
|
+
|
359
|
+
personal_email = Mail.new do
|
360
|
+
To 'jfredett@place.com'
|
361
|
+
From 'sfredett@somewhere.com'
|
362
|
+
end
|
363
|
+
work_email = Mail.new do
|
364
|
+
To 'joe@work.com'
|
365
|
+
From 'brian@work.com'
|
366
|
+
Subject 'You get a raise, you brilliant bastard.'
|
367
|
+
#shuttup, I can dream.
|
368
|
+
end
|
369
|
+
|
370
|
+
sortah.sort(@email).destination.should == 'new/'
|
371
|
+
sortah.sort(personal_email).destination.should == 'personal/sarah/new/'
|
372
|
+
sortah.sort(work_email).destination.should == 'work/brian/new/'
|
308
373
|
end
|
309
374
|
end
|
310
375
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: sortah
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.5.
|
4
|
+
version: 0.5.1
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,11 +9,11 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2011-
|
12
|
+
date: 2011-11-02 00:00:00.000000000Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: mail
|
16
|
-
requirement: &
|
16
|
+
requirement: &2164841700 !ruby/object:Gem::Requirement
|
17
17
|
none: false
|
18
18
|
requirements:
|
19
19
|
- - ! '>='
|
@@ -21,10 +21,10 @@ dependencies:
|
|
21
21
|
version: '0'
|
22
22
|
type: :runtime
|
23
23
|
prerelease: false
|
24
|
-
version_requirements: *
|
24
|
+
version_requirements: *2164841700
|
25
25
|
- !ruby/object:Gem::Dependency
|
26
26
|
name: trollop
|
27
|
-
requirement: &
|
27
|
+
requirement: &2164841260 !ruby/object:Gem::Requirement
|
28
28
|
none: false
|
29
29
|
requirements:
|
30
30
|
- - ! '>='
|
@@ -32,7 +32,7 @@ dependencies:
|
|
32
32
|
version: '0'
|
33
33
|
type: :runtime
|
34
34
|
prerelease: false
|
35
|
-
version_requirements: *
|
35
|
+
version_requirements: *2164841260
|
36
36
|
description: ! "\n Sortah provides a simple, declarative internal DSL for sorting
|
37
37
|
\n your email. It provides an executable which may serve as an external\n mail
|
38
38
|
delivery agent for such programs as `getmail`. Finally, since\n your sorting
|
@@ -50,6 +50,7 @@ files:
|
|
50
50
|
- .gitignore
|
51
51
|
- .rvmrc
|
52
52
|
- .travis.yml
|
53
|
+
- CHANGELOG.md
|
53
54
|
- CONTRIBUTION.md
|
54
55
|
- FUTURE_PLANS.md
|
55
56
|
- Gemfile
|
@@ -78,6 +79,10 @@ files:
|
|
78
79
|
- spec/destination_spec.rb
|
79
80
|
- spec/email_spec.rb
|
80
81
|
- spec/fixtures/rc
|
82
|
+
- spec/fixtures/semantic_acceptance/contact.rb
|
83
|
+
- spec/fixtures/semantic_acceptance/contacts.yml
|
84
|
+
- spec/fixtures/semantic_acceptance/coworkers.yml
|
85
|
+
- spec/fixtures/semantic_acceptance/rc
|
81
86
|
- spec/parser_spec.rb
|
82
87
|
- spec/semantic_spec.rb
|
83
88
|
- spec/sortah_handler_spec.rb
|
@@ -111,6 +116,10 @@ test_files:
|
|
111
116
|
- spec/destination_spec.rb
|
112
117
|
- spec/email_spec.rb
|
113
118
|
- spec/fixtures/rc
|
119
|
+
- spec/fixtures/semantic_acceptance/contact.rb
|
120
|
+
- spec/fixtures/semantic_acceptance/contacts.yml
|
121
|
+
- spec/fixtures/semantic_acceptance/coworkers.yml
|
122
|
+
- spec/fixtures/semantic_acceptance/rc
|
114
123
|
- spec/parser_spec.rb
|
115
124
|
- spec/semantic_spec.rb
|
116
125
|
- spec/sortah_handler_spec.rb
|