]> git.proxmox.com Git - cargo.git/commitdiff
Initial pass at VersionReq
authorCarl Lerche <me@carllerche.com>
Sat, 10 May 2014 21:16:12 +0000 (14:16 -0700)
committerCarl Lerche <me@carllerche.com>
Sat, 10 May 2014 21:16:12 +0000 (14:16 -0700)
Makefile
src/cargo/core/mod.rs
src/cargo/core/version_req.rs [new file with mode: 0644]

index accb5b87c905612e5669e0065e5899db929c0499..a39ab6b0c6b419b34c173c1e3a4114eb598d4550 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -57,7 +57,7 @@ target/tests/test-unit: $(HAMCREST) $(SRC) $(HAMMER)
        $(RUSTC) --test $(RUSTC_FLAGS) $(TEST_DEPS) -o $@ src/cargo/mod.rs
 
 test-unit: target/tests/test-unit
-       target/tests/test-unit
+       target/tests/test-unit $(only)
 
 test-integration: target/tests/test-integration
        RUST_TEST_TASKS=1 CARGO_BIN_PATH=$(PWD)/target/ $<
index 57179048651c3a8c8218d096eccd8dfe5e4cc49c..726f9c4b03c2d69c0662990bb58674d20278e2bf 100644 (file)
@@ -26,6 +26,7 @@ pub use self::summary::{
 };
 
 pub use self::dependency::Dependency;
+pub use self::version_req::VersionReq;
 
 pub mod errors;
 pub mod namever;
@@ -36,3 +37,4 @@ pub mod manifest;
 pub mod resolver;
 pub mod summary;
 mod registry;
+mod version_req;
diff --git a/src/cargo/core/version_req.rs b/src/cargo/core/version_req.rs
new file mode 100644 (file)
index 0000000..8d984da
--- /dev/null
@@ -0,0 +1,514 @@
+// Work in progress
+#![allow(dead_code)]
+
+use std::fmt;
+use std::str::CharOffsets;
+use semver::Version;
+use util::{other_error,CargoResult};
+
+pub struct VersionReq {
+    predicates: Vec<Predicate>
+}
+
+#[deriving(Eq)]
+enum Op {
+    Ex,   // Exact
+    Gt,   // Greater than
+    GtEq, // Greater than or equal to
+    Lt,   // Less than
+    LtEq  // Less than or equal to
+}
+
+struct Predicate {
+    op: Op,
+    major: uint,
+    minor: Option<uint>,
+    patch: Option<uint>
+}
+
+struct PredBuilder {
+    op: Option<Op>,
+    major: Option<uint>,
+    minor: Option<uint>,
+    patch: Option<uint>
+}
+
+
+impl VersionReq {
+    pub fn parse(input: &str) -> CargoResult<VersionReq> {
+        let mut lexer = Lexer::new(input);
+        let mut builder = PredBuilder::new();
+        let mut predicates = Vec::new();
+
+        for token in lexer {
+            match token {
+                Sigil(x) => try!(builder.set_sigil(x)),
+                AlphaNum(x) => try!(builder.set_version_part(x)),
+                Dot => (), // Nothing to do for now
+                _ => unimplemented!()
+            }
+        }
+
+        if lexer.is_error() {
+            return Err(other_error("invalid version requirement"));
+        }
+
+        predicates.push(try!(builder.build()));
+
+        Ok(VersionReq { predicates: predicates })
+    }
+
+    pub fn matches(&self, version: &Version) -> bool {
+        self.predicates.iter().all(|p| p.matches(version))
+    }
+}
+
+impl Predicate {
+    pub fn matches(&self, ver: &Version) -> bool {
+        match self.op {
+            Ex => self.is_exact(ver),
+            Gt => self.is_greater(ver),
+            GtEq => self.is_exact(ver) || self.is_greater(ver),
+            _ => false // not implemented
+        }
+    }
+
+    fn is_exact(&self, ver: &Version) -> bool {
+        if self.major != ver.major {
+            return false;
+        }
+
+        match self.minor {
+            Some(minor) => {
+                if minor != ver.minor {
+                    return false;
+                }
+            }
+            None => return true
+        }
+
+        match self.patch {
+            Some(patch) => {
+                if patch != ver.patch {
+                    return false;
+                }
+            }
+            None => return true
+        }
+
+        true
+    }
+
+    fn is_greater(self, ver: &Version) -> bool {
+        if self.major != ver.major {
+            return self.major > ver.major;
+        }
+
+        match self.minor {
+            Some(minor) => {
+                if minor != ver.minor {
+                    return minor > ver.minor
+                }
+            }
+            None => return false
+        }
+
+        match self.patch {
+            Some(patch) => {
+                if patch != ver.patch {
+                    return patch > ver.patch
+                }
+            }
+
+            None => return false
+        }
+
+        false
+    }
+
+    fn get_minor(&self) -> uint {
+        self.minor.unwrap()
+    }
+
+    fn get_patch(&self) -> uint {
+        self.patch.unwrap()
+    }
+}
+
+impl PredBuilder {
+    fn new() -> PredBuilder {
+        PredBuilder {
+            op: None,
+            major: None,
+            minor: None,
+            patch: None
+        }
+    }
+
+    fn set_sigil(&mut self, sigil: &str) -> CargoResult<()> {
+        if self.op.is_some() {
+            return Err(other_error("op already set"));
+        }
+
+        match Op::from_sigil(sigil) {
+            Some(op) => self.op = Some(op),
+            _ => return Err(other_error("invalid sigil"))
+        }
+
+        Ok(())
+    }
+
+    fn set_version_part(&mut self, part: &str) -> CargoResult<()> {
+        if self.op.is_none() {
+            // If no op is specified, then the predicate is an exact match on the version
+            self.op = Some(Ex);
+        }
+
+        if self.major.is_none() {
+            self.major = Some(try!(parse_version_part(part)));
+        }
+        else if self.minor.is_none() {
+            self.minor = Some(try!(parse_version_part(part)));
+        }
+        else if self.patch.is_none() {
+            self.patch = Some(try!(parse_version_part(part)));
+        }
+
+        Ok(())
+    }
+
+    /**
+     * Validates that a version predicate can be created given the present
+     * information.
+     */
+    fn build(&self) -> CargoResult<Predicate> {
+        let op = match self.op {
+            Some(x) => x,
+            None => return Err(other_error("op required"))
+        };
+
+        let major = match self.major {
+            Some(x) => x,
+            None => return Err(other_error("major version required"))
+        };
+
+        Ok(Predicate {
+            op: op,
+            major: major,
+            minor: self.minor,
+            patch: self.patch
+        })
+    }
+}
+
+struct Lexer<'a> {
+    c: char,
+    idx: uint,
+    iter: CharOffsets<'a>,
+    mark: Option<uint>,
+    input: &'a str,
+    state: LexState
+}
+
+#[deriving(Show,Eq)]
+enum LexState {
+    LexInit,
+    LexStart,
+    LexAlphaNum,
+    LexSigil,
+    LexErr,
+    LexWin
+}
+
+#[deriving(Show)]
+enum Token<'a> {
+    Sigil(&'a str),
+    AlphaNum(&'a str),
+    Comma,
+    Dot
+}
+
+impl<'a> Lexer<'a> {
+    fn new(input: &'a str) -> Lexer<'a> {
+        Lexer {
+            c: '\0',
+            idx: 0,
+            iter: input.char_indices(),
+            mark: None,
+            input: input,
+            state: LexInit
+        }
+    }
+
+    fn is_marked(&self) -> bool {
+        self.mark.is_some()
+    }
+
+    fn is_success(&self) -> bool {
+        self.state == LexWin
+    }
+
+    fn is_error(&self) -> bool {
+        self.state == LexErr
+    }
+
+    fn mark(&mut self, at: uint) {
+        self.mark = Some(at)
+    }
+
+    fn flush(&mut self, to: uint, kind: LexState) -> Option<Token<'a>> {
+        match self.mark {
+            Some(mark) => {
+                if to <= mark {
+                    return None;
+                }
+
+                let s = self.input.slice(mark, to);
+
+                self.mark = None;
+
+                match kind {
+                    LexAlphaNum => Some(AlphaNum(s)),
+                    LexSigil => Some(Sigil(s)),
+                    _ => None // bug
+                }
+            }
+            None => None
+        }
+    }
+}
+
+impl<'a> Iterator<Token<'a>> for Lexer<'a> {
+    fn next(&mut self) -> Option<Token<'a>> {
+        let mut c;
+        let mut idx = 0;
+
+        macro_rules! next(
+            () => (
+                match self.iter.next() {
+                    Some((n_idx, n_char)) => {
+                        c = n_char;
+                        idx = n_idx;
+                    }
+                    _ => return self.flush(idx + 1, self.state)
+                }
+            ))
+
+        macro_rules! flush(
+            ($s:expr) => ({
+                self.c = c;
+                self.idx = idx;
+                self.flush(idx, $s)
+            }))
+
+
+        if self.state == LexInit {
+            self.state = LexStart;
+            next!();
+        }
+        else {
+            c = self.c;
+            idx = self.idx;
+        }
+
+        loop {
+            match self.state {
+                LexStart => {
+                    if c.is_whitespace() {
+                        next!(); // Ignore
+                    }
+                    else if c.is_alphanumeric() {
+                        self.mark(idx);
+                        self.state = LexAlphaNum;
+                        next!();
+                    }
+                    else if is_sigil(c) {
+                        self.mark(idx);
+                        self.state = LexSigil;
+                        next!();
+                    }
+                    else if c == '.' {
+                        self.state = LexInit;
+                        return Some(Dot);
+                    }
+                    else if c == ',' {
+                        self.state = LexInit;
+                        return Some(Comma);
+                    }
+                    else {
+                        self.state = LexErr;
+                        return None;
+                    }
+                }
+                LexAlphaNum => {
+                    if c.is_alphanumeric() {
+                        next!();
+                    }
+                    else {
+                        self.state = LexStart;
+                        return flush!(LexAlphaNum);
+                    }
+                }
+                LexSigil => {
+                    if is_sigil(c) {
+                        next!();
+                    }
+                    else {
+                        self.state = LexStart;
+                        return flush!(LexSigil);
+                    }
+                }
+                LexErr | LexWin => return None,
+                LexInit => return None // bug
+            }
+        }
+    }
+}
+
+impl Op {
+    fn from_sigil(sigil: &str) -> Option<Op> {
+        match sigil {
+            "=" => Some(Ex),
+            ">" => Some(Gt),
+            ">=" => Some(GtEq),
+            "<" => Some(Lt),
+            "<=" => Some(LtEq),
+            _ => None
+        }
+    }
+}
+
+fn parse_version_part(s: &str) -> CargoResult<uint> {
+    let mut ret = 0;
+
+    for c in s.chars() {
+        let n = (c as uint) - ('0' as uint);
+
+        if n > 9 {
+            return Err(other_error("version components must be numeric"));
+        }
+
+        ret *= 10;
+        ret +=  n;
+    }
+
+    Ok(ret)
+}
+
+fn is_sigil(c: char) -> bool {
+    match c {
+        '>' | '<' | '=' | '~' | '^' => true,
+        _ => false
+    }
+}
+
+impl fmt::Show for VersionReq {
+    fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
+        if self.predicates.is_empty() {
+            try!(write!(fmt.buf, "*"));
+        }
+        else {
+            for (i, ref pred) in self.predicates.iter().enumerate() {
+                if i == 0 {
+                    try!(write!(fmt.buf, "{}", pred));
+                }
+                else {
+                    try!(write!(fmt.buf, ", {}", pred));
+                }
+            }
+        }
+
+        Ok(())
+    }
+}
+
+impl fmt::Show for Predicate {
+    fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
+        try!(write!(fmt.buf, "{} {}", self.op, self.major));
+
+        match self.minor {
+            Some(v) => try!(write!(fmt.buf, ".{}", v)),
+            None => ()
+        }
+
+        match self.patch {
+            Some(v) => try!(write!(fmt.buf, ".{}", v)),
+            None => ()
+        }
+
+        Ok(())
+    }
+}
+
+impl fmt::Show for Op {
+    fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
+        match *self {
+            Ex => try!(write!(fmt.buf, "=")),
+            Gt => try!(write!(fmt.buf, ">")),
+            GtEq => try!(write!(fmt.buf, ">=")),
+            Lt => try!(write!(fmt.buf, "<")),
+            LtEq => try!(write!(fmt.buf, "<="))
+        }
+        Ok(())
+    }
+}
+
+#[cfg(test)]
+mod test {
+    use super::VersionReq;
+    use semver;
+
+    fn req(s: &str) -> VersionReq {
+        VersionReq::parse(s).unwrap()
+    }
+
+    fn version(s: &str) -> semver::Version {
+        match semver::parse(s) {
+            Some(v) => v,
+            None => fail!("`{}` is not a valid version", s)
+        }
+    }
+
+    fn assert_match(req: &VersionReq, vers: &[&str]) {
+        for ver in vers.iter() {
+            assert!(req.matches(&version(*ver)), "did not match {}", ver);
+        }
+    }
+
+    fn assert_not_match(req: &VersionReq, vers: &[&str]) {
+        for ver in vers.iter() {
+            assert!(!req.matches(&version(*ver)), "matched {}", ver);
+        }
+    }
+
+    #[test]
+    pub fn test_parsing_exact() {
+        let r = req("1.0.0");
+
+        assert!(r.to_str() == "= 1.0.0".to_owned());
+
+        assert_match(&r, ["1.0.0"]);
+        assert_not_match(&r, ["1.0.1", "0.9.9", "0.10.0", "0.1.0"]);
+
+        let r = req("0.9.0");
+
+        assert!(r.to_str() == "= 0.9.0".to_owned());
+
+        assert_match(&r, ["0.9.0"]);
+        assert_not_match(&r, ["0.9.1", "1.9.0", "0.0.9"]);
+    }
+
+    #[test]
+    pub fn test_parsing_greater_than() {
+        let r = req(">= 1.0.0");
+
+        assert!(r.to_str() == ">= 1.0.0".to_owned());
+
+        assert_match(&r, ["1.0.0"]);
+    }
+
+    /* TODO:
+     * - Test parse errors
+     * - Handle pre releases
+     */
+}