Initial pass at VersionReq

This commit is contained in:
Carl Lerche 2014-05-10 14:16:12 -07:00
parent b8621c5042
commit 0f0158729e
3 changed files with 517 additions and 1 deletions

View File

@ -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/ $<

View 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;

View File

@ -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
*/
}