├── .gitignore ├── LICENSE.md ├── README.md ├── example-complex.js ├── example-stream.js ├── example.js ├── index.js └── package.json /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright (c) 2012, Deoxxa Development 2 | ====================================== 3 | All rights reserved. 4 | -------------------- 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 1. Redistributions of source code must retain the above copyright 9 | notice, this list of conditions and the following disclaimer. 10 | 2. Redistributions in binary form must reproduce the above copyright 11 | notice, this list of conditions and the following disclaimer in the 12 | documentation and/or other materials provided with the distribution. 13 | 3. Neither the name of Deoxxa Development nor the names of its contributors 14 | may be used to endorse or promote products derived from this software 15 | without specific prior written permission. 16 | 17 | THIS SOFTWARE IS PROVIDED BY DEOXXA DEVELOPMENT ''AS IS'' AND ANY 18 | EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 19 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 20 | DISCLAIMED. IN NO EVENT SHALL DEOXXA DEVELOPMENT BE LIABLE FOR ANY 21 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 22 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 23 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 24 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 25 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 26 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 27 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Concentrate 2 | =========== 3 | 4 | Produce binary data with a neat DSL. 5 | 6 | Overview 7 | -------- 8 | 9 | Concentrate allows you to efficiently create buffers by chaining together calls 10 | to write numbers, strings and even other buffers! Concentrate is also easily 11 | extendable so you can implement your own custom types. 12 | 13 | If you want to parse binary data, might I suggest [dissolve](https://github.com/deoxxa/dissolve)? 14 | 15 | Features 16 | -------- 17 | 18 | * Accurate handling of [u]int{8,16,32} numbers in both signed and unsigned 19 | variants using fast, built-in [Buffer](http://nodejs.org/docs/latest/api/buffer.html) 20 | methods 21 | * Fast and reliable operation through lazy compilation of results 22 | * Tiny (<150 LoC) implementation, allowing for easy debugging 23 | * Stream interface with explicit flushing for maximum speeeeeeed (inspired by 24 | felixge, so you know it's good!) 25 | 26 | Installation 27 | ------------ 28 | 29 | Available via [npm](http://npmjs.org/): 30 | 31 | > $ npm install concentrate 32 | 33 | Or via git: 34 | 35 | > $ git clone git://github.com/deoxxa/concentrate.git node_modules/concentrate 36 | 37 | Usage 38 | ----- 39 | 40 | Also see [example.js](https://github.com/deoxxa/concentrate/blob/master/example.js) and 41 | [example-complex.js](https://github.com/deoxxa/concentrate/blob/master/example-complex.js). 42 | 43 | ```javascript 44 | #!/usr/bin/env node 45 | 46 | var Concentrate = require("./index"); 47 | 48 | var data = Concentrate().uint8(1).uint8(2).uint32be(555).string("hi there", "utf8").result(); 49 | 50 | console.log(data); 51 | ``` 52 | 53 | ``` 54 | 55 | ``` 56 | 57 | Oh look! Streams! Also see [example-stream.js](https://github.com/deoxxa/concentrate/blob/master/example-stream.js). 58 | 59 | ```javascript 60 | #!/usr/bin/env node 61 | 62 | var Concentrate = require("./index"); 63 | 64 | var c = Concentrate(); 65 | 66 | c.on("end", function() { 67 | console.log("ended"); 68 | }); 69 | 70 | c.on("readable", function() { 71 | var e; 72 | while (e = c.read()) { 73 | console.log(e); 74 | } 75 | }); 76 | 77 | c.uint8(1).uint8(2).uint32be(555).string("hi there", "utf8").floatbe(2.1).doublebe(2.1).flush(); 78 | c.uint8(5).uint8(6); 79 | c.uint8(7); 80 | c.uint8(30); 81 | c.flush().end(); 82 | ``` 83 | 84 | ``` 85 | 86 | 87 | ended 88 | ``` 89 | 90 | Methods 91 | ------- 92 | 93 | All methods aside from `result` are chainable and all except `copy` return the 94 | same Concentrate instance they were called on. 95 | 96 | Execution Methods 97 | ----------------- 98 | 99 | * `copy()` - returns a new copy of the current Concentrate instance, copying the 100 | state of the current job list 101 | * `reset()` - resets the job list 102 | * `result()` - compiles the job list into a buffer and returns that buffer 103 | * `flush()` - compiles the current job list, emits it via the stream API, then 104 | clears the current job list 105 | * `end()` - ends the stream part of Concentrate (basically makes it emit "end") 106 | 107 | Writing methods 108 | --------------- 109 | 110 | * `buffer(data)` 111 | * `string(data, encoding)` - write a string with the given encoding 112 | * `int8(data)` - signed 8 bit integer 113 | * `sint8(data)` - signed 8 bit integer 114 | * `uint8(data)` - unsigned 8 bit integer 115 | * `int16(data)` - signed, little endian 16 bit integer 116 | * `int16le(data)` - signed, little endian 16 bit integer 117 | * `int16be(data)` - signed, big endian 16 bit integer 118 | * `sint16(data)` - signed, little endian 16 bit integer 119 | * `sint16le(data)` - signed, little endian 16 bit integer 120 | * `sint16be(data)` - signed, big endian 16 bit integer 121 | * `uint16(data)` - unsigned, little endian 16 bit integer 122 | * `uint16le(data)` - unsigned, little endian 16 bit integer 123 | * `uint16be(data)` - unsigned, big endian 16 bit integer 124 | * `int32(data)` - signed, little endian 32 bit integer 125 | * `int32le(data)` - signed, little endian 32 bit integer 126 | * `int32be(data)` - signed, big endian 32 bit integer 127 | * `sint32(data)` - signed, little endian 32 bit integer 128 | * `sint32le(data)` - signed, little endian 32 bit integer 129 | * `sint32be(data)` - signed, big endian 32 bit integer 130 | * `uint32(data)` - unsigned, little endian 32 bit integer 131 | * `uint32le(data)` - unsigned, little endian 32 bit integer 132 | * `uint32be(data)` - unsigned, big endian 32 bit integer 133 | * `floatbe(data)` - big endian 32 bit float 134 | * `floatle(data)` - little endian 32 bit float 135 | * `doublebe(data)` - big endian 64 bit double 136 | * `doublele(data)` - little endian 64 bit double 137 | 138 | License 139 | ------- 140 | 141 | 3-clause BSD. A copy is included with the source. 142 | 143 | Contact 144 | ------- 145 | 146 | * GitHub ([deoxxa](http://github.com/deoxxa)) 147 | * Twitter ([@deoxxa](http://twitter.com/deoxxa)) 148 | * ADN ([@deoxxa](https://alpha.app.net/deoxxa)) 149 | * Email ([deoxxa@fknsrs.biz](mailto:deoxxa@fknsrs.biz)) 150 | -------------------------------------------------------------------------------- /example-complex.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | var Concentrate = require("./index"), 4 | util = require("util"); 5 | 6 | function Serialiser() { 7 | if (!(this instanceof Serialiser)) { return new Serialiser(); } 8 | 9 | Concentrate.call(this); 10 | } 11 | util.inherits(Serialiser, Concentrate); 12 | 13 | Serialiser.prototype.metadata = function metadata(data) { 14 | return this.uint8(data.id).uint16be(data.data.length).string(data.data, "ucs2"); 15 | }; 16 | 17 | var data = Serialiser().uint8(1).uint8(2).metadata({id: 5, data: "some data"}).uint32be(555).string("hi there", "utf8").result(); 18 | 19 | console.log(data); 20 | -------------------------------------------------------------------------------- /example-stream.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | var Concentrate = require("./index"); 4 | 5 | var c = Concentrate(); 6 | 7 | c.on("end", function() { 8 | console.log("ended"); 9 | }); 10 | 11 | c.on("readable", function() { 12 | var e; 13 | while (e = c.read()) { 14 | console.log(e); 15 | } 16 | }); 17 | 18 | c.uint8(1).uint8(2).uint32be(555).string("hi there", "utf8").floatbe(2.1).doublebe(2.1).flush(); 19 | c.uint8(5).uint8(6); 20 | c.uint8(7); 21 | c.uint8(30); 22 | c.flush().end(); 23 | -------------------------------------------------------------------------------- /example.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | var Concentrate = require("./index"); 4 | 5 | var data = Concentrate().uint8(1).uint8(2).uint32be(555).string("hi there", "utf8").floatbe(2.1).doublebe(2.1).result(); 6 | 7 | console.log(data); 8 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | var stream = require("stream"), 2 | util = require("util"); 3 | 4 | var Concentrate = module.exports = function Concentrate(options) { 5 | if (!(this instanceof Concentrate)) { return new Concentrate(options); } 6 | 7 | stream.Readable.call(this, options); 8 | 9 | this.jobs = []; 10 | }; 11 | util.inherits(Concentrate, stream.Readable); 12 | 13 | Concentrate.prototype._read = function _read(n) {}; 14 | 15 | Concentrate.prototype.copy = function copy() { 16 | var copy = new Concentrate(); 17 | copy.jobs = this.jobs.slice(0); 18 | return copy; 19 | }; 20 | 21 | Concentrate.prototype.reset = function reset() { 22 | this.jobs.splice(0); 23 | 24 | return this; 25 | }; 26 | 27 | Concentrate.prototype.result = function result() { 28 | // optimisation for if there's only one job and it's a buffer - we don't need 29 | // to compile anything, we can just shove the buffer right on through. keep on 30 | // truckin'. 31 | if (this.jobs.length === 1 && this.jobs[0].type === "buffer") { 32 | return this.jobs[0].data; 33 | } 34 | 35 | var buffer = new Buffer(this.jobs.reduce(function(i, v) { return i + v.length; }, 0)); 36 | 37 | var offset = 0; 38 | this.jobs.forEach(function(job) { 39 | var method = ["write", job.type].join("_"); 40 | 41 | if (typeof this[method] === "function") { 42 | offset += this[method](job, buffer, offset); 43 | } 44 | }.bind(this)); 45 | 46 | return buffer; 47 | }; 48 | 49 | Concentrate.prototype.flush = function flush(no_reset) { 50 | this.push(this.result()); 51 | this.reset(); 52 | 53 | return this; 54 | }; 55 | 56 | Concentrate.prototype.end = function end() { 57 | this.push(null); 58 | 59 | return this; 60 | }; 61 | 62 | Concentrate.prototype.write_number = function write_number(job, buffer, offset) { 63 | buffer[job.method](job.data, offset); 64 | return job.length; 65 | }; 66 | 67 | Concentrate.prototype.write_buffer = function write_buffer(job, buffer, offset) { 68 | job.data.copy(buffer, offset); 69 | return job.data.length; 70 | }; 71 | 72 | Concentrate.prototype.buffer = function buffer(data) { 73 | this.jobs.push({type: "buffer", data: data, length: data.length}); 74 | return this; 75 | }; 76 | 77 | Concentrate.prototype.string = function string(data, encoding) { 78 | return this.buffer(new Buffer(data, encoding)); 79 | }; 80 | 81 | [8, 16, 32].forEach(function(b) { 82 | ["", "u"].forEach(function(s) { 83 | ["", "le", "be"].forEach(function(e) { 84 | // derive endiannes postfix supported by node Buffer api 85 | // for all the numbers, except 8 bit integer, endiannes is mandatory 86 | var endiannes = e || "le"; 87 | // for 8 bit integers - no endiannes postfix 88 | if(b === 8){ 89 | endiannes = ""; 90 | } 91 | 92 | var type = [s, "int", b, e].join(""), 93 | method = ["write", s.toUpperCase(), "Int", b, endiannes.toUpperCase()].join(""), 94 | length = b / 8; 95 | 96 | Concentrate.prototype[type] = function(data) { 97 | this.jobs.push({ 98 | type: "number", 99 | method: method, 100 | length: length, 101 | data: data, 102 | }); 103 | 104 | return this; 105 | }; 106 | }); 107 | }); 108 | }); 109 | 110 | [["float", 4], ["double", 8]].forEach(function(t) { 111 | ["le", "be"].forEach(function(e) { 112 | var type = [t[0], e].join(""), 113 | method = ["write", t[0].replace(/^(.)/, function(e) { return e.toUpperCase(); }), e.toUpperCase()].join(""), 114 | length = t[1]; 115 | 116 | Concentrate.prototype[type] = function(data) { 117 | this.jobs.push({ 118 | type: "number", 119 | method: method, 120 | length: length, 121 | data: data, 122 | }); 123 | 124 | return this; 125 | }; 126 | }); 127 | }); 128 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "concentrate", 3 | "version": "0.2.3", 4 | "description": "Produce binary data with a neat DSL", 5 | "main": "index.js", 6 | "engines": { 7 | "node": "~0.10.0" 8 | }, 9 | "dependencies": {}, 10 | "devDependencies": {}, 11 | "repository": { 12 | "type": "git", 13 | "url": "git://github.com/deoxxa/concentrate.git" 14 | }, 15 | "keywords": [ 16 | "concentrate", 17 | "binary", 18 | "data", 19 | "produce", 20 | "dsl" 21 | ], 22 | "author": "Conrad Pankoff (http://www.fknsrs.biz/)", 23 | "license": "BSD" 24 | } 25 | --------------------------------------------------------------------------------