<commit_before>/*!
This crate implements a simple arithmetic calculator, using the [`rust-scan`](https://github.com/DanielKeep/rust-scan.git) package to do the parsing.
*/
#![feature(phase)]

#[phase(plugin)] extern crate scan;
extern crate scan_util;

use std::collections::HashMap;
use std::num::Float;
use scan_util::{OtherScanError, ScanIoError};

use self::Stmt::{LetStmt, ExprStmt};
use self::Expr::{Add, Sub, Mul, Div, Var, Lit};

fn main() {
	let mut vars = HashMap::new();

	vars.insert("NaN".into_string(), Float::nan());
	vars.insert("inf".into_string(), Float::infinity());
	vars.insert("pi".into_string(), Float::pi());
	vars.insert("e".into_string(), Float::e());

	println!("Type `.help` or `.h` for brief instructions.");
	println!("Type `.quit` or `.q` to quit.");

    loop {
    	print!("> ");
    	let res = scanln! {
    		// Show brief help message.
    		".h" | ".help" => {
    			println!("Type expressions to be evaluated.");
    			println!("You can use +, -, *, /.");
    			println!("You can define variables like so: `let pi = 3.14159`.");
    			println!("Type .q or .quit to quit.");
    			continue;
    		},

    		// Exit.
    		".q" | ".quit" => {
    			println!("Bye!")
    			return;
    		},

    		// Fallback for unknown commands.
			"." cmd:&str, .._ => {
    			println!("error: unknown command `{}`", cmd);
    			println!("Try `.help`.");
    			continue;
    		},

    		// Ignore empty lines.
			_:() => continue,

    		// Otherwise, try to scan a statement.
    		stmt:Stmt => stmt,
    	};

    	let stmt = match res {
    		Err(ScanIoError(err)) => {
    			println!("io error: {}", err);
    			std::os::set_exit_status(1);
    			return;
    		},
    		Err(err @ OtherScanError(..)) => {
    			println!("error: {}", err);
    			continue;
    		},
    		Ok(stmt) => stmt,
    	};

    	match stmt {
    		LetStmt(name, expr) => {
    			let vars = &mut vars;
    			eval(expr, vars).map(|v| vars.insert(name.clone(), v));
    		},
    		ExprStmt(expr) => {
    			eval(expr, &vars).map(|v| println!("= {}", v));
    		},
    	}
    }
}

/**
Evaluates an expression, returning `Some(f64)` if there were no errors.

If something *does* go wrong, it will just print the error message and return `None`.  This really should be using `Result`, but I'm too lazy for that.
*/
fn eval(expr: Expr, vars: &HashMap<String, f64>) -> Option<f64> {
	match expr {
		Add(box lhs, box rhs) => eval(lhs, vars).zip(eval(rhs, vars)).map(|(l,r)| l+r),
		Sub(box lhs, box rhs) => eval(lhs, vars).zip(eval(rhs, vars)).map(|(l,r)| l-r),
		Mul(box lhs, box rhs) => eval(lhs, vars).zip(eval(rhs, vars)).map(|(l,r)| l*r),
		Div(box lhs, box rhs) => eval(lhs, vars).zip(eval(rhs, vars)).map(|(l,r)| l/r),
		Var(name) => {
			match vars.get(&name).cloned() {
				Some(v) => Some(v),
				None => {
					println!("error: undefined variable `{}`.", name);
					None
				}
			}
		},
		Lit(v) => Some(v),
	}
}

/*

The trick to using scan here is to map each grammar production to a type, then implement a scanner for that type.  As results are passed back up the call stack, we "unpack" the grammar production type into the actual semantic type.

This is actually something of a limitation of the `Scanner` trait; ideally, we could use UFCS (which isn't implemented yet) to have a capture like `lhs:MulExpr` which uses the scanner code defined for the `MulExpr` type, but which *results* in a `Expr` value.

The grammar we're parsing is given below.  Note that it's arranged this way to encode operator precedence directly into the grammar.

```{notrust}
<expr> := <add-expr>
<add-expr> := <mul-expr> ( ( "+" | "-" ) <add-expr> )?
<mul-expr> := <atom-expr> ( ( "*" | "/" ) <mul-expr> )?
<atom-expr> := "(" <add-expr> ")"
             | <literal>
             | <identifier>
```

*/

#[deriving(PartialEq, Show)]
enum Stmt {
	LetStmt(String, Expr),
	ExprStmt(Expr),
}

scanner! { Stmt,
	// We need to use a different tokeniser here to ensure that `name` can be any identifier.
	#[tokenizer="IdentsAndInts"]
	"let" name "=" expr => LetStmt(name, expr),

	expr => ExprStmt(expr),
}

#[deriving(PartialEq, Show)]
enum Expr {
	Add(Box<Expr>, Box<Expr>),
	Sub(Box<Expr>, Box<Expr>),
	Mul(Box<Expr>, Box<Expr>),
	Div(Box<Expr>, Box<Expr>),
	Var(String),
	Lit(f64),
}

scanner! { Expr,
	// Just forward on to the corresponding grammar production: AddExpr.
	expr:AddExpr => {
		let AddExpr(expr) = expr;
		expr
	}
}

struct AddExpr(Expr);

scanner! { AddExpr,
	// We *could* boil this down to a single arm, but it wouldn't be as neat.
	lhs:MulExpr "+" rhs:AddExpr => {
		// Again, we only need MulExpr and AddExpr in order to trigger the correct scanning rules.  What we actually *care* about are the Exprs inside.
		let (MulExpr(lhs), AddExpr(rhs)) = (lhs, rhs);
		AddExpr(Add(box lhs, box rhs))
	},
	lhs:MulExpr "-" rhs:AddExpr => {
		let (MulExpr(lhs), AddExpr(rhs)) = (lhs, rhs);
		AddExpr(Sub(box lhs, box rhs))
	},
	lhs:MulExpr => {
		let MulExpr(lhs) = lhs;
		AddExpr(lhs)
	}
}

struct MulExpr(Expr);

scanner! { MulExpr,
	lhs:AtomExpr "*" rhs:MulExpr => {
		let (AtomExpr(lhs), MulExpr(rhs)) = (lhs, rhs);
		MulExpr(Mul(box lhs, box rhs))
	},
	lhs:AtomExpr "/" rhs:MulExpr => {
		let (AtomExpr(lhs), MulExpr(rhs)) = (lhs, rhs);
		MulExpr(Div(box lhs, box rhs))
	},
	lhs:AtomExpr => {
		let AtomExpr(lhs) = lhs;
		MulExpr(lhs)
	}
}

struct AtomExpr(Expr);

scanner! { AtomExpr,
	"(" expr:AddExpr ")" => {
		let AddExpr(expr) = expr;
		AtomExpr(expr)
	},
	lit => AtomExpr(Lit(lit)),

	// Again, we use a different tokeniser.  Also note that it's not *entirely* correct.  This technically means we can have a variable called `*`, which means `***` is a perfectly valid expression.
	#[tokenizer="IdentsAndInts"]
	name => AtomExpr(Var(name)),
}

/**
This is just a helper trait used to simplify evaluating binary expressions.
*/
trait ZipOption<L> {
	fn zip<R>(self, other: Option<R>) -> Option<(L, R)>;
}

impl<L> ZipOption<L> for Option<L> {
	fn zip<R>(self, other: Option<R>) -> Option<(L, R)> {
		match (self, other) {
			(None, _) => None,
			(Some(_), None) => None,
			(Some(l), Some(r)) => Some((l, r))
		}
	}
}
<commit_msg>Updated to latest nightly.<commit_after>/*!
This crate implements a simple arithmetic calculator, using the [`rust-scan`](https://github.com/DanielKeep/rust-scan.git) package to do the parsing.
*/
#![feature(phase)]

#[phase(plugin)] extern crate scan;
extern crate scan_util;

use std::borrow::ToOwned;
use std::collections::HashMap;
use std::num::Float;
use scan_util::{OtherScanError, ScanIoError};

use self::Stmt::{LetStmt, ExprStmt};
use self::Expr::{Add, Sub, Mul, Div, Var, Lit};

fn main() {
	let mut vars = HashMap::new();

	vars.insert("NaN".to_owned(), Float::nan());
	vars.insert("inf".to_owned(), Float::infinity());
	vars.insert("pi".to_owned(), ::std::f64::consts::PI);
	vars.insert("e".to_owned(), ::std::f64::consts::E);

	println!("Type `.help` or `.h` for brief instructions.");
	println!("Type `.quit` or `.q` to quit.");

    loop {
    	print!("> ");
    	let res = scanln! {
    		// Show brief help message.
    		".h" | ".help" => {
    			println!("Type expressions to be evaluated.");
    			println!("You can use +, -, *, /.");
    			println!("You can define variables like so: `let pi = 3.14159`.");
    			println!("Type .q or .quit to quit.");
    			continue;
    		},

    		// Exit.
    		".q" | ".quit" => {
    			println!("Bye!");
    			return;
    		},

    		// Fallback for unknown commands.
			"." cmd:&str, .._ => {
    			println!("error: unknown command `{}`", cmd);
    			println!("Try `.help`.");
    			continue;
    		},

    		// Ignore empty lines.
			_:() => continue,

    		// Otherwise, try to scan a statement.
    		stmt:Stmt => stmt,
    	};

    	let stmt = match res {
    		Err(ScanIoError(err)) => {
    			println!("io error: {}", err);
    			std::os::set_exit_status(1);
    			return;
    		},
    		Err(err @ OtherScanError(..)) => {
    			println!("error: {}", err);
    			continue;
    		},
    		Ok(stmt) => stmt,
    	};

    	match stmt {
    		LetStmt(name, expr) => {
    			let vars = &mut vars;
    			eval(expr, vars).map(|v| vars.insert(name.clone(), v));
    		},
    		ExprStmt(expr) => {
    			eval(expr, &vars).map(|v| println!("= {}", v));
    		},
    	}
    }
}

/**
Evaluates an expression, returning `Some(f64)` if there were no errors.

If something *does* go wrong, it will just print the error message and return `None`.  This really should be using `Result`, but I'm too lazy for that.
*/
fn eval(expr: Expr, vars: &HashMap<String, f64>) -> Option<f64> {
	match expr {
		Add(box lhs, box rhs) => eval(lhs, vars).zip(eval(rhs, vars)).map(|(l,r)| l+r),
		Sub(box lhs, box rhs) => eval(lhs, vars).zip(eval(rhs, vars)).map(|(l,r)| l-r),
		Mul(box lhs, box rhs) => eval(lhs, vars).zip(eval(rhs, vars)).map(|(l,r)| l*r),
		Div(box lhs, box rhs) => eval(lhs, vars).zip(eval(rhs, vars)).map(|(l,r)| l/r),
		Var(name) => {
			match vars.get(&name).cloned() {
				Some(v) => Some(v),
				None => {
					println!("error: undefined variable `{}`.", name);
					None
				}
			}
		},
		Lit(v) => Some(v),
	}
}

/*

The trick to using scan here is to map each grammar production to a type, then implement a scanner for that type.  As results are passed back up the call stack, we "unpack" the grammar production type into the actual semantic type.

This is actually something of a limitation of the `Scanner` trait; ideally, we could use UFCS (which isn't implemented yet) to have a capture like `lhs:MulExpr` which uses the scanner code defined for the `MulExpr` type, but which *results* in a `Expr` value.

The grammar we're parsing is given below.  Note that it's arranged this way to encode operator precedence directly into the grammar.

```{notrust}
<expr> := <add-expr>
<add-expr> := <mul-expr> ( ( "+" | "-" ) <add-expr> )?
<mul-expr> := <atom-expr> ( ( "*" | "/" ) <mul-expr> )?
<atom-expr> := "(" <add-expr> ")"
             | <literal>
             | <identifier>
```

*/

#[derive(PartialEq, Show)]
enum Stmt {
	LetStmt(String, Expr),
	ExprStmt(Expr),
}

scanner! { Stmt,
	// We need to use a different tokeniser here to ensure that `name` can be any identifier.
	#[tokenizer="IdentsAndInts"]
	"let" name "=" expr => LetStmt(name, expr),

	expr => ExprStmt(expr),
}

#[derive(PartialEq, Show)]
enum Expr {
	Add(Box<Expr>, Box<Expr>),
	Sub(Box<Expr>, Box<Expr>),
	Mul(Box<Expr>, Box<Expr>),
	Div(Box<Expr>, Box<Expr>),
	Var(String),
	Lit(f64),
}

scanner! { Expr,
	// Just forward on to the corresponding grammar production: AddExpr.
	expr:AddExpr => {
		let AddExpr(expr) = expr;
		expr
	}
}

struct AddExpr(Expr);

scanner! { AddExpr,
	// We *could* boil this down to a single arm, but it wouldn't be as neat.
	lhs:MulExpr "+" rhs:AddExpr => {
		// Again, we only need MulExpr and AddExpr in order to trigger the correct scanning rules.  What we actually *care* about are the Exprs inside.
		let (MulExpr(lhs), AddExpr(rhs)) = (lhs, rhs);
		AddExpr(Add(box lhs, box rhs))
	},
	lhs:MulExpr "-" rhs:AddExpr => {
		let (MulExpr(lhs), AddExpr(rhs)) = (lhs, rhs);
		AddExpr(Sub(box lhs, box rhs))
	},
	lhs:MulExpr => {
		let MulExpr(lhs) = lhs;
		AddExpr(lhs)
	}
}

struct MulExpr(Expr);

scanner! { MulExpr,
	lhs:AtomExpr "*" rhs:MulExpr => {
		let (AtomExpr(lhs), MulExpr(rhs)) = (lhs, rhs);
		MulExpr(Mul(box lhs, box rhs))
	},
	lhs:AtomExpr "/" rhs:MulExpr => {
		let (AtomExpr(lhs), MulExpr(rhs)) = (lhs, rhs);
		MulExpr(Div(box lhs, box rhs))
	},
	lhs:AtomExpr => {
		let AtomExpr(lhs) = lhs;
		MulExpr(lhs)
	}
}

struct AtomExpr(Expr);

scanner! { AtomExpr,
	"(" expr:AddExpr ")" => {
		let AddExpr(expr) = expr;
		AtomExpr(expr)
	},
	lit => AtomExpr(Lit(lit)),

	// Again, we use a different tokeniser.  Also note that it's not *entirely* correct.  This technically means we can have a variable called `*`, which means `***` is a perfectly valid expression.
	#[tokenizer="IdentsAndInts"]
	name => AtomExpr(Var(name)),
}

/**
This is just a helper trait used to simplify evaluating binary expressions.
*/
trait ZipOption<L> {
	fn zip<R>(self, other: Option<R>) -> Option<(L, R)>;
}

impl<L> ZipOption<L> for Option<L> {
	fn zip<R>(self, other: Option<R>) -> Option<(L, R)> {
		match (self, other) {
			(None, _) => None,
			(Some(_), None) => None,
			(Some(l), Some(r)) => Some((l, r))
		}
	}
}
<|endoftext|>


<commit_before>/* Author : Thibault Barbie

A simple evolutionary algorithm written in Rust.

*/

extern crate rand;

use rand::Rng;
use std::collections::HashMap;

fn main() {
    let target : String ="METHINKS IT IS LIKE A WEASEL".to_string();
    println!("{}",target);

    let mut parent : String="".to_string();
    generate_first_sentence(&mut parent);
    println!("{}",parent);

    let nb_copy = 400;
    let mutation_rate : f64 = 0.05;
    let mut counter=0;

    while fitness(&target, &parent)!=0 {
        counter+=1;
        let mut sentences=HashMap::new();
        let mut f_min : u32 = 30;
        for i in 0..nb_copy {
            let sentence=mutate(&mut parent, mutation_rate);
            let f=fitness(&target, &sentence);
            sentences.insert(f,sentence);

            if f<f_min {f_min=f}
        }

        if fitness(&target, &parent)>f_min {
            match sentences.get(&f_min) {
                Some(s) => {
                    parent=s.clone();
                    println!("{} : {}",parent,counter);},
                None => println!("Error, fitness minimum but no sentence."),
            }
        }
    }
}

fn fitness(target : &String, sentence : &String) -> u32 {
    let mut fitness = 0;
    for (c1, c2) in target.chars().zip(sentence.chars())
    {
        if c1!=c2 { fitness+=1 };
    }
    fitness
}

fn mutate(sentence : &mut String, mutation_rate : f64) -> String {
    let mut rng=rand::thread_rng();

    let mut mutation : String ="".to_string();
    for c in sentence.chars() {
        match mutation_rate>rng.gen_range(0f64,1f64) {
            false => mutation.push(c),               // no mutation
            true  => mutation.push(random_char()),   // mutation
        }
    }
    mutation
}

fn generate_first_sentence (parent : &mut String) {
    for i in 0..28 {
        parent.push(random_char());
    }
}

fn random_char() -> char {
    match rand::thread_rng().gen_range('A' as u8, '\\' as u8) as char {
        '[' => ' ',
        c @ _ => c
    }
}
<commit_msg>fix two warnings<commit_after>/* Author : Thibault Barbie

A simple evolutionary algorithm written in Rust.

*/

extern crate rand;

use rand::Rng;
use std::collections::HashMap;

fn main() {
    let target : String ="METHINKS IT IS LIKE A WEASEL".to_string();
    println!("{}",target);

    let mut parent : String="".to_string();
    generate_first_sentence(&mut parent);
    println!("{}",parent);

    let nb_copy = 400;
    let mutation_rate : f64 = 0.05;
    let mut counter=0;

    while fitness(&target, &parent)!=0 {
        counter+=1;
        let mut sentences=HashMap::new();
        let mut f_min : u32 = 30;
        for _ in 0..nb_copy {
            let sentence=mutate(&mut parent, mutation_rate);
            let f=fitness(&target, &sentence);
            sentences.insert(f,sentence);

            if f<f_min {f_min=f}
        }

        if fitness(&target, &parent)>f_min {
            match sentences.get(&f_min) {
                Some(s) => {
                    parent=s.clone();
                    println!("{} : {}",parent,counter);},
                None => println!("Error, fitness minimum but no sentence."),
            }
        }
    }
}

fn fitness(target : &String, sentence : &String) -> u32 {
    let mut fitness = 0;
    for (c1, c2) in target.chars().zip(sentence.chars())
    {
        if c1!=c2 { fitness+=1 };
    }
    fitness
}

fn mutate(sentence : &mut String, mutation_rate : f64) -> String {
    let mut rng=rand::thread_rng();

    let mut mutation : String ="".to_string();
    for c in sentence.chars() {
        match mutation_rate>rng.gen_range(0f64,1f64) {
            false => mutation.push(c),               // no mutation
            true  => mutation.push(random_char()),   // mutation
        }
    }
    mutation
}

fn generate_first_sentence (parent : &mut String) {
    for _ in 0..28 {
        parent.push(random_char());
    }
}

fn random_char() -> char {
    match rand::thread_rng().gen_range('A' as u8, '\\' as u8) as char {
        '[' => ' ',
        c @ _ => c
    }
}
<|endoftext|>


<commit_before>mod error;

// My IP address providers
mod myip;

// DNS providers
mod dns;

// Configuration
mod config;

#[macro_use]
extern crate clap;

#[macro_use]
extern crate log;
extern crate env_logger;

extern crate xmlrpc;
extern crate rustc_serialize;
extern crate regex;
extern crate ip;
extern crate hyper;

use clap::{Arg, App};
use std::env;

use config::Config;
use dns::*;
use error::Result;
use myip::*;
use std::process;

fn main() {
    let config = build_config();

    match main_with_errors(&config) {
        Ok(_) => info!("Process ends with success"),
        Err(err) => {
            error!("Process failed with result: {}", err);
            process::exit(-1);
        }
    }
}

fn build_config() -> Config {
    let matches = App::new("gdu")
        .version(&crate_version!()[..])
        .author("Damien Lecan <dev@dlecan.com>")
        .about("Gandi DNS updater, useful to reflect your dynamic IP address to your Gandi DNS zone file.\nIP address is read from stdin.")
        .args_from_usage(
            "-a --apikey=[apikey] 'Your API key provided by Gandi'
            -d --domain=[domain] 'The domain name whose active zonefile will be updated, e.g. \"domain.com\"'
            -n --dry-run 'Dry run, don't really update Gandi zone file'
            -f --force 'Force new zonefile creation even if IP address isn\'t modified'
            -r --record-name=[record_name] 'Name of the A record to update or create (without domain)'
            [verbose]... -v 'Verbose mode'")
        .arg(Arg::with_name("ip_provider")
            .help("IP address provider name to use to get IP address.")
            .short("i")
            .long("ip-provider")
            .takes_value(true)
            .multiple(false))
        .get_matches();

    // Init logging to DEBUG only if user requires it
    match matches.occurrences_of("verbose") {
        0 => env::set_var("RUST_LOG", "INFO"),
        1 => {
            println!("Verbose mode");
            env::set_var("RUST_LOG", "DEBUG");
        }
        2 | _=> {
            println!("More verbose mode");
            env::set_var("RUST_LOG", "TRACE");
        }
    }
    env_logger::init().unwrap();

    let apikey = matches.value_of("apikey").unwrap();
    debug!("Using apikey: {}", apikey);

    let domain = matches.value_of("domain").unwrap();
    debug!("Using domain: {}", domain);

    let record_name = matches.value_of("record_name").unwrap();
    debug!("Using record name: {}", record_name);

    let dry_run = matches.is_present("dry-run");
    debug!("Dry run: {}", dry_run);

    let force = matches.is_present("force");
    debug!("Force: {}", force);

    let ip_provider = value_t_or_exit!(matches.value_of("ip_provider"), IpProvider);
    debug!("IP address provider: {:?}", ip_provider);

    Config {
        apikey: apikey.to_owned(),
        domain: domain.to_owned(),
        record_name: record_name.to_owned(),
        force: force,
        ip_provider: ip_provider,
    }
}

fn main_with_errors(config: &Config) -> Result<()> {

    let ip_provider = IpAddressProvider::from_config(config);

    let my_ip = try!(ip_provider.get_my_ip_addr());

    info!("My IP address: {}", my_ip);

    // Force Gandi DNS provider for now
    let mut dns_provider = dns::GandiDNSProvider::new(&config.apikey);

    try!(dns_provider.init(&config.domain));

    match my_ip {
        ip::IpAddr::V6(_)
            if !dns_provider.handle_ipv6_addr() => panic!("You cannot use IP v6 addresses with the selected DNS provider"),
        _ => (),
    }

    let maybe_checked = try!(dns_provider.is_record_already_declared(&config.record_name));

    match maybe_checked {
        Some(ip_addr) => {
            debug!("Record already declared, with IP address: {}", &ip_addr);

            if !config.force && (&ip_addr == &my_ip) {
                debug!("IP address not modified, no record to update");
                Ok(())
            } else {
                debug!("Update record '{}' with IP address '{}'", &config.record_name, &my_ip);
                Ok(try!(dns_provider.update_record(&config.record_name, &my_ip)))
            }
        }
        None => Ok(try!(dns_provider.create_record(&config.record_name, &my_ip)))
    }
}
<commit_msg>Need more logs in standard log level<commit_after>mod error;

// My IP address providers
mod myip;

// DNS providers
mod dns;

// Configuration
mod config;

#[macro_use]
extern crate clap;

#[macro_use]
extern crate log;
extern crate env_logger;

extern crate xmlrpc;
extern crate rustc_serialize;
extern crate regex;
extern crate ip;
extern crate hyper;

use clap::{Arg, App};
use std::env;

use config::Config;
use dns::*;
use error::Result;
use myip::*;
use std::process;

fn main() {
    let config = build_config();

    match main_with_errors(&config) {
        Ok(_) => info!("Process ends with success"),
        Err(err) => {
            error!("Process failed with result: {}", err);
            process::exit(-1);
        }
    }
}

fn build_config() -> Config {
    let matches = App::new("gdu")
        .version(&crate_version!()[..])
        .author("Damien Lecan <dev@dlecan.com>")
        .about("Gandi DNS updater, useful to reflect your dynamic IP address to your Gandi DNS zone file.\nIP address is read from stdin.")
        .args_from_usage(
            "-a --apikey=[apikey] 'Your API key provided by Gandi'
            -d --domain=[domain] 'The domain name whose active zonefile will be updated, e.g. \"domain.com\"'
            -n --dry-run 'Dry run, don't really update Gandi zone file'
            -f --force 'Force new zonefile creation even if IP address isn\'t modified'
            -r --record-name=[record_name] 'Name of the A record to update or create (without domain)'
            [verbose]... -v 'Verbose mode'")
        .arg(Arg::with_name("ip_provider")
            .help("IP address provider name to use to get IP address.")
            .short("i")
            .long("ip-provider")
            .takes_value(true)
            .multiple(false))
        .get_matches();

    // Init logging to DEBUG only if user requires it
    match matches.occurrences_of("verbose") {
        0 => env::set_var("RUST_LOG", "INFO"),
        1 => {
            println!("Verbose mode");
            env::set_var("RUST_LOG", "DEBUG");
        }
        2 | _=> {
            println!("More verbose mode");
            env::set_var("RUST_LOG", "TRACE");
        }
    }
    env_logger::init().unwrap();

    let apikey = matches.value_of("apikey").unwrap();
    debug!("Using apikey: {}", apikey);

    let domain = matches.value_of("domain").unwrap();
    debug!("Using domain: {}", domain);

    let record_name = matches.value_of("record_name").unwrap();
    debug!("Using record name: {}", record_name);

    let dry_run = matches.is_present("dry-run");
    debug!("Dry run: {}", dry_run);

    let force = matches.is_present("force");
    debug!("Force: {}", force);

    let ip_provider = value_t_or_exit!(matches.value_of("ip_provider"), IpProvider);
    debug!("IP address provider: {:?}", ip_provider);

    Config {
        apikey: apikey.to_owned(),
        domain: domain.to_owned(),
        record_name: record_name.to_owned(),
        force: force,
        ip_provider: ip_provider,
    }
}

fn main_with_errors(config: &Config) -> Result<()> {

    let ip_provider = IpAddressProvider::from_config(config);

    let my_ip = try!(ip_provider.get_my_ip_addr());

    info!("My IP address: {}", my_ip);

    // Force Gandi DNS provider for now
    let mut dns_provider = dns::GandiDNSProvider::new(&config.apikey);

    try!(dns_provider.init(&config.domain));

    match my_ip {
        ip::IpAddr::V6(_)
            if !dns_provider.handle_ipv6_addr() => panic!("You cannot use IP v6 addresses with the selected DNS provider"),
        _ => (),
    }

    let maybe_checked = try!(dns_provider.is_record_already_declared(&config.record_name));

    match maybe_checked {
        Some(ip_addr) => {
            debug!("Record already declared, with IP address: {}", &ip_addr);

            if !config.force && (&ip_addr == &my_ip) {
                info!("IP address not modified, no record to update");
                Ok(())
            } else {
                info!("Update record '{}' with IP address '{}'", &config.record_name, &my_ip);
                Ok(try!(dns_provider.update_record(&config.record_name, &my_ip)))
            }
        }
        None => Ok(try!(dns_provider.create_record(&config.record_name, &my_ip)))
    }
}
<|endoftext|>


<commit_before>#[macro_use] extern crate clap;
extern crate url;
extern crate rss; // https://github.com/rust-syndication/rss
extern crate hyper; // https://github.com/hyperium/hyper
extern crate hyper_native_tls; // https://github.com/sfackler/hyper-native-tls
extern crate curl; // https://docs.rs/curl/0.4.6/curl/easy/
extern crate rusqlite; // https://github.com/jgallagher/rusqlite
extern crate pbr; // https://a8m.github.io/pb/doc/pbr/index.html
//extern crate time; // https://doc.rust-lang.org/time/time/index.html

mod schema;
mod common;

use pbr::{ProgressBar, Units};
use std::process;
use clap::{App, ArgMatches};
use schema::*;
use url::Url;
use rusqlite::Connection;
use curl::easy::Easy;
use std::path::{Path};
use std::fs::{File, create_dir};
use std::env::home_dir;
use std::time::Duration;
use hyper::Client; // https://hyper.rs/hyper/v0.10.9/hyper/index.html
use hyper::net::HttpsConnector;
use hyper_native_tls::NativeTlsClient;
use std::io::{Write, Read, stdin}; // needed for read_to_string trait
use std::str::FromStr; // needed for FromStr trait on Channel
use rss::Channel;

const VERSION: &'static str = env!("RUSTY_VERSION");

fn main() {
    let lock_socket = common::create_app_lock(12345); // https://rosettacode.org/wiki/Category:Rust

    // Clap
    let yaml = load_yaml!("cli.yml");
    let matches = App::from_yaml(yaml).get_matches();

    // Init Sqlite database
    let database_url: String;

    if matches.is_present("database") { // get path from command line
        database_url = matches.value_of("database").unwrap().to_string();
    } else { // or put database file into ~/.config/rusty/rusty.sqlite3
        let db_path = home_dir().expect("/tmp/").into_os_string().into_string().unwrap() + "/.config/rusty";
        if !Path::new(&db_path).exists() { // If path doesn't exist we create it
            create_dir(&db_path).unwrap();
        }
        database_url = db_path + "/rusty.sqlite3";
    }
    let connection = match Connection::open(&database_url) {
        Ok(connection) => connection,
        Err(e) => {
            println!("Error database connection: {} {}", e, database_url);
            process::exit(1)
        }
    };
    init(&connection); // Initialize database

    match matches.subcommand() {
        ("subscribe", Some(sub_matches)) => subscribe(sub_matches, &connection),
        ("unsubscribe", Some(sub_matches)) => unsubscribe(sub_matches, &connection),
        ("list", Some(_)) => list(&connection),
        ("update", Some(sub_matches)) => {
            let feed_id = sub_matches.value_of("id").unwrap_or("0").parse::<i64>().unwrap();
            update(sub_matches, &connection, feed_id);
        },
        ("pending", Some(_)) => pending(&connection),
        ("download", Some(_)) => download(&connection),
        ("version", Some(_)) => version(),
        ("download-dir", Some(sub_matches)) => downloaddir(sub_matches, &connection),
        ("", None) => println!("No subcommand was used"),
        _ => println!("No!"),
    }

    common::remove_app_lock(lock_socket);
}

fn init(connection: &Connection) {
    // execute only the first query if there are several queries. So we need to used split()
    let split = include_str!("sql/init.sql").split(";");
    for s in split {
        if s.trim() == "" {
            continue;
        }
        connection.execute(s, &[]).unwrap();
    }
}

fn subscribe(args: &ArgMatches, connection: &Connection) {
    match Url::parse(args.value_of("url").unwrap()) {
        Ok(url) => {
            let subscription = Subscription { id: 0, url: url.as_str().to_string(), label: String::new(), last_build_date: String::new()};
            println!("Subscribing to: {}", subscription.url);

            let mut stmt = connection.prepare("select * from subscription where url = ?1").unwrap();
            if !stmt.exists(&[&subscription.url]).unwrap() {
                connection.execute("insert into subscription (url, label, last_build_date) values (?1, '', ?2)", &[&subscription.url, &subscription.last_build_date]).unwrap();
            } else {
                println!("    Feed already subscribed to");
                return
            }
            update(args, connection, connection.last_insert_rowid());
            println!("Subscribed");
        }
        Err(e) => println!("Could not parse url '{}' {}", args.value_of("url").unwrap(), e),
    }
}

fn unsubscribe(args: &ArgMatches, connection: &Connection) {
    let id = args.value_of("id").unwrap();

    match common::get_subscription(connection, &id) {
        Some(subscription) => {
            let mut stdin = stdin();
            let mut buffer = [0;1];
            println!("Unsubscribing from: {}", subscription.url);
            println!("Sure? [y/N]");
            stdin.read_exact(&mut buffer).unwrap();
            if buffer[0] == 121u8 { // 121 is ascii code for 'y'
                subscription.delete(connection);
                println!("Unsubscribed from: {}", subscription.url);
            }
        },
        None => println!("Subscription doesn't exist"),
    }
}

fn list(connection: &Connection) {
    println!("Subscriptions list:");
    match common::get_subscriptions(connection) {
        None => println!("{}", "    No subscription"),
        Some(subscriptions) => {
            for subscription in subscriptions {
                println!("    {}: {} ({})", subscription.id, subscription.label, subscription.url);
            }
        }
    }
}

fn update(args: &ArgMatches, connection: &Connection, feed_id: i64) {

    let ssl = NativeTlsClient::new().unwrap();
    let connector = HttpsConnector::new(ssl);
    let client = Client::with_connector(connector); // create http client with tls support

    let mut as_downloaded = 0;
    if args.is_present("as-downloaded") {
        as_downloaded = 1;
    }

    match common::get_subscriptions(connection) {
        None => println!("    No subscription to update"),
        Some(subscriptions) => for subscription in subscriptions {

            if feed_id != subscription.id && feed_id > 0 { // if an id is set, update/populate only this id
                continue;
            }

            println!("Updating {} ...", subscription.label);

            let mut res = client.get(subscription.clone()).send().unwrap(); // get query result thanks to IntoUrl trait implement for Subscription
            let mut body = String::new();
            let mut previous_insert_rowid: i64 = 0;
            res.read_to_string(&mut body).unwrap(); // extract body from query result

            let channel = match Channel::from_str(&body) { // parse rss into channel
                Err(e) => {
                    println!("Couldn't parse rss {} ({})", subscription.url, e);
                    continue
                },
                Ok(channel) => channel
            };

            if channel.last_build_date().unwrap() == &subscription.last_build_date {
                continue
            }
            connection.execute("update subscription set label = ?1, last_build_date = ?2 where id = ?3", &[&channel.title(), &channel.last_build_date(), &subscription.id]).unwrap(); // update podcast feed name
            for item in channel.items() {
                let url = Url::parse(item.enclosure().unwrap().url()).unwrap();
                let path_segments = url.path_segments().unwrap();

                // create filename
                let mut filename = String::new();
                for segment in path_segments {
                    if segment == "enclosure.mp3" || segment == "listen.mp3" {
                        filename = filename + ".mp3";
                        break;
                    }
                    filename = segment.to_string();
                }

                let podcast = Podcast { id: 0, subscription_id: subscription.id, url: url.as_str().to_string(), filename: filename};
                connection.execute("insert or ignore into podcast (subscription_id, url, filename, downloaded) values (?1, ?2, ?3, ?4)", &[&podcast.subscription_id, &podcast.url, &podcast.filename, &as_downloaded]).unwrap();
                if previous_insert_rowid != connection.last_insert_rowid() {
                    println!("    New podcast added: {:?}", podcast.filename);
                }
                previous_insert_rowid = connection.last_insert_rowid();
            }
            println!("{} updated", subscription.label);
        }
    }
}

fn pending(connection: &Connection) {
    println!("Pending list:");
    match common::get_pending_podcasts(connection) {
        None => println!("{}", "    Nothing to download"),
        Some(podcasts) => {
            for podcast in podcasts {
                println!("    {} ({})", podcast.filename, podcast.url);
            }
        }
    }
}

fn downloaddir(args: &ArgMatches, connection: &Connection) {
    match args.value_of("path") {
        None => println!("Current download dir: {}", common::getdownloaddir(connection)),
        Some(path) => {
            connection.execute("insert or replace into config (key, value) values ('downloaddir', ?1)", &[&path]).unwrap();
            println!("Download dir set to: {}", path);
        }
    }
}

fn download(connection: &Connection) {
    let mut curl = Easy::new();

    let mut pb = ProgressBar::new(100);
    pb.format("╢▌▌░╟");
    pb.set_units(Units::Bytes);
    pb.set_max_refresh_rate(Some(Duration::from_millis(100)));
    curl.progress(true).unwrap();
    curl.follow_location(true).unwrap();
    curl.progress_function( move |a, b, _, _| {
        pb.total = a as u64;
        pb.set(b as u64);
        true
    }).unwrap();

    println!("Download pending podcast:");
    match common::get_pending_podcasts(connection) {
        None => println!("{}", "    Nothing to download"),
        Some(podcasts) => {
            for podcast in podcasts {
                let temp = common::getdownloaddir(connection) + podcast.filename.as_str();
                let path = Path::new(&temp);

                let mut file = match File::create(&path) {
                    Err(why) => panic!("couldn't create {}",
                                       why),
                    Ok(file) => file,
                };
                curl.url(&podcast.url).unwrap();
                curl.write_function( move |data| {
                    Ok(file.write(data).unwrap())
                }).unwrap();
                println!("    {}", podcast.filename);
                curl.perform().unwrap();
                connection.execute("update podcast set downloaded = 1, downloaded_at = current_timestamp where id = ?1", &[&podcast.id]).unwrap();
            }
        }
    }
}

fn version() {
    println!("Rusty {}", VERSION);
}
<commit_msg>Control feed file content type<commit_after>#[macro_use] extern crate clap;
extern crate url;
extern crate rss; // https://github.com/rust-syndication/rss
extern crate hyper; // https://github.com/hyperium/hyper
extern crate hyper_native_tls; // https://github.com/sfackler/hyper-native-tls
extern crate curl; // https://docs.rs/curl/0.4.6/curl/easy/
extern crate rusqlite; // https://github.com/jgallagher/rusqlite
extern crate pbr; // https://a8m.github.io/pb/doc/pbr/index.html
extern crate time; // https://doc.rust-lang.org/time/time/index.html

mod schema;
mod common;

use pbr::{ProgressBar, Units};
use std::process;
use clap::{App, ArgMatches};
use schema::*;
use url::Url;
use rusqlite::Connection;
use curl::easy::Easy;
use std::path::{Path};
use std::fs::{File, create_dir};
use std::env::home_dir;
use std::time::Duration;
use hyper::Client; // https://hyper.rs/hyper/v0.10.9/hyper/index.html
use hyper::header::ContentType;
use hyper::net::HttpsConnector;
use hyper_native_tls::NativeTlsClient;
use hyper::mime::Mime;
use hyper::mime::TopLevel::{Text, Application};
use hyper::mime::SubLevel::Xml;
use std::io::{Write, Read, stdin}; // needed for read_to_string trait
use std::str::FromStr; // needed for FromStr trait on Channel
use rss::Channel;

const VERSION: &'static str = env!("RUSTY_VERSION");

fn main() {
    let lock_socket = common::create_app_lock(12345); // https://rosettacode.org/wiki/Category:Rust

    // Clap
    let yaml = load_yaml!("cli.yml");
    let matches = App::from_yaml(yaml).get_matches();

    // Init Sqlite database
    let database_url: String;

    if matches.is_present("database") { // get path from command line
        database_url = matches.value_of("database").unwrap().to_string();
    } else { // or put database file into ~/.config/rusty/rusty.sqlite3
        let db_path = home_dir().expect("/tmp/").into_os_string().into_string().unwrap() + "/.config/rusty";
        if !Path::new(&db_path).exists() { // If path doesn't exist we create it
            create_dir(&db_path).unwrap();
        }
        database_url = db_path + "/rusty.sqlite3";
    }
    let connection = match Connection::open(&database_url) {
        Ok(connection) => connection,
        Err(e) => {
            println!("Error database connection: {} {}", e, database_url);
            process::exit(1)
        }
    };
    init(&connection); // Initialize database

    match matches.subcommand() {
        ("subscribe", Some(sub_matches)) => subscribe(sub_matches, &connection),
        ("unsubscribe", Some(sub_matches)) => unsubscribe(sub_matches, &connection),
        ("list", Some(_)) => list(&connection),
        ("update", Some(sub_matches)) => {
            let feed_id = sub_matches.value_of("id").unwrap_or("0").parse::<i64>().unwrap();
            update(sub_matches, &connection, feed_id);
        },
        ("pending", Some(_)) => pending(&connection),
        ("download", Some(_)) => download(&connection),
        ("version", Some(_)) => version(),
        ("download-dir", Some(sub_matches)) => downloaddir(sub_matches, &connection),
        ("", None) => println!("No subcommand was used"),
        _ => println!("No!"),
    }

    common::remove_app_lock(lock_socket);
}

fn init(connection: &Connection) {
    // execute only the first query if there are several queries. So we need to used split()
    let split = include_str!("sql/init.sql").split(";");
    for s in split {
        if s.trim() == "" {
            continue;
        }
        connection.execute(s, &[]).unwrap();
    }
}

fn subscribe(args: &ArgMatches, connection: &Connection) {
    match Url::parse(args.value_of("url").unwrap()) {
        Ok(url) => {
            let subscription = Subscription { id: 0, url: url.as_str().to_string(), label: String::new(), last_build_date: String::new()};
            println!("Subscribing to: {}", subscription.url);

            let mut stmt = connection.prepare("select * from subscription where url = ?1").unwrap();
            if !stmt.exists(&[&subscription.url]).unwrap() {
                connection.execute("insert into subscription (url, label, last_build_date) values (?1, '', ?2)", &[&subscription.url, &subscription.last_build_date]).unwrap();
            } else {
                println!("    Feed already subscribed to");
                return
            }
            update(args, connection, connection.last_insert_rowid());
            println!("Subscribed");
        }
        Err(e) => println!("Could not parse url '{}' {}", args.value_of("url").unwrap(), e),
    }
}

fn unsubscribe(args: &ArgMatches, connection: &Connection) {
    let id = args.value_of("id").unwrap();

    match common::get_subscription(connection, &id) {
        Some(subscription) => {
            let mut stdin = stdin();
            let mut buffer = [0;1];
            println!("Unsubscribing from: {}", subscription.url);
            println!("Sure? [y/N]");
            stdin.read_exact(&mut buffer).unwrap();
            if buffer[0] == 121u8 { // 121 is ascii code for 'y'
                subscription.delete(connection);
                println!("Unsubscribed from: {}", subscription.url);
            }
        },
        None => println!("Subscription doesn't exist"),
    }
}

fn list(connection: &Connection) {
    println!("Subscriptions list:");
    match common::get_subscriptions(connection) {
        None => println!("{}", "    No subscription"),
        Some(subscriptions) => {
            for subscription in subscriptions {
                println!("    {}: {} ({})", subscription.id, subscription.label, subscription.url);
            }
        }
    }
}

fn update(args: &ArgMatches, connection: &Connection, feed_id: i64) {

    let ssl = NativeTlsClient::new().unwrap();
    let connector = HttpsConnector::new(ssl);
    let client = Client::with_connector(connector); // create http client with tls support

    let mut as_downloaded = 0;
    if args.is_present("as-downloaded") {
        as_downloaded = 1;
    }

    match common::get_subscriptions(connection) {
        None => println!("    No subscription to update"),
        Some(subscriptions) => for subscription in subscriptions {

            if feed_id != subscription.id && feed_id > 0 { // if an id is set, update/populate only this id
                continue;
            }

            print!("Updating {} ... ", subscription.label);

            let mut res = client.get(subscription.clone()).send().unwrap(); // get query result thanks to IntoUrl trait implement for Subscription
            let mut body = String::new();
            let mut previous_insert_rowid: i64 = 0;
            match res.headers.get() {
                Some(&ContentType(Mime(Text, Xml, _))) => (),
                Some(&ContentType(Mime(Application, Xml, _))) => (),
                Some(content_type) => {
                    println!("Content type unsupported: {}", content_type.to_string());
                    continue;
                },
                None => ()
            }
            res.read_to_string(&mut body).unwrap(); // extract body from query result

            let channel = match Channel::from_str(&body) { // parse rss into channel
                Err(e) => {
                    println!("Couldn't parse rss {} ({})", subscription.url, e);
                    continue
                },
                Ok(channel) => channel
            };

            if channel.last_build_date().unwrap() == &subscription.last_build_date {
                println!("Already up to date");
                continue
            }
            connection.execute("update subscription set label = ?1, last_build_date = ?2 where id = ?3", &[&channel.title(), &channel.last_build_date(), &subscription.id]).unwrap(); // update podcast feed name
            for item in channel.items() {
                let url = Url::parse(item.enclosure().unwrap().url()).unwrap();
                let path_segments = url.path_segments().unwrap();

                // create filename
                let mut filename = String::new();
                for segment in path_segments {
                    if segment == "enclosure.mp3" || segment == "listen.mp3" {
                        filename = filename + ".mp3";
                        break;
                    }
                    filename = segment.to_string();
                }

                let podcast = Podcast { id: 0, subscription_id: subscription.id, url: url.as_str().to_string(), filename: filename};
                connection.execute("insert or ignore into podcast (subscription_id, url, filename, downloaded) values (?1, ?2, ?3, ?4)", &[&podcast.subscription_id, &podcast.url, &podcast.filename, &as_downloaded]).unwrap();
                if previous_insert_rowid != connection.last_insert_rowid() {
                    println!("    New podcast added: {:?}", podcast.filename);
                }
                previous_insert_rowid = connection.last_insert_rowid();
            }
            println!("updated");
        }
    }
}

fn pending(connection: &Connection) {
    println!("Pending list:");
    match common::get_pending_podcasts(connection) {
        None => println!("{}", "    Nothing to download"),
        Some(podcasts) => {
            for podcast in podcasts {
                println!("    {} ({})", podcast.filename, podcast.url);
            }
        }
    }
}

fn downloaddir(args: &ArgMatches, connection: &Connection) {
    match args.value_of("path") {
        None => println!("Current download dir: {}", common::getdownloaddir(connection)),
        Some(path) => {
            connection.execute("insert or replace into config (key, value) values ('downloaddir', ?1)", &[&path]).unwrap();
            println!("Download dir set to: {}", path);
        }
    }
}

fn download(connection: &Connection) {
    let mut curl = Easy::new();

    let mut pb = ProgressBar::new(100);
    pb.format("╢▌▌░╟");
    pb.set_units(Units::Bytes);
    pb.set_max_refresh_rate(Some(Duration::from_millis(100)));
    curl.progress(true).unwrap();
    curl.follow_location(true).unwrap();
    curl.progress_function( move |a, b, _, _| {
        pb.total = a as u64;
        pb.set(b as u64);
        true
    }).unwrap();

    println!("Download pending podcast:");
    match common::get_pending_podcasts(connection) {
        None => println!("{}", "    Nothing to download"),
        Some(podcasts) => {
            for podcast in podcasts {
                let temp = common::getdownloaddir(connection) + podcast.filename.as_str();
                let path = Path::new(&temp);

                let mut file = match File::create(&path) {
                    Err(why) => panic!("couldn't create {}",
                                       why),
                    Ok(file) => file,
                };
                curl.url(&podcast.url).unwrap();
                curl.write_function( move |data| {
                    Ok(file.write(data).unwrap())
                }).unwrap();
                println!("    {}", podcast.filename);
                curl.perform().unwrap();
                connection.execute("update podcast set downloaded = 1, downloaded_at = current_timestamp where id = ?1", &[&podcast.id]).unwrap();
            }
        }
    }
}

fn version() {
    println!("Rusty {}", VERSION);
}
<|endoftext|>


<commit_before>// Bloom
//
// HTTP REST API caching middleware
// Copyright: 2017, Valerian Saliou <valerian@valeriansaliou.name>
// License: Mozilla Public License v2.0 (MPL v2.0)

#[macro_use] extern crate log;
#[macro_use] extern crate clap;
extern crate ini;
extern crate hyper;
extern crate farmhash;
extern crate futures;
extern crate memcached;

mod config;
mod header;
mod proxy;
mod cache;
mod control;
mod server;

use std::sync::Arc;
use clap::{App, Arg};
use config::logger::ConfigLogger;
use config::reader::ConfigReaderBuilder;
use cache::store::CacheStoreBuilder;
use proxy::serve::ProxyServeBuilder;
use control::listen::ControlListenBuilder;
use server::listen::ServerListenBuilder;

fn main() {
    let _logger = ConfigLogger::init();

    info!("starting up");

    let app = App::new(crate_name!())
                .version(crate_version!())
                .author(crate_authors!("\n"))
                .about(crate_description!())
                .arg(Arg::with_name("config")
                    .short("c")
                    .long("config")
                    .help("Path to configuration file")
                    .default_value("./config.cfg")
                    .takes_value(true));

    let args = app.get_matches();
    let conf = ConfigReaderBuilder::new().read(
        args.value_of("config").unwrap());

    // Bind to cache store
    let cache_store = Arc::new(CacheStoreBuilder::new(conf.memcached));
    cache_store.as_ref().bind();

    // Create serve manager
    let proxy_serve = Arc::new(ProxyServeBuilder::new(conf.proxy));

    // Run control interface (in its own thread)
    ControlListenBuilder::new(conf.control).run();

    // Run server (in main thread)
    ServerListenBuilder::new(conf.server).run(proxy_serve, cache_store);

    error!("could not start");
}
<commit_msg>Notice<commit_after>// Bloom
//
// HTTP REST API caching middleware
// Copyright: 2017, Valerian Saliou <valerian@valeriansaliou.name>
// License: Mozilla Public License v2.0 (MPL v2.0)

#[macro_use] extern crate log;
#[macro_use] extern crate clap;
extern crate ini;
extern crate hyper;
extern crate farmhash;
extern crate futures;
extern crate memcached;

mod config;
mod header;
mod proxy;
mod cache;
mod control;
mod server;

use std::sync::Arc;
use clap::{App, Arg};
use config::logger::ConfigLogger;
use config::reader::ConfigReaderBuilder;
use cache::store::CacheStoreBuilder;
use proxy::serve::ProxyServeBuilder;
use control::listen::ControlListenBuilder;
use server::listen::ServerListenBuilder;

fn main() {
    let _logger = ConfigLogger::init();

    info!("starting up");

    let app = App::new(crate_name!())
                .version(crate_version!())
                .author(crate_authors!("\n"))
                .about(crate_description!())
                .arg(Arg::with_name("config")
                    .short("c")
                    .long("config")
                    .help("Path to configuration file")
                    .default_value("./config.cfg")
                    .takes_value(true));

    let args = app.get_matches();
    let conf = ConfigReaderBuilder::new().read(
        args.value_of("config").unwrap());

    // Bind to cache store
    let cache_store = Arc::new(CacheStoreBuilder::new(conf.memcached));
    cache_store.as_ref().bind();

    // Create serve manager
    let proxy_serve = Arc::new(ProxyServeBuilder::new(conf.proxy));

    // Run control interface (in its own thread)
    ControlListenBuilder::new(conf.control).run();

    // Run server (in main thread)
    ServerListenBuilder::new(conf.server).run(proxy_serve, cache_store);

    // TODO: wrap the things in lazy_static?
    // @ref: https://github.com/rust-lang-nursery/lazy-static.rs

    error!("could not start");
}
<|endoftext|>


<commit_before>#![cfg_attr(feature = "dev", allow(unstable_features))]
#![cfg_attr(feature = "dev", feature(plugin))]
#![cfg_attr(feature = "dev", plugin(clippy))]

mod logger;
mod toml_file;
mod git;
mod changelog;
mod commit_analyzer;
mod cargo;
mod error;
mod github;
mod config;
mod utils;

extern crate rustc_serialize;
extern crate toml;
extern crate regex;
extern crate semver;
extern crate docopt;
extern crate git2;
extern crate clog;
extern crate hyper;
extern crate hubcaps;
extern crate url;
extern crate travis_after_all;
extern crate env_logger;

use docopt::Docopt;
use commit_analyzer::CommitType;
use config::ConfigBuilder;
use std::process;
use semver::Version;
use std::{env,fs};
use std::path::Path;
use std::error::Error;
use std::thread;
use std::time::Duration;
use travis_after_all::Build;
use utils::user_repo_from_url;

const VERSION: &'static str = env!("CARGO_PKG_VERSION");
const USERAGENT: &'static str = concat!("semantic-rs/", env!("CARGO_PKG_VERSION"));
const USAGE: &'static str = "
semantic.rs 🚀

Usage:
  semantic-rs [options]
  semantic-rs --version

Options:
  -h --help              Show this screen.
  --version              Show version.
  -p PATH, --path=PATH   Specifies the repository path. [default: .]
  -w W, --write=W        Write changes to files (default: yes if CI is set, otherwise no).
  -r R, --release=R      Create release on GitHub and publish on crates.io (only in write mode) [default: yes].
  -b B, --branch=B       The branch on which releases should happen. [default: master].
";

macro_rules! print_exit {
    ($fmt:expr) => {{
        logger::stderr($fmt);
        process::exit(1);
    }};
    ($fmt:expr, $($arg:tt)*) => {{
        logger::stderr(format!($fmt, $($arg)*));
        process::exit(1);
    }};
}

#[derive(Debug, RustcDecodable)]
struct Args {
    flag_path: String,
    flag_write: Option<String>,
    flag_version: bool,
    flag_release: String,
    flag_branch: String,
}

fn string_to_bool(answer: &str) -> bool {
    match &answer.to_lowercase()[..] {
        "yes" | "true" | "1" => true,
        _ => false
    }
}

fn version_bump(version: &Version, bump: CommitType) -> Option<Version> {
    let mut version = version.clone();
    match bump {
        CommitType::Unknown => return None,
        CommitType::Patch => version.increment_patch(),
        CommitType::Minor => version.increment_minor(),
        CommitType::Major => version.increment_major(),
    }

    Some(version)
}

fn ci_env_set() -> bool {
    env::var("CI").is_ok()
}

fn current_branch(repo: &git2::Repository) -> Option<String> {
    if let Ok(branch) = env::var("TRAVIS_BRANCH") {
        return Some(branch)
    }

    let head = repo.head().expect("No HEAD found for repository");

    if head.is_branch() {
        let short = head.shorthand().expect("No branch name found");
        return Some(short.into());
    }

    None
}

fn is_release_branch(current: &str, release: &str) -> bool {
    if let Ok(pr) = env::var("TRAVIS_PULL_REQUEST") {
        if pr != "false" {
            return false;
        }
    }

    current == release
}

fn push_to_github(config: &config::Config, tag_name: &str) {
    logger::stdout("Pushing new commit and tag");
    git::push(&config, &tag_name)
        .unwrap_or_else(|err| print_exit!("Failed to push git: {:?}", err));

    logger::stdout("Waiting a tiny bit, so GitHub can store the git tag");
    thread::sleep(Duration::from_secs(1));
}

fn release_on_github(config: &config::Config, tag_message: &str, tag_name: &str) {
    logger::stdout("Creating GitHub release");
    github::release(&config, &tag_name, &tag_message)
        .unwrap_or_else(|err| print_exit!("Failed to create GitHub release: {:?}", err));
}

fn release_on_cratesio(config: &config::Config) {
    logger::stdout("Publishing crate on crates.io");
    if !cargo::publish(&config.repository_path, &config.cargo_token.as_ref().unwrap()) {
        print_exit!("Failed to publish on crates.io");
    }
}

fn generate_changelog(repository_path: &str, version: &Version, new_version: &String) -> String {
    logger::stdout(format!("New version would be: {}", new_version));
    logger::stdout("Would write the following Changelog:");
    match changelog::generate(repository_path, &version.to_string(), new_version) {
        Ok(log) => log,
        Err(err) => {
            logger::stderr(format!("Generating Changelog failed: {:?}", err));
            process::exit(1)
        }
    }
}

fn print_changelog(changelog: &str) {
    logger::stdout("====================================");
    logger::stdout(changelog);
    logger::stdout("====================================");
    logger::stdout("Would create annotated git tag");
}

fn main() {
    env_logger::init().expect("Can't instantiate env logger");

    let args: Args = Docopt::new(USAGE)
        .and_then(|d| d.decode())
        .unwrap_or_else(|e| e.exit());

    let mut config_builder = ConfigBuilder::new();

    if args.flag_version {
        println!("semantic.rs 🚀 -- v{}", VERSION);
        process::exit(0);
    }

    // If write mode is requested OR denied,
    // adhere to the user's wish,
    // otherwise we decide based on whether we are running in CI.
    let write_mode = match args.flag_write {
        None => ci_env_set(),
        Some(ref flag) => string_to_bool(flag)
    };

    // We can only release, if we are allowed to write
    let release_mode = write_mode && string_to_bool(&args.flag_release);

    config_builder.write(write_mode);
    config_builder.release(release_mode);
    config_builder.branch(args.flag_branch);

    println!("semantic.rs 🚀");

    logger::stdout("Analyzing your repository");
    let path = Path::new(&args.flag_path);
    let path = fs::canonicalize(path)
        .unwrap_or_else(|_| print_exit!("Path does not exist or a component is not a directory"));
    let repository_path = path.to_str()
        .unwrap_or_else(|| print_exit!("Path is not valid unicode"));

    let repo = match git2::Repository::open(repository_path) {
        Ok(repo) => repo,
        Err(e) => {
            logger::stderr(format!("Could not open the git repository: {:?}", e));
            process::exit(1);
        }
    };

    config_builder.repository_path(repository_path.to_owned());

    // extra scope scope to make sure borrow of `repo` is dropped
    {
        let signature = match git::get_signature(&repo) {
            Ok(sig) => sig,
            Err(e) => {
                logger::stderr(format!("Failed to get the committer's name and email address: {}", e.description()));
                logger::stderr(r"
A release commit needs a committer name and email address.
We tried fetching it from different locations, but couldn't find one.

Committer information is taken from the following environment variables, if set:

GIT_COMMITTER_NAME
GIT_COMMITTER_EMAIL

If none is set the normal git config is tried in the following order:

Local repository config
User config
Global config");
                process::exit(1);
            }
        };

        config_builder.signature(signature.to_owned());
    }

    // In case we are in write-mode AND release mode,
    // we will make sure we got all configuration settings
    if write_mode && release_mode {
        let remote_or_none = repo.find_remote("origin");
        match remote_or_none {
            Ok(remote) => {
                let url = remote.url().expect("Remote URL is not valid UTF-8").to_owned();
                let (user, repo_name) = user_repo_from_url(&url)
                    .unwrap_or_else(|e| print_exit!("Could not extract user and repository name from URL: {:?}", e));
                config_builder.user(user);
                config_builder.repository_name(repo_name);

                let gh_token = env::var("GH_TOKEN")
                    .unwrap_or_else(|err| print_exit!("GH_TOKEN not set: {:?}", err));

                let cargo_token = env::var("CARGO_TOKEN")
                    .unwrap_or_else(|err| print_exit!("CARGO_TOKEN not set: {:?}", err));

                config_builder.gh_token(gh_token);
                config_builder.cargo_token(cargo_token);
            },
            Err(err) => {
                logger::warn(format!("Could not determine the origin remote url: {:?}", err));
                logger::warn("semantic-rs can't push changes or create a release on GitHub");
            }
        }
    }

    config_builder.repository(repo);
    let config = config_builder.build();

    let branch = current_branch(&config.repository)
        .unwrap_or_else(|| print_exit!("Could not determine current branch."));

    if !is_release_branch(&branch, &config.branch) {
        println!("Current branch is '{}', releases are only done from branch '{}'", branch, config.branch);
        println!("No release done from a pull request either.");
        process::exit(0);
    }

    if config.release_mode && ci_env_set() {
        let build_run = Build::from_env()
            .unwrap_or_else(|e| print_exit!("CI mode, but can't check other builds. Error: {:?}", e));

        if !build_run.is_leader() {
            println!("Not the build leader. Nothing to do. Bye.");
            process::exit(0);
        }

        println!("I am the build leader. Waiting for other jobs to finish.");
        match build_run.wait_for_others() {
            Ok(()) => println!("Other jobs finished and succeeded. Doing my work now."),
            Err(travis_after_all::Error::FailedBuilds) => {
                print_exit!("Some builds failed. Stopping here.");
            },
            Err(e) => print_exit!("Waiting for other builds failed Reason: {:?}", e),
        }
    }

    let version = toml_file::read_from_file(&config.repository_path)
        .unwrap_or_else(|err| print_exit!("Reading `Cargo.toml` failed: {:?}", err));

    let version = Version::parse(&version).expect("Not a valid version");
    logger::stdout(format!("Current version: {}", version.to_string()));

    logger::stdout("Analyzing commits");

    let bump = git::version_bump_since_latest(&config.repository);
    if write_mode {
        logger::stdout(format!("Commits analyzed. Bump will be {:?}", bump));
    } else {
        logger::stdout(format!("Commits analyzed. Bump would be {:?}", bump));
    }
    let new_version = match version_bump(&version, bump) {
        Some(new_version) => new_version.to_string(),
        None => {
            logger::stdout("No version bump. Nothing to do.");
            process::exit(0);
        }
    };

    if !config.write_mode {
        let changelog = generate_changelog(repository_path, &version, &new_version);
        print_changelog(&changelog);
    } else {
        logger::stdout(format!("New version: {}", new_version));

        toml_file::write_new_version(repository_path, &new_version)
            .unwrap_or_else(|err| print_exit!("Writing `Cargo.toml` failed: {:?}", err));

        logger::stdout("Writing Changelog");
        changelog::write(repository_path, &version.to_string(), &new_version)
            .unwrap_or_else(|err| print_exit!("Writing Changelog failed: {:?}", err));

        if config.release_mode {
            logger::stdout("Updating lockfile");
            if !cargo::update_lockfile(repository_path) {
                print_exit!("`cargo fetch` failed. See above for the cargo error message.");
            }
        }

        logger::stdout("Package crate");
        if !cargo::package(repository_path) {
            print_exit!("`cargo package` failed. See above for the cargo error message.");
        }

        git::commit_files(&config, &new_version)
            .unwrap_or_else(|err| print_exit!("Committing files failed: {:?}", err));

        logger::stdout("Creating annotated git tag");
        let tag_message = changelog::generate(repository_path, &version.to_string(), &new_version)
            .unwrap_or_else(|err| print_exit!("Can't generate changelog: {:?}", err));

        let tag_name = format!("v{}", new_version);
        git::tag(&config, &tag_name, &tag_message)
            .unwrap_or_else(|err| print_exit!("Failed to create git tag: {:?}", err));

        if config.release_mode && config.can_push() {
            push_to_github(&config, &tag_name);
            release_on_github(&config, &tag_message, &tag_name);
            release_on_cratesio(&config);
            println!("{} v{} is released. 🚀🚀🚀", config.repository_name.unwrap(), new_version);
        }
    }
}
<commit_msg>refactor: extract write changelog and package crate<commit_after>#![cfg_attr(feature = "dev", allow(unstable_features))]
#![cfg_attr(feature = "dev", feature(plugin))]
#![cfg_attr(feature = "dev", plugin(clippy))]

mod logger;
mod toml_file;
mod git;
mod changelog;
mod commit_analyzer;
mod cargo;
mod error;
mod github;
mod config;
mod utils;

extern crate rustc_serialize;
extern crate toml;
extern crate regex;
extern crate semver;
extern crate docopt;
extern crate git2;
extern crate clog;
extern crate hyper;
extern crate hubcaps;
extern crate url;
extern crate travis_after_all;
extern crate env_logger;

use docopt::Docopt;
use commit_analyzer::CommitType;
use config::ConfigBuilder;
use std::process;
use semver::Version;
use std::{env,fs};
use std::path::Path;
use std::error::Error;
use std::thread;
use std::time::Duration;
use travis_after_all::Build;
use utils::user_repo_from_url;

const VERSION: &'static str = env!("CARGO_PKG_VERSION");
const USERAGENT: &'static str = concat!("semantic-rs/", env!("CARGO_PKG_VERSION"));
const USAGE: &'static str = "
semantic.rs 🚀

Usage:
  semantic-rs [options]
  semantic-rs --version

Options:
  -h --help              Show this screen.
  --version              Show version.
  -p PATH, --path=PATH   Specifies the repository path. [default: .]
  -w W, --write=W        Write changes to files (default: yes if CI is set, otherwise no).
  -r R, --release=R      Create release on GitHub and publish on crates.io (only in write mode) [default: yes].
  -b B, --branch=B       The branch on which releases should happen. [default: master].
";

macro_rules! print_exit {
    ($fmt:expr) => {{
        logger::stderr($fmt);
        process::exit(1);
    }};
    ($fmt:expr, $($arg:tt)*) => {{
        logger::stderr(format!($fmt, $($arg)*));
        process::exit(1);
    }};
}

#[derive(Debug, RustcDecodable)]
struct Args {
    flag_path: String,
    flag_write: Option<String>,
    flag_version: bool,
    flag_release: String,
    flag_branch: String,
}

fn string_to_bool(answer: &str) -> bool {
    match &answer.to_lowercase()[..] {
        "yes" | "true" | "1" => true,
        _ => false
    }
}

fn version_bump(version: &Version, bump: CommitType) -> Option<Version> {
    let mut version = version.clone();
    match bump {
        CommitType::Unknown => return None,
        CommitType::Patch => version.increment_patch(),
        CommitType::Minor => version.increment_minor(),
        CommitType::Major => version.increment_major(),
    }

    Some(version)
}

fn ci_env_set() -> bool {
    env::var("CI").is_ok()
}

fn current_branch(repo: &git2::Repository) -> Option<String> {
    if let Ok(branch) = env::var("TRAVIS_BRANCH") {
        return Some(branch)
    }

    let head = repo.head().expect("No HEAD found for repository");

    if head.is_branch() {
        let short = head.shorthand().expect("No branch name found");
        return Some(short.into());
    }

    None
}

fn is_release_branch(current: &str, release: &str) -> bool {
    if let Ok(pr) = env::var("TRAVIS_PULL_REQUEST") {
        if pr != "false" {
            return false;
        }
    }

    current == release
}

fn push_to_github(config: &config::Config, tag_name: &str) {
    logger::stdout("Pushing new commit and tag");
    git::push(&config, &tag_name)
        .unwrap_or_else(|err| print_exit!("Failed to push git: {:?}", err));

    logger::stdout("Waiting a tiny bit, so GitHub can store the git tag");
    thread::sleep(Duration::from_secs(1));
}

fn release_on_github(config: &config::Config, tag_message: &str, tag_name: &str) {
    logger::stdout("Creating GitHub release");
    github::release(&config, &tag_name, &tag_message)
        .unwrap_or_else(|err| print_exit!("Failed to create GitHub release: {:?}", err));
}

fn release_on_cratesio(config: &config::Config) {
    logger::stdout("Publishing crate on crates.io");
    if !cargo::publish(&config.repository_path, &config.cargo_token.as_ref().unwrap()) {
        print_exit!("Failed to publish on crates.io");
    }
}

fn generate_changelog(repository_path: &str, version: &Version, new_version: &String) -> String {
    logger::stdout(format!("New version would be: {}", new_version));
    logger::stdout("Would write the following Changelog:");
    match changelog::generate(repository_path, &version.to_string(), new_version) {
        Ok(log) => log,
        Err(err) => {
            logger::stderr(format!("Generating Changelog failed: {:?}", err));
            process::exit(1)
        }
    }
}

fn write_changelog(repository_path: &str, version: &Version, new_version: &str) {
    logger::stdout("Writing Changelog");
    changelog::write(repository_path, &version.to_string(), &new_version)
        .unwrap_or_else(|err| print_exit!("Writing Changelog failed: {:?}", err));
}

fn print_changelog(changelog: &str) {
    logger::stdout("====================================");
    logger::stdout(changelog);
    logger::stdout("====================================");
    logger::stdout("Would create annotated git tag");
}

fn package_crate(config: &config::Config, repository_path: &str, new_version: &str) {
    if config.release_mode {
        logger::stdout("Updating lockfile");
        if !cargo::update_lockfile(repository_path) {
            print_exit!("`cargo fetch` failed. See above for the cargo error message.");
        }
    }

    logger::stdout("Package crate");
    if !cargo::package(repository_path) {
        print_exit!("`cargo package` failed. See above for the cargo error message.");
    }

    git::commit_files(&config, &new_version)
        .unwrap_or_else(|err| print_exit!("Committing files failed: {:?}", err));
}

fn main() {
    env_logger::init().expect("Can't instantiate env logger");

    let args: Args = Docopt::new(USAGE)
        .and_then(|d| d.decode())
        .unwrap_or_else(|e| e.exit());

    let mut config_builder = ConfigBuilder::new();

    if args.flag_version {
        println!("semantic.rs 🚀 -- v{}", VERSION);
        process::exit(0);
    }

    // If write mode is requested OR denied,
    // adhere to the user's wish,
    // otherwise we decide based on whether we are running in CI.
    let write_mode = match args.flag_write {
        None => ci_env_set(),
        Some(ref flag) => string_to_bool(flag)
    };

    // We can only release, if we are allowed to write
    let release_mode = write_mode && string_to_bool(&args.flag_release);

    config_builder.write(write_mode);
    config_builder.release(release_mode);
    config_builder.branch(args.flag_branch);

    println!("semantic.rs 🚀");

    logger::stdout("Analyzing your repository");
    let path = Path::new(&args.flag_path);
    let path = fs::canonicalize(path)
        .unwrap_or_else(|_| print_exit!("Path does not exist or a component is not a directory"));
    let repository_path = path.to_str()
        .unwrap_or_else(|| print_exit!("Path is not valid unicode"));

    let repo = match git2::Repository::open(repository_path) {
        Ok(repo) => repo,
        Err(e) => {
            logger::stderr(format!("Could not open the git repository: {:?}", e));
            process::exit(1);
        }
    };

    config_builder.repository_path(repository_path.to_owned());

    // extra scope scope to make sure borrow of `repo` is dropped
    {
        let signature = match git::get_signature(&repo) {
            Ok(sig) => sig,
            Err(e) => {
                logger::stderr(format!("Failed to get the committer's name and email address: {}", e.description()));
                logger::stderr(r"
A release commit needs a committer name and email address.
We tried fetching it from different locations, but couldn't find one.

Committer information is taken from the following environment variables, if set:

GIT_COMMITTER_NAME
GIT_COMMITTER_EMAIL

If none is set the normal git config is tried in the following order:

Local repository config
User config
Global config");
                process::exit(1);
            }
        };

        config_builder.signature(signature.to_owned());
    }

    // In case we are in write-mode AND release mode,
    // we will make sure we got all configuration settings
    if write_mode && release_mode {
        let remote_or_none = repo.find_remote("origin");
        match remote_or_none {
            Ok(remote) => {
                let url = remote.url().expect("Remote URL is not valid UTF-8").to_owned();
                let (user, repo_name) = user_repo_from_url(&url)
                    .unwrap_or_else(|e| print_exit!("Could not extract user and repository name from URL: {:?}", e));
                config_builder.user(user);
                config_builder.repository_name(repo_name);

                let gh_token = env::var("GH_TOKEN")
                    .unwrap_or_else(|err| print_exit!("GH_TOKEN not set: {:?}", err));

                let cargo_token = env::var("CARGO_TOKEN")
                    .unwrap_or_else(|err| print_exit!("CARGO_TOKEN not set: {:?}", err));

                config_builder.gh_token(gh_token);
                config_builder.cargo_token(cargo_token);
            },
            Err(err) => {
                logger::warn(format!("Could not determine the origin remote url: {:?}", err));
                logger::warn("semantic-rs can't push changes or create a release on GitHub");
            }
        }
    }

    config_builder.repository(repo);
    let config = config_builder.build();

    let branch = current_branch(&config.repository)
        .unwrap_or_else(|| print_exit!("Could not determine current branch."));

    if !is_release_branch(&branch, &config.branch) {
        println!("Current branch is '{}', releases are only done from branch '{}'", branch, config.branch);
        println!("No release done from a pull request either.");
        process::exit(0);
    }

    if config.release_mode && ci_env_set() {
        let build_run = Build::from_env()
            .unwrap_or_else(|e| print_exit!("CI mode, but can't check other builds. Error: {:?}", e));

        if !build_run.is_leader() {
            println!("Not the build leader. Nothing to do. Bye.");
            process::exit(0);
        }

        println!("I am the build leader. Waiting for other jobs to finish.");
        match build_run.wait_for_others() {
            Ok(()) => println!("Other jobs finished and succeeded. Doing my work now."),
            Err(travis_after_all::Error::FailedBuilds) => {
                print_exit!("Some builds failed. Stopping here.");
            },
            Err(e) => print_exit!("Waiting for other builds failed Reason: {:?}", e),
        }
    }

    let version = toml_file::read_from_file(&config.repository_path)
        .unwrap_or_else(|err| print_exit!("Reading `Cargo.toml` failed: {:?}", err));

    let version = Version::parse(&version).expect("Not a valid version");
    logger::stdout(format!("Current version: {}", version.to_string()));

    logger::stdout("Analyzing commits");

    let bump = git::version_bump_since_latest(&config.repository);
    if write_mode {
        logger::stdout(format!("Commits analyzed. Bump will be {:?}", bump));
    } else {
        logger::stdout(format!("Commits analyzed. Bump would be {:?}", bump));
    }
    let new_version = match version_bump(&version, bump) {
        Some(new_version) => new_version.to_string(),
        None => {
            logger::stdout("No version bump. Nothing to do.");
            process::exit(0);
        }
    };

    if !config.write_mode {
        let changelog = generate_changelog(repository_path, &version, &new_version);
        print_changelog(&changelog);
    } else {
        logger::stdout(format!("New version: {}", new_version));

        toml_file::write_new_version(repository_path, &new_version)
            .unwrap_or_else(|err| print_exit!("Writing `Cargo.toml` failed: {:?}", err));

        write_changelog(&repository_path, &version, &new_version);
        package_crate(&config, &repository_path, &new_version);

        logger::stdout("Creating annotated git tag");
        let tag_message = changelog::generate(repository_path, &version.to_string(), &new_version)
            .unwrap_or_else(|err| print_exit!("Can't generate changelog: {:?}", err));

        let tag_name = format!("v{}", new_version);
        git::tag(&config, &tag_name, &tag_message)
            .unwrap_or_else(|err| print_exit!("Failed to create git tag: {:?}", err));

        if config.release_mode && config.can_push() {
            push_to_github(&config, &tag_name);
            release_on_github(&config, &tag_message, &tag_name);
            release_on_cratesio(&config);
            println!("{} v{} is released. 🚀🚀🚀", config.repository_name.unwrap(), new_version);
        }
    }
}
<|endoftext|>


<commit_before>extern crate encoding;
extern crate rand;
extern crate toml;
extern crate eu4data;

mod config;
mod file;

use std::fs;
use std::path::PathBuf;
use rand::{Rng, StdRng};
use config::Config;
use eu4data::{Eu4Table, Eu4Value};

fn main() {
    let config = Config::load();
    prepare_output(&config);
    let source_data = load_eu4_data(&config);
    let target_data = process_eu4_data(source_data);
    write_eu4_data(&config, target_data);
}

fn prepare_output(config: &Config) {
    println!("=== preparing output ===");
    println!("Preparing mod folder at \"{}\"...", config.target_path.display());

    // Delete the old mod folder if it's already there
    if config.target_path.is_dir() {
        println!("Target already exists, deleting stale...");
        fs::remove_dir_all(&config.target_path).unwrap();
    }

    // Create a new mod folder for us
    fs::create_dir_all(&config.target_path).unwrap();

    // Copy over the .mod file TODO: Auto-generate
    let mut shattered_mod = config.target_path.clone();
    shattered_mod.push("..");
    shattered_mod.push("shattered.mod");
    fs::copy("./assets/shattered.mod", shattered_mod).unwrap();

    println!("");
}

#[derive(Clone)]
struct FileTable {
    file_name: String,
    data: Eu4Table
}

struct Eu4SourceData {
    provinces: Vec<FileTable>,
    countries: Vec<FileTable>,
    country_history: Vec<FileTable>,
    country_tags: Eu4Table,
}

fn load_eu4_data(config: &Config) -> Eu4SourceData {
    println!("=== loading eu4 game data ===");

    let provinces = load_eu4_data_from_folder(&config.game_path, "history", "provinces");
    let countries = load_eu4_data_from_folder(&config.game_path, "common", "countries");
    let country_history = load_eu4_data_from_folder(&config.game_path, "history", "countries");

    println!("Loading country tags...");
    let mut file = config.game_path.clone();
    file.push("common"); file.push("country_tags"); file.push("00_countries.txt");
    let text = file::read_all_win_1252(file);
    let country_tags = Eu4Table::parse(&text);

    println!("");

    Eu4SourceData {
        provinces: provinces,
        countries: countries,
        country_history: country_history,
        country_tags: country_tags,
    }
}

fn load_eu4_data_from_folder(base: &PathBuf, sub1: &str, sub2: &str) -> Vec<FileTable> {
    println!("Loading {}/{}...", sub1, sub2);

    // Get a path for the folder the data is in
    let mut dir = base.clone();
    dir.push(sub1);
    dir.push(sub2);
    assert!(dir.is_dir(), "\"{}\" is not an existing directory", dir.display());

    let mut data = Vec::new();

    // Get all the files from the directory
    for file_r in dir.read_dir().unwrap() {
        let file = file_r.unwrap();
        //println!("Loading {:?}...", file.file_name());

        // Load the file
        let text = file::read_all_win_1252(file.path());
        let file_data = Eu4Table::parse(&text);

        data.push(FileTable {
            file_name: file.file_name().to_str().unwrap().into(),
            data: file_data
        });
    }

    data
}

struct Eu4TargetData {
    provinces: Vec<FileTable>,
    countries: Vec<FileTable>,
    country_history: Vec<FileTable>,
    country_tags: Eu4Table
}

fn process_eu4_data(data: Eu4SourceData) -> Eu4TargetData {
    println!("=== processing ===");

    // Create copies of data for us to use
    let mut provinces = data.provinces.clone();
    let mut countries: Vec<FileTable> = Vec::new();
    let mut country_history: Vec<FileTable> = Vec::new();
    let mut country_tags = data.country_tags.clone();

    println!("Clearing events on provinces...");
    for province in &mut provinces {
        clear_events(&mut province.data);
    }

    println!("Generating new countries...");
    let mut rand = StdRng::new().unwrap();
    let mut tag_num = 0;
    for province in &mut provinces {
        // Check the province's owner tag, if it has one
        let old_country_tag = {
            if let Some(tag) = province.data.get("owner") {
                tag.as_str().to_string()
            } else {
                continue; // No owner, we can skip this province
            }
        };

        // Find the country data for this province
        let old_country_file = country_tags.get(&old_country_tag).unwrap().as_str().to_string();
        let old_country = data.countries.iter()
            .find(|d| (String::from("countries/") + &d.file_name) == old_country_file).unwrap();
        let old_country_history = data.country_history.iter()
            .find(|f| f.file_name.starts_with(&old_country_tag)).unwrap();

        // Find out the name of this province from the file
        // TODO: Retrieve this from the localization file instead of from the file name
        let province_segments: Vec<_> = province.file_name
            .split(|c| c == ' ' || c == '-' || c == '.')
            .filter(|s| s.len() != 0)
            .collect();
        let province_name = province_segments.iter().nth(1).unwrap().to_string();
        let province_id = province_segments.iter().nth(0).unwrap().to_string();
        let new_country_file_name = province_name + ".txt";

        // Make a new country with the old country's and data
        let mut new_country = old_country.clone();
        new_country.file_name = new_country_file_name.clone();
        let mut new_country_history = old_country_history.clone();

        // Clear the events on the new country
        clear_events(&mut new_country.data);
        clear_events(&mut new_country_history.data);

        // Generate a new tag for the country and add it to the tags list
        let new_country_tag = get_next_valid_tag(&mut tag_num, &country_tags);
        country_tags.set(
            &new_country_tag,
            Eu4Value::String(String::from("countries/") + &new_country_file_name));
        new_country_history.file_name = format!("{} - {}", new_country_tag, new_country_file_name);

        // Make the country's culture and religion match the province it was generated from
        new_country_history.data.set("culture", province.data.get("culture").unwrap().clone());
        new_country_history.data.set("religion", province.data.get("religion").unwrap().clone());

        // Generate a color for the country
        // TODO: Improve color generation
        // TODO: Generate flags
        new_country.data.set("color", Eu4Value::color(rand.gen(), rand.gen(), rand.gen()));

        // Update the province to be owned by the new country
        province.data.set("owner", Eu4Value::String(new_country_tag.clone()));
        province.data.set("controller", Eu4Value::String(new_country_tag.clone()));
        province.data.set("add_core", Eu4Value::String(new_country_tag.clone()));

        // Fix the HRE electors, only stay an elector if the country was the old country's capital
        if province.data.get("elector").map(|v| v.as_str() == "yes").unwrap_or(false) {
            if old_country_history.data.get("capital").unwrap().as_str() != province_id {
                province.data.set("elector", Eu4Value::String("no".into()));
            }
        }

        // Store the actual data in the lists
        countries.push(new_country);
        country_history.push(new_country_history);
    }

    println!("");

    Eu4TargetData {
        provinces: provinces,
        countries: countries,
        country_history: country_history,
        country_tags: country_tags,
    }
}

fn clear_events(table: &mut Eu4Table) {
    table.values = table.values.iter()
        .filter(|v| v.key.len() == 0 || !v.key.chars().nth(0).unwrap().is_digit(10))
        .map(|v| v.clone())
        .collect();
}

fn get_next_valid_tag(tag_num: &mut i32, country_tags: &Eu4Table) -> String {
    loop {
        // Get the next tag and increment
        let tag = get_tag_for_num(*tag_num);
        *tag_num += 1;

        // Make sure it's not one of these special cases
        if tag == "AUX" || tag == "CON" || tag == "AND" {
            continue;
        }

        // Make sure it's not already in use
        if country_tags.values.iter().any(|v| v.key == tag) {
            continue;
        }

        // It's valid, return it
        return tag;
    }
}

fn get_tag_for_num(num: i32) -> String {
    let mut b = [b'A'; 3];

    b[0] += (num / (26*26)) as u8;
    b[1] += ((num % (26*26)) / 26) as u8;
    b[2] += (num % 26) as u8;

    ::std::str::from_utf8(&b).unwrap().to_string()
}

fn write_eu4_data(config: &Config, data: Eu4TargetData) {
    println!("=== serializing to target ===");

    write_eu4_data_to_folder(&config.target_path, "history", "provinces", &data.provinces);
    write_eu4_data_to_folder(&config.target_path, "common", "countries", &data.countries);
    write_eu4_data_to_folder(&config.target_path, "history", "countries", &data.country_history);

    // Create the country tags file
    println!("Serializing country tags...");
    let mut file = config.target_path.clone();
    file.push("common"); file.push("country_tags");
    fs::create_dir_all(&file).unwrap();
    file.push("00_countries.txt");
    file::write_all_win_1252(file, &data.country_tags.serialize());
}

fn write_eu4_data_to_folder(base: &PathBuf, sub1: &str, sub2: &str, entries: &Vec<FileTable>) {
    println!("Serializing {}/{}...", sub1, sub2);

    // Create the directory
    let mut dir = base.clone();
    dir.push(sub1);
    dir.push(sub2);
    fs::create_dir_all(&dir).unwrap();

    // Write all the data to it
    for entry in entries {
        let mut file = dir.clone();
        file.push(&entry.file_name);

        file::write_all_win_1252(file, &entry.data.serialize());
    }
}
<commit_msg>Fix electorate generation<commit_after>extern crate encoding;
extern crate rand;
extern crate toml;
extern crate eu4data;

mod config;
mod file;

use std::fs;
use std::path::PathBuf;
use rand::{Rng, StdRng};
use config::Config;
use eu4data::{Eu4Table, Eu4Value};

fn main() {
    let config = Config::load();
    prepare_output(&config);
    let source_data = load_eu4_data(&config);
    let target_data = process_eu4_data(source_data);
    write_eu4_data(&config, target_data);
}

fn prepare_output(config: &Config) {
    println!("=== preparing output ===");
    println!("Preparing mod folder at \"{}\"...", config.target_path.display());

    // Delete the old mod folder if it's already there
    if config.target_path.is_dir() {
        println!("Target already exists, deleting stale...");
        fs::remove_dir_all(&config.target_path).unwrap();
    }

    // Create a new mod folder for us
    fs::create_dir_all(&config.target_path).unwrap();

    // Copy over the .mod file TODO: Auto-generate
    let mut shattered_mod = config.target_path.clone();
    shattered_mod.push("..");
    shattered_mod.push("shattered.mod");
    fs::copy("./assets/shattered.mod", shattered_mod).unwrap();

    println!("");
}

#[derive(Clone)]
struct FileTable {
    file_name: String,
    data: Eu4Table
}

struct Eu4SourceData {
    provinces: Vec<FileTable>,
    countries: Vec<FileTable>,
    country_history: Vec<FileTable>,
    country_tags: Eu4Table,
}

fn load_eu4_data(config: &Config) -> Eu4SourceData {
    println!("=== loading eu4 game data ===");

    let provinces = load_eu4_data_from_folder(&config.game_path, "history", "provinces");
    let countries = load_eu4_data_from_folder(&config.game_path, "common", "countries");
    let country_history = load_eu4_data_from_folder(&config.game_path, "history", "countries");

    println!("Loading country tags...");
    let mut file = config.game_path.clone();
    file.push("common"); file.push("country_tags"); file.push("00_countries.txt");
    let text = file::read_all_win_1252(file);
    let country_tags = Eu4Table::parse(&text);

    println!("");

    Eu4SourceData {
        provinces: provinces,
        countries: countries,
        country_history: country_history,
        country_tags: country_tags,
    }
}

fn load_eu4_data_from_folder(base: &PathBuf, sub1: &str, sub2: &str) -> Vec<FileTable> {
    println!("Loading {}/{}...", sub1, sub2);

    // Get a path for the folder the data is in
    let mut dir = base.clone();
    dir.push(sub1);
    dir.push(sub2);
    assert!(dir.is_dir(), "\"{}\" is not an existing directory", dir.display());

    let mut data = Vec::new();

    // Get all the files from the directory
    for file_r in dir.read_dir().unwrap() {
        let file = file_r.unwrap();
        //println!("Loading {:?}...", file.file_name());

        // Load the file
        let text = file::read_all_win_1252(file.path());
        let file_data = Eu4Table::parse(&text);

        data.push(FileTable {
            file_name: file.file_name().to_str().unwrap().into(),
            data: file_data
        });
    }

    data
}

struct Eu4TargetData {
    provinces: Vec<FileTable>,
    countries: Vec<FileTable>,
    country_history: Vec<FileTable>,
    country_tags: Eu4Table
}

fn process_eu4_data(data: Eu4SourceData) -> Eu4TargetData {
    println!("=== processing ===");

    // Create copies of data for us to use
    let mut provinces = data.provinces.clone();
    let mut countries: Vec<FileTable> = Vec::new();
    let mut country_history: Vec<FileTable> = Vec::new();
    let mut country_tags = data.country_tags.clone();

    println!("Clearing events on provinces...");
    for province in &mut provinces {
        clear_events(&mut province.data);
    }

    println!("Generating new countries...");
    let mut rand = StdRng::new().unwrap();
    let mut tag_num = 0;
    for province in &mut provinces {
        // Check the province's owner tag, if it has one
        let old_country_tag = {
            if let Some(tag) = province.data.get("owner") {
                tag.as_str().to_string()
            } else {
                continue; // No owner, we can skip this province
            }
        };

        // Find the country data for this province
        let old_country_file = country_tags.get(&old_country_tag).unwrap().as_str().to_string();
        let old_country = data.countries.iter()
            .find(|d| (String::from("countries/") + &d.file_name) == old_country_file).unwrap();
        let old_country_history = data.country_history.iter()
            .find(|f| f.file_name.starts_with(&old_country_tag)).unwrap();

        // Find out the name of this province from the file
        // TODO: Retrieve this from the localization file instead of from the file name
        let province_segments: Vec<_> = province.file_name
            .split(|c| c == ' ' || c == '-' || c == '.')
            .filter(|s| s.len() != 0)
            .collect();
        let province_name = province_segments.iter().nth(1).unwrap().to_string();
        let province_id = province_segments.iter().nth(0).unwrap().to_string();
        let new_country_file_name = format!("{}.txt", province_name);

        // Make a new country with the old country's and data
        let mut new_country = old_country.clone();
        new_country.file_name = new_country_file_name.clone();
        let mut new_country_history = old_country_history.clone();

        // Clear the events on the new country
        clear_events(&mut new_country.data);
        clear_events(&mut new_country_history.data);

        // Generate a new tag for the country and add it to the tags list
        let new_country_tag = get_next_valid_tag(&mut tag_num, &country_tags);
        country_tags.set(
            &new_country_tag,
            Eu4Value::String(String::from("countries/") + &new_country_file_name));
        new_country_history.file_name = format!("{} - {}", new_country_tag, new_country_file_name);

        // Make the country's culture and religion match the province it was generated from
        new_country_history.data.set("culture", province.data.get("culture").unwrap().clone());
        new_country_history.data.set("religion", province.data.get("religion").unwrap().clone());

        // Generate a color for the country
        // TODO: Improve color generation
        // TODO: Generate flags
        new_country.data.set("color", Eu4Value::color(rand.gen(), rand.gen(), rand.gen()));

        // Update the province to be owned by the new country
        province.data.set("owner", Eu4Value::String(new_country_tag.clone()));
        province.data.set("controller", Eu4Value::String(new_country_tag.clone()));
        province.data.set("add_core", Eu4Value::String(new_country_tag.clone()));

        // Fix the HRE electors, only stay an elector if the country was the old country's capital
        if new_country_history.data.get("elector").map(|v| v.as_str() == "yes").unwrap_or(false) {
            if old_country_history.data.get("capital").unwrap().as_str() != province_id {
                new_country_history.data.set("elector", Eu4Value::String("no".into()));
            } else {
                println!("Granted elector status to {}", province_name);
            }
        }

        // Store the actual data in the lists
        countries.push(new_country);
        country_history.push(new_country_history);
    }

    println!("");

    Eu4TargetData {
        provinces: provinces,
        countries: countries,
        country_history: country_history,
        country_tags: country_tags,
    }
}

fn clear_events(table: &mut Eu4Table) {
    table.values = table.values.iter()
        .filter(|v| v.key.len() == 0 || !v.key.chars().nth(0).unwrap().is_digit(10))
        .map(|v| v.clone())
        .collect();
}

fn get_next_valid_tag(tag_num: &mut i32, country_tags: &Eu4Table) -> String {
    loop {
        // Get the next tag and increment
        let tag = get_tag_for_num(*tag_num);
        *tag_num += 1;

        // Make sure it's not one of these special cases
        if tag == "AUX" || tag == "CON" || tag == "AND" {
            continue;
        }

        // Make sure it's not already in use
        if country_tags.values.iter().any(|v| v.key == tag) {
            continue;
        }

        // It's valid, return it
        return tag;
    }
}

fn get_tag_for_num(num: i32) -> String {
    let mut b = [b'A'; 3];

    b[0] += (num / (26*26)) as u8;
    b[1] += ((num % (26*26)) / 26) as u8;
    b[2] += (num % 26) as u8;

    ::std::str::from_utf8(&b).unwrap().to_string()
}

fn write_eu4_data(config: &Config, data: Eu4TargetData) {
    println!("=== serializing to target ===");

    write_eu4_data_to_folder(&config.target_path, "history", "provinces", &data.provinces);
    write_eu4_data_to_folder(&config.target_path, "common", "countries", &data.countries);
    write_eu4_data_to_folder(&config.target_path, "history", "countries", &data.country_history);

    // Create the country tags file
    println!("Serializing country tags...");
    let mut file = config.target_path.clone();
    file.push("common"); file.push("country_tags");
    fs::create_dir_all(&file).unwrap();
    file.push("00_countries.txt");
    file::write_all_win_1252(file, &data.country_tags.serialize());
}

fn write_eu4_data_to_folder(base: &PathBuf, sub1: &str, sub2: &str, entries: &Vec<FileTable>) {
    println!("Serializing {}/{}...", sub1, sub2);

    // Create the directory
    let mut dir = base.clone();
    dir.push(sub1);
    dir.push(sub2);
    fs::create_dir_all(&dir).unwrap();

    // Write all the data to it
    for entry in entries {
        let mut file = dir.clone();
        file.push(&entry.file_name);

        file::write_all_win_1252(file, &entry.data.serialize());
    }
}
<|endoftext|>