diff --git a/README.md b/README.md index 5119f55..e78db55 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ [![devDependencies Status](https://david-dm.org/llk/scratch-parser/dev-status.svg)](https://david-dm.org/llk/scratch-parser?type=dev) ## 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. +The Scratch Parser is a [Node.js](https://nodejs.org) module that parses and validates [Scratch](https://scratch.mit.edu) projects. ## API @@ -27,21 +27,6 @@ parser(buffer, function (err, project) { }); ``` -## Metadata -The `scratch-parser` module will append metadata about the project should validation and parsing be successful. The `_meta` object includes: - -| 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: diff --git a/index.js b/index.js index 4c9a32f..4a40166 100644 --- a/index.js +++ b/index.js @@ -3,7 +3,6 @@ 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, @@ -17,7 +16,6 @@ module.exports = function (input, callback) { unpack(input, cb); }, parse, - validate, - analyze + validate ], callback); }; diff --git a/lib/analyze.js b/lib/analyze.js deleted file mode 100644 index 3db958f..0000000 --- a/lib/analyze.js +++ /dev/null @@ -1,210 +0,0 @@ -/** - * 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; -} - -/** - * Extract number of sprites from a project object. Will attempt to ignore - * "children" which are not sprites. - * - * @param {Object} input Project object - * - * @return {Object} Sprite information - */ -function sprites (input) { - var result = 0; - - for (var i in input.children) { - if (input.children[i].hasOwnProperty('spriteInfo')) result++; - } - - return { count: 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].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 = { - 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') - }; - - // Sprites - meta.sprites = sprites(project); - - // Blocks - meta.blocks = blocks(project); - - // Extensions - meta.extensions = extensions(project); - - // Bind metadata to project and return - project._meta = meta; - callback(null, project); -}; diff --git a/package.json b/package.json index 4e7b834..fb80aef 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,7 @@ "scripts": { "test:lint": "eslint . --ext=js", "test:unit": "tap test/unit/*.js", - "test:integration": "tap test/unit/*.js", + "test:integration": "tap test/integration/*.js", "test:coverage": "tap test/{unit,integration}/*.js --coverage --coverage-report=lcov", "test:benchmark": "node test/benchmark/performance.js", "test": "npm run test:lint && npm run test:unit && npm run test:integration", diff --git a/test/integration/empty.js b/test/integration/empty.js index c4881d6..c2618ef 100644 --- a/test/integration/empty.js +++ b/test/integration/empty.js @@ -14,7 +14,6 @@ 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(); }); @@ -24,7 +23,6 @@ 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(); }); @@ -34,7 +32,6 @@ test('json string', function (t) { parser(data.empty.json.toString('utf-8'), function (err, res) { t.equal(err, null); t.type(res, 'object'); - t.type(res._meta, 'object'); t.type(res.info, 'object'); t.end(); }); diff --git a/test/integration/example.js b/test/integration/example.js index 6893cb3..18a5562 100644 --- a/test/integration/example.js +++ b/test/integration/example.js @@ -14,7 +14,6 @@ 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(); }); @@ -24,7 +23,6 @@ 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(); }); @@ -34,7 +32,6 @@ test('json string', function (t) { parser(data.example.json.toString('utf-8'), function (err, res) { t.equal(err, null); t.type(res, 'object'); - t.type(res._meta, 'object'); t.type(res.info, 'object'); t.end(); }); diff --git a/test/integration/stress.js b/test/integration/stress.js index 61dd38b..a5a243e 100644 --- a/test/integration/stress.js +++ b/test/integration/stress.js @@ -15,12 +15,11 @@ test('sb', function (t) { test('sb2', function (t) { var set = data.sb2; - t.plan(set.length * 4); + t.plan(set.length * 3); 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'); }); } @@ -28,12 +27,11 @@ test('sb2', function (t) { test('json', function (t) { var set = data.json; - t.plan(set.length * 4); + t.plan(set.length * 3); 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'); }); } @@ -41,12 +39,11 @@ test('json', function (t) { test('json string', function (t) { var set = data.json; - t.plan(set.length * 4); + t.plan(set.length * 3); for (var i in data.json) { parser(data.json[i].toString('utf-8'), function (err, res) { t.equal(err, null); t.type(res, 'object'); - t.type(res._meta, 'object'); t.type(res.info, 'object'); }); } diff --git a/test/unit/analyze.js b/test/unit/analyze.js deleted file mode 100644 index 561cc95..0000000 --- a/test/unit/analyze.js +++ /dev/null @@ -1,42 +0,0 @@ -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, res) { - t.equal(err, null); - t.type(res, 'object'); - t.type(res._meta, 'object'); - t.type(res._meta.sprites, 'object'); - t.type(res._meta.scripts, 'object'); - t.type(res._meta.variables, 'object'); - t.type(res._meta.lists, 'object'); - t.type(res._meta.comments, 'object'); - t.type(res._meta.sounds, 'object'); - t.type(res._meta.costumes, 'object'); - t.type(res._meta.blocks, 'object'); - t.type(res._meta.extensions, 'object'); - - t.equal(res._meta.sprites.count, 2, 'expected number of sprites'); - t.equal(res._meta.scripts.count, 5, 'expected number of scripts'); - t.equal(res._meta.variables.count, 2, 'expected number of variables'); - t.equal(res._meta.lists.count, 2, 'expected number of lists'); - t.equal(res._meta.comments.count, 1, 'expected number of comments'); - t.equal(res._meta.sounds.count, 4, 'expected number of sounds'); - t.equal(res._meta.costumes.count, 16, 'expected number of costumes'); - t.equal(res._meta.blocks.count, 16, 'expected number of blocks'); - t.equal(res._meta.blocks.unique, 11, 'exepected number of blocks'); - t.equal(res._meta.extensions.count, 1, 'expected number of extensions'); - - t.end(); - }); -});