comment_templates
Update markdown, TypeScript, and JavaScript files in place using comments as template tags.
Why?
Often, while developing a project, you will want to automatically update sections of content in the file. For example:
- The
versionhas been updated and should be reflected in multiple files. - The API has changed and the autogenerated documentation needs to be updated in the
readme.mdfile.
The solution is to wrap the content you want to replace in a language specific comment block. A build command is then run which injects values into the comment tag while preserving the tags. This means that blocks can be updated multiple times with new data.
API
commentTemplate
declare function commentTemplate(props: CommentTemplateProps): string;Description
Provide a string which contains template tags (using html and slash comments) that should be replaced with the variables provided.
<!-- ={exampleName} -->Placeholder text<!-- {/exampleName} -->The above code will replace Placeholder text with the value of the name variable.
Unlike usual template formats, the wrapping comment tags are not not processed by the template engine. Only the wrapped content is replaced. This allows the template values to be updated continually.
Since html comments don’t work within markdown codeblocks you should pass the full codeblock as one of the variable values. You can use pipes to make sure the content is properly wrapped as a codeblock.
<!-- ={sample|codeblock:"tsx"} -->|<!-- {/sample} -->The above would transform the variable value of sample into a codeblock with the language tsx.
|is used to separate the pipes.:is used to apply different arguments to the underlying pipe transformation.- Multiple pipes can be applied to a single value.
<!-- ={examplePipe|prefix:"This is a prefix"|codeblock:"tsx"|suffix:"This is a suffix"} --><!-- {/examplePipe} -->The supported pipe names are.
trim:|trim:nulltrim all whitespace from the start and end of the content.trimStart:|trim:nulltrim all whitespace from the start of the content.trimEnd:|trim:nulltrim all whitespace from the end of the content.string:|string:truewill wrap the value in single quotes. |string:false` will wrap the value in double quotes.prefix:|prefix:"prefix"will prefix the value with the provided string.suffix:|suffix:"suffix"will suffix the value with the provided string.codeblock:|codeblock:"language"will wrap the value in a codeblock with the provided language and set the indentation.indent:|indent:" "will indent each line by the provided string. This can be used to provide custom prefixes like|indent:" * "tocode:|code:nullwill wrap the value in inline code “` backticks.replace:|replace:"search,replace"will replace the search string with the replacement where the,is used to split the string.
The supported pipe arguments are true, false, null, any number 0123456789_ and any string wrapped in double quotes "string"
NOTE: The pipe arguments are not processed with regex and at the moment the regex is timing out when a single pipe is used without arguments. In order to use a single pipe, please provide an argument, even if it is an empty string.
Examples
import {
commentTemplate,
} from "https://deno.land/x/comment_templates@0.1.1/mod.ts";
import { assertEquals } from "./tests/deps.ts";
const exampleVersion = "2.1.0";
const exampleName = "Comment Template!";
const fileUrl = new URL("tests/fixtures/sample.md", import.meta.url);
const content = await Deno.readTextFile(fileUrl);
// Transform and use the variables in the content.
const transformed = commentTemplate({
content,
variables: { exampleVersion, exampleName },
});
assertEquals(
transformed,
`# <!-- ={exampleName} -->CommentTemplate!<!-- {/exampleName} --><!-- ={exampleVersion|prefix:"@"|code:null} -->\`@2.1.0\`<!-- {/exampleVersion} -->\n`,
);Before: readme.md
# <!-- ={name} --><!-- {/name} --><!-- ={version|prefix:"@"|code:null} --><!-- {/version} -->After: readme.md
# <!-- ={name} -->package<!-- {/name} --><!-- ={version} -->`@2.1.0`<!-- {/version} -->CommentTemplateProps
interface CommentTemplateProps {
content: string;
variables: CommentTemplateVariables;
throwIfMissingVariable?: boolean;
patterns?: Pattern[];
exclude?: ExcludeFunction;
}Description
These are the props that are passed into the commentTemplate function.
content: string
This is the content to transform and is required.
variables: CommentTemplateVariables
Pass variables to the template which replace the content.
If a function is provided it is called with the current value, which can be undefined. Variables must be a flat object structure and cannot contain nested objects.
There is currently no support for nesting.
throwIfMissingVariable: (optional) boolean
Throw an error if a variable is not found. This can be useful for making sure out of date comments don’t clutter up your markdown and Typescript files.
patterns: (optional) Pattern[]
The comment patterns to match for the provided content. You can limit the kind of comments that this function will transform.
htmlwill be able to transform markdown files with comments.slashwill be able to transform languages withslash starcomments like JavaScript and TypeScript.
exclude: (optional) ExcludeFunction
Return true when you want to exclude a match from being transformed.
Examples
content
The content can be pulled in from a file and then written back to the same file. All non-related content will be preserved.
import {
commentTemplate,
type CommentTemplateProps,
} from "https://deno.land/x/comment_templates@0.0.0/mod.ts";
const props: CommentTemplateProps = {
content: await Deno.readTextFile(
new URL("tests/fixtures/sample.md", import.meta.url),
),
variables: { name: "Deno" },
};
const transformedContent = commentTemplate(props);variables
Here is an example of creating variables with both a function and a string.
import {
type CommentTemplateProps,
} from "https://deno.land/x/comment_templates@0.0.0/mod.ts";
const props: CommentTemplateProps = {
content: await Deno.readTextFile(
new URL("tests/fixtures/sample.md", import.meta.url),
),
variables: {
simple: "a simple string",
complex: (value) => value ? `${value} is complex` : "seems undefined",
},
};exclude
The following example excludes a match based on the provided name.
import { CommentTemplateProps } from "https://deno.land/x/comment_templates@0.0.0/mod.ts";
const props: CommentTemplateProps = {
content: "<!-- ={excludedName} --><!-- {/excludedName} -->",
variables: {},
exclude: ({ name }) => name.startsWith("excluded"),
};extractTemplateValues
declare function extractTemplateValues(
content: string,
): ReadonlyMap<string, string>;Description
Extract the snippets from the provided content.
This returns each named snippet in a map.
Examples
The following example extracts the snippets from the provided content.
import {
extractTemplateValues,
} from "https://deno.land/x/comment_templates@0.1.1/mod.ts";
const content = await Deno.readTextFile("./mod.d.md");
const variables = extractTemplateValues(content);
// => ReadonlyMap<string, string>