I have a little utility called tmplr which purpose is to generate files/file sets from a human-readable templates (check it out, it's neat!).
It is at amazing version 0.0.9, and one thing that annoyed me every single time was the need of using version information in few places:
- HELP showed on
-hor--help -
README.md(that included copy of help text) Cargo.toml- Local git tag (git tag v0.0.9)
- Pushed tags it to the GitHub
It's not that bad but it's an overhead I wanted to eliminate. Since I'm a huge fan of CUE and I generate most of the configs/data I need with it I thought - heck, why not?
Thankfully Rust provides ability to have a pre-build "scripts" (it's actually full Rust program) which allowed me to do not only version injection but also:
- Generate full
README.md - Generate
CHANGELOG.mdbased on smarter entries - Inject Rust code into
src/main.rs(that was really neat) - Reuse part of the documentation in other places
Long story short, let me show you my build.rs:
use std::env;
use std::fs;
use std::path::Path;
use std::process::Command;
fn main() {
// Injects "HELP" into src/main.rs
prepare_documentation_code();
generate_doc("README.md", "readme.full");
generate_doc("CHANGELOG.md", "changelog.text");
println!("cargo:rerun-if-changed=cue");
}
fn generate_doc(file_in_manifest: &str, eval_command: &str) {
let manifest_dir = env::var("CARGO_MANIFEST_DIR").unwrap();
let readme_path = Path::new(&manifest_dir).join(file_in_manifest);
let output = Command::new("cue")
.args([
"export",
"-e",
eval_command,
"--out",
"text",
"./cue:documentation",
])
.output()
.expect("Failed to execute cue command");
if !output.status.success() {
panic!(
"Cue {} generation failed:\n{}",
file_in_manifest,
String::from_utf8_lossy(&output.stderr)
);
}
fs::write(&readme_path, &output.stdout).expect("Failed to write generated file");
}
fn prepare_documentation_code() {
let out_dir = env::var_os("OUT_DIR").unwrap();
let dest_path = Path::new(&out_dir).join("generated_docs.rs");
let output = Command::new("cue")
.args([
"export",
"-e",
"help.code",
"--out",
"text",
"./cue:documentation",
])
.output()
.expect("Failed to execute cue command");
if !output.status.success() {
panic!(
"Cue generation failed:\n{}",
String::from_utf8_lossy(&output.stderr)
);
}
fs::write(&dest_path, &output.stdout).expect("Failed to write generated file");
}
Documentation code is bit different because it's a source code that's going to be injected into main.rs using following code:
include!(concat!(env!("OUT_DIR"), "/generated_docs.rs"));
This generated_docs is actual build artifact I don't want to submit to VCS thus I don't plainly generate it. Changelog and Readme is another story, this should be checked in, so I generate them direclty in MANIFEST_DIR (i.e. Project's root).
I won't show my CUE files this time, as they're mostly filled with data and split across multiple files. Here's how my cue/ directory looks like:
cue
├── changelog.cue
├── documentation.cue
├── help.cue
├── LICENSE.cue
└── readme.cue
documentation.cue is a root package file - help, changelog and readme are files containing data (with some smart transformations)
One piece you might be interested in is the final README.md outline:
// ...rest of file, sections etc.
full: """
# tmplr
\(sections.tmplr)
## Why tmplr?
\(sections.why_tmplr)
# Quick Start
\(sections.quick_start)
# CLI Help
```
\(help.text)
```
# Installation
\(sections.installation)
# Usage (extended)
## .tmplr files
\(sections.tmplr_files)
### Section Types
\(sections.section_types)
### Magic Variables
\(sections.magic_variables)
## CLI
\(sections.cli)
## Templates directory
\(sections.templates_directory)
# TODO
\(sections.todo)
"""
Here you can clearly see that "CLI Help" section is reading directly from the help.text. Neat, right? No more updating README.md by hand, I just need to make sure I build before I push :)
btw. final result is already pushed to the root repo where you can see the end result and full .cue files for inspection. Project extension (see commit for full scope) took around 1.5h to develop, most of the time was spent porting the data from .md to .cue files
Top comments (0)