mirror of
https://github.com/TracksApp/tracks.git
synced 2026-01-02 15:28:50 +01:00
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:
parent
1441d53808
commit
2f043911c6
7 changed files with 180 additions and 25 deletions
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 <131012 >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><</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>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>
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
require 'date'
|
||||
require 'test/unit'
|
||||
require 'active_support/core_ext/object/blank'
|
||||
require_relative '../../app/services/rich_message_extractor.rb'
|
||||
|
|
@ -5,11 +6,15 @@ require_relative '../../app/services/rich_message_extractor.rb'
|
|||
class RichMessageExtractorTest < Test::Unit::TestCase
|
||||
|
||||
def test_message_with_all_options
|
||||
message = "ohai@some-context>in-this-project"
|
||||
message = "ohai@some-context~this-project>131012<131014#tag1#tag2*"
|
||||
extractor = RichMessageExtractor.new(message)
|
||||
assert_equal "ohai", extractor.description
|
||||
assert_equal "some-context", extractor.context
|
||||
assert_equal "in-this-project", extractor.project
|
||||
assert_equal "this-project", extractor.project
|
||||
assert_equal "2013-10-12", extractor.show_from.to_s
|
||||
assert_equal "2013-10-14", extractor.due.to_s
|
||||
assert_equal ["tag1","tag2"], extractor.tags
|
||||
assert extractor.starred?
|
||||
end
|
||||
|
||||
def test_message_without_project
|
||||
|
|
@ -20,12 +25,12 @@ class RichMessageExtractorTest < Test::Unit::TestCase
|
|||
assert_equal nil, extractor.project
|
||||
end
|
||||
|
||||
def test_message_without_project
|
||||
message = " ohai @ some-context"
|
||||
def test_message_without_context
|
||||
message = " ohai ~ some-project"
|
||||
extractor = RichMessageExtractor.new(message)
|
||||
assert_equal "ohai", extractor.description
|
||||
assert_equal "some-context", extractor.context
|
||||
assert_equal nil, extractor.project
|
||||
assert_equal "", extractor.context
|
||||
assert_equal "some-project", extractor.project
|
||||
end
|
||||
|
||||
def test_message_without_project_or_context
|
||||
|
|
@ -52,4 +57,52 @@ class RichMessageExtractorTest < Test::Unit::TestCase
|
|||
assert_equal nil, extractor.project
|
||||
end
|
||||
|
||||
def test_message_with_tags
|
||||
message = "some tags#tag 1#tag2"
|
||||
extractor = RichMessageExtractor.new(message)
|
||||
assert_equal ["tag 1","tag2"], extractor.tags
|
||||
end
|
||||
|
||||
def test_message_with_no_tags
|
||||
message = "no tags"
|
||||
extractor = RichMessageExtractor.new(message)
|
||||
assert_equal nil, extractor.tags
|
||||
end
|
||||
|
||||
def test_message_with_due_date
|
||||
message = "datetest<141013"
|
||||
extractor = RichMessageExtractor.new(message)
|
||||
assert_equal "2014-10-13", extractor.due.to_s
|
||||
end
|
||||
|
||||
def test_message_with_no_due_date
|
||||
message = "no date"
|
||||
extractor = RichMessageExtractor.new(message)
|
||||
assert_equal nil, extractor.due
|
||||
end
|
||||
|
||||
def test_message_with_show_from
|
||||
message = "datetest>161013"
|
||||
extractor = RichMessageExtractor.new(message)
|
||||
assert_equal "2016-10-13", extractor.show_from.to_s
|
||||
end
|
||||
|
||||
def test_message_with_no_show_from
|
||||
message = "no tickler"
|
||||
extractor = RichMessageExtractor.new(message)
|
||||
assert_equal nil, extractor.show_from
|
||||
end
|
||||
|
||||
def test_message_with_star
|
||||
message = "star test*"
|
||||
extractor = RichMessageExtractor.new(message)
|
||||
assert extractor.starred?
|
||||
end
|
||||
|
||||
def test_message_with_no_star
|
||||
message = "no star test"
|
||||
extractor = RichMessageExtractor.new(message)
|
||||
refute extractor.starred?
|
||||
end
|
||||
|
||||
end
|
||||
|
|
|
|||
|
|
@ -18,4 +18,23 @@ class TodoFromRichMessageTest < ActiveSupport::TestCase
|
|||
assert_equal default_context_id, new_todo.context_id
|
||||
end
|
||||
|
||||
def test_from_rich_message_adds_all_fields
|
||||
user = @completed.user
|
||||
context = Context.create(:name => 'context')
|
||||
project = Project.create(:name => 'project')
|
||||
message = "description@context~project>131014<131017#tag1#tag2*"
|
||||
builder = TodoFromRichMessage.new(user, context.id, message, "notes")
|
||||
new_todo = builder.construct
|
||||
|
||||
assert_not_nil new_todo
|
||||
assert_equal "description", new_todo.description
|
||||
assert_equal "notes", new_todo.notes
|
||||
assert_equal context.id, new_todo.context_id
|
||||
assert_equal project.id, new_todo.project_id
|
||||
assert_equal "2013-10-14 00:00:00 +0100", new_todo.show_from.to_s
|
||||
assert_equal "2013-10-17 00:00:00 +0100", new_todo.due.to_s
|
||||
assert_equal "starred, tag1, tag2", new_todo.tags.to_s
|
||||
assert new_todo.starred?
|
||||
end
|
||||
|
||||
end
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue