Skip to content

Commit

Permalink
Add parameter replacement
Browse files Browse the repository at this point in the history
  • Loading branch information
tai2 committed Jan 27, 2024
1 parent 1d5129c commit b8c3e1e
Show file tree
Hide file tree
Showing 9 changed files with 202 additions and 34 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -230,7 +230,7 @@ default mappings of the `paragraph`and `image` elements look like these.
/>
```

## Parameter Replacement (WIP)
## Parameter Replacement

With content and attribute specifiers, you can reference a special key name
starting with `param:`. When it is specified, decor tries to resolve the name
Expand Down
4 changes: 4 additions & 0 deletions contents/parameters.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"id": "replaced ID",
"title": "replaced title"
}
11 changes: 11 additions & 0 deletions contents/template_with_parameter_replacement.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<!DOCTYPE html>
<html>
<head>
<title data-decor-attribute-id="param:id" data-decor-content="param:title">
Decor default template
</title>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
</head>
<body></body>
</html>
29 changes: 26 additions & 3 deletions src/decor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@ import { PartialTemplate, Template } from './template.ts'
import { parsePartialTemplate, parseTemplate } from './extract_template.ts'
import { templateRenderer } from './template_renderer.ts'
import { renderHtml } from './render_html.ts'
import {
replaceDocumentParameters,
replaceTemplateParameters,
} from './replace_parameters.ts'
import assets from './assets.json' assert { type: 'json' }

async function renderDefaultTemplate(options: { output?: string }) {
Expand All @@ -30,6 +34,7 @@ async function runOneshot(options: {
input?: string
output?: string
defaultTemplate: [HTMLDocument, Template]
parameters: Record<string, string>
}) {
// Prepare the template
const [defaultHtmlDocument, defaultTemplate] = options.defaultTemplate
Expand All @@ -50,12 +55,15 @@ async function runOneshot(options: {
}
}

template = partialTemplate as Template
document = templateDocument
template = partialTemplate as Template

replaceDocumentParameters(document, options.parameters)
replaceTemplateParameters(template, options.parameters)
} else {
// Use the default template but clone the document so that we can reuse the original data.
template = defaultTemplate
document = defaultHtmlDocument.cloneNode(true) as HTMLDocument
template = defaultTemplate
}

// Execute rendering
Expand Down Expand Up @@ -94,6 +102,7 @@ async function runWatch(options: {
input?: string
output: string
defaultTemplate: [HTMLDocument, Template]
parameters: Record<string, string>
}) {
const watchTargets: string[] = []
if (options.template) {
Expand Down Expand Up @@ -129,7 +138,7 @@ async function main() {
...options
} = parseArgs(Deno.args, {
boolean: ['help', 'show-default-template', 'watch'],
string: ['template', 'output'],
string: ['template', 'output', 'parameters'],
})

if (options['show-default-template']) {
Expand All @@ -151,6 +160,18 @@ async function main() {
const defaultTemplate = parseTemplate(
assets.defaultTemplate,
)

let parameters: Record<string, string>
if (options.parameters) {
const parametersString = Deno.readTextFileSync(options.parameters)
parameters = JSON.parse(parametersString)
} else {
parameters = {}
}

replaceDocumentParameters(defaultTemplate[0], parameters)
replaceTemplateParameters(defaultTemplate[1], parameters)

if (options.watch) {
if (options.output === undefined) {
console.error('--output is required when --watch is specified')
Expand All @@ -162,13 +183,15 @@ async function main() {
input: input?.toString(),
output: options.output,
defaultTemplate,
parameters,
})
} else {
await runOneshot({
template: options.template,
input: input?.toString(),
output: options.output,
defaultTemplate,
parameters,
})
}
} catch (e) {
Expand Down
31 changes: 31 additions & 0 deletions src/decor_test.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
import {
assert,
assertEquals,
assertExists,
assertStringIncludes,
} from './deps/std/assert.ts'
import * as path from './deps/std/path.ts'
import * as fs from './deps/std/fs.ts'
import { delay } from './deps/std/async.ts'
import { DOMParser } from './deps/deno-dom.ts'

function decor(...args: string[]): Deno.Command {
const dirname = path.dirname(path.fromFileUrl(import.meta.url))
Expand Down Expand Up @@ -208,3 +210,32 @@ Deno.test(
)
},
)

Deno.test('decor runs parameter replacement', () => {
const dirname = path.dirname(path.fromFileUrl(import.meta.url))
const templatePath = path.join(
dirname,
'../contents/template_with_parameter_replacement.html',
)
const parametersPath = path.join(
dirname,
'../contents/parameters.json',
)

const { code, stdout, stderr } = decor(
'--template',
templatePath,
'--parameters',
parametersPath,
).outputSync()

assertEquals(code, 0)

const outputHtml = new TextDecoder().decode(stdout)
const document = new DOMParser().parseFromString(outputHtml, 'text/html')!
const title = document.querySelector('title')

assertExists(title)
assertEquals(title.getAttribute('id'), 'replaced ID')
assertEquals(title.innerHTML, 'replaced title')
})
32 changes: 2 additions & 30 deletions src/extract_template.ts
Original file line number Diff line number Diff line change
@@ -1,38 +1,10 @@
import { DOMParser, HTMLDocument } from './deps/deno-dom.ts'
import { PartialTemplate, Template } from './template.ts'
import { createPartialTemplate, PartialTemplate, Template } from './template.ts'

export function extractPartialTemplate(
templateDocument: HTMLDocument,
): PartialTemplate {
const template: PartialTemplate = {
heading1: null,
heading2: null,
heading3: null,
heading4: null,
heading5: null,
heading6: null,
thematic_break: null,
paragraph: null,
code_block: null,
block_quote: null,
table: null,
table_header: null,
table_header_cell: null,
table_row: null,
table_row_cell: null,
ordered_list: null,
ordered_list_item: null,
unordered_list: null,
unordered_list_item: null,
link: null,
image: null,
video: null,
code_span: null,
emphasis: null,
strong_emphasis: null,
strikethrough: null,
hard_line_break: null,
}
const template = createPartialTemplate()

for (const key of Object.keys(template) as Array<keyof PartialTemplate>) {
const fragment = templateDocument.querySelector(
Expand Down
56 changes: 56 additions & 0 deletions src/replace_parameters.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import { Element, HTMLDocument } from './deps/deno-dom.ts'
import { PartialTemplate, Template } from './template.ts'

const attributeRegex = /^data-decor-attribute-(.+)$/
const parameterRegex = /^param:(.+)$/

function replaceElementAttributes(
element: Element,
parameters: Record<string, string>,
): void {
for (const attribute of element.attributes) {
const parametersMatch = parameterRegex.exec(attribute.value)
if (!parametersMatch) {
continue
}

const parameterKey = parametersMatch[1]
if (!parameters[parameterKey]) {
continue
}

const attrebuteMatch = attributeRegex.exec(attribute.name)
if (attrebuteMatch) {
const attributeName = attrebuteMatch[1]
element.setAttribute(attributeName, parameters[parameterKey])
continue
}

if (attribute.name === 'data-decor-content') {
element.innerHTML = parameters[parameterKey]
}
}
}

export function replaceDocumentParameters(
document: HTMLDocument,
parameters: Record<string, string>,
): void {
document.querySelectorAll('*').forEach((element) => {
replaceElementAttributes(element as Element, parameters)
})
}

export function replaceTemplateParameters(
template: PartialTemplate,
parameters: Record<string, string>,
): void {
for (
const key of Object.keys(template) as Array<keyof Template>
) {
const element = template[key]
if (element) {
replaceElementAttributes(element, parameters)
}
}
}
39 changes: 39 additions & 0 deletions src/replace_parameters_test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { DOMParser } from './deps/deno-dom.ts'
import { assertEquals } from './deps/std/assert.ts'
import { createPartialTemplate } from './template.ts'
import {
replaceDocumentParameters,
replaceTemplateParameters,
} from './replace_parameters.ts'

Deno.test(
'replaceDocumentParameters handles parameter replacement in a document',
() => {
const document = new DOMParser().parseFromString(
'<div data-decor-attribute-foo="param:bar" data-decor-content="param:bar"></div>',
'text/html',
)!
replaceDocumentParameters(document, { bar: 'baz' })
const element = document.querySelector('div')!
assertEquals(element.getAttribute('foo'), 'baz')
assertEquals(element.innerHTML, 'baz')
},
)

Deno.test(
'replaceTemplateParameters handles parameter replacement in a template',
() => {
const document = new DOMParser().parseFromString(
'<h1 data-decor-attribute-foo="param:bar" data-decor-content="param:bar"></h1>',
'text/html',
)!
const template = {
...createPartialTemplate(),
heading1: document.querySelector('h1')!,
}
replaceTemplateParameters(template, { bar: 'baz' })
const element = template.heading1
assertEquals(element.getAttribute('foo'), 'baz')
assertEquals(element.innerHTML, 'baz')
},
)
32 changes: 32 additions & 0 deletions src/template.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,4 +33,36 @@ export type PartialTemplate = {
hard_line_break: Element | null
}

export function createPartialTemplate(): PartialTemplate {
return {
heading1: null,
heading2: null,
heading3: null,
heading4: null,
heading5: null,
heading6: null,
thematic_break: null,
paragraph: null,
code_block: null,
block_quote: null,
table: null,
table_header: null,
table_header_cell: null,
table_row: null,
table_row_cell: null,
ordered_list: null,
ordered_list_item: null,
unordered_list: null,
unordered_list_item: null,
link: null,
image: null,
video: null,
code_span: null,
emphasis: null,
strong_emphasis: null,
strikethrough: null,
hard_line_break: null,
}
}

export type Template = SetNonNullable<PartialTemplate>

0 comments on commit b8c3e1e

Please sign in to comment.