proto_cli 0.23.2

A multi-language version manager, a unified toolchain.
use proto_core::PluginLocator;
use starbase_styles::color::{self, OwoStyle};
use std::io::{BufWriter, StdoutLock, Write};

pub struct Printer<'std> {
    buffer: BufWriter<StdoutLock<'std>>,
    depth: u8,
}

impl<'std> Printer<'std> {
    pub fn new() -> Self {
        let stdout = std::io::stdout();
        let buffer = BufWriter::new(stdout.lock());

        Printer { buffer, depth: 0 }
    }

    pub fn flush(&mut self) {
        self.line();
        self.buffer.flush().unwrap();
    }

    pub fn line(&mut self) {
        writeln!(&mut self.buffer).unwrap();
    }

    pub fn header<K: AsRef<str>, V: AsRef<str>>(&mut self, id: K, name: V) {
        self.indent();

        writeln!(
            &mut self.buffer,
            "{} {} {}",
            OwoStyle::new().bold().style(color::id(id.as_ref())),
            color::muted("-"),
            color::muted_light(name.as_ref()),
        )
        .unwrap();
    }

    pub fn section(
        &mut self,
        func: impl FnOnce(&mut Printer) -> miette::Result<()>,
    ) -> miette::Result<()> {
        self.depth += 1;
        func(self)?;
        self.depth -= 1;

        Ok(())
    }

    pub fn named_section<T: AsRef<str>>(
        &mut self,
        name: T,
        func: impl FnOnce(&mut Printer) -> miette::Result<()>,
    ) -> miette::Result<()> {
        writeln!(&mut self.buffer).unwrap();

        self.indent();

        writeln!(
            &mut self.buffer,
            "{}",
            OwoStyle::new()
                .bold()
                .style(color::muted_light(name.as_ref()))
        )
        .unwrap();

        self.section(func)?;

        Ok(())
    }

    pub fn indent(&mut self) {
        if self.depth > 0 {
            write!(&mut self.buffer, "{}", "  ".repeat(self.depth as usize)).unwrap();
        }
    }

    pub fn entry<K: AsRef<str>, V: AsRef<str>>(&mut self, key: K, value: V) {
        self.indent();

        writeln!(&mut self.buffer, "{}: {}", key.as_ref(), value.as_ref()).unwrap();
    }

    pub fn entry_list<K: AsRef<str>, I: IntoIterator<Item = V>, V: AsRef<str>>(
        &mut self,
        key: K,
        list: I,
        empty: Option<String>,
    ) {
        let items = list.into_iter().collect::<Vec<_>>();

        if items.is_empty() {
            if let Some(fallback) = empty {
                self.entry(key, fallback);
            }
        } else {
            self.indent();

            writeln!(&mut self.buffer, "{}:", key.as_ref()).unwrap();

            self.depth += 1;

            for item in items {
                self.indent();

                writeln!(&mut self.buffer, "{} {}", color::muted("-"), item.as_ref()).unwrap();
            }

            self.depth -= 1;
        }
    }

    pub fn entry_map<
        K: AsRef<str>,
        I: IntoIterator<Item = (V1, V2)>,
        V1: AsRef<str>,
        V2: AsRef<str>,
    >(
        &mut self,
        key: K,
        map: I,
        empty: Option<String>,
    ) {
        let items = map.into_iter().collect::<Vec<_>>();

        if items.is_empty() {
            if let Some(fallback) = empty {
                self.entry(key, fallback);
            }
        } else {
            self.indent();

            writeln!(&mut self.buffer, "{}:", key.as_ref()).unwrap();

            self.depth += 1;

            for item in items {
                self.indent();

                writeln!(
                    &mut self.buffer,
                    "{} {} {}",
                    item.0.as_ref(),
                    color::muted("-"),
                    item.1.as_ref()
                )
                .unwrap();
            }

            self.depth -= 1;
        }
    }

    pub fn locator<L: AsRef<PluginLocator>>(&mut self, locator: L) {
        match locator.as_ref() {
            PluginLocator::SourceFile { path, .. } => {
                self.entry("Source", color::path(path.canonicalize().unwrap()));
            }
            PluginLocator::SourceUrl { url } => {
                self.entry("Source", color::url(url));
            }
            PluginLocator::GitHub(github) => {
                self.entry("GitHub", color::label(&github.repo_slug));
                self.entry(
                    "Tag",
                    color::hash(github.tag.as_deref().unwrap_or("latest")),
                );
            }
            PluginLocator::Wapm(wapm) => {
                self.entry("Package", color::label(&wapm.package_name));
                self.entry(
                    "Release",
                    color::hash(wapm.version.as_deref().unwrap_or("latest")),
                );
            }
        };
    }
}