harper 0.0.2

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

use either::Either;

use crate::error::{Result, Chainable};
use crate::fstree::Entry;
use crate::value::{Value, Sink};

pub trait Source: Debug {
    type Value: Into<Value> + 'static;

    fn read(self) -> Result<Self::Value>;

    fn try_read<T: TryFrom<Value> + 'static>(self) -> Result<T> where Self: Sized {
        let value = self.read()?;
        if std::any::TypeId::of::<T>() == std::any::TypeId::of::<Self::Value>() {
            unsafe {
                let value_ptr = &value as *const Self::Value as *const T;
                let transmuted_value = value_ptr.read();
                std::mem::forget(value);
                return Ok(transmuted_value);
            }
        }

        value.into()
            .try_into()
            .map_err(|_| error! {
                "invalid input value type",
                "expected" => std::any::type_name::<T>(),
                "actual type" => std::any::type_name::<Self::Value>(),
            })
    }

    fn path(&self) -> Option<&Path> {
        None
    }

    #[inline]
    fn read_to<S: Sink>(self, sink: S) -> Result<()> where Self: Sized {
        sink.write(self.read()?)
    }
}

impl Source for Value {
    type Value = Self;

    fn read(self) -> Result<Self::Value> {
        Ok(self)
    }
}

impl Source for String {
    type Value = String;

    fn read(self) -> Result<Self> {
        Ok(self)
    }
}

impl Source for &fs::File {
    type Value = Either<String, Vec<u8>>;

    fn read(self) -> Result<Self::Value> {
        use io::Read;

        let mut data = Vec::new();
        let mut file = io::BufReader::new(self);
        file.read_to_end(&mut data)?;

        let value = String::from_utf8(data)
            .map(Either::Left)
            .map_err(|v| v.into_bytes())
            .unwrap_or_else(Either::Right);

        Ok(value)
    }
}

impl Source for &Path {
    type Value = <&'static fs::File as Source>::Value;

    fn read(self) -> Result<Self::Value> {
        let file = fs::File::open(self).chain(error! {
            "failed to open file for reading",
            "file path" => self.display()
        })?;

        file.read()
    }

    fn path(&self) -> Option<&Path> {
        Some(self)
    }
}

impl Source for &Entry {
    type Value = <&'static Path as Source>::Value;

    fn read(self) -> Result<Self::Value> {
        self.path.as_ref().read()
    }

    fn path(&self) -> Option<&Path> {
        Some(&*self.path)
    }
}