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
122
123
124
125
126
//! # Default paths-based resolver
//!
//! Resolves packages based on a set of paths.
//!
//! These paths have the following structure:
//!
//! * `<root>/<package>/<last>.reproto`
//! * `<root>/<package>/<last>/<version>.reproto`
//!
//! The second form is only used when a version requirement is present.

use core::{Object, PathObject, RpRequiredPackage, Version, VersionReq};
use errors::*;
use resolver::Resolver;
use std::collections::BTreeMap;
use std::fs;
use std::path::{Path, PathBuf};

const EXT: &str = "reproto";

pub struct Paths {
    paths: Vec<PathBuf>,
}

impl Paths {
    pub fn new(paths: Vec<PathBuf>) -> Paths {
        Paths { paths: paths }
    }

    fn parse_stem<'a>(&self, stem: &'a str) -> Result<(&'a str, Option<Version>)> {
        let mut it = stem.splitn(2, '-');

        if let (Some(name_base), Some(name_version)) = (it.next(), it.next()) {
            let version = Version::parse(name_version).map_err(
                |_| format!("bad version"),
            )?;

            return Ok((name_base, Some(version)));
        }

        Ok((stem, None))
    }

    pub fn find_versions(
        &self,
        path: &Path,
        base: &str,
        version_req: Option<&VersionReq>,
    ) -> Result<Vec<(Option<Version>, Box<Object>)>> {
        let mut files: BTreeMap<_, Box<Object>> = BTreeMap::new();

        for e in fs::read_dir(path)? {
            let p = e?.path();

            // only look for files
            if !p.is_file() {
                continue;
            }

            if p.extension().map(|ext| ext != EXT).unwrap_or(true) {
                continue;
            }

            if let Some(stem) = p.file_stem().and_then(::std::ffi::OsStr::to_str) {
                let (name_base, version) = self.parse_stem(stem).map_err(|m| {
                    format!("{}: {}", p.display(), m)
                })?;

                if name_base != base {
                    continue;
                }

                if let Some(version_req) = version_req {
                    if let Some(version) = version {
                        if version_req.matches(&version) {
                            let object = PathObject::new(&p);
                            files.insert(Some(version), Box::new(object));
                        }

                        continue;
                    }

                    if *version_req == VersionReq::any() {
                        let object = PathObject::new(&p);
                        files.insert(None, Box::new(object));
                        continue;
                    }
                } else {
                    let object = PathObject::new(&p);
                    files.insert(version, Box::new(object));
                }
            }
        }

        Ok(files.into_iter().collect())
    }
}

impl Resolver for Paths {
    fn resolve(
        &mut self,
        package: &RpRequiredPackage,
    ) -> Result<Vec<(Option<Version>, Box<Object>)>> {
        let mut files = Vec::new();
        let version_req = package.version_req.as_ref();

        for path in &self.paths {
            let mut path: PathBuf = path.to_owned();
            let mut it = package.package.parts.iter().peekable();

            while let Some(step) = it.next() {
                if it.peek().is_none() {
                    if path.is_dir() {
                        files.extend(self.find_versions(&path, step, version_req)?);
                    }

                    break;
                }

                path = path.join(step);
            }
        }

        Ok(files)
    }
}