1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
//! Functions related to the OftLisp paths.

use std::collections::HashMap;
use std::env::var_os;
use std::fs::metadata;
use std::path::PathBuf;

use walkdir::{DirEntry, Result as WalkResult, WalkDir};

#[cfg(unix)]
const ROOT_PATH_DEFAULT: &str = "/usr/lib/oftlisp";

#[cfg(windows)]
const ROOT_PATH_DEFAULT: &str = r"C:\oftlisp";

#[cfg(not(any(unix, windows)))]
compile_error!("TODO: Choose an oftlisp::paths::ROOT_PATH_DEFAULT for this platform");

/// Returns the path of the module with the given import path.
pub fn find_module<S: AsRef<str>>(import_path: S) -> Option<PathBuf> {
    let import_path = import_path.as_ref().split('/').collect::<Vec<_>>();
    let find_module_in = |mut path: PathBuf| -> Option<PathBuf> {
        path.push("src");
        for component in &import_path {
            path.push(component);
        }
        if path.extension().is_none() {
            path.set_extension("oft");
        }
        metadata(&path).ok().and_then(|m| if m.is_file() {
            Some(path)
        } else {
            None
        })
    };

    match user_path() {
        Some(path) => {
            match find_module_in(path) {
                Some(m) => Some(m),
                None => find_module_in(root_path()),
            }
        }
        None => find_module_in(root_path()),
    }
}

/// Returns a list of all the modules on the system, along with metadata.
///
/// The key of the map is the import path, the value is the metadata.
///
/// When [RFC1522](https://github.com/rust-lang/rfcs/pull/1522) is in stable, this'll probably return an iterator instead.
pub fn find_modules() -> WalkResult<HashMap<String, ModuleMeta>> {
    fn is_oftlisp_source(entry: &DirEntry) -> bool {
        entry.path().extension().map(|e| e == "oft").unwrap_or(
            false,
        )
    }

    fn search(
        out: &mut HashMap<String, ModuleMeta>,
        mut path: PathBuf,
        is_root: bool,
    ) -> WalkResult<()> {
        path.push("src");
        let iter = WalkDir::new(&path).follow_links(true).into_iter().filter(
            |r| {
                r.as_ref().map(is_oftlisp_source).unwrap_or(false)
            },
        );
        for r in iter {
            let entry = r?;
            let import_path = entry
                .path()
                .strip_prefix(&path)
                .unwrap()
                .with_extension("")
                .display()
                .to_string();
            let metadata = ModuleMeta {
                is_root,
                path: entry.path().to_owned(),
            };
            out.entry(import_path).or_insert(metadata);
        }
        Ok(())
    }

    let mut paths = HashMap::new();
    if let Some(user_path) = user_path() {
        search(&mut paths, user_path, false)?;
    }
    search(&mut paths, root_path(), true)?;
    Ok(paths)
}

/// Returns the OftLisp root path (`OFTLISP_ROOT`).
pub fn root_path() -> PathBuf {
    if let Some(var) = var_os("OFTLISP_ROOT") {
        var.into()
    } else {
        option_env!("OFTLISP_ROOT")
            .map(PathBuf::from)
            .unwrap_or_else(|| PathBuf::from(ROOT_PATH_DEFAULT))
    }
}

/// Returns the OftLisp user path (`OFTLISP_HOME`), if it is set.
pub fn user_path() -> Option<PathBuf> {
    var_os("OFTLISP_HOME").map(PathBuf::from)
}

/// Metadata about a module.
pub struct ModuleMeta {
    /// Whether the module was loaded from `OFTLISP_ROOT` (if true) or
    /// `OFTLISP_HOME` (if false).
    pub is_root: bool,

    /// The path of the module.
    pub path: PathBuf,
}