From 226b61ba5f30e0ecb0799626a010161f3ce0b72d Mon Sep 17 00:00:00 2001 From: Zack Corr Date: Wed, 16 Jan 2013 21:59:37 +1000 Subject: [PATCH] rustpkg: Add package script parsing --- src/libcore/core.rc | 1 + src/libcore/semver.rs | 227 ++++++++++++++++++++++++++++++++++++++ src/librustpkg/api.rs | 6 +- src/librustpkg/rustpkg.rc | 170 ++++++++++++++++++++++++++-- src/librustpkg/util.rs | 52 +++++++++ 5 files changed, 446 insertions(+), 10 deletions(-) create mode 100644 src/libcore/semver.rs 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 or the MIT license +// , 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 { + 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(); +}