about summary refs log tree commit diff
diff options
context:
space:
mode:
authorZack Corr <zack@z0w0.me>2013-01-16 21:59:37 +1000
committerGraydon Hoare <graydon@mozilla.com>2013-02-15 18:04:10 -0800
commit226b61ba5f30e0ecb0799626a010161f3ce0b72d (patch)
treeed15c6860e9e94096eb2078df4d692f4d88f6b12
parent71d34a8872491f37011aa14c866a95165fc45f99 (diff)
downloadrust-226b61ba5f30e0ecb0799626a010161f3ce0b72d.tar.gz
rust-226b61ba5f30e0ecb0799626a010161f3ce0b72d.zip
rustpkg: Add package script parsing
-rw-r--r--src/libcore/core.rc1
-rw-r--r--src/libcore/semver.rs227
-rw-r--r--src/librustpkg/api.rs6
-rw-r--r--src/librustpkg/rustpkg.rc170
-rw-r--r--src/librustpkg/util.rs52
5 files changed, 446 insertions, 10 deletions
diff --git a/src/libcore/core.rc b/src/libcore/core.rc
index 5b6c40e09ef..6169ac24c05 100644
--- a/src/libcore/core.rc
+++ b/src/libcore/core.rc
@@ -169,6 +169,7 @@ pub mod reflect;
 pub mod condition;
 pub mod logging;
 pub mod util;
+pub mod semver;
 
 
 /* Reexported core operators */
diff --git a/src/libcore/semver.rs b/src/libcore/semver.rs
new file mode 100644
index 00000000000..5a739772d1e
--- /dev/null
+++ b/src/libcore/semver.rs
@@ -0,0 +1,227 @@
+// Copyright 2012 The Rust Project Developers. See the COPYRIGHT
+// file at the top-level directory of this distribution and at
+// http://rust-lang.org/COPYRIGHT.
+//
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+//! Semver parsing and logic
+
+use io;
+use io::{ReaderUtil};
+use option::{Option, Some, None};
+use uint;
+use str;
+use to_str::ToStr;
+use char;
+
+pub struct Version {
+    major: uint,
+    minor: uint,
+    patch: uint,
+    tag: Option<~str>,
+}
+
+impl Version: ToStr {
+    #[inline(always)]
+    pure fn to_str() -> ~str {
+        let suffix = match copy self.tag {
+            Some(tag) => ~"-" + tag,
+            None => ~""
+        };
+
+        fmt!("%u.%u.%u%s", self.major, self.minor, self.patch, suffix)
+    }
+}
+
+fn read_whitespace(rdr: io::Reader, ch: char) -> char {
+    let mut nch = ch;
+
+    while char::is_whitespace(nch) {
+        nch = rdr.read_char();
+    }
+
+    nch
+}
+
+fn parse_reader(rdr: io::Reader) -> Option<(Version, char)> {
+    fn read_digits(rdr: io::Reader, ch: char) -> Option<(uint, char)> {
+        let mut buf = ~"";
+        let mut nch = ch;
+
+        while nch != -1 as char {
+            match nch {
+              '0' .. '9' => buf += str::from_char(nch),
+              _ => break
+            }
+
+            nch = rdr.read_char();
+        }
+
+        do uint::from_str(buf).chain_ref |&i| {
+            Some((i, nch))
+        }
+    }
+
+    fn read_tag(rdr: io::Reader) -> Option<(~str, char)> {
+        let mut ch = rdr.read_char();
+        let mut buf = ~"";
+
+        while ch != -1 as char {
+            match ch {
+                '0' .. '9' | 'A' .. 'Z' | 'a' .. 'z' | '-' => {
+                    buf += str::from_char(ch);
+                }
+                _ => break
+            }
+
+            ch = rdr.read_char();
+        }
+
+        if buf == ~"" { return None; }
+        else { Some((buf, ch)) }
+    }
+
+    let ch = read_whitespace(rdr, rdr.read_char());
+    let (major, ch) = match read_digits(rdr, ch) {
+        None => return None,
+        Some(item) => item
+    };
+
+    if ch != '.' { return None; }
+
+    let (minor, ch) = match read_digits(rdr, rdr.read_char()) {
+        None => return None,
+        Some(item) => item
+    };
+
+    if ch != '.' { return None; }
+
+    let (patch, ch) = match read_digits(rdr, rdr.read_char()) {
+        None => return None,
+        Some(item) => item
+    };
+    let (tag, ch) = if ch == '-' {
+        match read_tag(rdr) {
+            None => return None,
+            Some((tag, ch)) => (Some(tag), ch)
+        }
+    } else {
+        (None, ch)
+    };
+
+    Some((Version { major: major, minor: minor, patch: patch, tag: tag },
+          ch))
+}
+
+pub fn parse(s: ~str) -> Option<Version> {
+    do io::with_str_reader(s) |rdr| {
+        do parse_reader(rdr).chain_ref |&item| {
+            let (version, ch) = item;
+
+            if read_whitespace(rdr, ch) != -1 as char {
+                None
+            } else {
+                Some(version)
+            }
+        }
+    }
+}
+
+#[test]
+fn test_parse() {
+    assert parse("") == None;
+    assert parse("  ") == None;
+    assert parse("1") == None;
+    assert parse("1.2") == None;
+    assert parse("1.2") == None;
+    assert parse("1") == None;
+    assert parse("1.2") == None;
+    assert parse("1.2.3-") == None;
+    assert parse("a.b.c") == None;
+    assert parse("1.2.3 abc") == None;
+
+    assert parse("1.2.3") == Some({
+        major: 1u,
+        minor: 2u,
+        patch: 3u,
+        tag: None,
+    });
+    assert parse("  1.2.3  ") == Some({
+        major: 1u,
+        minor: 2u,
+        patch: 3u,
+        tag: None,
+    });
+    assert parse("1.2.3-alpha1") == Some({
+        major: 1u,
+        minor: 2u,
+        patch: 3u,
+        tag: Some("alpha1")
+    });
+    assert parse("  1.2.3-alpha1  ") == Some({
+        major: 1u,
+        minor: 2u,
+        patch: 3u,
+        tag: Some("alpha1")
+    });
+}
+
+#[test]
+fn test_eq() {
+    assert parse("1.2.3")        == parse("1.2.3");
+    assert parse("1.2.3-alpha1") == parse("1.2.3-alpha1");
+}
+
+#[test]
+fn test_ne() {
+    assert parse("0.0.0")       != parse("0.0.1");
+    assert parse("0.0.0")       != parse("0.1.0");
+    assert parse("0.0.0")       != parse("1.0.0");
+    assert parse("1.2.3-alpha") != parse("1.2.3-beta");
+}
+
+#[test]
+fn test_lt() {
+    assert parse("0.0.0")        < parse("1.2.3-alpha2");
+    assert parse("1.0.0")        < parse("1.2.3-alpha2");
+    assert parse("1.2.0")        < parse("1.2.3-alpha2");
+    assert parse("1.2.3")        < parse("1.2.3-alpha2");
+    assert parse("1.2.3-alpha1") < parse("1.2.3-alpha2");
+
+    assert !(parse("1.2.3-alpha2") < parse("1.2.3-alpha2"));
+}
+
+#[test]
+fn test_le() {
+    assert parse("0.0.0")        <= parse("1.2.3-alpha2");
+    assert parse("1.0.0")        <= parse("1.2.3-alpha2");
+    assert parse("1.2.0")        <= parse("1.2.3-alpha2");
+    assert parse("1.2.3")        <= parse("1.2.3-alpha2");
+    assert parse("1.2.3-alpha1") <= parse("1.2.3-alpha2");
+    assert parse("1.2.3-alpha2") <= parse("1.2.3-alpha2");
+}
+
+#[test]
+fn test_gt() {
+    assert parse("1.2.3-alpha2") > parse("0.0.0");
+    assert parse("1.2.3-alpha2") > parse("1.0.0");
+    assert parse("1.2.3-alpha2") > parse("1.2.0");
+    assert parse("1.2.3-alpha2") > parse("1.2.3");
+    assert parse("1.2.3-alpha2") > parse("1.2.3-alpha1");
+
+    assert !(parse("1.2.3-alpha2") > parse("1.2.3-alpha2"));
+}
+
+#[test]
+fn test_ge() {
+    assert parse("1.2.3-alpha2") >= parse("0.0.0");
+    assert parse("1.2.3-alpha2") >= parse("1.0.0");
+    assert parse("1.2.3-alpha2") >= parse("1.2.0");
+    assert parse("1.2.3-alpha2") >= parse("1.2.3");
+    assert parse("1.2.3-alpha2") >= parse("1.2.3-alpha1");
+    assert parse("1.2.3-alpha2") >= parse("1.2.3-alpha2");
+}
diff --git a/src/librustpkg/api.rs b/src/librustpkg/api.rs
index 10868c23636..6dd238b3d5c 100644
--- a/src/librustpkg/api.rs
+++ b/src/librustpkg/api.rs
@@ -9,7 +9,7 @@ pub struct Crate {
 pub impl Crate {
     fn flag(flag: ~str) -> Crate {
         Crate {
-            flags: vec::append(self.flags, flag),
+            flags: vec::append(copy self.flags, ~[flag]),
             .. copy self
         }
     }
@@ -18,3 +18,7 @@ pub impl Crate {
 pub fn build(_targets: ~[Crate]) {
     // TODO: magic
 }
+
+pub mod util {
+    
+}
diff --git a/src/librustpkg/rustpkg.rc b/src/librustpkg/rustpkg.rc
index a21bd2dc9ec..e847a8935f6 100644
--- a/src/librustpkg/rustpkg.rc
+++ b/src/librustpkg/rustpkg.rc
@@ -17,6 +17,8 @@
 
 #[crate_type = "lib"];
 #[no_core];
+#[allow(vecs_implicitly_copyable,
+        non_implicitly_copyable_typarams)];
 
 extern mod core(vers = "0.6");
 extern mod std(vers = "0.6");
@@ -24,26 +26,171 @@ extern mod rustc(vers = "0.6");
 extern mod syntax(vers = "0.6");
 
 use core::*;
+use io::{ReaderUtil, WriterUtil};
 use std::getopts;
-use getopts::{optflag, optopt, opt_present};
 use rustc::metadata::{filesearch};
+use syntax::{ast, codemap, parse, visit, attr};
+use semver::Version;
 
 mod api;
 mod usage;
 mod util;
 
-use util::*;
+struct PackageScript {
+    id: ~str,
+    name: ~str,
+    vers: Version
+}
+
+impl PackageScript {
+    static fn parse(parent: Path) -> PackageScript {
+        let script = parent.push(~"package.rs");
+
+        if !os::path_exists(&script) {
+            fail ~"no package.rs file";
+        }
+
+        let sess = parse::new_parse_sess(None);
+        let crate = parse::parse_crate_from_file(&script, ~[], sess);
+        let mut id = None;
+        let mut vers = None;
+
+        fn load_pkg_attr(mis: ~[@ast::meta_item]) -> (Option<~str>,
+                                                      Option<~str>) {
+            let mut id = None;
+            let mut vers = None;
+
+            for mis.each |a| {
+                match a.node {
+                    ast::meta_name_value(v, ast::spanned {
+                                                node: ast::lit_str(s),
+                                                span: _}) => {
+                        match v {
+                            ~"id" => id = Some(*s),
+                            ~"vers" => vers = Some(*s),
+                            _ => ()
+                        }
+                    }
+                    _ => {}
+                }
+            }
+
+            (id, vers)
+        }
+
+        for crate.node.attrs.each |a| {
+            match a.node.value.node {
+                ast::meta_list(v, mis) => {
+                    match v {
+                        ~"pkg" => {
+                            let (i, v) = load_pkg_attr(mis);
+
+                            id = i;
+                            vers = v;
+                        }
+                        _ => {}
+                    }
+                }
+                _ => {}
+            }
+        }
+
+        if id.is_none() || vers.is_none() {
+            fail ~"id or vers isn't defined in a pkg attribute in package.rs";
+        }
+
+        let id = id.get();
+        let vers = vers.get();
+
+        PackageScript {
+            id: id,
+            name: util::parse_id(id),
+            vers: util::parse_vers(vers)
+        }
+    }
+
+    fn hash() -> ~str {
+        let hasher = hash::default_state();
+
+        hasher.write_str(self.id + self.vers.to_str());
+
+        self.name + hasher.result_str() + self.vers.to_str()
+    }
+
+    fn work_dir() -> Path {
+        util::root().push(self.hash())
+    }
+}
+
+struct Ctx {
+    cmd: ~str,
+    args: ~[~str],
+    cfgs: ~[~str],
+    prefer: bool
+}
+
+impl Ctx {
+    fn run() {
+        match self.cmd {
+            ~"build" => self.build(),
+            ~"clean" => self.clean(),
+            ~"install" => self.install(),
+            ~"prefer" => self.prefer(),
+            ~"test" => self.test(),
+            ~"uninstall" => self.uninstall(),
+            ~"unprefer" => self.unprefer(),
+            _ => fail ~"reached an unhandled command"
+        }
+    }
+
+    fn build() {
+        let script = PackageScript::parse(os::getcwd());
+
+        io::println(fmt!("build: %s (v%s)", script.id, script.vers.to_str()));
+    }
+
+    fn clean() {
+
+    }
+
+    fn install() {
+
+    }
+
+    fn prefer() {
+
+    }
+
+    fn test() {
+
+    }
+
+    fn uninstall() {
+
+    }
+
+    fn unprefer() {
+
+    }
+}
 
 pub fn main() {
     let args = os::args();
-    let opts = ~[optflag(~"h"), optflag(~"help")];
+    let opts = ~[getopts::optflag(~"h"), getopts::optflag(~"help"),
+                 getopts::optmulti(~"c"), getopts::optmulti(~"cfg"),
+                 getopts::optmulti(~"p"), getopts::optmulti(~"prefer")];
     let matches = &match getopts::getopts(args, opts) {
         result::Ok(m) => m,
         result::Err(f) => {
             fail fmt!("%s", getopts::fail_str(f));
         }
     };
-    let help = opt_present(matches, ~"h") || opt_present(matches, ~"help");
+    let help = getopts::opt_present(matches, ~"h") ||
+               getopts::opt_present(matches, ~"help");
+    let cfgs = vec::append(getopts::opt_strs(matches, ~"cfg"),
+                           getopts::opt_strs(matches, ~"c"));
+    let prefer = getopts::opt_present(matches, ~"p") ||
+                 getopts::opt_present(matches, ~"prefer");
     let mut args = copy matches.free;
 
     args.shift();
@@ -52,12 +199,12 @@ pub fn main() {
         return usage::general();
     }
 
-    let cmd = copy args[0];
+    let cmd = args.shift();
 
-    if !is_cmd(cmd) {
+    if !util::is_cmd(cmd) {
         return usage::general();
     } else if help {
-        match cmd {
+        return match cmd {
             ~"build" => usage::build(),
             ~"clean" => usage::clean(),
             ~"install" => usage::install(),
@@ -66,10 +213,15 @@ pub fn main() {
             ~"uninstall" => usage::uninstall(),
             ~"unprefer" => usage::unprefer(),
             _ => usage::general()
-        }
+        };
     }
 
-    Ctx { cmd: cmd, args: args }
+    Ctx {
+        cmd: cmd,
+        args: args,
+        cfgs: cfgs,
+        prefer: prefer
+    }.run();
 }
 
 pub use Crate = api::Crate;
diff --git a/src/librustpkg/util.rs b/src/librustpkg/util.rs
index 304c4864d4c..0f2aea5a33b 100644
--- a/src/librustpkg/util.rs
+++ b/src/librustpkg/util.rs
@@ -1,6 +1,58 @@
+use core::*;
+use rustc::metadata::filesearch;
+use semver::Version;
+use std::net::url;
+
+pub fn root() -> Path {
+    match filesearch::get_rustpkg_root() {
+        result::Ok(path) => path,
+        result::Err(err) => fail err
+    }
+}
+
 pub fn is_cmd(cmd: ~str) -> bool {
     let cmds = &[~"build", ~"clean", ~"install", ~"prefer", ~"test",
                  ~"uninstall", ~"unprefer"];
 
     vec::contains(cmds, &cmd)
 }
+
+pub fn parse_id(id: ~str) -> ~str {
+    let parts = str::split_char(id, '.');
+
+    for parts.each |&part| {
+        for str::chars(part).each |&char| {
+            if char::is_whitespace(char) {
+                fail ~"could not parse id: contains whitespace";
+            } else if char::is_uppercase(char) {
+                fail ~"could not parse id: should be all lowercase";
+            }
+        }
+    }
+
+    parts.last()
+}
+
+pub fn parse_vers(vers: ~str) -> Version {
+    match semver::parse(vers) {
+        Some(vers) => vers,
+        None => fail ~"could not parse version: invalid"
+    }
+}
+
+#[test]
+fn test_is_cmd() {
+    assert is_cmd(~"build");
+    assert is_cmd(~"clean");
+    assert is_cmd(~"install");
+    assert is_cmd(~"prefer");
+    assert is_cmd(~"test");
+    assert is_cmd(~"uninstall");
+    assert is_cmd(~"unprefer");
+}
+
+#[test]
+fn test_parse_id() {
+    assert parse_id(~"org.mozilla.servo").get() == ~"servo";
+    assert parse_id(~"org. mozilla.servo 2131").is_err();
+}