rustpkg: Add package script parsing

This commit is contained in:
Zack Corr 2013-01-16 21:59:37 +10:00 committed by Graydon Hoare
parent 71d34a8872
commit 226b61ba5f
5 changed files with 446 additions and 10 deletions

View File

@ -169,6 +169,7 @@ pub mod reflect;
pub mod condition;
pub mod logging;
pub mod util;
pub mod semver;
/* Reexported core operators */

227
src/libcore/semver.rs Normal file
View File

@ -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");
}

View File

@ -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 {
}

View File

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

View File

@ -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();
}