<commit_before>#[macro_use]
extern crate clap;
extern crate chrono;
#[macro_use]
extern crate serde_derive;
extern crate serde;
extern crate serde_json;

use std::env;
use std::fs;
use std::fs::{OpenOptions, File};
use std::io::{Write, ErrorKind};
use std::path::PathBuf;

use chrono::{DateTime, Utc};
use clap::{App, Arg, AppSettings, SubCommand};
use serde_json::Error;

#[derive(Serialize, Deserialize, Debug, Clone)]
struct Period {
    project: String,
    start_time: DateTime<Utc>,
    end_time: Option<DateTime<Utc>>,
}

fn main() {
    let matches = App::new("Doug")
        .version(crate_version!())
        .about("A time tracking command-line utility")
        .author(crate_authors!())
        .settings(&[
            AppSettings::DeriveDisplayOrder,
            AppSettings::GlobalVersion,
            AppSettings::SubcommandRequiredElseHelp,
            AppSettings::VersionlessSubcommands,
            AppSettings::DisableHelpSubcommand,
        ])
        .subcommand(SubCommand::with_name("start")
            .about("Track new or existing project")
            .arg(Arg::with_name("project")
                .help("project to track")
                .required(true)))
        .subcommand(SubCommand::with_name("status")
            .about("Display elapsed time, start time, and running project name"))
        .subcommand(SubCommand::with_name("stop")
            .about("Stop any running projects"))
        .subcommand(SubCommand::with_name("cancel")
            .about("Stop running project and remove most recent time interval"))
        .subcommand(SubCommand::with_name("restart")
            .about("Track last running project"))
        .subcommand(SubCommand::with_name("log")
            .about("Display time intervals across all projects"))
        .subcommand(SubCommand::with_name("report")
            .about("Display aggregate time from last week"))
        .subcommand(SubCommand::with_name("amend")
            .about("Change name of currently running project")
            .arg(Arg::with_name("project")
                .help("new project name")
                .required(true)))
        .subcommand(SubCommand::with_name("edit")
            .about("Edit last frame or currently running frame")
            .arg(Arg::with_name("project")
                .help("project to track")))
        .get_matches();

    if let Some(matches) = matches.subcommand_matches("start") {
        if matches.is_present("project") {
            start(matches.value_of("project").unwrap());
        }
    }

    if let Some(matches) = matches.subcommand_matches("amend") {
        if matches.is_present("project") {
            amend(matches.value_of("project").expect("missing project name"));
        }
    }

    if let Some(matches) = matches.subcommand_matches("edit") {
        edit(matches.value_of("project"));
    }

    match matches.subcommand_name() {
        Some("stop") => stop(),
        Some("cancel") => cancel(),
        Some("restart") => restart(),
        Some("log") => log(),
        Some("report") => report(),
        _ => {},
    }
}

fn start(project_name: &str) {

    let mut periods = get_periods();

    if !periods.is_empty() {
        let last_index = periods.len() - 1;
        if let Some(period) = periods.get_mut(last_index) {
            if period.end_time.is_none() {
                eprintln!("Sorry, you're currently tracking project `{}`, started `{}`", period.project, period.start_time);
                eprintln!("Try stopping your current project with `stop` first.`");
                return
            }
        }
    }
    let current_period = create_period(project_name);
    // store current period in file
    print!("Started tracking project '{}'", current_period.project);
    periods.push(current_period);
    save_periods(periods.to_vec());
}

fn stop() {
    let mut periods = get_periods();
    let mut updated_period = false;
    let last_index = periods.len() - 1;
    if periods.len() > 0 {
        if let Some(period) = periods.get_mut(last_index) {
            if period.end_time.is_none() {
                period.end_time = Some(Utc::now());
                updated_period = true;
                println!("Stopped project: `{}`", period.project);
            }
        }
    }
    if updated_period {
        save_periods(periods);
    } else {
        eprintln!("No running project to stop.");
    }
}

fn cancel() {
    let mut periods = get_periods();
    if let Some(period) = periods.pop() {
        if period.end_time.is_none() {
            save_periods(periods);
            println!("Canceled running project: {}", period.project);
            return
        }
    }
    eprintln!("No running project to cancel");
}

fn restart() {
    unimplemented!();
}

fn log() {
    unimplemented!();
}

fn report() {
    unimplemented!();
}

fn amend(project_name: &str) {
    println!("Amend project: {:?}", project_name);
    unimplemented!();
}

fn edit(project_name: Option<&str>) {
    if let Some(name) = project_name {
        println!("Edit project: {}", name);
    }
    unimplemented!();
}

fn create_period(project: &str) -> Period {
    Period {
        project: String::from(project),
        start_time: Utc::now(),
        end_time: None,
    }
}

fn get_config_folder() -> PathBuf {
    let home_dir = env::var("HOME").expect("Failed to find home directory from environment 'HOME'");
    let mut config_folder = PathBuf::from(home_dir);
    config_folder.push(".config/doug");
    config_folder
}

fn get_config_file_path() -> PathBuf {
    let config_folder = get_config_folder();
    let mut config_file = PathBuf::from(&config_folder);
    config_file.push("periods.json");
    config_file
}

fn get_config_back_file_path() -> PathBuf {
    let config_file = get_config_file_path();
    config_file.with_extension("json-backup")
}

fn get_config_file() -> File {
    let config_file = get_config_file_path();
    OpenOptions::new()
        .create(true)
        .read(true)
        .write(true)
        .open(&config_file)
        .expect(&format!("Couldn't open configuration file: {:?}", config_file))
}

fn get_periods() -> Vec<Period> {
    let config_folder = get_config_folder();

    match fs::create_dir(&config_folder) {
        Err(ref error) if error.kind() == ErrorKind::AlreadyExists => {},
        Err(error) => panic!("There was a problem creating the config directory: {:?}", error),
        Ok(_) => {},
    }

    let file = get_config_file();

    let periods: Result<Vec<Period>, Error> = serde_json::from_reader(file);
    let periods = match periods {
        Ok(p) => p,
        Err(ref error) if error.is_eof() => Vec::new(),
        Err(error) => panic!("There was a serialization issue: {:?}", error),
    };
    periods
}

fn save_periods(periods: Vec<Period>) {
    let serialized = serde_json::to_string(&periods).expect("Couldn't serialize data to string");
    let config_file = get_config_file_path();
    let config_file_backup = get_config_back_file_path();
    fs::copy(&config_file, &config_file_backup).expect("Couldn't create backup file");
    let mut file = OpenOptions::new()
                    .create(true)
                    .write(true)
                    .truncate(true)
                    .open(&config_file)
                    .expect("Couldn't open file for saving period.");

    file.write_all(serialized.as_bytes()).expect("Couldn't write serialized data to file");
}
<commit_msg>Add empty datafile error handling<commit_after>#[macro_use]
extern crate clap;
extern crate chrono;
#[macro_use]
extern crate serde_derive;
extern crate serde;
extern crate serde_json;

use std::env;
use std::fs;
use std::fs::{OpenOptions, File, Metadata};
use std::io::{Write, ErrorKind};
use std::path::PathBuf;

use chrono::{DateTime, Utc};
use clap::{App, Arg, AppSettings, SubCommand};
use serde_json::Error;

#[derive(Serialize, Deserialize, Debug, Clone)]
struct Period {
    project: String,
    start_time: DateTime<Utc>,
    end_time: Option<DateTime<Utc>>,
}

fn main() {
    let matches = App::new("Doug")
        .version(crate_version!())
        .about("A time tracking command-line utility")
        .author(crate_authors!())
        .settings(&[
            AppSettings::DeriveDisplayOrder,
            AppSettings::GlobalVersion,
            AppSettings::SubcommandRequiredElseHelp,
            AppSettings::VersionlessSubcommands,
            AppSettings::DisableHelpSubcommand,
        ])
        .subcommand(SubCommand::with_name("start")
            .about("Track new or existing project")
            .arg(Arg::with_name("project")
                .help("project to track")
                .required(true)))
        .subcommand(SubCommand::with_name("status")
            .about("Display elapsed time, start time, and running project name"))
        .subcommand(SubCommand::with_name("stop")
            .about("Stop any running projects"))
        .subcommand(SubCommand::with_name("cancel")
            .about("Stop running project and remove most recent time interval"))
        .subcommand(SubCommand::with_name("restart")
            .about("Track last running project"))
        .subcommand(SubCommand::with_name("log")
            .about("Display time intervals across all projects"))
        .subcommand(SubCommand::with_name("report")
            .about("Display aggregate time from last week"))
        .subcommand(SubCommand::with_name("amend")
            .about("Change name of currently running project")
            .arg(Arg::with_name("project")
                .help("new project name")
                .required(true)))
        .subcommand(SubCommand::with_name("edit")
            .about("Edit last frame or currently running frame")
            .arg(Arg::with_name("project")
                .help("project to track")))
        .get_matches();

    if let Some(matches) = matches.subcommand_matches("start") {
        if matches.is_present("project") {
            start(matches.value_of("project").unwrap());
        }
    }

    if let Some(matches) = matches.subcommand_matches("amend") {
        if matches.is_present("project") {
            amend(matches.value_of("project").expect("missing project name"));
        }
    }

    if let Some(matches) = matches.subcommand_matches("edit") {
        edit(matches.value_of("project"));
    }

    match matches.subcommand_name() {
        Some("stop") => stop(),
        Some("cancel") => cancel(),
        Some("restart") => restart(),
        Some("log") => log(),
        Some("report") => report(),
        _ => {},
    }
}

fn start(project_name: &str) {

    let mut periods = get_periods();

    if !periods.is_empty() {
        let last_index = periods.len() - 1;
        if let Some(period) = periods.get_mut(last_index) {
            if period.end_time.is_none() {
                eprintln!("Sorry, you're currently tracking project `{}`, started `{}`", period.project, period.start_time);
                eprintln!("Try stopping your current project with `stop` first.`");
                return
            }
        }
    }
    let current_period = create_period(project_name);
    // store current period in file
    print!("Started tracking project '{}'", current_period.project);
    periods.push(current_period);
    save_periods(periods.to_vec());
}

fn stop() {
    let mut periods = get_periods();
    let mut updated_period = false;
    let last_index = periods.len() - 1;
    if periods.len() > 0 {
        if let Some(period) = periods.get_mut(last_index) {
            if period.end_time.is_none() {
                period.end_time = Some(Utc::now());
                updated_period = true;
                println!("Stopped project: `{}`", period.project);
            }
        }
    }
    if updated_period {
        save_periods(periods);
    } else {
        eprintln!("No running project to stop.");
    }
}

fn cancel() {
    let mut periods = get_periods();
    if let Some(period) = periods.pop() {
        if period.end_time.is_none() {
            save_periods(periods);
            println!("Canceled running project: {}", period.project);
            return
        }
    }
    eprintln!("No running project to cancel");
}

fn restart() {
    unimplemented!();
}

fn log() {
    unimplemented!();
}

fn report() {
    unimplemented!();
}

fn amend(project_name: &str) {
    println!("Amend project: {:?}", project_name);
    unimplemented!();
}

fn edit(project_name: Option<&str>) {
    if let Some(name) = project_name {
        println!("Edit project: {}", name);
    }
    unimplemented!();
}

fn create_period(project: &str) -> Period {
    Period {
        project: String::from(project),
        start_time: Utc::now(),
        end_time: None,
    }
}

fn get_config_folder() -> PathBuf {
    let home_dir = env::var("HOME").expect("Failed to find home directory from environment 'HOME'");
    let mut config_folder = PathBuf::from(home_dir);
    config_folder.push(".config/doug");
    config_folder
}

fn get_config_file_path() -> PathBuf {
    let config_folder = get_config_folder();
    let mut config_file = PathBuf::from(&config_folder);
    config_file.push("periods.json");
    config_file
}

fn get_config_back_file_path() -> PathBuf {
    let config_file = get_config_file_path();
    config_file.with_extension("json-backup")
}

fn get_config_file() -> (File, Metadata) {
    let config_file = get_config_file_path();
    (OpenOptions::new()
        .create(true)
        .read(true)
        .write(true)
        .open(&config_file)
        .expect(&format!("Couldn't open datafile: {:?}", config_file)),
    fs::metadata(&config_file).expect("Couldn't access datafile metadata")
    )
}

fn get_periods() -> Vec<Period> {
    let config_folder = get_config_folder();

    match fs::create_dir(&config_folder) {
        Err(ref error) if error.kind() == ErrorKind::AlreadyExists => {},
        Err(error) => panic!("There was a problem creating the config directory: {:?}", error),
        Ok(_) => {},
    }

    let (file, metadata) = get_config_file();

    let periods: Result<Vec<Period>, Error> = serde_json::from_reader(file);
    let periods = match periods {
        Ok(p) => p,
        Err(_) if metadata.len() > 0 => Vec::new(),
        Err(error) => panic!("There was a serialization issue: {:?}", error),
    };
    periods
}

fn save_periods(periods: Vec<Period>) {
    let serialized = serde_json::to_string(&periods).expect("Couldn't serialize data to string");
    let config_file = get_config_file_path();
    let config_file_backup = get_config_back_file_path();
    fs::copy(&config_file, &config_file_backup).expect("Couldn't create backup file");
    let mut file = OpenOptions::new()
                    .create(true)
                    .write(true)
                    .truncate(true)
                    .open(&config_file)
                    .expect("Couldn't open file for saving period.");

    file.write_all(serialized.as_bytes()).expect("Couldn't write serialized data to file");
}
<|endoftext|>


<commit_before>#[macro_use]
extern crate websocat;

extern crate futures;
extern crate tokio_core;
extern crate tokio_stdin_stdout;

extern crate env_logger;

#[macro_use]
extern crate structopt;

use structopt::StructOpt;

use tokio_core::reactor::Core;

use websocat::{spec, Options, SpecifierClass, WebsocatConfiguration};

type Result<T> = std::result::Result<T, Box<std::error::Error>>;

#[derive(StructOpt, Debug)]
#[structopt(
    after_help = "
Basic examples:
  Connect stdin/stdout to a websocket:
    websocat - ws://echo.websocket.org/

  Listen websocket and redirect it to a TCP port:
    websocat ws-l:127.0.0.1:8080 tcp:127.0.0.1:5678

  See more examples with the --long-help option

Short list of specifiers (see --long-help):
  ws:// wss:// - inetd: ws-listen: inetd-ws: tcp: tcp-l: ws-c:
  autoreconnect: reuse: mirror: threadedstdio: clogged:
  literal: literalreply: assert: udp-connect: open-async:
  readfile: writefile: open-fd: unix-connect: unix-listen:
  unix-dgram: abstract-connect: abstract-listen:
  exec: sh-c:
"
)]
struct Opt {
    /// First, listening/connecting specifier. See --long-help for info about specifiers.
    s1: String,
    /// Second, connecting specifier
    s2: String,

    #[structopt(
        short = "u",
        long = "unidirectional",
        help = "Inhibit copying data from right specifier to left"
    )]
    unidirectional: bool,
    #[structopt(
        short = "U",
        long = "unidirectional-reverse",
        help = "Inhibit copying data from left specifier to right"
    )]
    unidirectional_reverse: bool,

    #[structopt(
        long = "exit-on-eof",
        short = "E",
        help = "Close a data transfer direction if the other one reached EOF"
    )]
    exit_on_eof: bool,

    #[structopt(
        short = "t", long = "text", help = "Send text WebSocket messages instead of binary"
    )]
    websocket_text_mode: bool,

    #[structopt(long = "oneshot", help = "Serve only once. Not to be confused with -1 (--one-message)")]
    oneshot: bool,

    #[structopt(long = "long-help", help = "Show full help aboput specifiers and examples")]
    longhelp: bool,

    #[structopt(
        long = "dump-spec",
        help = "Instead of running, dump the specifiers representation to stdout"
    )]
    dumpspec: bool,

    #[structopt(long = "protocol", help = "Specify Sec-WebSocket-Protocol: header")]
    websocket_protocol: Option<String>,

    #[structopt(long = "udp-oneshot", help = "udp-listen: replies only one packet per client")]
    udp_oneshot_mode: bool,

    #[structopt(long = "unlink", help = "Unlink listening UNIX socket before binding to it")]
    unlink_unix_socket: bool,

    #[structopt(
        long = "exec-args",
        raw(allow_hyphen_values = r#"true"#),
        help = "Arguments for the `exec:` specifier. Must be the last option, everything after it gets into the exec args list."
    )]
    exec_args: Vec<String>,

    #[structopt(
        long = "ws-c-uri", help = "URI to use for ws-c: specifier", default_value = "ws://0.0.0.0/"
    )]
    ws_c_uri: String,

    #[structopt(
        long = "linemode-retain-newlines",
        help = "In --line mode, don't chop off trailing \\n from messages"
    )]
    linemode_retain_newlines: bool,

    #[structopt(
        short = "-l", long = "--line", help = "Make each WebSocket message correspond to one line"
    )]
    linemode: bool,

    #[structopt(long="origin",help="Add Origin HTTP header to websocket client request")]
    origin: Option<String>,

    #[structopt(
        long="header",
        short="-H",
        help="Add custom HTTP header to websocket client request. Separate header name and value with a colon and optionally a single space. Can be used multiple times.",
        parse(try_from_str="interpret_custom_header"),
    )]
    custom_headers: Vec<(String,Vec<u8>)>,

    #[structopt(long="websocket-version", help="Override the Sec-WebSocket-Version value")]
    websocket_version: Option<String>,

    #[structopt(long="no-close", help="Don't send Close message to websocket on EOF")]
    websocket_dont_close: bool,

    #[structopt(
        short="1",
        long="one-message",
        help="Send and/or receive only one message. Use with --no-close and/or -u/-U.",
    )]
    one_message : bool,

    // TODO: -v --quiet
}

// TODO: make it byte-oriented/OsStr?
fn interpret_custom_header(x:&str) -> Result<(String,Vec<u8>)> {
    let colon = x.find(':');
    let colon = if let Some(colon) = colon { colon } else {
        Err("Argument to --header must contain `:` character")?
    };
    let hn = &x[0..colon];
    let mut hv = &x[colon+1..];
    if hv.chars().next() == Some(' ') {
        hv = &x[colon+2..];
    }
    Ok((hn.to_owned(), hv.as_bytes().to_vec()))
}

fn longhelp() {
    println!(
        r#"(see also the usual --help message)

Positional arguments to websocat are generally called specifiers.
Specifiers are ways to obtain a connection from some string representation (i.e. address).

Specifiers may be argumentless (like `mirror:`), can accept an argument (which
may be some path or socket address, like `tcp:`), or can accept a subspecifier
(like `reuse:` or `autoreconnect:`).

Here is the full list of specifier classes in this WebSocat build:

"#
    );

    fn help1(sc: &SpecifierClass) {
        let n = sc.get_name().replace("Class", "");
        let prefixes = sc
            .get_prefixes()
            .iter()
            .map(|x| format!("`{}`", x))
            .collect::<Vec<_>>()
            .join(", ");
        println!("### {}\n\n* {}", n, prefixes);

        let help =
            sc
            .help()
            //.lines()
            //.map(|x|format!("    {}",x))
            //.collect::<Vec<_>>()
            //.join("\n")
            ;
        println!("{}\n", help);
    }

    macro_rules! my {
        ($x:expr) => {
            help1(&$x);
        };
    }

    list_of_all_specifier_classes!(my);

    println!(
        r#"

TODO:
  sctp:
  ssl:

Final example just for fun: wacky mode

    websocat ws-c:ws-l:ws-c:- tcp:127.0.0.1:5678

Connect to a websocket using stdin/stdout as a transport,
then accept a websocket connection over the previous websocket used as a transport,
then connect to a websocket using previous step as a transport,
then forward resulting connection to the TCP port.

(Excercise to the reader: manage to make it actually connect to 5678).
"#
    );
}

fn run() -> Result<()> {
    if std::env::args().nth(1).unwrap_or_default() == "--long-help" {
        longhelp();
        return Ok(());
    }

    let cmd = Opt::from_args();

    if cmd.longhelp {
        longhelp();
        return Ok(());
    }

    if false
    //    || cmd.oneshot
    {
        Err("This mode is not implemented")?
    }

    let opts = {
        macro_rules! opts {
            ($($o:ident)*) => {
                Options {
                    $($o : cmd.$o,)*
                }
            };
        }
        opts!(
            websocket_text_mode
            websocket_protocol
            udp_oneshot_mode
            unidirectional
            unidirectional_reverse
            exit_on_eof
            oneshot
            unlink_unix_socket
            exec_args
            ws_c_uri
            linemode_retain_newlines
            origin
            custom_headers
            websocket_version
            websocket_dont_close
            one_message
        )
    };

    let s1 = spec(&cmd.s1)?;
    let s2 = spec(&cmd.s2)?;

    let mut websocat = WebsocatConfiguration { opts, s1, s2 };

    if cmd.linemode {
        use websocat::lints::AutoInstallLinemodeConcern::*;
        websocat = match websocat.auto_install_linemode() {
            Ok(x) => x,
            Err((NoWebsocket,_)) => Err("No websocket usage is specified. Use line2msg: and msg2line: specifiers manually if needed.")?,
            Err((MultipleWebsocket,_)) => Err("Multiple websocket usages are specified. Use line2msg: and msg2line: specifiers manually if needed.")?,
            Err((AlreadyLine,_)) => Err("Can't auto-insert msg2line:/line2msg: if you have already manually specified some of them")?,
        }
    }

    while let Some(concern) = websocat.get_concern() {
        use websocat::lints::ConfigurationConcern::*;
        if concern == StdinToStdout {
            if cmd.dumpspec {
                println!("cat mode");
                return Ok(());
            }

            // Degenerate mode: just copy stdin to stdout and call it a day
            ::std::io::copy(&mut ::std::io::stdin(), &mut ::std::io::stdout())?;
            return Ok(());
        }

        if concern == DegenerateMode {
            if cmd.dumpspec {
                println!("noop");
            }
            return Ok(());
        }

        if concern == StdioConflict {
            Err("Too many usages of stdin/stdout")?;
        }

        if concern == NeedsStdioReuser {
            eprintln!("Warning: replies on stdio get directed at random connected client");
            websocat = websocat.auto_install_reuser();
            continue;
        }

        if concern == NeedsStdioReuser2 {
            websocat = websocat.auto_install_reuser();
            continue;
        }

        if concern == MultipleReusers {
            eprintln!("Specifier dump: {:?} {:?}", websocat.s1, websocat.s2);
            Err("Multiple reusers is not allowed")?;
        }
        break;
    }

    if cmd.dumpspec {
        println!("{:?}", websocat.s1);
        println!("{:?}", websocat.s2);
        println!("{:?}", websocat.opts);
        return Ok(());
    }

    let mut core = Core::new()?;

    let prog = websocat.serve(
        core.handle(),
        std::rc::Rc::new(|e| {
            eprintln!("websocat: {}", e);
        }),
    );
    core.run(prog).map_err(|()| "error running".to_string())?;
    Ok(())
}

fn main() {
    env_logger::init();
    let r = run();

    if let Err(e) = r {
        eprintln!("websocat: {}", e);
        ::std::process::exit(1);
    }
}
<commit_msg>Short option -n<commit_after>#[macro_use]
extern crate websocat;

extern crate futures;
extern crate tokio_core;
extern crate tokio_stdin_stdout;

extern crate env_logger;

#[macro_use]
extern crate structopt;

use structopt::StructOpt;

use tokio_core::reactor::Core;

use websocat::{spec, Options, SpecifierClass, WebsocatConfiguration};

type Result<T> = std::result::Result<T, Box<std::error::Error>>;

#[derive(StructOpt, Debug)]
#[structopt(
    after_help = "
Basic examples:
  Connect stdin/stdout to a websocket:
    websocat - ws://echo.websocket.org/

  Listen websocket and redirect it to a TCP port:
    websocat ws-l:127.0.0.1:8080 tcp:127.0.0.1:5678

  See more examples with the --long-help option

Short list of specifiers (see --long-help):
  ws:// wss:// - inetd: ws-listen: inetd-ws: tcp: tcp-l: ws-c:
  autoreconnect: reuse: mirror: threadedstdio: clogged:
  literal: literalreply: assert: udp-connect: open-async:
  readfile: writefile: open-fd: unix-connect: unix-listen:
  unix-dgram: abstract-connect: abstract-listen:
  exec: sh-c:
"
)]
struct Opt {
    /// First, listening/connecting specifier. See --long-help for info about specifiers.
    s1: String,
    /// Second, connecting specifier
    s2: String,

    #[structopt(
        short = "u",
        long = "unidirectional",
        help = "Inhibit copying data from right specifier to left"
    )]
    unidirectional: bool,
    #[structopt(
        short = "U",
        long = "unidirectional-reverse",
        help = "Inhibit copying data from left specifier to right"
    )]
    unidirectional_reverse: bool,

    #[structopt(
        long = "exit-on-eof",
        short = "E",
        help = "Close a data transfer direction if the other one reached EOF"
    )]
    exit_on_eof: bool,

    #[structopt(
        short = "t", long = "text", help = "Send text WebSocket messages instead of binary"
    )]
    websocket_text_mode: bool,

    #[structopt(long = "oneshot", help = "Serve only once. Not to be confused with -1 (--one-message)")]
    oneshot: bool,

    #[structopt(long = "long-help", help = "Show full help aboput specifiers and examples")]
    longhelp: bool,

    #[structopt(
        long = "dump-spec",
        help = "Instead of running, dump the specifiers representation to stdout"
    )]
    dumpspec: bool,

    #[structopt(long = "protocol", help = "Specify Sec-WebSocket-Protocol: header")]
    websocket_protocol: Option<String>,

    #[structopt(long = "udp-oneshot", help = "udp-listen: replies only one packet per client")]
    udp_oneshot_mode: bool,

    #[structopt(long = "unlink", help = "Unlink listening UNIX socket before binding to it")]
    unlink_unix_socket: bool,

    #[structopt(
        long = "exec-args",
        raw(allow_hyphen_values = r#"true"#),
        help = "Arguments for the `exec:` specifier. Must be the last option, everything after it gets into the exec args list."
    )]
    exec_args: Vec<String>,

    #[structopt(
        long = "ws-c-uri", help = "URI to use for ws-c: specifier", default_value = "ws://0.0.0.0/"
    )]
    ws_c_uri: String,

    #[structopt(
        long = "linemode-retain-newlines",
        help = "In --line mode, don't chop off trailing \\n from messages"
    )]
    linemode_retain_newlines: bool,

    #[structopt(
        short = "-l", long = "--line", help = "Make each WebSocket message correspond to one line"
    )]
    linemode: bool,

    #[structopt(long="origin",help="Add Origin HTTP header to websocket client request")]
    origin: Option<String>,

    #[structopt(
        long="header",
        short="H",
        help="Add custom HTTP header to websocket client request. Separate header name and value with a colon and optionally a single space. Can be used multiple times.",
        parse(try_from_str="interpret_custom_header"),
    )]
    custom_headers: Vec<(String,Vec<u8>)>,

    #[structopt(long="websocket-version", help="Override the Sec-WebSocket-Version value")]
    websocket_version: Option<String>,

    #[structopt(long="no-close", short="n", help="Don't send Close message to websocket on EOF")]
    websocket_dont_close: bool,

    #[structopt(
        short="1",
        long="one-message",
        help="Send and/or receive only one message. Use with --no-close and/or -u/-U.",
    )]
    one_message : bool,

    // TODO: -v --quiet
}

// TODO: make it byte-oriented/OsStr?
fn interpret_custom_header(x:&str) -> Result<(String,Vec<u8>)> {
    let colon = x.find(':');
    let colon = if let Some(colon) = colon { colon } else {
        Err("Argument to --header must contain `:` character")?
    };
    let hn = &x[0..colon];
    let mut hv = &x[colon+1..];
    if hv.chars().next() == Some(' ') {
        hv = &x[colon+2..];
    }
    Ok((hn.to_owned(), hv.as_bytes().to_vec()))
}

fn longhelp() {
    println!(
        r#"(see also the usual --help message)

Positional arguments to websocat are generally called specifiers.
Specifiers are ways to obtain a connection from some string representation (i.e. address).

Specifiers may be argumentless (like `mirror:`), can accept an argument (which
may be some path or socket address, like `tcp:`), or can accept a subspecifier
(like `reuse:` or `autoreconnect:`).

Here is the full list of specifier classes in this WebSocat build:

"#
    );

    fn help1(sc: &SpecifierClass) {
        let n = sc.get_name().replace("Class", "");
        let prefixes = sc
            .get_prefixes()
            .iter()
            .map(|x| format!("`{}`", x))
            .collect::<Vec<_>>()
            .join(", ");
        println!("### {}\n\n* {}", n, prefixes);

        let help =
            sc
            .help()
            //.lines()
            //.map(|x|format!("    {}",x))
            //.collect::<Vec<_>>()
            //.join("\n")
            ;
        println!("{}\n", help);
    }

    macro_rules! my {
        ($x:expr) => {
            help1(&$x);
        };
    }

    list_of_all_specifier_classes!(my);

    println!(
        r#"

TODO:
  sctp:
  ssl:

Final example just for fun: wacky mode

    websocat ws-c:ws-l:ws-c:- tcp:127.0.0.1:5678

Connect to a websocket using stdin/stdout as a transport,
then accept a websocket connection over the previous websocket used as a transport,
then connect to a websocket using previous step as a transport,
then forward resulting connection to the TCP port.

(Excercise to the reader: manage to make it actually connect to 5678).
"#
    );
}

fn run() -> Result<()> {
    if std::env::args().nth(1).unwrap_or_default() == "--long-help" {
        longhelp();
        return Ok(());
    }

    let cmd = Opt::from_args();

    if cmd.longhelp {
        longhelp();
        return Ok(());
    }

    if false
    //    || cmd.oneshot
    {
        Err("This mode is not implemented")?
    }

    let opts = {
        macro_rules! opts {
            ($($o:ident)*) => {
                Options {
                    $($o : cmd.$o,)*
                }
            };
        }
        opts!(
            websocket_text_mode
            websocket_protocol
            udp_oneshot_mode
            unidirectional
            unidirectional_reverse
            exit_on_eof
            oneshot
            unlink_unix_socket
            exec_args
            ws_c_uri
            linemode_retain_newlines
            origin
            custom_headers
            websocket_version
            websocket_dont_close
            one_message
        )
    };

    let s1 = spec(&cmd.s1)?;
    let s2 = spec(&cmd.s2)?;

    let mut websocat = WebsocatConfiguration { opts, s1, s2 };

    if cmd.linemode {
        use websocat::lints::AutoInstallLinemodeConcern::*;
        websocat = match websocat.auto_install_linemode() {
            Ok(x) => x,
            Err((NoWebsocket,_)) => Err("No websocket usage is specified. Use line2msg: and msg2line: specifiers manually if needed.")?,
            Err((MultipleWebsocket,_)) => Err("Multiple websocket usages are specified. Use line2msg: and msg2line: specifiers manually if needed.")?,
            Err((AlreadyLine,_)) => Err("Can't auto-insert msg2line:/line2msg: if you have already manually specified some of them")?,
        }
    }

    while let Some(concern) = websocat.get_concern() {
        use websocat::lints::ConfigurationConcern::*;
        if concern == StdinToStdout {
            if cmd.dumpspec {
                println!("cat mode");
                return Ok(());
            }

            // Degenerate mode: just copy stdin to stdout and call it a day
            ::std::io::copy(&mut ::std::io::stdin(), &mut ::std::io::stdout())?;
            return Ok(());
        }

        if concern == DegenerateMode {
            if cmd.dumpspec {
                println!("noop");
            }
            return Ok(());
        }

        if concern == StdioConflict {
            Err("Too many usages of stdin/stdout")?;
        }

        if concern == NeedsStdioReuser {
            eprintln!("Warning: replies on stdio get directed at random connected client");
            websocat = websocat.auto_install_reuser();
            continue;
        }

        if concern == NeedsStdioReuser2 {
            websocat = websocat.auto_install_reuser();
            continue;
        }

        if concern == MultipleReusers {
            eprintln!("Specifier dump: {:?} {:?}", websocat.s1, websocat.s2);
            Err("Multiple reusers is not allowed")?;
        }
        break;
    }

    if cmd.dumpspec {
        println!("{:?}", websocat.s1);
        println!("{:?}", websocat.s2);
        println!("{:?}", websocat.opts);
        return Ok(());
    }

    let mut core = Core::new()?;

    let prog = websocat.serve(
        core.handle(),
        std::rc::Rc::new(|e| {
            eprintln!("websocat: {}", e);
        }),
    );
    core.run(prog).map_err(|()| "error running".to_string())?;
    Ok(())
}

fn main() {
    env_logger::init();
    let r = run();

    if let Err(e) = r {
        eprintln!("websocat: {}", e);
        ::std::process::exit(1);
    }
}
<|endoftext|>


<commit_before>extern crate getopts;
extern crate rustc_serialize;
#[macro_use] extern crate hyper;

use getopts::Options;
use rustc_serialize::json::{ToJson, Json};
use std::collections::BTreeMap;
use hyper::Client;
use hyper::header::Headers;
use hyper::header::ContentType;
header! { (OAuthToken, "Authorization") => [String] }

use std::env;
use std::process;
use std::io::Read;
use std::path::Path;
use std::fs::File;

const GIST_API: &'static str = "https://api.github.com/gists";
const GITHUB_TOKEN: &'static str = "GITHUB_TOKEN";

struct Gist {
    filename: String,
    private:  bool,
    contents: String,
}
type Gists = Vec<Gist>;

impl Gist {
    fn new(filename: String, public: bool) -> Gist {
        Gist {
            private:  !public,
            filename: filename,
            contents: String::new(),
        }
    }

    // Read standard input to contents buffer.
    fn read_stdin(&mut self) -> std::io::Result<usize> {
        std::io::stdin().read_to_string(&mut self.contents)
    }

    // Read file to contents buffer.
    fn read_file(&mut self) -> std::io::Result<usize> {
        let path = Path::new(&self.filename);
        let mut fh = File::open(&path).unwrap();

        fh.read_to_string(&mut self.contents)
    }
}

impl ToJson for Gist {
    fn to_json(&self) -> Json {
        let mut root = BTreeMap::new();
        root.insert("content".to_string(), self.contents.to_json());
        Json::Object(root)
    }
}

// Convert a Gist vector to JSON, suitable for Github's Gist API.
fn gists_to_json(gists : Gists) -> Json {
    let mut root  = BTreeMap::new();
    let mut files = BTreeMap::new();

    root.insert("public".to_string(), (!gists[0].private).to_json());
    for g in gists {
        files.insert(g.filename.clone(), g);
    }
    root.insert("files".to_string(), files.to_json());

    Json::Object(root)
}

fn send_gists(gists : Gists) {
    let mut client = Client::new();

    let mut auth = Headers::new();
    let key : String = env::var(&GITHUB_TOKEN.to_string()).unwrap();
    auth.set(OAuthToken(format!("token {}", key)));

    let json = gists_to_json(gists).to_string();
    let res = client.post(&GIST_API.to_string())
        .header(ContentType::json())
        .headers(auth)
        .body(json.as_bytes())
        .send()
        .unwrap();
    println!("{:?}", res);
    assert_eq!(res.status, hyper::status::StatusCode::Created);
}

fn print_usage(program: &str, opts: Options) {
    let brief = format!("Usage: {} [options]", program);
    print!("{}", opts.usage(&brief));
}

fn parse_args(args : Vec<String>) -> getopts::Matches {
    let mut opts = Options::new();

    opts.optopt("f", "file", "set file name", "NAME");
    opts.optflag("p", "public", "make public");
    opts.optflag("h", "help", "print this help menu");
    let params = match opts.parse(&args[1..]) {
        Ok(m)  => { m }
        Err(f) => { panic!(f.to_string()) }
    };
    if params.opt_present("h") {
        print_usage(&args[0].clone(), opts);
        process::exit(1);
    }

    params
}

fn main() {
    let params = parse_args(env::args().collect());
    let public = params.opt_present("p");
    let opt_name = match params.opt_str("f") {
        Some(name) => { name }
        None       => { "Untitled".to_string() }
    };
    let mut gists: Gists = vec![];

    // If we receive filenames, read them, else use STDIN.
    if !params.free.is_empty() {
        for file_param in params.free {
            let mut g = Gist::new(file_param, public);
            g.read_file().unwrap();
            gists.push(g);
        }
    } else {
        let mut gist = Gist::new(opt_name, public);
        gist.read_stdin().unwrap();
        gists.push(gist);
    }
    send_gists(gists);
}
<commit_msg>Match on hyper response to get a stack trace<commit_after>extern crate getopts;
extern crate rustc_serialize;
#[macro_use] extern crate hyper;

use getopts::Options;
use rustc_serialize::json::{ToJson, Json};
use std::collections::BTreeMap;
use hyper::Client;
use hyper::header::Headers;
use hyper::header::ContentType;
header! { (OAuthToken, "Authorization") => [String] }

use std::env;
use std::process;
use std::io::Read;
use std::path::Path;
use std::fs::File;

const GIST_API: &'static str = "https://api.github.com/gists";
const GITHUB_TOKEN: &'static str = "GITHUB_TOKEN";

struct Gist {
    filename: String,
    private:  bool,
    contents: String,
}
type Gists = Vec<Gist>;

impl Gist {
    fn new(filename: String, public: bool) -> Gist {
        Gist {
            private:  !public,
            filename: filename,
            contents: String::new(),
        }
    }

    // Read standard input to contents buffer.
    fn read_stdin(&mut self) -> std::io::Result<usize> {
        std::io::stdin().read_to_string(&mut self.contents)
    }

    // Read file to contents buffer.
    fn read_file(&mut self) -> std::io::Result<usize> {
        let path = Path::new(&self.filename);
        let mut fh = File::open(&path).unwrap();

        fh.read_to_string(&mut self.contents)
    }
}

impl ToJson for Gist {
    fn to_json(&self) -> Json {
        let mut root = BTreeMap::new();
        root.insert("content".to_string(), self.contents.to_json());
        Json::Object(root)
    }
}

// Convert a Gist vector to JSON, suitable for Github's Gist API.
fn gists_to_json(gists : Gists) -> Json {
    let mut root  = BTreeMap::new();
    let mut files = BTreeMap::new();

    root.insert("public".to_string(), (!gists[0].private).to_json());
    for g in gists {
        files.insert(g.filename.clone(), g);
    }
    root.insert("files".to_string(), files.to_json());

    Json::Object(root)
}

fn send_gists(gists : Gists) {
    let mut client = Client::new();
    let mut auth = Headers::new();
    let token : String = env::var(&GITHUB_TOKEN.to_string()).unwrap();
    auth.set(OAuthToken(format!("token {}", token)));

    let json = gists_to_json(gists).to_string();
    let res = client.post(&GIST_API.to_string())
        .header(ContentType::json())
        .headers(auth)
        .body(json.as_bytes())
        .send();
    match res {
        Ok(value)  => { println!("{:?}", value) }
        Err(value) => { panic!(value.to_string()) }
    }
}

fn print_usage(program: &str, opts: Options) {
    let brief = format!("Usage: {} [options]", program);
    print!("{}", opts.usage(&brief));
}

fn parse_args(args : Vec<String>) -> getopts::Matches {
    let mut opts = Options::new();

    opts.optopt("f", "file", "set file name", "NAME");
    opts.optflag("p", "public", "make public");
    opts.optflag("h", "help", "print this help menu");
    let params = match opts.parse(&args[1..]) {
        Ok(m)  => { m }
        Err(f) => { panic!(f.to_string()) }
    };
    if params.opt_present("h") {
        print_usage(&args[0].clone(), opts);
        process::exit(1);
    }

    params
}

fn main() {
    let params = parse_args(env::args().collect());
    let public = params.opt_present("p");
    let opt_name = match params.opt_str("f") {
        Some(name) => { name }
        None       => { "Untitled".to_string() }
    };
    let mut gists: Gists = vec![];

    // If we receive filenames, read them, else use STDIN.
    if !params.free.is_empty() {
        for file_param in params.free {
            let mut g = Gist::new(file_param, public);
            g.read_file().unwrap();
            gists.push(g);
        }
    } else {
        let mut gist = Gist::new(opt_name, public);
        gist.read_stdin().unwrap();
        gists.push(gist);
    }
    send_gists(gists);
}
<|endoftext|>


<commit_before>#![allow(non_snake_case)]

extern crate getopts;
extern crate jni;
extern crate rustc_serialize;

#[cfg(target_os = "macos")]
extern crate libc;

use getopts::Options;
use jni::JNI;
use jni::classpath::load_static_method;
use jni::consts::*;
use jni::ffi;
use jni::ffi::JNIEnv;
use jni::types as jni_types;
use jni::types::{Jclass, Jint};
use rustc_serialize::json;
use std::env;
use std::io::prelude::*;
use std::fs::File;
use std::path::{Path, PathBuf};

#[cfg(target_os = "macos")]
use libc::{c_int};
#[cfg(target_os = "macos")]
use std::sync::mpsc::{channel, Receiver, Sender};
#[cfg(target_os = "macos")]
use std::thread;

#[derive(RustcDecodable)]
struct Config {
    jar: String,
    mainClass: String,
    vmArgs: Vec<String>
}

#[cfg(target_os = "macos")]
fn get_root_path(program:&str) -> &Path {
    Path::new(program).parent().unwrap()
}

#[cfg(not(target_os = "macos"))]
fn get_root_path(program:&str) -> &Path {
    env::current_dir().unwrap()
}

#[cfg(target_os = "macos")]
fn get_libjvm_path_os(path: &mut PathBuf) {
    path.push("lib");
    path.push("jli");
    path.push("libjli");
    path.set_extension("dylib");
}

#[cfg(target_os = "linux")]
fn get_libjvm_path_os(path: &mut PathBuf) {
    path.push("lib");
    path.push("amd64");
    path.push("server");
    path.push("libjvm");
    path.set_extension("so");
}

#[cfg(target_os = "windows")]
fn get_libjvm_path_os(path: &mut PathBuf) {
    path.push("bin");
    path.push("server");
    path.push("jvm");
    path.set_extension("dll");
}

fn get_libjvm_path(jre: &Path) -> PathBuf {
    let mut path = PathBuf::new();
    path.push(jre);
    get_libjvm_path_os(&mut path);
    path
}

fn init_jvm_arguments(jni: &mut JNI, config: &Config) {
    let num_args = config.vmArgs.len();

    jni.init_vm_args(num_args);

    for i in 0..num_args {
        let ref vm_arg = config.vmArgs[i];
        jni.push_vm_arg(i, &vm_arg);
    }
}

fn load_jvm(jni: &mut JNI, config: &Config) {
    // create & fill arguments
    init_jvm_arguments(jni, config);

    // load lib, create VM instance
    match jni.create_java_vm() {
        Err(err) => panic!(err),
        Ok(_) => {}
    };

    // attach to current thread
    match ffi::attach_current_thread(jni.get_jvm()) {
        (JNI_OK, _) => {},
        (_, _) => panic!("Could not attach JVM to thread")
    }
    println!("JVM attached to thread ...");
}

fn check_for_exceptions(env: &mut JNIEnv) {
    let throwable = ffi::exception_occured(env);
    if !jni_types::is_null(throwable) {
        ffi::exception_describe(env);
        ffi::exception_clear(env);
        panic!("Exception caught!");
    }
}

fn call_main(env: &mut JNIEnv, path_to_jar: &str, main_class_name: &str, args: &Vec<String>) {

    // do class-loader voodoo

    let (main_class, main_method) = load_static_method(env, path_to_jar, main_class_name);

    match (main_class, main_method) {
        (JNI_NULL, JNI_NULL) => {
            println!("Could not find {}.main()", main_class_name);
            return;
        },
        (_, _) => {}
    };

    // pass program arguments

    let java_lang_String:Jclass = ffi::find_class(env, "java/lang/String");
    check_for_exceptions(env);
    assert!(!jni_types::is_null(java_lang_String));

    let argc = args.len();

    let argv = ffi::new_object_array(env, argc as Jint, java_lang_String, JNI_NULL);
    check_for_exceptions(env);
    assert!(!jni_types::is_null(argv));

    for i in 0..argc {
        println!("Application argument: {}", &args[i]);
        let arg = ffi::new_string_utf(env, &args[i]);
        ffi::set_object_array_element(env, argv, i as Jint, arg);
    }

    // call main()
    ffi::call_static_void_method_a(env, main_class, main_method, &[argv]);

    check_for_exceptions(env);

    println!("Quit from JVM ...");
}

fn read_config(path: &Path) -> Config {
    let mut f = match File::open(path) {
        Ok(f) => f,
        Err(_) => panic!("Could not open {}", path.display())
    };

    let mut content = String::new();

    if f.read_to_string(&mut content).is_err() {
        panic!("Error reading {}", path.display());
    }

    let config:Config = json::decode(&content).unwrap();

    println!("jar: {}", config.jar);
    println!("main class: {}", config.mainClass);

    for arg in config.vmArgs.iter() {
        println!("VM argument: {}", arg);
    }

    config
}

fn print_usage(program: &str, opts: Options) {
    let brief = format!("Usage: {} [options]", program);
    print!("{}", opts.usage(&brief));
    //println!("-- {{args}}\tUnparsed arguments passed through to Java main() method.");
}

fn spawn_vm() {
    let args: Vec<String> = env::args().collect();

    let program = args[0].clone();
    println!("application: {}", program);

    let mut opts = Options::new();
    opts.optflag("h", "help", "print this help menu");

    let matches = match opts.parse(&args[1..]) {
        Ok(m) => { m }
        Err(f) => {
            println!("{} Run {} --help to show options.", f.to_string(), program);
            return;
        }
    };

    if matches.opt_present("h") {
        print_usage(&program, opts);
        return;
    }

    let root_path = get_root_path(&program);
    println!("current directory: {}", root_path.display());

    // change working dir (MacOS: starts at parent folder of .app)
    if env::set_current_dir(&root_path).is_err() {
        panic!("Could not change working directory");
    }

    let libjvmpath = get_libjvm_path(root_path.join("jre").as_path());
    println!("JRE path: {}", libjvmpath.display());

    // read config JSON
    let config = read_config(root_path.join("config.json").as_path());

    let cp_path = Path::new(&config.jar);
    let class_path = format!("{}", cp_path.display());
    println!("class path: {}", class_path);

    println!("Loading JVM library ...");
    let mut jni:JNI = match JNI::new(&libjvmpath) {
        Err(error) => panic!(error),
        Ok(jni) => jni
    };

    println!("JNI initialized.");

    println!("Creating JVM instance ...");
    load_jvm(&mut jni, &config);

    println!("Invoking {}.main()", config.mainClass);
    let env:&mut JNIEnv = jni.get_env();
    call_main(env, &class_path, &config.mainClass, &matches.free);
}

#[cfg(target_os = "macos")]
#[link(name = "packrnative", kind = "static")]
#[link(name = "CoreFoundation", kind = "framework")]
#[link(name = "CoreServices", kind = "framework")]
extern {
    fn cfRunLoopRun(callback: extern fn(&Receiver<c_int>), signal:&Receiver<c_int>);
    fn cfRunLoopStop();
}

#[cfg(target_os = "macos")]
extern fn run_loop_callback(signal:&Receiver<c_int>) {
    match signal.try_recv() {
        Err(_) => {},
        Ok(_) => unsafe {
            cfRunLoopStop();
        }
    }
}

#[cfg(target_os = "macos")]
fn main() {

    let (tx, rx): (Sender<c_int>, Receiver<c_int>) = channel();
    let proc_tx = tx.clone();

    thread::spawn(move|| {
        spawn_vm();
        proc_tx.send(0).unwrap();
    });

    unsafe {
        cfRunLoopRun(run_loop_callback, &rx);
    }
}

#[cfg(not(target_os = "macos"))]
fn main() {
    spawn_vm();
}
<commit_msg>Minor cleanup.<commit_after>#![allow(non_snake_case)]

extern crate getopts;
extern crate jni;
extern crate rustc_serialize;

#[cfg(target_os = "macos")]
extern crate libc;

use getopts::Options;
use jni::JNI;
use jni::classpath::load_static_method;
use jni::consts::*;
use jni::ffi;
use jni::ffi::JNIEnv;
use jni::types as jni_types;
use jni::types::{Jclass, Jint};
use rustc_serialize::json;
use std::env;
use std::io::prelude::*;
use std::fs::File;
use std::path::{Path, PathBuf};

#[cfg(target_os = "macos")]
use libc::{c_int};
#[cfg(target_os = "macos")]
use std::sync::mpsc::{channel, Receiver, Sender};
#[cfg(target_os = "macos")]
use std::thread;

#[derive(RustcDecodable)]
struct Config {
    jar: String,
    mainClass: String,
    vmArgs: Vec<String>
}

#[cfg(target_os = "macos")]
fn select_root_path(program: &str) {
    let program_path = Path::new(&program).parent().unwrap();
    println!("executable: {}", program_path.display());

    if env::set_current_dir(&program_path).is_err() {
        panic!("Could not change working directory");
    }
}

#[cfg(not(target_os = "macos"))]
#[allow(unused_variables)]
fn select_root_path(program: &str) {
}

#[cfg(target_os = "macos")]
fn get_libjvm_path_os(path: &mut PathBuf) {
    path.push("lib");
    path.push("jli");
    path.push("libjli");
    path.set_extension("dylib");
}

#[cfg(target_os = "linux")]
fn get_libjvm_path_os(path: &mut PathBuf) {
    path.push("lib");
    path.push("amd64");
    path.push("server");
    path.push("libjvm");
    path.set_extension("so");
}

#[cfg(target_os = "windows")]
fn get_libjvm_path_os(path: &mut PathBuf) {
    path.push("bin");
    path.push("server");
    path.push("jvm");
    path.set_extension("dll");
}

fn get_libjvm_path(jre: &Path) -> PathBuf {
    let mut path = PathBuf::new();
    path.push(jre);
    get_libjvm_path_os(&mut path);
    path
}

fn init_jvm_arguments(jni: &mut JNI, config: &Config) {
    let num_args = config.vmArgs.len();

    jni.init_vm_args(num_args);

    for i in 0..num_args {
        let ref vm_arg = config.vmArgs[i];
        jni.push_vm_arg(i, &vm_arg);
    }
}

fn load_jvm(jni: &mut JNI, config: &Config) {
    // create & fill arguments
    init_jvm_arguments(jni, config);

    // load lib, create VM instance
    match jni.create_java_vm() {
        Err(err) => panic!(err),
        Ok(_) => {}
    };

    // attach to current thread
    match ffi::attach_current_thread(jni.get_jvm()) {
        (JNI_OK, _) => {},
        (_, _) => panic!("Could not attach JVM to thread")
    }
    println!("JVM attached to thread ...");
}

fn check_for_exceptions(env: &mut JNIEnv) {
    let throwable = ffi::exception_occured(env);
    if !jni_types::is_null(throwable) {
        ffi::exception_describe(env);
        ffi::exception_clear(env);
        panic!("Exception caught!");
    }
}

fn call_main(env: &mut JNIEnv, path_to_jar: &str, main_class_name: &str, args: &Vec<String>) {

    // do class-loader voodoo

    let (main_class, main_method) = load_static_method(env, path_to_jar, main_class_name);

    match (main_class, main_method) {
        (JNI_NULL, JNI_NULL) => {
            println!("Could not find {}.main()", main_class_name);
            return;
        },
        (_, _) => {}
    };

    // pass program arguments

    let java_lang_String:Jclass = ffi::find_class(env, "java/lang/String");
    check_for_exceptions(env);
    assert!(!jni_types::is_null(java_lang_String));

    let argc = args.len();

    let argv = ffi::new_object_array(env, argc as Jint, java_lang_String, JNI_NULL);
    check_for_exceptions(env);
    assert!(!jni_types::is_null(argv));

    for i in 0..argc {
        println!("Application argument: {}", &args[i]);
        let arg = ffi::new_string_utf(env, &args[i]);
        ffi::set_object_array_element(env, argv, i as Jint, arg);
    }

    // call main()
    ffi::call_static_void_method_a(env, main_class, main_method, &[argv]);

    check_for_exceptions(env);

    println!("Quit from JVM ...");
}

fn read_config(path: &Path) -> Config {
    let mut f = match File::open(path) {
        Ok(f) => f,
        Err(_) => panic!("Could not open {}", path.display())
    };

    let mut content = String::new();

    if f.read_to_string(&mut content).is_err() {
        panic!("Error reading {}", path.display());
    }

    let config:Config = json::decode(&content).unwrap();

    println!("jar: {}", config.jar);
    println!("main class: {}", config.mainClass);

    for arg in config.vmArgs.iter() {
        println!("VM argument: {}", arg);
    }

    config
}

fn print_usage(program: &str, opts: Options) {
    let brief = format!("Usage: {} [options]", program);
    print!("{}", opts.usage(&brief));
    //println!("-- {{args}}\tUnparsed arguments passed through to Java main() method.");
}

fn spawn_vm() {
    let args: Vec<String> = env::args().collect();
    let program = args[0].clone();

    let mut opts = Options::new();
    opts.optflag("h", "help", "print this help menu");

    let matches = match opts.parse(&args[1..]) {
        Ok(m) => { m }
        Err(f) => {
            println!("{} Run {} --help to show options.", f.to_string(), program);
            return;
        }
    };

    if matches.opt_present("h") {
        print_usage(&program, opts);
        return;
    }

    select_root_path(&program);

    let root_path = env::current_dir().unwrap();
    println!("working directory: {}", root_path.display());

    let libjvmpath = get_libjvm_path(root_path.join("jre").as_path());
    println!("JRE path: {}", libjvmpath.display());

    // read config JSON
    let config = read_config(root_path.join("config.json").as_path());

    let cp_path = Path::new(&config.jar);
    let class_path = format!("{}", cp_path.display());
    println!("class path: {}", class_path);

    println!("Loading JVM library ...");
    let mut jni:JNI = match JNI::new(&libjvmpath) {
        Err(error) => panic!(error),
        Ok(jni) => jni
    };

    println!("JNI initialized.");

    println!("Creating JVM instance ...");
    load_jvm(&mut jni, &config);

    println!("Invoking {}.main()", config.mainClass);
    let env:&mut JNIEnv = jni.get_env();
    call_main(env, &class_path, &config.mainClass, &matches.free);
}

#[cfg(target_os = "macos")]
#[link(name = "packrnative", kind = "static")]
#[link(name = "CoreFoundation", kind = "framework")]
#[link(name = "CoreServices", kind = "framework")]
extern {
    fn cfRunLoopRun(callback: extern fn(&Receiver<c_int>), signal:&Receiver<c_int>);
    fn cfRunLoopStop();
}

#[cfg(target_os = "macos")]
extern fn run_loop_callback(signal:&Receiver<c_int>) {
    match signal.try_recv() {
        Err(_) => {},
        Ok(_) => unsafe {
            cfRunLoopStop();
        }
    }
}

#[cfg(target_os = "macos")]
fn main() {

    let (tx, rx): (Sender<c_int>, Receiver<c_int>) = channel();
    let proc_tx = tx.clone();

    thread::spawn(move|| {
        spawn_vm();
        proc_tx.send(0).unwrap();
    });

    unsafe {
        cfRunLoopRun(run_loop_callback, &rx);
    }
}

#[cfg(not(target_os = "macos"))]
fn main() {
    spawn_vm();
}
<|endoftext|>


<commit_before>extern crate rust_guessing_game;

use std::io;
use std::rand;

use rust_guessing_game::my_funcs;

#[cfg(not(test))]
fn main() {
    println!("Guess the magic number!");
    let magic_number = ( rand::random::<uint>() % 100u ) + 1u;
    loop {
        let input = io::stdin().read_line().ok().expect("That was bad input");
        let input_optoin_uint: Option<uint> = from_str(input.as_slice().trim());
        let input_uint = match input_optoin_uint {
            Some(num) => num,
            None      => {
                println!("Please input a number");
                continue;
            }
        };
        match my_funcs::compare(input_uint, magic_number) {
            Less    => println!("Guess higher."),
            Greater => println!("Guess lower."),
            Equal   => {
                println!("You win!");
                return
            },
        }
    }
}
<commit_msg>Include from str<commit_after>extern crate rust_guessing_game;

use std::io;
use std::rand;
use std::from_str;

use rust_guessing_game::my_funcs;

#[cfg(not(test))]
fn main() {
    println!("Guess the magic number!");
    let magic_number = ( rand::random::<uint>() % 100u ) + 1u;
    loop {
        let input = io::stdin().read_line().ok().expect("That was bad input");
        let input_optoin_uint: Option<uint> = from_str(input.as_slice().trim());
        let input_uint = match input_optoin_uint {
            Some(num) => num,
            None      => {
                println!("Please input a number");
                continue;
            }
        };
        match my_funcs::compare(input_uint, magic_number) {
            Less    => println!("Guess higher."),
            Greater => println!("Guess lower."),
            Equal   => {
                println!("You win!");
                return
            },
        }
    }
}
<|endoftext|>


<commit_before>extern crate izhikevich_neurons;
extern crate gnuplot;

use izhikevich_neurons::{Config, State};
use gnuplot::{Figure, Caption, Color, AxesCommon};

fn main() {
    let config = Config::regular_spiking();
    let mut neuron = State::new();
    let mut time = 0.0;

    let mut times = Vec::new();
    let mut potentials = Vec::new();

    while time < 1_000.0 {
        // record current state
        times.push(time);
        potentials.push(neuron.potential());
        time += 1.0;

        // update state
        let syn_i = if time >= 200.0 && time <= 700.0 { -7.0 } else { 0.0 };
        neuron = neuron.step_1ms(syn_i, &config);
    }

    let mut fg = Figure::new();
    fg.axes2d().
        set_x_label("time [ms]", &[]).
        set_y_label("potential [mV]", &[]).
        lines(times.iter(), potentials.iter(), &[Caption("Neuron potential over time"), Color("black")]);
    fg.show();
}
<commit_msg>Multiple plots<commit_after>extern crate izhikevich_neurons;
extern crate gnuplot;

use izhikevich_neurons::{Config, State, Num};
use gnuplot::{Figure, Caption, Color, AxesCommon};

fn main() {
    let config = Config::regular_spiking();

    const PARAMS : &'static [(&'static str, Num, &'static str)] = &[
        ("Neuron 1 [-7 uA current 200..700ms]", -7.0, "blue"),
        ("Neuron 2 [-2.69 uA current 200..700ms]", -2.69, "red"),
        ("Neuron 3 [-2.7 uA current 200..700ms]", -2.7, "green")
    ];

    let mut neurons: Vec<_> = PARAMS.iter().map(|_| State::new()).collect();
    let mut potentials: Vec<_> = PARAMS.iter().map(|_| Vec::new()).collect();

    let mut time = 0.0;
    let mut times = Vec::new();

    while time < 1_000.0 {
        // record current state
        times.push(time);
        for (i, &mut neuron) in neurons.iter_mut().enumerate() {
            potentials[i].push(neuron.potential());
        }
        time += 1.0;

        // update state
        for (i, mut neuron) in neurons.iter_mut().enumerate() {
            let syn_i = if time >= 200.0 && time <= 700.0 { PARAMS[i].1 } else { 0.0 };
            *neuron = neuron.step_1ms(syn_i, &config);
        }
    }

    let mut fg = Figure::new();
    {
        let mut diag = fg.axes2d().
            set_x_label("time [ms]", &[]).
            set_y_label("potential [mV]", &[]);
        for (i, &p) in PARAMS.iter().enumerate() {
            diag.lines(times.iter(), potentials[i].iter(), &[Caption(p.0), Color(p.2)]);
        }
    }
    fg.show();
}
<|endoftext|>


<commit_before>extern crate rand;

use std::io;
use std::cmp::Ordering;
use rand::Rng;

fn main() {
    println!("Guess the number!");

    let secret_number = rand::thread_rng().gen_range(1,101);
    println!("The secret number is: {}", secret_number);

    println!("Please input your guess.");

    let mut guess = String::new();

    io::stdin().read_line(&mut guess)
    	.ok()
    	.expect("Failed to read line");
    let guess: u32 = guess.trim().parse()
    	.ok()
    	.expect("Please, a number");

    println!("You guessed: {}", guess);

    match guess.cmp(&secret_number) {
    	Ordering::Less    => println!("Too small"),
    	Ordering::Greater => println!("Too big!"),
    	Ordering::Equal   => println!("You win!"),
    }
}
<commit_msg>More tweaks<commit_after>extern crate rand;

use std::io;
use std::cmp::Ordering;
use rand::Rng;

fn main() {
    println!("Guess the number!");

    let secret_number = rand::thread_rng().gen_range(1,101);
    println!("The secret number is: {}", secret_number);

    loop {
   	    println!("Please input your guess.");

	    let mut guess = String::new();

	    io::stdin().read_line(&mut guess)
	    	.ok()
	    	.expect("Failed to read line");
	    let guess: u32 = match guess.trim().parse() {
	    	Ok(num) => num,
	    	Err(_) => continue,
	    };

	    println!("You guessed: {}", guess);

	    match guess.cmp(&secret_number) {
	    	Ordering::Less    => println!("Too small"),
	    	Ordering::Greater => println!("Too big!"),
	    	Ordering::Equal   => {
	    		println!("You win!");
	    		break;
	    	}
	    }
    }
}
<|endoftext|>


<commit_before>fn main() {
    println!("Hello, world!");
}
<commit_msg>Show command line arguments in rust<commit_after>use std::os;

fn main() {
	for arg in parse_command_line(os::args()).iter() {
		println!("{}", arg);
	}
}

fn parse_command_line(args: Vec<String>) -> Vec<String> {
	let mut result: Vec<String> = Vec::new();
	for arg in args.slice(1, args.len()).iter() {
		result.push(arg.clone());
	}
	result
}
<|endoftext|>


<commit_before>#![feature(box_syntax)]

extern crate iron;
#[macro_use]
extern crate router;
extern crate mount;
extern crate staticfile;
extern crate handlebars_iron;
#[macro_use]
extern crate error_chain;
extern crate discord;
extern crate serde;
#[macro_use]
extern crate serde_derive;
extern crate serde_json;
#[macro_use]
extern crate lazy_static;
extern crate url;

mod error;
use error::*;

use iron::prelude::*;
use iron::status;

use router::Router;

use mount::Mount;

use staticfile::Static;

use handlebars_iron::{HandlebarsEngine, DirectorySource, Template};
use handlebars_iron::handlebars::*;

use discord::model::{Message, Role, Member, Emoji, ChannelId};

use url::Url;

use std::path::{Path, PathBuf};
use std::fs::File;
use std::sync::RwLock;
use std::collections::HashMap;

lazy_static! {
  static ref MESSAGES: RwLock<HashMap<u64, HashMap<u64, Archive>>> = RwLock::default();
}

fn handlebars() -> Result<HandlebarsEngine> {
  fn range(h: &Helper, r: &Handlebars, rc: &mut RenderContext) -> std::result::Result<(), RenderError> {
    let param = h.params()[0].value().as_u64().unwrap();
    for i in 0..param {
      rc.push_block_context(&(i + 1));
      h.template().map(|t| t.render(r, rc)).unwrap_or(Ok(())).unwrap();
    }
    Ok(())
  }

  fn eq(h: &Helper, r: &Handlebars, rc: &mut RenderContext) -> std::result::Result<(), RenderError> {
    let param_1 = h.params()[0].value();
    let param_2 = h.params()[1].value();
    if param_1 == param_2 {
      h.template().map(|t| t.render(r, rc)).unwrap_or(Ok(())).unwrap();
    }
    Ok(())
  }

  fn hex(h: &Helper, _: &Handlebars, rc: &mut RenderContext) -> std::result::Result<(), RenderError> {
    let param = h.params()[0].value().as_u64().unwrap();
    let hex = format!("{:x}", param);
    rc.writer().write_all(&hex.as_bytes())?;
    Ok(())
  }

  fn break_lines(h: &Helper, _: &Handlebars, rc: &mut RenderContext) -> std::result::Result<(), RenderError> {
    let param = h.params()[0].value().as_str().unwrap();
    let broken = html_escape(&param).replace("\n", "
");
    rc.writer().write_all(&broken.as_bytes())?;
    Ok(())
  }

  let mut handlebars = Handlebars::new();
  handlebars.register_helper("range", box range);
  handlebars.register_helper("eq", box eq);
  handlebars.register_helper("hex", box hex);
  handlebars.register_helper("break_lines", box break_lines);

  let mut engine = HandlebarsEngine::from(handlebars);
  engine.add(box DirectorySource::new("web/templates", ".hbs"));

  engine.reload().chain_err(|| "error loading handlebars")?;

  Ok(engine)
}

fn mount(router: Router) -> Mount {
  let mut mount = Mount::new();
  mount.mount("/static", Static::new(Path::new("web/static")));
  mount.mount("/", router);
  mount
}

fn router() -> Router {
  router!(
    channel: get "/:server_id/:channel_id/:page" => channel,
    refresh: get "/refresh" => refresh
  )
}

fn add_messages(channel: PathBuf, server_id: u64, channel_id: u64) {
  let f = File::open(channel).unwrap();
  let mut archive: Archive = serde_json::from_reader(f).unwrap();
  for message in &mut archive.messages {
    if let Some(member) = archive.server.members.iter().find(|mem| mem.user.id == message.author.id) {
      if let Some(ref nick) = member.nick {
        message.author.name = nick.clone();
      }
    }
    // FIXME: continuing here doesn't escape and opens a vulnerability
    // can probably refactor these into individual methods that return, then always escape the end
    // result or base it on a return value
    let mut parts: Vec<String> = message.content.split(' ').map(ToOwned::to_owned).collect();
    for part in &mut parts {
      if part.starts_with("<@!") {
        let end = part.find('>').unwrap_or_else(|| part.len() - 1);
        let id: u64 = match part[3..end].parse() {
          Ok(u) => u,
          Err(_) => continue
        };
        if let Some(member) = archive.server.members.iter().find(|m| m.user.id.0 == id) {
          let name = member.nick.as_ref().unwrap_or(&member.user.name);
          *part = format!("@{}", html_escape(&name));
        }
      } else if part.starts_with("<@&") {
        let end = part.find('>').unwrap_or_else(|| part.len() - 1);
        let id: u64 = match part[3..end].parse() {
          Ok(u) => u,
          Err(_) => continue
        };
        if let Some(role) = archive.server.roles.iter().find(|r| r.id.0 == id) {
          let name = if role.name == "@everyone" { role.name.clone() } else { format!("@{}", role.name) };
          *part = format!("{}", html_escape(&name));
        }
      } else if part.starts_with("<@") {
        let end = part.find('>').unwrap_or_else(|| part.len() - 1);
        let id: u64 = match part[2..end].parse() {
          Ok(u) => u,
          Err(_) => continue
        };
        if let Some(member) = archive.server.members.iter().find(|m| m.user.id.0 == id) {
          let name = member.nick.as_ref().unwrap_or(&member.user.name);
          *part = format!("@{}", html_escape(&name));
        }
      } else if part.starts_with("<#") {
        let end = part.find('>').unwrap_or_else(|| part.len() - 1);
        let id: u64 = match part[2..end].parse() {
          Ok(u) => u,
          Err(_) => continue
        };
        if let Some(channel) = archive.server.channels.iter().find(|c| c.id.0 == id) {
          *part = format!("#{}", html_escape(&channel.name));
        }
      } else if part.starts_with("<:") {
        if let Some(index) = part[2..].find(':') {
          let end = part[2 + index..].find('>').map(|x| x + 2 + index).unwrap_or_else(|| part.len() - 1);
          let id: u64 = match part[3 + index..end].parse() {
            Ok(u) => u,
            Err(_) => continue
          };
          *part = format!("", &part[2..index], id);
        }
      } else {
        *part = html_escape(&part);
      }

      if let Ok(url) = Url::parse(&part) {
        if url.has_host() {
          *part = format!("{url}", url=part)
        }
      }
    }
    message.content = parts.join(" ");
  }
  let mut msgs = MESSAGES.write().unwrap();
  let server = msgs.entry(server_id).or_insert_with(Default::default);
  server.insert(channel_id, archive);
}

fn _refresh() {
  let archives = std::fs::read_dir("archives").unwrap();
  for server in archives {
    let server = server.unwrap().path();
    if server.is_dir() {
      let server_id: u64 = server.file_stem().unwrap().to_string_lossy().parse().unwrap();
      for channel in std::fs::read_dir(server).unwrap() {
        let channel = channel.unwrap().path();
        let channel_id: u64 = channel.file_stem().unwrap().to_string_lossy().parse().unwrap();
        add_messages(channel, server_id, channel_id);
      }
    }
  }
}

fn refresh(_: &mut Request) -> IronResult<Response> {
  _refresh();
  Ok(Response::with(("We gucci", status::Ok)))
}

fn chain(mount: Mount, handlebars: HandlebarsEngine) -> Chain {
  let mut chain = Chain::new(mount);
  chain.link_after(handlebars);
  chain
}

fn main() {
  let handlebars = handlebars().unwrap();
  let router = router();
  let mount = mount(router);
  let chain = chain(mount, handlebars);

  _refresh();

  Iron::new(chain).http("localhost:3000").unwrap();
}

fn channel(req: &mut Request) -> IronResult<Response> {
  let params = req.extensions.get::<Router>().unwrap();
  let server_id = match params.find("server_id") {
    Some(c) => c,
    None => return Ok(Response::with(("No server_id", status::BadRequest)))
  };
  let server_id = match server_id.parse::<u64>() {
    Ok(c) => c,
    Err(_) => return Ok(Response::with(("Bad server_id", status::BadRequest)))
  };
  let channel_id = match params.find("channel_id") {
    Some(c) => c,
    None => return Ok(Response::with(("No channel_id", status::BadRequest)))
  };
  let channel_id = match channel_id.parse::<u64>() {
    Ok(c) => c,
    Err(_) => return Ok(Response::with(("Bad channel_id", status::BadRequest)))
  };
  let page = params.find("page").unwrap_or("1");
  let page = match page.parse::<u64>() {
    Ok(p) if p > 0 => p - 1,
    _ => return Ok(Response::with(("Bad page number", status::BadRequest)))
  };

  let mut response = Response::new();

  let msgs = MESSAGES.read().unwrap();
  let server = match msgs.get(&server_id) {
    Some(s) => s,
    None => return Ok(Response::with(("No such server", status::BadRequest)))
  };
  let archive = match server.get(&channel_id) {
    Some(c) => c,
    None => return Ok(Response::with(("No such channel", status::BadRequest)))
  };
  let messages = &archive.messages;

  let message_chunk = match messages.chunks(50).nth(page as usize) {
    Some(m) => m,
    None => return Ok(Response::with(("No such page", status::BadRequest)))
  };
  let wrappers: Vec<_> = message_chunk.into_iter()
    .map(|m| {
      let timestamp = m.timestamp.format("%m/%d/%Y at %H:%M:%S").to_string();
      let mut color = 0;
      if let Some(member) = archive.server.members.iter().find(|mem| mem.user.id == m.author.id) {
        let roles = member.roles.iter().flat_map(|r| archive.server.roles.iter().find(|x| x.id == *r));
        if let Some(role) = roles.max_by_key(|r| r.position) {
          color = role.color;
        }
      }
      let data = MessageData {
        timestamp: timestamp,
        name_color: format!("#{:x}", color)
      };
      (m, data)
    })
    .rev()
    .collect();
  let pages = (messages.len() as f32 / 50.0).ceil() as u64;
  let data = ArchiveData {
    channel_name: &archive.channel.name,
    topic: archive.channel.topic.clone().unwrap_or_else(Default::default),
    messages: wrappers,
    channel_id: channel_id,
    prev_page: page,
    current_page: page + 1,
    next_page: page + 2,
    pages: pages,
    start: page == 0,
    end: page == pages - 1
  };

  // TODO: embeds
  // TODO: reactions

  response.set_mut(Template::new("channel", data)).set_mut(status::Ok);

  Ok(response)
}

#[derive(Debug, Serialize)]
struct ArchiveData<'a> {
  channel_name: &'a str,
  topic: String,
  messages: Vec<(&'a Message, MessageData)>,
  channel_id: u64,
  prev_page: u64,
  current_page: u64,
  next_page: u64,
  pages: u64,
  start: bool,
  end: bool
}

#[derive(Debug, Serialize)]
struct MessageData {
  timestamp: String,
  name_color: String
}

#[derive(Debug, Deserialize)]
struct Archive {
  server: ArchiveServer,
  channel: ArchiveChannel,
  messages: Vec<Message>
}

#[derive(Debug, Deserialize)]
struct ArchiveServer {
  name: String,
  roles: Vec<Role>,
  members: Vec<Member>,
  channels: Vec<ArchiveChannel>,
  icon: Option<String>,
  emojis: Vec<Emoji>
}

#[derive(Debug, Deserialize)]
struct ArchiveChannel {
  id: ChannelId,
  name: String,
  topic: Option<String>
}
<commit_msg>refactor: use clippy suggestion<commit_after>#![feature(box_syntax)]

extern crate iron;
#[macro_use]
extern crate router;
extern crate mount;
extern crate staticfile;
extern crate handlebars_iron;
#[macro_use]
extern crate error_chain;
extern crate discord;
extern crate serde;
#[macro_use]
extern crate serde_derive;
extern crate serde_json;
#[macro_use]
extern crate lazy_static;
extern crate url;

mod error;
use error::*;

use iron::prelude::*;
use iron::status;

use router::Router;

use mount::Mount;

use staticfile::Static;

use handlebars_iron::{HandlebarsEngine, DirectorySource, Template};
use handlebars_iron::handlebars::*;

use discord::model::{Message, Role, Member, Emoji, ChannelId};

use url::Url;

use std::path::{Path, PathBuf};
use std::fs::File;
use std::sync::RwLock;
use std::collections::HashMap;

lazy_static! {
  static ref MESSAGES: RwLock<HashMap<u64, HashMap<u64, Archive>>> = RwLock::default();
}

fn handlebars() -> Result<HandlebarsEngine> {
  fn range(h: &Helper, r: &Handlebars, rc: &mut RenderContext) -> std::result::Result<(), RenderError> {
    let param = h.params()[0].value().as_u64().unwrap();
    for i in 0..param {
      rc.push_block_context(&(i + 1));
      h.template().map(|t| t.render(r, rc)).unwrap_or(Ok(())).unwrap();
    }
    Ok(())
  }

  fn eq(h: &Helper, r: &Handlebars, rc: &mut RenderContext) -> std::result::Result<(), RenderError> {
    let param_1 = h.params()[0].value();
    let param_2 = h.params()[1].value();
    if param_1 == param_2 {
      h.template().map(|t| t.render(r, rc)).unwrap_or(Ok(())).unwrap();
    }
    Ok(())
  }

  fn hex(h: &Helper, _: &Handlebars, rc: &mut RenderContext) -> std::result::Result<(), RenderError> {
    let param = h.params()[0].value().as_u64().unwrap();
    let hex = format!("{:x}", param);
    rc.writer().write_all(hex.as_bytes())?;
    Ok(())
  }

  fn break_lines(h: &Helper, _: &Handlebars, rc: &mut RenderContext) -> std::result::Result<(), RenderError> {
    let param = h.params()[0].value().as_str().unwrap();
    let broken = html_escape(param).replace("\n", "
");
    rc.writer().write_all(broken.as_bytes())?;
    Ok(())
  }

  let mut handlebars = Handlebars::new();
  handlebars.register_helper("range", box range);
  handlebars.register_helper("eq", box eq);
  handlebars.register_helper("hex", box hex);
  handlebars.register_helper("break_lines", box break_lines);

  let mut engine = HandlebarsEngine::from(handlebars);
  engine.add(box DirectorySource::new("web/templates", ".hbs"));

  engine.reload().chain_err(|| "error loading handlebars")?;

  Ok(engine)
}

fn mount(router: Router) -> Mount {
  let mut mount = Mount::new();
  mount.mount("/static", Static::new(Path::new("web/static")));
  mount.mount("/", router);
  mount
}

fn router() -> Router {
  router!(
    channel: get "/:server_id/:channel_id/:page" => channel,
    refresh: get "/refresh" => refresh
  )
}

fn add_messages(channel: PathBuf, server_id: u64, channel_id: u64) {
  let f = File::open(channel).unwrap();
  let mut archive: Archive = serde_json::from_reader(f).unwrap();
  for message in &mut archive.messages {
    if let Some(member) = archive.server.members.iter().find(|mem| mem.user.id == message.author.id) {
      if let Some(ref nick) = member.nick {
        message.author.name = nick.clone();
      }
    }
    // FIXME: continuing here doesn't escape and opens a vulnerability
    // can probably refactor these into individual methods that return, then always escape the end
    // result or base it on a return value
    let mut parts: Vec<String> = message.content.split(' ').map(ToOwned::to_owned).collect();
    for part in &mut parts {
      if part.starts_with("<@!") {
        let end = part.find('>').unwrap_or_else(|| part.len() - 1);
        let id: u64 = match part[3..end].parse() {
          Ok(u) => u,
          Err(_) => continue
        };
        if let Some(member) = archive.server.members.iter().find(|m| m.user.id.0 == id) {
          let name = member.nick.as_ref().unwrap_or(&member.user.name);
          *part = format!("@{}", html_escape(name));
        }
      } else if part.starts_with("<@&") {
        let end = part.find('>').unwrap_or_else(|| part.len() - 1);
        let id: u64 = match part[3..end].parse() {
          Ok(u) => u,
          Err(_) => continue
        };
        if let Some(role) = archive.server.roles.iter().find(|r| r.id.0 == id) {
          let name = if role.name == "@everyone" { role.name.clone() } else { format!("@{}", role.name) };
          *part = format!("{}", html_escape(&name));
        }
      } else if part.starts_with("<@") {
        let end = part.find('>').unwrap_or_else(|| part.len() - 1);
        let id: u64 = match part[2..end].parse() {
          Ok(u) => u,
          Err(_) => continue
        };
        if let Some(member) = archive.server.members.iter().find(|m| m.user.id.0 == id) {
          let name = member.nick.as_ref().unwrap_or(&member.user.name);
          *part = format!("@{}", html_escape(name));
        }
      } else if part.starts_with("<#") {
        let end = part.find('>').unwrap_or_else(|| part.len() - 1);
        let id: u64 = match part[2..end].parse() {
          Ok(u) => u,
          Err(_) => continue
        };
        if let Some(channel) = archive.server.channels.iter().find(|c| c.id.0 == id) {
          *part = format!("#{}", html_escape(&channel.name));
        }
      } else if part.starts_with("<:") {
        if let Some(index) = part[2..].find(':') {
          let end = part[2 + index..].find('>').map(|x| x + 2 + index).unwrap_or_else(|| part.len() - 1);
          let id: u64 = match part[3 + index..end].parse() {
            Ok(u) => u,
            Err(_) => continue
          };
          *part = format!("", &part[2..index], id);
        }
      } else {
        *part = html_escape(part);
      }

      if let Ok(url) = Url::parse(part) {
        if url.has_host() {
          *part = format!("{url}", url=part)
        }
      }
    }
    message.content = parts.join(" ");
  }
  let mut msgs = MESSAGES.write().unwrap();
  let server = msgs.entry(server_id).or_insert_with(Default::default);
  server.insert(channel_id, archive);
}

fn _refresh() {
  let archives = std::fs::read_dir("archives").unwrap();
  for server in archives {
    let server = server.unwrap().path();
    if server.is_dir() {
      let server_id: u64 = server.file_stem().unwrap().to_string_lossy().parse().unwrap();
      for channel in std::fs::read_dir(server).unwrap() {
        let channel = channel.unwrap().path();
        let channel_id: u64 = channel.file_stem().unwrap().to_string_lossy().parse().unwrap();
        add_messages(channel, server_id, channel_id);
      }
    }
  }
}

fn refresh(_: &mut Request) -> IronResult<Response> {
  _refresh();
  Ok(Response::with(("We gucci", status::Ok)))
}

fn chain(mount: Mount, handlebars: HandlebarsEngine) -> Chain {
  let mut chain = Chain::new(mount);
  chain.link_after(handlebars);
  chain
}

fn main() {
  let handlebars = handlebars().unwrap();
  let router = router();
  let mount = mount(router);
  let chain = chain(mount, handlebars);

  _refresh();

  Iron::new(chain).http("localhost:3000").unwrap();
}

fn channel(req: &mut Request) -> IronResult<Response> {
  let params = req.extensions.get::<Router>().unwrap();
  let server_id = match params.find("server_id") {
    Some(c) => c,
    None => return Ok(Response::with(("No server_id", status::BadRequest)))
  };
  let server_id = match server_id.parse::<u64>() {
    Ok(c) => c,
    Err(_) => return Ok(Response::with(("Bad server_id", status::BadRequest)))
  };
  let channel_id = match params.find("channel_id") {
    Some(c) => c,
    None => return Ok(Response::with(("No channel_id", status::BadRequest)))
  };
  let channel_id = match channel_id.parse::<u64>() {
    Ok(c) => c,
    Err(_) => return Ok(Response::with(("Bad channel_id", status::BadRequest)))
  };
  let page = params.find("page").unwrap_or("1");
  let page = match page.parse::<u64>() {
    Ok(p) if p > 0 => p - 1,
    _ => return Ok(Response::with(("Bad page number", status::BadRequest)))
  };

  let mut response = Response::new();

  let msgs = MESSAGES.read().unwrap();
  let server = match msgs.get(&server_id) {
    Some(s) => s,
    None => return Ok(Response::with(("No such server", status::BadRequest)))
  };
  let archive = match server.get(&channel_id) {
    Some(c) => c,
    None => return Ok(Response::with(("No such channel", status::BadRequest)))
  };
  let messages = &archive.messages;

  let message_chunk = match messages.chunks(50).nth(page as usize) {
    Some(m) => m,
    None => return Ok(Response::with(("No such page", status::BadRequest)))
  };
  let wrappers: Vec<_> = message_chunk.into_iter()
    .map(|m| {
      let timestamp = m.timestamp.format("%m/%d/%Y at %H:%M:%S").to_string();
      let mut color = 0;
      if let Some(member) = archive.server.members.iter().find(|mem| mem.user.id == m.author.id) {
        let roles = member.roles.iter().flat_map(|r| archive.server.roles.iter().find(|x| x.id == *r));
        if let Some(role) = roles.max_by_key(|r| r.position) {
          color = role.color;
        }
      }
      let data = MessageData {
        timestamp: timestamp,
        name_color: format!("#{:x}", color)
      };
      (m, data)
    })
    .rev()
    .collect();
  let pages = (messages.len() as f32 / 50.0).ceil() as u64;
  let data = ArchiveData {
    channel_name: &archive.channel.name,
    topic: archive.channel.topic.clone().unwrap_or_else(Default::default),
    messages: wrappers,
    channel_id: channel_id,
    prev_page: page,
    current_page: page + 1,
    next_page: page + 2,
    pages: pages,
    start: page == 0,
    end: page == pages - 1
  };

  // TODO: embeds
  // TODO: reactions

  response.set_mut(Template::new("channel", data)).set_mut(status::Ok);

  Ok(response)
}

#[derive(Debug, Serialize)]
struct ArchiveData<'a> {
  channel_name: &'a str,
  topic: String,
  messages: Vec<(&'a Message, MessageData)>,
  channel_id: u64,
  prev_page: u64,
  current_page: u64,
  next_page: u64,
  pages: u64,
  start: bool,
  end: bool
}

#[derive(Debug, Serialize)]
struct MessageData {
  timestamp: String,
  name_color: String
}

#[derive(Debug, Deserialize)]
struct Archive {
  server: ArchiveServer,
  channel: ArchiveChannel,
  messages: Vec<Message>
}

#[derive(Debug, Deserialize)]
struct ArchiveServer {
  name: String,
  roles: Vec<Role>,
  members: Vec<Member>,
  channels: Vec<ArchiveChannel>,
  icon: Option<String>,
  emojis: Vec<Emoji>
}

#[derive(Debug, Deserialize)]
struct ArchiveChannel {
  id: ChannelId,
  name: String,
  topic: Option<String>
}
<|endoftext|>


<commit_before>use tokenizer::Tokenizer;
use event::{Event, Rule};
use std::collections::HashMap;

fn substitute_variables<'a>(value: &'a str, substitutions: &'a HashMap<String, String>) -> String {
    value.split(' ').map(|value_part|
        match substitutions.get(value_part) {
            Some(v) => &v[..],
            None => value_part,
        }
    ).collect::<Vec<_>>().connect(" ")
}

pub fn nested(tokenizer: &mut Tokenizer) -> String {
    let mut output = String::from_str("");
    let mut last = Event::End(Rule::SassRule);
    let mut variables: HashMap<String, String> = HashMap::new();

    while let Some(token) = tokenizer.next() {
        let print_token = match token.clone() {
            Event::Start(_) => continue,
            Event::Variable(name, value) => {
                let val = substitute_variables(&value, &variables);
                variables.insert((*name).to_string(), val);
                continue
            },
            Event::Selector(name) => format!("{} ", name),
            Event::Property(name, value) => {
                let real_value = substitute_variables(&value, &variables);
                match last {
                    Event::Selector(_) => format!("{{\n  {}: {};", name, real_value),
                    _ => format!("\n  {}: {};", name, real_value),
                }
            },
            Event::End(_) => {
                match last {
                    Event::End(_) => continue,
                    _ => format!(" }}\n"),
                }
            },
        };
        output.push_str(print_token.as_str());
        last = token;
    }
    output
}

pub fn compressed(tokenizer: &mut Tokenizer) -> String {
    let mut output =  String::from_str("");
    let mut last = Event::End(Rule::SassRule);
    let mut variables = HashMap::new();

    while let Some(token) = tokenizer.next() {
        let print_token = match token.clone() {
            Event::Start(_) => continue,
            Event::Variable(name, value) => {
                let val = substitute_variables(&value, &variables);
                variables.insert((*name).to_string(), val);
                continue
            },
            Event::Selector(name) => {
                match last {
                    Event::Selector(_) => format!(" {}", name),
                    _ => format!("{}", name),
                }
            },
            Event::Property(name, value) => {
                let real_value = substitute_variables(&value, &variables);
                match last {
                    Event::Selector(_) => format!("{{{}:{}", name, real_value),
                    Event::Property(_, _) => format!(";{}:{}", name, real_value),
                    _ => format!("{}:{}", name, real_value),
                }
            },
            Event::End(_) => {
                match last {
                    Event::End(_) => continue,
                    _ => format!("}}"),
                }
            },
        };
        output.push_str(print_token.as_str());
        last = token;
    }
    output
}

pub fn expanded(tokenizer: &mut Tokenizer) -> String {
    let mut output =  String::from_str("");
    let mut last = Event::End(Rule::SassRule);
    let mut variables = HashMap::new();

    while let Some(token) = tokenizer.next() {
        let print_token = match token.clone() {
            Event::Start(_) => continue,
            Event::Variable(name, value) => {
                let val = substitute_variables(&value, &variables);
                variables.insert((*name).to_string(), val);
                continue
            },
            Event::Selector(name) => {
                match last {
                    Event::Selector(_) => format!(" {}", name),
                    Event::End(_) => format!("\n{}", name),
                    _ => format!("{}", name),
                }
            },
            Event::Property(name, value) => {
                let real_value = substitute_variables(&value, &variables);
                match last {
                    Event::Selector(_) => format!(" {{\n  {}: {};", name, real_value),
                    _ => format!("\n  {}: {};", name, real_value),
                }
            },
            Event::End(_) => {
                match last {
                    Event::End(_) => continue,
                    _ => format!("\n}}"),
                }
            },
        };
        output.push_str(print_token.as_str());
        last = token;
    }
    output
}

pub fn compact(tokenizer: &mut Tokenizer) -> String {
    let mut output =  String::from_str("");
    let mut last = Event::End(Rule::SassRule);
    let mut variables = HashMap::new();

    while let Some(token) = tokenizer.next() {
        let print_token = match token.clone() {
            Event::Start(_) => continue,
            Event::Variable(name, value) => {
                let val = substitute_variables(&value, &variables);
                variables.insert((*name).to_string(), val);
                continue
            },
            Event::Selector(name) => {
                match last {
                    Event::Selector(_) => format!(" {}", name),
                    Event::End(_) => format!("\n{}", name),
                    _ => format!("{}", name),
                }
            },
            Event::Property(name, value) => {
                let real_value = substitute_variables(&value, &variables);
                match last {
                    Event::Selector(_) => format!(" {{ {}: {};", name, real_value),
                    _ => format!(" {}: {};", name, real_value),
                }
            },
            Event::End(_) => {
                match last {
                    Event::End(_) => continue,
                    _ => format!(" }}"),
                }
            },
        };
        output.push_str(print_token.as_str());
        last = token;
    }
    output
}

pub fn debug(tokenizer: &mut Tokenizer) -> String {
    let mut output =  String::from_str("");
    while let Some(token) = tokenizer.next() {
        output.push_str(format!("{:?}\n", token).as_str());
    }
    output
}
<commit_msg>Test out my idea a bit on the debug output formatter<commit_after>use tokenizer::Tokenizer;
use event::{Event, Rule};
use std::collections::HashMap;

fn substitute_variables<'a>(value: &'a str, substitutions: &'a HashMap<String, String>) -> String {
    value.split(' ').map(|value_part|
        match substitutions.get(value_part) {
            Some(v) => &v[..],
            None => value_part,
        }
    ).collect::<Vec<_>>().connect(" ")
}

pub fn nested(tokenizer: &mut Tokenizer) -> String {
    let mut output = String::from_str("");
    let mut last = Event::End(Rule::SassRule);
    let mut variables: HashMap<String, String> = HashMap::new();

    while let Some(token) = tokenizer.next() {
        let print_token = match token.clone() {
            Event::Start(_) => continue,
            Event::Variable(name, value) => {
                let val = substitute_variables(&value, &variables);
                variables.insert((*name).to_string(), val);
                continue
            },
            Event::Selector(name) => format!("{} ", name),
            Event::Property(name, value) => {
                let real_value = substitute_variables(&value, &variables);
                match last {
                    Event::Selector(_) => format!("{{\n  {}: {};", name, real_value),
                    _ => format!("\n  {}: {};", name, real_value),
                }
            },
            Event::End(_) => {
                match last {
                    Event::End(_) => continue,
                    _ => format!(" }}\n"),
                }
            },
        };
        output.push_str(print_token.as_str());
        last = token;
    }
    output
}

pub fn compressed(tokenizer: &mut Tokenizer) -> String {
    let mut output =  String::from_str("");
    let mut last = Event::End(Rule::SassRule);
    let mut variables = HashMap::new();

    while let Some(token) = tokenizer.next() {
        let print_token = match token.clone() {
            Event::Start(_) => continue,
            Event::Variable(name, value) => {
                let val = substitute_variables(&value, &variables);
                variables.insert((*name).to_string(), val);
                continue
            },
            Event::Selector(name) => {
                match last {
                    Event::Selector(_) => format!(" {}", name),
                    _ => format!("{}", name),
                }
            },
            Event::Property(name, value) => {
                let real_value = substitute_variables(&value, &variables);
                match last {
                    Event::Selector(_) => format!("{{{}:{}", name, real_value),
                    Event::Property(_, _) => format!(";{}:{}", name, real_value),
                    _ => format!("{}:{}", name, real_value),
                }
            },
            Event::End(_) => {
                match last {
                    Event::End(_) => continue,
                    _ => format!("}}"),
                }
            },
        };
        output.push_str(print_token.as_str());
        last = token;
    }
    output
}

pub fn expanded(tokenizer: &mut Tokenizer) -> String {
    let mut output =  String::from_str("");
    let mut last = Event::End(Rule::SassRule);
    let mut variables = HashMap::new();

    while let Some(token) = tokenizer.next() {
        let print_token = match token.clone() {
            Event::Start(_) => continue,
            Event::Variable(name, value) => {
                let val = substitute_variables(&value, &variables);
                variables.insert((*name).to_string(), val);
                continue
            },
            Event::Selector(name) => {
                match last {
                    Event::Selector(_) => format!(" {}", name),
                    Event::End(_) => format!("\n{}", name),
                    _ => format!("{}", name),
                }
            },
            Event::Property(name, value) => {
                let real_value = substitute_variables(&value, &variables);
                match last {
                    Event::Selector(_) => format!(" {{\n  {}: {};", name, real_value),
                    _ => format!("\n  {}: {};", name, real_value),
                }
            },
            Event::End(_) => {
                match last {
                    Event::End(_) => continue,
                    _ => format!("\n}}"),
                }
            },
        };
        output.push_str(print_token.as_str());
        last = token;
    }
    output
}

pub fn compact(tokenizer: &mut Tokenizer) -> String {
    let mut output =  String::from_str("");
    let mut last = Event::End(Rule::SassRule);
    let mut variables = HashMap::new();

    while let Some(token) = tokenizer.next() {
        let print_token = match token.clone() {
            Event::Start(_) => continue,
            Event::Variable(name, value) => {
                let val = substitute_variables(&value, &variables);
                variables.insert((*name).to_string(), val);
                continue
            },
            Event::Selector(name) => {
                match last {
                    Event::Selector(_) => format!(" {}", name),
                    Event::End(_) => format!("\n{}", name),
                    _ => format!("{}", name),
                }
            },
            Event::Property(name, value) => {
                let real_value = substitute_variables(&value, &variables);
                match last {
                    Event::Selector(_) => format!(" {{ {}: {};", name, real_value),
                    _ => format!(" {}: {};", name, real_value),
                }
            },
            Event::End(_) => {
                match last {
                    Event::End(_) => continue,
                    _ => format!(" }}"),
                }
            },
        };
        output.push_str(print_token.as_str());
        last = token;
    }
    output
}

pub fn debug(tokenizer: &mut Tokenizer) -> String {
    let mut output = String::from_str("");
    let mut current = String::from_str("");

    while let Some(token) = tokenizer.next() {
        current.push_str(&format!("{:?}\n", token)[..]);
        match token {
            Event::End(_) => {
                output.push_str(&current[..]);
                current = String::from_str("");
            },
            _ => {},
        }
    }
    output
}
<|endoftext|>