Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 16 additions & 16 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "technique"
version = "0.5.6"
version = "0.5.7"
edition = "2021"
description = "A domain specific language for procedures."
authors = [ "Andrew Cowie" ]
Expand Down
111 changes: 110 additions & 1 deletion src/main.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
use clap::builder::{PossibleValue, TypedValueParser};
use clap::value_parser;
use clap::{Arg, ArgAction, Command};
use owo_colors::OwoColorize;
use std::io::IsTerminal;
use std::path::Path;
use std::str::FromStr;
use tracing::debug;
use tracing_subscriber::{self, EnvFilter};

Expand Down Expand Up @@ -31,6 +33,89 @@ enum Phase {
Translation,
}

// Page dimensions in millimetres
#[derive(Clone, Debug)]
struct PaperSize {
width: f64,
height: f64,
}

impl FromStr for PaperSize {
type Err = String;

fn from_str(s: &str) -> Result<Self, Self::Err> {
let (width, height) = match s {
"a4" => (210.0, 297.0),
"a5" => (148.0, 210.0),
"letter" => (215.9, 279.4),
_ => {
let (w, h) = s
.split_once(|c: char| c == 'x' || c == '×')
.ok_or_else(|| format!("invalid paper size '{}'", s))?;
let w: f64 = w
.trim()
.parse()
.map_err(|e| format!("invalid width '{}': {}", w, e))?;
let h: f64 = h
.trim()
.parse()
.map_err(|e| format!("invalid height '{}': {}", h, e))?;
(w, h)
}
};
Ok(PaperSize { width, height })
}
}

// Custom clap parser so the named presets show up under "possible values" in
// help output, while still allowing the parser to accept free-form width x
// height dimensions.
#[derive(Clone)]
struct PaperSizeParser;

impl TypedValueParser for PaperSizeParser {
type Value = PaperSize;

fn parse_ref(
&self,
cmd: &Command,
arg: Option<&Arg>,
value: &std::ffi::OsStr,
) -> Result<Self::Value, clap::Error> {
let s = value
.to_str()
.ok_or_else(|| clap::Error::new(clap::error::ErrorKind::InvalidUtf8).with_cmd(cmd))?;
s.parse::<PaperSize>()
.map_err(|e| {
let mut err =
clap::Error::new(clap::error::ErrorKind::ValueValidation).with_cmd(cmd);
if let Some(arg) = arg {
err.insert(
clap::error::ContextKind::InvalidArg,
clap::error::ContextValue::String(arg.to_string()),
);
}
err.insert(
clap::error::ContextKind::InvalidValue,
clap::error::ContextValue::String(s.to_string()),
);
err.insert(
clap::error::ContextKind::Custom,
clap::error::ContextValue::String(e),
);
err
})
}

fn possible_values(&self) -> Option<Box<dyn Iterator<Item = PossibleValue> + '_>> {
Some(Box::new(
["a4", "a5", "letter", "WxH"]
.into_iter()
.map(PossibleValue::new),
))
}
}

fn main() {
const VERSION: &str = concat!("v", env!("CARGO_PKG_VERSION"));

Expand Down Expand Up @@ -158,6 +243,15 @@ fn main() {
.action(ArgAction::Set)
.help("Path to a Typst template file for rendering."),
)
.arg(
Arg::new("paper")
.short('p')
.long("paper")
.value_name("SIZE")
.value_parser(PaperSizeParser)
.action(ArgAction::Set)
.help("Paper size for the rendered output. You can use the name of one of the well-known standard sizes, or give explicit dimensions for the width and height in millimetres (\"140x210\", for example). If a paper size is not specified, the template's default will be used."),
)
.arg(
Arg::new("keep")
.short('k')
Expand Down Expand Up @@ -477,8 +571,23 @@ fn main() {
None => None,
};

let (paper_width, paper_height) = match submatches.get_one::<PaperSize>("paper") {
Some(p) => (p.width, p.height),
None => template
.default_paper()
.unwrap_or((210.0, 297.0)),
};

debug!(paper_width, paper_height);

let markup = template.markup(&technique);
let document = templating::assemble(template.domain(), &markup, custom);
let document = templating::assemble(
template.domain(),
&markup,
custom,
paper_width,
paper_height,
);

let keep = *submatches
.get_one::<bool>("keep")
Expand Down
16 changes: 14 additions & 2 deletions src/templating/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,24 @@ pub use template::Template;
/// customize the output template. Because the import is * any functions that
/// the user redefines in their template will override the names from the
/// default.
pub fn assemble(domain: &str, markup: &str, custom: Option<&str>) -> String {
///
/// `width` and `height` are the page dimensions in millimetres, supplied via
/// the `--paper` CLI option.
pub fn assemble(
domain: &str,
markup: &str,
custom: Option<&str>,
width: f64,
height: f64,
) -> String {
let mut doc = format!("#import \".{}.typ\": *\n", domain);
if let Some(path) = custom {
doc.push_str(&format!("#import \"/{}\": *\n", path));
}
doc.push_str("\n#show: template\n\n");
doc.push_str(&format!(
"\n#set page(width: {}mm, height: {}mm)\n\n#show: template\n\n",
width, height
));
doc.push_str(markup);
doc
}
4 changes: 4 additions & 0 deletions src/templating/nasa_esa_iss.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,4 +27,8 @@ impl Template for NasaEsaIss {
fn domain(&self) -> &str {
"nasa-esa-iss"
}

fn default_paper(&self) -> Option<(f64, f64)> {
Some((148.0, 210.0))
}
}
1 change: 0 additions & 1 deletion src/templating/nasa_esa_iss.typ
Original file line number Diff line number Diff line change
Expand Up @@ -199,7 +199,6 @@

#let template(body) = {
set page(
paper: "a5",
margin: (top: 1.5cm, bottom: 1.5cm, left: 1.5cm, right: 1.5cm),
numbering: "1",
number-align: center + bottom,
Expand Down
7 changes: 7 additions & 0 deletions src/templating/template.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,11 @@ pub trait Template {

/// Return the domain name (used for the template filename on disk).
fn domain(&self) -> &str;

/// The page size, as (width, height) in millimetres, that this domain
/// prefers when the user has not specified `--paper`. Return `None` to
/// fall back to the global default.
fn default_paper(&self) -> Option<(f64, f64)> {
None
}
}