<commit_before>// Copyright 2014 The Rust Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.

//! This module provides a simplified abstraction for working with
//! code blocks identified by their integer node-id.  In particular,
//! it captures a common set of attributes that all "function-like
//! things" (represented by `FnLike` instances) share.  For example,
//! all `FnLike` instances have a type signature (be it explicit or
//! inferred).  And all `FnLike` instances have a body, i.e. the code
//! that is run when the function-like thing it represents is invoked.
//!
//! With the above abstraction in place, one can treat the program
//! text as a collection of blocks of code (and most such blocks are
//! nested within a uniquely determined `FnLike`), and users can ask
//! for the `Code` associated with a particular NodeId.

use hir as ast;
use hir::map::{self, Node};
use hir::{Expr, FnDecl};
use hir::intravisit::FnKind;
use syntax::abi;
use syntax::ast::{Attribute, Name, NodeId};
use syntax_pos::Span;

/// An FnLikeNode is a Node that is like a fn, in that it has a decl
/// and a body (as well as a NodeId, a span, etc).
///
/// More specifically, it is one of either:
///   - A function item,
///   - A closure expr (i.e. an ExprClosure), or
///   - The default implementation for a trait method.
///
/// To construct one, use the `Code::from_node` function.
#[derive(Copy, Clone, Debug)]
pub struct FnLikeNode<'a> { node: map::Node<'a> }

/// MaybeFnLike wraps a method that indicates if an object
/// corresponds to some FnLikeNode.
pub trait MaybeFnLike { fn is_fn_like(&self) -> bool; }

impl MaybeFnLike for ast::Item {
    fn is_fn_like(&self) -> bool {
        match self.node { ast::ItemFn(..) => true, _ => false, }
    }
}

impl MaybeFnLike for ast::TraitItem {
    fn is_fn_like(&self) -> bool {
        match self.node {
            ast::TraitItemKind::Method(_, ast::TraitMethod::Provided(_)) => true,
            _ => false,
        }
    }
}

impl MaybeFnLike for ast::Expr {
    fn is_fn_like(&self) -> bool {
        match self.node {
            ast::ExprClosure(..) => true,
            _ => false,
        }
    }
}

/// Carries either an FnLikeNode or a Expr, as these are the two
/// constructs that correspond to "code" (as in, something from which
/// we can construct a control-flow graph).
#[derive(Copy, Clone)]
pub enum Code<'a> {
    FnLike(FnLikeNode<'a>),
    Expr(&'a Expr),
}

impl<'a> Code<'a> {
    pub fn id(&self) -> NodeId {
        match *self {
            Code::FnLike(node) => node.id(),
            Code::Expr(block) => block.id,
        }
    }

    /// Attempts to construct a Code from presumed FnLike or Expr node input.
    pub fn from_node(map: &map::Map<'a>, id: NodeId) -> Option<Code<'a>> {
        match map.get(id) {
            map::NodeBlock(_) => {
                //  Use the parent, hopefully an expression node.
                Code::from_node(map, map.get_parent_node(id))
            }
            map::NodeExpr(expr) => Some(Code::Expr(expr)),
            node => FnLikeNode::from_node(node).map(Code::FnLike)
        }
    }
}

/// These are all the components one can extract from a fn item for
/// use when implementing FnLikeNode operations.
struct ItemFnParts<'a> {
    name:     Name,
    decl:     &'a ast::FnDecl,
    unsafety: ast::Unsafety,
    constness: ast::Constness,
    abi:      abi::Abi,
    vis:      &'a ast::Visibility,
    generics: &'a ast::Generics,
    body:     ast::BodyId,
    id:       NodeId,
    span:     Span,
    attrs:    &'a [Attribute],
}

/// These are all the components one can extract from a closure expr
/// for use when implementing FnLikeNode operations.
struct ClosureParts<'a> {
    decl: &'a FnDecl,
    body: ast::BodyId,
    id: NodeId,
    span: Span,
    attrs: &'a [Attribute],
}

impl<'a> ClosureParts<'a> {
    fn new(d: &'a FnDecl, b: ast::BodyId, id: NodeId, s: Span, attrs: &'a [Attribute]) -> Self {
        ClosureParts {
            decl: d,
            body: b,
            id,
            span: s,
            attrs,
        }
    }
}

impl<'a> FnLikeNode<'a> {
    /// Attempts to construct a FnLikeNode from presumed FnLike node input.
    pub fn from_node(node: Node) -> Option<FnLikeNode> {
        let fn_like = match node {
            map::NodeItem(item) => item.is_fn_like(),
            map::NodeTraitItem(tm) => tm.is_fn_like(),
            map::NodeImplItem(_) => true,
            map::NodeExpr(e) => e.is_fn_like(),
            _ => false
        };
        if fn_like {
            Some(FnLikeNode {
                node,
            })
        } else {
            None
        }
    }

    pub fn body(self) -> ast::BodyId {
        self.handle(|i: ItemFnParts<'a>|  i.body,
                    |_, _, _: &'a ast::MethodSig, _, body: ast::BodyId, _, _|  body,
                    |c: ClosureParts<'a>| c.body)
    }

    pub fn decl(self) -> &'a FnDecl {
        self.handle(|i: ItemFnParts<'a>|  &*i.decl,
                    |_, _, sig: &'a ast::MethodSig, _, _, _, _|  &sig.decl,
                    |c: ClosureParts<'a>| c.decl)
    }

    pub fn span(self) -> Span {
        self.handle(|i: ItemFnParts|     i.span,
                    |_, _, _: &'a ast::MethodSig, _, _, span, _| span,
                    |c: ClosureParts|    c.span)
    }

    pub fn id(self) -> NodeId {
        self.handle(|i: ItemFnParts|     i.id,
                    |id, _, _: &'a ast::MethodSig, _, _, _, _| id,
                    |c: ClosureParts|    c.id)
    }

    pub fn constness(self) -> ast::Constness {
        match self.kind() {
            FnKind::ItemFn(_, _, _, constness, ..) => {
                constness
            }
            FnKind::Method(_, m, ..) => {
                m.constness
            }
            _ => ast::Constness::NotConst
        }
    }

    pub fn kind(self) -> FnKind<'a> {
        let item = |p: ItemFnParts<'a>| -> FnKind<'a> {
            FnKind::ItemFn(p.name, p.generics, p.unsafety, p.constness, p.abi, p.vis, p.attrs)
        };
        let closure = |c: ClosureParts<'a>| {
            FnKind::Closure(c.attrs)
        };
        let method = |_, name: Name, sig: &'a ast::MethodSig, vis, _, _, attrs| {
            FnKind::Method(name, sig, vis, attrs)
        };
        self.handle(item, method, closure)
    }

    fn handle<A, I, M, C>(self, item_fn: I, method: M, closure: C) -> A where
        I: FnOnce(ItemFnParts<'a>) -> A,
        M: FnOnce(NodeId,
                  Name,
                  &'a ast::MethodSig,
                  Option<&'a ast::Visibility>,
                  ast::BodyId,
                  Span,
                  &'a [Attribute])
                  -> A,
        C: FnOnce(ClosureParts<'a>) -> A,
    {
        match self.node {
            map::NodeItem(i) => match i.node {
                ast::ItemFn(ref decl, unsafety, constness, abi, ref generics, block) =>
                    item_fn(ItemFnParts {
                        id: i.id,
                        name: i.name,
                        decl: &decl,
                        unsafety,
                        body: block,
                        generics,
                        abi,
                        vis: &i.vis,
                        constness,
                        span: i.span,
                        attrs: &i.attrs,
                    }),
                _ => bug!("item FnLikeNode that is not fn-like"),
            },
            map::NodeTraitItem(ti) => match ti.node {
                ast::TraitItemKind::Method(ref sig, ast::TraitMethod::Provided(body)) => {
                    method(ti.id, ti.name, sig, None, body, ti.span, &ti.attrs)
                }
                _ => bug!("trait method FnLikeNode that is not fn-like"),
            },
            map::NodeImplItem(ii) => {
                match ii.node {
                    ast::ImplItemKind::Method(ref sig, body) => {
                        method(ii.id, ii.name, sig, Some(&ii.vis), body, ii.span, &ii.attrs)
                    }
                    _ => {
                        bug!("impl method FnLikeNode that is not fn-like")
                    }
                }
            },
            map::NodeExpr(e) => match e.node {
                ast::ExprClosure(_, ref decl, block, _fn_decl_span, _gen) =>
                    // FIXME: Does this need handling for generators?
                    closure(ClosureParts::new(&decl, block, e.id, e.span, &e.attrs)),
                _ => bug!("expr FnLikeNode that is not fn-like"),
            },
            _ => bug!("other FnLikeNode that is not fn-like"),
        }
    }
}
<commit_msg>Remove a FIXME<commit_after>// Copyright 2014 The Rust Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.

//! This module provides a simplified abstraction for working with
//! code blocks identified by their integer node-id.  In particular,
//! it captures a common set of attributes that all "function-like
//! things" (represented by `FnLike` instances) share.  For example,
//! all `FnLike` instances have a type signature (be it explicit or
//! inferred).  And all `FnLike` instances have a body, i.e. the code
//! that is run when the function-like thing it represents is invoked.
//!
//! With the above abstraction in place, one can treat the program
//! text as a collection of blocks of code (and most such blocks are
//! nested within a uniquely determined `FnLike`), and users can ask
//! for the `Code` associated with a particular NodeId.

use hir as ast;
use hir::map::{self, Node};
use hir::{Expr, FnDecl};
use hir::intravisit::FnKind;
use syntax::abi;
use syntax::ast::{Attribute, Name, NodeId};
use syntax_pos::Span;

/// An FnLikeNode is a Node that is like a fn, in that it has a decl
/// and a body (as well as a NodeId, a span, etc).
///
/// More specifically, it is one of either:
///   - A function item,
///   - A closure expr (i.e. an ExprClosure), or
///   - The default implementation for a trait method.
///
/// To construct one, use the `Code::from_node` function.
#[derive(Copy, Clone, Debug)]
pub struct FnLikeNode<'a> { node: map::Node<'a> }

/// MaybeFnLike wraps a method that indicates if an object
/// corresponds to some FnLikeNode.
pub trait MaybeFnLike { fn is_fn_like(&self) -> bool; }

impl MaybeFnLike for ast::Item {
    fn is_fn_like(&self) -> bool {
        match self.node { ast::ItemFn(..) => true, _ => false, }
    }
}

impl MaybeFnLike for ast::TraitItem {
    fn is_fn_like(&self) -> bool {
        match self.node {
            ast::TraitItemKind::Method(_, ast::TraitMethod::Provided(_)) => true,
            _ => false,
        }
    }
}

impl MaybeFnLike for ast::Expr {
    fn is_fn_like(&self) -> bool {
        match self.node {
            ast::ExprClosure(..) => true,
            _ => false,
        }
    }
}

/// Carries either an FnLikeNode or a Expr, as these are the two
/// constructs that correspond to "code" (as in, something from which
/// we can construct a control-flow graph).
#[derive(Copy, Clone)]
pub enum Code<'a> {
    FnLike(FnLikeNode<'a>),
    Expr(&'a Expr),
}

impl<'a> Code<'a> {
    pub fn id(&self) -> NodeId {
        match *self {
            Code::FnLike(node) => node.id(),
            Code::Expr(block) => block.id,
        }
    }

    /// Attempts to construct a Code from presumed FnLike or Expr node input.
    pub fn from_node(map: &map::Map<'a>, id: NodeId) -> Option<Code<'a>> {
        match map.get(id) {
            map::NodeBlock(_) => {
                //  Use the parent, hopefully an expression node.
                Code::from_node(map, map.get_parent_node(id))
            }
            map::NodeExpr(expr) => Some(Code::Expr(expr)),
            node => FnLikeNode::from_node(node).map(Code::FnLike)
        }
    }
}

/// These are all the components one can extract from a fn item for
/// use when implementing FnLikeNode operations.
struct ItemFnParts<'a> {
    name:     Name,
    decl:     &'a ast::FnDecl,
    unsafety: ast::Unsafety,
    constness: ast::Constness,
    abi:      abi::Abi,
    vis:      &'a ast::Visibility,
    generics: &'a ast::Generics,
    body:     ast::BodyId,
    id:       NodeId,
    span:     Span,
    attrs:    &'a [Attribute],
}

/// These are all the components one can extract from a closure expr
/// for use when implementing FnLikeNode operations.
struct ClosureParts<'a> {
    decl: &'a FnDecl,
    body: ast::BodyId,
    id: NodeId,
    span: Span,
    attrs: &'a [Attribute],
}

impl<'a> ClosureParts<'a> {
    fn new(d: &'a FnDecl, b: ast::BodyId, id: NodeId, s: Span, attrs: &'a [Attribute]) -> Self {
        ClosureParts {
            decl: d,
            body: b,
            id,
            span: s,
            attrs,
        }
    }
}

impl<'a> FnLikeNode<'a> {
    /// Attempts to construct a FnLikeNode from presumed FnLike node input.
    pub fn from_node(node: Node) -> Option<FnLikeNode> {
        let fn_like = match node {
            map::NodeItem(item) => item.is_fn_like(),
            map::NodeTraitItem(tm) => tm.is_fn_like(),
            map::NodeImplItem(_) => true,
            map::NodeExpr(e) => e.is_fn_like(),
            _ => false
        };
        if fn_like {
            Some(FnLikeNode {
                node,
            })
        } else {
            None
        }
    }

    pub fn body(self) -> ast::BodyId {
        self.handle(|i: ItemFnParts<'a>|  i.body,
                    |_, _, _: &'a ast::MethodSig, _, body: ast::BodyId, _, _|  body,
                    |c: ClosureParts<'a>| c.body)
    }

    pub fn decl(self) -> &'a FnDecl {
        self.handle(|i: ItemFnParts<'a>|  &*i.decl,
                    |_, _, sig: &'a ast::MethodSig, _, _, _, _|  &sig.decl,
                    |c: ClosureParts<'a>| c.decl)
    }

    pub fn span(self) -> Span {
        self.handle(|i: ItemFnParts|     i.span,
                    |_, _, _: &'a ast::MethodSig, _, _, span, _| span,
                    |c: ClosureParts|    c.span)
    }

    pub fn id(self) -> NodeId {
        self.handle(|i: ItemFnParts|     i.id,
                    |id, _, _: &'a ast::MethodSig, _, _, _, _| id,
                    |c: ClosureParts|    c.id)
    }

    pub fn constness(self) -> ast::Constness {
        match self.kind() {
            FnKind::ItemFn(_, _, _, constness, ..) => {
                constness
            }
            FnKind::Method(_, m, ..) => {
                m.constness
            }
            _ => ast::Constness::NotConst
        }
    }

    pub fn kind(self) -> FnKind<'a> {
        let item = |p: ItemFnParts<'a>| -> FnKind<'a> {
            FnKind::ItemFn(p.name, p.generics, p.unsafety, p.constness, p.abi, p.vis, p.attrs)
        };
        let closure = |c: ClosureParts<'a>| {
            FnKind::Closure(c.attrs)
        };
        let method = |_, name: Name, sig: &'a ast::MethodSig, vis, _, _, attrs| {
            FnKind::Method(name, sig, vis, attrs)
        };
        self.handle(item, method, closure)
    }

    fn handle<A, I, M, C>(self, item_fn: I, method: M, closure: C) -> A where
        I: FnOnce(ItemFnParts<'a>) -> A,
        M: FnOnce(NodeId,
                  Name,
                  &'a ast::MethodSig,
                  Option<&'a ast::Visibility>,
                  ast::BodyId,
                  Span,
                  &'a [Attribute])
                  -> A,
        C: FnOnce(ClosureParts<'a>) -> A,
    {
        match self.node {
            map::NodeItem(i) => match i.node {
                ast::ItemFn(ref decl, unsafety, constness, abi, ref generics, block) =>
                    item_fn(ItemFnParts {
                        id: i.id,
                        name: i.name,
                        decl: &decl,
                        unsafety,
                        body: block,
                        generics,
                        abi,
                        vis: &i.vis,
                        constness,
                        span: i.span,
                        attrs: &i.attrs,
                    }),
                _ => bug!("item FnLikeNode that is not fn-like"),
            },
            map::NodeTraitItem(ti) => match ti.node {
                ast::TraitItemKind::Method(ref sig, ast::TraitMethod::Provided(body)) => {
                    method(ti.id, ti.name, sig, None, body, ti.span, &ti.attrs)
                }
                _ => bug!("trait method FnLikeNode that is not fn-like"),
            },
            map::NodeImplItem(ii) => {
                match ii.node {
                    ast::ImplItemKind::Method(ref sig, body) => {
                        method(ii.id, ii.name, sig, Some(&ii.vis), body, ii.span, &ii.attrs)
                    }
                    _ => {
                        bug!("impl method FnLikeNode that is not fn-like")
                    }
                }
            },
            map::NodeExpr(e) => match e.node {
                ast::ExprClosure(_, ref decl, block, _fn_decl_span, _gen) =>
                    closure(ClosureParts::new(&decl, block, e.id, e.span, &e.attrs)),
                _ => bug!("expr FnLikeNode that is not fn-like"),
            },
            _ => bug!("other FnLikeNode that is not fn-like"),
        }
    }
}
<|endoftext|>


<commit_before>mod err;

use anime::local::{EpisodeMatcher, Episodes};
use anime::remote::anilist::AniList;
use anime::remote::{RemoteService, SeriesInfo};
use err::{Error, Result};
use gumdrop::Options;
use snafu::{ensure, OptionExt, ResultExt};
use std::borrow::Cow;
use std::fs;
use std::io;
use std::os::unix::fs::symlink;
use std::path::{Path, PathBuf};
use std::thread;
use std::time::Duration;

#[derive(Options)]
struct CmdOptions {
    #[options(help = "print help message")]
    help: bool,
    #[options(free, required, help = "the path pointing to the series to split")]
    path: PathBuf,
    #[options(
        help = "the path to create the split seasons in. By default, the parent directory of the series path will be used"
    )]
    out_dir: Option<PathBuf>,
    #[options(
        help = "the anime series ID. Use if the program doesn't detect the right series automatically"
    )]
    series_id: Option<u32>,
    #[options(
        help = "the format to rename the files as. Must contain \"{title}\" and \"{episode}\""
    )]
    name_format: Option<String>,
    #[options(help = "the custom regex pattern to match episode files with")]
    matcher: Option<String>,
    #[options(no_short, help = "link episode files via symlinks")]
    symlink: bool,
    #[options(no_short, help = "link episode files via hardlinks")]
    hardlink: bool,
    #[options(no_short, help = "link episode files via file moves")]
    move_files: bool,
}

fn main() {
    let args = CmdOptions::parse_args_default_or_exit();

    if let Err(err) = run(args) {
        err::display_error(err);
        std::process::exit(1);
    }
}

fn run(args: CmdOptions) -> Result<()> {
    let remote = AniList::unauthenticated();

    let path = args.path.canonicalize().context(err::IO)?;

    let name_format = match &args.name_format {
        Some(format) => NameFormat::new(format)?,
        None => NameFormat::new("{title} - {episode}.mkv")?,
    };

    let matcher = match &args.matcher {
        Some(pattern) => {
            let pattern = pattern
                .replace("{title}", "(?P<title>.+)")
                .replace("{episode}", r"(?P<episode>\d+)");
            EpisodeMatcher::from_pattern(pattern)?
        }
        None => EpisodeMatcher::new(),
    };

    let out_dir = match &args.out_dir {
        Some(out_dir) => PathBuf::from(out_dir),
        None => path.parent().context(err::NoDirParent)?.into(),
    };

    let data = SeriesData {
        episodes: Episodes::parse(&path, &matcher)?,
        name_format,
        link_method: LinkMethod::from_args(&args),
        path,
        out_dir,
    };

    let series = {
        let title = parse_path_title(&data.path)?;
        find_series_info(&args, title, &remote)?
    };

    format_sequels(&data, series, &remote)
}

fn format_sequels(data: &SeriesData, mut info: SeriesInfo, remote: &AniList) -> Result<()> {
    let mut episode_offset = 0;
    let mut total_actions = 0;

    while let Some(sequel) = info.sequel {
        info = remote.search_info_by_id(sequel)?;
        episode_offset += info.episodes;

        println!("splitting up {}", info.title.preferred);

        let actions = PendingActions::generate(data, &info, episode_offset)?;

        if !actions.confirm_proceed()? {
            continue;
        }

        total_actions += actions.execute()?;

        // We don't need to sleep if there isn't another sequel
        if info.sequel.is_none() {
            break;
        }

        thread::sleep(Duration::from_millis(500));
    }

    println!("\n{} actions performed", total_actions);
    Ok(())
}

struct SeriesData {
    episodes: Episodes,
    name_format: NameFormat,
    link_method: LinkMethod,
    path: PathBuf,
    out_dir: PathBuf,
}

struct NameFormat(String);

impl NameFormat {
    fn new<S>(format: S) -> Result<NameFormat>
    where
        S: Into<String>,
    {
        let format = format.into();

        ensure!(
            format.contains("{title}"),
            err::MissingFormatGroup { group: "title" }
        );

        ensure!(
            format.contains("{episode}"),
            err::MissingFormatGroup { group: "episode" }
        );

        Ok(NameFormat(format))
    }

    fn process<S>(&self, name: S, episode: u32) -> String
    where
        S: AsRef<str>,
    {
        self.0
            .replace("{title}", name.as_ref())
            .replace("{episode}", &format!("{:02}", episode))
    }
}

#[derive(Copy, Clone)]
enum LinkMethod {
    Symlink,
    Hardlink,
    Move,
}

impl LinkMethod {
    fn from_args(args: &CmdOptions) -> Self {
        if args.symlink {
            Self::Symlink
        } else if args.hardlink {
            Self::Hardlink
        } else if args.move_files {
            Self::Move
        } else {
            Self::default()
        }
    }

    fn execute<P>(self, from: P, to: P) -> Result<()>
    where
        P: AsRef<Path>,
    {
        let from = from.as_ref();
        let to = to.as_ref();

        let result = match self {
            Self::Symlink => symlink(from, to),
            Self::Hardlink => fs::hard_link(from, to),
            Self::Move => fs::rename(from, to),
        };

        result.context(err::LinkIO { from, to })
    }

    fn plural_str(self) -> &'static str {
        match self {
            Self::Symlink => "symlinks",
            Self::Hardlink => "hardlinks",
            Self::Move => "moves",
        }
    }
}

impl Default for LinkMethod {
    fn default() -> LinkMethod {
        LinkMethod::Symlink
    }
}

struct FormatAction {
    from: PathBuf,
    to: PathBuf,
}

impl FormatAction {
    #[inline(always)]
    fn new<S, O>(from: S, to: O) -> Self
    where
        S: Into<PathBuf>,
        O: Into<PathBuf>,
    {
        Self {
            from: from.into(),
            to: to.into(),
        }
    }
}

struct PendingActions {
    actions: Vec<FormatAction>,
    out_dir: PathBuf,
    method: LinkMethod,
}

impl PendingActions {
    fn generate(data: &SeriesData, info: &SeriesInfo, episode_offset: u32) -> Result<Self> {
        let out_dir = data.out_dir.join(&info.title.preferred);
        let mut actions = Vec::new();

        for real_ep_num in (1 + episode_offset)..=(episode_offset + info.episodes) {
            let original_filename = match data.episodes.get(&real_ep_num) {
                Some(filename) => filename,
                None => continue,
            };

            let episode_path = data.path.join(original_filename);

            let new_filename = data
                .name_format
                .process(&info.title.preferred, real_ep_num - episode_offset);

            let new_path = out_dir.join(new_filename);

            actions.push(FormatAction::new(episode_path, new_path));
        }

        Ok(Self {
            actions,
            out_dir,
            method: data.link_method,
        })
    }

    fn confirm_proceed(&self) -> Result<bool> {
        if self.actions.is_empty() {
            println!("| no actions to be performed");
            return Ok(true);
        }

        println!(
            "| the following {} will be executed:",
            self.method.plural_str()
        );

        for action in &self.actions {
            println!(
                "{} -> {}",
                action.from.to_string_lossy(),
                action.to.to_string_lossy()
            );
        }

        println!("| is this okay? (Y/n)");

        let answer = {
            let mut buffer = String::new();
            io::stdin().read_line(&mut buffer).context(err::IO)?;
            buffer.trim_end().to_string()
        };

        match answer.as_ref() {
            "n" | "N" => Ok(false),
            _ => Ok(true),
        }
    }

    fn execute(self) -> Result<u32> {
        if self.actions.is_empty() {
            return Ok(0);
        }

        if !self.out_dir.exists() {
            fs::create_dir_all(&self.out_dir).context(err::FileIO {
                path: &self.out_dir,
            })?;
        }

        let mut actions_performed = 0;

        for action in self.actions {
            match self.method.execute(action.from, action.to) {
                Ok(_) => actions_performed += 1,
                Err(Error::LinkIO { source, .. })
                    if source.kind() == io::ErrorKind::AlreadyExists =>
                {
                    actions_performed += 1;
                }
                Err(err) => eprintln!("{}", err),
            }
        }

        Ok(actions_performed)
    }
}

fn parse_path_title<P>(path: P) -> Result<String>
where
    P: AsRef<Path>,
{
    let path = path.as_ref();

    ensure!(path.is_dir(), err::NotADirectory);

    let fname = path.file_name().context(err::NoDirName)?.to_string_lossy();
    let title = detect::dir::parse_title(fname).context(err::FolderTitleParse)?;

    Ok(title)
}

fn find_series_info<S>(args: &CmdOptions, title: S, remote: &AniList) -> Result<SeriesInfo>
where
    S: AsRef<str>,
{
    match args.series_id {
        Some(id) => {
            let info = remote.search_info_by_id(id)?;
            Ok(info)
        }
        None => {
            let title = title.as_ref();
            let results = remote.search_info_by_name(title)?.map(Cow::Owned);

            detect::series_info::closest_match(results, title)
                .map(|(_, info)| info.into_owned())
                .context(err::UnableToDetectSeries { title })
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    #[should_panic]
    fn name_format_detect_no_group() {
        NameFormat::new("useless").unwrap();
    }

    #[test]
    fn name_format_detect_no_title_group() {
        let result = NameFormat::new("missing_title - {episode}.mkv");

        match result {
            Err(err::Error::MissingFormatGroup { group }) if group == "title" => (),
            Ok(_) => panic!("expected missing title group error"),
            Err(err) => panic!("expected missing title group error, got: {:?}", err),
        }
    }

    #[test]
    fn name_format_detect_no_episode_group() {
        let result = NameFormat::new("{title} - missing_episode.mkv");

        match result {
            Err(err::Error::MissingFormatGroup { group }) if group == "episode" => (),
            Ok(_) => panic!("expected missing episode group error"),
            Err(err) => panic!("expected missing episode group error, got: {:?}", err),
        }
    }
}
<commit_msg>anisplit: handle multiple titles being found in a directory<commit_after>mod err;

use anime::local::{EpisodeMatcher, Episodes};
use anime::remote::anilist::AniList;
use anime::remote::{RemoteService, SeriesInfo};
use err::{Error, Result};
use gumdrop::Options;
use snafu::{ensure, OptionExt, ResultExt};
use std::borrow::Cow;
use std::collections::HashMap;
use std::fs;
use std::io;
use std::os::unix::fs::symlink;
use std::path::{Path, PathBuf};
use std::thread;
use std::time::Duration;

#[derive(Options)]
struct CmdOptions {
    #[options(help = "print help message")]
    help: bool,
    #[options(free, required, help = "the path pointing to the series to split")]
    path: PathBuf,
    #[options(
        help = "the path to create the split seasons in. By default, the parent directory of the series path will be used"
    )]
    out_dir: Option<PathBuf>,
    #[options(
        help = "the anime series ID. Use if the program doesn't detect the right series automatically"
    )]
    series_id: Option<u32>,
    #[options(
        help = "the format to rename the files as. Must contain \"{title}\" and \"{episode}\""
    )]
    name_format: Option<String>,
    #[options(help = "the custom regex pattern to match episode files with")]
    matcher: Option<String>,
    #[options(no_short, help = "link episode files via symlinks")]
    symlink: bool,
    #[options(no_short, help = "link episode files via hardlinks")]
    hardlink: bool,
    #[options(no_short, help = "link episode files via file moves")]
    move_files: bool,
}

fn main() {
    let args = CmdOptions::parse_args_default_or_exit();

    if let Err(err) = run(args) {
        err::display_error(err);
        std::process::exit(1);
    }
}

fn run(args: CmdOptions) -> Result<()> {
    let remote = AniList::unauthenticated();

    let path = args.path.canonicalize().context(err::IO)?;

    let name_format = match &args.name_format {
        Some(format) => NameFormat::new(format)?,
        None => NameFormat::new("{title} - {episode}.mkv")?,
    };

    let matcher = match &args.matcher {
        Some(pattern) => {
            let pattern = pattern
                .replace("{title}", "(?P<title>.+)")
                .replace("{episode}", r"(?P<episode>\d+)");
            EpisodeMatcher::from_pattern(pattern)?
        }
        None => EpisodeMatcher::new(),
    };

    let out_dir = match &args.out_dir {
        Some(out_dir) => PathBuf::from(out_dir),
        None => path.parent().context(err::NoDirParent)?.into(),
    };

    let all_episodes = Episodes::parse_all(&path, &matcher)?;

    match all_episodes.len() {
        len if len > 1 => {
            println!("found multiple titles in directory.. these will be moved instead\nrerun the tool afterwards to split up merged seasons\n");

            let data = SeriesData {
                name_format,
                link_method: LinkMethod::Move,
                path,
                out_dir,
            };

            split_multiple_titles(&args, data, all_episodes, remote)
        }
        1 => {
            let (_, episodes) = all_episodes.into_iter().next().unwrap();

            let series = {
                let title = parse_path_title(&path)?;
                find_series_info(&args, title, &remote)?
            };

            let data = SeriesData {
                name_format,
                link_method: LinkMethod::from_args(&args),
                path,
                out_dir,
            };

            format_sequels(data, series, episodes, remote)
        }
        _ => Ok(()),
    }
}

fn split_multiple_titles(
    args: &CmdOptions,
    data: SeriesData,
    all_episodes: HashMap<String, Episodes>,
    remote: AniList,
) -> Result<()> {
    let original_title = parse_path_title(&data.path)?;

    for (title, episodes) in all_episodes {
        if title == original_title {
            continue;
        }

        println!("splitting up {}", title);

        let info = find_series_info(args, title, &remote)?;
        let actions = PendingActions::generate(&data, &info, &episodes, 0)?;

        if !actions.confirm_proceed()? {
            continue;
        }

        actions.execute()?;
    }

    Ok(())
}

fn format_sequels(
    data: SeriesData,
    mut info: SeriesInfo,
    episodes: Episodes,
    remote: AniList,
) -> Result<()> {
    let mut episode_offset = 0;
    let mut total_actions = 0;

    while let Some(sequel) = info.sequel {
        info = remote.search_info_by_id(sequel)?;
        episode_offset += info.episodes;

        println!("splitting up {}", info.title.preferred);

        let actions = PendingActions::generate(&data, &info, &episodes, episode_offset)?;

        if !actions.confirm_proceed()? {
            continue;
        }

        total_actions += actions.execute()?;

        // We don't need to sleep if there isn't another sequel
        if info.sequel.is_none() {
            break;
        }

        thread::sleep(Duration::from_millis(500));
    }

    println!("\n{} actions performed", total_actions);
    Ok(())
}

struct SeriesData {
    name_format: NameFormat,
    link_method: LinkMethod,
    path: PathBuf,
    out_dir: PathBuf,
}

struct NameFormat(String);

impl NameFormat {
    fn new<S>(format: S) -> Result<NameFormat>
    where
        S: Into<String>,
    {
        let format = format.into();

        ensure!(
            format.contains("{title}"),
            err::MissingFormatGroup { group: "title" }
        );

        ensure!(
            format.contains("{episode}"),
            err::MissingFormatGroup { group: "episode" }
        );

        Ok(NameFormat(format))
    }

    fn process<S>(&self, name: S, episode: u32) -> String
    where
        S: AsRef<str>,
    {
        self.0
            .replace("{title}", name.as_ref())
            .replace("{episode}", &format!("{:02}", episode))
    }
}

#[derive(Copy, Clone)]
enum LinkMethod {
    Symlink,
    Hardlink,
    Move,
}

impl LinkMethod {
    fn from_args(args: &CmdOptions) -> Self {
        if args.symlink {
            Self::Symlink
        } else if args.hardlink {
            Self::Hardlink
        } else if args.move_files {
            Self::Move
        } else {
            Self::default()
        }
    }

    fn execute<P>(self, from: P, to: P) -> Result<()>
    where
        P: AsRef<Path>,
    {
        let from = from.as_ref();
        let to = to.as_ref();

        let result = match self {
            Self::Symlink => symlink(from, to),
            Self::Hardlink => fs::hard_link(from, to),
            Self::Move => fs::rename(from, to),
        };

        result.context(err::LinkIO { from, to })
    }

    fn plural_str(self) -> &'static str {
        match self {
            Self::Symlink => "symlinks",
            Self::Hardlink => "hardlinks",
            Self::Move => "moves",
        }
    }
}

impl Default for LinkMethod {
    fn default() -> LinkMethod {
        LinkMethod::Symlink
    }
}

struct FormatAction {
    from: PathBuf,
    to: PathBuf,
}

impl FormatAction {
    #[inline(always)]
    fn new<S, O>(from: S, to: O) -> Self
    where
        S: Into<PathBuf>,
        O: Into<PathBuf>,
    {
        Self {
            from: from.into(),
            to: to.into(),
        }
    }
}

struct PendingActions {
    actions: Vec<FormatAction>,
    out_dir: PathBuf,
    method: LinkMethod,
}

impl PendingActions {
    fn generate(
        data: &SeriesData,
        info: &SeriesInfo,
        episodes: &Episodes,
        episode_offset: u32,
    ) -> Result<Self> {
        let out_dir = data.out_dir.join(&info.title.preferred);
        let mut actions = Vec::new();

        for real_ep_num in (1 + episode_offset)..=(episode_offset + info.episodes) {
            let original_filename = match episodes.get(&real_ep_num) {
                Some(filename) => filename,
                None => continue,
            };

            let episode_path = data.path.join(original_filename);

            let new_filename = data
                .name_format
                .process(&info.title.preferred, real_ep_num - episode_offset);

            let new_path = out_dir.join(new_filename);

            actions.push(FormatAction::new(episode_path, new_path));
        }

        Ok(Self {
            actions,
            out_dir,
            method: data.link_method,
        })
    }

    fn confirm_proceed(&self) -> Result<bool> {
        if self.actions.is_empty() {
            println!("| no actions to be performed");
            return Ok(true);
        }

        println!(
            "| the following file {} will be executed:",
            self.method.plural_str()
        );

        for action in &self.actions {
            println!(
                "{} -> {}",
                action.from.to_string_lossy(),
                action.to.to_string_lossy()
            );
        }

        println!("| is this okay? (Y/n)");

        let answer = {
            let mut buffer = String::new();
            io::stdin().read_line(&mut buffer).context(err::IO)?;
            buffer.trim_end().to_string()
        };

        match answer.as_ref() {
            "n" | "N" => Ok(false),
            _ => Ok(true),
        }
    }

    fn execute(self) -> Result<u32> {
        if self.actions.is_empty() {
            return Ok(0);
        }

        if !self.out_dir.exists() {
            fs::create_dir_all(&self.out_dir).context(err::FileIO {
                path: &self.out_dir,
            })?;
        }

        let mut actions_performed = 0;

        for action in self.actions {
            match self.method.execute(action.from, action.to) {
                Ok(_) => actions_performed += 1,
                Err(Error::LinkIO { source, .. })
                    if source.kind() == io::ErrorKind::AlreadyExists =>
                {
                    actions_performed += 1;
                }
                Err(err) => eprintln!("{}", err),
            }
        }

        Ok(actions_performed)
    }
}

fn parse_path_title<P>(path: P) -> Result<String>
where
    P: AsRef<Path>,
{
    let path = path.as_ref();

    ensure!(path.is_dir(), err::NotADirectory);

    let fname = path.file_name().context(err::NoDirName)?.to_string_lossy();
    let title = detect::dir::parse_title(fname).context(err::FolderTitleParse)?;

    Ok(title)
}

fn find_series_info<S>(args: &CmdOptions, title: S, remote: &AniList) -> Result<SeriesInfo>
where
    S: AsRef<str>,
{
    match args.series_id {
        Some(id) => {
            let info = remote.search_info_by_id(id)?;
            Ok(info)
        }
        None => {
            let title = title.as_ref();
            let results = remote.search_info_by_name(title)?.map(Cow::Owned);

            detect::series_info::closest_match(results, title)
                .map(|(_, info)| info.into_owned())
                .context(err::UnableToDetectSeries { title })
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    #[should_panic]
    fn name_format_detect_no_group() {
        NameFormat::new("useless").unwrap();
    }

    #[test]
    fn name_format_detect_no_title_group() {
        let result = NameFormat::new("missing_title - {episode}.mkv");

        match result {
            Err(err::Error::MissingFormatGroup { group }) if group == "title" => (),
            Ok(_) => panic!("expected missing title group error"),
            Err(err) => panic!("expected missing title group error, got: {:?}", err),
        }
    }

    #[test]
    fn name_format_detect_no_episode_group() {
        let result = NameFormat::new("{title} - missing_episode.mkv");

        match result {
            Err(err::Error::MissingFormatGroup { group }) if group == "episode" => (),
            Ok(_) => panic!("expected missing episode group error"),
            Err(err) => panic!("expected missing episode group error, got: {:?}", err),
        }
    }
}
<|endoftext|>


<commit_before>/*
    This file is a part of cargo-esr.

    Copyright (C) 2017 Mohammad AlSaleh <CE.Mohammad.AlSaleh at gmail.com>
    https://github.com/rust-alt/cargo-esr

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

#[macro_use]
extern crate clap;
extern crate isatty;
extern crate cargo_esr;

use clap::{App, ArgGroup};

use cargo_esr::esr_crate::CrateSearch;
use cargo_esr::esr_score::CrateScores;
use cargo_esr::esr_printer::EsrPrinter;

use std::env;

fn check_limit(limit: &str, printer: EsrPrinter) -> usize {
    match str::parse::<usize>(limit) {
        Ok(limit_num) => {
            if limit_num < 3 || limit_num > 30 {
                printer.limit_out_of_range(limit_num, 3, 30);
                std::process::exit(1);
            } else {
                limit_num
            }
        },
        Err(_) => {
            printer.limit_invalid(limit);
            std::process::exit(1);
        },
    }
}

fn main() {
    // clap
    let mut args: Vec<_> = env::args().collect();

    // cargo subcommand compat
    if args.len() >= 2 && args[1] == "esr" {
        args[0] = "cargo-esr".into();
        let _ = args.remove(1);
    }

    let yaml = load_yaml!("cargo-esr.yml");
    let search_or_score = ArgGroup::with_name("search-or-score")
        .args(&["search", "score"])
        .required(true);

    let clap_app = App::from_yaml(yaml).group(search_or_score);

    let m = clap_app.get_matches_from(args);

    let crate_only = m.is_present("crate-only");
    let sort_positive = m.is_present("sort-positive");
    let results_limit = m.value_of("results-limit").unwrap_or("10");
    let search_limit = m.value_of("search-limit").unwrap_or("30");
    let is_tty = isatty::stdout_isatty() && !m.is_present("no-color");
    let printer = EsrPrinter::new_with_term(is_tty);

    let results_limit_num = check_limit(results_limit, printer);
    let search_limit_num = check_limit(search_limit, printer);

    let mut gh_token = String::with_capacity(48);
    if !crate_only {
        if let Some(arg_token) = m.value_of("gh-token") {
            gh_token.push_str(arg_token);
        } else if let Ok(env_token) = std::env::var("CARGO_ESR_GH_TOKEN") {
            gh_token.push_str(&env_token);
        } else {
            printer.no_token();
            std::process::exit(1);
        }
    }

    match (m.value_of("score"), m.values_of("search")) {
        (Some(crate_name), _) => {
            let crate_scores_res = if crate_only {
                CrateScores::from_id_crate_only(crate_name, printer)
            } else {
                CrateScores::from_id_with_token(crate_name, &gh_token, printer)
            };

            if let Ok(crate_scores) = crate_scores_res {
                crate_scores.print_detailed_scores();
            } else {
                printer.crate_no_score(crate_name);
                std::process::exit(1);
            }
        },

        (_, Some(search_pattern)) => {
            let search_str = search_pattern.fold(String::with_capacity(128), |s, p| s + p + "+")
                // In case a multi-word search is quoted
                .replace(' ', "+")
                .trim_right_matches('+')
                .to_string();

            let search_res = CrateSearch::from_id_single_page(&("per_page=".to_string() + search_limit + "&q=" +
                                                                &search_str));

            if let Ok(search) = search_res {
                let crates = search.get_crates();

                if crates.len() == 0 {
                    printer.search_no_results(&search_str);
                    std::process::exit(1);
                }

                let crates_scores_res = CrateScores::collect_scores(
                    crates,
                    &gh_token,
                    crate_only,
                    search_limit_num,
                    printer);
                CrateScores::print_search_results(
                    &*crates_scores_res,
                    sort_positive,
                    results_limit_num,
                    printer);
            } else {
                printer.search_failed(&search_str);
                std::process::exit(1);
            }
        },

        (_, _) => unreachable!(),
    }
}
<commit_msg>Use constants for limit bounds<commit_after>/*
    This file is a part of cargo-esr.

    Copyright (C) 2017 Mohammad AlSaleh <CE.Mohammad.AlSaleh at gmail.com>
    https://github.com/rust-alt/cargo-esr

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

#[macro_use]
extern crate clap;
extern crate isatty;
extern crate cargo_esr;

use clap::{App, ArgGroup};

use cargo_esr::esr_crate::CrateSearch;
use cargo_esr::esr_score::CrateScores;
use cargo_esr::esr_printer::EsrPrinter;

use std::env;

const LIMIT_LOW: usize = 3;
const LIMIT_HIGH: usize = 30;

fn check_limit(limit: &str, printer: EsrPrinter) -> usize {
    match str::parse::<usize>(limit) {
        Ok(limit_num) => {
            let ll = LIMIT_LOW;
            let lh = LIMIT_HIGH;
            if limit_num < ll || limit_num > lh {
                printer.limit_out_of_range(limit_num, ll, lh);
                std::process::exit(1);
            } else {
                limit_num
            }
        },
        Err(_) => {
            printer.limit_invalid(limit);
            std::process::exit(1);
        },
    }
}

fn main() {
    // clap
    let mut args: Vec<_> = env::args().collect();

    // cargo subcommand compat
    if args.len() >= 2 && args[1] == "esr" {
        args[0] = "cargo-esr".into();
        let _ = args.remove(1);
    }

    let yaml = load_yaml!("cargo-esr.yml");
    let search_or_score = ArgGroup::with_name("search-or-score")
        .args(&["search", "score"])
        .required(true);

    let clap_app = App::from_yaml(yaml).group(search_or_score);

    let m = clap_app.get_matches_from(args);

    let crate_only = m.is_present("crate-only");
    let sort_positive = m.is_present("sort-positive");
    let results_limit = m.value_of("results-limit").unwrap_or("10");
    let search_limit = m.value_of("search-limit").unwrap_or("30");
    let is_tty = isatty::stdout_isatty() && !m.is_present("no-color");
    let printer = EsrPrinter::new_with_term(is_tty);

    let results_limit_num = check_limit(results_limit, printer);
    let search_limit_num = check_limit(search_limit, printer);

    let mut gh_token = String::with_capacity(48);
    if !crate_only {
        if let Some(arg_token) = m.value_of("gh-token") {
            gh_token.push_str(arg_token);
        } else if let Ok(env_token) = std::env::var("CARGO_ESR_GH_TOKEN") {
            gh_token.push_str(&env_token);
        } else {
            printer.no_token();
            std::process::exit(1);
        }
    }

    match (m.value_of("score"), m.values_of("search")) {
        (Some(crate_name), _) => {
            let crate_scores_res = if crate_only {
                CrateScores::from_id_crate_only(crate_name, printer)
            } else {
                CrateScores::from_id_with_token(crate_name, &gh_token, printer)
            };

            if let Ok(crate_scores) = crate_scores_res {
                crate_scores.print_detailed_scores();
            } else {
                printer.crate_no_score(crate_name);
                std::process::exit(1);
            }
        },

        (_, Some(search_pattern)) => {
            let search_str = search_pattern.fold(String::with_capacity(128), |s, p| s + p + "+")
                // In case a multi-word search is quoted
                .replace(' ', "+")
                .trim_right_matches('+')
                .to_string();

            let search_res = CrateSearch::from_id_single_page(&("per_page=".to_string() + search_limit + "&q=" +
                                                                &search_str));

            if let Ok(search) = search_res {
                let crates = search.get_crates();

                if crates.len() == 0 {
                    printer.search_no_results(&search_str);
                    std::process::exit(1);
                }

                let crates_scores_res = CrateScores::collect_scores(
                    crates,
                    &gh_token,
                    crate_only,
                    search_limit_num,
                    printer);
                CrateScores::print_search_results(
                    &*crates_scores_res,
                    sort_positive,
                    results_limit_num,
                    printer);
            } else {
                printer.search_failed(&search_str);
                std::process::exit(1);
            }
        },

        (_, _) => unreachable!(),
    }
}
<|endoftext|>


<commit_before>extern crate rustc_serialize;
extern crate mio;
extern crate docopt;
#[macro_use]
extern crate log;
extern crate rasputin;

use log::LogLevel;
use docopt::Docopt;
use mio::{EventLoop};

use rasputin::server::Server;

static USAGE: &'static str = "
rasputin - HA transactional store with a focus on usability, stability and performance.

This program is the Rasputin DB server process.

Usage:
    rasputind --help
    rasputind [--cli-port=<listening port>] [--peer-port=<listening port>] [--peers=<peers>] [--logfile=<file>] [--storage-dir=<directory>]

Options:
    --help                          Show this help message.
    --cli-port=<port>               Listening port for communication between servers.
    --peer-port=<port>              Listening port for communication with clients.
    --peers=<host1:port1,...>       List of comma-delimited peers, e.g:
                                    foo.baz.com:7777,bar.baz.com:7777
    --logfile=<path>                File to log output to instead of stdout.
    --storage-dir=<path>            Directory to store the persisted data in.
";

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

    rasputin::logging::init_logger(args.flag_logfile, LogLevel::Info).unwrap();
    print_banner();

    let peer_port: u16 = match args.flag_peer_port {
        Some(p) => p,
        None => 7770,
    };

    let cli_port: u16 = match args.flag_cli_port {
        Some(p) => p,
        None => 8880,
    };

    let peers: Vec<String> = args.flag_peers
        .split(",")
        .map(|s| s.to_string())
        .filter(|s| s != "")
        .collect();

    Server::run(peer_port, cli_port, peers);
}

#[derive(Debug, RustcDecodable)]
struct Args {
    flag_help: bool,
    flag_cli_port: Option<u16>,
    flag_peer_port: Option<u16>,
    flag_peers: String,
    flag_logfile: Option<String>,
    flag_storagedir: Option<String>,
}

fn print_banner() {
    info!("
 )xxxxx[:::::::::>
 ______ _______ _______  _____  _     _ _______ _____ __   _
 |_____/ |_____| |______ |_____] |     |    |      |   | \\  |
 |    \\_ |     | ______| |       |_____|    |    __|__ |  \\_|");
}
<commit_msg>mehr upgrayedd<commit_after>extern crate rustc_serialize;
extern crate mio;
extern crate docopt;
#[macro_use]
extern crate log;
extern crate rasputin;

use log::LogLevel;
use docopt::Docopt;
use mio::{EventLoop};

use rasputin::server::Server;

static USAGE: &'static str = "
rasputin - HA transactional store with a focus on usability, stability and performance.

This program is the Rasputin DB server process.

Usage:
    rasputind --help
    rasputind [--cli-port=<listening port>] [--peer-port=<listening port>] [--peers=<peers>] [--logfile=<file>] [--storage-dir=<directory>]

Options:
    --help                          Show this help message.
    --cli-port=<port>               Listening port for communication between servers.
    --peer-port=<port>              Listening port for communication with clients.
    --peers=<host1:port1,...>       List of comma-delimited peers, e.g:
                                    foo.baz.com:7777,bar.baz.com:7777
    --logfile=<path>                File to log output to instead of stdout.
    --storage-dir=<path>            Directory to store the persisted data in.
";

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

    rasputin::logging::init_logger(args.flag_logfile, LogLevel::Info).unwrap();
    print_banner();

    let peer_port: u16 = match args.flag_peer_port {
        Some(p) => p,
        None => 7770,
    };

    let cli_port: u16 = match args.flag_cli_port {
        Some(p) => p,
        None => 8880,
    };

    let peers: Vec<String> = args.flag_peers
        .split(",")
        .map(|s| s.to_string())
        .filter(|s| s != "")
        .collect();

    Server::run(peer_port, cli_port, peers);
}

#[derive(Debug, RustcDecodable)]
struct Args {
    flag_help: bool,
    flag_cli_port: Option<u16>,
    flag_peer_port: Option<u16>,
    flag_peers: String,
    flag_logfile: Option<String>,
    flag_storagedir: Option<String>,
}

fn print_banner() {
    info!("
 )xxxxx[:::::::::>
  ______ _______ _______  _____  _     _ _______ _____ __   _
 |_____/ |_____| |______ |_____] |     |    |      |   | \\  |
 |    \\_ |     | ______| |       |_____|    |    __|__ |  \\_|");
}
<|endoftext|>


<commit_before>// Copyright (C) 2016, 2017, 2018 Élisabeth HENRY.
//
// This file is part of Crowbook.
//
// Crowbook is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation, either version 2.1 of the License, or
// (at your option) any later version.
//
// Crowbook is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with Crowbook.  If not, see <http://www.gnu.org/licenses/>.

use helpers::*;

use yaml_rust::Yaml;
use console;
use crowbook::{Result, Book, BookOptions};
use crowbook_intl_runtime::set_lang;
use crowbook::Stats;
use tempdir::TempDir;
use clap::ArgMatches;
use std::process::exit;
use std::io;
use std::io::Read;
use std::env;
use std::fs::File;
use simplelog::{Config, TermLogger, Level, LevelFilter, SimpleLogger, WriteLogger};

/// Render a book to specific format
fn render_format(book: &mut Book, emoji: bool, matches: &ArgMatches, format: &str) {
    let mut key = String::from("output.");
    key.push_str(format);

    let mut stdout = false;
    let mut file = None;

    if let Some(f) = matches.value_of("output") {
        if f == "-" {
            stdout = true;
        } else {
            file = Some(String::from(f));
        }
    }

    let res = book.options.get_path(&key);

    let result = match(file, res, stdout) {
        (Some(file), _, _) |
        (None, Ok(file), false) => book.render_format_to_file(format, file),

        (None, Err(_), _) |
        (None, _, true)
        => book.render_format_to(format, &mut io::stdout()),
    };

    match result {
        Err(err) => print_error(&format!("{}", err), emoji),
        Ok(_) => {}
    }
}

pub fn try_main() -> Result<()> {
    let lang = get_lang()
        .or_else(|| {
            match env::var("LANG") {
                Ok(val) => {
                    Some(val)
                },
                Err(_) => None,
            }
        });
    if let Some(val) = lang {
        if val.starts_with("fr") {
            set_lang("fr");
        } else {
            set_lang("en");
        }
    }

    let mut fancy_ui = true;
    let mut emoji = console::Term::stderr().want_emoji();

    let (matches, help, version) = create_matches();

    if matches.is_present("force-emoji") {
        emoji = true;
    }

    if !matches.is_present("quiet") {
        display_header(emoji);
    }

    if matches.is_present("list-options") {
        println!("{}", BookOptions::description(false));
        exit(0);
    }

    if matches.is_present("no-fancy") || matches.is_present("stats") {
        fancy_ui = false;
        emoji = false;
    }

    if matches.is_present("list-options-md") {
        println!("{}", BookOptions::description(true));
        exit(0);
    }

    if matches.is_present("print-template") {
        let template = matches.value_of("print-template").unwrap();
        let mut book = Book::new();
        set_book_options(&mut book, &matches);
        let result = book.get_template(template.as_ref());
        match result {
            Ok(s) => {
                println!("{}", s);
                exit(0);
            }
            Err(_) => print_error_and_exit(&lformat!("{} is not a valid template name.",
                                                     template),
                                           emoji),
        }
    }

    if matches.is_present("help") {
        println!("{}", help);
        exit(0);
    }

    if matches.is_present("version") {
        println!("{}", version);
        exit(0);
    }

    if matches.is_present("create") {
        create_book(&matches);
    }

    if !matches.is_present("BOOK") {
        print_error_and_exit(&lformat!("You must pass the file of a book configuration \
                               file.\n\n{}\n\nFor more information try --help.",
                                       matches.usage()),
                             emoji);
    }

    // ok to unwrap since clap checks it's there
    let s = matches.value_of("BOOK").unwrap();

    // Initalize logger
    let mut log_config = Config::default();
    log_config.target = None;
    log_config.location = None;
    log_config.time = None;
    let verbosity = if matches.is_present("verbose") {
        log_config.time = Some(Level::Error);
        log_config.target = Some(Level::Error);
        fancy_ui = false;
        LevelFilter::Debug
    } else if matches.is_present("quiet") {
        fancy_ui = false;
        LevelFilter::Error
    } else if fancy_ui {
        LevelFilter::Warn
    } else {
        LevelFilter::Info
    };

    let error_dir = TempDir::new("crowbook").unwrap();
    let error_path = "error.log";
    if fancy_ui {
        let errors = File::create(error_dir.path().join(error_path)).unwrap();
        let _ = WriteLogger::init(verbosity, log_config, errors);
    } else {
        if TermLogger::init(verbosity, log_config).is_err() {
            // If it failed, not much we can do, we just won't display log
            let _ = SimpleLogger::init(verbosity, log_config);
        }
    }

    {
        let mut book = Book::new();
        if matches.is_present("autograph") {
            println!("{}", &lformat!("Enter autograph: "));
            let mut autograph = String::new();
            match io::stdin().read_to_string(&mut autograph) {
                Ok(_) => {
                    book.options.set_yaml(Yaml::String("autograph".to_string()), Yaml::String(autograph)).unwrap();
                },
                Err(_) => print_error(&lformat!("could not read autograph from stdin"), emoji),
            }
        }

        if fancy_ui {
            book.add_progress_bar(emoji);
        }
        book.set_options(&get_book_options(&matches));

        {
            let res = if matches.is_present("single") {
                if s != "-" {
                    book.load_markdown_file(s)
                } else {
                    book.read_markdown_config(io::stdin())
                }
            } else if s != "-" {
                book.load_file(s)
            } else {
                book.read_config(io::stdin())
            }.map(|_| ());

            match res {
                Ok(..) => {},
                Err(err) => {
                    book.set_error(&format!("{}", err));
                    return Err(err);
                }
            }
        }

        set_book_options(&mut book, &matches);

        if matches.is_present("stats") {
            let stats = Stats::new(&book, matches.is_present("verbose"));
            println!("{}", stats);
            exit(0);
        }

        if let Some(format) = matches.value_of("to") {
            render_format(&mut book, emoji,& matches, format);
        } else {
            book.render_all();
        }
    }
    if fancy_ui {
        let mut errors = String::new();
        let mut file = File::open(error_dir.path().join(error_path)).unwrap();
        file.read_to_string(&mut errors).unwrap();
        if !errors.is_empty() {
            print_warning(&lformat!("Crowbook exited successfully, but the following errors occurred:"),
                          emoji);
            // Non-efficient dedup algorithm but we need to keep the order
            let mut lines: Vec<String> = vec!();
            for line in errors.lines().into_iter() {
                let mut contains = false;
                for l in &lines {
                    if &*l == line {
                        contains = true;
                        break;
                    }
                }
                if !contains {
                    lines.push(line.to_string());
                }
            }
            for line in &lines {
                if line.starts_with("[ERROR]") {
                    let line = &line[8..];
                    print_error(line, emoji);
                } else if line.starts_with("[WARN]") {
                    let line = &line[7..];
                    print_warning(line, emoji);
                }
            }
        }
    }

    Ok(())
}

pub fn real_main() {
    if let Err(err) = try_main() {
        print_error_and_exit(&format!("{}", err), false);
    }
}
<commit_msg>Don't display debug messages using verbose with stats<commit_after>// Copyright (C) 2016, 2017, 2018 Élisabeth HENRY.
//
// This file is part of Crowbook.
//
// Crowbook is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation, either version 2.1 of the License, or
// (at your option) any later version.
//
// Crowbook is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with Crowbook.  If not, see <http://www.gnu.org/licenses/>.

use helpers::*;

use yaml_rust::Yaml;
use console;
use crowbook::{Result, Book, BookOptions};
use crowbook_intl_runtime::set_lang;
use crowbook::Stats;
use tempdir::TempDir;
use clap::ArgMatches;
use std::process::exit;
use std::io;
use std::io::Read;
use std::env;
use std::fs::File;
use simplelog::{Config, TermLogger, Level, LevelFilter, SimpleLogger, WriteLogger};

/// Render a book to specific format
fn render_format(book: &mut Book, emoji: bool, matches: &ArgMatches, format: &str) {
    let mut key = String::from("output.");
    key.push_str(format);

    let mut stdout = false;
    let mut file = None;

    if let Some(f) = matches.value_of("output") {
        if f == "-" {
            stdout = true;
        } else {
            file = Some(String::from(f));
        }
    }

    let res = book.options.get_path(&key);

    let result = match(file, res, stdout) {
        (Some(file), _, _) |
        (None, Ok(file), false) => book.render_format_to_file(format, file),

        (None, Err(_), _) |
        (None, _, true)
        => book.render_format_to(format, &mut io::stdout()),
    };

    match result {
        Err(err) => print_error(&format!("{}", err), emoji),
        Ok(_) => {}
    }
}

pub fn try_main() -> Result<()> {
    let lang = get_lang()
        .or_else(|| {
            match env::var("LANG") {
                Ok(val) => {
                    Some(val)
                },
                Err(_) => None,
            }
        });
    if let Some(val) = lang {
        if val.starts_with("fr") {
            set_lang("fr");
        } else {
            set_lang("en");
        }
    }

    let mut fancy_ui = true;
    let mut emoji = console::Term::stderr().want_emoji();

    let (matches, help, version) = create_matches();

    if matches.is_present("force-emoji") {
        emoji = true;
    }

    if !matches.is_present("quiet") {
        display_header(emoji);
    }

    if matches.is_present("list-options") {
        println!("{}", BookOptions::description(false));
        exit(0);
    }

    if matches.is_present("no-fancy") || matches.is_present("stats") {
        fancy_ui = false;
        emoji = false;
    }

    if matches.is_present("list-options-md") {
        println!("{}", BookOptions::description(true));
        exit(0);
    }

    if matches.is_present("print-template") {
        let template = matches.value_of("print-template").unwrap();
        let mut book = Book::new();
        set_book_options(&mut book, &matches);
        let result = book.get_template(template.as_ref());
        match result {
            Ok(s) => {
                println!("{}", s);
                exit(0);
            }
            Err(_) => print_error_and_exit(&lformat!("{} is not a valid template name.",
                                                     template),
                                           emoji),
        }
    }

    if matches.is_present("help") {
        println!("{}", help);
        exit(0);
    }

    if matches.is_present("version") {
        println!("{}", version);
        exit(0);
    }

    if matches.is_present("create") {
        create_book(&matches);
    }

    if !matches.is_present("BOOK") {
        print_error_and_exit(&lformat!("You must pass the file of a book configuration \
                               file.\n\n{}\n\nFor more information try --help.",
                                       matches.usage()),
                             emoji);
    }

    // ok to unwrap since clap checks it's there
    let s = matches.value_of("BOOK").unwrap();

    // Initalize logger
    let mut log_config = Config::default();
    log_config.target = None;
    log_config.location = None;
    log_config.time = None;
    let verbosity = if matches.is_present("verbose") && !matches.is_present("stats") {
        log_config.time = Some(Level::Error);
        log_config.target = Some(Level::Error);
        fancy_ui = false;
        LevelFilter::Debug
    } else if matches.is_present("quiet") {
        fancy_ui = false;
        LevelFilter::Error
    } else if fancy_ui {
        LevelFilter::Warn
    } else {
        LevelFilter::Info
    };

    let error_dir = TempDir::new("crowbook").unwrap();
    let error_path = "error.log";
    if fancy_ui {
        let errors = File::create(error_dir.path().join(error_path)).unwrap();
        let _ = WriteLogger::init(verbosity, log_config, errors);
    } else {
        if TermLogger::init(verbosity, log_config).is_err() {
            // If it failed, not much we can do, we just won't display log
            let _ = SimpleLogger::init(verbosity, log_config);
        }
    }

    {
        let mut book = Book::new();
        if matches.is_present("autograph") {
            println!("{}", &lformat!("Enter autograph: "));
            let mut autograph = String::new();
            match io::stdin().read_to_string(&mut autograph) {
                Ok(_) => {
                    book.options.set_yaml(Yaml::String("autograph".to_string()), Yaml::String(autograph)).unwrap();
                },
                Err(_) => print_error(&lformat!("could not read autograph from stdin"), emoji),
            }
        }

        if fancy_ui {
            book.add_progress_bar(emoji);
        }
        book.set_options(&get_book_options(&matches));

        {
            let res = if matches.is_present("single") {
                if s != "-" {
                    book.load_markdown_file(s)
                } else {
                    book.read_markdown_config(io::stdin())
                }
            } else if s != "-" {
                book.load_file(s)
            } else {
                book.read_config(io::stdin())
            }.map(|_| ());

            match res {
                Ok(..) => {},
                Err(err) => {
                    book.set_error(&format!("{}", err));
                    return Err(err);
                }
            }
        }

        set_book_options(&mut book, &matches);

        if matches.is_present("stats") {
            let stats = Stats::new(&book, matches.is_present("verbose"));
            println!("{}", stats);
            exit(0);
        }

        if let Some(format) = matches.value_of("to") {
            render_format(&mut book, emoji,& matches, format);
        } else {
            book.render_all();
        }
    }
    if fancy_ui {
        let mut errors = String::new();
        let mut file = File::open(error_dir.path().join(error_path)).unwrap();
        file.read_to_string(&mut errors).unwrap();
        if !errors.is_empty() {
            print_warning(&lformat!("Crowbook exited successfully, but the following errors occurred:"),
                          emoji);
            // Non-efficient dedup algorithm but we need to keep the order
            let mut lines: Vec<String> = vec!();
            for line in errors.lines().into_iter() {
                let mut contains = false;
                for l in &lines {
                    if &*l == line {
                        contains = true;
                        break;
                    }
                }
                if !contains {
                    lines.push(line.to_string());
                }
            }
            for line in &lines {
                if line.starts_with("[ERROR]") {
                    let line = &line[8..];
                    print_error(line, emoji);
                } else if line.starts_with("[WARN]") {
                    let line = &line[7..];
                    print_warning(line, emoji);
                }
            }
        }
    }

    Ok(())
}

pub fn real_main() {
    if let Err(err) = try_main() {
        print_error_and_exit(&format!("{}", err), false);
    }
}
<|endoftext|>


<commit_before>extern crate html2text;
extern crate termion;
extern crate argparse;
use std::io::{self, Write};
use html2text::render::text_renderer::{RichAnnotation};
use argparse::{ArgumentParser, Store, StoreOption};

fn to_style(tag: &Vec<RichAnnotation>) -> String {
    let mut style = String::new();

    for ann in tag {
        match *ann {
            RichAnnotation::Default => (),
            RichAnnotation::Link(_) => {
                style.push_str(&format!("{}", termion::style::Underline));
            },
            RichAnnotation::Image => {
                style.push_str(&format!("{}", termion::color::Fg(termion::color::LightBlue)));
            },
        }
    }
    style
}

fn main() {
    let stdin = io::stdin();
    let mut filename = String::new();
    {
        let mut ap = ArgumentParser::new();
        ap.refer(&mut filename)
          .add_argument("filename", Store, "Set HTML filename");
        ap.parse_args_or_exit();
    }

    let (width, _) = termion::terminal_size().unwrap();

    let mut file = std::fs::File::open(filename).expect("Tried to open file");
    let annotated = html2text::from_read_rich(&mut file, width as usize);
    for line in annotated {
        for (s, tag) in line.iter() {
            let style = to_style(tag);
            write!(io::stdout(), "{}{}{}", style, s, termion::style::Reset).unwrap();
        }
        write!(io::stdout(), "\n").unwrap();
    }
}<commit_msg>Add basic interactive navigation.<commit_after>extern crate html2text;
extern crate termion;
extern crate argparse;
use std::io::{self, Write};
use html2text::render::text_renderer::{RichAnnotation};
use argparse::{ArgumentParser, Store};
use termion::input::TermRead;
use termion::event::Key;
use termion::raw::IntoRawMode;
use termion::cursor::Goto;

fn to_style(tag: &Vec<RichAnnotation>) -> String {
    let mut style = String::new();

    for ann in tag {
        match *ann {
            RichAnnotation::Default => (),
            RichAnnotation::Link(_) => {
                style.push_str(&format!("{}", termion::style::Underline));
            },
            RichAnnotation::Image => {
                style.push_str(&format!("{}", termion::color::Fg(termion::color::LightBlue)));
            },
        }
    }
    style
}

fn main() {
    let mut filename = String::new();
    {
        let mut ap = ArgumentParser::new();
        ap.refer(&mut filename)
          .add_argument("filename", Store, "Set HTML filename");
        ap.parse_args_or_exit();
    }

    let (width, height) = termion::terminal_size().unwrap();
    let (width, height) = (width as usize, height as usize);

    let mut file = std::fs::File::open(filename).expect("Tried to open file");
    let annotated = html2text::from_read_rich(&mut file, width as usize);

    let mut keys = io::stdin().keys();
    let mut stdout = io::stdout().into_raw_mode().unwrap();
    let mut pos = 0;
    let mut cursor_x = 1;
    let mut cursor_y = 1;
    loop {
        let maxpos = std::cmp::min(pos+height, annotated.len());
        write!(stdout, "{}", termion::clear::All).unwrap();
        for (i, line) in annotated[pos..maxpos].iter().enumerate() {
            write!(stdout, "{}", Goto(1, i as u16 +1)).unwrap();
            for (s, tag) in line.iter() {
                let style = to_style(tag);
                write!(stdout, "{}{}{}", style, s, termion::style::Reset).unwrap();
            }
        }
        write!(stdout, "{}", Goto(cursor_x, cursor_y)).unwrap();
        stdout.flush().unwrap();
        if let Some(Ok(k)) = keys.next() {
            match k {
                Key::Char('q') => break,
                Key::Char('j') => {
                    if cursor_y < height as u16 {
                        cursor_y += 1;
                    }
                },
                Key::Char('k') => {
                    if cursor_y > 1 {
                        cursor_y -= 1;
                    }
                },
                Key::Char('h') => {
                    if cursor_x > 1 {
                        cursor_x -= 1;
                    }
                },
                Key::Char('l') => {
                    if cursor_x < width as u16 {
                        cursor_x += 1;
                    }
                },
                Key::Char(' ') | Key::PageDown => {
                    if pos + height-1 < annotated.len() {
                        pos += height-1;
                    }
                },
                Key::PageUp => {
                    if pos >= height-1 {
                        pos -= height-1;
                    }
                },
                _ => {},
            }
        }
    }
}<|endoftext|>


<commit_before>/*
 * Copyright 2017-2018 Ben Ashford
 *
 * Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
 * http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
 * <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
 * option. This file may not be copied, modified, or distributed
 * except according to those terms.
 */

use std::collections::VecDeque;
use std::net::SocketAddr;

use futures::{
    future,
    future::Either,
    sync::{mpsc, oneshot},
    Async, AsyncSink, Future, Poll, Sink, Stream,
};

use tokio_executor::{DefaultExecutor, Executor};

use super::connect::{connect, RespConnection};
use error;
use reconnect::{reconnect, Reconnect};
use resp;

enum SendStatus {
    Ok,
    End,
    Full(resp::RespValue),
}

#[derive(Debug)]
enum ReceiveStatus {
    ReadyFinished,
    ReadyMore,
    NotReady,
}

struct PairedConnectionInner {
    connection: RespConnection,
    out_rx: mpsc::UnboundedReceiver<(resp::RespValue, oneshot::Sender<resp::RespValue>)>,
    waiting: VecDeque<oneshot::Sender<resp::RespValue>>,

    send_status: SendStatus,
}

impl PairedConnectionInner {
    fn new(
        con: RespConnection,
        out_rx: mpsc::UnboundedReceiver<(resp::RespValue, oneshot::Sender<resp::RespValue>)>,
    ) -> Self {
        PairedConnectionInner {
            connection: con,
            out_rx: out_rx,
            waiting: VecDeque::new(),
            send_status: SendStatus::Ok,
        }
    }

    fn impl_start_send(&mut self, msg: resp::RespValue) -> Result<bool, ()> {
        match self
            .connection
            .start_send(msg)
            .map_err(|e| error!("Error sending message to connection: {}", e))?
        {
            AsyncSink::Ready => {
                self.send_status = SendStatus::Ok;
                Ok(true)
            }
            AsyncSink::NotReady(msg) => {
                self.send_status = SendStatus::Full(msg);
                Ok(false)
            }
        }
    }

    fn poll_start_send(&mut self) -> Result<bool, ()> {
        let mut status = SendStatus::Ok;
        ::std::mem::swap(&mut status, &mut self.send_status);

        let message = match status {
            SendStatus::End => {
                self.send_status = SendStatus::End;
                return Ok(false);
            }
            SendStatus::Full(msg) => msg,
            SendStatus::Ok => match self
                .out_rx
                .poll()
                .map_err(|_| error!("Error polling for messages to send"))?
            {
                Async::Ready(Some((msg, tx))) => {
                    self.waiting.push_back(tx);
                    msg
                }
                Async::Ready(None) => {
                    self.send_status = SendStatus::End;
                    return Ok(false);
                }
                Async::NotReady => return Ok(false),
            },
        };

        self.impl_start_send(message)
    }

    fn poll_complete(&mut self) -> Result<(), ()> {
        self.connection
            .poll_complete()
            .map_err(|e| error!("Error polling for completeness: {}", e))?;
        Ok(())
    }

    fn receive(&mut self) -> Result<ReceiveStatus, ()> {
        if let SendStatus::End = self.send_status {
            if self.waiting.is_empty() {
                return Ok(ReceiveStatus::ReadyFinished);
            }
        }
        match self
            .connection
            .poll()
            .map_err(|e| error!("Error polling to receive messages: {}", e))?
        {
            Async::Ready(None) => {
                error!("Connection to Redis closed unexpectedly");
                Err(())
            }
            Async::Ready(Some(msg)) => {
                let tx = match self.waiting.pop_front() {
                    Some(tx) => tx,
                    None => panic!("Received unexpected message: {:?}", msg),
                };
                let _ = tx.send(msg);
                Ok(ReceiveStatus::ReadyMore)
            }
            Async::NotReady => Ok(ReceiveStatus::NotReady),
        }
    }
}

impl Future for PairedConnectionInner {
    type Item = ();
    type Error = ();

    fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
        // If there's something to send, send it...
        let mut sending = true;
        while sending {
            sending = self.poll_start_send()?;
        }

        self.poll_complete()?;

        // If there's something to receive, receive it...
        loop {
            match self.receive()? {
                ReceiveStatus::NotReady => return Ok(Async::NotReady),
                ReceiveStatus::ReadyMore => (),
                ReceiveStatus::ReadyFinished => return Ok(Async::Ready(())),
            }
        }
    }
}

type SendPayload = (resp::RespValue, oneshot::Sender<resp::RespValue>);

/// A shareable and cheaply cloneable connection to which Redis commands can be sent
#[derive(Clone)]
pub struct PairedConnection {
    out_tx_c:
        Reconnect<SendPayload, mpsc::UnboundedSender<SendPayload>, error::Error, error::Error>,
}

/// The default starting point to use most default Redis functionality.
///
/// Returns a future that resolves to a `PairedConnection`.
pub fn paired_connect(
    addr: &SocketAddr,
) -> impl Future<Item = PairedConnection, Error = error::Error> + Send {
    // NOTE - the lazy here isn't strictly necessary, but to avoid two potential problems, both
    // related to backward compatibility.
    //
    // 1. It keeps the signature of this function the same, so this change doesn't require a new
    // major version.  Although we might use a new major version anyway as the internal behaviour
    // is different enough to warrant it.
    //
    // 2. It ensures that a Tokio executor runs the future.  This function would work correctly
    // without it, if we could be sure this function was only called by other futures that were
    // executed within the Tokio executor, but we cannot guarantee that.
    let addr = addr.clone();
    future::lazy(move || {
        let out_tx_c = reconnect(
            |con: &mpsc::UnboundedSender<SendPayload>, act| {
                Box::new(future::result(con.unbounded_send(act)).map_err(|e| e.into()))
            },
            move || {
                let con_f = connect(&addr).map_err(|e| e.into()).and_then(|connection| {
                    let (out_tx, out_rx) = mpsc::unbounded();
                    let paired_connection_inner =
                        Box::new(PairedConnectionInner::new(connection, out_rx));
                    let mut executor = DefaultExecutor::current();

                    match executor.spawn(paired_connection_inner) {
                        Ok(_) => Ok(out_tx),
                        Err(e) => Err(error::Error::Internal(format!(
                            "Cannot spawn paired connection: {:?}",
                            e
                        ))),
                    }
                });
                Box::new(con_f)
            },
        );
        Ok(PairedConnection { out_tx_c })
    })
}

impl PairedConnection {
    /// Sends a command to Redis.
    ///
    /// The message must be in the format of a single RESP message, this can be constructed
    /// manually or with the `resp_array!` macro.  Returned is a future that resolves to the value
    /// returned from Redis.  The type must be one for which the `resp::FromResp` trait is defined.
    ///
    /// The future will fail for numerous reasons, including but not limited to: IO issues, conversion
    /// problems, and server-side errors being returned by Redis.
    ///
    /// Behind the scenes the message is queued up and sent to Redis asynchronously before the
    /// future is realised.  As such, it is guaranteed that messages are sent in the same order
    /// that `send` is called.
    pub fn send<T>(&self, msg: resp::RespValue) -> impl Future<Item = T, Error = error::Error>
    where
        T: resp::FromResp,
    {
        match &msg {
            &resp::RespValue::Array(_) => (),
            _ => {
                return Either::B(future::err(error::internal(
                    "Command must be a RespValue::Array",
                )))
            }
        }

        let (tx, rx) = oneshot::channel();
        let send_f = self
            .out_tx_c
            .do_work((msg, tx))
            .map_err(|_| error::Error::EndOfStream);

        Either::A(send_f.and_then(|_| {
            rx.then(|v| match v {
                Ok(v) => future::result(T::from_resp(v)),
                Err(e) => future::err(e.into()),
            })
        }))

        // if let Err(_e) = self.out_tx.unbounded_send((msg, tx)) {
        //     // receiving end of a channel droppped
        //     return Either::B(future::err(error::Error::EndOfStream));
        // }

        // Either::A(rx.then(|v| match v {
        //     Ok(v) => future::result(T::from_resp(v)),
        //     Err(e) => future::err(e.into()),
        // }))
    }

    pub fn send_and_forget(&self, msg: resp::RespValue) {
        let _ = self.send::<resp::RespValue>(msg);
    }
}
<commit_msg>Remove out-of-date comment<commit_after>/*
 * Copyright 2017-2018 Ben Ashford
 *
 * Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
 * http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
 * <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
 * option. This file may not be copied, modified, or distributed
 * except according to those terms.
 */

use std::collections::VecDeque;
use std::net::SocketAddr;

use futures::{
    future,
    future::Either,
    sync::{mpsc, oneshot},
    Async, AsyncSink, Future, Poll, Sink, Stream,
};

use tokio_executor::{DefaultExecutor, Executor};

use super::connect::{connect, RespConnection};
use error;
use reconnect::{reconnect, Reconnect};
use resp;

enum SendStatus {
    Ok,
    End,
    Full(resp::RespValue),
}

#[derive(Debug)]
enum ReceiveStatus {
    ReadyFinished,
    ReadyMore,
    NotReady,
}

struct PairedConnectionInner {
    connection: RespConnection,
    out_rx: mpsc::UnboundedReceiver<(resp::RespValue, oneshot::Sender<resp::RespValue>)>,
    waiting: VecDeque<oneshot::Sender<resp::RespValue>>,

    send_status: SendStatus,
}

impl PairedConnectionInner {
    fn new(
        con: RespConnection,
        out_rx: mpsc::UnboundedReceiver<(resp::RespValue, oneshot::Sender<resp::RespValue>)>,
    ) -> Self {
        PairedConnectionInner {
            connection: con,
            out_rx: out_rx,
            waiting: VecDeque::new(),
            send_status: SendStatus::Ok,
        }
    }

    fn impl_start_send(&mut self, msg: resp::RespValue) -> Result<bool, ()> {
        match self
            .connection
            .start_send(msg)
            .map_err(|e| error!("Error sending message to connection: {}", e))?
        {
            AsyncSink::Ready => {
                self.send_status = SendStatus::Ok;
                Ok(true)
            }
            AsyncSink::NotReady(msg) => {
                self.send_status = SendStatus::Full(msg);
                Ok(false)
            }
        }
    }

    fn poll_start_send(&mut self) -> Result<bool, ()> {
        let mut status = SendStatus::Ok;
        ::std::mem::swap(&mut status, &mut self.send_status);

        let message = match status {
            SendStatus::End => {
                self.send_status = SendStatus::End;
                return Ok(false);
            }
            SendStatus::Full(msg) => msg,
            SendStatus::Ok => match self
                .out_rx
                .poll()
                .map_err(|_| error!("Error polling for messages to send"))?
            {
                Async::Ready(Some((msg, tx))) => {
                    self.waiting.push_back(tx);
                    msg
                }
                Async::Ready(None) => {
                    self.send_status = SendStatus::End;
                    return Ok(false);
                }
                Async::NotReady => return Ok(false),
            },
        };

        self.impl_start_send(message)
    }

    fn poll_complete(&mut self) -> Result<(), ()> {
        self.connection
            .poll_complete()
            .map_err(|e| error!("Error polling for completeness: {}", e))?;
        Ok(())
    }

    fn receive(&mut self) -> Result<ReceiveStatus, ()> {
        if let SendStatus::End = self.send_status {
            if self.waiting.is_empty() {
                return Ok(ReceiveStatus::ReadyFinished);
            }
        }
        match self
            .connection
            .poll()
            .map_err(|e| error!("Error polling to receive messages: {}", e))?
        {
            Async::Ready(None) => {
                error!("Connection to Redis closed unexpectedly");
                Err(())
            }
            Async::Ready(Some(msg)) => {
                let tx = match self.waiting.pop_front() {
                    Some(tx) => tx,
                    None => panic!("Received unexpected message: {:?}", msg),
                };
                let _ = tx.send(msg);
                Ok(ReceiveStatus::ReadyMore)
            }
            Async::NotReady => Ok(ReceiveStatus::NotReady),
        }
    }
}

impl Future for PairedConnectionInner {
    type Item = ();
    type Error = ();

    fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
        // If there's something to send, send it...
        let mut sending = true;
        while sending {
            sending = self.poll_start_send()?;
        }

        self.poll_complete()?;

        // If there's something to receive, receive it...
        loop {
            match self.receive()? {
                ReceiveStatus::NotReady => return Ok(Async::NotReady),
                ReceiveStatus::ReadyMore => (),
                ReceiveStatus::ReadyFinished => return Ok(Async::Ready(())),
            }
        }
    }
}

type SendPayload = (resp::RespValue, oneshot::Sender<resp::RespValue>);

/// A shareable and cheaply cloneable connection to which Redis commands can be sent
#[derive(Clone)]
pub struct PairedConnection {
    out_tx_c:
        Reconnect<SendPayload, mpsc::UnboundedSender<SendPayload>, error::Error, error::Error>,
}

/// The default starting point to use most default Redis functionality.
///
/// Returns a future that resolves to a `PairedConnection`.
pub fn paired_connect(
    addr: &SocketAddr,
) -> impl Future<Item = PairedConnection, Error = error::Error> + Send {
    // NOTE - the lazy here isn't strictly necessary, but to avoid two potential problems, both
    // related to backward compatibility.
    //
    // 1. It keeps the signature of this function the same, so this change doesn't require a new
    // major version.  Although we might use a new major version anyway as the internal behaviour
    // is different enough to warrant it.
    //
    // 2. It ensures that a Tokio executor runs the future.  This function would work correctly
    // without it, if we could be sure this function was only called by other futures that were
    // executed within the Tokio executor, but we cannot guarantee that.
    let addr = addr.clone();
    future::lazy(move || {
        let out_tx_c = reconnect(
            |con: &mpsc::UnboundedSender<SendPayload>, act| {
                Box::new(future::result(con.unbounded_send(act)).map_err(|e| e.into()))
            },
            move || {
                let con_f = connect(&addr).map_err(|e| e.into()).and_then(|connection| {
                    let (out_tx, out_rx) = mpsc::unbounded();
                    let paired_connection_inner =
                        Box::new(PairedConnectionInner::new(connection, out_rx));
                    let mut executor = DefaultExecutor::current();

                    match executor.spawn(paired_connection_inner) {
                        Ok(_) => Ok(out_tx),
                        Err(e) => Err(error::Error::Internal(format!(
                            "Cannot spawn paired connection: {:?}",
                            e
                        ))),
                    }
                });
                Box::new(con_f)
            },
        );
        Ok(PairedConnection { out_tx_c })
    })
}

impl PairedConnection {
    /// Sends a command to Redis.
    ///
    /// The message must be in the format of a single RESP message, this can be constructed
    /// manually or with the `resp_array!` macro.  Returned is a future that resolves to the value
    /// returned from Redis.  The type must be one for which the `resp::FromResp` trait is defined.
    ///
    /// The future will fail for numerous reasons, including but not limited to: IO issues, conversion
    /// problems, and server-side errors being returned by Redis.
    ///
    /// Behind the scenes the message is queued up and sent to Redis asynchronously before the
    /// future is realised.  As such, it is guaranteed that messages are sent in the same order
    /// that `send` is called.
    pub fn send<T>(&self, msg: resp::RespValue) -> impl Future<Item = T, Error = error::Error>
    where
        T: resp::FromResp,
    {
        match &msg {
            &resp::RespValue::Array(_) => (),
            _ => {
                return Either::B(future::err(error::internal(
                    "Command must be a RespValue::Array",
                )))
            }
        }

        let (tx, rx) = oneshot::channel();
        let send_f = self
            .out_tx_c
            .do_work((msg, tx))
            .map_err(|_| error::Error::EndOfStream);

        Either::A(send_f.and_then(|_| {
            rx.then(|v| match v {
                Ok(v) => future::result(T::from_resp(v)),
                Err(e) => future::err(e.into()),
            })
        }))
    }

    pub fn send_and_forget(&self, msg: resp::RespValue) {
        let _ = self.send::<resp::RespValue>(msg);
    }
}
<|endoftext|>


<commit_before>// src/common/routes.rs

/// MM Routes

// Import Modules
// External
use nickel::{HttpRouter, MediaType, Nickel, Router};
// Common Utilities
use ::common::api_result::{JsonEncoder};
// Controllers
use ::controllers::controller_manager::ControllerManager;

pub fn get_routes() -> Router<ControllerManager> {
    let mut router = Nickel::router();
    router.get("/", middleware! { |_request, mut response|
        info!("API Endpoint: /");
        response.set(MediaType::Json);
        format!(r#"{{"name":"Money Map", "version":"v0.0.1", "status":"ok"}}"#)
    });

    //Test Actions
    router.get("/test/retrieve", middleware! { |request, mut response|
        info!("API Endpoint: POST /test/retrieve");
        let cm: &ControllerManager = request.server_data();
        let result = &cm.test_controller.retrieve();

        response.set(MediaType::Json);
        JsonEncoder::encode(result)
    });

    router.post("/test/save", middleware! { |request, mut response|
        info!("API Endpoint: POST /test/save");
        let cm: &ControllerManager = request.server_data();
        let result = &cm.test_controller.save(request);

        response.set(MediaType::Json);
        JsonEncoder::encode(result)
    });

    router.get("/test/failure", middleware! { |request, mut response|
        info!("API Endpoint: GET /test/failure");
        let cm: &ControllerManager = request.server_data();
        let result = &cm.test_controller.failure();

        response.set(MediaType::Json);
        JsonEncoder::encode(result)
    });

    //Users Actions
    router.get("/users", middleware! { |_request, mut response|
        info!("API Endpoint: GET /users");
        response.set(MediaType::Json);
        //let user = Controllers::users::PubUser::new("John".to_string(), "Smith".to_string(), "test@test.com".to_string());
        format!("{{\"status\":\"success\"}}")
    });

    router.post("/users", middleware! { |request, mut response|
        info!("API Endpoint: POST /users");
        let cm: &ControllerManager = request.server_data();
        let result = &cm.users_controller.create(request);

        response.set(MediaType::Json);
        JsonEncoder::encode(result)
    });
router
}
<commit_msg>Use manifest version for api root<commit_after>// src/common/routes.rs

/// MM Routes

// Import Modules
// External
use nickel::{HttpRouter, MediaType, Nickel, Router};
// Common Utilities
use ::common::api_result::{JsonEncoder};
// Controllers
use ::controllers::controller_manager::ControllerManager;

pub fn get_routes() -> Router<ControllerManager> {
    let mut router = Nickel::router();
    router.get("/", middleware! { |_request, mut response|
        info!("API Endpoint: /");
        response.set(MediaType::Json);
        format!(r#"{{"name":"Money Map", "version":"{}", "status":"ok"}}"#, env!("CARGO_PKG_VERSION"))
    });

    //Test Actions
    router.get("/test/retrieve", middleware! { |request, mut response|
        info!("API Endpoint: POST /test/retrieve");
        let cm: &ControllerManager = request.server_data();
        let result = &cm.test_controller.retrieve();

        response.set(MediaType::Json);
        JsonEncoder::encode(result)
    });

    router.post("/test/save", middleware! { |request, mut response|
        info!("API Endpoint: POST /test/save");
        let cm: &ControllerManager = request.server_data();
        let result = &cm.test_controller.save(request);

        response.set(MediaType::Json);
        JsonEncoder::encode(result)
    });

    router.get("/test/failure", middleware! { |request, mut response|
        info!("API Endpoint: GET /test/failure");
        let cm: &ControllerManager = request.server_data();
        let result = &cm.test_controller.failure();

        response.set(MediaType::Json);
        JsonEncoder::encode(result)
    });

    //Users Actions
    router.get("/users", middleware! { |_request, mut response|
        info!("API Endpoint: GET /users");
        response.set(MediaType::Json);
        //let user = Controllers::users::PubUser::new("John".to_string(), "Smith".to_string(), "test@test.com".to_string());
        format!("{{\"status\":\"success\"}}")
    });

    router.post("/users", middleware! { |request, mut response|
        info!("API Endpoint: POST /users");
        let cm: &ControllerManager = request.server_data();
        let result = &cm.users_controller.create(request);

        response.set(MediaType::Json);
        JsonEncoder::encode(result)
    });
router
}
<|endoftext|>


<commit_before><commit_msg>Sketch some not-quite-compiling code into comp/fe/lexer.rs.<commit_after>import std._io.buf_reader;

iter buffers(buf_reader rdr) -> vec[u8] {
  while (true) {
    let vec[u8] v = rdr.read();
    if (std._vec.len[u8](v) == uint(0)) {
      ret;
    }
    put v;
  }
}

iter bytes(buf_reader rdr) -> u8 {
  for each (vec[u8] buf in buffers(rdr)) {
    for (u8 b in buf) {
      // FIXME: doesn't compile at the moment.
      // put b;
    }
  }
}
<|endoftext|>


<commit_before>extern crate bip_handshake;
extern crate bip_metainfo;
extern crate bip_util;
extern crate rotor;
extern crate rotor_stream;
#[macro_use]
extern crate nom;

mod disk;
mod message;
mod piece;
mod protocol;
mod registration;
mod token;
<commit_msg>Disable bip_peer For bip_handshake Migration<commit_after>extern crate bip_handshake;
extern crate bip_metainfo;
extern crate bip_util;
extern crate rotor;
extern crate rotor_stream;
#[macro_use]
extern crate nom;

//mod disk;
//mod message;
//mod piece;
//mod protocol;
//mod registration;
//mod token;
<|endoftext|>