Skip to content

Commit

Permalink
Merge pull request #6 from ulkajs/featureadd
Browse files Browse the repository at this point in the history
async support in ulka and tests updated
  • Loading branch information
coderosh-zz authored Aug 6, 2020
2 parents 1c919e0 + 4e1c858 commit bb3912f
Show file tree
Hide file tree
Showing 11 changed files with 133 additions and 99 deletions.
18 changes: 10 additions & 8 deletions bin/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

const path = require('path');
const fs = require('fs');
const parse = require('../src/parse');
const parseUlka = require('../src/parse');

const args = process.argv.splice(2);

Expand Down Expand Up @@ -34,8 +34,9 @@ if (fs.statSync(templatePath).isDirectory()) {
.replace('.ulka', '.html');

createDirectories(path.parse(newOutputPath).dir).then(_ => {
generateHtml(file, newOutputPath);
console.log('>> Html File generated: ' + newOutputPath);
generateHtml(file, newOutputPath).then(_ => {
console.log('>> Html File generated: ' + newOutputPath);
});
});
});
} else {
Expand All @@ -44,15 +45,16 @@ if (fs.statSync(templatePath).isDirectory()) {
path.parse(outputPath).dir,
path.parse(templatePath).name + '.html',
);
generateHtml(templatePath, newOutputPath);
console.log('>> Html File generated: ' + newOutputPath);
generateHtml(templatePath, newOutputPath).then(_ => {
console.log('>> Html File generated: ' + newOutputPath);
});
});
}

function generateHtml(templatePath, outputPath) {
async function generateHtml(templatePath, outputPath) {
const ulkaTemplate = fs.readFileSync(templatePath, 'utf-8');
const htmlTemplate = parse(ulkaTemplate);
fs.writeFileSync(outputPath, htmlTemplate);
const htmlTemplate = await parseUlka(ulkaTemplate);
fs.writeFileSync(outputPath, htmlTemplate.trim());
}

async function createDirectories(pathname) {
Expand Down
2 changes: 1 addition & 1 deletion examples/cli/output/ulkas/about.html
Original file line number Diff line number Diff line change
@@ -1 +1 @@
index
about
2 changes: 1 addition & 1 deletion examples/cli/ulkas/about.ulka
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
{% const page = "index" %}
{% const page = "about" %}

{% page %}
10 changes: 10 additions & 0 deletions examples/parse/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<html>
<head>
<title>Roshan Acharya</title>
</head>

<body>
<div>Name: Roshan Acharya</div>
<div>Age: 20</div>
</body>
</html>
6 changes: 3 additions & 3 deletions examples/parse/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,6 @@ const ulkaTemplate = fs.readFileSync(
'utf-8',
);

const parsedHtml = ulkaParser.parse(ulkaTemplate);

fs.writeFileSync(path.join(__dirname, 'index.html'), parsedHtml);
ulkaParser.parse(ulkaTemplate).then(html => {
fs.writeFileSync(path.join(__dirname, 'index.html'), html.trim());
});
2 changes: 1 addition & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "ulka-parser",
"version": "0.2.6",
"version": "0.2.7",
"description": "Templating engine for ulka static site generator",
"main": "src/index.js",
"scripts": {
Expand Down
23 changes: 14 additions & 9 deletions src/engine.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,20 @@ const path = require('path');
const fs = require('fs');
const parse = require('./parse');

const engine = ({ globalOptions }) => {
return (filePath, options, callback) => {
const content = fs.readFileSync(filePath, 'utf-8');
const parsed = parse(
content,
{ ...globalOptions, ...options },
{ base: path.dirname(filePath) },
);
return callback(null, parsed);
const engine = globalOptions => {
return async (filePath, options, callback) => {
try {
const content = fs.readFileSync(filePath, 'utf-8');
const parsed = await parse(
content,
{ ...globalOptions, ...options },
{ base: path.dirname(filePath) },
);
return callback(null, parsed);
} catch (error) {
console.log(error.message);
throw error;
}
};
};

Expand Down
96 changes: 51 additions & 45 deletions src/parse.js
Original file line number Diff line number Diff line change
@@ -1,60 +1,66 @@
const vm = require('vm');
const path = require('path');
const fs = require('fs');
const { replaceString, hasEqualSign } = require('./utils');
const { replaceString, hasEqualSign, replaceAsync } = require('./utils');

const defaultOptions = {
base: '',
};

function parser(ulkaTemplate, values = {}, options = defaultOptions) {
async function parser(ulkaTemplate, values = {}, options = defaultOptions) {
try {
return ulkaTemplate
.replace(/\\?{%(.*?)%}/gs, (...args) => {
let jsCode = args[1];

values = {
...values,
require: reqPath => {
const rPath = path.join(options.base, reqPath);
if (fs.existsSync(rPath)) return require(rPath);
return require(reqPath);
},
console,
};

// If first index is equal sign then remove equal or minus sign
const containsEqualsInFirstIndex = jsCode[0] === '=';
const containsMinusInFirstIndex = jsCode[0] === '-';

if (containsEqualsInFirstIndex || containsMinusInFirstIndex)
jsCode = jsCode.substr(1);

/*
- {% sth = "roshan" %}
- \{% sth %} => {% sth %}
- \\{% sth %} => \{% "roshan" %}
*/
if (args[0][0] === '\\' && ulkaTemplate[args[2] - 1] !== '\\')
return args[0].slice(1);

jsCode = jsCode.replace(/(var |let |const )/gs, '');

const result = vm.runInNewContext(jsCode, values);

const codeWithoutString = replaceString(jsCode, '');
const containsEqual = hasEqualSign(codeWithoutString);
const shouldPrintResult =
(!containsEqual || containsEqualsInFirstIndex) &&
!containsMinusInFirstIndex;

return !shouldPrintResult ? '' : result || '';
})
.trim();
return await replaceAsync(
ulkaTemplate,
/\\?{%(.*?)%}/gs,
parseCallback(ulkaTemplate, values, options),
);
} catch (e) {
console.log(`Ulka Praser Error: `, e.message);
console.log(e.message);
throw e;
}
}

const parseCallback = (ulkaTemplate, values, options) => async (...args) => {
let jsCode = args[1];

values = {
require: reqPath => {
const rPath = path.join(options.base, reqPath);
if (fs.existsSync(rPath)) return require(rPath);
return require(reqPath);
},
...values,
console,
};

// If first index is equal sign then remove equal or minus sign
const containsEqualsInFirstIndex = jsCode[0] === '=';
const containsMinusInFirstIndex = jsCode[0] === '-';

if (containsEqualsInFirstIndex || containsMinusInFirstIndex)
jsCode = jsCode.substr(1);

/*
- {% sth = "roshan" %}
- \{% sth %} => {% sth %}
- \\{% sth %} => \{% "roshan" %}
*/
if (args[0][0] === '\\' && ulkaTemplate[args[2] - 1] !== '\\')
return args[0].slice(1);

jsCode = jsCode.replace(/(var |let |const )/gs, '');

const result = vm.runInNewContext(jsCode, values);

const codeWithoutString = replaceString(jsCode, '');
const containsEqual = hasEqualSign(codeWithoutString);
const shouldPrintResult =
(!containsEqual || containsEqualsInFirstIndex) &&
!containsMinusInFirstIndex;

const dataToReturn = await result;

return !shouldPrintResult ? '' : dataToReturn || '';
};

module.exports = parser;
11 changes: 11 additions & 0 deletions src/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,18 @@ const replaceString = (text, value = '') =>

const hasEqualSign = code => /(?:^|[^=])=(?:$|[^=>])/gs.exec(code) !== null;

async function replaceAsync(str, regex, asyncFn) {
const promises = [];
str.replace(regex, (match, ...args) => {
const promise = asyncFn(match, ...args);
promises.push(promise);
});
const data = await Promise.all(promises);
return str.replace(regex, () => data.shift());
}

module.exports = {
replaceString,
hasEqualSign,
replaceAsync,
};
60 changes: 30 additions & 30 deletions tests/parse.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,84 +2,84 @@ const parse = require('../src/parse');

describe('parse funcion', () => {
describe('given a ulka template', () => {
test("returns 'Roshan Acharya' when name is passed as javascript with cotext of {name: 'Roshan Acharya'}", () => {
expect(parse('{% name %}', { name: 'Roshan Acharya' })).toBe(
test("returns 'Roshan Acharya' when name is passed as javascript with cotext of {name: 'Roshan Acharya'}", async () => {
expect(await parse('{% name %}', { name: 'Roshan Acharya' })).toBe(
'Roshan Acharya',
);
});

test("returns '<h1>Roshan Acharya</h1>' when template used called inside html tags with context of {name: 'Roshan Acharya'}", () => {
expect(parse('<h1>{% name %}</h1>', { name: 'Roshan Acharya' })).toBe(
'<h1>Roshan Acharya</h1>',
);
test("returns '<h1>Roshan Acharya</h1>' when template used called inside html tags with context of {name: 'Roshan Acharya'}", async () => {
expect(
await parse('<h1>{% name %}</h1>', { name: 'Roshan Acharya' }),
).toBe('<h1>Roshan Acharya</h1>');
});

test('throws error when undeclared variable is used inside ulka tags', () => {
expect(() => {
parse('{% undeclaredVariable %}');
}).toThrow('undeclaredVariable is not defined');
test('throws error when undeclared variable is used inside ulka tags', async () => {
expect(async () => {
await parse('{% undeclaredVariable %}');
}).rejects.toThrow('undeclaredVariable is not defined');
});

test("returns '2,4,6' when [1, 2, 3].map(e => e*2) is called inside ulka tags with no context", () => {
expect(parse('{% [1, 2, 3].map(e => e * 2) %}')).toBe('2,4,6');
test("returns '2,4,6' when [1, 2, 3].map(e => e*2) is called inside ulka tags with no context", async () => {
expect(await parse('{% [1, 2, 3].map(e => e * 2) %}')).toBe('2,4,6');
});
});

describe('given a string with no javascript', () => {
test("returns 'a string with no javascript'", () => {
expect(parse('a string with no javascript')).toBe(
test("returns 'a string with no javascript'", async () => {
expect(await parse('a string with no javascript')).toBe(
'a string with no javascript',
);
});
});

describe('given nothing', () => {
test('throws a type error', () => {
expect(parse).toThrow(TypeError);
test('throws a type error', async () => {
expect(parse).rejects.toThrow(TypeError);
});
});

describe('given assignment of variable', () => {
test('returns empty string on assignment only', () => {
expect(parse("{% const name = 'Roshan Acharya' %}")).toBe('');
test('returns empty string on assignment only', async () => {
expect(await parse("{% const name = 'Roshan Acharya' %}")).toBe('');
});

test('returns a value assigned', () => {
test('returns a value assigned', async () => {
const template = `
{% const name = "Roshan Acharya" %}
{% name %}
`;
expect(parse(template)).toBe('Roshan Acharya');
expect((await parse(template)).trim()).toBe('Roshan Acharya');
});
});

describe('given escaped tags', () => {
test('returns ulka tags', () => {
test('returns ulka tags', async () => {
const template = `
{%const name = "Roshan Acharya"%}
\\{% name %}
`;
expect(parse(template)).toBe('{% name %}');
expect((await parse(template)).trim()).toBe('{% name %}');
});

test('returns value with \\ should escape the escape', () => {
expect(parse('\\\\{% name %}', { name: 'Roshan Acharya' })).toBe(
'\\Roshan Acharya',
);
test('returns value with \\ should escape the escape', async () => {
expect(
(await parse('\\\\{% name %}', { name: 'Roshan Acharya' })).trim(),
).toBe('\\Roshan Acharya');
});
});

describe('given a statement with assignment with equal tags', () => {
test('should return roshan acharya', () => {
expect(parse(`{%= const name = "Roshan Acharya" %}`)).toBe(
test('should return roshan acharya', async () => {
expect(await parse(`{%= const name = "Roshan Acharya" %}`)).toBe(
'Roshan Acharya',
);
});
});

describe('given a variable with minus tags', () => {
test('should return empty string', () => {
expect(parse(`{%- name %}`, { name: 'Roshan Acharya' })).toBe('');
test('should return empty string', async () => {
expect(await parse(`{%- name %}`, { name: 'Roshan Acharya' })).toBe('');
});
});
});

0 comments on commit bb3912f

Please sign in to comment.