Skip to content

Commit

Permalink
feat(config): support .clog.toml configuration file
Browse files Browse the repository at this point in the history
  • Loading branch information
kbknapp committed Apr 26, 2015
1 parent 44f7d49 commit bb3072b
Show file tree
Hide file tree
Showing 10 changed files with 218 additions and 134 deletions.
18 changes: 16 additions & 2 deletions Cargo.lock

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

1 change: 1 addition & 0 deletions Cargo.toml
Expand Up @@ -14,3 +14,4 @@ regex = "*"
time = "*"
clap = "*"
semver = "*"
toml = "*"
132 changes: 132 additions & 0 deletions src/clogconfig.rs
@@ -0,0 +1,132 @@
use std::fs::File;
use std::io::Read;
use std::path::Path;
use std::borrow::ToOwned;
use std::fmt::Display;
use std::env;

use clap::ArgMatches;
use toml::{Value, Parser};

use git;
use common::CommitType;
use CLOG_CONFIG_FILE;

pub struct ClogConfig {
pub grep: String,
pub format: String,
pub repo: String,
pub version: String,
pub subtitle: String,
pub from: String,
pub to: String,
}

pub type ConfigResult = Result<ClogConfig, Box<Display>>;

impl ClogConfig {
pub fn from_matches(matches: &ArgMatches) -> ConfigResult {
// compute version early, so we can exit on error
let version = {
// less typing later...
let (major, minor, patch) = (matches.is_present("major"), matches.is_present("minor"), matches.is_present("patch"));
if matches.is_present("setversion") {
matches.value_of("setversion").unwrap().to_owned()
} else if major || minor || patch {
match git::get_latest_tag_ver() {
Ok(ref mut v) => {
// if-else may be quicker, but it's longer mentally, and this isn't slow
match (major, minor, patch) {
(true,_,_) => { v.major += 1; v.minor = 0; v.patch = 0; },
(_,true,_) => { v.minor += 1; v.patch = 0; },
(_,_,true) => { v.patch += 1; },
_ => unreachable!()
}
format!("v{}", v)
},
Err(e) => {
return Err(Box::new(format!("Error: {}\n\n\tEnsure the tag format follows Semantic Versioning such as N.N.N\n\tor set the version manually with --setversion <version>" , e )));
}
}
} else {
// Use short hash
(&git::get_last_commit()[0..8]).to_owned()
}
};

let cwd = match env::current_dir() {
Ok(d) => d,
Err(e) => return Err(Box::new(e)),
};

let cfg_file = Path::new(&cwd).join(CLOG_CONFIG_FILE);
let mut toml_from_latest = None;
let mut toml_repo = None;
let mut toml_subtitle = None;

if let Ok(ref mut toml_f) = File::open(cfg_file){
let mut toml_s = String::with_capacity(100);

if let Err(e) = toml_f.read_to_string(&mut toml_s) {
return Err(Box::new(e))
}

toml_s.shrink_to_fit();

let mut toml = Parser::new(&toml_s[..]);

let toml_table = match toml.parse() {
Some(table) => table,
None => {
return Err(Box::new(format!("Error parsing file {}\n\nPlease check the format or specify the options manually", CLOG_CONFIG_FILE)))
}
};

let clog_table = match toml_table.get("clog") {
Some(table) => table,
None => {
return Err(Box::new(format!("Error parsing file {}\n\nPlease check the format or specify the options manually", CLOG_CONFIG_FILE)))
}
};

toml_from_latest = clog_table.lookup("from-latest-tag").unwrap_or(&Value::Boolean(false)).as_bool();
toml_repo = match clog_table.lookup("repository") {
Some(val) => Some(val.as_str().unwrap_or("").to_owned()),
None => Some("".to_owned())
};
toml_subtitle = match clog_table.lookup("subtitle") {
Some(val) => Some(val.as_str().unwrap_or("").to_owned()),
None => Some("".to_owned())
};
};

let from = if matches.is_present("from-latest-tag") || toml_from_latest.unwrap_or(false) {
git::get_latest_tag()
} else if let Some(from) = matches.value_of("from") {
from.to_owned()
} else {
"".to_owned()
};

let repo = match matches.value_of("repository") {
Some(repo) => repo.to_owned(),
None => toml_repo.unwrap_or("".to_owned())
};

let subtitle = match matches.value_of("subtitle") {
Some(title) => title.to_owned(),
None => toml_subtitle.unwrap_or("".to_owned())
};

Ok(ClogConfig{
grep: format!("{}BREAKING'", CommitType::all_aliases().iter().fold(String::new(),|acc, al| acc + &format!("^{}|", al)[..])),
format: "%H%n%s%n%b%n==END==".to_owned(),
repo: repo,
version: version,
subtitle: subtitle,
from: from,
to: matches.value_of("to").unwrap_or("HEAD").to_owned(),
})
}

}
9 changes: 5 additions & 4 deletions src/common.rs
Expand Up @@ -2,15 +2,16 @@ use std::fmt;
use std::collections::HashMap;

// Creates an enum where the poritions inside the '(' and ')' act as aliases for that
// commit type. The only one you MUST specify is 'Unknown ()'
// commit type. This macro auto-generates an "Unknown" variant for failures, no need to specify
//
// Later you can call CommitType::Fix.aliases() to get all the aliases as a Vec<'statci str>
// Later you can call CommitType::Fix.aliases() to get all the aliases as a Vec<'static str>
// or CommitType::all_aliases() to get a Vec<'static str> of all aliases
// This macro also implements std::str::FromStr to allow things like "feat".parse<CommitType>();
commit_type_enum!{
#[derive(Debug, PartialEq, Clone)]
pub enum CommitType {
Feature ( feat, ft ),
Fix ( fix, fx),
Unknown ()
Fix ( fix, fx )
}
}

Expand Down
4 changes: 0 additions & 4 deletions src/format_util.rs

This file was deleted.

33 changes: 13 additions & 20 deletions src/git.rs
@@ -1,20 +1,12 @@
use std::process::Command;
use common:: { LogEntry };
use common::CommitType;
use std::borrow::ToOwned;

use semver;

use clogconfig::ClogConfig;
use common::{ LogEntry, CommitType };

#[derive(Debug)]
pub struct LogReaderConfig {
pub grep: String,
pub format: String,
pub from: Option<String>,
pub to: String
}

pub fn get_latest_tag () -> String {
pub fn get_latest_tag() -> String {
let output = Command::new("git")
.arg("rev-list")
.arg("--tags")
Expand All @@ -25,17 +17,18 @@ pub fn get_latest_tag () -> String {
buf.trim_matches('\n').to_owned()
}

pub fn get_latest_tag_ver () -> Result<semver::Version, semver::ParseError> {
pub fn get_latest_tag_ver() -> Result<semver::Version, semver::ParseError> {
let output = Command::new("git")
.arg("describe")
.arg("--tags")
.arg("--abbrev=0")
.output().unwrap_or_else(|e| panic!("Failed to run 'git describe' with error: {}",e));

semver::Version::parse(&String::from_utf8_lossy(&output.stdout)[..])
let v_string = String::from_utf8_lossy(&output.stdout);
semver::Version::parse(&v_string[..].trim_left_matches(|c| c == 'v' || c == 'V'))
}

pub fn get_last_commit () -> String {
pub fn get_last_commit() -> String {
let output = Command::new("git")
.arg("rev-parse")
.arg("HEAD")
Expand All @@ -44,18 +37,18 @@ pub fn get_last_commit () -> String {
String::from_utf8_lossy(&output.stdout).into_owned()
}

pub fn get_log_entries (config:LogReaderConfig) -> Vec<LogEntry>{
pub fn get_log_entries(config: &ClogConfig) -> Vec<LogEntry>{

let range = match config.from {
Some(ref from) => format!("{}..{}", from, config.to),
None => "HEAD".to_owned()
let range = match &config.from[..] {
"" => "HEAD".to_owned(),
_ => format!("{}..{}", config.from, config.to)
};

let output = Command::new("git")
.arg("log")
.arg("-E")
.arg(&format!("--grep={}",config.grep))
.arg(&format!("--format={}", "%H%n%s%n%b%n==END=="))
.arg(&format!("--grep={}", config.grep))
.arg(&format!("--format={}", config.format))
.arg(&range)
.output().unwrap_or_else(|e| panic!("Failed to run 'git log' with error: {}", e));

Expand Down
31 changes: 13 additions & 18 deletions src/log_writer.rs
@@ -1,34 +1,29 @@
use std::collections::HashMap;
use std::io::{Write, Result};
use time;
use format_util;
use common::{ LogEntry };
use std::borrow::ToOwned;

pub struct LogWriter<'a, 'lwo> {
writer: &'a mut (Write + 'a),
options: LogWriterOptions<'lwo>
}
use time;

pub struct LogWriterOptions<'a> {
pub repository_link: &'a str,
pub version: String,
pub subtitle: String
}
use common::LogEntry;
use clogconfig::ClogConfig;

impl<'a, 'lwo> LogWriter<'a, 'lwo> {
pub struct LogWriter<'a, 'cc> {
writer: &'a mut (Write + 'a),
options: &'cc ClogConfig
}

fn commit_link(hash: &String, options: &LogWriterOptions) -> String {
let short_hash = format_util::get_short_hash(&hash[..]);
match &options.repository_link[..] {
impl<'a, 'cc> LogWriter<'a, 'cc> {
fn commit_link(hash: &String, options: &ClogConfig) -> String {
let short_hash = &hash[0..8];
match &options.repo[..] {
"" => format!("({})", short_hash),
link => format!("[{}]({}/commit/{})", short_hash, link, hash)

}
}

fn issue_link(&self, issue: &String) -> String {
match &self.options.repository_link[..] {
match &self.options.repo[..] {
"" => format!("(#{})", issue),
link => format!("[#{}]({}/issues/{})", issue, link, issue)
}
Expand Down Expand Up @@ -97,7 +92,7 @@ impl<'a, 'lwo> LogWriter<'a, 'lwo> {
write!(self.writer, "{}", content)
}

pub fn new<T>(writer: &'a mut T, options: LogWriterOptions<'lwo>) -> LogWriter<'a, 'lwo>
pub fn new<T>(writer: &'a mut T, options: &'cc ClogConfig) -> LogWriter<'a, 'cc>
where T: Write + Send {
LogWriter {
writer: writer,
Expand Down
12 changes: 5 additions & 7 deletions src/macros.rs
Expand Up @@ -4,24 +4,22 @@ macro_rules! regex(
);

// A macro creating an entry types, and their aliases
//
// This is a little hacky, because it expects an Unknown () variant
//
// TODO: de-dup with recursive calls
macro_rules! commit_type_enum {
(#[derive($($d:ident),+)] pub enum $e:ident { $($v:ident ( $($a:ident),* ) ),+ }) => {
#[derive($($d,)+)]
pub enum $e {
Unknown,
$($v,)+
}

impl $e {
#[allow(dead_code)]
pub fn aliases(&self) -> Vec<&'static str> {
pub fn aliases(&self) -> Option<Vec<&'static str>> {
match *self {
$($e::$v => vec![
$e::Unknown => None,
$($e::$v => Some(vec![
$( stringify!($a) ),*
],)+
]),)+
}
}
#[allow(dead_code)]
Expand Down

0 comments on commit bb3072b

Please sign in to comment.