diff --git a/packages/bbob-core/README.md b/packages/bbob-core/README.md index 1272302..276b6d0 100644 --- a/packages/bbob-core/README.md +++ b/packages/bbob-core/README.md @@ -1,4 +1,4 @@ -# bbob +# @bbob/core ## Usage @@ -7,5 +7,7 @@ const bbob = require('@bbob/core'); const presetHTML5 = require('@bbob/preset-html5'); const code = `[i]Text[/i]`; -const processor = bbob([presetHTML5]).process(code, {sync: true}).html +const html = bbob([presetHTML5()]).process(code).html; + +console.log(html); // Text ``` diff --git a/packages/bbob-core/lib/index.js b/packages/bbob-core/lib/index.js index cb71e68..d870286 100644 --- a/packages/bbob-core/lib/index.js +++ b/packages/bbob-core/lib/index.js @@ -1,21 +1,44 @@ -class BBob { - constructor(plugins) { - this.plugins = plugins; - } +const parser = require('@bbob/parser'); +const render = require('@bbob/html'); - // parse() { - // - // } - // - // stringify() { - // - // } - // - // process(input) { - // - // } +const { iterate, match } = require('./utils'); + +function walk(cb) { + return iterate(this, cb); } -module.exports = function bbob(...plugins) { - return new BBob(plugins); +module.exports = function bbob(plugs) { + const plugins = typeof plugs === 'function' ? [plugs] : plugs || []; + + let options = { + skipParse: false, + }; + + return { + process(input, opts) { + options = opts || {}; + + const parseFn = options.parser || parser; + const renderFn = options.render || render; + + let tree = options.skipParse + ? input || [] + : parseFn(input, options); + + tree.walk = walk; + tree.match = match; + + plugins.forEach((plugin) => { + tree = plugin(tree) || tree; + }); + + return { + get html() { + return renderFn(tree, tree.options); + }, + tree, + messages: tree.messages, + }; + }, + }; }; diff --git a/packages/bbob-core/lib/utils.js b/packages/bbob-core/lib/utils.js new file mode 100644 index 0000000..89fac36 --- /dev/null +++ b/packages/bbob-core/lib/utils.js @@ -0,0 +1,65 @@ +/* eslint-disable no-plusplus */ +const isObj = value => (typeof value === 'object'); +const isBool = value => (typeof value === 'boolean'); + +function iterate(t, cb) { + const tree = t; + + if (Array.isArray(tree)) { + for (let idx = 0; idx < tree.length; idx++) { + tree[idx] = iterate(cb(tree[idx]), cb); + } + } else if (tree && isObj(tree) && tree.content) { + iterate(tree.content, cb); + } + + return tree; +} + +function same(expected, actual) { + if (typeof expected !== typeof actual) { + return false; + } + + if (!isObj(expected) || expected === null) { + return expected === actual; + } + + if (Array.isArray(expected)) { + return expected.every(exp => [].some.call(actual, act => same(exp, act))); + } + + return Object.keys(expected).every((key) => { + const ao = actual[key]; + const eo = expected[key]; + + if (isObj(eo) && eo !== null && ao !== null) { + return same(eo, ao); + } + + if (isBool(eo)) { + return eo !== (ao === null); + } + + return ao === eo; + }); +} + +function match(expression, cb) { + return Array.isArray(expression) + ? iterate(this, (node) => { + for (let idx = 0; idx < expression.length; idx++) { + if (same(expression[idx], node)) { + return cb(node); + } + } + + return node; + }) + : iterate(this, node => (same(expression, node) ? cb(node) : node)); +} + +module.exports = { + iterate, + match, +}; diff --git a/packages/bbob-core/test/index.test.js b/packages/bbob-core/test/index.test.js index 832aa75..060a5f6 100644 --- a/packages/bbob-core/test/index.test.js +++ b/packages/bbob-core/test/index.test.js @@ -1,5 +1,99 @@ +const core = require('../lib'); + +const stringify = val => JSON.stringify(val); + describe('@bbob/core', () => { - test('1', () => { - expect(1).toBe(1); + test('parse bbcode string to ast and html', () => { + const res = core().process('[style size="15px"]Large Text[/style]'); + const ast = res.tree; + + expect(res.html).toBe(``); + expect(ast).toBeInstanceOf(Array); + expect(stringify(ast)).toEqual(stringify([ + { + tag: 'style', + attrs: { size: '15px' }, + content: ["Large", " ", "Text"] + } + ])) }); + + test('plugin walk api', () => { + const testPlugin = () => (tree) => tree.walk(node => { + if (node.tag === 'mytag') { + node.attrs = { + pass: 1 + }; + + node.content.push('Test'); + } + + return node + }); + + const res = core([testPlugin()]).process('[mytag size="15px"]Large Text[/mytag]'); + const ast = res.tree; + + expect(ast).toBeInstanceOf(Array); + expect(ast.walk).toBeInstanceOf(Function); + expect(stringify(ast)).toEqual(stringify([ + { + tag: 'mytag', + attrs: { + pass: 1 + }, + content: [ + 'Large', + ' ', + 'Text', + 'Test' + ] + } + ])); + }); + + test('plugin match api', () => { + const testPlugin = () => (tree) => tree.match([{ tag: 'mytag1' }, { tag: 'mytag2' }], node => { + if (node.attrs) { + node.attrs['pass'] = 1 + } + + return node + }); + + const res = core([testPlugin()]).process(`[mytag1 size="15"]Tag1[/mytag1][mytag2 size="16"]Tag2[/mytag2][mytag3]Tag3[/mytag3]`); + const ast = res.tree; + + expect(ast).toBeInstanceOf(Array); + expect(ast.walk).toBeInstanceOf(Function); + expect(stringify(ast)).toEqual(stringify([ + { + tag: 'mytag1', + attrs: { + size: '15', + pass: 1 + }, + content: [ + 'Tag1' + ] + }, + { + tag: 'mytag2', + attrs: { + size: '16', + pass: 1 + }, + content: [ + 'Tag2' + ] + }, + { + tag: 'mytag3', + attrs: {}, + content: [ + 'Tag3' + ] + } + ])); + }) }); diff --git a/packages/bbob-core/test/utils.test.js b/packages/bbob-core/test/utils.test.js new file mode 100644 index 0000000..e57c21d --- /dev/null +++ b/packages/bbob-core/test/utils.test.js @@ -0,0 +1,29 @@ +const { iterate } = require('../lib/utils'); + +describe('@bbob/core utils', () => { + test('iterate', () => { + const testArr = [{ + one: true, + content: [{ oneInside: true }] + }, { + two: true, + content: [{ twoInside: true }] + }]; + + const resultArr = iterate(testArr, node => { + node.pass = 1; + + return node; + }); + + expect(resultArr).toEqual([{ + one: true, + pass: 1, + content: [{ oneInside: true, pass: 1, }] + }, { + two: true, + pass: 1, + content: [{ twoInside: true, pass: 1, }] + }]); + }); +});