<commit_before>//!
//! A `cargo` subcommand for building `GraphViz` DOT files of dependency graphs.
//! This subcommand was originally based off and inspired by the project
//! [cargo-dot](https://github.com/maxsnew/cargo-dot) by [Max
//! New](https://github.com/maxsnew)
//!
//!
//! ## Demo
//!
//! Let's say we wanted to build a dependency graph of
//! [cargo-count](https://github.com/kbknapp/cargo-count) but we wanted
//! optional dependencies to use red dashed lines and black boxes, and regular
//! (aka "build") dependencies to use orange lines to green diamonds, one would
//! run the following.
//!
//! **NOTE:** `GraphViz` `dot` needs to be installed to produce the .PNG from the
//! dotfile
//!
//! ```ignore
//! $ cargo graph --optional-line-style dashed --optional-line-color red
//! --optional-shape box --build-shape diamond --build-color green
//! --build-line-color orange > cargo-count.dot
//! $ dot -Tpng > rainbow-graph.png cargo-count.dot
//! ```
//!
//! **NOTE:** It's also possible to run `cargo graph [options] | dot [options]
//! > [file]` instead of individual commands
//!
//! The above commands would produce the following graph:
//!
//! ![cargo-graph dependencies](rainbow-graph.png)
//!
//! Now, *why* someone would do that to a graph is a different story...but it's
//! possible :)
//!
//! ## Installing
//!
//! `cargo-graph` can be installed with `cargo install`
//!
//! ```ignore
//! $ cargo install cargo-graph
//! ```
//!
//! This may require a nightly version of `cargo` if you get an error about the
//! `install` command not being found. You may also compile and install the
//! traditional way by following the instructions below.
//!
//!
//! ## Compiling
//!
//! Follow these instructions to compile `cargo-count`, then skip down to
//! Installation.
//!
//! 1. Ensure you have current version of `cargo` and
//! [Rust](https://www.rust-lang.org) installed
//! 2. Clone the project `$ git clone https://github.com/kbknapp/cargo-graph
//! && cd cargo-graph`
//! 3. Build the project `$ cargo build --release` (**NOTE:** There is a large
//! performance difference when compiling without optimizations, so I recommend
//! always using `--release` to enable to them)
//! 4. Once complete, the binary will be located at
//! `target/release/cargo-graph`
//!
//! ## Installation and Usage
//!
//! All you need to do is place `cargo-graph` somewhere in your `$PATH`. Then
//! run `cargo graph` anywhere in your project directory. For full details see
//! below.
//!
//! ### Linux / OS X
//!
//! You have two options, place `cargo-graph` into a directory that is already
//! located in your `$PATH` variable (To see which directories those are, open
//! a terminal and type `echo "${PATH//:/\n}"`, the quotation marks are
//! important), or you can add a custom directory to your `$PATH`
//!
//! **Option 1**
//! If you have write permission to a directory listed in your `$PATH` or you
//! have root permission (or via `sudo`), simply copy the `cargo-graph` to that
//! directory `# sudo cp cargo-graph/usr/local/bin`
//!
//! **Option 2**
//! If you do not have root, `sudo`, or write permission to any directory
//! already in `$PATH` you can create a directory inside your home directory,
//! and add that. Many people use `$HOME/.bin` to keep it hidden (and not
//! clutter your home directory), or `$HOME/bin` if you want it to be always
//! visible. Here is an example to make the directory, add it to `$PATH`, and
//! copy `cargo-graph` there.
//!
//! Simply change `bin` to whatever you'd like to name the directory, and
//! `.bashrc` to whatever your shell startup file is (usually `.bashrc`,
//! `.bash_profile`, or `.zshrc`)
//!
//! ```ignore
//! $ mkdir ~/bin
//! $ echo "export PATH=$PATH:$HOME/bin" >> ~/.bashrc
//! $ cp cargo-graph~/bin
//! $ source ~/.bashrc
//! ```
//!
//! ### Windows
//!
//! On Windows 7/8 you can add directory to the `PATH` variable by opening a
//! command line as an administrator and running
//!
//! ```ignore
//! C\:> setx path \"%path%;C:\path\to\cargo-graph\binary\"
//! ```
//!
//! Otherwise, ensure you have the `cargo-graph` binary in the directory which
//! you operating in the command line from, because Windows automatically adds
//! your current directory to PATH (i.e. if you open a command line to
//! `C:\my_project\` to use `cargo-graph` ensure `cargo-graph.exe` is inside
//! that directory as well).
//!
//!
//! ### Options
//!
//! There are a few options for using `cargo-graph` which should be somewhat
//! self explanatory.
//!
//! ```ignore
//! USAGE:
//!     cargo graph [FLAGS] [OPTIONS]
//!
//! FLAGS:
//!     -h, --help       Prints help information
//!     -V, --version    Prints version information
//!
//! OPTIONS:
//! --build-color <COLOR>            Color for regular deps (Defaults
//! to 'black')
//! [values: blue black yellow purple
//! green red white orange]
//! --build-deps <true|false>        Should build deps be in the graph?
//! (Defaults to 'true')
//! ex. --build-deps=false OR
//! --build-deps=no
//! --build-line-color <COLOR>       Line color for regular deps
//! (Defaults to 'black')
//! [values: blue black yellow purple
//! green red white orange]
//! --build-line-style <STYLE>       Line style for build deps
//! (Defaults to 'solid')
//!                                           [values: solid dotted dashed]
//! --build-shape <SHAPE>            Shape for regular deps (Defaults
//! to 'round')
//! [values: box round diamond
//! triangle]
//! --dev-color <COLOR>              Color for dev deps (Defaults to
//! 'black')
//! [values: blue black yellow purple
//! green red white orange]
//! --dev-deps <true|false>          Should dev deps be included in the
//! graph? (Defaults to 'false')
//! ex. --dev-deps=true OR
//! --dev-deps=yes
//! --dev-line-color <COLOR>         Line color for dev deps (Defaults
//! to 'black')
//! [values: blue black yellow purple
//! green red white orange]
//! --dev-line-style <STYLE>         Line style for dev deps (Defaults
//! to 'solid')
//!                                           [values: solid dotted dashed]
//! --dev-shape <SHAPE>              Shape for dev deps (Defaults to
//! 'round')
//! [values: box round diamond
//! triangle]
//!         --dot-file <FILE>                Output file (Default to stdout)
//! --lock-file <FILE>               Specify location of .lock file
//! (Default 'Cargo.lock')
//! --manifest-file <FILE>           Specify location of manifest file
//! (Default 'Cargo.toml')
//! --optional-color <COLOR>         Color for optional deps (Defaults
//! to 'black')
//! [values: blue black yellow purple
//! green red white orange]
//! --optional-deps <true|false>     Should optional deps be in the
//! graph? (Defaults to 'true')
//! ex. --optional-deps=false OR
//! --optional-deps=no
//! --optional-line-color <COLOR>    Line color for optional deps
//! (Defaults to 'black')
//! [values: blue black yellow purple
//! green red white orange]
//! --optional-line-style <STYLE>    Line style for optional deps
//! (Defaults to 'solid')
//!                                           [values: solid dotted dashed]
//! --optional-shape <SHAPE>         Shape for optional deps (Defaults
//! to 'round')
//! [values: box round diamond
//! triangle]
//! ```
//!
//! ## License
//!
//! `cargo-graph` is released under the terms of the MIT. See the LICENSE-MIT
//! file for the details.
//!
//! ## Dependencies Tree
//! ![cargo-graph dependencies](cargo-graph.png)

#![cfg_attr(feature = "nightly", feature(plugin))]
#![cfg_attr(feature = "lints", plugin(clippy))]
#![cfg_attr(feature = "lints", allow(explicit_iter_loop))]
#![cfg_attr(feature = "lints", allow(should_implement_trait))]
#![cfg_attr(feature = "lints", allow(unstable_features))]
#![cfg_attr(feature = "lints", deny(warnings))]
#![cfg_attr(not(any(feature = "nightly", feature = "unstable")), deny(unstable_features))]
#![deny(missing_docs,
        missing_debug_implementations,
        missing_copy_implementations,
        trivial_casts, trivial_numeric_casts,
        unsafe_code,
        unused_import_braces,
        unused_qualifications)]

extern crate toml;
#[macro_use]
extern crate clap;
#[cfg(feature = "color")]
extern crate ansi_term;

use std::fs::File;
use std::io::{self, BufWriter};
use std::path::Path;

use clap::{App, AppSettings, Arg, ArgMatches, SubCommand};

use error::CliResult;
use config::Config;
use project::Project;

#[macro_use]
mod macros;
mod error;
mod graph;
mod fmt;
mod project;
mod dep;
mod config;
mod util;

static LINE_STYLES: [&'static str; 3] = ["solid", "dotted", "dashed"];
static COLORS: [&'static str; 8] = ["blue", "black", "yellow", "purple", "green", "red", "white",
                                    "orange"];
static DEP_SHAPES: [&'static str; 4] = ["box", "round", "diamond", "triangle"];

fn parse_cli<'a>() -> ArgMatches<'a> {
    App::new("cargo-graph")
        .version(&*format!("v{}", crate_version!()))
        .bin_name("cargo")
        .settings(&[AppSettings::GlobalVersion, AppSettings::SubcommandRequired])
        .subcommand(SubCommand::with_name("graph")
                        .author("Kevin K. <kbknapp@gamil.com>\nMax New <maxsnew@gmail.com>")
                        .about("Generate a graph of package dependencies in graphviz format")
                        .args_from_usage("
                            -I, --include-versions 'Include the dependency version on nodes'
                                --dot-file [PATH] 'Output file (Default stdout)'
                                --dev-deps [true|false] 'Should dev deps be included in the graph? (Default false, also allows yes|no)'
                                --build-deps [true|false] 'Should build deps be in the graph? (Default true, also allows yes|no)'
                                --optional-deps [true|false] 'Should optional deps be in the graph? (Default true, also allows yes|no)'
                        ")
                        .args(&[
                            Arg::from_usage("--lock-file [PATH] 'Specify location of .lock file (Default Cargo.lock)'")
                                .validator(is_file),
                            Arg::from_usage("--manifest-file [PATH] 'Specify location of manifest file (Default Cargo.toml)'")
                                .validator(is_file),
                            Arg::from_usage("--build-line-style [STYLE] 'Line style for build deps (Default solid)'")
                                .possible_values(&LINE_STYLES),
                            Arg::from_usage("--build-line-color [COLOR] 'Line color for regular deps (Default black)'")
                                .possible_values(&COLORS),
                            Arg::from_usage("--build-shape [SHAPE] 'Shape for regular deps (Default round)'")
                                .possible_values(&DEP_SHAPES),
                            Arg::from_usage("--build-color [COLOR] 'Color for regular deps (Default black)'")
                                .possible_values(&COLORS),
                            Arg::from_usage("--optional-line-style [STYLE] 'Line style for optional deps (Default solid)'")
                                .possible_values(&LINE_STYLES),
                            Arg::from_usage("--optional-line-color [COLOR] 'Line color for optional deps (Default black)'")
                                .possible_values(&COLORS),
                            Arg::from_usage("--optional-shape [SHAPE] 'Shape for optional deps (Default round)'")
                                .possible_values(&DEP_SHAPES),
                            Arg::from_usage("--optional-color [COLOR] 'Color for optional deps (Default black)'")
                                .possible_values(&COLORS),
                            Arg::from_usage("--dev-line-style [STYLE] 'Line style for dev deps (Default solid)'")
                                .possible_values(&LINE_STYLES),
                            Arg::from_usage("--dev-line-color [COLOR] 'Line color for dev deps (Default black)'")
                                .possible_values(&COLORS),
                            Arg::from_usage("--dev-shape [SHAPE] 'Shape for dev deps (Default round)'")
                                .possible_values(&DEP_SHAPES),
                            Arg::from_usage("--dev-color [COLOR] 'Color for dev deps (Default black)'")
                                .possible_values(&COLORS)]))
        .get_matches()
}

fn main() {
    debugln!("parsing cli...");
    let m = parse_cli();

    if let Some(m) = m.subcommand_matches("graph") {
        let cfg = Config::from_matches(m).unwrap_or_else(|e| e.exit());
        debugln!("cfg={:#?}", cfg);
        execute(cfg).map_err(|e| e.exit()).unwrap();
    }
}

fn execute(cfg: Config) -> CliResult<()> {
    let project = try!(Project::with_config(&cfg));
    let graph = try!(project.graph());

    match cfg.dot_file {
        None => {
            let o = io::stdout();
            let mut bw = BufWriter::new(o.lock());
            graph.render_to(&mut bw)
        }
        Some(file) => {
            let o = File::create(&Path::new(&file)).expect("Failed to create file");
            let mut bw = BufWriter::new(o);
            graph.render_to(&mut bw)
        }
    }
}

fn is_file(s: String) -> Result<(), String> {
    let p = Path::new(&*s);
    if let None = p.file_name() {
        return Err(format!("'{}' doesn't appear to be a valid file name", &*s));
    }
    Ok(())
}
<commit_msg>style: takes advantage of some new clap methods<commit_after>//!
//! A `cargo` subcommand for building `GraphViz` DOT files of dependency graphs.
//! This subcommand was originally based off and inspired by the project
//! [cargo-dot](https://github.com/maxsnew/cargo-dot) by [Max
//! New](https://github.com/maxsnew)
//!
//!
//! ## Demo
//!
//! Let's say we wanted to build a dependency graph of
//! [cargo-count](https://github.com/kbknapp/cargo-count) but we wanted
//! optional dependencies to use red dashed lines and black boxes, and regular
//! (aka "build") dependencies to use orange lines to green diamonds, one would
//! run the following.
//!
//! **NOTE:** `GraphViz` `dot` needs to be installed to produce the .PNG from the
//! dotfile
//!
//! ```ignore
//! $ cargo graph --optional-line-style dashed --optional-line-color red
//! --optional-shape box --build-shape diamond --build-color green
//! --build-line-color orange > cargo-count.dot
//! $ dot -Tpng > rainbow-graph.png cargo-count.dot
//! ```
//!
//! **NOTE:** It's also possible to run `cargo graph [options] | dot [options]
//! > [file]` instead of individual commands
//!
//! The above commands would produce the following graph:
//!
//! ![cargo-graph dependencies](rainbow-graph.png)
//!
//! Now, *why* someone would do that to a graph is a different story...but it's
//! possible :)
//!
//! ## Installing
//!
//! `cargo-graph` can be installed with `cargo install`
//!
//! ```ignore
//! $ cargo install cargo-graph
//! ```
//!
//! This may require a nightly version of `cargo` if you get an error about the
//! `install` command not being found. You may also compile and install the
//! traditional way by following the instructions below.
//!
//!
//! ## Compiling
//!
//! Follow these instructions to compile `cargo-count`, then skip down to
//! Installation.
//!
//! 1. Ensure you have current version of `cargo` and
//! [Rust](https://www.rust-lang.org) installed
//! 2. Clone the project `$ git clone https://github.com/kbknapp/cargo-graph
//! && cd cargo-graph`
//! 3. Build the project `$ cargo build --release` (**NOTE:** There is a large
//! performance difference when compiling without optimizations, so I recommend
//! always using `--release` to enable to them)
//! 4. Once complete, the binary will be located at
//! `target/release/cargo-graph`
//!
//! ## Installation and Usage
//!
//! All you need to do is place `cargo-graph` somewhere in your `$PATH`. Then
//! run `cargo graph` anywhere in your project directory. For full details see
//! below.
//!
//! ### Linux / OS X
//!
//! You have two options, place `cargo-graph` into a directory that is already
//! located in your `$PATH` variable (To see which directories those are, open
//! a terminal and type `echo "${PATH//:/\n}"`, the quotation marks are
//! important), or you can add a custom directory to your `$PATH`
//!
//! **Option 1**
//! If you have write permission to a directory listed in your `$PATH` or you
//! have root permission (or via `sudo`), simply copy the `cargo-graph` to that
//! directory `# sudo cp cargo-graph/usr/local/bin`
//!
//! **Option 2**
//! If you do not have root, `sudo`, or write permission to any directory
//! already in `$PATH` you can create a directory inside your home directory,
//! and add that. Many people use `$HOME/.bin` to keep it hidden (and not
//! clutter your home directory), or `$HOME/bin` if you want it to be always
//! visible. Here is an example to make the directory, add it to `$PATH`, and
//! copy `cargo-graph` there.
//!
//! Simply change `bin` to whatever you'd like to name the directory, and
//! `.bashrc` to whatever your shell startup file is (usually `.bashrc`,
//! `.bash_profile`, or `.zshrc`)
//!
//! ```ignore
//! $ mkdir ~/bin
//! $ echo "export PATH=$PATH:$HOME/bin" >> ~/.bashrc
//! $ cp cargo-graph~/bin
//! $ source ~/.bashrc
//! ```
//!
//! ### Windows
//!
//! On Windows 7/8 you can add directory to the `PATH` variable by opening a
//! command line as an administrator and running
//!
//! ```ignore
//! C\:> setx path \"%path%;C:\path\to\cargo-graph\binary\"
//! ```
//!
//! Otherwise, ensure you have the `cargo-graph` binary in the directory which
//! you operating in the command line from, because Windows automatically adds
//! your current directory to PATH (i.e. if you open a command line to
//! `C:\my_project\` to use `cargo-graph` ensure `cargo-graph.exe` is inside
//! that directory as well).
//!
//!
//! ### Options
//!
//! There are a few options for using `cargo-graph` which should be somewhat
//! self explanatory.
//!
//! ```ignore
//! USAGE:
//!     cargo graph [FLAGS] [OPTIONS]
//!
//! FLAGS:
//!     -h, --help       Prints help information
//!     -V, --version    Prints version information
//!
//! OPTIONS:
//! --build-color <COLOR>            Color for regular deps (Defaults
//! to 'black')
//! [values: blue black yellow purple
//! green red white orange]
//! --build-deps <true|false>        Should build deps be in the graph?
//! (Defaults to 'true')
//! ex. --build-deps=false OR
//! --build-deps=no
//! --build-line-color <COLOR>       Line color for regular deps
//! (Defaults to 'black')
//! [values: blue black yellow purple
//! green red white orange]
//! --build-line-style <STYLE>       Line style for build deps
//! (Defaults to 'solid')
//!                                           [values: solid dotted dashed]
//! --build-shape <SHAPE>            Shape for regular deps (Defaults
//! to 'round')
//! [values: box round diamond
//! triangle]
//! --dev-color <COLOR>              Color for dev deps (Defaults to
//! 'black')
//! [values: blue black yellow purple
//! green red white orange]
//! --dev-deps <true|false>          Should dev deps be included in the
//! graph? (Defaults to 'false')
//! ex. --dev-deps=true OR
//! --dev-deps=yes
//! --dev-line-color <COLOR>         Line color for dev deps (Defaults
//! to 'black')
//! [values: blue black yellow purple
//! green red white orange]
//! --dev-line-style <STYLE>         Line style for dev deps (Defaults
//! to 'solid')
//!                                           [values: solid dotted dashed]
//! --dev-shape <SHAPE>              Shape for dev deps (Defaults to
//! 'round')
//! [values: box round diamond
//! triangle]
//!         --dot-file <FILE>                Output file (Default to stdout)
//! --lock-file <FILE>               Specify location of .lock file
//! (Default 'Cargo.lock')
//! --manifest-file <FILE>           Specify location of manifest file
//! (Default 'Cargo.toml')
//! --optional-color <COLOR>         Color for optional deps (Defaults
//! to 'black')
//! [values: blue black yellow purple
//! green red white orange]
//! --optional-deps <true|false>     Should optional deps be in the
//! graph? (Defaults to 'true')
//! ex. --optional-deps=false OR
//! --optional-deps=no
//! --optional-line-color <COLOR>    Line color for optional deps
//! (Defaults to 'black')
//! [values: blue black yellow purple
//! green red white orange]
//! --optional-line-style <STYLE>    Line style for optional deps
//! (Defaults to 'solid')
//!                                           [values: solid dotted dashed]
//! --optional-shape <SHAPE>         Shape for optional deps (Defaults
//! to 'round')
//! [values: box round diamond
//! triangle]
//! ```
//!
//! ## License
//!
//! `cargo-graph` is released under the terms of the MIT. See the LICENSE-MIT
//! file for the details.
//!
//! ## Dependencies Tree
//! ![cargo-graph dependencies](cargo-graph.png)

#![cfg_attr(feature = "nightly", feature(plugin))]
#![cfg_attr(feature = "lints", plugin(clippy))]
#![cfg_attr(feature = "lints", allow(explicit_iter_loop))]
#![cfg_attr(feature = "lints", allow(should_implement_trait))]
#![cfg_attr(feature = "lints", allow(unstable_features))]
#![cfg_attr(feature = "lints", deny(warnings))]
#![cfg_attr(not(any(feature = "nightly", feature = "unstable")), deny(unstable_features))]
#![deny(missing_docs,
        missing_debug_implementations,
        missing_copy_implementations,
        trivial_casts, trivial_numeric_casts,
        unsafe_code,
        unused_import_braces,
        unused_qualifications)]

extern crate toml;
#[macro_use]
extern crate clap;
#[cfg(feature = "color")]
extern crate ansi_term;

use std::fs::File;
use std::io::{self, BufWriter};
use std::path::Path;

use clap::{App, AppSettings, Arg, ArgMatches, SubCommand};

use error::CliResult;
use config::Config;
use project::Project;

#[macro_use]
mod macros;
mod error;
mod graph;
mod fmt;
mod project;
mod dep;
mod config;
mod util;

static LINE_STYLES: [&'static str; 3] = ["solid", "dotted", "dashed"];
static COLORS: [&'static str; 8] = ["blue", "black", "yellow", "purple", "green", "red", "white",
                                    "orange"];
static DEP_SHAPES: [&'static str; 4] = ["box", "round", "diamond", "triangle"];

fn parse_cli<'a>() -> ArgMatches<'a> {
    App::new("cargo-graph")
        .version(&*format!("v{}", crate_version!()))
        .bin_name("cargo")
        .settings(&[AppSettings::GlobalVersion, AppSettings::SubcommandRequired])
        .subcommand(SubCommand::with_name("graph")
                        .author("Kevin K. <kbknapp@gamil.com>\nMax New <maxsnew@gmail.com>")
                        .about("Generate a graph of package dependencies in graphviz format")
                        .args_from_usage("
                            -I, --include-versions 'Include the dependency version on nodes'
                                --dot-file [PATH] 'Output file (Default stdout)'
                                --dev-deps [true|false] 'Should dev deps be included in the graph? (Default false, also allows yes|no)'
                                --build-deps [true|false] 'Should build deps be in the graph? (Default true, also allows yes|no)'
                                --optional-deps [true|false] 'Should optional deps be in the graph? (Default true, also allows yes|no)'
                        ")
                        .args(&[
                            Arg::from_usage("--lock-file [PATH] 'Specify location of .lock file'")
								.default_value("Cargo.lock")
                                .validator(is_file),
                            Arg::from_usage("--manifest-file [PATH] 'Specify location of manifest file'")
								.default_value("Cargo.toml")
                                .validator(is_file),
                            Arg::from_usage("--build-line-style [STYLE] 'Line style for build deps'")
								.default_value("solid")
                                .possible_values(&LINE_STYLES),
                            Arg::from_usage("--build-line-color [COLOR] 'Line color for regular deps'")
								.default_value("black")
                                .possible_values(&COLORS),
                            Arg::from_usage("--build-shape [SHAPE] 'Shape for regular deps'")
								.default_value("round")
                                .possible_values(&DEP_SHAPES),
                            Arg::from_usage("--build-color [COLOR] 'Color for regular deps'")
								.default_value("black")
                                .possible_values(&COLORS),
                            Arg::from_usage("--optional-line-style [STYLE] 'Line style for optional deps'")
								.default_value("solid")
                                .possible_values(&LINE_STYLES),
                            Arg::from_usage("--optional-line-color [COLOR] 'Line color for optional deps'")
								.default_value("black")
                                .possible_values(&COLORS),
                            Arg::from_usage("--optional-shape [SHAPE] 'Shape for optional deps'")
								.default_value("round")
                                .possible_values(&DEP_SHAPES),
                            Arg::from_usage("--optional-color [COLOR] 'Color for optional deps'")
								.default_value("black")
                                .possible_values(&COLORS),
                            Arg::from_usage("--dev-line-style [STYLE] 'Line style for dev deps'")
								.default_value("solid")
                                .possible_values(&LINE_STYLES),
                            Arg::from_usage("--dev-line-color [COLOR] 'Line color for dev deps'")
								.default_value("black")
                                .possible_values(&COLORS),
                            Arg::from_usage("--dev-shape [SHAPE] 'Shape for dev deps'")
								.default_value("round")
                                .possible_values(&DEP_SHAPES),
                            Arg::from_usage("--dev-color [COLOR] 'Color for dev deps'")
								.default_value("black")
                                .possible_values(&COLORS)]))
        .get_matches()
}

fn main() {
    debugln!("parsing cli...");
    let m = parse_cli();

    if let Some(m) = m.subcommand_matches("graph") {
        let cfg = Config::from_matches(m).unwrap_or_else(|e| e.exit());
        debugln!("cfg={:#?}", cfg);
        execute(cfg).map_err(|e| e.exit()).unwrap();
    }
}

fn execute(cfg: Config) -> CliResult<()> {
    let project = try!(Project::with_config(&cfg));
    let graph = try!(project.graph());

    match cfg.dot_file {
        None => {
            let o = io::stdout();
            let mut bw = BufWriter::new(o.lock());
            graph.render_to(&mut bw)
        }
        Some(file) => {
            let o = File::create(&Path::new(&file)).expect("Failed to create file");
            let mut bw = BufWriter::new(o);
            graph.render_to(&mut bw)
        }
    }
}

fn is_file(s: String) -> Result<(), String> {
    let p = Path::new(&*s);
    if let None = p.file_name() {
        return Err(format!("'{}' doesn't appear to be a valid file name", &*s));
    }
    Ok(())
}
<|endoftext|>


<commit_before>extern crate loc;

#[macro_use]
extern crate clap;
extern crate deque;
extern crate itertools;
extern crate num_cpus;
extern crate walkdir;
extern crate regex;

use clap::{Arg, App, AppSettings};
use walkdir::WalkDir;

use std::collections::HashMap;
use std::collections::hash_map::Entry;
use std::thread;

use deque::{Stealer, Stolen};
use regex::Regex;

use loc::*;

enum Work {
    File(String),
    Quit,
}

struct Worker {
    chan: Stealer<Work>,
}

#[derive(Clone)]
struct FileCount {
    path: String,
    lang: Lang,
    count: Count,
}

// This concurrency pattern ripped directly from ripgrep
impl Worker {
    fn run(self) -> Vec<FileCount> {
        let mut v: Vec<FileCount> = vec![];
        loop {
            match self.chan.steal() {
                // What causes these?
                Stolen::Empty | Stolen::Abort => continue,
                Stolen::Data(Work::Quit) => break,
                Stolen::Data(Work::File(path)) => {
                    let lang = lang_from_ext(&path);
                    if lang != Lang::Unrecognized {
                        let count = count(&path);
                        v.push(FileCount {
                            lang: lang,
                            path: String::from(path),
                            count: count,
                        });
                    }
                }
            };
        }
        v
    }
}

fn main() {

    let matches = App::new("count")
        .global_settings(&[AppSettings::ColoredHelp])
        .version("0.1")
        .author("Curtis Gagliardi <curtis@curtis.io>")
        .about("counts things quickly hopefully")
        // TODO(cgag): actually implement filtering
        .arg(Arg::with_name("exclude")
            .required(false)
            .long("exclude")
            .value_name("REGEX")
            .takes_value(true)
            .help("Rust regex of files to exclude"))
        .arg(Arg::with_name("include")
            .required(false)
            .long("include")
            .value_name("REGEX")
            .takes_value(true)
            .help("Rust regex matching files to include. Anything not matched will be excluded"))
        .arg(Arg::with_name("files")
             .required(false)
             .long("files")
             .takes_value(false)
             .help("Show stats for individual files"))
        .arg(Arg::with_name("sort")
            .required(false)
            .long("sort")
            .value_name("COLUMN")
            .takes_value(true)
            .help("Column to sort by"))
        .arg(Arg::with_name("target")
            .multiple(true)
            .help("File or directory to count"))
        .get_matches();

    let targets = values_t!(matches.values_of("target"), String).unwrap_or(vec![String::from(".")]);
    let sort = matches.value_of("sort").unwrap_or("code");
    let by_file = matches.is_present("files");
    let exclude_regex = match matches.value_of("exclude") {
        Some(rx_str) => {
            match Regex::new(rx_str) {
                Ok(r) => Some(r),
                Err(e) => {
                    println!("Error processing exclude regex: {}", e);
                    std::process::exit(1);
                }
            }
        }
        None => None,
    };
    let include_regex = match matches.value_of("include") {
        Some(rx_str) => {
            match Regex::new(rx_str) {
                Ok(r) => Some(r),
                Err(e) => {
                    println!("Error processing include regex: {}", e);
                    std::process::exit(1);
                }
            }
        }
        None => None,
    };

    let threads = num_cpus::get();
    let mut workers = vec![];
    let (workq, stealer) = deque::new();
    for _ in 0..threads {
        let worker = Worker { chan: stealer.clone() };
        workers.push(thread::spawn(|| worker.run()));
    }

    for target in targets {
        let files = WalkDir::new(target)
            .into_iter()
            .filter_map(|entry| entry.ok())
            .filter(|entry| entry.file_type().is_file())
            .map(|entry| String::from(entry.path().to_str().unwrap()))
            .filter(|path| match include_regex {
                None => true,
                Some(ref include) => include.is_match(path),
            })
            .filter(|path| match exclude_regex {
                None => true,
                Some(ref exclude) => !exclude.is_match(path),
            });

        for path in files {
            workq.push(Work::File(path));
        }
    }

    for _ in 0..workers.len() {
        workq.push(Work::Quit);
    }

    let mut filecounts: Vec<FileCount> = Vec::new();
    for worker in workers {
        filecounts.extend(worker.join().unwrap().iter().cloned())
    }

    let mut by_lang: HashMap<Lang, Vec<FileCount>> = HashMap::new();
    for fc in filecounts {
        match by_lang.entry(fc.lang) {
            Entry::Occupied(mut by_lang) => by_lang.get_mut().push(fc),
            Entry::Vacant(by_lang) => {
                by_lang.insert(vec![fc]);
            }
        };
    }

    let linesep = str_repeat("-", 80);

    if by_file {
        println!("{}", linesep);
        println!(" {0: <17} {1: >8} {2: >12} {3: >12} {4: >12} {5: >12}",
                 "Language",
                 "Files",
                 "Lines",
                 "Blank",
                 "Comment",
                 "Code");
        println!("{}", linesep);

        // TODO(cgag): do the summing first, so we can do additional sorting
        // by totals.
        for (lang, mut filecounts) in by_lang {
            let mut total = Count::default();
            for fc in &filecounts {
                total.merge(&fc.count);
            }

            println!("{}", linesep);
            println!(" {0: <17} {1: >8} {2: >12} {3: >12} {4: >12} {5: >12}",
                     lang,
                     filecounts.len(),
                     total.lines,
                     total.blank,
                     total.comment,
                     total.code);

            match sort {
                "code" => filecounts.sort_by(|fc1, fc2| fc2.count.code.cmp(&fc1.count.code)),
                "comment" => {
                    filecounts.sort_by(|fc1, fc2| fc2.count.comment.cmp(&fc1.count.comment))
                }
                "blank" => filecounts.sort_by(|fc1, fc2| fc2.count.blank.cmp(&fc1.count.blank)),
                "lines" => filecounts.sort_by(|fc1, fc2| fc2.count.lines.cmp(&fc1.count.lines)),
                // No sorting by language or files here. Need to do it at a higher level.
                _ => (),
            }

            println!("{}", linesep);
            for fc in filecounts {
                println!("|{0: <25} {1: >12} {2: >12} {3: >12} {4: >12}",
                         last_n_chars(&fc.path, 25),
                         fc.count.lines,
                         fc.count.blank,
                         fc.count.comment,
                         fc.count.code);
            }
        }
    } else {
        let mut lang_totals: HashMap<&Lang, LangTotal> = HashMap::new();
        for (lang, filecounts) in &by_lang {
            let mut lang_total = Count::default();
            for fc in filecounts {
                lang_total.merge(&fc.count);
            }
            lang_totals.insert(lang,
                               LangTotal {
                                   files: filecounts.len() as u32,
                                   count: lang_total,
                               });
        }

        let mut totals_by_lang = lang_totals.iter().collect::<Vec<(&&Lang, &LangTotal)>>();
        match sort {
            "language" => totals_by_lang.sort_by(|&(l1, _), &(l2, _)| l1.to_s().cmp(l2.to_s())),
            "files" => totals_by_lang.sort_by(|&(_, c1), &(_, c2)| c2.files.cmp(&c1.files)),
            "code" => {
                totals_by_lang.sort_by(|&(_, c1), &(_, c2)| c2.count.code.cmp(&c1.count.code))
            }
            "comment" => {
                totals_by_lang.sort_by(|&(_, c1), &(_, c2)| c2.count.comment.cmp(&c1.count.comment))
            }
            "blank" => {
                totals_by_lang.sort_by(|&(_, c1), &(_, c2)| c2.count.blank.cmp(&c1.count.blank))
            }
            "lines" => {
                totals_by_lang.sort_by(|&(_, c1), &(_, c2)| c2.count.lines.cmp(&c1.count.lines))
            }
            _ => {
                println!("invalid sort option {}, sorting by code", sort);
                totals_by_lang.sort_by(|&(_, c1), &(_, c2)| c2.count.code.cmp(&c1.count.code))
            }
        }

        print_totals_by_lang(&linesep, &totals_by_lang);
    }

    fn last_n_chars(s: &str, n: usize) -> String {
        if s.len() <= n {
            return String::from(s);
        }
        s.chars().skip(s.len() - n).collect::<String>()
    }

    fn str_repeat(s: &str, n: usize) -> String {
        std::iter::repeat(s).take(n).collect::<Vec<_>>().join("")
    }

    fn print_totals_by_lang(linesep: &str, totals_by_lang: &[(&&Lang, &LangTotal)]) {
        println!("{}", linesep);
        println!(" {0: <17} {1: >8} {2: >12} {3: >12} {4: >12} {5: >12}",
                 "Language",
                 "Files",
                 "Lines",
                 "Blank",
                 "Comment",
                 "Code");
        println!("{}", linesep);

        for &(lang, total) in totals_by_lang {
            println!(" {0: <17} {1: >8} {2: >12} {3: >12} {4: >12} {5: >12}",
                     lang,
                     total.files,
                     total.count.lines,
                     total.count.blank,
                     total.count.comment,
                     total.count.code);
        }

        let mut totals = LangTotal {
            files: 0,
            count: Count::default(),
        };
        for &(_, total) in totals_by_lang {
            totals.files += total.files;
            totals.count.code += total.count.code;
            totals.count.blank += total.count.blank;
            totals.count.comment += total.count.comment;
            totals.count.lines += total.count.lines;
        }

        println!("{}", linesep);
        println!(" {0: <17} {1: >8} {2: >12} {3: >12} {4: >12} {5: >12}",
                 "Total",
                 totals.files,
                 totals.count.lines,
                 totals.count.blank,
                 totals.count.comment,
                 totals.count.code);
        println!("{}", linesep);
    }
}
<commit_msg>fix --version output<commit_after>extern crate loc;

#[macro_use]
extern crate clap;
extern crate deque;
extern crate itertools;
extern crate num_cpus;
extern crate walkdir;
extern crate regex;

use clap::{Arg, App, AppSettings};
use walkdir::WalkDir;

use std::collections::HashMap;
use std::collections::hash_map::Entry;
use std::thread;

use deque::{Stealer, Stolen};
use regex::Regex;

use loc::*;

enum Work {
    File(String),
    Quit,
}

struct Worker {
    chan: Stealer<Work>,
}

#[derive(Clone)]
struct FileCount {
    path: String,
    lang: Lang,
    count: Count,
}

// This concurrency pattern ripped directly from ripgrep
impl Worker {
    fn run(self) -> Vec<FileCount> {
        let mut v: Vec<FileCount> = vec![];
        loop {
            match self.chan.steal() {
                // What causes these?
                Stolen::Empty | Stolen::Abort => continue,
                Stolen::Data(Work::Quit) => break,
                Stolen::Data(Work::File(path)) => {
                    let lang = lang_from_ext(&path);
                    if lang != Lang::Unrecognized {
                        let count = count(&path);
                        v.push(FileCount {
                            lang: lang,
                            path: String::from(path),
                            count: count,
                        });
                    }
                }
            };
        }
        v
    }
}

fn main() {

    let matches = App::new("count")
        .global_settings(&[AppSettings::ColoredHelp])
        .version(crate_version!())
        .author("Curtis Gagliardi <curtis@curtis.io>")
        .about("counts things quickly hopefully")
        // TODO(cgag): actually implement filtering
        .arg(Arg::with_name("exclude")
            .required(false)
            .long("exclude")
            .value_name("REGEX")
            .takes_value(true)
            .help("Rust regex of files to exclude"))
        .arg(Arg::with_name("include")
            .required(false)
            .long("include")
            .value_name("REGEX")
            .takes_value(true)
            .help("Rust regex matching files to include. Anything not matched will be excluded"))
        .arg(Arg::with_name("files")
             .required(false)
             .long("files")
             .takes_value(false)
             .help("Show stats for individual files"))
        .arg(Arg::with_name("sort")
            .required(false)
            .long("sort")
            .value_name("COLUMN")
            .takes_value(true)
            .help("Column to sort by"))
        .arg(Arg::with_name("target")
            .multiple(true)
            .help("File or directory to count"))
        .get_matches();

    let targets = values_t!(matches.values_of("target"), String).unwrap_or(vec![String::from(".")]);
    let sort = matches.value_of("sort").unwrap_or("code");
    let by_file = matches.is_present("files");
    let exclude_regex = match matches.value_of("exclude") {
        Some(rx_str) => {
            match Regex::new(rx_str) {
                Ok(r) => Some(r),
                Err(e) => {
                    println!("Error processing exclude regex: {}", e);
                    std::process::exit(1);
                }
            }
        }
        None => None,
    };
    let include_regex = match matches.value_of("include") {
        Some(rx_str) => {
            match Regex::new(rx_str) {
                Ok(r) => Some(r),
                Err(e) => {
                    println!("Error processing include regex: {}", e);
                    std::process::exit(1);
                }
            }
        }
        None => None,
    };

    let threads = num_cpus::get();
    let mut workers = vec![];
    let (workq, stealer) = deque::new();
    for _ in 0..threads {
        let worker = Worker { chan: stealer.clone() };
        workers.push(thread::spawn(|| worker.run()));
    }

    for target in targets {
        let files = WalkDir::new(target)
            .into_iter()
            .filter_map(|entry| entry.ok())
            .filter(|entry| entry.file_type().is_file())
            .map(|entry| String::from(entry.path().to_str().unwrap()))
            .filter(|path| match include_regex {
                None => true,
                Some(ref include) => include.is_match(path),
            })
            .filter(|path| match exclude_regex {
                None => true,
                Some(ref exclude) => !exclude.is_match(path),
            });

        for path in files {
            workq.push(Work::File(path));
        }
    }

    for _ in 0..workers.len() {
        workq.push(Work::Quit);
    }

    let mut filecounts: Vec<FileCount> = Vec::new();
    for worker in workers {
        filecounts.extend(worker.join().unwrap().iter().cloned())
    }

    let mut by_lang: HashMap<Lang, Vec<FileCount>> = HashMap::new();
    for fc in filecounts {
        match by_lang.entry(fc.lang) {
            Entry::Occupied(mut by_lang) => by_lang.get_mut().push(fc),
            Entry::Vacant(by_lang) => {
                by_lang.insert(vec![fc]);
            }
        };
    }

    let linesep = str_repeat("-", 80);

    if by_file {
        println!("{}", linesep);
        println!(" {0: <17} {1: >8} {2: >12} {3: >12} {4: >12} {5: >12}",
                 "Language",
                 "Files",
                 "Lines",
                 "Blank",
                 "Comment",
                 "Code");
        println!("{}", linesep);

        // TODO(cgag): do the summing first, so we can do additional sorting
        // by totals.
        for (lang, mut filecounts) in by_lang {
            let mut total = Count::default();
            for fc in &filecounts {
                total.merge(&fc.count);
            }

            println!("{}", linesep);
            println!(" {0: <17} {1: >8} {2: >12} {3: >12} {4: >12} {5: >12}",
                     lang,
                     filecounts.len(),
                     total.lines,
                     total.blank,
                     total.comment,
                     total.code);

            match sort {
                "code" => filecounts.sort_by(|fc1, fc2| fc2.count.code.cmp(&fc1.count.code)),
                "comment" => {
                    filecounts.sort_by(|fc1, fc2| fc2.count.comment.cmp(&fc1.count.comment))
                }
                "blank" => filecounts.sort_by(|fc1, fc2| fc2.count.blank.cmp(&fc1.count.blank)),
                "lines" => filecounts.sort_by(|fc1, fc2| fc2.count.lines.cmp(&fc1.count.lines)),
                // No sorting by language or files here. Need to do it at a higher level.
                _ => (),
            }

            println!("{}", linesep);
            for fc in filecounts {
                println!("|{0: <25} {1: >12} {2: >12} {3: >12} {4: >12}",
                         last_n_chars(&fc.path, 25),
                         fc.count.lines,
                         fc.count.blank,
                         fc.count.comment,
                         fc.count.code);
            }
        }
    } else {
        let mut lang_totals: HashMap<&Lang, LangTotal> = HashMap::new();
        for (lang, filecounts) in &by_lang {
            let mut lang_total = Count::default();
            for fc in filecounts {
                lang_total.merge(&fc.count);
            }
            lang_totals.insert(lang,
                               LangTotal {
                                   files: filecounts.len() as u32,
                                   count: lang_total,
                               });
        }

        let mut totals_by_lang = lang_totals.iter().collect::<Vec<(&&Lang, &LangTotal)>>();
        match sort {
            "language" => totals_by_lang.sort_by(|&(l1, _), &(l2, _)| l1.to_s().cmp(l2.to_s())),
            "files" => totals_by_lang.sort_by(|&(_, c1), &(_, c2)| c2.files.cmp(&c1.files)),
            "code" => {
                totals_by_lang.sort_by(|&(_, c1), &(_, c2)| c2.count.code.cmp(&c1.count.code))
            }
            "comment" => {
                totals_by_lang.sort_by(|&(_, c1), &(_, c2)| c2.count.comment.cmp(&c1.count.comment))
            }
            "blank" => {
                totals_by_lang.sort_by(|&(_, c1), &(_, c2)| c2.count.blank.cmp(&c1.count.blank))
            }
            "lines" => {
                totals_by_lang.sort_by(|&(_, c1), &(_, c2)| c2.count.lines.cmp(&c1.count.lines))
            }
            _ => {
                println!("invalid sort option {}, sorting by code", sort);
                totals_by_lang.sort_by(|&(_, c1), &(_, c2)| c2.count.code.cmp(&c1.count.code))
            }
        }

        print_totals_by_lang(&linesep, &totals_by_lang);
    }

    fn last_n_chars(s: &str, n: usize) -> String {
        if s.len() <= n {
            return String::from(s);
        }
        s.chars().skip(s.len() - n).collect::<String>()
    }

    fn str_repeat(s: &str, n: usize) -> String {
        std::iter::repeat(s).take(n).collect::<Vec<_>>().join("")
    }

    fn print_totals_by_lang(linesep: &str, totals_by_lang: &[(&&Lang, &LangTotal)]) {
        println!("{}", linesep);
        println!(" {0: <17} {1: >8} {2: >12} {3: >12} {4: >12} {5: >12}",
                 "Language",
                 "Files",
                 "Lines",
                 "Blank",
                 "Comment",
                 "Code");
        println!("{}", linesep);

        for &(lang, total) in totals_by_lang {
            println!(" {0: <17} {1: >8} {2: >12} {3: >12} {4: >12} {5: >12}",
                     lang,
                     total.files,
                     total.count.lines,
                     total.count.blank,
                     total.count.comment,
                     total.count.code);
        }

        let mut totals = LangTotal {
            files: 0,
            count: Count::default(),
        };
        for &(_, total) in totals_by_lang {
            totals.files += total.files;
            totals.count.code += total.count.code;
            totals.count.blank += total.count.blank;
            totals.count.comment += total.count.comment;
            totals.count.lines += total.count.lines;
        }

        println!("{}", linesep);
        println!(" {0: <17} {1: >8} {2: >12} {3: >12} {4: >12} {5: >12}",
                 "Total",
                 totals.files,
                 totals.count.lines,
                 totals.count.blank,
                 totals.count.comment,
                 totals.count.code);
        println!("{}", linesep);
    }
}
<|endoftext|>


<commit_before>#![feature(managed_boxes,phase)]
#[phase(syntax, link)] extern crate log;

extern crate syntax;
extern crate time;

#[cfg(not(test))]
use racer::{getline,Match,do_file_search, do_external_search};
#[cfg(not(test))]
use std::io::File;
#[cfg(not(test))]
use std::io::BufferedReader;
#[cfg(not(test))]
use std::str;
#[cfg(not(test))]
use racer::scopes;

pub mod racer;

#[cfg(not(test))]
fn match_fn(m:Match) {
    let (linenum, charnum) = scopes::point_to_coords2(&m.filepath, m.point).unwrap();
    println!("MATCH {},{},{},{}", m.matchstr,
                                    linenum.to_str(),
                                    charnum.to_str(),
                                    m.filepath.as_str().unwrap());
}

#[cfg(not(test))]
fn complete() {
    let args = std::os::args();
    match std::uint::parse_bytes(std::os::args().as_slice()[2].as_bytes(), 10) {
        Some(linenum) => {
            // input: linenum, colnum, fname
            let charnum = std::uint::parse_bytes(std::os::args().as_slice()[3].as_bytes(), 10).unwrap();
            let fname = args.as_slice()[4].as_slice();
            let fpath = Path::new(fname);
            let filetxt = BufferedReader::new(File::open(&fpath)).read_to_end().unwrap();
            // print the start-end of the identifier being matched
            let src = str::from_utf8(filetxt.as_slice()).unwrap();
            let line = getline(&fpath, linenum);
            let (start, pos) = racer::expand_ident(line.as_slice(), charnum);
            println!("PREFIX {},{},{}", start, pos, line.as_slice().slice(start, pos));

            let point = scopes::coords_to_point(src, linenum, charnum);
            racer::complete_from_file(src, &fpath, point, &mut |m| match_fn(m));
        }
        None => {
            // input: a command line string passed in
            let arg = args.as_slice()[2].as_slice();
            let mut it = arg.split_str("::");
            let p : Vec<&str> = it.collect();

            do_file_search(p.as_slice()[0], &Path::new("."), &mut |m| {
                if p.len() == 1 {
                    match_fn(m);
                } else {
                    do_external_search(p.slice_from(1), &m.filepath, m.point, racer::StartsWith, &mut |m| match_fn(m));
                }
            });

        }
    }
}

#[cfg(not(test))]
fn prefix() {
    let args_ = std::os::args();
    let args = args_.as_slice();
    let linenum = std::uint::parse_bytes(args[2].as_bytes(), 10).unwrap();
    let charnum = std::uint::parse_bytes(args[3].as_bytes(), 10).unwrap();
    let fname = args[4].as_slice();

    // print the start, end, and the identifier prefix being matched
    let path = Path::new(fname);
    let line = getline(&path, linenum);
    let (start, pos) = racer::expand_ident(line.as_slice(), charnum);
    println!("PREFIX {},{},{}", start, pos, line.as_slice().slice(start, pos));
}

#[cfg(not(test))]
fn find_definition() {
    let args_ = std::os::args();
    let args = args_.as_slice();
    let linenum = std::uint::parse_bytes(args[2].as_bytes(), 10).unwrap();
    let charnum = std::uint::parse_bytes(args[3].as_bytes(), 10).unwrap();
    let fname = args[4].as_slice();
    let fpath = Path::new(fname);
    let filetxt = BufferedReader::new(File::open(&fpath)).read_to_end().unwrap();
    let src = str::from_utf8(filetxt.as_slice()).unwrap();
    let pos = scopes::coords_to_point(src, linenum, charnum);

    racer::find_definition(src, &fpath, pos).map(match_fn);
}

#[cfg(not(test))]
fn print_usage() {
    let program = std::os::args().as_slice()[0].clone();
    println!("usage: {} complete linenum charnum fname", program);
    println!("or:    {} find-definition linenum charnum fname", program);
    println!("or:    {} complete fullyqualifiedname   (e.g. std::io::)",program);
    println!("or:    {} prefix linenum charnum fname",program);
}

#[cfg(not(test))]
fn main() {
    if std::os::getenv("RUST_SRC_PATH").is_none() {
        println!("RUST_SRC_PATH environment variable must be set");
        std::os::set_exit_status(1);
        return;
    }

    let args_ = std::os::args();
    let args = args_.as_slice();

    if args.len() == 1 {
        print_usage();
        std::os::set_exit_status(1);
        return;
    }

    let command = args[1].as_slice();
    match command.as_slice() {
        "prefix" => prefix(),
        "complete" => complete(),
        "find-definition" => find_definition(),
        "help" => print_usage(),
        _ => {
            println!("Sorry, I didn't understand command {}", command );
            print_usage();
            std::os::set_exit_status(1);
            return;
        }
    }
}
<commit_msg>'{:?}' requires linking to the debug crate<commit_after>#![feature(managed_boxes,phase)]
#[phase(syntax, link)] extern crate log;

extern crate syntax;
extern crate time;
extern crate debug;

#[cfg(not(test))]
use racer::{getline,Match,do_file_search, do_external_search};
#[cfg(not(test))]
use std::io::File;
#[cfg(not(test))]
use std::io::BufferedReader;
#[cfg(not(test))]
use std::str;
#[cfg(not(test))]
use racer::scopes;

pub mod racer;

#[cfg(not(test))]
fn match_fn(m:Match) {
    let (linenum, charnum) = scopes::point_to_coords2(&m.filepath, m.point).unwrap();
    println!("MATCH {},{},{},{}", m.matchstr,
                                    linenum.to_str(),
                                    charnum.to_str(),
                                    m.filepath.as_str().unwrap());
}

#[cfg(not(test))]
fn complete() {
    let args = std::os::args();
    match std::uint::parse_bytes(std::os::args().as_slice()[2].as_bytes(), 10) {
        Some(linenum) => {
            // input: linenum, colnum, fname
            let charnum = std::uint::parse_bytes(std::os::args().as_slice()[3].as_bytes(), 10).unwrap();
            let fname = args.as_slice()[4].as_slice();
            let fpath = Path::new(fname);
            let filetxt = BufferedReader::new(File::open(&fpath)).read_to_end().unwrap();
            // print the start-end of the identifier being matched
            let src = str::from_utf8(filetxt.as_slice()).unwrap();
            let line = getline(&fpath, linenum);
            let (start, pos) = racer::expand_ident(line.as_slice(), charnum);
            println!("PREFIX {},{},{}", start, pos, line.as_slice().slice(start, pos));

            let point = scopes::coords_to_point(src, linenum, charnum);
            racer::complete_from_file(src, &fpath, point, &mut |m| match_fn(m));
        }
        None => {
            // input: a command line string passed in
            let arg = args.as_slice()[2].as_slice();
            let mut it = arg.split_str("::");
            let p : Vec<&str> = it.collect();

            do_file_search(p.as_slice()[0], &Path::new("."), &mut |m| {
                if p.len() == 1 {
                    match_fn(m);
                } else {
                    do_external_search(p.slice_from(1), &m.filepath, m.point, racer::StartsWith, &mut |m| match_fn(m));
                }
            });

        }
    }
}

#[cfg(not(test))]
fn prefix() {
    let args_ = std::os::args();
    let args = args_.as_slice();
    let linenum = std::uint::parse_bytes(args[2].as_bytes(), 10).unwrap();
    let charnum = std::uint::parse_bytes(args[3].as_bytes(), 10).unwrap();
    let fname = args[4].as_slice();

    // print the start, end, and the identifier prefix being matched
    let path = Path::new(fname);
    let line = getline(&path, linenum);
    let (start, pos) = racer::expand_ident(line.as_slice(), charnum);
    println!("PREFIX {},{},{}", start, pos, line.as_slice().slice(start, pos));
}

#[cfg(not(test))]
fn find_definition() {
    let args_ = std::os::args();
    let args = args_.as_slice();
    let linenum = std::uint::parse_bytes(args[2].as_bytes(), 10).unwrap();
    let charnum = std::uint::parse_bytes(args[3].as_bytes(), 10).unwrap();
    let fname = args[4].as_slice();
    let fpath = Path::new(fname);
    let filetxt = BufferedReader::new(File::open(&fpath)).read_to_end().unwrap();
    let src = str::from_utf8(filetxt.as_slice()).unwrap();
    let pos = scopes::coords_to_point(src, linenum, charnum);

    racer::find_definition(src, &fpath, pos).map(match_fn);
}

#[cfg(not(test))]
fn print_usage() {
    let program = std::os::args().as_slice()[0].clone();
    println!("usage: {} complete linenum charnum fname", program);
    println!("or:    {} find-definition linenum charnum fname", program);
    println!("or:    {} complete fullyqualifiedname   (e.g. std::io::)",program);
    println!("or:    {} prefix linenum charnum fname",program);
}

#[cfg(not(test))]
fn main() {
    if std::os::getenv("RUST_SRC_PATH").is_none() {
        println!("RUST_SRC_PATH environment variable must be set");
        std::os::set_exit_status(1);
        return;
    }

    let args_ = std::os::args();
    let args = args_.as_slice();

    if args.len() == 1 {
        print_usage();
        std::os::set_exit_status(1);
        return;
    }

    let command = args[1].as_slice();
    match command.as_slice() {
        "prefix" => prefix(),
        "complete" => complete(),
        "find-definition" => find_definition(),
        "help" => print_usage(),
        _ => {
            println!("Sorry, I didn't understand command {}", command );
            print_usage();
            std::os::set_exit_status(1);
            return;
        }
    }
}
<|endoftext|>


<commit_before>extern crate clap;
extern crate snout;

use clap::{Arg, App};
use snout::sniffer;

fn main() {
	const VERSION: &str = "0.1.0";
	const AUTHOR: &str = "Kevin Brebanov <kevin.brebanov@gmail.com>";
	const ABOUT: &str = "DNS packet sniffer that outputs to JSON";

    let arguments = App::new("snout")
                            .version(VERSION)
                            .author(AUTHOR)
                            .about(ABOUT)
                            .arg(Arg::with_name("interface")
                                .short("i")
                                .long("interface")
                                .value_name("INTERFACE")
                                .required(true)
                                .help("Network interface to sniff packets on")
                                .takes_value(true))
                            .arg(Arg::with_name("promiscuous")
                                .short("p")
                                .long("promiscuous")
                                .required(false)
                                .help("Enable promiscuous mode"))
                            .arg(Arg::with_name("snaplen")
                                .short("s")
                                .long("snaplen")
                                .value_name("SNAPLEN")
                                .required(false)
                                .help("Snapshot length")
                                .takes_value(true))
                            .arg(Arg::with_name("timeout")
                                .short("t")
                                .long("timeout")
                                .value_name("TIMEOUT")
                                .required(false)
                                .help("Read timeout in milliseconds")
                                .takes_value(true))
                            .arg(Arg::with_name("filter")
                                 .short("f")
                                 .long("filter")
                                 .value_name("FILTER")
                                 .required(false)
                                 .help("Berkeley Packet Filter (BPF)")
                                 .takes_value(true))
                            .get_matches();

    let interface = arguments.value_of("interface").unwrap();
    let mut promiscuous = false;
    if arguments.is_present("promiscuous") {
        promiscuous = true;
    }
    let snaplen: i32 = arguments.value_of("snaplen").unwrap_or("65535").parse().unwrap();
    let timeout: i32 = arguments.value_of("timeout").unwrap_or("0").parse().unwrap();
    let filter: &str = arguments.value_of("filter").unwrap_or("");

    sniffer::sniff(interface, promiscuous, snaplen, timeout, filter);
}
<commit_msg>Bump version<commit_after>extern crate clap;
extern crate snout;

use clap::{Arg, App};
use snout::sniffer;

fn main() {
	const VERSION: &str = "0.2.0";
	const AUTHOR: &str = "Kevin Brebanov <kevin.brebanov@gmail.com>";
	const ABOUT: &str = "DNS packet sniffer that outputs to JSON";

    let arguments = App::new("snout")
                            .version(VERSION)
                            .author(AUTHOR)
                            .about(ABOUT)
                            .arg(Arg::with_name("interface")
                                .short("i")
                                .long("interface")
                                .value_name("INTERFACE")
                                .required(true)
                                .help("Network interface to sniff packets on")
                                .takes_value(true))
                            .arg(Arg::with_name("promiscuous")
                                .short("p")
                                .long("promiscuous")
                                .required(false)
                                .help("Enable promiscuous mode"))
                            .arg(Arg::with_name("snaplen")
                                .short("s")
                                .long("snaplen")
                                .value_name("SNAPLEN")
                                .required(false)
                                .help("Snapshot length")
                                .takes_value(true))
                            .arg(Arg::with_name("timeout")
                                .short("t")
                                .long("timeout")
                                .value_name("TIMEOUT")
                                .required(false)
                                .help("Read timeout in milliseconds")
                                .takes_value(true))
                            .arg(Arg::with_name("filter")
                                 .short("f")
                                 .long("filter")
                                 .value_name("FILTER")
                                 .required(false)
                                 .help("Berkeley Packet Filter (BPF)")
                                 .takes_value(true))
                            .get_matches();

    let interface = arguments.value_of("interface").unwrap();
    let mut promiscuous = false;
    if arguments.is_present("promiscuous") {
        promiscuous = true;
    }
    let snaplen: i32 = arguments.value_of("snaplen").unwrap_or("65535").parse().unwrap();
    let timeout: i32 = arguments.value_of("timeout").unwrap_or("0").parse().unwrap();
    let filter: &str = arguments.value_of("filter").unwrap_or("");

    sniffer::sniff(interface, promiscuous, snaplen, timeout, filter);
}
<|endoftext|>


<commit_before>extern crate serde;
extern crate serde_json;
#[macro_use] extern crate serde_derive;
#[macro_use] extern crate exonum;
extern crate router;
extern crate bodyparser;
extern crate iron;

use exonum::blockchain::{self, Blockchain, Service, GenesisConfig, ValidatorKeys, Transaction, ApiContext};
use exonum::messages::{RawTransaction, FromRaw, Message};
use exonum::node::{Node, NodeConfig, NodeApiConfig, TransactionSend, TxSender, NodeChannel};
use exonum::storage::{Fork, MemoryDB, MapIndex};
use exonum::crypto::{PublicKey, Hash};
use exonum::encoding::{self, Field};
use exonum::api::{Api, ApiError};
use iron::prelude::*;
use iron::Handler;
use router::Router;

// // // // // // // // // // CONSTANTS // // // // // // // // // //

const SERVICE_ID: u16 = 1;

const TX_CREATE_WALLET_ID: u16 = 1;

const TX_TRANSFER_ID: u16 = 2;

const INIT_BALANCE: u64 = 100;

// // // // // // // // // // PERSISTENT DATA // // // // // // // // // //

encoding_struct! {
    struct Wallet {
        const SIZE = 48;

        field pub_key:            &PublicKey  [00 => 32]
        field name:               &str        [32 => 40]
        field balance:            u64         [40 => 48]
    }
}

impl Wallet {
    pub fn increase(&mut self, amount: u64) {
        let balance = self.balance() + amount;
        Field::write(&balance, &mut self.raw, 40, 48);
    }

    pub fn decrease(&mut self, amount: u64) {
        let balance = self.balance() - amount;
        Field::write(&balance, &mut self.raw, 40, 48);
    }
}

// // // // // // // // // // TRANSACTIONS // // // // // // // // // //

message! {
    struct TxCreateWallet {
        const TYPE = SERVICE_ID;
        const ID = TX_CREATE_WALLET_ID;
        const SIZE = 40;

        field pub_key:     &PublicKey  [00 => 32]
        field name:        &str        [32 => 40]
    }
}

message! {
    struct TxTransfer {
        const TYPE = SERVICE_ID;
        const ID = TX_TRANSFER_ID;
        const SIZE = 80;

        field from:        &PublicKey  [00 => 32]
        field to:          &PublicKey  [32 => 64]
        field amount:      u64         [64 => 72]
        field seed:        u64         [72 => 80]
    }
}

// // // // // // // // // // CONTRACTS // // // // // // // // // //

impl Transaction for TxCreateWallet {
    fn verify(&self) -> bool {
        self.verify_signature(self.pub_key())
    }

    fn execute(&self, view: &mut Fork) {
        let mut schema = CurrencySchema { view };
        if let None = schema.wallet(self.pub_key()) {
            let wallet = Wallet::new(self.pub_key(), self.name(), INIT_BALANCE);
            println!("Create the wallet: {:?}", wallet);
            schema.wallets().put(self.pub_key(), wallet)
        }
    }
}

impl Transaction for TxTransfer {
    fn verify(&self) -> bool {
        (*self.from() != *self.to()) && self.verify_signature(self.from())
    }

    fn execute(&self, view: &mut Fork) {
        let mut schema = CurrencySchema { view };
        let sender = schema.wallet(self.from());
        let receiver = schema.wallet(self.to());
        if let (Some(mut sender), Some(mut receiver)) = (sender, receiver) {
            let amount = self.amount();
            if sender.balance() >= amount {
                sender.decrease(amount);
                receiver.increase(amount);
                println!("Transfer between wallets: {:?} => {:?}", sender, receiver);
                let mut wallets = schema.wallets();
                wallets.put(self.from(), sender);
                wallets.put(self.to(), receiver);
            }
        }
    }
}

// // // // // // // // // // REST API // // // // // // // // // //

#[derive(Clone)]
struct CryptocurrencyApi {
    channel: TxSender<NodeChannel>,
}

impl Api for CryptocurrencyApi {
    fn wire(&self, router: &mut Router) {

        #[serde(untagged)]
        #[derive(Clone, Serialize, Deserialize)]
        enum TransactionRequest {
            CreateWallet(TxCreateWallet),
            Transfer(TxTransfer),
        }

        impl Into<Box<Transaction>> for TransactionRequest {
            fn into(self) -> Box<Transaction> {
                match self {
                    TransactionRequest::CreateWallet(trans) => Box::new(trans),
                    TransactionRequest::Transfer(trans) => Box::new(trans),
                }
            }
        }

        #[derive(Serialize, Deserialize)]
        struct TransactionResponse {
            tx_hash: Hash,
        }

        let self_ = self.clone();
        let transaction = move |req: &mut Request| -> IronResult<Response> {
            match req.get::<bodyparser::Struct<TransactionRequest>>() {
                Ok(Some(transaction)) => {
                    let transaction: Box<Transaction> = transaction.into();
                    let tx_hash = transaction.hash();
                    self_.channel.send(transaction).map_err(|e| ApiError::Events(e))?;
                    let json = TransactionResponse { tx_hash };
                    self_.ok_response(&serde_json::to_value(&json).unwrap())
                }
                Ok(None) => Err(ApiError::IncorrectRequest("Empty request body".into()))?,
                Err(e) => Err(ApiError::IncorrectRequest(Box::new(e)))?,
            }
        };
        let route_post = "/v1/wallets/transaction";
        router.post(&route_post, transaction, "transaction");
    }
}

// // // // // // // // // // STORAGE DATA LAYOUT // // // // // // // // // //

pub struct CurrencySchema<'a> {
    view: &'a mut Fork,
}

impl<'a> CurrencySchema<'a> {
    pub fn wallets(&mut self) -> MapIndex<&mut Fork, PublicKey, Wallet> {
        let prefix = blockchain::gen_prefix(SERVICE_ID, 0, &());
        MapIndex::new(prefix, self.view)
    }

    pub fn wallet(&mut self, pub_key: &PublicKey) -> Option<Wallet> {
        self.wallets().get(pub_key)
    }
}

// // // // // // // // // // SERVICE DECLARATION // // // // // // // // // //

struct CurrencyService;

impl Service for CurrencyService {
    fn service_name(&self) -> &'static str { "cryptocurrency" }

    fn service_id(&self) -> u16 { SERVICE_ID }

    fn tx_from_raw(&self, raw: RawTransaction) -> Result<Box<Transaction>, encoding::Error> {
        let trans: Box<Transaction> = match raw.message_type() {
            TX_TRANSFER_ID => Box::new(TxTransfer::from_raw(raw)?),
            TX_CREATE_WALLET_ID => Box::new(TxCreateWallet::from_raw(raw)?),
            _ => {
                return Err(encoding::Error::IncorrectMessageType { message_type: raw.message_type() });
            },
        };
        Ok(trans)
    }

    fn public_api_handler(&self, ctx: &ApiContext) -> Option<Box<Handler>> {
        let mut router = Router::new();
        let api = CryptocurrencyApi {
            channel: ctx.node_channel().clone(),
        };
        api.wire(&mut router);
        Some(Box::new(router))
    }
}

// // // // // // // // // // ENTRY POINT // // // // // // // // // //

fn main() {
    exonum::helpers::init_logger().unwrap();

    println!("Creating in-memory database...");
    let db = MemoryDB::new();
    let services: Vec<Box<Service>> = vec![
        Box::new(CurrencyService),
    ];
    let blockchain = Blockchain::new(Box::new(db), services);

    let (consensus_public_key, consensus_secret_key) = exonum::crypto::gen_keypair();
    let (service_public_key, service_secret_key) = exonum::crypto::gen_keypair();

    let peer_address = "0.0.0.0:2000".parse().unwrap();
    let api_address = "0.0.0.0:8000".parse().unwrap();

    let validator_keys = ValidatorKeys {
        consensus_key: consensus_public_key,
        service_key: service_public_key,
    };
    let genesis = GenesisConfig::new(vec![validator_keys].into_iter());

    let api_cfg = NodeApiConfig {
        enable_blockchain_explorer: true,
        public_api_address: Some(api_address),
        private_api_address: None,
    };

    let node_cfg = NodeConfig {
        listen_address: peer_address,
        peers: vec![],
        service_public_key,
        service_secret_key,
        consensus_public_key,
        consensus_secret_key,
        genesis,
        external_address: None,
        network: Default::default(),
        whitelist: Default::default(),
        api: api_cfg,
        mempool: Default::default(),
        services_configs: Default::default(),
    };

    println!("Starting a single node...");
    let mut node = Node::new(blockchain, node_cfg);

    println!("Blockchain in ready for transactions!");
    node.run().unwrap();
}
<commit_msg>Split imports<commit_after>extern crate serde;
extern crate serde_json;
#[macro_use] extern crate serde_derive;
#[macro_use] extern crate exonum;
extern crate router;
extern crate bodyparser;
extern crate iron;

use exonum::blockchain::{self, Blockchain, Service, GenesisConfig,
                         ValidatorKeys, Transaction, ApiContext};
use exonum::node::{Node, NodeConfig, NodeApiConfig, TransactionSend,
                   TxSender, NodeChannel};
use exonum::messages::{RawTransaction, FromRaw, Message};
use exonum::storage::{Fork, MemoryDB, MapIndex};
use exonum::crypto::{PublicKey, Hash};
use exonum::encoding::{self, Field};
use exonum::api::{Api, ApiError};
use iron::prelude::*;
use iron::Handler;
use router::Router;

// // // // // // // // // // CONSTANTS // // // // // // // // // //

const SERVICE_ID: u16 = 1;

const TX_CREATE_WALLET_ID: u16 = 1;

const TX_TRANSFER_ID: u16 = 2;

const INIT_BALANCE: u64 = 100;

// // // // // // // // // // PERSISTENT DATA // // // // // // // // // //

encoding_struct! {
    struct Wallet {
        const SIZE = 48;

        field pub_key:            &PublicKey  [00 => 32]
        field name:               &str        [32 => 40]
        field balance:            u64         [40 => 48]
    }
}

impl Wallet {
    pub fn increase(&mut self, amount: u64) {
        let balance = self.balance() + amount;
        Field::write(&balance, &mut self.raw, 40, 48);
    }

    pub fn decrease(&mut self, amount: u64) {
        let balance = self.balance() - amount;
        Field::write(&balance, &mut self.raw, 40, 48);
    }
}

// // // // // // // // // // TRANSACTIONS // // // // // // // // // //

message! {
    struct TxCreateWallet {
        const TYPE = SERVICE_ID;
        const ID = TX_CREATE_WALLET_ID;
        const SIZE = 40;

        field pub_key:     &PublicKey  [00 => 32]
        field name:        &str        [32 => 40]
    }
}

message! {
    struct TxTransfer {
        const TYPE = SERVICE_ID;
        const ID = TX_TRANSFER_ID;
        const SIZE = 80;

        field from:        &PublicKey  [00 => 32]
        field to:          &PublicKey  [32 => 64]
        field amount:      u64         [64 => 72]
        field seed:        u64         [72 => 80]
    }
}

// // // // // // // // // // CONTRACTS // // // // // // // // // //

impl Transaction for TxCreateWallet {
    fn verify(&self) -> bool {
        self.verify_signature(self.pub_key())
    }

    fn execute(&self, view: &mut Fork) {
        let mut schema = CurrencySchema { view };
        if let None = schema.wallet(self.pub_key()) {
            let wallet = Wallet::new(self.pub_key(), self.name(), INIT_BALANCE);
            println!("Create the wallet: {:?}", wallet);
            schema.wallets().put(self.pub_key(), wallet)
        }
    }
}

impl Transaction for TxTransfer {
    fn verify(&self) -> bool {
        (*self.from() != *self.to()) && self.verify_signature(self.from())
    }

    fn execute(&self, view: &mut Fork) {
        let mut schema = CurrencySchema { view };
        let sender = schema.wallet(self.from());
        let receiver = schema.wallet(self.to());
        if let (Some(mut sender), Some(mut receiver)) = (sender, receiver) {
            let amount = self.amount();
            if sender.balance() >= amount {
                sender.decrease(amount);
                receiver.increase(amount);
                println!("Transfer between wallets: {:?} => {:?}", sender, receiver);
                let mut wallets = schema.wallets();
                wallets.put(self.from(), sender);
                wallets.put(self.to(), receiver);
            }
        }
    }
}

// // // // // // // // // // REST API // // // // // // // // // //

#[derive(Clone)]
struct CryptocurrencyApi {
    channel: TxSender<NodeChannel>,
}

impl Api for CryptocurrencyApi {
    fn wire(&self, router: &mut Router) {

        #[serde(untagged)]
        #[derive(Clone, Serialize, Deserialize)]
        enum TransactionRequest {
            CreateWallet(TxCreateWallet),
            Transfer(TxTransfer),
        }

        impl Into<Box<Transaction>> for TransactionRequest {
            fn into(self) -> Box<Transaction> {
                match self {
                    TransactionRequest::CreateWallet(trans) => Box::new(trans),
                    TransactionRequest::Transfer(trans) => Box::new(trans),
                }
            }
        }

        #[derive(Serialize, Deserialize)]
        struct TransactionResponse {
            tx_hash: Hash,
        }

        let self_ = self.clone();
        let transaction = move |req: &mut Request| -> IronResult<Response> {
            match req.get::<bodyparser::Struct<TransactionRequest>>() {
                Ok(Some(transaction)) => {
                    let transaction: Box<Transaction> = transaction.into();
                    let tx_hash = transaction.hash();
                    self_.channel.send(transaction).map_err(|e| ApiError::Events(e))?;
                    let json = TransactionResponse { tx_hash };
                    self_.ok_response(&serde_json::to_value(&json).unwrap())
                }
                Ok(None) => Err(ApiError::IncorrectRequest("Empty request body".into()))?,
                Err(e) => Err(ApiError::IncorrectRequest(Box::new(e)))?,
            }
        };
        let route_post = "/v1/wallets/transaction";
        router.post(&route_post, transaction, "transaction");
    }
}

// // // // // // // // // // STORAGE DATA LAYOUT // // // // // // // // // //

pub struct CurrencySchema<'a> {
    view: &'a mut Fork,
}

impl<'a> CurrencySchema<'a> {
    pub fn wallets(&mut self) -> MapIndex<&mut Fork, PublicKey, Wallet> {
        let prefix = blockchain::gen_prefix(SERVICE_ID, 0, &());
        MapIndex::new(prefix, self.view)
    }

    pub fn wallet(&mut self, pub_key: &PublicKey) -> Option<Wallet> {
        self.wallets().get(pub_key)
    }
}

// // // // // // // // // // SERVICE DECLARATION // // // // // // // // // //

struct CurrencyService;

impl Service for CurrencyService {
    fn service_name(&self) -> &'static str { "cryptocurrency" }

    fn service_id(&self) -> u16 { SERVICE_ID }

    fn tx_from_raw(&self, raw: RawTransaction) -> Result<Box<Transaction>, encoding::Error> {
        let trans: Box<Transaction> = match raw.message_type() {
            TX_TRANSFER_ID => Box::new(TxTransfer::from_raw(raw)?),
            TX_CREATE_WALLET_ID => Box::new(TxCreateWallet::from_raw(raw)?),
            _ => {
                return Err(encoding::Error::IncorrectMessageType { message_type: raw.message_type() });
            },
        };
        Ok(trans)
    }

    fn public_api_handler(&self, ctx: &ApiContext) -> Option<Box<Handler>> {
        let mut router = Router::new();
        let api = CryptocurrencyApi {
            channel: ctx.node_channel().clone(),
        };
        api.wire(&mut router);
        Some(Box::new(router))
    }
}

// // // // // // // // // // ENTRY POINT // // // // // // // // // //

fn main() {
    exonum::helpers::init_logger().unwrap();

    println!("Creating in-memory database...");
    let db = MemoryDB::new();
    let services: Vec<Box<Service>> = vec![
        Box::new(CurrencyService),
    ];
    let blockchain = Blockchain::new(Box::new(db), services);

    let (consensus_public_key, consensus_secret_key) = exonum::crypto::gen_keypair();
    let (service_public_key, service_secret_key) = exonum::crypto::gen_keypair();

    let peer_address = "0.0.0.0:2000".parse().unwrap();
    let api_address = "0.0.0.0:8000".parse().unwrap();

    let validator_keys = ValidatorKeys {
        consensus_key: consensus_public_key,
        service_key: service_public_key,
    };
    let genesis = GenesisConfig::new(vec![validator_keys].into_iter());

    let api_cfg = NodeApiConfig {
        enable_blockchain_explorer: true,
        public_api_address: Some(api_address),
        private_api_address: None,
    };

    let node_cfg = NodeConfig {
        listen_address: peer_address,
        peers: vec![],
        service_public_key,
        service_secret_key,
        consensus_public_key,
        consensus_secret_key,
        genesis,
        external_address: None,
        network: Default::default(),
        whitelist: Default::default(),
        api: api_cfg,
        mempool: Default::default(),
        services_configs: Default::default(),
    };

    println!("Starting a single node...");
    let mut node = Node::new(blockchain, node_cfg);

    println!("Blockchain in ready for transactions!");
    node.run().unwrap();
}
<|endoftext|>


<commit_before>use std::cell::RefCell;
use std::path::PathBuf;
use std::rc::Rc;
use std::sync::{Arc, Mutex};

use anyhow::Context;
use once_cell::sync::OnceCell;
use reqwest;
use structopt::StructOpt;
use tbot;
use tokio;

mod data;
mod feed;

use crate::data::Database;

static BOT_NAME: OnceCell<String> = OnceCell::new();

#[derive(Debug, StructOpt)]
#[structopt(about = "A simple Telegram RSS bot.")]
struct Opt {
    /// Telegram bot token
    token: String,
    /// Path to database
    #[structopt(short = "d", default_value = "./rssbot.json")]
    database: PathBuf,
}

macro_rules! handle {
    ($env: expr, $f: expr) => {{
        let env = $env.clone();
        let f = $f;
        move |cmd| {
            let future = f(env.clone(), cmd);
            async {
                if let Err(e) = future.await {
                    dbg!(e);
                }
            }
        }
    }};
}

#[tokio::main]
async fn main() -> anyhow::Result<()> {
    let opt = Opt::from_args();
    let db = Arc::new(Mutex::new(Database::open(opt.database)?));
    let bot = tbot::Bot::new(opt.token);
    let me = bot
        .get_me()
        .call()
        .await
        .context("Initialization failed, check your network and Telegram token")?;

    BOT_NAME.set(me.user.username.clone().unwrap()).unwrap();

    let mut event_loop = bot.event_loop();
    event_loop.username(me.user.username.unwrap());
    event_loop.command("rss", handle!(db, handlers::rss));
    event_loop.command("sub", handle!(db, handlers::sub));

    event_loop.polling().start().await.unwrap();
    Ok(())
}

mod handlers {
    use std::future::Future;
    use std::pin::Pin;
    use std::sync::Mutex;
    use std::sync::{Arc, Once};
    use std::time::Duration;

    use reqwest;
    use tbot::{
        connectors::Https,
        contexts::{Command, Text},
        types::parameters,
    };

    use crate::data::{DataError, Database};
    use crate::feed::RSS;

    pub const TELEGRAM_MAX_MSG_LEN: usize = 4096;
    const RESP_SIZE_LIMIT: usize = 2 * 1024 * 1024;

    fn client() -> Arc<reqwest::Client> {
        static mut CLIENT: Option<Arc<reqwest::Client>> = None;
        static INIT: Once = Once::new();

        INIT.call_once(|| {
            let mut headers = reqwest::header::HeaderMap::new();
            let ua = format!(
                concat!(
                    env!("CARGO_PKG_NAME"),
                    "/",
                    env!("CARGO_PKG_VERSION"),
                    " (+https://t.me/{})"
                ),
                crate::BOT_NAME.get().expect("BOT_NAME not initialized")
            );
            headers.insert(
                reqwest::header::USER_AGENT,
                reqwest::header::HeaderValue::from_str(&ua).unwrap(),
            );
            let client = reqwest::Client::builder()
                .timeout(Duration::from_secs(5))
                .default_headers(headers)
                .redirect(reqwest::redirect::Policy::limited(5))
                .build()
                .unwrap();

            unsafe {
                CLIENT = Some(Arc::new(client));
            }
        });

        unsafe { CLIENT.clone() }.unwrap()
    }

    pub async fn rss(
        db: Arc<Mutex<Database>>,
        cmd: Arc<Command<Text<Https>>>,
    ) -> anyhow::Result<()> {
        let chat = cmd.chat.id;
        let text = &cmd.text.value;

        let feeds = db.lock().unwrap().subscribed_feeds(chat.0);
        let msgs = if let Some(feeds) = feeds {
            format_and_split_msgs("订阅列表：".to_string(), &feeds, |feed| {
                format!(
                    "{}",
                    Escape(&feed.link),
                    Escape(&feed.title)
                )
            })
        } else {
            vec!["订阅列表为空".to_string()]
        };
        for msg in msgs {
            let text = parameters::Text::html(&msg);
            cmd.bot
                .send_message(chat, text)
                .reply_to_message_id(cmd.message_id)
                .call()
                .await?;
        }
        Ok(())
    }

    pub async fn sub(
        db: Arc<Mutex<Database>>,
        cmd: Arc<Command<Text<Https>>>,
    ) -> anyhow::Result<()> {
        let chat_id = cmd.chat.id;
        let text = &cmd.text.value;
        let args = text.split_whitespace().collect::<Vec<_>>();
        let mut target_id = chat_id;
        let feed_url;

        match &*args {
            [url] => feed_url = url,
            [channel, url] => {
                //...
                target_id = todo!();
                feed_url = url;
            }
            [..] => {
                return Ok(());
            }
        };
        let msg = match pull_feed(feed_url).await {
            Ok(feed) => match db.lock().unwrap().subscribe(target_id.0, feed_url, &feed) {
                Ok(()) => format!("{} 订阅成功", feed.title),
                Err(DataError::Subscribed) => "".into(),
                Err(e) => unreachable!(e),
            },
            Err(e) => format!("订阅失败 {}", e),
        };
        cmd.bot
            .send_message(chat_id, text)
            .reply_to_message_id(cmd.message_id)
            .call()
            .await?;
        Ok(())
    }

    async fn pull_feed(url: &str) -> anyhow::Result<RSS> {
        let mut resp = client().get(url).send().await?.error_for_status()?;
        if let Some(len) = resp.content_length() {
            if len > RESP_SIZE_LIMIT as u64 {
                return Err(anyhow::format_err!("too big"));
            }
        }
        let mut buf = Vec::new(); // TODO: capacity?
        while let Some(bytes) = resp.chunk().await? {
            if buf.len() + bytes.len() > RESP_SIZE_LIMIT {
                return Err(anyhow::format_err!("too big"));
            }
            buf.extend_from_slice(&bytes);
        }

        crate::feed::parse(std::io::Cursor::new(buf))
    }

    pub fn format_and_split_msgs<T, F>(head: String, data: &[T], line_format_fn: F) -> Vec<String>
    where
        F: Fn(&T) -> String,
    {
        let mut msgs = vec![head];
        for item in data {
            let line = line_format_fn(item);
            if msgs.last_mut().unwrap().len() + line.len() > TELEGRAM_MAX_MSG_LEN {
                msgs.push(line);
            } else {
                let msg = msgs.last_mut().unwrap();
                msg.push('\n');
                msg.push_str(&line);
            }
        }
        msgs
    }

    pub struct Escape<'a>(pub &'a str);

    impl<'a> ::std::fmt::Display for Escape<'a> {
        fn fmt(&self, fmt: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
            // https://core.telegram.org/bots/api#html-style
            let Escape(s) = *self;
            let pile_o_bits = s;
            let mut last = 0;
            for (i, ch) in s.bytes().enumerate() {
                match ch as char {
                    '<' | '>' | '&' | '"' => {
                        fmt.write_str(&pile_o_bits[last..i])?;
                        let s = match ch as char {
                            '>' => ">",
                            '<' => "<",
                            '&' => "&",
                            '"' => """,
                            _ => unreachable!(),
                        };
                        fmt.write_str(s)?;
                        last = i + 1;
                    }
                    _ => {}
                }
            }

            if last < s.len() {
                fmt.write_str(&pile_o_bits[last..])?;
            }
            Ok(())
        }
    }
}
<commit_msg>Link large messages<commit_after>use std::cell::RefCell;
use std::path::PathBuf;
use std::rc::Rc;
use std::sync::{Arc, Mutex};

use anyhow::Context;
use once_cell::sync::OnceCell;
use reqwest;
use structopt::StructOpt;
use tbot;
use tokio;

mod data;
mod feed;

use crate::data::Database;

static BOT_NAME: OnceCell<String> = OnceCell::new();

#[derive(Debug, StructOpt)]
#[structopt(about = "A simple Telegram RSS bot.")]
struct Opt {
    /// Telegram bot token
    token: String,
    /// Path to database
    #[structopt(short = "d", default_value = "./rssbot.json")]
    database: PathBuf,
}

macro_rules! handle {
    ($env: expr, $f: expr) => {{
        let env = $env.clone();
        let f = $f;
        move |cmd| {
            let future = f(env.clone(), cmd);
            async {
                if let Err(e) = future.await {
                    dbg!(e);
                }
            }
        }
    }};
}

#[tokio::main]
async fn main() -> anyhow::Result<()> {
    let opt = Opt::from_args();
    let db = Arc::new(Mutex::new(Database::open(opt.database)?));
    let bot = tbot::Bot::new(opt.token);
    let me = bot
        .get_me()
        .call()
        .await
        .context("Initialization failed, check your network and Telegram token")?;

    BOT_NAME.set(me.user.username.clone().unwrap()).unwrap();

    let mut event_loop = bot.event_loop();
    event_loop.username(me.user.username.unwrap());
    event_loop.command("rss", handle!(db, handlers::rss));
    event_loop.command("sub", handle!(db, handlers::sub));

    event_loop.polling().start().await.unwrap();
    Ok(())
}

mod handlers {
    use std::future::Future;
    use std::pin::Pin;
    use std::sync::Mutex;
    use std::sync::{Arc, Once};
    use std::time::Duration;

    use reqwest;
    use tbot::{
        connectors::Https,
        contexts::{Command, Text},
        types::parameters,
    };

    use crate::data::{DataError, Database};
    use crate::feed::RSS;

    pub const TELEGRAM_MAX_MSG_LEN: usize = 4096;
    const RESP_SIZE_LIMIT: usize = 2 * 1024 * 1024;

    fn client() -> Arc<reqwest::Client> {
        static mut CLIENT: Option<Arc<reqwest::Client>> = None;
        static INIT: Once = Once::new();

        INIT.call_once(|| {
            let mut headers = reqwest::header::HeaderMap::new();
            let ua = format!(
                concat!(
                    env!("CARGO_PKG_NAME"),
                    "/",
                    env!("CARGO_PKG_VERSION"),
                    " (+https://t.me/{})"
                ),
                crate::BOT_NAME.get().expect("BOT_NAME not initialized")
            );
            headers.insert(
                reqwest::header::USER_AGENT,
                reqwest::header::HeaderValue::from_str(&ua).unwrap(),
            );
            let client = reqwest::Client::builder()
                .timeout(Duration::from_secs(5))
                .default_headers(headers)
                .redirect(reqwest::redirect::Policy::limited(5))
                .build()
                .unwrap();

            unsafe {
                CLIENT = Some(Arc::new(client));
            }
        });

        unsafe { CLIENT.clone() }.unwrap()
    }

    pub async fn rss(
        db: Arc<Mutex<Database>>,
        cmd: Arc<Command<Text<Https>>>,
    ) -> anyhow::Result<()> {
        let chat = cmd.chat.id;
        let text = &cmd.text.value;

        let feeds = db.lock().unwrap().subscribed_feeds(chat.0);
        let msgs = if let Some(feeds) = feeds {
            format_and_split_msgs("订阅列表：".to_string(), &feeds, |feed| {
                format!(
                    "{}",
                    Escape(&feed.link),
                    Escape(&feed.title)
                )
            })
        } else {
            vec!["订阅列表为空".to_string()]
        };

        let mut prev_msg = cmd.message_id;
        for msg in msgs {
            let text = parameters::Text::html(&msg);
            let msg = cmd.bot
                .send_message(chat, text)
                .reply_to_message_id(prev_msg)
                .call()
                .await?;
            prev_msg = msg.id;
        }
        Ok(())
    }

    pub async fn sub(
        db: Arc<Mutex<Database>>,
        cmd: Arc<Command<Text<Https>>>,
    ) -> anyhow::Result<()> {
        let chat_id = cmd.chat.id;
        let text = &cmd.text.value;
        let args = text.split_whitespace().collect::<Vec<_>>();
        let mut target_id = chat_id;
        let feed_url;

        match &*args {
            [url] => feed_url = url,
            [channel, url] => {
                //...
                target_id = todo!();
                feed_url = url;
            }
            [..] => {
                return Ok(());
            }
        };
        let msg = match pull_feed(feed_url).await {
            Ok(feed) => match db.lock().unwrap().subscribe(target_id.0, feed_url, &feed) {
                Ok(()) => format!("{} 订阅成功", feed.title),
                Err(DataError::Subscribed) => "".into(),
                Err(e) => unreachable!(e),
            },
            Err(e) => format!("订阅失败 {}", e),
        };
        cmd.bot
            .send_message(chat_id, text)
            .reply_to_message_id(cmd.message_id)
            .call()
            .await?;
        Ok(())
    }

    async fn pull_feed(url: &str) -> anyhow::Result<RSS> {
        let mut resp = client().get(url).send().await?.error_for_status()?;
        if let Some(len) = resp.content_length() {
            if len > RESP_SIZE_LIMIT as u64 {
                return Err(anyhow::format_err!("too big"));
            }
        }
        let mut buf = Vec::new(); // TODO: capacity?
        while let Some(bytes) = resp.chunk().await? {
            if buf.len() + bytes.len() > RESP_SIZE_LIMIT {
                return Err(anyhow::format_err!("too big"));
            }
            buf.extend_from_slice(&bytes);
        }

        crate::feed::parse(std::io::Cursor::new(buf))
    }

    pub fn format_and_split_msgs<T, F>(head: String, data: &[T], line_format_fn: F) -> Vec<String>
    where
        F: Fn(&T) -> String,
    {
        let mut msgs = vec![head];
        for item in data {
            let line = line_format_fn(item);
            if msgs.last_mut().unwrap().len() + line.len() > TELEGRAM_MAX_MSG_LEN {
                msgs.push(line);
            } else {
                let msg = msgs.last_mut().unwrap();
                msg.push('\n');
                msg.push_str(&line);
            }
        }
        msgs
    }

    pub struct Escape<'a>(pub &'a str);

    impl<'a> ::std::fmt::Display for Escape<'a> {
        fn fmt(&self, fmt: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
            // https://core.telegram.org/bots/api#html-style
            let Escape(s) = *self;
            let pile_o_bits = s;
            let mut last = 0;
            for (i, ch) in s.bytes().enumerate() {
                match ch as char {
                    '<' | '>' | '&' | '"' => {
                        fmt.write_str(&pile_o_bits[last..i])?;
                        let s = match ch as char {
                            '>' => ">",
                            '<' => "<",
                            '&' => "&",
                            '"' => """,
                            _ => unreachable!(),
                        };
                        fmt.write_str(s)?;
                        last = i + 1;
                    }
                    _ => {}
                }
            }

            if last < s.len() {
                fmt.write_str(&pile_o_bits[last..])?;
            }
            Ok(())
        }
    }
}
<|endoftext|>