Permalink
Browse files

Rename columnNames to header; Test the header option

  • Loading branch information...
1 parent 18066b3 commit 9b9fd16e8fdce3bbd724101535842400c3ce38bd @wdavidw committed Apr 1, 2012
Showing with 208 additions and 154 deletions.
  1. +93 −90 lib/csv.js
  2. +3 −0 readme.md
  3. +103 −64 test/columns.coffee
  4. +2 −0 test/columns/header.in
  5. +3 −0 test/columns/header.out
  6. +2 −0 test/columns/out_no_transform.in
  7. +2 −0 test/columns/out_no_transform.out
View
183 lib/csv.js
@@ -49,7 +49,7 @@ module.exports = function(){
quote: null,
escape: null,
columns: null,
- columnNames: false,
+ header: false,
lineBreaks: null,
flags: 'w',
encoding: 'utf8',
@@ -222,91 +222,6 @@ module.exports = function(){
// Private API
/**
- * Write a line to the written stream.
- * Line may be an object, an array or a string
- * Preserve is for line which are not considered as CSV data
- */
- function write(line, preserve){
- if(typeof line === 'undefined' || line === null){
- return;
- }
- if(!preserve){
- try {
- csv.emit('data', line, state.count);
- }catch(e){
- csv.emit('error', e);
- csv.readable = false;
- csv.writable = false;
- }
- }
- if(typeof line === 'object'){
- if(!(line instanceof Array)){
- var columns = csv.writeOptions.columns || csv.readOptions.columns;
- var _line = [];
- if(columns){
- columns.forEach(function(column, i){
- _line[i] = (typeof line[column] === 'undefined' || line[column] === null) ? '' : line[column];
- })
- }else{
- for(var column in line){
- _line.push(line[column]);
- }
- }
- line = _line;
- _line = null;
- }
- if(line instanceof Array){
- var newLine = state.countWriten ? csv.writeOptions.lineBreaks || "\r" : '';
- line.forEach(function(field,i){
- if(typeof field === 'string'){
- // fine 99% of the cases, keep going
- }else if(typeof field === 'number'){
- // Cast number to string
- field = '' + field;
- }else if(typeof field === 'boolean'){
- // Cast boolean to string
- field = field ? '1' : '';
- }else if(field instanceof Date){
- // Cast date to timestamp string
- field = '' + field.getTime();
- }
- if(field){
- var containsdelimiter = field.indexOf(csv.writeOptions.delimiter || csv.readOptions.delimiter) >= 0;
- var containsQuote = field.indexOf(csv.writeOptions.quote || csv.readOptions.quote) >= 0;
- var containsLinebreak = field.indexOf("\r") >= 0 || field.indexOf("\n") >= 0;
- if(containsQuote){
- field = field.replace(
- new RegExp(csv.writeOptions.quote || csv.readOptions.quote,'g')
- , (csv.writeOptions.escape || csv.readOptions.escape)
- + (csv.writeOptions.quote || csv.readOptions.quote));
- }
- if(containsQuote || containsdelimiter || containsLinebreak){
- field = (csv.writeOptions.quote || csv.readOptions.quote) + field + (csv.writeOptions.quote || csv.readOptions.quote);
- }
- newLine += field;
- }
- if(i!==line.length-1){
- newLine += csv.writeOptions.delimiter || csv.readOptions.delimiter;
- }
- });
- line = newLine;
- }
- }
- if(state.buffer){
- if(state.bufferPosition + Buffer.byteLength(line,'utf8') > csv.readOptions.bufferSize){
- csv.writeStream.write(state.buffer.slice(0, state.bufferPosition));
- state.buffer = new Buffer(csv.readOptions.bufferSize);
- state.bufferPosition = 0;
- }
- state.bufferPosition += state.buffer.write(line, state.bufferPosition,'utf8');
- }
- if(!preserve){
- state.countWriten++;
- }
- return true;
- }
-
- /**
* Parse a string which may hold multiple lines.
* Private state object is enriched on each character until
* flush is called on a new line
@@ -404,13 +319,12 @@ module.exports = function(){
// Called by the `parse` function on each line. It will then call `write`
function flush(){
+ if(state.count === 0 && csv.writeOptions.header === true){
+ write(csv.writeOptions.columns || csv.readOptions.columns);
+ }
if(csv.readOptions.columns){
if(state.count === 0 && csv.readOptions.columns === true){
csv.readOptions.columns = state.line;
- if(csv.writeOptions.columnNames === true)
- {
- write(csv.writeOptions.columns || csv.readOptions.columns);
- }
state.line = [];
state.lastC = '';
return;
@@ -436,5 +350,94 @@ module.exports = function(){
state.lastC = '';
}
+ /**
+ * Write a line to the written stream.
+ * Line may be an object, an array or a string
+ * Preserve is for line which are not considered as CSV data
+ */
+ function write(line, preserve){
+ if(typeof line === 'undefined' || line === null){
+ return;
+ }
+ if(!preserve){
+ try {
+ csv.emit('data', line, state.count);
+ }catch(e){
+ csv.emit('error', e);
+ csv.readable = false;
+ csv.writable = false;
+ }
+ }
+ if(typeof line === 'object'){
+ if(!(line instanceof Array)){
+ var columns = csv.writeOptions.columns || csv.readOptions.columns;
+ var _line = [];
+ if(columns){
+ columns.forEach(function(column, i){
+ _line[i] = (typeof line[column] === 'undefined' || line[column] === null) ? '' : line[column];
+ })
+ }else{
+ for(var column in line){
+ _line.push(line[column]);
+ }
+ }
+ line = _line;
+ _line = null;
+ }else if(csv.writeOptions.columns){
+ // We are getting an array but the user want specified output columns. In
+ // this case, we respect the columns indexes
+ line.splice(csv.writeOptions.columns.length);
+ }
+ if(line instanceof Array){
+ var newLine = state.countWriten ? csv.writeOptions.lineBreaks || "\r" : '';
+ line.forEach(function(field,i){
+ if(typeof field === 'string'){
+ // fine 99% of the cases, keep going
+ }else if(typeof field === 'number'){
+ // Cast number to string
+ field = '' + field;
+ }else if(typeof field === 'boolean'){
+ // Cast boolean to string
+ field = field ? '1' : '';
+ }else if(field instanceof Date){
+ // Cast date to timestamp string
+ field = '' + field.getTime();
+ }
+ if(field){
+ var containsdelimiter = field.indexOf(csv.writeOptions.delimiter || csv.readOptions.delimiter) >= 0;
+ var containsQuote = field.indexOf(csv.writeOptions.quote || csv.readOptions.quote) >= 0;
+ var containsLinebreak = field.indexOf("\r") >= 0 || field.indexOf("\n") >= 0;
+ if(containsQuote){
+ field = field.replace(
+ new RegExp(csv.writeOptions.quote || csv.readOptions.quote,'g')
+ , (csv.writeOptions.escape || csv.readOptions.escape)
+ + (csv.writeOptions.quote || csv.readOptions.quote));
+ }
+ if(containsQuote || containsdelimiter || containsLinebreak){
+ field = (csv.writeOptions.quote || csv.readOptions.quote) + field + (csv.writeOptions.quote || csv.readOptions.quote);
+ }
+ newLine += field;
+ }
+ if(i!==line.length-1){
+ newLine += csv.writeOptions.delimiter || csv.readOptions.delimiter;
+ }
+ });
+ line = newLine;
+ }
+ }
+ if(state.buffer){
+ if(state.bufferPosition + Buffer.byteLength(line,'utf8') > csv.readOptions.bufferSize){
+ csv.writeStream.write(state.buffer.slice(0, state.bufferPosition));
+ state.buffer = new Buffer(csv.readOptions.bufferSize);
+ state.bufferPosition = 0;
+ }
+ state.bufferPosition += state.buffer.write(line, state.bufferPosition,'utf8');
+ }
+ if(!preserve){
+ state.countWriten++;
+ }
+ return true;
+ }
+
return csv;
};
View
3 readme.md
@@ -141,6 +141,9 @@ Options are:
- *encoding*
Default to 'utf8', apply when a writable stream is created.
+- *header*
+ Display the column names on the first line if the columns option is provided.
+
- *lineBreaks*
String used to delimit record rows or a special value; special values are 'auto', 'unix', 'mac', 'windows', 'unicode'; default to 'auto' (discovered in source).
View
167 test/columns.coffee
@@ -6,67 +6,106 @@ should = require 'should'
csv = require '..'
describe 'columns', ->
- it 'Test columns in true', ->
- # Note: if true, columns are expected to be in first line
- csv()
- .fromPath( "#{__dirname}/columns/in_true.in", columns: true )
- .toPath( "#{__dirname}/columns/in_true.tmp" )
- .transform( (data, index) ->
- data.should.be.a 'object'
- data.should.not.be.an.instanceof Array
- if index is 0
- data.FIELD_1.should.eql '20322051544'
- else if index is 1
- data.FIELD_4.should.eql 'DEF'
- data
- )
- .on('end', (count) ->
- count.should.eql 2
- expect = fs.readFileSync("#{__dirname}/columns/in_true.out").toString()
- result = fs.readFileSync("#{__dirname}/columns/in_true.tmp").toString()
- result.should.eql expect
- fs.unlink("#{__dirname}/columns/in_true.tmp")
- )
- it 'Test columns in named', ->
- # Note: if true, columns are expected to be in first line
- csv()
- .fromPath("#{__dirname}/columns/in_named.in", {
- columns: ["FIELD_1", "FIELD_2", "FIELD_3", "FIELD_4", "FIELD_5", "FIELD_6"]
- })
- .toPath("#{__dirname}/columns/in_named.tmp")
- .transform (data, index) ->
- data.should.be.a 'object'
- data.should.not.be.an.instanceof Array
- if index is 0
- data.FIELD_1.should.eql '20322051544'
- else if index is 1
- data.FIELD_4.should.eql 'DEF'
- data
- .on 'data',(data, index) ->
- data.should.be.a 'object'
- data.should.not.be.an.instanceof Array
- .on 'end',(count) ->
- count.should.eql 2
- expect = fs.readFileSync("#{__dirname}/columns/in_named.out").toString()
- result = fs.readFileSync("#{__dirname}/columns/in_named.tmp").toString()
- result.should.eql expect
- fs.unlink "#{__dirname}/columns/in_named.tmp"
- it 'Test columns out named', ->
- # Note: if true, columns are expected to be in first line
- csv()
- .fromPath("#{__dirname}/columns/out_named.in")
- .toPath("#{__dirname}/columns/out_named.tmp",
- columns: ["FIELD_1", "FIELD_2"]
- )
- .transform (data, index) ->
- data.should.be.an.instanceof Array
- return {FIELD_2: data[3], FIELD_1: data[4]}
- .on 'data', (data, index) ->
- data.should.be.a 'object'
- data.should.not.be.an.instanceof Array
- .on 'end', (count) ->
- count.should.eql 2
- expect = fs.readFileSync("#{__dirname}/columns/out_named.out").toString()
- result = fs.readFileSync("#{__dirname}/columns/out_named.tmp").toString()
- result.should.eql expect
- fs.unlink "#{__dirname}/columns/out_named.tmp"
+ describe 'in read option', ->
+ it 'Test columns in true', (next) ->
+ # Note: if true, columns are expected to be in first line
+ csv()
+ .fromPath( "#{__dirname}/columns/in_true.in", columns: true )
+ .toPath( "#{__dirname}/columns/in_true.tmp" )
+ .transform (data, index) ->
+ data.should.be.a 'object'
+ data.should.not.be.an.instanceof Array
+ if index is 0
+ data.FIELD_1.should.eql '20322051544'
+ else if index is 1
+ data.FIELD_4.should.eql 'DEF'
+ data
+ .on 'end', (count) ->
+ count.should.eql 2
+ expect = fs.readFileSync("#{__dirname}/columns/in_true.out").toString()
+ result = fs.readFileSync("#{__dirname}/columns/in_true.tmp").toString()
+ result.should.eql expect
+ fs.unlink("#{__dirname}/columns/in_true.tmp")
+ next()
+ it 'Test columns in named', (next) ->
+ # Note: if true, columns are expected to be in first line
+ csv()
+ .fromPath("#{__dirname}/columns/in_named.in", {
+ columns: ["FIELD_1", "FIELD_2", "FIELD_3", "FIELD_4", "FIELD_5", "FIELD_6"]
+ })
+ .toPath("#{__dirname}/columns/in_named.tmp")
+ .transform (data, index) ->
+ data.should.be.a 'object'
+ data.should.not.be.an.instanceof Array
+ if index is 0
+ data.FIELD_1.should.eql '20322051544'
+ else if index is 1
+ data.FIELD_4.should.eql 'DEF'
+ data
+ .on 'data',(data, index) ->
+ data.should.be.a 'object'
+ data.should.not.be.an.instanceof Array
+ .on 'end',(count) ->
+ count.should.eql 2
+ expect = fs.readFileSync("#{__dirname}/columns/in_named.out").toString()
+ result = fs.readFileSync("#{__dirname}/columns/in_named.tmp").toString()
+ result.should.eql expect
+ fs.unlink "#{__dirname}/columns/in_named.tmp"
+ next()
+ describe 'in write option', ->
+ it 'should be the same length', (next) ->
+ # Since there is not columns set in input options, we just expect
+ # the output stream to contains 2 fields
+ csv()
+ .fromPath("#{__dirname}/columns/out_no_transform.in")
+ .toPath("#{__dirname}/columns/out_no_transform.tmp",
+ columns: ["FIELD_1", "FIELD_2"]
+ )
+ .on 'data', (data, index) ->
+ data.should.be.an.instanceof Array
+ .on 'end', (count) ->
+ count.should.eql 2
+ expect = fs.readFileSync("#{__dirname}/columns/out_no_transform.out").toString()
+ result = fs.readFileSync("#{__dirname}/columns/out_no_transform.tmp").toString()
+ result.should.eql expect
+ fs.unlink "#{__dirname}/columns/out_named.tmp"
+ next()
+ it 'should filter from a transformed object', (next) ->
+ # We are no returning an object
+ csv()
+ .fromPath("#{__dirname}/columns/out_named.in")
+ .toPath("#{__dirname}/columns/out_named.tmp",
+ columns: ["FIELD_1", "FIELD_2"]
+ )
+ .transform (data, index) ->
+ data.should.be.an.instanceof Array
+ {FIELD_2: data[3], zombie: data[1], FIELD_1: data[4]}
+ .on 'data', (data, index) ->
+ data.should.be.a 'object'
+ data.should.not.be.an.instanceof Array
+ .on 'end', (count) ->
+ count.should.eql 2
+ expect = fs.readFileSync("#{__dirname}/columns/out_named.out").toString()
+ result = fs.readFileSync("#{__dirname}/columns/out_named.tmp").toString()
+ result.should.eql expect
+ fs.unlink "#{__dirname}/columns/out_named.tmp"
+ next()
+ it 'should print names if header', (next) ->
+ csv()
+ .fromPath("#{__dirname}/columns/header.in")
+ .toPath("#{__dirname}/columns/header.tmp",
+ header: true
+ columns: ["FIELD_1", "FIELD_2"]
+ )
+ .on 'end', (count) ->
+ count.should.eql 2
+ expect = fs.readFileSync("#{__dirname}/columns/header.out").toString()
+ result = fs.readFileSync("#{__dirname}/columns/header.tmp").toString()
+ result.should.eql expect
+ fs.unlink "#{__dirname}/columns/header.tmp"
+ next()
+
+
+
+
+
View
2 test/columns/header.in
@@ -0,0 +1,2 @@
+20322051544,1979,8.8017226E7,ABC,45,2000-01-01
+28392898392,1974,8.8392926E7,DEF,23,2050-11-27
View
3 test/columns/header.out
@@ -0,0 +1,3 @@
+FIELD_1,FIELD_2
+20322051544,1979
+28392898392,1974
View
2 test/columns/out_no_transform.in
@@ -0,0 +1,2 @@
+20322051544,1979,8.8017226E7,ABC,45,2000-01-01
+28392898392,1974,8.8392926E7,DEF,23,2050-11-27
View
2 test/columns/out_no_transform.out
@@ -0,0 +1,2 @@
+20322051544,1979
+28392898392,1974

0 comments on commit 9b9fd16

Please sign in to comment.