nestrs-cli-rs 0.1.0

Rust port of the Nest CLI for the nestrs organization.
Documentation
//! Upstream source: `../nest-cli/lib/compiler/base-compiler.ts`.

use std::path::{Component, Path, PathBuf};

use crate::configuration::{CompilerOptions, Configuration, DEFAULT_SOURCE_ROOT, Plugin};

use super::plugins::plugins_loader::{MultiNestCompilerPlugins, PluginsLoader};

#[derive(Clone, Debug, PartialEq, Eq)]
pub struct BaseCompilerContext {
    pub cwd: PathBuf,
    pub configuration: Configuration,
}

impl BaseCompilerContext {
    pub fn new(cwd: impl Into<PathBuf>, configuration: Configuration) -> Self {
        Self {
            cwd: cwd.into(),
            configuration,
        }
    }

    pub fn compiler_options_for(&self, app_name: Option<&str>) -> CompilerOptions {
        app_name
            .and_then(|name| self.configuration.projects.get(name))
            .and_then(|project| project.compiler_options.clone())
            .unwrap_or_else(|| self.configuration.compiler_options.clone())
    }

    pub fn source_root_for(&self, app_name: Option<&str>) -> String {
        app_name
            .and_then(|name| self.configuration.projects.get(name))
            .and_then(|project| project.source_root.clone())
            .unwrap_or_else(|| {
                if self.configuration.source_root.is_empty() {
                    DEFAULT_SOURCE_ROOT.to_string()
                } else {
                    self.configuration.source_root.clone()
                }
            })
    }

    pub fn plugins_for(&self, app_name: Option<&str>) -> Vec<Plugin> {
        self.compiler_options_for(app_name).plugins
    }
}

#[derive(Clone, Debug, PartialEq, Eq)]
pub struct BaseCompiler {
    plugins_loader: PluginsLoader,
}

impl Default for BaseCompiler {
    fn default() -> Self {
        Self::new(PluginsLoader::default())
    }
}

impl BaseCompiler {
    pub fn new(plugins_loader: PluginsLoader) -> Self {
        Self { plugins_loader }
    }

    pub fn load_plugins(
        &self,
        context: &BaseCompilerContext,
        ts_config_path: impl AsRef<Path>,
        app_name: Option<&str>,
    ) -> Result<MultiNestCompilerPlugins, String> {
        let path_to_source = self.get_path_to_source(context, ts_config_path, app_name);
        self.plugins_loader
            .load(&context.plugins_for(app_name), Some(path_to_source))
    }

    pub fn get_path_to_source(
        &self,
        context: &BaseCompilerContext,
        ts_config_path: impl AsRef<Path>,
        app_name: Option<&str>,
    ) -> PathBuf {
        let source_root = normalize_path(Path::new(&context.source_root_for(app_name)));
        let ts_config_path = ts_config_path.as_ref();
        let ts_config_dir = ts_config_path.parent().unwrap_or_else(|| Path::new(""));
        let relative_root = normalize_path(ts_config_dir);

        if starts_with_components(&source_root, &relative_root) {
            context.cwd.join(source_root)
        } else {
            context.cwd.join(relative_root).join(source_root)
        }
    }
}

fn normalize_path(path: &Path) -> PathBuf {
    let mut normalized = PathBuf::new();
    for component in path.components() {
        match component {
            Component::CurDir => {}
            Component::ParentDir => {
                normalized.pop();
            }
            _ => normalized.push(component.as_os_str()),
        }
    }
    normalized
}

fn starts_with_components(path: &Path, prefix: &Path) -> bool {
    if prefix.as_os_str().is_empty() {
        return true;
    }
    path.components()
        .zip(prefix.components())
        .all(|(a, b)| a == b)
        && path.components().count() >= prefix.components().count()
}