mirror of
https://github.com/scratchfoundation/scratch-parser.git
synced 2025-08-21 10:38:53 -04:00
Initial functioning parser
This commit is contained in:
parent
4642c66e08
commit
a0a03ef679
36 changed files with 113034 additions and 102 deletions
3
Makefile
3
Makefile
|
@ -5,6 +5,9 @@ TAP=./node_modules/.bin/tap
|
|||
# ------------------------------------------------------------------------------
|
||||
|
||||
lint:
|
||||
$(ESLINT) ./*.js
|
||||
$(ESLINT) ./bin/*.js
|
||||
$(ESLINT) ./lib/*.js
|
||||
$(ESLINT) ./test/**/*.js
|
||||
|
||||
test:
|
||||
|
|
73
README.md
73
README.md
|
@ -1,39 +1,68 @@
|
|||
## scratch-parser
|
||||
#### Streaming parser for Scratch projects
|
||||
# scratch-parser
|
||||
#### Parser for Scratch projects
|
||||
|
||||
## Installation
|
||||
[](https://travis-ci.com/LLK/scratch-parser)
|
||||
[](https://david-dm.org/llk/scratch-parser)
|
||||
[](https://david-dm.org/llk/scratch-parser#info=devDependencies)
|
||||
|
||||
## Overview
|
||||
The Scratch Parser is a [Node.js](https://nodejs.org) module that parses and validates [Scratch](https://scratch.mit.edu) projects. Internally, this utility is used for validation of Scratch projects as well as for extracting metadata from projects for research and search purposes.
|
||||
|
||||
## API
|
||||
|
||||
#### Installation
|
||||
```bash
|
||||
npm install scratch-parser
|
||||
```
|
||||
|
||||
## Interface
|
||||
|
||||
#### HTTP Stream
|
||||
```js
|
||||
var parser = require('scratch-parser');
|
||||
|
||||
parser({
|
||||
method: 'GET',
|
||||
url: 'https://scratch.mit.edu/path/to/project.json'
|
||||
}, function (err, callback) {
|
||||
if (err) // handle the error
|
||||
// do something interesting
|
||||
});
|
||||
```
|
||||
|
||||
#### File Stream
|
||||
#### Basic Use
|
||||
```js
|
||||
var fs = require('fs');
|
||||
var parser = require('scratch-parser');
|
||||
var scratchp = require('scratch-parser');
|
||||
|
||||
var stream = fs.createReadStream('/path/to/project.json');
|
||||
parser(stream, function (err, callback) {
|
||||
var buffer = fs.readFileSync('/path/to/project.sb2');
|
||||
scratchp(buffer, function (err, project) {
|
||||
if (err) // handle the error
|
||||
// do something interesting
|
||||
});
|
||||
```
|
||||
|
||||
## Metadata
|
||||
| Key | Attributes |
|
||||
| ----------------- | -------------------------------------------------------- |
|
||||
| `scripts` | `count` |
|
||||
| `blocks` | `count`, `unique`, `list`, `frequency` |
|
||||
| `sprites` | `count` |
|
||||
| `variables` | `count` |
|
||||
| `lists` | `count` |
|
||||
| `costumes` | `count`, `list`, `hash` |
|
||||
| `sounds` | `count`, `list`, `hash` |
|
||||
| `extensions` | `count`, `list` |
|
||||
| `comments` | `count` |
|
||||
|
||||
#### "Info"
|
||||
In addition to the `_meta` data described above, Scratch projects include an attribute called `info` that *may* include the following:
|
||||
|
||||
| Key | Description |
|
||||
| ----------------- | -------------------------------------------------------- |
|
||||
| `flashVersion` | Installed version of Adobe Flash |
|
||||
| `swfVersion` | Version of the Scratch editor used to create the project |
|
||||
| `userAgent` | User agent used to create the project |
|
||||
| `savedExtensions` | Array of Scratch Extensions used in the project |
|
||||
|
||||
## Testing
|
||||
|
||||
#### Running the Test Suite
|
||||
```bash
|
||||
npm test
|
||||
```
|
||||
|
||||
#### Code Coverage Report
|
||||
```bash
|
||||
make coverage
|
||||
```
|
||||
|
||||
#### Performance Benchmarks / Stress Testing
|
||||
```bash
|
||||
make benchmark
|
||||
```
|
||||
|
|
30
index.js
30
index.js
|
@ -1,13 +1,25 @@
|
|||
var validate = require('./lib/validate');
|
||||
var async = require('async');
|
||||
|
||||
var unpack = require('./lib/unpack');
|
||||
var parse = require('./lib/parse');
|
||||
var validate = require('./lib/validate');
|
||||
var analyze = require('./lib/analyze');
|
||||
|
||||
/**
|
||||
* Unpacks, parses, validates, and analyzes Scratch projects. If successful,
|
||||
* will return a valid Scratch project object with appended metadata.
|
||||
*
|
||||
* @param {Buffer} Input buffer
|
||||
*
|
||||
* @return {Object}
|
||||
*/
|
||||
module.exports = function (input, callback) {
|
||||
parse(input, function (err, project) {
|
||||
if (err) return callback(err);
|
||||
|
||||
validate(project, function (err) {
|
||||
if (err) return callback(err);
|
||||
callback(null, project);
|
||||
});
|
||||
});
|
||||
async.waterfall([
|
||||
function (cb) {
|
||||
unpack(input, cb);
|
||||
},
|
||||
parse,
|
||||
validate,
|
||||
analyze
|
||||
], callback);
|
||||
};
|
||||
|
|
190
lib/analyze.js
Normal file
190
lib/analyze.js
Normal file
|
@ -0,0 +1,190 @@
|
|||
/**
|
||||
* Returns an array of items matching the specified attribute.
|
||||
*
|
||||
* @param {Object} Project
|
||||
* @param {String} Attribute to extract and flatten
|
||||
*
|
||||
* @return {Array}
|
||||
*/
|
||||
function flatten (project, attribute) {
|
||||
// Storage object
|
||||
var result = [];
|
||||
|
||||
// If attribute exists at the top level of the project, append it
|
||||
if (typeof project[attribute] !== 'undefined') {
|
||||
result = project[attribute];
|
||||
}
|
||||
|
||||
// Iterate over child elements and append to result array
|
||||
for (var i in project.children) {
|
||||
var child = project.children[i];
|
||||
if (typeof child[attribute] !== 'undefined') {
|
||||
result = result.concat(child[attribute]);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract summary information from a specific project attribute. Will attempt
|
||||
* to concatinate all children when generating summary.
|
||||
*
|
||||
* @param {Object} Project
|
||||
* @param {String} Attribute key
|
||||
* @param {String, Optional} "id" key
|
||||
* @param {String, Optional} "hash" key
|
||||
*
|
||||
* @return {Object}
|
||||
*/
|
||||
function extract (project, attribute, id, hash) {
|
||||
// Create storage objects and flatten project
|
||||
var idList = null;
|
||||
var hashList = null;
|
||||
var elements = flatten(project, attribute);
|
||||
|
||||
// Extract ids if specified
|
||||
if (typeof id !== 'undefined') {
|
||||
idList = [];
|
||||
for (var x in elements) {
|
||||
idList.push(elements[x][id]);
|
||||
}
|
||||
}
|
||||
|
||||
// Extract hashes if specified
|
||||
if (typeof hash !== 'undefined') {
|
||||
hashList = [];
|
||||
for (var y in elements) {
|
||||
hashList.push(elements[y][hash]);
|
||||
}
|
||||
}
|
||||
|
||||
// Build result and return
|
||||
var result = {
|
||||
count: elements.length
|
||||
};
|
||||
if (idList !== null) result.id = idList;
|
||||
if (hashList !== null) result.hash = hashList;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tallys term frequency from an array of strings.
|
||||
*
|
||||
* @param {Array} Array of strings
|
||||
*
|
||||
* @return {Object}
|
||||
*/
|
||||
function frequency (input) {
|
||||
var result = Object.create(null);
|
||||
|
||||
for (var i in input) {
|
||||
var term = input[i];
|
||||
if (typeof result[term] === 'undefined') result[term] = 0;
|
||||
result[term]++;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract blocks and generate frequency count.
|
||||
*
|
||||
* @param {Object} Project
|
||||
*
|
||||
* @return {Object}
|
||||
*/
|
||||
function blocks (project) {
|
||||
// Storage objects
|
||||
var blocks = [];
|
||||
|
||||
// Walk scripts array(s) and build block list
|
||||
function walk (stack) {
|
||||
for (var i in stack) {
|
||||
// Skip if item is not array
|
||||
if (!Array.isArray(stack[i])) continue;
|
||||
|
||||
// Recurse if first item is not a string
|
||||
if (typeof stack[i][0] !== 'string') {
|
||||
walk(stack[i]);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Add to block list
|
||||
blocks.push(stack[i][0]);
|
||||
|
||||
// Don't pull in params from procedures
|
||||
if (stack[i][0] === 'procDef') continue;
|
||||
|
||||
// Move to next item and walk
|
||||
walk(stack[i][0].slice(1));
|
||||
}
|
||||
}
|
||||
walk(flatten(project, 'scripts'));
|
||||
|
||||
// Generate frequency count
|
||||
var freq = frequency(blocks);
|
||||
|
||||
// Build result and return
|
||||
return {
|
||||
count: blocks.length,
|
||||
unique: Object.keys(freq).length,
|
||||
id: blocks,
|
||||
frequency: freq
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract extensions list.
|
||||
*
|
||||
* @param {Object} Project
|
||||
*
|
||||
* @return {Object}
|
||||
*/
|
||||
function extensions (project) {
|
||||
var result = {count: 0, id: []};
|
||||
|
||||
// Check to ensure project includes any extensions
|
||||
if (typeof project.info.savedExtensions === 'undefined') return result;
|
||||
|
||||
// Iterate over extensions and build list
|
||||
var ext = project.info.savedExtensions;
|
||||
for (var i in ext) {
|
||||
result.id.push(ext[i].extensionName);
|
||||
}
|
||||
|
||||
// Count and return
|
||||
result.count = result.id.length;
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Analyzes project and appends metadata to the project object.
|
||||
*
|
||||
* @param {Object} Project
|
||||
*
|
||||
* @return {Object}
|
||||
*/
|
||||
module.exports = function (project, callback) {
|
||||
// Create metadata object
|
||||
var meta = {
|
||||
sprites: extract(project, 'children'),
|
||||
scripts: extract(project, 'scripts'),
|
||||
variables: extract(project, 'variables', 'name'),
|
||||
lists: extract(project, 'lists', 'listName'),
|
||||
comments: extract(project, 'scriptComments'),
|
||||
sounds: extract(project, 'sounds', 'soundName', 'md5'),
|
||||
costumes: extract(project, 'costumes', 'costumeName', 'baseLayerMD5')
|
||||
};
|
||||
|
||||
// Blocks
|
||||
meta.blocks = blocks(project);
|
||||
|
||||
// Extensions
|
||||
meta.extensions = extensions(project);
|
||||
|
||||
// Bind metadata to project and return
|
||||
project._meta = meta;
|
||||
callback(null, project);
|
||||
};
|
74
lib/parse.js
74
lib/parse.js
|
@ -1,63 +1,17 @@
|
|||
var oboe = require('oboe');
|
||||
var once = require('once');
|
||||
|
||||
/**
|
||||
* Converts string from unpack method into a project object. Note: this method
|
||||
* will be expanded greatly in the future in order to support the Scratch 1.4
|
||||
* file format. For now, this is nothing but an (awkward) async wrapper around
|
||||
* the `JSON.parse` function.
|
||||
*
|
||||
* @param {String} Input
|
||||
*
|
||||
* @return {Object}
|
||||
*/
|
||||
module.exports = function (input, callback) {
|
||||
// Ensure callback is only called once
|
||||
callback = once(callback);
|
||||
|
||||
// Metadata storage object
|
||||
var _meta = {
|
||||
assets: [],
|
||||
blocks: [],
|
||||
variables: []
|
||||
};
|
||||
|
||||
// Node handlers
|
||||
function _infoHandler (obj) {
|
||||
return obj;
|
||||
try {
|
||||
callback(null, JSON.parse(input));
|
||||
} catch (e) {
|
||||
return callback(e.toString());
|
||||
}
|
||||
|
||||
function _costumeHandler (obj) {
|
||||
return obj;
|
||||
}
|
||||
|
||||
function _listHandler (obj) {
|
||||
return obj;
|
||||
}
|
||||
|
||||
function _soundHandler (obj) {
|
||||
return obj;
|
||||
}
|
||||
|
||||
function _variableHandler (obj) {
|
||||
return obj;
|
||||
}
|
||||
|
||||
// Start parser
|
||||
oboe(input)
|
||||
// Meta
|
||||
.node('info', _infoHandler)
|
||||
|
||||
// Stage
|
||||
.node('costumes.*', _costumeHandler)
|
||||
.node('lists.*', _listHandler)
|
||||
.node('sounds.*', _soundHandler)
|
||||
.node('variables.*', _variableHandler)
|
||||
|
||||
// Sprites
|
||||
.node('children.*.costumes.*', _costumeHandler)
|
||||
.node('children.*.lists.*', _listHandler)
|
||||
.node('children.*.sounds.*', _soundHandler)
|
||||
.node('children.*.variables.*', _variableHandler)
|
||||
|
||||
// Error handler
|
||||
.fail(function (err) {
|
||||
callback(err);
|
||||
})
|
||||
|
||||
// Return
|
||||
.done(function () {
|
||||
var result = this.root();
|
||||
callback(null, result);
|
||||
});
|
||||
};
|
||||
|
|
170
lib/schema.json
170
lib/schema.json
|
@ -6,11 +6,121 @@
|
|||
"objName": {
|
||||
"type": "string"
|
||||
},
|
||||
"sounds": {
|
||||
"variables": {
|
||||
"type": "array",
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"value": {
|
||||
"type": "string"
|
||||
},
|
||||
"isPersistent": {
|
||||
"type": "boolean"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"name",
|
||||
"value"
|
||||
]
|
||||
},
|
||||
"lists": {
|
||||
"type": "array",
|
||||
"properties": {
|
||||
"listName": {
|
||||
"type": "string"
|
||||
},
|
||||
"contents": {
|
||||
"type": "array"
|
||||
},
|
||||
"isPersistent": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"x": {
|
||||
"type": "number"
|
||||
},
|
||||
"y": {
|
||||
"type": "number"
|
||||
},
|
||||
"width": {
|
||||
"type": "number"
|
||||
},
|
||||
"height": {
|
||||
"type": "number"
|
||||
},
|
||||
"visible": {
|
||||
"type": "boolean"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"listName",
|
||||
"contents"
|
||||
]
|
||||
},
|
||||
"scripts": {
|
||||
"type": "array"
|
||||
},
|
||||
"sounds": {
|
||||
"type": "array",
|
||||
"properties": {
|
||||
"soundName": {
|
||||
"type": "string"
|
||||
},
|
||||
"soundID": {
|
||||
"type": "number"
|
||||
},
|
||||
"md5": {
|
||||
"type": "string"
|
||||
},
|
||||
"sampleCount": {
|
||||
"type": "number"
|
||||
},
|
||||
"rate": {
|
||||
"type": "number"
|
||||
},
|
||||
"format": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"soundName",
|
||||
"soundID",
|
||||
"md5",
|
||||
"sampleCount",
|
||||
"rate",
|
||||
"format"
|
||||
]
|
||||
},
|
||||
"costumes": {
|
||||
"type": "array"
|
||||
"type": "array",
|
||||
"properties": {
|
||||
"costumeName": {
|
||||
"type": "string"
|
||||
},
|
||||
"baseLayerID": {
|
||||
"type": "number"
|
||||
},
|
||||
"baseLayerMD5": {
|
||||
"type": "string"
|
||||
},
|
||||
"bitmapResolution": {
|
||||
"type": "number"
|
||||
},
|
||||
"rotationCenterX": {
|
||||
"type": "number"
|
||||
},
|
||||
"rotationCenterY": {
|
||||
"type": "number"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"costumeName",
|
||||
"baseLayerID",
|
||||
"baseLayerMD5",
|
||||
"bitmapResolution",
|
||||
"rotationCenterX",
|
||||
"rotationCenterY"
|
||||
]
|
||||
},
|
||||
"currentCostumeIndex": {
|
||||
"type": "number",
|
||||
|
@ -28,13 +138,62 @@
|
|||
},
|
||||
"videoAlpha": {
|
||||
"type": "number",
|
||||
"minimum": 0
|
||||
"minimum": 0,
|
||||
"maximum": 1
|
||||
},
|
||||
"children": {
|
||||
"type": "array"
|
||||
"type": "array",
|
||||
"properties": {},
|
||||
"required": [
|
||||
"objName",
|
||||
"sounds",
|
||||
"costumes",
|
||||
"currentCostumeIndex"
|
||||
]
|
||||
},
|
||||
"info": {
|
||||
"type": "object"
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"flashVersion": {
|
||||
"type": "string"
|
||||
},
|
||||
"swfVersion": {
|
||||
"type": "string"
|
||||
},
|
||||
"userAgent": {
|
||||
"type": "string"
|
||||
},
|
||||
"videoOn": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"savedExtensions": {
|
||||
"type": "array",
|
||||
"properties": {
|
||||
"extensionName": {
|
||||
"type": "string"
|
||||
},
|
||||
"blockSpecs": {
|
||||
"type": "array"
|
||||
},
|
||||
"menus": {
|
||||
"type": "object"
|
||||
},
|
||||
"javascriptURL": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"extensionName",
|
||||
"blockSpecs",
|
||||
"menus",
|
||||
"javascriptURL"
|
||||
]
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"flashVersion",
|
||||
"swfVersion"
|
||||
]
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
|
@ -43,7 +202,6 @@
|
|||
"costumes",
|
||||
"currentCostumeIndex",
|
||||
"penLayerMD5",
|
||||
"penLayerID",
|
||||
"tempoBPM",
|
||||
"videoAlpha",
|
||||
"children",
|
||||
|
|
39
lib/unpack.js
Normal file
39
lib/unpack.js
Normal file
|
@ -0,0 +1,39 @@
|
|||
var Adm = require('adm-zip');
|
||||
|
||||
/**
|
||||
* Transforms buffer into a UTF-8 string. If input is encoded in ZIP format,
|
||||
* the input will be extracted and decoded.
|
||||
*
|
||||
* @param {Buffer} Input
|
||||
*
|
||||
* @return {String}
|
||||
*/
|
||||
module.exports = function (input, callback) {
|
||||
// Validate input type
|
||||
var typeError = 'Input must be a Buffer.';
|
||||
if (!Buffer.isBuffer(input)) return callback(typeError);
|
||||
|
||||
// Determine format
|
||||
// We don't use the file suffix as this is unreliable and mine-type
|
||||
// information is unavailable from Scratch's project CDN. Instead, we look
|
||||
// at the first few bytes from the provided buffer (byte signature).
|
||||
// https://en.wikipedia.org/wiki/List_of_file_signatures
|
||||
var signature = input.slice(0, 3).join(' ');
|
||||
var isLegacy = false;
|
||||
var isZip = false;
|
||||
|
||||
if (signature.indexOf('83 99 114') === 0) isLegacy = true;
|
||||
if (signature.indexOf('80 75') === 0) isZip = true;
|
||||
|
||||
// If not legacy or zip, convert buffer to UTF-8 string and return
|
||||
if (!isZip && !isLegacy) return callback(null, input.toString('utf-8'));
|
||||
|
||||
// Return error if legacy encoding detected
|
||||
if (isLegacy) return callback('Parser only supports Scratch 2.X');
|
||||
|
||||
// Handle zip
|
||||
// @todo Handle error
|
||||
var zip = new Adm(input);
|
||||
var project = zip.readAsText('project.json', 'utf8');
|
||||
callback(null, project);
|
||||
};
|
|
@ -6,5 +6,5 @@ module.exports = function (input, callback) {
|
|||
var valid = validate(input);
|
||||
|
||||
if (!valid) return callback(validate.errors);
|
||||
callback();
|
||||
callback(null, input);
|
||||
};
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"name": "scratch-parser",
|
||||
"version": "1.0.0",
|
||||
"description": "Streaming parser for Scratch projects",
|
||||
"description": "Parser for Scratch projects",
|
||||
"author": "MIT Media Lab",
|
||||
"license": "BSD-3-Clause",
|
||||
"homepage": "https://github.com/LLK/scratch-parser#readme",
|
||||
|
@ -14,12 +14,12 @@
|
|||
"test": "make test"
|
||||
},
|
||||
"dependencies": {
|
||||
"adm-zip": "0.4.7",
|
||||
"ajv": "3.4.0",
|
||||
"oboe": "2.1.2",
|
||||
"once": "1.3.3"
|
||||
"async": "1.5.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"benchmark": "2.0.0",
|
||||
"benchmark": "^2.0.0",
|
||||
"eslint": "^1.10.3",
|
||||
"glob": "^6.0.4",
|
||||
"tap": "^5.1.1"
|
||||
|
|
52
test/benchmark/performance.js
Normal file
52
test/benchmark/performance.js
Normal file
|
@ -0,0 +1,52 @@
|
|||
var Benchmark = require('benchmark');
|
||||
var suite = new Benchmark.Suite();
|
||||
|
||||
var data = require('../fixtures/data');
|
||||
var parser = require('../../index');
|
||||
|
||||
// Run suite
|
||||
suite
|
||||
.add({
|
||||
name: 'JSON - Empty',
|
||||
defer: true,
|
||||
fn: function (deferred) {
|
||||
parser(data.empty.json, function () {
|
||||
deferred.resolve();
|
||||
});
|
||||
}
|
||||
})
|
||||
.add({
|
||||
name: 'SB2 - Empty',
|
||||
defer: true,
|
||||
fn: function (deferred) {
|
||||
parser(data.empty.sb2, function () {
|
||||
deferred.resolve();
|
||||
});
|
||||
}
|
||||
})
|
||||
.add({
|
||||
name: 'JSON - Example',
|
||||
defer: true,
|
||||
fn: function (deferred) {
|
||||
parser(data.example.json, function () {
|
||||
deferred.resolve();
|
||||
});
|
||||
}
|
||||
})
|
||||
.add({
|
||||
name: 'SB2 - Example',
|
||||
defer: true,
|
||||
fn: function (deferred) {
|
||||
parser(data.example.sb2, function () {
|
||||
deferred.resolve();
|
||||
});
|
||||
}
|
||||
})
|
||||
.on('cycle', function (event) {
|
||||
process.stdout.write(String(event.target) + '\n');
|
||||
})
|
||||
.run({
|
||||
async: false,
|
||||
minSamples: 100,
|
||||
delay: 2
|
||||
});
|
30
test/fixtures/data.js
vendored
Normal file
30
test/fixtures/data.js
vendored
Normal file
|
@ -0,0 +1,30 @@
|
|||
var fs = require('fs');
|
||||
var glob = require('glob');
|
||||
var path = require('path');
|
||||
|
||||
// Build file listings
|
||||
var sb = glob.sync(path.resolve(__dirname, './data/*.sb'));
|
||||
var sb2 = glob.sync(path.resolve(__dirname, './data/*.sb2'));
|
||||
var json = glob.sync(path.resolve(__dirname, './data/*.json'));
|
||||
|
||||
// Read files and convert to buffers
|
||||
for (var x in sb) sb[x] = fs.readFileSync(sb[x]);
|
||||
for (var y in sb2) sb2[y] = fs.readFileSync(sb2[y]);
|
||||
for (var z in json) json[z] = fs.readFileSync(json[z]);
|
||||
|
||||
// Return listings
|
||||
module.exports = {
|
||||
empty: {
|
||||
sb: fs.readFileSync(path.resolve(__dirname, './data/_empty.sb')),
|
||||
sb2: fs.readFileSync(path.resolve(__dirname, './data/_empty.sb2')),
|
||||
json: fs.readFileSync(path.resolve(__dirname, './data/_empty.json'))
|
||||
},
|
||||
example: {
|
||||
sb: fs.readFileSync(path.resolve(__dirname, './data/_example.sb')),
|
||||
sb2: fs.readFileSync(path.resolve(__dirname, './data/_example.sb2')),
|
||||
json: fs.readFileSync(path.resolve(__dirname, './data/_example.json'))
|
||||
},
|
||||
sb: sb,
|
||||
sb2: sb2,
|
||||
json: json
|
||||
};
|
BIN
test/fixtures/data/11168978.sb
vendored
Normal file
BIN
test/fixtures/data/11168978.sb
vendored
Normal file
Binary file not shown.
BIN
test/fixtures/data/14634522.sb
vendored
Normal file
BIN
test/fixtures/data/14634522.sb
vendored
Normal file
Binary file not shown.
BIN
test/fixtures/data/21235386.sb2
vendored
Normal file
BIN
test/fixtures/data/21235386.sb2
vendored
Normal file
Binary file not shown.
BIN
test/fixtures/data/22002488.sb2
vendored
Normal file
BIN
test/fixtures/data/22002488.sb2
vendored
Normal file
Binary file not shown.
BIN
test/fixtures/data/22690261.sb
vendored
Normal file
BIN
test/fixtures/data/22690261.sb
vendored
Normal file
Binary file not shown.
108889
test/fixtures/data/53355028.json
vendored
Normal file
108889
test/fixtures/data/53355028.json
vendored
Normal file
File diff suppressed because it is too large
Load diff
BIN
test/fixtures/data/68971934.sb2
vendored
Normal file
BIN
test/fixtures/data/68971934.sb2
vendored
Normal file
Binary file not shown.
2735
test/fixtures/data/89993699.json
vendored
Normal file
2735
test/fixtures/data/89993699.json
vendored
Normal file
File diff suppressed because it is too large
Load diff
36
test/fixtures/data/92887076.json
vendored
Normal file
36
test/fixtures/data/92887076.json
vendored
Normal file
|
@ -0,0 +1,36 @@
|
|||
{
|
||||
"objName": "Stage",
|
||||
"scripts": [[10, 10, [["whenGreenFlag"], ["doPlaySoundAndWait", "bensound-betterdays.mp3"]]]],
|
||||
"sounds": [{
|
||||
"soundName": "bensound-betterdays.mp3",
|
||||
"soundID": -1,
|
||||
"md5": "807e47a2f44fac0e26d47b0ea9d79cab.wav",
|
||||
"sampleCount": 3392064,
|
||||
"rate": 22050,
|
||||
"format": "adpcm"
|
||||
}],
|
||||
"costumes": [{
|
||||
"costumeName": "backdrop1",
|
||||
"baseLayerID": -1,
|
||||
"baseLayerMD5": "59f422e5dac2cf43026eb8a47d4d9d5f.png",
|
||||
"bitmapResolution": 2,
|
||||
"rotationCenterX": 480,
|
||||
"rotationCenterY": 360
|
||||
}],
|
||||
"currentCostumeIndex": 0,
|
||||
"penLayerMD5": "5c81a336fab8be57adc039a8a2b33ca9.png",
|
||||
"penLayerID": -1,
|
||||
"tempoBPM": 60,
|
||||
"videoAlpha": 0.5,
|
||||
"children": [],
|
||||
"info": {
|
||||
"scriptCount": 1,
|
||||
"videoOn": false,
|
||||
"swfVersion": "v442",
|
||||
"userAgent": "Mozilla\/5.0 (Windows NT 6.3; WOW64; Trident\/7.0; .NET4.0E; .NET4.0C; .NET CLR 3.5.30729; .NET CLR 2.0.50727; .NET CLR 3.0.30729; Tablet PC 2.0; InfoPath.2; GWX:DOWNLOADED; rv:11.0) like Gecko",
|
||||
"flashVersion": "WIN 20,0,0,267",
|
||||
"hasCloudData": false,
|
||||
"projectID": "92887076",
|
||||
"spriteCount": 0
|
||||
}
|
||||
}
|
70
test/fixtures/data/_empty.json
vendored
Executable file
70
test/fixtures/data/_empty.json
vendored
Executable file
|
@ -0,0 +1,70 @@
|
|||
{
|
||||
"objName": "Stage",
|
||||
"sounds": [{
|
||||
"soundName": "pop",
|
||||
"soundID": 1,
|
||||
"md5": "83a9787d4cb6f3b7632b4ddfebf74367.wav",
|
||||
"sampleCount": 258,
|
||||
"rate": 11025,
|
||||
"format": ""
|
||||
}],
|
||||
"costumes": [{
|
||||
"costumeName": "backdrop1",
|
||||
"baseLayerID": 3,
|
||||
"baseLayerMD5": "739b5e2a2435f6e1ec2993791b423146.png",
|
||||
"bitmapResolution": 1,
|
||||
"rotationCenterX": 240,
|
||||
"rotationCenterY": 180
|
||||
}],
|
||||
"currentCostumeIndex": 0,
|
||||
"penLayerMD5": "5c81a336fab8be57adc039a8a2b33ca9.png",
|
||||
"penLayerID": 0,
|
||||
"tempoBPM": 60,
|
||||
"videoAlpha": 0.5,
|
||||
"children": [{
|
||||
"objName": "Sprite1",
|
||||
"sounds": [{
|
||||
"soundName": "meow",
|
||||
"soundID": 0,
|
||||
"md5": "83c36d806dc92327b9e7049a565c6bff.wav",
|
||||
"sampleCount": 18688,
|
||||
"rate": 22050,
|
||||
"format": ""
|
||||
}],
|
||||
"costumes": [{
|
||||
"costumeName": "costume1",
|
||||
"baseLayerID": 1,
|
||||
"baseLayerMD5": "f9a1c175dbe2e5dee472858dd30d16bb.svg",
|
||||
"bitmapResolution": 1,
|
||||
"rotationCenterX": 47,
|
||||
"rotationCenterY": 55
|
||||
},
|
||||
{
|
||||
"costumeName": "costume2",
|
||||
"baseLayerID": 2,
|
||||
"baseLayerMD5": "6e8bd9ae68fdb02b7e1e3df656a75635.svg",
|
||||
"bitmapResolution": 1,
|
||||
"rotationCenterX": 47,
|
||||
"rotationCenterY": 55
|
||||
}],
|
||||
"currentCostumeIndex": 0,
|
||||
"scratchX": 0,
|
||||
"scratchY": 0,
|
||||
"scale": 1,
|
||||
"direction": 90,
|
||||
"rotationStyle": "normal",
|
||||
"isDraggable": false,
|
||||
"indexInLibrary": 1,
|
||||
"visible": true,
|
||||
"spriteInfo": {
|
||||
}
|
||||
}],
|
||||
"info": {
|
||||
"spriteCount": 1,
|
||||
"flashVersion": "MAC 20,0,0,267",
|
||||
"userAgent": "Scratch 2.0 Offline Editor",
|
||||
"swfVersion": "v442",
|
||||
"scriptCount": 0,
|
||||
"videoOn": false
|
||||
}
|
||||
}
|
BIN
test/fixtures/data/_empty.sb
vendored
Normal file
BIN
test/fixtures/data/_empty.sb
vendored
Normal file
Binary file not shown.
BIN
test/fixtures/data/_empty.sb2
vendored
Normal file
BIN
test/fixtures/data/_empty.sb2
vendored
Normal file
Binary file not shown.
309
test/fixtures/data/_example.json
vendored
Executable file
309
test/fixtures/data/_example.json
vendored
Executable file
|
@ -0,0 +1,309 @@
|
|||
{
|
||||
"objName": "Stage",
|
||||
"variables": [{
|
||||
"name": "foo",
|
||||
"value": "500",
|
||||
"isPersistent": false
|
||||
}],
|
||||
"lists": [{
|
||||
"listName": "baz",
|
||||
"contents": ["asdf", "asdf", "1", "2", "3", "4", ""],
|
||||
"isPersistent": false,
|
||||
"x": 5,
|
||||
"y": 59,
|
||||
"width": 102,
|
||||
"height": 202,
|
||||
"visible": true
|
||||
}],
|
||||
"scripts": [[32, 32, [["whenGreenFlag"], ["playSound:", "pop"], ["call", "foo %n bar", 1]]],
|
||||
[29, 150, [["PicoBoard.whenSensorConnected", "button pressed"], ["playSound:", "pop"]]],
|
||||
[29.8,
|
||||
259.6,
|
||||
[["procDef", "foo %n bar", ["number1"], [1], false],
|
||||
["startScene", "backdrop1"],
|
||||
["changeGraphicEffect:by:", "color", 25]]]],
|
||||
"sounds": [{
|
||||
"soundName": "pop",
|
||||
"soundID": 3,
|
||||
"md5": "83a9787d4cb6f3b7632b4ddfebf74367.wav",
|
||||
"sampleCount": 258,
|
||||
"rate": 11025,
|
||||
"format": ""
|
||||
}],
|
||||
"costumes": [{
|
||||
"costumeName": "backdrop1",
|
||||
"baseLayerID": 16,
|
||||
"baseLayerMD5": "739b5e2a2435f6e1ec2993791b423146.png",
|
||||
"bitmapResolution": 1,
|
||||
"rotationCenterX": 240,
|
||||
"rotationCenterY": 180
|
||||
}],
|
||||
"currentCostumeIndex": 0,
|
||||
"penLayerMD5": "5c81a336fab8be57adc039a8a2b33ca9.png",
|
||||
"penLayerID": 0,
|
||||
"tempoBPM": 60,
|
||||
"videoAlpha": 0.5,
|
||||
"children": [{
|
||||
"objName": "Sprite1",
|
||||
"variables": [{
|
||||
"name": "bar",
|
||||
"value": "25",
|
||||
"isPersistent": false
|
||||
}],
|
||||
"lists": [{
|
||||
"listName": "baq",
|
||||
"contents": ["1", "2", "5", "5", "5", "5"],
|
||||
"isPersistent": false,
|
||||
"x": 111,
|
||||
"y": 58,
|
||||
"width": 102,
|
||||
"height": 202,
|
||||
"visible": true
|
||||
}],
|
||||
"scripts": [[23, 42, [["whenGreenFlag"], ["playSound:", "recording1"], ["say:duration:elapsed:from:", "Hello!", 2]]]],
|
||||
"scriptComments": [[23, 154, 151, 201, true, -1, "Hello world"]],
|
||||
"sounds": [{
|
||||
"soundName": "meow",
|
||||
"soundID": 0,
|
||||
"md5": "83c36d806dc92327b9e7049a565c6bff.wav",
|
||||
"sampleCount": 18688,
|
||||
"rate": 22050,
|
||||
"format": ""
|
||||
},
|
||||
{
|
||||
"soundName": "recording1",
|
||||
"soundID": 1,
|
||||
"md5": "b586745b98e94d7574f7f7b48d831e20.wav",
|
||||
"sampleCount": 1,
|
||||
"rate": 22050,
|
||||
"format": ""
|
||||
}],
|
||||
"costumes": [{
|
||||
"costumeName": "costume1",
|
||||
"baseLayerID": 1,
|
||||
"baseLayerMD5": "f9a1c175dbe2e5dee472858dd30d16bb.svg",
|
||||
"bitmapResolution": 1,
|
||||
"rotationCenterX": 47,
|
||||
"rotationCenterY": 55
|
||||
},
|
||||
{
|
||||
"costumeName": "costume2",
|
||||
"baseLayerID": 2,
|
||||
"baseLayerMD5": "6e8bd9ae68fdb02b7e1e3df656a75635.svg",
|
||||
"bitmapResolution": 1,
|
||||
"rotationCenterX": 47,
|
||||
"rotationCenterY": 55
|
||||
}],
|
||||
"currentCostumeIndex": 0,
|
||||
"scratchX": 0,
|
||||
"scratchY": 0,
|
||||
"scale": 1,
|
||||
"direction": 90,
|
||||
"rotationStyle": "normal",
|
||||
"isDraggable": false,
|
||||
"indexInLibrary": 1,
|
||||
"visible": true,
|
||||
"spriteInfo": {
|
||||
}
|
||||
},
|
||||
{
|
||||
"objName": "1080 Hip-Hop",
|
||||
"scripts": [[27,
|
||||
26,
|
||||
[["whenKeyPressed", "space"],
|
||||
["doRepeat", 10, [["playSound:", "dance celebrate"]]],
|
||||
["doForever", [["changeGraphicEffect:by:", "color", 25]]]]]],
|
||||
"sounds": [{
|
||||
"soundName": "dance celebrate",
|
||||
"soundID": 2,
|
||||
"md5": "0edb8fb88af19e6e17d0f8cf64c1d136.wav",
|
||||
"sampleCount": 176401,
|
||||
"rate": 22050,
|
||||
"format": "adpcm"
|
||||
}],
|
||||
"costumes": [{
|
||||
"costumeName": "1080 stance",
|
||||
"baseLayerID": 3,
|
||||
"baseLayerMD5": "c013c7ae0cdb245e7cbcd1f0ef803d5c.png",
|
||||
"bitmapResolution": 2,
|
||||
"rotationCenterX": 70,
|
||||
"rotationCenterY": 278
|
||||
},
|
||||
{
|
||||
"costumeName": "1080 top stand",
|
||||
"baseLayerID": 4,
|
||||
"baseLayerMD5": "2beeccef2956cf00f3fe04a70941a304.png",
|
||||
"bitmapResolution": 2,
|
||||
"rotationCenterX": 74,
|
||||
"rotationCenterY": 274
|
||||
},
|
||||
{
|
||||
"costumeName": "1080 top R step",
|
||||
"baseLayerID": 5,
|
||||
"baseLayerMD5": "877253c7955364d01d3cdf6c00ad8f26.png",
|
||||
"bitmapResolution": 2,
|
||||
"rotationCenterX": 200,
|
||||
"rotationCenterY": 270
|
||||
},
|
||||
{
|
||||
"costumeName": "1080 top L step",
|
||||
"baseLayerID": 6,
|
||||
"baseLayerMD5": "0182ebabc5f0e2ba2f8c5a88543c841a.png",
|
||||
"bitmapResolution": 2,
|
||||
"rotationCenterX": 144,
|
||||
"rotationCenterY": 266
|
||||
},
|
||||
{
|
||||
"costumeName": "1080 top freeze",
|
||||
"baseLayerID": 7,
|
||||
"baseLayerMD5": "eeb7ec45ab14f11c59faa86d68aa6711.png",
|
||||
"bitmapResolution": 2,
|
||||
"rotationCenterX": 54,
|
||||
"rotationCenterY": 258
|
||||
},
|
||||
{
|
||||
"costumeName": "1080 top R cross",
|
||||
"baseLayerID": 8,
|
||||
"baseLayerMD5": "69eae69a7bb4951d2bf44031d709e9b7.png",
|
||||
"bitmapResolution": 2,
|
||||
"rotationCenterX": 206,
|
||||
"rotationCenterY": 252
|
||||
},
|
||||
{
|
||||
"costumeName": "1080 pop front",
|
||||
"baseLayerID": 9,
|
||||
"baseLayerMD5": "dd96cd3037b3e48814529bbf1d615bea.png",
|
||||
"bitmapResolution": 2,
|
||||
"rotationCenterX": 72,
|
||||
"rotationCenterY": 266
|
||||
},
|
||||
{
|
||||
"costumeName": "1080 pop down",
|
||||
"baseLayerID": 10,
|
||||
"baseLayerMD5": "0a4fe68e96f9194f3b5a039af8598c05.png",
|
||||
"bitmapResolution": 2,
|
||||
"rotationCenterX": 74,
|
||||
"rotationCenterY": 188
|
||||
},
|
||||
{
|
||||
"costumeName": "1080 pop left",
|
||||
"baseLayerID": 11,
|
||||
"baseLayerMD5": "097b4b61b323e4041315992f41196ec9.png",
|
||||
"bitmapResolution": 2,
|
||||
"rotationCenterX": 184,
|
||||
"rotationCenterY": 266
|
||||
},
|
||||
{
|
||||
"costumeName": "1080 pop right",
|
||||
"baseLayerID": 12,
|
||||
"baseLayerMD5": "9ab30ea9351f88b0bfcc7c1eff219ec4.png",
|
||||
"bitmapResolution": 2,
|
||||
"rotationCenterX": 78,
|
||||
"rotationCenterY": 276
|
||||
},
|
||||
{
|
||||
"costumeName": "1080 pop L arm",
|
||||
"baseLayerID": 13,
|
||||
"baseLayerMD5": "532ca24749bd0968b329c0970209805b.png",
|
||||
"bitmapResolution": 2,
|
||||
"rotationCenterX": 100,
|
||||
"rotationCenterY": 280
|
||||
},
|
||||
{
|
||||
"costumeName": "1080 pop stand",
|
||||
"baseLayerID": 14,
|
||||
"baseLayerMD5": "b140b94daf02503e0abfc1ec284c6ccd.png",
|
||||
"bitmapResolution": 2,
|
||||
"rotationCenterX": 92,
|
||||
"rotationCenterY": 280
|
||||
},
|
||||
{
|
||||
"costumeName": "1080 pop R arm",
|
||||
"baseLayerID": 15,
|
||||
"baseLayerMD5": "c69c7b764530b8a464f2ca4e5e85c303.png",
|
||||
"bitmapResolution": 2,
|
||||
"rotationCenterX": 74,
|
||||
"rotationCenterY": 278
|
||||
}],
|
||||
"currentCostumeIndex": 0,
|
||||
"scratchX": 96,
|
||||
"scratchY": 12,
|
||||
"scale": 1,
|
||||
"direction": 90,
|
||||
"rotationStyle": "normal",
|
||||
"isDraggable": false,
|
||||
"indexInLibrary": 2,
|
||||
"visible": true,
|
||||
"spriteInfo": {
|
||||
}
|
||||
},
|
||||
{
|
||||
"target": "Stage",
|
||||
"cmd": "getVar:",
|
||||
"param": "foo",
|
||||
"color": 15629590,
|
||||
"label": "foo",
|
||||
"mode": 1,
|
||||
"sliderMin": 0,
|
||||
"sliderMax": 100,
|
||||
"isDiscrete": true,
|
||||
"x": 5,
|
||||
"y": 5,
|
||||
"visible": true
|
||||
},
|
||||
{
|
||||
"target": "Sprite1",
|
||||
"cmd": "getVar:",
|
||||
"param": "bar",
|
||||
"color": 15629590,
|
||||
"label": "Sprite1: bar",
|
||||
"mode": 1,
|
||||
"sliderMin": 0,
|
||||
"sliderMax": 100,
|
||||
"isDiscrete": true,
|
||||
"x": 5,
|
||||
"y": 32,
|
||||
"visible": true
|
||||
},
|
||||
{
|
||||
"listName": "baz",
|
||||
"contents": ["asdf", "asdf", "1", "2", "3", "4", ""],
|
||||
"isPersistent": false,
|
||||
"x": 5,
|
||||
"y": 59,
|
||||
"width": 102,
|
||||
"height": 202,
|
||||
"visible": true
|
||||
},
|
||||
{
|
||||
"listName": "baq",
|
||||
"contents": ["1", "2", "5", "5", "5", "5"],
|
||||
"isPersistent": false,
|
||||
"x": 111,
|
||||
"y": 58,
|
||||
"width": 102,
|
||||
"height": 202,
|
||||
"visible": true
|
||||
}],
|
||||
"info": {
|
||||
"spriteCount": 2,
|
||||
"flashVersion": "MAC 20,0,0,267",
|
||||
"savedExtensions": [{
|
||||
"extensionName": "PicoBoard",
|
||||
"blockSpecs": [["h", "when %m.booleanSensor", "whenSensorConnected", "button pressed"],
|
||||
["h", "when %m.sensor %m.lessMore %n", "whenSensorPass", "slider", ">", 50],
|
||||
["b", "sensor %m.booleanSensor?", "sensorPressed", "button pressed"],
|
||||
["rR", "%m.sensor sensor value", "sensor", "slider"]],
|
||||
"menus": {
|
||||
"booleanSensor": ["button pressed", "A connected", "B connected", "C connected", "D connected"],
|
||||
"sensor": ["slider", "light", "sound", "resistance-A", "resistance-B", "resistance-C", "resistance-D"],
|
||||
"lessMore": [">", "<"]
|
||||
},
|
||||
"javascriptURL": "file:\/\/\/\/Users\/asliwinski\/Library\/Application Support\/edu.media.mit.Scratch2Editor\/Local Store\/static\/js\/scratch_extensions\/picoExtension.js"
|
||||
}],
|
||||
"swfVersion": "v442",
|
||||
"scriptCount": 5,
|
||||
"videoOn": false,
|
||||
"userAgent": "Scratch 2.0 Offline Editor"
|
||||
}
|
||||
}
|
BIN
test/fixtures/data/_example.sb
vendored
Normal file
BIN
test/fixtures/data/_example.sb
vendored
Normal file
Binary file not shown.
BIN
test/fixtures/data/_example.sb2
vendored
Normal file
BIN
test/fixtures/data/_example.sb2
vendored
Normal file
Binary file not shown.
BIN
test/fixtures/data/_large.sb2
vendored
Normal file
BIN
test/fixtures/data/_large.sb2
vendored
Normal file
Binary file not shown.
150
test/fixtures/meta.json
vendored
Normal file
150
test/fixtures/meta.json
vendored
Normal file
|
@ -0,0 +1,150 @@
|
|||
{
|
||||
"id": "http://json-schema.org/draft-04/schema#",
|
||||
"$schema": "http://json-schema.org/draft-04/schema#",
|
||||
"description": "Core schema meta-schema",
|
||||
"definitions": {
|
||||
"schemaArray": {
|
||||
"type": "array",
|
||||
"minItems": 1,
|
||||
"items": { "$ref": "#" }
|
||||
},
|
||||
"positiveInteger": {
|
||||
"type": "integer",
|
||||
"minimum": 0
|
||||
},
|
||||
"positiveIntegerDefault0": {
|
||||
"allOf": [ { "$ref": "#/definitions/positiveInteger" }, { "default": 0 } ]
|
||||
},
|
||||
"simpleTypes": {
|
||||
"enum": [ "array", "boolean", "integer", "null", "number", "object", "string" ]
|
||||
},
|
||||
"stringArray": {
|
||||
"type": "array",
|
||||
"items": { "type": "string" },
|
||||
"minItems": 1,
|
||||
"uniqueItems": true
|
||||
}
|
||||
},
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string",
|
||||
"format": "uri"
|
||||
},
|
||||
"$schema": {
|
||||
"type": "string",
|
||||
"format": "uri"
|
||||
},
|
||||
"title": {
|
||||
"type": "string"
|
||||
},
|
||||
"description": {
|
||||
"type": "string"
|
||||
},
|
||||
"default": {},
|
||||
"multipleOf": {
|
||||
"type": "number",
|
||||
"minimum": 0,
|
||||
"exclusiveMinimum": true
|
||||
},
|
||||
"maximum": {
|
||||
"type": "number"
|
||||
},
|
||||
"exclusiveMaximum": {
|
||||
"type": "boolean",
|
||||
"default": false
|
||||
},
|
||||
"minimum": {
|
||||
"type": "number"
|
||||
},
|
||||
"exclusiveMinimum": {
|
||||
"type": "boolean",
|
||||
"default": false
|
||||
},
|
||||
"maxLength": { "$ref": "#/definitions/positiveInteger" },
|
||||
"minLength": { "$ref": "#/definitions/positiveIntegerDefault0" },
|
||||
"pattern": {
|
||||
"type": "string",
|
||||
"format": "regex"
|
||||
},
|
||||
"additionalItems": {
|
||||
"anyOf": [
|
||||
{ "type": "boolean" },
|
||||
{ "$ref": "#" }
|
||||
],
|
||||
"default": {}
|
||||
},
|
||||
"items": {
|
||||
"anyOf": [
|
||||
{ "$ref": "#" },
|
||||
{ "$ref": "#/definitions/schemaArray" }
|
||||
],
|
||||
"default": {}
|
||||
},
|
||||
"maxItems": { "$ref": "#/definitions/positiveInteger" },
|
||||
"minItems": { "$ref": "#/definitions/positiveIntegerDefault0" },
|
||||
"uniqueItems": {
|
||||
"type": "boolean",
|
||||
"default": false
|
||||
},
|
||||
"maxProperties": { "$ref": "#/definitions/positiveInteger" },
|
||||
"minProperties": { "$ref": "#/definitions/positiveIntegerDefault0" },
|
||||
"required": { "$ref": "#/definitions/stringArray" },
|
||||
"additionalProperties": {
|
||||
"anyOf": [
|
||||
{ "type": "boolean" },
|
||||
{ "$ref": "#" }
|
||||
],
|
||||
"default": {}
|
||||
},
|
||||
"definitions": {
|
||||
"type": "object",
|
||||
"additionalProperties": { "$ref": "#" },
|
||||
"default": {}
|
||||
},
|
||||
"properties": {
|
||||
"type": "object",
|
||||
"additionalProperties": { "$ref": "#" },
|
||||
"default": {}
|
||||
},
|
||||
"patternProperties": {
|
||||
"type": "object",
|
||||
"additionalProperties": { "$ref": "#" },
|
||||
"default": {}
|
||||
},
|
||||
"dependencies": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"anyOf": [
|
||||
{ "$ref": "#" },
|
||||
{ "$ref": "#/definitions/stringArray" }
|
||||
]
|
||||
}
|
||||
},
|
||||
"enum": {
|
||||
"type": "array",
|
||||
"minItems": 1,
|
||||
"uniqueItems": true
|
||||
},
|
||||
"type": {
|
||||
"anyOf": [
|
||||
{ "$ref": "#/definitions/simpleTypes" },
|
||||
{
|
||||
"type": "array",
|
||||
"items": { "$ref": "#/definitions/simpleTypes" },
|
||||
"minItems": 1,
|
||||
"uniqueItems": true
|
||||
}
|
||||
]
|
||||
},
|
||||
"allOf": { "$ref": "#/definitions/schemaArray" },
|
||||
"anyOf": { "$ref": "#/definitions/schemaArray" },
|
||||
"oneOf": { "$ref": "#/definitions/schemaArray" },
|
||||
"not": { "$ref": "#" }
|
||||
},
|
||||
"dependencies": {
|
||||
"exclusiveMaximum": [ "maximum" ],
|
||||
"exclusiveMinimum": [ "minimum" ]
|
||||
},
|
||||
"default": {}
|
||||
}
|
31
test/integration/empty.js
Normal file
31
test/integration/empty.js
Normal file
|
@ -0,0 +1,31 @@
|
|||
var test = require('tap').test;
|
||||
var data = require('../fixtures/data');
|
||||
var parser = require('../../index');
|
||||
|
||||
test('sb', function (t) {
|
||||
parser(data.empty.sb, function (err, res) {
|
||||
t.type(err, 'string');
|
||||
t.type(res, 'undefined');
|
||||
t.end();
|
||||
});
|
||||
});
|
||||
|
||||
test('sb2', function (t) {
|
||||
parser(data.empty.sb2, function (err, res) {
|
||||
t.equal(err, null);
|
||||
t.type(res, 'object');
|
||||
t.type(res._meta, 'object');
|
||||
t.type(res.info, 'object');
|
||||
t.end();
|
||||
});
|
||||
});
|
||||
|
||||
test('json', function (t) {
|
||||
parser(data.empty.json, function (err, res) {
|
||||
t.equal(err, null);
|
||||
t.type(res, 'object');
|
||||
t.type(res._meta, 'object');
|
||||
t.type(res.info, 'object');
|
||||
t.end();
|
||||
});
|
||||
});
|
31
test/integration/example.js
Normal file
31
test/integration/example.js
Normal file
|
@ -0,0 +1,31 @@
|
|||
var test = require('tap').test;
|
||||
var data = require('../fixtures/data');
|
||||
var parser = require('../../index');
|
||||
|
||||
test('sb', function (t) {
|
||||
parser(data.example.sb, function (err, res) {
|
||||
t.type(err, 'string');
|
||||
t.type(res, 'undefined');
|
||||
t.end();
|
||||
});
|
||||
});
|
||||
|
||||
test('sb2', function (t) {
|
||||
parser(data.example.sb2, function (err, res) {
|
||||
t.equal(err, null);
|
||||
t.type(res, 'object');
|
||||
t.type(res._meta, 'object');
|
||||
t.type(res.info, 'object');
|
||||
t.end();
|
||||
});
|
||||
});
|
||||
|
||||
test('json', function (t) {
|
||||
parser(data.example.json, function (err, res) {
|
||||
t.equal(err, null);
|
||||
t.type(res, 'object');
|
||||
t.type(res._meta, 'object');
|
||||
t.type(res.info, 'object');
|
||||
t.end();
|
||||
});
|
||||
});
|
40
test/integration/stress.js
Normal file
40
test/integration/stress.js
Normal file
|
@ -0,0 +1,40 @@
|
|||
var test = require('tap').test;
|
||||
var data = require('../fixtures/data');
|
||||
var parser = require('../../index');
|
||||
|
||||
test('sb', function (t) {
|
||||
var set = data.sb;
|
||||
t.plan(set.length * 2);
|
||||
for (var i in data.sb) {
|
||||
parser(data.sb[i], function (err, res) {
|
||||
t.type(err, 'string');
|
||||
t.type(res, 'undefined');
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
test('sb2', function (t) {
|
||||
var set = data.sb2;
|
||||
t.plan(set.length * 4);
|
||||
for (var i in data.sb2) {
|
||||
parser(data.sb2[i], function (err, res) {
|
||||
t.equal(err, null);
|
||||
t.type(res, 'object');
|
||||
t.type(res._meta, 'object');
|
||||
t.type(res.info, 'object');
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
test('json', function (t) {
|
||||
var set = data.json;
|
||||
t.plan(set.length * 4);
|
||||
for (var i in data.json) {
|
||||
parser(data.json[i], function (err, res) {
|
||||
t.equal(err, null);
|
||||
t.type(res, 'object');
|
||||
t.type(res._meta, 'object');
|
||||
t.type(res.info, 'object');
|
||||
});
|
||||
}
|
||||
});
|
22
test/unit/analyze.js
Normal file
22
test/unit/analyze.js
Normal file
|
@ -0,0 +1,22 @@
|
|||
var test = require('tap').test;
|
||||
var data = require('../fixtures/data');
|
||||
var analyze = require('../../lib/analyze');
|
||||
|
||||
test('spec', function (t) {
|
||||
t.type(analyze, 'function');
|
||||
t.end();
|
||||
});
|
||||
|
||||
test('empty project', function (t) {
|
||||
t.end();
|
||||
});
|
||||
|
||||
test('example project', function (t) {
|
||||
analyze(JSON.parse(data.example.json.toString()), function (err, project) {
|
||||
t.equal(err, null);
|
||||
t.type(project, 'object');
|
||||
t.type(project._meta, 'object');
|
||||
|
||||
t.end();
|
||||
});
|
||||
});
|
26
test/unit/parser.js
Normal file
26
test/unit/parser.js
Normal file
|
@ -0,0 +1,26 @@
|
|||
var test = require('tap').test;
|
||||
var parse = require('../../lib/parse');
|
||||
var data = require('../fixtures/data');
|
||||
|
||||
test('spec', function (t) {
|
||||
t.type(parse, 'function');
|
||||
t.end();
|
||||
});
|
||||
|
||||
test('valid', function (t) {
|
||||
for (var i = 0; i < data.json.length; i++) {
|
||||
parse(data.json[i].toString(), function (err, res) {
|
||||
t.equal(err, null);
|
||||
t.type(res, 'object');
|
||||
});
|
||||
}
|
||||
t.end();
|
||||
});
|
||||
|
||||
test('invalid', function (t) {
|
||||
parse('&%@', function (err, res) {
|
||||
t.type(err, 'string');
|
||||
t.equal(res, undefined);
|
||||
t.end();
|
||||
});
|
||||
});
|
19
test/unit/schema.js
Normal file
19
test/unit/schema.js
Normal file
|
@ -0,0 +1,19 @@
|
|||
var ajv = require('ajv')();
|
||||
var test = require('tap').test;
|
||||
var meta = require('../fixtures/meta.json');
|
||||
var schema = require('../../lib/schema.json');
|
||||
|
||||
test('spec', function (t) {
|
||||
t.type(schema, 'object');
|
||||
t.end();
|
||||
});
|
||||
|
||||
test('is valid', function (t) {
|
||||
// Validate schema against the JSON meta schema:
|
||||
// http://json-schema.org/draft-04/schema#
|
||||
var validate = ajv.compile(meta);
|
||||
var valid = validate(schema);
|
||||
|
||||
t.equal(valid, true);
|
||||
t.end();
|
||||
});
|
76
test/unit/unpack.js
Normal file
76
test/unit/unpack.js
Normal file
|
@ -0,0 +1,76 @@
|
|||
var fs = require('fs');
|
||||
var path = require('path');
|
||||
var test = require('tap').test;
|
||||
var unpack = require('../../lib/unpack');
|
||||
|
||||
var fixtures = {
|
||||
sb: path.resolve(__dirname, '../fixtures/data/_example.sb'),
|
||||
sb2: path.resolve(__dirname, '../fixtures/data/_example.sb2'),
|
||||
json: path.resolve(__dirname, '../fixtures/data/_example.json')
|
||||
};
|
||||
|
||||
for (var i in fixtures) {
|
||||
fixtures[i] = fs.readFileSync(fixtures[i]);
|
||||
}
|
||||
|
||||
test('spec', function (t) {
|
||||
t.type(unpack, 'function');
|
||||
t.end();
|
||||
});
|
||||
|
||||
test('sb', function (t) {
|
||||
var buffer = new Buffer(fixtures.sb);
|
||||
unpack(buffer, function (err, res) {
|
||||
t.type(err, 'string');
|
||||
t.type(res, 'undefined');
|
||||
t.end();
|
||||
});
|
||||
});
|
||||
|
||||
test('sb2', function (t) {
|
||||
var buffer = new Buffer(fixtures.sb2);
|
||||
unpack(buffer, function (err, res) {
|
||||
t.equal(err, null);
|
||||
t.type(res, 'string');
|
||||
t.doesNotThrow(function () {
|
||||
JSON.parse(res);
|
||||
});
|
||||
t.end();
|
||||
});
|
||||
});
|
||||
|
||||
test('json', function (t) {
|
||||
var buffer = new Buffer(fixtures.json);
|
||||
unpack(buffer, function (err, res) {
|
||||
t.equal(err, null);
|
||||
t.type(res, 'string');
|
||||
t.doesNotThrow(function () {
|
||||
JSON.parse(res);
|
||||
});
|
||||
t.end();
|
||||
});
|
||||
});
|
||||
|
||||
test('undefined', function (t) {
|
||||
unpack(undefined, function (err, res) {
|
||||
t.type(err, 'string');
|
||||
t.equal(res, undefined);
|
||||
t.end();
|
||||
});
|
||||
});
|
||||
|
||||
test('null', function (t) {
|
||||
unpack(null, function (err, obj) {
|
||||
t.type(err, 'string');
|
||||
t.equal(obj, undefined);
|
||||
t.end();
|
||||
});
|
||||
});
|
||||
|
||||
test('object', function (t) {
|
||||
unpack({}, function (err, obj) {
|
||||
t.type(err, 'string');
|
||||
t.equal(obj, undefined);
|
||||
t.end();
|
||||
});
|
||||
});
|
31
test/unit/validate.js
Normal file
31
test/unit/validate.js
Normal file
|
@ -0,0 +1,31 @@
|
|||
var test = require('tap').test;
|
||||
var data = require('../fixtures/data');
|
||||
var validate = require('../../lib/validate');
|
||||
|
||||
test('spec', function (t) {
|
||||
t.type(validate, 'function');
|
||||
t.end();
|
||||
});
|
||||
|
||||
test('valid', function (t) {
|
||||
validate(JSON.parse(data.example.json), function (err, res) {
|
||||
t.equal(err, null);
|
||||
t.type(res, 'object');
|
||||
t.end();
|
||||
});
|
||||
});
|
||||
|
||||
test('invalid', function (t) {
|
||||
validate({foo:1}, function (err, res) {
|
||||
t.equal(Array.isArray(err), true);
|
||||
t.equal(res, undefined);
|
||||
t.type(err[0], 'object');
|
||||
t.type(err[0].keyword, 'string');
|
||||
t.type(err[0].dataPath, 'string');
|
||||
t.type(err[0].schemaPath, 'string');
|
||||
t.type(err[0].message, 'string');
|
||||
t.type(err[0].params, 'object');
|
||||
t.type(err[0].params.missingProperty, 'string');
|
||||
t.end();
|
||||
});
|
||||
});
|
Loading…
Add table
Add a link
Reference in a new issue