<commit_before>extern mod std;

use core::libc::{c_char, c_int, c_void};
use core::option::{Some, None};
use core::result::{Ok, Err};
use core::either::Either;
use core::result::Result;
use consts::*;

enum Pcre {}
enum PcreExtra {}
struct PcreRes {
    p: *Pcre,
    drop { unsafe { c::free(self.p as *c_void); } }
}

#[doc = "
The result type of <compile>.
"]
pub type CompileResult = Result<Pattern, CompileErr>;

#[doc = "
The result type of <exec>.
"]
pub type ExecResult = Result<Match, ExecErr>;

#[doc = "
The result type of <search>.
"]
pub type SearchResult = Result<Match, RegexErr>;

#[doc = "
The result type of <replace>.
"]
pub type ReplaceResult = Result<@~str, RegexErr>;

#[doc = "
The type that represents compile error.
"]
pub struct CompileErr {
    code: int,
    reason: @~str,
    offset: uint,
}

#[doc = "
The type that represents exec error.
"]
pub type ExecErr = int;

#[doc = "
Either compile or exec error.
"]
pub enum RegexErr {
    CompileErr(CompileErr),
    ExecErr(ExecErr),
}

pub struct Pattern {
    str: @~str,
    priv pcre_res: @PcreRes,
}

pub struct Match {
    subject: @~str,
    pattern: Pattern,
    priv captures: @~[uint],
}

#[nolink]
#[abi = "cdecl"]
extern mod c {
    fn free(p: *c_void);
}

extern mod pcre {
    fn pcre_compile2(pattern: *c_char, options: c_int,
                     errorcodeptr: *c_int,
                     errptr: **c_char, erroffset: *c_int,
                     tableptr: *c_char) -> *Pcre;
    fn pcre_exec(code: *Pcre, extra: *PcreExtra,
                 subject: *c_char, length: c_int, startoffset: c_int,
                 options: c_int, ovector: * c_int, ovecsize: c_int) -> c_int;
    fn pcre_fullinfo(code: *Pcre, extra: *PcreExtra,
                     what: c_int, where: *c_void) -> c_int;
    fn pcre_get_stringnumber(code: *Pcre, name: *c_char) -> c_int;
}

pub trait PatternUtil {
    fn info_capture_count(self) -> uint;
    fn info_name_count(self) -> uint;
    fn info_name_entry_size(self) -> uint;
    fn with_name_table(self, blk: fn(*u8));
    fn group_count(self) -> uint;
    fn group_names(self) -> ~[~str];
}

impl Pattern: PatternUtil {
    fn info_capture_count(self) -> uint {
        let count = -1 as c_int;
        unsafe {
            pcre::pcre_fullinfo(self.pcre_res.p, ptr::null(),
                                PCRE_INFO_CAPTURECOUNT as c_int,
                                ptr::addr_of(&count) as *c_void);
        }
        assert count >= 0 as c_int;
        return count as uint;
    }

    fn info_name_count(self) -> uint {
        let count = -1 as c_int;
        unsafe {
            pcre::pcre_fullinfo(self.pcre_res.p, ptr::null(),
                                PCRE_INFO_NAMECOUNT as c_int,
                                ptr::addr_of(&count) as *c_void);
        }
        assert count >= 0 as c_int;
        return count as uint;
    }

    fn info_name_entry_size(self) -> uint {
        let size = -1 as c_int;
        unsafe {
            pcre::pcre_fullinfo(self.pcre_res.p, ptr::null(),
                                PCRE_INFO_NAMEENTRYSIZE as c_int,
                                ptr::addr_of(&size) as *c_void);
        }
        assert size >= 0 as c_int;
        return size as uint;
    }

    fn with_name_table(self, blk: fn(*u8)) {
        let table = ptr::null::<u8>();
        unsafe {
            pcre::pcre_fullinfo(self.pcre_res.p, ptr::null(),
                                PCRE_INFO_NAMETABLE as c_int,
                                ptr::addr_of(&table) as *c_void);
        }
        assert table != ptr::null::<u8>();
        blk(table);
    }

    fn group_count(self) -> uint {
        return self.info_capture_count();
    }

    fn group_names(self) -> ~[~str] {
        let count = self.info_name_count();
        if count == 0u { return ~[]; }
        let size = self.info_name_entry_size();
        let mut names: ~[~str] = ~[];
        do self.with_name_table |table| {
            for uint::range(0u, count) |i| {
                unsafe {
                    let p = ptr::offset(table, size * i + 2u);
                    let s = str::raw::from_c_str(p as *c_char);
                    vec::push(&mut names, s);
                }
            }
        }
        return names;
    }
}

pub trait PatternLike {
    fn compile(&self, options: int) -> CompileResult;
}

impl &str: PatternLike {
    fn compile(&self, options: int) -> CompileResult { compile(*self, options) }
}

impl ~str: PatternLike {
    fn compile(&self, options: int) -> CompileResult { compile(*self, options) }
}

impl @str: PatternLike {
    fn compile(&self, options: int) -> CompileResult { compile(*self, options) }
}

impl Pattern: PatternLike {
    fn compile(&self, _options: int) -> CompileResult { Ok(*self) }
}

impl CompileResult: PatternLike {
    fn compile(&self, _options: int) -> CompileResult { *self }
}

pub trait MatchExtensions {
    fn matched(self) -> ~str;
    fn prematch(self) -> ~str;
    fn postmatch(self) -> ~str;
    fn begin(self) -> uint;
    fn end(self) -> uint;
    fn group(self, i: uint) -> Option<@~str>;
    fn named_group(self, name: &str) -> Option<@~str>;
    fn subgroups(self) -> ~[~str];
    fn subgroups_iter(self, blk: fn(&str));
    fn group_count(self) -> uint;
    fn group_names(self) -> ~[~str];
}

impl Match: MatchExtensions {
    fn matched(self) -> ~str {
        return str::slice(*self.subject, self.begin(), self.end());
    }

    fn prematch(self) -> ~str {
        return str::slice(*self.subject, 0u, self.begin());
    }

    fn postmatch(self) -> ~str {
        return str::slice(*self.subject ,self.end(),
                          str::char_len(*self.subject));
    }

    fn begin(self) -> uint {
        return self.captures[0];
    }

    fn end(self) -> uint {
        return self.captures[1];
    }

    fn group(self, i: uint) -> Option<@~str> {
        if i > self.group_count() {
            return None;
        }
        return Some(@str::slice(*self.subject,
                                self.captures[i * 2u],
                                self.captures[i * 2u + 1u]));
    }

    fn named_group(self, name: &str) -> Option<@~str> {
        let i =  unsafe {
            str::as_buf(name, |s, _n| {
                pcre::pcre_get_stringnumber(self.pattern.pcre_res.p, s as *c_char)
            })
        };
        if i <= 0 as c_int { return None; }
        return self.group(i as uint);
    }

    fn subgroups(self) -> ~[~str] {
        let mut v = ~[];
        vec::reserve(&mut v, self.group_count());
        do self.subgroups_iter |subgroup| { vec::push(&mut v, str::from_slice(subgroup)); }
        return v;
    }

    fn subgroups_iter(self, blk: fn(&str)) {
        for uint::range(1u, self.group_count() + 1u) |i| {
            match self.group(i) {
              Some(s) => blk(*s),
              None => die!(),
            }
        }
    }

    fn group_count(self) -> uint {
        return vec::len(*self.captures) / 2u - 1u;
    }

    fn group_names(self) -> ~[~str] {
        return self.pattern.group_names();
    }
}

pub fn compile(pattern: &str, options: int) -> CompileResult {
    if options | COMPILE_OPTIONS != COMPILE_OPTIONS {
        warn!("unrecognized option bit(s) are set");
    }

    let options = options | PCRE_NO_UTF8_CHECK; // str is always valid
    let errcode = 0 as c_int;
    let errreason: *c_char = ptr::null();
    let erroffset = 0 as c_int;
    let p = unsafe {
        str::as_buf(pattern, |pat, _n| {
            pcre::pcre_compile2(pat as *c_char,
                                options as c_int,
                                ptr::addr_of(&errcode),
                                ptr::addr_of(&errreason),
                                ptr::addr_of(&erroffset),
                                ptr::null())
        })
    };
    if p == ptr::null() {
        return Err(CompileErr {code: errcode as int,
                               reason: unsafe { @str::raw::from_c_str(errreason) },
                               offset: erroffset as uint});
    }
    return Ok(Pattern {str: @str::from_slice(pattern), pcre_res: @PcreRes {p: p}});
}

pub fn exec(pattern: Pattern,
        subject: &str, offset: uint,
        options: int) -> ExecResult {

    if (options | EXEC_OPTIONS) != EXEC_OPTIONS {
        warn!("unrecognized option bit(s) are set");
    }

    let options = options | PCRE_NO_UTF8_CHECK; // str is always valid

    let count = (pattern.info_capture_count() + 1u) as c_int;
    let mut ovec = vec::from_elem((count as uint) * 3u, 0u as c_int);

    let ret_code = unsafe {
        str::as_buf(subject, |s, _n| {
            pcre::pcre_exec(pattern.pcre_res.p, ptr::null(),
                            s as *c_char, str::len(subject) as c_int,
                            offset as c_int, options as c_int,
                            vec::raw::to_ptr(ovec) as *c_int,
                            count * (3 as c_int)) as int
        })
    };

    if ret_code < 0 { return Err(ret_code as ExecErr); }

    // Cut off the working space
    unsafe { vec::raw::set_len(&mut ovec, count as uint * 2u) }

    let mut captures: ~[uint] = ~[];
    vec::reserve(&mut captures, vec::len(ovec));
    for ovec.each |o| {
        if *o as int < 0 { loop; }
        vec::push(&mut captures, *o as uint);
    }
    assert vec::len(captures) % 2u == 0u;

    return Ok(Match {subject: @str::from_slice(subject), pattern: pattern, captures: @captures});
}

pub fn search<T: PatternLike>(pattern: T, subject: &str,
                              options: int) -> SearchResult {
    return search_from(pattern, subject, 0u, options);
}

pub fn search_from<T: PatternLike>(pattern: T, subject: &str,
                                   offset: uint, options: int) -> SearchResult {
    assert offset <= str::len(subject);

    let c_opts = options & COMPILE_OPTIONS;
    let e_opts = options & EXEC_OPTIONS;

    let c = pattern.compile(c_opts);
    match c {
        Ok(pattern) => {
            let e = exec(pattern, subject, offset, e_opts);
            match e {
                Ok(m) => {
                    return Ok(m);
                }
                Err(e_err) => {
                    return Err(ExecErr(e_err));
                }
            }
        }
        Err(c_err) => {
            return Err(CompileErr(c_err));
        }
    }
}

pub fn replace<T: PatternLike Copy>(pattern: T, subject: &str, repl: &str,
                                    options: int) -> ReplaceResult {
    return replace_fn_from(pattern, subject, |_m| { str::from_slice(repl) }, 0u, options);
}

pub fn replace_from<T: PatternLike Copy>(pattern: T, subject: &str, repl: &str,
                                         offset: uint, options: int)
                                         -> ReplaceResult {
    return replace_fn_from(pattern, subject, |_m| { str::from_slice(repl) }, offset, options);
}

pub fn replace_fn<T: PatternLike Copy>(pattern: T, subject: &str,
                                       repl_fn: fn(Match) -> ~str, options: int)
                                       -> ReplaceResult {
    return replace_fn_from(pattern, subject, repl_fn, 0u, options);
}

pub fn replace_fn_from<T: PatternLike Copy>(pattern: T, subject: &str,
                                            repl_fn: fn(Match) -> ~str, offset: uint,
                                            options: int)
                                            -> ReplaceResult {
    let r = search_from(pattern, subject, offset, options);
    match r {
        Ok(m) => {
            return Ok(@(m.prematch() + repl_fn(m) + m.postmatch()));
        }
        Err(e) => { return Err(e); }
    }
}

pub fn replace_all<T: PatternLike Copy>(pattern: T, subject: &str,
                                        repl: &str,
                                        options: int)
                                        -> ReplaceResult {
    return replace_all_fn_from(pattern, subject, |_m| { str::from_slice(repl) }, 0u, options);
}

pub fn replace_all_fn<T: PatternLike Copy>(pattern: T, subject: &str,
                                           repl_fn: fn(Match) -> ~str,
                                           options: int)
                                           -> ReplaceResult {
    return replace_all_fn_from(pattern, subject, repl_fn, 0u, options);
}

pub fn replace_all_from<T: PatternLike Copy>(pattern: T, subject: &str,
                                             repl: &str,
                                             offset: uint,
                                             options: int)
                                             -> ReplaceResult {
    return replace_all_fn_from(pattern, subject, |_m| { str::from_slice(repl) }, offset, options);
}

pub fn replace_all_fn_from<T: PatternLike Copy>(pattern: T, subject: &str,
                                                repl_fn: fn(Match) -> ~str,
                                                offset: uint,
                                                options: int)
                                                -> ReplaceResult {
    let mut offset = offset;
    let subject_len = str::len(subject);
    assert offset <= subject_len;

    let mut s = str::slice(subject, 0, offset);
    loop {
        let r = search_from(pattern, subject, offset, options);
        match r {
            Ok(m) => {
                s += str::slice(subject, offset, m.begin());
                s += repl_fn(m);
                offset = m.end();
            }
            Err(ExecErr(e)) if e == PCRE_ERROR_NOMATCH => {
                if offset != subject_len {
                    s += str::slice(subject, offset, subject_len);
                }
                break;
            }
            Err(e) => {
                return Err(copy e);
            }
        }
    }
    return Ok(@s);
}

pub fn fmt_compile_err(e: CompileErr) -> ~str {
    return fmt!("error %d: %s at offset %u", e.code, *e.reason, e.offset);
}

#[doc = "
Returns true iff sr indicates that the subject did not match the pattern.
"]
pub pure fn is_nomatch(sr: SearchResult) -> bool {
    match sr {
      Err(ExecErr(e)) if e == PCRE_ERROR_NOMATCH => true,
      _ => false,
    }
}
<commit_msg>Rustdoc-ify<commit_after>extern mod std;

use core::libc::{c_char, c_int, c_void};
use core::option::{Some, None};
use core::result::{Ok, Err};
use core::either::Either;
use core::result::Result;
use consts::*;

enum Pcre {}
enum PcreExtra {}
struct PcreRes {
    p: *Pcre,
    drop { unsafe { c::free(self.p as *c_void); } }
}

/// The result type of `compile`
pub type CompileResult = Result<Pattern, CompileErr>;

/// The result type of `exec`
pub type ExecResult = Result<Match, ExecErr>;

/// The result type of `search`
pub type SearchResult = Result<Match, RegexErr>;

// The result type of `replace`
pub type ReplaceResult = Result<@~str, RegexErr>;

/// The type that represents compile error
pub struct CompileErr {
    code: int,
    reason: @~str,
    offset: uint,
}

/// The type that represents exec error
pub type ExecErr = int;

/// Either compile or exec error
pub enum RegexErr {
    CompileErr(CompileErr),
    ExecErr(ExecErr),
}

/// Compiled regular expression
pub struct Pattern {
    str: @~str,
    priv pcre_res: @PcreRes,
}

/// Match
pub struct Match {
    subject: @~str,
    pattern: Pattern,
    priv captures: @~[uint],
}

#[nolink]
#[abi = "cdecl"]
extern mod c {
    fn free(p: *c_void);
}

extern mod pcre {
    fn pcre_compile2(pattern: *c_char, options: c_int,
                     errorcodeptr: *c_int,
                     errptr: **c_char, erroffset: *c_int,
                     tableptr: *c_char) -> *Pcre;
    fn pcre_exec(code: *Pcre, extra: *PcreExtra,
                 subject: *c_char, length: c_int, startoffset: c_int,
                 options: c_int, ovector: * c_int, ovecsize: c_int) -> c_int;
    fn pcre_fullinfo(code: *Pcre, extra: *PcreExtra,
                     what: c_int, where: *c_void) -> c_int;
    fn pcre_get_stringnumber(code: *Pcre, name: *c_char) -> c_int;
}

pub trait PatternUtil {
    fn info_capture_count(self) -> uint;
    fn info_name_count(self) -> uint;
    fn info_name_entry_size(self) -> uint;
    fn with_name_table(self, blk: fn(*u8));
    fn group_count(self) -> uint;
    fn group_names(self) -> ~[~str];
}

impl Pattern: PatternUtil {
    fn info_capture_count(self) -> uint {
        let count = -1 as c_int;
        unsafe {
            pcre::pcre_fullinfo(self.pcre_res.p, ptr::null(),
                                PCRE_INFO_CAPTURECOUNT as c_int,
                                ptr::addr_of(&count) as *c_void);
        }
        assert count >= 0 as c_int;
        return count as uint;
    }

    fn info_name_count(self) -> uint {
        let count = -1 as c_int;
        unsafe {
            pcre::pcre_fullinfo(self.pcre_res.p, ptr::null(),
                                PCRE_INFO_NAMECOUNT as c_int,
                                ptr::addr_of(&count) as *c_void);
        }
        assert count >= 0 as c_int;
        return count as uint;
    }

    fn info_name_entry_size(self) -> uint {
        let size = -1 as c_int;
        unsafe {
            pcre::pcre_fullinfo(self.pcre_res.p, ptr::null(),
                                PCRE_INFO_NAMEENTRYSIZE as c_int,
                                ptr::addr_of(&size) as *c_void);
        }
        assert size >= 0 as c_int;
        return size as uint;
    }

    fn with_name_table(self, blk: fn(*u8)) {
        let table = ptr::null::<u8>();
        unsafe {
            pcre::pcre_fullinfo(self.pcre_res.p, ptr::null(),
                                PCRE_INFO_NAMETABLE as c_int,
                                ptr::addr_of(&table) as *c_void);
        }
        assert table != ptr::null::<u8>();
        blk(table);
    }

    fn group_count(self) -> uint {
        return self.info_capture_count();
    }

    fn group_names(self) -> ~[~str] {
        let count = self.info_name_count();
        if count == 0u { return ~[]; }
        let size = self.info_name_entry_size();
        let mut names: ~[~str] = ~[];
        do self.with_name_table |table| {
            for uint::range(0u, count) |i| {
                unsafe {
                    let p = ptr::offset(table, size * i + 2u);
                    let s = str::raw::from_c_str(p as *c_char);
                    vec::push(&mut names, s);
                }
            }
        }
        return names;
    }
}

pub trait PatternLike {
    fn compile(&self, options: int) -> CompileResult;
}

impl &str: PatternLike {
    fn compile(&self, options: int) -> CompileResult { compile(*self, options) }
}

impl ~str: PatternLike {
    fn compile(&self, options: int) -> CompileResult { compile(*self, options) }
}

impl @str: PatternLike {
    fn compile(&self, options: int) -> CompileResult { compile(*self, options) }
}

impl Pattern: PatternLike {
    fn compile(&self, _options: int) -> CompileResult { Ok(*self) }
}

impl CompileResult: PatternLike {
    fn compile(&self, _options: int) -> CompileResult { *self }
}

pub trait MatchExtensions {
    fn matched(self) -> ~str;
    fn prematch(self) -> ~str;
    fn postmatch(self) -> ~str;
    fn begin(self) -> uint;
    fn end(self) -> uint;
    fn group(self, i: uint) -> Option<@~str>;
    fn named_group(self, name: &str) -> Option<@~str>;
    fn subgroups(self) -> ~[~str];
    fn subgroups_iter(self, blk: fn(&str));
    fn group_count(self) -> uint;
    fn group_names(self) -> ~[~str];
}

impl Match: MatchExtensions {
    fn matched(self) -> ~str {
        return str::slice(*self.subject, self.begin(), self.end());
    }

    fn prematch(self) -> ~str {
        return str::slice(*self.subject, 0u, self.begin());
    }

    fn postmatch(self) -> ~str {
        return str::slice(*self.subject ,self.end(),
                          str::char_len(*self.subject));
    }

    fn begin(self) -> uint {
        return self.captures[0];
    }

    fn end(self) -> uint {
        return self.captures[1];
    }

    fn group(self, i: uint) -> Option<@~str> {
        if i > self.group_count() {
            return None;
        }
        return Some(@str::slice(*self.subject,
                                self.captures[i * 2u],
                                self.captures[i * 2u + 1u]));
    }

    fn named_group(self, name: &str) -> Option<@~str> {
        let i =  unsafe {
            str::as_buf(name, |s, _n| {
                pcre::pcre_get_stringnumber(self.pattern.pcre_res.p, s as *c_char)
            })
        };
        if i <= 0 as c_int { return None; }
        return self.group(i as uint);
    }

    fn subgroups(self) -> ~[~str] {
        let mut v = ~[];
        vec::reserve(&mut v, self.group_count());
        do self.subgroups_iter |subgroup| { vec::push(&mut v, str::from_slice(subgroup)); }
        return v;
    }

    fn subgroups_iter(self, blk: fn(&str)) {
        for uint::range(1u, self.group_count() + 1u) |i| {
            match self.group(i) {
              Some(s) => blk(*s),
              None => die!(),
            }
        }
    }

    fn group_count(self) -> uint {
        return vec::len(*self.captures) / 2u - 1u;
    }

    fn group_names(self) -> ~[~str] {
        return self.pattern.group_names();
    }
}

pub fn compile(pattern: &str, options: int) -> CompileResult {
    if options | COMPILE_OPTIONS != COMPILE_OPTIONS {
        warn!("unrecognized option bit(s) are set");
    }

    let options = options | PCRE_NO_UTF8_CHECK; // str is always valid
    let errcode = 0 as c_int;
    let errreason: *c_char = ptr::null();
    let erroffset = 0 as c_int;
    let p = unsafe {
        str::as_buf(pattern, |pat, _n| {
            pcre::pcre_compile2(pat as *c_char,
                                options as c_int,
                                ptr::addr_of(&errcode),
                                ptr::addr_of(&errreason),
                                ptr::addr_of(&erroffset),
                                ptr::null())
        })
    };
    if p == ptr::null() {
        return Err(CompileErr {code: errcode as int,
                               reason: unsafe { @str::raw::from_c_str(errreason) },
                               offset: erroffset as uint});
    }
    return Ok(Pattern {str: @str::from_slice(pattern), pcre_res: @PcreRes {p: p}});
}

pub fn exec(pattern: Pattern,
        subject: &str, offset: uint,
        options: int) -> ExecResult {

    if (options | EXEC_OPTIONS) != EXEC_OPTIONS {
        warn!("unrecognized option bit(s) are set");
    }

    let options = options | PCRE_NO_UTF8_CHECK; // str is always valid

    let count = (pattern.info_capture_count() + 1u) as c_int;
    let mut ovec = vec::from_elem((count as uint) * 3u, 0u as c_int);

    let ret_code = unsafe {
        str::as_buf(subject, |s, _n| {
            pcre::pcre_exec(pattern.pcre_res.p, ptr::null(),
                            s as *c_char, str::len(subject) as c_int,
                            offset as c_int, options as c_int,
                            vec::raw::to_ptr(ovec) as *c_int,
                            count * (3 as c_int)) as int
        })
    };

    if ret_code < 0 { return Err(ret_code as ExecErr); }

    // Cut off the working space
    unsafe { vec::raw::set_len(&mut ovec, count as uint * 2u) }

    let mut captures: ~[uint] = ~[];
    vec::reserve(&mut captures, vec::len(ovec));
    for ovec.each |o| {
        if *o as int < 0 { loop; }
        vec::push(&mut captures, *o as uint);
    }
    assert vec::len(captures) % 2u == 0u;

    return Ok(Match {subject: @str::from_slice(subject), pattern: pattern, captures: @captures});
}

pub fn search<T: PatternLike>(pattern: T, subject: &str,
                              options: int) -> SearchResult {
    return search_from(pattern, subject, 0u, options);
}

pub fn search_from<T: PatternLike>(pattern: T, subject: &str,
                                   offset: uint, options: int) -> SearchResult {
    assert offset <= str::len(subject);

    let c_opts = options & COMPILE_OPTIONS;
    let e_opts = options & EXEC_OPTIONS;

    let c = pattern.compile(c_opts);
    match c {
        Ok(pattern) => {
            let e = exec(pattern, subject, offset, e_opts);
            match e {
                Ok(m) => {
                    return Ok(m);
                }
                Err(e_err) => {
                    return Err(ExecErr(e_err));
                }
            }
        }
        Err(c_err) => {
            return Err(CompileErr(c_err));
        }
    }
}

pub fn replace<T: PatternLike Copy>(pattern: T, subject: &str, repl: &str,
                                    options: int) -> ReplaceResult {
    return replace_fn_from(pattern, subject, |_m| { str::from_slice(repl) }, 0u, options);
}

pub fn replace_from<T: PatternLike Copy>(pattern: T, subject: &str, repl: &str,
                                         offset: uint, options: int)
                                         -> ReplaceResult {
    return replace_fn_from(pattern, subject, |_m| { str::from_slice(repl) }, offset, options);
}

pub fn replace_fn<T: PatternLike Copy>(pattern: T, subject: &str,
                                       repl_fn: fn(Match) -> ~str, options: int)
                                       -> ReplaceResult {
    return replace_fn_from(pattern, subject, repl_fn, 0u, options);
}

pub fn replace_fn_from<T: PatternLike Copy>(pattern: T, subject: &str,
                                            repl_fn: fn(Match) -> ~str, offset: uint,
                                            options: int)
                                            -> ReplaceResult {
    let r = search_from(pattern, subject, offset, options);
    match r {
        Ok(m) => {
            return Ok(@(m.prematch() + repl_fn(m) + m.postmatch()));
        }
        Err(e) => { return Err(e); }
    }
}

pub fn replace_all<T: PatternLike Copy>(pattern: T, subject: &str,
                                        repl: &str,
                                        options: int)
                                        -> ReplaceResult {
    return replace_all_fn_from(pattern, subject, |_m| { str::from_slice(repl) }, 0u, options);
}

pub fn replace_all_fn<T: PatternLike Copy>(pattern: T, subject: &str,
                                           repl_fn: fn(Match) -> ~str,
                                           options: int)
                                           -> ReplaceResult {
    return replace_all_fn_from(pattern, subject, repl_fn, 0u, options);
}

pub fn replace_all_from<T: PatternLike Copy>(pattern: T, subject: &str,
                                             repl: &str,
                                             offset: uint,
                                             options: int)
                                             -> ReplaceResult {
    return replace_all_fn_from(pattern, subject, |_m| { str::from_slice(repl) }, offset, options);
}

pub fn replace_all_fn_from<T: PatternLike Copy>(pattern: T, subject: &str,
                                                repl_fn: fn(Match) -> ~str,
                                                offset: uint,
                                                options: int)
                                                -> ReplaceResult {
    let mut offset = offset;
    let subject_len = str::len(subject);
    assert offset <= subject_len;

    let mut s = str::slice(subject, 0, offset);
    loop {
        let r = search_from(pattern, subject, offset, options);
        match r {
            Ok(m) => {
                s += str::slice(subject, offset, m.begin());
                s += repl_fn(m);
                offset = m.end();
            }
            Err(ExecErr(e)) if e == PCRE_ERROR_NOMATCH => {
                if offset != subject_len {
                    s += str::slice(subject, offset, subject_len);
                }
                break;
            }
            Err(e) => {
                return Err(copy e);
            }
        }
    }
    return Ok(@s);
}

pub fn fmt_compile_err(e: CompileErr) -> ~str {
    return fmt!("error %d: %s at offset %u", e.code, *e.reason, e.offset);
}

/// Return true iff `sr` indicates that the subject did not match the pattern
pub pure fn is_nomatch(sr: SearchResult) -> bool {
    match sr {
      Err(ExecErr(e)) if e == PCRE_ERROR_NOMATCH => true,
      _ => false,
    }
}
<|endoftext|>


<commit_before>use std::ptr;

use env::NapiEnv;
use result::NapiResult;
use sys;

use super::NapiValue;

#[derive(Clone, Copy, Debug)]
pub struct NapiBoolean<'a> {
    value: sys::napi_value,
    env: &'a NapiEnv,
}

impl<'a> NapiBoolean<'a> {
    fn new(env: &'a NapiEnv, value: bool) -> NapiResult<Self> {
        let mut sys_value = ptr::null_mut();
        env.handle_status(unsafe {
            sys::napi_get_boolean(env.as_sys_env(), value, &mut sys_value)
        })?;

        Ok(Self {
            value: sys_value,
            env,
        })
    }

    pub fn truth(env: &'a NapiEnv) -> NapiResult<Self> {
        NapiBoolean::new(env, true)
    }

    pub fn lie(env: &'a NapiEnv) -> NapiResult<Self> {
        NapiBoolean::new(env, false)
    }
}

impl<'a> NapiValue for NapiBoolean<'a> {
    fn as_sys_value(&self) -> sys::napi_value {
        self.value
    }

    fn env(&self) -> &NapiEnv {
        self.env
    }
}

pub fn construct<'a>(
    value: sys::napi_value,
    env: &'a NapiEnv,
) -> NapiBoolean<'a> {
    NapiBoolean { value, env }
}
<commit_msg>feat(napi): implemet NapiBoolean::to_bool<commit_after>use std::ptr;

use env::NapiEnv;
use result::NapiResult;
use sys;

use super::NapiValue;

#[derive(Clone, Copy, Debug)]
pub struct NapiBoolean<'a> {
    value: sys::napi_value,
    env: &'a NapiEnv,
}

impl<'a> NapiBoolean<'a> {
    fn new(env: &'a NapiEnv, value: bool) -> NapiResult<Self> {
        let mut sys_value = ptr::null_mut();
        env.handle_status(unsafe {
            sys::napi_get_boolean(env.as_sys_env(), value, &mut sys_value)
        })?;

        Ok(Self {
            value: sys_value,
            env,
        })
    }

    pub fn truth(env: &'a NapiEnv) -> NapiResult<Self> {
        NapiBoolean::new(env, true)
    }

    pub fn lie(env: &'a NapiEnv) -> NapiResult<Self> {
        NapiBoolean::new(env, false)
    }

    pub fn to_bool(&self) -> NapiResult<bool> {
        let mut result = false;

        self.env.handle_status(unsafe {
            sys::napi_get_value_bool(
                self.env.as_sys_env(),
                self.value,
                &mut result,
            )
        })?;

        Ok(result)
    }
}

impl<'a> NapiValue for NapiBoolean<'a> {
    fn as_sys_value(&self) -> sys::napi_value {
        self.value
    }

    fn env(&self) -> &NapiEnv {
        self.env
    }
}

pub fn construct<'a>(
    value: sys::napi_value,
    env: &'a NapiEnv,
) -> NapiBoolean<'a> {
    NapiBoolean { value, env }
}
<|endoftext|>


<commit_before>use std::io::{Read, Write};

use rayon::prelude::*;

#[cfg(feature = "zstd")]
use zstd::block::{compress, decompress};

use super::*;

/// A snapshot of the state required to quickly restart
/// the `PageCache` and `SegmentAccountant`.
/// TODO consider splitting `Snapshot` into separate
/// snapshots for PC, SA, Materializer.
#[derive(Clone, Debug, Default, Serialize, Deserialize)]
pub struct Snapshot {
    /// the last lsn included in the `Snapshot`
    pub max_lsn: Lsn,
    /// the highest LSN persisted to a segment trailer
    pub max_header_stable_lsn: Lsn,
    /// the last lid included in the `Snapshot`
    pub last_lid: LogId,
    /// the highest lid observed while generating the `Snapshot`
    pub max_lid: LogId,
    /// the highest allocated pid
    pub max_pid: PageId,
    /// the mapping from pages to (lsn, lid)
    pub pt: FastMap8<PageId, PageState>,
    /// replaced pages per segment index
    pub replacements: FastMap8<SegmentId, (Lsn, FastSet8<(PageId, SegmentId)>)>,
    /// the free pids
    pub free: FastSet8<PageId>,
}

#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub enum PageState {
    Present(Vec<(Lsn, DiskPtr)>),
    Free(Lsn, DiskPtr),
}

impl PageState {
    fn push(&mut self, item: (Lsn, DiskPtr)) {
        match *self {
            PageState::Present(ref mut items) => items.push(item),
            PageState::Free(_, _) => {
                panic!("pushed items to a PageState::Free")
            }
        }
    }

    /// Iterate over the (lsn, lid) pairs that hold this page's state.
    pub fn iter(&self) -> Box<dyn Iterator<Item = (Lsn, DiskPtr)>> {
        match *self {
            PageState::Present(ref items) => {
                Box::new(items.clone().into_iter())
            }
            PageState::Free(lsn, ptr) => Box::new(vec![(lsn, ptr)].into_iter()),
        }
    }

    fn is_free(&self) -> bool {
        match *self {
            PageState::Free(_, _) => true,
            _ => false,
        }
    }
}

impl Snapshot {
    fn apply(
        &mut self,
        log_kind: LogKind,
        pid: PageId,
        lsn: Lsn,
        disk_ptr: DiskPtr,
        config: &Config,
    ) {
        // unwrapping this because it's already passed the crc check
        // in the log iterator
        trace!("trying to deserialize buf for ptr {} lsn {}", disk_ptr, lsn);

        if pid >= self.max_pid {
            self.max_pid = pid + 1;
        }

        let replaced_at_idx = disk_ptr.lid() as SegmentId / config.io_buf_size;
        let replaced_at_segment_lsn =
            (lsn / config.io_buf_size as Lsn) * config.io_buf_size as Lsn;

        match log_kind {
            LogKind::Replace => {
                trace!(
                    "compact of pid {} at ptr {} lsn {}",
                    pid,
                    disk_ptr,
                    lsn,
                );

                self.replace_pid(
                    pid,
                    replaced_at_segment_lsn,
                    replaced_at_idx,
                    config,
                );

                self.pt
                    .insert(pid, PageState::Present(vec![(lsn, disk_ptr)]));

                self.free.remove(&pid);
            }
            LogKind::Append => {
                // Because we rewrite pages over time, we may have relocated
                // a page's initial Compact to a later segment. We should skip
                // over pages here unless we've encountered a Compact for them.
                if let Some(lids) = self.pt.get_mut(&pid) {
                    trace!(
                        "append of pid {} at lid {} lsn {}",
                        pid,
                        disk_ptr,
                        lsn,
                    );

                    if lids.is_free() {
                        // this can happen if the allocate or replace
                        // has been moved to a later segment.

                        trace!(
                            "we have not yet encountered an \
                             allocation of this page, skipping push"
                        );

                        return;
                    }

                    lids.push((lsn, disk_ptr));
                }
                self.free.remove(&pid);
            }
            LogKind::Free => {
                trace!("free of pid {} at ptr {} lsn {}", pid, disk_ptr, lsn);
                self.replace_pid(
                    pid,
                    replaced_at_segment_lsn,
                    replaced_at_idx,
                    config,
                );
                self.pt.insert(pid, PageState::Free(lsn, disk_ptr));
                self.free.insert(pid);
            }
            LogKind::Corrupted | LogKind::Skip => panic!(
                "unexppected messagekind in snapshot application: {:?}",
                log_kind
            ),
        }
    }

    fn replace_pid(
        &mut self,
        pid: PageId,
        replaced_at_segment_lsn: Lsn,
        replaced_at_idx: usize,
        config: &Config,
    ) {
        let replacements = self
            .replacements
            .get_mut(&replaced_at_idx)
            .expect("we should have initialized this before calling here");

        assert_eq!(replaced_at_segment_lsn, replacements.0);

        match self.pt.remove(&pid) {
            Some(PageState::Present(coords)) => {
                for (_lsn, ptr) in &coords {
                    let old_idx = ptr.lid() as SegmentId / config.io_buf_size;
                    if replaced_at_idx == old_idx {
                        continue;
                    }
                    replacements.1.insert((pid, old_idx));
                }

                // re-run any blob removals in case
                // they were not completed. blobs are
                // not rewritten if they are the only
                // frag for a page during rewrite.
                if coords.len() > 1 {
                    let blob_ptrs = coords
                        .iter()
                        .filter(|(_, ptr)| ptr.is_blob())
                        .map(|(_, ptr)| ptr.blob().1);

                    for blob_ptr in blob_ptrs {
                        trace!(
                            "removing blob while advancing \
                             snapshot: {}",
                            blob_ptr,
                        );

                        // we don't care if this actually works
                        // because it's possible that a previous
                        // snapshot has run over this log and
                        // removed the blob already.
                        let _ = remove_blob(blob_ptr, config);
                    }
                }
            }
            Some(PageState::Free(_lsn, ptr)) => {
                let old_idx = ptr.lid() as SegmentId / config.io_buf_size;
                if replaced_at_idx != old_idx {
                    replacements.1.insert((pid, old_idx));
                }
            }
            None => {
                // we just encountered a replace without it
                // existing in the pt. this means we've
                // relocated it to a later segment than
                // the one we're currently at during recovery.
                // so we can skip it here.
            }
        }
    }
}

pub(super) fn advance_snapshot<PM, P>(
    iter: LogIter,
    mut snapshot: Snapshot,
    config: &Config,
) -> Result<Snapshot>
where
    PM: Materializer<PageFrag = P>,
    P: 'static + Debug + Clone + Serialize + DeserializeOwned + Send + Sync,
{
    let _measure = Measure::new(&M.advance_snapshot);

    trace!("building on top of old snapshot: {:?}", snapshot);

    let io_buf_size = config.io_buf_size;

    let mut last_seg_lsn = snapshot.max_lsn / io_buf_size as Lsn;

    for (log_kind, pid, lsn, ptr, _sz) in iter {
        trace!(
            "in advance_snapshot looking at item with lsn {} ptr {}",
            lsn,
            ptr
        );

        if lsn <= snapshot.max_lsn {
            // don't process already-processed Lsn's. max_lsn is for the last
            // item ALREADY INCLUDED lsn in the snapshot.
            trace!(
                "continuing in advance_snapshot, lsn {} ptr {} max_lsn {}",
                lsn,
                ptr,
                snapshot.max_lsn
            );
            continue;
        }

        assert!(lsn > snapshot.max_lsn);
        snapshot.max_lsn = lsn;
        snapshot.last_lid = ptr.lid();
        if ptr.lid() > snapshot.max_lid {
            snapshot.max_lid = ptr.lid();
        }

        let segment_idx = ptr.lid() as usize / io_buf_size;
        let segment_lsn = lsn / (io_buf_size as Lsn) * (io_buf_size as Lsn);

        if lsn / (io_buf_size as Lsn) > last_seg_lsn {
            // invalidate any removed pids

            snapshot
                .replacements
                .insert(segment_idx, (segment_lsn, FastSet8::default()));
            last_seg_lsn = segment_lsn;
        } else {
            // ensure it's present for later SA initialization
            snapshot
                .replacements
                .entry(segment_idx)
                .or_insert((segment_lsn, FastSet8::default()));
        }

        snapshot.apply(log_kind, pid, lsn, ptr, config);
    }

    // read the max stable lsn written into all trailers
    let max_segment =
        config.file.metadata()?.len() / config.io_buf_size as LogId;
    let max_header_stable_lsn = (0..max_segment)
        .into_par_iter()
        .flat_map(|idx| {
            let base = idx * config.io_buf_size as LogId;
            match config.file.read_segment_header(base) {
                Ok(header) if header.ok => {
                    Some(header.highest_known_stable_lsn)
                }
                _ => None,
            }
        })
        .max()
        .unwrap_or(-1);

    if max_header_stable_lsn != -1
        && max_header_stable_lsn < snapshot.max_header_stable_lsn
    {
        debug!(
            "the snapshot max_header_stable_lsn went \
             down over time from {} before to {} after",
            snapshot.max_header_stable_lsn, max_header_stable_lsn
        );
    }

    snapshot.max_header_stable_lsn = max_header_stable_lsn;

    write_snapshot(config, &snapshot)?;

    trace!("generated new snapshot: {:?}", snapshot);

    Ok(snapshot)
}

/// Read a `Snapshot` or generate a default, then advance it to
/// the tip of the data file, if present.
pub fn read_snapshot_or_default(config: &Config) -> Result<Snapshot> {
    let last_snap = read_snapshot(config)?.unwrap_or_else(Snapshot::default);

    let (log_iter, max_header_stable_lsn) =
        raw_segment_iter_from(last_snap.last_lsn, config)?;

    advance_snapshot(log_iter, last_snap, config)
}

/// Read a `Snapshot` from disk.
fn read_snapshot(config: &Config) -> std::io::Result<Option<Snapshot>> {
    let mut candidates = config.get_snapshot_files()?;
    if candidates.is_empty() {
        debug!("no previous snapshot found");
        return Ok(None);
    }

    candidates.sort();

    let path = candidates.pop().unwrap();

    let mut f = std::fs::OpenOptions::new().read(true).open(&path)?;
    if f.metadata()?.len() <= 12 {
        warn!("empty/corrupt snapshot file found");
        return Ok(None);
    }

    let mut buf = vec![];
    f.read_to_end(&mut buf)?;
    let len = buf.len();
    let mut len_expected_bytes = [0u8; 8];
    len_expected_bytes.copy_from_slice(&buf[len - 12..len - 4]);

    let mut crc_expected_bytes = [0u8; 4];
    crc_expected_bytes.copy_from_slice(&buf[len - 4..]);

    buf.split_off(len - 12);
    let crc_expected: u32 = arr_to_u32(&crc_expected_bytes);

    let crc_actual = crc32(&buf);

    if crc_expected != crc_actual {
        return Ok(None);
    }

    #[cfg(feature = "zstd")]
    let bytes = if config.use_compression {
        let len_expected: u64 = arr_to_u64(&len_expected_bytes);
        decompress(&*buf, len_expected as usize).unwrap()
    } else {
        buf
    };

    #[cfg(not(feature = "zstd"))]
    let bytes = buf;

    Ok(deserialize::<Snapshot>(&*bytes).ok())
}

fn write_snapshot(config: &Config, snapshot: &Snapshot) -> Result<()> {
    let raw_bytes = serialize(&snapshot).unwrap();
    let decompressed_len = raw_bytes.len();

    #[cfg(feature = "zstd")]
    let bytes = if config.use_compression {
        compress(&*raw_bytes, config.compression_factor).unwrap()
    } else {
        raw_bytes
    };

    #[cfg(not(feature = "zstd"))]
    let bytes = raw_bytes;

    let crc32: [u8; 4] = u32_to_arr(crc32(&bytes));
    let len_bytes: [u8; 8] = u64_to_arr(decompressed_len as u64);

    let path_1_suffix = format!("snap.{:016X}.generating", snapshot.max_lsn);

    let mut path_1 = config.snapshot_prefix();
    path_1.push(path_1_suffix);

    let path_2_suffix = format!("snap.{:016X}", snapshot.max_lsn);

    let mut path_2 = config.snapshot_prefix();
    path_2.push(path_2_suffix);

    let parent = path_1.parent().unwrap();
    std::fs::create_dir_all(parent)?;
    let mut f = std::fs::OpenOptions::new()
        .write(true)
        .create(true)
        .open(&path_1)?;

    // write the snapshot bytes, followed by a crc64 checksum at the end
    maybe_fail!("snap write");
    f.write_all(&*bytes)?;
    maybe_fail!("snap write len");
    f.write_all(&len_bytes)?;
    maybe_fail!("snap write crc");
    f.write_all(&crc32)?;
    f.sync_all()?;
    maybe_fail!("snap write post");

    trace!("wrote snapshot to {}", path_1.to_string_lossy());

    maybe_fail!("snap write mv");
    std::fs::rename(&path_1, &path_2)?;
    maybe_fail!("snap write mv post");

    trace!("renamed snapshot to {}", path_2.to_string_lossy());

    // clean up any old snapshots
    let candidates = config.get_snapshot_files()?;
    for path in candidates {
        let path_str = path.file_name().unwrap().to_str().unwrap();
        if !path_2.to_string_lossy().ends_with(&*path_str) {
            debug!("removing old snapshot file {:?}", path);

            maybe_fail!("snap write rm old");

            if let Err(_e) = std::fs::remove_file(&path) {
                // TODO should this just be a try return?
                warn!(
                    "failed to remove old snapshot file, maybe snapshot race? {}",
                    _e
                );
            }
        }
    }
    Ok(())
}
<commit_msg>Simplify snapshot considerably<commit_after>use std::io::{Read, Write};

#[cfg(feature = "zstd")]
use zstd::block::{compress, decompress};

use super::*;

/// A snapshot of the state required to quickly restart
/// the `PageCache` and `SegmentAccountant`.
#[derive(Clone, Debug, Default, Serialize, Deserialize)]
pub struct Snapshot {
    /// The last read message lsn
    pub last_lsn: Lsn,
    /// The last read message lid
    pub last_lid: LogId,
    /// the mapping from pages to (lsn, lid)
    pub pt: FastMap8<PageId, PageState>,
    /// The highest stable offset persisted
    /// into a segment header
    pub max_header_stable_lsn: Lsn,
}

#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub enum PageState {
    Present(Vec<(Lsn, DiskPtr, usize)>),
    Free(Lsn, DiskPtr),
}

impl PageState {
    fn push(&mut self, item: (Lsn, DiskPtr, usize)) {
        match *self {
            PageState::Present(ref mut items) => items.push(item),
            PageState::Free(_, _) => {
                panic!("pushed items to a PageState::Free")
            }
        }
    }

    /// Iterate over the (lsn, lid) pairs that hold this page's state.
    pub fn iter(&self) -> impl Iterator<Item = (Lsn, DiskPtr, usize)> {
        match *self {
            PageState::Present(ref items) => items.clone().into_iter(),
            PageState::Free(lsn, ptr) => {
                vec![(lsn, ptr, MSG_HEADER_LEN)].into_iter()
            }
        }
    }

    fn is_free(&self) -> bool {
        match *self {
            PageState::Free(_, _) => true,
            _ => false,
        }
    }
}

impl Snapshot {
    fn apply(
        &mut self,
        log_kind: LogKind,
        pid: PageId,
        lsn: Lsn,
        disk_ptr: DiskPtr,
        sz: usize,
    ) {
        // unwrapping this because it's already passed the crc check
        // in the log iterator
        trace!("trying to deserialize buf for ptr {} lsn {}", disk_ptr, lsn);

        match log_kind {
            LogKind::Replace => {
                trace!(
                    "compact of pid {} at ptr {} lsn {}",
                    pid,
                    disk_ptr,
                    lsn,
                );

                self.pt
                    .insert(pid, PageState::Present(vec![(lsn, disk_ptr, sz)]));
            }
            LogKind::Append => {
                // Because we rewrite pages over time, we may have relocated
                // a page's initial Compact to a later segment. We should skip
                // over pages here unless we've encountered a Compact for them.
                if let Some(lids) = self.pt.get_mut(&pid) {
                    trace!(
                        "append of pid {} at lid {} lsn {}",
                        pid,
                        disk_ptr,
                        lsn,
                    );

                    if lids.is_free() {
                        // this can happen if the allocate or replace
                        // has been moved to a later segment.

                        trace!(
                            "we have not yet encountered an \
                             allocation of this page, skipping push"
                        );

                        return;
                    }

                    lids.push((lsn, disk_ptr, sz));
                }
            }
            LogKind::Free => {
                trace!("free of pid {} at ptr {} lsn {}", pid, disk_ptr, lsn);
                self.pt.insert(pid, PageState::Free(lsn, disk_ptr));
            }
            LogKind::Corrupted | LogKind::Skip => panic!(
                "unexppected messagekind in snapshot application: {:?}",
                log_kind
            ),
        }
    }
}

pub(super) fn advance_snapshot(
    iter: LogIter,
    mut snapshot: Snapshot,
    config: &Config,
) -> Result<Snapshot> {
    let _measure = Measure::new(&M.advance_snapshot);

    trace!("building on top of old snapshot: {:?}", snapshot);

    for (log_kind, pid, lsn, ptr, sz) in iter {
        trace!(
            "in advance_snapshot looking at item with lsn {} ptr {}",
            lsn,
            ptr
        );

        if lsn <= snapshot.last_lsn {
            // don't process already-processed Lsn's. last_lsn is for the last
            // item ALREADY INCLUDED lsn in the snapshot.
            trace!(
                "continuing in advance_snapshot, lsn {} ptr {} last_lsn {}",
                lsn,
                ptr,
                snapshot.last_lsn
            );
            continue;
        }

        assert!(lsn > snapshot.last_lsn);
        snapshot.last_lsn = lsn;
        snapshot.last_lid = ptr.lid();

        snapshot.apply(log_kind, pid, lsn, ptr, sz);
    }

    write_snapshot(config, &snapshot)?;

    trace!("generated new snapshot: {:?}", snapshot);

    Ok(snapshot)
}

/// Read a `Snapshot` or generate a default, then advance it to
/// the tip of the data file, if present.
pub fn read_snapshot_or_default(config: &Config) -> Result<Snapshot> {
    let mut last_snap =
        read_snapshot(config)?.unwrap_or_else(Snapshot::default);

    let (log_iter, max_header_stable_lsn) =
        raw_segment_iter_from(last_snap.last_lsn, config)?;

    last_snap.max_header_stable_lsn = max_header_stable_lsn;

    advance_snapshot(log_iter, last_snap, config)
}

/// Read a `Snapshot` from disk.
fn read_snapshot(config: &Config) -> std::io::Result<Option<Snapshot>> {
    let mut candidates = config.get_snapshot_files()?;
    if candidates.is_empty() {
        debug!("no previous snapshot found");
        return Ok(None);
    }

    candidates.sort();

    let path = candidates.pop().unwrap();

    let mut f = std::fs::OpenOptions::new().read(true).open(&path)?;
    if f.metadata()?.len() <= 12 {
        warn!("empty/corrupt snapshot file found");
        return Ok(None);
    }

    let mut buf = vec![];
    f.read_to_end(&mut buf)?;
    let len = buf.len();
    let mut len_expected_bytes = [0u8; 8];
    len_expected_bytes.copy_from_slice(&buf[len - 12..len - 4]);

    let mut crc_expected_bytes = [0u8; 4];
    crc_expected_bytes.copy_from_slice(&buf[len - 4..]);

    buf.split_off(len - 12);
    let crc_expected: u32 = arr_to_u32(&crc_expected_bytes);

    let crc_actual = crc32(&buf);

    if crc_expected != crc_actual {
        return Ok(None);
    }

    #[cfg(feature = "zstd")]
    let bytes = if config.use_compression {
        let len_expected: u64 = arr_to_u64(&len_expected_bytes);
        decompress(&*buf, len_expected as usize).unwrap()
    } else {
        buf
    };

    #[cfg(not(feature = "zstd"))]
    let bytes = buf;

    Ok(deserialize::<Snapshot>(&*bytes).ok())
}

fn write_snapshot(config: &Config, snapshot: &Snapshot) -> Result<()> {
    let raw_bytes = serialize(&snapshot).unwrap();
    let decompressed_len = raw_bytes.len();

    #[cfg(feature = "zstd")]
    let bytes = if config.use_compression {
        compress(&*raw_bytes, config.compression_factor).unwrap()
    } else {
        raw_bytes
    };

    #[cfg(not(feature = "zstd"))]
    let bytes = raw_bytes;

    let crc32: [u8; 4] = u32_to_arr(crc32(&bytes));
    let len_bytes: [u8; 8] = u64_to_arr(decompressed_len as u64);

    let path_1_suffix = format!("snap.{:016X}.generating", snapshot.last_lsn);

    let mut path_1 = config.snapshot_prefix();
    path_1.push(path_1_suffix);

    let path_2_suffix = format!("snap.{:016X}", snapshot.last_lsn);

    let mut path_2 = config.snapshot_prefix();
    path_2.push(path_2_suffix);

    let parent = path_1.parent().unwrap();
    std::fs::create_dir_all(parent)?;
    let mut f = std::fs::OpenOptions::new()
        .write(true)
        .create(true)
        .open(&path_1)?;

    // write the snapshot bytes, followed by a crc64 checksum at the end
    maybe_fail!("snap write");
    f.write_all(&*bytes)?;
    maybe_fail!("snap write len");
    f.write_all(&len_bytes)?;
    maybe_fail!("snap write crc");
    f.write_all(&crc32)?;
    f.sync_all()?;
    maybe_fail!("snap write post");

    trace!("wrote snapshot to {}", path_1.to_string_lossy());

    maybe_fail!("snap write mv");
    std::fs::rename(&path_1, &path_2)?;
    maybe_fail!("snap write mv post");

    trace!("renamed snapshot to {}", path_2.to_string_lossy());

    // clean up any old snapshots
    let candidates = config.get_snapshot_files()?;
    for path in candidates {
        let path_str = path.file_name().unwrap().to_str().unwrap();
        if !path_2.to_string_lossy().ends_with(&*path_str) {
            debug!("removing old snapshot file {:?}", path);

            maybe_fail!("snap write rm old");

            if let Err(_e) = std::fs::remove_file(&path) {
                // TODO should this just be a try return?
                warn!(
                    "failed to remove old snapshot file, maybe snapshot race? {}",
                    _e
                );
            }
        }
    }
    Ok(())
}
<|endoftext|>


<commit_before>//! Wrapper for DPDK's environment abstraction layer (EAL).
use ffi;
use std::sync::{Arc, RwLock};
use thiserror::Error;

#[derive(Debug)]
struct EalSharedInner {} // TODO Remove this if unnecessary

#[derive(Debug)]
struct EalInner {
    shared: RwLock<EalSharedInner>,
}

/// DPDK's environment abstraction layer (EAL).
///
/// This object indicates that EAL has been initialized and its APIs are available now.
#[derive(Debug, Clone)]
pub struct Eal {
    inner: Arc<EalInner>,
}

#[derive(Debug, Error)]
pub enum EalError {
    #[error("EAL function returned an error code: {}", code)]
    ErrorCode { code: i32 },
}

impl Eal {
    /// Create an `Eal` instance.
    ///
    /// It takes command-line arguments and consumes used arguments.
    #[inline]
    pub fn new(args: &mut Vec<String>) -> Result<Self, EalError> {
        Ok(Eal {
            inner: Arc::new(EalInner::new(args)?),
        })
    }
}
pub use super::dpdk_sys::EalStaticFunctions as EalGlobalApi;

unsafe impl EalGlobalApi for Eal {}

impl EalInner {
    // Create `EalInner`.
    #[inline]
    fn new(args: &mut Vec<String>) -> Result<Self, EalError> {
        // To prevent DPDK PMDs' being unlinked, we explicitly create symbolic dependency via
        // calling `load_drivers`.
        dpdk_sys::load_drivers();

        // DPDK returns number of consumed argc
        // Safety: foriegn function (safe unless there is a bug)
        let ret = unsafe { ffi::run_with_args(dpdk_sys::rte_eal_init, &*args) };
        if ret < 0 {
            return Err(EalError::ErrorCode { code: ret });
        }

        // Strip first n args and return the remaining
        args.drain(..ret as usize);
        Ok(EalInner {
            shared: RwLock::new(EalSharedInner {}),
        })
    }
}

impl Drop for EalInner {
    #[inline]
    fn drop(&mut self) {
        // Safety: foriegn function (safe unless there is a bug)
        unsafe {
            let ret = dpdk_sys::rte_eal_cleanup();
            assert_eq!(ret, 0);
        }
    }
}
<commit_msg>candidate<commit_after>//! Wrapper for DPDK's environment abstraction layer (EAL).
use ffi;
use std::sync::{Arc, RwLock};
use thiserror::Error;

#[derive(Debug)]
struct EalSharedInner {} // TODO Remove this if unnecessary

#[derive(Debug)]
struct EalInner {
    shared: RwLock<EalSharedInner>,
}

/// DPDK's environment abstraction layer (EAL).
///
/// This object indicates that EAL has been initialized and its APIs are available now.
#[derive(Debug, Clone)]
pub struct Eal {
    inner: Arc<EalInner>,
}

#[derive(Debug, Error)]
pub enum EalError {
    #[error("EAL function returned an error code: {}", code)]
    ErrorCode { code: i32 },
}

/// Supported CPU layout
#[derive(Debug)]
pub enum CPULayout {
    /// Each CPU has dedicated RX/TX queues for every NIC.
    FullMesh,
    /// Each CPU has dedicated TX queues for every NIC, but RX queues for NICs on same NUMA node.
    RxNumaAffinity,
    /// Each CPU has dedicated RX/TX queues for NICs on same NUMA node.
    RxTxNumaAffinity,
}

impl Eal {
    /// Create an `Eal` instance.
    ///
    /// It takes command-line arguments and consumes used arguments.
    #[inline]
    pub fn new(args: &mut Vec<String>) -> Result<Self, EalError> {
        Ok(Eal {
            inner: Arc::new(EalInner::new(args)?),
        })
    }

    /// Candidate 1, return (thread, rxqs, txqs)
    #[inline]
    pub fn setup(&self, layout: CPULayout) -> impl Iterator<RteThread, Vec<RxQ>, Vec<TxQ>> {
        panic!("not implemented.")
    }

    /// Candidate 2, get a functor for per-thread functions
    #[inline]
    pub fn launch(&self, layout: CPULayout, per_thread: Fn(Vec<RxQ>, Vec<TxQ>) -> Fn() -> impl Future) {
        panic!("not implemented.")
    }
}

pub use super::dpdk_sys::EalStaticFunctions as EalGlobalApi;

unsafe impl EalGlobalApi for Eal {}

impl EalInner {
    // Create `EalInner`.
    #[inline]
    fn new(args: &mut Vec<String>) -> Result<Self, EalError> {
        // To prevent DPDK PMDs' being unlinked, we explicitly create symbolic dependency via
        // calling `load_drivers`.
        dpdk_sys::load_drivers();

        // DPDK returns number of consumed argc
        // Safety: foriegn function (safe unless there is a bug)
        let ret = unsafe { ffi::run_with_args(dpdk_sys::rte_eal_init, &*args) };
        if ret < 0 {
            return Err(EalError::ErrorCode { code: ret });
        }

        // Strip first n args and return the remaining
        args.drain(..ret as usize);
        Ok(EalInner {
            shared: RwLock::new(EalSharedInner {}),
        })
    }
}

impl Drop for EalInner {
    #[inline]
    fn drop(&mut self) {
        // Safety: foriegn function (safe unless there is a bug)
        unsafe {
            let ret = dpdk_sys::rte_eal_cleanup();
            assert_eq!(ret, 0);
        }
    }
}
<|endoftext|>


<commit_before>use clap::{App, Arg, Format, ArgMatches};
use std::io::{self, Write};
use std::process::exit;
use std::fs;
use crowbook::Book;

/// Prints an error on stderr and exit the program
pub fn print_error(s: &str) -> ! {
    writeln!(&mut io::stderr(), "{} {}", Format::Error("Error:"), s).unwrap();
    exit(0);
}

/// Gets the book options in a (key, value) list, or print an error
pub fn get_book_options<'a>(matches: &'a ArgMatches) -> Vec<(&'a str, &'a str)> {
    let mut output = vec!();
    if let Some(iter) = matches.values_of("set") {
        let v:Vec<_> = iter.collect();
        if v.len() %2 != 0 {
            print_error(&lformat!("An odd number of arguments was passed to --set, but it takes a list of key value pairs."));
        }

        for i in 0..v.len()/2 {
            let key = v[i * 2];
            let value = v[i * 2 + 1];
            output.push((key, value));
        }
    }
    if matches.is_present("proofread") {
        output.push(("proofread", "true"));
    }
    output
}

/// Sets the book options according to command line arguments
/// Also print these options to a string, so it can be used at
/// the creation of a book to check that parameters are OK and t
/// then print them to file
pub fn set_book_options(book: &mut Book, matches: &ArgMatches) -> String {
    let mut output = String::new();
    let options = get_book_options(matches);

    for (key, value) in options {
        let res = book.options.set(key, value);
        if let Err(err) = res {
            print_error(&lformat!("Error in setting key {}: {}", key, err));
        }
        output.push_str(&format!("{}: {}\n", key, value));
    }
    output
}

/// create a book file with the command line arguments
/// and exit the process at the end
pub fn create_book(matches: &ArgMatches) -> ! {
    let mut f:Box<Write> = if let Some(book) = matches.value_of("BOOK") {
        if fs::metadata(book).is_ok() {
            print_error(&lformat!("Could not create file {}: it already exists!", book));
        }
        Box::new(fs::File::create(book).unwrap())
    } else {
        Box::new(io::stdout())
    };

    if let Some(values) = matches.values_of("create") {
        if matches.is_present("set") {
            let mut book = Book::new(&[]);
            let s = set_book_options(&mut book, matches);
            f.write_all(&s.as_bytes()).unwrap();
        } else {
            f.write_all(b"author: Your name
title: Your title
lang: en

# Uncomment and fill to generate files
# output.html: some_file.html
# output.epub: some_file.epub
# output.pdf: some_file.pdf

# Uncomment and fill to set cover image (for Epub)
# cover: some_cover.png\n").unwrap();
        }
        f.write_all(b"\n# List of chapters\n").unwrap();
        for file in values {
            f.write_all(&format!("+ {}\n", file).as_bytes()).unwrap();
        }
        if let Some(s) = matches.value_of("BOOK") {
            println!("{}", lformat!("Created {}, now you'll have to complete it!", s));
        }
        exit(0);
    } else {
        unreachable!(); // because Clap takes care of it
    }
}

pub fn create_matches<'a>() -> ArgMatches<'a> {
    lazy_static! {
        static ref ABOUT: String = lformat!("Render a Markdown book in EPUB, PDF or HTML.");
        static ref SINGLE: String = lformat!("Use a single Markdown file instead of a book configuration file");
        static ref VERBOSE: String = lformat!("Print warnings in parsing/rendering");
        static ref QUIET: String = lformat!("Don't print info/error messages");
        static ref PROOFREAD: String = lformat!("Enable proofreading");
        static ref DEBUG: String = lformat!("Print debugging information");
        static ref CREATE: String = lformat!("Create a new book with existing Markdown files");
        static ref OUTPUT: String = lformat!("Specify output file");
        static ref TO: String = lformat!("Generate specific format");
        static ref SET: String = lformat!("Set a list of book options");
        static ref LIST_OPTIONS: String = lformat!("List all possible options");
        static ref LIST_OPTIONS_MD: String = lformat!("List all possible options, formatted in Markdown");
        static ref PRINT_TEMPLATE: String = lformat!("Prints the default content of a template");
        static ref BOOK: String = lformat!("File containing the book configuration file, or a Markdown file when called with --single");
    }

    let app = App::new("crowbook")
        .version(env!("CARGO_PKG_VERSION"))
        .author("Élisabeth Henry <liz.henry@ouvaton.org>")
        .about(ABOUT.as_str())
        .arg(Arg::from_usage("-s, --single")
             .help(SINGLE.as_str()))
        .arg(Arg::from_usage("-v, --verbose")
             .help(VERBOSE.as_str()))
        .arg(Arg::from_usage("-q, --quiet")
             .help(QUIET.as_str())
             .conflicts_with("verbose")
             .conflicts_with("debug"))
        .arg(Arg::from_usage("-p, --proofread")
             .help(PROOFREAD.as_str()))
        .arg(Arg::from_usage("-d, --debug")
             .help(DEBUG.as_str())
             .conflicts_with("verbose")
             .hidden(true))
        .arg(Arg::from_usage("-c, --create [FILES]...")
             .help(CREATE.as_str()))
        .arg(Arg::from_usage("-o, --output [FILE]")
             .help(OUTPUT.as_str())
             .requires("to"))
        .arg(Arg::from_usage("-t, --to [FORMAT]")
             .help(TO.as_str())
             .possible_values(&["epub", "pdf", "html", "tex", "odt", "proofread.html", "proofread.html_dir", "proofread.pdf", "proofread.tex"]))
        .arg(Arg::from_usage("--set [KEY_VALUES]")
             .help(SET.as_str())
             .min_values(2))
        .arg(Arg::from_usage("-l --list-options")
             .help(LIST_OPTIONS.as_str()))
        .arg(Arg::from_usage("--list-options-md")
             .help(LIST_OPTIONS_MD.as_str())
             .hidden(true))
        .arg(Arg::from_usage("--print-template [TEMPLATE]")
             .help(PRINT_TEMPLATE.as_str()))
        .arg(Arg::with_name("BOOK")
             .index(1)
             .help(BOOK.as_str()));

    let matches = app.get_matches();

    pre_check(&matches);
    matches
}

/// Pre-check the matches to see if there isn't illegal options not detected by clap
fn pre_check(matches: &ArgMatches) {
    if matches.is_present("files") && !matches.is_present("create") {
        print_error(&lformat!("A list of additional files is only valid with the --create option."));
    }
}
<commit_msg>Add lformat! to an untranslated string<commit_after>use clap::{App, Arg, Format, ArgMatches};
use std::io::{self, Write};
use std::process::exit;
use std::fs;
use crowbook::Book;

/// Prints an error on stderr and exit the program
pub fn print_error(s: &str) -> ! {
    writeln!(&mut io::stderr(), "{} {}", Format::Error(lformat!("Error:")), s).unwrap();
    exit(0);
}

/// Gets the book options in a (key, value) list, or print an error
pub fn get_book_options<'a>(matches: &'a ArgMatches) -> Vec<(&'a str, &'a str)> {
    let mut output = vec!();
    if let Some(iter) = matches.values_of("set") {
        let v:Vec<_> = iter.collect();
        if v.len() %2 != 0 {
            print_error(&lformat!("An odd number of arguments was passed to --set, but it takes a list of key value pairs."));
        }

        for i in 0..v.len()/2 {
            let key = v[i * 2];
            let value = v[i * 2 + 1];
            output.push((key, value));
        }
    }
    if matches.is_present("proofread") {
        output.push(("proofread", "true"));
    }
    output
}

/// Sets the book options according to command line arguments
/// Also print these options to a string, so it can be used at
/// the creation of a book to check that parameters are OK and t
/// then print them to file
pub fn set_book_options(book: &mut Book, matches: &ArgMatches) -> String {
    let mut output = String::new();
    let options = get_book_options(matches);

    for (key, value) in options {
        let res = book.options.set(key, value);
        if let Err(err) = res {
            print_error(&lformat!("Error in setting key {}: {}", key, err));
        }
        output.push_str(&format!("{}: {}\n", key, value));
    }
    output
}

/// create a book file with the command line arguments
/// and exit the process at the end
pub fn create_book(matches: &ArgMatches) -> ! {
    let mut f:Box<Write> = if let Some(book) = matches.value_of("BOOK") {
        if fs::metadata(book).is_ok() {
            print_error(&lformat!("Could not create file {}: it already exists!", book));
        }
        Box::new(fs::File::create(book).unwrap())
    } else {
        Box::new(io::stdout())
    };

    if let Some(values) = matches.values_of("create") {
        if matches.is_present("set") {
            let mut book = Book::new(&[]);
            let s = set_book_options(&mut book, matches);
            f.write_all(&s.as_bytes()).unwrap();
        } else {
            f.write_all(b"author: Your name
title: Your title
lang: en

# Uncomment and fill to generate files
# output.html: some_file.html
# output.epub: some_file.epub
# output.pdf: some_file.pdf

# Uncomment and fill to set cover image (for Epub)
# cover: some_cover.png\n").unwrap();
        }
        f.write_all(b"\n# List of chapters\n").unwrap();
        for file in values {
            f.write_all(&format!("+ {}\n", file).as_bytes()).unwrap();
        }
        if let Some(s) = matches.value_of("BOOK") {
            println!("{}", lformat!("Created {}, now you'll have to complete it!", s));
        }
        exit(0);
    } else {
        unreachable!(); // because Clap takes care of it
    }
}

pub fn create_matches<'a>() -> ArgMatches<'a> {
    lazy_static! {
        static ref ABOUT: String = lformat!("Render a Markdown book in EPUB, PDF or HTML.");
        static ref SINGLE: String = lformat!("Use a single Markdown file instead of a book configuration file");
        static ref VERBOSE: String = lformat!("Print warnings in parsing/rendering");
        static ref QUIET: String = lformat!("Don't print info/error messages");
        static ref PROOFREAD: String = lformat!("Enable proofreading");
        static ref DEBUG: String = lformat!("Print debugging information");
        static ref CREATE: String = lformat!("Create a new book with existing Markdown files");
        static ref OUTPUT: String = lformat!("Specify output file");
        static ref TO: String = lformat!("Generate specific format");
        static ref SET: String = lformat!("Set a list of book options");
        static ref LIST_OPTIONS: String = lformat!("List all possible options");
        static ref LIST_OPTIONS_MD: String = lformat!("List all possible options, formatted in Markdown");
        static ref PRINT_TEMPLATE: String = lformat!("Prints the default content of a template");
        static ref BOOK: String = lformat!("File containing the book configuration file, or a Markdown file when called with --single");
    }

    let app = App::new("crowbook")
        .version(env!("CARGO_PKG_VERSION"))
        .author("Élisabeth Henry <liz.henry@ouvaton.org>")
        .about(ABOUT.as_str())
        .arg(Arg::from_usage("-s, --single")
             .help(SINGLE.as_str()))
        .arg(Arg::from_usage("-v, --verbose")
             .help(VERBOSE.as_str()))
        .arg(Arg::from_usage("-q, --quiet")
             .help(QUIET.as_str())
             .conflicts_with("verbose")
             .conflicts_with("debug"))
        .arg(Arg::from_usage("-p, --proofread")
             .help(PROOFREAD.as_str()))
        .arg(Arg::from_usage("-d, --debug")
             .help(DEBUG.as_str())
             .conflicts_with("verbose")
             .hidden(true))
        .arg(Arg::from_usage("-c, --create [FILES]...")
             .help(CREATE.as_str()))
        .arg(Arg::from_usage("-o, --output [FILE]")
             .help(OUTPUT.as_str())
             .requires("to"))
        .arg(Arg::from_usage("-t, --to [FORMAT]")
             .help(TO.as_str())
             .possible_values(&["epub", "pdf", "html", "tex", "odt", "proofread.html", "proofread.html_dir", "proofread.pdf", "proofread.tex"]))
        .arg(Arg::from_usage("--set [KEY_VALUES]")
             .help(SET.as_str())
             .min_values(2))
        .arg(Arg::from_usage("-l --list-options")
             .help(LIST_OPTIONS.as_str()))
        .arg(Arg::from_usage("--list-options-md")
             .help(LIST_OPTIONS_MD.as_str())
             .hidden(true))
        .arg(Arg::from_usage("--print-template [TEMPLATE]")
             .help(PRINT_TEMPLATE.as_str()))
        .arg(Arg::with_name("BOOK")
             .index(1)
             .help(BOOK.as_str()));

    let matches = app.get_matches();

    pre_check(&matches);
    matches
}

/// Pre-check the matches to see if there isn't illegal options not detected by clap
fn pre_check(matches: &ArgMatches) {
    if matches.is_present("files") && !matches.is_present("create") {
        print_error(&lformat!("A list of additional files is only valid with the --create option."));
    }
}
<|endoftext|>


<commit_before>// Copyright 2015 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.

#![cfg(not(test))]

extern crate log;
extern crate rustfmt_nightly as rustfmt;
extern crate toml;
extern crate env_logger;
extern crate getopts;

use rustfmt::{run, Input, Summary};
use rustfmt::file_lines::FileLines;
use rustfmt::config::{Config, WriteMode, get_toml_path};

use std::{env, error};
use std::fs::File;
use std::io::{self, Read, Write};
use std::path::{Path, PathBuf};
use std::str::FromStr;

use getopts::{Matches, Options};

type FmtError = Box<error::Error + Send + Sync>;
type FmtResult<T> = std::result::Result<T, FmtError>;

/// Rustfmt operations.
enum Operation {
    /// Format files and their child modules.
    Format {
        files: Vec<PathBuf>,
        config_path: Option<PathBuf>,
        minimal_config_path: Option<String>,
    },
    /// Print the help message.
    Help,
    // Print version information
    Version,
    /// Print detailed configuration help.
    ConfigHelp,
    /// Output default config to a file
    ConfigOutputDefault { path: String },
    /// No file specified, read from stdin
    Stdin {
        input: String,
        config_path: Option<PathBuf>,
    },
}

/// Parsed command line options.
#[derive(Clone, Debug, Default)]
struct CliOptions {
    skip_children: bool,
    verbose: bool,
    write_mode: Option<WriteMode>,
    file_lines: FileLines, // Default is all lines in all files.
}

impl CliOptions {
    fn from_matches(matches: &Matches) -> FmtResult<CliOptions> {
        let mut options = CliOptions::default();
        options.skip_children = matches.opt_present("skip-children");
        options.verbose = matches.opt_present("verbose");

        if let Some(ref write_mode) = matches.opt_str("write-mode") {
            if let Ok(write_mode) = WriteMode::from_str(write_mode) {
                options.write_mode = Some(write_mode);
            } else {
                return Err(FmtError::from(
                    format!("Invalid write-mode: {}", write_mode),
                ));
            }
        }

        if let Some(ref file_lines) = matches.opt_str("file-lines") {
            options.file_lines = file_lines.parse()?;
        }

        Ok(options)
    }

    fn apply_to(self, config: &mut Config) {
        config.set().skip_children(self.skip_children);
        config.set().verbose(self.verbose);
        config.set().file_lines(self.file_lines);
        if let Some(write_mode) = self.write_mode {
            config.set().write_mode(write_mode);
        }
    }
}

/// read the given config file path recursively if present else read the project file path
fn match_cli_path_or_file(
    config_path: Option<PathBuf>,
    input_file: &Path,
) -> FmtResult<(Config, Option<PathBuf>)> {

    if let Some(config_file) = config_path {
        let toml = Config::from_toml_path(config_file.as_ref())?;
        return Ok((toml, Some(config_file)));
    }
    Config::from_resolved_toml_path(input_file).map_err(|e| FmtError::from(e))
}

fn make_opts() -> Options {
    let mut opts = Options::new();
    opts.optflag("h", "help", "show this message");
    opts.optflag("V", "version", "show version information");
    opts.optflag("v", "verbose", "print verbose output");
    opts.optopt(
        "",
        "write-mode",
        "mode to write in (not usable when piping from stdin)",
        "[replace|overwrite|display|diff|coverage|checkstyle]",
    );
    opts.optflag("", "skip-children", "don't reformat child modules");

    opts.optflag(
        "",
        "config-help",
        "show details of rustfmt configuration options",
    );
    opts.optopt(
        "",
        "dump-default-config",
        "Dumps the default configuration to a file and exits.",
        "PATH",
    );
    opts.optopt(
        "",
        "dump-minimal-config",
        "Dumps configuration options that were checked during formatting to a file.",
        "PATH",
    );
    opts.optopt(
        "",
        "config-path",
        "Recursively searches the given path for the rustfmt.toml config file. If not \
         found reverts to the input file path",
        "[Path for the configuration file]",
    );
    opts.optopt(
        "",
        "file-lines",
        "Format specified line ranges. See README for more detail on the JSON format.",
        "JSON",
    );

    opts
}

fn execute(opts: &Options) -> FmtResult<Summary> {
    let matches = opts.parse(env::args().skip(1))?;

    match determine_operation(&matches)? {
        Operation::Help => {
            print_usage(opts, "");
            Summary::print_exit_codes();
            Ok(Summary::new())
        }
        Operation::Version => {
            print_version();
            Ok(Summary::new())
        }
        Operation::ConfigHelp => {
            Config::print_docs();
            Ok(Summary::new())
        }
        Operation::ConfigOutputDefault { path } => {
            let mut file = File::create(path)?;
            let toml = Config::default().all_options().to_toml()?;
            file.write_all(toml.as_bytes())?;
            Ok(Summary::new())
        }
        Operation::Stdin { input, config_path } => {
            // try to read config from local directory
            let (mut config, _) =
                match_cli_path_or_file(config_path, &env::current_dir().unwrap())?;

            // write_mode is always Plain for Stdin.
            config.set().write_mode(WriteMode::Plain);

            // parse file_lines
            if let Some(ref file_lines) = matches.opt_str("file-lines") {
                config.set().file_lines(file_lines.parse()?);
                for f in config.file_lines().files() {
                    if f != "stdin" {
                        println!("Warning: Extra file listed in file_lines option '{}'", f);
                    }
                }
            }

            Ok(run(Input::Text(input), &config))
        }
        Operation::Format {
            files,
            config_path,
            minimal_config_path,
        } => {
            let options = CliOptions::from_matches(&matches)?;

            for f in options.file_lines.files() {
                if !files.contains(&PathBuf::from(f)) {
                    println!("Warning: Extra file listed in file_lines option '{}'", f);
                }
            }

            let mut config = Config::default();
            // Load the config path file if provided
            if let Some(config_file) = config_path.as_ref() {
                config = Config::from_toml_path(config_file.as_ref())?;
            };

            if options.verbose {
                if let Some(path) = config_path.as_ref() {
                    println!("Using rustfmt config file {}", path.display());
                }
            }

            let mut error_summary = Summary::new();
            for file in files {
                if !file.exists() {
                    println!("Error: file `{}` does not exist", file.to_str().unwrap());
                    error_summary.add_operational_error();
                } else if file.is_dir() {
                    println!("Error: `{}` is a directory", file.to_str().unwrap());
                    error_summary.add_operational_error();
                } else {
                    // Check the file directory if the config-path could not be read or not provided
                    if config_path.is_none() {
                        let (config_tmp, path_tmp) =
                            Config::from_resolved_toml_path(file.parent().unwrap())?;
                        if options.verbose {
                            if let Some(path) = path_tmp.as_ref() {
                                println!(
                                    "Using rustfmt config file {} for {}",
                                    path.display(),
                                    file.display()
                                );
                            }
                        }
                        config = config_tmp;
                    }

                    options.clone().apply_to(&mut config);
                    error_summary.add(run(Input::File(file), &config));
                }
            }

            // If we were given a path via dump-minimal-config, output any options
            // that were used during formatting as TOML.
            if let Some(path) = minimal_config_path {
                let mut file = File::create(path)?;
                let toml = config.used_options().to_toml()?;
                file.write_all(toml.as_bytes())?;
            }

            Ok(error_summary)
        }
    }
}

fn main() {
    let _ = env_logger::init();

    let opts = make_opts();

    let exit_code = match execute(&opts) {
        Ok(summary) => {
            if summary.has_operational_errors() {
                1
            } else if summary.has_parsing_errors() {
                2
            } else if summary.has_formatting_errors() {
                3
            } else if summary.has_diff {
                // should only happen in diff mode
                4
            } else {
                assert!(summary.has_no_errors());
                0
            }
        }
        Err(e) => {
            print_usage(&opts, &e.to_string());
            1
        }
    };
    // Make sure standard output is flushed before we exit.
    std::io::stdout().flush().unwrap();

    // Exit with given exit code.
    //
    // NOTE: This immediately terminates the process without doing any cleanup,
    // so make sure to finish all necessary cleanup before this is called.
    std::process::exit(exit_code);
}

fn print_usage(opts: &Options, reason: &str) {
    let reason = format!(
        "{}\n\nusage: {} [options] <file>...",
        reason,
        env::args_os().next().unwrap().to_string_lossy()
    );
    println!("{}", opts.usage(&reason));
}

fn print_version() {
    println!(
        "{}-nightly{}",
        env!("CARGO_PKG_VERSION"),
        include_str!(concat!(env!("OUT_DIR"), "/commit-info.txt"))
    )
}

fn determine_operation(matches: &Matches) -> FmtResult<Operation> {
    if matches.opt_present("h") {
        return Ok(Operation::Help);
    }

    if matches.opt_present("config-help") {
        return Ok(Operation::ConfigHelp);
    }

    if let Some(path) = matches.opt_str("dump-default-config") {
        return Ok(Operation::ConfigOutputDefault { path });
    }

    if matches.opt_present("version") {
        return Ok(Operation::Version);
    }

    let config_path_not_found = |path: &str| -> FmtResult<Operation> {
        Err(FmtError::from(format!(
            "Error: unable to find a config file for the given path: `{}`",
            path
        )))
    };

    // Read the config_path and convert to parent dir if a file is provided.
    // If a config file cannot be found from the given path, return error.
    let config_path: Option<PathBuf> = match matches.opt_str("config-path").map(PathBuf::from) {
        Some(ref path) if !path.exists() => return config_path_not_found(path.to_str().unwrap()),
        Some(ref path) if path.is_dir() => {
            let config_file_path = get_toml_path(path)?;
            if config_file_path.is_some() {
                config_file_path
            } else {
                return config_path_not_found(path.to_str().unwrap());
            }
        }
        path @ _ => path,
    };

    // If no path is given, we won't output a minimal config.
    let minimal_config_path = matches.opt_str("dump-minimal-config");

    // if no file argument is supplied, read from stdin
    if matches.free.is_empty() {
        let mut buffer = String::new();
        io::stdin().read_to_string(&mut buffer)?;

        return Ok(Operation::Stdin {
            input: buffer,
            config_path: config_path,
        });
    }

    let files: Vec<_> = matches
        .free
        .iter()
        .map(|s| {
            let p = PathBuf::from(s);
            // we will do comparison later, so here tries to canonicalize first
            // to get the expected behavior.
            p.canonicalize().unwrap_or(p)
        })
        .collect();

    Ok(Operation::Format {
        files: files,
        config_path: config_path,
        minimal_config_path: minimal_config_path,
    })
}
<commit_msg>Add warning about write-mode change<commit_after>// Copyright 2015 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.

#![cfg(not(test))]

extern crate log;
extern crate rustfmt_nightly as rustfmt;
extern crate toml;
extern crate env_logger;
extern crate getopts;

use rustfmt::{run, Input, Summary};
use rustfmt::file_lines::FileLines;
use rustfmt::config::{Config, WriteMode, get_toml_path};

use std::{env, error};
use std::fs::File;
use std::io::{self, Read, Write};
use std::path::{Path, PathBuf};
use std::str::FromStr;

use getopts::{Matches, Options};

type FmtError = Box<error::Error + Send + Sync>;
type FmtResult<T> = std::result::Result<T, FmtError>;

/// Rustfmt operations.
enum Operation {
    /// Format files and their child modules.
    Format {
        files: Vec<PathBuf>,
        config_path: Option<PathBuf>,
        minimal_config_path: Option<String>,
    },
    /// Print the help message.
    Help,
    // Print version information
    Version,
    /// Print detailed configuration help.
    ConfigHelp,
    /// Output default config to a file
    ConfigOutputDefault { path: String },
    /// No file specified, read from stdin
    Stdin {
        input: String,
        config_path: Option<PathBuf>,
    },
}

/// Parsed command line options.
#[derive(Clone, Debug, Default)]
struct CliOptions {
    skip_children: bool,
    verbose: bool,
    write_mode: Option<WriteMode>,
    file_lines: FileLines, // Default is all lines in all files.
}

impl CliOptions {
    fn from_matches(matches: &Matches) -> FmtResult<CliOptions> {
        let mut options = CliOptions::default();
        options.skip_children = matches.opt_present("skip-children");
        options.verbose = matches.opt_present("verbose");

        if let Some(ref write_mode) = matches.opt_str("write-mode") {
            if let Ok(write_mode) = WriteMode::from_str(write_mode) {
                options.write_mode = Some(write_mode);
            } else {
                return Err(FmtError::from(
                    format!("Invalid write-mode: {}", write_mode),
                ));
            }
        } else {
            println!("Warning: the default write-mode for Rustfmt will soon change to overwrite - this will not leave backups of changed files.");
        }

        if let Some(ref file_lines) = matches.opt_str("file-lines") {
            options.file_lines = file_lines.parse()?;
        }

        Ok(options)
    }

    fn apply_to(self, config: &mut Config) {
        config.set().skip_children(self.skip_children);
        config.set().verbose(self.verbose);
        config.set().file_lines(self.file_lines);
        if let Some(write_mode) = self.write_mode {
            config.set().write_mode(write_mode);
        }
    }
}

/// read the given config file path recursively if present else read the project file path
fn match_cli_path_or_file(
    config_path: Option<PathBuf>,
    input_file: &Path,
) -> FmtResult<(Config, Option<PathBuf>)> {

    if let Some(config_file) = config_path {
        let toml = Config::from_toml_path(config_file.as_ref())?;
        return Ok((toml, Some(config_file)));
    }
    Config::from_resolved_toml_path(input_file).map_err(|e| FmtError::from(e))
}

fn make_opts() -> Options {
    let mut opts = Options::new();
    opts.optflag("h", "help", "show this message");
    opts.optflag("V", "version", "show version information");
    opts.optflag("v", "verbose", "print verbose output");
    opts.optopt(
        "",
        "write-mode",
        "mode to write in (not usable when piping from stdin)",
        "[replace|overwrite|display|diff|coverage|checkstyle]",
    );
    opts.optflag("", "skip-children", "don't reformat child modules");

    opts.optflag(
        "",
        "config-help",
        "show details of rustfmt configuration options",
    );
    opts.optopt(
        "",
        "dump-default-config",
        "Dumps the default configuration to a file and exits.",
        "PATH",
    );
    opts.optopt(
        "",
        "dump-minimal-config",
        "Dumps configuration options that were checked during formatting to a file.",
        "PATH",
    );
    opts.optopt(
        "",
        "config-path",
        "Recursively searches the given path for the rustfmt.toml config file. If not \
         found reverts to the input file path",
        "[Path for the configuration file]",
    );
    opts.optopt(
        "",
        "file-lines",
        "Format specified line ranges. See README for more detail on the JSON format.",
        "JSON",
    );

    opts
}

fn execute(opts: &Options) -> FmtResult<Summary> {
    let matches = opts.parse(env::args().skip(1))?;

    match determine_operation(&matches)? {
        Operation::Help => {
            print_usage(opts, "");
            Summary::print_exit_codes();
            Ok(Summary::new())
        }
        Operation::Version => {
            print_version();
            Ok(Summary::new())
        }
        Operation::ConfigHelp => {
            Config::print_docs();
            Ok(Summary::new())
        }
        Operation::ConfigOutputDefault { path } => {
            let mut file = File::create(path)?;
            let toml = Config::default().all_options().to_toml()?;
            file.write_all(toml.as_bytes())?;
            Ok(Summary::new())
        }
        Operation::Stdin { input, config_path } => {
            // try to read config from local directory
            let (mut config, _) =
                match_cli_path_or_file(config_path, &env::current_dir().unwrap())?;

            // write_mode is always Plain for Stdin.
            config.set().write_mode(WriteMode::Plain);

            // parse file_lines
            if let Some(ref file_lines) = matches.opt_str("file-lines") {
                config.set().file_lines(file_lines.parse()?);
                for f in config.file_lines().files() {
                    if f != "stdin" {
                        println!("Warning: Extra file listed in file_lines option '{}'", f);
                    }
                }
            }

            Ok(run(Input::Text(input), &config))
        }
        Operation::Format {
            files,
            config_path,
            minimal_config_path,
        } => {
            let options = CliOptions::from_matches(&matches)?;

            for f in options.file_lines.files() {
                if !files.contains(&PathBuf::from(f)) {
                    println!("Warning: Extra file listed in file_lines option '{}'", f);
                }
            }

            let mut config = Config::default();
            // Load the config path file if provided
            if let Some(config_file) = config_path.as_ref() {
                config = Config::from_toml_path(config_file.as_ref())?;
            };

            if options.verbose {
                if let Some(path) = config_path.as_ref() {
                    println!("Using rustfmt config file {}", path.display());
                }
            }

            let mut error_summary = Summary::new();
            for file in files {
                if !file.exists() {
                    println!("Error: file `{}` does not exist", file.to_str().unwrap());
                    error_summary.add_operational_error();
                } else if file.is_dir() {
                    println!("Error: `{}` is a directory", file.to_str().unwrap());
                    error_summary.add_operational_error();
                } else {
                    // Check the file directory if the config-path could not be read or not provided
                    if config_path.is_none() {
                        let (config_tmp, path_tmp) =
                            Config::from_resolved_toml_path(file.parent().unwrap())?;
                        if options.verbose {
                            if let Some(path) = path_tmp.as_ref() {
                                println!(
                                    "Using rustfmt config file {} for {}",
                                    path.display(),
                                    file.display()
                                );
                            }
                        }
                        config = config_tmp;
                    }

                    options.clone().apply_to(&mut config);
                    error_summary.add(run(Input::File(file), &config));
                }
            }

            // If we were given a path via dump-minimal-config, output any options
            // that were used during formatting as TOML.
            if let Some(path) = minimal_config_path {
                let mut file = File::create(path)?;
                let toml = config.used_options().to_toml()?;
                file.write_all(toml.as_bytes())?;
            }

            Ok(error_summary)
        }
    }
}

fn main() {
    let _ = env_logger::init();

    let opts = make_opts();

    let exit_code = match execute(&opts) {
        Ok(summary) => {
            if summary.has_operational_errors() {
                1
            } else if summary.has_parsing_errors() {
                2
            } else if summary.has_formatting_errors() {
                3
            } else if summary.has_diff {
                // should only happen in diff mode
                4
            } else {
                assert!(summary.has_no_errors());
                0
            }
        }
        Err(e) => {
            print_usage(&opts, &e.to_string());
            1
        }
    };
    // Make sure standard output is flushed before we exit.
    std::io::stdout().flush().unwrap();

    // Exit with given exit code.
    //
    // NOTE: This immediately terminates the process without doing any cleanup,
    // so make sure to finish all necessary cleanup before this is called.
    std::process::exit(exit_code);
}

fn print_usage(opts: &Options, reason: &str) {
    let reason = format!(
        "{}\n\nusage: {} [options] <file>...",
        reason,
        env::args_os().next().unwrap().to_string_lossy()
    );
    println!("{}", opts.usage(&reason));
}

fn print_version() {
    println!(
        "{}-nightly{}",
        env!("CARGO_PKG_VERSION"),
        include_str!(concat!(env!("OUT_DIR"), "/commit-info.txt"))
    )
}

fn determine_operation(matches: &Matches) -> FmtResult<Operation> {
    if matches.opt_present("h") {
        return Ok(Operation::Help);
    }

    if matches.opt_present("config-help") {
        return Ok(Operation::ConfigHelp);
    }

    if let Some(path) = matches.opt_str("dump-default-config") {
        return Ok(Operation::ConfigOutputDefault { path });
    }

    if matches.opt_present("version") {
        return Ok(Operation::Version);
    }

    let config_path_not_found = |path: &str| -> FmtResult<Operation> {
        Err(FmtError::from(format!(
            "Error: unable to find a config file for the given path: `{}`",
            path
        )))
    };

    // Read the config_path and convert to parent dir if a file is provided.
    // If a config file cannot be found from the given path, return error.
    let config_path: Option<PathBuf> = match matches.opt_str("config-path").map(PathBuf::from) {
        Some(ref path) if !path.exists() => return config_path_not_found(path.to_str().unwrap()),
        Some(ref path) if path.is_dir() => {
            let config_file_path = get_toml_path(path)?;
            if config_file_path.is_some() {
                config_file_path
            } else {
                return config_path_not_found(path.to_str().unwrap());
            }
        }
        path @ _ => path,
    };

    // If no path is given, we won't output a minimal config.
    let minimal_config_path = matches.opt_str("dump-minimal-config");

    // if no file argument is supplied, read from stdin
    if matches.free.is_empty() {
        let mut buffer = String::new();
        io::stdin().read_to_string(&mut buffer)?;

        return Ok(Operation::Stdin {
            input: buffer,
            config_path: config_path,
        });
    }

    let files: Vec<_> = matches
        .free
        .iter()
        .map(|s| {
            let p = PathBuf::from(s);
            // we will do comparison later, so here tries to canonicalize first
            // to get the expected behavior.
            p.canonicalize().unwrap_or(p)
        })
        .collect();

    Ok(Operation::Format {
        files: files,
        config_path: config_path,
        minimal_config_path: minimal_config_path,
    })
}
<|endoftext|>