<commit_before>use std::cmp::Ordering;
use std::iter::FromIterator;

use ::longest_common_subsequence;
use ::longest_common_subsequence::CommonSubsequence;

/// Represents the states of a 4-state machine representing the traversal through `old` to find
/// stable and unstable chunks: at any given moment, the part of `old` under consideration is either
/// unmatched in both `new` and `other`, matched in only one, or matched in both.
#[derive(Debug)]
enum MatchState {
    NeitherMatch,
    /// Parameters are the offsets into old and new where the match started.
    OnlyNewMatches(usize, usize),
    /// Parameters are the offsets into old and other where the match started.
    OnlyOtherMatches(usize, usize),
    /// Parameters are the offsets into old, new, and other where the match started.
    BothMatch(usize, usize, usize),
}

/// Represents the transitions in the 4-state machine representing the traversal through `old` to
/// find stable and unstable chunks: at any index, either `new` or `other` may either start matching
/// `old` (if it were not already matching `old` at that index), or stop matching `old` (if if was
/// already matching `old` at that index).
///
/// For the set of valid (state, transition) pairs, see `merge()`.
#[derive(Debug, PartialEq, Eq)]
enum MatchStateTransition {
    /// Offset into old, offset into new
    NewStartsMatching(usize, usize),
    /// Offset into old, offset into new
    NewStopsMatching(usize, usize),
    /// Offset into old, offset into other
    OtherStartsMatching(usize, usize),
    /// Offset into old, offset into other
    OtherStopsMatching(usize, usize),
}

use merge::MatchState::*;
use merge::MatchStateTransition::*;

/// Orders MatchStateTransitions by their offset into old (which is the ordering that
/// `calculate_match_state_transitions()` cares about).
impl Ord for MatchStateTransition {
    fn cmp(&self, other: &MatchStateTransition) -> Ordering {
        let self_offset = match self {
            &NewStartsMatching(ref offset, _) => offset,
            &NewStopsMatching(ref offset, _) => offset,
            &OtherStartsMatching(ref offset, _) => offset,
            &OtherStopsMatching(ref offset, _) => offset,
        };
        let other_offset = match other {
            &NewStartsMatching(ref offset, _) => offset,
            &NewStopsMatching(ref offset, _) => offset,
            &OtherStartsMatching(ref offset, _) => offset,
            &OtherStopsMatching(ref offset, _) => offset,
        };
        match self_offset.cmp(other_offset) {
            Ordering::Less | Ordering::Greater => self_offset.cmp(other_offset),
            Ordering::Equal => {
                // For transitions at the same offset in old, we put stops before starts to minimize
                // the number of empty chunks output, and put New* before Other* arbitrarily.
                let self_type = match self {
                    &NewStopsMatching(..) => 1,
                    &OtherStopsMatching(..) => 2,
                    &NewStartsMatching(..) => 3,
                    &OtherStartsMatching(..) => 4,
                };
                let other_type = match other {
                    &NewStopsMatching(..) => 1,
                    &OtherStopsMatching(..) => 2,
                    &NewStartsMatching(..) => 3,
                    &OtherStartsMatching(..) => 4,
                };
                self_type.cmp(&other_type)
            }
        }
    }
}

impl PartialOrd for MatchStateTransition {
    fn partial_cmp(&self, other: &MatchStateTransition) -> Option<Ordering> {
        Some(self.cmp(other))
    }
}

/// Reprpesents the  TODO: finish comment. Or, just use tuples (usize, usize, usize, bool) for this.
#[derive(Debug)]
enum ChunkEnd {
    Stable(usize, usize, usize),
    Unstable(usize, usize, usize),
}

#[derive(Debug, PartialEq, Eq)]
enum Chunk {
    // Parameters: The offset into old, and length of the chunk.
    Stable(usize, usize),
    // Parameters: The (offset into string, length) of the chunk for old, new, and other
    // respectively.
    Unstable((usize, usize), (usize, usize), (usize, usize)),
}

fn try_merge(old: &str, new: &str, other: &str) -> String {
    let new_substrings = longest_common_subsequence::get_longest_common_subsequence(old, new)
        .common_substrings.into_iter();
    let other_substrings = longest_common_subsequence::get_longest_common_subsequence(old, other)
        .common_substrings.into_iter();
    "asdf".to_string()
}

// TODO: doc comment
fn parse(new_lcs: CommonSubsequence, other_lcs: CommonSubsequence) -> Vec<Chunk> {
    let match_state_transitions = calculate_match_state_transitions(new_lcs, other_lcs);

    let mut chunk_ends: Vec<ChunkEnd> = Vec::new();
    let mut match_state = NeitherMatch;
    for transition in match_state_transitions {
        match calculate_chunk_end(&match_state, &transition) {
            Some(chunk_end) => chunk_ends.push(chunk_end),
            None => (),
        }
        match_state = calculate_next_state(&match_state, &transition);
    }

    let mut chunks: Vec<Chunk> = Vec::with_capacity(chunk_ends.len());
    let mut old_offset = 0;
    let mut new_offset = 0;
    let mut other_offset = 0;
    for chunk_end in chunk_ends {
        match chunk_end {
            ChunkEnd::Stable(old, new, other) => {
                if old != old_offset {
                    chunks.push(Chunk::Stable(old_offset, old - old_offset));
                    old_offset = old;
                    new_offset = new;
                    other_offset = other;
                }
            },
            ChunkEnd::Unstable(old, new, other) => {
                if old != old_offset || new != new_offset || other != other_offset {
                    chunks.push(Chunk::Unstable((old_offset, old - old_offset),
                                                (new_offset, new - new_offset),
                                                (other_offset, other - other_offset)));
                    old_offset = old;
                    new_offset = new;
                    other_offset = other;
                }
            },
        }
    }
    chunks
}

/// From the LCS's for `old`/`new` and `old`/`other`, constructs a vector representing the state transitions
/// over the course of the string.
fn calculate_match_state_transitions(new_lcs: CommonSubsequence, other_lcs: CommonSubsequence) ->
    Vec<MatchStateTransition> {
    let mut match_state_transitions = Vec::from_iter(
        new_lcs.common_substrings.into_iter().flat_map(|common_substring| vec![
            NewStartsMatching(common_substring.str1_offset, common_substring.str2_offset),
            NewStopsMatching(
                common_substring.str1_offset + common_substring.size_bytes,
                common_substring.str2_offset + common_substring.size_bytes)].into_iter()).chain(
            other_lcs.common_substrings.into_iter().flat_map(|common_substring| vec![
                OtherStartsMatching(common_substring.str1_offset, common_substring.str2_offset),
                OtherStopsMatching(
                    common_substring.str1_offset + common_substring.size_bytes,
                    common_substring.str2_offset + common_substring.size_bytes)].into_iter())));
    match_state_transitions.sort();
    match_state_transitions
}

// TODO: doc comment
fn calculate_chunk_end(match_state: &MatchState, transition: &MatchStateTransition) -> Option<ChunkEnd> {
    match (match_state, transition) {
        (&OnlyNewMatches(previous_old_offset, previous_new_offset),
         &OtherStartsMatching(current_old_offset, current_other_offset)) => {
            Some(ChunkEnd::Unstable(
                current_old_offset,
                previous_new_offset + (current_old_offset - previous_old_offset),
                current_other_offset))
        },
        (&OnlyOtherMatches(previous_old_offset, previous_other_offset),
         &NewStartsMatching(current_old_offset, current_new_offset)) => {
            Some(ChunkEnd::Unstable(
                current_old_offset, current_new_offset,
                previous_other_offset + (current_old_offset - previous_old_offset)))
        },
        (&BothMatch(previous_old_offset, _, previous_other_offset),
         &NewStopsMatching(current_old_offset, current_new_offset)) => {
            let length = current_old_offset - previous_old_offset;
            Some(ChunkEnd::Stable(
                current_old_offset, current_new_offset, previous_other_offset + length))
        }
        (&BothMatch(previous_old_offset, previous_new_offset, _),
         &OtherStopsMatching(current_old_offset, current_other_offset)) => {
            let length = current_old_offset - previous_old_offset;
            Some(ChunkEnd::Stable(
                current_old_offset, previous_new_offset + length, current_other_offset))
        }
        _ => None,
    }
}

// TODO: doc comment
fn calculate_next_state(match_state: &MatchState, transition: &MatchStateTransition) -> MatchState {
    match (match_state, transition) {
        (&NeitherMatch, &NewStartsMatching(old, new))   => OnlyNewMatches(old, new),
        (&NeitherMatch, &OtherStartsMatching(old, new)) => OnlyOtherMatches(old, new),

        (&OnlyNewMatches(previous_old_offset, previous_new_offset),
         &OtherStartsMatching(current_old_offset, current_other_offset)) => {
            let length = current_old_offset - previous_old_offset;
            BothMatch(current_old_offset, previous_new_offset + length,
                      current_other_offset)
        },
        (&OnlyNewMatches(_, _), &NewStopsMatching(_, _)) => NeitherMatch,

        (&OnlyOtherMatches(previous_old_offset, previous_other_offset),
         &NewStartsMatching(current_old_offset, current_new_offset))   => {
            let length = current_old_offset - previous_old_offset;
            BothMatch(current_old_offset, current_new_offset,
                      previous_other_offset + length)
        },
        (&OnlyOtherMatches(_, _), &OtherStopsMatching(_, _))  => NeitherMatch,

        (&BothMatch(old, new, other), &NewStopsMatching(_, _)) => OnlyOtherMatches(old, other),
        (&BothMatch(old, new, other), &OtherStopsMatching(_, _))  => OnlyNewMatches(old, new),

        (state, transition) => {
            unreachable!("Illegal transition {:?} from state {:?}", transition, state);
        },
    }
}

#[cfg(test)]
mod tests {
    use super::{Chunk, calculate_match_state_transitions, parse};
    use super::MatchStateTransition::*;
    use longest_common_subsequence::{CommonSubsequence, CommonSubstring};

    #[test]
    fn test_calculate_match_state_transitions() {
        // This is from figure 1 of diff3-short.pdf.
        // TODO: comment better.
        let new_lcs = CommonSubsequence {
            common_substrings: vec![
                CommonSubstring {
                    str1_offset: 0,
                    str2_offset: 0,
                    size_bytes: 1,
                },
                CommonSubstring {
                    str1_offset: 1,
                    str2_offset: 3,
                    size_bytes: 2,
                },
                CommonSubstring {
                    str1_offset: 5,
                    str2_offset: 5,
                    size_bytes: 1,
                }],
            size_bytes: 4,
            size_chars: 4,
        };
        let other_lcs = CommonSubsequence {
            common_substrings: vec![
                CommonSubstring {
                    str1_offset: 0,
                    str2_offset: 0,
                    size_bytes: 2,
                },
                CommonSubstring {
                    str1_offset: 3,
                    str2_offset: 2,
                    size_bytes: 2,
                },
                CommonSubstring {
                    str1_offset: 5,
                    str2_offset: 5,
                    size_bytes: 1,
                }],
            size_bytes: 5,
            size_chars: 5,
        };
        let expected = vec![
            NewStartsMatching(0, 0),
            OtherStartsMatching(0, 0),

            NewStopsMatching(1, 1),
            NewStartsMatching(1, 3),

            OtherStopsMatching(2, 2),

            NewStopsMatching(3, 5),
            OtherStartsMatching(3, 2),

            OtherStopsMatching(5, 4),
            NewStartsMatching(5, 5),
            OtherStartsMatching(5, 5),

            NewStopsMatching(6, 6),
            OtherStopsMatching(6, 6)];
        assert_eq!(expected, calculate_match_state_transitions(new_lcs, other_lcs));
    }

    // TODO: Make sure this test covers all branches, transitions, states, etc.
    #[test]
    fn test_whatever() {
        // This is from figure 1 of diff3-short.pdf.
        // TODO: comment better.
        let new_lcs = CommonSubsequence {
            common_substrings: vec![
                CommonSubstring {
                    str1_offset: 0,
                    str2_offset: 0,
                    size_bytes: 1,
                },
                CommonSubstring {
                    str1_offset: 1,
                    str2_offset: 3,
                    size_bytes: 2,
                },
                CommonSubstring {
                    str1_offset: 5,
                    str2_offset: 5,
                    size_bytes: 1,
                }],
            size_bytes: 4,
            size_chars: 4,
        };
        let other_lcs = CommonSubsequence {
            common_substrings: vec![
                CommonSubstring {
                    str1_offset: 0,
                    str2_offset: 0,
                    size_bytes: 2,
                },
                CommonSubstring {
                    str1_offset: 3,
                    str2_offset: 2,
                    size_bytes: 2,
                },
                CommonSubstring {
                    str1_offset: 5,
                    str2_offset: 5,
                    size_bytes: 1,
                }],
            size_bytes: 5,
            size_chars: 5,
        };
        let expected = vec![Chunk::Stable(0, 1),
                            Chunk::Unstable((1, 0), (1, 2), (1, 0)),
                            Chunk::Stable(1, 1),
                            Chunk::Unstable((2, 3), (4, 1), (2, 3)),
                            Chunk::Stable(5, 1)];
        assert_eq!(expected, parse(new_lcs, other_lcs));
    }
}
<commit_msg>Incomplete (non-compiling) implementation of merge::try_merge().<commit_after>use std::cmp::Ordering;
use std::iter::FromIterator;

use ::longest_common_subsequence;
use ::longest_common_subsequence::CommonSubsequence;

/// Represents the states of a 4-state machine representing the traversal through `old` to find
/// stable and unstable chunks: at any given moment, the part of `old` under consideration is either
/// unmatched in both `new` and `other`, matched in only one, or matched in both.
#[derive(Debug)]
enum MatchState {
    NeitherMatch,
    /// Parameters are the offsets into old and new where the match started.
    OnlyNewMatches(usize, usize),
    /// Parameters are the offsets into old and other where the match started.
    OnlyOtherMatches(usize, usize),
    /// Parameters are the offsets into old, new, and other where the match started.
    BothMatch(usize, usize, usize),
}

/// Represents the transitions in the 4-state machine representing the traversal through `old` to
/// find stable and unstable chunks: at any index, either `new` or `other` may either start matching
/// `old` (if it were not already matching `old` at that index), or stop matching `old` (if if was
/// already matching `old` at that index).
///
/// For the set of valid (state, transition) pairs, see `merge()`.
#[derive(Debug, PartialEq, Eq)]
enum MatchStateTransition {
    /// Offset into old, offset into new
    NewStartsMatching(usize, usize),
    /// Offset into old, offset into new
    NewStopsMatching(usize, usize),
    /// Offset into old, offset into other
    OtherStartsMatching(usize, usize),
    /// Offset into old, offset into other
    OtherStopsMatching(usize, usize),
}

use merge::MatchState::*;
use merge::MatchStateTransition::*;

/// Orders MatchStateTransitions by their offset into old (which is the ordering that
/// `calculate_match_state_transitions()` cares about).
impl Ord for MatchStateTransition {
    fn cmp(&self, other: &MatchStateTransition) -> Ordering {
        let self_offset = match self {
            &NewStartsMatching(ref offset, _) => offset,
            &NewStopsMatching(ref offset, _) => offset,
            &OtherStartsMatching(ref offset, _) => offset,
            &OtherStopsMatching(ref offset, _) => offset,
        };
        let other_offset = match other {
            &NewStartsMatching(ref offset, _) => offset,
            &NewStopsMatching(ref offset, _) => offset,
            &OtherStartsMatching(ref offset, _) => offset,
            &OtherStopsMatching(ref offset, _) => offset,
        };
        match self_offset.cmp(other_offset) {
            Ordering::Less | Ordering::Greater => self_offset.cmp(other_offset),
            Ordering::Equal => {
                // For transitions at the same offset in old, we put stops before starts to minimize
                // the number of empty chunks output, and put New* before Other* arbitrarily.
                let self_type = match self {
                    &NewStopsMatching(..) => 1,
                    &OtherStopsMatching(..) => 2,
                    &NewStartsMatching(..) => 3,
                    &OtherStartsMatching(..) => 4,
                };
                let other_type = match other {
                    &NewStopsMatching(..) => 1,
                    &OtherStopsMatching(..) => 2,
                    &NewStartsMatching(..) => 3,
                    &OtherStartsMatching(..) => 4,
                };
                self_type.cmp(&other_type)
            }
        }
    }
}

impl PartialOrd for MatchStateTransition {
    fn partial_cmp(&self, other: &MatchStateTransition) -> Option<Ordering> {
        Some(self.cmp(other))
    }
}

/// Reprpesents the  TODO: finish comment. Or, just use tuples (usize, usize, usize, bool) for this.
#[derive(Debug)]
enum ChunkEnd {
    Stable(usize, usize, usize),
    Unstable(usize, usize, usize),
}

#[derive(Debug, PartialEq, Eq)]
enum Chunk {
    // TODO: change this to (start, end), instead of (start, length). Lengths are basically never useful.
    // Parameters: The offset into old, and length of the chunk.
    Stable(usize, usize),
    // Parameters: The (offset into string, length) of the chunk for old, new, and other
    // respectively.
    Unstable((usize, usize), (usize, usize), (usize, usize)),
}

fn try_merge(old: &str, new: &str, other: &str) -> String {
    let new_lcs = longest_common_subsequence::get_longest_common_subsequence(old, new);
    let other_lcs = longest_common_subsequence::get_longest_common_subsequence(old, other);
    let mut chunks = parse(new_lcs, other_lcs);
    let merged_bytes = Vec::<&[u8]>::new();
    for chunk in chunks {
        let chunk_output = match chunk {
            Chunk::Stable(start, length) => {
                merged_bytes.push(&old.as_bytes()[start..(start+length)]);
            },
            Chunk::Unstable((old_start, old_length), (new_start, new_length),
                            (other_start, other_length)) => {
                let old_chunk = old.as_bytes()[old_start..(old_start+old_length)];
                let new_chunk = new.as_bytes()[new_start..(new_start+new_length)];
                let other_chunk = other.as_bytes()[other_start..(other_start+other_length)];
                if old_chunk == new_chunk && old_chunk != other_chunk {
                    // Changed only in other
                    chunks.push(other_chunk);
                } else if old_chunk != new_chunk && old_chunk == other_chunk {
                    // Changed only in new
                    chunks.push(new_chunk);
                } else if old_chunk != new_chunk && new_chunk == other_chunk {
                    // Falsely conflicting, i.e. changed identically in both new and other
                    chunks.push(new_chunk);
                } else if (old_chunk != new_chunk && old_chunk != other_chunk &&
                           new_chunk != other_chunk) {
                    // Truly conflicting
                    return new;
                }
            },
        };
    }

    "asdf".to_string()
}

// TODO: doc comment
fn parse(new_lcs: CommonSubsequence, other_lcs: CommonSubsequence) -> Vec<Chunk> {
    let match_state_transitions = calculate_match_state_transitions(new_lcs, other_lcs);

    let mut chunk_ends: Vec<ChunkEnd> = Vec::new();
    let mut match_state = NeitherMatch;
    for transition in match_state_transitions {
        match calculate_chunk_end(&match_state, &transition) {
            Some(chunk_end) => chunk_ends.push(chunk_end),
            None => (),
        }
        match_state = calculate_next_state(&match_state, &transition);
    }

    let mut chunks: Vec<Chunk> = Vec::with_capacity(chunk_ends.len());
    let mut old_offset = 0;
    let mut new_offset = 0;
    let mut other_offset = 0;
    for chunk_end in chunk_ends {
        match chunk_end {
            ChunkEnd::Stable(old, new, other) => {
                if old != old_offset {
                    chunks.push(Chunk::Stable(old_offset, old - old_offset));
                    old_offset = old;
                    new_offset = new;
                    other_offset = other;
                }
            },
            ChunkEnd::Unstable(old, new, other) => {
                if old != old_offset || new != new_offset || other != other_offset {
                    chunks.push(Chunk::Unstable((old_offset, old - old_offset),
                                                (new_offset, new - new_offset),
                                                (other_offset, other - other_offset)));
                    old_offset = old;
                    new_offset = new;
                    other_offset = other;
                }
            },
        }
    }
    chunks
}

/// From the LCS's for `old`/`new` and `old`/`other`, constructs a vector representing the state transitions
/// over the course of the string.
fn calculate_match_state_transitions(new_lcs: CommonSubsequence, other_lcs: CommonSubsequence) ->
    Vec<MatchStateTransition> {
    let mut match_state_transitions = Vec::from_iter(
        new_lcs.common_substrings.into_iter().flat_map(|common_substring| vec![
            NewStartsMatching(common_substring.str1_offset, common_substring.str2_offset),
            NewStopsMatching(
                common_substring.str1_offset + common_substring.size_bytes,
                common_substring.str2_offset + common_substring.size_bytes)].into_iter()).chain(
            other_lcs.common_substrings.into_iter().flat_map(|common_substring| vec![
                OtherStartsMatching(common_substring.str1_offset, common_substring.str2_offset),
                OtherStopsMatching(
                    common_substring.str1_offset + common_substring.size_bytes,
                    common_substring.str2_offset + common_substring.size_bytes)].into_iter())));
    match_state_transitions.sort();
    match_state_transitions
}

// TODO: doc comment
fn calculate_chunk_end(match_state: &MatchState, transition: &MatchStateTransition) -> Option<ChunkEnd> {
    match (match_state, transition) {
        (&OnlyNewMatches(previous_old_offset, previous_new_offset),
         &OtherStartsMatching(current_old_offset, current_other_offset)) => {
            Some(ChunkEnd::Unstable(
                current_old_offset,
                previous_new_offset + (current_old_offset - previous_old_offset),
                current_other_offset))
        },
        (&OnlyOtherMatches(previous_old_offset, previous_other_offset),
         &NewStartsMatching(current_old_offset, current_new_offset)) => {
            Some(ChunkEnd::Unstable(
                current_old_offset, current_new_offset,
                previous_other_offset + (current_old_offset - previous_old_offset)))
        },
        (&BothMatch(previous_old_offset, _, previous_other_offset),
         &NewStopsMatching(current_old_offset, current_new_offset)) => {
            let length = current_old_offset - previous_old_offset;
            Some(ChunkEnd::Stable(
                current_old_offset, current_new_offset, previous_other_offset + length))
        }
        (&BothMatch(previous_old_offset, previous_new_offset, _),
         &OtherStopsMatching(current_old_offset, current_other_offset)) => {
            let length = current_old_offset - previous_old_offset;
            Some(ChunkEnd::Stable(
                current_old_offset, previous_new_offset + length, current_other_offset))
        }
        _ => None,
    }
}

// TODO: doc comment
fn calculate_next_state(match_state: &MatchState, transition: &MatchStateTransition) -> MatchState {
    match (match_state, transition) {
        (&NeitherMatch, &NewStartsMatching(old, new))   => OnlyNewMatches(old, new),
        (&NeitherMatch, &OtherStartsMatching(old, new)) => OnlyOtherMatches(old, new),

        (&OnlyNewMatches(previous_old_offset, previous_new_offset),
         &OtherStartsMatching(current_old_offset, current_other_offset)) => {
            let length = current_old_offset - previous_old_offset;
            BothMatch(current_old_offset, previous_new_offset + length,
                      current_other_offset)
        },
        (&OnlyNewMatches(_, _), &NewStopsMatching(_, _)) => NeitherMatch,

        (&OnlyOtherMatches(previous_old_offset, previous_other_offset),
         &NewStartsMatching(current_old_offset, current_new_offset))   => {
            let length = current_old_offset - previous_old_offset;
            BothMatch(current_old_offset, current_new_offset,
                      previous_other_offset + length)
        },
        (&OnlyOtherMatches(_, _), &OtherStopsMatching(_, _))  => NeitherMatch,

        (&BothMatch(old, new, other), &NewStopsMatching(_, _)) => OnlyOtherMatches(old, other),
        (&BothMatch(old, new, other), &OtherStopsMatching(_, _))  => OnlyNewMatches(old, new),

        (state, transition) => {
            unreachable!("Illegal transition {:?} from state {:?}", transition, state);
        },
    }
}

#[cfg(test)]
mod tests {
    use super::{Chunk, calculate_match_state_transitions, parse};
    use super::MatchStateTransition::*;
    use longest_common_subsequence::{CommonSubsequence, CommonSubstring};

    #[test]
    fn test_calculate_match_state_transitions() {
        // This is from figure 1 of diff3-short.pdf.
        // TODO: comment better.
        let new_lcs = CommonSubsequence {
            common_substrings: vec![
                CommonSubstring {
                    str1_offset: 0,
                    str2_offset: 0,
                    size_bytes: 1,
                },
                CommonSubstring {
                    str1_offset: 1,
                    str2_offset: 3,
                    size_bytes: 2,
                },
                CommonSubstring {
                    str1_offset: 5,
                    str2_offset: 5,
                    size_bytes: 1,
                }],
            size_bytes: 4,
            size_chars: 4,
        };
        let other_lcs = CommonSubsequence {
            common_substrings: vec![
                CommonSubstring {
                    str1_offset: 0,
                    str2_offset: 0,
                    size_bytes: 2,
                },
                CommonSubstring {
                    str1_offset: 3,
                    str2_offset: 2,
                    size_bytes: 2,
                },
                CommonSubstring {
                    str1_offset: 5,
                    str2_offset: 5,
                    size_bytes: 1,
                }],
            size_bytes: 5,
            size_chars: 5,
        };
        let expected = vec![
            NewStartsMatching(0, 0),
            OtherStartsMatching(0, 0),

            NewStopsMatching(1, 1),
            NewStartsMatching(1, 3),

            OtherStopsMatching(2, 2),

            NewStopsMatching(3, 5),
            OtherStartsMatching(3, 2),

            OtherStopsMatching(5, 4),
            NewStartsMatching(5, 5),
            OtherStartsMatching(5, 5),

            NewStopsMatching(6, 6),
            OtherStopsMatching(6, 6)];
        assert_eq!(expected, calculate_match_state_transitions(new_lcs, other_lcs));
    }

    // TODO: Make sure this test covers all branches, transitions, states, etc.
    #[test]
    fn test_whatever() {
        // This is from figure 1 of diff3-short.pdf.
        // TODO: comment better.
        let new_lcs = CommonSubsequence {
            common_substrings: vec![
                CommonSubstring {
                    str1_offset: 0,
                    str2_offset: 0,
                    size_bytes: 1,
                },
                CommonSubstring {
                    str1_offset: 1,
                    str2_offset: 3,
                    size_bytes: 2,
                },
                CommonSubstring {
                    str1_offset: 5,
                    str2_offset: 5,
                    size_bytes: 1,
                }],
            size_bytes: 4,
            size_chars: 4,
        };
        let other_lcs = CommonSubsequence {
            common_substrings: vec![
                CommonSubstring {
                    str1_offset: 0,
                    str2_offset: 0,
                    size_bytes: 2,
                },
                CommonSubstring {
                    str1_offset: 3,
                    str2_offset: 2,
                    size_bytes: 2,
                },
                CommonSubstring {
                    str1_offset: 5,
                    str2_offset: 5,
                    size_bytes: 1,
                }],
            size_bytes: 5,
            size_chars: 5,
        };
        let expected = vec![Chunk::Stable(0, 1),
                            Chunk::Unstable((1, 0), (1, 2), (1, 0)),
                            Chunk::Stable(1, 1),
                            Chunk::Unstable((2, 3), (4, 1), (2, 3)),
                            Chunk::Stable(5, 1)];
        assert_eq!(expected, parse(new_lcs, other_lcs));
    }
}
<|endoftext|>


<commit_before>extern crate regex;
use self::regex::Regex;
use super::*;

#[test]
fn parse_args_help_works() {
    assert_eq!(parse_args(vec!["-h".to_string()]), Ok(ShowHelp));
    assert_eq!(parse_args(vec!["--help".to_string()]), Ok(ShowHelp));
}

#[test]
fn parse_args_version_works() {
    assert_eq!(parse_args(vec!["-v".to_string()]), Ok(ShowVersion));
    assert_eq!(parse_args(vec!["--version".to_string()]), Ok(ShowVersion));
}

#[test]
fn parse_args_uses_default_string() {
    assert_eq!(parse_args(vec![]), Ok(RepeatString(DEFAULT_STRING.to_string())));
}

#[test]
fn parse_args_uses_arguments() {
    assert_eq!(parse_args(vec!["str".to_string()]),
               Ok(RepeatString("str".to_string())));
}

#[test]
fn parse_args_concatenates_arguments() {
    assert_eq!(parse_args(vec!["str".to_string(), "dup".to_string()]),
               Ok(RepeatString("str dup".to_string())));
}

#[test]
fn parse_args_allows_escaped_flags() {
    assert_eq!(parse_args(vec!["--".to_string(), "-h".to_string()]),
               Ok(RepeatString("-h".to_string())));
}

#[test]
fn parse_args_raises_error_on_illegal_argument() {
    assert!(parse_args(vec!["-g".to_string()]).is_err());
    match parse_args(vec!["--global".to_string()]) {
        Err(InvalidOption(_)) => assert!(true),
        _ => assert!(false)
    }
}

#[test]
fn string_newline_terminate_works() {
    assert_eq!("word".to_string().newline_terminate(), "word\n");
    assert_eq!("myname".to_string().newline_terminate(), "myname\n");
}

#[test]
fn string_fill_works() {
    let max = 1500;
    let buf = "myword\n".to_string().fill(max);

    // make sure string isn't too long
    assert!(buf.len() <= max);

    // make sure buf only contains the word followed by a newline
    let re = Regex::new(r"^(myword\n)+$").unwrap();
    assert!(re.is_match(&buf));
}

#[test]
fn version_text_returns_version() {
    let re = Regex::new(r"^.* version \d+(\.\d+)*.*$").unwrap();
    assert!(re.is_match(&version_text()));
}

#[test]
fn help_text_documents_options() {
    // test if it documents the help flag
    let help_flag = Regex::new(r"--help").unwrap();
    assert!(help_flag.is_match(&help_text()));

    // test if it documents the version flag
    let version_flag = Regex::new(r"--version").unwrap();
    assert!(version_flag.is_match(&help_text()));

    // test if it documents the usage
    let usage = Regex::new(r"Usage: ").unwrap();
    assert!(usage.is_match(&help_text()));
}
<commit_msg>adds test for Error.message()<commit_after>extern crate regex;
use self::regex::Regex;
use super::*;

#[test]
fn parse_args_help_works() {
    assert_eq!(parse_args(vec!["-h".to_string()]), Ok(ShowHelp));
    assert_eq!(parse_args(vec!["--help".to_string()]), Ok(ShowHelp));
}

#[test]
fn parse_args_version_works() {
    assert_eq!(parse_args(vec!["-v".to_string()]), Ok(ShowVersion));
    assert_eq!(parse_args(vec!["--version".to_string()]), Ok(ShowVersion));
}

#[test]
fn parse_args_uses_default_string() {
    assert_eq!(parse_args(vec![]), Ok(RepeatString(DEFAULT_STRING.to_string())));
}

#[test]
fn parse_args_uses_arguments() {
    assert_eq!(parse_args(vec!["str".to_string()]),
               Ok(RepeatString("str".to_string())));
}

#[test]
fn parse_args_concatenates_arguments() {
    assert_eq!(parse_args(vec!["str".to_string(), "dup".to_string()]),
               Ok(RepeatString("str dup".to_string())));
}

#[test]
fn parse_args_allows_escaped_flags() {
    assert_eq!(parse_args(vec!["--".to_string(), "-h".to_string()]),
               Ok(RepeatString("-h".to_string())));
}

#[test]
fn parse_args_raises_error_on_illegal_argument() {
    assert!(parse_args(vec!["-g".to_string()]).is_err());
    match parse_args(vec!["--global".to_string()]) {
        Err(InvalidOption(_)) => assert!(true),
        _ => unreachable!()
    }
}

#[test]
fn parse_args_error_has_message() {
    let re = Regex::new(r"Unrecognized option").unwrap();

    assert!(parse_args(vec!["-g".to_string()]).is_err());
    match parse_args(vec!["--global".to_string()]) {
        Err(e) => assert!(re.is_match(&e.message())),
        _ => unreachable!()
    }
}

#[test]
fn string_newline_terminate_works() {
    assert_eq!("word".to_string().newline_terminate(), "word\n");
    assert_eq!("myname".to_string().newline_terminate(), "myname\n");
}

#[test]
fn string_fill_works() {
    let max = 1500;
    let buf = "myword\n".to_string().fill(max);

    // make sure string isn't too long
    assert!(buf.len() <= max);

    // make sure buf only contains the word followed by a newline
    let re = Regex::new(r"^(myword\n)+$").unwrap();
    assert!(re.is_match(&buf));
}

#[test]
fn version_text_returns_version() {
    let re = Regex::new(r"^.* version \d+(\.\d+)*.*$").unwrap();
    assert!(re.is_match(&version_text()));
}

#[test]
fn help_text_documents_options() {
    // test if it documents the help flag
    let help_flag = Regex::new(r"--help").unwrap();
    assert!(help_flag.is_match(&help_text()));

    // test if it documents the version flag
    let version_flag = Regex::new(r"--version").unwrap();
    assert!(version_flag.is_match(&help_text()));

    // test if it documents the usage
    let usage = Regex::new(r"Usage: ").unwrap();
    assert!(usage.is_match(&help_text()));
}
<|endoftext|>


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

// This file is part of the uutils coreutils package.
//
// (c) Orvar Segerström <orvarsegerstrom@gmail.com>
// (c) Sokovikov Evgeniy  <skv-headless@yandex.ru>
//
// For the full copyright and license information, please view the LICENSE file
// that was distributed with this source code.
//

extern crate getopts;

#[macro_use]
extern crate uucore;

use std::fs;
use std::env;
use std::io::{stdin, BufRead, BufReader, Result};
use std::path::{Path, PathBuf};

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

pub struct Behaviour {
    overwrite: OverwriteMode,
    backup: BackupMode,
    suffix: String,
    update: bool,
    target_dir: Option<String>,
    no_target_dir: bool,
    verbose: bool,
}

#[derive(Clone, Eq, PartialEq)]
pub enum OverwriteMode {
    NoClobber,
    Interactive,
    Force,
}

#[derive(Clone, Copy, Eq, PartialEq)]
pub enum BackupMode {
    NoBackup,
    SimpleBackup,
    NumberedBackup,
    ExistingBackup,
}

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

    opts.optflagopt(
        "",
        "backup",
        "make a backup of each existing destination file",
        "CONTROL",
    );
    opts.optflag("b", "", "like --backup but does not accept an argument");
    opts.optflag("f", "force", "do not prompt before overwriting");
    opts.optflag("i", "interactive", "prompt before override");
    opts.optflag("n", "no-clobber", "do not overwrite an existing file");
    opts.optflag(
        "",
        "strip-trailing-slashes",
        "remove any trailing slashes from each SOURCE\n \
         argument",
    );
    opts.optopt("S", "suffix", "override the usual backup suffix", "SUFFIX");
    opts.optopt(
        "t",
        "target-directory",
        "move all SOURCE arguments into DIRECTORY",
        "DIRECTORY",
    );
    opts.optflag("T", "no-target-directory", "treat DEST as a normal file");
    opts.optflag(
        "u",
        "update",
        "move only when the SOURCE file is newer\n \
         than the destination file or when the\n \
         destination file is missing",
    );
    opts.optflag("v", "verbose", "explain what is being done");
    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(f) => {
            show_error!("Invalid options\n{}", f);
            return 1;
        }
    };
    let usage = opts.usage("Move SOURCE to DEST, or multiple SOURCE(s) to DIRECTORY.");

    let overwrite_mode = determine_overwrite_mode(&matches);
    let backup_mode = determine_backup_mode(&matches);

    if overwrite_mode == OverwriteMode::NoClobber && backup_mode != BackupMode::NoBackup {
        show_error!(
            "options --backup and --no-clobber are mutually exclusive\n\
             Try '{} --help' for more information.",
            NAME
        );
        return 1;
    }

    let backup_suffix = determine_backup_suffix(backup_mode, &matches);

    if matches.opt_present("T") && matches.opt_present("t") {
        show_error!("cannot combine --target-directory (-t) and --no-target-directory (-T)");
        return 1;
    }

    let behaviour = Behaviour {
        overwrite: overwrite_mode,
        backup: backup_mode,
        suffix: backup_suffix,
        update: matches.opt_present("u"),
        target_dir: matches.opt_str("t"),
        no_target_dir: matches.opt_present("T"),
        verbose: matches.opt_present("v"),
    };

    let paths: Vec<PathBuf> = {
        fn strip_slashes<'a>(p: &'a Path) -> &'a Path {
            p.components().as_path()
        }
        let to_owned = |p: &Path| p.to_owned();
        let arguments = matches.free.iter().map(Path::new);
        if matches.opt_present("strip-trailing-slashes") {
            arguments.map(strip_slashes).map(to_owned).collect()
        } else {
            arguments.map(to_owned).collect()
        }
    };

    if matches.opt_present("version") {
        println!("{} {}", NAME, VERSION);
        0
    } else if matches.opt_present("help") {
        help(&usage);
        0
    } else {
        exec(&paths[..], behaviour)
    }
}

fn determine_overwrite_mode(matches: &getopts::Matches) -> OverwriteMode {
    // This does not exactly match the GNU implementation:
    // The GNU mv defaults to Force, but if more than one of the
    // overwrite options are supplied, only the last takes effect.
    // To default to no-clobber in that situation seems safer:
    //
    if matches.opt_present("no-clobber") {
        OverwriteMode::NoClobber
    } else if matches.opt_present("interactive") {
        OverwriteMode::Interactive
    } else {
        OverwriteMode::Force
    }
}

fn determine_backup_mode(matches: &getopts::Matches) -> BackupMode {
    if matches.opt_present("b") {
        BackupMode::SimpleBackup
    } else if matches.opt_present("backup") {
        match matches.opt_str("backup") {
            None => BackupMode::SimpleBackup,
            Some(mode) => match &mode[..] {
                "simple" | "never" => BackupMode::SimpleBackup,
                "numbered" | "t" => BackupMode::NumberedBackup,
                "existing" | "nil" => BackupMode::ExistingBackup,
                "none" | "off" => BackupMode::NoBackup,
                x => {
                    crash!(
                        1,
                        "invalid argument ‘{}’ for ‘backup type’\n\
                         Try '{} --help' for more information.",
                        x,
                        NAME
                    );
                }
            },
        }
    } else {
        BackupMode::NoBackup
    }
}

fn determine_backup_suffix(backup_mode: BackupMode, matches: &getopts::Matches) -> String {
    if matches.opt_present("suffix") {
        match matches.opt_str("suffix") {
            Some(x) => x,
            None => {
                crash!(
                    1,
                    "option '--suffix' requires an argument\n\
                     Try '{} --help' for more information.",
                    NAME
                );
            }
        }
    } else {
        if let (Ok(s), BackupMode::SimpleBackup) = (env::var("SIMPLE_BACKUP_SUFFIX"), backup_mode) {
            s
        } else {
            "~".to_owned()
        }
    }
}

fn help(usage: &str) {
    println!(
        "{0} {1}\n\n\
         Usage: {0} SOURCE DEST\n   \
         or: {0} SOURCE... DIRECTORY\n\n\
         {2}",
        NAME, VERSION, usage
    );
}

fn exec(files: &[PathBuf], b: Behaviour) -> i32 {
    if let Some(ref name) = b.target_dir {
        return move_files_into_dir(files, &PathBuf::from(name), &b);
    }
    match files.len() {
        0 | 1 => {
            show_error!(
                "missing file operand\n\
                 Try '{} --help' for more information.",
                NAME
            );
            return 1;
        }
        2 => {
            let source = &files[0];
            let target = &files[1];
            if !source.exists() {
                show_error!(
                    "cannot stat ‘{}’: No such file or directory",
                    source.display()
                );
                return 1;
            }

            if target.is_dir() {
                if b.no_target_dir {
                    if !source.is_dir() {
                        show_error!(
                            "cannot overwrite directory ‘{}’ with non-directory",
                            target.display()
                        );
                        return 1;
                    }

                    return match rename(source, target, &b) {
                        Err(e) => {
                            show_error!("{}", e);
                            1
                        }
                        _ => 0,
                    };
                }

                return move_files_into_dir(&[source.clone()], target, &b);
            } else if target.exists() && source.is_dir() {
                show_error!(
                    "cannot overwrite non-directory ‘{}’ with directory ‘{}’",
                    target.display(), source.display()
                );
                return 1;
            }

            if let Err(e) = rename(source, target, &b) {
                show_error!("{}", e);
                return 1;
            }
        }
        _ => {
            if b.no_target_dir {
                show_error!(
                    "mv: extra operand ‘{}’\n\
                     Try '{} --help' for more information.",
                    files[2].display(),
                    NAME
                );
                return 1;
            }
            let target_dir = files.last().unwrap();
            move_files_into_dir(&files[..files.len() - 1], target_dir, &b);
        }
    }
    0
}

fn move_files_into_dir(files: &[PathBuf], target_dir: &PathBuf, b: &Behaviour) -> i32 {
    if !target_dir.is_dir() {
        show_error!("target ‘{}’ is not a directory", target_dir.display());
        return 1;
    }

    let mut all_successful = true;
    for sourcepath in files.iter() {
        let targetpath = match sourcepath.as_os_str().to_str() {
            Some(name) => target_dir.join(name),
            None => {
                show_error!(
                    "cannot stat ‘{}’: No such file or directory",
                    sourcepath.display()
                );

                all_successful = false;
                continue;
            }
        };

        if let Err(e) = rename(sourcepath, &targetpath, b) {
            show_error!(
                "mv: cannot move ‘{}’ to ‘{}’: {}",
                sourcepath.display(),
                targetpath.display(),
                e
            );
            all_successful = false;
        }
    }
    if all_successful {
        0
    } else {
        1
    }
}

fn rename(from: &PathBuf, to: &PathBuf, b: &Behaviour) -> Result<()> {
    let mut backup_path = None;

    if to.exists() {
        match b.overwrite {
            OverwriteMode::NoClobber => return Ok(()),
            OverwriteMode::Interactive => {
                print!("{}: overwrite ‘{}’? ", NAME, to.display());
                if !read_yes() {
                    return Ok(());
                }
            }
            OverwriteMode::Force => {}
        };

        backup_path = match b.backup {
            BackupMode::NoBackup => None,
            BackupMode::SimpleBackup => Some(simple_backup_path(to, &b.suffix)),
            BackupMode::NumberedBackup => Some(numbered_backup_path(to)),
            BackupMode::ExistingBackup => Some(existing_backup_path(to, &b.suffix)),
        };
        if let Some(ref p) = backup_path {
            try!(fs::rename(to, p));
        }

        if b.update {
            if try!(try!(fs::metadata(from)).modified()) <= try!(try!(fs::metadata(to)).modified())
            {
                return Ok(());
            }
        }
    }

    // "to" may no longer exist if it was backed up
    if to.exists() && to.is_dir() {
        // normalize behavior between *nix and windows
        if from.is_dir() {
            if is_empty_dir(to) {
                try!(fs::remove_dir(to))
            } else {
                return Err(std::io::Error::new(std::io::ErrorKind::Other, "Directory not empty"));
            }
        }
    }

    try!(fs::rename(from, to));

    if b.verbose {
        print!("‘{}’ -> ‘{}’", from.display(), to.display());
        match backup_path {
            Some(path) => println!(" (backup: ‘{}’)", path.display()),
            None => println!(""),
        }
    }
    Ok(())
}

fn read_yes() -> bool {
    let mut s = String::new();
    match BufReader::new(stdin()).read_line(&mut s) {
        Ok(_) => match s.chars().nth(0) {
            Some(x) => x == 'y' || x == 'Y',
            _ => false,
        },
        _ => false,
    }
}

fn simple_backup_path(path: &PathBuf, suffix: &str) -> PathBuf {
    let mut p = path.to_string_lossy().into_owned();
    p.push_str(suffix);
    PathBuf::from(p)
}

fn numbered_backup_path(path: &PathBuf) -> PathBuf {
    (1_u64..)
        .map(|i| path.with_extension(format!("~{}~", i)))
        .skip_while(|p| p.exists())
        .next()
        .expect("cannot create backup")
}

fn existing_backup_path(path: &PathBuf, suffix: &str) -> PathBuf {
    let test_path = path.with_extension("~1~");
    if test_path.exists() {
        numbered_backup_path(path)
    } else {
        simple_backup_path(path, suffix)
    }
}

fn is_empty_dir(path: &PathBuf) -> bool {
    match fs::read_dir(path) {
        Ok(contents) => {
            return contents.peekable().peek().is_none();
        },
        Err(_e) => { return false; }
    }
}
<commit_msg>mv: improve error message<commit_after>#![crate_name = "uu_mv"]

// This file is part of the uutils coreutils package.
//
// (c) Orvar Segerström <orvarsegerstrom@gmail.com>
// (c) Sokovikov Evgeniy  <skv-headless@yandex.ru>
//
// For the full copyright and license information, please view the LICENSE file
// that was distributed with this source code.
//

extern crate getopts;

#[macro_use]
extern crate uucore;

use std::fs;
use std::env;
use std::io::{stdin, BufRead, BufReader, Result};
use std::path::{Path, PathBuf};

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

pub struct Behaviour {
    overwrite: OverwriteMode,
    backup: BackupMode,
    suffix: String,
    update: bool,
    target_dir: Option<String>,
    no_target_dir: bool,
    verbose: bool,
}

#[derive(Clone, Eq, PartialEq)]
pub enum OverwriteMode {
    NoClobber,
    Interactive,
    Force,
}

#[derive(Clone, Copy, Eq, PartialEq)]
pub enum BackupMode {
    NoBackup,
    SimpleBackup,
    NumberedBackup,
    ExistingBackup,
}

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

    opts.optflagopt(
        "",
        "backup",
        "make a backup of each existing destination file",
        "CONTROL",
    );
    opts.optflag("b", "", "like --backup but does not accept an argument");
    opts.optflag("f", "force", "do not prompt before overwriting");
    opts.optflag("i", "interactive", "prompt before override");
    opts.optflag("n", "no-clobber", "do not overwrite an existing file");
    opts.optflag(
        "",
        "strip-trailing-slashes",
        "remove any trailing slashes from each SOURCE\n \
         argument",
    );
    opts.optopt("S", "suffix", "override the usual backup suffix", "SUFFIX");
    opts.optopt(
        "t",
        "target-directory",
        "move all SOURCE arguments into DIRECTORY",
        "DIRECTORY",
    );
    opts.optflag("T", "no-target-directory", "treat DEST as a normal file");
    opts.optflag(
        "u",
        "update",
        "move only when the SOURCE file is newer\n \
         than the destination file or when the\n \
         destination file is missing",
    );
    opts.optflag("v", "verbose", "explain what is being done");
    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(f) => {
            show_error!("Invalid options\n{}", f);
            return 1;
        }
    };
    let usage = opts.usage("Move SOURCE to DEST, or multiple SOURCE(s) to DIRECTORY.");

    let overwrite_mode = determine_overwrite_mode(&matches);
    let backup_mode = determine_backup_mode(&matches);

    if overwrite_mode == OverwriteMode::NoClobber && backup_mode != BackupMode::NoBackup {
        show_error!(
            "options --backup and --no-clobber are mutually exclusive\n\
             Try '{} --help' for more information.",
            NAME
        );
        return 1;
    }

    let backup_suffix = determine_backup_suffix(backup_mode, &matches);

    if matches.opt_present("T") && matches.opt_present("t") {
        show_error!("cannot combine --target-directory (-t) and --no-target-directory (-T)");
        return 1;
    }

    let behaviour = Behaviour {
        overwrite: overwrite_mode,
        backup: backup_mode,
        suffix: backup_suffix,
        update: matches.opt_present("u"),
        target_dir: matches.opt_str("t"),
        no_target_dir: matches.opt_present("T"),
        verbose: matches.opt_present("v"),
    };

    let paths: Vec<PathBuf> = {
        fn strip_slashes<'a>(p: &'a Path) -> &'a Path {
            p.components().as_path()
        }
        let to_owned = |p: &Path| p.to_owned();
        let arguments = matches.free.iter().map(Path::new);
        if matches.opt_present("strip-trailing-slashes") {
            arguments.map(strip_slashes).map(to_owned).collect()
        } else {
            arguments.map(to_owned).collect()
        }
    };

    if matches.opt_present("version") {
        println!("{} {}", NAME, VERSION);
        0
    } else if matches.opt_present("help") {
        help(&usage);
        0
    } else {
        exec(&paths[..], behaviour)
    }
}

fn determine_overwrite_mode(matches: &getopts::Matches) -> OverwriteMode {
    // This does not exactly match the GNU implementation:
    // The GNU mv defaults to Force, but if more than one of the
    // overwrite options are supplied, only the last takes effect.
    // To default to no-clobber in that situation seems safer:
    //
    if matches.opt_present("no-clobber") {
        OverwriteMode::NoClobber
    } else if matches.opt_present("interactive") {
        OverwriteMode::Interactive
    } else {
        OverwriteMode::Force
    }
}

fn determine_backup_mode(matches: &getopts::Matches) -> BackupMode {
    if matches.opt_present("b") {
        BackupMode::SimpleBackup
    } else if matches.opt_present("backup") {
        match matches.opt_str("backup") {
            None => BackupMode::SimpleBackup,
            Some(mode) => match &mode[..] {
                "simple" | "never" => BackupMode::SimpleBackup,
                "numbered" | "t" => BackupMode::NumberedBackup,
                "existing" | "nil" => BackupMode::ExistingBackup,
                "none" | "off" => BackupMode::NoBackup,
                x => {
                    crash!(
                        1,
                        "invalid argument ‘{}’ for ‘backup type’\n\
                         Try '{} --help' for more information.",
                        x,
                        NAME
                    );
                }
            },
        }
    } else {
        BackupMode::NoBackup
    }
}

fn determine_backup_suffix(backup_mode: BackupMode, matches: &getopts::Matches) -> String {
    if matches.opt_present("suffix") {
        match matches.opt_str("suffix") {
            Some(x) => x,
            None => {
                crash!(
                    1,
                    "option '--suffix' requires an argument\n\
                     Try '{} --help' for more information.",
                    NAME
                );
            }
        }
    } else {
        if let (Ok(s), BackupMode::SimpleBackup) = (env::var("SIMPLE_BACKUP_SUFFIX"), backup_mode) {
            s
        } else {
            "~".to_owned()
        }
    }
}

fn help(usage: &str) {
    println!(
        "{0} {1}\n\n\
         Usage: {0} SOURCE DEST\n   \
         or: {0} SOURCE... DIRECTORY\n\n\
         {2}",
        NAME, VERSION, usage
    );
}

fn exec(files: &[PathBuf], b: Behaviour) -> i32 {
    if let Some(ref name) = b.target_dir {
        return move_files_into_dir(files, &PathBuf::from(name), &b);
    }
    match files.len() {
        0 | 1 => {
            show_error!(
                "missing file operand\n\
                 Try '{} --help' for more information.",
                NAME
            );
            return 1;
        }
        2 => {
            let source = &files[0];
            let target = &files[1];
            if !source.exists() {
                show_error!(
                    "cannot stat ‘{}’: No such file or directory",
                    source.display()
                );
                return 1;
            }

            if target.is_dir() {
                if b.no_target_dir {
                    if !source.is_dir() {
                        show_error!(
                            "cannot overwrite directory ‘{}’ with non-directory",
                            target.display()
                        );
                        return 1;
                    }

                    return match rename(source, target, &b) {
                        Err(e) => {
                            show_error!(
                                "cannot move ‘{}’ to ‘{}’: {}",
                                source.display(), target.display(), e
                            );
                            1
                        }
                        _ => 0,
                    };
                }

                return move_files_into_dir(&[source.clone()], target, &b);
            } else if target.exists() && source.is_dir() {
                show_error!(
                    "cannot overwrite non-directory ‘{}’ with directory ‘{}’",
                    target.display(), source.display()
                );
                return 1;
            }

            if let Err(e) = rename(source, target, &b) {
                show_error!("{}", e);
                return 1;
            }
        }
        _ => {
            if b.no_target_dir {
                show_error!(
                    "mv: extra operand ‘{}’\n\
                     Try '{} --help' for more information.",
                    files[2].display(),
                    NAME
                );
                return 1;
            }
            let target_dir = files.last().unwrap();
            move_files_into_dir(&files[..files.len() - 1], target_dir, &b);
        }
    }
    0
}

fn move_files_into_dir(files: &[PathBuf], target_dir: &PathBuf, b: &Behaviour) -> i32 {
    if !target_dir.is_dir() {
        show_error!("target ‘{}’ is not a directory", target_dir.display());
        return 1;
    }

    let mut all_successful = true;
    for sourcepath in files.iter() {
        let targetpath = match sourcepath.as_os_str().to_str() {
            Some(name) => target_dir.join(name),
            None => {
                show_error!(
                    "cannot stat ‘{}’: No such file or directory",
                    sourcepath.display()
                );

                all_successful = false;
                continue;
            }
        };

        if let Err(e) = rename(sourcepath, &targetpath, b) {
            show_error!(
                "mv: cannot move ‘{}’ to ‘{}’: {}",
                sourcepath.display(),
                targetpath.display(),
                e
            );
            all_successful = false;
        }
    }
    if all_successful {
        0
    } else {
        1
    }
}

fn rename(from: &PathBuf, to: &PathBuf, b: &Behaviour) -> Result<()> {
    let mut backup_path = None;

    if to.exists() {
        match b.overwrite {
            OverwriteMode::NoClobber => return Ok(()),
            OverwriteMode::Interactive => {
                print!("{}: overwrite ‘{}’? ", NAME, to.display());
                if !read_yes() {
                    return Ok(());
                }
            }
            OverwriteMode::Force => {}
        };

        backup_path = match b.backup {
            BackupMode::NoBackup => None,
            BackupMode::SimpleBackup => Some(simple_backup_path(to, &b.suffix)),
            BackupMode::NumberedBackup => Some(numbered_backup_path(to)),
            BackupMode::ExistingBackup => Some(existing_backup_path(to, &b.suffix)),
        };
        if let Some(ref p) = backup_path {
            try!(fs::rename(to, p));
        }

        if b.update {
            if try!(try!(fs::metadata(from)).modified()) <= try!(try!(fs::metadata(to)).modified())
            {
                return Ok(());
            }
        }
    }

    // "to" may no longer exist if it was backed up
    if to.exists() && to.is_dir() {
        // normalize behavior between *nix and windows
        if from.is_dir() {
            if is_empty_dir(to) {
                try!(fs::remove_dir(to))
            } else {
                return Err(std::io::Error::new(std::io::ErrorKind::Other, "Directory not empty"));
            }
        }
    }

    try!(fs::rename(from, to));

    if b.verbose {
        print!("‘{}’ -> ‘{}’", from.display(), to.display());
        match backup_path {
            Some(path) => println!(" (backup: ‘{}’)", path.display()),
            None => println!(""),
        }
    }
    Ok(())
}

fn read_yes() -> bool {
    let mut s = String::new();
    match BufReader::new(stdin()).read_line(&mut s) {
        Ok(_) => match s.chars().nth(0) {
            Some(x) => x == 'y' || x == 'Y',
            _ => false,
        },
        _ => false,
    }
}

fn simple_backup_path(path: &PathBuf, suffix: &str) -> PathBuf {
    let mut p = path.to_string_lossy().into_owned();
    p.push_str(suffix);
    PathBuf::from(p)
}

fn numbered_backup_path(path: &PathBuf) -> PathBuf {
    (1_u64..)
        .map(|i| path.with_extension(format!("~{}~", i)))
        .skip_while(|p| p.exists())
        .next()
        .expect("cannot create backup")
}

fn existing_backup_path(path: &PathBuf, suffix: &str) -> PathBuf {
    let test_path = path.with_extension("~1~");
    if test_path.exists() {
        numbered_backup_path(path)
    } else {
        simple_backup_path(path, suffix)
    }
}

fn is_empty_dir(path: &PathBuf) -> bool {
    match fs::read_dir(path) {
        Ok(contents) => {
            return contents.peekable().peek().is_none();
        },
        Err(_e) => { return false; }
    }
}
<|endoftext|>


<commit_before>use rgtk::*;
use rustc_serialize::{Encodable, json};
use std::cell::Cell;
use std::env;
use std::collections::{HashMap, HashSet};
use std::io::{Read, Write};
use std::fs::{self, PathExt};
use std::ops::Deref;
use std::path::{Path, PathBuf};

pub static WINDOW_WIDTH : i32 = 1242;
pub static WINDOW_HEIGHT : i32 = 768;
pub static EDITOR_HEIGHT_PCT : f32 = 0.70;
pub static MIN_FONT_SIZE : i32 = 0;
pub static MAX_FONT_SIZE : i32 = 50;

pub static DATA_DIR : &'static str = ".soak";
pub static CONFIG_FILE : &'static str = ".soakrc";
pub static CONFIG_CONTENT : &'static str = include_str!("../resources/soakrc");
pub static PREFS_FILE : &'static str = "prefs.json";
pub static NO_WINDOW_FLAG : &'static str = "-nw";
pub static RACER_FLAG : &'static str = "--racer";

struct Resource {
    pub path: &'static [&'static str],
    pub data: &'static str,
}
pub static DATA_CONTENT : &'static [Resource] = &[
    Resource{path: &["after", "syntax", "rust.vim"],
             data: include_str!("../resources/soak/after/syntax/rust.vim")},

    Resource{path: &["autoload", "paste.vim"],
             data: include_str!("../resources/soak/autoload/paste.vim")},
    Resource{path: &["autoload", "rust.vim"],
             data: include_str!("../resources/soak/autoload/rust.vim")},

    Resource{path: &["compiler", "rustc.vim"],
             data: include_str!("../resources/soak/compiler/rustc.vim")},
    Resource{path: &["compiler", "cargo.vim"],
             data: include_str!("../resources/soak/compiler/cargo.vim")},

    Resource{path: &["doc", "rust.txt"],
             data: include_str!("../resources/soak/doc/rust.txt")},

    Resource{path: &["ftdetect", "rust.vim"],
             data: include_str!("../resources/soak/ftdetect/rust.vim")},

    Resource{path: &["ftplugin", "rust.vim"],
             data: include_str!("../resources/soak/ftplugin/rust.vim")},
    Resource{path: &["ftplugin", "c.vim"],
             data: include_str!("../resources/soak/ftplugin/c.vim")},

    Resource{path: &["indent", "rust.vim"],
             data: include_str!("../resources/soak/indent/rust.vim")},
    Resource{path: &["indent", "c.vim"],
             data: include_str!("../resources/soak/indent/c.vim")},

    Resource{path: &["plugin", "eunuch.vim"],
             data: include_str!("../resources/soak/plugin/eunuch.vim")},
    Resource{path: &["plugin", "racer.vim"],
             data: include_str!("../resources/soak/plugin/racer.vim")},
    Resource{path: &["plugin", "rust.vim"],
             data: include_str!("../resources/soak/plugin/rust.vim")},

    Resource{path: &["syntax", "c.vim"],
             data: include_str!("../resources/soak/syntax/c.vim")},
    Resource{path: &["syntax", "nosyntax.vim"],
             data: include_str!("../resources/soak/syntax/nosyntax.vim")},
    Resource{path: &["syntax", "rust.vim"],
             data: include_str!("../resources/soak/syntax/rust.vim")},
    Resource{path: &["syntax", "syncolor.vim"],
             data: include_str!("../resources/soak/syntax/syncolor.vim")},
    Resource{path: &["syntax", "synload.vim"],
             data: include_str!("../resources/soak/syntax/synload.vim")},
    Resource{path: &["syntax", "syntax.vim"],
             data: include_str!("../resources/soak/syntax/syntax.vim")},

    Resource{path: &["syntax_checkers", "rust", "rustc.vim"],
             data: include_str!("../resources/soak/syntax_checkers/rust/rustc.vim")},

    Resource{path: &["filetype.vim"],
             data: include_str!("../resources/soak/filetype.vim")}
];

pub struct State<'a> {
    pub projects: HashSet<String>,
    pub expansions: HashSet<String>,
    pub selection: Option<String>,
    pub easy_mode: bool,
    pub font_size: i32,
    pub builders: HashMap<PathBuf, (gtk::VteTerminal, Cell<i32>)>,
    pub window: &'a gtk::Window,
    pub tree_store: &'a gtk::TreeStore,
    pub tree_model: &'a gtk::TreeModel,
    pub tree_selection: &'a gtk::TreeSelection,
    pub rename_button: &'a gtk::Button,
    pub remove_button: &'a gtk::Button,
    pub is_refreshing_tree: bool
}

#[derive(RustcDecodable, RustcEncodable)]
struct Prefs {
    projects: Vec<String>,
    expansions: Vec<String>,
    selection: Option<String>,
    easy_mode: bool,
    font_size: i32
}

pub fn get_home_dir() -> PathBuf {
    if let Some(path) = env::home_dir() {
        path
    } else {
        PathBuf::new(".")
    }
}

fn get_prefs(state: &State) -> Prefs {
    Prefs {
        projects: state.projects.clone().into_iter().collect(),
        expansions: state.expansions.clone().into_iter().collect(),
        selection: state.selection.clone(),
        easy_mode: state.easy_mode,
        font_size: state.font_size
    }
}

pub fn is_parent_path(parent_str: &String, child_str: &String) -> bool {
    child_str.starts_with(parent_str.as_slice()) &&
    Path::new(parent_str).parent() != Path::new(child_str).parent()
}

pub fn get_selected_path(state: &State) -> Option<String> {
    let mut iter = gtk::TreeIter::new().unwrap();

    if state.tree_selection.get_selected(state.tree_model, &mut iter) {
        state.tree_model.get_value(&iter, 1).get_string()
    } else {
        None
    }
}

fn is_project_path(path: &Path) -> bool {
    path.join("Cargo.toml").exists()
}

fn is_project_root(state: &State, path: &Path) -> bool {
    if let Some(path_str) = path.to_str() {
        state.projects.contains(&path_str.to_string())
    } else {
        false
    }
}

pub fn get_project_path(state: &State, path: &Path) -> Option<PathBuf> {
    if is_project_path(path) || is_project_root(state, path) {
        Some(PathBuf::new(path))
    } else {
        if let Some(parent_path) = path.parent() {
            get_project_path(state, parent_path.deref())
        } else {
            None
        }
    }
}

pub fn get_selected_project_path(state: &State) -> Option<PathBuf> {
    if let Some(path_str) = get_selected_path(state) {
        get_project_path(state, Path::new(&path_str))
    } else {
        None
    }
}

pub fn write_prefs(state: &State) {
    let prefs = get_prefs(state);

    let mut json_str = String::new();
    {
        let mut encoder = json::Encoder::new_pretty(&mut json_str);
        prefs.encode(&mut encoder).ok().expect("Error encoding prefs.");
    }

    let prefs_path = get_home_dir().deref().join(DATA_DIR).join(PREFS_FILE);
    if let Some(mut f) = fs::File::create(&prefs_path).ok() {
        match f.write(json_str.as_bytes()) {
            Ok(_) => {},
            Err(e) => println!("Error writing prefs: {}", e)
        };
    }
}

pub fn read_prefs(state: &mut State) {
    let prefs_path = get_home_dir().deref().join(DATA_DIR).join(PREFS_FILE);
    if let Some(mut f) = fs::File::open(&prefs_path).ok() {
        let mut json_str = String::new();
        let prefs_option : Option<Prefs> = match f.read_to_string(&mut json_str) {
            Ok(_) => {
                match json::decode(json_str.as_slice()) {
                    Ok(object) => Some(object),
                    Err(e) => {
                        println!("Error decoding prefs: {}", e);
                        None
                    }
                }
            },
            Err(_) => None
        };

        if let Some(prefs) = prefs_option {
            state.projects.clear();
            for path_str in prefs.projects.iter() {
                state.projects.insert(path_str.clone());
            }

            state.expansions.clear();
            for path_str in prefs.expansions.iter() {
                state.expansions.insert(path_str.clone());
            }

            state.selection = prefs.selection;
            state.easy_mode = prefs.easy_mode;

            if (prefs.font_size >= MIN_FONT_SIZE) && (prefs.font_size <= MAX_FONT_SIZE) {
                state.font_size = prefs.font_size;
            }
        }
    }
}
<commit_msg>Make struct public<commit_after>use rgtk::*;
use rustc_serialize::{Encodable, json};
use std::cell::Cell;
use std::env;
use std::collections::{HashMap, HashSet};
use std::io::{Read, Write};
use std::fs::{self, PathExt};
use std::ops::Deref;
use std::path::{Path, PathBuf};

pub static WINDOW_WIDTH : i32 = 1242;
pub static WINDOW_HEIGHT : i32 = 768;
pub static EDITOR_HEIGHT_PCT : f32 = 0.70;
pub static MIN_FONT_SIZE : i32 = 0;
pub static MAX_FONT_SIZE : i32 = 50;

pub static DATA_DIR : &'static str = ".soak";
pub static CONFIG_FILE : &'static str = ".soakrc";
pub static CONFIG_CONTENT : &'static str = include_str!("../resources/soakrc");
pub static PREFS_FILE : &'static str = "prefs.json";
pub static NO_WINDOW_FLAG : &'static str = "-nw";
pub static RACER_FLAG : &'static str = "--racer";

pub struct Resource {
    pub path: &'static [&'static str],
    pub data: &'static str,
}
pub static DATA_CONTENT : &'static [Resource] = &[
    Resource{path: &["after", "syntax", "rust.vim"],
             data: include_str!("../resources/soak/after/syntax/rust.vim")},

    Resource{path: &["autoload", "paste.vim"],
             data: include_str!("../resources/soak/autoload/paste.vim")},
    Resource{path: &["autoload", "rust.vim"],
             data: include_str!("../resources/soak/autoload/rust.vim")},

    Resource{path: &["compiler", "rustc.vim"],
             data: include_str!("../resources/soak/compiler/rustc.vim")},
    Resource{path: &["compiler", "cargo.vim"],
             data: include_str!("../resources/soak/compiler/cargo.vim")},

    Resource{path: &["doc", "rust.txt"],
             data: include_str!("../resources/soak/doc/rust.txt")},

    Resource{path: &["ftdetect", "rust.vim"],
             data: include_str!("../resources/soak/ftdetect/rust.vim")},

    Resource{path: &["ftplugin", "rust.vim"],
             data: include_str!("../resources/soak/ftplugin/rust.vim")},
    Resource{path: &["ftplugin", "c.vim"],
             data: include_str!("../resources/soak/ftplugin/c.vim")},

    Resource{path: &["indent", "rust.vim"],
             data: include_str!("../resources/soak/indent/rust.vim")},
    Resource{path: &["indent", "c.vim"],
             data: include_str!("../resources/soak/indent/c.vim")},

    Resource{path: &["plugin", "eunuch.vim"],
             data: include_str!("../resources/soak/plugin/eunuch.vim")},
    Resource{path: &["plugin", "racer.vim"],
             data: include_str!("../resources/soak/plugin/racer.vim")},
    Resource{path: &["plugin", "rust.vim"],
             data: include_str!("../resources/soak/plugin/rust.vim")},

    Resource{path: &["syntax", "c.vim"],
             data: include_str!("../resources/soak/syntax/c.vim")},
    Resource{path: &["syntax", "nosyntax.vim"],
             data: include_str!("../resources/soak/syntax/nosyntax.vim")},
    Resource{path: &["syntax", "rust.vim"],
             data: include_str!("../resources/soak/syntax/rust.vim")},
    Resource{path: &["syntax", "syncolor.vim"],
             data: include_str!("../resources/soak/syntax/syncolor.vim")},
    Resource{path: &["syntax", "synload.vim"],
             data: include_str!("../resources/soak/syntax/synload.vim")},
    Resource{path: &["syntax", "syntax.vim"],
             data: include_str!("../resources/soak/syntax/syntax.vim")},

    Resource{path: &["syntax_checkers", "rust", "rustc.vim"],
             data: include_str!("../resources/soak/syntax_checkers/rust/rustc.vim")},

    Resource{path: &["filetype.vim"],
             data: include_str!("../resources/soak/filetype.vim")}
];

pub struct State<'a> {
    pub projects: HashSet<String>,
    pub expansions: HashSet<String>,
    pub selection: Option<String>,
    pub easy_mode: bool,
    pub font_size: i32,
    pub builders: HashMap<PathBuf, (gtk::VteTerminal, Cell<i32>)>,
    pub window: &'a gtk::Window,
    pub tree_store: &'a gtk::TreeStore,
    pub tree_model: &'a gtk::TreeModel,
    pub tree_selection: &'a gtk::TreeSelection,
    pub rename_button: &'a gtk::Button,
    pub remove_button: &'a gtk::Button,
    pub is_refreshing_tree: bool
}

#[derive(RustcDecodable, RustcEncodable)]
struct Prefs {
    projects: Vec<String>,
    expansions: Vec<String>,
    selection: Option<String>,
    easy_mode: bool,
    font_size: i32
}

pub fn get_home_dir() -> PathBuf {
    if let Some(path) = env::home_dir() {
        path
    } else {
        PathBuf::new(".")
    }
}

fn get_prefs(state: &State) -> Prefs {
    Prefs {
        projects: state.projects.clone().into_iter().collect(),
        expansions: state.expansions.clone().into_iter().collect(),
        selection: state.selection.clone(),
        easy_mode: state.easy_mode,
        font_size: state.font_size
    }
}

pub fn is_parent_path(parent_str: &String, child_str: &String) -> bool {
    child_str.starts_with(parent_str.as_slice()) &&
    Path::new(parent_str).parent() != Path::new(child_str).parent()
}

pub fn get_selected_path(state: &State) -> Option<String> {
    let mut iter = gtk::TreeIter::new().unwrap();

    if state.tree_selection.get_selected(state.tree_model, &mut iter) {
        state.tree_model.get_value(&iter, 1).get_string()
    } else {
        None
    }
}

fn is_project_path(path: &Path) -> bool {
    path.join("Cargo.toml").exists()
}

fn is_project_root(state: &State, path: &Path) -> bool {
    if let Some(path_str) = path.to_str() {
        state.projects.contains(&path_str.to_string())
    } else {
        false
    }
}

pub fn get_project_path(state: &State, path: &Path) -> Option<PathBuf> {
    if is_project_path(path) || is_project_root(state, path) {
        Some(PathBuf::new(path))
    } else {
        if let Some(parent_path) = path.parent() {
            get_project_path(state, parent_path.deref())
        } else {
            None
        }
    }
}

pub fn get_selected_project_path(state: &State) -> Option<PathBuf> {
    if let Some(path_str) = get_selected_path(state) {
        get_project_path(state, Path::new(&path_str))
    } else {
        None
    }
}

pub fn write_prefs(state: &State) {
    let prefs = get_prefs(state);

    let mut json_str = String::new();
    {
        let mut encoder = json::Encoder::new_pretty(&mut json_str);
        prefs.encode(&mut encoder).ok().expect("Error encoding prefs.");
    }

    let prefs_path = get_home_dir().deref().join(DATA_DIR).join(PREFS_FILE);
    if let Some(mut f) = fs::File::create(&prefs_path).ok() {
        match f.write(json_str.as_bytes()) {
            Ok(_) => {},
            Err(e) => println!("Error writing prefs: {}", e)
        };
    }
}

pub fn read_prefs(state: &mut State) {
    let prefs_path = get_home_dir().deref().join(DATA_DIR).join(PREFS_FILE);
    if let Some(mut f) = fs::File::open(&prefs_path).ok() {
        let mut json_str = String::new();
        let prefs_option : Option<Prefs> = match f.read_to_string(&mut json_str) {
            Ok(_) => {
                match json::decode(json_str.as_slice()) {
                    Ok(object) => Some(object),
                    Err(e) => {
                        println!("Error decoding prefs: {}", e);
                        None
                    }
                }
            },
            Err(_) => None
        };

        if let Some(prefs) = prefs_option {
            state.projects.clear();
            for path_str in prefs.projects.iter() {
                state.projects.insert(path_str.clone());
            }

            state.expansions.clear();
            for path_str in prefs.expansions.iter() {
                state.expansions.insert(path_str.clone());
            }

            state.selection = prefs.selection;
            state.easy_mode = prefs.easy_mode;

            if (prefs.font_size >= MIN_FONT_SIZE) && (prefs.font_size <= MAX_FONT_SIZE) {
                state.font_size = prefs.font_size;
            }
        }
    }
}
<|endoftext|>


<commit_before>//! Things we have not found a home for.

use std::sync::Arc;
use std::time::{SystemTime, Duration};
use basetypes::*;
use moves::*;
use tt::*;
use position::*;
use search::*;

/// A sequence of moves from some starting position, together with the
/// value assigned to the final position.
pub struct Variation {
    /// A sequence of moves from some starting position.
    pub moves: Vec<Move>,

    /// The value assigned to the final position.
    pub value: Value,

    /// The accuracy of the assigned value.
    pub bound: BoundType,
}

/// Contains information about the current progress of a search.
pub struct SearchStatus {
    /// The starting time for the search.
    pub started_at: SystemTime,

    /// The starting (root) position for the search.
    pub position: Position,

    /// `true` if the search has stopped, `false` otherwise.
    pub done: bool,

    /// The reached search depth.
    pub depth: u8,

    /// The number of legal moves in the starting (root) position.
    pub legal_moves_count: usize,

    /// The best variations found so far. The first move in each
    /// variation will be different.
    pub variations: Vec<Variation>,

    /// The duration of the search in milliseconds.
    pub duration_millis: u64,

    /// The number of analyzed nodes.
    pub searched_nodes: NodeCount,

    /// Average number of analyzed nodes per second.
    pub nps: NodeCount,
}

/// A thread that executes consecutive searches in different starting
/// positions.
pub struct SearchThread {
    tt: Arc<Tt>,
    status: SearchStatus,
    searcher: DeepeningSearcher<MultipvSearcher<AlphabetaSearcher>>,
}

impl SearchThread {
    /// Creates a new instance.
    pub fn new(tt: Arc<Tt>) -> SearchThread {
        SearchThread {
            tt: tt.clone(),
            status: SearchStatus {
                started_at: SystemTime::now(),
                position: Position::from_fen(START_POSITION_FEN).ok().unwrap(),
                done: true,
                depth: 0,
                variations: vec![Variation {
                                     value: VALUE_MIN,
                                     bound: BOUND_LOWER,
                                     moves: vec![],
                                 }],
                legal_moves_count: 20,
                searched_nodes: 0,
                duration_millis: 0,
                nps: 0,
            },
            searcher: DeepeningSearcher::new(tt),
        }
    }

    /// Stops the current search, starts a new search, updates the
    /// status.
    ///
    /// `position` is the starting position for the new
    /// search. `variation_count` specifies how many best lines of
    /// play to calculate (the first move in each line will be
    /// different). `searchmoves` restricts the analysis to the
    /// supplied list of moves only (no restrictions if the suppied
    /// list is empty). The move format is long algebraic
    /// notation. Examples: e2e4, e7e5, e1g1 (white short castling),
    /// e7e8q (for promotion).
    #[allow(unused_variables)]
    pub fn search(&mut self,
                  position: &Position,
                  variation_count: usize,
                  mut searchmoves: Vec<String>) {
        let mut legal_moves = position.legal_moves();
        let legal_moves_count = legal_moves.len();

        // Remove all legal moves not allowed by `searchmoves`.
        if !searchmoves.is_empty() {
            searchmoves.sort();
            let mut moves = vec![];
            for m in legal_moves.iter() {
                if searchmoves.binary_search(&m.notation()).is_ok() {
                    moves.push(*m);
                }
            }
            if !moves.is_empty() {
                legal_moves = moves;
            }
        };

        self.stop();
        self.searcher.start_search(SearchParams {
            search_id: 0,
            position: position.clone(),
            depth: MAX_DEPTH,
            lower_bound: VALUE_MIN,
            upper_bound: VALUE_MAX,
            searchmoves: legal_moves,
            variation_count: variation_count,
        });

        self.status = SearchStatus {
            started_at: SystemTime::now(),
            position: position.clone(),
            done: false,
            depth: 0,
            legal_moves_count: legal_moves_count,
            variations: vec![Variation {
                                 value: VALUE_MIN,
                                 bound: BOUND_LOWER,
                                 moves: vec![],
                             }], // TODO: Set good initial value (vec![best_move]).
            searched_nodes: 0,
            duration_millis: 0,
            ..self.status
        };
    }

    /// Stops the current search, updates the status.
    ///
    /// Does nothing if the current search is already stopped.
    pub fn stop(&mut self) {
        self.searcher.terminate_search();
        while !self.status().done {
            self.wait_status_update(Duration::from_millis(1000));
        }
    }

    /// Returns the status of the current search.
    ///
    /// **Important note:** Consecutive calls to this method will
    /// return the same unchanged result. Only after calling `search`,
    /// `stop`, or `wait_status_update`, the result returned by
    /// `status` may change.
    #[inline(always)]
    pub fn status(&self) -> &SearchStatus {
        &self.status
    }

    /// Waits for a search status update, timing out after a specified
    /// duration or earlier.
    pub fn wait_status_update(&mut self, duration: Duration) {
        self.searcher.wait_report(duration);
        while let Ok(r) = self.searcher.try_recv_report() {
            self.process_report(r)
        }
    }

    /// A helper method. It updates the current status according to
    /// the received report message.
    fn process_report(&mut self, report: Report) {
        let duration = self.status.started_at.elapsed().unwrap();
        self.status.duration_millis = 1000 * duration.as_secs() +
                                      (duration.subsec_nanos() / 1000000) as u64;
        self.status.searched_nodes = report.searched_nodes;
        self.status.nps = 1000 * (self.status.nps + self.status.searched_nodes) /
                          (1000 + self.status.duration_millis);
        if self.status.depth < report.depth {
            self.status.depth = report.depth;
            self.status.variations = vec![extract_pv(&self.tt,
                                                     &self.status.position,
                                                     report.depth)];
        }
        self.status.done = report.done;
    }
}

/// A helper function. It extracts the primary variation (PV) from the
/// transposition table (TT) and returns it.
///
/// **Note:** Because the PV is a moving target (the search continues
/// to run in parallel), imperfections in the reported PVs are
/// unavoidable. To deal with this, we turn a blind eye if the value
/// at the root of the PV differs from the value at the leaf by no
/// more than `EPSILON`.
fn extract_pv(tt: &Tt, position: &Position, depth: u8) -> Variation {
    const EPSILON: Value = 8; // A sufficiently small value (in centipawns).

    // Extract the PV, the leaf value, the root value, and the bound
    // type from the TT.
    let mut p = position.clone();
    let mut our_turn = true;
    let mut prev_move = None;
    let mut moves = Vec::new();
    let mut leaf_value = -9999;
    let mut root_value = leaf_value;
    let mut bound = BOUND_LOWER;
    while let Some(entry) = tt.peek(p.hash()) {
        if entry.bound() != BOUND_NONE {
            // Get the value and the bound type. In half of the
            // cases the value stored in `entry` is from other
            // side's perspective.
            if our_turn {
                leaf_value = entry.value();
                bound = entry.bound();
            } else {
                leaf_value = -entry.value();
                bound = match entry.bound() {
                    BOUND_UPPER => BOUND_LOWER,
                    BOUND_LOWER => BOUND_UPPER,
                    x => x,
                };
            }

            // The values under -9999 and over 9999 may look ugly in
            // some GUIs, so we trim them.
            if leaf_value > 9999 {
                leaf_value = 9999;
                if bound == BOUND_LOWER {
                    bound = BOUND_EXACT
                }
            }
            if leaf_value < -9999 {
                leaf_value = 9999;
                if bound == BOUND_UPPER {
                    bound = BOUND_EXACT
                }
            }

            if let Some(m) = prev_move {
                // Extend the PV with the previous move.
                moves.push(m);
            } else {
                // We are at the root -- set the root value.
                root_value = leaf_value;
            }

            if moves.len() < depth as usize && (leaf_value - root_value).abs() <= EPSILON {
                if let Some(m) = p.try_move_digest(entry.move16()) {
                    if p.do_move(m) {
                        if bound == BOUND_EXACT {
                            // Extend the PV with one more move.
                            prev_move = Some(m);
                            our_turn = !our_turn;
                            continue;
                        } else {
                            // This is the last move in the PV.
                            moves.push(m);
                        }
                    }
                }
            }
        }
        break;
    }

    // Change the bound type if the leaf value in the PV differs too
    // much from the root value.
    bound = match leaf_value - root_value {
        x if x > EPSILON && bound != BOUND_UPPER => BOUND_LOWER,
        x if x < -EPSILON && bound != BOUND_LOWER => BOUND_UPPER,
        _ => bound,
    };

    Variation {
        value: root_value,
        bound: bound,
        moves: moves,
    }
}
<commit_msg>use AspirationSearcher<commit_after>//! Things we have not found a home for.

use std::sync::Arc;
use std::time::{SystemTime, Duration};
use basetypes::*;
use moves::*;
use tt::*;
use position::*;
use search::*;

/// A sequence of moves from some starting position, together with the
/// value assigned to the final position.
pub struct Variation {
    /// A sequence of moves from some starting position.
    pub moves: Vec<Move>,

    /// The value assigned to the final position.
    pub value: Value,

    /// The accuracy of the assigned value.
    pub bound: BoundType,
}

/// Contains information about the current progress of a search.
pub struct SearchStatus {
    /// The starting time for the search.
    pub started_at: SystemTime,

    /// The starting (root) position for the search.
    pub position: Position,

    /// `true` if the search has stopped, `false` otherwise.
    pub done: bool,

    /// The reached search depth.
    pub depth: u8,

    /// The number of legal moves in the starting (root) position.
    pub legal_moves_count: usize,

    /// The best variations found so far. The first move in each
    /// variation will be different.
    pub variations: Vec<Variation>,

    /// The duration of the search in milliseconds.
    pub duration_millis: u64,

    /// The number of analyzed nodes.
    pub searched_nodes: NodeCount,

    /// Average number of analyzed nodes per second.
    pub nps: NodeCount,
}

/// A thread that executes consecutive searches in different starting
/// positions.
pub struct SearchThread {
    tt: Arc<Tt>,
    status: SearchStatus,
    searcher: DeepeningSearcher<AspirationSearcher<AlphabetaSearcher>>,
}

impl SearchThread {
    /// Creates a new instance.
    pub fn new(tt: Arc<Tt>) -> SearchThread {
        SearchThread {
            tt: tt.clone(),
            status: SearchStatus {
                started_at: SystemTime::now(),
                position: Position::from_fen(START_POSITION_FEN).ok().unwrap(),
                done: true,
                depth: 0,
                variations: vec![Variation {
                                     value: VALUE_MIN,
                                     bound: BOUND_LOWER,
                                     moves: vec![],
                                 }],
                legal_moves_count: 20,
                searched_nodes: 0,
                duration_millis: 0,
                nps: 0,
            },
            searcher: DeepeningSearcher::new(tt),
        }
    }

    /// Stops the current search, starts a new search, updates the
    /// status.
    ///
    /// `position` is the starting position for the new
    /// search. `variation_count` specifies how many best lines of
    /// play to calculate (the first move in each line will be
    /// different). `searchmoves` restricts the analysis to the
    /// supplied list of moves only (no restrictions if the suppied
    /// list is empty). The move format is long algebraic
    /// notation. Examples: e2e4, e7e5, e1g1 (white short castling),
    /// e7e8q (for promotion).
    #[allow(unused_variables)]
    pub fn search(&mut self,
                  position: &Position,
                  variation_count: usize,
                  mut searchmoves: Vec<String>) {
        let mut legal_moves = position.legal_moves();
        let legal_moves_count = legal_moves.len();

        // Remove all legal moves not allowed by `searchmoves`.
        if !searchmoves.is_empty() {
            searchmoves.sort();
            let mut moves = vec![];
            for m in legal_moves.iter() {
                if searchmoves.binary_search(&m.notation()).is_ok() {
                    moves.push(*m);
                }
            }
            if !moves.is_empty() {
                legal_moves = moves;
            }
        };

        self.stop();
        self.searcher.start_search(SearchParams {
            search_id: 0,
            position: position.clone(),
            depth: MAX_DEPTH,
            lower_bound: VALUE_MIN,
            upper_bound: VALUE_MAX,
            searchmoves: legal_moves,
            variation_count: variation_count,
        });

        self.status = SearchStatus {
            started_at: SystemTime::now(),
            position: position.clone(),
            done: false,
            depth: 0,
            legal_moves_count: legal_moves_count,
            variations: vec![Variation {
                                 value: VALUE_MIN,
                                 bound: BOUND_LOWER,
                                 moves: vec![],
                             }], // TODO: Set good initial value (vec![best_move]).
            searched_nodes: 0,
            duration_millis: 0,
            ..self.status
        };
    }

    /// Stops the current search, updates the status.
    ///
    /// Does nothing if the current search is already stopped.
    pub fn stop(&mut self) {
        self.searcher.terminate_search();
        while !self.status().done {
            self.wait_status_update(Duration::from_millis(1000));
        }
    }

    /// Returns the status of the current search.
    ///
    /// **Important note:** Consecutive calls to this method will
    /// return the same unchanged result. Only after calling `search`,
    /// `stop`, or `wait_status_update`, the result returned by
    /// `status` may change.
    #[inline(always)]
    pub fn status(&self) -> &SearchStatus {
        &self.status
    }

    /// Waits for a search status update, timing out after a specified
    /// duration or earlier.
    pub fn wait_status_update(&mut self, duration: Duration) {
        self.searcher.wait_report(duration);
        while let Ok(r) = self.searcher.try_recv_report() {
            self.process_report(r)
        }
    }

    /// A helper method. It updates the current status according to
    /// the received report message.
    fn process_report(&mut self, report: Report) {
        let duration = self.status.started_at.elapsed().unwrap();
        self.status.duration_millis = 1000 * duration.as_secs() +
                                      (duration.subsec_nanos() / 1000000) as u64;
        self.status.searched_nodes = report.searched_nodes;
        self.status.nps = 1000 * (self.status.nps + self.status.searched_nodes) /
                          (1000 + self.status.duration_millis);
        if self.status.depth < report.depth {
            self.status.depth = report.depth;
            self.status.variations = vec![extract_pv(&self.tt,
                                                     &self.status.position,
                                                     report.depth)];
        }
        self.status.done = report.done;
    }
}

/// A helper function. It extracts the primary variation (PV) from the
/// transposition table (TT) and returns it.
///
/// **Note:** Because the PV is a moving target (the search continues
/// to run in parallel), imperfections in the reported PVs are
/// unavoidable. To deal with this, we turn a blind eye if the value
/// at the root of the PV differs from the value at the leaf by no
/// more than `EPSILON`.
fn extract_pv(tt: &Tt, position: &Position, depth: u8) -> Variation {
    const EPSILON: Value = 8; // A sufficiently small value (in centipawns).

    // Extract the PV, the leaf value, the root value, and the bound
    // type from the TT.
    let mut p = position.clone();
    let mut our_turn = true;
    let mut prev_move = None;
    let mut moves = Vec::new();
    let mut leaf_value = -9999;
    let mut root_value = leaf_value;
    let mut bound = BOUND_LOWER;
    while let Some(entry) = tt.peek(p.hash()) {
        if entry.bound() != BOUND_NONE {
            // Get the value and the bound type. In half of the
            // cases the value stored in `entry` is from other
            // side's perspective.
            if our_turn {
                leaf_value = entry.value();
                bound = entry.bound();
            } else {
                leaf_value = -entry.value();
                bound = match entry.bound() {
                    BOUND_UPPER => BOUND_LOWER,
                    BOUND_LOWER => BOUND_UPPER,
                    x => x,
                };
            }

            // The values under -9999 and over 9999 may look ugly in
            // some GUIs, so we trim them.
            if leaf_value > 9999 {
                leaf_value = 9999;
                if bound == BOUND_LOWER {
                    bound = BOUND_EXACT
                }
            }
            if leaf_value < -9999 {
                leaf_value = 9999;
                if bound == BOUND_UPPER {
                    bound = BOUND_EXACT
                }
            }

            if let Some(m) = prev_move {
                // Extend the PV with the previous move.
                moves.push(m);
            } else {
                // We are at the root -- set the root value.
                root_value = leaf_value;
            }

            if moves.len() < depth as usize && (leaf_value - root_value).abs() <= EPSILON {
                if let Some(m) = p.try_move_digest(entry.move16()) {
                    if p.do_move(m) {
                        if bound == BOUND_EXACT {
                            // Extend the PV with one more move.
                            prev_move = Some(m);
                            our_turn = !our_turn;
                            continue;
                        } else {
                            // This is the last move in the PV.
                            moves.push(m);
                        }
                    }
                }
            }
        }
        break;
    }

    // Change the bound type if the leaf value in the PV differs too
    // much from the root value.
    bound = match leaf_value - root_value {
        x if x > EPSILON && bound != BOUND_UPPER => BOUND_LOWER,
        x if x < -EPSILON && bound != BOUND_LOWER => BOUND_UPPER,
        _ => bound,
    };

    Variation {
        value: root_value,
        bound: bound,
        moves: moves,
    }
}
<|endoftext|>