<commit_before>// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.

#![feature(iter_arith, zero_one, custom_derive,
           custom_attribute, plugin, const_fn)]
#![plugin(serde_macros)]
#![plugin(clippy)]

#![allow(match_same_arms)] // we seem to have instances where same arms are good
#![allow(dead_code)] // only temporary, until more stuff is filled in

extern crate devicemapper;
#[macro_use] extern crate clap;
#[macro_use] extern crate nix;
extern crate crc;
extern crate byteorder;
extern crate uuid;
extern crate time;
extern crate serde;
extern crate serde_json;
extern crate bytesize;
extern crate dbus;
extern crate term;

#[macro_use] extern crate custom_derive;
#[macro_use] extern crate newtype_derive;

pub static mut debug: bool = false;

macro_rules! dbgp {
    ($($arg:tt)*) => (
        unsafe {
            if ::debug {
                println!($($arg)*)
            }
        })
}

mod types;
mod consts;
mod froyo;
mod blockdev;
mod raid;
mod thin;
mod util;
mod dbus_api;

use std::io::Write;
use std::error::Error;
use std::process::exit;
use std::path::{Path, PathBuf};
use std::rc::Rc;
use std::cell::RefCell;

use clap::{App, Arg, SubCommand, ArgMatches};
use bytesize::ByteSize;
use dbus::{Connection, BusType};

use types::{FroyoResult, FroyoError, InternalError};
use consts::SECTOR_SIZE;
use froyo::{Froyo, FroyoStatus};

// We are given BlockDevs to start.
// We allocate LinearDevs from each for the meta and data devices.
// We use all these to make RaidDevs.
// We create two RaidLinearDevs from these for meta and data devices.
// We use these to make a ThinPoolDev.
// From that, we allocate a ThinDev.

fn list(_args: &ArgMatches) -> FroyoResult<()> {
    let froyos = try!(Froyo::find_all());
    for f in &froyos {
        println!("{}", f.name);
    }

    Ok(())
}

fn status(args: &ArgMatches) -> FroyoResult<()> {
    let name = args.value_of("froyodevname").unwrap();
    match try!(Froyo::find(&name)) {
        Some(f) => {
            let status = match try!(f.status()) {
                FroyoStatus::Good(_) => "good",
                FroyoStatus::RaidFailed => "RAID failed",
                FroyoStatus::ThinPoolFailed => "Thin pool failed",
                FroyoStatus::ThinFailed => "Thin device failed",
            };

            let space = *try!(f.avail_redundant_space()) * SECTOR_SIZE;
            let total = *f.total_redundant_space() * SECTOR_SIZE;

            let percent = ((total-space) * 100) / total;

            println!("Status: {}, {}% used ({} of {} free)",
                     status, percent,
                     ByteSize::b(space as usize).to_string(true),
                     ByteSize::b(total as usize).to_string(true));
        },
        None => return Err(FroyoError::Froyo(InternalError(
            format!("Froyodev \"{}\" not found", name)))),
    }
    Ok(())
}

fn add(args: &ArgMatches) -> FroyoResult<()> {
    let name = args.value_of("froyodevname").unwrap();
    let dev_paths: Vec<_> = args.values_of("devices").unwrap().into_iter()
        .map(|dev| {
            if !Path::new(dev).is_absolute() {
                PathBuf::from(format!("/dev/{}", dev))
            } else {
                PathBuf::from(dev)
            }})
        .collect();
    let force = args.is_present("force");

    let mut froyo = match try!(Froyo::find(&name)) {
        Some(f) => f,
        None => return Err(FroyoError::Froyo(InternalError(
            format!("Froyodev \"{}\" not found", name)))),
    };

    for path in &dev_paths {
        try!(froyo.add_blockdev(path, force))
    }

    Ok(())
}

fn remove(_args: &ArgMatches) -> FroyoResult<()> {
    let mut out = term::stdout().expect("could not get stdout");

    try!(out.fg(term::color::GREEN));
    try!(writeln!(out, "hello color"));
    try!(out.reset());

    Ok(())
}

fn create(args: &ArgMatches) -> FroyoResult<()> {
    let name = args.value_of("froyodevname").unwrap();
    let dev_paths: Vec<_> = args.values_of("devices").unwrap().into_iter()
        .map(|dev| {
            if !Path::new(dev).is_absolute() {
                PathBuf::from(format!("/dev/{}", dev))
            } else {
                PathBuf::from(dev)
            }})
        .collect();
    let force = args.is_present("force");

    let froyo = try!(Froyo::new(name, &dev_paths, force));

    try!(froyo.save_state());

    dbgp!("Froyodev {} created", froyo.name);

    Ok(())
}

fn rename(args: &ArgMatches) -> FroyoResult<()> {
    let old_name = args.value_of("froyodev_old_name").unwrap();
    let new_name = args.value_of("froyodev_new_name").unwrap();

    match try!(Froyo::find(&old_name)) {
        Some(mut f) => {
            f.name = new_name.to_owned();
            try!(f.save_state());
            dbgp!("Froyodev name {} changed to {}", old_name, new_name);
        },
        None => return Err(FroyoError::Froyo(InternalError(
            format!("Froyodev \"{}\" not found", old_name)))),
    }

    Ok(())
}

fn destroy(args: &ArgMatches) -> FroyoResult<()> {
    let name = args.value_of("froyodev").unwrap();

    match try!(Froyo::find(&name)) {
        Some(f) => {
            try!(f.destroy());
            dbgp!("Froyodev {} destroyed", name);
        },
        None => return Err(FroyoError::Froyo(InternalError(
            format!("Froyodev \"{}\" not found", name)))),
    }

    Ok(())
}

fn dump_meta(args: &ArgMatches) -> FroyoResult<()> {
    let name = args.value_of("froyodevname").unwrap();
    match try!(Froyo::find(&name)) {
        Some(f) =>
            println!("{}", try!(f.to_metadata_pretty())),
        None => return Err(FroyoError::Froyo(InternalError(
            format!("Froyodev \"{}\" not found", name)))),
    }

    Ok(())
}

fn dbus_server(_args: &ArgMatches) -> FroyoResult<()> {
    let c = Connection::get_private(BusType::Session).unwrap();
    let froyos = try!(Froyo::find_all());
    let froyos = froyos.into_iter()
        .map(|f| Rc::new(RefCell::new(f)))
        .collect::<Vec<_>>();
    let mut froyos = Rc::new(RefCell::new(froyos));
    let tree = try!(dbus_api::get_tree(&c, &mut froyos));

    // TODO: event loop needs to handle dbus and also dm events (or polling)
    // so we can extend/reshape/delay/whatever in a timely fashion
    for _ in tree.run(&c, c.iter(1000)) {

    }

    Ok(())
}

fn teardown(args: &ArgMatches) -> FroyoResult<()> {
    let name = args.value_of("froyodevname").unwrap();
    match try!(Froyo::find(&name)) {
        Some(mut f) => try!(f.teardown()),
        None => return Err(FroyoError::Froyo(InternalError(
            format!("Froyodev \"{}\" not found", name)))),
    }

    Ok(())
}

fn write_err(err: FroyoError) -> FroyoResult<()> {
    let mut out = term::stderr().expect("could not get stderr");

    try!(out.fg(term::color::RED));
    try!(writeln!(out, "{}", err.description()));
    try!(out.reset());
    Ok(())
}

fn main() {

    let matches = App::new("froyo")
        .version(&crate_version!())
        .author("Andy Grover <andy@groveronline.com>")
        .about("Drobo + Free + YOLO")
        .arg(Arg::with_name("debug")
             .short("d")
             .long("debug")
             .help("Print additional output for debugging")
             )
        .subcommand(SubCommand::with_name("list")
                    .about("List all froyodevs")
                    .arg(Arg::with_name("long")
                         .short("l")
                         .long("long")
                         .help("Use a long listing format")
                         )
                    )
        .subcommand(SubCommand::with_name("status")
                    .about("Get the status of a single froyodev")
                    .arg(Arg::with_name("froyodevname")
                         .required(true)
                         .help("Froyodev to get info on")
                         .index(1)
                         )
                    )
        .subcommand(SubCommand::with_name("add")
                    .about("Add one or more additional block devices to a froyodev")
                    .arg(Arg::with_name("force")
                         .short("f")
                         .long("force")
                         .help("Force")
                    )
                    .arg(Arg::with_name("froyodevname")
                         .help("Froyodev to add the device to")
                         .required(true)
                         .index(1)
                         )
                    .arg(Arg::with_name("devices")
                         .help("device(s) to add")
                         .multiple(true)
                         .required(true)
                         .index(2)
                         )
                    )
        .subcommand(SubCommand::with_name("remove")
                    .about("Remove a block device from a froyodev")
                    .arg(Arg::with_name("froyodevname")
                         .help("Froyodev to remove the device from")
                         .required(true)
                         .index(1)
                         )
                    .arg(Arg::with_name("devices")
                         .help("Block device(s) to remove")
                         .multiple(true)
                         .required(true)
                         .index(2)
                         )
                    )
        .subcommand(SubCommand::with_name("create")
                    .about("Create a new froyodev")
                    .arg(Arg::with_name("force")
                         .short("f")
                         .long("force")
                         .help("Force")
                         )
                    .arg(Arg::with_name("froyodevname")
                         .help("Name of the new froyodev")
                         .required(true)
                         .index(1)
                         )
                    .arg(Arg::with_name("devices")
                         .help("Initial block device(s) to use")
                         .multiple(true)
                         .required(true)
                         .index(2)
                         )
                    )
        .subcommand(SubCommand::with_name("rename")
                    .about("Rename a froyodev")
                    .arg(Arg::with_name("froyodev_old_name")
                         .help("Old name of froyodev")
                         .required(true)
                         .index(1)
                    )
                    .arg(Arg::with_name("froyodev_new_name")
                         .help("New name of froyodev")
                         .required(true)
                         .index(2)
                    )
        )
        .subcommand(SubCommand::with_name("destroy")
                    .about("Destroy a froyodev")
                    .arg(Arg::with_name("froyodev")
                         .help("Froyodev to destroy")
                         .required(true)
                         .index(1)
                    )
        )
        .subcommand(SubCommand::with_name("dev")
                    .about("Developer/debug commands")
                    .subcommand(SubCommand::with_name("dump_meta")
                                .about("Output the JSON metadata for a froyodev")
                                .arg(Arg::with_name("froyodevname")
                                     .help("Name of the froyodev")
                                     .required(true)
                                     .index(1)
                                     )
                                )
                    .subcommand(SubCommand::with_name("teardown")
                                .about("Remove the DM mappings for a froyodev")
                                .arg(Arg::with_name("froyodevname")
                                     .help("Name of the froyodev")
                                     .required(true)
                                     .index(1)
                                     )
                                )
                    .subcommand(SubCommand::with_name("dbus_server")
                                .about("Serve the Froyo DBus API")
                                )
                    )
        .get_matches();

    if matches.is_present("debug") {
        // must use unsafe to change a mut static, sigh
        unsafe { debug = true };
    }

    let r = match matches.subcommand() {
        ("list", Some(matches)) => list(matches),
        ("status", Some(matches)) => status(matches),
        ("add", Some(matches)) => add(matches),
        ("remove", Some(matches)) => remove(matches),
        ("create", Some(matches)) => create(matches),
        ("rename", Some(matches)) => rename(matches),
        ("destroy", Some(matches)) => destroy(matches),
        ("dev", Some(matches)) => match matches.subcommand() {
            ("dump_meta", Some(matches)) => dump_meta(matches),
            ("dbus_server", Some(matches)) => dbus_server(matches),
            ("teardown", Some(matches)) => teardown(matches),
            ("", None) => {
                println!("No command given, try \"help\"");
                Ok(())
            }
            _ => unreachable!(),
        },
        ("", None) => {
            println!("No command given, try \"help\"");
            Ok(())
        }
        _ => unreachable!(),
    };

    if let Err(r) = r {
        if let Err(e) = write_err(r) {
            panic!("Unable to write to stderr: {}", e)
        }

        exit(1);
    }
}
<commit_msg>main: Fill in remove subcommand implementation<commit_after>// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.

#![feature(iter_arith, zero_one, custom_derive,
           custom_attribute, plugin, const_fn)]
#![plugin(serde_macros)]
#![plugin(clippy)]

#![allow(match_same_arms)] // we seem to have instances where same arms are good
#![allow(dead_code)] // only temporary, until more stuff is filled in

extern crate devicemapper;
#[macro_use] extern crate clap;
#[macro_use] extern crate nix;
extern crate crc;
extern crate byteorder;
extern crate uuid;
extern crate time;
extern crate serde;
extern crate serde_json;
extern crate bytesize;
extern crate dbus;
extern crate term;

#[macro_use] extern crate custom_derive;
#[macro_use] extern crate newtype_derive;

pub static mut debug: bool = false;

macro_rules! dbgp {
    ($($arg:tt)*) => (
        unsafe {
            if ::debug {
                println!($($arg)*)
            }
        })
}

mod types;
mod consts;
mod froyo;
mod blockdev;
mod raid;
mod thin;
mod util;
mod dbus_api;

use std::io::Write;
use std::error::Error;
use std::process::exit;
use std::path::{Path, PathBuf};
use std::rc::Rc;
use std::cell::RefCell;

use clap::{App, Arg, SubCommand, ArgMatches};
use bytesize::ByteSize;
use dbus::{Connection, BusType};

use types::{FroyoResult, FroyoError, InternalError};
use consts::SECTOR_SIZE;
use froyo::{Froyo, FroyoStatus};

// We are given BlockDevs to start.
// We allocate LinearDevs from each for the meta and data devices.
// We use all these to make RaidDevs.
// We create two RaidLinearDevs from these for meta and data devices.
// We use these to make a ThinPoolDev.
// From that, we allocate a ThinDev.

fn list(_args: &ArgMatches) -> FroyoResult<()> {
    let froyos = try!(Froyo::find_all());
    for f in &froyos {
        println!("{}", f.name);
    }

    Ok(())
}

fn status(args: &ArgMatches) -> FroyoResult<()> {
    let name = args.value_of("froyodevname").unwrap();
    match try!(Froyo::find(&name)) {
        Some(f) => {
            let status = match try!(f.status()) {
                FroyoStatus::Good(_) => "good",
                FroyoStatus::RaidFailed => "RAID failed",
                FroyoStatus::ThinPoolFailed => "Thin pool failed",
                FroyoStatus::ThinFailed => "Thin device failed",
            };

            let space = *try!(f.avail_redundant_space()) * SECTOR_SIZE;
            let total = *f.total_redundant_space() * SECTOR_SIZE;

            let percent = ((total-space) * 100) / total;

            println!("Status: {}, {}% used ({} of {} free)",
                     status, percent,
                     ByteSize::b(space as usize).to_string(true),
                     ByteSize::b(total as usize).to_string(true));
        },
        None => return Err(FroyoError::Froyo(InternalError(
            format!("Froyodev \"{}\" not found", name)))),
    }
    Ok(())
}

fn add(args: &ArgMatches) -> FroyoResult<()> {
    let name = args.value_of("froyodevname").unwrap();
    let dev_paths: Vec<_> = args.values_of("devices").unwrap().into_iter()
        .map(|dev| {
            if !Path::new(dev).is_absolute() {
                PathBuf::from(format!("/dev/{}", dev))
            } else {
                PathBuf::from(dev)
            }})
        .collect();
    let force = args.is_present("force");

    let mut froyo = match try!(Froyo::find(&name)) {
        Some(f) => f,
        None => return Err(FroyoError::Froyo(InternalError(
            format!("Froyodev \"{}\" not found", name)))),
    };

    for path in &dev_paths {
        try!(froyo.add_block_device(path, force))
    }
    try!(froyo.save_state());

    Ok(())
}

fn remove(args: &ArgMatches) -> FroyoResult<()> {
    let name = args.value_of("froyodevname").unwrap();
    let bd_path = {
        let dev = args.value_of("blockdev").unwrap();
        if !Path::new(dev).is_absolute() {
            PathBuf::from(format!("/dev/{}", dev))
        } else {
            PathBuf::from(dev)
        }
    };

    let mut froyo = match try!(Froyo::find(&name)) {
        Some(f) => f,
        None => return Err(FroyoError::Froyo(InternalError(
            format!("Froyodev \"{}\" not found", name)))),
    };

    try!(froyo.remove_block_device(&bd_path));
    try!(froyo.save_state());

    Ok(())
}

fn create(args: &ArgMatches) -> FroyoResult<()> {
    let name = args.value_of("froyodevname").unwrap();
    let dev_paths: Vec<_> = args.values_of("devices").unwrap().into_iter()
        .map(|dev| {
            if !Path::new(dev).is_absolute() {
                PathBuf::from(format!("/dev/{}", dev))
            } else {
                PathBuf::from(dev)
            }})
        .collect();
    let force = args.is_present("force");

    let froyo = try!(Froyo::new(name, &dev_paths, force));

    try!(froyo.save_state());

    dbgp!("Froyodev {} created", froyo.name);

    Ok(())
}

fn rename(args: &ArgMatches) -> FroyoResult<()> {
    let old_name = args.value_of("froyodev_old_name").unwrap();
    let new_name = args.value_of("froyodev_new_name").unwrap();

    match try!(Froyo::find(&old_name)) {
        Some(mut f) => {
            f.name = new_name.to_owned();
            try!(f.save_state());
            dbgp!("Froyodev name {} changed to {}", old_name, new_name);
        },
        None => return Err(FroyoError::Froyo(InternalError(
            format!("Froyodev \"{}\" not found", old_name)))),
    }

    Ok(())
}

fn destroy(args: &ArgMatches) -> FroyoResult<()> {
    let name = args.value_of("froyodev").unwrap();

    match try!(Froyo::find(&name)) {
        Some(f) => {
            try!(f.destroy());
            dbgp!("Froyodev {} destroyed", name);
        },
        None => return Err(FroyoError::Froyo(InternalError(
            format!("Froyodev \"{}\" not found", name)))),
    }

    Ok(())
}

fn dump_meta(args: &ArgMatches) -> FroyoResult<()> {
    let name = args.value_of("froyodevname").unwrap();
    match try!(Froyo::find(&name)) {
        Some(f) =>
            println!("{}", try!(f.to_metadata_pretty())),
        None => return Err(FroyoError::Froyo(InternalError(
            format!("Froyodev \"{}\" not found", name)))),
    }

    Ok(())
}

fn dbus_server(_args: &ArgMatches) -> FroyoResult<()> {
    let c = Connection::get_private(BusType::Session).unwrap();
    let froyos = try!(Froyo::find_all());
    let froyos = froyos.into_iter()
        .map(|f| Rc::new(RefCell::new(f)))
        .collect::<Vec<_>>();
    let mut froyos = Rc::new(RefCell::new(froyos));
    let tree = try!(dbus_api::get_tree(&c, &mut froyos));

    // TODO: event loop needs to handle dbus and also dm events (or polling)
    // so we can extend/reshape/delay/whatever in a timely fashion
    for _ in tree.run(&c, c.iter(1000)) {

    }

    Ok(())
}

fn teardown(args: &ArgMatches) -> FroyoResult<()> {
    let name = args.value_of("froyodevname").unwrap();
    match try!(Froyo::find(&name)) {
        Some(mut f) => try!(f.teardown()),
        None => return Err(FroyoError::Froyo(InternalError(
            format!("Froyodev \"{}\" not found", name)))),
    }

    Ok(())
}

fn write_err(err: FroyoError) -> FroyoResult<()> {
    let mut out = term::stderr().expect("could not get stderr");

    try!(out.fg(term::color::RED));
    try!(writeln!(out, "{}", err.description()));
    try!(out.reset());
    Ok(())
}

fn main() {

    let matches = App::new("froyo")
        .version(&crate_version!())
        .author("Andy Grover <andy@groveronline.com>")
        .about("Drobo + Free + YOLO")
        .arg(Arg::with_name("debug")
             .short("d")
             .long("debug")
             .help("Print additional output for debugging")
             )
        .subcommand(SubCommand::with_name("list")
                    .about("List all froyodevs")
                    .arg(Arg::with_name("long")
                         .short("l")
                         .long("long")
                         .help("Use a long listing format")
                         )
                    )
        .subcommand(SubCommand::with_name("status")
                    .about("Get the status of a single froyodev")
                    .arg(Arg::with_name("froyodevname")
                         .required(true)
                         .help("Froyodev to get info on")
                         .index(1)
                         )
                    )
        .subcommand(SubCommand::with_name("add")
                    .about("Add one or more additional block devices to a froyodev")
                    .arg(Arg::with_name("force")
                         .short("f")
                         .long("force")
                         .help("Force")
                    )
                    .arg(Arg::with_name("froyodevname")
                         .help("Froyodev to add the device to")
                         .required(true)
                         .index(1)
                         )
                    .arg(Arg::with_name("devices")
                         .help("device(s) to add")
                         .multiple(true)
                         .required(true)
                         .index(2)
                         )
                    )
        .subcommand(SubCommand::with_name("remove")
                    .about("Remove a block device from a froyodev")
                    .arg(Arg::with_name("froyodevname")
                         .help("Froyodev to remove the device from")
                         .required(true)
                         .index(1)
                         )
                    .arg(Arg::with_name("blockdev")
                         .help("Block device to remove")
                         .required(true)
                         .index(2)
                         )
                    )
        .subcommand(SubCommand::with_name("create")
                    .about("Create a new froyodev")
                    .arg(Arg::with_name("force")
                         .short("f")
                         .long("force")
                         .help("Force")
                         )
                    .arg(Arg::with_name("froyodevname")
                         .help("Name of the new froyodev")
                         .required(true)
                         .index(1)
                         )
                    .arg(Arg::with_name("devices")
                         .help("Initial block device(s) to use")
                         .multiple(true)
                         .required(true)
                         .index(2)
                         )
                    )
        .subcommand(SubCommand::with_name("rename")
                    .about("Rename a froyodev")
                    .arg(Arg::with_name("froyodev_old_name")
                         .help("Old name of froyodev")
                         .required(true)
                         .index(1)
                    )
                    .arg(Arg::with_name("froyodev_new_name")
                         .help("New name of froyodev")
                         .required(true)
                         .index(2)
                    )
        )
        .subcommand(SubCommand::with_name("destroy")
                    .about("Destroy a froyodev")
                    .arg(Arg::with_name("froyodev")
                         .help("Froyodev to destroy")
                         .required(true)
                         .index(1)
                    )
        )
        .subcommand(SubCommand::with_name("dev")
                    .about("Developer/debug commands")
                    .subcommand(SubCommand::with_name("dump_meta")
                                .about("Output the JSON metadata for a froyodev")
                                .arg(Arg::with_name("froyodevname")
                                     .help("Name of the froyodev")
                                     .required(true)
                                     .index(1)
                                     )
                                )
                    .subcommand(SubCommand::with_name("teardown")
                                .about("Remove the DM mappings for a froyodev")
                                .arg(Arg::with_name("froyodevname")
                                     .help("Name of the froyodev")
                                     .required(true)
                                     .index(1)
                                     )
                                )
                    .subcommand(SubCommand::with_name("dbus_server")
                                .about("Serve the Froyo DBus API")
                                )
                    )
        .get_matches();

    if matches.is_present("debug") {
        // must use unsafe to change a mut static, sigh
        unsafe { debug = true };
    }

    let r = match matches.subcommand() {
        ("list", Some(matches)) => list(matches),
        ("status", Some(matches)) => status(matches),
        ("add", Some(matches)) => add(matches),
        ("remove", Some(matches)) => remove(matches),
        ("create", Some(matches)) => create(matches),
        ("rename", Some(matches)) => rename(matches),
        ("destroy", Some(matches)) => destroy(matches),
        ("dev", Some(matches)) => match matches.subcommand() {
            ("dump_meta", Some(matches)) => dump_meta(matches),
            ("dbus_server", Some(matches)) => dbus_server(matches),
            ("teardown", Some(matches)) => teardown(matches),
            ("", None) => {
                println!("No command given, try \"help\"");
                Ok(())
            }
            _ => unreachable!(),
        },
        ("", None) => {
            println!("No command given, try \"help\"");
            Ok(())
        }
        _ => unreachable!(),
    };

    if let Err(r) = r {
        if let Err(e) = write_err(r) {
            panic!("Unable to write to stderr: {}", e)
        }

        exit(1);
    }
}
<|endoftext|>


<commit_before>extern crate log4rs;
extern crate hyper;

#[macro_use] extern crate nickel;
use nickel::{Nickel, HttpRouter, Request, Response, MiddlewareResult, MediaType, QueryString};
use nickel::status::StatusCode;
use hyper::header::Location;

use hyper::client::{Client,RequestBuilder};
use std::io::Read;

extern crate json;
use json::iterators::Members;

extern crate mustache;
use mustache::MapBuilder;

extern crate itertools;
use itertools::Itertools;
extern crate time;

// https://api.tfl.gov.uk/StopPoint?lat=51.423896&lon=-0.045525&stopTypes=NaptanOnstreetBusCoachStopPair&radius=300

fn json_for_request(rb: RequestBuilder) -> Result<json::JsonValue, String> {
    let mut res = match rb.send()
    {
        Ok(val) => val,
        Err(_) => {
            return Err("Can't connect to TfL".to_string());
        }
    };
    let mut buffer: String = String::new();
    res.read_to_string(&mut buffer).expect("can't read");
    let obj = match json::parse(&buffer)
    {
        Ok(val) => val,
        Err(_) => {
            return Err(format!("Bad json: {} ", buffer));
        }
    };
    return Ok(obj);
}

fn render_to_response<'a, D>(mut response: Response<'a, D>, path: &str, data: &mustache::Data) -> MiddlewareResult<'a, D> {
    let template = mustache::compile_path(path).expect("working template");
    let mut buffer: Vec<u8> = vec![];
    template.render_data(&mut buffer, data);
    response.set(MediaType::Html);
    response.send(buffer)
}

fn arrivals_handler<'a, D>(request: &mut Request<D>, mut response: Response<'a, D>) -> MiddlewareResult<'a, D> {
    let client = Client::new();
    let obj = match json_for_request(client.get(&format!("https://api.tfl.gov.uk/StopPoint/{}/Arrivals",
        request.param("stopid").unwrap_or("Missing stopid"))))
    {
        Ok(val) => val,
        Err(val) => {
            response.set(StatusCode::BadGateway);
            return response.send(val);
        }
    };

    let members = match obj.members() {
        Members::Some(val) => val,
        Members::None => {
            response.set(StatusCode::BadGateway);
            return response.send(format!("No stops in: {:?}", obj));
        }
    };
    let member_slice = members.as_slice();
    let data = {
        if member_slice.len() == 0 {
            MapBuilder::new().build()
        }
        else {
            let last_item = member_slice[0].clone();
            let sorted_members = members.sorted_by(|a, b| a["expectedArrival"].as_str().unwrap().cmp(b["expectedArrival"].as_str().unwrap()));

            MapBuilder::new()
                .insert_vec("buses", |vecbuilder| {
                    let mut vecb = vecbuilder;
                    for stop in sorted_members.clone() {
                        vecb = vecb.push_map(|mapbuilder| {
                            let when = time::strptime(stop["expectedArrival"].as_str().unwrap(), "%FT%TZ").unwrap();
                            mapbuilder
                                .insert_str("line", stop["lineName"].as_str().unwrap())
                                .insert_str("destination", stop["destinationName"].as_str().unwrap())
                                .insert_str("towards", stop["towards"].as_str().unwrap())
                                .insert_str("expectedArrival", when.to_local().strftime("%H:%M").unwrap())
                        });
                    }
                    vecb
                })
                .insert_str("stopName", last_item["stationName"].as_str().unwrap())
                .insert_str("stopNumber", last_item["platformName"].as_str().unwrap())
                .build()
            }
        };

    render_to_response(response, "resources/templates/arrivals.mustache", &data)
}

fn root_handler<'a, D>(_: &mut Request<D>, mut response: Response<'a, D>) -> MiddlewareResult<'a, D> {
    let data = MapBuilder::new().build();
    render_to_response(response, "resources/templates/root.mustache", &data)
}

fn search_handler<'a, D>(request: &mut Request<D>, mut response: Response<'a, D>) -> MiddlewareResult<'a, D> {
    //https://api.tfl.gov.uk/StopPoint/Search/knighton?faresOnly=False&app_id=&app_key=
    let client = Client::new();
    let query = request.query().get("query").expect("Missing query");
    let url = &format!("https://api.tfl.gov.uk/StopPoint/Search/{}?modes=bus", query);
    let obj = match json_for_request(client.get(url))
    {
        Ok(val) => val,
        Err(val) => {
            response.set(StatusCode::BadGateway);
            return response.send(val);
        }
    };

    let data = MapBuilder::new()
        .insert_vec("stops", |vecbuilder| {
            let mut vecb = vecbuilder;
            for stop in obj["matches"].members() {
                vecb = vecb.push_map(|mapbuilder| {
                    mapbuilder
                        .insert_str("id", stop["id"].as_str().unwrap())
                        .insert_str("name", stop["name"].as_str().unwrap())
                });
            }
            vecb
        })
        .insert_str("query", query)
        .build();
    render_to_response(response, "resources/templates/search.mustache", &data)
}

fn id_handler<'a, D>(request: &mut Request<D>, mut response: Response<'a, D>) -> MiddlewareResult<'a, D> {
    //https://api.tfl.gov.uk/StopPoint/Search/knighton?faresOnly=False&app_id=&app_key=
    let client = Client::new();
    let query = request.param("id").expect("Missing id");
    let url = &format!("https://api.tfl.gov.uk/StopPoint/{}", query);
    let obj = match json_for_request(client.get(url))
    {
        Ok(val) => val,
        Err(val) => {
            response.set(StatusCode::BadGateway);
            return response.send(val);
        }
    };

    let id = &obj["lineGroup"][0]["naptanIdReference"];
    match id {
        &json::JsonValue::Null => panic!("null!"),
        &json::JsonValue::String(ref val) => {
            response.set(Location(format!("/arrivals/{}", val)))
                    .set(StatusCode::PermanentRedirect);
        }
        _ => panic!("Something else!")
    };
    response.send("")
}

fn main() {
    /*let gridref = osgridref::OsGridRef::new(535987 as f32,171440 as f32);
    let latlon = gridref.to_lat_lon(datum::WGS84);
    println!("{:?}", latlon);*/
    log4rs::init_file("log.yaml", Default::default()).unwrap();
    let mut server = Nickel::new();
    let mut router = Nickel::router();

    router.get("/", root_handler);
    router.get("/search", search_handler);
    router.get("/id/:id", id_handler);
    router.get("/arrivals/:stopid", arrivals_handler);

    server.utilize(router);
    let port = 8000;
    server.listen(("127.0.0.1", port));
}
<commit_msg>Run through rustfmt<commit_after>extern crate log4rs;
extern crate hyper;

#[macro_use]
extern crate nickel;
use nickel::{Nickel, HttpRouter, Request, Response, MiddlewareResult, MediaType, QueryString};
use nickel::status::StatusCode;
use hyper::header::Location;

use hyper::client::{Client, RequestBuilder};
use std::io::Read;

extern crate json;
use json::iterators::Members;

extern crate mustache;
use mustache::MapBuilder;

extern crate itertools;
use itertools::Itertools;
extern crate time;

fn json_for_request(rb: RequestBuilder) -> Result<json::JsonValue, String> {
    let mut res = match rb.send() {
        Ok(val) => val,
        Err(_) => {
            return Err("Can't connect to TfL".to_string());
        }
    };
    let mut buffer: String = String::new();
    res.read_to_string(&mut buffer).expect("can't read");
    let obj = match json::parse(&buffer) {
        Ok(val) => val,
        Err(_) => {
            return Err(format!("Bad json: {} ", buffer));
        }
    };
    return Ok(obj);
}

fn render_to_response<'a, D>(mut response: Response<'a, D>,
                             path: &str,
                             data: &mustache::Data)
                             -> MiddlewareResult<'a, D> {
    let template = mustache::compile_path(path).expect("working template");
    let mut buffer: Vec<u8> = vec![];
    template.render_data(&mut buffer, data);
    response.set(MediaType::Html);
    response.send(buffer)
}

fn arrivals_handler<'a, D>(request: &mut Request<D>,
                           mut response: Response<'a, D>)
                           -> MiddlewareResult<'a, D> {
    let client = Client::new();
    let obj =
        match json_for_request(client.get(&format!("https://api.tfl.gov.uk/StopPoint/{}/Arrivals",
                          request.param("stopid").unwrap_or("Missing stopid")))) {
            Ok(val) => val,
            Err(val) => {
                response.set(StatusCode::BadGateway);
                return response.send(val);
            }
        };

    let members = match obj.members() {
        Members::Some(val) => val,
        Members::None => {
            response.set(StatusCode::BadGateway);
            return response.send(format!("No stops in: {:?}", obj));
        }
    };
    let member_slice = members.as_slice();
    let data = {
        if member_slice.len() == 0 {
            MapBuilder::new().build()
        } else {
            let last_item = member_slice[0].clone();
            let sorted_members = members.sorted_by(|a, b| {
                a["expectedArrival"].as_str().unwrap().cmp(b["expectedArrival"].as_str().unwrap())
            });

            MapBuilder::new()
                .insert_vec("buses", |vecbuilder| {
                    let mut vecb = vecbuilder;
                    for stop in sorted_members.clone() {
                        vecb = vecb.push_map(|mapbuilder| {
                            let when = time::strptime(stop["expectedArrival"].as_str().unwrap(),
                                                      "%FT%TZ")
                                .unwrap();
                            mapbuilder.insert_str("line", stop["lineName"].as_str().unwrap())
                                .insert_str("destination",
                                            stop["destinationName"].as_str().unwrap())
                                .insert_str("towards", stop["towards"].as_str().unwrap())
                                .insert_str("expectedArrival",
                                            when.to_local().strftime("%H:%M").unwrap())
                        });
                    }
                    vecb
                })
                .insert_str("stopName", last_item["stationName"].as_str().unwrap())
                .insert_str("stopNumber", last_item["platformName"].as_str().unwrap())
                .build()
        }
    };

    render_to_response(response, "resources/templates/arrivals.mustache", &data)
}

fn root_handler<'a, D>(_: &mut Request<D>,
                       mut response: Response<'a, D>)
                       -> MiddlewareResult<'a, D> {
    let data = MapBuilder::new().build();
    render_to_response(response, "resources/templates/root.mustache", &data)
}

fn search_handler<'a, D>(request: &mut Request<D>,
                         mut response: Response<'a, D>)
                         -> MiddlewareResult<'a, D> {
    // https://api.tfl.gov.uk/StopPoint/Search/knighton?faresOnly=False&app_id=&app_key=
    let client = Client::new();
    let query = request.query().get("query").expect("Missing query");
    let url = &format!("https://api.tfl.gov.uk/StopPoint/Search/{}?modes=bus",
                       query);
    let obj = match json_for_request(client.get(url)) {
        Ok(val) => val,
        Err(val) => {
            response.set(StatusCode::BadGateway);
            return response.send(val);
        }
    };

    let data = MapBuilder::new()
        .insert_vec("stops", |vecbuilder| {
            let mut vecb = vecbuilder;
            for stop in obj["matches"].members() {
                vecb = vecb.push_map(|mapbuilder| {
                    mapbuilder.insert_str("id", stop["id"].as_str().unwrap())
                        .insert_str("name", stop["name"].as_str().unwrap())
                });
            }
            vecb
        })
        .insert_str("query", query)
        .build();
    render_to_response(response, "resources/templates/search.mustache", &data)
}

fn id_handler<'a, D>(request: &mut Request<D>,
                     mut response: Response<'a, D>)
                     -> MiddlewareResult<'a, D> {
    // https://api.tfl.gov.uk/StopPoint/Search/knighton?faresOnly=False&app_id=&app_key=
    let client = Client::new();
    let query = request.param("id").expect("Missing id");
    let url = &format!("https://api.tfl.gov.uk/StopPoint/{}", query);
    let obj = match json_for_request(client.get(url)) {
        Ok(val) => val,
        Err(val) => {
            response.set(StatusCode::BadGateway);
            return response.send(val);
        }
    };

    let id = &obj["lineGroup"][0]["naptanIdReference"];
    match id {
        &json::JsonValue::Null => panic!("null!"),
        &json::JsonValue::String(ref val) => {
            response.set(Location(format!("/arrivals/{}", val)))
                .set(StatusCode::PermanentRedirect);
        }
        _ => panic!("Something else!"),
    };
    response.send("")
}

fn main() {
    // let gridref = osgridref::OsGridRef::new(535987 as f32,171440 as f32);
    // let latlon = gridref.to_lat_lon(datum::WGS84);
    // println!("{:?}", latlon);
    log4rs::init_file("log.yaml", Default::default()).unwrap();
    let mut server = Nickel::new();
    let mut router = Nickel::router();

    router.get("/", root_handler);
    router.get("/search", search_handler);
    router.get("/id/:id", id_handler);
    router.get("/arrivals/:stopid", arrivals_handler);

    server.utilize(router);
    let port = 8000;
    server.listen(("127.0.0.1", port));
}
<|endoftext|>


<commit_before>// Copyright 2015 Gomez Guillaume
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//   http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

use std::fs::{self, File, OpenOptions};
use std::io::{BufReader, SeekFrom, BufRead, Write, Read, Seek};
use std::fmt::{Display, Formatter, Error};
use std::path::PathBuf;

/*struct CommentEntry {
    file: String,
    text: String,
}

impl CommentEntry {
    fn new(file: &str, text: &str) -> CommentEntry {

    }
}

impl Display for CommentEntry {
    fn fmt(&self, f: &mut Formatter) -> Result<(), Error> {
        //let mut out = format!("=!={}\n=|={}\n", self.file, self.type_);
        let mut out = String::new();

        //println!("{}: {} => {:?}", self.file, self.line, self.comment_lines);
        /*for comment in &self.comment_lines {
            out.push_str(&comment);
            out.push_str("\n");
        }*/
        //writeln!(f, "{}", out)
        writeln!(f, "=!={}\n{}", self.file, self.text)
    }
}*/

enum EventType {
    Comment(String),
    Type(TypeStruct),
    InScope,
    OutScope,
}

struct TypeStruct {
    ty: Type,
    parent: Option<Box<TypeStruct>>,
    name: String,
}

impl TypeStruct {
    fn new(ty: Type, name: &str) -> TypeStruct {
        TypeStruct {
            ty: ty,
            name: name.to_owned(),
            parent: None,
        }
    }
}

#[derive(Debug)]
enum Type {
    Struct,
    Mod,
    Enum,
    Fn,
    Const,
    Static,
    Type,
    Variant,
}

impl Type {
    fn from(s: &str) -> Type {
        match s {
            "struct" => Type::Struct,
            "mod" => Type::Mod,
            "enum" => Type::Enum,
            "fn" => Type::Fn,
            "const" => Type::Const,
            "static" => Type::Static,
            "type" => Type::Type,
            _ => Type::Variant,
        }
    }
}

fn loop_over_files(path: &str/*, comments: &mut Vec<CommentEntry>*/) {
    match fs::read_dir(path) {
        Ok(it) => {
            for entry in it {
                check_path_type(entry.unwrap().path().to_str().unwrap()/*, comments*/);
            }
        }
        Err(e) => {
            println!("Error while trying to iterate over {}: {}", path, e);
        }
    }
}

fn move_to(words: &[&str], it: &mut usize, limit: &str) {
    while (*it + 1) < words.len() && words[*it + 1] != limit {
        *it += 1;
    }
}

fn strip_comments(path: &str/*, comments: &mut Vec<CommentEntry>*/) {
    match File::open(path) {
        Ok(mut f) => {
            let mut b_content = String::new();
            f.read_to_string(&mut b_content).unwrap();
            let content = b_content.replace("{", " { ")
                                   .replace("}", " } ")
                                   .replace("///", "/// ")
                                   .replace("\n", " \n ")
                                   .replace("(", " (");
            let b_content : Vec<&str> = b_content.split('\n').collect();
            let words : Vec<&str> = content.split(' ').filter(|s| s.len() > 0).collect();
            let mut it = 0;
            let mut line = 0;
            let mut event_list = vec!();

            while it < words.len() {
                match words[it] {
                    "///" => {
                        event_list.push(EventType::Comment(b_content[line].to_owned()));
                        move_to(&words, &mut it, "\n");
                    }
                    "struct" | "mod" | "fn" | "enum" | "const" | "static" | "type" => {
                        event_list.push(EventType::Type(TypeStruct::new(Type::from(words[it]), words[it + 1])));
                        it += 1;
                    }
                    "{" => {
                        event_list.push(EventType::InScope);
                    }
                    "}" => {
                        event_list.push(EventType::OutScope);
                    }
                    "\n" => {
                        line += 1;
                    }
                    _ => {}
                }
                it += 1;
            }
            for event in event_list {
                match event {
                    EventType::Comment(s) => { println!("{}", s); }
                    EventType::Type(t) => { println!("{:?} {}", t.ty, t.name);}
                    EventType::InScope => { println!("{{"); }
                    EventType::OutScope => { println!("}}"); }
                }
            }
        }
        Err(e) => {
            println!("Unable to open \"{}\": {}", path, e);
        }
    }
}

fn check_path_type(path: &str/*, comments: &mut Vec<CommentEntry>*/) {
    match fs::metadata(path) {
        Ok(m) => {
            if m.is_dir() {
                if path == ".." || path == "." {
                    return;
                }
                loop_over_files(path/*, comments*/);
            } else {
                if path == "./comments.cmts" {
                    return;
                }
                println!("-> {}", path);
                strip_comments(path/*, comments*/);
            }
        }
        Err(e) => {
            println!("An error occurred: {}", e);
        }
    }
}

fn main() {
    println!("Starting...");
    match fs::remove_file("comments.cmts") { _ => {} }
    //let mut comments = vec!();
    loop_over_files("."/*, &mut comments*/);
    match OpenOptions::new().write(true).create(true).truncate(true).open("comments.cmts") {
        Ok(mut f) => {
            /*for com_entry in comments {
                write!(f, "{}", com_entry).unwrap();
            }*/
            println!("Done !");
        }
        Err(e) => {
            println!("Error while opening \"{}\": {}", "comments.cmts", e);
        }
    }
}

// au lieu de splitter le fichier via '\n', lis le sur "une seule ligne".
// quand tu croises un "///", tu stockes ça comme un commentaire, un "mod", comme un module, etc...
// il faudra compter les '{' / '}' pour voir si t'es toujours danns le truc courant ou non<commit_msg>Few advancements<commit_after>// Copyright 2015 Gomez Guillaume
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//   http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

use std::fs::{self, File, OpenOptions};
use std::io::{BufReader, SeekFrom, BufRead, Write, Read, Seek};
use std::fmt::{Display, Formatter, Error};
use std::path::PathBuf;

/*struct CommentEntry {
    file: String,
    text: String,
}

impl CommentEntry {
    fn new(file: &str, text: &str) -> CommentEntry {

    }
}

impl Display for CommentEntry {
    fn fmt(&self, f: &mut Formatter) -> Result<(), Error> {
        //let mut out = format!("=!={}\n=|={}\n", self.file, self.type_);
        let mut out = String::new();

        //println!("{}: {} => {:?}", self.file, self.line, self.comment_lines);
        /*for comment in &self.comment_lines {
            out.push_str(&comment);
            out.push_str("\n");
        }*/
        //writeln!(f, "{}", out)
        writeln!(f, "=!={}\n{}", self.file, self.text)
    }
}*/

enum EventType {
    Comment(String),
    Type(TypeStruct),
    InScope,
    OutScope,
}

struct TypeStruct {
    ty: Type,
    parent: Option<Box<TypeStruct>>,
    name: String,
    args: Vec<String>,
}

impl TypeStruct {
    fn new(ty: Type, name: &str) -> TypeStruct {
        TypeStruct {
            ty: ty,
            name: name.to_owned(),
            args: vec!(),
            parent: None,
        }
    }

    fn from_args(ty: Type, args: Vec<String>) -> TypeStruct {
        TypeStruct {
            ty: ty,
            name: String::new(),
            args: args,
            parent: None,
        }
    }
}

impl Display for TypeStruct {
    fn fmt(&self, f: &mut Formatter) -> Result<(), Error> {
        let parent = self.parent;
        match parent {
            Some(p) => writeln!(f, "{}{} {}{}", p, self.ty, self.name, self.args.join(" ")),
            None => writeln!(f, "{} {}{}", self.ty, self.name, self.args.join(" ")),
        }
    }
}

#[derive(Debug)]
enum Type {
    Struct,
    Mod,
    Enum,
    Fn,
    Const,
    Static,
    Type,
    Variant,
    Impl,
    Unknown,
}

impl Type {
    fn from(s: &str) -> Type {
        match s {
            "struct" => Type::Struct,
            "mod" => Type::Mod,
            "enum" => Type::Enum,
            "fn" => Type::Fn,
            "const" => Type::Const,
            "static" => Type::Static,
            "type" => Type::Type,
            "impl" => Type::Impl,
            _ => Type::Variant,
        }
    }
}

impl Display for Type {
    fn fmt(&self, f: &mut Formatter) -> Result<(), Error> {
        match *self {
            Type::Struct => write!(f, "struct"),
            Type::Mod => write!(f, "mod"),
            Type::Enum => write!(f, "enum"),
            Type::Fn => write!(f, "fn"),
            Type::Const => write!(f, "const"),
            Type::Static => write!(f, "static"),
            Type::Type => write!(f, "type"),
            Type::Variant => write!(f, "variant"),
            Type::Impl => write!(f, "impl"),
            _ => write!(f, "?"),
        }
    }
}

fn loop_over_files(path: &str, f: &mut File) {
    match fs::read_dir(path) {
        Ok(it) => {
            for entry in it {
                check_path_type(entry.unwrap().path().to_str().unwrap(), f);
            }
        }
        Err(e) => {
            println!("Error while trying to iterate over {}: {}", path, e);
        }
    }
}

fn move_to(words: &[&str], it: &mut usize, limit: &str) {
    while (*it + 1) < words.len() && words[*it + 1] != limit {
        *it += 1;
    }
}

fn get_impl(words: &[&str], it: &mut usize) -> Vec<String> {
    let mut v = vec!();

    while (*it + 1) < words.len() {
        if words[(*it + 1)] == "{" || words[(*it + 1)] == ";" {
            break;
        }
        *it += 1;
        v.push(words[*it].to_owned());
    }
    v
}

fn strip_comments(path: &str, f: &mut File) {
    match File::open(path) {
        Ok(mut f) => {
            let mut b_content = String::new();
            f.read_to_string(&mut b_content).unwrap();
            let content = b_content.replace("{", " { ")
                                   .replace("}", " } ")
                                   .replace("///", "/// ")
                                   .replace("//!", "//! ")
                                   .replace("\n", " \n ")
                                   .replace("(", " (");
            let b_content : Vec<&str> = b_content.split('\n').collect();
            let words : Vec<&str> = content.split(' ').filter(|s| s.len() > 0).collect();
            let mut it = 0;
            let mut line = 0;
            let mut event_list = vec!();

            while it < words.len() {
                match words[it] {
                    "///" | "//!" => {
                        event_list.push(EventType::Comment(b_content[line].to_owned()));
                        move_to(&words, &mut it, "\n");
                    }
                    "struct" | "mod" | "fn" | "enum" | "const" | "static" | "type" => {
                        event_list.push(EventType::Type(TypeStruct::new(Type::from(words[it]), words[it + 1])));
                        it += 1;
                    }
                    "impl" => {
                        event_list.push(EventType::Type(TypeStruct::from_args(Type::Impl, get_impl(&words, &mut it))));
                    }
                    "{" => {
                        event_list.push(EventType::InScope);
                    }
                    "}" => {
                        event_list.push(EventType::OutScope);
                    }
                    "\n" => {
                        line += 1;
                    }
                    _ => {
                        event_list.push(EventType::Type(TypeStruct::new(Type::Unknown, words[it])));
                    }
                }
                it += 1;
            }
            writeln!(f, "=! {}", path).unwrap();
            let mut current = None;

            it = 0;
            while it < event_list.len() {
                let begin = it;
                while match event_list[it] {
                    EventType::Comment(_) => true,
                    _ => false,
                } {
                    it += 1;
                }
                let mut current = match current {
                    Some(c) => {
                        match event_list[it] {
                            EventType::Type(t) => {
                                t.parent = Some(Box::new(c));
                                t
                            }
                            _ => panic!("unexpected thing"),
                        }
                    },
                    None => match event_list[it] {
                        EventType::Type(t) => t,
                        _ => panic!("unexpected thing"),
                    }
                };
                // write data in file here
                it += 1;
            }
            /*for event in event_list {
                match event {
                    EventType::Comment(s) => {
                        writeln!(f, "== {}", )
                        println!("{}", s);
                    }
                    EventType::Type(t) => { println!("{:?} {}", t.ty, t.name);}
                    EventType::InScope => { println!("{{"); }
                    EventType::OutScope => { println!("}}"); }
                }
            }*/
        }
        Err(e) => {
            println!("Unable to open \"{}\": {}", path, e);
        }
    }
}

fn check_path_type(path: &str, f: &mut File) {
    match fs::metadata(path) {
        Ok(m) => {
            if m.is_dir() {
                if path == ".." || path == "." {
                    return;
                }
                loop_over_files(path, f);
            } else {
                if path == "./comments.cmts" {
                    return;
                }
                println!("-> {}", path);
                strip_comments(path, f);
            }
        }
        Err(e) => {
            println!("An error occurred: {}", e);
        }
    }
}

fn main() {
    println!("Starting...");
    match fs::remove_file("comments.cmts") { _ => {} }
    match OpenOptions::new().write(true).create(true).truncate(true).open("comments.cmts") {
        Ok(mut f) => {
            loop_over_files(".", &mut f);
            /*for com_entry in comments {
                write!(f, "{}", com_entry).unwrap();
            }*/
            println!("Done !");
        }
        Err(e) => {
            println!("Error while opening \"{}\": {}", "comments.cmts", e);
        }
    }
}

// au lieu de splitter le fichier via '\n', lis le sur "une seule ligne".
// quand tu croises un "///", tu stockes ça comme un commentaire, un "mod", comme un module, etc...
// il faudra compter les '{' / '}' pour voir si t'es toujours danns le truc courant ou non<|endoftext|>


<commit_before>extern crate rustyline;
use rustyline::Editor;
use rustyline::error::ReadlineError;
use rustyline::completion::Completer;
use std::env::args;
use std::path::PathBuf;
use std::process::Command;
use std::env::home_dir;
use std::fs::create_dir;
use std::fs::File;

struct BashCompleter{
    command: String,
}

impl Completer for BashCompleter {
    fn complete(&self, line: &str, pos: usize) -> rustyline::Result<(usize, Vec<String>)> {
        let words: Vec<&str> = line.split(" ").collect();
        let last_word = words.iter().nth(words.len()-1).unwrap();
        let count_words = words.len();

        let script = format!(
                    // COMP_WORDS must be specified in a bash context because normal environment
                    // variables can't be arrays
                    "
                    COMP_CWORD={}
                    COMP_POINT={}
                    COMP_LINE=\"{} {}\"
                    COMP_WORDS=({} {})
                    _xfunc git _git
                    printf \"%s\\n\" \"${{COMPREPLY[@]}}\"
                    ",
                    count_words,
                    pos + self.command.len() + 1,
                    self.command,
                    line,
                    self.command,
                    line,
                );
        let completion_result = Command::new("bash")
            .args(vec![
                "-lic",
                script.as_str(),
            ])
            .output();
        let completion_str = match completion_result {
            Ok(completion_string) => String::from_utf8(completion_string.stdout).unwrap(),
            Err(err) => {
                return Err(ReadlineError::Io(err));
            },
        };

        let word_len = last_word.len();
        let completions: Vec<String> = completion_str.split("\n").map(|s| s.to_string()).collect();

        Ok((pos-word_len, completions))
    }
}

fn main() {
    // TODO use a real arg parser
    let program = args().nth(1).expect("no program specified");

    let mut line_editor = Editor::<BashCompleter>::new();
    line_editor.set_completer(Some(BashCompleter{command: program.to_string()}));

    let history_file_option = match home_dir() {
        Some(home) => {
            let config_dir: PathBuf = [home, PathBuf::from(".tpol")].iter().collect();
            if !config_dir.exists() {
                println!("creating config dir at {}", config_dir.to_str().unwrap());
                create_dir(&config_dir).unwrap();
            }

            let history_dir: PathBuf = [config_dir, PathBuf::from("history")].iter().collect();
            if !history_dir.exists() {
                println!("creating history dir at {}", history_dir.to_str().unwrap());
                create_dir(&history_dir).unwrap();
            }

            let command_history: PathBuf = [history_dir, PathBuf::from(&program)].iter().collect();
            if !command_history.exists() {
                println!("creating history file for {} at {}", &program, command_history.to_str().unwrap());
                let _ = File::create(&command_history).unwrap();
            }

            Some(command_history)
        }
        None => None,
    };

    if let Some(ref history_file) = history_file_option.clone() {
        if let Err(err) = line_editor.load_history(history_file) {
            eprintln!("Warning: error while trying to load history file {}: {}", history_file.to_str().unwrap(), err);
        }
    } else {
        eprintln!("Warning: can't find home directory, no history will be saved");
    }

    loop {
        let line_result = line_editor.readline(format!(">{} ", program.clone()).as_str());
        match line_result {
            Ok(line) => {
                let mut process_result = Command::new(&program)
                    // TODO deal with quoted strings
                    .args(line.split(" ").collect::<Vec<&str>>())
                    .spawn();
                match process_result {
                    Ok(mut process) => {
                        // TODO maybe display return code?
                        let _ = process.wait();
                        // TODO maybe only save in history if it's successful?
                        line_editor.add_history_entry(&line);
                    },
                    Err(err) => println!("error while trying to run command: {:?}", err),
                }
            },
            Err(ReadlineError::Interrupted) => {
                break;
            },
            Err(ReadlineError::Eof) => {
                break;
            },
            Err(err) => {
                eprintln!("{:?}", err);
                break;
            }
        }
    }

    if let Some(ref history_file) = history_file_option {
        if let Err(err) = line_editor.save_history(history_file) {
            eprintln!("Warning: error while trying to load history file {}: {}", history_file.to_str().unwrap(), err);
        }
    }
}
<commit_msg>add support for custom prompt string commands<commit_after>extern crate rustyline;
extern crate serde;
extern crate serde_json;
use rustyline::Editor;
use rustyline::error::ReadlineError;
use rustyline::completion::Completer;
use std::env::args;
use std::path::PathBuf;
use std::process::Command;
use std::env::home_dir;
use std::fs::create_dir;
use std::fs::File;
use std::collections::HashMap;

struct BashCompleter{
    command: String,
}

impl Completer for BashCompleter {
    fn complete(&self, line: &str, pos: usize) -> rustyline::Result<(usize, Vec<String>)> {
        let words: Vec<&str> = line.split(" ").collect();
        let last_word = words.iter().nth(words.len()-1).unwrap();
        let count_words = words.len();

        let script = format!(
                    // COMP_WORDS must be specified in a bash context because normal environment
                    // variables can't be arrays
                    "
                    COMP_CWORD={}
                    COMP_POINT={}
                    COMP_LINE=\"{} {}\"
                    COMP_WORDS=({} {})
                    _xfunc git _git
                    printf \"%s\\n\" \"${{COMPREPLY[@]}}\"
                    ",
                    count_words,
                    pos + self.command.len() + 1,
                    self.command,
                    line,
                    self.command,
                    line,
                );
        let completion_result = Command::new("bash")
            .args(vec![
                "-lic",
                script.as_str(),
            ])
            .output();
        let completion_str = match completion_result {
            Ok(completion_string) => String::from_utf8(completion_string.stdout).unwrap(),
            Err(err) => {
                return Err(ReadlineError::Io(err));
            },
        };

        let word_len = last_word.len();
        let completions: Vec<String> = completion_str.split("\n").map(|s| s.to_string()).collect();

        Ok((pos-word_len, completions))
    }
}

fn main() {
    // TODO use a real arg parser
    let program = args().nth(1).expect("no program specified");

    let mut line_editor = Editor::<BashCompleter>::new();
    line_editor.set_completer(Some(BashCompleter{command: program.to_string()}));

    let (history_file_option, prompts_config_path_option) = match home_dir() {
        Some(home) => {
            let config_dir: PathBuf = [home, PathBuf::from(".tpol")].iter().collect();
            if !config_dir.exists() {
                println!("creating config dir at {}", config_dir.to_str().unwrap());
                create_dir(&config_dir).unwrap();
            }

            (
                {
                    let history_dir: PathBuf = [&config_dir, &PathBuf::from("history")].iter().collect();
                    if !history_dir.exists() {
                        println!("creating history dir at {}", history_dir.to_str().unwrap());
                        create_dir(&history_dir).unwrap();
                    }

                    let command_history: PathBuf = [history_dir, PathBuf::from(&program)].iter().collect();
                    if !command_history.exists() {
                        println!("creating history file for {} at {}", &program, command_history.to_str().unwrap());
                        let _ = File::create(&command_history).unwrap();
                    }

                    Some(command_history)
                },
                {
                    let prompts_config_path: PathBuf = [&config_dir, &PathBuf::from("prompts.json")].iter().collect();
                    Some(prompts_config_path)
                },
            )
        }
        None => (None, None)
    };

    if let Some(ref history_file) = history_file_option.clone() {
        if let Err(err) = line_editor.load_history(history_file) {
            eprintln!("Warning: error while trying to load history file {}: {}", history_file.to_str().unwrap(), err);
        }
    } else {
        eprintln!("Warning: can't find home directory, no history will be saved");
    }

    let program_to_prompt_string_command = match prompts_config_path_option {
        Some(prompts_config_path) => {
            File::open(prompts_config_path)
                .map(|file| {
                    let prompts_config: HashMap<String, String> = serde_json::from_reader(file)
                        .unwrap_or(HashMap::new());
                    prompts_config
                })
                .unwrap_or(HashMap::new())
        }
        None => HashMap::new(),
    };

    let prompt_string_command_option = program_to_prompt_string_command.get(&program);

    loop {
        let prompt_string = match prompt_string_command_option.as_ref() {
            Some(ref command) => {
                match Command::new("bash").args(vec!["-lic", command]).output() {
                    Ok(output) => String::from_utf8(output.stdout).unwrap(),
                    Err(_) => {
                        // TODO log error
                        "".to_string()
                    }
                }
            },
            None => "".to_string(),
        };

        let line_result = line_editor.readline(format!("{}>{} ", prompt_string, &program).as_str());
        match line_result {
            Ok(line) => {
                let mut process_result = Command::new(&program)
                    // TODO deal with quoted strings
                    .args(line.split(" ").collect::<Vec<&str>>())
                    .spawn();
                match process_result {
                    Ok(mut process) => {
                        // TODO maybe display return code?
                        let _ = process.wait();
                        // TODO maybe only save in history if it's successful?
                        line_editor.add_history_entry(&line);
                    },
                    Err(err) => println!("error while trying to run command: {:?}", err),
                }
            },
            Err(ReadlineError::Interrupted) => {
                break;
            },
            Err(ReadlineError::Eof) => {
                break;
            },
            Err(err) => {
                eprintln!("{:?}", err);
                break;
            }
        }
    }

    if let Some(ref history_file) = history_file_option {
        if let Err(err) = line_editor.save_history(history_file) {
            eprintln!("Warning: error while trying to load history file {}: {}", history_file.to_str().unwrap(), err);
        }
    }
}
<|endoftext|>


<commit_before>//! Our main CLI tool.

#![cfg_attr(feature="clippy", feature(plugin))]
#![cfg_attr(feature="clippy", plugin(clippy))]

#![deny(warnings)]

#[macro_use]
extern crate cage;
#[macro_use]
extern crate clap;
extern crate env_logger;
#[macro_use]
extern crate log;
extern crate rustc_serialize;
extern crate yaml_rust;

use std::env;
use std::fs;
use std::io::{self, Write};
use std::path::Path;
use std::process;
use yaml_rust::yaml;

use cage::command_runner::{Command, CommandRunner, OsCommandRunner};
use cage::cmd::*;
use cage::Result;

/// Load our command-line interface definitions from an external `clap`
/// YAML file.  We could create these using code, but at the cost of more
/// verbosity.
fn cli(yaml: &yaml::Yaml) -> clap::App {
    clap::App::from_yaml(yaml)
        .version(crate_version!())
}

//cli.gen_completions_to("cage", Shell::Fish, &mut std::io::stdout());

/// Custom methods we want to add to `clap::App`.
trait ArgMatchesExt {
    /// Do we need to generate `.cage/pods`?  This will probably be
    /// refactored in the future.
    fn should_output_project(&self) -> bool;

    /// Get either the specified override name, or a reasonable default.
    fn override_name(&self) -> &str;

    /// Extract options shared by `exec` and `run` from our command-line
    /// arguments.
    fn to_common_options(&self) -> cage::exec::CommonOptions;

    /// Extract `exec` options from our command-line arguments.
    fn to_exec_options(&self) -> cage::exec::ExecOptions;

    /// Extract `run` options from our command-line arguments.
    fn to_run_options(&self) -> cage::exec::RunOptions;

    /// Extract `exec::Target` from our command-line arguments.
    fn to_exec_target<'a>(&'a self,
                          project: &'a cage::Project,
                          ovr: &'a cage::Override)
                          -> Result<Option<cage::exec::Target<'a>>>;

    /// Extract `exec::Command` from our command-line arguments.
    fn to_exec_command(&self) -> Option<cage::exec::Command>;
}

impl<'a> ArgMatchesExt for clap::ArgMatches<'a> {
    fn should_output_project(&self) -> bool {
        self.subcommand_name() != Some("export")
    }

    fn override_name(&self) -> &str {
        self.value_of("override")
            .unwrap_or_else(|| {
                if self.subcommand_name() == Some("test") {
                    "test"
                } else {
                    "development"
                }
            })
    }

    fn to_common_options(&self) -> cage::exec::CommonOptions {
        let mut opts = cage::exec::CommonOptions::default();
        opts.detached = self.is_present("detached");
        opts.user = self.value_of("user").map(|v| v.to_owned());
        opts.allocate_tty = !self.is_present("no-allocate-tty");
        opts
    }

    fn to_exec_options(&self) -> cage::exec::ExecOptions {
        let mut opts = cage::exec::ExecOptions::default();
        opts.common = self.to_common_options();
        opts.privileged = self.is_present("privileged");
        opts
    }

    fn to_run_options(&self) -> cage::exec::RunOptions {
        let mut opts = cage::exec::RunOptions::default();
        opts.common = self.to_common_options();
        opts.entrypoint = self.value_of("entrypoint").map(|v| v.to_owned());
        if let Some(environment) = self.values_of("environment") {
            let environment: Vec<&str> = environment.collect();
            for env_val in environment.chunks(2) {
                if env_val.len() != 2 {
                    // Clap should prevent this.
                    panic!("Environment binding '{}' has no value", env_val[0]);
                }
                opts.environment.insert(env_val[0].to_owned(),
                                        env_val[1].to_owned());
            }
        }
        opts
    }

    fn to_exec_target<'b>(&'b self,
                          project: &'b cage::Project,
                          ovr: &'b cage::Override)
                          -> Result<Option<cage::exec::Target<'b>>> {
        match (self.value_of("POD"), self.value_of("SERVICE")) {
            (Some(pod), Some(service)) => {
                Ok(Some(try!(cage::exec::Target::new(project, ovr, pod, service))))
            }
            _ => Ok(None),
        }
    }

    fn to_exec_command(&self) -> Option<cage::exec::Command> {
        if self.is_present("COMMAND") {
            let values: Vec<&str> = self.values_of("COMMAND").unwrap().collect();
            assert!(values.len() >= 1, "too few values from CLI parser");
            Some(cage::exec::Command::new(values[0]).with_args(&values[1..]))
        } else {
            None
        }
    }
}

/// The function which does the real work.  Unlike `main`, we have a return
/// type of `Result` and may therefore use `try!` to handle errors.
fn run(matches: &clap::ArgMatches) -> Result<()> {
    // We know that we always have a subcommand because our `cli.yml`
    // requires this and `clap` is supposed to enforce it.
    let sc_name = matches.subcommand_name().unwrap();
    let sc_matches: &clap::ArgMatches = matches.subcommand_matches(sc_name).unwrap();

    // Handle any subcommands that we can handle without a project
    // directory.
    match sc_name {
        "sysinfo" => {
            try!(all_versions());
            return Ok(());
        }
        "new" => {
            try!(cage::Project::generate_new(&try!(env::current_dir()),
                                             sc_matches.value_of("NAME").unwrap()));
            return Ok(());
        }
        _ => {},
    }

    // Handle our standard arguments that apply to all subcommands.
    let mut proj = try!(cage::Project::from_current_dir());
    if let Some(project_name) = matches.value_of("project-name") {
        proj.set_name(project_name);
    }
    if let Some(default_tags_path) = matches.value_of("default-tags") {
        let file = try!(fs::File::open(default_tags_path));
        proj.set_default_tags(try!(cage::DefaultTags::read(file)));
    }
    let override_name = matches.override_name();
    let ovr = try!(proj.ovr(override_name)
        .ok_or_else(|| err!("override {} is not defined", override_name)));

    // Output our project's `*.yml` files for `docker-compose` if we'll
    // need it.
    if matches.should_output_project() {
        try!(proj.output(ovr));
    }

    // Handle our subcommands that require a `Project`.
    let runner = OsCommandRunner::new();
    match sc_name {
        "pull" => try!(proj.pull(&runner, &ovr)),
        "build" => try!(proj.build(&runner, &ovr)),
        "up" => {
            let pods: Vec<&str> = sc_matches.values_of("POD")
                .map_or_else(|| vec![], |p| p.collect());
            if pods.is_empty() {
                try!(proj.up_all(&runner, &ovr));
            } else {
                try!(proj.up(&runner, &ovr, &pods));
            }
        }
        "stop" => try!(proj.stop(&runner, &ovr)),
        "run" => {
            let opts = sc_matches.to_run_options();
            let cmd = sc_matches.to_exec_command();
            let pod = sc_matches.value_of("POD").unwrap();
            try!(proj.run(&runner, &ovr, pod, cmd.as_ref(), &opts));
        }
        "exec" => {
            let target = try!(sc_matches.to_exec_target(&proj, &ovr)).unwrap();
            let opts = sc_matches.to_exec_options();
            let cmd = sc_matches.to_exec_command().unwrap();
            try!(proj.exec(&runner, &target, &cmd, &opts));
        }
        "shell" => {
            let target = try!(sc_matches.to_exec_target(&proj, &ovr)).unwrap();
            let opts = sc_matches.to_exec_options();
            try!(proj.shell(&runner, &target, &opts));
        }
        "test" => {
            let target = try!(sc_matches.to_exec_target(&proj, &ovr)).unwrap();
            let cmd = sc_matches.to_exec_command();
            try!(proj.test(&runner, &target, cmd.as_ref()));
        }
        "repo" => try!(run_repo(&runner, &proj, &ovr, sc_matches)),
        "generate" => try!(run_generate(&runner, &proj, &ovr, sc_matches)),
        "export" => {
            let dir = sc_matches.value_of("DIR").unwrap();
            try!(proj.export(&ovr, &Path::new(dir)));
        }
        unknown => unreachable!("Unexpected subcommand '{}'", unknown),
    }

    Ok(())
}

/// Our `repo` subcommand.
fn run_repo<R>(runner: &R,
               proj: &cage::Project,
               ovr: &cage::Override,
               matches: &clap::ArgMatches)
               -> Result<()>
    where R: CommandRunner
{
    // We know that we always have a subcommand because our `cli.yml`
    // requires this and `clap` is supposed to enforce it.
    let sc_name = matches.subcommand_name().unwrap();
    let sc_matches: &clap::ArgMatches = matches.subcommand_matches(sc_name).unwrap();

    // Dispatch our subcommand.
    match sc_name {
        "list" => try!(proj.repo_list(runner)),
        "clone" => {
            let alias = sc_matches.value_of("ALIAS").unwrap();
            try!(proj.repo_clone(runner, alias));
            // Regenerate our output now that we've cloned.
            try!(proj.output(ovr));
        }
        unknown => unreachable!("Unexpected subcommand '{}'", unknown),
    }
    Ok(())
}

/// Our `generate` subcommand.
fn run_generate<R>(_runner: &R,
                   proj: &cage::Project,
                   _ovr: &cage::Override,
                   matches: &clap::ArgMatches)
                   -> Result<()>
    where R: CommandRunner
{
    // We know that we always have a subcommand because our `cli.yml`
    // requires this and `clap` is supposed to enforce it.
    let sc_name = matches.subcommand_name().unwrap();
    let sc_matches: &clap::ArgMatches = matches.subcommand_matches(sc_name).unwrap();

    match sc_name {
        // TODO LOW: Allow running this without a project?
        "completion" => {
            let shell = match sc_matches.value_of("SHELL").unwrap() {
                "bash" => clap::Shell::Bash,
                "fish" => clap::Shell::Fish,
                unknown => unreachable!("Unknown shell '{}'", unknown),
            };
            let cli_yaml = load_yaml!("cli.yml");
            cli(cli_yaml).gen_completions("cage", shell, proj.root_dir());
        }
        other => try!(proj.generate(other)),
    }
    Ok(())
}

/// Print the version of this executable.
fn version() {
    println!("cage {}", cage::version());
}

/// Print the version of this executable and also the versions of several
/// tools we use.
fn all_versions() -> Result<()> {
    version();

    let runner = OsCommandRunner::new();
    for tool in &["docker", "docker-compose", "git"] {
        try!(runner.build(tool)
            .arg("--version")
            .exec());
    }
    Ok(())
}

/// Our main entry point.
fn main() {
    // Initialize logging with some custom options, mostly so we can see
    // our own warnings.
    let mut builder = env_logger::LogBuilder::new();
    builder.filter(Some("compose_yml"), log::LogLevelFilter::Warn);
    builder.filter(Some("cage"), log::LogLevelFilter::Warn);
    if let Ok(config) = env::var("RUST_LOG") {
        builder.parse(&config);
    }
    builder.init().unwrap();

    // Parse our command-line arguments.
    let cli_yaml = load_yaml!("cli.yml");
    let matches: clap::ArgMatches = cli(cli_yaml).get_matches();
    debug!("Arguments: {:?}", &matches);

    // Defer all our real work to `run`, and handle any errors.  This is a
    // standard Rust pattern to make error-handling in `main` nicer.
    if let Err(ref err) = run(&matches) {
        // We use `unwrap` here to turn I/O errors into application panics.
        // If we can't print a message to stderr without an I/O error,
        // the situation is hopeless.
        write!(io::stderr(), "Error: ").unwrap();
        for e in err.iter() {
            write!(io::stderr(), "{}\n", e).unwrap();
        }
        if let Some(backtrace) = err.backtrace() {
            write!(io::stderr(), "{:?}\n", backtrace).unwrap();
        }
        process::exit(1);
    }
}
<commit_msg>Run `cargo fmt`<commit_after>//! Our main CLI tool.

#![cfg_attr(feature="clippy", feature(plugin))]
#![cfg_attr(feature="clippy", plugin(clippy))]

#![deny(warnings)]

#[macro_use]
extern crate cage;
#[macro_use]
extern crate clap;
extern crate env_logger;
#[macro_use]
extern crate log;
extern crate rustc_serialize;
extern crate yaml_rust;

use std::env;
use std::fs;
use std::io::{self, Write};
use std::path::Path;
use std::process;
use yaml_rust::yaml;

use cage::command_runner::{Command, CommandRunner, OsCommandRunner};
use cage::cmd::*;
use cage::Result;

/// Load our command-line interface definitions from an external `clap`
/// YAML file.  We could create these using code, but at the cost of more
/// verbosity.
fn cli(yaml: &yaml::Yaml) -> clap::App {
    clap::App::from_yaml(yaml).version(crate_version!())
}

/// Custom methods we want to add to `clap::App`.
trait ArgMatchesExt {
    /// Do we need to generate `.cage/pods`?  This will probably be
    /// refactored in the future.
    fn should_output_project(&self) -> bool;

    /// Get either the specified override name, or a reasonable default.
    fn override_name(&self) -> &str;

    /// Extract options shared by `exec` and `run` from our command-line
    /// arguments.
    fn to_common_options(&self) -> cage::exec::CommonOptions;

    /// Extract `exec` options from our command-line arguments.
    fn to_exec_options(&self) -> cage::exec::ExecOptions;

    /// Extract `run` options from our command-line arguments.
    fn to_run_options(&self) -> cage::exec::RunOptions;

    /// Extract `exec::Target` from our command-line arguments.
    fn to_exec_target<'a>(&'a self,
                          project: &'a cage::Project,
                          ovr: &'a cage::Override)
                          -> Result<Option<cage::exec::Target<'a>>>;

    /// Extract `exec::Command` from our command-line arguments.
    fn to_exec_command(&self) -> Option<cage::exec::Command>;
}

impl<'a> ArgMatchesExt for clap::ArgMatches<'a> {
    fn should_output_project(&self) -> bool {
        self.subcommand_name() != Some("export")
    }

    fn override_name(&self) -> &str {
        self.value_of("override")
            .unwrap_or_else(|| {
                if self.subcommand_name() == Some("test") {
                    "test"
                } else {
                    "development"
                }
            })
    }

    fn to_common_options(&self) -> cage::exec::CommonOptions {
        let mut opts = cage::exec::CommonOptions::default();
        opts.detached = self.is_present("detached");
        opts.user = self.value_of("user").map(|v| v.to_owned());
        opts.allocate_tty = !self.is_present("no-allocate-tty");
        opts
    }

    fn to_exec_options(&self) -> cage::exec::ExecOptions {
        let mut opts = cage::exec::ExecOptions::default();
        opts.common = self.to_common_options();
        opts.privileged = self.is_present("privileged");
        opts
    }

    fn to_run_options(&self) -> cage::exec::RunOptions {
        let mut opts = cage::exec::RunOptions::default();
        opts.common = self.to_common_options();
        opts.entrypoint = self.value_of("entrypoint").map(|v| v.to_owned());
        if let Some(environment) = self.values_of("environment") {
            let environment: Vec<&str> = environment.collect();
            for env_val in environment.chunks(2) {
                if env_val.len() != 2 {
                    // Clap should prevent this.
                    panic!("Environment binding '{}' has no value", env_val[0]);
                }
                opts.environment.insert(env_val[0].to_owned(), env_val[1].to_owned());
            }
        }
        opts
    }

    fn to_exec_target<'b>(&'b self,
                          project: &'b cage::Project,
                          ovr: &'b cage::Override)
                          -> Result<Option<cage::exec::Target<'b>>> {
        match (self.value_of("POD"), self.value_of("SERVICE")) {
            (Some(pod), Some(service)) => {
                Ok(Some(try!(cage::exec::Target::new(project, ovr, pod, service))))
            }
            _ => Ok(None),
        }
    }

    fn to_exec_command(&self) -> Option<cage::exec::Command> {
        if self.is_present("COMMAND") {
            let values: Vec<&str> = self.values_of("COMMAND").unwrap().collect();
            assert!(values.len() >= 1, "too few values from CLI parser");
            Some(cage::exec::Command::new(values[0]).with_args(&values[1..]))
        } else {
            None
        }
    }
}

/// The function which does the real work.  Unlike `main`, we have a return
/// type of `Result` and may therefore use `try!` to handle errors.
fn run(matches: &clap::ArgMatches) -> Result<()> {
    // We know that we always have a subcommand because our `cli.yml`
    // requires this and `clap` is supposed to enforce it.
    let sc_name = matches.subcommand_name().unwrap();
    let sc_matches: &clap::ArgMatches = matches.subcommand_matches(sc_name).unwrap();

    // Handle any subcommands that we can handle without a project
    // directory.
    match sc_name {
        "sysinfo" => {
            try!(all_versions());
            return Ok(());
        }
        "new" => {
            try!(cage::Project::generate_new(&try!(env::current_dir()),
                                             sc_matches.value_of("NAME").unwrap()));
            return Ok(());
        }
        _ => {}
    }

    // Handle our standard arguments that apply to all subcommands.
    let mut proj = try!(cage::Project::from_current_dir());
    if let Some(project_name) = matches.value_of("project-name") {
        proj.set_name(project_name);
    }
    if let Some(default_tags_path) = matches.value_of("default-tags") {
        let file = try!(fs::File::open(default_tags_path));
        proj.set_default_tags(try!(cage::DefaultTags::read(file)));
    }
    let override_name = matches.override_name();
    let ovr = try!(proj.ovr(override_name)
        .ok_or_else(|| err!("override {} is not defined", override_name)));

    // Output our project's `*.yml` files for `docker-compose` if we'll
    // need it.
    if matches.should_output_project() {
        try!(proj.output(ovr));
    }

    // Handle our subcommands that require a `Project`.
    let runner = OsCommandRunner::new();
    match sc_name {
        "pull" => try!(proj.pull(&runner, &ovr)),
        "build" => try!(proj.build(&runner, &ovr)),
        "up" => {
            let pods: Vec<&str> = sc_matches.values_of("POD")
                .map_or_else(|| vec![], |p| p.collect());
            if pods.is_empty() {
                try!(proj.up_all(&runner, &ovr));
            } else {
                try!(proj.up(&runner, &ovr, &pods));
            }
        }
        "stop" => try!(proj.stop(&runner, &ovr)),
        "run" => {
            let opts = sc_matches.to_run_options();
            let cmd = sc_matches.to_exec_command();
            let pod = sc_matches.value_of("POD").unwrap();
            try!(proj.run(&runner, &ovr, pod, cmd.as_ref(), &opts));
        }
        "exec" => {
            let target = try!(sc_matches.to_exec_target(&proj, &ovr)).unwrap();
            let opts = sc_matches.to_exec_options();
            let cmd = sc_matches.to_exec_command().unwrap();
            try!(proj.exec(&runner, &target, &cmd, &opts));
        }
        "shell" => {
            let target = try!(sc_matches.to_exec_target(&proj, &ovr)).unwrap();
            let opts = sc_matches.to_exec_options();
            try!(proj.shell(&runner, &target, &opts));
        }
        "test" => {
            let target = try!(sc_matches.to_exec_target(&proj, &ovr)).unwrap();
            let cmd = sc_matches.to_exec_command();
            try!(proj.test(&runner, &target, cmd.as_ref()));
        }
        "repo" => try!(run_repo(&runner, &proj, &ovr, sc_matches)),
        "generate" => try!(run_generate(&runner, &proj, &ovr, sc_matches)),
        "export" => {
            let dir = sc_matches.value_of("DIR").unwrap();
            try!(proj.export(&ovr, &Path::new(dir)));
        }
        unknown => unreachable!("Unexpected subcommand '{}'", unknown),
    }

    Ok(())
}

/// Our `repo` subcommand.
fn run_repo<R>(runner: &R,
               proj: &cage::Project,
               ovr: &cage::Override,
               matches: &clap::ArgMatches)
               -> Result<()>
    where R: CommandRunner
{
    // We know that we always have a subcommand because our `cli.yml`
    // requires this and `clap` is supposed to enforce it.
    let sc_name = matches.subcommand_name().unwrap();
    let sc_matches: &clap::ArgMatches = matches.subcommand_matches(sc_name).unwrap();

    // Dispatch our subcommand.
    match sc_name {
        "list" => try!(proj.repo_list(runner)),
        "clone" => {
            let alias = sc_matches.value_of("ALIAS").unwrap();
            try!(proj.repo_clone(runner, alias));
            // Regenerate our output now that we've cloned.
            try!(proj.output(ovr));
        }
        unknown => unreachable!("Unexpected subcommand '{}'", unknown),
    }
    Ok(())
}

/// Our `generate` subcommand.
fn run_generate<R>(_runner: &R,
                   proj: &cage::Project,
                   _ovr: &cage::Override,
                   matches: &clap::ArgMatches)
                   -> Result<()>
    where R: CommandRunner
{
    // We know that we always have a subcommand because our `cli.yml`
    // requires this and `clap` is supposed to enforce it.
    let sc_name = matches.subcommand_name().unwrap();
    let sc_matches: &clap::ArgMatches = matches.subcommand_matches(sc_name).unwrap();

    match sc_name {
        // TODO LOW: Allow running this without a project?
        "completion" => {
            let shell = match sc_matches.value_of("SHELL").unwrap() {
                "bash" => clap::Shell::Bash,
                "fish" => clap::Shell::Fish,
                unknown => unreachable!("Unknown shell '{}'", unknown),
            };
            let cli_yaml = load_yaml!("cli.yml");
            cli(cli_yaml).gen_completions("cage", shell, proj.root_dir());
        }
        other => try!(proj.generate(other)),
    }
    Ok(())
}

/// Print the version of this executable.
fn version() {
    println!("cage {}", cage::version());
}

/// Print the version of this executable and also the versions of several
/// tools we use.
fn all_versions() -> Result<()> {
    version();

    let runner = OsCommandRunner::new();
    for tool in &["docker", "docker-compose", "git"] {
        try!(runner.build(tool)
            .arg("--version")
            .exec());
    }
    Ok(())
}

/// Our main entry point.
fn main() {
    // Initialize logging with some custom options, mostly so we can see
    // our own warnings.
    let mut builder = env_logger::LogBuilder::new();
    builder.filter(Some("compose_yml"), log::LogLevelFilter::Warn);
    builder.filter(Some("cage"), log::LogLevelFilter::Warn);
    if let Ok(config) = env::var("RUST_LOG") {
        builder.parse(&config);
    }
    builder.init().unwrap();

    // Parse our command-line arguments.
    let cli_yaml = load_yaml!("cli.yml");
    let matches: clap::ArgMatches = cli(cli_yaml).get_matches();
    debug!("Arguments: {:?}", &matches);

    // Defer all our real work to `run`, and handle any errors.  This is a
    // standard Rust pattern to make error-handling in `main` nicer.
    if let Err(ref err) = run(&matches) {
        // We use `unwrap` here to turn I/O errors into application panics.
        // If we can't print a message to stderr without an I/O error,
        // the situation is hopeless.
        write!(io::stderr(), "Error: ").unwrap();
        for e in err.iter() {
            write!(io::stderr(), "{}\n", e).unwrap();
        }
        if let Some(backtrace) = err.backtrace() {
            write!(io::stderr(), "{:?}\n", backtrace).unwrap();
        }
        process::exit(1);
    }
}
<|endoftext|>