From 139dba20ce2eb88db2fdb9cb19898cdf59843852 Mon Sep 17 00:00:00 2001 From: Ning Sun Date: Wed, 9 Mar 2016 12:59:22 +0800 Subject: [PATCH] (feat) initial commit --- .gitignore | 1 + Cargo.lock | 83 ++++++++++++++++++++++++++++++++++++++++++++++ Cargo.toml | 10 ++++++ src/cargo.rs | 6 ++++ src/cmd.rs | 19 +++++++++++ src/config.rs | 85 +++++++++++++++++++++++++++++++++++++++++++++++ src/error.rs | 20 +++++++++++ src/git.rs | 23 +++++++++++++ src/main.rs | 91 +++++++++++++++++++++++++++++++++++++++++++++++++++ 9 files changed, 338 insertions(+) create mode 100644 .gitignore create mode 100644 Cargo.lock create mode 100644 Cargo.toml create mode 100644 src/cargo.rs create mode 100644 src/cmd.rs create mode 100644 src/config.rs create mode 100644 src/error.rs create mode 100644 src/git.rs create mode 100644 src/main.rs diff --git a/.gitignore b/.gitignore new file mode 100644 index 000000000..eb5a316cb --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +target diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 000000000..6d92f8899 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,83 @@ +[root] +name = "cargo-release" +version = "0.1.0-pre" +dependencies = [ + "quick-error 0.1.4 (registry+git://crates.mirrors.ustc.edu.cn/index)", + "regex 0.1.55 (registry+git://crates.mirrors.ustc.edu.cn/index)", + "semver 0.2.3 (registry+git://crates.mirrors.ustc.edu.cn/index)", + "toml 0.1.27 (registry+git://crates.mirrors.ustc.edu.cn/index)", +] + +[[package]] +name = "aho-corasick" +version = "0.5.1" +source = "registry+git://crates.mirrors.ustc.edu.cn/index" +dependencies = [ + "memchr 0.1.10 (registry+git://crates.mirrors.ustc.edu.cn/index)", +] + +[[package]] +name = "libc" +version = "0.2.8" +source = "registry+git://crates.mirrors.ustc.edu.cn/index" + +[[package]] +name = "memchr" +version = "0.1.10" +source = "registry+git://crates.mirrors.ustc.edu.cn/index" +dependencies = [ + "libc 0.2.8 (registry+git://crates.mirrors.ustc.edu.cn/index)", +] + +[[package]] +name = "nom" +version = "1.2.1" +source = "registry+git://crates.mirrors.ustc.edu.cn/index" + +[[package]] +name = "quick-error" +version = "0.1.4" +source = "registry+git://crates.mirrors.ustc.edu.cn/index" + +[[package]] +name = "regex" +version = "0.1.55" +source = "registry+git://crates.mirrors.ustc.edu.cn/index" +dependencies = [ + "aho-corasick 0.5.1 (registry+git://crates.mirrors.ustc.edu.cn/index)", + "memchr 0.1.10 (registry+git://crates.mirrors.ustc.edu.cn/index)", + "regex-syntax 0.2.5 (registry+git://crates.mirrors.ustc.edu.cn/index)", + "utf8-ranges 0.1.3 (registry+git://crates.mirrors.ustc.edu.cn/index)", +] + +[[package]] +name = "regex-syntax" +version = "0.2.5" +source = "registry+git://crates.mirrors.ustc.edu.cn/index" + +[[package]] +name = "rustc-serialize" +version = "0.3.18" +source = "registry+git://crates.mirrors.ustc.edu.cn/index" + +[[package]] +name = "semver" +version = "0.2.3" +source = "registry+git://crates.mirrors.ustc.edu.cn/index" +dependencies = [ + "nom 1.2.1 (registry+git://crates.mirrors.ustc.edu.cn/index)", +] + +[[package]] +name = "toml" +version = "0.1.27" +source = "registry+git://crates.mirrors.ustc.edu.cn/index" +dependencies = [ + "rustc-serialize 0.3.18 (registry+git://crates.mirrors.ustc.edu.cn/index)", +] + +[[package]] +name = "utf8-ranges" +version = "0.1.3" +source = "registry+git://crates.mirrors.ustc.edu.cn/index" + diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 000000000..c33ec2139 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "cargo-release" +version = "0.1.0-pre" +authors = ["Ning Sun "] + +[dependencies] +toml = "0.1.27" +semver = "0.2.3" +quick-error = "0.1.3" +regex = "0.1.55" diff --git a/src/cargo.rs b/src/cargo.rs new file mode 100644 index 000000000..5aba5eaa3 --- /dev/null +++ b/src/cargo.rs @@ -0,0 +1,6 @@ +use cmd::call; +use error::FatalError; + +pub fn publish() -> Result { + call(vec!["cargo", "publish"]) +} diff --git a/src/cmd.rs b/src/cmd.rs new file mode 100644 index 000000000..8779bd6fd --- /dev/null +++ b/src/cmd.rs @@ -0,0 +1,19 @@ +use std::process::{Command}; + +use error::FatalError; + +pub fn call(command: Vec<&str>) -> Result { + let mut iter = command.iter(); + let cmd_name = iter.next().unwrap(); + + let mut cmd = Command::new(cmd_name); + + for arg in iter { + cmd.arg(arg); + } + + let mut child = try!(cmd.spawn().map_err(FatalError::from)); + let result = try!(child.wait().map_err(FatalError::from)); + + Ok(result.success()) +} diff --git a/src/config.rs b/src/config.rs new file mode 100644 index 000000000..0607cc05d --- /dev/null +++ b/src/config.rs @@ -0,0 +1,85 @@ +use std::io::prelude::*; +use std::io; +use std::io::BufReader; +use std::fs::{self, File}; +use std::path::Path; + +use toml::{Parser, Value, Table}; +use semver::Version; +use regex::Regex; + +use error::FatalError; + +fn load_from_file(path: &Path) -> io::Result { + let mut file = try!(File::open(path)); + let mut s = String::new(); + try!(file.read_to_string(&mut s)); + Ok(s) +} + +fn save_to_file(path: &Path, content: &str) -> io::Result<()> { + let mut file = try!(File::create(path)); + try!(file.write_all(&content.as_bytes())); + Ok(()) +} + +pub fn parse_cargo_config() -> Result { + let cargo_file_path = Path::new("Cargo.toml"); + + let cargo_file_content = try!(load_from_file(&cargo_file_path) + .map_err(FatalError::from)); + + let mut parser = Parser::new(&cargo_file_content); + + match parser.parse() { + Some(toml) => Ok(toml), + None => Err(FatalError::InvalidCargoFileFormat) + } +} + +pub fn save_cargo_config(config: &Table) -> Result<(), FatalError> { + let cargo_file_path = Path::new("Cargo.toml"); + + let serialized_data = format!("{}", Value::Table(config.clone())); + + try!(save_to_file(&cargo_file_path, &serialized_data).map_err(FatalError::from)); + Ok(()) +} + +pub fn rewrite_cargo_version(version: &str) -> Result<(), FatalError> { + { + let section_matcher = Regex::new("^\\[.+\\]").unwrap(); + let file_in = try!(File::open("Cargo.toml").map_err(FatalError::from)); + let mut bufreader = BufReader::new(file_in); + let mut line = String::new(); + + let mut file_out = try!(File::create("Cargo.toml.work").map_err(FatalError::from)); + let mut in_package = false; + + loop { + let b = try!(bufreader.read_line(&mut line).map_err(FatalError::from)); + if b <= 0 { + break; + } + + if section_matcher.is_match(&line) { + in_package = line.trim() == "[package]"; + } + + if in_package && line.starts_with("version") { + line = format!("version = \"{}\"\n", version); + } + + try!(file_out.write_all(line.as_bytes()).map_err(FatalError::from)); + line.clear(); + } + } + + try!(fs::rename("Cargo.toml.work", "Cargo.toml")); + + Ok(()) +} + +pub fn parse_version(version: &str) -> Result { + Version::parse(version).map_err(|e| FatalError::from(e)) +} diff --git a/src/error.rs b/src/error.rs new file mode 100644 index 000000000..9c59e34ae --- /dev/null +++ b/src/error.rs @@ -0,0 +1,20 @@ +use std::io::{Error as IOError}; +use semver::SemVerError; + +quick_error! { + #[derive(Debug)] + pub enum FatalError { + IOError(err: IOError) { + from() + cause(err) + } + InvalidCargoFileFormat { + display("Invalid cargo file format") + description("Invalid cargo file format") + } + SemVerError(err: SemVerError) { + from() + cause(err) + } + } +} diff --git a/src/git.rs b/src/git.rs new file mode 100644 index 000000000..fc455fdea --- /dev/null +++ b/src/git.rs @@ -0,0 +1,23 @@ +use std::process::{Command}; + +use cmd::call; +use error::FatalError; + +pub fn status() -> Result { + let output = try!(Command::new("git") + .arg("diff").arg("--exit-code") + .output().map_err(FatalError::from)); + Ok(output.status.success()) +} + +pub fn commit_all(msg: &str) -> Result { + call(vec!["git", "commit", "-am", msg]) +} + +pub fn tag(name: &str) -> Result { + call(vec!["git", "tag", name]) +} + +pub fn push() -> Result { + call(vec!["git", "push", "--follow-tags"]) +} diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 000000000..f449a1abe --- /dev/null +++ b/src/main.rs @@ -0,0 +1,91 @@ +#![allow(dead_code)] + +#[macro_use] extern crate quick_error; +extern crate regex; +extern crate toml; +extern crate semver; + +use std::process::exit; + +use semver::Identifier; + +mod config; +mod error; +mod cmd; +mod git; +mod cargo; + +fn execute() -> Result { + + // STEP 0: Check if working directory is clean + let git_clean = try!(git::status()); + if git_clean { + println!("Working directory is clean"); + } else { + println!("Uncommitted changes detected, please commit before release"); + return Ok(128); + } + + // STEP 1: Read version from Cargo.toml and remove + let result = try!(config::parse_cargo_config()); + + let mut version = result.get("package") + .and_then(|f| f.as_table()) + .and_then(|f| f.get("version")) + .and_then(|f| f.as_str()) + .and_then(|f| config::parse_version(f).ok()) + .unwrap(); + + // STEP 2: Remove pre extension, save and commit + if version.is_prerelease() { + version.pre.clear(); + let new_version_string = version.to_string(); + try!(config::rewrite_cargo_version(&new_version_string)); + + let commit_msg = format!("(cargo-release) version {}", new_version_string); + if !try!(git::commit_all(&commit_msg)) { + // commit failed, abort release + return Ok(128); + } + } + + // STEP 3: Tag + let current_version = version.to_string(); + if !try!(git::tag(¤t_version)) { + // tag failed, abort release + return Ok(128); + } + + // STEP 4: cargo publish + if !try!(cargo::publish()) { + return Ok(128); + } + + // STEP 5: bump version + version.increment_patch(); + version.pre.push(Identifier::AlphaNumeric("pre".to_owned())); + println!("Starting next development cycle {}", version); + let updated_version_string = version.to_string(); + try!(config::rewrite_cargo_version(&updated_version_string)); + let commit_msg = format!("(cargo-release) start next development cycle {}", + updated_version_string); + if !try!(git::commit_all(&commit_msg)) { + return Ok(128); + } + + // STEP 6: git push + if !try!(git::push()) { + return Ok(128); + } + Ok(0) +} + +fn main() { + match execute() { + Ok(code) => exit(code), + Err(e) => { + println!("Fatal: {}", e); + exit(128); + } + } +}