You've got

vars,name,subscribed,address {},AJ ONeal,True,solderjs@gmail.com {},Ryan Burnette,True,rburnt@example.com {},John Bobjohn,False,bobjohn@example.com ...

You want

"AJ ONeal" <solderjs@gmail.com>, "Ryan Burnette" <rburnt@example.com>, ...

So... Whaddayado?

TL;DR: Parse, Loop, Map, Join, & Print

mailgun-csv-to-email-list.js:

'use strict';


// Important: I happen to be writing these 3 lines as if this were in node.js,
//            but those are the only 3 node-specific lines. It's all VanillaJS.

// Read in the file as UTF-8 (not ASCII, since you may have internationals)
var fs = require('fs');
var filepath = process.argv[2] || './export.csv';
var csv = fs.readFileSync(filepath, 'utf8');

// Remove the trailing newline
csv = csv.trim()

// Map over split rows, and map over split fields, trimming along the way
var rows = txt.split(/\n/).map(function (line) {
  var cols = line.trim().split(',');
  return cols.map(function (col) {
    return col.trim();
  });
});

// Throw away the header
rows.shift();

// Get an array of formatted names
var list = rows.map(function (cols) {
  var subscribed = cols[2];
  // null out the unsubscribed
  if ('True' !== subscribed) { return null; }

  var name = cols[1];
  var email = cols[3].toLowerCase();

  // '<solderjs@gmail.com>'
  var addr = '<' + email + '>';

  // '"AJ ONeal" <solderjs@gmail.com>'
  if (name) {
    addr = '"' + name + '" ' + addr;
  }

  return addr;
}).filter(Boolean); // Drop the nulls

// Print the long string in Gmail-friendly copy/paste-able form
console.log(list.join(', '));

Wanna know why and how? Read on, reader!

Simple CSV

CSV can have many complex rules... and complex exceptions to those rules.

There's kinda-sorta a standard, but everyone follows it differently. If you need a complex CSV parser, Golang probably has you best option.

Fortunately, however, Mailgun uses a very dumb CSV generator (yes, in this case "dumb" is a good thing) with very simple rules.

Parsing Rows (Lines)

Try this:

"foo,bar\nbaz,qux\n".split(/\n/)

Two things to notice there:

First, the use of /\n/ instead of '\n'. This matches not just a single literal ASCII "linefeed" byte, but rather any type of newline (which can be different between Windows, Mac, Linux, and "web") - so '\r\n', '\r', '\n', etc.

Second, you get an extra empty line:

[ "foo,bar"
, "baz,qux"
, ""
]

So, instead, we'll just add a little .trim() to nix the trailing end-of-file newline:

"foo,bar\nbaz,qux\n".trim().split(/\n/)

Tada!

[ "foo,bar"
, "baz,qux"
]

Better?

So that's line parsing. Now let's try individual fields from the rows:

Parsing Fields (Columns)

"foo,bar,,baz".split(",")

Hopefully there are no surprises here:

[ "foo", "bar", "", "baz" ]

Hopefully with that you can see that constructing a simple CSV parser is just a matter of .trim(), .split() and .forEach() (or .map()).

var rows = [];
"foo,bar\nbaz,qux\n".trim().split(/\n/).forEach(function (row) {
  var cols = row.trim().split(",");
  rows.push(cols);
});

Lastly, there's the issue of the header.

The Header

The header, if any, will be your very first row.

This is where .shift() comes in:

// Get all rows, including the header
var lines = "First,Last,Email\nAJ,ONeal,solderjs@gmail.com\n".trim().split(/\n/);

// Remove the header from the *top* of the array
var header = lines.shift();

As you can surmise, .shift() is the opposite, no, inverse... maybe contra-positive (?)... of .pop(). It "pops from the top", as it were.

Bonus: Parsing Vars

If you're a cool modern hip dude or dudette you're too cool for schema-bound datasets like a CSV.

MailGun's got your back. The first field is a "bag" (as we say in typed-language parlance) titled Vars, which is just JSON. If you happen to actually be using it you could construct a dynamic list of headers like this:

// We'll use an object as a "map" to keep count of occurences of the extra headers
var xheadersMap = {};

var lines = csvtxt.trim().split(/\n/);
var headers = lines.shift().trim().split(",");

var data = lines.map(function (row) {
  var fields = row.trim().split(",");
  var json = JSON.parse(fields[0]); // the first field is json

  // keep track of how often each possible header is used
  Object.keys(json).forEach(function (k) {
    // add one to the previous count (or 0)
    xheadersMap[k] = (xheadersMap[k] || 0) + 1;
  });

  return fields;
});

And you could then flatten out the CSV like this:

var xheaders = Object.keys(xheadersMap).sort(function (a, b) {
  return xheadersMap[b] - xheadersMap[a]; // sort most frequent to least frequest, by count
});
headers = headers.concat(xheaders);

lines = data.map(function (fields) {
  var json = JSON.parse(fields.shift()); // shift to remove the first field (still json)
  xheaders.forEach(function (k) {
    fields.push(json[k] || '');
  });

  return fields.join(",");
});

console.log(headers.join(","));
console.log(lines.join("\n"));

Well, that's all folks. Now go spam your friends... I guess.


By AJ ONeal

If you loved this and want more like it, sign up!


Did I make your day?
Buy me a coffeeBuy me a coffee  

(you can learn about the bigger picture I'm working towards on my patreon page )