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, }]
+ }]);
+ });
+});