Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
language: node_js
node_js:
- '0.10'
- '0.12'
- '4'
- '5'
- '6'
41 changes: 38 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,23 @@

Node.js port of Github's EmailReplyParser, a small library to parse plain text email content.

## Usage

``` js
var EmailReplyParser = require('emailreplyparser')

// To parse the reply from an email body
var parsed = EmailReplyParser.parse(emailBody)

// To parse the reply from an email body, preserving signatures
var parsed = EmailReplyParser.parse(emailBody, true)

// Reads in an email and produces an array of fragments.
// Each fragment represents a part of the email.
var fragments = EmailReplyParser.read(emailBody)
```

For examples, refer to the tests.

## Known Issues
<small>(Taken from Github's version)</small>
Expand Down Expand Up @@ -65,6 +82,24 @@ Apparently, prefixing lines with `>` isn't universal either:
To: Rick


### To run the tests
* Install nodeunit `npm install nodeunit`
* Run the tests: `nodeunit test/email_reply_parser_test.js`
## To run the tests

* Install dependencies `npm install`
* Run the tests: `npm test`

## Upgrading to v1.0

- The `EmailReplyParser` is now exported directly. If upgrading from pre 1.0, change the following:

``` js
var EmailReplyParser = require('emailreplyparser').EmailReplyParser
```

to:

``` js
var EmailReplyParser = require('emailreplyparser')
```

- The `parse_reply` function is now called `parse`.
- The module no longer adds any methods to the `String` prototype. If your code was relying on the `trim`, `ltrim`, `strim`, `gsub`, `reverse` or `chomp` methods to be available on the prototype, you'll need to make changes.
149 changes: 67 additions & 82 deletions lib/emailreplyparser.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,77 +25,36 @@
//
// EmailReplyParser also attempts to figure out which of these blocks should
// be hidden from users.
var EmailReplyParser = {
VERSION: "0.4",

// Public: Splits an email body into a list of Fragments.
//
// text - A String email body.
//
// Returns an Email instance.
read: function(text) {
var email = new Email();
return email.read(text);
},
'use strict';

// Public: Get the text of the visible portions of the given email body.
//
// text - A String email body.
// [optional, default: false] include_signatures - Whether or not to include signatures in reply
//
// Returns a String.
parse_reply: function (text, include_signatures) {
if(typeof(include_signatures)==='undefined') include_signatures = false;
return this.read(text).visible_text(include_signatures);
}
};
/* jshint eqnull: true */

String.prototype.trim = function() {
return this.replace(/^\s*|\s*$/g, "");
}
// String manipulation utilities
var trim = function(str) {
return str.replace(/^\s*|\s*$/g, '');
};

String.prototype.ltrim = function() {
return this.replace(/^\s*/g, "");
}
var ltrim = function(str) {
return str.replace(/^\s*/g, '');
};

String.prototype.rtrim = function() {
return this.replace(/\s*$/g, "");
}
var rtrim = function(str) {
return str.replace(/\s*$/g, '');
};

String.prototype.reverse = function() {
var s = "";
var i = this.length;
while (i>0) {
s += this.substring(i-1,i);
var reverse = function(str) {
var s = '';
var i = str.length;
while (i > 0) {
s += str.substring(i-1, i);
i--;
}
return s;
}

//http://flochip.com/2011/09/06/rubys-string-gsub-in-javascript/
String.prototype.gsub = function(source, pattern, replacement) {
var match, result;
if (!((pattern != null) && (replacement != null))) {
return source;
}
result = '';
while (source.length > 0) {
if ((match = source.match(pattern))) {
result += source.slice(0, match.index);
result += replacement;
source = source.slice(match.index + match[0].length);
}
else {
result += source;
source = '';
}
}
return result;
};

//http://3dmdesign.com/development/extending-javascript-strings-with-chomp-using-prototypes
String.prototype.chomp = function() {
return this.replace(/(\n|\r)+$/, '');
var chomp = function(str) {
return str.replace(/(\n|\r)+$/, '');
};

// An Email instance represents a parsed body String.
Expand All @@ -117,16 +76,14 @@ Email.prototype = {
//
// Returns a String.
visible_text: function(include_signatures) {
if(typeof(include_signatures)==='undefined') include_signatures = false;

var visible_text = [];
for (var key in this.fragments) {
if (!this.fragments[key].hidden || (include_signatures && this.fragments[key].signature)) {
visible_text.push(this.fragments[key].to_s());
}
}

return visible_text.join("\n").rtrim();
return rtrim(visible_text.join('\n'));
},

// Splits the given text into a list of Fragments. This is roughly done by
Expand All @@ -140,6 +97,9 @@ Email.prototype = {
// in 1.9 we want to operate on the raw bytes
// text = text.dup.force_encoding('binary') if text.respond_to?(:force_encoding)

// Normalize line endings.
text = text.replace('\r\n', '\n');

// Check for multi-line reply headers. Some clients break up
// the "On DATE, NAME <EMAIL> wrote:" line into multiple lines.
var patt = /^(On\s(\n|.)*wrote:)$/m;
Expand All @@ -153,11 +113,11 @@ Email.prototype = {

// The text is reversed initially due to the way we check for hidden
// fragments.
text = text.reverse();
text = reverse(text);

// This determines if any 'visible' Fragment has been found. Once any
// visible Fragment is found, stop looking for hidden ones.
this.found_visible = false
this.found_visible = false;

// This instance variable points to the current Fragment. If the matched
// line fits, it should be added to this Fragment. Otherwise, finish it
Expand Down Expand Up @@ -191,16 +151,16 @@ Email.prototype = {
//
// Returns nothing.
scan_line: function(line) {
var SIG_REGEX = '(--|__|\\w-$)|(^(\\w+\\s*){1,3} ' + ("Sent from my").reverse() + '$)';
var SIG_REGEX = '(--|__|\\w-$)|(^(\\w+\\s*){1,3} ' + reverse('Sent from my') + '$)';

line = line.chomp('\n');
line = chomp(line);
if (!(new RegExp(SIG_REGEX)).test(line)) {
line = line.ltrim();
line = ltrim(line);
}

// Mark the current Fragment as a signature if the current line is ''
// and the Fragment starts with a common signature indicator.
if (this.fragment != null && line == '') {
if (this.fragment != null && line === '') {
if ((new RegExp(SIG_REGEX)).test(this.fragment.lines[this.fragment.lines.length - 1])) {
this.fragment.signature = true;
this.finish_fragment();
Expand All @@ -214,7 +174,7 @@ Email.prototype = {
// If the line matches the current fragment, add it. Note that a common
// reply header also counts as part of the quoted Fragment, even though
// it doesn't start with `>`.
if (this.fragment != null && ((this.fragment.quoted == is_quoted) || (this.fragment.quoted && (this.quote_header(line) || line == '')))) {
if (this.fragment != null && ((this.fragment.quoted === is_quoted) || (this.fragment.quoted && (this.quote_header(line) || line === '')))) {
this.fragment.lines.push(line);
}
// Otherwise, finish the fragment and start a new one.
Expand Down Expand Up @@ -257,30 +217,32 @@ Email.prototype = {
// Player 2 (signature, hidden)
//
finish_fragment: function() {
if (this.fragment != null) {
if(this.fragment != null) {
this.fragment.finish();

if (!this.found_visible) {
if (this.fragment.quoted || this.fragment.signature || this.fragment.to_s().trim() == '')
if (this.fragment.quoted || this.fragment.signature || trim(this.fragment.to_s()) === '') {
this.fragment.hidden = true;
else
}
else {
this.found_visible = true;
}
}

this.fragments.push(this.fragment);
this.fragment = null;
}
}
}
};

// Fragments

// Represents a group of paragraphs in the email sharing common attributes.
// Paragraphs should get their own fragment if they are a quoted area or a
// signature.
var Fragment = function(quoted, first_line) {
this.initialize(quoted, first_line)
};
function Fragment(quoted, first_line) {
this.initialize(quoted, first_line);
}

Fragment.prototype = {
// This is an Array of String lines of content. Since the content is
Expand All @@ -297,23 +259,46 @@ Fragment.prototype = {
this.quoted = quoted;
this.lines = [first_line];
this.content = null;
this.lines = this.lines.filter(function(){return true});
this.lines = this.lines.filter(function() { return true; });
},

// Builds the string content by joining the lines and reversing them.
//
// Returns nothing.
finish: function() {
this.content = this.lines.join("\n");
this.content = reverse(this.lines.join('\n'));
this.lines = [];
this.content = this.content.reverse();
},

to_s: function() {
return this.content.toString();
}
};

module.exports.EmailReplyParser = EmailReplyParser;
var EmailReplyParser = {
VERSION: '1.0',

// Public: Splits an email body into a list of Fragments.
//
// text - A String email body.
//
// Returns an Email instance.
read: function(text) {
var email = new Email();
return email.read(text);
},

// Public: Get the text of the visible portions of the given email body.
//
// text - A String email body.
// [optional, default: undefined] include_signatures - Whether or not to include signatures in reply
//
// Returns a String.
parse: function (text, include_signatures) {
return this.read(text).visible_text(include_signatures);
}
};

module.exports = EmailReplyParser;

//console.log(EmailReplyParser.read("I get proper rendering as well.\n\nSent from a magnificent torch of pixels\n\nOn Dec 16, 2011, at 12:47 PM, Corey Donohoe\n<[email protected]>\nwrote:\n\n> Was this caching related or fixed already? I get proper rendering here.\n>\n> ![](https://img.skitch.com/20111216-m9munqjsy112yqap5cjee5wr6c.jpg)\n>\n> ---\n> Reply to this email directly or view it on GitHub:\n> https://github.com/github/github/issues/2278#issuecomment-3182418\n"));
//console.log(EmailReplyParser.read("I get proper rendering as well.\n\nSent from a magnificent torch of pixels\n\nOn Dec 16, 2011, at 12:47 PM, Corey Donohoe\n<[email protected]>\nwrote:\n\n> Was this caching related or fixed already? I get proper rendering here.\n>\n> ![](https://img.skitch.com/20111216-m9munqjsy112yqap5cjee5wr6c.jpg)\n>\n> ---\n> Reply to this email directly or view it on GitHub:\n> https://github.com/github/github/issues/2278#issuecomment-3182418\n"));
20 changes: 17 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,13 +1,27 @@
{
"name": "emailreplyparser",
"version": "0.0.5",
"version": "1.0.0",
"description": "Node.js Port of GitHub's email_reply_parser.rb",
"author": "Michael Owens <[email protected]> (http://mowens.com/)",
"repository": {
"type": "git",
"url": "https://github.com/mowens/emailreplyparser.git"
},
"main": "./lib/emailreplyparser",
"engines": { "node": ">= 0.4.0" },
"keywords": ["email", "parser", "emailreplyparser", "email_reply_parser"]
"engines": {
"node": ">= 0.4.0"
},
"scripts": {
"test": "nodeunit test/email_reply_parser_test.js"
},
"keywords": [
"email",
"parser",
"emailreplyparser",
"email_reply_parser"
],
"devDependencies": {
"lodash": "^4.13.1",
"nodeunit": "^0.9.1"
}
}
Loading