<commit_before>use tokenizer::{TokenStream, Tokenizer, Token};
use std::collections::BTreeMap;
use Term;
use Document;
use Index;
use schema::FieldValue;
use schema::Value;
use tokenizer::BoxedTokenizer;
use htmlescape::encode_minimal;

#[derive(Debug)]
pub struct HighlightSection {
    start: usize,
    stop: usize,
}

impl HighlightSection {
    fn new(start: usize, stop: usize) -> HighlightSection {
        HighlightSection {
            start,
            stop
        }
    }
}

#[derive(Debug)]
pub struct FragmentCandidate {
    score: f32,
    start_offset: usize,
    stop_offset: usize,
    num_chars: usize,
    highlighted: Vec<HighlightSection>,
}

impl FragmentCandidate {

    /// Create a basic `FragmentCandidate`
    ///
    /// `score`, `num_chars` are set to 0
    /// and `highlighted` is set to empty vec
    /// stop_offset is set to start_offset, which is taken as a param.
    fn new(start_offset: usize) -> FragmentCandidate {
        FragmentCandidate{score: 0.0,
                          start_offset: start_offset,
                          stop_offset: start_offset,
                          num_chars: 0,
                          highlighted: vec![]}
    }

    /// Updates `score` and `highlighted` fields of the objects.
    ///
    /// taking the token and terms, the token is added to the fragment.
    /// if the token is one of the terms, the score
    /// and highlighted fields are updated in the fragment.
    fn try_add_token(&mut self, token: &Token, terms: &BTreeMap<String, f32>) {
        self.stop_offset = token.offset_to;

        if let Some(score) = terms.get(&token.text.to_lowercase()) {
            self.score += score;
            self.highlighted.push(HighlightSection{start: token.offset_from,
                                                   stop: token.offset_to});
        }
    }
}

#[derive(Debug)]
pub struct Snippet {
    fragments: String,
    highlighted: Vec<HighlightSection>,
}

const HIGHLIGHTEN_PREFIX:&str = "";
const HIGHLIGHTEN_POSTFIX:&str = "";

impl Snippet {

    /// Returns a hignlightned html from the `Snippet`.
    pub fn to_html(&self) -> String {
        let mut html = String::new();
        let mut start_from: usize = 0;

        for item in self.highlighted.iter() {
            html.push_str(&encode_minimal(&self.fragments[start_from..item.start]));
            html.push_str(HIGHLIGHTEN_PREFIX);
            html.push_str(&encode_minimal(&self.fragments[item.start..item.stop]));
            html.push_str(HIGHLIGHTEN_POSTFIX);
            start_from = item.stop;
        }
        html.push_str(&encode_minimal(&self.fragments[start_from..self.fragments.len()]));
        html
    }
}

/// Returns a non-empty list of "good" fragments.
///
/// If no target term is within the text, then the function
/// should return an empty Vec.
///
/// If a target term is within the text, then the returned
/// list is required to be non-empty.
///
/// The returned list is non-empty and contain less
/// than 12 possibly overlapping fragments.
///
/// All fragments should contain at least one target term
/// and have at most `max_num_chars` characters (not bytes).
///
/// It is ok to emit non-overlapping fragments, for instance,
/// one short and one long containing the same keyword, in order
/// to leave optimization opportunity to the fragment selector
/// upstream.
///
/// Fragments must be valid in the sense that `&text[fragment.start..fragment.stop]`\
/// has to be a valid string.
fn search_fragments<'a>(
    tokenizer: Box<BoxedTokenizer>,
    text: &'a str,
    terms: BTreeMap<String, f32>,
    max_num_chars: usize) -> Vec<FragmentCandidate> {
    let mut token_stream = tokenizer.token_stream(text);
    let mut fragment = FragmentCandidate::new(0);
    let mut fragments:Vec<FragmentCandidate> = vec![];

    while let Some(next) = token_stream.next() {
        if (next.offset_to - fragment.start_offset) > max_num_chars {
            if fragment.score > 0.0 {
                fragments.push(fragment)
            };
            fragment = FragmentCandidate::new(next.offset_from);
        }
        fragment.try_add_token(next, &terms);
    }
    if fragment.score > 0.0 {
        fragments.push(fragment)
    }

    fragments
}

/// Returns a Snippet
///
/// Takes a vector of `FragmentCandidate`s and the text.
/// Figures out the best fragment from it and creates a snippet.
fn select_best_fragment_combination<'a>(fragments: Vec<FragmentCandidate>,
                                        text: &'a str,) -> Snippet {
    if let Some(init) = fragments.iter().nth(0) {
        let fragment = fragments.iter().skip(1).fold(init, |acc, item| {
            if item.score > acc.score { item } else { acc }
        });
        let fragment_text = &text[fragment.start_offset..fragment.stop_offset];
        let highlighted = fragment.highlighted.iter().map(|item| {
            HighlightSection{start: item.start-fragment.start_offset,
                             stop: item.stop-fragment.start_offset}
        }).collect();
        Snippet{fragments: fragment_text.to_owned(),
                highlighted: highlighted}
    } else {
        // when there no fragments to chose from,
        // for now create a empty snippet
        Snippet{fragments: String::new(),
                highlighted: vec![]}
    }
}

pub fn generate_snippet<'a>(
    doc: &'a [FieldValue],
    index: &Index,
    terms: Vec<Term>,
    max_num_chars: usize) -> Snippet {
    unimplemented!();
}

#[cfg(test)]
mod tests {
    use tokenizer::{SimpleTokenizer, box_tokenizer};
    use std::iter::Iterator;
    use std::collections::BTreeMap;
    use super::{search_fragments, select_best_fragment_combination};

    #[test]
    fn test_snippet() {
        let tokenizer = SimpleTokenizer;

        let boxed_tokenizer = box_tokenizer(tokenizer);

        let text = "Rust is a systems programming language sponsored by Mozilla which describes it as a \"safe, concurrent, practical language\", supporting functional and imperative-procedural paradigms. Rust is syntactically similar to C++[according to whom?], but its designers intend it to provide better memory safety while still maintaining performance.

Rust is free and open-source software, released under an MIT License, or Apache License 2.0. Its designers have refined the language through the experiences of writing the Servo web browser layout engine[14] and the Rust compiler. A large proportion of current commits to the project are from community members.[15]

Rust won first place for \"most loved programming language\" in the Stack Overflow Developer Survey in 2016, 2017, and 2018.
";

        let mut terms = BTreeMap::new();
        terms.insert(String::from("rust"), 1.0);
        terms.insert(String::from("language"), 0.9);

        let fragments = search_fragments(boxed_tokenizer, &text, terms, 100);
        assert_eq!(fragments.len(), 7);
        {
            let first = fragments.iter().nth(0).unwrap();
            assert_eq!(first.score, 1.9);
            assert_eq!(first.stop_offset, 89);
        }
        let snippet = select_best_fragment_combination(fragments, &text);
        assert_eq!(snippet.fragments, "Rust is a systems programming language sponsored by Mozilla which describes it as a \"safe".to_owned());
        assert_eq!(snippet.to_html(), "Rust is a systems programming language sponsored by Mozilla which describes it as a "safe".to_owned())
    }

    #[test]
    fn test_snippet_in_second_fragment() {
        let tokenizer = SimpleTokenizer;

        let boxed_tokenizer = box_tokenizer(tokenizer);

        let text = "a b c d e f g";

        let mut terms = BTreeMap::new();
        terms.insert(String::from("c"), 1.0);

        let fragments = search_fragments(boxed_tokenizer, &text, terms, 3);

        assert_eq!(fragments.len(), 1);
        {
            let first = fragments.iter().nth(0).unwrap();
            assert_eq!(first.score, 1.0);
            assert_eq!(first.start_offset, 4);
            assert_eq!(first.stop_offset, 6);
        }

        let snippet = select_best_fragment_combination(fragments, &text);
        assert_eq!(snippet.fragments, "c d");
        assert_eq!(snippet.to_html(), "c d");
    }
}
<commit_msg>add more tests<commit_after>use tokenizer::{TokenStream, Tokenizer, Token};
use std::collections::BTreeMap;
use Term;
use Document;
use Index;
use schema::FieldValue;
use schema::Value;
use tokenizer::BoxedTokenizer;
use htmlescape::encode_minimal;

#[derive(Debug)]
pub struct HighlightSection {
    start: usize,
    stop: usize,
}

impl HighlightSection {
    fn new(start: usize, stop: usize) -> HighlightSection {
        HighlightSection {
            start,
            stop
        }
    }
}

#[derive(Debug)]
pub struct FragmentCandidate {
    score: f32,
    start_offset: usize,
    stop_offset: usize,
    num_chars: usize,
    highlighted: Vec<HighlightSection>,
}

impl FragmentCandidate {

    /// Create a basic `FragmentCandidate`
    ///
    /// `score`, `num_chars` are set to 0
    /// and `highlighted` is set to empty vec
    /// stop_offset is set to start_offset, which is taken as a param.
    fn new(start_offset: usize) -> FragmentCandidate {
        FragmentCandidate{score: 0.0,
                          start_offset: start_offset,
                          stop_offset: start_offset,
                          num_chars: 0,
                          highlighted: vec![]}
    }

    /// Updates `score` and `highlighted` fields of the objects.
    ///
    /// taking the token and terms, the token is added to the fragment.
    /// if the token is one of the terms, the score
    /// and highlighted fields are updated in the fragment.
    fn try_add_token(&mut self, token: &Token, terms: &BTreeMap<String, f32>) {
        self.stop_offset = token.offset_to;

        if let Some(score) = terms.get(&token.text.to_lowercase()) {
            self.score += score;
            self.highlighted.push(HighlightSection{start: token.offset_from,
                                                   stop: token.offset_to});
        }
    }
}

#[derive(Debug)]
pub struct Snippet {
    fragments: String,
    highlighted: Vec<HighlightSection>,
}

const HIGHLIGHTEN_PREFIX:&str = "";
const HIGHLIGHTEN_POSTFIX:&str = "";

impl Snippet {

    /// Returns a hignlightned html from the `Snippet`.
    pub fn to_html(&self) -> String {
        let mut html = String::new();
        let mut start_from: usize = 0;

        for item in self.highlighted.iter() {
            html.push_str(&encode_minimal(&self.fragments[start_from..item.start]));
            html.push_str(HIGHLIGHTEN_PREFIX);
            html.push_str(&encode_minimal(&self.fragments[item.start..item.stop]));
            html.push_str(HIGHLIGHTEN_POSTFIX);
            start_from = item.stop;
        }
        html.push_str(&encode_minimal(&self.fragments[start_from..self.fragments.len()]));
        html
    }
}

/// Returns a non-empty list of "good" fragments.
///
/// If no target term is within the text, then the function
/// should return an empty Vec.
///
/// If a target term is within the text, then the returned
/// list is required to be non-empty.
///
/// The returned list is non-empty and contain less
/// than 12 possibly overlapping fragments.
///
/// All fragments should contain at least one target term
/// and have at most `max_num_chars` characters (not bytes).
///
/// It is ok to emit non-overlapping fragments, for instance,
/// one short and one long containing the same keyword, in order
/// to leave optimization opportunity to the fragment selector
/// upstream.
///
/// Fragments must be valid in the sense that `&text[fragment.start..fragment.stop]`\
/// has to be a valid string.
fn search_fragments<'a>(
    tokenizer: Box<BoxedTokenizer>,
    text: &'a str,
    terms: BTreeMap<String, f32>,
    max_num_chars: usize) -> Vec<FragmentCandidate> {
    let mut token_stream = tokenizer.token_stream(text);
    let mut fragment = FragmentCandidate::new(0);
    let mut fragments:Vec<FragmentCandidate> = vec![];

    while let Some(next) = token_stream.next() {
        if (next.offset_to - fragment.start_offset) > max_num_chars {
            if fragment.score > 0.0 {
                fragments.push(fragment)
            };
            fragment = FragmentCandidate::new(next.offset_from);
        }
        fragment.try_add_token(next, &terms);
    }
    if fragment.score > 0.0 {
        fragments.push(fragment)
    }

    fragments
}

/// Returns a Snippet
///
/// Takes a vector of `FragmentCandidate`s and the text.
/// Figures out the best fragment from it and creates a snippet.
fn select_best_fragment_combination<'a>(fragments: Vec<FragmentCandidate>,
                                        text: &'a str,) -> Snippet {
    if let Some(init) = fragments.iter().nth(0) {
        let fragment = fragments.iter().skip(1).fold(init, |acc, item| {
            if item.score > acc.score { item } else { acc }
        });
        let fragment_text = &text[fragment.start_offset..fragment.stop_offset];
        let highlighted = fragment.highlighted.iter().map(|item| {
            HighlightSection{start: item.start-fragment.start_offset,
                             stop: item.stop-fragment.start_offset}
        }).collect();
        Snippet{fragments: fragment_text.to_owned(),
                highlighted: highlighted}
    } else {
        // when there no fragments to chose from,
        // for now create a empty snippet
        Snippet{fragments: String::new(),
                highlighted: vec![]}
    }
}

pub fn generate_snippet<'a>(
    doc: &'a [FieldValue],
    index: &Index,
    terms: Vec<Term>,
    max_num_chars: usize) -> Snippet {
    unimplemented!();
}

#[cfg(test)]
mod tests {
    use tokenizer::{SimpleTokenizer, box_tokenizer};
    use std::iter::Iterator;
    use std::collections::BTreeMap;
    use super::{search_fragments, select_best_fragment_combination};

    const TOKENIZER:SimpleTokenizer = SimpleTokenizer;

    #[test]
    fn test_snippet() {
        let boxed_tokenizer = box_tokenizer(TOKENIZER);

        let text = "Rust is a systems programming language sponsored by Mozilla which describes it as a \"safe, concurrent, practical language\", supporting functional and imperative-procedural paradigms. Rust is syntactically similar to C++[according to whom?], but its designers intend it to provide better memory safety while still maintaining performance.

Rust is free and open-source software, released under an MIT License, or Apache License 2.0. Its designers have refined the language through the experiences of writing the Servo web browser layout engine[14] and the Rust compiler. A large proportion of current commits to the project are from community members.[15]

Rust won first place for \"most loved programming language\" in the Stack Overflow Developer Survey in 2016, 2017, and 2018.
";

        let mut terms = BTreeMap::new();
        terms.insert(String::from("rust"), 1.0);
        terms.insert(String::from("language"), 0.9);

        let fragments = search_fragments(boxed_tokenizer, &text, terms, 100);
        assert_eq!(fragments.len(), 7);
        {
            let first = fragments.iter().nth(0).unwrap();
            assert_eq!(first.score, 1.9);
            assert_eq!(first.stop_offset, 89);
        }
        let snippet = select_best_fragment_combination(fragments, &text);
        assert_eq!(snippet.fragments, "Rust is a systems programming language sponsored by Mozilla which describes it as a \"safe".to_owned());
        assert_eq!(snippet.to_html(), "Rust is a systems programming language sponsored by Mozilla which describes it as a "safe".to_owned())
    }

    #[test]
    fn test_snippet_in_second_fragment() {
        let boxed_tokenizer = box_tokenizer(TOKENIZER);

        let text = "a b c d e f g";

        let mut terms = BTreeMap::new();
        terms.insert(String::from("c"), 1.0);

        let fragments = search_fragments(boxed_tokenizer, &text, terms, 3);

        assert_eq!(fragments.len(), 1);
        {
            let first = fragments.iter().nth(0).unwrap();
            assert_eq!(first.score, 1.0);
            assert_eq!(first.start_offset, 4);
            assert_eq!(first.stop_offset, 7);
        }

        let snippet = select_best_fragment_combination(fragments, &text);
        assert_eq!(snippet.fragments, "c d");
        assert_eq!(snippet.to_html(), "c d");
    }

    #[test]
    fn test_snippet_with_term_at_the_end_of_fragment() {
        let boxed_tokenizer = box_tokenizer(TOKENIZER);

        let text = "a b c d e f f g";

        let mut terms = BTreeMap::new();
        terms.insert(String::from("f"), 1.0);

        let fragments = search_fragments(boxed_tokenizer, &text, terms, 3);

        assert_eq!(fragments.len(), 2);
        {
            let first = fragments.iter().nth(0).unwrap();
            assert_eq!(first.score, 1.0);
            assert_eq!(first.stop_offset, 11);
            assert_eq!(first.start_offset, 8);
        }

        let snippet = select_best_fragment_combination(fragments, &text);
        assert_eq!(snippet.fragments, "e f");
        assert_eq!(snippet.to_html(), "e f");
    }

    #[test]
    fn test_snippet_with_term_not_in_text() {
        let boxed_tokenizer = box_tokenizer(TOKENIZER);

        let text = "a b c d";

        let mut terms = BTreeMap::new();
        terms.insert(String::from("z"), 1.0);

        let fragments = search_fragments(boxed_tokenizer, &text, terms, 3);

        assert_eq!(fragments.len(), 0);

        let snippet = select_best_fragment_combination(fragments, &text);
        assert_eq!(snippet.fragments, "");
        assert_eq!(snippet.to_html(), "");
    }

    fn test_snippet_with_no_terms() {
        let boxed_tokenizer = box_tokenizer(TOKENIZER);

        let text = "a b c d";

        let mut terms = BTreeMap::new();
        let fragments = search_fragments(boxed_tokenizer, &text, terms, 3);
        assert_eq!(fragments.len(), 0);

        let snippet = select_best_fragment_combination(fragments, &text);
        assert_eq!(snippet.fragments, "");
        assert_eq!(snippet.to_html(), "");
    }
}
<|endoftext|>


<commit_before>
use super::Fixed;
use Error;
use Result;
use std::io::Cursor;
use byteorder::{BigEndian, ReadBytesExt};

#[derive(Debug, Default)]
pub struct HHEA {
    version: Fixed,
    ascent: i16,
    descent: i16,
    line_gap: i16,
    advance_width_max: u16,
    min_left_side_bearing: i16,
    min_right_side_bearing: i16,
    x_max_extent: i16,
    caret_slope_rise: i16,
    caret_slope_run: i16,
    caret_offset: i16,
    reserved1: i16,
    reserved2: i16,
    reserved3: i16,
    reserved4: i16,
    metric_data_format: i16,
    num_of_long_hor_metrics: u16,
}

impl HHEA {
    pub fn from_data(data: &[u8]) -> Result<HHEA> {
        let mut cursor = Cursor::new(data);
        let version = Fixed(try!(cursor.read_i32::<BigEndian>()));
        if version != Fixed(0x00010000) {
            return Err(Error::HHEAVersionNotSupported);
        }

        let mut hhea = HHEA::default();
        hhea.version = version;
        hhea.ascent = try!(cursor.read_i16::<BigEndian>());
        hhea.descent = try!(cursor.read_i16::<BigEndian>());
        hhea.line_gap = try!(cursor.read_i16::<BigEndian>());
        hhea.advance_width_max = try!(cursor.read_u16::<BigEndian>());
        hhea.min_left_side_bearing = try!(cursor.read_i16::<BigEndian>());
        hhea.min_right_side_bearing = try!(cursor.read_i16::<BigEndian>());
        hhea.x_max_extent = try!(cursor.read_i16::<BigEndian>());
        hhea.caret_slope_rise = try!(cursor.read_i16::<BigEndian>());
        hhea.caret_slope_run = try!(cursor.read_i16::<BigEndian>());
        hhea.caret_offset = try!(cursor.read_i16::<BigEndian>());
        hhea.reserved1 = try!(cursor.read_i16::<BigEndian>());
        hhea.reserved2 = try!(cursor.read_i16::<BigEndian>());
        hhea.reserved3 = try!(cursor.read_i16::<BigEndian>());
        hhea.reserved4 = try!(cursor.read_i16::<BigEndian>());
        hhea.metric_data_format = try!(cursor.read_i16::<BigEndian>());
        hhea.num_of_long_hor_metrics = try!(cursor.read_u16::<BigEndian>());

        Ok(hhea)
    }
}

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

    const SIZE: usize = 16 * 2 + 4;
    const OFFSET: usize = 340;

    #[test]
    fn runner() {
        let data = super::super::read_file("tests/Tuffy_Bold.ttf");
        success_read(&data);
    }

    fn success_read(data: &[u8]) {
        let _ = HHEA::from_data(&data[OFFSET..OFFSET + SIZE]).unwrap();
    }
}

<commit_msg>Add bytes method for hhea font table.<commit_after>
use super::Fixed;
use Error;
use Result;
use std::io::Cursor;
use byteorder::{BigEndian, ReadBytesExt};

#[derive(Debug, Default)]
pub struct HHEA {
    version: Fixed,
    ascent: i16,
    descent: i16,
    line_gap: i16,
    advance_width_max: u16,
    min_left_side_bearing: i16,
    min_right_side_bearing: i16,
    x_max_extent: i16,
    caret_slope_rise: i16,
    caret_slope_run: i16,
    caret_offset: i16,
    reserved1: i16,
    reserved2: i16,
    reserved3: i16,
    reserved4: i16,
    metric_data_format: i16,
    num_of_long_hor_metrics: u16,
}

impl HHEA {
    pub fn from_data(data: &[u8]) -> Result<HHEA> {
        let mut cursor = Cursor::new(data);
        let version = Fixed(try!(cursor.read_i32::<BigEndian>()));
        if version != Fixed(0x00010000) {
            return Err(Error::HHEAVersionNotSupported);
        }

        let mut hhea = HHEA::default();
        hhea.version = version;
        hhea.ascent = try!(cursor.read_i16::<BigEndian>());
        hhea.descent = try!(cursor.read_i16::<BigEndian>());
        hhea.line_gap = try!(cursor.read_i16::<BigEndian>());
        hhea.advance_width_max = try!(cursor.read_u16::<BigEndian>());
        hhea.min_left_side_bearing = try!(cursor.read_i16::<BigEndian>());
        hhea.min_right_side_bearing = try!(cursor.read_i16::<BigEndian>());
        hhea.x_max_extent = try!(cursor.read_i16::<BigEndian>());
        hhea.caret_slope_rise = try!(cursor.read_i16::<BigEndian>());
        hhea.caret_slope_run = try!(cursor.read_i16::<BigEndian>());
        hhea.caret_offset = try!(cursor.read_i16::<BigEndian>());
        hhea.reserved1 = try!(cursor.read_i16::<BigEndian>());
        hhea.reserved2 = try!(cursor.read_i16::<BigEndian>());
        hhea.reserved3 = try!(cursor.read_i16::<BigEndian>());
        hhea.reserved4 = try!(cursor.read_i16::<BigEndian>());
        hhea.metric_data_format = try!(cursor.read_i16::<BigEndian>());
        hhea.num_of_long_hor_metrics = try!(cursor.read_u16::<BigEndian>());

        Ok(hhea)
    }

    #[cfg(test)]
    fn bytes(&self) -> Vec<u8> {
        use byteorder::WriteBytesExt;

        let mut data = vec![];
        data.write_i32::<BigEndian>(self.version.0).unwrap();
        data.write_i16::<BigEndian>(self.ascent).unwrap();
        data.write_i16::<BigEndian>(self.descent).unwrap();
        data.write_i16::<BigEndian>(self.line_gap).unwrap();
        data.write_u16::<BigEndian>(self.advance_width_max).unwrap();
        data.write_i16::<BigEndian>(self.min_left_side_bearing).unwrap();
        data.write_i16::<BigEndian>(self.min_right_side_bearing).unwrap();
        data.write_i16::<BigEndian>(self.x_max_extent).unwrap();
        data.write_i16::<BigEndian>(self.caret_slope_rise).unwrap();
        data.write_i16::<BigEndian>(self.caret_slope_run).unwrap();
        data.write_i16::<BigEndian>(self.caret_offset).unwrap();
        data.write_i16::<BigEndian>(self.reserved1).unwrap();
        data.write_i16::<BigEndian>(self.reserved2).unwrap();
        data.write_i16::<BigEndian>(self.reserved3).unwrap();
        data.write_i16::<BigEndian>(self.reserved4).unwrap();
        data.write_i16::<BigEndian>(self.metric_data_format).unwrap();
        data.write_u16::<BigEndian>(self.num_of_long_hor_metrics).unwrap();
        data
    }
}

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

    const OFFSET: usize = 340;
    const SIZE: usize = 16 * 2 + 4;

    #[test]
    fn runner() {
        let data = super::super::read_file("tests/Tuffy_Bold.ttf");
        test_read_write(&data);
    }

    fn test_read_write(data: &[u8]) {
        let data = &data[OFFSET..OFFSET + SIZE];
        let hhea = HHEA::from_data(data).unwrap();
        assert_eq!(hhea.bytes(), data);
    }
}

<|endoftext|>


<commit_before>#![crate_name = "uu_touch"]

// This file is part of the uutils coreutils package.
//
// (c) Nick Platt <platt.nicholas@gmail.com>
// (c) Jian Zeng <anonymousknight96 AT gmail.com>
//
// For the full copyright and license information, please view the LICENSE file
// that was distributed with this source code.
//

pub extern crate filetime;
extern crate getopts;
extern crate time;

#[macro_use]
extern crate uucore;

use filetime::*;
use std::fs::{self, File};
use std::io::{self, Error};
use std::path::Path;

static NAME: &str = "touch";
static VERSION: &str = env!("CARGO_PKG_VERSION");

// Since touch's date/timestamp parsing doesn't account for timezone, the
// returned value from time::strptime() is UTC. We get system's timezone to
// localize the time.
macro_rules! to_local(
    ($exp:expr) => ({
        let mut tm = $exp;
        tm.tm_utcoff = time::now().tm_utcoff;
        tm
    })
);

macro_rules! local_tm_to_filetime(
    ($exp:expr) => ({
        let ts = $exp.to_timespec();
        FileTime::from_unix_time(ts.sec as i64, ts.nsec as u32)
    })
);

macro_rules! to_timeval {
    ($ft:expr) => (
        timeval {
            tv_sec: $ft.seconds() as time_t,
            tv_usec: ($ft.nanoseconds() / 1000) as suseconds_t,
        }
    )
}

#[cfg(unix)]
fn set_symlink_times(p: &str, atime: FileTime, mtime: FileTime) -> io::Result<()> {
    use std::ffi::CString;
    use uucore::libc::{lutimes, suseconds_t, time_t, timeval};

    let times = [to_timeval!(atime), to_timeval!(mtime)];
    let p = try!(CString::new(p));
    return unsafe {
        if lutimes(p.as_ptr() as *const _, times.as_ptr()) == 0 {
            Ok(())
        } else {
            Err(io::Error::last_os_error())
        }
    };
}

pub fn uumain(args: Vec<String>) -> i32 {
    let mut opts = getopts::Options::new();

    opts.optflag("a", "", "change only the access time");
    opts.optflag("c", "no-create", "do not create any files");
    opts.optopt(
        "d",
        "date",
        "parse argument and use it instead of current time",
        "STRING",
    );
    opts.optflag(
        "h",
        "no-dereference",
        "affect each symbolic link instead of any referenced file \
         (only for systems that can change the timestamps of a symlink)",
    );
    opts.optflag("m", "", "change only the modification time");
    opts.optopt(
        "r",
        "reference",
        "use this file's times instead of the current time",
        "FILE",
    );
    opts.optopt(
        "t",
        "",
        "use [[CC]YY]MMDDhhmm[.ss] instead of the current time",
        "STAMP",
    );
    opts.optopt(
        "",
        "time",
        "change only the specified time: \"access\", \"atime\", or \
         \"use\" are equivalent to -a; \"modify\" or \"mtime\" are \
         equivalent to -m",
        "WORD",
    );
    opts.optflag("h", "help", "display this help and exit");
    opts.optflag("V", "version", "output version information and exit");

    let matches = match opts.parse(&args[1..]) {
        Ok(m) => m,
        Err(e) => panic!("Invalid options\n{}", e),
    };

    if matches.opt_present("version") {
        println!("{} {}", NAME, VERSION);
        return 0;
    }

    if matches.opt_present("help") || matches.free.is_empty() {
        println!("{} {}", NAME, VERSION);
        println!("");
        println!("Usage: {} [OPTION]... FILE...", NAME);
        println!("");
        println!(
            "{}",
            opts.usage(
                "Update the access and modification times of \
                 each FILE to the current time."
            )
        );
        if matches.free.is_empty() {
            return 1;
        }
        return 0;
    }

    if matches.opt_present("date")
        && matches.opts_present(&["reference".to_owned(), "t".to_owned()])
        || matches.opt_present("reference")
            && matches.opts_present(&["date".to_owned(), "t".to_owned()])
        || matches.opt_present("t")
            && matches.opts_present(&["date".to_owned(), "reference".to_owned()])
    {
        panic!("Invalid options: cannot specify reference time from more than one source");
    }

    let (mut atime, mut mtime) = if matches.opt_present("reference") {
        stat(
            &matches.opt_str("reference").unwrap()[..],
            !matches.opt_present("no-dereference"),
        )
    } else if matches.opts_present(&["date".to_owned(), "t".to_owned()]) {
        let timestamp = if matches.opt_present("date") {
            parse_date(matches.opt_str("date").unwrap().as_ref())
        } else {
            parse_timestamp(matches.opt_str("t").unwrap().as_ref())
        };
        (timestamp, timestamp)
    } else {
        let now = local_tm_to_filetime!(time::now());
        (now, now)
    };

    for filename in &matches.free {
        let path = &filename[..];

        if !Path::new(path).exists() {
            // no-dereference included here for compatibility
            if matches.opts_present(&["no-create".to_owned(), "no-dereference".to_owned()]) {
                continue;
            }

            if let Err(e) = File::create(path) {
                show_warning!("cannot touch '{}': {}", path, e);
                continue;
            };

            // Minor optimization: if no reference time was specified, we're done.
            if !matches.opts_present(&["date".to_owned(), "reference".to_owned(), "t".to_owned()]) {
                continue;
            }
        }

        // If changing "only" atime or mtime, grab the existing value of the other.
        // Note that "-a" and "-m" may be passed together; this is not an xor.
        if matches.opts_present(&["a".to_owned(), "m".to_owned(), "time".to_owned()]) {
            let st = stat(path, !matches.opt_present("no-dereference"));
            let time = matches.opt_strs("time");

            if !(matches.opt_present("a") || time.contains(&"access".to_owned())
                || time.contains(&"atime".to_owned())
                || time.contains(&"use".to_owned()))
            {
                atime = st.0;
            }

            if !(matches.opt_present("m") || time.contains(&"modify".to_owned())
                || time.contains(&"mtime".to_owned()))
            {
                mtime = st.1;
            }
        }

        if matches.opt_present("h") {
            if let Err(e) = set_symlink_times(path, atime, mtime) {
                show_warning!("cannot touch '{}': {}", path, e);
            }
        } else {
            if let Err(e) = filetime::set_file_times(path, atime, mtime) {
                show_warning!("cannot touch '{}': {}", path, e);
            }
        }
    }

    0
}

fn stat(path: &str, follow: bool) -> (FileTime, FileTime) {
    let metadata = if follow {
        fs::symlink_metadata(path)
    } else {
        fs::metadata(path)
    };

    match metadata {
        Ok(m) => (
            FileTime::from_last_access_time(&m),
            FileTime::from_last_modification_time(&m),
        ),
        Err(_) => crash!(
            1,
            "failed to get attributes of '{}': {}",
            path,
            Error::last_os_error()
        ),
    }
}

fn parse_date(str: &str) -> FileTime {
    // This isn't actually compatible with GNU touch, but there doesn't seem to
    // be any simple specification for what format this parameter allows and I'm
    // not about to implement GNU parse_datetime.
    // http://git.savannah.gnu.org/gitweb/?p=gnulib.git;a=blob_plain;f=lib/parse-datetime.y
    match time::strptime(str, "%c") {
        Ok(tm) => local_tm_to_filetime!(to_local!(tm)),
        Err(e) => panic!("Unable to parse date\n{}", e),
    }
}

fn parse_timestamp(s: &str) -> FileTime {
    let now = time::now();
    let (format, ts) = match s.chars().count() {
        15 => ("%Y%m%d%H%M.%S", s.to_owned()),
        12 => ("%Y%m%d%H%M", s.to_owned()),
        13 => ("%y%m%d%H%M.%S", s.to_owned()),
        10 => ("%y%m%d%H%M", s.to_owned()),
        11 => ("%Y%m%d%H%M.%S", format!("{}{}", now.tm_year + 1900, s)),
        8 => ("%Y%m%d%H%M", format!("{}{}", now.tm_year + 1900, s)),
        _ => panic!("Unknown timestamp format"),
    };

    match time::strptime(&ts, format) {
        Ok(tm) => local_tm_to_filetime!(to_local!(tm)),
        Err(e) => panic!("Unable to parse timestamp\n{}", e),
    }
}
<commit_msg>touch: fix ~ use `set_symlink_file_times()` + remove unused code<commit_after>#![crate_name = "uu_touch"]

// This file is part of the uutils coreutils package.
//
// (c) Nick Platt <platt.nicholas@gmail.com>
// (c) Jian Zeng <anonymousknight96 AT gmail.com>
//
// For the full copyright and license information, please view the LICENSE file
// that was distributed with this source code.
//

pub extern crate filetime;
extern crate getopts;
extern crate time;

#[macro_use]
extern crate uucore;

use filetime::*;
use std::fs::{self, File};
use std::io::Error;
use std::path::Path;

static NAME: &str = "touch";
static VERSION: &str = env!("CARGO_PKG_VERSION");

// Since touch's date/timestamp parsing doesn't account for timezone, the
// returned value from time::strptime() is UTC. We get system's timezone to
// localize the time.
macro_rules! to_local(
    ($exp:expr) => ({
        let mut tm = $exp;
        tm.tm_utcoff = time::now().tm_utcoff;
        tm
    })
);

macro_rules! local_tm_to_filetime(
    ($exp:expr) => ({
        let ts = $exp.to_timespec();
        FileTime::from_unix_time(ts.sec as i64, ts.nsec as u32)
    })
);

pub fn uumain(args: Vec<String>) -> i32 {
    let mut opts = getopts::Options::new();

    opts.optflag("a", "", "change only the access time");
    opts.optflag("c", "no-create", "do not create any files");
    opts.optopt(
        "d",
        "date",
        "parse argument and use it instead of current time",
        "STRING",
    );
    opts.optflag(
        "h",
        "no-dereference",
        "affect each symbolic link instead of any referenced file \
         (only for systems that can change the timestamps of a symlink)",
    );
    opts.optflag("m", "", "change only the modification time");
    opts.optopt(
        "r",
        "reference",
        "use this file's times instead of the current time",
        "FILE",
    );
    opts.optopt(
        "t",
        "",
        "use [[CC]YY]MMDDhhmm[.ss] instead of the current time",
        "STAMP",
    );
    opts.optopt(
        "",
        "time",
        "change only the specified time: \"access\", \"atime\", or \
         \"use\" are equivalent to -a; \"modify\" or \"mtime\" are \
         equivalent to -m",
        "WORD",
    );
    opts.optflag("h", "help", "display this help and exit");
    opts.optflag("V", "version", "output version information and exit");

    let matches = match opts.parse(&args[1..]) {
        Ok(m) => m,
        Err(e) => panic!("Invalid options\n{}", e),
    };

    if matches.opt_present("version") {
        println!("{} {}", NAME, VERSION);
        return 0;
    }

    if matches.opt_present("help") || matches.free.is_empty() {
        println!("{} {}", NAME, VERSION);
        println!("");
        println!("Usage: {} [OPTION]... FILE...", NAME);
        println!("");
        println!(
            "{}",
            opts.usage(
                "Update the access and modification times of \
                 each FILE to the current time."
            )
        );
        if matches.free.is_empty() {
            return 1;
        }
        return 0;
    }

    if matches.opt_present("date")
        && matches.opts_present(&["reference".to_owned(), "t".to_owned()])
        || matches.opt_present("reference")
            && matches.opts_present(&["date".to_owned(), "t".to_owned()])
        || matches.opt_present("t")
            && matches.opts_present(&["date".to_owned(), "reference".to_owned()])
    {
        panic!("Invalid options: cannot specify reference time from more than one source");
    }

    let (mut atime, mut mtime) = if matches.opt_present("reference") {
        stat(
            &matches.opt_str("reference").unwrap()[..],
            !matches.opt_present("no-dereference"),
        )
    } else if matches.opts_present(&["date".to_owned(), "t".to_owned()]) {
        let timestamp = if matches.opt_present("date") {
            parse_date(matches.opt_str("date").unwrap().as_ref())
        } else {
            parse_timestamp(matches.opt_str("t").unwrap().as_ref())
        };
        (timestamp, timestamp)
    } else {
        let now = local_tm_to_filetime!(time::now());
        (now, now)
    };

    for filename in &matches.free {
        let path = &filename[..];

        if !Path::new(path).exists() {
            // no-dereference included here for compatibility
            if matches.opts_present(&["no-create".to_owned(), "no-dereference".to_owned()]) {
                continue;
            }

            if let Err(e) = File::create(path) {
                show_warning!("cannot touch '{}': {}", path, e);
                continue;
            };

            // Minor optimization: if no reference time was specified, we're done.
            if !matches.opts_present(&["date".to_owned(), "reference".to_owned(), "t".to_owned()]) {
                continue;
            }
        }

        // If changing "only" atime or mtime, grab the existing value of the other.
        // Note that "-a" and "-m" may be passed together; this is not an xor.
        if matches.opts_present(&["a".to_owned(), "m".to_owned(), "time".to_owned()]) {
            let st = stat(path, !matches.opt_present("no-dereference"));
            let time = matches.opt_strs("time");

            if !(matches.opt_present("a") || time.contains(&"access".to_owned())
                || time.contains(&"atime".to_owned())
                || time.contains(&"use".to_owned()))
            {
                atime = st.0;
            }

            if !(matches.opt_present("m") || time.contains(&"modify".to_owned())
                || time.contains(&"mtime".to_owned()))
            {
                mtime = st.1;
            }
        }

        if matches.opt_present("h") {
            if let Err(e) = set_symlink_file_times(path, atime, mtime) {
                show_warning!("cannot touch '{}': {}", path, e);
            }
        } else {
            if let Err(e) = filetime::set_file_times(path, atime, mtime) {
                show_warning!("cannot touch '{}': {}", path, e);
            }
        }
    }

    0
}

fn stat(path: &str, follow: bool) -> (FileTime, FileTime) {
    let metadata = if follow {
        fs::symlink_metadata(path)
    } else {
        fs::metadata(path)
    };

    match metadata {
        Ok(m) => (
            FileTime::from_last_access_time(&m),
            FileTime::from_last_modification_time(&m),
        ),
        Err(_) => crash!(
            1,
            "failed to get attributes of '{}': {}",
            path,
            Error::last_os_error()
        ),
    }
}

fn parse_date(str: &str) -> FileTime {
    // This isn't actually compatible with GNU touch, but there doesn't seem to
    // be any simple specification for what format this parameter allows and I'm
    // not about to implement GNU parse_datetime.
    // http://git.savannah.gnu.org/gitweb/?p=gnulib.git;a=blob_plain;f=lib/parse-datetime.y
    match time::strptime(str, "%c") {
        Ok(tm) => local_tm_to_filetime!(to_local!(tm)),
        Err(e) => panic!("Unable to parse date\n{}", e),
    }
}

fn parse_timestamp(s: &str) -> FileTime {
    let now = time::now();
    let (format, ts) = match s.chars().count() {
        15 => ("%Y%m%d%H%M.%S", s.to_owned()),
        12 => ("%Y%m%d%H%M", s.to_owned()),
        13 => ("%y%m%d%H%M.%S", s.to_owned()),
        10 => ("%y%m%d%H%M", s.to_owned()),
        11 => ("%Y%m%d%H%M.%S", format!("{}{}", now.tm_year + 1900, s)),
        8 => ("%Y%m%d%H%M", format!("{}{}", now.tm_year + 1900, s)),
        _ => panic!("Unknown timestamp format"),
    };

    match time::strptime(&ts, format) {
        Ok(tm) => local_tm_to_filetime!(to_local!(tm)),
        Err(e) => panic!("Unable to parse timestamp\n{}", e),
    }
}
<|endoftext|>


<commit_before>// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements.  See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership.  The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License.  You may obtain a copy of the License at
//
//   http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied.  See the License for the
// specific language governing permissions and limitations
// under the License.

use std::cell::Cell;
use std::cmp;
use std::fmt::{Display, Result as FmtResult, Formatter};
use std::io::{Result as IoResult, Write};
use std::mem;
use std::rc::Rc;

use arena::TypedArena;

// ----------------------------------------------------------------------
// Buffer classes

/// A resize-able byte buffer class.
///
/// Note that a byte buffer has two attributes:
/// `capacity` and `size`: the former is the total bytes allocated for
/// the buffer, while the latter is the actual bytes that have valid data.
/// Invariant: `capacity` >= `size`.
#[derive(Debug, PartialEq)]
pub struct ByteBuffer {
  data: Vec<u8>
}

impl ByteBuffer {
  pub fn new() -> Self {
    ByteBuffer { data: vec!() }
  }

  pub fn new_with_cap(init_cap: usize) -> Self {
    ByteBuffer { data: Vec::with_capacity(init_cap) }
  }

  pub fn data(&self) -> &[u8] {
    self.data.as_slice()
  }

  pub fn set_data(&mut self, new_data: Vec<u8>) {
    self.data = new_data;
  }

  pub fn resize(&mut self, new_capacity: usize) {
    let extra_capacity = new_capacity - self.data.capacity();
    if extra_capacity > 0 {
      self.data.reserve(extra_capacity);
    }
  }

  pub fn consume(&mut self) -> BytePtr {
    let old_data = mem::replace(&mut self.data, vec!());
    BytePtr::new(old_data)
  }

  pub fn capacity(&self) -> usize {
    self.data.capacity()
  }

  pub fn size(&self) -> usize {
    self.data.len()
  }
}

impl Write for ByteBuffer {
  fn write(&mut self, buf: &[u8]) -> IoResult<usize> {
    // Check if we have enough capacity for the new data
    if self.data.len() + buf.len() > self.data.capacity() {
      let new_capacity = ::std::cmp::max(
        self.data.capacity() * 2, self.data.len() + buf.len());
      self.resize(new_capacity);
    }
    self.data.write(buf)
  }

  fn flush(&mut self) -> IoResult<()> {
    self.data.flush()
  }
}

// ----------------------------------------------------------------------
// Immutable Buffer (BytePtr) classes

/// An representation of a slice on a reference-counting and read-only byte array.
/// Sub-slices can be further created from this. The byte array will be released
/// when all slices are dropped.
#[derive(Clone, Debug, PartialEq)]
pub struct BytePtr {
  data: Rc<Vec<u8>>,
  start: usize,
  len: usize
}

impl BytePtr {
  pub fn new(v: Vec<u8>) -> Self {
    let len = v.len();
    Self { data: Rc::new(v), start: 0, len: len }
  }

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

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

  pub fn all(&self) -> BytePtr {
    BytePtr { data: self.data.clone(), start: self.start, len: self.len }
  }

  pub fn start_from(&self, start: usize) -> BytePtr {
    assert!(start <= self.len);
    BytePtr { data: self.data.clone(), start: self.start + start, len: self.len - start }
  }

  pub fn range(&self, start: usize, len: usize) -> BytePtr {
    assert!(start + len <= self.len);
    BytePtr { data: self.data.clone(), start: self.start + start, len: len }
  }

  pub fn slice(&self) -> &[u8] {
    &self.data[self.start..self.start + self.len]
  }
}

impl Display for BytePtr {
  fn fmt(&self, f: &mut Formatter) -> FmtResult {
    write!(f, "{:?}", self.data)
  }
}

// ----------------------------------------------------------------------
// MemoryPool classes

/// A central place for managing memory.
/// NOTE: client can only acquire bytes through this API, but not releasing.
/// All the memory will be released once the instance of this trait goes out of scope.
pub struct MemoryPool {
  arena: TypedArena<Vec<u8>>,

  // NOTE: these need to be in `Cell` since all public APIs of
  // this struct take `&self`, instead of `&mut self`. Otherwise, we cannot make the
  // lifetime of outputs to be the same as this memory pool.
  cur_bytes_allocated: Cell<i64>,
  max_bytes_allocated: Cell<i64>
}

impl MemoryPool {
  pub fn new() -> Self {
    let arena = TypedArena::new();
    Self { arena: arena, cur_bytes_allocated: Cell::new(0), max_bytes_allocated: Cell::new(0) }
  }

  /// Acquire a new byte buffer of at least `size` bytes
  /// Return a unique reference to the buffer
  pub fn acquire(&self, size: usize) -> &mut [u8] {
    let buf = vec![0; size];
    self.consume(buf)
  }

  /// Consume `buf` and add it to this memory pool
  /// After the call, `buf` has the same lifetime as the pool.
  /// Return a unique reference to the consumed buffer.
  pub fn consume(&self, data: Vec<u8>) -> &mut [u8] {
    let bytes_allocated = data.capacity();
    let result = self.arena.alloc(data);
    self.cur_bytes_allocated.set(self.cur_bytes_allocated.get() + bytes_allocated as i64);
    self.max_bytes_allocated.set(
      cmp::max(self.max_bytes_allocated.get(), self.cur_bytes_allocated.get()));
    result
  }

  /// Return the total number of bytes allocated so far
  fn cur_allocated(&self) -> i64 {
    self.cur_bytes_allocated.get()
  }

  /// Return the maximum number of bytes allocated so far
  fn max_allocated(&self) -> i64 {
    self.max_bytes_allocated.get()
  }
}

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

  #[test]
  fn test_byte_buffer() {
    let mut buffer = ByteBuffer::new();
    assert_eq!(buffer.size(), 0);
    assert_eq!(buffer.capacity(), 0);

    let buffer2 = ByteBuffer::new_with_cap(40);
    assert_eq!(buffer2.size(), 0);
    assert_eq!(buffer2.capacity(), 40);

    buffer.set_data((0..5).collect());
    assert_eq!(buffer.size(), 5);

    buffer.set_data((0..20).collect());
    assert_eq!(buffer.size(), 20);

    let expected: Vec<u8> = (0..20).collect();
    {
      let data = buffer.data();
      assert_eq!(data, expected.as_slice());
    }

    buffer.resize(40);
    assert!(buffer.capacity() >= 40);

    let byte_ptr = buffer.consume();
    assert_eq!(buffer.size(), 0);
    assert_eq!(byte_ptr.slice(), expected.as_slice());

    let values: Vec<u8> = (0..30).collect();
    let _ = buffer.write(values.as_slice());
    let _ = buffer.flush();

    assert_eq!(buffer.data(), values.as_slice());
  }
}
<commit_msg>Add tests for BytePtr<commit_after>// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements.  See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership.  The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License.  You may obtain a copy of the License at
//
//   http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied.  See the License for the
// specific language governing permissions and limitations
// under the License.

use std::cell::Cell;
use std::cmp;
use std::fmt::{Display, Result as FmtResult, Formatter};
use std::io::{Result as IoResult, Write};
use std::mem;
use std::rc::Rc;

use arena::TypedArena;

// ----------------------------------------------------------------------
// Buffer classes

/// A resize-able byte buffer class.
///
/// Note that a byte buffer has two attributes:
/// `capacity` and `size`: the former is the total bytes allocated for
/// the buffer, while the latter is the actual bytes that have valid data.
/// Invariant: `capacity` >= `size`.
#[derive(Debug, PartialEq)]
pub struct ByteBuffer {
  data: Vec<u8>
}

impl ByteBuffer {
  pub fn new() -> Self {
    ByteBuffer { data: vec!() }
  }

  pub fn new_with_cap(init_cap: usize) -> Self {
    ByteBuffer { data: Vec::with_capacity(init_cap) }
  }

  pub fn data(&self) -> &[u8] {
    self.data.as_slice()
  }

  pub fn set_data(&mut self, new_data: Vec<u8>) {
    self.data = new_data;
  }

  pub fn resize(&mut self, new_capacity: usize) {
    let extra_capacity = new_capacity - self.data.capacity();
    if extra_capacity > 0 {
      self.data.reserve(extra_capacity);
    }
  }

  pub fn consume(&mut self) -> BytePtr {
    let old_data = mem::replace(&mut self.data, vec!());
    BytePtr::new(old_data)
  }

  pub fn capacity(&self) -> usize {
    self.data.capacity()
  }

  pub fn size(&self) -> usize {
    self.data.len()
  }
}

impl Write for ByteBuffer {
  fn write(&mut self, buf: &[u8]) -> IoResult<usize> {
    // Check if we have enough capacity for the new data
    if self.data.len() + buf.len() > self.data.capacity() {
      let new_capacity = ::std::cmp::max(
        self.data.capacity() * 2, self.data.len() + buf.len());
      self.resize(new_capacity);
    }
    self.data.write(buf)
  }

  fn flush(&mut self) -> IoResult<()> {
    self.data.flush()
  }
}

// ----------------------------------------------------------------------
// Immutable Buffer (BytePtr) classes

/// An representation of a slice on a reference-counting and read-only byte array.
/// Sub-slices can be further created from this. The byte array will be released
/// when all slices are dropped.
#[derive(Clone, Debug, PartialEq)]
pub struct BytePtr {
  data: Rc<Vec<u8>>,
  start: usize,
  len: usize
}

impl BytePtr {
  pub fn new(v: Vec<u8>) -> Self {
    let len = v.len();
    Self { data: Rc::new(v), start: 0, len: len }
  }

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

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

  pub fn all(&self) -> BytePtr {
    BytePtr { data: self.data.clone(), start: self.start, len: self.len }
  }

  pub fn start_from(&self, start: usize) -> BytePtr {
    assert!(start <= self.len);
    BytePtr { data: self.data.clone(), start: self.start + start, len: self.len - start }
  }

  pub fn range(&self, start: usize, len: usize) -> BytePtr {
    assert!(start + len <= self.len);
    BytePtr { data: self.data.clone(), start: self.start + start, len: len }
  }

  pub fn slice(&self) -> &[u8] {
    &self.data[self.start..self.start + self.len]
  }
}

impl Display for BytePtr {
  fn fmt(&self, f: &mut Formatter) -> FmtResult {
    write!(f, "{:?}", self.data)
  }
}

// ----------------------------------------------------------------------
// MemoryPool classes

/// A central place for managing memory.
/// NOTE: client can only acquire bytes through this API, but not releasing.
/// All the memory will be released once the instance of this trait goes out of scope.
pub struct MemoryPool {
  arena: TypedArena<Vec<u8>>,

  // NOTE: these need to be in `Cell` since all public APIs of
  // this struct take `&self`, instead of `&mut self`. Otherwise, we cannot make the
  // lifetime of outputs to be the same as this memory pool.
  cur_bytes_allocated: Cell<i64>,
  max_bytes_allocated: Cell<i64>
}

impl MemoryPool {
  pub fn new() -> Self {
    let arena = TypedArena::new();
    Self { arena: arena, cur_bytes_allocated: Cell::new(0), max_bytes_allocated: Cell::new(0) }
  }

  /// Acquire a new byte buffer of at least `size` bytes
  /// Return a unique reference to the buffer
  pub fn acquire(&self, size: usize) -> &mut [u8] {
    let buf = vec![0; size];
    self.consume(buf)
  }

  /// Consume `buf` and add it to this memory pool
  /// After the call, `buf` has the same lifetime as the pool.
  /// Return a unique reference to the consumed buffer.
  pub fn consume(&self, data: Vec<u8>) -> &mut [u8] {
    let bytes_allocated = data.capacity();
    let result = self.arena.alloc(data);
    self.cur_bytes_allocated.set(self.cur_bytes_allocated.get() + bytes_allocated as i64);
    self.max_bytes_allocated.set(
      cmp::max(self.max_bytes_allocated.get(), self.cur_bytes_allocated.get()));
    result
  }

  /// Return the total number of bytes allocated so far
  fn cur_allocated(&self) -> i64 {
    self.cur_bytes_allocated.get()
  }

  /// Return the maximum number of bytes allocated so far
  fn max_allocated(&self) -> i64 {
    self.max_bytes_allocated.get()
  }
}

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

  #[test]
  fn test_byte_buffer() {
    let mut buffer = ByteBuffer::new();
    assert_eq!(buffer.size(), 0);
    assert_eq!(buffer.capacity(), 0);

    let buffer2 = ByteBuffer::new_with_cap(40);
    assert_eq!(buffer2.size(), 0);
    assert_eq!(buffer2.capacity(), 40);

    buffer.set_data((0..5).collect());
    assert_eq!(buffer.size(), 5);

    buffer.set_data((0..20).collect());
    assert_eq!(buffer.size(), 20);

    let expected: Vec<u8> = (0..20).collect();
    {
      let data = buffer.data();
      assert_eq!(data, expected.as_slice());
    }

    buffer.resize(40);
    assert!(buffer.capacity() >= 40);

    let byte_ptr = buffer.consume();
    assert_eq!(buffer.size(), 0);
    assert_eq!(byte_ptr.slice(), expected.as_slice());

    let values: Vec<u8> = (0..30).collect();
    let _ = buffer.write(values.as_slice());
    let _ = buffer.flush();

    assert_eq!(buffer.data(), values.as_slice());
  }

  #[test]
  fn test_byte_ptr() {
    let values = (0..50).collect();
    let ptr = BytePtr::new(values);
    assert_eq!(ptr.len(), 50);
    assert_eq!(ptr.start(), 0);

    let ptr2 = ptr.all();
    assert_eq!(ptr2.len(), 50);
    assert_eq!(ptr2.start(), 0);

    let ptr3 = ptr.start_from(20);
    assert_eq!(ptr3.len(), 30);
    assert_eq!(ptr3.start(), 20);

    let ptr4 = ptr3.range(10, 10);
    assert_eq!(ptr4.len(), 10);
    assert_eq!(ptr4.start(), 30);

    let expected: Vec<u8> = (30..40).collect();
    assert_eq!(ptr4.slice(), expected.as_slice());
  }
}
<|endoftext|>


<commit_before>/*! Hashconsed maps from variables to terms.
*/

use std::cmp::Ordering ;

use hashconsing::{ HashConsign, HConsed, HConser } ;

use common::* ;

/// Factory for hash consed arguments.
pub type Factory = RwLock<HashConsign<RVarVals>> ;

lazy_static! {
  /// Term factory.
  static ref factory: Factory = RwLock::new(
    HashConsign::with_capacity( conf.instance.term_capa )
  ) ;
}

/// Creates hashconsed arguments.
pub fn new<RA: Into<RVarVals>>(args: RA) -> VarVals {
  factory.mk( args.into() )
}
/// Creates hashconsed arguments, returns `true` if the arguments are actually
/// new.
pub fn new_is_new<RA: Into<RVarVals>>(args: RA) -> (VarVals, bool) {
  factory.mk_is_new( args.into() )
}
/// Creates hashconsed arguments from iterators.
pub fn of<V: Into<Val>, A: IntoIterator<Item = V>>(
  args: A
) -> VarVals {
  let mut map = VarMap::new() ;
  for val in args.into_iter() {
    map.push( val.into() )
  }
  new(map)
}

/// Mapping from variables to values, used for learning data.
#[derive(PartialEq, Eq, Hash, Clone, Debug)]
pub struct RVarVals {
  /// Internal map.
  map: VarMap<Val>,
}

impl_fmt! {
  RVarVals(self, fmt) {
    self.map.fmt(fmt)
  }
}
impl ::std::ops::Deref for RVarVals {
  type Target = VarMap<Val> ;
  fn deref(& self) -> & VarMap<Val> { & self.map }
}
impl ::std::ops::DerefMut for RVarVals {
  fn deref_mut(& mut self) -> & mut VarMap<Val> { & mut self.map }
}
impl From< VarMap<Val> > for RVarVals {
  fn from(map: VarMap<Val>) -> Self {
    RVarVals { map }
  }
}
impl Evaluator for RVarVals {
  #[inline]
  fn get(& self, var: VarIdx) -> & Val {
    & self.map[var]
  }
  #[inline]
  fn len(& self) -> usize { VarMap::len(& self.map) }
}

impl RVarVals {

  /// Constructor with some capacity.
  pub fn with_capacity(capa: usize) -> Self {
    Self { map: VarMap::with_capacity(capa) }
  }

  /// True if at least one value is `Val::N`.
  pub fn is_partial(& self) -> bool {
    for v in self.iter() {
      if ! v.is_known() { return true }
    }
    false
  }

  /// True if the two args are semantically the same.
  ///
  /// "Semantically" here means that `Val::N` is not equal to itself.
  pub fn equal(& self, other: & Self) -> bool {
    for (v_1, v_2) in self.map.iter().zip( other.map.iter() ) {
      if ! v_1.equal(v_2) { return false }
    }
    true
  }

  /// Constructor from a model.
  pub fn of_model<T>(
    info: & VarMap<::instance::info::VarInfo>,
    model: Vec<(VarIdx, T, Val)>,
    partial: bool,
  ) -> Res<Self> {
    let mut slf = RVarVals {
      map: info.iter().map(
        |info| if partial {
          val::none(info.typ.clone())
        } else {
          let default = info.typ.default_val() ;
          default
        }
      ).collect()
    } ;
    for (var, _, val) in model {
      slf[var] = val.cast(& info[var].typ) ? ;
    }
    Ok(slf)
  }

  /// Evaluates some arguments and yields the resulting `VarMap`.
  pub fn apply_to(
    & self, args: & VarMap<::term::Term>
  ) -> ::errors::Res<Self> {
    let mut res = Self::with_capacity( args.len() ) ;
    for arg in args {
      res.push( arg.eval(self) ? )
    }
    Ok(res)
  }
}

/// Hash consed arguments.
pub type VarVals = HConsed<RVarVals> ;

/// A set of hashconsed arguments.
pub type VarValsSet = HConSet<VarVals> ;

/// A map from hashconsed arguments to something.
pub type VarValsMap<T> = HConMap<VarVals, T> ;

/// Helper functions for `VarVals`.
pub trait SubsumeExt {
  /// Type of the sets we want to check for subsumption.
  type Set ;

  /// Compares two arguments w.r.t. subsumption.
  ///
  /// Returns
  ///
  /// - `Some(Greater)` if `self` subsumes `other`,
  /// - `Some(Equal)` if `self` is equal to `other`,
  /// - `Some(Less)` if `other` subsumes `self`,
  /// - `None` if `self` and `other` cannot be compared.
  ///
  /// Returns an error if `self` and `other` do not have the same length.
  fn compare(& self, other: & Self) -> Option<Ordering> ;

  /// Returns true if the two samples are related.
  ///
  /// Two samples are related if they're equal or one subsumes the other.
  fn is_related_to(& self, other: & Self) -> bool {
    self.compare(other).is_some()
  }

  /// True iff `self` subsumes or is equal to `other`.
  fn subsumes(& self, other: & Self) -> bool {
    match self.compare(other) {
      Some(Ordering::Greater) | Some(Ordering::Equal) => true,
      _ => false,
    }
  }
  /// True iff `other` subsumes or is equal to `self`.
  fn subsumed(& self, other: & Self) -> bool {
    other.subsumes(self)
  }

  /// Checks whether `self` is subsumed by anything in the set.
  ///
  /// Returns `(b, n)`:
  ///
  /// - `b` indicates whether `self` is subsumed
  /// - `n` is the number of elements removed because they were subsumed
  ///   by `self`
  ///
  /// Generally speaking, it is expected that `n > 0 => ! b`. In particular, if
  /// `self` is in the set the expected output is `(true, 0)`.
  fn set_subsumed_rm(& self, set: & mut Self::Set) -> (bool, usize) ;

  /// Checks whether `self` is subsumed by anything in the set.
  ///
  /// Same as `set_subsumed_rm`, but does remove anything.
  fn set_subsumed(& self, set: & Self::Set) -> bool ;
}
impl SubsumeExt for VarVals {
  type Set = VarValsSet ;
  fn compare(& self, other: & Self) -> Option<Ordering> {
    debug_assert_eq! { self.len(), other.len() }

    if self == other { return Some(Ordering::Equal) }

    if ! conf.teacher.partial {
      None
    } else {

      let (mut less, mut greater) = (true, true) ;

      for (val, other_val) in self.iter().zip( other.iter() ) {
        greater = greater && (
          ! val.is_known() || val == other_val
        ) ;
        less = less && (
          ! other_val.is_known() || val == other_val
        ) ;
      }

      match (less, greater) {
        (false, false) => None,
        (true, false) => Some(Ordering::Less),
        (false, true) => Some(Ordering::Greater),
        (true, true) => fail_with!(
          "Fatal error in sample hashconsing"
        ),
      }

    }
  }

  fn set_subsumed(& self, set: & Self::Set) -> bool {
    if ! conf.teacher.partial {
      set.contains(self)
    } else {
      for elem in set.iter() {
        if elem.subsumes(self) {
          return true
        }
      }
      false
    }
  }

  fn set_subsumed_rm(
    & self, set: & mut VarValsSet
  ) -> (bool, usize) {
    if ! conf.teacher.partial {
      (set.contains(self), 0)
    } else if ! self.is_partial() {
      for elem in set.iter() {
        if elem.subsumes(self) {
          return { (true, 0) }
        }
      }
      (false, 0)
    } else {
      let mut subsumed = false ;
      let mut count = 0 ;
      set.retain(
        |other| match self.compare(other) {
          Some(Ordering::Equal) => {
            subsumed = true ;
            true
          },
          Some(Ordering::Greater) => {
            count += 1 ;
            false
          },
          Some(Ordering::Less) => {
            subsumed = true ;
            true
          },
          None => true,
        }
      ) ;
      (subsumed, count)
    }
  }
}<commit_msg>better unsat detection<commit_after>/*! Hashconsed maps from variables to terms.
*/

use std::cmp::Ordering ;

use hashconsing::{ HashConsign, HConsed, HConser } ;

use common::* ;

/// Factory for hash consed arguments.
pub type Factory = RwLock<HashConsign<RVarVals>> ;

lazy_static! {
  /// Term factory.
  static ref factory: Factory = RwLock::new(
    HashConsign::with_capacity( conf.instance.term_capa )
  ) ;
}

/// Creates hashconsed arguments.
pub fn new<RA: Into<RVarVals>>(args: RA) -> VarVals {
  factory.mk( args.into() )
}
/// Creates hashconsed arguments, returns `true` if the arguments are actually
/// new.
pub fn new_is_new<RA: Into<RVarVals>>(args: RA) -> (VarVals, bool) {
  factory.mk_is_new( args.into() )
}
/// Creates hashconsed arguments from iterators.
pub fn of<V: Into<Val>, A: IntoIterator<Item = V>>(
  args: A
) -> VarVals {
  let mut map = VarMap::new() ;
  for val in args.into_iter() {
    map.push( val.into() )
  }
  new(map)
}

/// Mapping from variables to values, used for learning data.
#[derive(PartialEq, Eq, Hash, Clone, Debug)]
pub struct RVarVals {
  /// Internal map.
  map: VarMap<Val>,
}

impl_fmt! {
  RVarVals(self, fmt) {
    self.map.fmt(fmt)
  }
}
impl ::std::ops::Deref for RVarVals {
  type Target = VarMap<Val> ;
  fn deref(& self) -> & VarMap<Val> { & self.map }
}
impl ::std::ops::DerefMut for RVarVals {
  fn deref_mut(& mut self) -> & mut VarMap<Val> { & mut self.map }
}
impl From< VarMap<Val> > for RVarVals {
  fn from(map: VarMap<Val>) -> Self {
    RVarVals { map }
  }
}
impl Evaluator for RVarVals {
  #[inline]
  fn get(& self, var: VarIdx) -> & Val {
    & self.map[var]
  }
  #[inline]
  fn len(& self) -> usize { VarMap::len(& self.map) }
}

impl RVarVals {

  /// Constructor with some capacity.
  pub fn with_capacity(capa: usize) -> Self {
    Self { map: VarMap::with_capacity(capa) }
  }

  /// True if at least one value is `Val::N`.
  pub fn is_partial(& self) -> bool {
    for v in self.iter() {
      if ! v.is_known() { return true }
    }
    false
  }

  /// True if the two args are semantically the same.
  ///
  /// "Semantically" here means that `Val::N` is not equal to itself.
  pub fn equal(& self, other: & Self) -> bool {
    for (v_1, v_2) in self.map.iter().zip( other.map.iter() ) {
      if ! v_1.equal(v_2) { return false }
    }
    true
  }

  /// Constructor from a model.
  pub fn of_model<T>(
    info: & VarMap<::instance::info::VarInfo>,
    model: Vec<(VarIdx, T, Val)>,
    partial: bool,
  ) -> Res<Self> {
    let mut slf = RVarVals {
      map: info.iter().map(
        |info| if partial {
          val::none(info.typ.clone())
        } else {
          let default = info.typ.default_val() ;
          default
        }
      ).collect()
    } ;
    for (var, _, val) in model {
      slf[var] = val.cast(& info[var].typ) ? ;
    }
    Ok(slf)
  }

  /// Evaluates some arguments and yields the resulting `VarMap`.
  pub fn apply_to(
    & self, args: & VarMap<::term::Term>
  ) -> ::errors::Res<Self> {
    let mut res = Self::with_capacity( args.len() ) ;
    for arg in args {
      res.push( arg.eval(self) ? )
    }
    Ok(res)
  }
}

/// Hash consed arguments.
pub type VarVals = HConsed<RVarVals> ;

/// A set of hashconsed arguments.
pub type VarValsSet = HConSet<VarVals> ;

/// A map from hashconsed arguments to something.
pub type VarValsMap<T> = HConMap<VarVals, T> ;

/// Helper functions for `VarVals`.
pub trait SubsumeExt {
  /// Type of the sets we want to check for subsumption.
  type Set ;

  /// Compares two arguments w.r.t. subsumption.
  ///
  /// Returns
  ///
  /// - `Some(Greater)` if `self` subsumes `other`,
  /// - `Some(Equal)` if `self` is equal to `other`,
  /// - `Some(Less)` if `other` subsumes `self`,
  /// - `None` if `self` and `other` cannot be compared.
  ///
  /// Returns an error if `self` and `other` do not have the same length.
  fn compare(& self, other: & Self) -> Option<Ordering> ;

  /// True if two samples are complementary.
  ///
  /// Two samples are complementary iff for all `i`
  ///
  /// - `v_i` is a non-value, or
  /// - `v'_i` is a non-value, or
  /// - `v_1 == v'_i`.
  fn is_complementary(& self, other: & Self) -> bool ;

  /// Returns true if the two samples are related.
  ///
  /// Two samples are related if they're equal or one subsumes the other.
  fn is_related_to(& self, other: & Self) -> bool {
    self.compare(other).is_some()
  }

  /// True iff `self` subsumes or is equal to `other`.
  fn subsumes(& self, other: & Self) -> bool {
    match self.compare(other) {
      Some(Ordering::Greater) | Some(Ordering::Equal) => true,
      _ => false,
    }
  }
  /// True iff `other` subsumes or is equal to `self`.
  fn subsumed(& self, other: & Self) -> bool {
    other.subsumes(self)
  }

  /// Checks whether `self` is subsumed by anything in the set.
  ///
  /// Returns `(b, n)`:
  ///
  /// - `b` indicates whether `self` is subsumed
  /// - `n` is the number of elements removed because they were subsumed
  ///   by `self`
  ///
  /// Generally speaking, it is expected that `n > 0 => ! b`. In particular, if
  /// `self` is in the set the expected output is `(true, 0)`.
  fn set_subsumed_rm(& self, set: & mut Self::Set) -> (bool, usize) ;

  /// Checks whether `self` is subsumed by anything in the set.
  ///
  /// Same as `set_subsumed_rm`, but does remove anything.
  fn set_subsumed(& self, set: & Self::Set) -> bool ;
}
impl SubsumeExt for VarVals {
  type Set = VarValsSet ;

  fn is_complementary(& self, other: & Self) -> bool {
    for (val, other_val) in self.iter().zip( other.iter() ) {
      if val.is_known() && other_val.is_known() && val != other_val {
        return false
      }
    }
    true
  }

  fn compare(& self, other: & Self) -> Option<Ordering> {
    debug_assert_eq! { self.len(), other.len() }

    if self == other { return Some(Ordering::Equal) }

    if ! conf.teacher.partial {
      None
    } else {

      let (mut less, mut greater) = (true, true) ;

      for (val, other_val) in self.iter().zip( other.iter() ) {
        greater = greater && (
          ! val.is_known() || val == other_val
        ) ;
        less = less && (
          ! other_val.is_known() || val == other_val
        ) ;
      }

      match (less, greater) {
        (false, false) => None,
        (true, false) => Some(Ordering::Less),
        (false, true) => Some(Ordering::Greater),
        (true, true) => fail_with!(
          "Fatal error in sample hashconsing"
        ),
      }

    }
  }

  fn set_subsumed(& self, set: & Self::Set) -> bool {
    if ! conf.teacher.partial {
      set.contains(self)
    } else {
      for elem in set.iter() {
        if elem.subsumes(self) {
          return true
        }
      }
      false
    }
  }

  fn set_subsumed_rm(
    & self, set: & mut VarValsSet
  ) -> (bool, usize) {
    if ! conf.teacher.partial {
      (set.contains(self), 0)
    } else if ! self.is_partial() {
      for elem in set.iter() {
        if elem.subsumes(self) {
          return { (true, 0) }
        }
      }
      (false, 0)
    } else {
      let mut subsumed = false ;
      let mut count = 0 ;
      set.retain(
        |other| match self.compare(other) {
          Some(Ordering::Equal) => {
            subsumed = true ;
            true
          },
          Some(Ordering::Greater) => {
            count += 1 ;
            false
          },
          Some(Ordering::Less) => {
            subsumed = true ;
            true
          },
          None => true,
        }
      ) ;
      (subsumed, count)
    }
  }
}<|endoftext|>


<commit_before>use Printer;
use std::cmp::{max, min};
use theme::ColorStyle;
use vec::Vec2;
use div::div_up;

/// Provide scrolling functionalities to a view.
///
/// You're not supposed to use this directly,
/// but it can be helpful if you create your own Views.
#[derive(Default)]
pub struct ScrollBase {
    /// First line visible
    pub start_line: usize,

    /// Content height
    pub content_height: usize,

    /// Number of lines displayed
    pub view_height: usize,

    /// Padding for the scrollbar
    ///
    /// If present, the scrollbar will be shifted
    /// `scrollbar_offset` columns to the left.
    ///
    /// (Useful when each item includes its own side borders,
    /// to draw the scrollbar inside.)
    pub scrollbar_offset: usize,

    /// Blank between the text and the scrollbar.
    pub right_padding: usize,

    /// Initial position of the cursor when dragging.
    pub thumb_grab: usize,
}

/// Defines the scrolling behaviour on content or size change
pub enum ScrollStrategy {
    /// Keeps the same row number
    KeepRow,
    /// Sticks to the top.
    StickToTop,
    /// Sticks to the bottom of the view.
    StickToBottom,
}

impl Default for ScrollStrategy {
    fn default() -> Self {
        ScrollStrategy::KeepRow
    }
}

impl ScrollBase {
    /// Creates a new, uninitialized scrollbar.
    pub fn new() -> Self {
        ScrollBase {
            start_line: 0,
            content_height: 0,
            view_height: 0,
            scrollbar_offset: 0,
            right_padding: 1,
            thumb_grab: 0,
        }
    }

    /// Shifts the scrollbar toward the inside of the view.
    ///
    /// Used by views that draw their side borders in the children.
    /// Pushing the scrollbar to the left allows it to stay inside
    /// the borders.
    pub fn scrollbar_offset(mut self, offset: usize) -> Self {
        self.scrollbar_offset = offset;
        self
    }

    /// Sets the number of blank cells between the text and the scrollbar.
    ///
    /// Defaults to 1.
    pub fn right_padding(mut self, padding: usize) -> Self {
        self.right_padding = padding;
        self
    }

    /// Call this method whem the content or the view changes.
    pub fn set_heights(&mut self, view_height: usize, content_height: usize) {
        self.view_height = view_height;
        self.content_height = content_height;

        if self.scrollable() {
            self.start_line =
                min(self.start_line, self.content_height - self.view_height);
        } else {
            self.start_line = 0;
        }
    }

    /// Returns `TRUE` if the view needs to scroll.
    pub fn scrollable(&self) -> bool {
        self.view_height < self.content_height
    }

    /// Returns `TRUE` unless we are at the top.
    pub fn can_scroll_up(&self) -> bool {
        self.start_line > 0
    }

    /// Returns `TRUE` unless we are at the bottom.
    pub fn can_scroll_down(&self) -> bool {
        self.start_line + self.view_height < self.content_height
    }

    /// Scroll to the top of the view.
    pub fn scroll_top(&mut self) {
        self.start_line = 0;
    }

    /// Makes sure that the given line is visible, scrolling if needed.
    pub fn scroll_to(&mut self, y: usize) {
        if y >= self.start_line + self.view_height {
            self.start_line = 1 + y - self.view_height;
        } else if y < self.start_line {
            self.start_line = y;
        }
    }

    /// Scroll to the bottom of the view.
    pub fn scroll_bottom(&mut self) {
        if self.scrollable() {
            self.start_line = self.content_height - self.view_height;
        }
    }

    /// Scroll down by the given number of line.
    ///
    /// Never further than the bottom of the view.
    pub fn scroll_down(&mut self, n: usize) {
        if self.scrollable() {
            self.start_line = min(
                self.start_line + n,
                self.content_height - self.view_height,
            );
        }
    }

    /// Scrolls down until the scrollbar thumb is at the given location.
    pub fn scroll_to_thumb(&mut self, thumb_y: usize, thumb_height: usize) {
        // The min() is there to stop at the bottom of the content.
        // The saturating_sub is there to stop at the bottom of the content.
        // eprintln!("Scrolling to {}", thumb_y);
        self.start_line = min(
            div_up((1 + self.content_height - self.view_height) * thumb_y
                , (self.view_height - thumb_height + 1)),
            self.content_height - self.view_height,
        );
    }

    /// Scroll up by the given number of lines.
    ///
    /// Never above the top of the view.
    pub fn scroll_up(&mut self, n: usize) {
        if self.scrollable() {
            self.start_line -= min(self.start_line, n);
        }
    }

    /// Starts scrolling from the given cursor position.
    pub fn start_drag(&mut self, position: Vec2, width: usize) -> bool {
        // First: are we on the correct column?
        if position.x != self.scrollbar_x(width) {
            return false;
        }

        // Now, did we hit the thumb? Or should we direct-jump?
        let height = self.scrollbar_thumb_height();
        let thumb_y = self.scrollbar_thumb_y(height);

        if position.y >= thumb_y && position.y < thumb_y + height {
            // Grabbed!
            self.thumb_grab = position.y - thumb_y;
        } else {
            // Just jump a bit...
            self.thumb_grab = height / 2;
            self.drag(position);
        }

        true
    }

    /// Keeps scrolling by dragging the cursor.
    pub fn drag(&mut self, position: Vec2) {
        // Our goal is self.scrollbar_thumb_y()+thumb_grab == position.y
        // Which means that position.y is the middle of the scrollbar.
        // eprintln!("Dragged: {:?}", position);
        let height = self.scrollbar_thumb_height();
        let grab = self.thumb_grab;
        self.scroll_to_thumb(position.y.saturating_sub(grab), height);
    }

    /// Draws the scroll bar and the content using the given drawer.
    ///
    /// `line_drawer` will be called once for each line that needs to be drawn.
    /// It will be given the absolute ID of the item to draw..
    /// It will also be given a printer with the correct offset,
    /// so it should only print on the first line.
    ///
    /// # Examples
    ///
    /// ```no_run
    /// # use cursive::view::ScrollBase;
    /// # use cursive::Printer;
    /// # use cursive::theme;
    /// # use cursive::backend::{self, Backend};
    /// # let scrollbase = ScrollBase::new();
    /// # let b = backend::Concrete::init();
    /// # let t = theme::load_default();
    /// # let printer = Printer::new((5,1), &t, &b);
    /// # let printer = &printer;
    /// let lines = ["Line 1", "Line number 2"];
    /// scrollbase.draw(printer, |printer, i| {
    ///     printer.print((0,0), lines[i]);
    /// });
    /// ```
    pub fn draw<F>(&self, printer: &Printer, line_drawer: F)
    where
        F: Fn(&Printer, usize),
    {
        if self.view_height == 0 {
            return;
        }
        // Print the content in a sub_printer
        let max_y =
            min(self.view_height, self.content_height - self.start_line);
        let w = if self.scrollable() {
            // We have to remove the bar width and the padding.
            printer.size.x.saturating_sub(1 + self.right_padding)
        } else {
            printer.size.x
        };

        for y in 0..max_y {
            // Y is the actual coordinate of the line.
            // The item ID is then Y + self.start_line
            line_drawer(
                &printer.sub_printer(Vec2::new(0, y), Vec2::new(w, 1), true),
                y + self.start_line,
            );
        }

        // And draw the scrollbar if needed
        if self.view_height < self.content_height {
            // We directly compute the size of the scrollbar
            // (that way we avoid using floats).
            // (ratio) * max_height
            // Where ratio is ({start or end} / content.height)
            let height = self.scrollbar_thumb_height();
            let start = self.scrollbar_thumb_y(height);

            let color = if printer.focused {
                ColorStyle::Highlight
            } else {
                ColorStyle::HighlightInactive
            };

            let scrollbar_x = self.scrollbar_x(printer.size.x);

            // The background
            printer.print_vline((scrollbar_x, 0), printer.size.y, "|");

            // The scrollbar thumb
            printer.with_color(color, |printer| {
                printer.print_vline((scrollbar_x, start), height, "▒");
            });
        }
    }

    /// Returns the X position of the scrollbar, given the size available.
    ///
    /// Note that this does not depend whether or
    /// not a scrollbar will actually be present.
    pub fn scrollbar_x(&self, total_size: usize) -> usize {
        total_size.saturating_sub(1 + self.scrollbar_offset)
    }

    /// Returns the height of the scrollbar thumb.
    pub fn scrollbar_thumb_height(&self) -> usize {
        max(1, self.view_height * self.view_height / self.content_height)
    }

    /// Returns the y position of the scrollbar thumb.
    pub fn scrollbar_thumb_y(&self, scrollbar_thumb_height: usize) -> usize {
        let steps = self.view_height - scrollbar_thumb_height + 1;
        steps * self.start_line / (1 + self.content_height - self.view_height)
    }
}
<commit_msg>More natural thumb drag<commit_after>use Printer;
use div::div_up;
use std::cmp::{max, min};
use theme::ColorStyle;
use vec::Vec2;

/// Provide scrolling functionalities to a view.
///
/// You're not supposed to use this directly,
/// but it can be helpful if you create your own Views.
#[derive(Default)]
pub struct ScrollBase {
    /// First line visible
    pub start_line: usize,

    /// Content height
    pub content_height: usize,

    /// Number of lines displayed
    pub view_height: usize,

    /// Padding for the scrollbar
    ///
    /// If present, the scrollbar will be shifted
    /// `scrollbar_offset` columns to the left.
    ///
    /// (Useful when each item includes its own side borders,
    /// to draw the scrollbar inside.)
    pub scrollbar_offset: usize,

    /// Blank between the text and the scrollbar.
    pub right_padding: usize,

    /// Initial position of the cursor when dragging.
    pub thumb_grab: usize,
}

/// Defines the scrolling behaviour on content or size change
pub enum ScrollStrategy {
    /// Keeps the same row number
    KeepRow,
    /// Sticks to the top.
    StickToTop,
    /// Sticks to the bottom of the view.
    StickToBottom,
}

impl Default for ScrollStrategy {
    fn default() -> Self {
        ScrollStrategy::KeepRow
    }
}

impl ScrollBase {
    /// Creates a new, uninitialized scrollbar.
    pub fn new() -> Self {
        ScrollBase {
            start_line: 0,
            content_height: 0,
            view_height: 0,
            scrollbar_offset: 0,
            right_padding: 1,
            thumb_grab: 0,
        }
    }

    /// Shifts the scrollbar toward the inside of the view.
    ///
    /// Used by views that draw their side borders in the children.
    /// Pushing the scrollbar to the left allows it to stay inside
    /// the borders.
    pub fn scrollbar_offset(mut self, offset: usize) -> Self {
        self.scrollbar_offset = offset;
        self
    }

    /// Sets the number of blank cells between the text and the scrollbar.
    ///
    /// Defaults to 1.
    pub fn right_padding(mut self, padding: usize) -> Self {
        self.right_padding = padding;
        self
    }

    /// Call this method whem the content or the view changes.
    pub fn set_heights(&mut self, view_height: usize, content_height: usize) {
        self.view_height = view_height;
        self.content_height = content_height;

        if self.scrollable() {
            self.start_line =
                min(self.start_line, self.content_height - self.view_height);
        } else {
            self.start_line = 0;
        }
    }

    /// Returns `TRUE` if the view needs to scroll.
    pub fn scrollable(&self) -> bool {
        self.view_height < self.content_height
    }

    /// Returns `TRUE` unless we are at the top.
    pub fn can_scroll_up(&self) -> bool {
        self.start_line > 0
    }

    /// Returns `TRUE` unless we are at the bottom.
    pub fn can_scroll_down(&self) -> bool {
        self.start_line + self.view_height < self.content_height
    }

    /// Scroll to the top of the view.
    pub fn scroll_top(&mut self) {
        self.start_line = 0;
    }

    /// Makes sure that the given line is visible, scrolling if needed.
    pub fn scroll_to(&mut self, y: usize) {
        if y >= self.start_line + self.view_height {
            self.start_line = 1 + y - self.view_height;
        } else if y < self.start_line {
            self.start_line = y;
        }
    }

    /// Scroll to the bottom of the view.
    pub fn scroll_bottom(&mut self) {
        if self.scrollable() {
            self.start_line = self.content_height - self.view_height;
        }
    }

    /// Scroll down by the given number of line.
    ///
    /// Never further than the bottom of the view.
    pub fn scroll_down(&mut self, n: usize) {
        if self.scrollable() {
            self.start_line = min(
                self.start_line + n,
                self.content_height - self.view_height,
            );
        }
    }

    /// Scrolls down until the scrollbar thumb is at the given location.
    pub fn scroll_to_thumb(&mut self, thumb_y: usize, thumb_height: usize) {
        // The min() is there to stop at the bottom of the content.
        // The saturating_sub is there to stop at the bottom of the content.
        // eprintln!("Scrolling to {}", thumb_y);
        self.start_line = min(
            div_up(
                (1 + self.content_height - self.view_height) * thumb_y,
                (self.view_height - thumb_height + 1),
            ),
            self.content_height - self.view_height,
        );
    }

    /// Scroll up by the given number of lines.
    ///
    /// Never above the top of the view.
    pub fn scroll_up(&mut self, n: usize) {
        if self.scrollable() {
            self.start_line -= min(self.start_line, n);
        }
    }

    /// Starts scrolling from the given cursor position.
    pub fn start_drag(&mut self, position: Vec2, width: usize) -> bool {
        // First: are we on the correct column?
        if position.x != self.scrollbar_x(width) {
            return false;
        }

        // Now, did we hit the thumb? Or should we direct-jump?
        let height = self.scrollbar_thumb_height();
        let thumb_y = self.scrollbar_thumb_y(height);

        if position.y >= thumb_y && position.y < thumb_y + height {
            // Grabbed!
            self.thumb_grab = position.y - thumb_y;
        } else {
            // Just jump a bit...
            self.thumb_grab = (height - 1) / 2;
            eprintln!("Grabbed at {}", self.thumb_grab);
            self.drag(position);
        }

        true
    }

    /// Keeps scrolling by dragging the cursor.
    pub fn drag(&mut self, position: Vec2) {
        // Our goal is self.scrollbar_thumb_y()+thumb_grab == position.y
        // Which means that position.y is the middle of the scrollbar.
        // eprintln!("Dragged: {:?}", position);
        let height = self.scrollbar_thumb_height();
        let grab = self.thumb_grab;
        self.scroll_to_thumb(position.y.saturating_sub(grab), height);
    }

    /// Draws the scroll bar and the content using the given drawer.
    ///
    /// `line_drawer` will be called once for each line that needs to be drawn.
    /// It will be given the absolute ID of the item to draw..
    /// It will also be given a printer with the correct offset,
    /// so it should only print on the first line.
    ///
    /// # Examples
    ///
    /// ```no_run
    /// # use cursive::view::ScrollBase;
    /// # use cursive::Printer;
    /// # use cursive::theme;
    /// # use cursive::backend::{self, Backend};
    /// # let scrollbase = ScrollBase::new();
    /// # let b = backend::Concrete::init();
    /// # let t = theme::load_default();
    /// # let printer = Printer::new((5,1), &t, &b);
    /// # let printer = &printer;
    /// let lines = ["Line 1", "Line number 2"];
    /// scrollbase.draw(printer, |printer, i| {
    ///     printer.print((0,0), lines[i]);
    /// });
    /// ```
    pub fn draw<F>(&self, printer: &Printer, line_drawer: F)
    where
        F: Fn(&Printer, usize),
    {
        if self.view_height == 0 {
            return;
        }
        // Print the content in a sub_printer
        let max_y =
            min(self.view_height, self.content_height - self.start_line);
        let w = if self.scrollable() {
            // We have to remove the bar width and the padding.
            printer.size.x.saturating_sub(1 + self.right_padding)
        } else {
            printer.size.x
        };

        for y in 0..max_y {
            // Y is the actual coordinate of the line.
            // The item ID is then Y + self.start_line
            line_drawer(
                &printer.sub_printer(Vec2::new(0, y), Vec2::new(w, 1), true),
                y + self.start_line,
            );
        }

        // And draw the scrollbar if needed
        if self.view_height < self.content_height {
            // We directly compute the size of the scrollbar
            // (that way we avoid using floats).
            // (ratio) * max_height
            // Where ratio is ({start or end} / content.height)
            let height = self.scrollbar_thumb_height();
            let start = self.scrollbar_thumb_y(height);

            let color = if printer.focused {
                ColorStyle::Highlight
            } else {
                ColorStyle::HighlightInactive
            };

            let scrollbar_x = self.scrollbar_x(printer.size.x);

            // The background
            printer.print_vline((scrollbar_x, 0), printer.size.y, "|");

            // The scrollbar thumb
            printer.with_color(color, |printer| {
                printer.print_vline((scrollbar_x, start), height, "▒");
            });
        }
    }

    /// Returns the X position of the scrollbar, given the size available.
    ///
    /// Note that this does not depend whether or
    /// not a scrollbar will actually be present.
    pub fn scrollbar_x(&self, total_size: usize) -> usize {
        total_size.saturating_sub(1 + self.scrollbar_offset)
    }

    /// Returns the height of the scrollbar thumb.
    pub fn scrollbar_thumb_height(&self) -> usize {
        max(1, self.view_height * self.view_height / self.content_height)
    }

    /// Returns the y position of the scrollbar thumb.
    pub fn scrollbar_thumb_y(&self, scrollbar_thumb_height: usize) -> usize {
        let steps = self.view_height - scrollbar_thumb_height + 1;
        steps * self.start_line / (1 + self.content_height - self.view_height)
    }
}
<|endoftext|>