├── .babelrc ├── .gitignore ├── .npmignore ├── .travis.yml ├── CHANGELOG.md ├── LICENSE ├── README.md ├── corrode.svg ├── docs-md ├── configuration.md ├── getting-started.md └── overview.md ├── docs ├── CNAME ├── ast │ └── source │ │ ├── .external-ecmascript.js.json │ │ ├── assert │ │ └── index.js.json │ │ ├── base.js.json │ │ ├── index.js.json │ │ ├── map │ │ └── index.js.json │ │ ├── utils │ │ └── index.js.json │ │ └── variable-stack.js.json ├── badge.svg ├── class │ └── src │ │ ├── base.js~CorrodeBase.html │ │ ├── index.js~Corrode.html │ │ └── variable-stack.js~VariableStack.html ├── coverage.json ├── css │ ├── github.css │ ├── identifiers.css │ ├── manual.css │ ├── prettify-tomorrow.css │ ├── search.css │ ├── source.css │ ├── style.css │ └── test.css ├── file │ └── src │ │ ├── assert │ │ └── index.js.html │ │ ├── base.js.html │ │ ├── index.js.html │ │ ├── map │ │ └── index.js.html │ │ ├── utils │ │ └── index.js.html │ │ └── variable-stack.js.html ├── function │ └── index.html ├── identifiers.html ├── image │ ├── badge.svg │ ├── esdoc-logo-mini-black.png │ ├── esdoc-logo-mini.png │ ├── github.png │ ├── manual-badge.svg │ └── search.png ├── index.html ├── index.json ├── lint.json ├── manual │ ├── CHANGELOG.html │ ├── configuration.html │ ├── getting-started.html │ ├── index.html │ └── overview.html ├── script │ ├── inherited-summary.js │ ├── inner-link.js │ ├── manual.js │ ├── patch-for-local.js │ ├── prettify │ │ ├── Apache-License-2.0.txt │ │ └── prettify.js │ ├── pretty-print.js │ ├── search.js │ ├── search_index.js │ └── test-summary.js ├── source.html ├── test-file │ └── test │ │ ├── assert.test.js.html │ │ ├── base-aborts.test.js.html │ │ ├── base-edge-cases.test.js.html │ │ ├── base-flags.test.js.html │ │ ├── base-loop-anonymous.test.js.html │ │ ├── base-loop-named.test.js.html │ │ ├── base-primitves.test.js.html │ │ ├── base-push-jobs.test.js.html │ │ ├── base-skip.test.js.html │ │ ├── base-stack.test.js.html │ │ ├── base-tap.test.js.html │ │ ├── corrode-extensions.test.js.html │ │ ├── corrode-helper.test.js.html │ │ ├── corrode-pointer.test.js.html │ │ ├── corrode-position.test.js.html │ │ ├── corrode-repeat.test.js.html │ │ ├── corrode-terminated-buffer.test.js.html │ │ ├── corrode-terminated-string.test.js.html │ │ ├── map.test.js.html │ │ ├── utils.test.js.html │ │ └── variable-stack.test.js.html ├── test.html └── variable │ └── index.html ├── esdoc.json ├── examples └── id3.js ├── package-lock.json ├── package.json ├── src ├── assert │ └── index.js ├── base.js ├── index.js ├── map │ └── index.js ├── utils │ └── index.js └── variable-stack.js └── test ├── assert.test.js ├── base-aborts.test.js ├── base-edge-cases.test.js ├── base-flags.test.js ├── base-loop-anonymous.test.js ├── base-loop-named.test.js ├── base-primitves.test.js ├── base-push-jobs.test.js ├── base-skip.test.js ├── base-stack.test.js ├── base-tap.test.js ├── corrode-extensions.test.js ├── corrode-helper.test.js ├── corrode-pointer.test.js ├── corrode-position.test.js ├── corrode-repeat.test.js ├── corrode-terminated-buffer.test.js ├── corrode-terminated-string.test.js ├── fixtures ├── double-seq.bin ├── float-seq.bin ├── int16-seq.bin ├── int32-seq.bin ├── int64-seq.bin ├── int8-seq.bin ├── string-utf8.bin └── vars.js ├── helpers └── asserts.js ├── map.test.js ├── utils.test.js └── variable-stack.test.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | "es2015", 4 | "stage-0" 5 | ], 6 | "plugins": [ 7 | "add-module-exports" 8 | ] 9 | } 10 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | 6 | # Runtime data 7 | pids 8 | *.pid 9 | *.seed 10 | 11 | # Directory for instrumented libs generated by jscoverage/JSCover 12 | lib-cov 13 | 14 | # Coverage directory used by tools like istanbul 15 | coverage 16 | 17 | # nyc test coverage 18 | .nyc_output 19 | 20 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 21 | .grunt 22 | 23 | # node-waf configuration 24 | .lock-wscript 25 | 26 | # Compiled binary addons (http://nodejs.org/api/addons.html) 27 | build/Release 28 | 29 | # Dependency directories 30 | node_modules 31 | jspm_packages 32 | 33 | # Optional npm cache directory 34 | .npm 35 | 36 | # Optional REPL history 37 | .node_repl_history 38 | 39 | 40 | .DS_Store 41 | dist 42 | esdoc 43 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .nyc_output 2 | .travis.yml 3 | corrode.svg 4 | esdoc.json 5 | 6 | coverage 7 | esdoc 8 | test 9 | docs 10 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - stable 4 | - 5.12.0 5 | - 4.4.7 6 | 7 | install: 8 | - npm install 9 | 10 | script: 11 | - npm run coverall 12 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## corrode v1.0.2 4 | * Update ESDoc to v0.5.2 for better ES7 support 5 | 6 | ## corrode v1.0.1 7 | * Update lodash from 4.16.1 to 4.17.2 8 | * Update readable-stream from 2.1.5 to 2.2.2 9 | * Fix http links in npm-shrinkwrap.json 10 | 11 | ## corrode v1.0.0 12 | * Initial release. Any Changes prior to this version are merged into this one. 13 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Sebastian Langer 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | ∆ corrode 3 |

4 | 5 | [![MIT license](https://img.shields.io/npm/l/corrode.svg?style=flat-square)](./LICENSE) 6 | [![NPM version](https://img.shields.io/npm/v/corrode.svg?style=flat-square)](https://www.npmjs.com/package/corrode) 7 | [![dependencies](https://img.shields.io/david/screeny05/corrode.svg?style=flat-square)](https://david-dm.org/screeny05/corrode) 8 | [![coverage](https://img.shields.io/coveralls/screeny05/corrode.svg?style=flat-square)](https://coveralls.io/github/screeny05/corrode) 9 | [![build status](https://img.shields.io/travis/screeny05/corrode.svg?style=flat-square)](https://travis-ci.org/screeny05/corrode) 10 | [![docs](https://corrode.scn.cx/badge.svg)](https://corrode.scn.cx/) 11 | 12 | Corrode is a batteries-included library for reading binary data. It helps you converting that blob-mess into useable data. 13 | 14 | Use it to parse _that one_ obscure binary-file with the help of JavaScript. 15 | 16 | #### Install 17 | ``` 18 | $ npm install --save corrode 19 | ``` 20 | 21 | #### Tests 22 | ``` 23 | $ npm test 24 | ``` 25 | 26 | #### Offline Docs 27 | ``` 28 | $ npm run docs 29 | $ open doc/index.html 30 | ``` 31 | 32 | 33 | ## What's this? 34 | corrode provides standard read-actions like uint8-uint64 for big & little endian, strings, buffers and control-structures like loops, skipping, etc. for your buffers and files. 35 | Additionally you can use assertions to always be sure, the data you parse corresponds to a specified format. 36 | The parsing is done not by a configuration-object, but by imperative code, allowing for far greater flexibility. 37 | 38 | corrode is an abstraction on top of `TransformStream` and as such is pipeable to but also provides functions for more simple usage. 39 | 40 | This library is not only heavily inspired by [dissolve](https://github.com/deoxxa/dissolve), it in fact can be seen as a total rewrite with even more features. 41 | The code is written in ES7, fully documented and tested. 42 | 43 | 44 | ## Quick examples 45 | ```javascript 46 | const Corrode = require('corrode'); 47 | const parser = new Corrode(); 48 | 49 | parser 50 | .uint8('val_1') 51 | .uint32('val_2') 52 | .int16be('val_3') 53 | .tap(function(){ 54 | console.log(this.vars.val_1 * this.vars.val_3); 55 | }) 56 | .repeat('array', 5, function(){ 57 | this 58 | .uint32('array_val_1') 59 | .string('array_val_4', 5); 60 | }); 61 | ``` 62 | 63 | #### Parsing a buffer 64 | ```javascript 65 | parser.fromBuffer(buffer, () => console.log(parser.vars)); 66 | ``` 67 | 68 | #### Parsing a filestream 69 | ```javascript 70 | var stream = fs.createReadStream(file); 71 | stream.pipe(parser); 72 | parser.on('finish', () => console.log(parser.vars)); 73 | ``` 74 | 75 | These are just some of the very basic operations supported by Corrode. 76 | 77 | 78 | ## Examples 79 | All examples can be found in the examples/-folder. Included: 80 | * ID3v2.3-Parser - strict, unforgiving parser for a subset of the standard used to store meta-data in mp3-files. It needs `npm i image-to-ascii temp` and can be run with `node examples/id3 test.mp3`. 81 | 82 | If you'd like to include your own examples, just open a PR. I'm more than happy to not have to think about existing complex structured binary data to parse myself. 83 | 84 | 85 | ## Documentation & API Reference 86 | * [Corrode Overview](https://corrode.scn.cx/manual/overview.html) 87 | * [API Reference](https://corrode.scn.cx/identifiers.html) 88 | * [Getting Started](https://corrode.scn.cx/manual/getting-started.html) 89 | 90 | 91 | ## Why use corrode over dissolve 92 | It solves most of the major shortcomings dissolve has: 93 | * EOF terminates corrode. If not explicitly asked not to do so it will give you all variables, without you having to fiddle with its intestines. 94 | * Loops get unwinded correctly. 95 | * Thoroughly tested. 96 | * As a js-library from 2016 it has all the swag you need. 97 | 98 | 99 | ## When not to use corrode 100 | * Your data is too complex - If you need to apply black magic on your data, to retrieve meaningful values, corrode currently may not support your use-case. 101 | * Your data is really simple - If you don't need to read structured data, but instead just numbers or strings you should simply use the built-in read-functions provided by `Buffer`. 102 | 103 | Not yet included are additions like bignum-support for int64 and additional non-node-standard-encodings. 104 | 105 | corrode also works in browsers. You will need a `setImmediate()` polyfill, but you're able to parse ArrayBuffers as usual. 106 | 107 | 108 | ## Used dependencies (3) 109 | The following dependencies are installed when installing corrode: 110 | * bl - used for buffering data, in case a job gets greedy or you don't want to auto-flush 111 | * readable-streams - ensures consistent and stable behaviour of the underlying Transform-Stream 112 | * lodash - several utility functions 113 | 114 | 115 | ## License 116 | This library is issued under the [MIT license](./LICENSE). 117 | 118 | The Logo is from The Noun Project, created by [Michael Senkow](https://thenounproject.com/mhsenkow/) and licensed under the [CC-BY-3.0](https://creativecommons.org/licenses/by/3.0/us/). 119 | -------------------------------------------------------------------------------- /corrode.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /docs-md/configuration.md: -------------------------------------------------------------------------------- 1 | # Configuration 2 | 3 | Corrode provides some configuration-options. These are either defaults, safeguards or advanced-user-stuff. 4 | 5 | Corrode accepts an object as its first parameter, containing options. 6 | 7 | These options also get passed on to the TransformStream-constructor, so [those](https://nodejs.org/api/stream.html#stream_constructor_new_stream_writable_options) [options](https://nodejs.org/api/stream.html#stream_new_stream_readable_options) are also valid. 8 | 9 | 10 | ## `endianness` 11 | **default:** `'LE'` 12 | 13 | **accepts:** `'LE', 'BE'` (for little endian & big endian) 14 | 15 | If you use methods like `.uint8()` or `.int32()` this option determines what endianness corrode uses when reading your bytes. 16 | 17 | Of course using `.uint16be()` or `.doublele()` will overwrite this default, as you'd expect. 18 | 19 | 20 | ## `loopIdentifier` 21 | **default:** `Symbol(loop-variable)` 22 | 23 | **accepts:** Anything which can be used as an identifier in an object. 24 | 25 | Determines the identifier of the temporary variable which gets created when using `.loop()`. 26 | 27 | 28 | ## `encoding` 29 | **default:** `'utf8'` 30 | 31 | **accepts:** Any encoding `Buffer.prototype.toString` accepts. [Full list here](https://nodejs.org/api/buffer.html#buffer_buffers_and_character_encodings). 32 | 33 | Determines which encoding to use for string-functions like `.string()` or `.terminatedString()`. Can be overwitten on a per-use-basis by the functions themselves. 34 | 35 | 36 | ## `finishJobsOnEOF` 37 | **default:** `true` 38 | 39 | **accepts:** `true, false` 40 | 41 | Determines whether or not to finish everything up, once corrode encounters the end of the stream being piped into corrode, or the end of the buffer. 42 | 43 | Set to `false` if you want to parse a file which was split in two or more parts. Or somethin along those lines. 44 | 45 | What this flag does in detail is to clean up all remaining jobs from the job list, which are read-related, once EOF is reached. Then the job list will be worked on, until there are no more jobs. Meaning all VariableStack-Layers have been popped, giving you access to all that sweet data of yours. 46 | 47 | 48 | ## `anonymousLoopDiscardDeep` 49 | **default:** `false` 50 | 51 | **accepts:** `true, false` 52 | 53 | Corrode provides the `discard()` function inside `.loop()`- and `.repeat()`-callbacks. If you use those anonymously (meaning you don't push an array onto the variable-stack by giving a string as the first parameter) and then call the `discard()` function, corrode will discard whatever data you read, and restore what's been there before. 54 | 55 | By default this is done in a shallow way, meaning that corrode will clone the users data before the callback is called shallowly by using `Object.assign()` and that shallow copy gets assigned again when calling `discard()`. 56 | 57 | This may lead to problems, when modifiyng objects within the curren VariableStack-layer. Those won't get replaced with their original version. 58 | 59 | To circumvent this problem you can set this option to true. This is not done, because probably no-one needs this, and it may be a huge performance-hit to clone an entire object, everytime the loop-callback is called. 60 | 61 | 62 | ## `strictObjectMode` 63 | **default:** `true` 64 | 65 | **accepts:** `true, false` 66 | 67 | When this option is set to true, corrode will prevent you from pushing into anything that's not an object. Meaning moves like this: 68 | ```javascript 69 | parser.uint8('val').tap('val', function(){}); 70 | ``` 71 | will throw an error. This way corrode provides a naive way of type-safety. 72 | -------------------------------------------------------------------------------- /docs-md/overview.md: -------------------------------------------------------------------------------- 1 | # Overview 2 | 3 | ## Components 4 | Corrode consists of 5 components: 5 | 1. The [VariableStack](https://doc.esdoc.org/github.com/screeny05/corrode/class/src/variable-stack.js~VariableStack.html). It handles all things variables when reading data, pushing structures and looping bytes. 6 | 2. The [CorrodeBase](https://doc.esdoc.org/github.com/screeny05/corrode/class/src/base.js~CorrodeBase.html). This is the part of corrode, reading all bytes. It's the low-level part handling all the small things, preventing you from shooting yourself into the foot. Of course, it also enables you to do just that, if you explicitly ask for it. 7 | 3. The [Assertions](). Just like your assertion-library of choice these ensure you read correct data. If not it will abort mission. 8 | 4. The [Mappers](). Doing all the bulk parts of work for you, the mappers ensure your data is in the way you need it to be. Think of these like JavaScripts Array.prototype.map. Except for bits and bytes. 9 | 5. Corrode itself. This is the only thing you - as a developer - are directly in contact with. It connects The CorrodeBase with the Assertions and Mappers to provide a unified interface. 10 | 11 | For more info on each of those see the Reference. 12 | 13 | For info on ways to configure corrode to your liking, see Configuration. 14 | 15 | ## The Assertions 16 | Asserts help you to make sure, the buffer you're parsing is in the correct format. 17 | These assertions are like chai, throwing an error when an assertion doesn't hold. 18 | 19 | These functions won't modify your variables. 20 | 21 | All assertions can be called from a Corrode instance like this: 22 | ```javascript 23 | parser.assert.equal('var', 5); 24 | ``` 25 | 26 | 27 | ## The Mappers 28 | These functions provide basic mapping-abilities for Corrode's VariableStack. 29 | 30 | Imagine them like this: 31 | ```javascript 32 | const parser = new Corrode(); 33 | parser.uint8('value').map.double('value'); 34 | ``` 35 | 36 | Of course there's no existing mapping-function which doubles a value (yet?). 37 | But the concept is that they are functions receiving a value, processing it 38 | and saving a new value in the {@link VariableStack} in place of the old one. 39 | 40 | The imaginary code above would yield `{ value: 4 }`, parsing a buffer like this `[2]`. 41 | 42 | Note that all mappers don't check for existance, validity or other assumptions. 43 | You have to do that yourself with assertions. 44 | 45 | 46 | ## When will i be able to access the variables? (Extensions, Asserts & Mappers) 47 | 48 | ## Corrode#position / Corrode#skip issues (isSeeking) 49 | -------------------------------------------------------------------------------- /docs/CNAME: -------------------------------------------------------------------------------- 1 | corrode.scn.cx -------------------------------------------------------------------------------- /docs/badge.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | document 13 | document 14 | 90% 15 | 90% 16 | 17 | 18 | -------------------------------------------------------------------------------- /docs/coverage.json: -------------------------------------------------------------------------------- 1 | { 2 | "coverage": "90.83%", 3 | "expectCount": 120, 4 | "actualCount": 109, 5 | "files": { 6 | "src/assert/index.js": { 7 | "expectCount": 9, 8 | "actualCount": 9, 9 | "undocumentLines": [] 10 | }, 11 | "src/base.js": { 12 | "expectCount": 65, 13 | "actualCount": 61, 14 | "undocumentLines": [ 15 | 6, 16 | 7, 17 | 8, 18 | 9 19 | ] 20 | }, 21 | "src/index.js": { 22 | "expectCount": 18, 23 | "actualCount": 18, 24 | "undocumentLines": [] 25 | }, 26 | "src/map/index.js": { 27 | "expectCount": 10, 28 | "actualCount": 10, 29 | "undocumentLines": [] 30 | }, 31 | "src/utils/index.js": { 32 | "expectCount": 2, 33 | "actualCount": 2, 34 | "undocumentLines": [] 35 | }, 36 | "src/variable-stack.js": { 37 | "expectCount": 16, 38 | "actualCount": 9, 39 | "undocumentLines": [ 40 | 1, 41 | 2, 42 | 3, 43 | 4, 44 | 5, 45 | 64, 46 | 65 47 | ] 48 | } 49 | } 50 | } -------------------------------------------------------------------------------- /docs/css/github.css: -------------------------------------------------------------------------------- 1 | /* github markdown */ 2 | .github-markdown { 3 | font-size: 16px; 4 | } 5 | 6 | .github-markdown h1, 7 | .github-markdown h2, 8 | .github-markdown h3, 9 | .github-markdown h4, 10 | .github-markdown h5 { 11 | margin-top: 1em; 12 | margin-bottom: 16px; 13 | font-weight: bold; 14 | padding: 0; 15 | } 16 | 17 | .github-markdown h1:nth-of-type(1) { 18 | margin-top: 0; 19 | } 20 | 21 | .github-markdown h1 { 22 | font-size: 2em; 23 | padding-bottom: 0.3em; 24 | } 25 | 26 | .github-markdown h2 { 27 | font-size: 1.75em; 28 | padding-bottom: 0.3em; 29 | } 30 | 31 | .github-markdown h3 { 32 | font-size: 1.5em; 33 | } 34 | 35 | .github-markdown h4 { 36 | font-size: 1.25em; 37 | } 38 | 39 | .github-markdown h5 { 40 | font-size: 1em; 41 | } 42 | 43 | .github-markdown ul, .github-markdown ol { 44 | padding-left: 2em; 45 | } 46 | 47 | .github-markdown pre > code { 48 | font-size: 0.85em; 49 | } 50 | 51 | .github-markdown table { 52 | margin-bottom: 1em; 53 | border-collapse: collapse; 54 | border-spacing: 0; 55 | } 56 | 57 | .github-markdown table tr { 58 | background-color: #fff; 59 | border-top: 1px solid #ccc; 60 | } 61 | 62 | .github-markdown table th, 63 | .github-markdown table td { 64 | padding: 6px 13px; 65 | border: 1px solid #ddd; 66 | } 67 | 68 | .github-markdown table tr:nth-child(2n) { 69 | background-color: #f8f8f8; 70 | } 71 | 72 | .github-markdown hr { 73 | border-right: 0; 74 | border-bottom: 1px solid #e5e5e5; 75 | border-left: 0; 76 | border-top: 0; 77 | } 78 | 79 | /** badge(.svg) does not have border */ 80 | .github-markdown img:not([src*=".svg"]) { 81 | max-width: 100%; 82 | box-shadow: 1px 1px 1px rgba(0,0,0,0.5); 83 | } 84 | -------------------------------------------------------------------------------- /docs/css/identifiers.css: -------------------------------------------------------------------------------- 1 | .identifiers-wrap { 2 | display: flex; 3 | align-items: flex-start; 4 | } 5 | 6 | .identifier-dir-tree { 7 | background: #fff; 8 | border: solid 1px #ddd; 9 | border-radius: 0.25em; 10 | top: 52px; 11 | position: -webkit-sticky; 12 | position: sticky; 13 | max-height: calc(100vh - 155px); 14 | overflow-y: scroll; 15 | min-width: 200px; 16 | margin-left: 1em; 17 | } 18 | 19 | .identifier-dir-tree-header { 20 | padding: 0.5em; 21 | background-color: #fafafa; 22 | border-bottom: solid 1px #ddd; 23 | } 24 | 25 | .identifier-dir-tree-content { 26 | padding: 0 0.5em 0; 27 | } 28 | 29 | .identifier-dir-tree-content > div { 30 | padding-top: 0.25em; 31 | padding-bottom: 0.25em; 32 | } 33 | 34 | .identifier-dir-tree-content a { 35 | color: inherit; 36 | } 37 | 38 | -------------------------------------------------------------------------------- /docs/css/manual.css: -------------------------------------------------------------------------------- 1 | .github-markdown .manual-toc { 2 | padding-left: 0; 3 | } 4 | 5 | .manual-index .manual-cards { 6 | display: flex; 7 | flex-wrap: wrap; 8 | } 9 | 10 | .manual-index .manual-card-wrap { 11 | width: 280px; 12 | padding: 10px 20px 10px 0; 13 | box-sizing: border-box; 14 | } 15 | 16 | .manual-index .manual-card-wrap > h1 { 17 | margin: 0; 18 | font-size: 1em; 19 | font-weight: 600; 20 | padding: 0.2em 0 0.2em 0.5em; 21 | border-radius: 0.1em 0.1em 0 0; 22 | border: none; 23 | } 24 | 25 | .manual-index .manual-card-wrap > h1 span { 26 | color: #555; 27 | } 28 | 29 | .manual-index .manual-card { 30 | height: 200px; 31 | overflow: hidden; 32 | border: solid 1px rgba(230, 230, 230, 0.84); 33 | border-radius: 0 0 0.1em 0.1em; 34 | padding: 8px; 35 | position: relative; 36 | } 37 | 38 | .manual-index .manual-card > div { 39 | transform: scale(0.4); 40 | transform-origin: 0 0; 41 | width: 250%; 42 | } 43 | 44 | .manual-index .manual-card > a { 45 | position: absolute; 46 | top: 0; 47 | left: 0; 48 | width: 100%; 49 | height: 100%; 50 | background: rgba(210, 210, 210, 0.1); 51 | } 52 | 53 | .manual-index .manual-card > a:hover { 54 | background: none; 55 | } 56 | 57 | .manual-index .manual-badge { 58 | margin: 0; 59 | } 60 | 61 | .manual-index .manual-user-index { 62 | margin-bottom: 1em; 63 | border-bottom: solid 1px #ddd; 64 | } 65 | 66 | .manual-root .navigation { 67 | padding-left: 4px; 68 | margin-top: 4px; 69 | } 70 | 71 | .navigation .manual-toc-root > div { 72 | padding-left: 0.25em; 73 | padding-right: 0.75em; 74 | } 75 | 76 | .github-markdown .manual-toc-title a { 77 | color: inherit; 78 | } 79 | 80 | .manual-breadcrumb-list { 81 | font-size: 0.8em; 82 | margin-bottom: 1em; 83 | } 84 | 85 | .manual-toc-title a:hover { 86 | color: #039BE5; 87 | } 88 | 89 | .manual-toc li { 90 | margin: 0.75em 0; 91 | list-style-type: none; 92 | } 93 | 94 | .navigation .manual-toc [class^="indent-h"] a { 95 | color: #666; 96 | } 97 | 98 | .navigation .manual-toc .indent-h1 a { 99 | color: #555; 100 | font-weight: 600; 101 | display: block; 102 | } 103 | 104 | .manual-toc .indent-h1 { 105 | display: block; 106 | margin: 0.4em 0 0 0.25em; 107 | padding: 0.2em 0 0.2em 0.5em; 108 | border-radius: 0.1em; 109 | } 110 | 111 | .manual-root .navigation .manual-toc li:not(.indent-h1) { 112 | margin-top: 0.5em; 113 | } 114 | 115 | .manual-toc .indent-h2 { 116 | display: none; 117 | margin-left: 1.5em; 118 | } 119 | .manual-toc .indent-h3 { 120 | display: none; 121 | margin-left: 2.5em; 122 | } 123 | .manual-toc .indent-h4 { 124 | display: none; 125 | margin-left: 3.5em; 126 | } 127 | .manual-toc .indent-h5 { 128 | display: none; 129 | margin-left: 4.5em; 130 | } 131 | 132 | .manual-nav li { 133 | margin: 0.75em 0; 134 | } 135 | -------------------------------------------------------------------------------- /docs/css/prettify-tomorrow.css: -------------------------------------------------------------------------------- 1 | /* Tomorrow Theme */ 2 | /* Original theme - https://github.com/chriskempson/tomorrow-theme */ 3 | /* Pretty printing styles. Used with prettify.js. */ 4 | /* SPAN elements with the classes below are added by prettyprint. */ 5 | /* plain text */ 6 | .pln { 7 | color: #4d4d4c; } 8 | 9 | @media screen { 10 | /* string content */ 11 | .str { 12 | color: #718c00; } 13 | 14 | /* a keyword */ 15 | .kwd { 16 | color: #8959a8; } 17 | 18 | /* a comment */ 19 | .com { 20 | color: #8e908c; } 21 | 22 | /* a type name */ 23 | .typ { 24 | color: #4271ae; } 25 | 26 | /* a literal value */ 27 | .lit { 28 | color: #f5871f; } 29 | 30 | /* punctuation */ 31 | .pun { 32 | color: #4d4d4c; } 33 | 34 | /* lisp open bracket */ 35 | .opn { 36 | color: #4d4d4c; } 37 | 38 | /* lisp close bracket */ 39 | .clo { 40 | color: #4d4d4c; } 41 | 42 | /* a markup tag name */ 43 | .tag { 44 | color: #c82829; } 45 | 46 | /* a markup attribute name */ 47 | .atn { 48 | color: #f5871f; } 49 | 50 | /* a markup attribute value */ 51 | .atv { 52 | color: #3e999f; } 53 | 54 | /* a declaration */ 55 | .dec { 56 | color: #f5871f; } 57 | 58 | /* a variable name */ 59 | .var { 60 | color: #c82829; } 61 | 62 | /* a function name */ 63 | .fun { 64 | color: #4271ae; } } 65 | /* Use higher contrast and text-weight for printable form. */ 66 | @media print, projection { 67 | .str { 68 | color: #060; } 69 | 70 | .kwd { 71 | color: #006; 72 | font-weight: bold; } 73 | 74 | .com { 75 | color: #600; 76 | font-style: italic; } 77 | 78 | .typ { 79 | color: #404; 80 | font-weight: bold; } 81 | 82 | .lit { 83 | color: #044; } 84 | 85 | .pun, .opn, .clo { 86 | color: #440; } 87 | 88 | .tag { 89 | color: #006; 90 | font-weight: bold; } 91 | 92 | .atn { 93 | color: #404; } 94 | 95 | .atv { 96 | color: #060; } } 97 | /* Style */ 98 | /* 99 | pre.prettyprint { 100 | background: white; 101 | font-family: Consolas, Monaco, 'Andale Mono', monospace; 102 | font-size: 12px; 103 | line-height: 1.5; 104 | border: 1px solid #ccc; 105 | padding: 10px; } 106 | */ 107 | 108 | /* Specify class=linenums on a pre to get line numbering */ 109 | ol.linenums { 110 | margin-top: 0; 111 | margin-bottom: 0; } 112 | 113 | /* IE indents via margin-left */ 114 | li.L0, 115 | li.L1, 116 | li.L2, 117 | li.L3, 118 | li.L4, 119 | li.L5, 120 | li.L6, 121 | li.L7, 122 | li.L8, 123 | li.L9 { 124 | /* */ } 125 | 126 | /* Alternate shading for lines */ 127 | li.L1, 128 | li.L3, 129 | li.L5, 130 | li.L7, 131 | li.L9 { 132 | /* */ } 133 | -------------------------------------------------------------------------------- /docs/css/search.css: -------------------------------------------------------------------------------- 1 | /* search box */ 2 | .search-box { 3 | position: absolute; 4 | top: 10px; 5 | right: 50px; 6 | padding-right: 8px; 7 | padding-bottom: 10px; 8 | line-height: normal; 9 | font-size: 12px; 10 | } 11 | 12 | .search-box img { 13 | width: 20px; 14 | vertical-align: top; 15 | } 16 | 17 | .search-input { 18 | display: inline; 19 | visibility: hidden; 20 | width: 0; 21 | padding: 2px; 22 | height: 1.5em; 23 | outline: none; 24 | background: transparent; 25 | border: 1px #0af; 26 | border-style: none none solid none; 27 | vertical-align: bottom; 28 | } 29 | 30 | .search-input-edge { 31 | display: none; 32 | width: 1px; 33 | height: 5px; 34 | background-color: #0af; 35 | vertical-align: bottom; 36 | } 37 | 38 | .search-result { 39 | position: absolute; 40 | display: none; 41 | height: 600px; 42 | width: 100%; 43 | padding: 0; 44 | margin-top: 5px; 45 | margin-left: 24px; 46 | background: white; 47 | box-shadow: 1px 1px 4px rgb(0,0,0); 48 | white-space: nowrap; 49 | overflow-y: scroll; 50 | } 51 | 52 | .search-result-import-path { 53 | color: #aaa; 54 | font-size: 12px; 55 | } 56 | 57 | .search-result li { 58 | list-style: none; 59 | padding: 2px 4px; 60 | } 61 | 62 | .search-result li a { 63 | display: block; 64 | } 65 | 66 | .search-result li.selected { 67 | background: #ddd; 68 | } 69 | 70 | .search-result li.search-separator { 71 | background: rgb(37, 138, 175); 72 | color: white; 73 | } 74 | 75 | .search-box.active .search-input { 76 | visibility: visible; 77 | transition: width 0.2s ease-out; 78 | width: 300px; 79 | } 80 | 81 | .search-box.active .search-input-edge { 82 | display: inline-block; 83 | } 84 | 85 | -------------------------------------------------------------------------------- /docs/css/source.css: -------------------------------------------------------------------------------- 1 | table.files-summary { 2 | width: 100%; 3 | margin: 10px 0; 4 | border-spacing: 0; 5 | border: 0; 6 | border-collapse: collapse; 7 | text-align: right; 8 | } 9 | 10 | table.files-summary tbody tr:hover { 11 | background: #eee; 12 | } 13 | 14 | table.files-summary td:first-child, 15 | table.files-summary td:nth-of-type(2) { 16 | text-align: left; 17 | } 18 | 19 | table.files-summary[data-use-coverage="false"] td.coverage { 20 | display: none; 21 | } 22 | 23 | table.files-summary thead { 24 | background: #fafafa; 25 | } 26 | 27 | table.files-summary td { 28 | border: solid 1px #ddd; 29 | padding: 4px 10px; 30 | vertical-align: top; 31 | } 32 | 33 | table.files-summary td.identifiers > span { 34 | display: block; 35 | margin-top: 4px; 36 | } 37 | table.files-summary td.identifiers > span:first-child { 38 | margin-top: 0; 39 | } 40 | 41 | table.files-summary .coverage-count { 42 | font-size: 12px; 43 | color: #aaa; 44 | display: inline-block; 45 | min-width: 40px; 46 | } 47 | 48 | .total-coverage-count { 49 | position: relative; 50 | bottom: 2px; 51 | font-size: 12px; 52 | color: #666; 53 | font-weight: 500; 54 | padding-left: 5px; 55 | } 56 | -------------------------------------------------------------------------------- /docs/css/test.css: -------------------------------------------------------------------------------- 1 | table.test-summary thead { 2 | background: #fafafa; 3 | } 4 | 5 | table.test-summary thead .test-description { 6 | width: 50%; 7 | } 8 | 9 | table.test-summary { 10 | width: 100%; 11 | margin: 10px 0; 12 | border-spacing: 0; 13 | border: 0; 14 | border-collapse: collapse; 15 | } 16 | 17 | table.test-summary thead .test-count { 18 | width: 3em; 19 | } 20 | 21 | table.test-summary tbody tr:hover { 22 | background-color: #eee; 23 | } 24 | 25 | table.test-summary td { 26 | border: solid 1px #ddd; 27 | padding: 4px 10px; 28 | vertical-align: top; 29 | } 30 | 31 | table.test-summary td p { 32 | margin: 0; 33 | } 34 | 35 | table.test-summary tr.test-interface .toggle { 36 | display: inline-block; 37 | float: left; 38 | margin-right: 4px; 39 | cursor: pointer; 40 | font-size: 0.8em; 41 | padding-top: 0.25em; 42 | } 43 | 44 | table.test-summary tr.test-interface .toggle.opened:before { 45 | content: '▼'; 46 | } 47 | 48 | table.test-summary tr.test-interface .toggle.closed:before { 49 | content: '▶'; 50 | } 51 | 52 | table.test-summary .test-target > span { 53 | display: block; 54 | margin-top: 4px; 55 | } 56 | table.test-summary .test-target > span:first-child { 57 | margin-top: 0; 58 | } 59 | -------------------------------------------------------------------------------- /docs/file/src/utils/index.js.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | src/utils/index.js | corrode 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | Home 16 | Manual 17 | Reference 18 | Source 19 | Test 20 | 27 |
28 | 29 | 58 | 59 |

src/utils/index.js

60 |
import { mapValues } from 'lodash';
 61 | 
 62 | /**
 63 |  * bind each function in an object with a tap to a given context
 64 |  * @param  {object} obj object with functions
 65 |  * @param  {object} ctx context
 66 |  * @return {object}     copy of object with each function wrapped in a tap
 67 |  */
 68 | export function tapBindObject(obj, ctx){
 69 |     return mapValues(obj, fn => typeof fn === 'function' ? function(...args){
 70 |         return ctx.tap(fn.bind(ctx, ...args));
 71 |     } : fn);
 72 | }
 73 | 
 74 | /**
 75 |  * bind each function in an object to a given context
 76 |  * @param  {object} obj object with functions
 77 |  * @param  {object} ctx context
 78 |  * @return {object}     copy of object obj with each function bound to ctx
 79 |  */
 80 | export function bindObject(obj, ctx){
 81 |     return mapValues(obj, fn => typeof fn === 'function' ? fn.bind(ctx) : fn);
 82 | }
 83 | 
84 | 85 |
86 | 87 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | -------------------------------------------------------------------------------- /docs/image/badge.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | document 13 | document 14 | @ratio@ 15 | @ratio@ 16 | 17 | 18 | -------------------------------------------------------------------------------- /docs/image/esdoc-logo-mini-black.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/screeny05/corrode/8ef6322ba5b2daee2c90b7f3799a018ed5cf9507/docs/image/esdoc-logo-mini-black.png -------------------------------------------------------------------------------- /docs/image/esdoc-logo-mini.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/screeny05/corrode/8ef6322ba5b2daee2c90b7f3799a018ed5cf9507/docs/image/esdoc-logo-mini.png -------------------------------------------------------------------------------- /docs/image/github.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/screeny05/corrode/8ef6322ba5b2daee2c90b7f3799a018ed5cf9507/docs/image/github.png -------------------------------------------------------------------------------- /docs/image/manual-badge.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | manual 13 | manual 14 | @value@ 15 | @value@ 16 | 17 | 18 | -------------------------------------------------------------------------------- /docs/image/search.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/screeny05/corrode/8ef6322ba5b2daee2c90b7f3799a018ed5cf9507/docs/image/search.png -------------------------------------------------------------------------------- /docs/lint.json: -------------------------------------------------------------------------------- 1 | [] -------------------------------------------------------------------------------- /docs/manual/CHANGELOG.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Manual | corrode 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | Home 16 | Manual 17 | Reference 18 | Source 19 | Test 20 | 27 |
28 | 29 | 86 | 87 |

Changelog

corrode v1.0.2

90 |

corrode v1.0.1

95 |

corrode v1.0.0

98 |
99 |
100 | 101 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | -------------------------------------------------------------------------------- /docs/script/inherited-summary.js: -------------------------------------------------------------------------------- 1 | (function(){ 2 | function toggle(ev) { 3 | var button = ev.target; 4 | var parent = ev.target.parentElement; 5 | while(parent) { 6 | if (parent.tagName === 'TABLE' && parent.classList.contains('summary')) break; 7 | parent = parent.parentElement; 8 | } 9 | 10 | if (!parent) return; 11 | 12 | var tbody = parent.querySelector('tbody'); 13 | if (button.classList.contains('opened')) { 14 | button.classList.remove('opened'); 15 | button.classList.add('closed'); 16 | tbody.style.display = 'none'; 17 | } else { 18 | button.classList.remove('closed'); 19 | button.classList.add('opened'); 20 | tbody.style.display = 'block'; 21 | } 22 | } 23 | 24 | var buttons = document.querySelectorAll('.inherited-summary thead .toggle'); 25 | for (var i = 0; i < buttons.length; i++) { 26 | buttons[i].addEventListener('click', toggle); 27 | } 28 | })(); 29 | -------------------------------------------------------------------------------- /docs/script/inner-link.js: -------------------------------------------------------------------------------- 1 | // inner link(#foo) can not correctly scroll, because page has fixed header, 2 | // so, I manually scroll. 3 | (function(){ 4 | var matched = location.hash.match(/errorLines=([\d,]+)/); 5 | if (matched) return; 6 | 7 | function adjust() { 8 | window.scrollBy(0, -55); 9 | var el = document.querySelector('.inner-link-active'); 10 | if (el) el.classList.remove('inner-link-active'); 11 | 12 | // ``[ ] . ' " @`` are not valid in DOM id. so must escape these. 13 | var id = location.hash.replace(/([\[\].'"@$])/g, '\\$1'); 14 | var el = document.querySelector(id); 15 | if (el) el.classList.add('inner-link-active'); 16 | } 17 | 18 | window.addEventListener('hashchange', adjust); 19 | 20 | if (location.hash) { 21 | setTimeout(adjust, 0); 22 | } 23 | })(); 24 | 25 | (function(){ 26 | var els = document.querySelectorAll('[href^="#"]'); 27 | var href = location.href.replace(/#.*$/, ''); // remove existed hash 28 | for (var i = 0; i < els.length; i++) { 29 | var el = els[i]; 30 | el.href = href + el.getAttribute('href'); // because el.href is absolute path 31 | } 32 | })(); 33 | -------------------------------------------------------------------------------- /docs/script/manual.js: -------------------------------------------------------------------------------- 1 | (function(){ 2 | var matched = location.pathname.match(/\/(manual\/.*\.html)$/); 3 | if (!matched) return; 4 | 5 | var currentName = matched[1]; 6 | var cssClass = '.navigation .manual-toc li[data-link="' + currentName + '"]'; 7 | var styleText = cssClass + '{ display: block; }\n'; 8 | styleText += cssClass + '.indent-h1 a { color: #039BE5 }'; 9 | var style = document.createElement('style'); 10 | style.textContent = styleText; 11 | document.querySelector('head').appendChild(style); 12 | })(); 13 | -------------------------------------------------------------------------------- /docs/script/patch-for-local.js: -------------------------------------------------------------------------------- 1 | (function(){ 2 | if (location.protocol === 'file:') { 3 | var elms = document.querySelectorAll('a[href="./"]'); 4 | for (var i = 0; i < elms.length; i++) { 5 | elms[i].href = './index.html'; 6 | } 7 | } 8 | })(); 9 | -------------------------------------------------------------------------------- /docs/script/pretty-print.js: -------------------------------------------------------------------------------- 1 | (function(){ 2 | prettyPrint(); 3 | var lines = document.querySelectorAll('.prettyprint.linenums li[class^="L"]'); 4 | for (var i = 0; i < lines.length; i++) { 5 | lines[i].id = 'lineNumber' + (i + 1); 6 | } 7 | 8 | var matched = location.hash.match(/errorLines=([\d,]+)/); 9 | if (matched) { 10 | var lines = matched[1].split(','); 11 | for (var i = 0; i < lines.length; i++) { 12 | var id = '#lineNumber' + lines[i]; 13 | var el = document.querySelector(id); 14 | el.classList.add('error-line'); 15 | } 16 | return; 17 | } 18 | 19 | if (location.hash) { 20 | // ``[ ] . ' " @`` are not valid in DOM id. so must escape these. 21 | var id = location.hash.replace(/([\[\].'"@$])/g, '\\$1'); 22 | var line = document.querySelector(id); 23 | if (line) line.classList.add('active'); 24 | } 25 | })(); 26 | -------------------------------------------------------------------------------- /docs/script/search.js: -------------------------------------------------------------------------------- 1 | (function(){ 2 | var searchIndex = window.esdocSearchIndex; 3 | var searchBox = document.querySelector('.search-box'); 4 | var input = document.querySelector('.search-input'); 5 | var result = document.querySelector('.search-result'); 6 | var selectedIndex = -1; 7 | var prevText; 8 | 9 | // active search box and focus when mouse enter on search box. 10 | searchBox.addEventListener('mouseenter', function(){ 11 | searchBox.classList.add('active'); 12 | input.focus(); 13 | }); 14 | 15 | // search with text when key is upped. 16 | input.addEventListener('keyup', function(ev){ 17 | var text = ev.target.value.toLowerCase(); 18 | if (!text) { 19 | result.style.display = 'none'; 20 | result.innerHTML = ''; 21 | return; 22 | } 23 | 24 | if (text === prevText) return; 25 | prevText = text; 26 | 27 | var html = {class: [], method: [], member: [], function: [], variable: [], typedef: [], external: [], file: [], test: [], testFile: []}; 28 | var len = searchIndex.length; 29 | var kind; 30 | for (var i = 0; i < len; i++) { 31 | var pair = searchIndex[i]; 32 | if (pair[0].indexOf(text) !== -1) { 33 | kind = pair[3]; 34 | html[kind].push('
  • ' + pair[2] + '
  • '); 35 | } 36 | } 37 | 38 | var innerHTML = ''; 39 | for (kind in html) { 40 | var list = html[kind]; 41 | if (!list.length) continue; 42 | innerHTML += '
  • ' + kind + '
  • \n' + list.join('\n'); 43 | } 44 | result.innerHTML = innerHTML; 45 | if (innerHTML) result.style.display = 'block'; 46 | selectedIndex = -1; 47 | }); 48 | 49 | // down, up and enter key are pressed, select search result. 50 | input.addEventListener('keydown', function(ev){ 51 | if (ev.keyCode === 40) { 52 | // arrow down 53 | var current = result.children[selectedIndex]; 54 | var selected = result.children[selectedIndex + 1]; 55 | if (selected && selected.classList.contains('search-separator')) { 56 | var selected = result.children[selectedIndex + 2]; 57 | selectedIndex++; 58 | } 59 | 60 | if (selected) { 61 | if (current) current.classList.remove('selected'); 62 | selectedIndex++; 63 | selected.classList.add('selected'); 64 | } 65 | } else if (ev.keyCode === 38) { 66 | // arrow up 67 | var current = result.children[selectedIndex]; 68 | var selected = result.children[selectedIndex - 1]; 69 | if (selected && selected.classList.contains('search-separator')) { 70 | var selected = result.children[selectedIndex - 2]; 71 | selectedIndex--; 72 | } 73 | 74 | if (selected) { 75 | if (current) current.classList.remove('selected'); 76 | selectedIndex--; 77 | selected.classList.add('selected'); 78 | } 79 | } else if (ev.keyCode === 13) { 80 | // enter 81 | var current = result.children[selectedIndex]; 82 | if (current) { 83 | var link = current.querySelector('a'); 84 | if (link) location.href = link.href; 85 | } 86 | } else { 87 | return; 88 | } 89 | 90 | ev.preventDefault(); 91 | }); 92 | 93 | // select search result when search result is mouse over. 94 | result.addEventListener('mousemove', function(ev){ 95 | var current = result.children[selectedIndex]; 96 | if (current) current.classList.remove('selected'); 97 | 98 | var li = ev.target; 99 | while (li) { 100 | if (li.nodeName === 'LI') break; 101 | li = li.parentElement; 102 | } 103 | 104 | if (li) { 105 | selectedIndex = Array.prototype.indexOf.call(result.children, li); 106 | li.classList.add('selected'); 107 | } 108 | }); 109 | 110 | // clear search result when body is clicked. 111 | document.body.addEventListener('click', function(ev){ 112 | selectedIndex = -1; 113 | result.style.display = 'none'; 114 | result.innerHTML = ''; 115 | }); 116 | 117 | })(); 118 | -------------------------------------------------------------------------------- /docs/script/test-summary.js: -------------------------------------------------------------------------------- 1 | (function(){ 2 | function toggle(ev) { 3 | var button = ev.target; 4 | var parent = ev.target.parentElement; 5 | while(parent) { 6 | if (parent.tagName === 'TR' && parent.classList.contains('test-interface')) break; 7 | parent = parent.parentElement; 8 | } 9 | 10 | if (!parent) return; 11 | 12 | var direction; 13 | if (button.classList.contains('opened')) { 14 | button.classList.remove('opened'); 15 | button.classList.add('closed'); 16 | direction = 'closed'; 17 | } else { 18 | button.classList.remove('closed'); 19 | button.classList.add('opened'); 20 | direction = 'opened'; 21 | } 22 | 23 | var targetDepth = parseInt(parent.dataset.testDepth, 10) + 1; 24 | var nextElement = parent.nextElementSibling; 25 | while (nextElement) { 26 | var depth = parseInt(nextElement.dataset.testDepth, 10); 27 | if (depth >= targetDepth) { 28 | if (direction === 'opened') { 29 | if (depth === targetDepth) nextElement.style.display = ''; 30 | } else if (direction === 'closed') { 31 | nextElement.style.display = 'none'; 32 | var innerButton = nextElement.querySelector('.toggle'); 33 | if (innerButton && innerButton.classList.contains('opened')) { 34 | innerButton.classList.remove('opened'); 35 | innerButton.classList.add('closed'); 36 | } 37 | } 38 | } else { 39 | break; 40 | } 41 | nextElement = nextElement.nextElementSibling; 42 | } 43 | } 44 | 45 | var buttons = document.querySelectorAll('.test-summary tr.test-interface .toggle'); 46 | for (var i = 0; i < buttons.length; i++) { 47 | buttons[i].addEventListener('click', toggle); 48 | } 49 | 50 | var topDescribes = document.querySelectorAll('.test-summary tr[data-test-depth="0"]'); 51 | for (var i = 0; i < topDescribes.length; i++) { 52 | topDescribes[i].style.display = ''; 53 | } 54 | })(); 55 | -------------------------------------------------------------------------------- /docs/test-file/test/base-aborts.test.js.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | test/base-aborts.test.js | corrode 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 |
    15 | Home 16 | Manual 17 | Reference 18 | Source 19 | Test 20 | 27 |
    28 | 29 | 58 | 59 |

    test/base-aborts.test.js

    60 |
    const { expect } = require('chai');
     61 | const Base = require('../src/base');
     62 | 
     63 | /** @test {CorrodeBase#jobLoop} */
     64 | describe('CorrodeBase - Aborts', () => {
     65 |     beforeEach(function(){
     66 |         this.base = new Base();
     67 |         this.eqArray = require('./helpers/asserts').eqArray.bind(this);
     68 |     });
     69 | 
     70 |     it('correctly aborts too short int16', function(done){
     71 |         this.base.int16('val');
     72 |         this.eqArray([1], done, {});
     73 |     });
     74 | 
     75 |     it('correctly aborts too short int32', function(done){
     76 |         this.base.int32('val');
     77 |         this.eqArray([1, 2, 3], done, {});
     78 |     });
     79 | 
     80 |     it('correctly aborts too short int64', function(done){
     81 |         this.base.int64('val');
     82 |         this.eqArray([1, 2, 3, 4, 5, 6, 7], done, {});
     83 |     });
     84 | 
     85 |     it('correctly aborts too short float', function(done){
     86 |         this.base.float('val');
     87 |         this.eqArray([1, 2, 3], done, {});
     88 |     });
     89 | 
     90 |     it('correctly aborts too short double', function(done){
     91 |         this.base.double('val');
     92 |         this.eqArray([1, 2, 3, 4, 5, 6, 7], done, {});
     93 |     });
     94 | 
     95 |     it('correctly aborts too short string', function(done){
     96 |         this.base.string('foo', 2);
     97 |         this.eqArray([1], done, {});
     98 |     });
     99 | 
    100 |     it('correctly aborts too short buffer', function(done){
    101 |         this.base.buffer('foo', 2);
    102 |         this.eqArray([1], done, {});
    103 |     });
    104 | 
    105 |     it('correctly aborts too short skip', function(done){
    106 |         this.base
    107 |             .uint8('var_1')
    108 |             .skip(10)
    109 |             .uint8('var_2');
    110 | 
    111 |         this.eqArray([2, 0], done, {
    112 |             var_1: 2
    113 |         });
    114 |     });
    115 | });
    116 | 
    117 | 118 |
    119 | 120 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | -------------------------------------------------------------------------------- /docs/test-file/test/base-skip.test.js.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | test/base-skip.test.js | corrode 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 |
    15 | Home 16 | Manual 17 | Reference 18 | Source 19 | Test 20 | 27 |
    28 | 29 | 58 | 59 |

    test/base-skip.test.js

    60 |
    const { expect } = require('chai');
     61 | const Base = require('../src/base');
     62 | 
     63 | /** @test {CorrodeBase#skip} */
     64 | describe('CorrodeBase#skip', () => {
     65 |     beforeEach(function(){
     66 |         this.base = new Base();
     67 |         this.eqArray = require('./helpers/asserts').eqArray.bind(this);
     68 |         this.eqMultiArray = require('./helpers/asserts').eqMultiArray.bind(this);
     69 |     });
     70 | 
     71 |     it('allows us to skip content', function(done){
     72 |         this.base
     73 |             .uint8('var_1')
     74 |             .skip(2)
     75 |             .uint8('var_2')
     76 |             .skip(4)
     77 |             .uint8('var_3');
     78 | 
     79 |         this.eqArray([1, 0, 0, 2, 0, 0, 0, 0, 3], done, {
     80 |             var_1: 1,
     81 |             var_2: 2,
     82 |             var_3: 3
     83 |         });
     84 |     });
     85 | 
     86 |     /** @test {CorrodeBase#isSeeking} */
     87 |     it('prevents us from unskipping content with isSeeking = false', function(done){
     88 |         this.base
     89 |             .uint8('var_1')
     90 |             .skip(2)
     91 |             .uint8('var_2')
     92 |             .skip(-3)
     93 |             .uint8('var_3');
     94 | 
     95 |         expect(this.eqMultiArray.bind(this, [[1], [3], [0], [2]], done, {})).to.throw(RangeError);
     96 | 
     97 |         done();
     98 |     });
     99 | 
    100 |     /** @test {CorrodeBase#isSeeking} */
    101 |     it('allows us to unskip content with isSeeking = true', function(done){
    102 |         this.base.isSeeking = true;
    103 | 
    104 |         this.base
    105 |             .uint8('var_1')
    106 |             .skip(2)
    107 |             .uint8('var_2')
    108 |             .skip(-3)
    109 |             .uint8('var_3');
    110 | 
    111 |         this.eqArray([1, 3, 0, 2], done, {
    112 |             var_1: 1,
    113 |             var_2: 2,
    114 |             var_3: 3
    115 |         });
    116 |     });
    117 | 
    118 |     it('prevents us from unskipping too far', function(){
    119 |         this.base
    120 |             .uint8('var_1')
    121 |             .skip(-10);
    122 | 
    123 |         expect(this.eqArray.bind(this, [1, 2], () => {}, {})).to.throw(RangeError);
    124 |     });
    125 | });
    126 | 
    127 | 128 |
    129 | 130 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | -------------------------------------------------------------------------------- /docs/test-file/test/corrode-helper.test.js.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | test/corrode-helper.test.js | corrode 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 |
    15 | Home 16 | Manual 17 | Reference 18 | Source 19 | Test 20 | 27 |
    28 | 29 | 58 | 59 |

    test/corrode-helper.test.js

    60 |
    const { expect } = require('chai');
     61 | const Corrode = require('../src');
     62 | 
     63 | /** @test {Corrode} */
     64 | describe('Corrode - Helpers', () => {
     65 |     beforeEach(function(){
     66 |         this.base = new Corrode();
     67 |         this.eqArray = require('./helpers/asserts').eqArray.bind(this);
     68 |     });
     69 | 
     70 |     /**
     71 |      * coverage fix
     72 |      * @test {Corrode#debug}
     73 |      */
     74 |     it('debugs', function(done){
     75 |         let output = [];
     76 |         const orgConsoleLog = console.log;
     77 |         console.log = (...strings) => output = strings;
     78 | 
     79 |         this.base
     80 |             .loop('array', function(end, discard, i){
     81 |                 this
     82 |                     .uint8('values')
     83 |                     .map.push();
     84 |             })
     85 |             .debug();
     86 | 
     87 |         this.eqArray([3, 5, 7], function(){
     88 |             expect(output).to.deep.equal([
     89 |                 '{ array: [ 3, 5, 7 ] }'
     90 |             ]);
     91 |             console.log = orgConsoleLog;
     92 |             done();
     93 |         }, {
     94 |             array: [3, 5, 7],
     95 |         });
     96 |     });
     97 | 
     98 |     /** @test {Corrode#fromBuffer} */
     99 |     it('converts from buffer', function(){
    100 |         this.base
    101 |             .loop('array', function(end, discard, i){
    102 |                 this
    103 |                     .uint8('values')
    104 |                     .map.push();
    105 |             })
    106 |             .fromBuffer(Buffer.from([0, 1, 2, 3, 4, 5]), vars => {
    107 |                 expect(vars).to.deep.equal({
    108 |                     array: [0, 1, 2, 3, 4, 5]
    109 |                 });
    110 |             });
    111 |     });
    112 | });
    113 | 
    114 | 115 |
    116 | 117 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | -------------------------------------------------------------------------------- /docs/test-file/test/corrode-pointer.test.js.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | test/corrode-pointer.test.js | corrode 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 |
    15 | Home 16 | Manual 17 | Reference 18 | Source 19 | Test 20 | 27 |
    28 | 29 | 58 | 59 |

    test/corrode-pointer.test.js

    60 |
    const { expect } = require('chai');
     61 | const Corrode = require('../src');
     62 | 
     63 | /** @test {Corrode#pointer} */
     64 | describe('Corrode#pointer', () => {
     65 |     beforeEach(function(){
     66 |         this.base = new Corrode();
     67 |         this.eqArray = require('./helpers/asserts').eqArray.bind(this);
     68 |     });
     69 | 
     70 |     it('retrieves from array', function(done){
     71 |         this.base
     72 |             .loop('array', function(end, discard, i){
     73 |                 if(i >= 3){
     74 |                     return end();
     75 |                 }
     76 |                 this
     77 |                     .uint8('values')
     78 |                     .map.push();
     79 |             })
     80 |             .pointer('alphabet', ['a', 'b', 'c'], 'uint8')
     81 |             .pointer('numeric', 'array', 'uint8');
     82 | 
     83 |         this.eqArray([3, 5, 7, 1, 2], done, {
     84 |             array: [3, 5, 7],
     85 |             alphabet: 'b',
     86 |             numeric: 7
     87 |         });
     88 |     });
     89 | 
     90 |     it('retrieves from object', function(done){
     91 |         this.base
     92 |             .tap('obj', function(){
     93 |                 this.loop(function(end, discard, i){
     94 |                     if(i >= 3){
     95 |                         return end();
     96 |                     }
     97 |                     this.uint8(i);
     98 |                 });
     99 |             })
    100 |             .pointer('alphabet', { 0: 'a', 1: 'b', 2: 'c' }, 'uint8')
    101 |             .pointer('numeric', 'obj', 'uint8');
    102 | 
    103 |         this.eqArray([3, 5, 7, 1, 2], done, {
    104 |             obj: { 0: 3, 1: 5, 2: 7},
    105 |             alphabet: 'b',
    106 |             numeric: 7
    107 |         });
    108 |     });
    109 | 
    110 |     // why not?
    111 |     it('retrieves from string', function(done){
    112 |         this.base
    113 |             .terminatedString('string')
    114 |             .pointer('numeric', 'string', 'uint8');
    115 | 
    116 |         this.eqArray([0x68, 0x65, 0x6c, 0x6c, 0x6f, 0, 4], done, {
    117 |             string: 'hello',
    118 |             numeric: 'o'
    119 |         });
    120 |     });
    121 | });
    122 | 
    123 | 124 |
    125 | 126 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | -------------------------------------------------------------------------------- /docs/test-file/test/corrode-position.test.js.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | test/corrode-position.test.js | corrode 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 |
    15 | Home 16 | Manual 17 | Reference 18 | Source 19 | Test 20 | 27 |
    28 | 29 | 58 | 59 |

    test/corrode-position.test.js

    60 |
    const { expect } = require('chai');
     61 | const Corrode = require('../src');
     62 | 
     63 | /** @test {Corrode#position} */
     64 | describe('Corrode#position', () => {
     65 |     beforeEach(function(){
     66 |         this.base = new Corrode();
     67 |         this.eqArray = require('./helpers/asserts').eqArray.bind(this);
     68 |     });
     69 | 
     70 |     it('jumps to absolute positions', function(done){
     71 |         this.base.isSeeking = true;
     72 | 
     73 |         this.base
     74 |             .uint8('var_1')
     75 |             .position(0)
     76 |             .uint8('var_2')
     77 |             .position(3)
     78 |             .uint8('var_3')
     79 |             .position('var_3')
     80 |             .uint8('var_4')
     81 | 
     82 |         this.eqArray([0, 1, 2, 5, 4, 3, 7], done, {
     83 |             var_1: 0,
     84 |             var_2: 0,
     85 |             var_3: 5,
     86 |             var_4: 3
     87 |         });
     88 |     });
     89 | 
     90 |     it('prevents jumps to invalid negative positions', function(){
     91 |         this.base.isSeeking = true;
     92 | 
     93 |         this.base.position(-1);
     94 | 
     95 |         expect(this.eqArray.bind(this, [0], () => {}, {})).to.throw(RangeError);
     96 |     });
     97 | 
     98 |     it('allows jumps to future positive positions', function(done){
     99 |         this.base.isSeeking = true;
    100 | 
    101 |         this.base
    102 |             .uint8('var_1')
    103 |             .position(10)
    104 |             .uint8('var_2');
    105 | 
    106 |         this.eqArray([0, 1, 2, 3, 4, 5], done, {
    107 |             var_1: 0
    108 |         });
    109 |     });
    110 | });
    111 | 
    112 | 113 |
    114 | 115 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | -------------------------------------------------------------------------------- /docs/test-file/test/utils.test.js.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | test/utils.test.js | corrode 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 |
    15 | Home 16 | Manual 17 | Reference 18 | Source 19 | Test 20 | 27 |
    28 | 29 | 58 | 59 |

    test/utils.test.js

    60 |
    const expect = require('chai').expect;
     61 | const utils = require('../src/utils');
     62 | 
     63 | describe('Utils', () => {
     64 |     beforeEach(function(){
     65 |         this.context = {
     66 |             fixture: 'fixture'
     67 |         };
     68 |     });
     69 | 
     70 |     /** @test {tapBindObject} */
     71 |     it('binds object with tap', function(){
     72 |         let boundFixture = utils.tapBindObject({
     73 |             fnFixture: function(argOne, argTwo){
     74 |                 expect(argOne).to.equal('foo');
     75 |                 expect(argTwo).to.equal('bar');
     76 |             }
     77 |         }, {
     78 |             tap: function(fn){
     79 |                 return fn;
     80 |             },
     81 |             ...this.context
     82 |         });
     83 | 
     84 |         boundFixture.fnFixture('foo', 'bar')();
     85 |     });
     86 | 
     87 |     /** @test {bindObject} */
     88 |     it('binds object with data', function(){
     89 |         let obj = {
     90 |             fnFixture: function(){
     91 |                 expect(this).to.not.be.empty;
     92 |                 expect(this.fixture).to.equal('fixture');
     93 |             },
     94 |             fnEmpty: function(){
     95 |                 expect(this).to.be.empty;
     96 |             }
     97 |         };
     98 | 
     99 |         let boundFixture = utils.bindObject(obj, this.context);
    100 |         boundFixture.fnFixture();
    101 | 
    102 |         let boundObj = utils.bindObject(obj, {});
    103 |         boundObj.fnEmpty();
    104 |     });
    105 | });
    106 | 
    107 | 108 |
    109 | 110 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | -------------------------------------------------------------------------------- /esdoc.json: -------------------------------------------------------------------------------- 1 | { 2 | "source": "./src", 3 | "destination": "./docs", 4 | "plugins": [{ 5 | "name": "esdoc2-standard-plugin", 6 | "option": { 7 | "manual": { 8 | "files": [ 9 | "./docs-md/overview.md", 10 | "./docs-md/getting-started.md", 11 | "./docs-md/configuration.md", 12 | "./CHANGELOG.md" 13 | ] 14 | }, 15 | "test": { 16 | "source": "./test" 17 | } 18 | } 19 | }, { 20 | "name": "esdoc2-ecmascript-proposal-plugin", 21 | "option": { 22 | "classProperties": true, 23 | "objectRestSpread": true, 24 | "doExpressions": true, 25 | "functionBind": true, 26 | "functionSent": true, 27 | "asyncGenerators": true, 28 | "decorators": true, 29 | "exportExtensions": true, 30 | "dynamicImport": true 31 | } 32 | }, { 33 | "name": "esdoc2-coverage-plugin" 34 | }] 35 | } 36 | -------------------------------------------------------------------------------- /examples/id3.js: -------------------------------------------------------------------------------- 1 | /** 2 | * The most harsh, incomplete and unforgiving ID3v2.3-Parser, ever. 3 | * 4 | * includes image-extraction! 5 | * `npm i temp image-to-asci` 6 | * to enable it 7 | */ 8 | const Corrode = require('../dist'); 9 | const fs = require('fs'); 10 | const path = require('path'); 11 | 12 | const FILE_PATH = process.argv[process.argv.length - 1]; 13 | 14 | const TAG_FLAG_UNSYNCHRONISATION = 0b10000000; 15 | const TAG_FLAG_EXTENDED_HEADER = 0b01000000; 16 | const TAG_FLAG_EXPERIMENTAL = 0b00100000; 17 | const TAG_FLAG_INVALID = 0b00011111; 18 | const TAG_SIZE_INVALID = 0b10000000; 19 | const TAG_SIZE_VALID = 0b01111111; 20 | 21 | const FRAME_FLAG_TAG_ALTER_PRESERVE = 0b1000000000000000; 22 | const FRAME_FLAG_FILE_ALTER_PRESERVE = 0b0100000000000000; 23 | const FRAME_FLAG_READ_ONLY = 0b0010000000000000; 24 | const FRAME_FLAG_COMPRESSION = 0b0000000010000000; 25 | const FRAME_FLAG_ENCRYPTION = 0b0000000001000000; 26 | const FRAME_FLAG_GROUPING_IDENTITY = 0b0000000000100000; 27 | const FRAME_FLAG_INVALID = 0b0001111100011111; 28 | 29 | const FRAME_TYPE_TEXT = /^(T[0-9A-WYZ]{3}|W[0-9A-WYZ]{3}|IPLS)/; 30 | const FRAME_TYPE_PICTURE = /^APIC$/; 31 | const FRAME_TYPE_EXPERIMENTAL = /^[XYZ]/; 32 | 33 | Corrode.addExtension('flagField', function flagField(src, flagsInvalidBitmask, flagsMap){ 34 | if((this.vars[src] & flagsInvalidBitmask) > 0){ 35 | throw new TypeError('ID3 File contains invalid flag, aborting mission.'); 36 | } 37 | 38 | this.map.bitmask('src', flagsMap); 39 | }); 40 | 41 | Corrode.addExtension('id3frameHeader', function id3frameHeader(){ 42 | this 43 | .string('id', 4) 44 | .uint32be('size') 45 | .uint16('flags') 46 | .assert.bitmask('flags', FRAME_FLAG_INVALID, false) 47 | .map.bitmask('flags', { 48 | tagAlterPreserve: FRAME_FLAG_TAG_ALTER_PRESERVE, 49 | fileAlterPreserve: FRAME_FLAG_FILE_ALTER_PRESERVE, 50 | readOnly: FRAME_FLAG_READ_ONLY, 51 | compression: FRAME_FLAG_COMPRESSION, 52 | encryption: FRAME_FLAG_ENCRYPTION, 53 | groupingIdentity: FRAME_FLAG_GROUPING_IDENTITY 54 | }) 55 | .tap(function(){ 56 | this.vars.isExperimental = this.vars.id.match(FRAME_TYPE_EXPERIMENTAL) !== null; 57 | }); 58 | }); 59 | 60 | Corrode.addExtension('id3frameContent', function id3frameContent(header){ 61 | if(header.id.match(FRAME_TYPE_TEXT)){ 62 | this.string('content', header.size).map.callback('content', val => val.replace(/\u0000/g, '')); 63 | } else if(header.id.match(FRAME_TYPE_PICTURE)){ 64 | this.tap('content', function(){ 65 | this 66 | .uint8('encoding') 67 | .terminatedString('mimeType') 68 | .uint8('pictureType') 69 | .terminatedString('description') 70 | .tap(function(){ 71 | var remainingSize = header.size - this.vars.mimeType.length - this.vars.description.length - 4; 72 | this.buffer('data', remainingSize); 73 | }); 74 | }); 75 | } else { 76 | this.buffer('content', header.size); 77 | } 78 | 79 | this.map.push('content'); 80 | }); 81 | 82 | let id3Parser = new Corrode(); 83 | 84 | // header 85 | id3Parser 86 | .string('tag', 3) 87 | .assert.equal('tag', 'ID3') 88 | .uint8('version') 89 | .uint8('revision') 90 | .assert.equal('version', 3) 91 | .uint8('flags') 92 | .assert.bitmask('flags', TAG_FLAG_INVALID, false) 93 | .map.bitmask('flags', { 94 | unsynchronisation: TAG_FLAG_UNSYNCHRONISATION, 95 | extendedHeader: TAG_FLAG_EXTENDED_HEADER, 96 | experimental: TAG_FLAG_EXPERIMENTAL 97 | }) 98 | 99 | // get tag-size 100 | id3Parser.tap('size', function(){ 101 | this 102 | .uint8('size1') 103 | .uint8('size2') 104 | .uint8('size3') 105 | .uint8('size4') 106 | .assert.bitmask('size1', TAG_SIZE_INVALID, false) 107 | .assert.bitmask('size2', TAG_SIZE_INVALID, false) 108 | .assert.bitmask('size3', TAG_SIZE_INVALID, false) 109 | .assert.bitmask('size4', TAG_SIZE_INVALID, false) 110 | .tap(function(){ 111 | const size1 = this.vars.size1 & TAG_SIZE_VALID; 112 | const size2 = this.vars.size2 & TAG_SIZE_VALID; 113 | const size3 = this.vars.size3 & TAG_SIZE_VALID; 114 | const size4 = this.vars.size4 & TAG_SIZE_VALID; 115 | this.vars = size4 | (size3 << 7) | (size2 << 14) | (size1 << 22); 116 | }); 117 | }); 118 | 119 | // extended header 120 | id3Parser.tap(function(){ 121 | if(this.vars.flags.extendedHeader){ 122 | throw new TypeError('ID3 contains extended header. Not supported, aborting mission.'); 123 | } 124 | }); 125 | 126 | // frames 127 | id3Parser.loop('frames', function(end, discard, i){ 128 | this 129 | .ext.id3frameHeader('header') 130 | .tap(function(){ 131 | if(this.vars.header.size === 0){ 132 | return end(true); 133 | } 134 | 135 | this.ext.id3frameContent('content', this.vars.header); 136 | }); 137 | }); 138 | 139 | 140 | // run parser & print data 141 | fs.createReadStream(FILE_PATH) 142 | .pipe(id3Parser) 143 | .on('finish', () => { 144 | let id3info = id3Parser.vars.frames 145 | .map(frame => `${frame.header.id}: ${typeof frame.content === 'string' ? frame.content : 'Buffer'}`) 146 | .join('\n'); 147 | 148 | console.log(id3info); 149 | 150 | // try printing images 151 | id3Parser.vars.frames 152 | .filter(frame => typeof frame.content.pictureType !== 'undefined') 153 | .forEach(frame => { 154 | try { 155 | var path = require('temp').path({ suffix: '.jpg' }); 156 | require('fs').writeFileSync(path, frame.content.data); 157 | require('image-to-ascii')(path, { bg: true, width: 40 }, (err, conv) => console.log(err || conv)); 158 | } catch(e){} 159 | }); 160 | }); 161 | -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "corrode", 3 | "version": "1.0.4", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "bl": { 8 | "version": "1.2.1", 9 | "resolved": "https://registry.npmjs.org/bl/-/bl-1.2.1.tgz", 10 | "integrity": "sha1-ysMo977kVzDUBLaSID/LWQ4XLV4=", 11 | "requires": { 12 | "readable-stream": "2.3.3" 13 | } 14 | }, 15 | "core-util-is": { 16 | "version": "1.0.2", 17 | "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", 18 | "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" 19 | }, 20 | "inherits": { 21 | "version": "2.0.3", 22 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", 23 | "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" 24 | }, 25 | "isarray": { 26 | "version": "1.0.0", 27 | "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", 28 | "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" 29 | }, 30 | "lodash": { 31 | "version": "4.17.5", 32 | "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.5.tgz", 33 | "integrity": "sha512-svL3uiZf1RwhH+cWrfZn3A4+U58wbP0tGVTLQPbjplZxZ8ROD9VLuNgsRniTlLe7OlSqR79RUehXgpBW/s0IQw==" 34 | }, 35 | "process-nextick-args": { 36 | "version": "1.0.7", 37 | "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-1.0.7.tgz", 38 | "integrity": "sha1-FQ4gt1ZZCtP5EJPyWk8q2L/zC6M=" 39 | }, 40 | "readable-stream": { 41 | "version": "2.3.3", 42 | "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.3.tgz", 43 | "integrity": "sha512-m+qzzcn7KUxEmd1gMbchF+Y2eIUbieUaxkWtptyHywrX0rE8QEYqPC07Vuy4Wm32/xE16NcdBctb8S0Xe/5IeQ==", 44 | "requires": { 45 | "core-util-is": "1.0.2", 46 | "inherits": "2.0.3", 47 | "isarray": "1.0.0", 48 | "process-nextick-args": "1.0.7", 49 | "safe-buffer": "5.1.1", 50 | "string_decoder": "1.0.3", 51 | "util-deprecate": "1.0.2" 52 | } 53 | }, 54 | "safe-buffer": { 55 | "version": "5.1.1", 56 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz", 57 | "integrity": "sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg==" 58 | }, 59 | "string_decoder": { 60 | "version": "1.0.3", 61 | "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz", 62 | "integrity": "sha512-4AH6Z5fzNNBcH+6XDMfA/BTt87skxqJlO0lAh3Dker5zThcAxG6mKz+iGu308UKoPPQ8Dcqx/4JhujzltRa+hQ==", 63 | "requires": { 64 | "safe-buffer": "5.1.1" 65 | } 66 | }, 67 | "util-deprecate": { 68 | "version": "1.0.2", 69 | "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", 70 | "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" 71 | } 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "corrode", 3 | "version": "1.0.6", 4 | "description": "A batteries-included library for reading binary data.", 5 | "main": "dist/index.js", 6 | "scripts": { 7 | "build": "babel src --out-dir dist --source-maps", 8 | "watch": "npm run build -- --watch", 9 | "test": "mocha test --compilers js:babel-register ./test", 10 | "lint": "eslint src", 11 | "docs": "esdoc2 -c esdoc.json", 12 | "coverage": "nyc --require babel-core/register --reporter=lcov mocha", 13 | "coverall": "nyc npm test && nyc report --reporter=text-lcov | coveralls", 14 | "prepublish": "npm run build && npm run docs" 15 | }, 16 | "repository": { 17 | "type": "git", 18 | "url": "git+https://github.com/screeny05/corrode.git" 19 | }, 20 | "author": "Sebastian Langer ", 21 | "license": "MIT", 22 | "bugs": { 23 | "url": "https://github.com/screeny05/corrode/issues" 24 | }, 25 | "homepage": "https://github.com/screeny05/corrode", 26 | "devDependencies": { 27 | "babel-cli": "^6.10.1", 28 | "babel-eslint": "^6.1.0", 29 | "babel-plugin-add-module-exports": "^0.2.1", 30 | "babel-preset-es2015": "^6.9.0", 31 | "babel-preset-stage-0": "^6.5.0", 32 | "babel-register": "^6.9.0", 33 | "buffer-safe": "^1.0.0", 34 | "chai": "^3.5.0", 35 | "coveralls": "^2.11.9", 36 | "esdoc2": "^2.1.3", 37 | "esdoc2-coverage-plugin": "^2.0.0", 38 | "esdoc2-ecmascript-proposal-plugin": "^2.0.0", 39 | "esdoc2-publish-html-plugin": "^2.0.1", 40 | "esdoc2-standard-plugin": "^2.1.0", 41 | "eslint": "^3.0.0", 42 | "eslint-config-xo": "^0.15.2", 43 | "eslint-plugin-babel": "^3.3.0", 44 | "istanbul": "^0.4.4", 45 | "lodash": "^4.13.1", 46 | "mocha": "^2.5.3", 47 | "nyc": "^6.6.1" 48 | }, 49 | "dependencies": { 50 | "bl": "^1.1.2", 51 | "lodash": "^4.17.2", 52 | "readable-stream": "^2.2.2" 53 | }, 54 | "eslintConfig": { 55 | "extends": "xo/esnext", 56 | "rules": { 57 | "indent": [ 58 | "error", 59 | 4 60 | ], 61 | "keyword-spacing": 0, 62 | "space-before-function-paren": [ 63 | 2, 64 | "never" 65 | ], 66 | "space-before-blocks": [ 67 | 2, 68 | "never" 69 | ] 70 | } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/assert/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Asserts help you to make sure, the buffer you're parsing 3 | * is in the correct format. These assertions are like chai, throwing an error 4 | * when an assertion doesn't hold. 5 | * 6 | * These functions won't modify {@link Corrode#vars}. 7 | */ 8 | 9 | import lodash from 'lodash'; 10 | 11 | /** 12 | * assert strict equal single value 13 | * @param {string} name key of the value to test 14 | * @param {mixed} value comparision 15 | * @throws TypeError assertion-error 16 | */ 17 | export function equal(name, value){ 18 | if(this.vars[name] !== value){ 19 | throw new TypeError(`Expected ${value}, found ${this.vars[name]} at ${name}`); 20 | } 21 | } 22 | 23 | /** 24 | * assert deep equality each value in Object|Array 25 | * @param {string} name key of the object|array to test 26 | * @param {mixed} testValue comparision, undefined for auto-detect 27 | * @throws TypeError assertion-error 28 | */ 29 | export function allEqual(name, testValue){ 30 | let values = this.vars[name]; 31 | if(!Array.isArray(values)){ 32 | values = lodash.values(values); 33 | } 34 | 35 | if(typeof testValue === 'undefined'){ 36 | testValue = values[0]; 37 | } 38 | 39 | const notEqualObjects = values.filter(varValue => varValue !== testValue); 40 | if(notEqualObjects.length !== 0){ 41 | throw new TypeError(`Expected values in ${JSON.stringify(this.vars[name])} to all be ${testValue}`); 42 | } 43 | } 44 | 45 | /** 46 | * assert equality objects 47 | * @param {string} name key of the object to test 48 | * @param {object} value comparision 49 | * @throws TypeError assertion-error 50 | */ 51 | export function deepEqual(name, value){ 52 | const binaryValue = this.vars[name]; 53 | if(!lodash.isEqual(binaryValue, value)){ 54 | throw new TypeError(`Expected ${JSON.stringify(value)}, found ${JSON.stringify(binaryValue)}`); 55 | } 56 | } 57 | 58 | /** 59 | * assert array|object to contain item 60 | * @param {string} name key of the value to test 61 | * @param {array|object} arr comparision 62 | * @throws TypeError assertion-error 63 | */ 64 | export function includes(name, arr){ 65 | if(!lodash.includes(arr, this.vars[name])){ 66 | throw new TypeError(`Expected ${JSON.stringify(arr)} to include ${this.vars[name]}`); 67 | } 68 | } 69 | 70 | /** 71 | * assert value to be within the bounds of an array 72 | * @param {string} name key of the number to test 73 | * @param {array} value comparision 74 | * @throws TypeError assertion-error 75 | */ 76 | export function inBounds(name, value){ 77 | const index = this.vars[name]; 78 | 79 | if(index < 0 || index >= value.length){ 80 | throw new TypeError(`Expected Array of ${value.length} items to be at least ${index} long`); 81 | } 82 | } 83 | 84 | /** 85 | * assert value via callback 86 | * @param {string} name key of the value to test 87 | * @param {function} fn callback 88 | * @param {string} testname optional test-name 89 | * @throws TypeError assertion-error 90 | */ 91 | export function callback(name, fn, testname = fn.name){ 92 | if(!fn(this.vars[name])){ 93 | throw new TypeError(`Callback failed at ${testname}(${this.vars[name]})`); 94 | } 95 | } 96 | 97 | /** 98 | * assert array to be a given length 99 | * @param {string} name key of the value to test 100 | * @param {number|string} length comparision 101 | * @throws TypeError assertion-error 102 | */ 103 | export function arrayLength(name, length){ 104 | // try to get the length param from the vars if available 105 | if(typeof length === 'string' && this.vars[length] !== 'undefined'){ 106 | length = this.vars[length]; 107 | } 108 | 109 | if(typeof this.vars[name] === 'undefined' || this.vars[name].length !== length){ 110 | throw new TypeError(`Expected array to have a length of ${length}, has ${this.vars[name].length}`); 111 | } 112 | } 113 | 114 | /** 115 | * asserts a variable exists in the first place 116 | * @param {string} name key of the value to test 117 | * @throws TypeError assertion-error 118 | */ 119 | export function exists(name){ 120 | if(typeof this.vars[name] === 'undefined'){ 121 | throw new TypeError(`Expected var ${name} to exist`); 122 | } 123 | } 124 | 125 | /** 126 | * asserts a variable matches a given bitmask 127 | * @param {string} name key of the value to test 128 | * @param {number} mask bitmask to match 129 | * @param {boolean} assertMatch true: should match; false: shouldn't match 130 | * @throws TypeError assertion-error 131 | */ 132 | export function bitmask(name, mask, assertMatch = true){ 133 | const val = this.vars[name]; 134 | 135 | if((val & mask) === mask === !assertMatch){ 136 | throw new TypeError(`Expected var ${name} to ${assertMatch ? '' : 'not '}match bitmask (value: 0b${val.toString(2)} assert: 0b${mask.toString(2)})`); 137 | } 138 | } 139 | -------------------------------------------------------------------------------- /src/map/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * These functions provide basic mapping-abilities for Corrode's VariableStack 3 | * {@link Corrode#vars} 4 | * 5 | * Imagine them like this: 6 | * ``` 7 | * const parser = new Corrode(); 8 | * parser.uint8('value').map.double('value'); 9 | * ``` 10 | * 11 | * Of course there's no real mapping-function which doubles a value. 12 | * But the concept is that they are functions receiving a value, processing it 13 | * and saving a new value in the {@link VariableStack} in place of the old one. 14 | * 15 | * The imaginary code above would yield `{ value: 4 }`, parsing a buffer like this `[2]`. 16 | * 17 | * There are two ways to create a mapper. Either by using the {bind} helper-function 18 | * which simply receives a value and returns one, or by defining the function yourself. 19 | * 20 | * The bind-utility only allows for simple functions with no additional parameters. 21 | * Our double-mapper would be a perfect example: `export const double = bind(val => val * 2)`. 22 | * These should be pure functions. 23 | * 24 | * The other way - defining your own mapper-function accepts deals with the {@link VariableStack} 25 | * at {@link Corrode#vars} by itself. This means: reads and writes from {@link Corrode#vars}. Because of that 26 | * they are inherently impure. A next step should be to move all mappers to pure functions. 27 | * (see Issue #28) 28 | * 29 | * Note that all mappers don't check for existance, validity or other assumptions. 30 | * You have to do that yourself with assertions. 31 | */ 32 | 33 | /** 34 | * helper function to bind a mapper 35 | * mappers created with this utility accept two parameters: 36 | * name and src, with the src defaulting to name. 37 | * This way, we get a mapper which per-default takes the target as the source 38 | * but also accepts a different source. 39 | * @param {function(val: *)} fn map-function 40 | * @return {function} function ready to use in tap 41 | */ 42 | const bind = function(fn){ 43 | return function(name, src = name){ 44 | this.vars[name] = fn(this.vars[src]); 45 | }; 46 | }; 47 | 48 | /** 49 | * replace a variable in the stack by a mapped version of itself 50 | * @param {string} name identifier of the variable to map 51 | * @param {function(val: *)} fn map-function 52 | * @example 53 | * parser.uint8('value').map.callback('value', val => (val - 1) * 2) 54 | * 55 | * // [21] => { value: 10 } 56 | */ 57 | export function callback(name, fn){ 58 | this.vars[name] = fn(this.vars[name]); 59 | } 60 | 61 | /** 62 | * retrieve a value from an accessable type (like array[0] or object['foo']) 63 | * @param {string} name identifier of the variable to map 64 | * @param {array|object|string} accessable accessable variable 65 | * @param {string} [src=name] identifier of the variable in {@link Corrode#vars} by which to access `accessable` 66 | * @example get from array 67 | * parser.uint8('accessor').map.get('accessor', ['A', 'B', 'C', 'D']) 68 | * 69 | * // [2] => { accessor: 'C' } 70 | * 71 | * @example get from object 72 | * parser.terminatedString('accessor').map.get('accessor', { foo: 'A', bar: 'B', qux: 'C' }) 73 | * 74 | * // ['q', 'u', 'x', 0x00] => { accessor: 'C' } 75 | */ 76 | export function get(name, accessable, src = name){ 77 | this.vars[name] = accessable[this.vars[src]]; 78 | } 79 | 80 | /** 81 | * retrieve a filtered array of objects from an array of objects, matching a specified attribute against a specified value 82 | * @param {string} name identifier of the variable, to write to {@link Corrode#vars} 83 | * @param {Array} array array, containing the objects to filter 84 | * @param {string} attr identifier of the attribute from an object of `array` to compare against 85 | * @param {string} [src=name] {@link Corrode#vars}-identifier to read from 86 | * @throws {Error} when no object can be found 87 | * @example 88 | * parser.uint8('matchAgainst').map.findAll('matchAgainst', [ 89 | * { children: 1, name: 'foo' }, 90 | * { children: 2, name: 'bar' }, 91 | * { children: 2, name: 'qux' } 92 | * ], 'children') 93 | * 94 | * // [2] => { matchAgainst: [ 95 | * // { children: 2, name: 'bar' }, 96 | * // { children: 2, name: 'qux' } 97 | * // ]} 98 | * 99 | * // [1] => { matchAgainst: [ 100 | * // { children: 1, name: 'foo' } 101 | * // ]} 102 | */ 103 | export function findAll(name, array, attr, src = name){ 104 | const filtered = array.filter(item => item[attr] === this.vars[src]); 105 | if(filtered.length === 0){ 106 | throw new Error(`cannot find object in array with ${attr} === ${src}(${this.vars[src]})`); 107 | } 108 | this.vars[name] = filtered; 109 | } 110 | 111 | /** 112 | * retrieve the first object from an array of objects, matching a specified attribute against a specified value 113 | * like {@link findAll}, but returning only the first element 114 | * @param {string} name identifier of the variable, to write to {@link Corrode#vars} 115 | * @param {Array} array array, containing the objects to filter 116 | * @param {string} attr identifier of the attribute from an object of `array` to compare against 117 | * @param {string} [src=name] {@link Corrode#vars}-identifier to read from 118 | * @throws {Error} when no object can be found 119 | * @example 120 | * parser.uint8('matchAgainst').map.find('matchAgainst', [ 121 | * { id: 1, name: 'foo' }, 122 | * { id: 7, name: 'bar' }, 123 | * { id: 4, name: 'qux' } 124 | * ], 'id') 125 | * 126 | * // [4] => { matchAgainst: { id: 4, name: 'qux' } } 127 | * 128 | * // [2] => Error cannot find object! 129 | */ 130 | export function find(name, array, attr, src = name){ 131 | findAll.call(this, name, array, attr, src); 132 | this.vars[name] = this.vars[name][0]; 133 | } 134 | 135 | /** 136 | * replace {@link Corrode#vars} completely with a value from {@link Corrode#vars} 137 | * especially useful when pushing a variable further up in the stack 138 | * 139 | * @example push loop-variables up 140 | * parser.loop('array', function(){ 141 | * this 142 | * .uint8('value') 143 | * .map.double() 144 | * .map.push('value'); 145 | * }); 146 | * 147 | * // [1, 2, 3, 4] => { array: [2, 4, 6, 8] } 148 | * 149 | * @example push values in an extension 150 | * Corrode.addExtension('doStuff', function(){ 151 | * this 152 | * .uint32('address') 153 | * .tap(function(){ 154 | * this.vars.address = `0x${this.vars.address.toString(16)}`; 155 | * }) 156 | * .map.push('address'); 157 | * }); 158 | * 159 | * parser.ext.doStuff('hexAddress'); 160 | * 161 | * // [245] => { hexAddress: '0xf5' } 162 | * 163 | * @param {string} [name='values'] identifier of the variable being used as replacement 164 | */ 165 | export function push(name = 'values'){ 166 | this.vars = this.vars[name]; 167 | } 168 | 169 | /** 170 | * map a value by checking whether it has some bits set 171 | * @param {string} name identifier of the variable, to write to {@link Corrode#vars} 172 | * @param {Object|number} maskObject Object or number by which to check the bits of the variable to map 173 | * @example map via number 174 | * parser.uint8('bits').map.bitmask('bits', 0x80) 175 | * 176 | * // [0b10111110] => { bits: true } 177 | * 178 | * @example map via object 179 | * parser.uint8('bits').map.bitmask('bits', { 180 | * isCompressed: 0x80, 181 | * isReadOnly: 0x40 182 | * }) 183 | * 184 | * // [0b10111110] => { bits: { isCompressed: true, isReadOnly: false } } 185 | */ 186 | export function bitmask(name, maskObject){ 187 | const bits = this.vars[name]; 188 | 189 | // shortcut for single values 190 | if(typeof maskObject === 'number'){ 191 | return this.vars[name] = (bits & maskObject) === maskObject; 192 | } 193 | 194 | const values = {}; 195 | Object.keys(maskObject).forEach(maskName => { 196 | const mask = maskObject[maskName]; 197 | values[maskName] = (bits & mask) === mask; 198 | }); 199 | this.vars[name] = values; 200 | } 201 | 202 | /** 203 | * retrieve absolute value of a number 204 | * {@link Math.abs} 205 | * @type {function} 206 | * @example 207 | * this.int8('value').map.abs('value') 208 | * 209 | * // [-14] => { value: 14 } 210 | */ 211 | export const abs = bind(Math.abs); 212 | 213 | /** 214 | * retrieve inverted number 215 | * @type {function} 216 | * @example 217 | * this.uint8('value').map.abs('value') 218 | * 219 | * // [27] => { value: -27 } 220 | */ 221 | export const invert = bind(val => val * -1); 222 | 223 | /** 224 | * retrieve trimmed string 225 | * @type {function} 226 | * @example 227 | * this.terminatedString('value').map.trim('value') 228 | * 229 | * // [' ', '\t', 'f', 'o', 'b', 'r', '\n'] => { value: 'fobr' } 230 | */ 231 | export const trim = bind(str => str.trim()); 232 | -------------------------------------------------------------------------------- /src/utils/index.js: -------------------------------------------------------------------------------- 1 | import { mapValues } from 'lodash'; 2 | 3 | /** 4 | * bind each function in an object with a tap to a given context 5 | * @param {object} obj object with functions 6 | * @param {object} ctx context 7 | * @return {object} copy of object with each function wrapped in a tap 8 | */ 9 | export function tapBindObject(obj, ctx){ 10 | return mapValues(obj, fn => typeof fn === 'function' ? function(...args){ 11 | return ctx.tap(fn.bind(ctx, ...args)); 12 | } : fn); 13 | } 14 | 15 | /** 16 | * bind each function in an object to a given context 17 | * @param {object} obj object with functions 18 | * @param {object} ctx context 19 | * @return {object} copy of object obj with each function bound to ctx 20 | */ 21 | export function bindObject(obj, ctx){ 22 | return mapValues(obj, fn => typeof fn === 'function' ? fn.bind(ctx) : fn); 23 | } 24 | -------------------------------------------------------------------------------- /src/variable-stack.js: -------------------------------------------------------------------------------- 1 | class VariableStackLayer { 2 | constructor(value = {}, isRoot = false, name = null){ 3 | this.value = value; 4 | this.isRoot = isRoot; 5 | this.name = name; 6 | } 7 | 8 | isRoot = false; 9 | value = {}; 10 | name = null; 11 | } 12 | 13 | /** 14 | * The VariableStack is a special kind of stack. 15 | * It allows corrode to do black magic like loops in loops and other crazy stuff. 16 | * 17 | * To enable this we define a stack as an object, containing other objects. 18 | * Seen this way it looks more like a TreeStructure which layers you can 19 | * push and pop as you like. 20 | * 21 | * The VariableStack starts as an "empty" object. 22 | * "empty" meaning, that each layer is an object consisting of two/three values: 23 | * * `isRoot` telling the user whether this layer is the uppermost one. 24 | * * `value` holding the value for this layer. 25 | * * `[name]` name of this layer (root-layer won't have one) 26 | * 27 | * Pushing a new layer means adding a new object to the `value`-object of 28 | * the current layer and setting the current layer to our newly created one. 29 | * 30 | * Actually, the object getting added to the `value`-object is not just any object. 31 | * It itself is an object like `{ isRoot: false, value: {} }`. 32 | * 33 | * The root-layer (layer-object where `isRoot === true`) is the lowest layer. 34 | * The current layer is the topmost one. 35 | * 36 | * @example 37 | * +--------------------------+ +-----------------+ 38 | * | VariableStack#value: | | | current layer 39 | * | { | | {} | 40 | * | value_1: { foo: 'bar' }| | | isRoot: false 41 | * | } | +-----------------+ 42 | * +------------+-------------+ | | 43 | * | | { | 44 | * + | foo: 'bar', | VariableStack 45 | * .push('value_1') | value_2: {} | #peek(1) 46 | * + +----------> | } | 47 | * | | | isRoot: false 48 | * v +----------> +-----------------+ 49 | * +-----------+----------+ | | 50 | * |#value: { foo: 'bar' }| | { | 51 | * +-----------+----------+ | value_1: { | VariableStack 52 | * | | foo: 'bar', | #peek(2) 53 | * + | value_2: {} | 54 | * .push('value_2') | } | isRoot: true 55 | * + | } | 56 | * | | | 57 | * v +-----------------+ 58 | * +----+-----+ 59 | * |#value: {}| 60 | * +----------+ 61 | * 62 | */ 63 | export default class VariableStack { 64 | constructor(){ 65 | this.top = this.stack[0]; 66 | } 67 | 68 | /** 69 | * internal storage for the stack 70 | * @access public 71 | * @type {Array} 72 | */ 73 | stack = [new VariableStackLayer({}, true)]; 74 | 75 | /** 76 | * retrieve the top-layer 77 | * @return {Object} the current layer 78 | */ 79 | top = null; 80 | 81 | /** 82 | * retrieve the value of the top.layer 83 | * @return {Object|*} the current value 84 | */ 85 | get value(){ 86 | return this.top.value; 87 | } 88 | 89 | /** 90 | * set the current value 91 | * this also updates the value in the parent-layer 92 | * @param {Object|*} val the new value 93 | */ 94 | set value(val){ 95 | if(!this.top.isRoot){ 96 | this.peek()[this.top.name] = val; 97 | } 98 | this.top.value = val; 99 | } 100 | 101 | /** 102 | * get a layer below the current one 103 | * @param {number} layerCount how many layers deeper relative from the current 104 | * @return {Object} layer-object 105 | */ 106 | peekLayer(layerCount = 1){ 107 | if(layerCount > this.stack.length - 1){ 108 | throw new ReferenceError(`can't retrieve layer ${layerCount}, stack is ${this.stack.length - 1} layers`); 109 | } 110 | return this.stack[this.stack.length - 1 - layerCount]; 111 | } 112 | 113 | /** 114 | * get the value of a layer below the current one 115 | * @param {number} layerCount how many layers deeper relative from the current 116 | * @return {Object|*} value 117 | */ 118 | peek(layerCount = 1){ 119 | return this.peekLayer(layerCount).value; 120 | } 121 | 122 | /** 123 | * push a value onto the stack 124 | * 125 | * The value doesn't have to be a object, but only objects will properly support child-layers. 126 | * When pushing the new layer the current one will receive a reference to the pushed 127 | * object as a value at the given name. 128 | * 129 | * Note, that if you're pushing a non-object value this reference will not work, 130 | * as only arrays & objects are passed by reference. Instead the value in the 131 | * layer above will be updated, when the current layer's value will be set. 132 | * 133 | * If the value you want to push already exists at the current layer 134 | * VariableStack ignores your value and just re-uses the old one, so no 135 | * layer will be replaced. 136 | * 137 | * @example 138 | * varStack.push('foo'); 139 | * varStack.value.bar = 'baz'; 140 | * // varStack.value => { bar: 'baz' } 141 | * // varStack.peek() => { foo: { bar: 'baz' } } 142 | * 143 | * @param {string} name name of the new layer 144 | * @param {Object|*} [value={}] value-object of the new layer 145 | */ 146 | push(name, value = {}){ 147 | if(typeof this.value[name] === 'undefined'){ 148 | // only push new value if there's no old one 149 | this.value[name] = value; 150 | } else { 151 | // otherwise re-push the current one 152 | value = this.value[name]; 153 | } 154 | 155 | const index = this.stack.push(new VariableStackLayer(value, false, name)); 156 | this.top = this.stack[index - 1]; 157 | } 158 | 159 | /** 160 | * pop the current layer 161 | * @throws {ReferenceError} thrown if the layer to be popped is the root-layer 162 | */ 163 | pop(){ 164 | const popLayer = this.top; 165 | if(popLayer.isRoot){ 166 | throw new ReferenceError('can\'t pop root layer'); 167 | } 168 | 169 | this.stack.pop(); 170 | 171 | this.top = this.stack[this.stack.length - 1]; 172 | 173 | // reassure that the value in the layer above is right 174 | // (in case of non-object values) 175 | this.value[popLayer.name] = popLayer.value; 176 | } 177 | 178 | /** 179 | * pop all layers until the root-layer is reached 180 | */ 181 | popAll(){ 182 | while(!this.top.isRoot){ 183 | this.pop(); 184 | } 185 | } 186 | } 187 | -------------------------------------------------------------------------------- /test/assert.test.js: -------------------------------------------------------------------------------- 1 | const expect = require('chai').expect; 2 | const fixture = require('./fixtures/vars'); 3 | const assert = require('../src/assert'); 4 | const utils = require('../src/utils'); 5 | 6 | describe('Assert', () => { 7 | beforeEach(function(){ 8 | this.fixture = fixture.clone(); 9 | 10 | this.assert = function(name, ...args){ 11 | let assertFn = utils.bindObject(assert, { vars: this.fixture }); 12 | return assertFn[name].bind(assertFn, ...args); 13 | }; 14 | }); 15 | 16 | /** @test {equal} */ 17 | it('asserts equal', function(){ 18 | expect(this.assert('equal', 'string', 'fixture')).to.not.throw(TypeError); 19 | expect(this.assert('equal', 'string', 'wrong')).to.throw(TypeError); 20 | expect(this.assert('equal', 'number', 1337)).to.not.throw(TypeError); 21 | expect(this.assert('equal', 'number', 0)).to.throw(TypeError); 22 | }); 23 | 24 | /** @test {allEqual} */ 25 | it('asserts allEqual for objects', function(){ 26 | expect(this.assert('allEqual', 'objectWithSameValues', 'fixture')).to.not.throw(TypeError); 27 | expect(this.assert('allEqual', 'objectWithSameValues', 'wrong')).to.throw(TypeError); 28 | expect(this.assert('allEqual', 'objectWithSameValues')).to.not.throw(TypeError); 29 | expect(this.assert('allEqual', 'object', 'wrong')).to.throw(TypeError); 30 | expect(this.assert('allEqual', 'object')).to.throw(TypeError); 31 | }); 32 | 33 | /** @test {allEqual} */ 34 | it('asserts allEqual for arrays', function(){ 35 | expect(this.assert('allEqual', 'arrayWithSameValues', 'fixture')).to.not.throw(TypeError); 36 | expect(this.assert('allEqual', 'arrayWithSameValues', 'wrong')).to.throw(TypeError); 37 | expect(this.assert('allEqual', 'arrayWithSameValues')).to.not.throw(TypeError); 38 | expect(this.assert('allEqual', 'array', 'wrong')).to.throw(TypeError); 39 | expect(this.assert('allEqual', 'array')).to.throw(TypeError); 40 | }); 41 | 42 | /** @test {deepEqual} */ 43 | it('asserts deepEqual', function(){ 44 | expect(this.assert('deepEqual', 'objectWithSameValues', this.fixture.objectWithSameValues)).to.not.throw(TypeError); 45 | expect(this.assert('deepEqual', 'objectWithSameValues', this.fixture.object)).to.throw(TypeError); 46 | expect(this.assert('deepEqual', 'object', this.fixture.object)).to.not.throw(TypeError); 47 | expect(this.assert('deepEqual', 'object', this.fixture.objectWithSameValues)).to.throw(TypeError); 48 | }); 49 | 50 | /** @test {includes} */ 51 | it('asserts includes for arrays', function(){ 52 | expect(this.assert('includes', 'string', ['fixture'])).to.not.throw(TypeError); 53 | expect(this.assert('includes', 'string', ['wrong'])).to.throw(TypeError); 54 | }); 55 | 56 | /** @test {includes} */ 57 | it('asserts includes for objects', function(){ 58 | expect(this.assert('includes', 'string', { child: 'fixture' })).to.not.throw(TypeError); 59 | expect(this.assert('includes', 'string', { child: 'wrong' })).to.throw(TypeError); 60 | }); 61 | 62 | /** @test {inBounds} */ 63 | it('asserts inBounds', function(){ 64 | expect(this.assert('inBounds', 'negative', this.fixture.array)).to.throw(TypeError); 65 | expect(this.assert('inBounds', 'zero', this.fixture.array)).to.not.throw(TypeError); 66 | expect(this.assert('inBounds', 'one', this.fixture.array)).to.not.throw(TypeError); 67 | expect(this.assert('inBounds', 'two', this.fixture.array)).to.not.throw(TypeError); 68 | expect(this.assert('inBounds', 'three', this.fixture.array)).to.throw(TypeError); 69 | }); 70 | 71 | /** @test {callback} */ 72 | it('asserts via callback', function(){ 73 | expect(this.assert('callback', 'string', val => true)).to.not.throw(TypeError); 74 | expect(this.assert('callback', 'string', val => false)).to.throw(TypeError); 75 | expect(this.assert('callback', 'string', val => true, 'custom')).to.not.throw(TypeError); 76 | expect(this.assert('callback', 'string', val => false, 'custom')).to.throw(TypeError); 77 | }); 78 | 79 | /** @test {arrayLength} */ 80 | it('asserts arrayLength', function(){ 81 | expect(this.assert('arrayLength', 'array', 3)).to.not.throw(TypeError); 82 | expect(this.assert('arrayLength', 'array', -1)).to.throw(TypeError); 83 | expect(this.assert('arrayLength', 'array', 2)).to.throw(TypeError); 84 | expect(this.assert('arrayLength', 'array', 4)).to.throw(TypeError); 85 | }); 86 | 87 | /** @test {exists} */ 88 | it('asserts exists', function(){ 89 | expect(this.assert('exists', 'array')).to.not.throw(TypeError); 90 | expect(this.assert('exists', 'string')).to.not.throw(TypeError); 91 | expect(this.assert('exists', 'number')).to.not.throw(TypeError); 92 | expect(this.assert('exists', 'wrong')).to.throw(TypeError); 93 | expect(this.assert('exists', -1)).to.throw(TypeError); 94 | }); 95 | 96 | /** @test {bitmask} */ 97 | it('asserts bitmask', function(){ 98 | expect(this.assert('bitmask', 'bitmask1', this.fixture.bitmask1)).to.not.throw(TypeError); 99 | expect(this.assert('bitmask', 'bitmask1', this.fixture.bitmask2)).to.throw(TypeError); 100 | expect(this.assert('bitmask', 'bitmask2', this.fixture.bitmask1)).to.throw(TypeError); 101 | expect(this.assert('bitmask', 'bitmask2', this.fixture.bitmask2)).to.not.throw(TypeError); 102 | expect(this.assert('bitmask', 'bitmaskMatch', this.fixture.bitmask1)).to.not.throw(TypeError); 103 | expect(this.assert('bitmask', 'bitmaskMatch', this.fixture.bitmask2)).to.throw(TypeError); 104 | expect(this.assert('bitmask', 'bitmask1', this.fixture.bitmask1, false)).to.throw(TypeError); 105 | expect(this.assert('bitmask', 'bitmask1', this.fixture.bitmask2, false)).to.not.throw(TypeError); 106 | expect(this.assert('bitmask', 'bitmask2', this.fixture.bitmask1, false)).to.not.throw(TypeError); 107 | expect(this.assert('bitmask', 'bitmask2', this.fixture.bitmask2, false)).to.throw(TypeError); 108 | expect(this.assert('bitmask', 'bitmaskMatch', this.fixture.bitmask1, false)).to.throw(TypeError); 109 | expect(this.assert('bitmask', 'bitmaskMatch', this.fixture.bitmask2, false)).to.not.throw(TypeError); 110 | }); 111 | }); 112 | -------------------------------------------------------------------------------- /test/base-aborts.test.js: -------------------------------------------------------------------------------- 1 | const { expect } = require('chai'); 2 | const Base = require('../src/base'); 3 | 4 | /** @test {CorrodeBase#jobLoop} */ 5 | describe('CorrodeBase - Aborts', () => { 6 | beforeEach(function(){ 7 | this.base = new Base(); 8 | this.eqArray = require('./helpers/asserts').eqArray.bind(this); 9 | }); 10 | 11 | it('correctly aborts too short int16', function(done){ 12 | this.base.int16('val'); 13 | this.eqArray([1], done, {}); 14 | }); 15 | 16 | it('correctly aborts too short int32', function(done){ 17 | this.base.int32('val'); 18 | this.eqArray([1, 2, 3], done, {}); 19 | }); 20 | 21 | it('correctly aborts too short int64', function(done){ 22 | this.base.int64('val'); 23 | this.eqArray([1, 2, 3, 4, 5, 6, 7], done, {}); 24 | }); 25 | 26 | it('correctly aborts too short float', function(done){ 27 | this.base.float('val'); 28 | this.eqArray([1, 2, 3], done, {}); 29 | }); 30 | 31 | it('correctly aborts too short double', function(done){ 32 | this.base.double('val'); 33 | this.eqArray([1, 2, 3, 4, 5, 6, 7], done, {}); 34 | }); 35 | 36 | it('correctly aborts too short string', function(done){ 37 | this.base.string('foo', 2); 38 | this.eqArray([1], done, {}); 39 | }); 40 | 41 | it('correctly aborts too short buffer', function(done){ 42 | this.base.buffer('foo', 2); 43 | this.eqArray([1], done, {}); 44 | }); 45 | 46 | it('correctly aborts too short skip', function(done){ 47 | this.base 48 | .uint8('var_1') 49 | .skip(10) 50 | .uint8('var_2'); 51 | 52 | this.eqArray([2, 0], done, { 53 | var_1: 2 54 | }); 55 | }); 56 | }); 57 | -------------------------------------------------------------------------------- /test/base-edge-cases.test.js: -------------------------------------------------------------------------------- 1 | const { expect } = require('chai'); 2 | const Base = require('../src/base'); 3 | 4 | /** @test {CorrodeBase} */ 5 | describe('CorrodeBase - Edge Cases', () => { 6 | beforeEach(function(){ 7 | this.base = new Base(); 8 | this.eqArray = require('./helpers/asserts').eqArray.bind(this); 9 | this.eqMultiArray = require('./helpers/asserts').eqMultiArray.bind(this); 10 | }); 11 | 12 | /** @test {CorrodeBase#jobLoop} */ 13 | it('correctly overrides primitves', function(done){ 14 | this.base 15 | .uint8('var') 16 | .uint8('var'); 17 | 18 | this.eqArray([1, 2, 3], done, { 19 | var: 2 20 | }); 21 | }); 22 | 23 | /** @test {CorrodeBase#string} */ 24 | it('considers strings as length as references to vars - strings', function(done){ 25 | this.base 26 | .uint8('length') 27 | .string('string', 'length'); 28 | 29 | this.eqArray([2, 0x21, 0x22], done, { 30 | length: 2, 31 | string: '!"' 32 | }); 33 | }); 34 | 35 | /** @test {CorrodeBase#buffer} */ 36 | it('considers strings as length as references to vars - buffers', function(done){ 37 | this.base 38 | .uint8('length') 39 | .buffer('buffer', 'length'); 40 | 41 | this.eqArray([2, 0x21, 0x22], done, { 42 | length: 2, 43 | buffer: Buffer.from([0x21, 0x22]) 44 | }); 45 | }); 46 | 47 | /** @test {CorrodeBase#skip} */ 48 | it('considers strings as length as references to vars - skip', function(done){ 49 | this.base 50 | .uint8('length') 51 | .skip('length') 52 | .uint8('var_1') 53 | 54 | this.eqArray([2, 1, 2, 3, 4], done, { 55 | length: 2, 56 | var_1: 3 57 | }); 58 | }); 59 | 60 | /** @test {CorrodeBase#string} */ 61 | it('throws errors for invalid variables when using strings as length as references to vars - strings', function(){ 62 | this.base 63 | .uint8('unknown') 64 | .string('string', 'length'); 65 | 66 | expect(this.eqArray.bind(this, [2, 0x21, 0x22], () => {}, {})).to.throw(TypeError); 67 | }); 68 | 69 | /** @test {CorrodeBase#buffer} */ 70 | it('throws errors for invalid variables when using strings as length as references to vars - buffers', function(){ 71 | this.base 72 | .uint8('unknown') 73 | .buffer('buffer', 'length'); 74 | 75 | expect(this.eqArray.bind(this, [2, 0x21, 0x22], () => {}, {})).to.throw(TypeError); 76 | }); 77 | 78 | /** @test {CorrodeBase#skip} */ 79 | it('throws errors for invalid variables when using strings as length as references to vars - skip', function(){ 80 | this.base 81 | .uint8('unknown') 82 | .skip('length') 83 | .uint8('var_1') 84 | 85 | expect(this.eqArray.bind(this, [2, 1, 2, 3, 4], () => {}, {})).to.throw(TypeError); 86 | }); 87 | 88 | /** 89 | * @test {CorrodeBase#chunkOffset} 90 | * @test {CorrodeBase#streamOffset} 91 | */ 92 | it('has the correct offset', function(done){ 93 | this.base 94 | .uint8('var_1') 95 | .tap(function(){ 96 | expect(this.chunkOffset).to.equal(1); 97 | expect(this.streamOffset).to.equal(1); 98 | }) 99 | .uint8('var_2') 100 | .uint8('var_3') 101 | .tap(function(){ 102 | expect(this.chunkOffset).to.equal(1); 103 | expect(this.streamOffset).to.equal(3); 104 | }); 105 | 106 | this.eqMultiArray([[1, 2], [3], [4]], done, { 107 | var_1: 1, 108 | var_2: 2, 109 | var_3: 3 110 | }); 111 | }); 112 | }); 113 | -------------------------------------------------------------------------------- /test/base-flags.test.js: -------------------------------------------------------------------------------- 1 | const { expect } = require('chai'); 2 | const Base = require('../src/base'); 3 | 4 | /** @test {CorrodeBase#options} */ 5 | describe('CorrodeBase#options', () => { 6 | beforeEach(function(){ 7 | this.base = new Base(); 8 | this.eqArray = require('./helpers/asserts').eqArray.bind(this); 9 | this.eqMultiArray = require('./helpers/asserts').eqMultiArray.bind(this); 10 | }); 11 | 12 | /** @test {CorrodeBase#options.finishJobsOnEOF} */ 13 | it('correctly finishes primitive jobs on EOF', function(done){ 14 | this.base 15 | .uint8('var_1') 16 | .uint8('var_2') 17 | .uint8('var_3') 18 | .uint8('var_4') 19 | .uint8('var_5'); 20 | 21 | this.eqArray([1, 2, 3], done, vars => { 22 | expect(vars).to.deep.equal({ 23 | var_1: 1, 24 | var_2: 2, 25 | var_3: 3 26 | }); 27 | expect(this.base.jobs).to.be.empty; 28 | }); 29 | }); 30 | 31 | /** @test {CorrodeBase#options.finishJobsOnEOF} */ 32 | it('correctly finishes tap jobs on EOF', function(done){ 33 | this.base 34 | .uint8('var_1') 35 | .tap('struct', function(){ 36 | this 37 | .uint8('var_2') 38 | .uint8('var_3') 39 | .uint8('var_4'); 40 | }); 41 | 42 | this.eqArray([1, 2, 3], done, vars => { 43 | expect(vars).to.deep.equal({ 44 | var_1: 1, 45 | struct: { 46 | var_2: 2, 47 | var_3: 3 48 | } 49 | }); 50 | expect(this.base.jobs).to.be.empty; 51 | }); 52 | }); 53 | 54 | /** @test {CorrodeBase#options.finishJobsOnEOF} */ 55 | it('correctly finishes loop jobs on EOF', function(done){ 56 | this.base 57 | .uint8('var_1') 58 | .loop('loop', function(){ 59 | this 60 | .uint8('var_2') 61 | .uint8('var_3'); 62 | }); 63 | 64 | this.eqArray([1, 2, 3, 4, 5, 6], done, vars => { 65 | expect(vars).to.deep.equal({ 66 | var_1: 1, 67 | loop: [{ 68 | var_2: 2, 69 | var_3: 3 70 | }, { 71 | var_2: 4, 72 | var_3: 5 73 | }, { 74 | var_2: 6 75 | }] 76 | }); 77 | expect(this.base.jobs).to.be.empty; 78 | }); 79 | }); 80 | 81 | /** @test {CorrodeBase#options.finishJobsOnEOF} */ 82 | it('correctly finishes nested jobs on EOF', function(done){ 83 | this.base 84 | .uint8('var_1') 85 | .loop('loop', function(){ 86 | this 87 | .uint8('var_2') 88 | .uint8('var_3') 89 | .tap('innerStruct', function(){ 90 | this 91 | .uint8('var_4') 92 | .loop('innerLoop', function(finish, discard, i){ 93 | this 94 | .uint8('var_5') 95 | .uint8('var_6'); 96 | 97 | if(i >= 1){ 98 | finish(); 99 | } 100 | }); 101 | }); 102 | }); 103 | 104 | this.eqArray([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14], done, { 105 | var_1: 1, 106 | loop: [{ 107 | var_2: 2, 108 | var_3: 3, 109 | innerStruct: { 110 | var_4: 4, 111 | innerLoop: [{ 112 | var_5: 5, 113 | var_6: 6 114 | }, { 115 | var_5: 7, 116 | var_6: 8 117 | }] 118 | } 119 | }, { 120 | var_2: 9, 121 | var_3: 10, 122 | innerStruct: { 123 | var_4: 11, 124 | innerLoop: [{ 125 | var_5: 12, 126 | var_6: 13 127 | }, { 128 | var_5: 14 129 | }] 130 | } 131 | }] 132 | }); 133 | }); 134 | 135 | /** @test {CorrodeBase#options.finishJobsOnEOF} */ 136 | it('correctly rests in the current state when finishJobsOnEOF is false - tap', function(done){ 137 | this.base = new Base({ finishJobsOnEOF: false }); 138 | 139 | this.base 140 | .uint8('var_1') 141 | .tap('struct', function(){ 142 | this 143 | .uint8('var_2') 144 | .uint8('var_3') 145 | .uint8('var_4'); 146 | }); 147 | 148 | this.eqArray([1, 2, 3], done, () => { 149 | expect(this.base.varStack.top.isRoot).to.not.be.true; 150 | expect(this.base.varStack.stack.length).to.be.greaterThan(1); 151 | expect(this.base.jobs).to.not.be.empty; 152 | }); 153 | }); 154 | 155 | /** @test {CorrodeBase#options.finishJobsOnEOF} */ 156 | it('correctly rests in the current state when finishJobsOnEOF is false - loop', function(done){ 157 | this.base = new Base({ finishJobsOnEOF: false }); 158 | 159 | this.base 160 | .uint8('var_1') 161 | .loop('loop', function(){ 162 | this 163 | .uint8('var_2') 164 | .uint8('var_3') 165 | .uint8('var_4'); 166 | }); 167 | 168 | this.eqArray([1, 2, 3], done, () => { 169 | expect(this.base.varStack.peek()[this.base.options.loopIdentifier]).to.exist; 170 | expect(this.base.varStack.top.isRoot).to.not.be.true; 171 | expect(this.base.varStack.stack.length).to.be.greaterThan(1); 172 | expect(this.base.jobs).to.not.be.empty; 173 | }); 174 | }); 175 | 176 | /** @test {CorrodeBase#options.loopIdentifier} */ 177 | it('should not be disturbed, when changing the loopIdentifier', function(done){ 178 | this.base = new Base({ loopIdentifier: '__loop' }); 179 | 180 | this.base.loop('loop', function(){ 181 | this.uint8('var'); 182 | }); 183 | 184 | this.eqArray([1, 2, 3], done, { 185 | loop: [{ 186 | var: 1 187 | }, { 188 | var: 2 189 | }, { 190 | var: 3 191 | }] 192 | }); 193 | }); 194 | 195 | /** @test {CorrodeBase#isSeeking} */ 196 | it('flushes when isSeeking = false', function(done){ 197 | this.base.loop('loop', function(){ 198 | this.uint8('var'); 199 | }); 200 | 201 | this.eqMultiArray([[1], [2], [3], [4, 5], [6], [7], [8, 9], [0]], done, () => { 202 | expect(this.base.streamBuffer.length).to.equal(0); 203 | }); 204 | }); 205 | 206 | /** @test {CorrodeBase#isSeeking} */ 207 | it('prevents flushes when isSeeking = true', function(done){ 208 | this.base.isSeeking = true; 209 | 210 | this.base.loop('loop', function(){ 211 | this.uint8('var'); 212 | }); 213 | 214 | this.eqMultiArray([[1], [2], [3], [4, 5], [6], [7], [8, 9], [0]], done, () => { 215 | expect(this.base.streamBuffer.length).to.equal(10); 216 | }); 217 | }); 218 | 219 | /** @test {CorrodeBase#isSeeking} */ 220 | it('allows mixing of isSeeking-modes', function(done){ 221 | this.base.loop('loop', function(end, discard, i){ 222 | this.uint8('var'); 223 | 224 | if(i >= 8){ 225 | this.isSeeking = true; 226 | } 227 | }); 228 | 229 | this.eqMultiArray([[1], [2], [3], [4], [5], [6], [7], [8], [9], [0]], done, () => { 230 | expect(this.base.streamBuffer.length).to.equal(2); 231 | }); 232 | }); 233 | }); 234 | -------------------------------------------------------------------------------- /test/base-loop-anonymous.test.js: -------------------------------------------------------------------------------- 1 | const expect = require('chai').expect; 2 | const Base = require('../src/base'); 3 | 4 | /** @test {CorrodeBase#loop} */ 5 | describe('CorrodeBase#loop - anonymous', () => { 6 | beforeEach(function(){ 7 | this.base = new Base(); 8 | this.eqArray = require('./helpers/asserts').eqArray.bind(this); 9 | }); 10 | 11 | 12 | it('anonymous loop (overriding)', function(done){ 13 | this.base.loop(function(finish, discard, i){ 14 | this.uint8('var'); 15 | this.vars.iterations = i + 1; 16 | }); 17 | 18 | this.eqArray([1, 2, 3, 4, 5], done, { 19 | var: 5, 20 | iterations: 5 21 | }); 22 | }); 23 | 24 | it('anonymous loop (scope)', function(done){ 25 | this.base.loop(function(){ 26 | if(typeof this.vars.iterations === 'undefined'){ 27 | this.vars.iterations = 0; 28 | } 29 | this.vars.iterations++; 30 | 31 | this.uint8('var'); 32 | }); 33 | 34 | this.eqArray([1, 2, 3], done, { 35 | iterations: 3, 36 | var: 3 37 | }); 38 | }); 39 | 40 | it('anonymous loop (no discard, no finish)', function(done){ 41 | this.base.loop(function(finish, discard, i){ 42 | this.vars['it_' + i] = i; 43 | this.uint8('var_' + i); 44 | }); 45 | 46 | this.eqArray([1, 2, 3, 4, 5], done, { 47 | var_0: 1, 48 | var_1: 2, 49 | var_2: 3, 50 | var_3: 4, 51 | var_4: 5, 52 | it_0: 0, 53 | it_1: 1, 54 | it_2: 2, 55 | it_3: 3, 56 | it_4: 4 57 | }); 58 | }); 59 | 60 | it('anonymous loop (no discard, finish after)', function(done){ 61 | this.base.loop(function(finish, discard, i){ 62 | this.uint8('var_' + i); 63 | if(i >= 2){ 64 | finish(); 65 | } 66 | }); 67 | 68 | this.eqArray([1, 2, 3, 4, 5], done, { 69 | var_0: 1, 70 | var_1: 2, 71 | var_2: 3 72 | }); 73 | }); 74 | 75 | it('anonymous loop (no discard, finish before)', function(done){ 76 | this.base.loop(function(finish, discard, i){ 77 | if(i >= 3){ 78 | return finish(); 79 | } 80 | this.uint8('var_' + i); 81 | }); 82 | 83 | this.eqArray([1, 2, 3, 4, 5], done, { 84 | var_0: 1, 85 | var_1: 2, 86 | var_2: 3 87 | }); 88 | }); 89 | 90 | it('anonymous loop (discard before, no finish)', function(done){ 91 | this.base.loop(function(finish, discard, i){ 92 | if(i % 2 !== 0){ 93 | discard(); 94 | } 95 | this.uint8('var_' + i); 96 | }); 97 | 98 | this.eqArray([0, 1, 2, 3, 4, 5, 6], done, { 99 | var_0: 0, 100 | var_2: 2, 101 | var_4: 4, 102 | var_6: 6 103 | }); 104 | }); 105 | 106 | it('anonymous loop (discard after, no finish)', function(done){ 107 | this.base.loop(function(finish, discard, i){ 108 | this 109 | .uint8('var_' + i) 110 | .tap(function(){ 111 | if(this.vars['var_' + i] % 2 !== 0){ 112 | discard(); 113 | } 114 | }); 115 | if(i % 3 === 0){ 116 | discard(); 117 | } 118 | }); 119 | 120 | this.eqArray([0, 1, 2, 3, 4, 5, 6], done, { 121 | var_2: 2, 122 | var_4: 4 123 | }); 124 | }); 125 | 126 | /** @test {CorrodeBase#options.anonymousLoopDiscardDeep} */ 127 | it('anonymous loop (discard deep, no finish)', function(done){ 128 | this.base = new Base({ anonymousLoopDiscardDeep: true }); 129 | 130 | this.base.loop(function(finish, discard, i){ 131 | if(!this.vars['fix']){ 132 | this.vars['fix'] = { iterations: 0, arr: [] }; 133 | } 134 | this.vars.fix.iterations++; 135 | this.vars.fix.arr.push(i); 136 | 137 | this.uint8('var_' + i); 138 | if(i % 2 !== 0){ 139 | discard(); 140 | } 141 | }); 142 | 143 | this.eqArray([0, 1, 2, 3, 4, 5, 6, 7], done, { 144 | fix: { 145 | iterations: 4, 146 | arr: [0, 2, 4, 6] 147 | }, 148 | var_0: 0, 149 | var_2: 2, 150 | var_4: 4, 151 | var_6: 6 152 | }); 153 | }); 154 | 155 | /** @test {CorrodeBase#options.anonymousLoopDiscardDeep} */ 156 | it('anonymous loop (discard shallow, no finish)', function(done){ 157 | this.base.loop(function(finish, discard, i){ 158 | if(!this.vars['fix']){ 159 | this.vars['fix'] = { iterations: 0, arr: [] }; 160 | } 161 | this.vars.fix.iterations++; 162 | this.vars.fix.arr.push(i); 163 | 164 | this.uint8('var_' + i); 165 | if(i % 2 !== 0){ 166 | discard(); 167 | } 168 | }); 169 | 170 | this.eqArray([0, 1, 2, 3, 4, 5, 6, 7], done, { 171 | fix: { 172 | iterations: 8, 173 | arr: [0, 1, 2, 3, 4, 5, 6, 7] 174 | }, 175 | var_0: 0, 176 | var_2: 2, 177 | var_4: 4, 178 | var_6: 6 179 | }); 180 | }); 181 | 182 | it('anonymous loop (discard, finish before)', function(done){ 183 | this.base.loop(function(finish, discard, i){ 184 | if(i >= 3){ 185 | finish(true); 186 | } 187 | this.uint8('var_' + i); 188 | }); 189 | 190 | this.eqArray([1, 2, 3, 4, 5], done, { 191 | var_0: 1, 192 | var_1: 2, 193 | var_2: 3 194 | }); 195 | }); 196 | 197 | it('anonymous loop (discard, finish after)', function(done){ 198 | this.base.loop(function(finish, discard, i){ 199 | this.uint8('var_' + i); 200 | if(i >= 3){ 201 | finish(true); 202 | } 203 | }); 204 | 205 | this.eqArray([1, 2, 3, 4, 5], done, { 206 | var_0: 1, 207 | var_1: 2, 208 | var_2: 3 209 | }); 210 | }); 211 | }); 212 | -------------------------------------------------------------------------------- /test/base-loop-named.test.js: -------------------------------------------------------------------------------- 1 | const { expect } = require('chai'); 2 | const Base = require('../src/base'); 3 | 4 | /** @test {CorrodeBase#loop} */ 5 | describe('CorrodeBase#loop - named', () => { 6 | beforeEach(function(){ 7 | this.base = new Base(); 8 | this.eqArray = require('./helpers/asserts').eqArray.bind(this); 9 | }); 10 | 11 | 12 | it('named loop', function(done){ 13 | this.base.loop('fix', function(){ 14 | this.uint8('var'); 15 | }); 16 | 17 | this.eqArray([0, 1, 2], done, { 18 | fix: [{ 19 | var: 0 20 | }, { 21 | var: 1 22 | }, { 23 | var: 2 24 | }] 25 | }); 26 | }); 27 | 28 | it('named loop (scope)', function(done){ 29 | this.base 30 | .uint8('rootFix') 31 | .loop('fix', function(){ 32 | if(typeof this.vars.fix === 'undefined'){ 33 | this.vars.fix = -1; 34 | } 35 | this.vars.fix++; 36 | this.varStack.peek().rootFix++; 37 | this.uint8('var'); 38 | }); 39 | 40 | this.eqArray([0, 0, 1, 2], done, { 41 | rootFix: 3, 42 | fix: [{ 43 | fix: 0, 44 | var: 0 45 | }, { 46 | fix: 0, 47 | var: 1 48 | }, { 49 | fix: 0, 50 | var: 2 51 | }] 52 | }); 53 | }); 54 | 55 | it('named loop (no discard, finish after)', function(done){ 56 | this.base.loop('loop', function(finish, discard, i){ 57 | this.uint8('var_' + i); 58 | if(i >= 2){ 59 | finish(); 60 | } 61 | }); 62 | 63 | this.eqArray([0, 1, 2, 3, 4, 5], done, { 64 | loop: [{ 65 | var_0: 0 66 | }, { 67 | var_1: 1 68 | }, { 69 | var_2: 2 70 | }] 71 | }); 72 | }); 73 | 74 | it('named loop (no discard, finish before)', function(done){ 75 | this.base.loop('loop', function(finish, discard, i){ 76 | if(i >= 3){ 77 | return finish(); 78 | } 79 | this.uint8('var_' + i); 80 | }); 81 | 82 | this.eqArray([0, 1, 2, 3, 4, 5], done, { 83 | loop: [{ 84 | var_0: 0 85 | }, { 86 | var_1: 1 87 | }, { 88 | var_2: 2 89 | }] 90 | }); 91 | }); 92 | 93 | it('named loop (discard before, no finish)', function(done){ 94 | this.base.loop('loop', function(finish, discard, i){ 95 | if(i % 2 !== 0){ 96 | discard(); 97 | } 98 | this.uint8('var_' + i); 99 | }); 100 | 101 | this.eqArray([0, 1, 2, 3, 4, 5, 6], done, { 102 | loop: [{ 103 | var_0: 0 104 | }, { 105 | var_2: 2 106 | }, { 107 | var_4: 4 108 | }, { 109 | var_6: 6 110 | }] 111 | }); 112 | }); 113 | 114 | it('named loop (discard after, no finish)', function(done){ 115 | this.base 116 | .uint8('rootFix') 117 | .loop('loop', function(finish, discard, i){ 118 | this 119 | .uint8('var_' + i) 120 | .tap(function(){ 121 | if(this.vars['var_' + i] % 2 !== 0){ 122 | discard(); 123 | } 124 | }); 125 | this.varStack.peek().rootFix++; 126 | if(i % 3 === 0){ 127 | discard(); 128 | } 129 | }); 130 | 131 | this.eqArray([0, 0, 1, 2, 3, 4, 5, 6], done, { 132 | rootFix: 7, 133 | loop: [{ 134 | var_2: 2 135 | }, { 136 | var_4: 4 137 | }] 138 | }); 139 | }); 140 | 141 | it('named loop (discard, finish before)', function(done){ 142 | this.base.loop('loop', function(finish, discard, i){ 143 | if(i >= 3){ 144 | finish(true); 145 | } 146 | this.uint8('var_' + i); 147 | }); 148 | 149 | this.eqArray([0, 1, 2, 3, 4, 5], done, { 150 | loop: [{ 151 | var_0: 0 152 | }, { 153 | var_1: 1 154 | }, { 155 | var_2: 2 156 | }] 157 | }); 158 | }); 159 | 160 | it('named loop (discard, finish after)', function(done){ 161 | this.base.loop('loop', function(finish, discard, i){ 162 | this.uint8('var_' + i); 163 | if(i >= 3){ 164 | finish(true); 165 | } 166 | }); 167 | 168 | this.eqArray([0, 1, 2, 3, 4, 5], done, { 169 | loop: [{ 170 | var_0: 0 171 | }, { 172 | var_1: 1 173 | }, { 174 | var_2: 2 175 | }] 176 | }); 177 | }); 178 | 179 | it('named loops replaced var', function(done){ 180 | this.base.loop('loop', function(){ 181 | this 182 | .uint8('value') 183 | .tap(function(){ 184 | this.vars = this.vars.value; 185 | }); 186 | }); 187 | 188 | this.eqArray([0, 1, 2, 3, 4], done, { 189 | loop: [0, 1, 2, 3, 4] 190 | }); 191 | }); 192 | }); 193 | -------------------------------------------------------------------------------- /test/base-primitves.test.js: -------------------------------------------------------------------------------- 1 | const { expect } = require('chai'); 2 | const Base = require('../src/base'); 3 | 4 | /** @test {CorrodeBase} */ 5 | describe('CorrodeBase - Primitives', () => { 6 | beforeEach(function(){ 7 | this.base = new Base(); 8 | this.eqFile = require('./helpers/asserts').eqFile.bind(this); 9 | this.eqMultiArray = require('./helpers/asserts').eqMultiArray.bind(this); 10 | }); 11 | 12 | /** 13 | * @test {CorrodeBase#int8} 14 | * @test {CorrodeBase#int8le} 15 | * @test {CorrodeBase#int8be} 16 | * @test {CorrodeBase#uint8} 17 | * @test {CorrodeBase#uint8le} 18 | * @test {CorrodeBase#uint8be} 19 | */ 20 | it('reads int8', function(done){ 21 | this.base 22 | .int8('int8') 23 | .int8le('int8le') 24 | .int8be('int8be') 25 | .uint8('uint8') 26 | .uint8le('uint8le') 27 | .uint8be('uint8be') 28 | .int8('int8n') 29 | .int8le('int8len') 30 | .int8be('int8ben'); 31 | 32 | this.eqFile('int8-seq.bin', done, { 33 | int8: 2, 34 | int8le: 4, 35 | int8be: 6, 36 | uint8: 8, 37 | uint8le: 10, 38 | uint8be: 12, 39 | int8n: -14, 40 | int8len: -16, 41 | int8ben: -18 42 | }); 43 | }); 44 | 45 | /** 46 | * @test {CorrodeBase#int16} 47 | * @test {CorrodeBase#int16le} 48 | * @test {CorrodeBase#int16be} 49 | * @test {CorrodeBase#uint16} 50 | * @test {CorrodeBase#uint16le} 51 | * @test {CorrodeBase#uint16be} 52 | */ 53 | it('reads int16', function(done){ 54 | this.base 55 | .int16('int16') 56 | .int16le('int16le') 57 | .int16be('int16be') 58 | .uint16('uint16') 59 | .uint16le('uint16le') 60 | .uint16be('uint16be') 61 | .int16('int16n') 62 | .int16le('int16len') 63 | .int16be('int16ben'); 64 | 65 | this.eqFile('int16-seq.bin', done, { 66 | int16: 2000, 67 | int16le: 4000, 68 | int16be: 6000, 69 | uint16: 34000, 70 | uint16le: 36000, 71 | uint16be: 38000, 72 | int16n: -30000, 73 | int16len: -31000, 74 | int16ben: -32000 75 | }); 76 | }); 77 | 78 | /** 79 | * @test {CorrodeBase#int32} 80 | * @test {CorrodeBase#int32le} 81 | * @test {CorrodeBase#int32be} 82 | * @test {CorrodeBase#uint32} 83 | * @test {CorrodeBase#uint32le} 84 | * @test {CorrodeBase#uint32be} 85 | */ 86 | it('reads int32', function(done){ 87 | this.base 88 | .int32('int32') 89 | .int32le('int32le') 90 | .int32be('int32be') 91 | .uint32('uint32') 92 | .uint32le('uint32le') 93 | .uint32be('uint32be') 94 | .int32('int32n') 95 | .int32le('int32len') 96 | .int32be('int32ben'); 97 | 98 | this.eqFile('int32-seq.bin', done, { 99 | int32: 100000, 100 | int32le: 110000, 101 | int32be: 120000, 102 | uint32: 3000000000, 103 | uint32le: 3100000000, 104 | uint32be: 3200000000, 105 | int32n: -1000000000, 106 | int32len: -1100000000, 107 | int32ben: -1200000000 108 | }); 109 | }); 110 | 111 | /** 112 | * @see http://www.ecma-international.org/ecma-262/5.1/#sec-8.5 113 | * @test {CorrodeBase#int64} 114 | * @test {CorrodeBase#int64le} 115 | * @test {CorrodeBase#int64be} 116 | * @test {CorrodeBase#uint64} 117 | * @test {CorrodeBase#uint64le} 118 | * @test {CorrodeBase#uint64be} 119 | */ 120 | it('reads int64', function(done){ 121 | this.base 122 | .int64('int64') 123 | .int64le('int64le') 124 | .int64be('int64be') 125 | .uint64('uint64') 126 | .uint64le('uint64le') 127 | .uint64be('uint64be') 128 | .int64('int64n') 129 | .int64le('int64len') 130 | .int64be('int64ben'); 131 | 132 | this.eqFile('int64-seq.bin', done, { 133 | int64: 100000, 134 | int64le: 110000, 135 | int64be: 120000, 136 | uint64: 3000000000, 137 | uint64le: 3100000000, 138 | uint64be: 3200000000, 139 | int64n: -1000000000, 140 | int64len: -1100000000, 141 | int64ben: -1200000000 142 | }); 143 | }); 144 | 145 | /** 146 | * @test {CorrodeBase#float} 147 | * @test {CorrodeBase#floatle} 148 | * @test {CorrodeBase#floatbe} 149 | */ 150 | it('reads float', function(done){ 151 | this.base 152 | .float('float') 153 | .floatle('floatle') 154 | .floatbe('floatbe') 155 | .floatle('floatlen') 156 | .floatbe('floatben'); 157 | 158 | // floats are pretty unprecise 159 | this.eqFile('float-seq.bin', done, vars => { 160 | expect(vars.float).to.be.within(1.233, 1.235); 161 | expect(vars.floatle).to.be.within(5.677, 5.679); 162 | expect(vars.floatbe).to.be.within(9.1010, 9.1012); 163 | expect(vars.floatlen).to.be.within(-12.1315, -12.1313); 164 | expect(vars.floatben).to.be.within(-15.1618, -15.1616); 165 | }); 166 | }); 167 | 168 | /** 169 | * @test {CorrodeBase#double} 170 | * @test {CorrodeBase#doublele} 171 | * @test {CorrodeBase#doublebe} 172 | */ 173 | it('reads double', function(done){ 174 | this.base 175 | .double('double') 176 | .doublele('doublele') 177 | .doublebe('doublebe') 178 | .doublele('doublelen') 179 | .doublebe('doubleben'); 180 | 181 | this.eqFile('double-seq.bin', done, { 182 | double: 1.234, 183 | doublele: 5.678, 184 | doublebe: 9.1011, 185 | doublelen: -12.1314, 186 | doubleben: -15.1617 187 | }); 188 | }); 189 | 190 | /** @test {Corrode#string} */ 191 | it('reads utf8-strings', function(done){ 192 | this.base.string('string', 16, 'utf8'); 193 | 194 | this.eqFile('string-utf8.bin', done, { 195 | string: 'asdfghjklyxc𝌆' 196 | }); 197 | }); 198 | 199 | /** @test {Corrode#string} */ 200 | it('reads strings, regardless of the underlying buffer', function(done){ 201 | this.base 202 | .string('hi', 4) 203 | .string('lo', 4); 204 | 205 | this.eqMultiArray([[0x61], [0x62], [0x63, 0x64, 0x65], [0x66, 0x67], [0x68, 9, 10]], done, { 206 | hi: 'abcd', 207 | lo: 'efgh' 208 | }); 209 | }); 210 | 211 | /** @test {Corrode#buffer} */ 212 | it('reads buffers', function(done){ 213 | this.base 214 | .buffer('hi', 4) 215 | .buffer('lo', 4); 216 | 217 | this.eqFile('int64-seq.bin', done, { 218 | hi: Buffer.from([160, 134, 1, 0]), 219 | lo: Buffer.from([0, 0, 0, 0]) 220 | }); 221 | }); 222 | 223 | /** @test {Corrode#buffer} */ 224 | it('reads buffers, regardless of the underlying buffer', function(done){ 225 | this.base 226 | .buffer('hi', 4) 227 | .buffer('lo', 4); 228 | 229 | this.eqMultiArray([[1], [2], [3, 4, 5], [6, 7], [8, 9, 10]], done, { 230 | hi: Buffer.from([1, 2, 3, 4]), 231 | lo: Buffer.from([5, 6, 7, 8]) 232 | }); 233 | }); 234 | }); 235 | -------------------------------------------------------------------------------- /test/base-skip.test.js: -------------------------------------------------------------------------------- 1 | const { expect } = require('chai'); 2 | const Base = require('../src/base'); 3 | 4 | /** @test {CorrodeBase#skip} */ 5 | describe('CorrodeBase#skip', () => { 6 | beforeEach(function(){ 7 | this.base = new Base(); 8 | this.eqArray = require('./helpers/asserts').eqArray.bind(this); 9 | this.eqMultiArray = require('./helpers/asserts').eqMultiArray.bind(this); 10 | }); 11 | 12 | it('allows us to skip content', function(done){ 13 | this.base 14 | .uint8('var_1') 15 | .skip(2) 16 | .uint8('var_2') 17 | .skip(4) 18 | .uint8('var_3'); 19 | 20 | this.eqArray([1, 0, 0, 2, 0, 0, 0, 0, 3], done, { 21 | var_1: 1, 22 | var_2: 2, 23 | var_3: 3 24 | }); 25 | }); 26 | 27 | /** @test {CorrodeBase#isSeeking} */ 28 | it('prevents us from unskipping content with isSeeking = false', function(done){ 29 | this.base 30 | .uint8('var_1') 31 | .skip(2) 32 | .uint8('var_2') 33 | .skip(-3) 34 | .uint8('var_3'); 35 | 36 | expect(this.eqMultiArray.bind(this, [[1], [3], [0], [2]], done, {})).to.throw(RangeError); 37 | 38 | done(); 39 | }); 40 | 41 | /** @test {CorrodeBase#isSeeking} */ 42 | it('allows us to unskip content with isSeeking = true', function(done){ 43 | this.base.isSeeking = true; 44 | 45 | this.base 46 | .uint8('var_1') 47 | .skip(2) 48 | .uint8('var_2') 49 | .skip(-3) 50 | .uint8('var_3'); 51 | 52 | this.eqArray([1, 3, 0, 2], done, { 53 | var_1: 1, 54 | var_2: 2, 55 | var_3: 3 56 | }); 57 | }); 58 | 59 | it('prevents us from unskipping too far', function(){ 60 | this.base 61 | .uint8('var_1') 62 | .skip(-10); 63 | 64 | expect(this.eqArray.bind(this, [1, 2], () => {}, {})).to.throw(RangeError); 65 | }); 66 | }); 67 | -------------------------------------------------------------------------------- /test/base-stack.test.js: -------------------------------------------------------------------------------- 1 | const { expect } = require('chai'); 2 | const Base = require('../src/base'); 3 | 4 | /** @test {CorrodeBase#varStack} */ 5 | describe('CorrodeBase#varStack', () => { 6 | beforeEach(function(){ 7 | this.base = new Base(); 8 | this.eqArray = require('./helpers/asserts').eqArray.bind(this); 9 | }); 10 | 11 | /** @test {CorrodeBase#push} */ 12 | it('pushes and pops', function(done){ 13 | this.base 14 | .uint8('val_0') 15 | .push('child') 16 | .uint8('val_1') 17 | .push('child') 18 | .uint8('val_2') 19 | .pop() 20 | .uint8('val_3') 21 | .pop() 22 | .uint8('val_4'); 23 | 24 | this.eqArray([0, 1, 2, 3, 4, 5], done, { 25 | val_0: 0, 26 | val_4: 4, 27 | child: { 28 | val_1: 1, 29 | val_3: 3, 30 | child: { 31 | val_2: 2, 32 | } 33 | } 34 | }) 35 | }); 36 | 37 | /** @test {CorrodeBase#pop} */ 38 | it('denies popping the root-layer', function(){ 39 | this.base.pop(); 40 | expect(this.eqArray.bind(this, [], () => {}, {})).to.throw(ReferenceError); 41 | }); 42 | 43 | /** 44 | * @test {CorrodeBase#push} 45 | * @test {CorrodeBase#pop} 46 | */ 47 | it('pushes back into old layers', function(done){ 48 | this.base 49 | .uint8('val_0') 50 | .push('child') 51 | .uint8('val_1') 52 | .pop() 53 | .uint8('val_2') 54 | .push('child') 55 | .uint8('val_3') 56 | .pop() 57 | .uint8('val_4'); 58 | 59 | this.eqArray([0, 1, 2, 3, 4], done, { 60 | val_0: 0, 61 | val_2: 2, 62 | val_4: 4, 63 | child: { 64 | val_1: 1, 65 | val_3: 3 66 | } 67 | }); 68 | }); 69 | 70 | /** 71 | * @test {CorrodeBase#push} 72 | * @test {CorrodeBase#finishRemainingJobs} 73 | */ 74 | it('automatically unpushes as it unwinds', function(done){ 75 | this.base 76 | .uint8('var_0') 77 | .push('child') 78 | .uint8('var_1'); 79 | 80 | this.eqArray([0, 1, 2], done, { 81 | var_0: 0, 82 | child: { 83 | var_1: 1 84 | } 85 | }); 86 | }); 87 | 88 | /** 89 | * @test {CorrodeBase#options.strictObjectMode} 90 | */ 91 | it('denies pushing into non-object vars', function(){ 92 | this.base 93 | .uint8('var_0') 94 | .push('var_0') 95 | .uint8('var_1'); 96 | 97 | expect(this.eqArray.bind(this, [0, 1, 2], () => {}, {})).to.throw(TypeError); 98 | }); 99 | }); 100 | -------------------------------------------------------------------------------- /test/base-tap.test.js: -------------------------------------------------------------------------------- 1 | const { expect } = require('chai'); 2 | const Base = require('../src/base'); 3 | 4 | /** @test {CorrodeBase#tap} */ 5 | describe('CorrodeBase#tap', () => { 6 | beforeEach(function(){ 7 | this.base = new Base(); 8 | this.eqArray = require('./helpers/asserts').eqArray.bind(this); 9 | }); 10 | 11 | it('taps into the current state', function(done){ 12 | this.base 13 | .uint8('var_1') 14 | .tap(function(){ 15 | expect(this.vars.var_1).to.equal(1); 16 | }); 17 | 18 | this.eqArray([1, 2, 3], done, { 19 | var_1: 1 20 | }); 21 | }); 22 | 23 | it('taps into a named var', function(done){ 24 | this.base 25 | .uint8('var_1') 26 | .tap('struct', function(){ 27 | this 28 | .uint8('var_2') 29 | .uint8('var_3'); 30 | }); 31 | 32 | this.eqArray([1, 2, 3], done, { 33 | var_1: 1, 34 | struct: { 35 | var_2: 2, 36 | var_3: 3 37 | } 38 | }); 39 | }); 40 | 41 | it('re-taps into existing objects', function(done){ 42 | this.base 43 | .uint8('var_1') 44 | .tap('structure', function(){ 45 | this 46 | .uint8('var_2') 47 | .uint8('var_3') 48 | }) 49 | .uint8('var_4') 50 | .tap('structure', function(){ 51 | this 52 | .uint8('var_5') 53 | .uint8('var_6'); 54 | }); 55 | 56 | this.eqArray([1, 2, 3, 4, 5, 6], done, { 57 | var_1: 1, 58 | var_4: 4, 59 | structure: { 60 | var_2: 2, 61 | var_3: 3, 62 | var_5: 5, 63 | var_6: 6 64 | } 65 | }); 66 | }); 67 | 68 | it('supports custom arguments in anonymous taps', function(done){ 69 | this.base 70 | .uint8('var_1') 71 | .tap(function(subvar_1, subvar_2, subvar_3){ 72 | expect(subvar_1).to.be.true; 73 | expect(subvar_2).to.be.false; 74 | expect(subvar_3).to.be.undefined; 75 | this.uint8('var_3'); 76 | }, [true, false]); 77 | 78 | this.eqArray([1, 2, 3], done, { 79 | var_1: 1, 80 | var_3: 2 81 | }); 82 | }); 83 | 84 | it('supports custom arguments in named taps', function(done){ 85 | this.base 86 | .uint8('var_1') 87 | .tap('structure', function(subvar_1, subvar_2, subvar_3){ 88 | expect(subvar_1).to.be.true; 89 | expect(subvar_2).to.be.false; 90 | expect(subvar_3).to.be.undefined; 91 | this.uint8('var_3'); 92 | }, [true, false]); 93 | 94 | this.eqArray([1, 2, 3], done, { 95 | var_1: 1, 96 | structure: { 97 | var_3: 2 98 | } 99 | }); 100 | }); 101 | 102 | /** @test {CorrodeBase#options.strictObjectMode} */ 103 | it('does not allow tapping into other objects', function(){ 104 | this.base 105 | .uint8('var_1') 106 | .tap('var_1', function(){ 107 | this.uint8('var_2'); 108 | }) 109 | .uint8('var_3'); 110 | 111 | expect(this.eqArray.bind(this, [1, 2, 3], () => {}, {})).to.throw(TypeError); 112 | }); 113 | 114 | /** @test {CorrodeBase#options.strictObjectMode} */ 115 | it('allows tapping into other objects when strictObjectMode is false', function(done){ 116 | this.base = new Base({ strictObjectMode: false }); 117 | 118 | this.base 119 | .uint8('var_1') 120 | .tap('var_1', function(){ 121 | expect(this.vars).to.be.a.number; 122 | }); 123 | 124 | this.eqArray([1, 2, 3], done, { 125 | var_1: 1 126 | }); 127 | }); 128 | }); 129 | -------------------------------------------------------------------------------- /test/corrode-extensions.test.js: -------------------------------------------------------------------------------- 1 | const { expect } = require('chai'); 2 | const Corrode = require('../src'); 3 | 4 | /** 5 | * @test {Corrode#addExtension} 6 | * @test {Corrode#ext} 7 | */ 8 | describe('Corrode#ext', () => { 9 | beforeEach(function(){ 10 | this.base = new Corrode(); 11 | this.eqArray = require('./helpers/asserts').eqArray.bind(this); 12 | }); 13 | 14 | 15 | Corrode.addExtension('simpleRepeat', function(arg1, arg2){ 16 | this 17 | .loop('values', function(end, discard, i){ 18 | if(i >= 3){ 19 | return end(); 20 | } 21 | 22 | this 23 | .uint8(arg1) 24 | .uint8(arg2); 25 | }) 26 | .map.push(); 27 | }); 28 | 29 | 30 | it('supports extensions', function(done){ 31 | this.base.ext.simpleRepeat('extObj', 'val_1', 'val_2'); 32 | 33 | this.eqArray([1, 2, 3, 4, 5, 6], done, { 34 | extObj: [{ 35 | val_1: 1, 36 | val_2: 2 37 | }, { 38 | val_1: 3, 39 | val_2: 4 40 | }, { 41 | val_1: 5, 42 | val_2: 6 43 | }] 44 | }); 45 | }); 46 | 47 | 48 | Corrode.addExtension('simpleReturn', function(arg1, arg2){ 49 | return this.varStack.peek()[arg1] + this.varStack.peek()[arg2]; 50 | }); 51 | 52 | it('supports extensions with return values and scope-access', function(done){ 53 | this.base 54 | .uint8('val_1') 55 | .uint8('val_2') 56 | .ext.simpleReturn('extReturn', 'val_1', 'val_2'); 57 | 58 | this.eqArray([2, 3, 4, 5, 6], done, { 59 | val_1: 2, 60 | val_2: 3, 61 | extReturn: 5 62 | }); 63 | }); 64 | 65 | 66 | Corrode.addExtension('mixReturnReadNonObject', function(){ 67 | this.uint8('fix'); 68 | return 4; // chosen by fair dice roll 69 | }); 70 | 71 | it('supports extensions with return and read', function(){ 72 | this.base 73 | .uint8('var_1') 74 | .ext.mixReturnReadNonObject('extObj'); 75 | 76 | expect(this.eqArray.bind(this, [1, 2, 3], () => {}, {})).to.throw(TypeError); 77 | }); 78 | 79 | 80 | Corrode.addExtension('mixReturnReadObject', function(){ 81 | this.uint8('fix'); 82 | return { 83 | objVal: 'foo' 84 | }; 85 | }); 86 | 87 | it('supports extensions with return and read', function(done){ 88 | this.base 89 | .uint8('var_1') 90 | .ext.mixReturnReadObject('extObj'); 91 | 92 | this.eqArray([1, 2, 3], done, { 93 | var_1: 1, 94 | extObj: { 95 | fix: 2, 96 | objVal: 'foo' 97 | } 98 | }); 99 | }); 100 | 101 | 102 | Corrode.addExtension('simpleRead', function(arg1, arg2){ 103 | this 104 | .uint8(arg1) 105 | .uint8(arg2) 106 | .ext.simpleReturn('added', arg1, arg2); 107 | }); 108 | 109 | Corrode.addExtension('callProxy', function(extName, ...args){ 110 | this 111 | .ext[extName]('values', ...args) 112 | .map.push(); 113 | }); 114 | 115 | it('supports calling extensions within extensions', function(done){ 116 | this.base 117 | .uint8('var_1') 118 | .ext.callProxy('proxyVal1', 'simpleRead', 'val_2', 'val_3') 119 | .ext.callProxy('proxyVal2', 'simpleRead', 'val_4', 'val_5'); 120 | 121 | this.eqArray([1, 2, 3, 4, 5, 6], done, { 122 | var_1: 1, 123 | proxyVal1: { 124 | val_2: 2, 125 | val_3: 3, 126 | added: 5 127 | }, 128 | proxyVal2: { 129 | val_4: 4, 130 | val_5: 5, 131 | added: 9 132 | } 133 | }); 134 | }); 135 | }); 136 | -------------------------------------------------------------------------------- /test/corrode-helper.test.js: -------------------------------------------------------------------------------- 1 | const { expect } = require('chai'); 2 | const Corrode = require('../src'); 3 | 4 | /** @test {Corrode} */ 5 | describe('Corrode - Helpers', () => { 6 | beforeEach(function(){ 7 | this.base = new Corrode(); 8 | this.eqArray = require('./helpers/asserts').eqArray.bind(this); 9 | }); 10 | 11 | /** 12 | * coverage fix 13 | * @test {Corrode#debug} 14 | */ 15 | it('debugs', function(done){ 16 | let output = []; 17 | const orgConsoleLog = console.log; 18 | console.log = (...strings) => output = strings; 19 | 20 | this.base 21 | .loop('array', function(end, discard, i){ 22 | this 23 | .uint8('values') 24 | .map.push(); 25 | }) 26 | .debug(); 27 | 28 | this.eqArray([3, 5, 7], function(){ 29 | expect(output).to.deep.equal([ 30 | '{ array: [ 3, 5, 7 ] }' 31 | ]); 32 | console.log = orgConsoleLog; 33 | done(); 34 | }, { 35 | array: [3, 5, 7], 36 | }); 37 | }); 38 | 39 | /** @test {Corrode#fromBuffer} */ 40 | it('converts from buffer', function(){ 41 | this.base 42 | .loop('array', function(end, discard, i){ 43 | this 44 | .uint8('values') 45 | .map.push(); 46 | }) 47 | .fromBuffer(Buffer.from([0, 1, 2, 3, 4, 5]), vars => { 48 | expect(vars).to.deep.equal({ 49 | array: [0, 1, 2, 3, 4, 5] 50 | }); 51 | }); 52 | }); 53 | }); 54 | -------------------------------------------------------------------------------- /test/corrode-pointer.test.js: -------------------------------------------------------------------------------- 1 | const { expect } = require('chai'); 2 | const Corrode = require('../src'); 3 | 4 | /** @test {Corrode#pointer} */ 5 | describe('Corrode#pointer', () => { 6 | beforeEach(function(){ 7 | this.base = new Corrode(); 8 | this.eqArray = require('./helpers/asserts').eqArray.bind(this); 9 | }); 10 | 11 | it('retrieves from array', function(done){ 12 | this.base 13 | .loop('array', function(end, discard, i){ 14 | if(i >= 3){ 15 | return end(); 16 | } 17 | this 18 | .uint8('values') 19 | .map.push(); 20 | }) 21 | .pointer('alphabet', ['a', 'b', 'c'], 'uint8') 22 | .pointer('numeric', 'array', 'uint8'); 23 | 24 | this.eqArray([3, 5, 7, 1, 2], done, { 25 | array: [3, 5, 7], 26 | alphabet: 'b', 27 | numeric: 7 28 | }); 29 | }); 30 | 31 | it('retrieves from object', function(done){ 32 | this.base 33 | .tap('obj', function(){ 34 | this.loop(function(end, discard, i){ 35 | if(i >= 3){ 36 | return end(); 37 | } 38 | this.uint8(i); 39 | }); 40 | }) 41 | .pointer('alphabet', { 0: 'a', 1: 'b', 2: 'c' }, 'uint8') 42 | .pointer('numeric', 'obj', 'uint8'); 43 | 44 | this.eqArray([3, 5, 7, 1, 2], done, { 45 | obj: { 0: 3, 1: 5, 2: 7}, 46 | alphabet: 'b', 47 | numeric: 7 48 | }); 49 | }); 50 | 51 | // why not? 52 | it('retrieves from string', function(done){ 53 | this.base 54 | .terminatedString('string') 55 | .pointer('numeric', 'string', 'uint8'); 56 | 57 | this.eqArray([0x68, 0x65, 0x6c, 0x6c, 0x6f, 0, 4], done, { 58 | string: 'hello', 59 | numeric: 'o' 60 | }); 61 | }); 62 | }); 63 | -------------------------------------------------------------------------------- /test/corrode-position.test.js: -------------------------------------------------------------------------------- 1 | const { expect } = require('chai'); 2 | const Corrode = require('../src'); 3 | 4 | /** @test {Corrode#position} */ 5 | describe('Corrode#position', () => { 6 | beforeEach(function(){ 7 | this.base = new Corrode(); 8 | this.eqArray = require('./helpers/asserts').eqArray.bind(this); 9 | }); 10 | 11 | it('jumps to absolute positions', function(done){ 12 | this.base.isSeeking = true; 13 | 14 | this.base 15 | .uint8('var_1') 16 | .position(0) 17 | .uint8('var_2') 18 | .position(3) 19 | .uint8('var_3') 20 | .position('var_3') 21 | .uint8('var_4') 22 | 23 | this.eqArray([0, 1, 2, 5, 4, 3, 7], done, { 24 | var_1: 0, 25 | var_2: 0, 26 | var_3: 5, 27 | var_4: 3 28 | }); 29 | }); 30 | 31 | it('prevents jumps to invalid negative positions', function(){ 32 | this.base.isSeeking = true; 33 | 34 | this.base.position(-1); 35 | 36 | expect(this.eqArray.bind(this, [0], () => {}, {})).to.throw(RangeError); 37 | }); 38 | 39 | it('allows jumps to future positive positions', function(done){ 40 | this.base.isSeeking = true; 41 | 42 | this.base 43 | .uint8('var_1') 44 | .position(10) 45 | .uint8('var_2'); 46 | 47 | this.eqArray([0, 1, 2, 3, 4, 5], done, { 48 | var_1: 0 49 | }); 50 | }); 51 | }); 52 | -------------------------------------------------------------------------------- /test/corrode-terminated-buffer.test.js: -------------------------------------------------------------------------------- 1 | const { expect } = require('chai'); 2 | const Corrode = require('../src'); 3 | 4 | /** @test {Corrode#terminatedBuffer} */ 5 | describe('Corrode#terminatedBuffer', () => { 6 | beforeEach(function(){ 7 | this.base = new Corrode(); 8 | this.eqArray = require('./helpers/asserts').eqArray.bind(this); 9 | this.eqMultiArray = require('./helpers/asserts').eqMultiArray.bind(this); 10 | }); 11 | 12 | it('retrieves a terminated buffer', function(done){ 13 | this.base 14 | .terminatedBuffer('buffer_1') 15 | .terminatedBuffer('buffer_2'); 16 | 17 | this.eqArray([3, 5, 7, 0, 9, 1, 2, 4, 0, 6, 8], done, { 18 | buffer_1: Buffer.from([3, 5, 7]), 19 | buffer_2: Buffer.from([9, 1, 2, 4]) 20 | }); 21 | }); 22 | 23 | it('retrieves a terminated buffer spanned over multiple buffers', function(done){ 24 | this.base 25 | .terminatedBuffer('buffer_1') 26 | .terminatedBuffer('buffer_2'); 27 | 28 | this.eqMultiArray([[2, 4, 6], [8], [0], [1, 3], [5, 7], [0, 0]], done, { 29 | buffer_1: Buffer.from([2, 4, 6, 8]), 30 | buffer_2: Buffer.from([1, 3, 5, 7]) 31 | }); 32 | }); 33 | 34 | it('retrieves a terminated buffer spanned over multiple buffers with options', function(done){ 35 | this.base 36 | .terminatedBuffer('buffer_1', 8, false) 37 | .terminatedBuffer('buffer_2', 3) 38 | .uint8('terminator') 39 | .terminatedBuffer('buffer_3', 'terminator'); 40 | 41 | this.eqMultiArray([[2, 4, 6], [8], [0], [1, 3], [5, 7], [0, 0, 2, 5]], done, { 42 | buffer_1: Buffer.from([2, 4, 6, 8]), 43 | buffer_2: Buffer.from([0, 1]), 44 | terminator: 5, 45 | buffer_3: Buffer.from([7, 0, 0, 2]) 46 | }); 47 | }); 48 | 49 | it('retrieves a terminated buffer in a complex parser', function(done){ 50 | this.base 51 | .uint8('terminator_1') 52 | .loop('buffers', function(end, discard, i){ 53 | if(i >= 3){ 54 | return end(); 55 | } 56 | 57 | this 58 | .uint8('prefix') 59 | .terminatedBuffer('buffer', this.varStack.peek().terminator_1) 60 | }) 61 | .terminatedBuffer('buffer_1', 'terminator_1') 62 | .terminatedBuffer('buffer_2', 3); 63 | 64 | this.eqMultiArray([[2, 7, 4, 6], [8, 2], [7, 0], [1, 2, 7, 3], [5, 7, 2], [0, 0, 2, 5, 6, 3]], done, { 65 | terminator_1: 2, 66 | buffers: [{ 67 | prefix: 7, 68 | buffer: Buffer.from([4, 6, 8]) 69 | }, { 70 | prefix: 7, 71 | buffer: Buffer.from([0, 1]) 72 | }, { 73 | prefix: 7, 74 | buffer: Buffer.from([3, 5, 7]) 75 | }], 76 | buffer_1: Buffer.from([0, 0]), 77 | buffer_2: Buffer.from([5, 6]) 78 | }); 79 | }); 80 | }); 81 | -------------------------------------------------------------------------------- /test/corrode-terminated-string.test.js: -------------------------------------------------------------------------------- 1 | const { expect } = require('chai'); 2 | const Corrode = require('../src'); 3 | 4 | /** @test {Corrode#terminatedString} */ 5 | describe('Corrode#terminatedString', () => { 6 | beforeEach(function(){ 7 | this.base = new Corrode(); 8 | this.eqArray = require('./helpers/asserts').eqArray.bind(this); 9 | this.eqMultiArray = require('./helpers/asserts').eqMultiArray.bind(this); 10 | }); 11 | 12 | it('retrieves a terminated string', function(done){ 13 | this.base 14 | .terminatedString('string_1') 15 | .terminatedString('string_2') 16 | .terminatedString('string_3'); 17 | 18 | this.eqArray([0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x00, 0x2c, 0x20, 0x77, 0x6f, 0x72, 0x6c, 0x64, 0x00, 0x6f, 0x6f], done, { 19 | string_1: 'hello', 20 | string_2: ', world', 21 | string_3: '' 22 | }); 23 | }); 24 | 25 | it('retrieves a terminated string spanned over multiple buffers', function(done){ 26 | this.base 27 | .terminatedString('string_1') 28 | .terminatedString('string_2') 29 | .terminatedString('string_3'); 30 | 31 | this.eqMultiArray([[0x68, 0x65], [0x6c], [0x6c, 0x6f, 0x00], [0x2c], [0x20, 0x77], [0x6f, 0x72], [0x6c], [0x64], [0x00, 0x6f, 0x6f]], done, { 32 | string_1: 'hello', 33 | string_2: ', world', 34 | string_3: '' 35 | }); 36 | }); 37 | 38 | it('retrieves a terminated string with options', function(done){ 39 | this.base 40 | .terminatedString('string_1', 0x2c) 41 | .terminatedString('string_2', 0x64, false) 42 | .terminatedString('string_3'); 43 | 44 | this.eqMultiArray([[0x68, 0x65], [0x6c], [0x6c, 0x6f], [0x2c], [0x20, 0xe2, 0x9a, 0xa1, 0x20, 0x77], [0x6f, 0x72], [0x6c], [0x64], [0x00, 0x6f, 0x6f]], done, { 45 | string_1: 'hello', 46 | string_2: ' ⚡ world', 47 | string_3: '' 48 | }); 49 | }); 50 | 51 | it('accepts custom encoding', function(done){ 52 | this.base.terminatedString('string', 0, true, 'ascii'); 53 | 54 | this.eqArray([0xe2, 0x9a, 0xa1, 0x00], done, { 55 | string: 'b\u001a!' 56 | }); 57 | }); 58 | }); 59 | -------------------------------------------------------------------------------- /test/fixtures/double-seq.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/screeny05/corrode/8ef6322ba5b2daee2c90b7f3799a018ed5cf9507/test/fixtures/double-seq.bin -------------------------------------------------------------------------------- /test/fixtures/float-seq.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/screeny05/corrode/8ef6322ba5b2daee2c90b7f3799a018ed5cf9507/test/fixtures/float-seq.bin -------------------------------------------------------------------------------- /test/fixtures/int16-seq.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/screeny05/corrode/8ef6322ba5b2daee2c90b7f3799a018ed5cf9507/test/fixtures/int16-seq.bin -------------------------------------------------------------------------------- /test/fixtures/int32-seq.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/screeny05/corrode/8ef6322ba5b2daee2c90b7f3799a018ed5cf9507/test/fixtures/int32-seq.bin -------------------------------------------------------------------------------- /test/fixtures/int64-seq.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/screeny05/corrode/8ef6322ba5b2daee2c90b7f3799a018ed5cf9507/test/fixtures/int64-seq.bin -------------------------------------------------------------------------------- /test/fixtures/int8-seq.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/screeny05/corrode/8ef6322ba5b2daee2c90b7f3799a018ed5cf9507/test/fixtures/int8-seq.bin -------------------------------------------------------------------------------- /test/fixtures/string-utf8.bin: -------------------------------------------------------------------------------- 1 | asdfghjklyxc𝌆 2 | -------------------------------------------------------------------------------- /test/fixtures/vars.js: -------------------------------------------------------------------------------- 1 | const getFixture = () => ({ 2 | string: 'fixture', 3 | string2: 'fixture', 4 | number: 1337, 5 | object: { 6 | fixture: 'string', 7 | has: 'to', 8 | deep: ['equal', 'this', 1337], 9 | me: 1337 10 | }, 11 | objectWithSameValues: { 12 | val1: 'fixture', 13 | val2: 'fixture', 14 | val3: 'fixture', 15 | val4: 'fixture', 16 | }, 17 | arrayWithSameValues: ['fixture', 'fixture', 'fixture', 'fixture'], 18 | array: ['fixture', { object: 'foo' }, 1337], 19 | objectArray: [{ id: 1, name: 'foobar' }, { id: 2, name: 'lorem' }, { id: 2, name: 'ipsum' }, { id: 4, name: 'quxbaz' }], 20 | negative: -1, 21 | zero: 0, 22 | one: 1, 23 | two: 2, 24 | three: 3, 25 | id: 4, 26 | untrimmed: '\r\nfoobar ', 27 | trimmed: 'foobar', 28 | bitmask1: 0b10000000, 29 | bitmask2: 0b01000000, 30 | bitmaskMatch: 0b10111110 31 | }); 32 | 33 | module.exports = getFixture(); 34 | module.exports.clone = getFixture; 35 | -------------------------------------------------------------------------------- /test/helpers/asserts.js: -------------------------------------------------------------------------------- 1 | require('buffer-safe'); 2 | const fs = require('fs'); 3 | const { expect } = require('chai'); 4 | 5 | /** 6 | * parse a file and compare the result against a provided fixture 7 | * @param {string} file file-path 8 | * @param {Function} done mocha-callback 9 | * @param {object} obj expected result 10 | * @throws {Error} assertion error 11 | */ 12 | module.exports.eqFile = function(file, done, obj){ 13 | fs.createReadStream(__dirname + '/../fixtures/' + file) 14 | .pipe(this.base) 15 | .on('finish', () => { 16 | if(typeof obj === 'function'){ 17 | obj.call(this, this.base.vars); 18 | } else { 19 | expect(this.base.vars).to.deep.equal(obj); 20 | } 21 | done(); 22 | }); 23 | }; 24 | 25 | /** 26 | * parse an array and compare the result against a provided fixture 27 | * @param {Array} arr array getting converted to buffer and fed to corrode 28 | * @param {Function} done mocha-callback 29 | * @param {object} obj expected result 30 | * @throws {Error} assertion error 31 | */ 32 | module.exports.eqArray = function(arr, done, obj){ 33 | let arrMiddle = Math.floor(arr.length / 2); 34 | let arrFirst = arr.slice(0, arrMiddle); 35 | let arrSecond = arr.slice(arrMiddle); 36 | let arrData = [arrFirst, arrSecond]; 37 | 38 | // uncomment this line to create a new buffer for each byte (may be overkill) 39 | //arrData = arr.map(val => [val]); 40 | module.exports.eqMultiArray.call(this, arrData, done, obj); 41 | }; 42 | 43 | 44 | /** 45 | * parse an array of arrays and compare the result against a provided fixture 46 | * @param {Array} arr array getting converted to buffer and fed to corrode 47 | * @param {Function} done mocha-callback 48 | * @param {object} obj expected result 49 | * @throws {Error} assertion error 50 | */ 51 | module.exports.eqMultiArray = function(arrs, done, obj){ 52 | // i sometimes forget to do this 53 | if(typeof done !== 'function'){ 54 | throw new Error('done not given'); 55 | } 56 | 57 | arrs.forEach(arr => this.base.write(Buffer.from(arr))); 58 | this.base.end(); 59 | this.base.on('finish', () => { 60 | if(typeof obj === 'function'){ 61 | obj(this.base.vars); 62 | return done(); 63 | } 64 | expect(this.base.vars).to.deep.equal(obj); 65 | done(); 66 | }); 67 | }; 68 | -------------------------------------------------------------------------------- /test/map.test.js: -------------------------------------------------------------------------------- 1 | const expect = require('chai').expect; 2 | const fixture = require('./fixtures/vars'); 3 | const map = require('../src/map'); 4 | 5 | describe('Map', () => { 6 | beforeEach(function(){ 7 | this.fixture = fixture.clone(); 8 | 9 | this.map = function(fn, on, ...args){ 10 | map[fn].call({ vars: this.fixture }, on, ...args); 11 | return this.fixture[on]; 12 | }; 13 | }); 14 | 15 | /** @test {map} */ 16 | it('maps via callback', function(){ 17 | expect(this.map('callback', 'number', val => val * 2)).to.equal(fixture.number * 2); 18 | }); 19 | 20 | /** @test {get} */ 21 | it('maps via get - array', function(){ 22 | expect(this.map('get', 'zero', ['fixture'])).to.equal('fixture'); 23 | expect(this.map('get', 'one', [0, {}])).to.be.object; 24 | expect(this.map('get', 'two', [0, 0, {}])).to.be.empty; 25 | }); 26 | 27 | /** @test {get} */ 28 | it('maps via get - object', function(){ 29 | expect(this.map('get', 'string', { fixture: 'string' })).to.equal('string'); 30 | expect(this.map('get', 'string2', { fixture: {} })).to.be.object; 31 | }); 32 | 33 | /** @test {findAll} */ 34 | it('maps via findAll', function(){ 35 | expect(this.map('findAll', 'one', fixture.objectArray, 'id')).to.deep.equal([fixture.objectArray[0]]); 36 | expect(this.map('findAll', 'two', fixture.objectArray, 'id')).to.have.length.of(2); 37 | expect(this.map('findAll', 'id', fixture.objectArray, 'id')[0].name).to.equal('quxbaz'); 38 | expect(this.map.bind(this, 'findAll', 'three', fixture.objectArray, 'id')).to.throw(Error); 39 | }); 40 | 41 | /** @test {find} */ 42 | it('maps via find', function(){ 43 | expect(this.map('find', 'one', fixture.objectArray, 'id')).to.deep.equal(fixture.objectArray[0]); 44 | expect(this.map('find', 'id', fixture.objectArray, 'id').name).to.equal('quxbaz'); 45 | expect(this.map.bind(this, 'find', 'three', fixture.objectArray, 'id')).to.throw(Error); 46 | }); 47 | 48 | /** @test {abs} */ 49 | it('maps via abs', function(){ 50 | expect(this.map('abs', 'negative')).to.equal(1); 51 | expect(this.map('abs', 'two')).to.equal(2); 52 | }); 53 | 54 | /** @test {invert} */ 55 | it('maps via invert', function(){ 56 | expect(this.map('invert', 'negative')).to.equal(1); 57 | expect(this.map('invert', 'three')).to.equal(-3); 58 | }); 59 | 60 | /** @test {trim} */ 61 | it('maps via trim', function(){ 62 | expect(this.map('trim', 'untrimmed')).to.equal(fixture.trimmed); 63 | expect(this.map('trim', 'trimmed')).to.equal(fixture.trimmed); 64 | }); 65 | 66 | /** @test {push} */ 67 | it('pushes', function(){ 68 | let that = { vars: fixture.clone() }; 69 | map.push.call(that, 'object'); 70 | expect(that.vars).to.deep.equal(fixture.object); 71 | }); 72 | 73 | /** @test {bitmask} */ 74 | it('maps via bitmask', function(){ 75 | expect(this.map('bitmask', 'bitmaskMatch', { 76 | isX80: 0x80, 77 | isX40: 0x40, 78 | isX20: 0x20, 79 | isX10: 0x10, 80 | isX08: 0x08, 81 | isX04: 0x04, 82 | isX02: 0x02, 83 | isX01: 0x01 84 | })).to.deep.equal({ 85 | isX80: true, 86 | isX40: false, 87 | isX20: true, 88 | isX10: true, 89 | isX08: true, 90 | isX04: true, 91 | isX02: true, 92 | isX01: false 93 | }); 94 | }); 95 | 96 | /** @test {bitmask} */ 97 | it('maps single values via bitmask', function(){ 98 | expect(this.map('bitmask', 'bitmaskMatch', 0x80)).to.be.true; 99 | }); 100 | }); 101 | -------------------------------------------------------------------------------- /test/utils.test.js: -------------------------------------------------------------------------------- 1 | const expect = require('chai').expect; 2 | const utils = require('../src/utils'); 3 | 4 | describe('Utils', () => { 5 | beforeEach(function(){ 6 | this.context = { 7 | fixture: 'fixture' 8 | }; 9 | }); 10 | 11 | /** @test {tapBindObject} */ 12 | it('binds object with tap', function(){ 13 | let boundFixture = utils.tapBindObject({ 14 | fnFixture: function(argOne, argTwo){ 15 | expect(argOne).to.equal('foo'); 16 | expect(argTwo).to.equal('bar'); 17 | } 18 | }, { 19 | tap: function(fn){ 20 | return fn; 21 | }, 22 | ...this.context 23 | }); 24 | 25 | boundFixture.fnFixture('foo', 'bar')(); 26 | }); 27 | 28 | /** @test {bindObject} */ 29 | it('binds object with data', function(){ 30 | let obj = { 31 | fnFixture: function(){ 32 | expect(this).to.not.be.empty; 33 | expect(this.fixture).to.equal('fixture'); 34 | }, 35 | fnEmpty: function(){ 36 | expect(this).to.be.empty; 37 | } 38 | }; 39 | 40 | let boundFixture = utils.bindObject(obj, this.context); 41 | boundFixture.fnFixture(); 42 | 43 | let boundObj = utils.bindObject(obj, {}); 44 | boundObj.fnEmpty(); 45 | }); 46 | }); 47 | --------------------------------------------------------------------------------