Skip to content

Commit

Permalink
Support variables to be defined as by JSON (#1117)
Browse files Browse the repository at this point in the history
  • Loading branch information
crphang authored and Marvin Chin committed Apr 10, 2020
1 parent bf1239d commit c25c7bc
Show file tree
Hide file tree
Showing 18 changed files with 151 additions and 20 deletions.
39 changes: 39 additions & 0 deletions docs/userGuide/syntax/variables.mbdf
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,26 @@ However, this is a combination of *both* syntaxes above, and thus this will allo

</box>

### Defining variables with JSON

You could also have your variables defined in a JSON file to define multiple variables in a more concise manner.

{{ icon_example }}
`variables.md`:
```html
<variable from="variables.json" />
```

`variables.json`:
```json
{
"variable1": "This is the first variable",
"variable2": "This is the second variable"
}
```

Variables defined in JSON file will be scoped according to where it is being referenced.

### Variables: Tips and Tricks

**Variables can refer to other variables** that are declared earlier, including built-in variables.
Expand Down Expand Up @@ -218,6 +238,25 @@ You must use the `safe` filter when using such variables:
<code>{<span></span>{ const_note }}</code> :fas-arrow-right: <span style="color: blue">Note: </span> This is a constant.
</div>

When defining variables with incomplete HTML fragments, we can define variables as a separate JSON file.

{{ icon_example }} variables containing HTML fragments:<br>

`index.md`:
```html
<variable from="variableFileName.json" />
```

`variableFileName.json`:
```json
{
"back_fragment": "Back</div>",
"front_fragment": "<div>Front"
}
```

<code>{<span></span>{ front_fragment }}</code> and <code>{<span></span>{ back_fragment }}</code> :fas-arrow-right: Front and Back

<span id="short" class="d-none">

Global variables:
Expand Down
28 changes: 23 additions & 5 deletions src/Site.js
Original file line number Diff line number Diff line change
Expand Up @@ -504,10 +504,10 @@ class Site {
this.baseUrlMap.forEach((base) => {
const userDefinedVariables = {};
Object.assign(userDefinedVariables, markbindVariable);

const userDefinedVariablesPath = path.resolve(base, USER_VARIABLES_PATH);
const userDefinedVariablesDir = path.dirname(userDefinedVariablesPath);
let content;
try {
const userDefinedVariablesPath = path.resolve(base, USER_VARIABLES_PATH);
content = fs.readFileSync(userDefinedVariablesPath, 'utf8');
} catch (e) {
content = '';
Expand All @@ -522,9 +522,27 @@ class Site {
const $ = cheerio.load(content);
$('variable,span').each(function () {
const name = $(this).attr('name') || $(this).attr('id');
// Process the content of the variable with nunjucks, in case it refers to other variables.
const html = nunjuckUtils.renderEscaped(nunjucks, $(this).html(), userDefinedVariables);
userDefinedVariables[name] = html;
const variableSource = $(this).attr('from');

if (variableSource !== undefined) {
try {
const variableFilePath = path.resolve(userDefinedVariablesDir, variableSource);
const jsonData = fs.readFileSync(variableFilePath);
const varData = JSON.parse(jsonData);
Object.entries(varData).forEach(([varName, varValue]) => {
// Process the content of the variable with nunjucks, in case it refers to other variables.
const variableValue = nunjuckUtils.renderEscaped(nunjucks, varValue, userDefinedVariables);

userDefinedVariables[varName] = variableValue;
});
} catch (err) {
logger.warn(`Error ${err.message}`);
}
} else {
// Process the content of the variable with nunjucks, in case it refers to other variables.
const html = nunjuckUtils.renderEscaped(nunjucks, $(this).html(), userDefinedVariables);
userDefinedVariables[name] = html;
}
});
});
}
Expand Down
45 changes: 34 additions & 11 deletions src/lib/markbind/src/parser.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ const ensurePosix = require('ensure-posix-path');
const componentParser = require('./parsers/componentParser');
const componentPreprocessor = require('./preprocessors/componentPreprocessor');
const nunjuckUtils = require('./utils/nunjuckUtils');
const logger = require('../../../util/logger');

const _ = {};
_.clone = require('lodash/clone');
Expand Down Expand Up @@ -68,6 +69,7 @@ class Parser {
*/
// eslint-disable-next-line class-methods-use-this
extractPageVariables(fileName, data, userDefinedVariables, includedVariables) {
const fileDir = path.dirname(fileName);
const $ = cheerio.load(data);
const pageVariables = {};
Parser.VARIABLE_LOOKUP.set(fileName, new Map());
Expand All @@ -93,21 +95,42 @@ class Parser {
importedVariables[name] = `{{${alias}.${name}}}`;
});
});
$('variable').each(function () {
const variableElement = $(this);
const variableName = variableElement.attr('name');
if (!variableName) {
// eslint-disable-next-line no-console
console.warn(`Missing 'name' for variable in ${fileName}\n`);
return;
}
const setPageVariable = (variableName, rawVariableValue) => {
const otherVariables = {
...importedVariables,
...pageVariables,
...userDefinedVariables,
...includedVariables,
};
const variableValue = nunjuckUtils.renderEscaped(nunjucks, rawVariableValue, otherVariables);
if (!pageVariables[variableName]) {
const variableValue = nunjuckUtils.renderEscaped(nunjucks, md.renderInline(variableElement.html()), {
...importedVariables, ...pageVariables, ...userDefinedVariables, ...includedVariables,
});
pageVariables[variableName] = variableValue;
Parser.VARIABLE_LOOKUP.get(fileName).set(variableName, variableValue);
}
};
$('variable').each(function () {
const variableElement = $(this);
const variableName = variableElement.attr('name');
const variableSource = $(this).attr('from');
if (variableSource !== undefined) {
try {
const variableFilePath = path.resolve(fileDir, variableSource);
const jsonData = fs.readFileSync(variableFilePath);
const varData = JSON.parse(jsonData);
Object.entries(varData).forEach(([varName, varValue]) => {
setPageVariable(varName, varValue);
});
} catch (err) {
logger.warn(`Error ${err.message}`);
}
} else {
if (!variableName) {
// eslint-disable-next-line no-console
console.warn(`Missing 'name' for variable in ${fileName}\n`);
return;
}
setPageVariable(variableName, md.renderInline(variableElement.html()));
}
});
return { ...importedVariables, ...pageVariables };
}
Expand Down
3 changes: 3 additions & 0 deletions src/template/default/_markbind/variables.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"jsonVariableExample": "Your variables can be defined here as well"
}
4 changes: 3 additions & 1 deletion src/template/default/_markbind/variables.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
<variable name="example">
To inject this HTML segment in your markbind files, use {{ example }} where you want to place it.
More generally, surround the segment's id with double curly braces.
</variable>
</variable>

<variable from="variables.json" />
3 changes: 3 additions & 0 deletions src/template/minimal/_markbind/variables.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"jsonVariableExample": "Your variables can be defined here as well"
}
4 changes: 3 additions & 1 deletion src/template/minimal/_markbind/variables.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
<variable name="example">
To inject this HTML segment in your markbind files, use {{ example }} where you want to place it.
More generally, surround the segment's id with double curly braces.
</variable>
</variable>

<variable from="variables.json" />
6 changes: 6 additions & 0 deletions test/functional/test_site/_markbind/variable.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"back": "back </div>",
"front": "<div> front",
"jsonVar1": "Json Variable can be referenced",
"jsonVar2": "Referencing jsonVar1: {{ jsonVar1 }}"
}
1 change: 1 addition & 0 deletions test/functional/test_site/_markbind/variables.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,4 @@
<variable name="global_variable_overriding_included_variable">Global Variable Overriding Included Variable</variable>
<variable name="global_variable">Global Variable</variable>
<variable name="page_global_variable_overriding_page_variable">Global Variable Overriding Page Variable</variable>
<variable from="variable.json"></variable>
6 changes: 6 additions & 0 deletions test/functional/test_site/expected/_markbind/variable.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"front": "<div> front",
"back": "back </div>",
"jsonVar1": "Json Variable can be referenced",
"jsonVar2": "Referencing jsonVar1: {{ jsonVar1 }}"
}
6 changes: 5 additions & 1 deletion test/functional/test_site/expected/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -150,12 +150,16 @@ <h3 id="testing-site-nav">Testing Site-Nav<a class="fa fa-anchor" href="#testing
<p>Here is a repeated footnote to <span for="pop:footnote1" v-b-popover.hover.top.html="popoverGenerator" v-b-tooltip.hover.top.html="tooltipContentGetter" v-on:mouseover="$refs['pop:footnote1'].show()" class="trigger"><sup class="footnote-ref"><a aria-describedby="footnote-label" href="#footnote1" id="footnoteref1:1">[1]</a></sup></span></p>
<p><strong>Inline footnotes:</strong> Here is an inline note.<span for="pop:footnote3" v-b-popover.hover.top.html="popoverGenerator" v-b-tooltip.hover.top.html="tooltipContentGetter" v-on:mouseover="$refs['pop:footnote3'].show()" class="trigger"><sup class="footnote-ref"><a aria-describedby="footnote-label" href="#footnote3" id="footnoteref3">[3]</a></sup></span></p>
</div>
<p><strong>Json Variable</strong></p>
<div> front back </div>
<p>Json Variable can be referenced Referencing jsonVar1: Json Variable can be referenced</p>
<p><strong>Variables that reference another variable</strong></p>
<p>This variable can be referenced.</p>
<p>References can be several levels deep.</p>
<p><strong>Page Variable</strong></p>
<div></div>
Page Variable
<div></div>
<p>Page Variable Json Variable</p>
<p><strong>Page Variable with HTML and MD</strong></p>
<div></div>
Page Variable with <span style="color: blue;">HTML</span> and <strong>Markdown</strong>
Expand Down
3 changes: 3 additions & 0 deletions test/functional/test_site/expected/jsonPageVariable.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"json_page_variable": "Json Variable"
}
10 changes: 9 additions & 1 deletion test/functional/test_site/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,12 @@ tags: ["tag-frontmatter-shown", "tag-included-file", "+tag-exp*", "-tag-exp-hidd

<include src="testFootnotes.md" />

**Json Variable**

{{ front }} {{ back }}

{{ jsonVar1 }} {{ jsonVar2 }}

**Variables that reference another variable**

{{finalized_value}}
Expand All @@ -27,7 +33,9 @@ tags: ["tag-frontmatter-shown", "tag-included-file", "+tag-exp*", "-tag-exp-hidd
**Page Variable**

<variable name="page_variable">Page Variable</variable>
{{ page_variable }}
<variable from="jsonPageVariable.json" />

{{ page_variable }} {{ json_page_variable }}

**Page Variable with HTML and MD**

Expand Down
3 changes: 3 additions & 0 deletions test/functional/test_site/jsonPageVariable.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"json_page_variable": "Json Variable"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"jsonVariableExample": "Your variables can be defined here as well"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"jsonVariableExample": "Your variables can be defined here as well"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"jsonVariableExample": "Your variables can be defined here as well"
}
1 change: 1 addition & 0 deletions test/unit/parser.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ const MarkBind = require('../../src/lib/markbind/src/parser.js');
const { USER_VARIABLES_DEFAULT } = require('./utils/data');

jest.mock('fs');
jest.mock('../../src/util/logger');

afterEach(() => fs.vol.reset());

Expand Down

0 comments on commit c25c7bc

Please sign in to comment.