murkdown 0.1.1

An experimental semantic markup language and static site generator for composing and decomposing hypertext documents
Documentation
use std::{
    collections::HashSet,
    path::{Path, PathBuf},
    process::{Output, Stdio},
};

use tokio::{
    io::AsyncWriteExt,
    process::{Child, Command},
};
use walkdir::DirEntry;

use super::types::{AppError, Source};

pub fn is_visible(entry: &DirEntry) -> bool {
    entry
        .file_name()
        .to_str()
        .map(|s| !s.starts_with(".") || s.starts_with("./") || s == ".")
        .unwrap_or(true)
}

pub fn is_sensible(entry: &DirEntry) -> bool {
    entry
        .path()
        .to_str()
        .map(|s| !s.starts_with("./build/") && !s.starts_with("./target/"))
        .unwrap_or(true)
}

pub fn is_file(entry: &DirEntry) -> bool {
    entry.path().is_file()
}

pub fn into_uri_path_tuple(entry: DirEntry) -> (String, PathBuf) {
    let path = entry.path().to_path_buf();
    let id = path
        .strip_prefix("./")
        .unwrap_or(&path)
        .display()
        .to_string();
    (id, path)
}

pub fn into_id_source_tuple(entry: DirEntry) -> (String, Source) {
    let path = entry.path().to_path_buf();
    let id = path
        .strip_prefix("./")
        .unwrap_or(&path)
        .display()
        .to_string();
    (id, Source::from(path))
}

pub fn spawn_command(program: &str, args: &str) -> Result<Child, AppError> {
    let args = shlex::split(args).ok_or(AppError::bad_exec_args(program, args))?;

    Command::new(program)
        .args(args)
        .stdout(Stdio::piped())
        .stdin(Stdio::piped())
        .kill_on_drop(true)
        .spawn()
        .map_err(|e| AppError::execution_io_failed(e, program))
}

pub async fn write_command(
    child: &mut Child,
    stdin: Option<&str>,
    name: &str,
) -> Result<(), AppError> {
    if let Some(input) = stdin {
        let mut stdin = child.stdin.take().ok_or(AppError::execution_failed(
            "could not take stdin",
            name.to_string(),
        ))?;

        stdin
            .write_all(input.as_bytes())
            .await
            .map_err(|e| AppError::execution_io_failed(e, name.to_string()))?;
    }
    Ok(())
}

pub async fn wait_command(child: Child, name: &str) -> Result<Output, AppError> {
    child
        .wait_with_output()
        .await
        .map_err(|e| AppError::execution_io_failed(e, name.to_string()))
}

pub fn parents<I>(paths: I) -> Result<HashSet<PathBuf>, AppError>
where
    I: Iterator<Item = PathBuf>,
{
    paths
        .map(|p| match p.parent() {
            Some(parent) if parent == Path::new("") => Ok(PathBuf::from(".")),
            Some(parent) => Ok(parent.to_path_buf()),
            None => Err(AppError::bad_path(p)),
        })
        .collect()
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_parents() {
        let result = parents(
            [
                PathBuf::from("./foo.md"),
                PathBuf::from("./bar/bar.md"),
                PathBuf::from("./"),
            ]
            .into_iter(),
        )
        .unwrap();
        let expected = [PathBuf::from("./"), PathBuf::from("./bar")].into();

        assert_eq!(&result, &expected);
    }
}