Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat/better templates #7

Merged
merged 3 commits into from
Sep 30, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 39 additions & 8 deletions src/transforms/template-data.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@ import { frontmatter } from './frontmatter.js';
import { resolve } from '../resolver.js';
import { SissiConfig } from "../sissi-config.js";

const TEMPLATE_REGEX = /\{\{\s*([\w\.\[\]]+)\s*\}\}/g;
const JSON_PATH_REGEX = /^\w+((?:\.\w+)|(?:\[\d+\]))*$/
const JSON_PATH_TOKEN = /(^\w+)|(\.\w+)|(\[\d+\])/g
const TEMPLATE_REGEX = /\{\{\s*([\w\.\[\]]+)(?:\((.*)\))?(?:\s*\|\s([a-zA-Z*]\w*)?(?:\s*\:\s*(.+))?)?\s*\}\}/g;
const JSON_PATH_REGEX = /^[a-zA-Z_]\w*((?:\.\w+)|(?:\[\d+\]))*$/
const JSON_PATH_TOKEN = /(^[a-zA-Z_]\w*)|(\.[a-zA-Z_]\w*)|(\[\d+\])/g

/**
* Poor girl's jsonpath
Expand All @@ -32,24 +32,55 @@ export function dataPath(path) {
return result;
}
}
/**
*
* @param {string} args a string with a comma separated
* @param {any} data data object that is used to fill in data-path parameters
* @returns
*/
export function parseArguments(args, data) {
if (!args) return [];
return args.trim().split(/\s*,\s*/).map(arg => {
if (JSON_PATH_REGEX.test(arg)) {
return dataPath(arg)(data);
}
try {
return JSON.parse(arg)
} catch (_err) {
return null;
}
});
}

/**
* Poor girl's handlebars
*
* @param {string} str the template content
* @returns {(data: any) => string} a function that takes a data object and returns the processed template
* @returns {(data: any, filters: Map<string, function>) => string} a function that takes a data object and returns the processed template
*/
export function template(str) {
return (data) => {
return str.replace(TEMPLATE_REGEX, (_, expr) => {
const result = dataPath(expr)(data);
return dataPath(expr)(data)
return (data, filters) => {
return str.replace(TEMPLATE_REGEX, (_, expr, params, filter, filterParams) => {
let result = dataPath(expr)(data);
const args = parseArguments(params, data);

if (typeof result === "function") {
result = result(...args);
}

if (filter && filters instanceof Map &&
filters.has(filter) && typeof filters.get(filter) === 'function') {
const filterArgs = parseArguments(filterParams, data);
result = filters.get(filter)(result, ...filterArgs);
}
return result;
});
}
}

/**
* Complete Template processing function
*
* @param {SissiConfig} config
* @param {any} data
* @param {string} inputFile
Expand Down
50 changes: 48 additions & 2 deletions tests/transforms/template-data.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { describe, it } from 'node:test';
import assert from 'node:assert/strict';
import path from 'node:path';

import { dataPath, handleTemplateFile, template } from '../../src/transforms/template-data.js';
import { dataPath, handleTemplateFile, parseArguments, template } from '../../src/transforms/template-data.js';
import { SissiConfig } from '../../src/sissi-config.js';
import md from '../../src/md.js';

Expand All @@ -13,6 +13,12 @@ const TEST_DATA = {
'authors': ['Joe', 'Lea'],
},
'theMatrix': [[1,2,3],[4,5,6],[7,8,9]],
date() {
return '12.03.2024'
},
greet(str) {
return 'Hello ' + str;
}
}

const TEST_MD = `---
Expand All @@ -32,6 +38,12 @@ const TEST_TEMPLATE_EXPECTED = `<h1>This is a title</h1>
<p>Blog article by Lea</p>
`

const TEST_TEMPLATE_2 = `{{ date }}`
const TEST_TEMPLATE_EXPECTED_2 = '12.03.2024';

const TEST_TEMPLATE_3 = `{{ greet(meta.authors[1]) }}`
const TEST_TEMPLATE_EXPECTED_3 = 'Hello Lea';

describe('dataPath tests', () => {

it('creates a function to get data properties', () => {
Expand All @@ -57,14 +69,48 @@ describe('dataPath tests', () => {
assert.throws(() => dataPath('object.[1]'));
assert.throws(() => dataPath('object..[1]'));
});
});

describe('parseArguments function', () => {
it('should parse argument lists', () => {
assert.deepEqual(parseArguments('"DE"', {}), ["DE"]);
assert.deepEqual(parseArguments('12.3, "DE"', {}), [12.3, "DE"]);
});

it('should support data path arguments', () => {
assert.deepEqual(parseArguments('meta.author, 12', {meta:{author:'Lea'}}), ['Lea', 12]);
});

});

describe('template function', () => {
it('should insert data into the placeholders wrapped in double curly brackets', () => {
assert.equal(template(TEST_TEMPLATE)(TEST_DATA), TEST_TEMPLATE_EXPECTED);
});
})

it('should be able to invoke functions', () => {
assert.equal(template(TEST_TEMPLATE_2)(TEST_DATA), TEST_TEMPLATE_EXPECTED_2);
assert.equal(template(TEST_TEMPLATE_3)(TEST_DATA), TEST_TEMPLATE_EXPECTED_3);
});

it('should be able to apply a filter', () => {
const filters = new Map();
filters.set('shout', (str) => (str||'').toUpperCase());
const result = template('{{greeting | shout }}')({greeting: "Hello"}, filters);

assert.equal(result, "HELLO");
});

it('should be able to apply a filter with additional parameters', () => {
const data = { greeting: 'Hello Lea'}
const filters = new Map();
filters.set('piratify', (str, prefix = 'Yo-ho-ho', suffix = 'yarrr') => `${prefix}! ${str}, ${suffix}!`);

assert.equal(template('{{ greeting | piratify }}')(data, filters), 'Yo-ho-ho! Hello Lea, yarrr!');
assert.equal(template('{{ greeting | piratify: "AYE" }}')(data, filters), 'AYE! Hello Lea, yarrr!');
assert.equal(template('{{ greeting | piratify: "Ahoy", "matey" }}')(data, filters), 'Ahoy! Hello Lea, matey!');
});
});

describe('handleTemplateFile function', () => {
it('should work with the default markdown plugin', async () => {
Expand Down