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

/*!
Syntax extension to generate FourCCs.

Once loaded, fourcc!() is called with a single 4-character string,
and an optional ident that is either `big`, `little`, or `target`.
The ident represents endianness, and specifies in which direction
the characters should be read. If the ident is omitted, it is assumed
to be `big`, i.e. left-to-right order. It returns a u32.

# Examples

To load the extension and use it:

```rust,ignore
#[phase(syntax)]
extern crate fourcc;

fn main() {
    let val = fourcc!("\xC0\xFF\xEE!");
    assert_eq!(val, 0xC0FFEE21u32);
    let little_val = fourcc!("foo ", little);
    assert_eq!(little_val, 0x21EEFFC0u32);
}
 ```

# References

* [Wikipedia: FourCC](http://en.wikipedia.org/wiki/FourCC)

*/

#![crate_id = "fourcc#0.10"]
#![crate_type = "rlib"]
#![crate_type = "dylib"]
#![license = "MIT/ASL2"]
#![doc(html_logo_url = "http://www.rust-lang.org/logos/rust-logo-128x128-blk-v2.png",
       html_favicon_url = "http://www.rust-lang.org/favicon.ico",
       html_root_url = "http://static.rust-lang.org/doc/master")]

#![deny(deprecated_owned_vector)]
#![feature(macro_registrar, managed_boxes)]

extern crate syntax;

use syntax::ast;
use syntax::ast::Name;
use syntax::attr::contains;
use syntax::codemap::{Span, mk_sp};
use syntax::ext::base;
use syntax::ext::base::{SyntaxExtension, BasicMacroExpander, NormalTT, ExtCtxt, MRExpr};
use syntax::ext::build::AstBuilder;
use syntax::parse;
use syntax::parse::token;
use syntax::parse::token::InternedString;

#[macro_registrar]
pub fn macro_registrar(register: |Name, SyntaxExtension|) {
    register(token::intern("fourcc"),
        NormalTT(~BasicMacroExpander {
            expander: expand_syntax_ext,
            span: None,
        },
        None));
}

pub fn expand_syntax_ext(cx: &mut ExtCtxt, sp: Span, tts: &[ast::TokenTree]) -> base::MacResult {
    let (expr, endian) = parse_tts(cx, tts);

    let little = match endian {
        None => false,
        Some(Ident{ident, span}) => match token::get_ident(ident).get() {
            "little" => true,
            "big" => false,
            "target" => target_endian_little(cx, sp),
            _ => {
                cx.span_err(span, "invalid endian directive in fourcc!");
                target_endian_little(cx, sp)
            }
        }
    };

    let s = match expr.node {
        // expression is a literal
        ast::ExprLit(lit) => match lit.node {
            // string literal
            ast::LitStr(ref s, _) => {
                if s.get().char_len() != 4 {
                    cx.span_err(expr.span, "string literal with len != 4 in fourcc!");
                }
                s
            }
            _ => {
                cx.span_err(expr.span, "unsupported literal in fourcc!");
                return MRExpr(cx.expr_lit(sp, ast::LitUint(0u64, ast::TyU32)));
            }
        },
        _ => {
            cx.span_err(expr.span, "non-literal in fourcc!");
            return MRExpr(cx.expr_lit(sp, ast::LitUint(0u64, ast::TyU32)));
        }
    };

    let mut val = 0u32;
    for codepoint in s.get().chars().take(4) {
        let byte = if codepoint as u32 > 0xFF {
            cx.span_err(expr.span, "fourcc! literal character out of range 0-255");
            0u8
        } else {
            codepoint as u8
        };

        val = if little {
            (val >> 8) | ((byte as u32) << 24)
        } else {
            (val << 8) | (byte as u32)
        };
    }
    let e = cx.expr_lit(sp, ast::LitUint(val as u64, ast::TyU32));
    MRExpr(e)
}

struct Ident {
    ident: ast::Ident,
    span: Span
}

fn parse_tts(cx: &ExtCtxt, tts: &[ast::TokenTree]) -> (@ast::Expr, Option<Ident>) {
    let p = &mut parse::new_parser_from_tts(cx.parse_sess(),
                                            cx.cfg(),
                                            tts.iter()
                                               .map(|x| (*x).clone())
                                               .collect());
    let ex = p.parse_expr();
    let id = if p.token == token::EOF {
        None
    } else {
        p.expect(&token::COMMA);
        let lo = p.span.lo;
        let ident = p.parse_ident();
        let hi = p.last_span.hi;
        Some(Ident{ident: ident, span: mk_sp(lo, hi)})
    };
    if p.token != token::EOF {
        p.unexpected();
    }
    (ex, id)
}

fn target_endian_little(cx: &ExtCtxt, sp: Span) -> bool {
    let meta = cx.meta_name_value(sp, InternedString::new("target_endian"),
        ast::LitStr(InternedString::new("little"), ast::CookedStr));
    contains(cx.cfg().as_slice(), meta)
}

// FIXME (10872): This is required to prevent an LLVM assert on Windows
#[test]
fn dummy_test() { }
<commit_msg>Bump version to 0.11-pre<commit_after>// Copyright 2014 The Rust Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.

/*!
Syntax extension to generate FourCCs.

Once loaded, fourcc!() is called with a single 4-character string,
and an optional ident that is either `big`, `little`, or `target`.
The ident represents endianness, and specifies in which direction
the characters should be read. If the ident is omitted, it is assumed
to be `big`, i.e. left-to-right order. It returns a u32.

# Examples

To load the extension and use it:

```rust,ignore
#[phase(syntax)]
extern crate fourcc;

fn main() {
    let val = fourcc!("\xC0\xFF\xEE!");
    assert_eq!(val, 0xC0FFEE21u32);
    let little_val = fourcc!("foo ", little);
    assert_eq!(little_val, 0x21EEFFC0u32);
}
 ```

# References

* [Wikipedia: FourCC](http://en.wikipedia.org/wiki/FourCC)

*/

#![crate_id = "fourcc#0.11-pre"]
#![crate_type = "rlib"]
#![crate_type = "dylib"]
#![license = "MIT/ASL2"]
#![doc(html_logo_url = "http://www.rust-lang.org/logos/rust-logo-128x128-blk-v2.png",
       html_favicon_url = "http://www.rust-lang.org/favicon.ico",
       html_root_url = "http://static.rust-lang.org/doc/master")]

#![deny(deprecated_owned_vector)]
#![feature(macro_registrar, managed_boxes)]

extern crate syntax;

use syntax::ast;
use syntax::ast::Name;
use syntax::attr::contains;
use syntax::codemap::{Span, mk_sp};
use syntax::ext::base;
use syntax::ext::base::{SyntaxExtension, BasicMacroExpander, NormalTT, ExtCtxt, MRExpr};
use syntax::ext::build::AstBuilder;
use syntax::parse;
use syntax::parse::token;
use syntax::parse::token::InternedString;

#[macro_registrar]
pub fn macro_registrar(register: |Name, SyntaxExtension|) {
    register(token::intern("fourcc"),
        NormalTT(~BasicMacroExpander {
            expander: expand_syntax_ext,
            span: None,
        },
        None));
}

pub fn expand_syntax_ext(cx: &mut ExtCtxt, sp: Span, tts: &[ast::TokenTree]) -> base::MacResult {
    let (expr, endian) = parse_tts(cx, tts);

    let little = match endian {
        None => false,
        Some(Ident{ident, span}) => match token::get_ident(ident).get() {
            "little" => true,
            "big" => false,
            "target" => target_endian_little(cx, sp),
            _ => {
                cx.span_err(span, "invalid endian directive in fourcc!");
                target_endian_little(cx, sp)
            }
        }
    };

    let s = match expr.node {
        // expression is a literal
        ast::ExprLit(lit) => match lit.node {
            // string literal
            ast::LitStr(ref s, _) => {
                if s.get().char_len() != 4 {
                    cx.span_err(expr.span, "string literal with len != 4 in fourcc!");
                }
                s
            }
            _ => {
                cx.span_err(expr.span, "unsupported literal in fourcc!");
                return MRExpr(cx.expr_lit(sp, ast::LitUint(0u64, ast::TyU32)));
            }
        },
        _ => {
            cx.span_err(expr.span, "non-literal in fourcc!");
            return MRExpr(cx.expr_lit(sp, ast::LitUint(0u64, ast::TyU32)));
        }
    };

    let mut val = 0u32;
    for codepoint in s.get().chars().take(4) {
        let byte = if codepoint as u32 > 0xFF {
            cx.span_err(expr.span, "fourcc! literal character out of range 0-255");
            0u8
        } else {
            codepoint as u8
        };

        val = if little {
            (val >> 8) | ((byte as u32) << 24)
        } else {
            (val << 8) | (byte as u32)
        };
    }
    let e = cx.expr_lit(sp, ast::LitUint(val as u64, ast::TyU32));
    MRExpr(e)
}

struct Ident {
    ident: ast::Ident,
    span: Span
}

fn parse_tts(cx: &ExtCtxt, tts: &[ast::TokenTree]) -> (@ast::Expr, Option<Ident>) {
    let p = &mut parse::new_parser_from_tts(cx.parse_sess(),
                                            cx.cfg(),
                                            tts.iter()
                                               .map(|x| (*x).clone())
                                               .collect());
    let ex = p.parse_expr();
    let id = if p.token == token::EOF {
        None
    } else {
        p.expect(&token::COMMA);
        let lo = p.span.lo;
        let ident = p.parse_ident();
        let hi = p.last_span.hi;
        Some(Ident{ident: ident, span: mk_sp(lo, hi)})
    };
    if p.token != token::EOF {
        p.unexpected();
    }
    (ex, id)
}

fn target_endian_little(cx: &ExtCtxt, sp: Span) -> bool {
    let meta = cx.meta_name_value(sp, InternedString::new("target_endian"),
        ast::LitStr(InternedString::new("little"), ast::CookedStr));
    contains(cx.cfg().as_slice(), meta)
}

// FIXME (10872): This is required to prevent an LLVM assert on Windows
#[test]
fn dummy_test() { }
<|endoftext|>


<commit_before>// Natord: Natural ordering for Rust.
// Copyright (c) 2014, Kang Seonghoon.
// See README.md and LICENSE.txt for details.

/*!

# Natord 1.0.1

Natural ordering for Rust. (also known as `rust-natord`)
This allows for the comparison like this:

~~~~ {.rust}
let mut files = vec!("rfc2086.txt", "rfc822.txt", "rfc1.txt");
files.sort_by(|&a, &b| natord::compare(a, b));
assert_eq!(files.as_slice(), ["rfc1.txt", "rfc822.txt", "rfc2086.txt"].as_slice());
~~~~

There are multiple natural ordering algorithms available.
This version of natural ordering is inspired by
[Martin Pool's `strnatcmp.c`](http://sourcefrog.net/projects/natsort/).

*/

#![crate_name = "natord"]
#![crate_type = "lib"]

#![feature(macro_rules)]

/// Compares two iterators of "characters" possibly containing "digits".
/// The natural ordering can be customized with the following parameters:
///
/// * `skip` returns true if the "character" does not affect the comparison,
///   other than splitting two consecutive digits.
/// * `cmp` compares two "characters", assuming that they are not "digits".
/// * `to_digit` converts a "character" into a "digit" if possible. The digit of zero is special.
pub fn compare_iter<T, L: Iterator<T>, R: Iterator<T>>(left: L, right: R,
                                                       skip: |&T| -> bool,
                                                       cmp: |&T, &T| -> Ordering,
                                                       to_digit: |&T| -> Option<int>) -> Ordering {
    let mut left = left.fuse();
    let mut right = right.fuse();

    let mut l;
    let mut r;
    let mut ll;
    let mut rr;

    macro_rules! read_left {
        () => ({
            l = left.next();
            ll = l.as_ref().and_then(|v| to_digit(v));
        })
    }

    macro_rules! read_right {
        () => ({
            r = right.next();
            rr = r.as_ref().and_then(|v| to_digit(v));
        })
    }

    macro_rules! return_unless_equal {
        ($ord:expr) => (
            match $ord {
                Equal => {}
                lastcmp => return lastcmp,
            }
        )
    }

    read_left!();
    read_right!();
    'nondigits: loop {
        // skip preceding whitespaces
        while l.as_ref().map_or(false, |c| skip(c)) { read_left!(); }
        while r.as_ref().map_or(false, |c| skip(c)) { read_right!(); }

        match (l, r) {
            (Some(l_), Some(r_)) => match (ll, rr) {
                (Some(ll_), Some(rr_)) => {
                    if ll_ == 0 || rr_ == 0 {
                        // left-aligned matching. (`015` < `12`)
                        return_unless_equal!(ll_.cmp(&rr_));
                        'digits: loop {
                            read_left!();
                            read_right!();
                            match (ll, rr) {
                                (Some(ll_), Some(rr_)) => return_unless_equal!(ll_.cmp(&rr_)),
                                (Some(_), None) => return Greater,
                                (None, Some(_)) => return Less,
                                (None, None) => break 'digits,
                            }
                        }
                    } else {
                        // right-aligned matching. (`15` < `123`)
                        let mut lastcmp = ll_.cmp(&rr_);
                        'digits: loop {
                            read_left!();
                            read_right!();
                            match (ll, rr) {
                                (Some(ll_), Some(rr_)) => {
                                    // `lastcmp` is only used when there are the same number of
                                    // digits, so we only update it.
                                    if lastcmp == Equal { lastcmp = ll_.cmp(&rr_); }
                                }
                                (Some(_), None) => return Greater,
                                (None, Some(_)) => return Less,
                                (None, None) => break 'digits,
                            }
                        }
                        return_unless_equal!(lastcmp);
                    }
                    continue 'nondigits; // do not read from the iterators again
                },
                (_, _) => return_unless_equal!(cmp(&l_, &r_)),
            },
            (Some(_), None) => return Greater,
            (None, Some(_)) => return Less,
            (None, None) => return Equal,
        }

        read_left!();
        read_right!();
    }
}

/// Compares two strings case-sensitively.
/// It skips any Unicode whitespaces and handles a series of decimal digits.
pub fn compare(left: &str, right: &str) -> Ordering {
    compare_iter(left.chars(), right.chars(),
                 |&c| c.is_whitespace(),
                 |&l, &r| l.cmp(&r),
                 |&c| c.to_digit(10).map(|v| v as int))
}

/// Compares two strings case-insensitively.
/// It skips any Unicode whitespaces and handles a series of decimal digits.
pub fn compare_ignore_case(left: &str, right: &str) -> Ordering {
    // XXX what we really want is a case folding!
    // Unicode case folding can be done iteratively, but currently we don't have them in stdlib.
    compare_iter(left.chars(), right.chars(),
                 |&c| c.is_whitespace(),
                 |&l, &r| l.to_lowercase().cmp(&r.to_lowercase()),
                 |&c| c.to_digit(10).map(|v| v as int))
}

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

    fn check_total_order(strs: &[&str]) {
        fn ordering_to_op(ord: Ordering) -> &'static str {
            match ord {
                Greater => ">",
                Equal => "=",
                Less => "<",
            }
        }

        for (i, &x) in strs.iter().enumerate() {
            for (j, &y) in strs.iter().enumerate() {
                assert!(compare(x, y) == i.cmp(&j),
                        "expected x {} y, returned x {} y (where x = `{}`, y = `{}`)",
                        ordering_to_op(i.cmp(&j)), ordering_to_op(compare(x, y)), x, y);
            }
        }
    }

    #[test]
    fn test_numeric() {
        check_total_order(&["a", "a0", "a1", "a1a", "a1b", "a2", "a10", "a20"]);
    }

    #[test]
    fn test_multiple_parts() {
        check_total_order(&["x2-g8", "x2-y7", "x2-y8", "x8-y8"]);
    }

    #[test]
    fn test_leading_zeroes() {
        check_total_order(&["1.001", "1.002", "1.010", "1.02", "1.1", "1.3"]);
    }

    #[test]
    fn test_longer() {
        check_total_order(&[
            "1-02",
            "1-2",
            "1-20",
            "10-20",
            "fred",
            "jane",
            "pic1",
            "pic2",
            "pic2a",
            "pic3",
            "pic4",
            "pic4   alpha",
            "pic 4 else",
            "pic4  last",
            "pic5",
            "pic5.07",
            "pic5.08",
            "pic5.13",
            "pic5.113",
            "pic 5 something",
            "pic 6",
            "pic   7",
            "pic100",
            "pic100a",
            "pic120",
            "pic121",
            "pic2000",
            "tom",
            "x2-g8",
            "x2-y7",
            "x2-y8",
            "x8-y8",
        ]);
    }
}
<commit_msg>Latest Rust requires std::cmp imports<commit_after>// Natord: Natural ordering for Rust.
// Copyright (c) 2014, Kang Seonghoon.
// See README.md and LICENSE.txt for details.

/*!

# Natord 1.0.1

Natural ordering for Rust. (also known as `rust-natord`)
This allows for the comparison like this:

~~~~ {.rust}
let mut files = vec!("rfc2086.txt", "rfc822.txt", "rfc1.txt");
files.sort_by(|&a, &b| natord::compare(a, b));
assert_eq!(files.as_slice(), ["rfc1.txt", "rfc822.txt", "rfc2086.txt"].as_slice());
~~~~

There are multiple natural ordering algorithms available.
This version of natural ordering is inspired by
[Martin Pool's `strnatcmp.c`](http://sourcefrog.net/projects/natsort/).

*/

#![crate_name = "natord"]
#![crate_type = "lib"]

#![feature(macro_rules)]

use std::cmp::Ordering;
use std::cmp::Ordering::{Less, Equal, Greater};

/// Compares two iterators of "characters" possibly containing "digits".
/// The natural ordering can be customized with the following parameters:
///
/// * `skip` returns true if the "character" does not affect the comparison,
///   other than splitting two consecutive digits.
/// * `cmp` compares two "characters", assuming that they are not "digits".
/// * `to_digit` converts a "character" into a "digit" if possible. The digit of zero is special.
pub fn compare_iter<T, L: Iterator<T>, R: Iterator<T>>(left: L, right: R,
                                                       skip: |&T| -> bool,
                                                       cmp: |&T, &T| -> Ordering,
                                                       to_digit: |&T| -> Option<int>) -> Ordering {
    let mut left = left.fuse();
    let mut right = right.fuse();

    let mut l;
    let mut r;
    let mut ll;
    let mut rr;

    macro_rules! read_left {
        () => ({
            l = left.next();
            ll = l.as_ref().and_then(|v| to_digit(v));
        })
    }

    macro_rules! read_right {
        () => ({
            r = right.next();
            rr = r.as_ref().and_then(|v| to_digit(v));
        })
    }

    macro_rules! return_unless_equal {
        ($ord:expr) => (
            match $ord {
                Equal => {}
                lastcmp => return lastcmp,
            }
        )
    }

    read_left!();
    read_right!();
    'nondigits: loop {
        // skip preceding whitespaces
        while l.as_ref().map_or(false, |c| skip(c)) { read_left!(); }
        while r.as_ref().map_or(false, |c| skip(c)) { read_right!(); }

        match (l, r) {
            (Some(l_), Some(r_)) => match (ll, rr) {
                (Some(ll_), Some(rr_)) => {
                    if ll_ == 0 || rr_ == 0 {
                        // left-aligned matching. (`015` < `12`)
                        return_unless_equal!(ll_.cmp(&rr_));
                        'digits: loop {
                            read_left!();
                            read_right!();
                            match (ll, rr) {
                                (Some(ll_), Some(rr_)) => return_unless_equal!(ll_.cmp(&rr_)),
                                (Some(_), None) => return Greater,
                                (None, Some(_)) => return Less,
                                (None, None) => break 'digits,
                            }
                        }
                    } else {
                        // right-aligned matching. (`15` < `123`)
                        let mut lastcmp = ll_.cmp(&rr_);
                        'digits: loop {
                            read_left!();
                            read_right!();
                            match (ll, rr) {
                                (Some(ll_), Some(rr_)) => {
                                    // `lastcmp` is only used when there are the same number of
                                    // digits, so we only update it.
                                    if lastcmp == Equal { lastcmp = ll_.cmp(&rr_); }
                                }
                                (Some(_), None) => return Greater,
                                (None, Some(_)) => return Less,
                                (None, None) => break 'digits,
                            }
                        }
                        return_unless_equal!(lastcmp);
                    }
                    continue 'nondigits; // do not read from the iterators again
                },
                (_, _) => return_unless_equal!(cmp(&l_, &r_)),
            },
            (Some(_), None) => return Greater,
            (None, Some(_)) => return Less,
            (None, None) => return Equal,
        }

        read_left!();
        read_right!();
    }
}

/// Compares two strings case-sensitively.
/// It skips any Unicode whitespaces and handles a series of decimal digits.
pub fn compare(left: &str, right: &str) -> Ordering {
    compare_iter(left.chars(), right.chars(),
                 |&c| c.is_whitespace(),
                 |&l, &r| l.cmp(&r),
                 |&c| c.to_digit(10).map(|v| v as int))
}

/// Compares two strings case-insensitively.
/// It skips any Unicode whitespaces and handles a series of decimal digits.
pub fn compare_ignore_case(left: &str, right: &str) -> Ordering {
    // XXX what we really want is a case folding!
    // Unicode case folding can be done iteratively, but currently we don't have them in stdlib.
    compare_iter(left.chars(), right.chars(),
                 |&c| c.is_whitespace(),
                 |&l, &r| l.to_lowercase().cmp(&r.to_lowercase()),
                 |&c| c.to_digit(10).map(|v| v as int))
}

#[cfg(test)]
mod tests {
    use super::compare;
    use std::cmp::Ordering;
    use std::cmp::Ordering::{Less, Equal, Greater};

    fn check_total_order(strs: &[&str]) {
        fn ordering_to_op(ord: Ordering) -> &'static str {
            match ord {
                Greater => ">",
                Equal => "=",
                Less => "<",
            }
        }

        for (i, &x) in strs.iter().enumerate() {
            for (j, &y) in strs.iter().enumerate() {
                assert!(compare(x, y) == i.cmp(&j),
                        "expected x {} y, returned x {} y (where x = `{}`, y = `{}`)",
                        ordering_to_op(i.cmp(&j)), ordering_to_op(compare(x, y)), x, y);
            }
        }
    }

    #[test]
    fn test_numeric() {
        check_total_order(&["a", "a0", "a1", "a1a", "a1b", "a2", "a10", "a20"]);
    }

    #[test]
    fn test_multiple_parts() {
        check_total_order(&["x2-g8", "x2-y7", "x2-y8", "x8-y8"]);
    }

    #[test]
    fn test_leading_zeroes() {
        check_total_order(&["1.001", "1.002", "1.010", "1.02", "1.1", "1.3"]);
    }

    #[test]
    fn test_longer() {
        check_total_order(&[
            "1-02",
            "1-2",
            "1-20",
            "10-20",
            "fred",
            "jane",
            "pic1",
            "pic2",
            "pic2a",
            "pic3",
            "pic4",
            "pic4   alpha",
            "pic 4 else",
            "pic4  last",
            "pic5",
            "pic5.07",
            "pic5.08",
            "pic5.13",
            "pic5.113",
            "pic 5 something",
            "pic 6",
            "pic   7",
            "pic100",
            "pic100a",
            "pic120",
            "pic121",
            "pic2000",
            "tom",
            "x2-g8",
            "x2-y7",
            "x2-y8",
            "x8-y8",
        ]);
    }
}
<|endoftext|>


<commit_before>/*!

LZ4 Decompression

This module contains an implementation in Rust of decompression of LZ4-encoded
streams. This is exposed as a standard `Reader` interface wrapping an
underlying `Reader` interface. In this manner, the decompressor can be viewed
as a stream. It maintains its own internal buffer, but the size of this is
constant as the stream is read.

# Example

```rust
let stream = Path::new("path/to/file.lz4").open_reader(io::Open);
let decompressed = l4z::Decoder::new(stream).read_to_end();
```

*/

use std::rt::io;
use std::rt::io::extensions::{ReaderUtil, ReaderByteConversions};
use std::vec;
use std::num;

#[deriving(Eq)]
enum State {
    StreamStart,
    BlockStart,
    BlockEnd,

    // raw blocks of uncompressed data
    RawBlock(u32),

    // blocks of compressed data (no headers or checksums)
    DataStart,
    NeedsConsumption(u8, uint),
    DecompressStart(u8),
    Decompress(uint),

    EndOfStream,
    Cleanup,
}

enum ConsumptionResult {
    KeepGoing(uint),
    Consumed(uint),
    SomethingBad,
}

static BUF_SIZE: uint = 128 << 10;
static FLUSH_SIZE: uint = 1 << 16;
static MAGIC: u32 = 0x184d2204;

pub struct Decoder<R> {
    r: R,

    priv state: State,
    priv end: uint,
    priv start: uint,
    priv buf: ~[u8],
    priv remaining_bytes: uint,

    // metadata from the stream descriptor
    priv blk_checksum: bool,
    priv stream_checksum: bool,
}

impl<R: io::Reader + io::Seek> Decoder<R> {
    /// Creates a new decoder which will decompress the LZ4-encoded stream
    /// which will be read from `r`. This decoder will consume ownership of the
    /// reader, but it is accessible via the `r` field on the object returned.
    pub fn new(r: R) -> Decoder<R> {
        Decoder {
            r: r,
            state: StreamStart,
            start: 0,
            end: 0,
            buf: vec::from_elem(BUF_SIZE, 0u8),
            blk_checksum: false,
            stream_checksum: false,
            remaining_bytes: 0,
        }
    }

    fn inner_byte(&mut self) -> Option<u8> {
        match self.r.read_byte() {
            Some(b) => {
                self.remaining_bytes -= 1;
                Some(b)
            }
            None => None,
        }
    }

    fn inner_u16(&mut self) -> u16 {
        self.remaining_bytes -= 2;
        self.r.read_le_u16_()
    }

    fn inner_read(&mut self, dst: &mut [u8]) -> Option<uint> {
        match self.r.read(dst) {
            Some(n) => {
                self.remaining_bytes -= n;
                Some(n)
            }
            None => None,
        }
    }

    fn read_len(&mut self, code: u8) -> Option<uint> {
        let mut ret = code as uint;
        assert!(code < 16);
        if code == 0xf {
            loop {
                match self.inner_byte() {
                    None => return None,
                    Some(255) => ret += 255,
                    Some(n) => {
                        ret += n as uint;
                        break
                    }
                }
            }
        }
        return Some(ret);
    }

    fn consume(&mut self, len: uint, dst: &mut [u8]) -> ConsumptionResult {
        let in_output = self.flush(len, dst);
        if self.end + len > self.buf.len() {
            return KeepGoing(in_output);
        }

        let buf = unsafe {
            use std::cast;
            cast::transmute_copy(&self.buf.mut_slice(self.end, self.end + len))
        };
        match self.inner_read(buf) {
            Some(n) => {
                self.end += n;
                Consumed(in_output)
            }
            None => SomethingBad
        }
    }

    fn flush(&mut self, len: uint, dst: &mut [u8]) -> uint {
        if self.end + len > self.buf.len() {
            assert!(self.start > FLUSH_SIZE);
            let s = self.start - FLUSH_SIZE;

            // Copy bytes out into the destination
            let fill = num::min(dst.len(), s);
            info!("flushing out {}", fill);
            vec::bytes::copy_memory(dst, self.buf, fill);

            // Slide all the bytes back down in the internal buffer
            for i in range(0, fill) {
                self.buf[i] = self.buf[fill + i];
            }

            self.end -= fill;
            self.start -= fill;
            return fill;
        }
        return 0;
    }

    fn cp(&mut self, len: uint, decr: uint, dst: &mut [u8]) -> ConsumptionResult {
        let in_output = self.flush(len, dst);
        if self.end + len > self.buf.len() {
            return KeepGoing(in_output);
        }

        for i in range(0, len) {
            self.buf[self.end + i] = self.buf[self.start + i];
        }

        self.end += len;
        self.start += len - decr;
        return Consumed(in_output);
    }

    fn exec(&mut self, mut state: State, buf: &mut [u8]) -> Option<uint> {
        let mut offset = 0;
        loop {
            debug!("state: {:?} {:x} {:x} at {}", state, self.start, self.end,
                   self.r.tell());
            match state {
                StreamStart => {
                    match self.header() {
                        Some(()) => {}
                        None => return None,
                    }
                    state = BlockStart;
                }

                BlockStart => {
                    state = match self.r.read_le_u32_() {
                        0 => EndOfStream,
                        n if n & 0x80000000 != 0 => RawBlock(n & 0x7fffffff),
                        n => {
                            self.remaining_bytes = n as uint;
                            info!("bytes left {}", self.remaining_bytes);
                            DataStart
                        }
                    }
                }

                DataStart => {
                    //info!("remaining: {}", self.remaining_bytes as int);
                    assert!(self.remaining_bytes as int > 0);
                    let code = match self.inner_byte() {
                        Some(i) => i,
                        None => return None,
                    };
                    debug!("at: {:x} {} {:x}", self.r.tell(),
                           self.remaining_bytes as int, code);

                    // XXX: I/O errors
                    let len = match self.read_len(code >> 4) {
                        Some(l) => l, None => return None,
                    };
                    state = NeedsConsumption(code, len);
                }

                NeedsConsumption(code, len) => {
                    match self.consume(len, buf.mut_slice_from(offset)) {
                        // XXX: I/O error
                        SomethingBad => return None,
                        KeepGoing(amt_written) => {
                            self.state = state;
                            return Some(amt_written + offset);
                        }
                        Consumed(amt_written) => {
                            offset += amt_written;
                            if self.remaining_bytes == 0 {
                                state = BlockStart;
                            } else {
                                state = DecompressStart(code);
                            }
                        }
                    }
                }

                DecompressStart(code) => {
                    // XXX: I/O errors
                    let back = self.inner_u16();
                    debug!("got back: {}", back);
                    self.start = self.end - back as uint;

                    let len = match self.read_len(code & 0xf) {
                        Some(l) => l, None => return None,
                    };

                    state = Decompress(len);
                }

                Decompress(len) => {
                    let literal = self.end - self.start;
                    if literal < 4 {
                        static DECR: [uint, ..4] = [0, 3, 2, 3];
                        match self.cp(4, DECR[literal],
                                      buf.mut_slice_from(offset)) {
                            SomethingBad => unreachable!(),
                            KeepGoing(amt_written) => {
                                self.state = state;
                                return Some(amt_written + offset);
                            }
                            Consumed(amt_written) => offset += amt_written,
                        }
                    } else {
                        len += 4;
                    }
                    match self.cp(len, 0, buf.mut_slice_from(offset)) {
                        SomethingBad => unreachable!(),
                        KeepGoing(amt_written) => {
                            self.state = state;
                            return Some(amt_written + offset);
                        }
                        Consumed(amt_written) => {
                            offset += amt_written;
                            state = DataStart;
                        }
                    }
                }

                RawBlock(remaining) => {
                    let dst = buf.mut_slice_from(offset);
                    if dst.len() == 0 {
                        self.state = state;
                        return Some(offset);
                    }
                    if remaining == 0 {
                        state = BlockEnd;
                        continue
                    }
                    let amt = num::min(remaining as uint, dst.len());
                    match self.r.read(dst.mut_slice(offset, offset + amt)) {
                        Some(n) => {
                            offset += n;
                            state = RawBlock(remaining - n as u32);
                        }
                        None => return None
                    }
                }

                BlockEnd => {
                    // XXX: implement checksums
                    if self.blk_checksum {
                        let cksum = self.r.read_le_u32_();
                        debug!("ignoring block cksum: {:?}", cksum);
                    }
                    state = BlockStart;
                }

                EndOfStream => {
                    // XXX: implement checksums
                    if self.stream_checksum {
                        let cksum = self.r.read_le_u32_();
                        debug!("ignoring stream checksum: {:?}", cksum);
                    }
                    state = Cleanup;
                    self.start = 0;
                }

                Cleanup => {
                    let dst = buf.mut_slice_from(offset);
                    let src = self.buf.slice(self.start, self.end);
                    let amt = num::min(dst.len(), src.len());
                    if amt == 0 { return None }
                    vec::bytes::copy_memory(dst, src, amt);
                    self.start += amt;
                    self.state = Cleanup;
                    return Some(amt + offset);
                }
            }
        }
    }

    fn header(&mut self) -> Option<()> {
        // Make sure the magic number is what's expected.
        if self.r.read_le_u32_() != MAGIC { return None }

        let flg = match self.r.read_byte() { Some(i) => i, None => return None };
        let bd = match self.r.read_byte() { Some(i) => i, None => return None };

        // bits 7/6, the version number. Right now this must be 1
        if (flg >> 6) != 0b01 { return None }
        // bit 5 is the "block independence", don't care about this yet
        // bit 4 is whether blocks have checksums or not
        self.blk_checksum = (flg & 0x10) != 0;
        // bit 3 is whether there is a following stream size
        let stream_size = (flg & 0x08) != 0;
        // bit 2 is whether there is a stream checksum
        self.stream_checksum = (flg & 0x04) != 0;
        // bit 1 is reserved
        // bit 0 is whether there is a preset dictionary
        let preset_dictionary = (flg & 0x01) != 0;

        static MAX_SIZES: [uint, ..8] =
            [0, 0, 0, 0, // all N/A
             64 << 10,   // 64KB
             256 << 10,  // 256 KB
             1 << 20,    // 1MB
             4 << 20];   // 4MB

        // bit 7 is reserved
        // bits 6-4 are the maximum block size
        let max_block_size = MAX_SIZES[(bd >> 4) & 0x7];
        // bits 3-0 are reserved

        // read off other portions of the stream
        let size = if stream_size {Some(self.r.read_le_u64_())} else {None};
        assert!(!preset_dictionary, "preset dictionaries not supported yet");

        debug!("blk: {}", self.blk_checksum);
        debug!("stream: {}", self.stream_checksum);
        debug!("max size: {}", max_block_size);
        debug!("stream size: {:?}", size);

        // XXX: implement checksums
        let cksum = self.r.read_byte();
        debug!("ignoring header checksum: {:?}", cksum);
        return Some(());
    }
}

impl<R: io::Reader + io::Seek> io::Reader for Decoder<R> {
    fn read(&mut self, buf: &mut [u8]) -> Option<uint> {
        info!("reading {}", buf.len());
        let out = self.exec(self.state, buf);
        info!("got {:?}", out);
        out
    }

    fn eof(&mut self) -> bool { fail!() }
}

#[cfg(test)]
mod test {
    use super::Decoder;
    use std::rt::io::extensions::ReaderUtil;
    use std::rt::io::mem::BufReader;
    use std::rt::io::Reader;
    use std::rand;

    fn test_decode(input: &[u8], output: &[u8]) {
        let mut d = Decoder::new(BufReader::new(input));

        let got = d.read_to_end();
        assert!(got.as_slice() == output);
    }

    #[test]
    fn decode() {
        let reference = include_bin!("data/test.txt");
        test_decode(include_bin!("data/test.lz4.1"), reference);
        test_decode(include_bin!("data/test.lz4.2"), reference);
        test_decode(include_bin!("data/test.lz4.3"), reference);
        test_decode(include_bin!("data/test.lz4.4"), reference);
        test_decode(include_bin!("data/test.lz4.5"), reference);
        test_decode(include_bin!("data/test.lz4.6"), reference);
        test_decode(include_bin!("data/test.lz4.7"), reference);
        test_decode(include_bin!("data/test.lz4.8"), reference);
        test_decode(include_bin!("data/test.lz4.9"), reference);
    }

    #[test]
    fn one_byte_at_a_time() {
        let input = include_bin!("data/test.lz4.1");
        let mut d = Decoder::new(BufReader::new(input));
        let mut out = ~[];
        loop {
            match d.read_byte() {
                Some(b) => out.push(b),
                None => break
            }
        }
        assert!(out.as_slice() == include_bin!("data/test.txt"));
    }

    #[test]
    fn random_byte_lengths() {
        let input = include_bin!("data/test.lz4.1");
        let mut d = Decoder::new(BufReader::new(input));
        let mut out = ~[];
        let mut buf = [0u8, ..40];
        loop {
            match d.read(buf.mut_slice_to(1 + rand::random::<uint>() % 40)) {
                Some(n) => {
                    out.push_all(buf.slice_to(n));
                }
                None => break
            }
        }
        assert!(out.as_slice() == include_bin!("data/test.txt"));
    }
}
<commit_msg>Credit where credit is due<commit_after>/*!

LZ4 Decompression

This module contains an implementation in Rust of decompression of LZ4-encoded
streams. This is exposed as a standard `Reader` interface wrapping an
underlying `Reader` interface. In this manner, the decompressor can be viewed
as a stream. It maintains its own internal buffer, but the size of this is
constant as the stream is read.

# Example

```rust
let stream = Path::new("path/to/file.lz4").open_reader(io::Open);
let decompressed = l4z::Decoder::new(stream).read_to_end();
```

# Credit

This implementation is largely based on Branimir Karadžić's implementation which
can be found at https://github.com/bkaradzic/go-lz4.

*/

use std::rt::io;
use std::rt::io::extensions::{ReaderUtil, ReaderByteConversions};
use std::vec;
use std::num;

#[deriving(Eq)]
enum State {
    StreamStart,
    BlockStart,
    BlockEnd,

    // raw blocks of uncompressed data
    RawBlock(u32),

    // blocks of compressed data (no headers or checksums)
    DataStart,
    NeedsConsumption(u8, uint),
    DecompressStart(u8),
    Decompress(uint),

    EndOfStream,
    Cleanup,
}

enum ConsumptionResult {
    KeepGoing(uint),
    Consumed(uint),
    SomethingBad,
}

static BUF_SIZE: uint = 128 << 10;
static FLUSH_SIZE: uint = 1 << 16;
static MAGIC: u32 = 0x184d2204;

pub struct Decoder<R> {
    r: R,

    priv state: State,
    priv end: uint,
    priv start: uint,
    priv buf: ~[u8],
    priv remaining_bytes: uint,

    // metadata from the stream descriptor
    priv blk_checksum: bool,
    priv stream_checksum: bool,
}

impl<R: io::Reader + io::Seek> Decoder<R> {
    /// Creates a new decoder which will decompress the LZ4-encoded stream
    /// which will be read from `r`. This decoder will consume ownership of the
    /// reader, but it is accessible via the `r` field on the object returned.
    pub fn new(r: R) -> Decoder<R> {
        Decoder {
            r: r,
            state: StreamStart,
            start: 0,
            end: 0,
            buf: vec::from_elem(BUF_SIZE, 0u8),
            blk_checksum: false,
            stream_checksum: false,
            remaining_bytes: 0,
        }
    }

    fn inner_byte(&mut self) -> Option<u8> {
        match self.r.read_byte() {
            Some(b) => {
                self.remaining_bytes -= 1;
                Some(b)
            }
            None => None,
        }
    }

    fn inner_u16(&mut self) -> u16 {
        self.remaining_bytes -= 2;
        self.r.read_le_u16_()
    }

    fn inner_read(&mut self, dst: &mut [u8]) -> Option<uint> {
        match self.r.read(dst) {
            Some(n) => {
                self.remaining_bytes -= n;
                Some(n)
            }
            None => None,
        }
    }

    fn read_len(&mut self, code: u8) -> Option<uint> {
        let mut ret = code as uint;
        assert!(code < 16);
        if code == 0xf {
            loop {
                match self.inner_byte() {
                    None => return None,
                    Some(255) => ret += 255,
                    Some(n) => {
                        ret += n as uint;
                        break
                    }
                }
            }
        }
        return Some(ret);
    }

    fn consume(&mut self, len: uint, dst: &mut [u8]) -> ConsumptionResult {
        let in_output = self.flush(len, dst);
        if self.end + len > self.buf.len() {
            return KeepGoing(in_output);
        }

        let buf = unsafe {
            use std::cast;
            cast::transmute_copy(&self.buf.mut_slice(self.end, self.end + len))
        };
        match self.inner_read(buf) {
            Some(n) => {
                self.end += n;
                Consumed(in_output)
            }
            None => SomethingBad
        }
    }

    fn flush(&mut self, len: uint, dst: &mut [u8]) -> uint {
        if self.end + len > self.buf.len() {
            assert!(self.start > FLUSH_SIZE);
            let s = self.start - FLUSH_SIZE;

            // Copy bytes out into the destination
            let fill = num::min(dst.len(), s);
            info!("flushing out {}", fill);
            vec::bytes::copy_memory(dst, self.buf, fill);

            // Slide all the bytes back down in the internal buffer
            for i in range(0, fill) {
                self.buf[i] = self.buf[fill + i];
            }

            self.end -= fill;
            self.start -= fill;
            return fill;
        }
        return 0;
    }

    fn cp(&mut self, len: uint, decr: uint, dst: &mut [u8]) -> ConsumptionResult {
        let in_output = self.flush(len, dst);
        if self.end + len > self.buf.len() {
            return KeepGoing(in_output);
        }

        for i in range(0, len) {
            self.buf[self.end + i] = self.buf[self.start + i];
        }

        self.end += len;
        self.start += len - decr;
        return Consumed(in_output);
    }

    fn exec(&mut self, mut state: State, buf: &mut [u8]) -> Option<uint> {
        let mut offset = 0;
        loop {
            debug!("state: {:?} {:x} {:x} at {}", state, self.start, self.end,
                   self.r.tell());
            match state {
                StreamStart => {
                    match self.header() {
                        Some(()) => {}
                        None => return None,
                    }
                    state = BlockStart;
                }

                BlockStart => {
                    state = match self.r.read_le_u32_() {
                        0 => EndOfStream,
                        n if n & 0x80000000 != 0 => RawBlock(n & 0x7fffffff),
                        n => {
                            self.remaining_bytes = n as uint;
                            info!("bytes left {}", self.remaining_bytes);
                            DataStart
                        }
                    }
                }

                DataStart => {
                    //info!("remaining: {}", self.remaining_bytes as int);
                    assert!(self.remaining_bytes as int > 0);
                    let code = match self.inner_byte() {
                        Some(i) => i,
                        None => return None,
                    };
                    debug!("at: {:x} {} {:x}", self.r.tell(),
                           self.remaining_bytes as int, code);

                    // XXX: I/O errors
                    let len = match self.read_len(code >> 4) {
                        Some(l) => l, None => return None,
                    };
                    state = NeedsConsumption(code, len);
                }

                NeedsConsumption(code, len) => {
                    match self.consume(len, buf.mut_slice_from(offset)) {
                        // XXX: I/O error
                        SomethingBad => return None,
                        KeepGoing(amt_written) => {
                            self.state = state;
                            return Some(amt_written + offset);
                        }
                        Consumed(amt_written) => {
                            offset += amt_written;
                            if self.remaining_bytes == 0 {
                                state = BlockStart;
                            } else {
                                state = DecompressStart(code);
                            }
                        }
                    }
                }

                DecompressStart(code) => {
                    // XXX: I/O errors
                    let back = self.inner_u16();
                    debug!("got back: {}", back);
                    self.start = self.end - back as uint;

                    let len = match self.read_len(code & 0xf) {
                        Some(l) => l, None => return None,
                    };

                    state = Decompress(len);
                }

                Decompress(len) => {
                    let literal = self.end - self.start;
                    if literal < 4 {
                        static DECR: [uint, ..4] = [0, 3, 2, 3];
                        match self.cp(4, DECR[literal],
                                      buf.mut_slice_from(offset)) {
                            SomethingBad => unreachable!(),
                            KeepGoing(amt_written) => {
                                self.state = state;
                                return Some(amt_written + offset);
                            }
                            Consumed(amt_written) => offset += amt_written,
                        }
                    } else {
                        len += 4;
                    }
                    match self.cp(len, 0, buf.mut_slice_from(offset)) {
                        SomethingBad => unreachable!(),
                        KeepGoing(amt_written) => {
                            self.state = state;
                            return Some(amt_written + offset);
                        }
                        Consumed(amt_written) => {
                            offset += amt_written;
                            state = DataStart;
                        }
                    }
                }

                RawBlock(remaining) => {
                    let dst = buf.mut_slice_from(offset);
                    if dst.len() == 0 {
                        self.state = state;
                        return Some(offset);
                    }
                    if remaining == 0 {
                        state = BlockEnd;
                        continue
                    }
                    let amt = num::min(remaining as uint, dst.len());
                    match self.r.read(dst.mut_slice(offset, offset + amt)) {
                        Some(n) => {
                            offset += n;
                            state = RawBlock(remaining - n as u32);
                        }
                        None => return None
                    }
                }

                BlockEnd => {
                    // XXX: implement checksums
                    if self.blk_checksum {
                        let cksum = self.r.read_le_u32_();
                        debug!("ignoring block cksum: {:?}", cksum);
                    }
                    state = BlockStart;
                }

                EndOfStream => {
                    // XXX: implement checksums
                    if self.stream_checksum {
                        let cksum = self.r.read_le_u32_();
                        debug!("ignoring stream checksum: {:?}", cksum);
                    }
                    state = Cleanup;
                    self.start = 0;
                }

                Cleanup => {
                    let dst = buf.mut_slice_from(offset);
                    let src = self.buf.slice(self.start, self.end);
                    let amt = num::min(dst.len(), src.len());
                    if amt == 0 { return None }
                    vec::bytes::copy_memory(dst, src, amt);
                    self.start += amt;
                    self.state = Cleanup;
                    return Some(amt + offset);
                }
            }
        }
    }

    fn header(&mut self) -> Option<()> {
        // Make sure the magic number is what's expected.
        if self.r.read_le_u32_() != MAGIC { return None }

        let flg = match self.r.read_byte() { Some(i) => i, None => return None };
        let bd = match self.r.read_byte() { Some(i) => i, None => return None };

        // bits 7/6, the version number. Right now this must be 1
        if (flg >> 6) != 0b01 { return None }
        // bit 5 is the "block independence", don't care about this yet
        // bit 4 is whether blocks have checksums or not
        self.blk_checksum = (flg & 0x10) != 0;
        // bit 3 is whether there is a following stream size
        let stream_size = (flg & 0x08) != 0;
        // bit 2 is whether there is a stream checksum
        self.stream_checksum = (flg & 0x04) != 0;
        // bit 1 is reserved
        // bit 0 is whether there is a preset dictionary
        let preset_dictionary = (flg & 0x01) != 0;

        static MAX_SIZES: [uint, ..8] =
            [0, 0, 0, 0, // all N/A
             64 << 10,   // 64KB
             256 << 10,  // 256 KB
             1 << 20,    // 1MB
             4 << 20];   // 4MB

        // bit 7 is reserved
        // bits 6-4 are the maximum block size
        let max_block_size = MAX_SIZES[(bd >> 4) & 0x7];
        // bits 3-0 are reserved

        // read off other portions of the stream
        let size = if stream_size {Some(self.r.read_le_u64_())} else {None};
        assert!(!preset_dictionary, "preset dictionaries not supported yet");

        debug!("blk: {}", self.blk_checksum);
        debug!("stream: {}", self.stream_checksum);
        debug!("max size: {}", max_block_size);
        debug!("stream size: {:?}", size);

        // XXX: implement checksums
        let cksum = self.r.read_byte();
        debug!("ignoring header checksum: {:?}", cksum);
        return Some(());
    }
}

impl<R: io::Reader + io::Seek> io::Reader for Decoder<R> {
    fn read(&mut self, buf: &mut [u8]) -> Option<uint> {
        info!("reading {}", buf.len());
        let out = self.exec(self.state, buf);
        info!("got {:?}", out);
        out
    }

    fn eof(&mut self) -> bool { fail!() }
}

#[cfg(test)]
mod test {
    use super::Decoder;
    use std::rt::io::extensions::ReaderUtil;
    use std::rt::io::mem::BufReader;
    use std::rt::io::Reader;
    use std::rand;

    fn test_decode(input: &[u8], output: &[u8]) {
        let mut d = Decoder::new(BufReader::new(input));

        let got = d.read_to_end();
        assert!(got.as_slice() == output);
    }

    #[test]
    fn decode() {
        let reference = include_bin!("data/test.txt");
        test_decode(include_bin!("data/test.lz4.1"), reference);
        test_decode(include_bin!("data/test.lz4.2"), reference);
        test_decode(include_bin!("data/test.lz4.3"), reference);
        test_decode(include_bin!("data/test.lz4.4"), reference);
        test_decode(include_bin!("data/test.lz4.5"), reference);
        test_decode(include_bin!("data/test.lz4.6"), reference);
        test_decode(include_bin!("data/test.lz4.7"), reference);
        test_decode(include_bin!("data/test.lz4.8"), reference);
        test_decode(include_bin!("data/test.lz4.9"), reference);
    }

    #[test]
    fn one_byte_at_a_time() {
        let input = include_bin!("data/test.lz4.1");
        let mut d = Decoder::new(BufReader::new(input));
        let mut out = ~[];
        loop {
            match d.read_byte() {
                Some(b) => out.push(b),
                None => break
            }
        }
        assert!(out.as_slice() == include_bin!("data/test.txt"));
    }

    #[test]
    fn random_byte_lengths() {
        let input = include_bin!("data/test.lz4.1");
        let mut d = Decoder::new(BufReader::new(input));
        let mut out = ~[];
        let mut buf = [0u8, ..40];
        loop {
            match d.read(buf.mut_slice_to(1 + rand::random::<uint>() % 40)) {
                Some(n) => {
                    out.push_all(buf.slice_to(n));
                }
                None => break
            }
        }
        assert!(out.as_slice() == include_bin!("data/test.txt"));
    }
}
<|endoftext|>


<commit_before>#![feature(conservative_impl_trait)]

extern crate calx;
#[macro_use]
extern crate calx_ecs;
#[macro_use]
extern crate error_chain;
extern crate euclid;
extern crate image;
#[macro_use]
extern crate lazy_static;
extern crate rand;
extern crate ron;
extern crate serde;
#[macro_use]
extern crate serde_derive;

use calx::Deciban;
use std::collections::HashMap;

/// Helper macro for formatting textual event messages.
macro_rules! msg {
    ($ctx: expr, $fmt:expr) => {
        $crate::MessageFormatter0::new($ctx, $fmt.to_string())
    };

    ($ctx: expr, $fmt:expr, $($arg:expr),*) => {
        {
            let __txt = format!($fmt, $($arg),*);
            $crate::MessageFormatter0::new($ctx, __txt)
        }
    };
}

// XXX: Lots of code for pretty simple end result on the MessageFormatter...
#[must_use]
pub struct MessageFormatter0<'a, W: 'a> {
    world: &'a mut W,
    msg: String,
}

#[must_use]
pub struct MessageFormatter1<'a, W: 'a> {
    world: &'a mut W,
    subject: grammar::Noun,
    msg: String,
}

#[must_use]
pub struct MessageFormatter2<'a, W: 'a> {
    world: &'a mut W,
    subject: grammar::Noun,
    object: grammar::Noun,
    msg: String,
}

impl<'a, W: mutate::Mutate> MessageFormatter0<'a, W> {
    pub fn new(world: &'a mut W, msg: String) -> MessageFormatter0<'a, W> {
        MessageFormatter0 { world, msg }
    }

    pub fn subject(self, e: calx_ecs::Entity) -> MessageFormatter1<'a, W> {
        let subject = self.world.noun(e);
        MessageFormatter1 {
            world: self.world,
            subject,
            msg: self.msg,
        }
    }

    pub fn send(self) {
        use grammar::Templater;
        let event = Event::Msg(grammar::EmptyTemplater.format(&self.msg).unwrap());
        self.world.push_event(event);
    }
}

impl<'a, W: mutate::Mutate> MessageFormatter1<'a, W> {
    pub fn object(self, e: calx_ecs::Entity) -> MessageFormatter2<'a, W> {
        let object = self.world.noun(e);
        MessageFormatter2 {
            world: self.world,
            subject: self.subject,
            object,
            msg: self.msg,
        }
    }

    pub fn send(self) {
        use grammar::Templater;
        let event = Event::Msg(
            grammar::SubjectTemplater::new(self.subject)
                .format(&self.msg)
                .unwrap(),
        );
        self.world.push_event(event);
    }
}

impl<'a, W: mutate::Mutate> MessageFormatter2<'a, W> {
    pub fn send(self) {
        use grammar::Templater;
        let event = Event::Msg(
            grammar::ObjectTemplater::new(
                grammar::SubjectTemplater::new(self.subject),
                self.object,
            ).format(&self.msg)
                .unwrap(),
        );
        self.world.push_event(event);
    }
}

mod command;
pub use command::{Command, CommandResult};

mod components;
pub use components::Icon;

mod effect;

mod event;
pub use event::Event;

mod flags;

mod fov;

mod grammar;

mod item;
pub use item::{ItemType, Slot};

mod location;
pub use location::{Location, Portal, Sector, SECTOR_HEIGHT, SECTOR_WIDTH};

mod location_set;

mod mapfile;
pub use mapfile::{load_prefab, save_prefab};

pub mod mapgen;

mod mutate;
pub use mutate::Mutate;

mod query;
pub use query::Query;

mod spatial;
mod spec;
mod stats;

mod terraform;
pub use terraform::{Terraform, TerrainQuery};

pub mod terrain;
pub use terrain::Terrain;

mod volume;

mod world;
pub use world::{Ecs, World};

mod worldgen;
pub use worldgen::Room;

/// Standard Prefab type, terrain type and spawn name list.
pub type Prefab = HashMap<calx::CellVector, (Terrain, Vec<String>)>;

#[derive(Copy, Clone, Eq, PartialEq, Debug)]
pub enum FovStatus {
    Seen,
    Remembered,
}

pub type Rng = calx::EncodeRng<rand::XorShiftRng>;

/// The combat formula.
///
/// Given a deciban roll and the relevant stats, determine amount of damage dealt.
/// Advantage is attacker skill - target defense.
pub fn attack_damage(roll: f32, advantage: i32, weapon_power: i32) -> i32 {
    const MAX_DAMAGE_MULTIPLIER: f32 = 4.0;

    let roll = roll + advantage as f32;
    (weapon_power as f32 * calx::clamp(0.0, MAX_DAMAGE_MULTIPLIER, (roll - 2.0) * 0.05)) as i32
}

/// Standard deciban roll, clamp into [-20, 20].
pub fn roll<R: rand::Rng>(rng: &mut R) -> f32 { calx::clamp(-20.0, 20.0, rng.gen::<Deciban>().0) }

pub mod errors {
    error_chain! {
        foreign_links {
            Io(::std::io::Error);
            RonSerialize(::ron::ser::Error);
            RonDeserialize(::ron::de::Error);
            PrefabError(::calx::PrefabError);
        }
    }
}

/// Wrapper class for things that should not be serialized.
struct Cache<T> {
    inner: T,
}

impl<T: Default> Cache<T> {
    pub fn new() -> Cache<T> {
        Cache {
            inner: Default::default(),
        }
    }
}

impl<T> ::std::ops::Deref for Cache<T> {
    type Target = T;

    fn deref(&self) -> &T { &self.inner }
}

impl<T> ::std::ops::DerefMut for Cache<T> {
    fn deref_mut(&mut self) -> &mut T { &mut self.inner }
}
impl<T: Default> serde::Serialize for Cache<T> {
    fn serialize<S: serde::Serializer>(&self, s: S) -> Result<S::Ok, S::Error> { ().serialize(s) }
}

impl<'a, T: Default> serde::Deserialize<'a> for Cache<T> {
    fn deserialize<D: serde::Deserializer<'a>>(d: D) -> Result<Self, D::Error> {
        let _: () = serde::Deserialize::deserialize(d)?;
        Ok(Cache::new())
    }
}
<commit_msg>Feature is stable now<commit_after>extern crate calx;
#[macro_use]
extern crate calx_ecs;
#[macro_use]
extern crate error_chain;
extern crate euclid;
extern crate image;
#[macro_use]
extern crate lazy_static;
extern crate rand;
extern crate ron;
extern crate serde;
#[macro_use]
extern crate serde_derive;

use calx::Deciban;
use std::collections::HashMap;

/// Helper macro for formatting textual event messages.
macro_rules! msg {
    ($ctx: expr, $fmt:expr) => {
        $crate::MessageFormatter0::new($ctx, $fmt.to_string())
    };

    ($ctx: expr, $fmt:expr, $($arg:expr),*) => {
        {
            let __txt = format!($fmt, $($arg),*);
            $crate::MessageFormatter0::new($ctx, __txt)
        }
    };
}

// XXX: Lots of code for pretty simple end result on the MessageFormatter...
#[must_use]
pub struct MessageFormatter0<'a, W: 'a> {
    world: &'a mut W,
    msg: String,
}

#[must_use]
pub struct MessageFormatter1<'a, W: 'a> {
    world: &'a mut W,
    subject: grammar::Noun,
    msg: String,
}

#[must_use]
pub struct MessageFormatter2<'a, W: 'a> {
    world: &'a mut W,
    subject: grammar::Noun,
    object: grammar::Noun,
    msg: String,
}

impl<'a, W: mutate::Mutate> MessageFormatter0<'a, W> {
    pub fn new(world: &'a mut W, msg: String) -> MessageFormatter0<'a, W> {
        MessageFormatter0 { world, msg }
    }

    pub fn subject(self, e: calx_ecs::Entity) -> MessageFormatter1<'a, W> {
        let subject = self.world.noun(e);
        MessageFormatter1 {
            world: self.world,
            subject,
            msg: self.msg,
        }
    }

    pub fn send(self) {
        use grammar::Templater;
        let event = Event::Msg(grammar::EmptyTemplater.format(&self.msg).unwrap());
        self.world.push_event(event);
    }
}

impl<'a, W: mutate::Mutate> MessageFormatter1<'a, W> {
    pub fn object(self, e: calx_ecs::Entity) -> MessageFormatter2<'a, W> {
        let object = self.world.noun(e);
        MessageFormatter2 {
            world: self.world,
            subject: self.subject,
            object,
            msg: self.msg,
        }
    }

    pub fn send(self) {
        use grammar::Templater;
        let event = Event::Msg(
            grammar::SubjectTemplater::new(self.subject)
                .format(&self.msg)
                .unwrap(),
        );
        self.world.push_event(event);
    }
}

impl<'a, W: mutate::Mutate> MessageFormatter2<'a, W> {
    pub fn send(self) {
        use grammar::Templater;
        let event = Event::Msg(
            grammar::ObjectTemplater::new(
                grammar::SubjectTemplater::new(self.subject),
                self.object,
            ).format(&self.msg)
                .unwrap(),
        );
        self.world.push_event(event);
    }
}

mod command;
pub use command::{Command, CommandResult};

mod components;
pub use components::Icon;

mod effect;

mod event;
pub use event::Event;

mod flags;

mod fov;

mod grammar;

mod item;
pub use item::{ItemType, Slot};

mod location;
pub use location::{Location, Portal, Sector, SECTOR_HEIGHT, SECTOR_WIDTH};

mod location_set;

mod mapfile;
pub use mapfile::{load_prefab, save_prefab};

pub mod mapgen;

mod mutate;
pub use mutate::Mutate;

mod query;
pub use query::Query;

mod spatial;
mod spec;
mod stats;

mod terraform;
pub use terraform::{Terraform, TerrainQuery};

pub mod terrain;
pub use terrain::Terrain;

mod volume;

mod world;
pub use world::{Ecs, World};

mod worldgen;
pub use worldgen::Room;

/// Standard Prefab type, terrain type and spawn name list.
pub type Prefab = HashMap<calx::CellVector, (Terrain, Vec<String>)>;

#[derive(Copy, Clone, Eq, PartialEq, Debug)]
pub enum FovStatus {
    Seen,
    Remembered,
}

pub type Rng = calx::EncodeRng<rand::XorShiftRng>;

/// The combat formula.
///
/// Given a deciban roll and the relevant stats, determine amount of damage dealt.
/// Advantage is attacker skill - target defense.
pub fn attack_damage(roll: f32, advantage: i32, weapon_power: i32) -> i32 {
    const MAX_DAMAGE_MULTIPLIER: f32 = 4.0;

    let roll = roll + advantage as f32;
    (weapon_power as f32 * calx::clamp(0.0, MAX_DAMAGE_MULTIPLIER, (roll - 2.0) * 0.05)) as i32
}

/// Standard deciban roll, clamp into [-20, 20].
pub fn roll<R: rand::Rng>(rng: &mut R) -> f32 { calx::clamp(-20.0, 20.0, rng.gen::<Deciban>().0) }

pub mod errors {
    error_chain! {
        foreign_links {
            Io(::std::io::Error);
            RonSerialize(::ron::ser::Error);
            RonDeserialize(::ron::de::Error);
            PrefabError(::calx::PrefabError);
        }
    }
}

/// Wrapper class for things that should not be serialized.
struct Cache<T> {
    inner: T,
}

impl<T: Default> Cache<T> {
    pub fn new() -> Cache<T> {
        Cache {
            inner: Default::default(),
        }
    }
}

impl<T> ::std::ops::Deref for Cache<T> {
    type Target = T;

    fn deref(&self) -> &T { &self.inner }
}

impl<T> ::std::ops::DerefMut for Cache<T> {
    fn deref_mut(&mut self) -> &mut T { &mut self.inner }
}
impl<T: Default> serde::Serialize for Cache<T> {
    fn serialize<S: serde::Serializer>(&self, s: S) -> Result<S::Ok, S::Error> { ().serialize(s) }
}

impl<'a, T: Default> serde::Deserialize<'a> for Cache<T> {
    fn deserialize<D: serde::Deserializer<'a>>(d: D) -> Result<Self, D::Error> {
        let _: () = serde::Deserialize::deserialize(d)?;
        Ok(Cache::new())
    }
}
<|endoftext|>


<commit_before>mod serialize;

use self::serialize::{Media, MediaDate, MediaListEntry, MediaStatus, ScoreFormat, User};
use super::{AnimeEntry, AnimeInfo, ScoreParser, Status, SyncBackend};
use config::Config;
use error::BackendError;
use input;
use process;
use reqwest::header::{Accept, Authorization, Bearer, ContentType, Headers};
use reqwest::{Client, Response};
use serde_json as json;
use std::borrow::Cow;

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 init(offline_mode: bool, config: &mut Config) -> Result<AniList, BackendError> {
        if offline_mode {
            let anilist = AniList {
                client: Client::new(),
                user: User::default(),
                access_token: String::new(),
            };

            return Ok(anilist);
        }

        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: Media = 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: Media = 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.unwrap_or(0.0),
                "status": MediaStatus::from(entry.status),
                "start_date": MediaDate::from_date(entry.start_date),
                "finish_date": MediaDate::from_date(entry.finish_date),
            },
        )?;

        Ok(())
    }
}

impl ScoreParser for AniList {
    fn formatted_score_range(&self) -> (Cow<str>, Cow<str>) {
        match self.user.list_options.score_format {
            ScoreFormat::Point3 => (":(".into(), ":)".into()),
            ScoreFormat::Point10Decimal => ("1.0".into(), "10.0".into()),
            format => ("1".into(), format.max_score().to_string().into()),
        }
    }

    fn parse_score(&self, input: &str) -> Result<f32, BackendError> {
        match self.user.list_options.score_format {
            ScoreFormat::Point3 => match input {
                ":(" => Ok(1.0),
                ":|" => Ok(2.0),
                ":)" => Ok(3.0),
                _ => Err(BackendError::UnknownScoreValue(input.into())),
            },
            format => {
                let value = input.parse::<f32>()?;
                let max_score = f32::from(format.max_score());

                if value < 1.0 || value > max_score {
                    return Err(BackendError::OutOfRangeScore);
                }

                Ok(value)
            }
        }
    }

    fn format_score(&self, raw_score: f32) -> Result<String, BackendError> {
        match self.user.list_options.score_format {
            ScoreFormat::Point3 => match raw_score.round() as u32 {
                1 => Ok(":(".into()),
                2 => Ok(":|".into()),
                3 => Ok(":)".into()),
                _ => Err(BackendError::OutOfRangeScore),
            },
            ScoreFormat::Point5 | ScoreFormat::Point10 | ScoreFormat::Point100 => {
                let value = raw_score.round() as u32;
                Ok(value.to_string())
            }
            ScoreFormat::Point10Decimal => Ok(raw_score.to_string()),
        }
    }
}

fn try_open_url(url: &str) {
    match process::open_with_default(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);
            }
        }
    }
}
<commit_msg>[AniList] Reduce size of API requests by ~30% on average<commit_after>mod serialize;

use self::serialize::{Media, MediaDate, MediaListEntry, MediaStatus, ScoreFormat, User};
use super::{AnimeEntry, AnimeInfo, ScoreParser, Status, SyncBackend};
use config::Config;
use error::BackendError;
use input;
use process;
use reqwest::header::{Accept, Authorization, Bearer, ContentType, Headers};
use reqwest::{Client, Response};
use serde_json as json;
use std::borrow::Cow;

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)*
        });

        let query = minimize_graphql_query($query_str);

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

fn minimize_graphql_query(query: &str) -> String {
    let mut minimized = String::from(query);
    minimized.retain(|c| c != ' ');

    minimized
}

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 init(offline_mode: bool, config: &mut Config) -> Result<AniList, BackendError> {
        if offline_mode {
            let anilist = AniList {
                client: Client::new(),
                user: User::default(),
                access_token: String::new(),
            };

            return Ok(anilist);
        }

        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: Media = 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: Media = 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.unwrap_or(0.0),
                "status": MediaStatus::from(entry.status),
                "start_date": MediaDate::from_date(entry.start_date),
                "finish_date": MediaDate::from_date(entry.finish_date),
            },
        )?;

        Ok(())
    }
}

impl ScoreParser for AniList {
    fn formatted_score_range(&self) -> (Cow<str>, Cow<str>) {
        match self.user.list_options.score_format {
            ScoreFormat::Point3 => (":(".into(), ":)".into()),
            ScoreFormat::Point10Decimal => ("1.0".into(), "10.0".into()),
            format => ("1".into(), format.max_score().to_string().into()),
        }
    }

    fn parse_score(&self, input: &str) -> Result<f32, BackendError> {
        match self.user.list_options.score_format {
            ScoreFormat::Point3 => match input {
                ":(" => Ok(1.0),
                ":|" => Ok(2.0),
                ":)" => Ok(3.0),
                _ => Err(BackendError::UnknownScoreValue(input.into())),
            },
            format => {
                let value = input.parse::<f32>()?;
                let max_score = f32::from(format.max_score());

                if value < 1.0 || value > max_score {
                    return Err(BackendError::OutOfRangeScore);
                }

                Ok(value)
            }
        }
    }

    fn format_score(&self, raw_score: f32) -> Result<String, BackendError> {
        match self.user.list_options.score_format {
            ScoreFormat::Point3 => match raw_score.round() as u32 {
                1 => Ok(":(".into()),
                2 => Ok(":|".into()),
                3 => Ok(":)".into()),
                _ => Err(BackendError::OutOfRangeScore),
            },
            ScoreFormat::Point5 | ScoreFormat::Point10 | ScoreFormat::Point100 => {
                let value = raw_score.round() as u32;
                Ok(value.to_string())
            }
            ScoreFormat::Point10Decimal => Ok(raw_score.to_string()),
        }
    }
}

fn try_open_url(url: &str) {
    match process::open_with_default(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);
            }
        }
    }
}
<|endoftext|>