Added Types and Unit tests
This commit is contained in:
61
.gitignore
vendored
Normal file
61
.gitignore
vendored
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
# Logs
|
||||||
|
logs
|
||||||
|
*.log
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
|
||||||
|
# Runtime data
|
||||||
|
pids
|
||||||
|
*.pid
|
||||||
|
*.seed
|
||||||
|
*.pid.lock
|
||||||
|
|
||||||
|
# Directory for instrumented libs generated by jscoverage/JSCover
|
||||||
|
lib-cov
|
||||||
|
|
||||||
|
# Coverage directory used by tools like istanbul
|
||||||
|
coverage
|
||||||
|
|
||||||
|
# nyc test coverage
|
||||||
|
.nyc_output
|
||||||
|
|
||||||
|
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
|
||||||
|
.grunt
|
||||||
|
|
||||||
|
# Bower dependency directory (https://bower.io/)
|
||||||
|
bower_components
|
||||||
|
|
||||||
|
# node-waf configuration
|
||||||
|
.lock-wscript
|
||||||
|
|
||||||
|
# Compiled binary addons (https://nodejs.org/api/addons.html)
|
||||||
|
build/Release
|
||||||
|
|
||||||
|
# Dependency directories
|
||||||
|
node_modules/
|
||||||
|
jspm_packages/
|
||||||
|
|
||||||
|
# TypeScript v1 declaration files
|
||||||
|
typings/
|
||||||
|
|
||||||
|
# Optional npm cache directory
|
||||||
|
.npm
|
||||||
|
|
||||||
|
# Optional eslint cache
|
||||||
|
.eslintcache
|
||||||
|
|
||||||
|
# Optional REPL history
|
||||||
|
.node_repl_history
|
||||||
|
|
||||||
|
# Output of 'npm pack'
|
||||||
|
*.tgz
|
||||||
|
|
||||||
|
# Yarn Integrity file
|
||||||
|
.yarn-integrity
|
||||||
|
|
||||||
|
# dotenv environment variables file
|
||||||
|
.env
|
||||||
|
|
||||||
|
# next.js build output
|
||||||
|
.next
|
20
README.md
20
README.md
@ -5,10 +5,26 @@ efficient and no dependencies.
|
|||||||
## Usage
|
## Usage
|
||||||
Only one method is provided;
|
Only one method is provided;
|
||||||
```js
|
```js
|
||||||
const csv = 'Name,Age,Location\nDom,26,Sydney\nSteve,30,New York';
|
const csv = 'Name,Age,Location\nDom,26,"Sydney, Australia"\nSteve,30,"New York, USA"';
|
||||||
parseCSV(csv, function(headers, line, index) {
|
parseCSV(csv, function(headers, line, index) {
|
||||||
console.log(index, '-', line[headers.indexOf('Name')]);//1 - Dom
|
console.log(index, '-', line[headers.indexOf('Name')]);//1 - Dom
|
||||||
return true;//Return false to break loop
|
return true;//Return false to break loop
|
||||||
})
|
})
|
||||||
```
|
```
|
||||||
To stay efficient we ask for a callback that is fired for every line parsed.
|
To stay efficient we ask for a callback that is fired for every line parsed.
|
||||||
|
|
||||||
|
## Options
|
||||||
|
Options can be provided if necessary;
|
||||||
|
```js
|
||||||
|
parseCSV(csv, (headers,line,index) => true, {
|
||||||
|
skipBlanks: false
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
Current Options;
|
||||||
|
```
|
||||||
|
skipBlanks - True or False, whether or not to skip blank lines
|
||||||
|
```
|
||||||
|
|
||||||
|
## Testing
|
||||||
|
Tests are written in jest. Run `yarn test`
|
9
index.d.ts
vendored
Normal file
9
index.d.ts
vendored
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
type CSVOptions = {
|
||||||
|
skipBlanks?:boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
type CSVCallback = (headers:string[], line:string[], index:number) => boolean;
|
||||||
|
|
||||||
|
type CSVParser = (csv:string, onLine:CSVCallback, options?:CSVOptions)=>void;
|
||||||
|
|
||||||
|
export const { parseCSV:CSVParser }
|
17
package.json
Normal file
17
package.json
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
{
|
||||||
|
"name": "micro-csv",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"main": "src/index.js",
|
||||||
|
"repository": "https://github.com/YourWishes/Micro-CSV.git",
|
||||||
|
"author": "Dominic Masters <dominic@domsplace.com>",
|
||||||
|
"license": "MIT",
|
||||||
|
"scripts": {
|
||||||
|
"test": "jest"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"jest": "^26.6.3"
|
||||||
|
},
|
||||||
|
"jest": {
|
||||||
|
"testMatch": [ "**/test/**/*.js" ]
|
||||||
|
}
|
||||||
|
}
|
23
src/index.js
23
src/index.js
@ -1,15 +1,25 @@
|
|||||||
function parseCSV(csv, onLine) {
|
/**
|
||||||
|
* Parses a CSV string and runs a callback for each line found.
|
||||||
|
*
|
||||||
|
* @param csv CSV string to parse
|
||||||
|
* @param onLine Callback to be fired for each line in the CSV
|
||||||
|
* @param options Modify how the callback is called
|
||||||
|
*/
|
||||||
|
function parseCSV(csv, onLine, options) {
|
||||||
//Split lines by delim, don't worry about \r here
|
//Split lines by delim, don't worry about \r here
|
||||||
const lines = csv.split('\n');
|
const lines = csv.split('\n');
|
||||||
let headers = null;
|
let headers = null;
|
||||||
let index = 0;
|
let index = 0;
|
||||||
|
options = options || {
|
||||||
|
skipBlanks: true
|
||||||
|
};
|
||||||
|
|
||||||
//For each line...
|
//For each line...
|
||||||
for(let i = 0; i < lines.length; i++) {
|
for(let i = 0; i < lines.length; i++) {
|
||||||
//Shave off trailing whitespaces
|
//Shave off trailing whitespaces
|
||||||
let line = lines[i].trim();
|
let line = lines[i].trim();
|
||||||
if(!line.replace(/\s/g, '').length) continue;//Skip blank lines
|
|
||||||
index++;//Count real lines
|
if(options.skipBlanks && !line.replace(/\s/g, '').length) continue;//Skip blank lines
|
||||||
|
|
||||||
//Setup line array
|
//Setup line array
|
||||||
let lineArray;
|
let lineArray;
|
||||||
@ -39,15 +49,20 @@ function parseCSV(csv, onLine) {
|
|||||||
} else {
|
} else {
|
||||||
lineArray = line.split(',');
|
lineArray = line.split(',');
|
||||||
}
|
}
|
||||||
|
if(options.skipBlanks && (!lineArray.length || lineArray.every(e => !e.length))) continue;
|
||||||
|
|
||||||
//Is this the headers line?
|
//Is this the headers line?
|
||||||
if(!headers) {
|
if(!headers) {
|
||||||
headers = lineArray;
|
headers = lineArray;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
index++;//Count real lines
|
||||||
|
|
||||||
//Callback
|
//Callback
|
||||||
let r = onLine(headers, lineArray, index);
|
let r = onLine(headers, lineArray, index);
|
||||||
if(!r) break;//If r != true then break loop
|
if(!r) break;//If r != true then break loop
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//CommonJS only, don't copy this into google script
|
||||||
|
module.exports = { parseCSV };
|
75
test/index.js
Normal file
75
test/index.js
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
const { expect, it } = require('@jest/globals');
|
||||||
|
const { parseCSV } = require('./../src/index');
|
||||||
|
|
||||||
|
describe('parseCSV', () => {
|
||||||
|
it('should parse a csv file', () => {
|
||||||
|
const csv = 'Name,Age,Location\nDom,26,"Sydney, Australia"\nSteve,30,"New York, USA"';
|
||||||
|
|
||||||
|
parseCSV(csv, (headers,line,index) => {
|
||||||
|
//Headers are parsed as arrays of strings
|
||||||
|
expect(headers).toEqual([ 'Name', 'Age', 'Location' ]);
|
||||||
|
|
||||||
|
switch(index) {
|
||||||
|
case 1:
|
||||||
|
expect(line).toEqual([ 'Dom', '26', 'Sydney, Australia' ]);
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
expect(line).toEqual([ 'Steve', '30', 'New York, USA' ]);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should skip blank lines', () => {
|
||||||
|
//Line 2 is blank, Line 3 ends with a return carraige
|
||||||
|
const csv = 'Name,Age,Location\n,,\nSteve,30,"New York, USA"\n\rDom,26,"Sydney, Australia"';
|
||||||
|
|
||||||
|
parseCSV(csv, (headers,line,index) => {
|
||||||
|
expect(headers).toEqual([ 'Name', 'Age', 'Location' ]);
|
||||||
|
|
||||||
|
switch(index) {
|
||||||
|
case 1:
|
||||||
|
expect(line).toEqual([ 'Steve', '30', 'New York, USA' ]);
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
expect(line).toEqual([ 'Dom', '26', 'Sydney, Australia' ]);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should break when returning false', () => {
|
||||||
|
const csv = 'Name,Age,Location\nDom,26,"Sydney, Australia"\nSteve,30,"New York, USA"';
|
||||||
|
|
||||||
|
parseCSV(csv, (headers,line,index) => {
|
||||||
|
expect(line).toEqual([ 'Dom', '26', 'Sydney, Australia' ]);
|
||||||
|
expect(index).not.toEqual(2);
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be allowed to not skip blanks', () => {
|
||||||
|
|
||||||
|
//Line 2 is blank, Line 3 ends with a return carraige
|
||||||
|
const csv = 'Name,Age,Location\n,,\nSteve,30,"New York, USA"\n\rDom,26,"Sydney, Australia"';
|
||||||
|
|
||||||
|
parseCSV(csv, (headers,line,index) => {
|
||||||
|
expect(headers).toEqual([ 'Name', 'Age', 'Location' ]);
|
||||||
|
|
||||||
|
switch(index) {
|
||||||
|
case 1:
|
||||||
|
expect(line).toEqual([ '', '', '' ]);
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
expect(line).toEqual([ 'Steve', '30', 'New York, USA' ]);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}, { skipBlanks: false });
|
||||||
|
});
|
||||||
|
});
|
Reference in New Issue
Block a user