use std::borrow::Cow;
use jotdown::{Container, Event, Render};
use crate::{MetaFormat, MetaOpts};
pub struct MetaRenderer {
opts: MetaOpts,
first_written: bool,
}
impl MetaRenderer {}
impl Render<'_> for MetaRenderer {
fn push_event<W>(&mut self, event: Event<'_>, mut out: W) -> std::fmt::Result
where
W: std::fmt::Write,
{
self.render_event(&event, &mut out)
}
}
fn must_escape_shell(ch: char) -> bool {
match ch {
'a'..='z' | 'A'..='Z' | '0'..='9' | '-' | '_' | '=' | '/' | ',' | '.' | '+' | '@' | '%' => {
false
}
_ => char::is_ascii(&ch),
}
}
fn escape_shell(s: Cow<str>) -> Cow<str> {
if s.is_empty() || !s.contains(must_escape_shell) {
return s;
}
let mut es = String::with_capacity(s.len() + 2);
for ch in s.chars() {
if must_escape_shell(ch) {
es.push('\\');
}
es.push(ch);
}
es.into()
}
fn must_escape_json(ch: char) -> bool {
matches!(ch, '"' | '\\')
}
fn escape_json(s: Cow<str>) -> Cow<str> {
if s.is_empty() || !s.contains(must_escape_json) {
return s;
}
let mut es = String::with_capacity(s.len() + 2);
for ch in s.chars() {
if ch == '"' || ch == '\\' {
es.push('\\');
}
es.push(ch);
}
es.into()
}
impl MetaRenderer {
pub fn new(opts: MetaOpts) -> Self {
MetaRenderer {
opts,
first_written: false,
}
}
fn start<W>(&mut self, out: &mut W) -> std::fmt::Result
where
W: std::fmt::Write,
{
if matches!(self.opts.format, MetaFormat::Json) {
out.write_str("{\n")?;
}
Ok(())
}
fn finish<W>(&mut self, out: &mut W) -> std::fmt::Result
where
W: std::fmt::Write,
{
if matches!(self.opts.format, MetaFormat::Json) {
out.write_str("\n}\n")?;
}
Ok(())
}
fn render_event<W>(&mut self, e: &Event<'_>, out: &mut W) -> std::fmt::Result
where
W: std::fmt::Write,
{
match e {
Event::Start(Container::Document, _) => self.start(out)?,
Event::End(Container::Document) => self.finish(out)?,
Event::Start(_, attr) => {
for (k, v) in attr {
if k.key().is_some_and(|key| key.starts_with("dmos:")) {
let def = match self.opts.format {
MetaFormat::Shell => {
format!(
"{}={}\n",
k.key().unwrap().replace(':', "_"),
escape_shell(v.to_string().into()),
)
}
MetaFormat::Json => {
if self.first_written {
out.write_str(",\n")?;
}
format!(
"\"{}\": \"{}\"",
k.key().unwrap().replace(':', "_"),
escape_json(v.to_string().into()),
)
}
};
out.write_str(&def)?;
self.first_written = true;
}
}
}
_ => (),
};
Ok(())
}
}