mailcatcher 0.2.4 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +10 -4
- data/bin/mailcatcher +1 -41
- data/lib/mail_catcher.rb +72 -9
- data/lib/mail_catcher/mail.rb +9 -3
- data/lib/mail_catcher/web.rb +20 -1
- data/public/javascripts/application.js +35 -31
- data/public/javascripts/date.js +104 -0
- data/public/stylesheets/application.css +33 -10
- data/views/index.haml +9 -5
- metadata +30 -34
- data/Rakefile +0 -44
- data/VERSION +0 -1
- data/contrib/mailcatcherd.bash +0 -35
data/README.md
CHANGED
@@ -4,6 +4,8 @@ Catches mail and serves it through a dream.
|
|
4
4
|
|
5
5
|
MailCatcher runs a super simple SMTP server which catches any message sent to it to display in a web interface. Run mailcatcher, set your favourite app to deliver to smtp://127.0.0.1:1025 instead of your default SMTP server, then check out http://127.0.0.1:1080 to see the mail that's arrived so far.
|
6
6
|
|
7
|
+
![MailCatcher screenshot](http://puu.sh/2fZR)
|
8
|
+
|
7
9
|
## How
|
8
10
|
|
9
11
|
1. `gem install mailcatcher`
|
@@ -15,21 +17,25 @@ MailCatcher runs a super simple SMTP server which catches any message sent to it
|
|
15
17
|
|
16
18
|
* Catches all mail and stores it for display.
|
17
19
|
* Shows HTML, Plain Text and Source version of messages, as applicable.
|
18
|
-
*
|
20
|
+
* Download original email to view in your native mail client(s).
|
21
|
+
* Rewrites HTML enabling display of embedded, inline images/etc and open links in a new window. (currently very basic)
|
19
22
|
* Lists attachments and allows separate downloading of parts.
|
20
23
|
* Written super-simply in EventMachine, easy to dig in and change.
|
21
24
|
* Command line options to override the default SMTP/HTTP IP and port settings.
|
22
25
|
* Mail appears instantly if your browser supports [WebSockets][websockets].
|
26
|
+
* Daemonizable to run in the background.
|
23
27
|
|
24
28
|
## Caveats
|
25
29
|
|
26
|
-
* Mail
|
27
|
-
* Mail proccessing is fairly basic but easily modified. If something doesn't work for you, fork and fix it or file an issue and let me know. Include the whole message you're having problems with.
|
30
|
+
* Mail processing is fairly basic but easily modified. If something doesn't work for you, fork and fix it or file an issue and let me know. Include the whole message you're having problems with.
|
28
31
|
* The interface is very basic and has not been tested on many browsers yet.
|
29
32
|
|
33
|
+
## API
|
34
|
+
|
35
|
+
A fairly RESTful URL schema means you can download a list of messages in JSON from `/messages`, each message's metadata with `/messages/:id.json`, and then the pertinent parts with `/messages/:id.html` and `/messages/:id.plain` for the default HTML and plain text version, `/messages/:id/:cid` for individual attachments by CID, or the whole message with `/messages/:id.source`.
|
36
|
+
|
30
37
|
## TODO
|
31
38
|
|
32
|
-
* Download link to view original message in mail client.
|
33
39
|
* Growl support.
|
34
40
|
* Test suite.
|
35
41
|
* Add mail delivery on request, optionally multiple times.
|
data/bin/mailcatcher
CHANGED
@@ -1,44 +1,4 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
2
|
|
3
|
-
$: << File.expand_path(File.join(File.dirname(__FILE__), '../lib'))
|
4
|
-
|
5
3
|
require 'mail_catcher'
|
6
|
-
|
7
|
-
|
8
|
-
options = {}
|
9
|
-
|
10
|
-
OptionParser.new do |opts|
|
11
|
-
opts.banner = 'Usage: mailcatcher [options]'
|
12
|
-
|
13
|
-
options[:smtp_ip] = '127.0.0.1'
|
14
|
-
opts.on('--smtp-ip IP', 'Set the ip address of the smtp server') do |ip|
|
15
|
-
options[:smtp_ip] = ip
|
16
|
-
end
|
17
|
-
|
18
|
-
options[:smtp_port] = 1025
|
19
|
-
opts.on('--smtp-port PORT', Integer, 'Set the port of the smtp server') do |port|
|
20
|
-
options[:smtp_port] = port
|
21
|
-
end
|
22
|
-
|
23
|
-
options[:http_ip] = '127.0.0.1'
|
24
|
-
opts.on('--http-ip IP', 'Set the ip address of the http server') do |ip|
|
25
|
-
options[:http_ip] = ip
|
26
|
-
end
|
27
|
-
|
28
|
-
options[:http_port] = 1080
|
29
|
-
opts.on('--http-port PORT', Integer, 'Set the port address of the http server') do |port|
|
30
|
-
options[:http_port] = port
|
31
|
-
end
|
32
|
-
|
33
|
-
options[:verbose] = false
|
34
|
-
opts.on('-v', '--verbose', 'Be more verbose') do
|
35
|
-
options[:verbose] = true
|
36
|
-
end
|
37
|
-
|
38
|
-
opts.on('-h', '--help', 'Display this help information') do
|
39
|
-
puts opts
|
40
|
-
exit!
|
41
|
-
end
|
42
|
-
end.parse!
|
43
|
-
|
44
|
-
MailCatcher.run(options)
|
4
|
+
MailCatcher.run!
|
data/lib/mail_catcher.rb
CHANGED
@@ -1,26 +1,89 @@
|
|
1
|
+
require 'active_support/all'
|
2
|
+
require 'daemons'
|
1
3
|
require 'eventmachine'
|
2
4
|
require 'thin'
|
3
5
|
|
4
6
|
module MailCatcher
|
5
|
-
|
6
|
-
autoload :Mail, 'mail_catcher/mail'
|
7
|
-
autoload :Smtp, 'mail_catcher/smtp'
|
8
|
-
autoload :Web, 'mail_catcher/web'
|
7
|
+
extend ActiveSupport::Autoload
|
9
8
|
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
9
|
+
autoload :Events
|
10
|
+
autoload :Mail
|
11
|
+
autoload :Smtp
|
12
|
+
autoload :Web
|
13
|
+
|
14
|
+
@@defaults = {
|
15
|
+
:smtp_ip => '127.0.0.1',
|
16
|
+
:smtp_port => '1025',
|
17
|
+
:http_ip => '127.0.0.1',
|
18
|
+
:http_port => '1080',
|
19
|
+
:verbose => false,
|
20
|
+
:daemon => false,
|
21
|
+
}
|
22
|
+
|
23
|
+
def self.parse! arguments=ARGV, defaults=@@defaults
|
24
|
+
@@defaults.dup.tap do |options|
|
25
|
+
OptionParser.new do |parser|
|
26
|
+
parser.banner = 'Usage: mailcatcher [options]'
|
27
|
+
|
28
|
+
parser.on('--ip IP', 'Set the ip address of both servers') do |ip|
|
29
|
+
options[:smtp_ip] = options[:http_ip] = ip
|
30
|
+
end
|
31
|
+
|
32
|
+
parser.on('--smtp-ip IP', 'Set the ip address of the smtp server') do |ip|
|
33
|
+
options[:smtp_ip] = ip
|
34
|
+
end
|
35
|
+
|
36
|
+
parser.on('--smtp-port PORT', Integer, 'Set the port of the smtp server') do |port|
|
37
|
+
options[:smtp_port] = port
|
38
|
+
end
|
39
|
+
|
40
|
+
parser.on('--http-ip IP', 'Set the ip address of the http server') do |ip|
|
41
|
+
options[:http_ip] = ip
|
42
|
+
end
|
43
|
+
|
44
|
+
parser.on('--http-port PORT', Integer, 'Set the port address of the http server') do |port|
|
45
|
+
options[:http_port] = port
|
46
|
+
end
|
47
|
+
|
48
|
+
parser.on('-d', '--daemon', 'Run as a daemon') do
|
49
|
+
options[:daemon] = true
|
50
|
+
end
|
51
|
+
|
52
|
+
parser.on('-v', '--verbose', 'Be more verbose') do
|
53
|
+
options[:verbose] = true
|
54
|
+
end
|
55
|
+
|
56
|
+
parser.on('-h', '--help', 'Display this help information') do
|
57
|
+
puts parser
|
58
|
+
exit!
|
59
|
+
end
|
60
|
+
end.parse!
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def self.run! options=nil
|
65
|
+
# If we are passed options, fill in the blanks
|
66
|
+
options &&= @@defaults.merge options
|
67
|
+
# Otherwise, parse them from ARGV
|
68
|
+
options ||= parse!
|
15
69
|
|
16
70
|
puts "Starting MailCatcher"
|
17
71
|
puts "==> smtp://#{options[:smtp_ip]}:#{options[:smtp_port]}"
|
18
72
|
puts "==> http://#{options[:http_ip]}:#{options[:http_port]}"
|
19
73
|
|
20
74
|
Thin::Logging.silent = true
|
75
|
+
|
21
76
|
EventMachine.run do
|
22
77
|
EventMachine.start_server options[:smtp_ip], options[:smtp_port], Smtp
|
78
|
+
|
23
79
|
Thin::Server.start options[:http_ip], options[:http_port], Web
|
80
|
+
|
81
|
+
if options[:daemon]
|
82
|
+
# Make sure the servers start before daemonizing.
|
83
|
+
EventMachine.next_tick do
|
84
|
+
Daemons.daemonize :app_name => "mailcatcher"
|
85
|
+
end
|
86
|
+
end
|
24
87
|
end
|
25
88
|
end
|
26
89
|
end
|
data/lib/mail_catcher/mail.rb
CHANGED
@@ -40,7 +40,7 @@ module MailCatcher::Mail
|
|
40
40
|
@@add_message_query ||= db.prepare("INSERT INTO message (sender, recipients, subject, source, size, created_at) VALUES (?, ?, ?, ?, ?, datetime('now'))")
|
41
41
|
|
42
42
|
mail = Mail.new(message[:source])
|
43
|
-
result = @@add_message_query.execute(message[:sender], message[:recipients].
|
43
|
+
result = @@add_message_query.execute(message[:sender], message[:recipients].to_json, mail.subject, message[:source], message[:source].length)
|
44
44
|
message_id = db.last_insert_row_id
|
45
45
|
parts = mail.all_parts
|
46
46
|
parts = [mail] if parts.empty?
|
@@ -69,12 +69,18 @@ module MailCatcher::Mail
|
|
69
69
|
|
70
70
|
def messages
|
71
71
|
@@messages_query ||= db.prepare "SELECT id, sender, recipients, subject, size, created_at FROM message ORDER BY created_at DESC"
|
72
|
-
@@messages_query.execute.to_a
|
72
|
+
@@messages_query.execute.to_a.tap do |messages|
|
73
|
+
messages.each do |message|
|
74
|
+
message["recipients"] &&= ActiveSupport::JSON.decode message["recipients"]
|
75
|
+
end
|
76
|
+
end
|
73
77
|
end
|
74
78
|
|
75
79
|
def message(id)
|
76
80
|
@@message_query ||= db.prepare "SELECT * FROM message WHERE id = ? LIMIT 1"
|
77
|
-
@@message_query.execute(id).next
|
81
|
+
@@message_query.execute(id).next.to_hash.tap do |message|
|
82
|
+
message["recipients"] &&= ActiveSupport::JSON.decode message["recipients"]
|
83
|
+
end
|
78
84
|
end
|
79
85
|
|
80
86
|
def message_has_html?(id)
|
data/lib/mail_catcher/web.rb
CHANGED
@@ -54,7 +54,16 @@ module MailCatcher
|
|
54
54
|
id = params[:id].to_i
|
55
55
|
if part = MailCatcher::Mail.message_part_html(id)
|
56
56
|
content_type part["type"], :charset => (part["charset"] || "utf8")
|
57
|
-
|
57
|
+
|
58
|
+
body = part["body"]
|
59
|
+
|
60
|
+
# Rewrite body to link to embedded attachments served by cid
|
61
|
+
body.gsub! /cid:([^'"> ]+)/, "#{id}/\\1"
|
62
|
+
|
63
|
+
# Rewrite body to open links in a new window
|
64
|
+
body.gsub! /<a\s+/, '<a target="_blank" '
|
65
|
+
|
66
|
+
body
|
58
67
|
else
|
59
68
|
not_found
|
60
69
|
end
|
@@ -80,6 +89,16 @@ module MailCatcher
|
|
80
89
|
end
|
81
90
|
end
|
82
91
|
|
92
|
+
get "/messages/:id.eml" do
|
93
|
+
id = params[:id].to_i
|
94
|
+
if message = MailCatcher::Mail.message(id)
|
95
|
+
content_type "message/rfc822"
|
96
|
+
message["source"]
|
97
|
+
else
|
98
|
+
not_found
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
83
102
|
get "/messages/:id/:cid" do
|
84
103
|
id = params[:id].to_i
|
85
104
|
if part = MailCatcher::Mail.message_part_cid(id, params[:cid])
|
@@ -4,24 +4,26 @@ var MailCatcher = {
|
|
4
4
|
MailCatcher.load($(this).attr('data-message-id'));
|
5
5
|
});
|
6
6
|
|
7
|
-
$('#message .
|
7
|
+
$('#message .actions ul li.tab').live('click', function() {
|
8
8
|
MailCatcher.loadBody($('#mail tr.selected').attr('data-message-id'), $(this).attr('data-message-format'));
|
9
9
|
});
|
10
10
|
|
11
11
|
MailCatcher.refresh();
|
12
|
+
|
12
13
|
MailCatcher.subscribe();
|
13
14
|
},
|
14
15
|
|
15
16
|
addMessage: function(message) {
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
17
|
+
$('#mail tbody').append(
|
18
|
+
$('<tr />').attr('data-message-id', message.id.toString())
|
19
|
+
.append($('<td/>').text(message.sender))
|
20
|
+
.append($('<td/>').text((message.recipients || []).join(', ')))
|
21
|
+
.append($('<td/>').text(message.subject))
|
22
|
+
.append($('<td/>').text((new Date(message.created_at)).toString("dddd, d MMM yyyy h:mm:ss tt")))
|
23
|
+
);
|
21
24
|
},
|
22
25
|
|
23
26
|
refresh: function() {
|
24
|
-
console.log('Refreshing messages');
|
25
27
|
$.getJSON('/messages', function(mail) {
|
26
28
|
$.each(mail, function(i, message) {
|
27
29
|
MailCatcher.addMessage(message);
|
@@ -30,27 +32,31 @@ var MailCatcher = {
|
|
30
32
|
},
|
31
33
|
|
32
34
|
subscribe: function () {
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
35
|
+
if (WebSocket !== undefined) {
|
36
|
+
MailCatcher.websocket = new WebSocket("ws" + (window.location.scheme == 'https' ? 's' : '') + "://" + window.location.host + "/messages");
|
37
|
+
MailCatcher.websocket.onmessage = function (event) {
|
38
|
+
MailCatcher.addMessage($.parseJSON(event.data));
|
39
|
+
};
|
40
|
+
} else {
|
41
|
+
if (!MailCatcher.refreshInterval) {
|
42
|
+
MailCatcher.refreshInterval = setInterval(MailCatcher.refresh, 30000);
|
43
|
+
}
|
44
|
+
}
|
38
45
|
},
|
39
46
|
|
40
47
|
load: function(id) {
|
41
48
|
id = id || $('#mail tr.selected').attr('data-message-id');
|
42
49
|
|
43
50
|
if (id !== null) {
|
44
|
-
console.log('Loading message', id);
|
45
|
-
|
46
51
|
$('#mail tbody tr:not([data-message-id="'+id+'"])').removeClass('selected');
|
47
52
|
$('#mail tbody tr[data-message-id="'+id+'"]').addClass('selected');
|
48
|
-
|
49
|
-
|
53
|
+
|
54
|
+
$.getJSON('/messages/' + id + '.json', function(message) {
|
55
|
+
$('#message .received span').text((new Date(message.created_at)).toString("dddd, d MMM yyyy h:mm:ss tt"));
|
50
56
|
$('#message .from span').text(message.sender);
|
51
|
-
$('#message .to span').text(message.recipients);
|
57
|
+
$('#message .to span').text((message.recipients || []).join(', '));
|
52
58
|
$('#message .subject span').text(message.subject);
|
53
|
-
$('#message .
|
59
|
+
$('#message .actions ul li.format').each(function(i, el) {
|
54
60
|
var $el = $(el),
|
55
61
|
format = $el.attr('data-message-format');
|
56
62
|
if ($.inArray(format, message.formats) >= 0) {
|
@@ -59,20 +65,20 @@ var MailCatcher = {
|
|
59
65
|
$el.hide();
|
60
66
|
}
|
61
67
|
});
|
62
|
-
if ($("#message .
|
63
|
-
$("#message .
|
64
|
-
$("#message .
|
68
|
+
if ($("#message .actions ul li.tab.selected:not(:visible)")) {
|
69
|
+
$("#message .actions ul li.tab.selected").removeClass("selected");
|
70
|
+
$("#message .actions ul li.tab:visible:first").addClass("selected");
|
65
71
|
}
|
66
72
|
if (message.attachments.length > 0) {
|
67
|
-
|
68
|
-
$('#message .attachments ul').empty();
|
73
|
+
$('#message .metadata .attachments ul').empty();
|
69
74
|
$.each(message.attachments, function (i, attachment) {
|
70
|
-
$('#message .attachments ul').append($('<li>').append($('<a>').attr('href', attachment['href']).addClass(attachment['type'].split('/', 1)[0]).addClass(attachment['type'].replace('/', '-')).text(attachment['filename'])));
|
75
|
+
$('#message .metadata .attachments ul').append($('<li>').append($('<a>').attr('href', attachment['href']).addClass(attachment['type'].split('/', 1)[0]).addClass(attachment['type'].replace('/', '-')).text(attachment['filename'])));
|
71
76
|
});
|
72
|
-
$('#message .attachments').show();
|
77
|
+
$('#message .metadata .attachments').show();
|
73
78
|
} else {
|
74
|
-
$('#message .attachments').hide();
|
79
|
+
$('#message .metadata .attachments').hide();
|
75
80
|
}
|
81
|
+
$('#message .actions ul li.download a').attr('href', '/messages/' + id + '.eml');
|
76
82
|
MailCatcher.loadBody();
|
77
83
|
});
|
78
84
|
}
|
@@ -80,14 +86,12 @@ var MailCatcher = {
|
|
80
86
|
|
81
87
|
loadBody: function(id, format) {
|
82
88
|
id = id || $('#mail tr.selected').attr('data-message-id');
|
83
|
-
format = format || $('#message .
|
89
|
+
format = format || $('#message .actions ul li.selected').first().attr('data-message-format') || 'html';
|
84
90
|
|
85
|
-
$('#message .
|
86
|
-
$('#message .
|
91
|
+
$('#message .actions ul li.tab[data-message-format="'+format+'"]').addClass('selected');
|
92
|
+
$('#message .actions ul li.tab:not([data-message-format="'+format+'"])').removeClass('selected');
|
87
93
|
|
88
94
|
if (id !== undefined && id !== null) {
|
89
|
-
console.log('Loading message', id, 'in format', format);
|
90
|
-
|
91
95
|
$('#message iframe').attr('src', '/messages/' + id + '.' + format);
|
92
96
|
}
|
93
97
|
}
|
@@ -0,0 +1,104 @@
|
|
1
|
+
/**
|
2
|
+
* Version: 1.0 Alpha-1
|
3
|
+
* Build Date: 13-Nov-2007
|
4
|
+
* Copyright (c) 2006-2007, Coolite Inc. (http://www.coolite.com/). All rights reserved.
|
5
|
+
* License: Licensed under The MIT License. See license.txt and http://www.datejs.com/license/.
|
6
|
+
* Website: http://www.datejs.com/ or http://www.coolite.com/datejs/
|
7
|
+
*/
|
8
|
+
Date.CultureInfo={name:"en-US",englishName:"English (United States)",nativeName:"English (United States)",dayNames:["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"],abbreviatedDayNames:["Sun","Mon","Tue","Wed","Thu","Fri","Sat"],shortestDayNames:["Su","Mo","Tu","We","Th","Fr","Sa"],firstLetterDayNames:["S","M","T","W","T","F","S"],monthNames:["January","February","March","April","May","June","July","August","September","October","November","December"],abbreviatedMonthNames:["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"],amDesignator:"AM",pmDesignator:"PM",firstDayOfWeek:0,twoDigitYearMax:2029,dateElementOrder:"mdy",formatPatterns:{shortDate:"M/d/yyyy",longDate:"dddd, MMMM dd, yyyy",shortTime:"h:mm tt",longTime:"h:mm:ss tt",fullDateTime:"dddd, MMMM dd, yyyy h:mm:ss tt",sortableDateTime:"yyyy-MM-ddTHH:mm:ss",universalSortableDateTime:"yyyy-MM-dd HH:mm:ssZ",rfc1123:"ddd, dd MMM yyyy HH:mm:ss GMT",monthDay:"MMMM dd",yearMonth:"MMMM, yyyy"},regexPatterns:{jan:/^jan(uary)?/i,feb:/^feb(ruary)?/i,mar:/^mar(ch)?/i,apr:/^apr(il)?/i,may:/^may/i,jun:/^jun(e)?/i,jul:/^jul(y)?/i,aug:/^aug(ust)?/i,sep:/^sep(t(ember)?)?/i,oct:/^oct(ober)?/i,nov:/^nov(ember)?/i,dec:/^dec(ember)?/i,sun:/^su(n(day)?)?/i,mon:/^mo(n(day)?)?/i,tue:/^tu(e(s(day)?)?)?/i,wed:/^we(d(nesday)?)?/i,thu:/^th(u(r(s(day)?)?)?)?/i,fri:/^fr(i(day)?)?/i,sat:/^sa(t(urday)?)?/i,future:/^next/i,past:/^last|past|prev(ious)?/i,add:/^(\+|after|from)/i,subtract:/^(\-|before|ago)/i,yesterday:/^yesterday/i,today:/^t(oday)?/i,tomorrow:/^tomorrow/i,now:/^n(ow)?/i,millisecond:/^ms|milli(second)?s?/i,second:/^sec(ond)?s?/i,minute:/^min(ute)?s?/i,hour:/^h(ou)?rs?/i,week:/^w(ee)?k/i,month:/^m(o(nth)?s?)?/i,day:/^d(ays?)?/i,year:/^y((ea)?rs?)?/i,shortMeridian:/^(a|p)/i,longMeridian:/^(a\.?m?\.?|p\.?m?\.?)/i,timezone:/^((e(s|d)t|c(s|d)t|m(s|d)t|p(s|d)t)|((gmt)?\s*(\+|\-)\s*\d\d\d\d?)|gmt)/i,ordinalSuffix:/^\s*(st|nd|rd|th)/i,timeContext:/^\s*(\:|a|p)/i},abbreviatedTimeZoneStandard:{GMT:"-000",EST:"-0400",CST:"-0500",MST:"-0600",PST:"-0700"},abbreviatedTimeZoneDST:{GMT:"-000",EDT:"-0500",CDT:"-0600",MDT:"-0700",PDT:"-0800"}};
|
9
|
+
Date.getMonthNumberFromName=function(name){var n=Date.CultureInfo.monthNames,m=Date.CultureInfo.abbreviatedMonthNames,s=name.toLowerCase();for(var i=0;i<n.length;i++){if(n[i].toLowerCase()==s||m[i].toLowerCase()==s){return i;}}
|
10
|
+
return-1;};Date.getDayNumberFromName=function(name){var n=Date.CultureInfo.dayNames,m=Date.CultureInfo.abbreviatedDayNames,o=Date.CultureInfo.shortestDayNames,s=name.toLowerCase();for(var i=0;i<n.length;i++){if(n[i].toLowerCase()==s||m[i].toLowerCase()==s){return i;}}
|
11
|
+
return-1;};Date.isLeapYear=function(year){return(((year%4===0)&&(year%100!==0))||(year%400===0));};Date.getDaysInMonth=function(year,month){return[31,(Date.isLeapYear(year)?29:28),31,30,31,30,31,31,30,31,30,31][month];};Date.getTimezoneOffset=function(s,dst){return(dst||false)?Date.CultureInfo.abbreviatedTimeZoneDST[s.toUpperCase()]:Date.CultureInfo.abbreviatedTimeZoneStandard[s.toUpperCase()];};Date.getTimezoneAbbreviation=function(offset,dst){var n=(dst||false)?Date.CultureInfo.abbreviatedTimeZoneDST:Date.CultureInfo.abbreviatedTimeZoneStandard,p;for(p in n){if(n[p]===offset){return p;}}
|
12
|
+
return null;};Date.prototype.clone=function(){return new Date(this.getTime());};Date.prototype.compareTo=function(date){if(isNaN(this)){throw new Error(this);}
|
13
|
+
if(date instanceof Date&&!isNaN(date)){return(this>date)?1:(this<date)?-1:0;}else{throw new TypeError(date);}};Date.prototype.equals=function(date){return(this.compareTo(date)===0);};Date.prototype.between=function(start,end){var t=this.getTime();return t>=start.getTime()&&t<=end.getTime();};Date.prototype.addMilliseconds=function(value){this.setMilliseconds(this.getMilliseconds()+value);return this;};Date.prototype.addSeconds=function(value){return this.addMilliseconds(value*1000);};Date.prototype.addMinutes=function(value){return this.addMilliseconds(value*60000);};Date.prototype.addHours=function(value){return this.addMilliseconds(value*3600000);};Date.prototype.addDays=function(value){return this.addMilliseconds(value*86400000);};Date.prototype.addWeeks=function(value){return this.addMilliseconds(value*604800000);};Date.prototype.addMonths=function(value){var n=this.getDate();this.setDate(1);this.setMonth(this.getMonth()+value);this.setDate(Math.min(n,this.getDaysInMonth()));return this;};Date.prototype.addYears=function(value){return this.addMonths(value*12);};Date.prototype.add=function(config){if(typeof config=="number"){this._orient=config;return this;}
|
14
|
+
var x=config;if(x.millisecond||x.milliseconds){this.addMilliseconds(x.millisecond||x.milliseconds);}
|
15
|
+
if(x.second||x.seconds){this.addSeconds(x.second||x.seconds);}
|
16
|
+
if(x.minute||x.minutes){this.addMinutes(x.minute||x.minutes);}
|
17
|
+
if(x.hour||x.hours){this.addHours(x.hour||x.hours);}
|
18
|
+
if(x.month||x.months){this.addMonths(x.month||x.months);}
|
19
|
+
if(x.year||x.years){this.addYears(x.year||x.years);}
|
20
|
+
if(x.day||x.days){this.addDays(x.day||x.days);}
|
21
|
+
return this;};Date._validate=function(value,min,max,name){if(typeof value!="number"){throw new TypeError(value+" is not a Number.");}else if(value<min||value>max){throw new RangeError(value+" is not a valid value for "+name+".");}
|
22
|
+
return true;};Date.validateMillisecond=function(n){return Date._validate(n,0,999,"milliseconds");};Date.validateSecond=function(n){return Date._validate(n,0,59,"seconds");};Date.validateMinute=function(n){return Date._validate(n,0,59,"minutes");};Date.validateHour=function(n){return Date._validate(n,0,23,"hours");};Date.validateDay=function(n,year,month){return Date._validate(n,1,Date.getDaysInMonth(year,month),"days");};Date.validateMonth=function(n){return Date._validate(n,0,11,"months");};Date.validateYear=function(n){return Date._validate(n,1,9999,"seconds");};Date.prototype.set=function(config){var x=config;if(!x.millisecond&&x.millisecond!==0){x.millisecond=-1;}
|
23
|
+
if(!x.second&&x.second!==0){x.second=-1;}
|
24
|
+
if(!x.minute&&x.minute!==0){x.minute=-1;}
|
25
|
+
if(!x.hour&&x.hour!==0){x.hour=-1;}
|
26
|
+
if(!x.day&&x.day!==0){x.day=-1;}
|
27
|
+
if(!x.month&&x.month!==0){x.month=-1;}
|
28
|
+
if(!x.year&&x.year!==0){x.year=-1;}
|
29
|
+
if(x.millisecond!=-1&&Date.validateMillisecond(x.millisecond)){this.addMilliseconds(x.millisecond-this.getMilliseconds());}
|
30
|
+
if(x.second!=-1&&Date.validateSecond(x.second)){this.addSeconds(x.second-this.getSeconds());}
|
31
|
+
if(x.minute!=-1&&Date.validateMinute(x.minute)){this.addMinutes(x.minute-this.getMinutes());}
|
32
|
+
if(x.hour!=-1&&Date.validateHour(x.hour)){this.addHours(x.hour-this.getHours());}
|
33
|
+
if(x.month!==-1&&Date.validateMonth(x.month)){this.addMonths(x.month-this.getMonth());}
|
34
|
+
if(x.year!=-1&&Date.validateYear(x.year)){this.addYears(x.year-this.getFullYear());}
|
35
|
+
if(x.day!=-1&&Date.validateDay(x.day,this.getFullYear(),this.getMonth())){this.addDays(x.day-this.getDate());}
|
36
|
+
if(x.timezone){this.setTimezone(x.timezone);}
|
37
|
+
if(x.timezoneOffset){this.setTimezoneOffset(x.timezoneOffset);}
|
38
|
+
return this;};Date.prototype.clearTime=function(){this.setHours(0);this.setMinutes(0);this.setSeconds(0);this.setMilliseconds(0);return this;};Date.prototype.isLeapYear=function(){var y=this.getFullYear();return(((y%4===0)&&(y%100!==0))||(y%400===0));};Date.prototype.isWeekday=function(){return!(this.is().sat()||this.is().sun());};Date.prototype.getDaysInMonth=function(){return Date.getDaysInMonth(this.getFullYear(),this.getMonth());};Date.prototype.moveToFirstDayOfMonth=function(){return this.set({day:1});};Date.prototype.moveToLastDayOfMonth=function(){return this.set({day:this.getDaysInMonth()});};Date.prototype.moveToDayOfWeek=function(day,orient){var diff=(day-this.getDay()+7*(orient||+1))%7;return this.addDays((diff===0)?diff+=7*(orient||+1):diff);};Date.prototype.moveToMonth=function(month,orient){var diff=(month-this.getMonth()+12*(orient||+1))%12;return this.addMonths((diff===0)?diff+=12*(orient||+1):diff);};Date.prototype.getDayOfYear=function(){return Math.floor((this-new Date(this.getFullYear(),0,1))/86400000);};Date.prototype.getWeekOfYear=function(firstDayOfWeek){var y=this.getFullYear(),m=this.getMonth(),d=this.getDate();var dow=firstDayOfWeek||Date.CultureInfo.firstDayOfWeek;var offset=7+1-new Date(y,0,1).getDay();if(offset==8){offset=1;}
|
39
|
+
var daynum=((Date.UTC(y,m,d,0,0,0)-Date.UTC(y,0,1,0,0,0))/86400000)+1;var w=Math.floor((daynum-offset+7)/7);if(w===dow){y--;var prevOffset=7+1-new Date(y,0,1).getDay();if(prevOffset==2||prevOffset==8){w=53;}else{w=52;}}
|
40
|
+
return w;};Date.prototype.isDST=function(){console.log('isDST');return this.toString().match(/(E|C|M|P)(S|D)T/)[2]=="D";};Date.prototype.getTimezone=function(){return Date.getTimezoneAbbreviation(this.getUTCOffset,this.isDST());};Date.prototype.setTimezoneOffset=function(s){var here=this.getTimezoneOffset(),there=Number(s)*-6/10;this.addMinutes(there-here);return this;};Date.prototype.setTimezone=function(s){return this.setTimezoneOffset(Date.getTimezoneOffset(s));};Date.prototype.getUTCOffset=function(){var n=this.getTimezoneOffset()*-10/6,r;if(n<0){r=(n-10000).toString();return r[0]+r.substr(2);}else{r=(n+10000).toString();return"+"+r.substr(1);}};Date.prototype.getDayName=function(abbrev){return abbrev?Date.CultureInfo.abbreviatedDayNames[this.getDay()]:Date.CultureInfo.dayNames[this.getDay()];};Date.prototype.getMonthName=function(abbrev){return abbrev?Date.CultureInfo.abbreviatedMonthNames[this.getMonth()]:Date.CultureInfo.monthNames[this.getMonth()];};Date.prototype._toString=Date.prototype.toString;Date.prototype.toString=function(format){var self=this;var p=function p(s){return(s.toString().length==1)?"0"+s:s;};return format?format.replace(/dd?d?d?|MM?M?M?|yy?y?y?|hh?|HH?|mm?|ss?|tt?|zz?z?/g,function(format){switch(format){case"hh":return p(self.getHours()<13?self.getHours():(self.getHours()-12));case"h":return self.getHours()<13?self.getHours():(self.getHours()-12);case"HH":return p(self.getHours());case"H":return self.getHours();case"mm":return p(self.getMinutes());case"m":return self.getMinutes();case"ss":return p(self.getSeconds());case"s":return self.getSeconds();case"yyyy":return self.getFullYear();case"yy":return self.getFullYear().toString().substring(2,4);case"dddd":return self.getDayName();case"ddd":return self.getDayName(true);case"dd":return p(self.getDate());case"d":return self.getDate().toString();case"MMMM":return self.getMonthName();case"MMM":return self.getMonthName(true);case"MM":return p((self.getMonth()+1));case"M":return self.getMonth()+1;case"t":return self.getHours()<12?Date.CultureInfo.amDesignator.substring(0,1):Date.CultureInfo.pmDesignator.substring(0,1);case"tt":return self.getHours()<12?Date.CultureInfo.amDesignator:Date.CultureInfo.pmDesignator;case"zzz":case"zz":case"z":return"";}}):this._toString();};
|
41
|
+
Date.now=function(){return new Date();};Date.today=function(){return Date.now().clearTime();};Date.prototype._orient=+1;Date.prototype.next=function(){this._orient=+1;return this;};Date.prototype.last=Date.prototype.prev=Date.prototype.previous=function(){this._orient=-1;return this;};Date.prototype._is=false;Date.prototype.is=function(){this._is=true;return this;};Number.prototype._dateElement="day";Number.prototype.fromNow=function(){var c={};c[this._dateElement]=this;return Date.now().add(c);};Number.prototype.ago=function(){var c={};c[this._dateElement]=this*-1;return Date.now().add(c);};(function(){var $D=Date.prototype,$N=Number.prototype;var dx=("sunday monday tuesday wednesday thursday friday saturday").split(/\s/),mx=("january february march april may june july august september october november december").split(/\s/),px=("Millisecond Second Minute Hour Day Week Month Year").split(/\s/),de;var df=function(n){return function(){if(this._is){this._is=false;return this.getDay()==n;}
|
42
|
+
return this.moveToDayOfWeek(n,this._orient);};};for(var i=0;i<dx.length;i++){$D[dx[i]]=$D[dx[i].substring(0,3)]=df(i);}
|
43
|
+
var mf=function(n){return function(){if(this._is){this._is=false;return this.getMonth()===n;}
|
44
|
+
return this.moveToMonth(n,this._orient);};};for(var j=0;j<mx.length;j++){$D[mx[j]]=$D[mx[j].substring(0,3)]=mf(j);}
|
45
|
+
var ef=function(j){return function(){if(j.substring(j.length-1)!="s"){j+="s";}
|
46
|
+
return this["add"+j](this._orient);};};var nf=function(n){return function(){this._dateElement=n;return this;};};for(var k=0;k<px.length;k++){de=px[k].toLowerCase();$D[de]=$D[de+"s"]=ef(px[k]);$N[de]=$N[de+"s"]=nf(de);}}());Date.prototype.toJSONString=function(){return this.toString("yyyy-MM-ddThh:mm:ssZ");};Date.prototype.toShortDateString=function(){return this.toString(Date.CultureInfo.formatPatterns.shortDatePattern);};Date.prototype.toLongDateString=function(){return this.toString(Date.CultureInfo.formatPatterns.longDatePattern);};Date.prototype.toShortTimeString=function(){return this.toString(Date.CultureInfo.formatPatterns.shortTimePattern);};Date.prototype.toLongTimeString=function(){return this.toString(Date.CultureInfo.formatPatterns.longTimePattern);};Date.prototype.getOrdinal=function(){switch(this.getDate()){case 1:case 21:case 31:return"st";case 2:case 22:return"nd";case 3:case 23:return"rd";default:return"th";}};
|
47
|
+
(function(){Date.Parsing={Exception:function(s){this.message="Parse error at '"+s.substring(0,10)+" ...'";}};var $P=Date.Parsing;var _=$P.Operators={rtoken:function(r){return function(s){var mx=s.match(r);if(mx){return([mx[0],s.substring(mx[0].length)]);}else{throw new $P.Exception(s);}};},token:function(s){return function(s){return _.rtoken(new RegExp("^\s*"+s+"\s*"))(s);};},stoken:function(s){return _.rtoken(new RegExp("^"+s));},until:function(p){return function(s){var qx=[],rx=null;while(s.length){try{rx=p.call(this,s);}catch(e){qx.push(rx[0]);s=rx[1];continue;}
|
48
|
+
break;}
|
49
|
+
return[qx,s];};},many:function(p){return function(s){var rx=[],r=null;while(s.length){try{r=p.call(this,s);}catch(e){return[rx,s];}
|
50
|
+
rx.push(r[0]);s=r[1];}
|
51
|
+
return[rx,s];};},optional:function(p){return function(s){var r=null;try{r=p.call(this,s);}catch(e){return[null,s];}
|
52
|
+
return[r[0],r[1]];};},not:function(p){return function(s){try{p.call(this,s);}catch(e){return[null,s];}
|
53
|
+
throw new $P.Exception(s);};},ignore:function(p){return p?function(s){var r=null;r=p.call(this,s);return[null,r[1]];}:null;},product:function(){var px=arguments[0],qx=Array.prototype.slice.call(arguments,1),rx=[];for(var i=0;i<px.length;i++){rx.push(_.each(px[i],qx));}
|
54
|
+
return rx;},cache:function(rule){var cache={},r=null;return function(s){try{r=cache[s]=(cache[s]||rule.call(this,s));}catch(e){r=cache[s]=e;}
|
55
|
+
if(r instanceof $P.Exception){throw r;}else{return r;}};},any:function(){var px=arguments;return function(s){var r=null;for(var i=0;i<px.length;i++){if(px[i]==null){continue;}
|
56
|
+
try{r=(px[i].call(this,s));}catch(e){r=null;}
|
57
|
+
if(r){return r;}}
|
58
|
+
throw new $P.Exception(s);};},each:function(){var px=arguments;return function(s){var rx=[],r=null;for(var i=0;i<px.length;i++){if(px[i]==null){continue;}
|
59
|
+
try{r=(px[i].call(this,s));}catch(e){throw new $P.Exception(s);}
|
60
|
+
rx.push(r[0]);s=r[1];}
|
61
|
+
return[rx,s];};},all:function(){var px=arguments,_=_;return _.each(_.optional(px));},sequence:function(px,d,c){d=d||_.rtoken(/^\s*/);c=c||null;if(px.length==1){return px[0];}
|
62
|
+
return function(s){var r=null,q=null;var rx=[];for(var i=0;i<px.length;i++){try{r=px[i].call(this,s);}catch(e){break;}
|
63
|
+
rx.push(r[0]);try{q=d.call(this,r[1]);}catch(ex){q=null;break;}
|
64
|
+
s=q[1];}
|
65
|
+
if(!r){throw new $P.Exception(s);}
|
66
|
+
if(q){throw new $P.Exception(q[1]);}
|
67
|
+
if(c){try{r=c.call(this,r[1]);}catch(ey){throw new $P.Exception(r[1]);}}
|
68
|
+
return[rx,(r?r[1]:s)];};},between:function(d1,p,d2){d2=d2||d1;var _fn=_.each(_.ignore(d1),p,_.ignore(d2));return function(s){var rx=_fn.call(this,s);return[[rx[0][0],r[0][2]],rx[1]];};},list:function(p,d,c){d=d||_.rtoken(/^\s*/);c=c||null;return(p instanceof Array?_.each(_.product(p.slice(0,-1),_.ignore(d)),p.slice(-1),_.ignore(c)):_.each(_.many(_.each(p,_.ignore(d))),px,_.ignore(c)));},set:function(px,d,c){d=d||_.rtoken(/^\s*/);c=c||null;return function(s){var r=null,p=null,q=null,rx=null,best=[[],s],last=false;for(var i=0;i<px.length;i++){q=null;p=null;r=null;last=(px.length==1);try{r=px[i].call(this,s);}catch(e){continue;}
|
69
|
+
rx=[[r[0]],r[1]];if(r[1].length>0&&!last){try{q=d.call(this,r[1]);}catch(ex){last=true;}}else{last=true;}
|
70
|
+
if(!last&&q[1].length===0){last=true;}
|
71
|
+
if(!last){var qx=[];for(var j=0;j<px.length;j++){if(i!=j){qx.push(px[j]);}}
|
72
|
+
p=_.set(qx,d).call(this,q[1]);if(p[0].length>0){rx[0]=rx[0].concat(p[0]);rx[1]=p[1];}}
|
73
|
+
if(rx[1].length<best[1].length){best=rx;}
|
74
|
+
if(best[1].length===0){break;}}
|
75
|
+
if(best[0].length===0){return best;}
|
76
|
+
if(c){try{q=c.call(this,best[1]);}catch(ey){throw new $P.Exception(best[1]);}
|
77
|
+
best[1]=q[1];}
|
78
|
+
return best;};},forward:function(gr,fname){return function(s){return gr[fname].call(this,s);};},replace:function(rule,repl){return function(s){var r=rule.call(this,s);return[repl,r[1]];};},process:function(rule,fn){return function(s){var r=rule.call(this,s);return[fn.call(this,r[0]),r[1]];};},min:function(min,rule){return function(s){var rx=rule.call(this,s);if(rx[0].length<min){throw new $P.Exception(s);}
|
79
|
+
return rx;};}};var _generator=function(op){return function(){var args=null,rx=[];if(arguments.length>1){args=Array.prototype.slice.call(arguments);}else if(arguments[0]instanceof Array){args=arguments[0];}
|
80
|
+
if(args){for(var i=0,px=args.shift();i<px.length;i++){args.unshift(px[i]);rx.push(op.apply(null,args));args.shift();return rx;}}else{return op.apply(null,arguments);}};};var gx="optional not ignore cache".split(/\s/);for(var i=0;i<gx.length;i++){_[gx[i]]=_generator(_[gx[i]]);}
|
81
|
+
var _vector=function(op){return function(){if(arguments[0]instanceof Array){return op.apply(null,arguments[0]);}else{return op.apply(null,arguments);}};};var vx="each any all".split(/\s/);for(var j=0;j<vx.length;j++){_[vx[j]]=_vector(_[vx[j]]);}}());(function(){var flattenAndCompact=function(ax){var rx=[];for(var i=0;i<ax.length;i++){if(ax[i]instanceof Array){rx=rx.concat(flattenAndCompact(ax[i]));}else{if(ax[i]){rx.push(ax[i]);}}}
|
82
|
+
return rx;};Date.Grammar={};Date.Translator={hour:function(s){return function(){this.hour=Number(s);};},minute:function(s){return function(){this.minute=Number(s);};},second:function(s){return function(){this.second=Number(s);};},meridian:function(s){return function(){this.meridian=s.slice(0,1).toLowerCase();};},timezone:function(s){return function(){var n=s.replace(/[^\d\+\-]/g,"");if(n.length){this.timezoneOffset=Number(n);}else{this.timezone=s.toLowerCase();}};},day:function(x){var s=x[0];return function(){this.day=Number(s.match(/\d+/)[0]);};},month:function(s){return function(){this.month=((s.length==3)?Date.getMonthNumberFromName(s):(Number(s)-1));};},year:function(s){return function(){var n=Number(s);this.year=((s.length>2)?n:(n+(((n+2000)<Date.CultureInfo.twoDigitYearMax)?2000:1900)));};},rday:function(s){return function(){switch(s){case"yesterday":this.days=-1;break;case"tomorrow":this.days=1;break;case"today":this.days=0;break;case"now":this.days=0;this.now=true;break;}};},finishExact:function(x){x=(x instanceof Array)?x:[x];var now=new Date();this.year=now.getFullYear();this.month=now.getMonth();this.day=1;this.hour=0;this.minute=0;this.second=0;for(var i=0;i<x.length;i++){if(x[i]){x[i].call(this);}}
|
83
|
+
this.hour=(this.meridian=="p"&&this.hour<13)?this.hour+12:this.hour;if(this.day>Date.getDaysInMonth(this.year,this.month)){throw new RangeError(this.day+" is not a valid value for days.");}
|
84
|
+
var r=new Date(this.year,this.month,this.day,this.hour,this.minute,this.second);if(this.timezone){r.set({timezone:this.timezone});}else if(this.timezoneOffset){r.set({timezoneOffset:this.timezoneOffset});}
|
85
|
+
return r;},finish:function(x){x=(x instanceof Array)?flattenAndCompact(x):[x];if(x.length===0){return null;}
|
86
|
+
for(var i=0;i<x.length;i++){if(typeof x[i]=="function"){x[i].call(this);}}
|
87
|
+
if(this.now){return new Date();}
|
88
|
+
var today=Date.today();var method=null;var expression=!!(this.days!=null||this.orient||this.operator);if(expression){var gap,mod,orient;orient=((this.orient=="past"||this.operator=="subtract")?-1:1);if(this.weekday){this.unit="day";gap=(Date.getDayNumberFromName(this.weekday)-today.getDay());mod=7;this.days=gap?((gap+(orient*mod))%mod):(orient*mod);}
|
89
|
+
if(this.month){this.unit="month";gap=(this.month-today.getMonth());mod=12;this.months=gap?((gap+(orient*mod))%mod):(orient*mod);this.month=null;}
|
90
|
+
if(!this.unit){this.unit="day";}
|
91
|
+
if(this[this.unit+"s"]==null||this.operator!=null){if(!this.value){this.value=1;}
|
92
|
+
if(this.unit=="week"){this.unit="day";this.value=this.value*7;}
|
93
|
+
this[this.unit+"s"]=this.value*orient;}
|
94
|
+
return today.add(this);}else{if(this.meridian&&this.hour){this.hour=(this.hour<13&&this.meridian=="p")?this.hour+12:this.hour;}
|
95
|
+
if(this.weekday&&!this.day){this.day=(today.addDays((Date.getDayNumberFromName(this.weekday)-today.getDay()))).getDate();}
|
96
|
+
if(this.month&&!this.day){this.day=1;}
|
97
|
+
return today.set(this);}}};var _=Date.Parsing.Operators,g=Date.Grammar,t=Date.Translator,_fn;g.datePartDelimiter=_.rtoken(/^([\s\-\.\,\/\x27]+)/);g.timePartDelimiter=_.stoken(":");g.whiteSpace=_.rtoken(/^\s*/);g.generalDelimiter=_.rtoken(/^(([\s\,]|at|on)+)/);var _C={};g.ctoken=function(keys){var fn=_C[keys];if(!fn){var c=Date.CultureInfo.regexPatterns;var kx=keys.split(/\s+/),px=[];for(var i=0;i<kx.length;i++){px.push(_.replace(_.rtoken(c[kx[i]]),kx[i]));}
|
98
|
+
fn=_C[keys]=_.any.apply(null,px);}
|
99
|
+
return fn;};g.ctoken2=function(key){return _.rtoken(Date.CultureInfo.regexPatterns[key]);};g.h=_.cache(_.process(_.rtoken(/^(0[0-9]|1[0-2]|[1-9])/),t.hour));g.hh=_.cache(_.process(_.rtoken(/^(0[0-9]|1[0-2])/),t.hour));g.H=_.cache(_.process(_.rtoken(/^([0-1][0-9]|2[0-3]|[0-9])/),t.hour));g.HH=_.cache(_.process(_.rtoken(/^([0-1][0-9]|2[0-3])/),t.hour));g.m=_.cache(_.process(_.rtoken(/^([0-5][0-9]|[0-9])/),t.minute));g.mm=_.cache(_.process(_.rtoken(/^[0-5][0-9]/),t.minute));g.s=_.cache(_.process(_.rtoken(/^([0-5][0-9]|[0-9])/),t.second));g.ss=_.cache(_.process(_.rtoken(/^[0-5][0-9]/),t.second));g.hms=_.cache(_.sequence([g.H,g.mm,g.ss],g.timePartDelimiter));g.t=_.cache(_.process(g.ctoken2("shortMeridian"),t.meridian));g.tt=_.cache(_.process(g.ctoken2("longMeridian"),t.meridian));g.z=_.cache(_.process(_.rtoken(/^(\+|\-)?\s*\d\d\d\d?/),t.timezone));g.zz=_.cache(_.process(_.rtoken(/^(\+|\-)\s*\d\d\d\d/),t.timezone));g.zzz=_.cache(_.process(g.ctoken2("timezone"),t.timezone));g.timeSuffix=_.each(_.ignore(g.whiteSpace),_.set([g.tt,g.zzz]));g.time=_.each(_.optional(_.ignore(_.stoken("T"))),g.hms,g.timeSuffix);g.d=_.cache(_.process(_.each(_.rtoken(/^([0-2]\d|3[0-1]|\d)/),_.optional(g.ctoken2("ordinalSuffix"))),t.day));g.dd=_.cache(_.process(_.each(_.rtoken(/^([0-2]\d|3[0-1])/),_.optional(g.ctoken2("ordinalSuffix"))),t.day));g.ddd=g.dddd=_.cache(_.process(g.ctoken("sun mon tue wed thu fri sat"),function(s){return function(){this.weekday=s;};}));g.M=_.cache(_.process(_.rtoken(/^(1[0-2]|0\d|\d)/),t.month));g.MM=_.cache(_.process(_.rtoken(/^(1[0-2]|0\d)/),t.month));g.MMM=g.MMMM=_.cache(_.process(g.ctoken("jan feb mar apr may jun jul aug sep oct nov dec"),t.month));g.y=_.cache(_.process(_.rtoken(/^(\d\d?)/),t.year));g.yy=_.cache(_.process(_.rtoken(/^(\d\d)/),t.year));g.yyy=_.cache(_.process(_.rtoken(/^(\d\d?\d?\d?)/),t.year));g.yyyy=_.cache(_.process(_.rtoken(/^(\d\d\d\d)/),t.year));_fn=function(){return _.each(_.any.apply(null,arguments),_.not(g.ctoken2("timeContext")));};g.day=_fn(g.d,g.dd);g.month=_fn(g.M,g.MMM);g.year=_fn(g.yyyy,g.yy);g.orientation=_.process(g.ctoken("past future"),function(s){return function(){this.orient=s;};});g.operator=_.process(g.ctoken("add subtract"),function(s){return function(){this.operator=s;};});g.rday=_.process(g.ctoken("yesterday tomorrow today now"),t.rday);g.unit=_.process(g.ctoken("minute hour day week month year"),function(s){return function(){this.unit=s;};});g.value=_.process(_.rtoken(/^\d\d?(st|nd|rd|th)?/),function(s){return function(){this.value=s.replace(/\D/g,"");};});g.expression=_.set([g.rday,g.operator,g.value,g.unit,g.orientation,g.ddd,g.MMM]);_fn=function(){return _.set(arguments,g.datePartDelimiter);};g.mdy=_fn(g.ddd,g.month,g.day,g.year);g.ymd=_fn(g.ddd,g.year,g.month,g.day);g.dmy=_fn(g.ddd,g.day,g.month,g.year);g.date=function(s){return((g[Date.CultureInfo.dateElementOrder]||g.mdy).call(this,s));};g.format=_.process(_.many(_.any(_.process(_.rtoken(/^(dd?d?d?|MM?M?M?|yy?y?y?|hh?|HH?|mm?|ss?|tt?|zz?z?)/),function(fmt){if(g[fmt]){return g[fmt];}else{throw Date.Parsing.Exception(fmt);}}),_.process(_.rtoken(/^[^dMyhHmstz]+/),function(s){return _.ignore(_.stoken(s));}))),function(rules){return _.process(_.each.apply(null,rules),t.finishExact);});var _F={};var _get=function(f){return _F[f]=(_F[f]||g.format(f)[0]);};g.formats=function(fx){if(fx instanceof Array){var rx=[];for(var i=0;i<fx.length;i++){rx.push(_get(fx[i]));}
|
100
|
+
return _.any.apply(null,rx);}else{return _get(fx);}};g._formats=g.formats(["yyyy-MM-ddTHH:mm:ss","ddd, MMM dd, yyyy H:mm:ss tt","ddd MMM d yyyy HH:mm:ss zzz","d"]);g._start=_.process(_.set([g.date,g.time,g.expression],g.generalDelimiter,g.whiteSpace),t.finish);g.start=function(s){try{var r=g._formats.call({},s);if(r[1].length===0){return r;}}catch(e){}
|
101
|
+
return g._start.call({},s);};}());Date._parse=Date.parse;Date.parse=function(s){var r=null;if(!s){return null;}
|
102
|
+
try{r=Date.Grammar.start.call({},s);}catch(e){return null;}
|
103
|
+
return((r[1].length===0)?r[0]:null);};Date.getParseFunction=function(fx){var fn=Date.Grammar.formats(fx);return function(s){var r=null;try{r=fn.call({},s);}catch(e){return null;}
|
104
|
+
return((r[1].length===0)?r[0]:null);};};Date.parseExact=function(s,fx){return Date.getParseFunction(fx)(s);};
|
@@ -1,23 +1,46 @@
|
|
1
1
|
html, body, div, span, applet, object, iframe, h1, h2, h3, h4, h5, h6, p, blockquote, pre, a, abbr, acronym, address, big, cite, code, del, dfn, em, font, img, ins, kbd, q, s, samp, small, strike, strong, sub, sup, tt, var, dl, dt, dd, ol, ul, li, fieldset, form, label, legend, table, caption, tbody, tfoot, thead, tr, th, td { margin:0px; padding:0px; border:0px; outline:0px; font-weight:inherit; font-style:inherit; font-size:100%; font-family:inherit; vertical-align:baseline; }
|
2
|
-
|
2
|
+
|
3
|
+
body { line-height: 1; background: #eee; color: #000; font-size: 12px; font-family: Helvetica, Arial, sans-serif; }
|
3
4
|
ol, ul { list-style: none; }
|
4
5
|
table { border-collapse: separate; border-spacing: 0px; }
|
5
6
|
caption, th, td { text-align: left; font-weight: normal; }
|
6
7
|
|
7
|
-
|
8
|
+
html, body { width: 100%; height: 100%; }
|
9
|
+
body {
|
10
|
+
display: -webkit-box; display: -moz-box; display: box;
|
11
|
+
-webkit-box-orient: vertical; -moz-box-orient: vertical; box-orient: vertical;
|
12
|
+
}
|
13
|
+
#message {
|
14
|
+
display: -webkit-box; display: -moz-box; display: box;
|
15
|
+
-webkit-box-orient: vertical; -moz-box-orient: vertical; box-orient: vertical;
|
16
|
+
-webkit-box-flex: 1; -moz-box-flex: 1; box-flex: 1;
|
17
|
+
}
|
18
|
+
#message iframe {
|
19
|
+
display: -webkit-box; display: -moz-box; display: box;
|
20
|
+
-webkit-box-flex: 1; -moz-box-flex: 1; box-flex: 1;
|
21
|
+
}
|
22
|
+
|
23
|
+
#mail { height: 10em; overflow: auto; background: #fff; border-bottom: 1px solid #ccc; }
|
8
24
|
#mail table { width: 100%; }
|
9
|
-
#mail table thead tr { background:
|
10
|
-
#mail table thead tr th { padding: .25em; font-weight: bold; }
|
11
|
-
#mail table tbody tr:nth-child(even) { background:
|
25
|
+
#mail table thead tr { background: #eee; color: #333; }
|
26
|
+
#mail table thead tr th { padding: .25em; font-weight: bold; color: #666; text-shadow: 0 1px 0 #fff; }
|
27
|
+
#mail table tbody tr:nth-child(even) { background: #ddd; color: #333; }
|
12
28
|
#mail table tbody tr.selected { background: Highlight; color: HighlightText; }
|
13
29
|
#mail table tbody tr td { padding: .25em; }
|
30
|
+
|
14
31
|
#message .metadata { padding: 1em; }
|
15
32
|
#message .metadata div { padding: .25em;; }
|
16
|
-
#message .metadata div label { display: inline-block; width: 8em; text-align: right; font-weight: bold; }
|
33
|
+
#message .metadata div label { display: inline-block; width: 8em; margin-right: .5em; text-align: right; font-weight: bold; color: #666; text-shadow: 0 1px 0 #fff; }
|
17
34
|
#message .metadata .attachments { display: none; }
|
18
35
|
#message .metadata .attachments ul { display: inline; }
|
19
36
|
#message .metadata .attachments ul li { display: inline-block; margin-right: .5em; }
|
20
|
-
#message .
|
21
|
-
#message .
|
22
|
-
#message .
|
23
|
-
#message
|
37
|
+
#message .actions ul { border-bottom: 1px solid #999; padding: 0 .5em; }
|
38
|
+
#message .actions ul li.tab { display: inline-block; padding: .5em; border: 1px solid #999; background: #ddd; color: #333; border-width: 1px 1px 0 1px; cursor: pointer; text-shadow: 0 1px 0 #eee; }
|
39
|
+
#message .actions ul li.tab:not(.selected):hover { background-color: #ddd; }
|
40
|
+
#message .actions ul li.tab.selected { background: #fff; color: #000; height: 13px; margin-bottom: -1px; }
|
41
|
+
#message .actions ul li.button { display: inline-block; float: right; margin: 0 .25em; }
|
42
|
+
#message .actions ul li.button a { display: inline-block; padding: .25em .5em; border: 1px solid #999; border-radius: .35em; background: #ddd; color: #333; text-decoration: none; text-shadow: 1px 1px 0 #eee; box-shadow: 1px 1px 0 #ccc; }
|
43
|
+
#message .actions ul li.button a:hover { background: #fff; text-shadow: none; }
|
44
|
+
#message .actions ul li.button a:active { margin: 1px -1px -1px 1px; box-shadow: none; }
|
45
|
+
|
46
|
+
#message .body { width: 100%; min-height: 20em; height: 100%; background: #fff; }
|
data/views/index.haml
CHANGED
@@ -4,6 +4,7 @@
|
|
4
4
|
%title MailCatcher
|
5
5
|
%link{:rel => "stylesheet", :href => "/stylesheets/application.css"}
|
6
6
|
%script{:src => "/javascripts/jquery.js"}
|
7
|
+
%script{:src => "/javascripts/date.js"}
|
7
8
|
%script{:src => "/javascripts/application.js"}
|
8
9
|
:javascript
|
9
10
|
$(MailCatcher.init);
|
@@ -33,9 +34,12 @@
|
|
33
34
|
.attachments
|
34
35
|
%label Attachments
|
35
36
|
%ul
|
36
|
-
.
|
37
|
+
.actions
|
37
38
|
%ul
|
38
|
-
%li.selected{'data-message-format' => 'html'} HTML
|
39
|
-
%li{'data-message-format' => 'plain'} Plain Text
|
40
|
-
%li{'data-message-format' => 'source'} Source
|
41
|
-
|
39
|
+
%li.format.tab.html.selected{'data-message-format' => 'html'} HTML
|
40
|
+
%li.format.tab.plain{'data-message-format' => 'plain'} Plain Text
|
41
|
+
%li.format.tab.source{'data-message-format' => 'source'} Source
|
42
|
+
%li.button.download
|
43
|
+
%a{:href => '#'}
|
44
|
+
%span Download
|
45
|
+
%iframe.body
|
metadata
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
name: mailcatcher
|
3
3
|
version: !ruby/object:Gem::Version
|
4
4
|
prerelease:
|
5
|
-
version: 0.
|
5
|
+
version: 0.3.0
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
8
8
|
- Samuel Cochran
|
@@ -10,51 +10,50 @@ autorequire:
|
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
12
|
|
13
|
-
date: 2011-05-
|
14
|
-
default_executable: mailcatcher
|
13
|
+
date: 2011-05-27 00:00:00 Z
|
15
14
|
dependencies:
|
16
15
|
- !ruby/object:Gem::Dependency
|
17
|
-
name:
|
16
|
+
name: activesupport
|
18
17
|
prerelease: false
|
19
18
|
requirement: &id001 !ruby/object:Gem::Requirement
|
20
19
|
none: false
|
21
20
|
requirements:
|
22
|
-
- -
|
21
|
+
- - ~>
|
23
22
|
- !ruby/object:Gem::Version
|
24
|
-
version: "0"
|
23
|
+
version: "3.0"
|
25
24
|
type: :runtime
|
26
25
|
version_requirements: *id001
|
27
26
|
- !ruby/object:Gem::Dependency
|
28
|
-
name:
|
27
|
+
name: eventmachine
|
29
28
|
prerelease: false
|
30
29
|
requirement: &id002 !ruby/object:Gem::Requirement
|
31
30
|
none: false
|
32
31
|
requirements:
|
33
|
-
- -
|
32
|
+
- - ~>
|
34
33
|
- !ruby/object:Gem::Version
|
35
|
-
version: "0"
|
34
|
+
version: "0.12"
|
36
35
|
type: :runtime
|
37
36
|
version_requirements: *id002
|
38
37
|
- !ruby/object:Gem::Dependency
|
39
|
-
name:
|
38
|
+
name: mail
|
40
39
|
prerelease: false
|
41
40
|
requirement: &id003 !ruby/object:Gem::Requirement
|
42
41
|
none: false
|
43
42
|
requirements:
|
44
|
-
- -
|
43
|
+
- - ~>
|
45
44
|
- !ruby/object:Gem::Version
|
46
|
-
version: "
|
45
|
+
version: "2.3"
|
47
46
|
type: :runtime
|
48
47
|
version_requirements: *id003
|
49
48
|
- !ruby/object:Gem::Dependency
|
50
|
-
name: sqlite3
|
49
|
+
name: sqlite3
|
51
50
|
prerelease: false
|
52
51
|
requirement: &id004 !ruby/object:Gem::Requirement
|
53
52
|
none: false
|
54
53
|
requirements:
|
55
|
-
- -
|
54
|
+
- - ~>
|
56
55
|
- !ruby/object:Gem::Version
|
57
|
-
version: "
|
56
|
+
version: "1.3"
|
58
57
|
type: :runtime
|
59
58
|
version_requirements: *id004
|
60
59
|
- !ruby/object:Gem::Dependency
|
@@ -63,9 +62,9 @@ dependencies:
|
|
63
62
|
requirement: &id005 !ruby/object:Gem::Requirement
|
64
63
|
none: false
|
65
64
|
requirements:
|
66
|
-
- -
|
65
|
+
- - ~>
|
67
66
|
- !ruby/object:Gem::Version
|
68
|
-
version: "
|
67
|
+
version: "1.2"
|
69
68
|
type: :runtime
|
70
69
|
version_requirements: *id005
|
71
70
|
- !ruby/object:Gem::Dependency
|
@@ -74,9 +73,9 @@ dependencies:
|
|
74
73
|
requirement: &id006 !ruby/object:Gem::Requirement
|
75
74
|
none: false
|
76
75
|
requirements:
|
77
|
-
- -
|
76
|
+
- - ~>
|
78
77
|
- !ruby/object:Gem::Version
|
79
|
-
version: 0.1
|
78
|
+
version: "0.1"
|
80
79
|
type: :runtime
|
81
80
|
version_requirements: *id006
|
82
81
|
- !ruby/object:Gem::Dependency
|
@@ -85,9 +84,9 @@ dependencies:
|
|
85
84
|
requirement: &id007 !ruby/object:Gem::Requirement
|
86
85
|
none: false
|
87
86
|
requirements:
|
88
|
-
- -
|
87
|
+
- - ~>
|
89
88
|
- !ruby/object:Gem::Version
|
90
|
-
version: "
|
89
|
+
version: "1.2"
|
91
90
|
type: :runtime
|
92
91
|
version_requirements: *id007
|
93
92
|
- !ruby/object:Gem::Dependency
|
@@ -96,9 +95,9 @@ dependencies:
|
|
96
95
|
requirement: &id008 !ruby/object:Gem::Requirement
|
97
96
|
none: false
|
98
97
|
requirements:
|
99
|
-
- -
|
98
|
+
- - ~>
|
100
99
|
- !ruby/object:Gem::Version
|
101
|
-
version: "
|
100
|
+
version: "3.1"
|
102
101
|
type: :runtime
|
103
102
|
version_requirements: *id008
|
104
103
|
- !ruby/object:Gem::Dependency
|
@@ -107,37 +106,34 @@ dependencies:
|
|
107
106
|
requirement: &id009 !ruby/object:Gem::Requirement
|
108
107
|
none: false
|
109
108
|
requirements:
|
110
|
-
- -
|
109
|
+
- - ~>
|
111
110
|
- !ruby/object:Gem::Version
|
112
|
-
version: "0"
|
111
|
+
version: "1.0"
|
113
112
|
type: :runtime
|
114
113
|
version_requirements: *id009
|
115
|
-
description: "
|
114
|
+
description: " MailCatcher runs a super simple SMTP server which catches any\n message sent to it to display in a web interface. Run\n mailcatcher, set your favourite app to deliver to\n smtp://127.0.0.1:1025 instead of your default SMTP server,\n then check out http://127.0.0.1:1080 to see the mail.\n"
|
116
115
|
email: sj26@sj26.com
|
117
116
|
executables:
|
118
117
|
- mailcatcher
|
119
118
|
extensions: []
|
120
119
|
|
121
120
|
extra_rdoc_files:
|
122
|
-
- LICENSE
|
123
121
|
- README.md
|
124
|
-
files:
|
125
122
|
- LICENSE
|
123
|
+
files:
|
126
124
|
- README.md
|
127
|
-
-
|
128
|
-
- VERSION
|
125
|
+
- LICENSE
|
129
126
|
- bin/mailcatcher
|
130
|
-
- contrib/mailcatcherd.bash
|
131
|
-
- lib/mail_catcher.rb
|
132
127
|
- lib/mail_catcher/events.rb
|
133
128
|
- lib/mail_catcher/mail.rb
|
134
129
|
- lib/mail_catcher/smtp.rb
|
135
130
|
- lib/mail_catcher/web.rb
|
131
|
+
- lib/mail_catcher.rb
|
136
132
|
- public/javascripts/application.js
|
133
|
+
- public/javascripts/date.js
|
137
134
|
- public/javascripts/jquery.js
|
138
135
|
- public/stylesheets/application.css
|
139
136
|
- views/index.haml
|
140
|
-
has_rdoc: true
|
141
137
|
homepage: http://github.com/sj26/mailcatcher
|
142
138
|
licenses: []
|
143
139
|
|
@@ -161,7 +157,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
161
157
|
requirements: []
|
162
158
|
|
163
159
|
rubyforge_project:
|
164
|
-
rubygems_version: 1.
|
160
|
+
rubygems_version: 1.7.2
|
165
161
|
signing_key:
|
166
162
|
specification_version: 3
|
167
163
|
summary: Runs an SMTP server, catches and displays email in a web interface.
|
data/Rakefile
DELETED
@@ -1,44 +0,0 @@
|
|
1
|
-
require 'rubygems'
|
2
|
-
require 'rake'
|
3
|
-
|
4
|
-
begin
|
5
|
-
require 'jeweler'
|
6
|
-
Jeweler::Tasks.new do |gem|
|
7
|
-
gem.name = "mailcatcher"
|
8
|
-
gem.summary = %Q{Runs an SMTP server, catches and displays email in a web interface.}
|
9
|
-
gem.description = <<-EOD
|
10
|
-
MailCatcher runs a super simple SMTP server which catches any
|
11
|
-
message sent to it to display in a web interface. Run
|
12
|
-
mailcatcher, set your favourite app to deliver to
|
13
|
-
smtp://127.0.0.1:1025 instead of your default SMTP server,
|
14
|
-
then check out http://127.0.0.1:1080 to see the mail.
|
15
|
-
EOD
|
16
|
-
gem.email = "sj26@sj26.com"
|
17
|
-
gem.homepage = "http://github.com/sj26/mailcatcher"
|
18
|
-
gem.authors = ["Samuel Cochran"]
|
19
|
-
|
20
|
-
gem.add_dependency 'eventmachine'
|
21
|
-
gem.add_dependency 'mail'
|
22
|
-
gem.add_dependency 'i18n'
|
23
|
-
gem.add_dependency 'sqlite3-ruby'
|
24
|
-
gem.add_dependency 'thin'
|
25
|
-
gem.add_dependency 'skinny', '>=0.1.2'
|
26
|
-
gem.add_dependency 'sinatra'
|
27
|
-
gem.add_dependency 'haml'
|
28
|
-
gem.add_dependency 'json'
|
29
|
-
end
|
30
|
-
Jeweler::GemcutterTasks.new
|
31
|
-
rescue LoadError
|
32
|
-
puts "Jeweler (or a dependency) not available. Install it with: gem install jeweler"
|
33
|
-
end
|
34
|
-
|
35
|
-
require 'rake/rdoctask'
|
36
|
-
Rake::RDocTask.new do |rdoc|
|
37
|
-
version = File.exist?('VERSION') ? File.read('VERSION') : ""
|
38
|
-
|
39
|
-
rdoc.rdoc_dir = 'rdoc'
|
40
|
-
rdoc.title = "MailCatcher #{version}"
|
41
|
-
rdoc.rdoc_files.include('README*')
|
42
|
-
rdoc.rdoc_files.include('lib/*.rb')
|
43
|
-
rdoc.rdoc_files.include('lib/**/*.rb')
|
44
|
-
end
|
data/VERSION
DELETED
@@ -1 +0,0 @@
|
|
1
|
-
0.2.4
|
data/contrib/mailcatcherd.bash
DELETED
@@ -1,35 +0,0 @@
|
|
1
|
-
# mailcatchered: Start mailcatcher in a screen session
|
2
|
-
mailcatcherd() {
|
3
|
-
local check=`screen -list|grep mailcatcher|awk '{print $1}'`
|
4
|
-
if [[ "$1" = "" ]]; then
|
5
|
-
|
6
|
-
local hostname=""
|
7
|
-
|
8
|
-
if [[ $hostname = "" ]]; then
|
9
|
-
hostname='localhost'
|
10
|
-
fi
|
11
|
-
|
12
|
-
|
13
|
-
if [[ "${check}" = "" ]]; then
|
14
|
-
screen -dmS mailcatcher mailcatcher
|
15
|
-
echo "Started mailcatcher in background"
|
16
|
-
open "http://${hostname}:1080"
|
17
|
-
else
|
18
|
-
echo "mailcatcher is running in background..."
|
19
|
-
|
20
|
-
read -ep "re-attach(y/n) or run(r)?" choice
|
21
|
-
if [[ $choice = [yY] ]]; then
|
22
|
-
echo "Attaching mailcatcher session..."
|
23
|
-
screen -r $check
|
24
|
-
elif [[ $choice = [rR] ]]; then
|
25
|
-
open "http://${hostname}:1080"
|
26
|
-
else
|
27
|
-
echo "mailcatcher currently running at ${check}"
|
28
|
-
fi
|
29
|
-
fi
|
30
|
-
else
|
31
|
-
if [[ $1 -eq "load" ]]; then
|
32
|
-
screen -r mailcatcher
|
33
|
-
fi
|
34
|
-
fi
|
35
|
-
}
|