Skip to content

Commit

Permalink
Finished re-writing main encode and decode funcs
Browse files Browse the repository at this point in the history
IMPROVEMENTS:
- Logic wise, no major changes
- Better error handling and type checking
- Cleaner code
- Better documentation

Complete unit tests

Gulp task to generate documentation

More test cases

Generate docs
  • Loading branch information
riyadhalnur committed May 28, 2016
1 parent 7d8f72d commit bb2b5cf
Show file tree
Hide file tree
Showing 9 changed files with 261 additions and 14 deletions.
6 changes: 4 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ coverage

# tmp folder
tmp
.tmp

# Compiled binary addons (http://nodejs.org/api/addons.html)
build/Release
Expand All @@ -27,5 +28,6 @@ node_modules
# Users Environment Variables
.lock-wscript

*.jpg
!test/test.jpg
test.jpg

.github
14 changes: 11 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,20 @@ Download images from remote URLs or use local images and encode/decode them to B
`npm install node-base64-image --save`

### Usage
`var base64 = require('node-base64-image');`
`var base64 = require('node-base64-image'); // ES5`

`import {encode, decode} from 'node-base64-image'; // ES6`

### Documentation
Read the documentation in [DOCUMENTATION](docs/docs.md).

### Contributing
Read the [CONTRIBUTING](CONTRIBUTING.md) guide for information.

### License
Licensed under MIT.
Licensed under MIT. See [LICENSE](LICENSE) for more information.

### Issues
Report a bug in the issues.
Report a bug in issues.

Made with love in Dhaka, Bangladesh by [Riyadh Al Nur](https://verticalaxisbd.com)
49 changes: 49 additions & 0 deletions docs/docs.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
# fnCallback

[src/node-base64-image.js:13-13](https://github.com/riyadhalnur/node-base64-image/blob/48ccb3a785ba53841dc0821548dff8177da7bfb0/src/node-base64-image.js#L13-L13 "Source code on GitHub")

Callback for encode/decode functions

**Parameters**

- `Error` **[Object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object)** object
- `Response` **([string](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String) \| [Object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object))** string or Buffer object

# encode

[src/node-base64-image.js:27-65](https://github.com/riyadhalnur/node-base64-image/blob/48ccb3a785ba53841dc0821548dff8177da7bfb0/src/node-base64-image.js#L27-L65 "Source code on GitHub")

Encodes a remote or local image to Base64 encoded string or Buffer

**Parameters**

- `url` **[string](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String)** URL of remote image or local path to image
- `options` **\[[Object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object)]** Options object for extra configuration (optional, default `{}`)
- `options.string` **[boolean](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean)** Returns a Base64 encoded string. Defaults to Buffer object
- `options.local` **[boolean](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean)** Encode a local image file instead of a remote image
- `callback` **fnCallback** Callback function

Returns **fnCallback** Returns the callback

# result

[src/node-base64-image.js:41-41](https://github.com/riyadhalnur/node-base64-image/blob/48ccb3a785ba53841dc0821548dff8177da7bfb0/src/node-base64-image.js#L41-L41 "Source code on GitHub")

# result

[src/node-base64-image.js:58-58](https://github.com/riyadhalnur/node-base64-image/blob/48ccb3a785ba53841dc0821548dff8177da7bfb0/src/node-base64-image.js#L58-L58 "Source code on GitHub")

# decode

[src/node-base64-image.js:77-89](https://github.com/riyadhalnur/node-base64-image/blob/48ccb3a785ba53841dc0821548dff8177da7bfb0/src/node-base64-image.js#L77-L89 "Source code on GitHub")

Decodes an base64 encoded image buffer and saves it to disk

**Parameters**

- `imageBuffer` **[Buffer](https://nodejs.org/api/buffer.html)** Image Buffer object
- `options` **\[[Object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object)]** Options object for extra configuration (optional, default `{}`)
- `options.filename` **[string](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String)** Filename for the final image file
- `callback` **fnCallback** Callback function

Returns **fnCallback** Returns the callback
13 changes: 12 additions & 1 deletion gulpfile.babel.js
Original file line number Diff line number Diff line change
Expand Up @@ -135,11 +135,19 @@ function typeCheck(done) {
}

function gitTag() {
_registerBabel();
return gulp.src(['./package.json'])
.pipe($.tagVersion());
}

const watchFiles = ['src/**/*', 'test/**/*', 'package.json', '**/.eslintrc', '.jscsrc'];
function generateDocs() {
_registerBabel();
return gulp.src('src/node-base64-image.js')
.pipe($.documentation({ format: 'md', filename: 'docs.md' }))
.pipe(gulp.dest('docs'));
}

const watchFiles = ['src/**/*', 'test/**/*', 'package.json', '**/.eslintrc'];

// Run the headless unit tests as you make changes.
function watch() {
Expand Down Expand Up @@ -225,6 +233,9 @@ gulp.task('flow', typeCheck);
// Tag with version in package.json
gulp.task('tag', gitTag);

// Generate documentation
gulp.task('doc', generateDocs);

// Set up a livereload environment for our spec runner `test/runner.html`
gulp.task('test-browser', ['lint', 'clean-tmp'], testBrowser);

Expand Down
4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@
"build": "gulp build",
"coverage": "gulp coverage",
"tag": "gulp tag",
"prepublish": "npm run build && npm test && npm run tag"
"doc": "gulp doc",
"prepublish": "npm run build && npm run coverage && npm run tag"
},
"repository": {
"type": "git",
Expand Down Expand Up @@ -49,6 +50,7 @@
"glob": "^6.0.3",
"gulp": "^3.9.0",
"gulp-babel": "^6.1.1",
"gulp-documentation": "2.2.0",
"gulp-eslint": "^2.0.0",
"gulp-filter": "^3.0.0",
"gulp-istanbul": "^0.10.3",
Expand Down
52 changes: 47 additions & 5 deletions src/node-base64-image.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,14 @@ import {readFile as read, writeFile as write} from 'fs';
*
* @callback fnCallback
* @param {Object} Error object
* @param {string} Response message
* @param {(string|Object)} Response string or Buffer object
*/
type Callback<T> = (err: ?Error, x?: T) => void;

/**
* Encodes a remote or local image to Base64 encoded string or Buffer
*
* @name encode
* @param {string} url - URL of remote image or local path to image
* @param {Object} [options={}] - Options object for extra configuration
* @param {boolean} options.string - Returns a Base64 encoded string. Defaults to Buffer object
Expand All @@ -22,26 +24,66 @@ type Callback<T> = (err: ?Error, x?: T) => void;
* @todo Option to wrap string every 76 characters for strings larger than 76 characters
* @return {fnCallback} - Returns the callback
*/
export function encode(url: string, options: Object = {}, callback: Callback<string>) {
export function encode(url: string, options: Object = {string: false, local: false}, callback: Callback<mixed>) { // eslint-disable-line
if (_.isUndefined(url) || _.isNull(url) || !_.isString(url)) {
return callback(new Error('URL is undefined or not properly formatted'));
}

if (!_.isFunction(callback)) {
return callback(new Error('Callback needs to be a function'));
if (options.local) {
read(url, (err, body) => {
if (err) {
return callback(err);
}

/**
* @todo Handle this better.
*/
let result = options.string ? body.toString('base64') : new Buffer(body, 'base64');
return callback(null, result);
});
} else {
request({ url: url, encoding: null }, (err, response, body) => {
if (err) {
return callback(err);
}

if (!body) {
return callback(new Error('Error retrieving image - Empty Body!'));
}

if (body && response.statusCode === 200) {
/**
* @todo Handle this better.
*/
let result = options.string ? body.toString('base64') : new Buffer(body, 'base64');
return callback(null, result);
}

return callback(new Error('Error retrieving image - Status Code ' + response.statusCode));
});
}
}

/**
* Decodes an base64 encoded image buffer and saves it to disk
*
* @name decode
* @param {Buffer} imageBuffer - Image Buffer object
* @param {Object} [options={}] - Options object for extra configuration
* @param {string} options.filename - Filename for the final image file
* @param {fnCallback} callback - Callback function
* @return {fnCallback} - Returns the callback
*/
export function decode(imageBuffer: any, options: Object = {}, callback: Callback<string>) {
export function decode(imageBuffer: any, options: Object = {filename: 'saved-image'}, callback: Callback<mixed>) { // eslint-disable-line
if (!_.isBuffer(imageBuffer)) {
return callback(new Error('The image is not a Buffer object type'));
}

write(options.filename + '.jpg', imageBuffer, 'base64', (err) => {
if (err) {
return callback(err);
}

return callback(null, 'Image saved successfully to disk!');
});
}
3 changes: 2 additions & 1 deletion test/setup/.globals.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
"stub": true,
"useFakeServer": true,
"useFakeTimers": true,
"useFakeXMLHttpRequest": true
"useFakeXMLHttpRequest": true,
"timeout": 15000
}
}
134 changes: 133 additions & 1 deletion test/unit/node-base64-image.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,140 @@
import {encode, decode} from '../../src/node-base64-image.js';

describe('Base64 Image', () => {
describe('Base64 Image decode/encode', () => {
it('should exist', () => {
should.exist(encode);
should.exist(decode);
});

describe('Encoder', () => {
it('should return an error if callback is not a function', (done) => {
let url;
let options = {};
let callback;

(function () {
encode(url, options, callback);
}).should.throw(TypeError);

done();
});

it('should return an error if url is null or undefined', (done) => {
let url;
let options = {};

encode(url, options, (err, image) => {
err.should.exist;
err.message.should.equal('URL is undefined or not properly formatted');

should.not.exist(image);
done();
});
});

it('should return an error if local image could not be loaded', (done) => {
let url = __dirname + '/noimage.jpg';
let options = {local: true};

encode(url, options, (err, image) => {
err.should.exist;

should.not.exist(image);
done();
});
});

it('should return an error if remote image could not be loaded', (done) => {
let url = 'https://verticalaxisbd.com/noimage.jpg';
let options = {};

encode(url, options, (err, image) => {
err.should.exist;
err.message.should.equal('Error retrieving image - Status Code 404');

should.not.exist(image);
done();
});
});

it('should download an image and return the Base64 encoded string', function (done) {
let url = 'https://res.cloudinary.com/verticalaxisbd/image/upload/h_239,w_239/rg1kxkgxayhdgoqdaejz.jpg'; // eslint-disable-line
let options = {string: true};

encode(url, options, (err, image) => {
should.not.exist(err);

image.should.exist;
image.should.match(/^([0-9a-zA-Z+/]{4})*(([0-9a-zA-Z+/]{2}==)|([0-9a-zA-Z+/]{3}=))?$/);
done();
});
});

it('should download an image and return the Buffer object by default', function (done) {
let url = 'https://res.cloudinary.com/verticalaxisbd/image/upload/h_239,w_239/rg1kxkgxayhdgoqdaejz.jpg'; // eslint-disable-line
let options = {};

encode(url, options, (err, image) => {
should.not.exist(err);

image.should.exist;
image.should.be.an.instanceOf(Buffer);
done();
});
});

it('should encode local image and return the Buffer object by default', (done) => {
let path = __dirname + '/test.jpg';
let options = {local: true};

encode(path, options, (err, image) => {
should.not.exist(err);

image.should.exist;
image.should.be.an.instanceOf(Buffer);
done();
});
});

it('should encode local image and return the Base64 encoded string', (done) => {
let path = __dirname + '/test.jpg';
let options = {local: true, string: true};

encode(path, options, (err, image) => {
should.not.exist(err);

image.should.exist;
image.should.match(/^([0-9a-zA-Z+/]{4})*(([0-9a-zA-Z+/]{2}==)|([0-9a-zA-Z+/]{3}=))?$/);
done();
});
});
});

describe('Decoder', () => {
it('should return an error if image is not a Buffer object', (done) => {
let imageData;
let options = {};

decode(imageData, options, (err, response) => {
err.should.exist;
err.message.should.equal('The image is not a Buffer object type');

should.not.exist(response);
done();
});
});

it('should decode a Base64 encoded image and save it to disk', (done) => {
let options = {filename: 'test'};
let imageData = new Buffer('/9j/4AAQSkZJRgABAQAAAQABAAD/2w//Z', 'base64');

decode(imageData, options, (err, response) => {
should.not.exist(err);

response.should.exist;
response.should.equal('Image saved successfully to disk!');
done();
});
});
});
});
Binary file added test/unit/test.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.

0 comments on commit bb2b5cf

Please sign in to comment.