Skip to content

Instantly share code, notes, and snippets.

@eduardonunesp
Created April 8, 2016 20:08
Show Gist options
  • Save eduardonunesp/925d2d6dd4b62d84ded66ceb0b435028 to your computer and use it in GitHub Desktop.
Save eduardonunesp/925d2d6dd4b62d84ded66ceb0b435028 to your computer and use it in GitHub Desktop.
Tags test

Node.js exam

Quick practical exam for node.js candidates.

Requirements

  • load tags.txt to get an array of tags
  • for each of these tags, find out how many times that tag appears within the objects in data/*.json (note: objects can be nested).
  • final output should look something like this (sorted by most popular tag first):
pizza 15
spoon 2
umbrella 0
cats 0
  • use only core modules.
  • use the asynchronous variants of the file IO functions (eg. use fs.readFile not fs.readFileSync).
  • if any of the data files contain invalid JSON, log the error with console.error and continue, ignoring that file.
'use strict';
const tagparser = require('./lib'),
mpath = require('path'),
http = require('http');
let NODE_ENV = process.env.NODE_ENV || 'development';
let PORT = process.env.PORT || 9000;
process.chdir(__dirname);
let httpServer = false;
process.argv.forEach(function (val, index, array) {
if (val == '--http') {
httpServer = true;
}
});
if (httpServer || NODE_ENV === 'production') {
http.createServer(function(req, res) {
// Read JSON files, tags file and calculate
tagparser.parseJSONFromDir(mpath.join(__dirname, 'data'))
.then(tagparser.getTagsValuesFromObject)
.then((values) => {
return tagparser.arrayAnalysis(values, mpath.join(__dirname, 'tags.txt'));
})
.then((result) => tagparser.formatOutput(result, '<br/>'))
.then((output) => {
res.writeHead(200, {'Content-Type': 'text/html'});
res.write(output);
res.end();
})
.catch((err) => {
res.writeHead(500, {'Content-Type': 'text/html'});
res.write(err.toString());
res.end();
});
}).listen(PORT);
return
}
console.log('Console only output');
// Read JSON files, tags file and calculate
tagparser.parseJSONFromDir(mpath.join(__dirname, 'data'))
.then(tagparser.getTagsValuesFromObject)
.then((values) => {
return tagparser.arrayAnalysis(values, mpath.join(__dirname, 'tags.txt'))
})
.then((result) => tagparser.formatOutput(result, '\n'))
.then((output) => console.log(output))
.catch((err) => console.log(err))
'use strict';
const fs = require('fs'),
mpath = require('path');
/**
* Check if file exists
*
* @param {String} path Path to the file
* @return {Object} Promise resolve filepath, or reject with error
*/
function fileExists(path) {
return new Promise(function(resolve, reject) {
fs.stat(path, (err) => err ? reject(err) : resolve(path));
});
};
/**
* List the contents from a directory and return an array of filenames
*
* @param {String} path Directory path
* @param {String} filterOnlyExt Type of file to return, based on extension
* @return {Object} Promise resolve array with filenames or reject with error
*/
function listDirectory(path, filterOnlyExt) {
return new Promise(function(resolve, reject) {
fs.readdir(path, function(err, data) {
if (err) {
return reject(err);
}
if (!filterOnlyExt) {
resolve(data);
} else {
// Make sure to filter specific extension if needed
const filteredData = data.filter(function(file) {
return file.split('.').pop() == filterOnlyExt;
});
resolve(filteredData);
}
});
});
};
/**
* Get the contents of a file in utf8 string
*
* @param {String} path Path to the file
* @return {Object} Promise resolve file contents in utf8 String or reject with error
*/
function fileGetContents(path) {
return new Promise(function(resolve, reject) {
fs.readFile(path, (err, data) => err ? reject(err) : resolve(data.toString('utf8')));
});
};
/**
* Turn a list of characters separated by CRLF or \n into an array
*
* @param {String} fileContents String with the contents
* @return {Array} Array of characteres
*/
const textListToJSArray = (fileContents) => fileContents.trim().split('\n');
/**
* Get an array of tags from a file by the given path
*
* @param {String} path Path to the file
* @return {Object} Promise resolve array of tags or reject with error
*/
function getArrayOfTagsFromFile(path) {
return fileExists(path)
.then(fileGetContents)
.then(textListToJSArray);
};
/**
* Transverse an entire object as a generator
*
* @param {Object} obj Object to transverse
* @yield {Generator} Generator with specific tags element
*/
function *transverseObject(obj) {
if (!obj) { return; }
for (let i = 0; i< obj.length; i++){
const val = obj[i];
// Yield the tag there's no children object
if (val.tags) {
yield val.tags;
}
// Have children objects, need to good deep
if (val.children) {
yield *transverseObject(val.children);
}
}
}
/**
* Verify if given JSON is valid
*
* @param {String} jsonFile JSON to be sanitized
* @return {String} Return the JSON or empty String
*/
function JSONSanitizer(jsonFile) {
try {
JSON.parse(jsonFile);
return jsonFile;
} catch (e) {
return "";
}
}
/**
* Parse JSON files from a specific directory
*
* @param {String} path Path to parse json files
* @return {Object} Promise resolve is an object of all JSON files
* found or reject with error
*/
function parseJSONFromDir(path) {
return listDirectory(path, 'json')
.then(function(files) {
const promises = [];
// Prepare to get contents and check each file
files.forEach(function(file) {
const readJSONPromise = fileExists(mpath.join(path, file))
.then(function(fileConfirmed) {
// Get the file
return fileGetContents(fileConfirmed)
// Check if a valid json from file, sanitize and clean
.then((fileContent) => JSONSanitizer(fileContent))
.then(function(jsonSanitized) {
if (!jsonSanitized) {
console.error('Invalid json file', fileConfirmed);
return "{}"
} else {
return jsonSanitized;
}
})
});
return promises.push(readJSONPromise);
});
return promises;
})
.then((files) => Promise.all(files))
.then(function(jsons) {
// Small trick to bind JSONs in an unique object
const json = '[' + jsons.join(',') + ']';
return JSON.parse(json);
});
};
/**
* Transverse an object and get all tags values in one array
*
* @param {Object} object Object to transverse
* @return {Array} Array with all tags found
*/
function getTagsValuesFromObject(object) {
const gen = transverseObject(object);
let arr = [];
let res = gen.next();
// Transverse with a generator
while(!res.done) {
arr = arr.concat(res.value);
res = gen.next();
}
return arr;
};
/**
* Sort properties of an particular object
*
* @param {Object} obj Object to sort
* @return {Array} Array of sorted properties and keys
*/
function sortProperties(obj) {
const sortable = [];
for(let key in obj) {
if(obj.hasOwnProperty(key)) {
sortable.push([key, obj[key]]);
}
}
return sortable
.sort((a, b) => a[1] - b[1])
.reverse()
};
/**
* Analyzes an array looking for specific tags and counting the valid occurrences
*
* @param {Array} array Array to analyse
* @return {Object} Promise with all ocurrences calculated and sorted or reject with error
*/
function arrayAnalysis(array, tagsFile) {
return fileExists(tagsFile)
.then((fileConfirmed) => fs.readFileSync(fileConfirmed).toString('utf8'))
.then(textListToJSArray)
.then((tags) => {
let occurrences = {};
let validOcurrences = {};
// Count the ocurrences
for (let i = 0; i < array.length; i++) {
const num = array[i];
occurrences[num] = occurrences[num] ? occurrences[num]+1 : 1;
}
// Which tags we want to compare if exists
tags.forEach((tag) => {
validOcurrences[tag] = occurrences[tag] ? occurrences[tag] : 0
});
// Sort and return
const sortedProperties = sortProperties(validOcurrences);
return sortedProperties;
});
};
/**
* Format the output to present to the data
*
* @param {Array} array Array already analyzed and ready to print
*/
function formatOutput(array, separator) {
const output = [];
for (let i = 0; i < array.length; i++) {
output.push(array[i][0] + ' ' + array[i][1]);
}
return separator ? output.join(separator) : output.join('\n')
};
module.exports = {}
module.exports.formatOutput = formatOutput;
module.exports.arrayAnalysis = arrayAnalysis;
module.exports.sortProperties = sortProperties;
module.exports.getTagsValuesFromObject = getTagsValuesFromObject;
module.exports.parseJSONFromDir = parseJSONFromDir;
module.exports.JSONSanitizer = JSONSanitizer;
module.exports.transverseObject = transverseObject;
module.exports.getArrayOfTagsFromFile = getArrayOfTagsFromFile
module.exports.textListToJSArray = textListToJSArray
module.exports.fileGetContents = fileGetContents
module.exports.listDirectory = listDirectory
module.exports.fileExists = fileExists
'use strict';
const assert = require('assert'),
tagparser = require('../lib'),
path = require('path');
let head = (arr) => arr[0]
let last = (arr) => arr[arr.length -1];
let contains = (arr, x) => arr.indexOf(x) > -1;
describe('Tag Parser', function() {
it('should fulfill the promise file exists', function(done) {
tagparser.fileExists(path.join(__dirname, 'tags.txt'))
.then((fileTested) => {
assert.equal(!!fileTested, true);
done();
})
.catch((err) => {
done(err);
});
});
it('should reject the promise if file not exists', function(done) {
tagparser.fileExists(path.join(__dirname, 'this_file_not_exists.txt'))
.then((fileTested) => {
done(new Error('File exists'));
})
.catch((err) => {
done();
});
});
it('should fulfill the promise list directory', function(done) {
tagparser.listDirectory(path.join(__dirname, 'data'), 'json')
.then((directory) => {
directory = directory.sort();
assert.equal(directory.length, 2)
assert.equal(directory[0], 'file1.json')
assert.equal(directory[1], 'file2.json')
done();
})
.catch((err) => {
done(err);
});
});
it('should reject the promise list directory', function(done) {
tagparser.listDirectory(path.join(__dirname, 'dir_not_exists'), 'json')
.then((directory) => {
done(new Error('Directory exists'));
})
.catch((err) => {
done();
});
});
it('should fulfill the promise get content of a file', function(done) {
tagparser.fileGetContents(path.join(__dirname, 'data', 'file1.json'))
.then((file) => {
done();
})
.catch((err) => {
done(err);
});
});
it('should reject the promise get a content of a file', function(done) {
tagparser.fileGetContents(path.join(__dirname, 'data', 'f.json'))
.then((directory) => {
done(new Error('Directory exists'));
})
.catch((err) => {
done();
});
});
it('should fulfill the promise get array from a file', function(done) {
tagparser.getArrayOfTagsFromFile(path.join(__dirname, 'tags.txt'))
.then((arrayFromFile) => {
arrayFromFile = arrayFromFile.sort();
assert.equal(arrayFromFile.length, 3)
assert.equal(arrayFromFile[0], 'tag1')
assert.equal(arrayFromFile[1], 'tag2')
assert.equal(arrayFromFile[2], 'tag3')
done();
})
.catch((err) => {
done(err);
});
});
it('should reject the promise get array from a file', function(done) {
tagparser.getArrayOfTagsFromFile(path.join(__dirname, 'not.txt'))
.then((directory) => {
done(new Error('Directory exists'));
})
.catch((err) => {
done();
});
});
it('should transverse entire JS object', function() {
const mockedObject = [{
name: 'First object',
tags : ['color'],
children : [{
'tags' : ['animal', 'car']
}]
}, {
name: 'Second object',
tags: ['cat'],
children: [{
children: [{
children: [{
tags: ['mole']
}]
}]
}]
}, {
name: 'Third object',
tags: 'argonaut'
}];
let arr = tagparser.getTagsValuesFromObject(mockedObject).sort();
assert.equal(arr.length, 6);
assert.equal(head(arr), 'animal');
assert.equal(last(arr), 'mole');
assert.notEqual(last(arr), 'argonaut');
assert.equal()
});
it('should parse json file from directory', function(done) {
tagparser.parseJSONFromDir(path.join(__dirname, 'data'))
.then((object) => {
let firstObject = head(object);
assert.equal(head(firstObject.tags), 'tag1');
assert.equal(last(firstObject.tags), 'tag3');
assert.equal(firstObject.tags.length, 3);
done();
})
.catch((err) => {
done(err);
});
});
it('should sort properties from an object', function() {
const mockedObject = {
foo: 10,
bar: 200,
bibier: 0,
tom: 1,
mom: 100
};
const sorted = tagparser.sortProperties(mockedObject);
assert.equal(contains(head(sorted), 'bar'), true);
assert.equal(contains(last(sorted), 'bibier'), true);
});
it('should analyzes an array', function(done) {
const mockedObject = ['tag1', 'tag1', 'tag4', 'tag5', 'tag2'];
tagparser.arrayAnalysis(mockedObject, path.join(__dirname, 'tags.txt'))
.then((result) => {
assert.equal(head(result)[0], 'tag1');
assert.equal(head(result)[1], 2);
assert.equal(last(result)[0], 'tag3');
assert.equal(last(result)[1], 0);
done();
})
.catch((err) => done(err));
});
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment