morp 0.1.0

Utilities for javascript monorepos
Documentation
use std::collections::HashMap;
use std::fs::{self, DirEntry, File};
use std::io;
use std::path::{Path, PathBuf};

use petgraph::{algo, graphmap::DiGraphMap};
use serde_json;

pub struct Monorepo {
    packages: Vec<PackageJson>,
}

#[derive(Debug)]
pub enum MonorepoError {
    PackagesDirRead(io::Error),
    PackagesDirEntryRead(io::Error),
    PackageJsonRead(io::Error),
    PackageJsonDeserialization {
        error: serde_json::Error,
        path: PathBuf,
    },
    CyclicDependencies,
}

#[derive(Debug, Deserialize)]
pub struct PackageJson {
    name: String,
    dependencies: Option<HashMap<String, String>>,
}

impl Monorepo {
    pub fn load(path: &Path) -> Result<Monorepo, MonorepoError> {
        let packages_path = path.join("packages/");

        let packages = Monorepo::load_packages(&packages_path)?;

        Ok(Monorepo { packages })
    }

    fn load_packages(path: &Path) -> Result<Vec<PackageJson>, MonorepoError> {
        let entries: Vec<DirEntry> = fs::read_dir(path)
            .map_err(MonorepoError::PackagesDirRead)?
            .map(|entry| entry.map_err(MonorepoError::PackagesDirEntryRead))
            .collect::<Result<Vec<DirEntry>, MonorepoError>>()?;

        entries
            .iter()
            .map(|entry| entry.path())
            .filter(|path| path.is_dir())
            .map(|p| Monorepo::load_package(&p))
            .collect::<Result<Vec<PackageJson>, MonorepoError>>()
    }

    fn load_package(path: &PathBuf) -> Result<PackageJson, MonorepoError> {
        let filepath = path.join("package.json");

        let json_file = File::open(filepath).map_err(MonorepoError::PackageJsonRead)?;

        serde_json::from_reader(json_file).map_err(|error| {
            MonorepoError::PackageJsonDeserialization {
                error,
                path: path.to_path_buf(),
            }
        })
    }

    pub fn get_deps_graph(&self) -> Result<DiGraphMap<&str, f32>, MonorepoError> {
        let mut graph = DiGraphMap::new();

        self.packages.iter().for_each(|package| {
            graph.add_node(package.name.as_str());
        });

        self.packages
            .iter()
            .for_each(|package| match &package.dependencies {
                None => (),
                Some(deps) => {
                    deps.keys().for_each(|dep_name| {
                        if let Some(d) = self.packages.iter().find(|d| &d.name == dep_name) {
                            graph.add_edge(&package.name, &d.name, 0.);
                        }
                    });
                }
            });

        if algo::is_cyclic_directed(&graph) {
            Err(MonorepoError::CyclicDependencies)
        } else {
            Ok(graph)
        }
    }
}