<commit_before>//! Askama implements a type-safe compiler for Jinja-like templates.
//! It lets you write templates in a Jinja-like syntax,
//! which are linked to a `struct` defining the template context.
//! This is done using a custom derive implementation (implemented
//! in [`askama_derive`](https://crates.io/crates/askama_derive)).
//!
//! For feature highlights and a quick start, please review the
//! [README](https://github.com/djc/askama/blob/master/README.md).
//!
//! # Creating Askama templates
//!
//! An Askama template is a `struct` definition which provides the template
//! context combined with a UTF-8 encoded text file (or inline source, see
//! below). Askama can be used to generate any kind of text-based format.
//! The template file's extension may can be used to provide content type hints.
//!
//! A template consists of **text contents**, which are passed through as-is,
//! **expressions**, which get replaced with content while being rendered, and
//! **tags**, which control the template's logic.
//! The template syntax is very similar to [Jinja](http://jinja.pocoo.org/),
//! as well as Jinja-derivatives like [Twig](http://twig.sensiolabs.org/) or
//! [Tera](https://github.com/Keats/tera).
//!
//! ## The `template()` attribute
//!
//! Askama works by generating one or more trait implementations for any
//! `struct` type decorated with the `#[derive(Template)]` attribute. The
//! code generation process takes some options that can be specified through
//! the `template()` attribute. The following sub-attributes are currently
//! recognized:
//!
//! * `path` (as `path = "foo.html"`): sets the path to the template file. The
//!   path is interpreted as relative to the `templates` dir in the directory
//!   where the originating crate's `Cargo.toml` resides. The file name
//!   extension is used to infer an escape mode (see below). In web framework
//!   integrations, the path's extension may also be used to infer the content
//!   type of the resulting response. Cannot be used together with `source`.
//! * `source` (as `source = "{{ foo }}"`): directly sets the template source.
//!   This can be useful for test cases or short templates. The generated path
//!   is empty, which generally makes it impossible to refer to this template
//!   from other templates. Cannot be used together with `path`.
//! * `ext` (as `ext = "txt"`): lets you specify the content type as a file
//!   extension. This is used to infer an escape mode (see below), and some
//!   web framework integrations use it to determine the content type.
//!   Cannot be used together with `path`.
//! * `print` (as `print = "code"`): enable debugging by printing nothing
//!   (`none`), the parsed syntax tree (`ast`), the generated code (`code`)
//!   or `all` for both. The requested data will be printed to stdout at
//!   compile time.
//! * `escape` (as `escape = "none"`): set the escape mode for expression
//!   output; the currently implemented modes are `none` and `html`. Askama
//!   infers the escape mode from the template file name (with `path`) or
//!   specified extension (`ext`): if the extension is `html`, `htm` or `xml`,
//!   the `html` escape mode is used; otherwise, no implicit escaping is done.
//!   Setting an escape mode explicitly overrides the inferred value.
//!
//!
//! ## Variables
//!
//! Top-level template variables are defined by the template's context type.
//! You can use a dot (`.`) to access variable's attributes or methods.
//! Reading from variables is subject to the usual borrowing policies.
//! For example, `{{ name }}` will get the ``name`` field from the template
//! context,
//! while `{{ user.name }}` will get the ``name`` field of the ``user``
//! field from the template context.
//!
//! ## Filters
//!
//! Values such as those obtained from variables can be post-processed
//! using **filters**.
//! Filters are applied to values using the pipe symbol (`|`) and may
//! have optional extra arguments in parentheses.
//! Filters can be chained, in which case the output from one filter
//! is passed to the next.
//!
//! For example, `{{ "{:?}"|format(name|escape) }}` will escape HTML
//! characters from the value obtained by accessing the `name` field,
//! and print the resulting string as a Rust literal.
//!
//! Consult the [filters module documentation](filters/index.html) for a list
//! of available filters.
//!
//! ## Whitespace control
//!
//! Askama preserves all whitespace in template code by default,
//! except that a single trailing newline characters are suppressed.
//! However, whitespace before and after expression and block delimiters
//! can be suppressed by writing a minus sign directly following a
//! start delimiter or leading into an end delimiter.
//! Askama considers all tabs, spaces, newlines and carriage returns to be
//! whitespace.
//!
//! ## Template inheritance
//!
//! Template inheritance allows you to build a base template with common
//! elements that can then be shared by all inheriting templates.
//! A base template defines **blocks** that child templates can then override.
//!
//! ### Base template
//!
//! ```text
//! <!DOCTYPE html>
//! <html lang="en">
//!   <head>
//!     <title>{{ block title %}{{ title }}{% endblock %} - My Site</title>
//!     {% block head %}{% endblock %}
//!   </head>
//!   <body>
//!     <div id="content">
//!       {% block content %}{% endblock %}
//!     </div>
//!   </body>
//! </html>
//! ```
//!
//! The `block` tags define three blocks that can be filled in by child
//! templates. The base template defines a default version of the block.
//! A base template must define one or more blocks in order to be enable
//! inheritance. Blocks can only be specified at the top level of a template,
//! not inside `if`/`else` branches or in `for`-loop bodies.
//!
//! ### Child template
//!
//! Here's an example child template:
//!
//! ```text
//! {% extends "base.html" %}
//!
//! {% block title %}Index{% endblock %}
//!
//! {% block head %}
//!   <style>
//!   </style>
//! {% endblock %}
//!
//! {% block content %}
//!   <h1>Index</h1>
//!   <p>Hello, world!</p>
//! {% endblock %}
//! ```
//!
//! The `extends` tag tells the code generator that this template inherits
//! from another template. It will search for the base template relative to
//! itself before looking relative to the template base directory. It will
//! render the top-level content from the base template, and substitute
//! blocks from the base template with those from the child template. The
//! inheriting template context `struct` must have a field called `_parent` of
//! the type used as the base template context. Blocks can refer to the context
//! of both parent and child template.
//!
//! Note that, if the base template lives in another module than the child
//! template, the child template's module should import all symbols from the
//! base template's module in order for it to find the trait definition that
//! supports the inheritance mechanism.
//!
//! ## HTML escaping
//!
//! Askama by default escapes variables if it thinks it is rendering HTML
//! content. It infers the escaping context from the extension of template
//! filenames, escaping by default if the extension is one of `html`, `htm`,
//! or `xml`. When specifying a template as `source` in an attribute, the
//! `ext` attribute parameter must be used to specify a type. Additionally,
//! you can specify an escape mode explicitly for your template by setting
//! the `escape` attribute parameter value (to `none` or `html`).
//!
//! Askama escapes `<`, `>`, `&`, `"`, `'`, `\` and `/`, according to the
//! [OWASP escaping recommendations][owasp]. Use the `safe` filter to
//! prevent escaping for a single expression, or the `escape` (or `e`)
//! filter to escape a single expression in an unescaped context.
//!
//! [owasp]: https://www.owasp.org/index.php/XSS_(Cross_Site_Scripting)_Prevention_Cheat_Sheet#RULE_.231_-_HTML_Escape_Before_Inserting_Untrusted_Data_into_HTML_Element_Content
//!
//! ## Control structures
//!
//! ### For
//!
//! Loop over each item in an iterator. For example:
//!
//! ```text
//! <h1>Users</h1>
//! <ul>
//! {% for user in users %}
//!   <li>{{ user.name|e }}</li>
//! {% endfor %}
//! </ul>
//! ```
//!
//! Inside for-loop blocks, some useful variables are accessible:
//!
//! * *loop.index*: current loop iteration (starting from 1)
//! * *loop.index0*: current loop iteration (starting from 0)
//!
//! ### If
//!
//! The *if* statement is used as you might expect:
//!
//! ```text
//! {% if users.len() == 0 %}
//!   No users
//! {% else if users.len() == 1 %}
//!   1 user
//! {% else %}
//!   {{ users.len() }} users
//! {% endif %}
//! ```
//!
//! ### Include
//!
//! The *include* statement lets you split large or repetitive blocks into
//! separate template files. Included templates get full access to the context
//! in which they're used, including local variables like those from loops:
//!
//! ```text
//! {% for i in iter %}
//!   {% include "item.html" %}
//! {% endfor %}
//! ```
//!
//! ```text
//! * Item: {{ i }}
//! ```
//!
//! The path to include must be a string literal, so that it is known at
//! compile time. Askama will try to find the specified template relative
//! to the including template's path before falling back to the absolute
//! template path. Use `include` within the branches of an `if`/`else`
//! block to use includes more dynamically.
//!
//! ## Expressions
//!
//! Askama supports string literals (`"foo"`) and integer literals (`1`).
//! It supports almost all binary operators that Rust supports,
//! including arithmetic, comparison and logic operators.
//! The parser applies the same precedence order as the Rust compiler.
//! Expressions can be grouped using parentheses.
//! The HTML special characters `&`, `<` and `>` will be replaced with their
//! character entities unless the `escape` mode is disabled for a template.
//!
//! # Optional functionality
//!
//! ## Rocket integration
//!
//! Enabling the `with-rocket` feature appends an implementation of Rocket's
//! `Responder` trait for each template type. This makes it easy to trivially
//! return a value of that type in a Rocket handler. See
//! [the example](https://github.com/djc/askama/blob/master/testing/tests/rocket.rs)
//! from the Askama test suite for more on how to integrate.
//!
//! In case a run-time error occurs during templating, a `500 Internal Server
//! Error` `Status` value will be returned, so that this can be further
//! handled by your error catcher.
//!
//! ## Iron integration
//!
//! Enabling the `with-iron` feature appends an implementation of Iron's
//! `Modifier<Response>` trait for each template type. This makes it easy to
//! trivially return a value of that type in an Iron handler. See
//! [the example](https://github.com/djc/askama/blob/master/testing/tests/iron.rs)
//! from the Askama test suite for more on how to integrate.
//!
//! Note that Askama's generated `Modifier<Response>` implementation currently
//! unwraps any run-time errors from the template. If you have a better
//! suggestion, please [file an issue](https://github.com/djc/askama/issues/new).
//!
//! ## The `json` filter
//!
//! Enabling the `serde-json` filter will enable the use of the `json` filter.
//! This will output formatted JSON for any value that implements the required
//! `Serialize` trait.

#![allow(unused_imports)]
#[macro_use]
extern crate askama_derive;
extern crate askama_shared as shared;

use shared::path;

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

/// Main `Template` trait; implementations are generally derived
pub trait Template {
    /// Renders the template to the given `writer` buffer
    fn render_into(&self, writer: &mut std::fmt::Write) -> Result<()>;
    /// Helper method which allocates a new `String` and renders into it
    fn render(&self) -> Result<String> {
        let mut buf = String::new();
        self.render_into(&mut buf)?;
        Ok(buf)
    }
}

pub use shared::filters;
pub use askama_derive::*;
pub use shared::{Error, MarkupDisplay, Result};

#[cfg(feature = "with-iron")]
pub mod iron {
    extern crate iron;
    pub use self::iron::modifier::Modifier;
    pub use self::iron::response::Response;
    pub use self::iron::headers::ContentType;
}

#[cfg(feature = "with-rocket")]
pub mod rocket {
    extern crate rocket;

    use self::rocket::http::{ContentType, Status};
    pub use self::rocket::request::Request;
    use self::rocket::response::Response;
    use std::io::Cursor;

    pub use self::rocket::response::{Responder, Result};

    pub fn respond(t: &super::Template, ext: &str) -> Result<'static> {
        let rsp = t.render().map_err(|_| Status::InternalServerError)?;
        let ctype = ContentType::from_extension(ext).ok_or(Status::InternalServerError)?;
        Response::build()
            .header(ctype)
            .sized_body(Cursor::new(rsp))
            .ok()
    }
}

fn visit_dirs(dir: &Path, cb: &Fn(&DirEntry)) -> io::Result<()> {
    if dir.is_dir() {
        for entry in fs::read_dir(dir)? {
            let entry = entry?;
            let path = entry.path();
            if path.is_dir() {
                visit_dirs(&path, cb)?;
            } else {
                cb(&entry);
            }
        }
    }
    Ok(())
}

/// Build script helper to rebuild crates if contained templates have changed
///
/// Iterates over all files in the template dir (`templates` in
/// `CARGO_MANIFEST_DIR`) and writes a `cargo:rerun-if-changed=` line for each
/// of them to stdout.
///
/// This helper method can be used in build scripts (`build.rs`) in crates
/// that have templates, to make sure the crate gets rebuilt when template
/// source code changes.
pub fn rerun_if_templates_changed() {
    visit_dirs(&path::template_dir(), &|e: &DirEntry| {
        println!("cargo:rerun-if-changed={}", e.path().to_str().unwrap());
    }).unwrap();
}
<commit_msg>Write up some documentation for match blocks<commit_after>//! Askama implements a type-safe compiler for Jinja-like templates.
//! It lets you write templates in a Jinja-like syntax,
//! which are linked to a `struct` defining the template context.
//! This is done using a custom derive implementation (implemented
//! in [`askama_derive`](https://crates.io/crates/askama_derive)).
//!
//! For feature highlights and a quick start, please review the
//! [README](https://github.com/djc/askama/blob/master/README.md).
//!
//! # Creating Askama templates
//!
//! An Askama template is a `struct` definition which provides the template
//! context combined with a UTF-8 encoded text file (or inline source, see
//! below). Askama can be used to generate any kind of text-based format.
//! The template file's extension may can be used to provide content type hints.
//!
//! A template consists of **text contents**, which are passed through as-is,
//! **expressions**, which get replaced with content while being rendered, and
//! **tags**, which control the template's logic.
//! The template syntax is very similar to [Jinja](http://jinja.pocoo.org/),
//! as well as Jinja-derivatives like [Twig](http://twig.sensiolabs.org/) or
//! [Tera](https://github.com/Keats/tera).
//!
//! ## The `template()` attribute
//!
//! Askama works by generating one or more trait implementations for any
//! `struct` type decorated with the `#[derive(Template)]` attribute. The
//! code generation process takes some options that can be specified through
//! the `template()` attribute. The following sub-attributes are currently
//! recognized:
//!
//! * `path` (as `path = "foo.html"`): sets the path to the template file. The
//!   path is interpreted as relative to the `templates` dir in the directory
//!   where the originating crate's `Cargo.toml` resides. The file name
//!   extension is used to infer an escape mode (see below). In web framework
//!   integrations, the path's extension may also be used to infer the content
//!   type of the resulting response. Cannot be used together with `source`.
//! * `source` (as `source = "{{ foo }}"`): directly sets the template source.
//!   This can be useful for test cases or short templates. The generated path
//!   is empty, which generally makes it impossible to refer to this template
//!   from other templates. Cannot be used together with `path`.
//! * `ext` (as `ext = "txt"`): lets you specify the content type as a file
//!   extension. This is used to infer an escape mode (see below), and some
//!   web framework integrations use it to determine the content type.
//!   Cannot be used together with `path`.
//! * `print` (as `print = "code"`): enable debugging by printing nothing
//!   (`none`), the parsed syntax tree (`ast`), the generated code (`code`)
//!   or `all` for both. The requested data will be printed to stdout at
//!   compile time.
//! * `escape` (as `escape = "none"`): set the escape mode for expression
//!   output; the currently implemented modes are `none` and `html`. Askama
//!   infers the escape mode from the template file name (with `path`) or
//!   specified extension (`ext`): if the extension is `html`, `htm` or `xml`,
//!   the `html` escape mode is used; otherwise, no implicit escaping is done.
//!   Setting an escape mode explicitly overrides the inferred value.
//!
//!
//! ## Variables
//!
//! Top-level template variables are defined by the template's context type.
//! You can use a dot (`.`) to access variable's attributes or methods.
//! Reading from variables is subject to the usual borrowing policies.
//! For example, `{{ name }}` will get the ``name`` field from the template
//! context,
//! while `{{ user.name }}` will get the ``name`` field of the ``user``
//! field from the template context.
//!
//! ## Filters
//!
//! Values such as those obtained from variables can be post-processed
//! using **filters**.
//! Filters are applied to values using the pipe symbol (`|`) and may
//! have optional extra arguments in parentheses.
//! Filters can be chained, in which case the output from one filter
//! is passed to the next.
//!
//! For example, `{{ "{:?}"|format(name|escape) }}` will escape HTML
//! characters from the value obtained by accessing the `name` field,
//! and print the resulting string as a Rust literal.
//!
//! Consult the [filters module documentation](filters/index.html) for a list
//! of available filters.
//!
//! ## Whitespace control
//!
//! Askama preserves all whitespace in template code by default,
//! except that a single trailing newline characters are suppressed.
//! However, whitespace before and after expression and block delimiters
//! can be suppressed by writing a minus sign directly following a
//! start delimiter or leading into an end delimiter.
//! Askama considers all tabs, spaces, newlines and carriage returns to be
//! whitespace.
//!
//! ## Template inheritance
//!
//! Template inheritance allows you to build a base template with common
//! elements that can then be shared by all inheriting templates.
//! A base template defines **blocks** that child templates can then override.
//!
//! ### Base template
//!
//! ```text
//! <!DOCTYPE html>
//! <html lang="en">
//!   <head>
//!     <title>{{ block title %}{{ title }}{% endblock %} - My Site</title>
//!     {% block head %}{% endblock %}
//!   </head>
//!   <body>
//!     <div id="content">
//!       {% block content %}{% endblock %}
//!     </div>
//!   </body>
//! </html>
//! ```
//!
//! The `block` tags define three blocks that can be filled in by child
//! templates. The base template defines a default version of the block.
//! A base template must define one or more blocks in order to be enable
//! inheritance. Blocks can only be specified at the top level of a template,
//! not inside `if`/`else` branches or in `for`-loop bodies.
//!
//! ### Child template
//!
//! Here's an example child template:
//!
//! ```text
//! {% extends "base.html" %}
//!
//! {% block title %}Index{% endblock %}
//!
//! {% block head %}
//!   <style>
//!   </style>
//! {% endblock %}
//!
//! {% block content %}
//!   <h1>Index</h1>
//!   <p>Hello, world!</p>
//! {% endblock %}
//! ```
//!
//! The `extends` tag tells the code generator that this template inherits
//! from another template. It will search for the base template relative to
//! itself before looking relative to the template base directory. It will
//! render the top-level content from the base template, and substitute
//! blocks from the base template with those from the child template. The
//! inheriting template context `struct` must have a field called `_parent` of
//! the type used as the base template context. Blocks can refer to the context
//! of both parent and child template.
//!
//! Note that, if the base template lives in another module than the child
//! template, the child template's module should import all symbols from the
//! base template's module in order for it to find the trait definition that
//! supports the inheritance mechanism.
//!
//! ## HTML escaping
//!
//! Askama by default escapes variables if it thinks it is rendering HTML
//! content. It infers the escaping context from the extension of template
//! filenames, escaping by default if the extension is one of `html`, `htm`,
//! or `xml`. When specifying a template as `source` in an attribute, the
//! `ext` attribute parameter must be used to specify a type. Additionally,
//! you can specify an escape mode explicitly for your template by setting
//! the `escape` attribute parameter value (to `none` or `html`).
//!
//! Askama escapes `<`, `>`, `&`, `"`, `'`, `\` and `/`, according to the
//! [OWASP escaping recommendations][owasp]. Use the `safe` filter to
//! prevent escaping for a single expression, or the `escape` (or `e`)
//! filter to escape a single expression in an unescaped context.
//!
//! [owasp]: https://www.owasp.org/index.php/XSS_(Cross_Site_Scripting)_Prevention_Cheat_Sheet#RULE_.231_-_HTML_Escape_Before_Inserting_Untrusted_Data_into_HTML_Element_Content
//!
//! ## Control structures
//!
//! ### For
//!
//! Loop over each item in an iterator. For example:
//!
//! ```text
//! <h1>Users</h1>
//! <ul>
//! {% for user in users %}
//!   <li>{{ user.name|e }}</li>
//! {% endfor %}
//! </ul>
//! ```
//!
//! Inside for-loop blocks, some useful variables are accessible:
//!
//! * *loop.index*: current loop iteration (starting from 1)
//! * *loop.index0*: current loop iteration (starting from 0)
//!
//! ### If
//!
//! The *if* statement is used as you might expect:
//!
//! ```text
//! {% if users.len() == 0 %}
//!   No users
//! {% else if users.len() == 1 %}
//!   1 user
//! {% else %}
//!   {{ users.len() }} users
//! {% endif %}
//! ```
//!
//! ### Match
//!
//! In order to deal with Rust `enum`s in a type-safe way, templates support
//! match blocks from version 0.6. Here is a simple example showing how to
//! expand an `Option`:
//!
//! ```text
//! {% match item %}
//!   {% when Some with ("foo") %}
//!     Found literal foo
//!   {% when Some with (val) %}
//!     Found {{ val }}
//!   {% when None %}
//! {% endmatch %}
//! ```
//!
//! That is, a `match` block can optionally contain some whitespace (but
//! no other literal content), followed by a number of `when` blocks and
//! and an optional `else` block. Each `when` block must name a list of
//! matches (`(val)`), optionally introduced with a variant name. The
//! `else` block is equivalent to matching on `_` (matching anything).
//!
//! ### Include
//!
//! The *include* statement lets you split large or repetitive blocks into
//! separate template files. Included templates get full access to the context
//! in which they're used, including local variables like those from loops:
//!
//! ```text
//! {% for i in iter %}
//!   {% include "item.html" %}
//! {% endfor %}
//! ```
//!
//! ```text
//! * Item: {{ i }}
//! ```
//!
//! The path to include must be a string literal, so that it is known at
//! compile time. Askama will try to find the specified template relative
//! to the including template's path before falling back to the absolute
//! template path. Use `include` within the branches of an `if`/`else`
//! block to use includes more dynamically.
//!
//! ## Expressions
//!
//! Askama supports string literals (`"foo"`) and integer literals (`1`).
//! It supports almost all binary operators that Rust supports,
//! including arithmetic, comparison and logic operators.
//! The parser applies the same precedence order as the Rust compiler.
//! Expressions can be grouped using parentheses.
//! The HTML special characters `&`, `<` and `>` will be replaced with their
//! character entities unless the `escape` mode is disabled for a template.
//!
//! # Optional functionality
//!
//! ## Rocket integration
//!
//! Enabling the `with-rocket` feature appends an implementation of Rocket's
//! `Responder` trait for each template type. This makes it easy to trivially
//! return a value of that type in a Rocket handler. See
//! [the example](https://github.com/djc/askama/blob/master/testing/tests/rocket.rs)
//! from the Askama test suite for more on how to integrate.
//!
//! In case a run-time error occurs during templating, a `500 Internal Server
//! Error` `Status` value will be returned, so that this can be further
//! handled by your error catcher.
//!
//! ## Iron integration
//!
//! Enabling the `with-iron` feature appends an implementation of Iron's
//! `Modifier<Response>` trait for each template type. This makes it easy to
//! trivially return a value of that type in an Iron handler. See
//! [the example](https://github.com/djc/askama/blob/master/testing/tests/iron.rs)
//! from the Askama test suite for more on how to integrate.
//!
//! Note that Askama's generated `Modifier<Response>` implementation currently
//! unwraps any run-time errors from the template. If you have a better
//! suggestion, please [file an issue](https://github.com/djc/askama/issues/new).
//!
//! ## The `json` filter
//!
//! Enabling the `serde-json` filter will enable the use of the `json` filter.
//! This will output formatted JSON for any value that implements the required
//! `Serialize` trait.

#![allow(unused_imports)]
#[macro_use]
extern crate askama_derive;
extern crate askama_shared as shared;

use shared::path;

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

/// Main `Template` trait; implementations are generally derived
pub trait Template {
    /// Renders the template to the given `writer` buffer
    fn render_into(&self, writer: &mut std::fmt::Write) -> Result<()>;
    /// Helper method which allocates a new `String` and renders into it
    fn render(&self) -> Result<String> {
        let mut buf = String::new();
        self.render_into(&mut buf)?;
        Ok(buf)
    }
}

pub use shared::filters;
pub use askama_derive::*;
pub use shared::{Error, MarkupDisplay, Result};

#[cfg(feature = "with-iron")]
pub mod iron {
    extern crate iron;
    pub use self::iron::modifier::Modifier;
    pub use self::iron::response::Response;
    pub use self::iron::headers::ContentType;
}

#[cfg(feature = "with-rocket")]
pub mod rocket {
    extern crate rocket;

    use self::rocket::http::{ContentType, Status};
    pub use self::rocket::request::Request;
    use self::rocket::response::Response;
    use std::io::Cursor;

    pub use self::rocket::response::{Responder, Result};

    pub fn respond(t: &super::Template, ext: &str) -> Result<'static> {
        let rsp = t.render().map_err(|_| Status::InternalServerError)?;
        let ctype = ContentType::from_extension(ext).ok_or(Status::InternalServerError)?;
        Response::build()
            .header(ctype)
            .sized_body(Cursor::new(rsp))
            .ok()
    }
}

fn visit_dirs(dir: &Path, cb: &Fn(&DirEntry)) -> io::Result<()> {
    if dir.is_dir() {
        for entry in fs::read_dir(dir)? {
            let entry = entry?;
            let path = entry.path();
            if path.is_dir() {
                visit_dirs(&path, cb)?;
            } else {
                cb(&entry);
            }
        }
    }
    Ok(())
}

/// Build script helper to rebuild crates if contained templates have changed
///
/// Iterates over all files in the template dir (`templates` in
/// `CARGO_MANIFEST_DIR`) and writes a `cargo:rerun-if-changed=` line for each
/// of them to stdout.
///
/// This helper method can be used in build scripts (`build.rs`) in crates
/// that have templates, to make sure the crate gets rebuilt when template
/// source code changes.
pub fn rerun_if_templates_changed() {
    visit_dirs(&path::template_dir(), &|e: &DirEntry| {
        println!("cargo:rerun-if-changed={}", e.path().to_str().unwrap());
    }).unwrap();
}
<|endoftext|>


<commit_before>use std::cmp::{min, max};
use std::io::Write;
use serde_json::{to_writer, Result};
use {Timer, TimeSpan, analysis};
use analysis::split_color;
use time_formatter::{Delta, Regular, TimeFormatter};
use time_formatter::none_wrapper::{EmptyWrapper, DashWrapper};
use Color;
use std::borrow::Cow;
use layout::editor::settings_description::{SettingsDescription, Field, Value};

#[derive(Default, Clone)]
pub struct Component {
    icon_ids: Vec<usize>,
    settings: Settings,
    scroll_offset: isize,
}

#[derive(Clone, Serialize, Deserialize)]
pub struct Settings {
    pub visual_split_count: usize,
    pub split_preview_count: usize,
    pub always_show_last_split: bool,
    pub separator_last_split: bool,
}

#[derive(Serialize, Deserialize)]
pub struct SplitState {
    pub icon_change: Option<String>,
    pub name: String,
    pub delta: String,
    pub time: String,
    pub color: Color,
    pub is_current_split: bool,
}

#[derive(Serialize, Deserialize)]
pub struct State {
    pub splits: Vec<SplitState>,
    pub show_final_separator: bool,
}

impl Default for Settings {
    fn default() -> Self {
        Settings {
            visual_split_count: 16,
            split_preview_count: 1,
            always_show_last_split: true,
            separator_last_split: true,
        }
    }
}

impl State {
    pub fn write_json<W>(&self, writer: W) -> Result<()>
    where
        W: Write,
    {
        to_writer(writer, self)
    }
}

impl Component {
    pub fn new() -> Self {
        Default::default()
    }

    pub fn with_settings(settings: Settings) -> Self {
        Self {
            settings,
            ..Default::default()
        }
    }

    pub fn settings(&self) -> &Settings {
        &self.settings
    }

    pub fn settings_mut(&mut self) -> &mut Settings {
        &mut self.settings
    }

    pub fn scroll_up(&mut self) {
        self.scroll_offset = self.scroll_offset.saturating_sub(1);
    }

    pub fn scroll_down(&mut self) {
        self.scroll_offset = self.scroll_offset.saturating_add(1);
    }

    pub fn remount(&mut self) {
        self.icon_ids.clear();
    }

    pub fn name(&self) -> Cow<str> {
        "Splits".into()
    }

    pub fn state(&mut self, timer: &Timer) -> State {
        self.icon_ids.resize(timer.run().len(), 0);

        let current_split = timer.current_split_index();
        let method = timer.current_timing_method();
        let comparison = timer.current_comparison();

        let always_show_last_split = if self.settings.always_show_last_split {
            0
        } else {
            1
        };
        let skip_count = min(
            max(
                0,
                current_split as isize -
                    (self.settings.visual_split_count as isize - 2 -
                         self.settings.split_preview_count as isize +
                         always_show_last_split),
            ),
            timer.run().len() as isize - self.settings.visual_split_count as isize,
        );
        self.scroll_offset = min(
            max(self.scroll_offset, -skip_count),
            timer.run().len() as isize - skip_count - self.settings.visual_split_count as isize,
        );
        let skip_count = max(0, skip_count + self.scroll_offset) as usize;
        let take_count = self.settings.visual_split_count + always_show_last_split as usize - 1;
        let always_show_last_split = self.settings.always_show_last_split;

        let show_final_separator = self.settings.separator_last_split && always_show_last_split &&
            skip_count + take_count + 1 < timer.run().len();

        State {
            splits: timer
                .run()
                .segments()
                .iter()
                .enumerate()
                .skip(skip_count)
                .zip(self.icon_ids.iter_mut())
                .filter(|&((i, _), _)| {
                    i - skip_count < take_count ||
                        (always_show_last_split && i + 1 == timer.run().len())
                })
                .map(|((i, segment), icon_id)| {
                    let split = segment.split_time()[method];
                    let comparison_time = segment.comparison(comparison)[method];

                    let (time, delta, color) = if current_split > i as isize {
                        let delta = TimeSpan::option_sub(split, comparison_time);
                        (
                            split,
                            delta,
                            split_color(timer, delta, i, true, true, comparison, method),
                        )
                    } else if current_split == i as isize {
                        (
                            comparison_time,
                            analysis::check_live_delta(timer, true, comparison, method),
                            Color::Default,
                        )
                    } else {
                        (comparison_time, None, Color::Default)
                    };

                    let delta = if current_split > i as isize {
                        DashWrapper::new(Delta::with_decimal_dropping())
                            .format(delta)
                            .to_string()
                    } else {
                        EmptyWrapper::new(Delta::with_decimal_dropping())
                            .format(delta)
                            .to_string()
                    };

                    SplitState {
                        icon_change: segment.icon().check_for_change(icon_id).map(str::to_owned),
                        name: segment.name().to_string(),
                        delta: delta,
                        time: Regular::new().format(time).to_string(),
                        color: color,
                        is_current_split: i as isize == current_split,
                    }
                })
                .collect(),
            show_final_separator: show_final_separator,
        }
    }

    pub fn settings_description(&self) -> SettingsDescription {
        SettingsDescription::with_fields(vec![
            Field::new(
                "Total Splits".into(),
                Value::UInt(self.settings.visual_split_count as _),
            ),
            Field::new(
                "Upcoming Splits".into(),
                Value::UInt(self.settings.split_preview_count as _),
            ),
            Field::new(
                "Always Show Last Split".into(),
                self.settings.always_show_last_split.into(),
            ),
            Field::new(
                "Show Separator Before Last Split".into(),
                self.settings.separator_last_split.into(),
            ),
        ])
    }

    pub fn set_value(&mut self, index: usize, value: Value) {
        match index {
            0 => self.settings.visual_split_count = value.into_uint().unwrap() as _,
            1 => self.settings.split_preview_count = value.into_uint().unwrap() as _,
            2 => self.settings.always_show_last_split = value.into(),
            3 => self.settings.separator_last_split = value.into(),
            _ => panic!("Unsupported Setting Index"),
        }
    }
}
<commit_msg>Reset Scroll Offset when scrolled through other means<commit_after>use std::cmp::{min, max};
use std::io::Write;
use serde_json::{to_writer, Result};
use {Timer, TimeSpan, analysis};
use analysis::split_color;
use time_formatter::{Delta, Regular, TimeFormatter};
use time_formatter::none_wrapper::{EmptyWrapper, DashWrapper};
use Color;
use std::borrow::Cow;
use layout::editor::settings_description::{SettingsDescription, Field, Value};

#[derive(Default, Clone)]
pub struct Component {
    icon_ids: Vec<usize>,
    settings: Settings,
    current_split_index: isize,
    scroll_offset: isize,
}

#[derive(Clone, Serialize, Deserialize)]
pub struct Settings {
    pub visual_split_count: usize,
    pub split_preview_count: usize,
    pub always_show_last_split: bool,
    pub separator_last_split: bool,
}

#[derive(Serialize, Deserialize)]
pub struct SplitState {
    pub icon_change: Option<String>,
    pub name: String,
    pub delta: String,
    pub time: String,
    pub color: Color,
    pub is_current_split: bool,
}

#[derive(Serialize, Deserialize)]
pub struct State {
    pub splits: Vec<SplitState>,
    pub show_final_separator: bool,
}

impl Default for Settings {
    fn default() -> Self {
        Settings {
            visual_split_count: 16,
            split_preview_count: 1,
            always_show_last_split: true,
            separator_last_split: true,
        }
    }
}

impl State {
    pub fn write_json<W>(&self, writer: W) -> Result<()>
    where
        W: Write,
    {
        to_writer(writer, self)
    }
}

impl Component {
    pub fn new() -> Self {
        Default::default()
    }

    pub fn with_settings(settings: Settings) -> Self {
        Self {
            settings,
            ..Default::default()
        }
    }

    pub fn settings(&self) -> &Settings {
        &self.settings
    }

    pub fn settings_mut(&mut self) -> &mut Settings {
        &mut self.settings
    }

    pub fn scroll_up(&mut self) {
        self.scroll_offset = self.scroll_offset.saturating_sub(1);
    }

    pub fn scroll_down(&mut self) {
        self.scroll_offset = self.scroll_offset.saturating_add(1);
    }

    pub fn remount(&mut self) {
        self.icon_ids.clear();
    }

    pub fn name(&self) -> Cow<str> {
        "Splits".into()
    }

    pub fn state(&mut self, timer: &Timer) -> State {
        // Reset Scroll Offset when any movement of the split index is observed.
        if self.current_split_index != timer.current_split_index() {
            self.current_split_index = timer.current_split_index();
            self.scroll_offset = 0;
        }

        self.icon_ids.resize(timer.run().len(), 0);

        let current_split = timer.current_split_index();
        let method = timer.current_timing_method();
        let comparison = timer.current_comparison();

        let always_show_last_split = if self.settings.always_show_last_split {
            0
        } else {
            1
        };
        let skip_count = min(
            max(
                0,
                current_split as isize -
                    (self.settings.visual_split_count as isize - 2 -
                         self.settings.split_preview_count as isize +
                         always_show_last_split),
            ),
            timer.run().len() as isize - self.settings.visual_split_count as isize,
        );
        self.scroll_offset = min(
            max(self.scroll_offset, -skip_count),
            timer.run().len() as isize - skip_count - self.settings.visual_split_count as isize,
        );
        let skip_count = max(0, skip_count + self.scroll_offset) as usize;
        let take_count = self.settings.visual_split_count + always_show_last_split as usize - 1;
        let always_show_last_split = self.settings.always_show_last_split;

        let show_final_separator = self.settings.separator_last_split && always_show_last_split &&
            skip_count + take_count + 1 < timer.run().len();

        State {
            splits: timer
                .run()
                .segments()
                .iter()
                .enumerate()
                .skip(skip_count)
                .zip(self.icon_ids.iter_mut())
                .filter(|&((i, _), _)| {
                    i - skip_count < take_count ||
                        (always_show_last_split && i + 1 == timer.run().len())
                })
                .map(|((i, segment), icon_id)| {
                    let split = segment.split_time()[method];
                    let comparison_time = segment.comparison(comparison)[method];

                    let (time, delta, color) = if current_split > i as isize {
                        let delta = TimeSpan::option_sub(split, comparison_time);
                        (
                            split,
                            delta,
                            split_color(timer, delta, i, true, true, comparison, method),
                        )
                    } else if current_split == i as isize {
                        (
                            comparison_time,
                            analysis::check_live_delta(timer, true, comparison, method),
                            Color::Default,
                        )
                    } else {
                        (comparison_time, None, Color::Default)
                    };

                    let delta = if current_split > i as isize {
                        DashWrapper::new(Delta::with_decimal_dropping())
                            .format(delta)
                            .to_string()
                    } else {
                        EmptyWrapper::new(Delta::with_decimal_dropping())
                            .format(delta)
                            .to_string()
                    };

                    SplitState {
                        icon_change: segment.icon().check_for_change(icon_id).map(str::to_owned),
                        name: segment.name().to_string(),
                        delta: delta,
                        time: Regular::new().format(time).to_string(),
                        color: color,
                        is_current_split: i as isize == current_split,
                    }
                })
                .collect(),
            show_final_separator: show_final_separator,
        }
    }

    pub fn settings_description(&self) -> SettingsDescription {
        SettingsDescription::with_fields(vec![
            Field::new(
                "Total Splits".into(),
                Value::UInt(self.settings.visual_split_count as _),
            ),
            Field::new(
                "Upcoming Splits".into(),
                Value::UInt(self.settings.split_preview_count as _),
            ),
            Field::new(
                "Always Show Last Split".into(),
                self.settings.always_show_last_split.into(),
            ),
            Field::new(
                "Show Separator Before Last Split".into(),
                self.settings.separator_last_split.into(),
            ),
        ])
    }

    pub fn set_value(&mut self, index: usize, value: Value) {
        match index {
            0 => self.settings.visual_split_count = value.into_uint().unwrap() as _,
            1 => self.settings.split_preview_count = value.into_uint().unwrap() as _,
            2 => self.settings.always_show_last_split = value.into(),
            3 => self.settings.separator_last_split = value.into(),
            _ => panic!("Unsupported Setting Index"),
        }
    }
}
<|endoftext|>


<commit_before>use super::*;

#[derive(Debug, Clone, Eq, PartialEq)]
pub struct Crate {
    shebang: Option<String>,
    attrs: Vec<Attribute>,
    items: Vec<Item>,
}

#[cfg(feature = "parsing")]
pub mod parsing {
    use super::*;
    use attr::parsing::inner_attr;
    use space::whitespace;
    use item::parsing::item;

    named!(pub krate -> Crate, do_parse!(
        attrs: many0!(inner_attr) >>
        items: many0!(item) >>
        option!(whitespace) >>
        (Crate {
            shebang: None,
            attrs: attrs,
            items: items,
        })
    ));
}

#[cfg(feature = "printing")]
mod printing {
    use super::*;
    use attr::FilterAttrs;
    use quote::{Tokens, ToTokens};

    impl ToTokens for Crate {
        fn to_tokens(&self, tokens: &mut Tokens) {
            for attr in self.attrs.inner() {
                attr.to_tokens(tokens);
            }
            for item in &self.items {
                item.to_tokens(tokens);
            }
        }
    }
}
<commit_msg>Public fields for Crate struct<commit_after>use super::*;

#[derive(Debug, Clone, Eq, PartialEq)]
pub struct Crate {
    pub shebang: Option<String>,
    pub attrs: Vec<Attribute>,
    pub items: Vec<Item>,
}

#[cfg(feature = "parsing")]
pub mod parsing {
    use super::*;
    use attr::parsing::inner_attr;
    use space::whitespace;
    use item::parsing::item;

    named!(pub krate -> Crate, do_parse!(
        attrs: many0!(inner_attr) >>
        items: many0!(item) >>
        option!(whitespace) >>
        (Crate {
            shebang: None,
            attrs: attrs,
            items: items,
        })
    ));
}

#[cfg(feature = "printing")]
mod printing {
    use super::*;
    use attr::FilterAttrs;
    use quote::{Tokens, ToTokens};

    impl ToTokens for Crate {
        fn to_tokens(&self, tokens: &mut Tokens) {
            for attr in self.attrs.inner() {
                attr.to_tokens(tokens);
            }
            for item in &self.items {
                item.to_tokens(tokens);
            }
        }
    }
}
<|endoftext|>


<commit_before>use tile::Tile;
use util::Grid;
use std::ops::{Index};
use na::Vec2;

pub struct Level {
    tiles: Grid<Option<Tile>>,
}

impl Level {
    pub fn new(width: usize, height: usize) -> Level {
        Level{
            tiles: Grid::new_filled_with(Some(Tile::Void) ,width, height),
        }
    }

    pub fn new_filled_with(tile: Option<Tile>, width: usize, height: usize) -> Level {
        Level{
            tiles: Grid::new_filled_with(tile, width, height),
        }
    }

    pub fn fill_with(&mut self, tile: Tile) {
        let width = self.tiles.get_width();
        let height = self.tiles.get_height();

        for x in 0..width {
            for y in 0..height {
                self.tiles[(x, y)] = Some(tile.clone());
            }
        }
    }

    pub fn get_width(&self) -> usize {
        self.tiles.get_width()
    }

    pub fn get_height(&self) -> usize {
        self.tiles.get_height()
    }

    pub fn get_mut_tile(&mut self, x: usize, y: usize) -> Option<&mut Tile> {
        if x < self.get_width() && y < self.get_height() {
            self.tiles[(x, y)].as_mut()
        } else {
            None
        }
    }

    pub fn get_mut_tile_with_vec(&mut self, pos: &Vec2<usize>) -> Option<&mut Tile> {
        self.get_mut_tile(pos.x, pos.y)
    }

    pub fn apply<F>(&mut self, gen: F) -> &mut Level where F: FnOnce(&mut Level) {
        gen(self);
        self
    }

}

static NONE: Option<Tile> = None;

impl Index<(usize, usize)> for Level {
    type Output= Option<Tile>;

    fn index(&self, (x, y): (usize, usize)) -> &Option<Tile>{
        if x < self.get_width() && y < self.get_height() {
            &self.tiles[(x, y)]
        } else {
            &NONE
        }
    }
}

impl Index<Vec2<usize>> for Level {
    type Output= Option<Tile>;

    fn index(&self, vec: Vec2<usize>) -> &Option<Tile>{
        if vec.x < self.get_width() && vec.y < self.get_height() {
            &self.tiles[(vec.x, vec.y)]
        } else {
            &NONE
        }
    }
}

pub fn fill_dead_ends(level: &mut Level) -> bool {
    let mut deadends = Vec::new();
    for y in 0..level.get_height() {
        for x in 0..level.get_width() {
            if let Some(ref n) = level[(x,y)] {
                if let &Tile::Floor = n {
                    if is_deadend(level, x, y) {
                        deadends.push((x,y));
                    }
                }
            }
        }
    }

    for (x,y) in deadends {
        if let Some(tile) = level.get_mut_tile(x, y) {
            *tile = Tile::Floor;
        }
    }
    deadends.is_empty()
}

pub fn is_deadend(level: &Level, x: usize, y: usize) -> bool {
    use util::Direction;
    use std::num;
    let mut paths = 0;
    for dir in Direction::get_orthogonal_dirs() {
        let vector = dir.get_vec();
        let coord = match (add_isize_to_usize(vector.x, x), add_isize_to_usize(vector.y, y)) {
            (Some(x), Some(y)) => (x,y),
            _ => continue,
        };

        if let Some(Tile::Floor) = level[coord] {
            paths += 1;
        }
	}
    paths < 2
}

fn add_isize_to_usize(i: isize, mut u: usize,) -> Option<usize> {
    if i < 0 && u != 0 {
        u -= (-i) as usize;
    } else if i >= 0 && u < usize::max_value() {
        u += i as usize;
    } else {
        return None
    }
    Some(u)
}
<commit_msg>dungeon generation seems to be done<commit_after>use tile::Tile;
use util::Grid;
use std::ops::{Index};
use na::Vec2;

pub struct Level {
    tiles: Grid<Option<Tile>>,
}

impl Level {
    pub fn new(width: usize, height: usize) -> Level {
        Level{
            tiles: Grid::new_filled_with(Some(Tile::Void) ,width, height),
        }
    }

    pub fn new_filled_with(tile: Option<Tile>, width: usize, height: usize) -> Level {
        Level{
            tiles: Grid::new_filled_with(tile, width, height),
        }
    }

    pub fn fill_with(&mut self, tile: Tile) {
        let width = self.tiles.get_width();
        let height = self.tiles.get_height();

        for x in 0..width {
            for y in 0..height {
                self.tiles[(x, y)] = Some(tile.clone());
            }
        }
    }

    pub fn get_width(&self) -> usize {
        self.tiles.get_width()
    }

    pub fn get_height(&self) -> usize {
        self.tiles.get_height()
    }

    pub fn get_mut_tile(&mut self, x: usize, y: usize) -> Option<&mut Tile> {
        if x < self.get_width() && y < self.get_height() {
            self.tiles[(x, y)].as_mut()
        } else {
            None
        }
    }

    pub fn get_mut_tile_with_vec(&mut self, pos: &Vec2<usize>) -> Option<&mut Tile> {
        self.get_mut_tile(pos.x, pos.y)
    }

    pub fn apply<F>(&mut self, gen: F) -> &mut Level where F: FnOnce(&mut Level) {
        gen(self);
        self
    }

}

static NONE: Option<Tile> = None;

impl Index<(usize, usize)> for Level {
    type Output= Option<Tile>;

    fn index(&self, (x, y): (usize, usize)) -> &Option<Tile>{
        if x < self.get_width() && y < self.get_height() {
            &self.tiles[(x, y)]
        } else {
            &NONE
        }
    }
}

impl Index<Vec2<usize>> for Level {
    type Output= Option<Tile>;

    fn index(&self, vec: Vec2<usize>) -> &Option<Tile>{
        if vec.x < self.get_width() && vec.y < self.get_height() {
            &self.tiles[(vec.x, vec.y)]
        } else {
            &NONE
        }
    }
}

pub fn fill_dead_ends(level: &mut Level) -> bool {
    let mut deadends = Vec::new();
    for y in 0..level.get_height() {
        for x in 0..level.get_width() {
            if let Some(ref n) = level[(x,y)] {
                if let &Tile::Floor = n {
                    if is_deadend(level, x, y) {
                        deadends.push((x,y));
                    }
                }
            }
        }
    }

    for &(x,y) in &deadends {
        if let Some(tile) = level.get_mut_tile(x, y) {
            *tile = Tile::Floor;
        }
    }
    deadends.is_empty()
}

pub fn is_deadend(level: &Level, x: usize, y: usize) -> bool {
    use util::Direction;
    let mut paths = 0;
    for dir in Direction::get_orthogonal_dirs() {
        let vector = dir.get_vec();
        let coord = match (add_isize_to_usize(vector.x, x), add_isize_to_usize(vector.y, y)) {
            (Some(x), Some(y)) => (x,y),
            _ => continue,
        };

        if let Some(Tile::Floor) = level[coord] {
            paths += 1;
        }
	}
    paths < 2
}

fn add_isize_to_usize(i: isize, mut u: usize,) -> Option<usize> {
    if i < 0 && u != 0 {
        u -= (-i) as usize;
    } else if i >= 0 && u < usize::max_value() {
        u += i as usize;
    } else {
        return None
    }
    Some(u)
}
<|endoftext|>


<commit_before>extern crate libc;

use {KeyboardControllable, Key, MouseControllable, MouseButton};

use self::libc::{c_char, c_int, c_void, useconds_t};
use std::{
    borrow::Cow,
    ffi::CString,
    ptr
};

const CURRENT_WINDOW: c_int = 0;
const DEFAULT_DELAY: u64 = 12000;
type Window  = c_int;
type Xdo     = *const c_void;

#[link(name = "xdo")]
extern "C" {
    fn xdo_free(xdo: Xdo);
    fn xdo_new(display: *const c_char) -> Xdo;

    fn xdo_click_window(xdo: Xdo, window: Window, button: c_int) -> c_int;
    fn xdo_mouse_down(xdo: Xdo, window: Window, button: c_int) -> c_int;
    fn xdo_mouse_up(xdo: Xdo, window: Window, button: c_int) -> c_int;
    fn xdo_move_mouse(xdo: Xdo, x: c_int, y: c_int, screen: c_int) -> c_int;
    fn xdo_move_mouse_relative(xdo: Xdo, x: c_int, y: c_int) -> c_int;

    fn xdo_enter_text_window(xdo: Xdo, window: Window, string: *const c_char, delay: useconds_t) -> c_int;
    fn xdo_send_keysequence_window(xdo: Xdo, window: Window, string: *const c_char, delay: useconds_t) -> c_int;
    fn xdo_send_keysequence_window_down(xdo: Xdo, window: Window, string: *const c_char, delay: useconds_t) -> c_int;
    fn xdo_send_keysequence_window_up(xdo: Xdo, window: Window, string: *const c_char, delay: useconds_t) -> c_int;
}

fn mousebutton(button: MouseButton) -> c_int {
    match button {
        MouseButton::Left => 1,
        MouseButton::Middle => 2,
        MouseButton::Right => 3,
        MouseButton::ScrollUp => 4,
        MouseButton::ScrollDown => 5,
        MouseButton::ScrollLeft => 6,
        MouseButton::ScrollRight => 7
    }
}

/// The main struct for handling the event emitting
pub struct Enigo {
    xdo: Xdo,
    delay: u64
}
// This is safe, we have a unique pointer.
// TODO: use Unique<c_char> once stable.
unsafe impl Send for Enigo {}

impl Default for Enigo {
    /// Create a new Enigo instance
    fn default() -> Self {
        Self {
            xdo: unsafe { xdo_new(ptr::null()) },
            delay: DEFAULT_DELAY
        }
    }
}
impl Enigo {
    /// Get the delay per keypress.
    /// Default value is 12000.
    /// This is Linux-specific.
    pub fn delay(&self) -> u64 {
        self.delay
    }
    /// Set the delay per keypress.
    /// This is Linux-specific.
    pub fn set_delay(&mut self, delay: u64) {
        self.delay = delay;
    }
}
impl Drop for Enigo {
    fn drop(&mut self) {
        unsafe {
            xdo_free(self.xdo);
        }
    }
}
impl MouseControllable for Enigo {
    fn mouse_move_to(&mut self, x: i32, y: i32) {
        unsafe {
            xdo_move_mouse(self.xdo, x as c_int, y as c_int, 0);
        }
    }
    fn mouse_move_relative(&mut self, x: i32, y: i32) {
        unsafe {
            xdo_move_mouse_relative(self.xdo, x as c_int, y as c_int);
        }
    }
    fn mouse_down(&mut self, button: MouseButton) {
        unsafe {
            xdo_mouse_down(self.xdo, CURRENT_WINDOW, mousebutton(button));
        }
    }
    fn mouse_up(&mut self, button: MouseButton) {
        unsafe {
            xdo_mouse_up(self.xdo, CURRENT_WINDOW, mousebutton(button));
        }
    }
    fn mouse_click(&mut self, button: MouseButton) {
        unsafe {
            xdo_click_window(self.xdo, CURRENT_WINDOW, mousebutton(button));
        }
    }
    fn mouse_scroll_x(&mut self, length: i32) {
        let button;
        let mut length = length;

        if length < 0 {
            button = MouseButton::ScrollLeft;
        } else {
            button = MouseButton::ScrollRight;
        }

        if length < 0 {
            length = -length;
        }

        for _ in 0..length {
            self.mouse_click(button);
        }
    }
    fn mouse_scroll_y(&mut self, length: i32) {
        let button;
        let mut length = length;

        if length < 0 {
            button = MouseButton::ScrollUp;
        } else {
            button = MouseButton::ScrollDown;
        }

        if length < 0 {
            length = -length;
        }

        for _ in 0..length {
            self.mouse_click(button);
        }
    }
}
fn keysequence<'a>(key: Key) -> Cow<'a, str> {
    if let Key::Layout(c) = key {
        return Cow::Owned(format!("U{:X}", c as u32));
    }
    #[allow(deprecated)] // I mean duh, we still need to support deprecated keys until they're removed
    Cow::Borrowed(match key {
        Key::Alt => "Alt",
        Key::Backspace => "BackSpace",
        Key::CapsLock => "CapsLock",
        Key::Control => "Control",
        Key::Delete => "Delete",
        Key::DownArrow => "DownArrow",
        Key::End => "End",
        Key::Escape => "Escape",
        Key::F1 => "F1",
        Key::F10 => "F10",
        Key::F11 => "F11",
        Key::F12 => "F12",
        Key::F2 => "F2",
        Key::F3 => "F3",
        Key::F4 => "F4",
        Key::F5 => "F5",
        Key::F6 => "F6",
        Key::F7 => "F7",
        Key::F8 => "F8",
        Key::F9 => "F9",
        Key::Home => "Home",
        Key::Layout(_) => unreachable!(),
        Key::LeftArrow => "Left",
        Key::Option => "Option",
        Key::PageDown => "PageDown",
        Key::PageUp => "PageUp",
        Key::Raw(_) => unimplemented!(),
        Key::Return => "Return",
        Key::RightArrow => "Right",
        Key::Shift => "Shift",
        Key::Space => "Space",
        Key::Tab => "Tab",
        Key::UpArrow => "Up",

        Key::Command |
        Key::Super |
        Key::Windows |
        Key::Meta => "Meta"
    })
}
impl KeyboardControllable for Enigo {
    fn key_sequence(&mut self, sequence: &str) {
        let string = CString::new(sequence).unwrap();
        unsafe {
            xdo_enter_text_window(self.xdo, CURRENT_WINDOW, string.as_ptr(), self.delay as useconds_t);
        }
    }
    fn key_down(&mut self, key: Key) {
        let string = CString::new(&*keysequence(key)).unwrap();
        unsafe {
            xdo_send_keysequence_window_down(self.xdo, CURRENT_WINDOW, string.as_ptr(), self.delay as useconds_t);
        }
    }
    fn key_up(&mut self, key: Key) {
        let string = CString::new(&*keysequence(key)).unwrap();
        unsafe {
            xdo_send_keysequence_window_up(self.xdo, CURRENT_WINDOW, string.as_ptr(), self.delay as useconds_t);
        }
    }
    fn key_click(&mut self, key: Key) {
        let string = CString::new(&*keysequence(key)).unwrap();
        unsafe {
            xdo_send_keysequence_window(self.xdo, CURRENT_WINDOW, string.as_ptr(), self.delay as useconds_t);
        }
    }
}
<commit_msg>Fix space and down arrow under linux.<commit_after>extern crate libc;

use {Key, KeyboardControllable, MouseButton, MouseControllable};

use self::libc::{c_char, c_int, c_void, useconds_t};
use std::{borrow::Cow, ffi::CString, ptr};

const CURRENT_WINDOW: c_int = 0;
const DEFAULT_DELAY: u64 = 12000;
type Window = c_int;
type Xdo = *const c_void;

#[link(name = "xdo")]
extern "C" {
    fn xdo_free(xdo: Xdo);
    fn xdo_new(display: *const c_char) -> Xdo;

    fn xdo_click_window(xdo: Xdo, window: Window, button: c_int) -> c_int;
    fn xdo_mouse_down(xdo: Xdo, window: Window, button: c_int) -> c_int;
    fn xdo_mouse_up(xdo: Xdo, window: Window, button: c_int) -> c_int;
    fn xdo_move_mouse(xdo: Xdo, x: c_int, y: c_int, screen: c_int) -> c_int;
    fn xdo_move_mouse_relative(xdo: Xdo, x: c_int, y: c_int) -> c_int;

    fn xdo_enter_text_window(
        xdo: Xdo,
        window: Window,
        string: *const c_char,
        delay: useconds_t,
    ) -> c_int;
    fn xdo_send_keysequence_window(
        xdo: Xdo,
        window: Window,
        string: *const c_char,
        delay: useconds_t,
    ) -> c_int;
    fn xdo_send_keysequence_window_down(
        xdo: Xdo,
        window: Window,
        string: *const c_char,
        delay: useconds_t,
    ) -> c_int;
    fn xdo_send_keysequence_window_up(
        xdo: Xdo,
        window: Window,
        string: *const c_char,
        delay: useconds_t,
    ) -> c_int;
}

fn mousebutton(button: MouseButton) -> c_int {
    match button {
        MouseButton::Left => 1,
        MouseButton::Middle => 2,
        MouseButton::Right => 3,
        MouseButton::ScrollUp => 4,
        MouseButton::ScrollDown => 5,
        MouseButton::ScrollLeft => 6,
        MouseButton::ScrollRight => 7,
    }
}

/// The main struct for handling the event emitting
pub struct Enigo {
    xdo: Xdo,
    delay: u64,
}
// This is safe, we have a unique pointer.
// TODO: use Unique<c_char> once stable.
unsafe impl Send for Enigo {}

impl Default for Enigo {
    /// Create a new Enigo instance
    fn default() -> Self {
        Self {
            xdo: unsafe { xdo_new(ptr::null()) },
            delay: DEFAULT_DELAY,
        }
    }
}
impl Enigo {
    /// Get the delay per keypress.
    /// Default value is 12000.
    /// This is Linux-specific.
    pub fn delay(&self) -> u64 {
        self.delay
    }
    /// Set the delay per keypress.
    /// This is Linux-specific.
    pub fn set_delay(&mut self, delay: u64) {
        self.delay = delay;
    }
}
impl Drop for Enigo {
    fn drop(&mut self) {
        unsafe {
            xdo_free(self.xdo);
        }
    }
}
impl MouseControllable for Enigo {
    fn mouse_move_to(&mut self, x: i32, y: i32) {
        unsafe {
            xdo_move_mouse(self.xdo, x as c_int, y as c_int, 0);
        }
    }
    fn mouse_move_relative(&mut self, x: i32, y: i32) {
        unsafe {
            xdo_move_mouse_relative(self.xdo, x as c_int, y as c_int);
        }
    }
    fn mouse_down(&mut self, button: MouseButton) {
        unsafe {
            xdo_mouse_down(self.xdo, CURRENT_WINDOW, mousebutton(button));
        }
    }
    fn mouse_up(&mut self, button: MouseButton) {
        unsafe {
            xdo_mouse_up(self.xdo, CURRENT_WINDOW, mousebutton(button));
        }
    }
    fn mouse_click(&mut self, button: MouseButton) {
        unsafe {
            xdo_click_window(self.xdo, CURRENT_WINDOW, mousebutton(button));
        }
    }
    fn mouse_scroll_x(&mut self, length: i32) {
        let button;
        let mut length = length;

        if length < 0 {
            button = MouseButton::ScrollLeft;
        } else {
            button = MouseButton::ScrollRight;
        }

        if length < 0 {
            length = -length;
        }

        for _ in 0..length {
            self.mouse_click(button);
        }
    }
    fn mouse_scroll_y(&mut self, length: i32) {
        let button;
        let mut length = length;

        if length < 0 {
            button = MouseButton::ScrollUp;
        } else {
            button = MouseButton::ScrollDown;
        }

        if length < 0 {
            length = -length;
        }

        for _ in 0..length {
            self.mouse_click(button);
        }
    }
}
fn keysequence<'a>(key: Key) -> Cow<'a, str> {
    if let Key::Layout(c) = key {
        return Cow::Owned(format!("U{:X}", c as u32));
    }
    #[allow(deprecated)]
    // I mean duh, we still need to support deprecated keys until they're removed
    Cow::Borrowed(match key {
        Key::Alt => "Alt",
        Key::Backspace => "BackSpace",
        Key::CapsLock => "CapsLock",
        Key::Control => "Control",
        Key::Delete => "Delete",
        Key::DownArrow => "Down",
        Key::End => "End",
        Key::Escape => "Escape",
        Key::F1 => "F1",
        Key::F10 => "F10",
        Key::F11 => "F11",
        Key::F12 => "F12",
        Key::F2 => "F2",
        Key::F3 => "F3",
        Key::F4 => "F4",
        Key::F5 => "F5",
        Key::F6 => "F6",
        Key::F7 => "F7",
        Key::F8 => "F8",
        Key::F9 => "F9",
        Key::Home => "Home",
        Key::Layout(_) => unreachable!(),
        Key::LeftArrow => "Left",
        Key::Option => "Option",
        Key::PageDown => "PageDown",
        Key::PageUp => "PageUp",
        Key::Raw(_) => unimplemented!(),
        Key::Return => "Return",
        Key::RightArrow => "Right",
        Key::Shift => "Shift",
        Key::Space => "space",
        Key::Tab => "Tab",
        Key::UpArrow => "Up",

        Key::Command | Key::Super | Key::Windows | Key::Meta => "Meta",
    })
}
impl KeyboardControllable for Enigo {
    fn key_sequence(&mut self, sequence: &str) {
        let string = CString::new(sequence).unwrap();
        unsafe {
            xdo_enter_text_window(
                self.xdo,
                CURRENT_WINDOW,
                string.as_ptr(),
                self.delay as useconds_t,
            );
        }
    }
    fn key_down(&mut self, key: Key) {
        let string = CString::new(&*keysequence(key)).unwrap();
        unsafe {
            xdo_send_keysequence_window_down(
                self.xdo,
                CURRENT_WINDOW,
                string.as_ptr(),
                self.delay as useconds_t,
            );
        }
    }
    fn key_up(&mut self, key: Key) {
        let string = CString::new(&*keysequence(key)).unwrap();
        unsafe {
            xdo_send_keysequence_window_up(
                self.xdo,
                CURRENT_WINDOW,
                string.as_ptr(),
                self.delay as useconds_t,
            );
        }
    }
    fn key_click(&mut self, key: Key) {
        let string = CString::new(&*keysequence(key)).unwrap();
        unsafe {
            xdo_send_keysequence_window(
                self.xdo,
                CURRENT_WINDOW,
                string.as_ptr(),
                self.delay as useconds_t,
            );
        }
    }
}
<|endoftext|>


<commit_before>use super::{AnimeEntry, AnimeInfo, Status, SyncBackend};
use chrono::{Date, Datelike, Local, NaiveDate, TimeZone};
use config::Config;
use error::BackendError;
use input;
use reqwest::header::{Accept, Authorization, Bearer, ContentType, Headers};
use reqwest::{Client, Response};
use serde_json as json;
use std::io;
use std::process::{Command, ExitStatus};

const LOGIN_URL: &str =
    "https://anilist.co/api/v2/oauth/authorize?client_id=427&response_type=token";

const API_URL: &str = "https://graphql.anilist.co";

macro_rules! send_query {
    ($backend:expr, $query_str:expr, {$($vars:tt)*}, $($response_root:expr)=>*) => {{
        let vars = json!({
            $($vars)*
        });

        $backend.send_json_request($query_str, &vars).map(|json| json$([$response_root])*.clone())
    }};
}

pub struct Anilist {
    client: Client,
    user_id: u32,
    access_token: String,
}

impl Anilist {
    fn send_request(
        &self,
        query_str: &str,
        variables: &json::Value,
    ) -> Result<Response, BackendError> {
        let body = json!({
            "query": query_str,
            "variables": variables,
        }).to_string();

        let mut headers = Headers::new();
        headers.set(ContentType::json());
        headers.set(Accept::json());
        headers.set(Authorization(Bearer {
            token: self.access_token.to_owned(),
        }));

        let response = self
            .client
            .post(API_URL)
            .headers(headers)
            .body(body)
            .send()?;

        Ok(response)
    }

    fn send_json_request(
        &self,
        query_str: &str,
        variables: &json::Value,
    ) -> Result<json::Value, BackendError> {
        let text = self.send_request(query_str, variables)?.text()?;
        let json: json::Value = json::from_str(&text)?;

        if json["errors"] != json::Value::Null {
            // TODO: add error chaining
            let err = &json["errors"][0];

            let msg = err["message"].as_str().unwrap_or("unknown error");
            let status_code = err["status"].as_u64().unwrap_or(0) as u32;

            return Err(BackendError::BadResponse(status_code, msg.into()));
        }

        Ok(json)
    }

    fn request_user_id(&self) -> Result<u32, BackendError> {
        let resp = send_query!(self,
            r#"
                query {
                    Viewer {
                        id
                    }
                }
            "#,
            {},
            "data" => "Viewer" => "id"
        )?;

        let id = json::from_value(resp)?;
        Ok(id)
    }

    fn prompt_for_access_token(open_url: bool) -> Result<String, BackendError> {
        if open_url {
            try_open_url(LOGIN_URL);
        }

        println!(
            "please authorize your account in the opened browser tab and paste the code below:"
        );

        let token = input::read_line()?;
        Ok(token)
    }

    fn login(&mut self, is_first_launch: bool, config: &mut Config) -> Result<(), BackendError> {
        let mut times_token_incorrect = 0;

        loop {
            match self.request_user_id() {
                Ok(user_id) => {
                    self.user_id = user_id;
                    break;
                }
                // As bad as checking for a specific error via its message is, the API does not provide
                // anything else to narrow it down to an invalid token error
                Err(BackendError::BadResponse(400, ref msg))
                    if msg.to_lowercase() == "invalid token" =>
                {
                    times_token_incorrect += 1;
                    println!("\ninvalid access token");

                    let should_open_url = !is_first_launch && times_token_incorrect <= 1;
                    let token = Anilist::prompt_for_access_token(should_open_url)?;

                    self.access_token = token;
                }
                Err(err) => return Err(err),
            }
        }

        if times_token_incorrect > 0 {
            config.anilist.token.encode(&self.access_token);
        }

        Ok(())
    }
}

impl SyncBackend for Anilist {
    fn name() -> &'static str {
        "AniList"
    }

    fn max_score(&self) -> u8 {
        // TODO: add support for other scoring types
        10
    }

    fn init(config: &mut Config) -> Result<Anilist, BackendError> {
        let is_first_launch = !config.anilist.token.is_set();

        let access_token = if is_first_launch {
            let token = Anilist::prompt_for_access_token(true)?;
            config.anilist.token.encode(&token);
            token
        } else {
            config.anilist.token.decode()?
        };

        let mut anilist = Anilist {
            client: Client::new(),
            user_id: 0,
            access_token,
        };

        anilist.login(is_first_launch, config)?;

        Ok(anilist)
    }

    fn search_by_name(&self, name: &str) -> Result<Vec<AnimeInfo>, BackendError> {
        let resp = send_query!(self,
            r#"
                query ($name: String) {
                    Page (page: 1, perPage: 30) {
                        media (search: $name, type: ANIME) {
                            id
                            title {
                                romaji
                            }
                            episodes
                        }
                    }
                }
            "#,
            { "name": name },
            "data" => "Page" => "media"
        )?;

        use self::json::Value;
        let mut series = Vec::new();

        match resp {
            Value::Array(ref entries) => {
                for entry in entries {
                    let series_info: MediaData = json::from_value(entry.clone())?;
                    series.push(series_info.into());
                }
            }
            _ => return Err(BackendError::InvalidJsonResponse),
        }

        Ok(series)
    }

    fn get_series_info_by_id(&self, id: u32) -> Result<AnimeInfo, BackendError> {
        let resp = send_query!(self,
            r#"
                query ($id: Int) {
                    Media (id: $id) {
                        id
                        title {
                            romaji
                        }
                        episodes
                    }
                }
            "#,
            { "id": id },
            "data" => "Media"
        )?;

        let info: MediaData = json::from_value(resp)?;
        Ok(info.into())
    }

    fn get_list_entry(&self, info: AnimeInfo) -> Result<Option<AnimeEntry>, BackendError> {
        let resp = send_query!(self,
            r#"
                query ($id: Int, $userID: Int) {
                    MediaList(mediaId: $id, userId: $userID) {
                        progress
                        status
                        score
                        startedAt {
                            year
                            month
                            day
                        }
                        completedAt {
                            year
                            month
                            day
                        }
                    }
                }
            "#,
            { "id": info.id, "userID": self.user_id },
            "data" => "MediaList"
        );

        match resp {
            Ok(entry_json) => {
                let media_entry: MediaListEntry = json::from_value(entry_json)?;
                Ok(Some(media_entry.into_generic_entry(info)))
            }
            Err(BackendError::BadResponse(404, _)) => Ok(None),
            Err(err) => Err(err),
        }
    }

    fn update_list_entry(&self, entry: &AnimeEntry) -> Result<(), BackendError> {
        send_query!(self,
            r#"
                mutation ($mediaId: Int, $watched_eps: Int, $score: Float, $status: MediaListStatus, $start_date: FuzzyDateInput, $finish_date: FuzzyDateInput) {
                    SaveMediaListEntry (mediaId: $mediaId, progress: $watched_eps, score: $score, status: $status, startedAt: $start_date, completedAt: $finish_date) {
                        mediaId
                    }
                }
            "#,
            {
                "mediaId": entry.info.id,
                "watched_eps": entry.watched_episodes,
                "score": entry.score,
                "status": MediaStatus::from(entry.status),
                "start_date": MediaDate::from_date(entry.start_date),
                "finish_date": MediaDate::from_date(entry.finish_date),
            },
        )?;

        Ok(())
    }
}

fn open_url(url: &str) -> io::Result<ExitStatus> {
    #[cfg(target_os = "windows")]
    const LAUNCH_PROGRAM: &str = "start";
    #[cfg(target_os = "macos")]
    const LAUNCH_PROGRAM: &str = "open";
    #[cfg(target_os = "linux")]
    const LAUNCH_PROGRAM: &str = "xdg-open";

    #[cfg(not(any(target_os = "windows", target_os = "macos", target_os = "linux")))]
    compile_error!("support for opening URL's not implemented for this platform");

    Command::new(LAUNCH_PROGRAM).arg(url).status()
}

fn try_open_url(url: &str) {
    match open_url(url) {
        Ok(status) if status.success() => (),
        result => {
            eprintln!(
                "failed to open URL in default browser. please open it manually: {}",
                url
            );

            if let Err(err) = result {
                eprintln!("error message: {}", err);
            }
        }
    }
}

#[derive(Deserialize)]
struct MediaData {
    id: u32,
    title: Title,
    episodes: Option<u32>,
}

#[derive(Deserialize)]
struct Title {
    romaji: String,
}

impl Into<AnimeInfo> for MediaData {
    fn into(self) -> AnimeInfo {
        AnimeInfo {
            id: self.id,
            title: self.title.romaji,
            episodes: self.episodes,
        }
    }
}

#[derive(Deserialize)]
struct MediaListEntry {
    progress: u32,
    status: MediaStatus,
    score: f32,
    #[serde(rename = "startedAt")]
    start_date: MediaDate,
    #[serde(rename = "completedAt")]
    finish_date: MediaDate,
}

impl MediaListEntry {
    fn into_generic_entry(self, info: AnimeInfo) -> AnimeEntry {
        AnimeEntry {
            info,
            watched_episodes: self.progress,
            score: self.score,
            status: self.status.into(),
            start_date: self.start_date.into_date(),
            finish_date: self.finish_date.into_date(),
        }
    }
}

#[derive(Serialize, Deserialize, Copy, Clone, PartialEq)]
enum MediaStatus {
    #[serde(rename = "CURRENT")]
    Current,
    #[serde(rename = "COMPLETED")]
    Completed,
    #[serde(rename = "PAUSED")]
    Paused,
    #[serde(rename = "DROPPED")]
    Dropped,
    #[serde(rename = "PLANNING")]
    Planning,
    #[serde(rename = "REPEATING")]
    Repeating,
}

impl Into<Status> for MediaStatus {
    fn into(self) -> Status {
        match self {
            MediaStatus::Current => Status::Watching,
            MediaStatus::Completed => Status::Completed,
            MediaStatus::Paused => Status::OnHold,
            MediaStatus::Dropped => Status::Dropped,
            MediaStatus::Planning => Status::PlanToWatch,
            MediaStatus::Repeating => Status::Rewatching,
        }
    }
}

impl From<Status> for MediaStatus {
    fn from(status: Status) -> MediaStatus {
        match status {
            Status::Watching => MediaStatus::Current,
            Status::Completed => MediaStatus::Completed,
            Status::OnHold => MediaStatus::Paused,
            Status::Dropped => MediaStatus::Dropped,
            Status::PlanToWatch => MediaStatus::Planning,
            Status::Rewatching => MediaStatus::Repeating,
        }
    }
}

#[derive(Serialize, Deserialize)]
struct MediaDate {
    year: Option<i32>,
    month: Option<u32>,
    day: Option<u32>,
}

impl MediaDate {
    fn into_date(self) -> Option<Date<Local>> {
        match (self.year, self.month, self.day) {
            (Some(year), Some(month), Some(day)) => Some(Local.ymd(year, month, day)),
            _ => None,
        }
    }

    fn from_date(date: Option<Date<Local>>) -> MediaDate {
        match date {
            Some(date) => MediaDate {
                year: Some(date.year()),
                month: Some(date.month()),
                day: Some(date.day()),
            },
            None => MediaDate {
                year: None,
                month: None,
                day: None,
            },
        }
    }
}

impl From<NaiveDate> for MediaDate {
    fn from(date: NaiveDate) -> MediaDate {
        MediaDate {
            year: Some(date.year()),
            month: Some(date.month()),
            day: Some(date.day()),
        }
    }
}
<commit_msg>[AniList] Parse & use user's scoring format<commit_after>use super::{AnimeEntry, AnimeInfo, Status, SyncBackend};
use chrono::{Date, Datelike, Local, NaiveDate, TimeZone};
use config::Config;
use error::BackendError;
use input;
use reqwest::header::{Accept, Authorization, Bearer, ContentType, Headers};
use reqwest::{Client, Response};
use serde_json as json;
use std::io;
use std::process::{Command, ExitStatus};

const LOGIN_URL: &str =
    "https://anilist.co/api/v2/oauth/authorize?client_id=427&response_type=token";

const API_URL: &str = "https://graphql.anilist.co";

macro_rules! send_query {
    ($backend:expr, $query_str:expr, {$($vars:tt)*}, $($response_root:expr)=>*) => {{
        let vars = json!({
            $($vars)*
        });

        $backend.send_json_request($query_str, &vars).map(|json| json$([$response_root])*.clone())
    }};
}

pub struct Anilist {
    client: Client,
    user: User,
    access_token: String,
}

impl Anilist {
    fn send_request(
        &self,
        query_str: &str,
        variables: &json::Value,
    ) -> Result<Response, BackendError> {
        let body = json!({
            "query": query_str,
            "variables": variables,
        }).to_string();

        let mut headers = Headers::new();
        headers.set(ContentType::json());
        headers.set(Accept::json());
        headers.set(Authorization(Bearer {
            token: self.access_token.to_owned(),
        }));

        let response = self
            .client
            .post(API_URL)
            .headers(headers)
            .body(body)
            .send()?;

        Ok(response)
    }

    fn send_json_request(
        &self,
        query_str: &str,
        variables: &json::Value,
    ) -> Result<json::Value, BackendError> {
        let text = self.send_request(query_str, variables)?.text()?;
        let json: json::Value = json::from_str(&text)?;

        if json["errors"] != json::Value::Null {
            // TODO: add error chaining
            let err = &json["errors"][0];

            let msg = err["message"].as_str().unwrap_or("unknown error");
            let status_code = err["status"].as_u64().unwrap_or(0) as u32;

            return Err(BackendError::BadResponse(status_code, msg.into()));
        }

        Ok(json)
    }

    fn request_user_info(&self) -> Result<User, BackendError> {
        let resp = send_query!(self,
            r#"
                query {
                    Viewer {
                        id
                        mediaListOptions {
                            scoreFormat
                        }
                    }
                }
            "#,
            {},
            "data" => "Viewer"
        )?;

        let user = json::from_value(resp)?;
        Ok(user)
    }

    fn prompt_for_access_token(open_url: bool) -> Result<String, BackendError> {
        if open_url {
            try_open_url(LOGIN_URL);
        }

        println!(
            "please authorize your account in the opened browser tab and paste the code below:"
        );

        let token = input::read_line()?;
        Ok(token)
    }

    fn login(&mut self, is_first_launch: bool, config: &mut Config) -> Result<(), BackendError> {
        let mut times_token_incorrect = 0;

        loop {
            match self.request_user_info() {
                Ok(user) => {
                    self.user = user;
                    break;
                }
                // As bad as checking for a specific error via its message is, the API does not provide
                // anything else to narrow it down to an invalid token error
                Err(BackendError::BadResponse(400, ref msg))
                    if msg.to_lowercase() == "invalid token" =>
                {
                    times_token_incorrect += 1;
                    println!("\ninvalid access token");

                    let should_open_url = !is_first_launch && times_token_incorrect <= 1;
                    let token = Anilist::prompt_for_access_token(should_open_url)?;

                    self.access_token = token;
                }
                Err(err) => return Err(err),
            }
        }

        if times_token_incorrect > 0 {
            config.anilist.token.encode(&self.access_token);
        }

        Ok(())
    }
}

impl SyncBackend for Anilist {
    fn name() -> &'static str {
        "AniList"
    }

    fn max_score(&self) -> u8 {
        self.user.list_options.score_format.max_score()
    }

    fn init(config: &mut Config) -> Result<Anilist, BackendError> {
        let is_first_launch = !config.anilist.token.is_set();

        let access_token = if is_first_launch {
            let token = Anilist::prompt_for_access_token(true)?;
            config.anilist.token.encode(&token);
            token
        } else {
            config.anilist.token.decode()?
        };

        let mut anilist = Anilist {
            client: Client::new(),
            user: User::default(),
            access_token,
        };

        anilist.login(is_first_launch, config)?;

        Ok(anilist)
    }

    fn search_by_name(&self, name: &str) -> Result<Vec<AnimeInfo>, BackendError> {
        let resp = send_query!(self,
            r#"
                query ($name: String) {
                    Page (page: 1, perPage: 30) {
                        media (search: $name, type: ANIME) {
                            id
                            title {
                                romaji
                            }
                            episodes
                        }
                    }
                }
            "#,
            { "name": name },
            "data" => "Page" => "media"
        )?;

        use self::json::Value;
        let mut series = Vec::new();

        match resp {
            Value::Array(ref entries) => {
                for entry in entries {
                    let series_info: MediaData = json::from_value(entry.clone())?;
                    series.push(series_info.into());
                }
            }
            _ => return Err(BackendError::InvalidJsonResponse),
        }

        Ok(series)
    }

    fn get_series_info_by_id(&self, id: u32) -> Result<AnimeInfo, BackendError> {
        let resp = send_query!(self,
            r#"
                query ($id: Int) {
                    Media (id: $id) {
                        id
                        title {
                            romaji
                        }
                        episodes
                    }
                }
            "#,
            { "id": id },
            "data" => "Media"
        )?;

        let info: MediaData = json::from_value(resp)?;
        Ok(info.into())
    }

    fn get_list_entry(&self, info: AnimeInfo) -> Result<Option<AnimeEntry>, BackendError> {
        let resp = send_query!(self,
            r#"
                query ($id: Int, $userID: Int) {
                    MediaList(mediaId: $id, userId: $userID) {
                        progress
                        status
                        score
                        startedAt {
                            year
                            month
                            day
                        }
                        completedAt {
                            year
                            month
                            day
                        }
                    }
                }
            "#,
            { "id": info.id, "userID": self.user.id },
            "data" => "MediaList"
        );

        match resp {
            Ok(entry_json) => {
                let media_entry: MediaListEntry = json::from_value(entry_json)?;
                Ok(Some(media_entry.into_generic_entry(info)))
            }
            Err(BackendError::BadResponse(404, _)) => Ok(None),
            Err(err) => Err(err),
        }
    }

    fn update_list_entry(&self, entry: &AnimeEntry) -> Result<(), BackendError> {
        send_query!(self,
            r#"
                mutation ($mediaId: Int, $watched_eps: Int, $score: Float, $status: MediaListStatus, $start_date: FuzzyDateInput, $finish_date: FuzzyDateInput) {
                    SaveMediaListEntry (mediaId: $mediaId, progress: $watched_eps, score: $score, status: $status, startedAt: $start_date, completedAt: $finish_date) {
                        mediaId
                    }
                }
            "#,
            {
                "mediaId": entry.info.id,
                "watched_eps": entry.watched_episodes,
                "score": entry.score,
                "status": MediaStatus::from(entry.status),
                "start_date": MediaDate::from_date(entry.start_date),
                "finish_date": MediaDate::from_date(entry.finish_date),
            },
        )?;

        Ok(())
    }
}

fn open_url(url: &str) -> io::Result<ExitStatus> {
    #[cfg(target_os = "windows")]
    const LAUNCH_PROGRAM: &str = "start";
    #[cfg(target_os = "macos")]
    const LAUNCH_PROGRAM: &str = "open";
    #[cfg(target_os = "linux")]
    const LAUNCH_PROGRAM: &str = "xdg-open";

    #[cfg(not(any(target_os = "windows", target_os = "macos", target_os = "linux")))]
    compile_error!("support for opening URL's not implemented for this platform");

    Command::new(LAUNCH_PROGRAM).arg(url).status()
}

fn try_open_url(url: &str) {
    match open_url(url) {
        Ok(status) if status.success() => (),
        result => {
            eprintln!(
                "failed to open URL in default browser. please open it manually: {}",
                url
            );

            if let Err(err) = result {
                eprintln!("error message: {}", err);
            }
        }
    }
}

#[derive(Deserialize)]
struct User {
    id: u32,
    #[serde(rename = "mediaListOptions")]
    list_options: MediaListOptions,
}

impl Default for User {
    fn default() -> User {
        User {
            id: 0,
            list_options: MediaListOptions::default(),
        }
    }
}

#[derive(Deserialize)]
struct MediaListOptions {
    #[serde(rename = "scoreFormat")]
    score_format: ScoreFormat,
}

impl Default for MediaListOptions {
    fn default() -> MediaListOptions {
        MediaListOptions {
            score_format: ScoreFormat::default(),
        }
    }
}

#[derive(Serialize, Deserialize, Copy, Clone)]
enum ScoreFormat {
    #[serde(rename = "POINT_100")]
    Point100,
    #[serde(rename = "POINT_10_DECIMAL")]
    Point10Decimal,
    #[serde(rename = "POINT_10")]
    Point10,
    #[serde(rename = "POINT_5")]
    Point5,
    #[serde(rename = "POINT_3")]
    Point3,
}

impl ScoreFormat {
    fn max_score(&self) -> u8 {
        use self::ScoreFormat::*;

        match self {
            Point100 => 100,
            Point10Decimal | Point10 => 10,
            Point5 => 5,
            Point3 => 3,
        }
    }
}

impl Default for ScoreFormat {
    fn default() -> ScoreFormat {
        ScoreFormat::Point100
    }
}

#[derive(Deserialize)]
struct MediaData {
    id: u32,
    title: Title,
    episodes: Option<u32>,
}

#[derive(Deserialize)]
struct Title {
    romaji: String,
}

impl Into<AnimeInfo> for MediaData {
    fn into(self) -> AnimeInfo {
        AnimeInfo {
            id: self.id,
            title: self.title.romaji,
            episodes: self.episodes,
        }
    }
}

#[derive(Deserialize)]
struct MediaListEntry {
    progress: u32,
    status: MediaStatus,
    score: f32,
    #[serde(rename = "startedAt")]
    start_date: MediaDate,
    #[serde(rename = "completedAt")]
    finish_date: MediaDate,
}

impl MediaListEntry {
    fn into_generic_entry(self, info: AnimeInfo) -> AnimeEntry {
        AnimeEntry {
            info,
            watched_episodes: self.progress,
            score: self.score,
            status: self.status.into(),
            start_date: self.start_date.into_date(),
            finish_date: self.finish_date.into_date(),
        }
    }
}

#[derive(Serialize, Deserialize, Copy, Clone, PartialEq)]
enum MediaStatus {
    #[serde(rename = "CURRENT")]
    Current,
    #[serde(rename = "COMPLETED")]
    Completed,
    #[serde(rename = "PAUSED")]
    Paused,
    #[serde(rename = "DROPPED")]
    Dropped,
    #[serde(rename = "PLANNING")]
    Planning,
    #[serde(rename = "REPEATING")]
    Repeating,
}

impl Into<Status> for MediaStatus {
    fn into(self) -> Status {
        match self {
            MediaStatus::Current => Status::Watching,
            MediaStatus::Completed => Status::Completed,
            MediaStatus::Paused => Status::OnHold,
            MediaStatus::Dropped => Status::Dropped,
            MediaStatus::Planning => Status::PlanToWatch,
            MediaStatus::Repeating => Status::Rewatching,
        }
    }
}

impl From<Status> for MediaStatus {
    fn from(status: Status) -> MediaStatus {
        match status {
            Status::Watching => MediaStatus::Current,
            Status::Completed => MediaStatus::Completed,
            Status::OnHold => MediaStatus::Paused,
            Status::Dropped => MediaStatus::Dropped,
            Status::PlanToWatch => MediaStatus::Planning,
            Status::Rewatching => MediaStatus::Repeating,
        }
    }
}

#[derive(Serialize, Deserialize)]
struct MediaDate {
    year: Option<i32>,
    month: Option<u32>,
    day: Option<u32>,
}

impl MediaDate {
    fn into_date(self) -> Option<Date<Local>> {
        match (self.year, self.month, self.day) {
            (Some(year), Some(month), Some(day)) => Some(Local.ymd(year, month, day)),
            _ => None,
        }
    }

    fn from_date(date: Option<Date<Local>>) -> MediaDate {
        match date {
            Some(date) => MediaDate {
                year: Some(date.year()),
                month: Some(date.month()),
                day: Some(date.day()),
            },
            None => MediaDate {
                year: None,
                month: None,
                day: None,
            },
        }
    }
}

impl From<NaiveDate> for MediaDate {
    fn from(date: NaiveDate) -> MediaDate {
        MediaDate {
            year: Some(date.year()),
            month: Some(date.month()),
            day: Some(date.day()),
        }
    }
}
<|endoftext|>


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

//! Points are fixed positions in affine space with no length or direction. This
//! disinguishes them from vectors, which have a length and direction, but do
//! not have a fixed position.

use traits::alg::*;
use traits::util::*;
use types::vector::{Vec2, Vec3};

#[deriving(Eq, Zero, Clone)]
struct Point2<S> { x: S, y: S }

#[deriving(Eq, Zero, Clone)]
struct Point3<S> { x: S, y: S, z: S }

impl<S: Field> Point2<S> {
    #[inline]
    pub fn new(x: S, y: S) -> Point2<S> {
        Point2 { x: x, y: y }
    }
}

impl<S: Field> Point3<S> {
    #[inline]
    pub fn new(x: S, y: S, z: S) -> Point3<S> {
        Point3 { x: x, y: y, z: z }
    }
}

// Operator impls

impl<S: Field> Mul<S, Point2<S>> for Point2<S> { #[inline(always)] fn mul(&self, s: &S) -> Point2<S> { self.map(|x| x.mul(s)) }  }
impl<S: Field> Mul<S, Point3<S>> for Point3<S> { #[inline(always)] fn mul(&self, s: &S) -> Point3<S> { self.map(|x| x.mul(s)) }  }

impl<S: Field> Div<S, Point2<S>> for Point2<S> { #[inline(always)] fn div(&self, s: &S) -> Point2<S> { self.map(|x| x.div(s)) }  }
impl<S: Field> Div<S, Point3<S>> for Point3<S> { #[inline(always)] fn div(&self, s: &S) -> Point3<S> { self.map(|x| x.div(s)) }  }

impl<S: Field> Rem<S, Point2<S>> for Point2<S> { #[inline(always)] fn rem(&self, s: &S) -> Point2<S> { self.map(|x| x.rem(s)) }  }
impl<S: Field> Rem<S, Point3<S>> for Point3<S> { #[inline(always)] fn rem(&self, s: &S) -> Point3<S> { self.map(|x| x.rem(s)) }  }

impl<S: Field> Add<Vec2<S>, Point2<S>> for Point2<S> { #[inline(always)] fn add(&self, other: &Vec2<S>) -> Point2<S> { self.bimap(other, |a, b| a.add(b)) } }
impl<S: Field> Add<Vec3<S>, Point3<S>> for Point3<S> { #[inline(always)] fn add(&self, other: &Vec3<S>) -> Point3<S> { self.bimap(other, |a, b| a.add(b)) } }

impl<S: Field> Sub<Point2<S>, Vec2<S>> for Point2<S> { #[inline(always)] fn sub(&self, other: &Point2<S>) -> Vec2<S> { self.bimap(other, |a, b| a.sub(b)) } }
impl<S: Field> Sub<Point3<S>, Vec3<S>> for Point3<S> { #[inline(always)] fn sub(&self, other: &Point3<S>) -> Vec3<S> { self.bimap(other, |a, b| a.sub(b)) } }

// Trait impls

impl_indexable!(Point2<S>, S, [S, ..2])
impl_indexable!(Point3<S>, S, [S, ..3])

impl<S: Clone + Field> Swappable<S, [S, ..2]> for Point2<S>;
impl<S: Clone + Field> Swappable<S, [S, ..3]> for Point3<S>;

impl<S: Clone + Field> Coordinate<S, [S, ..2]> for Point2<S>;
impl<S: Clone + Field> Coordinate<S, [S, ..3]> for Point3<S>;

impl<S: Field> ScalarMul<S> for Point2<S>;
impl<S: Field> ScalarMul<S> for Point3<S>;

impl<S: Field> AffineSpace<S, Vec2<S>> for Point2<S>;
impl<S: Field> AffineSpace<S, Vec3<S>> for Point3<S>;
<commit_msg>Use macros for Point impls<commit_after>// Copyright 2013 The CGMath Developers. For a full listing of the authors,
// refer to the AUTHORS file at the top-level directory of this distribution.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

//! Points are fixed positions in affine space with no length or direction. This
//! disinguishes them from vectors, which have a length and direction, but do
//! not have a fixed position.

use traits::alg::*;
use traits::util::*;
use types::vector::{Vec2, Vec3};

#[deriving(Eq, Zero, Clone)]
struct Point2<S> { x: S, y: S }

#[deriving(Eq, Zero, Clone)]
struct Point3<S> { x: S, y: S, z: S }

impl<S: Field> Point2<S> {
    #[inline]
    pub fn new(x: S, y: S) -> Point2<S> {
        Point2 { x: x, y: y }
    }
}

impl<S: Field> Point3<S> {
    #[inline]
    pub fn new(x: S, y: S, z: S) -> Point3<S> {
        Point3 { x: x, y: y, z: z }
    }
}

impl_indexable!(Point2<S>, S, [S, ..2])
impl_indexable!(Point3<S>, S, [S, ..3])

impl<S: Clone + Field> Swappable<S, [S, ..2]> for Point2<S>;
impl<S: Clone + Field> Swappable<S, [S, ..3]> for Point3<S>;

impl<S: Clone + Field> Coordinate<S, [S, ..2]> for Point2<S>;
impl<S: Clone + Field> Coordinate<S, [S, ..3]> for Point3<S>;

impl_scalar_binop!(Point2<S>, Mul, mul)
impl_scalar_binop!(Point3<S>, Mul, mul)

impl_scalar_binop!(Point2<S>, Div, div)
impl_scalar_binop!(Point3<S>, Div, div)

impl_scalar_binop!(Point2<S>, Rem, rem)
impl_scalar_binop!(Point3<S>, Rem, rem)

impl<S: Field> ScalarMul<S> for Point2<S>;
impl<S: Field> ScalarMul<S> for Point3<S>;

impl_coordinate_binop!(Point2<S>, Vec2<S>, Point2<S>, Add, add)
impl_coordinate_binop!(Point3<S>, Vec3<S>, Point3<S>, Add, add)

impl_coordinate_binop!(Point2<S>, Point2<S>, Vec2<S>, Sub, sub)
impl_coordinate_binop!(Point3<S>, Point3<S>, Vec3<S>, Sub, sub)

impl<S: Field> AffineSpace<S, Vec2<S>> for Point2<S>;
impl<S: Field> AffineSpace<S, Vec3<S>> for Point3<S>;
<|endoftext|>