twmail 0.0.3

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: c9c8cc0d210afa5b81ffa554fbc6a388eba8091e
4
+ data.tar.gz: fa0467db33a102d55a4edd2f3b01870b9f91e20f
5
+ SHA512:
6
+ metadata.gz: d88406f8e40b40ff64dc0e23de7ef0bcff22567036dc259907e723c502c0f90b302d1fc0780b1693963b2405bc3ffbb3d9b41d59d6b07849472ed07838c6b065
7
+ data.tar.gz: 01652de31f8ec363506076e6796bd1aaad78714bf45d92bb3747bf97f2810e738b583d4ba4b3288767a77d0819d5c446573da8df62bf2f521e1ac938f2fbe7a8
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/Gemfile ADDED
@@ -0,0 +1,7 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # use local twtest while developing
4
+ # gem "twtest", :path => "../twtest"
5
+
6
+ # Specify your gem's dependencies in twmail.gemspec
7
+ gemspec
@@ -0,0 +1,11 @@
1
+ guard 'bundler' do
2
+ watch('Gemfile')
3
+ watch(/^.+\.gemspec/)
4
+ end
5
+
6
+ guard :test, :test_paths => ['test/unit'] do
7
+ watch(%r{^lib/(.+)\.rb$}){|m| "test/#{m[1]}_test.rb"}
8
+ watch(%r{^test/unit/test_(.+)\.rb$})
9
+ watch('test/test_helper.rb'){"test"}
10
+ watch('test/helpers/**/*'){"test"}
11
+ end
data/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2012 Nicholas E. Rabenau
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,117 @@
1
+ # twmail
2
+
3
+ `twmail` allows you to mail new tasks to your TaskWarrior inbox.
4
+
5
+ ## Installation
6
+
7
+ $ gem install twmail
8
+
9
+ ## Usage
10
+
11
+ 1. Install ruby and this gem
12
+ 1. If you don't have a `~/.fetchmailrc` yet, copy `doc/fetchmailrc.sample` to `~/.fetchmailrc`
13
+ 1. Edit `~/.fetchmailrc` and adjust mail account settings (the example was made for Google Mail account). If in doubt, consult the `fetchmail` documentation, e.g. by executing `man fetchmailconf` in a terminal.
14
+
15
+ ## Motivation
16
+ I would like to add new tasks to my TaskWarrior inbox from remote places where I don't have immediate access to my personal TaskWarrior database; e.g. from my iPhone, from work (where I don't have access to my personal TaskWarrior installation) or from another computer.
17
+
18
+ Using eMail for this looks like a great candidate:
19
+
20
+ 1. I don't want to (or cannot) install TaskWarrior on all the places and machines where I would like to add tasks from. Sending a note as eMail is pretty much universally available.
21
+ 1. Many applications were not made for integration with TaskWarrior. But even the dumbest iPhone app can forward text or a URL via eMail.
22
+ 1. eMail is asynchronous by design (fire and forget). Even if disconnected from the net, I can send eMail and the system will deliver it on the very next occassion.
23
+
24
+ What is missing from a TaskWarrior perspective right now is a way to add these mails to a TaskWarrior installation automatically.
25
+
26
+ ## Architecture
27
+ The simplest solution I could come up with is this:
28
+
29
+ 1. A dedicated email account is used to collect the tasks.
30
+ 1. A script that imports all eMails as new tasks.
31
+
32
+ As a prerequisite, TaskWarrior is assumed to be installed and configured. With this architecture in place, the functionality is rather simple to implement:
33
+
34
+ For each mail{
35
+ Transaction{
36
+ * Fetch mail from mailbox
37
+ * Store mail as new task in Taskwarrior
38
+ * Delete mail from mailbox
39
+ }
40
+ }
41
+
42
+ As the word `Transaction` implies, the whole operation needs to be atomic per mail. No task must be added if fetching a mail went wrong, and no mail must be deleted if storing the task in TaskWarrior failed.
43
+
44
+ The solution presented here maintains a one-to-one relation between the INBOX of an mail account and the TaskWarrior database.
45
+
46
+ TODO Describe how to use fetchmail's daemon mode
47
+
48
+ TODO Describe how to use fetchmail's IMAP IDLE flag
49
+
50
+ ## Components
51
+ Mail fetching is done with `fetchmail`, a proven solution available on all major Unices incl. MacOS. It will be configured to use the `twmail` script as a mail delivery agent (mda), which means nothing more than that `fetchmail` fetches the mail from the configured account and hands it over to our script. There is no further storage of the received mails except in TaskWarrior.
52
+
53
+ ## Error Handling
54
+ If our MDA returns non-zero, `fetchmail` will not assume the message to be processed and it will try again.
55
+
56
+ TODO Do we need a dedicated dead-letter queue for all mails fetched, but not successfully processed?
57
+
58
+ ## Alternatives
59
+ One might think of more elaborate applications that do more clever things, but I wanted to create this solution with as few external dependencies as possible. `fetchmail` is available on all Unices, and who can afford to live without TaskWarrior anyway? I also played with the thought of a central tasks server that receives mail from services like CloudMailIn and auto-adds them to the server, but the result would not be much different (besides being more complex) to the solution presented here: No task will be fetched into TaskWarrior until the machine with the TaskWarrior database is online.
60
+
61
+ Another alternative would be to convert the email to JSON and use TaskWarrior's import command. This would allow to create and annotate a new task in one step without the `bin/task-uuid` workaround.
62
+
63
+ ## Advanced Usage
64
+ ### Filtering and Routing
65
+ Many more advanced use cases like filtering and routing can be implemented on the mail server side. There are plenty of user interfaces for routing eMails based on their subject, sender, body text, etc. The simplest way to integrate these features with `twmail` is to use IMAP folders. After all filtering and routing, each eMail must end up in a dedicated IMAP folder (by default, all tasks are fetched from the INBOX folder). `twmail` can then be configured to do different things depending on which IMAP folder a mail came from.
66
+
67
+ As an example, here is a simple way to route eMails to different projects in TaskWarrior, based on their subject line:
68
+
69
+ 1. Set up a dedicated IMAP folder for every project you work on, e.g. "Build Bikeshed", "Reading List", "Get Rich Fast"
70
+ 1. Configure the mail server to move every mail from INBOX to the
71
+ 1. "Build Bikeshed" folder if the mail subject contains "project:Bikeshed"
72
+ 1. "Reading List" folder if the mail subject contains "project:Reading"
73
+ 1. "Get Rich Fast" folder if the mail subject contains "project:GetRichFast"
74
+ 1. Tell `twmail` to fetch mails from the "Build Bikeshed", "Reading List", and "Get Rich Fast" IMAP folders (in addition to the INBOX):
75
+
76
+ TODO Continue description ... may need tracepolls for it.
77
+
78
+ The approach chosen for `twmail` also addresses SPAM filtering. Handling that remains the responsibility of the mail server. Anything that makes it to the INBOX is treated as task.
79
+
80
+ ### Hooks
81
+ `twmail` comes with an advanced implementation that supports hooks. This makes handling incoming mail very simple for someone familiar with shell scripting, and there is no need to edit the `twmail` scripts in order to customize its behavior.
82
+
83
+ When `fetchmail` is configured to use `twmail_hooks` instead of `twmail`, the script will call the `twmail_hook` command (must be in the user's `$PATH`). Within the hook script, the fields of the parsed email are available as environment variables:
84
+
85
+ TWMAIL_DATE
86
+ TWMAIL_MESSAGE_ID
87
+ TWMAIL_FROM
88
+ TWMAIL_TO
89
+ TWMAIL_SUBJECT
90
+ TWMAIL_BODY
91
+
92
+ Have a look at test/helpers/test_hook for a very simple implementation.
93
+
94
+ If you prefer a hook with a different name, specify it in the `TWMAIL_HOOK` environment variable in your `.fetchmailrc`. For example, if your home directory contains a script called `taskwarrior-import.sh`, edit the `mda` line to look like this:
95
+
96
+ mda TWMAIL_HOOK=~/taskwarrior-import.sh twmail_hooks
97
+
98
+ ## Housekeeping
99
+ By default `fetchmail` will mark retrieved messages as read, but leave them on the server. For housekeeping purposes, it may be desirable to delete messages from the server once they were successfully imported into TaskWarrior.
100
+
101
+ There are two ways to achieve this:
102
+
103
+ 1. Create a filter on the server side that deletes all read mail to a dedicated folder (perhaps "Archive" or "Trash"), or simply deletes it.
104
+ 1. Run `fetchmail` with the `--nokeep` option, which will delete retrieved messages from the server.
105
+
106
+ Which option to choose depends on the capabilities of your mail server (Google Mail cannot handle mails based on their read status), and on your level of trust in `twmail`. I recommend leaving mails on the server until you are confident that everything works as expected.
107
+
108
+ ## Testing
109
+ `twmail` comes with a basic set of tests. Execute them by running `rake` in the cloned source repo.
110
+
111
+ ## Contributing
112
+
113
+ 1. Fork it
114
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
115
+ 3. Commit your changes (`git commit -am 'Added some feature'`)
116
+ 4. Push to the branch (`git push origin my-new-feature`)
117
+ 5. Create new Pull Request
@@ -0,0 +1,10 @@
1
+ #!/usr/bin/env rake
2
+ require "bundler/gem_tasks"
3
+
4
+ require 'rake/testtask'
5
+ Rake::TestTask.new(:test) do |test|
6
+ test.libs << 'lib' << 'test' << 'test/helpers'
7
+ test.test_files = FileList['test/**/test_*.rb']
8
+ end
9
+
10
+ task :default => :test
@@ -0,0 +1,22 @@
1
+ #!/usr/bin/env ruby
2
+ require 'securerandom'
3
+
4
+ #
5
+ # Creates a new task using TaskWarrior and prints its UUID to STDOUT.
6
+ #
7
+
8
+ # 1. Make a temporary tag that unlikely to exist yet.
9
+ # To avoid shell troubles, we have it start with a character.
10
+ tag = "T#{SecureRandom.hex}"
11
+
12
+ # 2. Create the new task tagged with our temporary tag
13
+ %x|task rc.verbose=nothing add +#{tag} #{ARGV.join(' ')}|
14
+
15
+ # 3. Remember the UUID generated by TaskWarrior
16
+ task_uuid = %x|task "+#{tag}" uuid|
17
+
18
+ # 4. Remove the temporary tag from the new task
19
+ %x|task rc.verbose=nothing '#{task_uuid}' modify -#{tag}|
20
+
21
+ # 5. Print the UUID to STDOUT
22
+ puts task_uuid
@@ -0,0 +1,13 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ # This script is a dead-simple Mail Delivery Agent (MDA) that parses the received email
4
+ # and creates a new TaskWarrior task from the subject of the mail.
5
+ mail = Mail.new(ARGF.read)
6
+ task_uuid = %x[task-uuid \"#{mail.subject}\"]
7
+
8
+ body = mail.text? ? mail.body.decoded : mail.text_part.body.decoded
9
+
10
+ SEPARATOR = '-- '
11
+ body = body.split(SEPARATOR)[0] if (body.include?(SEPARATOR))
12
+
13
+ %x[task '#{task_uuid}' annotate \"#{body}\"]
@@ -0,0 +1,21 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ # This script is a dead-simple Mail Delivery Agent (MDA) that exports the parts of the received email as
4
+ # environment variables and calls a shell script that can make use of these variables to further process
5
+ # the mail.
6
+ mail = Mail.new(ARGF.read)
7
+
8
+ # Expose mail properties as environment variables
9
+ %w[date message_id from to subject body].each{|field|
10
+ value = mail.send(field.to_sym)
11
+ value = value.join(',') if value.respond_to?(:join)
12
+ ENV["TWMAIL_#{field.upcase}"] = Shellwords.escape(value.to_s)
13
+ }
14
+
15
+ # The hook to be executed is read from the environment variable TWMAIL_HOOK
16
+ # If none is set, this script will assume that twmail_on_new_mail is in the
17
+ # $PATH and executable.
18
+ cmd = ENV["TWMAIL_HOOK"] || 'twmail_hook'
19
+
20
+ # Call hook script
21
+ %x[#{cmd}]
@@ -0,0 +1,7 @@
1
+ require 'mail'
2
+
3
+ require "twmail/version"
4
+
5
+ module TaskWarriorMail
6
+ # Your code goes here...
7
+ end
@@ -0,0 +1,3 @@
1
+ module TaskWarriorMail
2
+ VERSION = "0.0.3"
3
+ end
File without changes
@@ -0,0 +1,39 @@
1
+ Delivered-To: tasks@example.com
2
+ Received: by 10.114.10.161 with SMTP id j1csp21288ldb;
3
+ Mon, 18 Jun 2012 13:14:55 -0700 (PDT)
4
+ Received: by 10.14.127.78 with SMTP id c54mr3708525eei.8.1340050495549;
5
+ Mon, 18 Jun 2012 13:14:55 -0700 (PDT)
6
+ Return-Path: <me@example.com>
7
+ Received: from mail-ey0-f169.google.com (mail-ey0-f169.google.com [209.85.215.169])
8
+ by mx.google.com with ESMTPS id x56si9033076eea.113.2012.06.18.13.14.55
9
+ (version=TLSv1/SSLv3 cipher=OTHER);
10
+ Mon, 18 Jun 2012 13:14:55 -0700 (PDT)
11
+ Received-SPF: neutral (google.com: 209.85.215.169 is neither permitted nor denied by best guess record for domain of me@example.com) client-ip=209.85.215.169;
12
+ Authentication-Results: mx.google.com; spf=neutral (google.com: 209.85.215.169 is neither permitted nor denied by best guess record for domain of me@example.com) smtp.mail=me@example.com
13
+ Received: by mail-ey0-f169.google.com with SMTP id n1so2188326eaa.14
14
+ for <tasks@example.com>; Mon, 18 Jun 2012 13:14:55 -0700 (PDT)
15
+ MIME-Version: 1.0
16
+ Received: by 10.14.101.144 with SMTP id b16mr3667426eeg.225.1340050495077;
17
+ Mon, 18 Jun 2012 13:14:55 -0700 (PDT)
18
+ Received: by 10.14.188.4 with HTTP; Mon, 18 Jun 2012 13:14:54 -0700 (PDT)
19
+ In-Reply-To: <-4460535785096073142@unknownmsgid>
20
+ References: <-4460535785096073142@unknownmsgid>
21
+ Date: Mon, 18 Jun 2012 22:14:54 +0200
22
+ Message-ID: <CAB-EwyF1hqX-mxUbWJhnoSUJEujDcrKxMLniu1A3bcwXcOu+FQ@mail.gmail.com>
23
+ Subject: MenTaLguY: Atomic Operations in Ruby
24
+ From: "Manager, Task" <me@example.com>
25
+ To: tasks@example.com
26
+ Content-Type: multipart/alternative; boundary=bcaec52159cbbd7c5604c2c4d12e
27
+
28
+ --bcaec52159cbbd7c5604c2c4d12e
29
+ Content-Type: text/plain; charset=ISO-8859-1
30
+
31
+ http://moonbase.rydia.net/mental/blog/programming/atomic-operations-in-ruby.html
32
+
33
+ --bcaec52159cbbd7c5604c2c4d12e
34
+ Content-Type: text/html; charset=ISO-8859-1
35
+
36
+ <div class="gmail_quote"><a href="http://moonbase.rydia.net/mental/blog/programming/atomic-operations-in-ruby.html" target="_blank">http://moonbase.rydia.net/mental/blog/programming/atomic-operations-in-ruby.html</a><br><br>
37
+ </div>
38
+
39
+ --bcaec52159cbbd7c5604c2c4d12e--
@@ -0,0 +1,26 @@
1
+ Date: Sat, 09 Jun 2012 21:09:29 +0200
2
+ From: king.crown@nigerian-lottery.com
3
+ To: you@example.com
4
+ Message-ID: <4fd39f695c947_827580443948558fd@example.com>
5
+ Subject: Send some test mails
6
+ Mime-Version: 1.0
7
+ Content-Type: text/plain;
8
+ charset=UTF-8
9
+ Content-Transfer-Encoding: 7bit
10
+
11
+ Hi there,
12
+ I am writing to you in order to inform you that you have
13
+ won the Nigerian Lottery.
14
+
15
+ In order to claim your win, please transfer the handling
16
+ fee to the following account:
17
+
18
+ AMOUNT: EUR 1234,56
19
+ IBAN: AT571234500234573201
20
+ BIC: ATBAATWWXXX
21
+
22
+ Sincerely
23
+
24
+ Toast Bread
25
+ King Crown
26
+ Head of The Nigerian Lottery
@@ -0,0 +1,46 @@
1
+ Delivered-To: task@example.com
2
+ Received: by 10.152.29.4 with SMTP id f4csp2763lah;
3
+ Sat, 13 Jul 2013 13:33:13 -0700 (PDT)
4
+ X-Received: by 10.14.177.196 with SMTP id d44mr52014105eem.35.1373747593216;
5
+ Sat, 13 Jul 2013 13:33:13 -0700 (PDT)
6
+ Return-Path: <john.doe@example.com>
7
+ Received: from mail-ee0-f45.google.com (mail-ee0-f45.google.com [74.125.83.45])
8
+ by mx.google.com with ESMTPS id 47si37216650eeu.98.2013.07.13.13.33.12
9
+ for <task@example.com>
10
+ (version=TLSv1 cipher=ECDHE-RSA-RC4-SHA bits=128/128);
11
+ Sat, 13 Jul 2013 13:33:13 -0700 (PDT)
12
+ Received-SPF: neutral (google.com: 74.125.83.45 is neither permitted nor denied by best guess record for domain of john.doe@example.com) client-ip=74.125.83.45;
13
+ Authentication-Results: mx.google.com;
14
+ spf=neutral (google.com: 74.125.83.45 is neither permitted nor denied by best guess record for domain of john.doe@example.com) smtp.mail=john.doe@example.com
15
+ Received: by mail-ee0-f45.google.com with SMTP id c1so6851062eek.32
16
+ for <task@example.com>; Sat, 13 Jul 2013 13:33:12 -0700 (PDT)
17
+ MIME-Version: 1.0
18
+ X-Received: by 10.14.183.135 with SMTP id q7mr51321537eem.97.1373747592866;
19
+ Sat, 13 Jul 2013 13:33:12 -0700 (PDT)
20
+ Received: by 10.14.136.203 with HTTP; Sat, 13 Jul 2013 13:33:12 -0700 (PDT)
21
+ Date: Sat, 13 Jul 2013 22:33:12 +0200
22
+ Message-ID: <CAM4tTWv5Jd--NTSTGRvLeO+0vGHjPdm0W7Z36PuN1KPvL4+q=A@mail.gmail.com>
23
+ Subject: Try IFTTT / PinReadable +kindle
24
+ From: "Doe, John" <john.doe@example.com>
25
+ To: "task@example.com" <task@example.com>
26
+ Content-Type: multipart/alternative; boundary=047d7b34385e48d21704e16a89ba
27
+
28
+ --047d7b34385e48d21704e16a89ba
29
+ Content-Type: text/plain; charset=UTF-8
30
+ Content-Transfer-Encoding: quoted-printable
31
+
32
+ https://ifttt.com/recipes/20357
33
+
34
+ --=20
35
+ Mit freundlichen Gr=C3=BCssen
36
+
37
+ John Doe
38
+
39
+ --047d7b34385e48d21704e16a89ba
40
+ Content-Type: text/html; charset=UTF-8
41
+ Content-Transfer-Encoding: quoted-printable
42
+
43
+ <a href=3D"https://ifttt.com/recipes/20357">https://ifttt.com/recipes/20357=
44
+ </a><br><br>-- <br>Mit freundlichen Gr=C3=BCssen<p>John Doe<br></p>
45
+
46
+ --047d7b34385e48d21704e16a89ba--
@@ -0,0 +1,29 @@
1
+ Date: Sat, 09 Jun 2012 21:09:29 +0200
2
+ From: king.crown@nigerian-lottery.com
3
+ To: you@example.com
4
+ Message-ID: <4fd39f695c947_827580443948558fd@example.com>
5
+ Subject: Send some test mails
6
+ Mime-Version: 1.0
7
+ Content-Type: text/plain;
8
+ charset=UTF-8
9
+ Content-Transfer-Encoding: 7bit
10
+
11
+ Hi there,
12
+ I am writing to you in order to inform you that you have
13
+ won the Nigerian Lottery.
14
+
15
+ In order to claim your win, please transfer the handling
16
+ fee to the following account:
17
+
18
+ AMOUNT: EUR 1234,56
19
+ IBAN: AT571234500234573201
20
+ BIC: ATBAATWWXXX
21
+
22
+ Sincerely
23
+
24
+ Toast Bread
25
+ King Crown
26
+ Head of The Nigerian Lottery
27
+
28
+ --
29
+ Foo
@@ -0,0 +1 @@
1
+ {"id":1,"description":"Import standard JSON with annotation","entry":"20120610T090557Z","status":"pending","uuid":"4c0be862-1f5f-4fc5-bd25-8a704c1207d2","annotations":[{"entry":"20120610T090617Z","description":"And here is somemulti-line annotation"}]}
@@ -0,0 +1 @@
1
+ {"description":"Import minimal JSON","annotations":[{"entry":"20120610T090617Z","description":"And here is some annotation"}]}
@@ -0,0 +1,4 @@
1
+ function assert_equal()
2
+ {
3
+ diff <(echo "$2" ) <(echo "$1") || [ "$?" -eq 0 ] || exit 1
4
+ }
@@ -0,0 +1,16 @@
1
+ #!/bin/bash
2
+ #
3
+ # twmail hook script that is called for every mail.
4
+ #
5
+ # The environment variables available to a hook are:
6
+ #
7
+ # TWMAIL_DATE
8
+ # TWMAIL_MESSAGE_ID
9
+ # TWMAIL_FROM
10
+ # TWMAIL_TO
11
+ # TWMAIL_SUBJECT
12
+ # TWMAIL_BODY
13
+ #
14
+ # This script assumes that task-uuid is in the path.
15
+ #
16
+ task $(task-uuid $TWMAIL_SUBJECT) annotate $TWMAIL_BODY > /dev/null
@@ -0,0 +1,8 @@
1
+ require 'bundler'
2
+ #Bundler.require
3
+
4
+ require 'twtest'
5
+ require 'test/unit'
6
+
7
+ module TaskWarriorMailTest
8
+ end
@@ -0,0 +1,15 @@
1
+ require 'test_helper'
2
+ require 'mail'
3
+
4
+ class TestMail < Test::Unit::TestCase
5
+ def test_signature
6
+ m = Mail.new(File.read(fixture('mail_with_signature.txt')))
7
+ assert(m)
8
+ assert(m.body.include?('-- '))
9
+ assert_false(m.body.decoded.split('-- ').include?('-- '))
10
+ end
11
+
12
+ def fixture(name)
13
+ File.join(File.dirname(__FILE__), '..', 'fixtures', name)
14
+ end
15
+ end
@@ -0,0 +1,120 @@
1
+ require 'open3'
2
+ require 'test/unit'
3
+
4
+ class Command
5
+ def exec(cmd = '', args = {})
6
+ env.each{|k,v| ENV[k.to_s] = v.to_s}
7
+ line = build_line(cmd, args)
8
+ Open3.capture3(line)
9
+ end
10
+
11
+ def env
12
+ {}
13
+ end
14
+
15
+ def default_args
16
+ {}
17
+ end
18
+
19
+ def executable
20
+ raise 'Subclasses must override this method'
21
+ end
22
+
23
+ private
24
+
25
+ def build_line(cmd = '', args = {})
26
+ [].tap{|line|
27
+ line << executable
28
+ line << default_args.merge(args).map{|k,v| "#{Shellwords.escape(k.strip)}=#{Shellwords.escape(v.strip)}"}.join(' ')
29
+ line << cmd.strip
30
+ line.reject!{|part| part.empty?}
31
+ }.join(' ')
32
+ end
33
+
34
+ def overrides(env)
35
+ intersection = env.keys.to_set & ENV.keys.to_set
36
+ ENV.select{|k,v| intersection.include?(k)}
37
+ end
38
+ end
39
+
40
+ class TaskWarriorCommand < Command
41
+ attr_accessor :data_dir
42
+
43
+ def version
44
+ exec('_version')
45
+ end
46
+
47
+ def count
48
+ exec('count')
49
+ end
50
+
51
+ def env
52
+ raise "data_dir must not be empty for '#{executable}'" if @data_dir.nil? || data_dir.empty?
53
+ {TASKDATA: @data_dir}
54
+ end
55
+
56
+ def default_args
57
+ {'rc.verbose' => 'off', 'rc.json.array' => 'on'}
58
+ end
59
+
60
+ def executable
61
+ 'task'
62
+ end
63
+ end
64
+
65
+ class TaskUUID < TaskWarriorCommand
66
+ def create(description)
67
+ exec(description)
68
+ end
69
+
70
+ def executable
71
+ # The gem version that uses the binstub fails if the script is not Ruby
72
+ #'task-uuid'
73
+
74
+ # The local version, called directly, works fine even if it's a Bash script
75
+ File.join(File.dirname(__FILE__), '..', '..', 'bin', 'task-uuid')
76
+ end
77
+
78
+ def default_args
79
+ {}
80
+ end
81
+ end
82
+
83
+ class TestTaskUUID < Test::Unit::TestCase#Minitest::Test
84
+ def setup
85
+ @tw = TaskWarriorCommand.new
86
+ @tw.data_dir = Dir.mktmpdir(name)
87
+
88
+ raise "TASKRC must not be set, but it is #{ENV['TASKRC']}" if ENV['TASKRC']
89
+
90
+ end
91
+
92
+ def teardown
93
+ FileUtils.rm_rf(@tw.data_dir)
94
+ end
95
+
96
+ def test_version
97
+ out, err, status = @tw.version
98
+ assert(status.success?)
99
+ assert_empty(err)
100
+ assert_not_empty(out)
101
+ assert_equal('2.3.0', out.chomp)
102
+ end
103
+
104
+ def test_empty
105
+ out, err, status = @tw.count
106
+ assert(status.success?)
107
+ assert_empty(err)
108
+ assert_not_empty(out)
109
+ assert_equal('0', out.chomp)
110
+ end
111
+
112
+ def test_task_uuid
113
+ task_uuid = TaskUUID.new
114
+ task_uuid.data_dir = Dir.mktmpdir(name)
115
+ out, err, status = task_uuid.create('foo bar')
116
+ assert_empty(err)
117
+ assert_not_empty(out)
118
+ assert_match(/[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}/, out.chomp)
119
+ end
120
+ end
@@ -0,0 +1,78 @@
1
+ require 'test_helper'
2
+
3
+ class TestHelpers < TaskWarrior::Test::Integration::TestCase
4
+ def teardown
5
+ ENV['TASKRC'] = nil
6
+ super
7
+ end
8
+
9
+ def test_regular
10
+ result = deliver_fixture(0, fixture('mail_regular.txt'))
11
+ assert_empty(result)
12
+ assert_equal(1, task('count').to_i)
13
+
14
+ tasks = export_tasks
15
+ assert_equal(1, tasks.size)
16
+ assert_equal('Send some test mails', tasks.first['description'])
17
+
18
+ assert_equal(1, tasks.first['annotations'].size)
19
+ assert(tasks.first['annotations'].first.to_s =~ /Nigeria/)
20
+ end
21
+
22
+ def test_regular_with_signature
23
+ result = deliver_fixture(0, fixture('mail_with_signature.txt'))
24
+ assert_empty(result)
25
+ assert_equal(1, task('count').to_i)
26
+
27
+ tasks = export_tasks
28
+ assert_equal(1, tasks.size)
29
+ assert_equal('Send some test mails', tasks.first['description'])
30
+
31
+ assert_equal(1, tasks.first['annotations'].size)
32
+ assert(tasks.first['annotations'].first.to_s =~ /Nigeria/)
33
+ end
34
+
35
+ def test_multipart
36
+ result = deliver_fixture(0, fixture('mail_multipart.txt'))
37
+ assert_empty(result)
38
+ assert_equal(1, task('count').to_i)
39
+
40
+ tasks = export_tasks
41
+ assert_equal(1, tasks.size)
42
+ assert_equal('MenTaLguY: Atomic Operations in Ruby', tasks.first['description'])
43
+
44
+ assert_equal(1, tasks.first['annotations'].size)
45
+ assert(tasks.first['annotations'].first.to_s =~ /atomic/)
46
+ end
47
+
48
+ def test_separator
49
+ result = deliver_fixture(0, fixture('mail_separator.txt'))
50
+ assert_empty(result)
51
+ assert_equal(1, task('count').to_i)
52
+
53
+ tasks = export_tasks
54
+ assert_equal(1, tasks.size)
55
+ task = tasks.first
56
+ assert_equal('Try IFTTT / PinReadable', task['description'])
57
+ assert_true(task['tags'].include?('kindle'))
58
+ end
59
+
60
+ def test_missing_fixture
61
+ result = deliver_fixture(1, 'missing fixture')
62
+ assert_empty(result)
63
+ end
64
+
65
+ protected
66
+
67
+ def deliver_fixture(status, fixture)
68
+ twmail = File.join(File.dirname(__FILE__), '..', '..', 'bin', 'twmail')
69
+ ENV['TASKRC'] = @taskrc_file
70
+ output = %x[cat #{fixture} | #{twmail}]
71
+ assert_equal(status, $?.exitstatus)
72
+ output
73
+ end
74
+
75
+ def fixture(name)
76
+ File.join(File.dirname(__FILE__), '..', 'fixtures', name)
77
+ end
78
+ end
@@ -0,0 +1,35 @@
1
+ #!/bin/bash
2
+
3
+ #
4
+ # Unit test for twmail_hooks
5
+ #
6
+
7
+ # Data locations
8
+ DIRNAME=$(cd $(dirname $0);pwd)
9
+ DATA_DIR=$(mktemp -dt $(basename $0))
10
+ export TASKRC=$(mktemp -t taskrc)
11
+
12
+ source $DIRNAME/../helpers/assertions
13
+
14
+ # Create custom taskrc
15
+ cat > $TASKRC <<HERE
16
+ data.location=$DATA_DIR
17
+ json.array=on
18
+ verbose=off
19
+ HERE
20
+
21
+ # Canonical path to test_hook
22
+ export TWMAIL_HOOK=$DIRNAME/../helpers/test_hook
23
+
24
+ # Create new task using mail fixture
25
+ cat $DIRNAME/../fixtures/mail01.txt | $DIRNAME/../../bin/twmail_hooks
26
+
27
+ # Read back and run assertions
28
+ SUBJECT=$(task export | ruby -r json -e "puts JSON[ARGF.read].first['description']")
29
+
30
+ assert_equal "Send some test mails" "$SUBJECT"
31
+
32
+ # Clean up
33
+ rm -Rf $DATA_DIR
34
+ rm -Rf $TASKRC
35
+ export TASKRC=
@@ -0,0 +1,25 @@
1
+ # -*- encoding: utf-8 -*-
2
+ require File.expand_path('../lib/twmail/version', __FILE__)
3
+
4
+ Gem::Specification.new do |gem|
5
+ gem.authors = ["Nicholas E. Rabenau"]
6
+ gem.email = ["nerab@gmx.net"]
7
+ gem.description = %q{Mail new tasks to your TaskWarrior inbox}
8
+ gem.summary = %q{Use fetchmail and the scripts in this project to mail tasks to your local TaskWarrior installation}
9
+ gem.homepage = 'https://github.com/nerab/TaskWarriorMail'
10
+
11
+ gem.files = `git ls-files`.split($\)
12
+ gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
13
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
14
+ gem.name = "twmail"
15
+ gem.require_paths = ["lib"]
16
+ gem.version = TaskWarriorMail::VERSION
17
+
18
+ gem.add_dependency 'twtest'
19
+ gem.add_dependency 'mail'
20
+ gem.add_dependency 'multi_json'
21
+
22
+ gem.add_development_dependency 'guard-test'
23
+ gem.add_development_dependency 'guard-bundler'
24
+ gem.add_development_dependency 'pry'
25
+ end
metadata ADDED
@@ -0,0 +1,171 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: twmail
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.3
5
+ platform: ruby
6
+ authors:
7
+ - Nicholas E. Rabenau
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-11-15 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: twtest
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: mail
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: multi_json
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: guard-test
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: guard-bundler
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: pry
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ description: Mail new tasks to your TaskWarrior inbox
98
+ email:
99
+ - nerab@gmx.net
100
+ executables:
101
+ - task-uuid
102
+ - twmail
103
+ - twmail_hooks
104
+ extensions: []
105
+ extra_rdoc_files: []
106
+ files:
107
+ - ".gitignore"
108
+ - Gemfile
109
+ - Guardfile
110
+ - LICENSE
111
+ - README.md
112
+ - Rakefile
113
+ - bin/task-uuid
114
+ - bin/twmail
115
+ - bin/twmail_hooks
116
+ - lib/twmail.rb
117
+ - lib/twmail/version.rb
118
+ - test/fixtures/mail_empty.txt
119
+ - test/fixtures/mail_multipart.txt
120
+ - test/fixtures/mail_regular.txt
121
+ - test/fixtures/mail_separator.txt
122
+ - test/fixtures/mail_with_signature.txt
123
+ - test/fixtures/task01.json
124
+ - test/fixtures/task02.json
125
+ - test/helpers/assertions
126
+ - test/helpers/test_hook
127
+ - test/test_helper.rb
128
+ - test/unit/test_mail.rb
129
+ - test/unit/test_task_uuid.rb
130
+ - test/unit/test_twmail.rb
131
+ - test/unit/test_twmail_hooks
132
+ - twmail.gemspec
133
+ homepage: https://github.com/nerab/TaskWarriorMail
134
+ licenses: []
135
+ metadata: {}
136
+ post_install_message:
137
+ rdoc_options: []
138
+ require_paths:
139
+ - lib
140
+ required_ruby_version: !ruby/object:Gem::Requirement
141
+ requirements:
142
+ - - ">="
143
+ - !ruby/object:Gem::Version
144
+ version: '0'
145
+ required_rubygems_version: !ruby/object:Gem::Requirement
146
+ requirements:
147
+ - - ">="
148
+ - !ruby/object:Gem::Version
149
+ version: '0'
150
+ requirements: []
151
+ rubyforge_project:
152
+ rubygems_version: 2.0.14
153
+ signing_key:
154
+ specification_version: 4
155
+ summary: Use fetchmail and the scripts in this project to mail tasks to your local
156
+ TaskWarrior installation
157
+ test_files:
158
+ - test/fixtures/mail_empty.txt
159
+ - test/fixtures/mail_multipart.txt
160
+ - test/fixtures/mail_regular.txt
161
+ - test/fixtures/mail_separator.txt
162
+ - test/fixtures/mail_with_signature.txt
163
+ - test/fixtures/task01.json
164
+ - test/fixtures/task02.json
165
+ - test/helpers/assertions
166
+ - test/helpers/test_hook
167
+ - test/test_helper.rb
168
+ - test/unit/test_mail.rb
169
+ - test/unit/test_task_uuid.rb
170
+ - test/unit/test_twmail.rb
171
+ - test/unit/test_twmail_hooks