Skip to content

Commit

Permalink
(feat) initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
sunng87 committed Mar 9, 2016
0 parents commit 139dba2
Show file tree
Hide file tree
Showing 9 changed files with 338 additions and 0 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
target
83 changes: 83 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 10 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
[package]
name = "cargo-release"
version = "0.1.0-pre"
authors = ["Ning Sun <[email protected]>"]

[dependencies]
toml = "0.1.27"
semver = "0.2.3"
quick-error = "0.1.3"
regex = "0.1.55"
6 changes: 6 additions & 0 deletions src/cargo.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
use cmd::call;
use error::FatalError;

pub fn publish() -> Result<bool, FatalError> {
call(vec!["cargo", "publish"])
}
19 changes: 19 additions & 0 deletions src/cmd.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
use std::process::{Command};

use error::FatalError;

pub fn call(command: Vec<&str>) -> Result<bool, FatalError> {
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())
}
85 changes: 85 additions & 0 deletions src/config.rs
Original file line number Diff line number Diff line change
@@ -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<String> {
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<Table, FatalError> {
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, FatalError> {
Version::parse(version).map_err(|e| FatalError::from(e))
}
20 changes: 20 additions & 0 deletions src/error.rs
Original file line number Diff line number Diff line change
@@ -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)
}
}
}
23 changes: 23 additions & 0 deletions src/git.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
use std::process::{Command};

use cmd::call;
use error::FatalError;

pub fn status() -> Result<bool, FatalError> {
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<bool, FatalError> {
call(vec!["git", "commit", "-am", msg])
}

pub fn tag(name: &str) -> Result<bool, FatalError> {
call(vec!["git", "tag", name])
}

pub fn push() -> Result<bool, FatalError> {
call(vec!["git", "push", "--follow-tags"])
}
91 changes: 91 additions & 0 deletions src/main.rs
Original file line number Diff line number Diff line change
@@ -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<i32, error::FatalError> {

// 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(&current_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);
}
}
}

0 comments on commit 139dba2

Please sign in to comment.