commit f8c7ae5776406ec5a047fcb407c086f9f364a0a7 Author: Zeke Sonxx Date: Mon Sep 29 19:40:13 2014 -0400 Inital Commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..93f1361 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +node_modules +npm-debug.log diff --git a/.jshintrc b/.jshintrc new file mode 100644 index 0000000..0fdcd13 --- /dev/null +++ b/.jshintrc @@ -0,0 +1,8 @@ +{ + "globalstrict": true, + "node": true, + "undef": true, + "indent": 2, + "quotmark": "single", + "predef": ["describe", "it", "before", "beforeEach", "after", "afterEach"] +} \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..50f52a7 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2014 Zeke Sonxx + +Permission is hereby granted, free of charge, to any pony obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit ponies to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..ed645a3 --- /dev/null +++ b/README.md @@ -0,0 +1,23 @@ +# wow-toc + +World Of Warcraft addon `.toc` file parser. Written in Node.js. + +Built on the documentation at [Wowpedia](http://wowpedia.org/TOC_format). + +## Usage +`$ npm install wow-toc` + +````js +var wowtoc = require('wow-toc'); + +var k = wowtoc.parse(fs.readFileSync('somefile.toc')); +wowtoc.stringify(k); +```` + +## Notes / Caveats +* Does not acknowledge `#@no-lib-strip@`/`#@end-no-lib-strip@` +* `Dependencies`/`OptionalDeps`/etc remain strings (easy to convert to arrays anyways) +* Doesn't validate tag names (`## Bacon: Strip` is valid to it but not to WoW) + +## License +MIT. See `LICENSE` \ No newline at end of file diff --git a/lib/index.js b/lib/index.js new file mode 100644 index 0000000..4850dc7 --- /dev/null +++ b/lib/index.js @@ -0,0 +1,62 @@ +/** + * World Of Warcraft Addon .toc file parser + * Based on the documentation at http://wowpedia.org/TOC_format + * @author Zeke Sonxx + * @license MIT + */ + +'use strict'; +var debug = require('debug')('wow-toc'); + +exports.parse = function(input) { + if (typeof input !== 'string') { + throw new Error('wowtoc.parse input must be a string, got '+typeof input); + } + var lines = input.split(/\r*\n/); //allow Windows newlines + debug('Got input, '+lines.length+' lines'); + var out = { + tags: {}, + files: [] + }; + lines.forEach(function(line) { + if (line.length > 1024) { + //WoW only reads the first 1024 lines + debug('Line is longer than 1024 characters, trunicating to be like WoW'); + line = line.substr(0, 1024); + } + + if (line.substr(0, 2) === '##') { //## Title: Recount + debug('Line is tag: '+line); + var tag = line.substr(2, line.indexOf(':')).trim().replace(':', ''); + var value = line.substr(line.indexOf(':')+1, line.length).trim(); + debug('`'+tag+'` : `'+value+'`'); + out.tags[tag] = value; + } else if (line.substr(0, 1) === '#') { //#bug not feature + debug('Line is comment: '+line); + } else { // recount.lua + //file loading + debug('Line is file: '+line); + if (line.trim() !== '') { + out.files.push(line.trim()); + } + } + }); + return out; +}; + +exports.stringify = function(input) { + if (typeof input !== 'object') { + throw new TypeError('wowtoc.stringify input must be an object, got '+typeof input); + } + + var out = []; + for(var tag in input.tags) { + if(input.tags.hasOwnProperty(tag)) { + out.push('## '+tag+': '+input.tags[tag]); + } + } + (input.files || []).forEach(function(file) { + out.push(file); + }); + return out.join('\n'); +}; \ No newline at end of file diff --git a/package.json b/package.json new file mode 100644 index 0000000..ae74207 --- /dev/null +++ b/package.json @@ -0,0 +1,31 @@ +{ + "name": "wow-toc", + "version": "0.1.0", + "description": "World Of Warcraft addon toc file parser", + "main": "lib/index.js", + "scripts": { + "test": "node node_modules/mocha/bin/_mocha -R spec" + }, + "repository": { + "type": "git", + "url": "https://github.com/zekesonxx/wow-toc" + }, + "keywords": [ + "wow", + "toc", + "parser" + ], + "author": "Zeke Sonxx ", + "license": "MIT", + "bugs": { + "url": "https://github.com/zekesonxx/wow-toc/issues" + }, + "homepage": "https://github.com/zekesonxx/wow-toc", + "dependencies": { + "debug": "^2.0.0" + }, + "devDependencies": { + "mocha": "^1.21.4", + "should": "^4.0.4" + } +} diff --git a/test/fixtures/Recount.json b/test/fixtures/Recount.json new file mode 100644 index 0000000..473e618 --- /dev/null +++ b/test/fixtures/Recount.json @@ -0,0 +1,58 @@ +{ + "files" : [ + "embeds.xml", + "locales\\Recount-enUS.lua", + "locales\\Recount-deDE.lua", + "locales\\Recount-esES.lua", + "locales\\Recount-esMX.lua", + "locales\\Recount-frFR.lua", + "locales\\Recount-ptBR.lua", + "locales\\Recount-ruRU.lua", + "locales\\Recount-koKR.lua", + "locales\\Recount-zhTW.lua", + "locales\\Recount-zhCN.lua", + "Recount.lua", + "Fonts.lua", + "colors.lua", + "Widgets.lua", + "WindowOrder.lua", + "Fights.lua", + "Recount_Modes.lua", + "TrackerModules\\TrackerModule_Dispels.lua", + "TrackerModules\\TrackerModule_Interrupts.lua", + "TrackerModules\\TrackerModule_Resurrection.lua", + "TrackerModules\\TrackerModule_CCBreakers.lua", + "TrackerModules\\TrackerModule_PowerGains.lua", + "Tracker.lua", + "roster.lua", + "LazySync.lua", + "deletion.lua", + "zonefilters.lua", + "debug.lua", + "GUI_Main.lua", + "GUI_Detail.lua", + "GUI_DeathGraph.lua", + "GUI_Graph.lua", + "GUI_Reset.lua", + "GUI_Report.lua", + "GUI_Config.lua", + "GUI_Realtime.lua" + ], + "tags" : { + "Author" : "Cryect, ported to 2.4 by Elsia, maintained by Resike from 5.4", + "Interface" : "50400", + "Notes" : "Records Damage and Healing for Graph Based Display.", + "Notes-ruRU" : "Записывает урон и исцеления и отоброжает различные графики.", + "Notes-zhCN" : "基于 Graph 裤开发的伤害/治疗统计插件.", + "Notes-zhTW" : "圖形化顯示的傷害/治療統計插件.", + "OptionalDeps" : "Ace3, LibDropdown-1.0, LibSharedMedia-3.0, LibBossIDs-1.0, LibGraph-2.0", + "SavedVariables" : "RecountDB", + "SavedVariablesPerCharacter" : "RecountPerCharDB", + "Title" : "Recount", + "Version" : "r1269", + "X-Curse-Packaged-Version" : "r1269", + "X-Curse-Project-ID" : "recount", + "X-Curse-Project-Name" : "Recount", + "X-Curse-Repository-ID" : "wow/recount/mainline" + } +} \ No newline at end of file diff --git a/test/fixtures/Recount.toc b/test/fixtures/Recount.toc new file mode 100644 index 0000000..b528353 --- /dev/null +++ b/test/fixtures/Recount.toc @@ -0,0 +1,60 @@ +## Interface: 50400 +## Version: r1269 +## Title: Recount +## Notes: Records Damage and Healing for Graph Based Display. +## Notes-ruRU: Записывает урон и исцеления и отоброжает различные графики. +## Notes-zhCN: 基于 Graph 裤开发的伤害/治疗统计插件. +## Notes-zhTW: 圖形化顯示的傷害/治療統計插件. +## Author: Cryect, ported to 2.4 by Elsia, maintained by Resike from 5.4 +## OptionalDeps: Ace3, LibDropdown-1.0, LibSharedMedia-3.0, LibBossIDs-1.0, LibGraph-2.0 +## SavedVariables: RecountDB +## SavedVariablesPerCharacter: RecountPerCharDB +## X-Curse-Packaged-Version: r1269 +## X-Curse-Project-Name: Recount +## X-Curse-Project-ID: recount +## X-Curse-Repository-ID: wow/recount/mainline + +#@no-lib-strip@ +embeds.xml +#@end-no-lib-strip@ + +locales\Recount-enUS.lua +locales\Recount-deDE.lua +locales\Recount-esES.lua +locales\Recount-esMX.lua +locales\Recount-frFR.lua +locales\Recount-ptBR.lua +locales\Recount-ruRU.lua +locales\Recount-koKR.lua +locales\Recount-zhTW.lua +locales\Recount-zhCN.lua + +Recount.lua + +Fonts.lua +colors.lua +Widgets.lua +WindowOrder.lua + +Fights.lua +Recount_Modes.lua +TrackerModules\TrackerModule_Dispels.lua +TrackerModules\TrackerModule_Interrupts.lua +TrackerModules\TrackerModule_Resurrection.lua +TrackerModules\TrackerModule_CCBreakers.lua +TrackerModules\TrackerModule_PowerGains.lua +Tracker.lua +roster.lua +LazySync.lua +deletion.lua +zonefilters.lua +debug.lua + +GUI_Main.lua +GUI_Detail.lua +GUI_DeathGraph.lua +GUI_Graph.lua +GUI_Reset.lua +GUI_Report.lua +GUI_Config.lua +GUI_Realtime.lua diff --git a/test/fixtures/basic-file.toc b/test/fixtures/basic-file.toc new file mode 100644 index 0000000..1636ba7 --- /dev/null +++ b/test/fixtures/basic-file.toc @@ -0,0 +1,7 @@ +## Interface: 50400 +## Title: Waiting for Godot +## Notes: "Nothing to be done." +## Version: 4.2 +#Comment +Vladimir.xml +Estragon.lua \ No newline at end of file diff --git a/test/fixtures/basic-output.toc b/test/fixtures/basic-output.toc new file mode 100644 index 0000000..4c4d480 --- /dev/null +++ b/test/fixtures/basic-output.toc @@ -0,0 +1,6 @@ +## Title: Bacon +## Interface: 50400 +## Notes: I hate everyone +## Version: 4.3.2.1 +load.xml +bacon.lua \ No newline at end of file diff --git a/test/fixtures/trunicate.toc b/test/fixtures/trunicate.toc new file mode 100644 index 0000000..6351a31 --- /dev/null +++ b/test/fixtures/trunicate.toc @@ -0,0 +1,3 @@ +## Title: Trunicate Me +## X-Long: dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd +somefile.lua \ No newline at end of file diff --git a/test/parse_spec.js b/test/parse_spec.js new file mode 100644 index 0000000..0046fab --- /dev/null +++ b/test/parse_spec.js @@ -0,0 +1,75 @@ +'use strict'; +var fs = require('fs'); +var path = require('path'); +var should = require('should'); +var wowtoc = require('../lib'); + +describe('wow-toc.parse', function() { + it ('should work on a basic file', function () { + var fixture = fs.readFileSync(path.join(__dirname, 'fixtures', 'basic-file.toc'), { encoding: 'utf8' }); + wowtoc.parse(fixture).should.eql({ + tags: { + 'Interface': '50400', + 'Title': 'Waiting for Godot', + 'Notes': '"Nothing to be done."', + 'Version': '4.2' + }, + files: [ + 'Vladimir.xml', + 'Estragon.lua' + ] + }); + }); + it ('should trunicate lines over 1024', function() { + var fixture = fs.readFileSync(path.join(__dirname, 'fixtures', 'trunicate.toc'), { encoding: 'utf8' }); + wowtoc.parse(fixture).tags['X-Long'].should.equal('d'.repeat(1024-11)); + }); + it ('should correctly parse Recount.toc', function() { + /** + * Recount was selected because it's well known has a fairly large toc, + * and it has non-latin characters in some translations. + * I can't find any data on the Recount license, so I'm attributing it + * and leaving it unmodified in test/fixtures/Recount.toc + */ + var fixture = fs.readFileSync(path.join(__dirname, 'fixtures', 'Recount.toc'), { encoding: 'utf8' }); + var correct = fs.readFileSync(path.join(__dirname, 'fixtures', 'Recount.json'), { encoding: 'utf8' }); + wowtoc.parse(fixture).should.eql(JSON.parse(correct)); + }); +}); + +//polyfill from MDN +if (!String.prototype.repeat) { + // jshint ignore:start + String.prototype.repeat = function (count) { + "use strict"; + if (this == null) + throw new TypeError("can't convert " + this + " to object"); + var str = "" + this; + count = +count; + if (count != count) + count = 0; + if (count < 0) + throw new RangeError("repeat count must be non-negative"); + if (count == Infinity) + throw new RangeError("repeat count must be less than infinity"); + count = Math.floor(count); + if (str.length == 0 || count == 0) + return ""; + // Ensuring count is a 31-bit integer allows us to heavily optimize the + // main part. But anyway, most current (august 2014) browsers can't handle + // strings 1 << 28 chars or longer, so : + if (str.length * count >= 1 << 28) + throw new RangeError("repeat count must not overflow maximum string size"); + var rpt = ""; + for (;;) { + if ((count & 1) == 1) + rpt += str; + count >>>= 1; + if (count == 0) + break; + str += str; + } + return rpt; + } + // jshint ignore:end +} \ No newline at end of file diff --git a/test/stringify_spec.js b/test/stringify_spec.js new file mode 100644 index 0000000..2fcfb0c --- /dev/null +++ b/test/stringify_spec.js @@ -0,0 +1,20 @@ +'use strict'; +var fs = require('fs'); +var path = require('path'); +var should = require('should'); +var wowtoc = require('../lib'); + +describe('wow-toc.stringify', function() { + it ('should work with basic data', function() { + var fixture = fs.readFileSync(path.join(__dirname, 'fixtures', 'basic-output.toc'), { encoding: 'utf8' }); + wowtoc.stringify({ + tags: { + 'Title': 'Bacon', + 'Interface': '50400', + 'Notes': 'I hate everyone', + 'Version': '4.3.2.1' + }, + files: ['load.xml', 'bacon.lua'] + }).should.equal(fixture); + }); +}); \ No newline at end of file