Extend RichMessage format to include other data

Uses new Regex to detect:

 @ context
 ~ project
 > tickler-date
 < due-date
 # tag (repeatable)
 * (starred)
This commit is contained in:
Greg Sutcliffe 2013-10-13 22:11:55 +01:00
parent 1441d53808
commit 2f043911c6
7 changed files with 180 additions and 25 deletions

View file

@ -1,6 +1,4 @@
class MessageGateway < ActionMailer::Base
include ActionView::Helpers::SanitizeHelper
extend ActionView::Helpers::SanitizeHelper::ClassMethods
def receive(email)
user = get_receiving_user_from_email_address(email)
@ -85,11 +83,11 @@ class MessageGateway < ActionMailer::Base
end
def get_text_or_nil(text)
return text ? sanitize(text.strip) : nil
return text ? text.strip : nil
end
def get_decoded_text_or_nil(text)
return text ? sanitize(text.decoded.strip) : nil
return text ? text.decoded.strip : nil
end
def get_first_text_plain_part(email)
@ -99,7 +97,7 @@ class MessageGateway < ActionMailer::Base
# remove all parts that are not text/plain
parts.reject{|part| !part.content_type.start_with?("text/plain") }
return parts.count > 0 ? sanitize(parts[0].decoded.strip) : ""
return parts.count > 0 ? parts[0].decoded.strip : ""
end
def get_all_parts(parts)

View file

@ -12,6 +12,7 @@ class Project < ActiveRecord::Base
scope :uncompleted, -> { where("NOT(state = ?)", 'completed') }
scope :with_name_or_description, lambda { |body| where("name LIKE ? OR description LIKE ?", body, body) }
scope :with_namepart, lambda { |body| where("name LIKE ?", body + '%') }
validates_presence_of :name
validates_length_of :name, :maximum => 255

View file

@ -1,28 +1,72 @@
require 'date'
class RichMessageExtractor
include ActionView::Helpers::SanitizeHelper
extend ActionView::Helpers::SanitizeHelper::ClassMethods
RICH_MESSAGE_FIELDS_REGEX = /([^>@]*)@?([^>]*)>?(.*)/
PROJECT_MARKER = '~'
CONTEXT_MARKER = '@'
TICKLER_MARKER = '>'
DUE_MARKER = '<'
TAG_MARKER = '#'
STAR_MARKER = '*'
ALL_MARKERS = [
PROJECT_MARKER,
CONTEXT_MARKER,
TICKLER_MARKER,
DUE_MARKER,
TAG_MARKER,
STAR_MARKER
]
def initialize(message)
@message = message
end
def description
fields[1].strip
desc = select_for('')
desc.blank? ? '' : sanitize(desc[1].strip)
end
def context
fields[2].strip
context = select_for(CONTEXT_MARKER)
context.blank? ? '' : sanitize(context[1].strip)
end
def project
stripped = fields[3].strip
stripped.blank? ? nil : stripped
project = select_for PROJECT_MARKER
project.blank? ? nil : sanitize(project[1].strip)
end
def tags
string = @message.dup
tags = []
# Regex only matches one tag, so recurse until we have them all
while string.match /#(.*?)(?=[#{ALL_MARKERS.join}]|\Z)/
tags << sanitize($1)
string.gsub!(/##{$1}/,'')
end
tags.empty? ? nil : tags
end
def due
due = select_for DUE_MARKER
due.blank? ? nil : Date.parse(due[1].strip)
end
def show_from
show_from = select_for TICKLER_MARKER
show_from.blank? ? nil : Date.parse(show_from[1].strip)
end
def starred?
@message.include? '*'
end
private
def fields
@message.match(RICH_MESSAGE_FIELDS_REGEX)
end
def select_for symbol
@message.match /#{symbol}(.*?)(?=[#{ALL_MARKERS.join}]|\Z)/
end
end

View file

@ -14,6 +14,10 @@ class TodoFromRichMessage
description = extractor.description
context = extractor.context
project = extractor.project
show_from = extractor.show_from
due = extractor.due
tags = extractor.tags
star = extractor.starred?
context_id = default_context_id
if context.present?
@ -33,17 +37,21 @@ class TodoFromRichMessage
found_project.name = project[4..259].strip
found_project.save!
else
found_project = user.projects.active.find_by_namepart(project)
found_project = user.projects.find_by_namepart(project) if found_project.nil?
found_project = user.projects.active.with_namepart(project).first
found_project = user.projects.with_namepart(project).first if found_project.nil?
end
project_id = found_project.id unless found_project.nil?
end
todo = user.todos.build
todo = user.todos.build
todo.description = description
todo.raw_notes = notes
todo.context_id = context_id
todo.project_id = project_id unless project_id.nil?
todo.raw_notes = notes
todo.context_id = context_id
todo.project_id = project_id unless project_id.nil?
todo.show_from = show_from if show_from.is_a? Date
todo.due = due if due.is_a? Date
todo.tag_with tags unless tags.nil? || tags.empty?
todo.starred = star
todo
end
end

View file

@ -14,6 +14,7 @@
<li><a href="#email-cron-section">Automatically Email Yourself Upcoming Actions</a></li>
<li><a href="#message_gateway">Integrate Tracks with an email server to be able to send an action through email to Tracks</a></li>
<li><a href="#mailgun">Send emails to Tracks with Mailgun</a>
<li><a href="#todo_rich_message_format">Rich Todo Message email format</a>
<li><a href="#google_gadget">Add Tracks as a Google Gmail gadget</a></li>
</ul><br/>
<p>Do you have one of your own to add?
@ -122,6 +123,7 @@
<a name="mailgun"> </a>
<h2>Send emails to Tracks with Mailgun</h2>
<p>
If you want to email tasks to Tracks, but cannot run a mailserver on the same host,
you could use the <a href='www.mailgun.com'>Mailgun</a> support built in to Tracks.
</p>
@ -155,6 +157,36 @@ mailmap:
<p>All the comments about the email format from the section above apply to the
Mailgun handling, as the data is processed the same way</p>
<a name="todo_rich_message_format"> </a>
<h2>Rich Todo Message Format</h2>
<p> For both of the above methods, the follow format can be used:</p>
<pre>my awesome todo @context ~project &lt;131012 &gt;131009 #tag1 #tag2 *</pre>
<p>The fields are:</p>
<table>
<tr>
<th>Symbol</th><th>Meaning</th>
</tr>
<tr>
<td>@</td><td>The context to place the Todo in</td>
</tr>
<tr>
<td>~</td><td>The project to place the Todo in</td>
</tr>
<tr>
<td>&lt;</td><td>The due date for the Todo (may be 2 digits for day, 4 digits for month-day, or 6 digits for yeah-month-day)</td>
</tr>
<tr>
<td>&gt;</td><td>The due date for the Todo (may be 2 digits for day, 4 digits for month-day, or 6 digits for yeah-month-day)</td>
</tr>
<tr>
<td>#</td><td>A tag to apply to the Todo - may be used multiple times</td>
</tr>
<tr>
<td>*</td><td>Flag to star the Todo</td>
</tr>
</table>
<p>All symbols are optional, and text up to the first symbol (or end of string) is used as the description of the todo</p>
<a name="google_gadget"> </a>
<h2>Add Tracks as a Google Gmail gadget</h2>
<p>