diff --git a/CHANGELOG.md b/CHANGELOG.md index 74a274b..4e60abe 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,11 @@ The project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html) ## [Unreleased] +### Changed + +- Allow configuring pixels per scroll using new `ScrollOptions.pixels_per_scroll` field. +- Change some default values and set more default values during `TemplateOptions` deserialization. + ## 0.3.0 - 2023-06-03 *(No substantial changes compared to the [0.3.0-beta.2 release](#030-beta2---2023-04-29))* diff --git a/Cargo.lock b/Cargo.lock index a0b4c73..5303bab 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -853,6 +853,7 @@ dependencies = [ "serde_json", "termcolor", "test-casing", + "toml 0.8.2", "tracing", "tracing-capture", "tracing-subscriber", @@ -873,6 +874,7 @@ dependencies = [ "tempfile", "term-transcript", "termcolor", + "toml 0.8.2", "tracing-subscriber", ] @@ -993,7 +995,19 @@ dependencies = [ "serde", "serde_spanned", "toml_datetime", - "toml_edit", + "toml_edit 0.19.15", +] + +[[package]] +name = "toml" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "185d8ab0dfbb35cf1399a6344d8484209c088f75f8f68230da55d48d95d43e3d" +dependencies = [ + "serde", + "serde_spanned", + "toml_datetime", + "toml_edit 0.20.2", ] [[package]] @@ -1018,6 +1032,19 @@ dependencies = [ "winnow", ] +[[package]] +name = "toml_edit" +version = "0.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "396e4d48bbb2b7554c944bde63101b5ae446cff6ec4a24227428f15eb72ef338" +dependencies = [ + "indexmap", + "serde", + "serde_spanned", + "toml_datetime", + "winnow", +] + [[package]] name = "tracing" version = "0.1.39" @@ -1184,7 +1211,7 @@ dependencies = [ "regex", "semver", "syn", - "toml", + "toml 0.7.8", "url", ] diff --git a/Cargo.toml b/Cargo.toml index 9644941..1390c05 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -38,6 +38,7 @@ anyhow = "1.0.40" assert_matches = "1.5.0" doc-comment = "0.3.3" test-casing = "0.1.1" +toml = "0.8.2" tracing-capture = "0.1.0" tracing-subscriber = { version = "0.3.16", features = ["env-filter"] } version-sync = "0.9.2" diff --git a/cli/CHANGELOG.md b/cli/CHANGELOG.md index a6c3a1e..35fc4cd 100644 --- a/cli/CHANGELOG.md +++ b/cli/CHANGELOG.md @@ -9,6 +9,7 @@ The project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html) - Allow specifying the snapshot width in pixels using `--width`. - Allow specifying at which char to hard-break lines using `--hard-wrap`. +- Allow reading template configuration using `--config-path`. ### Changed diff --git a/cli/Cargo.toml b/cli/Cargo.toml index 993831e..34889bb 100644 --- a/cli/Cargo.toml +++ b/cli/Cargo.toml @@ -22,6 +22,7 @@ humantime = "2.1.0" is-terminal = "0.4.9" serde_json = "1.0" termcolor = "1.3.0" +toml = "0.8.2" tracing-subscriber = { version = "0.3.16", features = ["env-filter"], optional = true } term-transcript = { version = "0.3.0", path = ".." } diff --git a/cli/src/template.rs b/cli/src/template.rs index 14fe39b..cf7929c 100644 --- a/cli/src/template.rs +++ b/cli/src/template.rs @@ -55,6 +55,17 @@ impl From for svg::LineNumbers { #[derive(Debug, Args)] pub(crate) struct TemplateArgs { + /// Path to the configuration TOML file. + /// + /// See https://slowli.github.io/term-transcript/term_transcript/svg/ for the configuration format. + #[arg( + long, + conflicts_with_all = [ + "palette", "line_numbers", "window_frame", "additional_styles", "font_family", "width", + "scroll", "hard_wrap", "no_wrap", + ] + )] + config_path: Option, /// Color palette to use. #[arg(long, short = 'p', default_value = "gjm8", value_enum)] palette: NamedPalette, @@ -103,7 +114,7 @@ pub(crate) struct TemplateArgs { /// Path to a custom Handlebars template to use. `-` means not to use a template at all, /// and instead output JSON data that would be fed to a template. /// - /// See https://slowli.github.io/term-transcript/term_transcript/ for docs on templating. + /// See https://slowli.github.io/term-transcript/term_transcript/svg/ for docs on templating. #[arg(long = "tpl")] template_path: Option, /// File to save the rendered SVG into. If omitted, the output will be printed to stdout. @@ -155,7 +166,18 @@ impl TemplateArgs { let pure_svg = self.pure_svg; let out_path = mem::take(&mut self.out); let template_path = mem::take(&mut self.template_path); - let options = TemplateOptions::from(self); + let config_path = mem::take(&mut self.config_path); + + let options = if let Some(path) = &config_path { + let config = fs::read_to_string(path) + .with_context(|| format!("cannot read TOML config from `{}`", path.display()))?; + toml::from_str(&config).with_context(|| { + format!("failed deserializing TOML config from `{}`", path.display()) + })? + } else { + TemplateOptions::from(self) + }; + let template = if let Some(template_path) = template_path { if template_path.as_os_str() == "-" { return Self::render_data(out_path.as_deref(), transcript, &options); @@ -169,18 +191,11 @@ impl TemplateArgs { }; if let Some(out_path) = out_path { - let out = File::create(&out_path).with_context(|| { - format!( - "cannot create output file `{}`", - out_path.as_os_str().to_string_lossy() - ) - })?; - template.render(transcript, out).with_context(|| { - format!( - "cannot render template to `{}`", - out_path.as_os_str().to_string_lossy() - ) - })?; + let out = File::create(&out_path) + .with_context(|| format!("cannot create output file `{}`", out_path.display()))?; + template + .render(transcript, out) + .with_context(|| format!("cannot render template to `{}`", out_path.display()))?; } else { template .render(transcript, io::stdout()) @@ -198,17 +213,10 @@ impl TemplateArgs { .render_data(transcript) .context("cannot render data for Handlebars template")?; if let Some(out_path) = out_path { - let out = File::create(out_path).with_context(|| { - format!( - "cannot create output file `{}`", - out_path.as_os_str().to_string_lossy() - ) - })?; + let out = File::create(out_path) + .with_context(|| format!("cannot create output file `{}`", out_path.display()))?; serde_json::to_writer(out, &data).with_context(|| { - format!( - "cannot write Handlebars data to `{}`", - out_path.as_os_str().to_string_lossy() - ) + format!("cannot write Handlebars data to `{}`", out_path.display()) })?; } else { serde_json::to_writer(io::stdout(), &data) @@ -221,13 +229,13 @@ impl TemplateArgs { let template_string = fs::read_to_string(template_path).with_context(|| { format!( "cannot read Handlebars template from `{}`", - template_path.as_os_str().to_string_lossy() + template_path.display() ) })?; let template = HandlebarsTemplate::compile(&template_string).with_context(|| { format!( "cannot compile Handlebars template from `{}`", - template_path.as_os_str().to_string_lossy() + template_path.display() ) })?; Ok(template) diff --git a/deny.toml b/deny.toml index 387e879..40f72bb 100644 --- a/deny.toml +++ b/deny.toml @@ -36,8 +36,6 @@ skip = [ skip-tree = [ # Used by `tracing-subscriber` together with the new version :( { name = "regex-automata", version = "^0.1" }, - # Used by `clap` via `terminal_size`; likely to get updated soon. - { name = "rustix", version = "^0.37" }, ] [sources] diff --git a/examples/README.md b/examples/README.md index 4e3793d..eee2275 100644 --- a/examples/README.md +++ b/examples/README.md @@ -174,6 +174,21 @@ term-transcript exec -T 250ms --palette gjm8 --window \ [CSP]: https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP +## Configuration file + +`--config-path` option allows reading rendering options from a TOML file. This enables +configuring low-level template details. The snapshot below uses a [configuration file](config.toml) +to customize palette colors and scroll animation step / interval. + +![Snapshot with config read from file](custom-config.svg) + +Generating command: + +```shell +term-transcript exec -T 250ms --config-path config.toml \ + 'rainbow --long-lines' +``` + ## Failed inputs Some shells may allow detecting whether an input resulted in a failure diff --git a/examples/config.toml b/examples/config.toml new file mode 100644 index 0000000..b6fda1e --- /dev/null +++ b/examples/config.toml @@ -0,0 +1,26 @@ +# Custom template configuration. +width = 900 +window_frame = true +line_numbers = 'continuous' +wrap = { hard_break_at = 100 } +scroll = { max_height = 300, pixels_per_scroll = 18, interval = 1.5 } + +[palette.colors] +black = '#3c3836' +red = '#b85651' +green = '#8f9a52' +yellow = '#c18f41' +blue = '#68948a' +magenta = '#ab6c7d' +cyan = '#72966c' +white = '#a89984' + +[palette.intense_colors] +black = '#5a524c' +red = '#b85651' +green = '#a9b665' +yellow = '#d8a657' +blue = '#7daea3' +magenta = '#d3869b' +cyan = '#89b482' +white = '#ddc7a1' diff --git a/examples/custom-config.svg b/examples/custom-config.svg new file mode 100644 index 0000000..11ef7d1 --- /dev/null +++ b/examples/custom-config.svg @@ -0,0 +1,106 @@ + + + + + + + + + + + + + +
+
1
$ rainbow --long-lines
+
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
Base colors:
+black black/italic blue blue/italic green green/italic red red/italic cyan cyan/italic magenta magen
ta/italic
yellow yellow/italic +black black/italic blue blue/italic green green/italic red red/italic cyan cyan/italic magenta magen
ta/italic
yellow yellow/italic +Base colors (bg): +black blue green red cyan magenta yellow +black blue green red cyan magenta yellow +ANSI color palette: +!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?
!
?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?
!
?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?
!
?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?
!
?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!? +ANSI grayscale palette: +!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!? +24-bit colors: +pink orange brown teal
+
+
+
+ + + +
+ + HTML embedding not supported. + Consult term-transcript docs for details. + +
+
diff --git a/examples/generate-snapshots.sh b/examples/generate-snapshots.sh index 56fd877..9b0042c 100755 --- a/examples/generate-snapshots.sh +++ b/examples/generate-snapshots.sh @@ -129,3 +129,8 @@ term-transcript exec $TT_ARGS --palette gjm8 --window \ --font 'Fira Mono, Consolas, Liberation Mono, Menlo' \ --styles '@import url(https://code.cdn.mozilla.net/fonts/fira.css);' rainbow \ > "$ROOT_DIR/examples/fira.$EXTENSION" + +echo "Creating snapshot with custom config..." +term-transcript exec $TT_ARGS --config-path "$ROOT_DIR/examples/config.toml" \ + 'rainbow --long-lines' \ + > "$ROOT_DIR/examples/custom-config.$EXTENSION" diff --git a/src/svg/data.rs b/src/svg/data.rs index 31a2fea..14f634b 100644 --- a/src/svg/data.rs +++ b/src/svg/data.rs @@ -51,7 +51,6 @@ use crate::{svg::TemplateOptions, write::SvgLine, UserInput}; /// "white": "#f7f7fb", /// }, /// }, -/// "additional_styles": "", /// "font_family": "Consolas, Menlo, monospace", /// "window_frame": false, /// "wrap": { diff --git a/src/svg/default.svg.handlebars b/src/svg/default.svg.handlebars index 13602cd..5538f7a 100644 --- a/src/svg/default.svg.handlebars +++ b/src/svg/default.svg.handlebars @@ -10,8 +10,6 @@ "LINE_HEIGHT": 18, {{! Height of the window frame }} "WINDOW_FRAME_HEIGHT": 22, - {{! Pixels scrolled vertically per each animation frame }} - "PIXELS_PER_SCROLL": 52, {{! Right offset of the scrollbar relative to the right border of the frame }} "SCROLLBAR_RIGHT_OFFSET": 7, {{! Height of the scrollbar in pixels }} @@ -56,7 +54,7 @@ null {{else}} {{#scope - steps=(div (sub content_height scroll.max_height) const.PIXELS_PER_SCROLL round="up") + steps=(div (sub content_height scroll.max_height) scroll.pixels_per_scroll round="up") y_step=0 view_box="" scrollbar_y="" @@ -65,7 +63,7 @@ {{y_step set=(div (sub scroll.max_height const.SCROLLBAR_HEIGHT) (steps))}} {{#each (range 0 (add (steps) 1))}} {{#sep}}{{#if @first}}""{{else}}";"{{/if}}{{/sep}} - {{#view_box}}"{{view_box}}{{sep}}0 {{mul ../const.PIXELS_PER_SCROLL @index}} {{../width}} {{../scroll.max_height}}"{{/view_box}} + {{#view_box}}"{{view_box}}{{sep}}0 {{mul ../scroll.pixels_per_scroll @index}} {{../width}} {{../scroll.max_height}}"{{/view_box}} {{#scrollbar_y}}"{{scrollbar_y}}{{sep}}0 {{mul (y_step) @index round="nearest"}}"{{/scrollbar_y}} {{/each}} diff --git a/src/svg/mod.rs b/src/svg/mod.rs index f91c762..e5eaf36 100644 --- a/src/svg/mod.rs +++ b/src/svg/mod.rs @@ -2,7 +2,8 @@ //! //! The included templating logic allows rendering SVG images. Templating is based on [Handlebars], //! and can be [customized](Template#customization) to support differing layout or even -//! data formats (e.g., HTML). +//! data formats (e.g., HTML). The default template supports [a variety of options](TemplateOptions) +//! controlling output aspects, e.g. image dimensions and scrolling animation. //! //! [Handlebars]: https://handlebarsjs.com/ //! @@ -49,28 +50,79 @@ pub enum LineNumbers { } /// Configurable options of a [`Template`]. +/// +/// # Serialization +/// +/// Options can be deserialized from `serde`-supported encoding formats, such as TOML. This is used +/// in the CLI app to read options from a file: +/// +/// ``` +/// # use assert_matches::assert_matches; +/// # use term_transcript::svg::{RgbColor, TemplateOptions, WrapOptions}; +/// let options_toml = r#" +/// width = 900 +/// window_frame = true +/// line_numbers = 'continuous' +/// wrap.hard_break_at = 100 +/// scroll = { max_height = 300, pixels_per_scroll = 18, interval = 1.5 } +/// +/// [palette.colors] +/// black = '#3c3836' +/// red = '#b85651' +/// green = '#8f9a52' +/// yellow = '#c18f41' +/// blue = '#68948a' +/// magenta = '#ab6c7d' +/// cyan = '#72966c' +/// white = '#a89984' +/// +/// [palette.intense_colors] +/// black = '#5a524c' +/// red = '#b85651' +/// green = '#a9b665' +/// yellow = '#d8a657' +/// blue = '#7daea3' +/// magenta = '#d3869b' +/// cyan = '#89b482' +/// white = '#ddc7a1' +/// "#; +/// +/// let options: TemplateOptions = toml::from_str(options_toml)?; +/// assert_eq!(options.width, 900); +/// assert_matches!(options.wrap, Some(WrapOptions::HardBreakAt(100))); +/// assert_eq!( +/// options.palette.colors.green, +/// RgbColor(0x8f, 0x9a, 0x52) +/// ); +/// # anyhow::Ok(()) +/// ``` #[derive(Debug, Clone, Serialize, Deserialize)] pub struct TemplateOptions { /// Width of the rendered terminal window in pixels. The default value is `720`. + #[serde(default = "TemplateOptions::default_width")] pub width: usize, /// Palette of terminal colors. The default value of [`Palette`] is used by default. + #[serde(default)] pub palette: Palette, /// CSS instructions to add at the beginning of the SVG `