use miden_formatting::prettier::{Document, indent, nl, text};
use semver::Version;
pub(crate) struct WitBuilder {
source: Document,
}
impl WitBuilder {
pub(crate) fn new(generated_by: &str, package_name: &str, package_version: &Version) -> Self {
let package_with_version = package_with_version(package_name, package_version);
let mut builder = Self {
source: Document::Empty,
};
builder.push_line(format!("// This file is auto-generated by the `{generated_by}` macro."));
builder.push_line("// Do not edit this file manually.");
builder.blank_line();
builder.push_line(format!("package {package_with_version};"));
builder.blank_line();
builder
}
pub(crate) fn use_path(&mut self, path: &str) {
self.push_line(format!("use {path};"));
}
pub(crate) fn blank_line(&mut self) {
self.source += nl();
}
pub(crate) fn interface<T>(&mut self, name: &str, build: impl FnOnce(&mut WitBody) -> T) -> T {
let mut body = WitBody::new();
let result = build(&mut body);
self.push_block(format!("interface {name} {{"), body.finish());
result
}
pub(crate) fn world<T>(&mut self, name: &str, build: impl FnOnce(&mut WitBody) -> T) -> T {
let mut body = WitBody::new();
let result = build(&mut body);
self.push_block(format!("world {name} {{"), body.finish());
result
}
pub(crate) fn finish(self) -> String {
self.source.to_string() + "\n"
}
fn push_line(&mut self, line: impl Into<String>) {
if !self.source.is_empty() {
self.source += nl();
}
self.source += text(line.into());
}
fn push_block(&mut self, header: impl Into<String>, body: Document) {
self.push_line(header);
if body.is_empty() {
self.source += nl();
} else {
self.source += indent(4, nl() + body) + nl();
}
self.source += text("}");
}
}
pub(crate) struct WitBody {
source: Document,
}
impl WitBody {
fn new() -> Self {
Self {
source: Document::Empty,
}
}
pub(crate) fn line(&mut self, line: &str) {
if !self.source.is_empty() {
self.source += nl();
}
self.source += text(line);
}
pub(crate) fn blank_line(&mut self) {
self.source += nl();
}
pub(crate) fn block(&mut self, header: &str, build: impl FnOnce(&mut WitBody)) {
if !self.source.is_empty() {
self.source += nl();
}
self.source += text(header);
let mut body = WitBody::new();
build(&mut body);
let body = body.finish();
if body.is_empty() {
self.source += nl();
} else {
self.source += indent(4, nl() + body) + nl();
}
self.source += text("}");
}
fn finish(self) -> Document {
self.source
}
}
fn package_with_version(package_name: &str, package_version: &Version) -> String {
if package_name.contains('@') {
package_name.to_string()
} else {
format!("{package_name}@{package_version}")
}
}
#[cfg(test)]
mod tests {
use semver::Version;
use super::WitBuilder;
#[test]
fn renders_nested_blocks_with_expected_indentation() {
let mut wit = WitBuilder::new("#[test]", "miden:test", &Version::new(1, 0, 0));
wit.use_path("miden:base/core-types@1.0.0");
wit.blank_line();
wit.interface("foo", |interface| {
interface.line("use core-types.{word};");
interface.blank_line();
interface.block("record payload {", |record| {
record.line("value: word,");
});
});
wit.blank_line();
wit.world("foo-world", |world| {
world.line("export foo;");
});
let expected = r#"// This file is auto-generated by the `#[test]` macro.
// Do not edit this file manually.
package miden:test@1.0.0;
use miden:base/core-types@1.0.0;
interface foo {
use core-types.{word};
record payload {
value: word,
}
}
world foo-world {
export foo;
}
"#;
assert_eq!(wit.finish(), expected);
}
}