Dataforge incorporates a powerful and unique templating system that shares syntax with Handlebars but is a custom implementation internal to Doh.js and Dataforge. It is crucial to understand that this is NOT the popular Handlebars.js library.
A fundamental characteristic of Dataforge's templating is that all strings that pass through a Dataforge pipeline are, by default, treated as templates and processed for handlebar content. This means any string can potentially contain {{...}}
expressions that will be evaluated. This behavior might be refined in future versions, but it is the current default.
This system provides mechanisms for simple variable substitution, as well as an advanced "Handlebar Protocol" feature for dynamic content generation.
When a template string is processed, Dataforge makes several sources of data available for substitution:
{{data}}
): The primary data currently held in the active Dataforge branch can often be accessed directly or implicitly.ToHandlebar
command (see below) are available (e.g., {{myVar}}
).this.handlebars
): The Dataforge instance itself has a handlebars
object where variables can be stored.{{branchName.data}}
or {{branchName.someProperty}}
if the data is an object).{{branch.name}}
: The name of the current branch.{{branch.data}}
: The data of the current branch.{{branch.tempMode}}
: The temporary mode of the current branch.{{branch.currentMode}}
: The persistent mode of the current branch.{{branch.defaultMode}}
: The default mode of the current branch.{{branch.isGlobal}}
: Boolean indicating if the current branch is global.{{outer.name}}
, {{outer.data}}
, etc.: Similar properties for the calling (outer) branch when in a sub-pipeline.{{file:path}}
inject content dynamically.If you need to include literal handlebar expressions in your output without them being processed, you can escape them using a backslash:
\{{this will not be processed}}
This will produce the literal text \{{this will not be processed}}
in the output. This is particularly useful when:
Handlebar replacement, including protocol execution, generally occurs when:
ApplyHandlebars
command is explicitly used.this.updateData()
, which internally calls this.replace_handlebars()
.These commands are used to manage and apply templates.
ApplyHandlebars
Explicitly processes the current branch's data as a Handlebars template, replacing all {{...}}
placeholders with their corresponding values from the Dataforge instance's handlebars
object, current branch data, or dynamic context. Optionally, an object of handlebars can be provided to this command to temporarily add or override handlebar variables for this specific application.
Syntax:
"ApplyHandlebars"
{ ApplyHandlebars: { temporaryVar: "value" } }
Example:
df.handlebars.user = "Alice";
df.forge("Hello {{user}} from branch {{branch.name}}!", [
{ ApplyHandlebars: { 'branch.name': 'main' } } // Temporarily add/override for this call
]); // Result: "Hello Alice from branch main!"
ToHandlebar
Stores the current branch's data into a specified variable within the Dataforge instance's handlebars
object. This makes the data available for subsequent template processing using {{handlebarName}}
.
Syntax:
{ ToHandlebar: "handlebarVariableName" }
Example:
df.forge("Alice", [{ ToHandlebar: "userName" }]);
// df.handlebars.userName is now "Alice"
// Later use: df.forge("User: {{userName}}", ["ApplyHandlebars"])
Alias: ExportToHandlebar
EmptyHandlebar
Sets the specified handlebar variable within the Dataforge instance's handlebars
object to an empty string ''
.
Syntax:
{ EmptyHandlebar: "handlebarVariableName" }
Example:
df.forge(null, [{ EmptyHandlebar: "userName" }]);
// df.handlebars.userName is now ''
// {{userName}} will now render as an empty string.
RemoveHandlebar
Deletes the specified handlebar variable from the Dataforge instance's handlebars
object. Use with caution as this removes the variable entirely.
Syntax:
{ RemoveHandlebar: "handlebarVariableName" }
Example:
df.forge(null, [{ RemoveHandlebar: "userName" }]);
// df.handlebars.userName is now undefined
// {{userName}} might render as "{{userName}}" or an empty string depending on settings.
Beyond simple variable replacement, Dataforge includes a powerful Handlebar Protocol system. This allows for dynamic content generation and processing directly within handlebar templates using the syntax: {{protocol:value}}
.
This system enables advanced features like embedding file contents, live-reloading parts of templates, and creating custom dynamic tags.
HandlebarProtocolHandler
PatternThe foundation of this system is the HandlebarProtocolHandler
pattern, defined in dataforge_handlebars.js
(this file's corresponding module).
Pattern Definition:
Pattern('HandlebarProtocolHandler', {
moc: {
protocol: ['IsString', 'NotNull'], // The unique name of the protocol (e.g., 'file')
handler: ['IsFunction', 'NotNull'],// The function to execute for this protocol
handlers: 'Static', // A static collection shared by all instances,
// acting as the global registry:
// Doh.Patterns.HandlebarProtocolHandler.handlers
enabled: 'IsBoolean', // Whether this protocol handler is active
},
enabled: true, // Default to enabled
handlers: {}, // Initialize the static handlers registry
object_phase: function(){
// When an instance of this pattern (or a child pattern) is created,
// it registers its protocol and handler function into the static 'handlers' registry.
if (this.enabled) {
this.handlers[this.protocol] = this.handler;
}
},
});
Key Properties and Behavior:
protocol
(String): A unique string that identifies the protocol. This is the name used in the Handlebars template (e.g., file
in {{file:some/path}}
).handler
(Function): The JavaScript function that gets executed when this protocol is encountered. This function typically receives the value
part of the {{protocol:value}}
syntax as an argument and may also receive other contextual information (like the full "load statement" of the template being processed, useful for features like HMR or Doh.reload
). It's responsible for returning the content that will replace the Handlebar tag.handlers
(Static Object): This is a crucial static property (Doh.Patterns.HandlebarProtocolHandler.handlers
). It acts as a global registry where each protocol
string is mapped to its corresponding handler
function. When a HandlebarProtocolHandler
instance is created (e.g., New('MyCustomProtocolHandler', { ... })
), its protocol
and handler
are automatically added to this shared registry during its object_phase
.enabled
(Boolean): Controls whether the protocol handler is active and registers itself. Defaults to true
.dataforge_core
)The actual processing of these protocols within a template string happens inside the replace_handlebars
method of the dataforge_core
pattern (found in dataforge.js
).
Simplified Logic:
// Inside dataforge_core.replace_handlebars:
const getReplacement = (key) => {
key = key.trim();
// ... (checks for dynamic handlebars like , {{branch.name}}, etc.)
if (key.includes(':')) { // Protocol syntax detected!
const [protocol, value] = key.split(':', 2); // Split only on the first colon
if (Doh.Patterns.HandlebarProtocolHandler.handlers.hasOwnProperty(protocol)) {
// If a handler is registered for this protocol, call it!
return Doh.Patterns.HandlebarProtocolHandler.handlers[protocol](value /*, other_context_if_any */);
}
}
// ... (checks for standard handlebars like {{myVariable}})
return `{{${key}}}`; // Return original if no replacement found
};
When replace_handlebars
encounters a tag like {{foo:bar}}
, it:
foo:bar
into protocol = 'foo'
and value = 'bar'
.'foo'
in the Doh.Patterns.HandlebarProtocolHandler.handlers
registry.'bar'
(and potentially other context).{{foo:bar}}
in the template.To create your own custom Handlebar protocol:
HandlebarProtocolHandler
.protocol
name and implement the handler
function.Example: A {{timestamp:format}}
protocol
// In your custom module, e.g., my_custom_dataforge_protocols.js
Doh.Module('my_custom_dataforge_protocols', ['dataforge_handlebars'], function() { // Depend on dataforge_handlebars
Pattern('TimestampProtocolHandler', ['HandlebarProtocolHandler'], {
protocol: 'timestamp', // Protocol name: {{timestamp:...}}
handler: function(formatString) {
// formatString is the value after 'timestamp:'
// Example: {{timestamp:YYYY-MM-DD HH:mm:ss}}
const now = new Date();
if (formatString === 'ISO') {
return now.toISOString();
}
if (formatString === 'UTC') {
return now.toUTCString();
}
if (formatString === 'time') {
return now.toLocaleTimeString();
}
// Add more sophisticated date formatting as needed (e.g., using a library)
return now.toLocaleString(); // Default
}
});
// Instantiate the pattern to register it.
New('TimestampProtocolHandler');
});
// Ensure this module (my_custom_dataforge_protocols) is loaded by Dataforge,
// perhaps by adding it to the dependencies in dataforge.js or a relevant sub-module.
Usage in a Dataforge command:
// Assuming 'my_custom_dataforge_protocols' module is loaded
let df = New('Dataforge');
let result = df.forge("Report generated on: {{timestamp:YYYY-MM-DD}} at {{timestamp:time}}", [
"ApplyHandlebars" // Process the template
]);
console.log(result); // Output: Report generated on: 2023-10-27 at 10:30:45 AM (example)
file
and editableFile
Dataforge comes with powerful built-in protocols, primarily for file system interaction, implemented in nodejs_fs_dataforge.js
(for Node.js/Bun) and __secret_browser_fs_dataforge.js
(for the Browser environment). These modules would typically ensure dataforge_handlebars
(or at least HandlebarProtocolHandler
) is available.
{{file:path/to/your/file.ext}}
Purpose: Loads and embeds the content of the specified file directly into the template. This is extremely useful for including snippets, partial templates, or any external file content.
loaderType
: Typically 'file'
. This indicates that the content should be treated as a standard file load, which might imply further processing based on Doh's load system conventions (e.g., if the path was myfile.md > md
, it could be converted to HTML).
Node.js/Bun Behavior (via nodejs_fs_dataforge.js
's HandlebarFileProtocolHandler
instance):
fs.readFileSync()
to synchronously read the file content. The path is made safe using an internal makeFilePathSafe
function.fileCache
, fileCacheMtime
). The file content is cached in memory, and the file is only re-read from disk if its modification time (mtime
) has changed since the last access. This significantly improves performance for templates that repeatedly include the same files.Doh.pod.hmr?.enabled
), the resolved file path is automatically added to HMR's watch list using Doh.Globals.HMR.addLoaderToWatch(resolvedPath + ' > ' + this.loaderType)
. This means if the included file is modified, the HMR system can intelligently update the relevant parts of your application.Browser Behavior (via __secret_browser_fs_dataforge.js
's HandlebarFileProtocolHandler
instance):
Doh.live_load(DohPath.DohSlash(value) + ' > ' + this.loaderType, callback)
to asynchronously fetch the file content.value
) changes on the server, Doh.live_load
detects this. Its callback function then executes Doh.reload(loadStatement)
, where loadStatement
refers to the original template or resource that contained the {{file:...}}
handlebar. This re-renders/re-processes the part of your application dependent on the changed file.{{editableFile:path/to/your/file.ext}}
file
protocol.loaderType
: Registered with loaderType: 'raw'
.file
protocol (i.e., fs.readFileSync
with caching/HMR in Node.js, and Doh.live_load
in the browser).loaderType: 'raw'
distinction means the content is treated as raw text, bypassing further transformations. This is suitable for exact content loading, e.g., into a <textarea>
for editing.Dataforge includes several built-in protocols specifically designed for working with packages, paths, and documentation:
{{DohPath:path}}
{{DohPath:some/relative/path}}
will resolve to the appropriate absolute path.{{DohPath.DohSlash:path}}
{{DohPath.DohSlash:some/path}}
will ensure the path uses the appropriate slashes for the current environment.{{Package:packageName}}
{{Package:myPackage}}
returns /path/to/myPackage
{{PackageReadme:packageName}}
{{PackageReadme:myPackage}}
returns /path/to/myPackage/README.md
{{DohballDocs:packageName}}
{{DohballDocs:myPackage}}
returns /docs/dohballs/path/to/myPackage
These protocols are particularly useful when generating documentation or creating package-related content that needs to reference other packages or their documentation.
Dataforge's custom Handlebars templating, especially with its "all strings are templates" default and the Handlebar Protocol system, offers:
{{file:...}}
with Doh.live_load
enables dynamic UI updates.This system provides a flexible and powerful way to manage and generate complex string-based content within the Doh.js ecosystem.