harper 0.0.2

A library to implement SSGs.
Documentation
use std::{fs, io};
use std::path::{Path, PathBuf};
use std::fmt::Debug;

use crate::error::{Result, Chainable};
use crate::value::{Value, Source};

pub trait Sink: Debug {
    fn write<V: Into<Value> + 'static>(&self, value: V) -> Result<()> {
        self.write_value(value.into())
    }

    fn write_value(&self, value: Value) -> Result<()>;

    #[inline]
    fn write_from<'a, S: Source>(&self, source: S) -> Result<()> {
        self.write(source.read()?)
    }
}

impl Sink for fs::File {
    fn write_value(&self, value: Value) -> Result<()> {
        #[inline(always)]
        fn write_bytes(to: &mut dyn io::Write, bytes: &[u8]) -> Result<()> {
            Ok(to.write_all(bytes)?)
        }

        fn write_value(to: &mut dyn io::Write, value: &Value) -> Result<()> {
            match value {
                Value::Null => Ok(()),
                Value::Bool(b) => write_bytes(to, &[*b as u8]),
                Value::String(s) => write_bytes(to, s.as_bytes()),
                Value::Path(s) => write_bytes(to, s.as_bytes()),
                Value::Array(array) => array.iter().try_for_each(|v| write_value(to, v)),
                Value::Num(n) => {
                    match n.to_u128_strict() {
                        Ok(v) => {
                            if let Ok(v) = u8::try_from(v) {
                                write_bytes(to, &v.to_le_bytes()[..])
                            } else {
                                write_bytes(to, &v.to_le_bytes()[..])
                            }
                        },
                        Err(v) => write_bytes(to, &v.to_le_bytes()[..]),
                    }
                },
                Value::Dict(_) => Err("file endpoint does not support dictionary writes".into()),
            }
        }

        let mut file = io::BufWriter::new(self);
        write_value(&mut file, &value.into())
    }
}

impl Sink for &Path {
    fn write_value(&self, value: Value) -> Result<()> {
        fs::File::create(self)
            .chain(error! {
                "failed to open/create file for writing",
                "file path" => self.display()
            })?
            .write(value)
    }
}

impl Sink for PathBuf {
    fn write_value(&self, value: Value) -> Result<()> {
        <&Path as Sink>::write(&self.as_path(), value)
    }
}

impl<T: Sink> Sink for &T {
    fn write_value(&self, value: Value) -> Result<()> {
        <T as Sink>::write(self, value)
    }
}