feat: Display full recipe with syntax highlighting (#166)

As I was re-arranging my recipe files, I needed a way to ensure that the
order of my recipe is correct without having to read through the
generated `Containerfile`. So I added a `-d`/`--display-full-recipe` arg
to `template` that will print out all of your modules in the order
defined by following the `from-file` property.

```
$> bluebuild template --help
Generate a Containerfile from a recipe

Usage: bluebuild template [OPTIONS] [RECIPE]

Arguments:
  [RECIPE]
          The recipe file to create a template from

Options:
  -o, --output <OUTPUT>
          File to output to instead of STDOUT

      --registry <REGISTRY>
          The registry domain the image will be published to.

          This is used for modules that need to know where the image is being published (i.e. the signing module).

      --registry-namespace <REGISTRY_NAMESPACE>
          The registry namespace the image will be published to.

          This is used for modules that need to know where the image is being published (i.e. the signing module).

  -d, --display-full-recipe
          Instead of creating a Containerfile, display the full recipe after traversing all `from-file` properties.

          This can be used to help debug the order you defined your recipe.

  -t, --syntax-theme <SYNTAX_THEME>
          Choose a theme for the syntax highlighting for the Containerfile or Yaml.

          The default is `mocha-dark`.

          [possible values: mocha-dark, ocean-dark, ocean-light, eighties-dark, inspired-github, solarized-dark, solarized-light]

  -s, --squash
          Puts the build in a `squash-stage` and COPY's the results to the final stage as one layer.

          WARN: This doesn't work with the docker driver as it has been deprecated.

          NOTE: Squash has a performance benefit for the newer versions of podman and buildah.

  -B, --build-driver <BUILD_DRIVER>
          Select which driver to use to build your image

          [possible values: buildah, podman, docker]

  -v, --verbose...
          Increase logging verbosity

  -I, --inspect-driver <INSPECT_DRIVER>
          Select which driver to use to inspect images

          [possible values: skopeo, podman, docker]

  -q, --quiet...
          Decrease logging verbosity

  -h, --help
          Print help (see a summary with '-h')
```

Preview of Containerfile/Dockerfile syntax highlighting:

![image](https://github.com/blue-build/cli/assets/4626052/cf2c452e-94b1-44f3-97ca-162d50a5047f)

Preview of Yaml highlighting:

![image](https://github.com/blue-build/cli/assets/4626052/b7c48b82-3e9e-431c-a55b-679848cd1fa6)
This commit is contained in:
Gerald Pinder 2024-04-27 09:12:04 -04:00 committed by GitHub
parent a7503d561e
commit 92150693d4
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 477 additions and 9 deletions

179
Cargo.lock generated
View file

@ -2,6 +2,12 @@
# It is not intended for manual editing.
version = 3
[[package]]
name = "adler"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
[[package]]
name = "aho-corasick"
version = "1.1.3"
@ -127,12 +133,29 @@ dependencies = [
"nom",
]
[[package]]
name = "atty"
version = "0.2.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
dependencies = [
"hermit-abi",
"libc",
"winapi",
]
[[package]]
name = "autocfg"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
[[package]]
name = "base64"
version = "0.21.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567"
[[package]]
name = "basic-toml"
version = "0.1.8"
@ -142,6 +165,15 @@ dependencies = [
"serde",
]
[[package]]
name = "bincode"
version = "1.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad"
dependencies = [
"serde",
]
[[package]]
name = "bitflags"
version = "1.3.2"
@ -223,7 +255,9 @@ name = "blue-build-utils"
version = "0.8.4"
dependencies = [
"anyhow",
"atty",
"chrono",
"clap",
"colored",
"directories",
"env_logger",
@ -233,6 +267,7 @@ dependencies = [
"serde",
"serde_json",
"serde_yaml 0.9.32",
"syntect",
"which",
]
@ -400,6 +435,15 @@ version = "0.8.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f"
[[package]]
name = "crc32fast"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b3855a8a784b474f333699ef2bbca9db2c4a1f6d9088a90a2d25b1eb53111eaa"
dependencies = [
"cfg-if",
]
[[package]]
name = "crossterm"
version = "0.25.0"
@ -522,6 +566,22 @@ version = "2.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "25cbce373ec4653f1a01a31e8a5e5ec0c622dc27ff9c4e6606eefef5cbbed4a5"
[[package]]
name = "flate2"
version = "1.0.29"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4556222738635b7a3417ae6130d8f52201e45a0c4d1a907f0826383adb5f85e7"
dependencies = [
"crc32fast",
"miniz_oxide",
]
[[package]]
name = "fnv"
version = "1.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
[[package]]
name = "form_urlencoded"
version = "1.2.1"
@ -610,6 +670,15 @@ version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8"
[[package]]
name = "hermit-abi"
version = "0.1.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33"
dependencies = [
"libc",
]
[[package]]
name = "home"
version = "0.5.9"
@ -790,6 +859,12 @@ dependencies = [
"vcpkg",
]
[[package]]
name = "line-wrap"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dd1bc4d24ad230d21fb898d1116b1801d7adfc449d42026475862ab48b11e70e"
[[package]]
name = "linked-hash-map"
version = "0.5.6"
@ -846,6 +921,15 @@ version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
[[package]]
name = "miniz_oxide"
version = "0.7.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9d811f3e15f28568be3407c8e7fdb6514c1cda3cb30683f15b6a1a1dc4ea14a7"
dependencies = [
"adler",
]
[[package]]
name = "mio"
version = "0.8.10"
@ -910,6 +994,28 @@ version = "1.19.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92"
[[package]]
name = "onig"
version = "6.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8c4b31c8722ad9171c6d77d3557db078cab2bd50afcc9d09c8b315c59df8ca4f"
dependencies = [
"bitflags 1.3.2",
"libc",
"once_cell",
"onig_sys",
]
[[package]]
name = "onig_sys"
version = "69.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7b829e3d7e9cc74c7e315ee8edb185bf4190da5acde74afd7fc59c35b1f086e7"
dependencies = [
"cc",
"pkg-config",
]
[[package]]
name = "open"
version = "5.0.1"
@ -979,6 +1085,20 @@ version = "0.3.30"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec"
[[package]]
name = "plist"
version = "1.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d9d34169e64b3c7a80c8621a48adaf44e0cf62c78a9b25dd9dd35f1881a17cf9"
dependencies = [
"base64",
"indexmap 2.2.3",
"line-wrap",
"quick-xml",
"serde",
"time",
]
[[package]]
name = "powerfmt"
version = "0.2.0"
@ -1005,6 +1125,15 @@ dependencies = [
"windows-sys 0.52.0",
]
[[package]]
name = "quick-xml"
version = "0.31.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1004a344b30a54e2ee58d66a71b32d2db2feb0a31f9a2d302bf0536f15de2a33"
dependencies = [
"memchr",
]
[[package]]
name = "quote"
version = "1.0.35"
@ -1156,6 +1285,15 @@ version = "1.0.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f98d2aa92eebf49b69786be48e4477826b256916e84a57ff2a4f21923b48eb4c"
[[package]]
name = "same-file"
version = "1.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502"
dependencies = [
"winapi-util",
]
[[package]]
name = "scopeguard"
version = "1.2.0"
@ -1316,6 +1454,28 @@ dependencies = [
"unicode-ident",
]
[[package]]
name = "syntect"
version = "5.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "874dcfa363995604333cf947ae9f751ca3af4522c60886774c4963943b4746b1"
dependencies = [
"bincode",
"bitflags 1.3.2",
"flate2",
"fnv",
"once_cell",
"onig",
"plist",
"regex-syntax",
"serde",
"serde_derive",
"serde_json",
"thiserror",
"walkdir",
"yaml-rust",
]
[[package]]
name = "tempfile"
version = "3.10.0"
@ -1601,6 +1761,16 @@ version = "0.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
[[package]]
name = "walkdir"
version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b"
dependencies = [
"same-file",
"winapi-util",
]
[[package]]
name = "wasi"
version = "0.11.0+wasi-snapshot-preview1"
@ -1690,6 +1860,15 @@ version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
[[package]]
name = "winapi-util"
version = "0.1.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4d4cc384e1e73b93bafa6fb4f1df8c41695c8a91cf9c4c64358067d15a7b6c6b"
dependencies = [
"windows-sys 0.52.0",
]
[[package]]
name = "winapi-x86_64-pc-windows-gnu"
version = "0.4.0"

View file

@ -12,6 +12,7 @@ version = "0.8.4"
[workspace.dependencies]
anyhow = "1"
chrono = "0.4.35"
clap = { version = "4", features = ["derive", "cargo", "unicode"] }
colored = "2.1.0"
env_logger = "0.11"
format_serde_error = "0.3.0"
@ -54,7 +55,6 @@ pre-release-replacements = [
blue-build-recipe = { version = "=0.8.4", path = "./recipe" }
blue-build-template = { version = "=0.8.4", path = "./template" }
blue-build-utils = { version = "=0.8.4", path = "./utils" }
clap = { version = "4", features = ["derive", "cargo", "unicode"] }
clap-verbosity-flag = "2"
clap_complete = "4"
clap_complete_nushell = "4"
@ -71,6 +71,7 @@ users = "0.11.0"
# Workspace dependencies
anyhow.workspace = true
chrono.workspace = true
clap.workspace = true
colored.workspace = true
env_logger.workspace = true
log.workspace = true

View file

@ -30,7 +30,7 @@ pub struct Recipe<'a> {
#[builder(setter(into))]
pub image_version: Cow<'a, str>,
#[serde(alias = "blue-build-tag")]
#[serde(alias = "blue-build-tag", skip_serializing_if = "Option::is_none")]
#[builder(default, setter(into, strip_option))]
pub blue_build_tag: Option<Cow<'a, str>>,

View file

@ -6,9 +6,12 @@ use std::{
use anyhow::Result;
use blue_build_recipe::Recipe;
use blue_build_template::{ContainerFileTemplate, Template};
use blue_build_utils::constants::{
CI_PROJECT_NAME, CI_PROJECT_NAMESPACE, CI_REGISTRY, CONFIG_PATH, GITHUB_REPOSITORY_OWNER,
RECIPE_FILE, RECIPE_PATH,
use blue_build_utils::{
constants::{
CI_PROJECT_NAME, CI_PROJECT_NAMESPACE, CI_REGISTRY, CONFIG_PATH, GITHUB_REPOSITORY_OWNER,
RECIPE_FILE, RECIPE_PATH,
},
syntax_highlighting::{self, DefaultThemes},
};
use clap::Args;
use log::{debug, info, trace, warn};
@ -46,6 +49,23 @@ pub struct TemplateCommand {
#[builder(default, setter(into, strip_option))]
registry_namespace: Option<String>,
/// Instead of creating a Containerfile, display
/// the full recipe after traversing all `from-file` properties.
///
/// This can be used to help debug the order
/// you defined your recipe.
#[arg(short, long)]
#[builder(default)]
display_full_recipe: bool,
/// Choose a theme for the syntax highlighting
/// for the Containerfile or Yaml.
///
/// The default is `mocha-dark`.
#[arg(short = 't', long)]
#[builder(default, setter(strip_option))]
syntax_theme: Option<DefaultThemes>,
#[clap(flatten)]
#[builder(default)]
drivers: DriverArgs,
@ -78,12 +98,21 @@ impl TemplateCommand {
}
});
info!("Templating for recipe at {}", recipe_path.display());
debug!("Deserializing recipe");
let recipe_de = Recipe::parse(&recipe_path)?;
trace!("recipe_de: {recipe_de:#?}");
if self.display_full_recipe {
if let Some(output) = self.output.as_ref() {
std::fs::write(output, serde_yaml::to_string(&recipe_de)?)?;
} else {
syntax_highlighting::print_ser(&recipe_de, "yml", self.syntax_theme)?;
}
return Ok(());
}
info!("Templating for recipe at {}", recipe_path.display());
let template = ContainerFileTemplate::builder()
.os_version(Driver::get_os_version(&recipe_de)?)
.build_id(Driver::get_build_id())
@ -101,10 +130,9 @@ impl TemplateCommand {
std::fs::write(output, output_str)?;
} else {
debug!("Templating to stdout");
println!("{output_str}");
syntax_highlighting::print(&output_str, "Dockerfile", self.syntax_theme)?;
}
info!("Finished templating Containerfile");
Ok(())
}

View file

@ -9,8 +9,10 @@ license.workspace = true
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
atty = "0.2.14"
directories = "5"
process_control = { version = "4.0.3", features = ["crossbeam-channel"] }
syntect = "5.2.0"
which = "6"
anyhow.workspace = true
@ -23,6 +25,13 @@ serde.workspace = true
serde_yaml.workspace = true
serde_json.workspace = true
[dependencies.clap]
workspace = true
features = ["derive"]
[build-dependencies]
syntect = "5.2.0"
[lints]
workspace = true

24
utils/build.rs Normal file
View file

@ -0,0 +1,24 @@
use std::env;
use std::path::PathBuf;
use syntect::dumps;
use syntect::parsing::syntax_definition::SyntaxDefinition;
use syntect::parsing::SyntaxSetBuilder;
fn main() {
let mut ssb = SyntaxSetBuilder::new();
ssb.add(
SyntaxDefinition::load_from_str(
include_str!("highlights/Dockerfile.sublime-syntax"),
true,
None,
)
.unwrap(),
);
let ss = ssb.build();
dumps::dump_to_uncompressed_file(
&ss,
PathBuf::from(env::var("OUT_DIR").unwrap()).join("docker_syntax.bin"),
)
.unwrap();
}

View file

@ -0,0 +1,141 @@
%YAML 1.2
# The MIT License (MIT)
#
# Copyright 2014 Asbjorn Enge
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
# https://github.com/asbjornenge/Docker.tmbundle
---
# http://www.sublimetext.com/docs/3/syntax.html
name: Dockerfile
scope: source.dockerfile
file_extensions:
- Dockerfile
- dockerfile
hidden_file_extensions:
- .Dockerfile
first_line_match: ^\s*(?i:(from(?!\s+\S+\s+import)|arg))\s+
variables:
onbuild_directive: (?i:(onbuild)\s+)?
onbuild_commands_directive:
"{{onbuild_directive}}(?i:add|arg|env|expose|healthcheck|label|run|shell|stopsignal|user|volume|workdir)"
nononbuild_commands_directive: (?i:maintainer)
runtime_directive: "{{onbuild_directive}}(?i:cmd|entrypoint)"
from_directive: (?i:(from))\s+[^\s:@]+(?:[:@](\S+))?(?:\s+(?i:(as))\s+(\S+))?
copy_directive: ({{onbuild_directive}}(?i:copy))(?:\s+--from=(\S+))?
contexts:
main:
- include: comments
- match: ^(?i:arg)\s
scope: keyword.control.dockerfile
- include: from
from:
- match: ^{{from_directive}}
captures:
1: keyword.control.dockerfile
2: entity.name.enum.tag-digest
3: keyword.control.dockerfile
4: variable.stage-name
push: body
body:
- include: comments
- include: directives
- include: invalid
- include: from
directives:
- match: ^\s*{{onbuild_commands_directive}}\s
captures:
0: keyword.control.dockerfile
1: keyword.other.special-method.dockerfile
push: args
- match: ^\s*{{nononbuild_commands_directive}}\s
scope: keyword.control.dockerfile
push: args
- match: ^\s*{{copy_directive}}\s
captures:
1: keyword.control.dockerfile
2: keyword.other.special-method.dockerfile
3: variable.stage-name
push: args
- match: ^\s*{{runtime_directive}}\s
captures:
0: keyword.operator.dockerfile
1: keyword.other.special-method.dockerfile
push: args
escaped-char:
- match: \\.
scope: constant.character.escaped.dockerfile
args:
- include: comments
- include: escaped-char
- match: ^\s*$
- match: \\\s+$
- match: \n
pop: true
- match: '"'
scope: punctuation.definition.string.begin.dockerfile
push: double_quote_string
- match: "'"
scope: punctuation.definition.string.begin.dockerfile
push: single_quote_string
double_quote_string:
- meta_scope: string.quoted.double.dockerfile
- include: escaped-char
- match: ^\s*$
- match: \\\s+$
- match: \n
set: invalid
- match: '"'
scope: punctuation.definition.string.end.dockerfile
pop: true
single_quote_string:
- meta_scope: string.quoted.single.dockerfile
- include: escaped-char
- match: ^\s*$
- match: \\\s+$
- match: \n
set: invalid
- match: "'"
scope: punctuation.definition.string.end.dockerfile
pop: true
comments:
- match: ^(\s*)((#).*$\n?)
comment: comment.line
captures:
1: punctuation.whitespace.comment.leading.dockerfile
2: comment.dockerfile
3: punctuation.definition.comment.dockerfile
invalid:
- match: ^[^A-Z\n](.*)$
scope: invalid
set: body

View file

@ -1,6 +1,7 @@
pub mod command_output;
pub mod constants;
pub mod logging;
pub mod syntax_highlighting;
use std::{ffi::OsStr, io::Write, path::PathBuf, process::Command, thread, time::Duration};

View file

@ -0,0 +1,85 @@
use anyhow::{anyhow, Result};
use clap::ValueEnum;
use log::trace;
use serde::ser::Serialize;
use syntect::{dumps, easy::HighlightLines, highlighting::ThemeSet, parsing::SyntaxSet};
#[derive(Debug, Default, Clone, Copy, ValueEnum)]
pub enum DefaultThemes {
#[default]
MochaDark,
OceanDark,
OceanLight,
EightiesDark,
InspiredGithub,
SolarizedDark,
SolarizedLight,
}
impl std::fmt::Display for DefaultThemes {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(match *self {
Self::MochaDark => "base16-mocha.dark",
Self::OceanDark => "base16-ocean.dark",
Self::OceanLight => "base16-ocean.light",
Self::EightiesDark => "base16-eighties.dark",
Self::InspiredGithub => "InspiredGithub",
Self::SolarizedDark => "Solarized (dark)",
Self::SolarizedLight => "Solarized (light)",
})
}
}
/// Prints the file with syntax highlighting.
///
/// # Errors
/// Will error if the theme doesn't exist, the syntax doesn't exist, or the file
/// failed to serialize.
pub fn print(file: &str, file_type: &str, theme: Option<DefaultThemes>) -> Result<()> {
trace!("syntax_highlighting::print({file}, {file_type}, {theme:?})");
if atty::is(atty::Stream::Stdout) {
let ss: SyntaxSet = if file_type == "dockerfile" || file_type == "Dockerfile" {
dumps::from_uncompressed_data(include_bytes!(concat!(
env!("OUT_DIR"),
"/docker_syntax.bin"
)))?
} else {
SyntaxSet::load_defaults_newlines()
};
let ts = ThemeSet::load_defaults();
let syntax = ss
.find_syntax_by_extension(file_type)
.ok_or_else(|| anyhow!("Failed to get syntax"))?;
let mut h = HighlightLines::new(
syntax,
ts.themes
.get(theme.unwrap_or_default().to_string().as_str())
.ok_or_else(|| anyhow!("Failed to get highlight theme"))?,
);
for line in file.lines() {
let ranges = h.highlight_line(line, &ss)?;
let escaped = syntect::util::as_24_bit_terminal_escaped(&ranges, false);
println!("{escaped}");
}
println!("\x1b[0m");
} else {
println!("{file}");
}
Ok(())
}
/// Takes a serializable struct and prints it out with syntax highlighting.
///
/// # Errors
/// Will error if the theme doesn't exist, the syntax doesn't exist, or the file
/// failed to serialize.
pub fn print_ser<T: Serialize>(
file: &T,
file_type: &str,
theme: Option<DefaultThemes>,
) -> Result<()> {
print(serde_yaml::to_string(file)?.as_str(), file_type, theme)?;
Ok(())
}