<commit_before>use super::options;
use clap::ArgMatches;

/// Abstraction for getopts
pub trait CommandLineOpts {
    /// returns all command line parameters which do not belong to an option.
    fn inputs(&self) -> Vec<&str>;
    /// tests if any of the specified options is present.
    fn opts_present(&self, _: &[&str]) -> bool;
}

/// Implementation for `getopts`
impl<'a> CommandLineOpts for ArgMatches {
    fn inputs(&self) -> Vec<&str> {
        self.values_of(options::FILENAME)
            .map(|values| values.collect())
            .unwrap_or_default()
    }

    fn opts_present(&self, opts: &[&str]) -> bool {
        opts.iter().any(|opt| self.is_present(opt))
    }
}

/// Contains the Input filename(s) with an optional offset.
///
/// `FileNames` is used for one or more file inputs ("-" = stdin)
/// `FileAndOffset` is used for a single file input, with an offset
/// and an optional label. Offset and label are specified in bytes.
/// `FileAndOffset` will be only used if an offset is specified,
/// but it might be 0.
#[derive(PartialEq, Debug)]
pub enum CommandLineInputs {
    FileNames(Vec<String>),
    FileAndOffset((String, usize, Option<usize>)),
}

/// Interprets the command line inputs of od.
///
/// Returns either an unspecified number of filenames.
/// Or it will return a single filename, with an offset and optional label.
/// Offset and label are specified in bytes.
/// '-' is used as filename if stdin is meant. This is also returned if
/// there is no input, as stdin is the default input.
pub fn parse_inputs(matches: &dyn CommandLineOpts) -> Result<CommandLineInputs, String> {
    let mut input_strings = matches.inputs();

    if matches.opts_present(&["traditional"]) {
        return parse_inputs_traditional(input_strings);
    }

    // test if command line contains: [file] <offset>
    // fall-through if no (valid) offset is found
    if input_strings.len() == 1 || input_strings.len() == 2 {
        // if any of the options -A, -j, -N, -t, -v or -w are present there is no offset
        if !matches.opts_present(&[
            options::ADDRESS_RADIX,
            options::READ_BYTES,
            options::READ_BYTES,
            options::FORMAT,
            options::OUTPUT_DUPLICATES,
            options::WIDTH,
        ]) {
            // test if the last input can be parsed as an offset.
            let offset = parse_offset_operand(input_strings[input_strings.len() - 1]);
            if let Ok(n) = offset {
                // if there is just 1 input (stdin), an offset must start with '+'
                if input_strings.len() == 1 && input_strings[0].starts_with('+') {
                    return Ok(CommandLineInputs::FileAndOffset(("-".to_string(), n, None)));
                }
                if input_strings.len() == 2 {
                    return Ok(CommandLineInputs::FileAndOffset((
                        input_strings[0].to_string(),
                        n,
                        None,
                    )));
                }
            }
        }
    }

    if input_strings.is_empty() {
        input_strings.push("-");
    }
    Ok(CommandLineInputs::FileNames(
        input_strings.iter().map(|&s| s.to_string()).collect(),
    ))
}

/// interprets inputs when --traditional is on the command line
///
/// normally returns CommandLineInputs::FileAndOffset, but if no offset is found,
/// it returns CommandLineInputs::FileNames (also to differentiate from the offset == 0)
pub fn parse_inputs_traditional(input_strings: Vec<&str>) -> Result<CommandLineInputs, String> {
    match input_strings.len() {
        0 => Ok(CommandLineInputs::FileNames(vec!["-".to_string()])),
        1 => {
            let offset0 = parse_offset_operand(input_strings[0]);
            Ok(match offset0 {
                Ok(n) => CommandLineInputs::FileAndOffset(("-".to_string(), n, None)),
                _ => CommandLineInputs::FileNames(
                    input_strings.iter().map(|&s| s.to_string()).collect(),
                ),
            })
        }
        2 => {
            let offset0 = parse_offset_operand(input_strings[0]);
            let offset1 = parse_offset_operand(input_strings[1]);
            match (offset0, offset1) {
                (Ok(n), Ok(m)) => Ok(CommandLineInputs::FileAndOffset((
                    "-".to_string(),
                    n,
                    Some(m),
                ))),
                (_, Ok(m)) => Ok(CommandLineInputs::FileAndOffset((
                    input_strings[0].to_string(),
                    m,
                    None,
                ))),
                _ => Err(format!("invalid offset: {}", input_strings[1])),
            }
        }
        3 => {
            let offset = parse_offset_operand(input_strings[1]);
            let label = parse_offset_operand(input_strings[2]);
            match (offset, label) {
                (Ok(n), Ok(m)) => Ok(CommandLineInputs::FileAndOffset((
                    input_strings[0].to_string(),
                    n,
                    Some(m),
                ))),
                (Err(_), _) => Err(format!("invalid offset: {}", input_strings[1])),
                (_, Err(_)) => Err(format!("invalid label: {}", input_strings[2])),
            }
        }
        _ => Err(format!(
            "too many inputs after --traditional: {}",
            input_strings[3]
        )),
    }
}

/// parses format used by offset and label on the command line
pub fn parse_offset_operand(s: &str) -> Result<usize, &'static str> {
    let mut start = 0;
    let mut len = s.len();
    let mut radix = 8;
    let mut multiply = 1;

    if s.starts_with('+') {
        start += 1;
    }

    if s[start..len].starts_with("0x") || s[start..len].starts_with("0X") {
        start += 2;
        radix = 16;
    } else {
        if s[start..len].ends_with('b') {
            len -= 1;
            multiply = 512;
        }
        if s[start..len].ends_with('.') {
            len -= 1;
            radix = 10;
        }
    }
    match usize::from_str_radix(&s[start..len], radix) {
        Ok(i) => Ok(i * multiply),
        Err(_) => Err("parse failed"),
    }
}

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

    /// A mock for the command line options type
    ///
    /// `inputs` are all command line parameters which do not belong to an option.
    /// `option_names` are the names of the options on the command line.
    struct MockOptions<'a> {
        inputs: Vec<String>,
        option_names: Vec<&'a str>,
    }

    impl<'a> MockOptions<'a> {
        fn new(inputs: Vec<&'a str>, option_names: Vec<&'a str>) -> MockOptions<'a> {
            MockOptions {
                inputs: inputs.iter().map(|&s| s.to_string()).collect::<Vec<_>>(),
                option_names,
            }
        }
    }

    impl<'a> CommandLineOpts for MockOptions<'a> {
        fn inputs(&self) -> Vec<&str> {
            self.inputs.iter().map(|s| s.as_str()).collect()
        }
        fn opts_present(&self, opts: &[&str]) -> bool {
            for expected in opts.iter() {
                for actual in self.option_names.iter() {
                    if *expected == *actual {
                        return true;
                    }
                }
            }
            false
        }
    }

    #[test]
    fn test_parse_inputs_normal() {
        assert_eq!(
            CommandLineInputs::FileNames(vec!["-".to_string()]),
            parse_inputs(&MockOptions::new(vec![], vec![])).unwrap()
        );

        assert_eq!(
            CommandLineInputs::FileNames(vec!["-".to_string()]),
            parse_inputs(&MockOptions::new(vec!["-"], vec![])).unwrap()
        );

        assert_eq!(
            CommandLineInputs::FileNames(vec!["file1".to_string()]),
            parse_inputs(&MockOptions::new(vec!["file1"], vec![])).unwrap()
        );

        assert_eq!(
            CommandLineInputs::FileNames(vec!["file1".to_string(), "file2".to_string()]),
            parse_inputs(&MockOptions::new(vec!["file1", "file2"], vec![])).unwrap()
        );

        assert_eq!(
            CommandLineInputs::FileNames(vec![
                "-".to_string(),
                "file1".to_string(),
                "file2".to_string(),
            ]),
            parse_inputs(&MockOptions::new(vec!["-", "file1", "file2"], vec![])).unwrap()
        );
    }

    #[test]
    fn test_parse_inputs_with_offset() {
        // offset is found without filename, so stdin will be used.
        assert_eq!(
            CommandLineInputs::FileAndOffset(("-".to_string(), 8, None)),
            parse_inputs(&MockOptions::new(vec!["+10"], vec![])).unwrap()
        );

        // offset must start with "+" if no input is specified.
        assert_eq!(
            CommandLineInputs::FileNames(vec!["10".to_string()]),
            parse_inputs(&MockOptions::new(vec!["10"], vec![""])).unwrap()
        );

        // offset is not valid, so it is considered a filename.
        assert_eq!(
            CommandLineInputs::FileNames(vec!["+10a".to_string()]),
            parse_inputs(&MockOptions::new(vec!["+10a"], vec![""])).unwrap()
        );

        // if -j is included in the command line, there cannot be an offset.
        assert_eq!(
            CommandLineInputs::FileNames(vec!["+10".to_string()]),
            parse_inputs(&MockOptions::new(vec!["+10"], vec!["j"])).unwrap()
        );

        // if -v is included in the command line, there cannot be an offset.
        assert_eq!(
            CommandLineInputs::FileNames(vec!["+10".to_string()]),
            parse_inputs(&MockOptions::new(vec!["+10"], vec!["o", "v"])).unwrap()
        );

        assert_eq!(
            CommandLineInputs::FileAndOffset(("file1".to_string(), 8, None)),
            parse_inputs(&MockOptions::new(vec!["file1", "+10"], vec![])).unwrap()
        );

        // offset does not need to start with "+" if a filename is included.
        assert_eq!(
            CommandLineInputs::FileAndOffset(("file1".to_string(), 8, None)),
            parse_inputs(&MockOptions::new(vec!["file1", "10"], vec![])).unwrap()
        );

        assert_eq!(
            CommandLineInputs::FileNames(vec!["file1".to_string(), "+10a".to_string()]),
            parse_inputs(&MockOptions::new(vec!["file1", "+10a"], vec![""])).unwrap()
        );

        assert_eq!(
            CommandLineInputs::FileNames(vec!["file1".to_string(), "+10".to_string()]),
            parse_inputs(&MockOptions::new(vec!["file1", "+10"], vec!["j"])).unwrap()
        );

        // offset must be last on the command line
        assert_eq!(
            CommandLineInputs::FileNames(vec!["+10".to_string(), "file1".to_string()]),
            parse_inputs(&MockOptions::new(vec!["+10", "file1"], vec![""])).unwrap()
        );
    }

    #[test]
    fn test_parse_inputs_traditional() {
        // it should not return FileAndOffset to signal no offset was entered on the command line.
        assert_eq!(
            CommandLineInputs::FileNames(vec!["-".to_string()]),
            parse_inputs(&MockOptions::new(vec![], vec!["traditional"])).unwrap()
        );

        assert_eq!(
            CommandLineInputs::FileNames(vec!["file1".to_string()]),
            parse_inputs(&MockOptions::new(vec!["file1"], vec!["traditional"])).unwrap()
        );

        // offset does not need to start with a +
        assert_eq!(
            CommandLineInputs::FileAndOffset(("-".to_string(), 8, None)),
            parse_inputs(&MockOptions::new(vec!["10"], vec!["traditional"])).unwrap()
        );

        // valid offset and valid label
        assert_eq!(
            CommandLineInputs::FileAndOffset(("-".to_string(), 8, Some(8))),
            parse_inputs(&MockOptions::new(vec!["10", "10"], vec!["traditional"])).unwrap()
        );

        assert_eq!(
            CommandLineInputs::FileAndOffset(("file1".to_string(), 8, None)),
            parse_inputs(&MockOptions::new(vec!["file1", "10"], vec!["traditional"])).unwrap()
        );

        // only one file is allowed, it must be the first
        parse_inputs(&MockOptions::new(vec!["10", "file1"], vec!["traditional"])).unwrap_err();

        assert_eq!(
            CommandLineInputs::FileAndOffset(("file1".to_string(), 8, Some(8))),
            parse_inputs(&MockOptions::new(
                vec!["file1", "10", "10"],
                vec!["traditional"]
            ))
            .unwrap()
        );

        parse_inputs(&MockOptions::new(
            vec!["10", "file1", "10"],
            vec!["traditional"],
        ))
        .unwrap_err();

        parse_inputs(&MockOptions::new(
            vec!["10", "10", "file1"],
            vec!["traditional"],
        ))
        .unwrap_err();

        parse_inputs(&MockOptions::new(
            vec!["10", "10", "10", "10"],
            vec!["traditional"],
        ))
        .unwrap_err();
    }

    fn parse_offset_operand_str(s: &str) -> Result<usize, &'static str> {
        parse_offset_operand(&String::from(s))
    }

    #[test]
    fn test_parse_offset_operand_invalid() {
        parse_offset_operand_str("").unwrap_err();
        parse_offset_operand_str("a").unwrap_err();
        parse_offset_operand_str("+").unwrap_err();
        parse_offset_operand_str("+b").unwrap_err();
        parse_offset_operand_str("0x1.").unwrap_err();
        parse_offset_operand_str("0x1.b").unwrap_err();
        parse_offset_operand_str("-").unwrap_err();
        parse_offset_operand_str("-1").unwrap_err();
        parse_offset_operand_str("1e10").unwrap_err();
    }

    #[test]
    fn test_parse_offset_operand() {
        assert_eq!(8, parse_offset_operand_str("10").unwrap()); // default octal
        assert_eq!(0, parse_offset_operand_str("0").unwrap());
        assert_eq!(8, parse_offset_operand_str("+10").unwrap()); // optional leading '+'
        assert_eq!(16, parse_offset_operand_str("0x10").unwrap()); // hex
        assert_eq!(16, parse_offset_operand_str("0X10").unwrap()); // hex
        assert_eq!(16, parse_offset_operand_str("+0X10").unwrap()); // hex
        assert_eq!(10, parse_offset_operand_str("10.").unwrap()); // decimal
        assert_eq!(10, parse_offset_operand_str("+10.").unwrap()); // decimal
        assert_eq!(4096, parse_offset_operand_str("10b").unwrap()); // b suffix = *512
        assert_eq!(4096, parse_offset_operand_str("+10b").unwrap()); // b suffix = *512
        assert_eq!(5120, parse_offset_operand_str("10.b").unwrap()); // b suffix = *512
        assert_eq!(5120, parse_offset_operand_str("+10.b").unwrap()); // b suffix = *512
        assert_eq!(267, parse_offset_operand_str("0x10b").unwrap()); // hex
    }
}
<commit_msg>od: use clap options instead of custom mock options for unit tests<commit_after>use super::options;
use clap::ArgMatches;

/// Abstraction for getopts
pub trait CommandLineOpts {
    /// returns all command line parameters which do not belong to an option.
    fn inputs(&self) -> Vec<&str>;
    /// tests if any of the specified options is present.
    fn opts_present(&self, _: &[&str]) -> bool;
}

/// Implementation for `getopts`
impl<'a> CommandLineOpts for ArgMatches {
    fn inputs(&self) -> Vec<&str> {
        self.values_of(options::FILENAME)
            .map(|values| values.collect())
            .unwrap_or_default()
    }

    fn opts_present(&self, opts: &[&str]) -> bool {
        opts.iter().any(|opt| self.is_present(opt))
    }
}

/// Contains the Input filename(s) with an optional offset.
///
/// `FileNames` is used for one or more file inputs ("-" = stdin)
/// `FileAndOffset` is used for a single file input, with an offset
/// and an optional label. Offset and label are specified in bytes.
/// `FileAndOffset` will be only used if an offset is specified,
/// but it might be 0.
#[derive(PartialEq, Debug)]
pub enum CommandLineInputs {
    FileNames(Vec<String>),
    FileAndOffset((String, usize, Option<usize>)),
}

/// Interprets the command line inputs of od.
///
/// Returns either an unspecified number of filenames.
/// Or it will return a single filename, with an offset and optional label.
/// Offset and label are specified in bytes.
/// '-' is used as filename if stdin is meant. This is also returned if
/// there is no input, as stdin is the default input.
pub fn parse_inputs(matches: &dyn CommandLineOpts) -> Result<CommandLineInputs, String> {
    let mut input_strings = matches.inputs();

    if matches.opts_present(&["traditional"]) {
        return parse_inputs_traditional(input_strings);
    }

    // test if command line contains: [file] <offset>
    // fall-through if no (valid) offset is found
    if input_strings.len() == 1 || input_strings.len() == 2 {
        // if any of the options -A, -j, -N, -t, -v or -w are present there is no offset
        if !matches.opts_present(&[
            options::ADDRESS_RADIX,
            options::READ_BYTES,
            options::SKIP_BYTES,
            options::FORMAT,
            options::OUTPUT_DUPLICATES,
            options::WIDTH,
        ]) {
            // test if the last input can be parsed as an offset.
            let offset = parse_offset_operand(input_strings[input_strings.len() - 1]);
            if let Ok(n) = offset {
                // if there is just 1 input (stdin), an offset must start with '+'
                if input_strings.len() == 1 && input_strings[0].starts_with('+') {
                    return Ok(CommandLineInputs::FileAndOffset(("-".to_string(), n, None)));
                }
                if input_strings.len() == 2 {
                    return Ok(CommandLineInputs::FileAndOffset((
                        input_strings[0].to_string(),
                        n,
                        None,
                    )));
                }
            }
        }
    }

    if input_strings.is_empty() {
        input_strings.push("-");
    }
    Ok(CommandLineInputs::FileNames(
        input_strings.iter().map(|&s| s.to_string()).collect(),
    ))
}

/// interprets inputs when --traditional is on the command line
///
/// normally returns CommandLineInputs::FileAndOffset, but if no offset is found,
/// it returns CommandLineInputs::FileNames (also to differentiate from the offset == 0)
pub fn parse_inputs_traditional(input_strings: Vec<&str>) -> Result<CommandLineInputs, String> {
    match input_strings.len() {
        0 => Ok(CommandLineInputs::FileNames(vec!["-".to_string()])),
        1 => {
            let offset0 = parse_offset_operand(input_strings[0]);
            Ok(match offset0 {
                Ok(n) => CommandLineInputs::FileAndOffset(("-".to_string(), n, None)),
                _ => CommandLineInputs::FileNames(
                    input_strings.iter().map(|&s| s.to_string()).collect(),
                ),
            })
        }
        2 => {
            let offset0 = parse_offset_operand(input_strings[0]);
            let offset1 = parse_offset_operand(input_strings[1]);
            match (offset0, offset1) {
                (Ok(n), Ok(m)) => Ok(CommandLineInputs::FileAndOffset((
                    "-".to_string(),
                    n,
                    Some(m),
                ))),
                (_, Ok(m)) => Ok(CommandLineInputs::FileAndOffset((
                    input_strings[0].to_string(),
                    m,
                    None,
                ))),
                _ => Err(format!("invalid offset: {}", input_strings[1])),
            }
        }
        3 => {
            let offset = parse_offset_operand(input_strings[1]);
            let label = parse_offset_operand(input_strings[2]);
            match (offset, label) {
                (Ok(n), Ok(m)) => Ok(CommandLineInputs::FileAndOffset((
                    input_strings[0].to_string(),
                    n,
                    Some(m),
                ))),
                (Err(_), _) => Err(format!("invalid offset: {}", input_strings[1])),
                (_, Err(_)) => Err(format!("invalid label: {}", input_strings[2])),
            }
        }
        _ => Err(format!(
            "too many inputs after --traditional: {}",
            input_strings[3]
        )),
    }
}

/// parses format used by offset and label on the command line
pub fn parse_offset_operand(s: &str) -> Result<usize, &'static str> {
    let mut start = 0;
    let mut len = s.len();
    let mut radix = 8;
    let mut multiply = 1;

    if s.starts_with('+') {
        start += 1;
    }

    if s[start..len].starts_with("0x") || s[start..len].starts_with("0X") {
        start += 2;
        radix = 16;
    } else {
        if s[start..len].ends_with('b') {
            len -= 1;
            multiply = 512;
        }
        if s[start..len].ends_with('.') {
            len -= 1;
            radix = 10;
        }
    }
    match usize::from_str_radix(&s[start..len], radix) {
        Ok(i) => Ok(i * multiply),
        Err(_) => Err("parse failed"),
    }
}

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

    #[test]
    fn test_parse_inputs_normal() {
        assert_eq!(
            CommandLineInputs::FileNames(vec!["-".to_string()]),
            parse_inputs(&uu_app().get_matches_from(vec!["od"])).unwrap()
        );

        assert_eq!(
            CommandLineInputs::FileNames(vec!["-".to_string()]),
            parse_inputs(&uu_app().get_matches_from(vec!["od", "-"])).unwrap()
        );

        assert_eq!(
            CommandLineInputs::FileNames(vec!["file1".to_string()]),
            parse_inputs(&uu_app().get_matches_from(vec!["od", "file1"])).unwrap()
        );

        assert_eq!(
            CommandLineInputs::FileNames(vec!["file1".to_string(), "file2".to_string()]),
            parse_inputs(&uu_app().get_matches_from(vec!["od", "file1", "file2"])).unwrap()
        );

        assert_eq!(
            CommandLineInputs::FileNames(vec![
                "-".to_string(),
                "file1".to_string(),
                "file2".to_string(),
            ]),
            parse_inputs(&uu_app().get_matches_from(vec!["od", "-", "file1", "file2"])).unwrap()
        );
    }

    #[test]
    fn test_parse_inputs_with_offset() {
        // offset is found without filename, so stdin will be used.
        assert_eq!(
            CommandLineInputs::FileAndOffset(("-".to_string(), 8, None)),
            parse_inputs(&uu_app().get_matches_from(vec!["od", "+10"])).unwrap()
        );

        // offset must start with "+" if no input is specified.
        assert_eq!(
            CommandLineInputs::FileNames(vec!["10".to_string()]),
            parse_inputs(&uu_app().get_matches_from(vec!["od", "10"])).unwrap()
        );

        // offset is not valid, so it is considered a filename.
        assert_eq!(
            CommandLineInputs::FileNames(vec!["+10a".to_string()]),
            parse_inputs(&uu_app().get_matches_from(vec!["od", "+10a"])).unwrap()
        );

        // if -j is included in the command line, there cannot be an offset.
        assert_eq!(
            CommandLineInputs::FileNames(vec!["+10".to_string()]),
            parse_inputs(&uu_app().get_matches_from(vec!["od", "-j10", "+10"])).unwrap()
        );

        // if -v is included in the command line, there cannot be an offset.
        assert_eq!(
            CommandLineInputs::FileNames(vec!["+10".to_string()]),
            parse_inputs(&uu_app().get_matches_from(vec!["od", "-o", "-v", "+10"])).unwrap()
        );

        assert_eq!(
            CommandLineInputs::FileAndOffset(("file1".to_string(), 8, None)),
            parse_inputs(&uu_app().get_matches_from(vec!["od", "file1", "+10"])).unwrap()
        );

        // offset does not need to start with "+" if a filename is included.
        assert_eq!(
            CommandLineInputs::FileAndOffset(("file1".to_string(), 8, None)),
            parse_inputs(&uu_app().get_matches_from(vec!["od", "file1", "10"])).unwrap()
        );

        assert_eq!(
            CommandLineInputs::FileNames(vec!["file1".to_string(), "+10a".to_string()]),
            parse_inputs(&uu_app().get_matches_from(vec!["od", "file1", "+10a"])).unwrap()
        );

        assert_eq!(
            CommandLineInputs::FileNames(vec!["file1".to_string(), "+10".to_string()]),
            parse_inputs(&uu_app().get_matches_from(vec!["od", "-j10", "file1", "+10"])).unwrap()
        );

        // offset must be last on the command line
        assert_eq!(
            CommandLineInputs::FileNames(vec!["+10".to_string(), "file1".to_string()]),
            parse_inputs(&uu_app().get_matches_from(vec!["od", "+10", "file1"])).unwrap()
        );
    }

    #[test]
    fn test_parse_inputs_traditional() {
        // it should not return FileAndOffset to signal no offset was entered on the command line.
        assert_eq!(
            CommandLineInputs::FileNames(vec!["-".to_string()]),
            parse_inputs(&uu_app().get_matches_from(vec!["od", "--traditional"])).unwrap()
        );

        assert_eq!(
            CommandLineInputs::FileNames(vec!["file1".to_string()]),
            parse_inputs(&uu_app().get_matches_from(vec!["od", "--traditional", "file1"])).unwrap()
        );

        // offset does not need to start with a +
        assert_eq!(
            CommandLineInputs::FileAndOffset(("-".to_string(), 8, None)),
            parse_inputs(&uu_app().get_matches_from(vec!["od", "--traditional", "10"])).unwrap()
        );

        // valid offset and valid label
        assert_eq!(
            CommandLineInputs::FileAndOffset(("-".to_string(), 8, Some(8))),
            parse_inputs(&uu_app().get_matches_from(vec!["od", "--traditional", "10", "10"]))
                .unwrap()
        );

        assert_eq!(
            CommandLineInputs::FileAndOffset(("file1".to_string(), 8, None)),
            parse_inputs(&uu_app().get_matches_from(vec!["od", "--traditional", "file1", "10"]))
                .unwrap()
        );

        // only one file is allowed, it must be the first
        parse_inputs(&uu_app().get_matches_from(vec!["od", "--traditional", "10", "file1"]))
            .unwrap_err();

        assert_eq!(
            CommandLineInputs::FileAndOffset(("file1".to_string(), 8, Some(8))),
            parse_inputs(&uu_app().get_matches_from(vec![
                "od",
                "--traditional",
                "file1",
                "10",
                "10"
            ]))
            .unwrap()
        );

        parse_inputs(&uu_app().get_matches_from(vec!["od", "--traditional", "10", "file1", "10"]))
            .unwrap_err();

        parse_inputs(&uu_app().get_matches_from(vec!["od", "--traditional", "10", "10", "file1"]))
            .unwrap_err();

        parse_inputs(&uu_app().get_matches_from(vec![
            "od",
            "--traditional",
            "10",
            "10",
            "10",
            "10",
        ]))
        .unwrap_err();
    }

    fn parse_offset_operand_str(s: &str) -> Result<usize, &'static str> {
        parse_offset_operand(&String::from(s))
    }

    #[test]
    fn test_parse_offset_operand_invalid() {
        parse_offset_operand_str("").unwrap_err();
        parse_offset_operand_str("a").unwrap_err();
        parse_offset_operand_str("+").unwrap_err();
        parse_offset_operand_str("+b").unwrap_err();
        parse_offset_operand_str("0x1.").unwrap_err();
        parse_offset_operand_str("0x1.b").unwrap_err();
        parse_offset_operand_str("-").unwrap_err();
        parse_offset_operand_str("-1").unwrap_err();
        parse_offset_operand_str("1e10").unwrap_err();
    }

    #[test]
    fn test_parse_offset_operand() {
        assert_eq!(8, parse_offset_operand_str("10").unwrap()); // default octal
        assert_eq!(0, parse_offset_operand_str("0").unwrap());
        assert_eq!(8, parse_offset_operand_str("+10").unwrap()); // optional leading '+'
        assert_eq!(16, parse_offset_operand_str("0x10").unwrap()); // hex
        assert_eq!(16, parse_offset_operand_str("0X10").unwrap()); // hex
        assert_eq!(16, parse_offset_operand_str("+0X10").unwrap()); // hex
        assert_eq!(10, parse_offset_operand_str("10.").unwrap()); // decimal
        assert_eq!(10, parse_offset_operand_str("+10.").unwrap()); // decimal
        assert_eq!(4096, parse_offset_operand_str("10b").unwrap()); // b suffix = *512
        assert_eq!(4096, parse_offset_operand_str("+10b").unwrap()); // b suffix = *512
        assert_eq!(5120, parse_offset_operand_str("10.b").unwrap()); // b suffix = *512
        assert_eq!(5120, parse_offset_operand_str("+10.b").unwrap()); // b suffix = *512
        assert_eq!(267, parse_offset_operand_str("0x10b").unwrap()); // hex
    }
}
<|endoftext|>


<commit_before>use std::sync::Arc;
use std::fs::File;
use std::io::BufReader;
use std::collections::HashMap;

use ply_rs::ply;
use ply_rs::parser;

use {Normal3f, Point2f, Point3f};
use paramset::ParamSet;
use shapes::Shape;
use shapes::mesh::create_triangle_mesh;
use texture::{ConstantTexture, Texture};
use transform::Transform;

pub fn create(
    o2w: &Transform,
    _w2o: &Transform,
    reverse_orientation: bool,
    params: &ParamSet,
    float_textures: &HashMap<String, Arc<Texture<f32>>>,
) -> Vec<Arc<Shape>> {
    let filename = params.find_one_filename("filename", "".into());
    let f = File::open(&filename).unwrap();
    let mut f = BufReader::new(f);

    // create a parser
    let vertex_parser = parser::Parser::<Vertex>::new();
    let face_parser = parser::Parser::<Face>::new();

    // use the parser: read the entire file
    let header = vertex_parser.read_header(&mut f).unwrap();
    let mut vertex_count = 0;
    let mut face_count = 0;
    let mut has_normals = false;
    let mut has_texture = false;
    for (key, elem) in &header.elements {
        if key == "vertex" {
            vertex_count = elem.count;
            if !elem.properties.contains_key("x") || !elem.properties.contains_key("y")
                || !elem.properties.contains_key("z")
            {
                error!(
                    "PLY file \"{}\": Vertex coordinate property not found",
                    filename
                );
                return Vec::new();
            }
            if elem.properties.contains_key("nx") && elem.properties.contains_key("ny")
                && elem.properties.contains_key("nz")
            {
                has_normals = true;
            }
            if (elem.properties.contains_key("u") && elem.properties.contains_key("v"))
                || (elem.properties.contains_key("s") && elem.properties.contains_key("t"))
                || (elem.properties.contains_key("texture_u")
                    && elem.properties.contains_key("texture_v"))
                || (elem.properties.contains_key("texture_s")
                    && elem.properties.contains_key("texture_t"))
            {
                has_texture = true;
            }
        } else if key == "face" {
            face_count = elem.count;
        }
    }

    if vertex_count == 0 || face_count == 0 {
        error!(
            "PLY file \"{}\" is invalid! No face/vertex elements found!",
            filename
        );
        return Vec::new();
    } else {
        info!(
            "Loading PLY file with {} vertices and {} faces",
            vertex_count, face_count
        );
    }

    let mut vertices = Vec::new();
    let mut faces = Vec::new();
    for (_key, elem) in &header.elements {
        match elem.name.as_ref() {
            "vertex" => {
                vertices = vertex_parser
                    .read_payload_for_element(&mut f, elem, &header)
                    .unwrap();
                // TODO normals + texture
            }
            "face" => {
                faces = face_parser
                    .read_payload_for_element(&mut f, elem, &header)
                    .unwrap();
            }
            _ => panic!("Unexpected element \"{}\"", elem.name),
        }
    }

    let vertex_indices: Vec<usize> = faces
        .into_iter()
        .flat_map(|f| {
            let length = f.vertex_indices.len();
            if length != 3 && length != 4 {
                warn!("plymesh: Ignoring face with {} vertices (only triangles and quads are supported!", f.vertex_indices.len());
                Vec::new()
            } else {
                let mut vec = Vec::new();
                vec.push(f.vertex_indices[0] as usize);
                vec.push(f.vertex_indices[1] as usize);
                vec.push(f.vertex_indices[2] as usize);
                if length == 4 {
                    // If it's a quad, split it into 2 triangles
                    vec.push(f.vertex_indices[3] as usize);
                    vec.push(f.vertex_indices[0] as usize);
                    vec.push(f.vertex_indices[2] as usize);
                }

                vec
            }
        })
        .collect();

    let mut p = Vec::with_capacity(vertex_count);
    let mut n = Vec::with_capacity(vertex_count);
    let mut uv = Vec::with_capacity(vertex_count);

    for v in vertices {
        p.push(v.p);
        if has_normals {
            n.push(v.n);
        }
        if has_texture {
            uv.push(v.uv);
        }
    }

    let mut alpha_mask = None;
    let alpha_tex_name = params.find_texture("alpha", String::from(""));
    if &alpha_tex_name != "" {
        if let Some(tex) = float_textures.get(&alpha_tex_name) {
            alpha_mask = Some(tex.clone());
        } else {
            error!("");
        }
    } else if params.find_one_float("alpha", 1.0) == 0.0 {
        alpha_mask = Some(Arc::new(ConstantTexture::new(0.0)));
    }

    let mut shadow_alpha_mask = None;
    let shadow_alpha_tex_name = params.find_texture("shadowalpha", String::from(""));
    if &shadow_alpha_tex_name != "" {
        if let Some(tex) = float_textures.get(&shadow_alpha_tex_name) {
            shadow_alpha_mask = Some(tex.clone());
        } else {
            error!("");
        }
    } else if params.find_one_float("shadowalpha", 1.0) == 0.0 {
        shadow_alpha_mask = Some(Arc::new(ConstantTexture::new(0.0)));
    }

    create_triangle_mesh(
        o2w,
        reverse_orientation,
        &vertex_indices,
        &p,
        None,
        if has_normals { Some(&n) } else { None },
        if has_texture { Some(&uv) } else { None },
        alpha_mask,
        shadow_alpha_mask,
    )
}

struct Vertex {
    p: Point3f,
    n: Normal3f,
    uv: Point2f,
}

impl ply::PropertyAccess for Vertex {
    fn new() -> Vertex {
        Vertex {
            p: Point3f::default(),
            n: Normal3f::default(),
            uv: Point2f::default(),
        }
    }

    fn set_property(&mut self, key: String, prop: ply::Property) {
        match (key.as_ref(), prop) {
            // point
            ("x", ply::Property::Float(v)) => self.p.x = v,
            ("y", ply::Property::Float(v)) => self.p.y = v,
            ("z", ply::Property::Float(v)) => self.p.z = v,
            // normal
            ("nx", ply::Property::Float(v)) => self.n.x = v,
            ("ny", ply::Property::Float(v)) => self.n.y = v,
            ("nz", ply::Property::Float(v)) => self.n.z = v,
            // texture coordinates
            ("u", ply::Property::Float(v))
            | ("texture_u", ply::Property::Float(v))
            | ("s", ply::Property::Float(v))
            | ("texture_s", ply::Property::Float(v)) => self.uv.x = v,
            ("v", ply::Property::Float(v))
            | ("t", ply::Property::Float(v))
            | ("texture_v", ply::Property::Float(v))
            | ("texture_t", ply::Property::Float(v)) => self.uv.y = v,
            _ => debug!("Unknown property \"{}\" found for vertex element", key),
        }
    }
}

struct Face {
    vertex_indices: Vec<i32>,
}

impl ply::PropertyAccess for Face {
    fn new() -> Face {
        Face {
            vertex_indices: Vec::new(),
        }
    }

    fn set_property(&mut self, key: String, prop: ply::Property) {
        match (key.as_ref(), prop) {
            ("vertex_indices", ply::Property::ListInt(v)) => self.vertex_indices = v,
            (_k, p) => debug!(
                "Face: Invalid combination key/value for key {} / prop {:?}",
                key, p
            ),
        }
    }
}
<commit_msg>Vertex indices in PLY files can be Int or UInt...<commit_after>use std::sync::Arc;
use std::fs::File;
use std::io::BufReader;
use std::collections::HashMap;

use ply_rs::ply;
use ply_rs::parser;

use {Normal3f, Point2f, Point3f};
use paramset::ParamSet;
use shapes::Shape;
use shapes::mesh::create_triangle_mesh;
use texture::{ConstantTexture, Texture};
use transform::Transform;

pub fn create(
    o2w: &Transform,
    _w2o: &Transform,
    reverse_orientation: bool,
    params: &ParamSet,
    float_textures: &HashMap<String, Arc<Texture<f32>>>,
) -> Vec<Arc<Shape>> {
    let filename = params.find_one_filename("filename", "".into());
    let f = File::open(&filename).unwrap();
    let mut f = BufReader::new(f);

    // create a parser
    let vertex_parser = parser::Parser::<Vertex>::new();
    let face_parser = parser::Parser::<Face>::new();

    // use the parser: read the entire file
    let header = vertex_parser.read_header(&mut f).unwrap();
    let mut vertex_count = 0;
    let mut face_count = 0;
    let mut has_normals = false;
    let mut has_texture = false;
    for (key, elem) in &header.elements {
        if key == "vertex" {
            vertex_count = elem.count;
            if !elem.properties.contains_key("x") || !elem.properties.contains_key("y")
                || !elem.properties.contains_key("z")
            {
                error!(
                    "PLY file \"{}\": Vertex coordinate property not found",
                    filename
                );
                return Vec::new();
            }
            if elem.properties.contains_key("nx") && elem.properties.contains_key("ny")
                && elem.properties.contains_key("nz")
            {
                has_normals = true;
            }
            if (elem.properties.contains_key("u") && elem.properties.contains_key("v"))
                || (elem.properties.contains_key("s") && elem.properties.contains_key("t"))
                || (elem.properties.contains_key("texture_u")
                    && elem.properties.contains_key("texture_v"))
                || (elem.properties.contains_key("texture_s")
                    && elem.properties.contains_key("texture_t"))
            {
                has_texture = true;
            }
        } else if key == "face" {
            face_count = elem.count;
        }
    }

    if vertex_count == 0 || face_count == 0 {
        error!(
            "PLY file \"{}\" is invalid! No face/vertex elements found!",
            filename
        );
        return Vec::new();
    } else {
        info!(
            "Loading PLY file with {} vertices and {} faces",
            vertex_count, face_count
        );
    }

    let mut vertices = Vec::new();
    let mut faces = Vec::new();
    for (_key, elem) in &header.elements {
        match elem.name.as_ref() {
            "vertex" => {
                vertices = vertex_parser
                    .read_payload_for_element(&mut f, elem, &header)
                    .unwrap();
                // TODO normals + texture
            }
            "face" => {
                faces = face_parser
                    .read_payload_for_element(&mut f, elem, &header)
                    .unwrap();
            }
            _ => panic!("Unexpected element \"{}\"", elem.name),
        }
    }

    let vertex_indices: Vec<usize> = faces
        .into_iter()
        .flat_map(|f| {
            let length = f.vertex_indices.len();
            if length != 3 && length != 4 {
                warn!("plymesh: Ignoring face with {} vertices (only triangles and quads are supported!", f.vertex_indices.len());
                Vec::new()
            } else {
                let mut vec = Vec::new();
                vec.push(f.vertex_indices[0] as usize);
                vec.push(f.vertex_indices[1] as usize);
                vec.push(f.vertex_indices[2] as usize);
                if length == 4 {
                    // If it's a quad, split it into 2 triangles
                    vec.push(f.vertex_indices[3] as usize);
                    vec.push(f.vertex_indices[0] as usize);
                    vec.push(f.vertex_indices[2] as usize);
                }

                vec
            }
        })
        .collect();

    let mut p = Vec::with_capacity(vertex_count);
    let mut n = Vec::with_capacity(vertex_count);
    let mut uv = Vec::with_capacity(vertex_count);

    for v in vertices {
        p.push(v.p);
        if has_normals {
            n.push(v.n);
        }
        if has_texture {
            uv.push(v.uv);
        }
    }

    let mut alpha_mask = None;
    let alpha_tex_name = params.find_texture("alpha", String::from(""));
    if &alpha_tex_name != "" {
        if let Some(tex) = float_textures.get(&alpha_tex_name) {
            alpha_mask = Some(tex.clone());
        } else {
            error!("");
        }
    } else if params.find_one_float("alpha", 1.0) == 0.0 {
        alpha_mask = Some(Arc::new(ConstantTexture::new(0.0)));
    }

    let mut shadow_alpha_mask = None;
    let shadow_alpha_tex_name = params.find_texture("shadowalpha", String::from(""));
    if &shadow_alpha_tex_name != "" {
        if let Some(tex) = float_textures.get(&shadow_alpha_tex_name) {
            shadow_alpha_mask = Some(tex.clone());
        } else {
            error!("");
        }
    } else if params.find_one_float("shadowalpha", 1.0) == 0.0 {
        shadow_alpha_mask = Some(Arc::new(ConstantTexture::new(0.0)));
    }

    create_triangle_mesh(
        o2w,
        reverse_orientation,
        &vertex_indices,
        &p,
        None,
        if has_normals { Some(&n) } else { None },
        if has_texture { Some(&uv) } else { None },
        alpha_mask,
        shadow_alpha_mask,
    )
}

struct Vertex {
    p: Point3f,
    n: Normal3f,
    uv: Point2f,
}

impl ply::PropertyAccess for Vertex {
    fn new() -> Vertex {
        Vertex {
            p: Point3f::default(),
            n: Normal3f::default(),
            uv: Point2f::default(),
        }
    }

    fn set_property(&mut self, key: String, prop: ply::Property) {
        match (key.as_ref(), prop) {
            // point
            ("x", ply::Property::Float(v)) => self.p.x = v,
            ("y", ply::Property::Float(v)) => self.p.y = v,
            ("z", ply::Property::Float(v)) => self.p.z = v,
            // normal
            ("nx", ply::Property::Float(v)) => self.n.x = v,
            ("ny", ply::Property::Float(v)) => self.n.y = v,
            ("nz", ply::Property::Float(v)) => self.n.z = v,
            // texture coordinates
            ("u", ply::Property::Float(v))
            | ("texture_u", ply::Property::Float(v))
            | ("s", ply::Property::Float(v))
            | ("texture_s", ply::Property::Float(v)) => self.uv.x = v,
            ("v", ply::Property::Float(v))
            | ("t", ply::Property::Float(v))
            | ("texture_v", ply::Property::Float(v))
            | ("texture_t", ply::Property::Float(v)) => self.uv.y = v,
            _ => debug!("Unknown property \"{}\" found for vertex element", key),
        }
    }
}

struct Face {
    vertex_indices: Vec<i32>,
}

impl ply::PropertyAccess for Face {
    fn new() -> Face {
        Face {
            vertex_indices: Vec::new(),
        }
    }

    fn set_property(&mut self, key: String, prop: ply::Property) {
        match (key.as_ref(), prop) {
            ("vertex_indices", ply::Property::ListInt(v)) => self.vertex_indices = v,
            ("vertex_indices", ply::Property::ListUInt(v)) => {
                self.vertex_indices = v.iter().map(|u| *u as i32).collect()
            }
            (_k, p) => debug!(
                "Face: Invalid combination key/value for key {} / prop {:?}",
                key, p
            ),
        }
    }
}
<|endoftext|>


<commit_before>// check-pass

use std::ptr::NonNull;

const fn test() {
    let _x = NonNull::<[i32; 0]>::dangling() as NonNull<[i32]>;
}

// Regression test for #75118.
pub const fn dangling_slice<T>() -> NonNull<[T]> {
    NonNull::<[T; 0]>::dangling()
}

fn main() {}
<commit_msg>const_fn_unsize: check that the cast behaves correctly<commit_after>// run-pass
#![feature(slice_ptr_len)]

use std::ptr::NonNull;

#[allow(unused)]
const fn test() {
    let _x = NonNull::<[i32; 0]>::dangling() as NonNull<[i32]>;
}

// Regression test for #75118.
pub const fn dangling_slice<T>() -> NonNull<[T]> {
    NonNull::<[T; 1]>::dangling()
}

const C: NonNull<[i32]> = dangling_slice();

fn main() {
    assert_eq!(C.as_ptr(), NonNull::<[i32; 1]>::dangling().as_ptr() as *mut _);
    assert_eq!(C.as_ptr().len(), 1);
}
<|endoftext|>


<commit_before>fn main () {

}
<commit_msg>deleted unneeded files<commit_after><|endoftext|>


<commit_before>use cripes::symbol::{Pool,Type,Unpacked,Inline,PackFormat};
use std::string::String;

macro_rules! inline_test_strings {
    () => ((0u8..16u8).map(|i| (0u8..i).map(|j| (j  + 'a' as u8) as char).collect::<String>()));
}

#[test]
fn inline_as_slice() {
    for s in inline_test_strings!() {
        let sym = Inline::new(s.as_slice());
        assert_eq!(sym.as_slice(), s.as_slice());
    }
}

#[test]
fn inline_packed_as_slice() {
    for s in inline_test_strings!() {
        let inl = Inline::new(s.as_slice());
        assert_eq!(inl.as_slice(), s.as_slice());
        assert_eq!(inl.pack().as_slice(), s.as_slice());
    }
}

#[test]
fn inline_pack_unpack() {
    let foo = Inline::new("foo");
    assert_eq!(foo.as_slice(), "foo");

    let bar = foo.pack();

    println!("{:?}", foo);
    println!("{:?}", bar);

    panic_unless!(foo == foo, "each symbol::Inline instance should be equal to itself");
    panic_unless!(bar == bar, "each symbol::Symbol instance should be equal to itself");

    panic_unless!(Unpacked::Inline(foo) == bar.unpack(),
                  "pack-unpack cycle on a symbol::Inline should yield the same symbol");
}

#[test]
fn pooled_pack_unpack() {
    let mut pool = Pool::new();
    let a_str = "it was a very nice day";
    let b_str = "and everyone was happy";

    let a = pool.symbol(a_str);
    let b = pool.symbol(b_str);
    assert_eq!(a.type_of(), Type::POOLED);
    assert_eq!(b.type_of(), Type::POOLED);

   println!("{:?} => {:?}", a, a.as_slice());
    println!("{:?}", a.unpack());

    assert_eq!(a, a);
    assert_ne!(a, b);
    assert_eq!(a.as_slice(), a_str);
    assert_eq!(b.as_slice(), b_str);
}
<commit_msg>test/test_symbol: switch `assert_ne!` -> `panic_unless!`<commit_after>use cripes::symbol::{Pool,Type,Unpacked,Inline,PackFormat};
use std::string::String;

macro_rules! inline_test_strings {
    () => ((0u8..16u8).map(|i| (0u8..i).map(|j| (j  + 'a' as u8) as char).collect::<String>()));
}

#[test]
fn inline_as_slice() {
    for s in inline_test_strings!() {
        let sym = Inline::new(s.as_slice());
        assert_eq!(sym.as_slice(), s.as_slice());
    }
}

#[test]
fn inline_packed_as_slice() {
    for s in inline_test_strings!() {
        let inl = Inline::new(s.as_slice());
        assert_eq!(inl.as_slice(), s.as_slice());
        assert_eq!(inl.pack().as_slice(), s.as_slice());
    }
}

#[test]
fn inline_pack_unpack() {
    let foo = Inline::new("foo");
    assert_eq!(foo.as_slice(), "foo");

    let bar = foo.pack();

    println!("{:?}", foo);
    println!("{:?}", bar);

    panic_unless!(foo == foo, "each symbol::Inline instance should be equal to itself");
    panic_unless!(bar == bar, "each symbol::Symbol instance should be equal to itself");

    panic_unless!(Unpacked::Inline(foo) == bar.unpack(),
                  "pack-unpack cycle on a symbol::Inline should yield the same symbol");
}

#[test]
fn pooled_pack_unpack() {
    let mut pool = Pool::new();
    let a_str = "it was a very nice day";
    let b_str = "and everyone was happy";

    let a = pool.symbol(a_str);
    let b = pool.symbol(b_str);
    assert_eq!(a.type_of(), Type::POOLED);
    assert_eq!(b.type_of(), Type::POOLED);

   println!("{:?} => {:?}", a, a.as_slice());
    println!("{:?}", a.unpack());

    assert_eq!(a, a);
    panic_unless!(a != b && b != a, "`a` and `b` are distinct symbols and should not be equal");
    assert_eq!(a.as_slice(), a_str);
    assert_eq!(b.as_slice(), b_str);
}
<|endoftext|>


<commit_before>use ::std::collections::hash_map::DefaultHasher;
use ::std::hash::{Hash, Hasher};
use ::inflector::Inflector;
use ::quote::Tokens;
use ::syn::Ident;
use ::yaml_rust::Yaml;

use infer::Error;

type TypeSupportResult = Result<
    (Tokens, Option<Tokens>),
    Box<Error + Send + Sync>
>;

pub(super) fn infer_v3(schema: &Yaml) -> TypeSupportResult {
    if let Some(schema_ref) = schema["$ref"].as_str() {
        let ref_name = schema_ref.rsplit('/')
            .next().expect("Malformed $ref")
            .to_class_case();
        let ident = Ident::new(ref_name);

        Ok((quote!{ schema_ref::#ident }, None))
    } else {
        match schema["type"].as_str() {
            None => Err(From::from("Parameter schema type must be a string.")),

            Some("array") => {
                let (item_type, supp_types) = infer_v3(&schema["items"])?;
                Ok((quote!{ Vec<#item_type> }, supp_types))
            },

            Some("object") => {
                let mut fields: Vec<Tokens> = Vec::new();
                let mut additional_types: Vec<Tokens> = Vec::new();
                let required: Vec<&str> = match schema["required"].as_vec() {
                    Some(v) => v.iter()
                        .map(|e| e.as_str()
                            .expect("Required field names must be strings.")
                        )
                        .collect(),
                    None => Vec::new(),
                };

                for (name, schema) in schema["properties"].as_hash()
                    .expect("Properties must be a map.")
                {
                    let name = name.as_str()
                        .expect("Property keys must be strings.");

                    let rusty_ident = Ident::new(name.to_snake_case());
                    let (field_type, supp_types) = infer_v3(&schema)?;

                    if let Some(supp_types) = supp_types {
                        additional_types.push(supp_types);
                    }

                    if required.contains(&name) {
                        fields.push(quote!{
                            #[serde(rename=#name)]
                            #rusty_ident: #field_type
                        });
                    } else {
                        fields.push(quote!{
                            #[serde(rename=#name)]
                            #rusty_ident: Option<#field_type>
                        });
                    }
                }

                let mut hasher = DefaultHasher::new();
                let field_strs: Vec<String> = fields.iter()
                    .map(|f| f.to_string())
                    .collect();
                field_strs.hash(&mut hasher);
                let ident = Ident::new(format!("Type{}", hasher.finish()));

                Ok((
                    quote!(#ident),
                    Some(quote!{
                        #(#additional_types)*

                        #[derive(Clone, Debug, Deserialize)]
                        struct #ident {
                            #(#fields),*
                        }
                    })
                ))
            },

            Some("integer") => {
                match schema["format"].as_str() {
                    None
                        | Some("int64") => Ok((quote!{i64}, None)),
                    Some("int32") => Ok((quote!{i32}, None)),
                    Some(_) => Err(From::from("Invalid format for `integer` type.")),
                }
            },

            Some("number") => {
                match schema["format"].as_str() {
                    None
                        | Some("double") => Ok((quote!{f64}, None)),
                    Some("float") => Ok((quote!{f32}, None)),
                    Some(_) => Err(From::from("Invalid format for `number` type.")),
                }
            },

            Some("string") => {
                match schema["format"].as_str() {
                    None => Ok((quote!{String}, None)),
                    Some("byte") => Ok((quote!{::tapioca::Base64}, None)),
                    Some("binary") => Ok((quote!{&'static [u8]}, None)),
                    Some("date") => Ok((quote!{::tapioca::Date}, None)),
                    Some("date-time") => Ok((quote!{::tapioca::DateTime}, None)),
                    Some("password") => Ok((quote!{String}, None)),
                    Some(_) => Ok((quote!{String}, None)),
                }
            },

            Some("boolean") => {
                match schema["format"].as_str() {
                    None => Ok((quote!{bool}, None)),
                    Some(_) => Err(From::from("Unexpected format for `boolean` type.")),
                }
            },

            Some(ptype) => Err(From::from(format!("Parameter type `{}` invalid", ptype))),
        }
    }
}
<commit_msg>Fix response containing private type<commit_after>use ::std::collections::hash_map::DefaultHasher;
use ::std::hash::{Hash, Hasher};
use ::inflector::Inflector;
use ::quote::Tokens;
use ::syn::Ident;
use ::yaml_rust::Yaml;

use infer::Error;

type TypeSupportResult = Result<
    (Tokens, Option<Tokens>),
    Box<Error + Send + Sync>
>;

pub(super) fn infer_v3(schema: &Yaml) -> TypeSupportResult {
    if let Some(schema_ref) = schema["$ref"].as_str() {
        let ref_name = schema_ref.rsplit('/')
            .next().expect("Malformed $ref")
            .to_class_case();
        let ident = Ident::new(ref_name);

        Ok((quote!{ schema_ref::#ident }, None))
    } else {
        match schema["type"].as_str() {
            None => Err(From::from("Parameter schema type must be a string.")),

            Some("array") => {
                let (item_type, supp_types) = infer_v3(&schema["items"])?;
                Ok((quote!{ Vec<#item_type> }, supp_types))
            },

            Some("object") => {
                let mut fields: Vec<Tokens> = Vec::new();
                let mut additional_types: Vec<Tokens> = Vec::new();
                let required: Vec<&str> = match schema["required"].as_vec() {
                    Some(v) => v.iter()
                        .map(|e| e.as_str()
                            .expect("Required field names must be strings.")
                        )
                        .collect(),
                    None => Vec::new(),
                };

                for (name, schema) in schema["properties"].as_hash()
                    .expect("Properties must be a map.")
                {
                    let name = name.as_str()
                        .expect("Property keys must be strings.");

                    let rusty_ident = Ident::new(name.to_snake_case());
                    let (field_type, supp_types) = infer_v3(&schema)?;

                    if let Some(supp_types) = supp_types {
                        additional_types.push(supp_types);
                    }

                    if required.contains(&name) {
                        fields.push(quote!{
                            #[serde(rename=#name)]
                            pub #rusty_ident: #field_type
                        });
                    } else {
                        fields.push(quote!{
                            #[serde(rename=#name)]
                            pub #rusty_ident: Option<#field_type>
                        });
                    }
                }

                let mut hasher = DefaultHasher::new();
                let field_strs: Vec<String> = fields.iter()
                    .map(|f| f.to_string())
                    .collect();
                field_strs.hash(&mut hasher);
                let ident = Ident::new(format!("Type{}", hasher.finish()));

                Ok((
                    quote!(#ident),
                    Some(quote!{
                        #(#additional_types)*

                        #[derive(Clone, Debug, Deserialize)]
                        pub struct #ident {
                            #(#fields),*
                        }
                    })
                ))
            },

            Some("integer") => {
                match schema["format"].as_str() {
                    None
                        | Some("int64") => Ok((quote!{i64}, None)),
                    Some("int32") => Ok((quote!{i32}, None)),
                    Some(_) => Err(From::from("Invalid format for `integer` type.")),
                }
            },

            Some("number") => {
                match schema["format"].as_str() {
                    None
                        | Some("double") => Ok((quote!{f64}, None)),
                    Some("float") => Ok((quote!{f32}, None)),
                    Some(_) => Err(From::from("Invalid format for `number` type.")),
                }
            },

            Some("string") => {
                match schema["format"].as_str() {
                    None => Ok((quote!{String}, None)),
                    Some("byte") => Ok((quote!{::tapioca::Base64}, None)),
                    Some("binary") => Ok((quote!{&'static [u8]}, None)),
                    Some("date") => Ok((quote!{::tapioca::Date}, None)),
                    Some("date-time") => Ok((quote!{::tapioca::DateTime}, None)),
                    Some("password") => Ok((quote!{String}, None)),
                    Some(_) => Ok((quote!{String}, None)),
                }
            },

            Some("boolean") => {
                match schema["format"].as_str() {
                    None => Ok((quote!{bool}, None)),
                    Some(_) => Err(From::from("Unexpected format for `boolean` type.")),
                }
            },

            Some(ptype) => Err(From::from(format!("Parameter type `{}` invalid", ptype))),
        }
    }
}
<|endoftext|>


<commit_before>#![allow(dead_code)]

extern crate walkdir;

use std::env;
use std::path::Path;
use std::process::Command;
use std::u32;

use self::walkdir::DirEntry;

macro_rules! errorf {
    ($($tt:tt)*) => {
        {
            use ::std::io::Write;
            write!(::std::io::stderr(), $($tt)*).unwrap();
        }
    };
}

pub mod parse;
pub mod respan;

pub fn check_min_stack() {
    let min_stack_value = env::var("RUST_MIN_STACK")
        .expect("RUST_MIN_STACK env var should be set since some tests require it.");
    let min_stack_value: usize = min_stack_value.parse()
        .expect("RUST_MIN_STACK env var should be set since some tests require it.");
    assert!(min_stack_value >= 16000000);
}

/// Read the `ABORT_AFTER_FAILURE` environment variable, and parse it.
pub fn abort_after() -> u32 {
    if let Ok(s) = env::var("ABORT_AFTER_FAILURE") {
        if let Ok(n) = s.parse::<u32>() {
            return n;
        }
    }
    u32::MAX
}

pub fn base_dir_filter(entry: &DirEntry) -> bool {
    let path = entry.path();
    if path.is_dir() {
        return true; // otherwise walkdir does not visit the files
    }
    if path.extension().map(|e| e != "rs").unwrap_or(true) {
        return false;
    }
    let path_string = path.to_string_lossy();
    let path_string = if cfg!(windows) {
        path_string.replace('\\', "/").into()
    } else {
        path_string
    };
    // TODO assert that parsing fails on the parse-fail cases
    if path_string.starts_with("tests/rust/src/test/parse-fail") ||
       path_string.starts_with("tests/rust/src/test/compile-fail") {
        return false;
    }

    if path_string.starts_with("tests/rust/src/test/ui") {
        let stderr_path = path.with_extension("stderr");
        if stderr_path.exists() {
            // Expected to fail in some way
            return false;
        }
    }

    match path_string.as_ref() {
        // TODO better support for attributes
        "tests/rust/src/librustc_data_structures/blake2b.rs" |
        // TODO better support for attributes
        "tests/rust/src/test/incremental/hashes/enum_defs.rs" |
        // TODO better support for attributes
        "tests/rust/src/test/pretty/stmt_expr_attributes.rs" |
        // not actually a test case
        "tests/rust/src/test/run-pass/auxiliary/macro-include-items-expr.rs" |
        // TODO better support for attributes
        "tests/rust/src/test/run-pass/cfg_stmt_expr.rs" |
        // TODO weird glob import
        "tests/rust/src/test/run-pass/import-glob-crate.rs" |
        // TODO better support for attributes
        "tests/rust/src/test/run-pass/inner-attrs-on-impl.rs" |
        // TODO better support for attributes
        "tests/rust/src/test/run-pass/item-attributes.rs" => false,
        _ => true,
    }
}

pub fn clone_rust() {
    if Path::new("tests/rust").is_dir() {
        return
    }

    println!("cloning rust-lang/rust");
    let result = Command::new("git")
                    .arg("clone")
                    .arg("https://github.com/rust-lang/rust")
                    .arg("tests/rust")
                    .status()
                    .unwrap();
    println!("result: {}", result);
    assert!(result.success());

    println!("reset to known-good rev");
    let result = Command::new("git")
                    .arg("reset")
                    .arg("--hard")
                    .arg("ddc5d7bd4b9ea3e8a8ccf82cb443e950be311d61")
                    .current_dir("tests/rust")
                    .status()
                    .unwrap();
    println!("result: {}", result);
    assert!(result.success());
}
<commit_msg>Annotate TODO with what syntax needs to be parsed<commit_after>#![allow(dead_code)]

extern crate walkdir;

use std::env;
use std::path::Path;
use std::process::Command;
use std::u32;

use self::walkdir::DirEntry;

macro_rules! errorf {
    ($($tt:tt)*) => {
        {
            use ::std::io::Write;
            write!(::std::io::stderr(), $($tt)*).unwrap();
        }
    };
}

pub mod parse;
pub mod respan;

pub fn check_min_stack() {
    let min_stack_value = env::var("RUST_MIN_STACK")
        .expect("RUST_MIN_STACK env var should be set since some tests require it.");
    let min_stack_value: usize = min_stack_value.parse()
        .expect("RUST_MIN_STACK env var should be set since some tests require it.");
    assert!(min_stack_value >= 16000000);
}

/// Read the `ABORT_AFTER_FAILURE` environment variable, and parse it.
pub fn abort_after() -> u32 {
    if let Ok(s) = env::var("ABORT_AFTER_FAILURE") {
        if let Ok(n) = s.parse::<u32>() {
            return n;
        }
    }
    u32::MAX
}

pub fn base_dir_filter(entry: &DirEntry) -> bool {
    let path = entry.path();
    if path.is_dir() {
        return true; // otherwise walkdir does not visit the files
    }
    if path.extension().map(|e| e != "rs").unwrap_or(true) {
        return false;
    }
    let path_string = path.to_string_lossy();
    let path_string = if cfg!(windows) {
        path_string.replace('\\', "/").into()
    } else {
        path_string
    };
    // TODO assert that parsing fails on the parse-fail cases
    if path_string.starts_with("tests/rust/src/test/parse-fail") ||
       path_string.starts_with("tests/rust/src/test/compile-fail") {
        return false;
    }

    if path_string.starts_with("tests/rust/src/test/ui") {
        let stderr_path = path.with_extension("stderr");
        if stderr_path.exists() {
            // Expected to fail in some way
            return false;
        }
    }

    match path_string.as_ref() {
        // TODO better support for attributes
        //
        //      let a = A { #[a] b: c };
        "tests/rust/src/librustc_data_structures/blake2b.rs" |
        // TODO better support for attributes
        //
        //      enum A { B = #[a] 2 }
        "tests/rust/src/test/incremental/hashes/enum_defs.rs" |
        // TODO better support for attributes
        //
        //      { #![foo] }
        "tests/rust/src/test/pretty/stmt_expr_attributes.rs" |
        // TODO better support for attributes
        "tests/rust/src/test/run-pass/cfg_stmt_expr.rs" |
        // TODO weird glob import
        //
        //      use ::*;
        "tests/rust/src/test/run-pass/import-glob-crate.rs" |
        // TODO better support for attributes
        //
        //      impl Foo { #![a] }
        "tests/rust/src/test/run-pass/inner-attrs-on-impl.rs" |
        // TODO better support for attributes
        "tests/rust/src/test/run-pass/item-attributes.rs" |
        // not actually a test case
        "tests/rust/src/test/run-pass/auxiliary/macro-include-items-expr.rs" => false,
        _ => true,
    }
}

pub fn clone_rust() {
    if Path::new("tests/rust").is_dir() {
        return
    }

    println!("cloning rust-lang/rust");
    let result = Command::new("git")
                    .arg("clone")
                    .arg("https://github.com/rust-lang/rust")
                    .arg("tests/rust")
                    .status()
                    .unwrap();
    println!("result: {}", result);
    assert!(result.success());

    println!("reset to known-good rev");
    let result = Command::new("git")
                    .arg("reset")
                    .arg("--hard")
                    .arg("ddc5d7bd4b9ea3e8a8ccf82cb443e950be311d61")
                    .current_dir("tests/rust")
                    .status()
                    .unwrap();
    println!("result: {}", result);
    assert!(result.success());
}
<|endoftext|>


<commit_before>extern crate foxbox_taxonomy;
extern crate foxbox_thinkerbell;

extern crate transformable_channels;

use foxbox_thinkerbell::fake_env::*;
use foxbox_thinkerbell::parse::*;
use foxbox_thinkerbell::run::*;
use foxbox_thinkerbell::ast::*;

use foxbox_taxonomy::util::Id;
use foxbox_taxonomy::selector::*;
use foxbox_taxonomy::services::*;
use foxbox_taxonomy::values::{ OnOff, Range, Value };

use std::marker::PhantomData;
use std::thread;
use std::collections::{ HashMap, HashSet };

use transformable_channels::mpsc::*;

#[derive(Debug)]
enum Event {
    Env(FakeEnvEvent),
    Run(ExecutionEvent),
}

#[test]
fn test_compile() {
    let (tx, rx) : (_, Receiver<Event>)= channel();

    let tx_env = Box::new(tx.map(|event| Event::Env(event)));
    let tx_run = tx.map(|event| Event::Run(event));

    let env = FakeEnv::new(tx_env);
    let mut exec = Execution::<FakeEnv>::new();

    thread::spawn(move || {
        for msg in rx {
            println!("LOG: {:?}", msg)
        }
    });

    println!("* Attempting to parse an run an empty script will raise an error.");
    let script = Script::parse("{\"rules\": []}").unwrap();
    match exec.start(env, script, tx_run) {
        Err(Error::CompileError(CompileError::SourceError(SourceError::NoRule))) => {},
        other => panic!("Unexpected result {:?}", other)
    }

    println!("//FIXME: Attempting to parse a script with an empty condition will raise an error.");
    println!("//FIXME: Attempting to parse a script with an empty statement will raise an error.");
    println!("//FIXME: Attempting to parse a script with an empty source will raise an error.");
    println!("//FIXME: Attempting to parse a script with an empty destination will raise an error.");
    println!("//FIXME: Attempting to parse a script with a type error in a match will raise an error.");
    println!("//FIXME: Attempting to parse a script with a type error in a send will raise an error.");

    println!("");
}

#[test]
fn test_run() {
    let (tx, rx) : (_, Receiver<Event>) = channel();

    let tx_env = Box::new(tx.map(|event| Event::Env(event)));
    let tx_run = tx.map(|event| Event::Run(event));
    let (tx_done, rx_done) = channel();

    let env = FakeEnv::new(tx_env);
    let mut exec = Execution::<FakeEnv>::new();

    thread::spawn(move || {
        for msg in rx {
            if let Event::Env(FakeEnvEvent::Done) = msg {
                tx_done.send(()).unwrap();
            } else {
                println!("LOG: {:?}", msg);
            }
        }
    });

    let script = Script {
        rules: vec![
            Rule {
                conditions: vec![
                    Match {
                        source: vec![
                            GetterSelector::new()
                        ],
                        kind: ChannelKind::OnOff,
                        range: Range::Eq(Value::OnOff(OnOff::On)),
                        duration: None,
                        phantom: PhantomData
                    }
                ],
                execute: vec![
                    Statement {
                        destination: vec![
                            SetterSelector::new()
                        ],
                        value: Value::OnOff(OnOff::Off),
                        kind: ChannelKind::OnOff,
                        phantom: PhantomData,
                    }
                ],
                phantom: PhantomData
            }
        ],
        phantom: PhantomData,
    };

    let adapter_id_1 = Id::<AdapterId>::new("Adapter 1");
    let service_id_1 = Id::<ServiceId>::new("Service 1");
    let getter_id_1 = Id::<Getter>::new("Getter 1");
    let setter_id_1 = Id::<Setter>::new("Setter 1");

    println!("* We can start executing a trivial rule.");
    exec.start(env.clone(), script, tx_run).unwrap();

    println!("* Changing the structure of the network doesn't break the rule.");
    env.execute(Instruction::AddAdapters(vec![adapter_id_1.to_string()]));
    rx_done.recv().unwrap();

    env.execute(Instruction::AddServices(vec![
        Service {
            id: service_id_1.clone(),
            adapter: adapter_id_1.clone(),
            getters: HashMap::new(),
            setters: HashMap::new(),
            tags: HashSet::new(),
        }
    ]));
    rx_done.recv().unwrap();

    env.execute(Instruction::AddGetters(vec![
        Channel {
            id: getter_id_1.clone(),
            adapter: adapter_id_1.clone(),
            service: service_id_1.clone(),
            tags: HashSet::new(),
            last_seen: None,
            mechanism: Getter {
                watch: true,
                poll: None,
                updated: None,
                trigger: None,
                kind: ChannelKind::OnOff,
            }
        }
    ]));
    rx_done.recv().unwrap();

    env.execute(Instruction::AddSetters(vec![
        Channel {
            id: setter_id_1.clone(),
            adapter: adapter_id_1.clone(),
            service: service_id_1.clone(),
            last_seen: None,
            tags: HashSet::new(),
            mechanism: Setter {
                push: None,
                updated: None,
                kind: ChannelKind::OnOff,
            }
        }
    ]));
    rx_done.recv().unwrap();

    println!("FIXME: Injecting the expected value triggers the send.");

    println!("");
}<commit_msg>test_rules: First meaningful test passes<commit_after>extern crate foxbox_taxonomy;
extern crate foxbox_thinkerbell;

extern crate transformable_channels;

use foxbox_thinkerbell::fake_env::*;
use foxbox_thinkerbell::parse::*;
use foxbox_thinkerbell::run::*;
use foxbox_thinkerbell::ast::*;

use foxbox_taxonomy::util::Id;
use foxbox_taxonomy::selector::*;
use foxbox_taxonomy::services::*;
use foxbox_taxonomy::values::{ OnOff, Range, Value };

use std::marker::PhantomData;
use std::thread;
use std::collections::{ HashMap, HashSet };

use transformable_channels::mpsc::*;

#[derive(Debug)]
enum Event {
    Env(FakeEnvEvent),
    Run(ExecutionEvent),
}

#[test]
fn test_compile() {
    let (tx, rx) : (_, Receiver<Event>)= channel();

    let tx_env = Box::new(tx.map(|event| Event::Env(event)));
    let tx_run = tx.map(|event| Event::Run(event));

    let env = FakeEnv::new(tx_env);
    let mut exec = Execution::<FakeEnv>::new();

    thread::spawn(move || {
        for msg in rx {
            println!("LOG: {:?}", msg)
        }
    });

    println!("* Attempting to parse an run an empty script will raise an error.");
    let script = Script::parse("{\"rules\": []}").unwrap();
    match exec.start(env, script, tx_run) {
        Err(Error::CompileError(CompileError::SourceError(SourceError::NoRule))) => {},
        other => panic!("Unexpected result {:?}", other)
    }

    println!("//FIXME: Attempting to parse a script with an empty condition will raise an error.");
    println!("//FIXME: Attempting to parse a script with an empty statement will raise an error.");
    println!("//FIXME: Attempting to parse a script with an empty source will raise an error.");
    println!("//FIXME: Attempting to parse a script with an empty destination will raise an error.");
    println!("//FIXME: Attempting to parse a script with a type error in a match will raise an error.");
    println!("//FIXME: Attempting to parse a script with a type error in a send will raise an error.");

    println!("");
}

fn race<T>(receivers: &[Receiver<T>]) -> T {
    use std::time::Duration;
    loop {
        for rx in receivers {
            if let Ok(result) = rx.try_recv() {
                return result
            }
        }
        thread::sleep(std::time::Duration::new(1, 0));
    }
}

#[test]
fn test_run() {
    let (tx, rx) : (_, Receiver<Event>)= channel();

    let tx_env = Box::new(tx.map(|event| Event::Env(event)));
    let tx_run = tx.map(|event| Event::Run(event));
    let (tx_done, rx_done) = channel();
    let (tx_send, rx_send) = channel();

    let env = FakeEnv::new(tx_env);
    let mut exec = Execution::<FakeEnv>::new();

    thread::spawn(move || {
        for msg in rx {
            if let Event::Env(FakeEnvEvent::Done) = msg {
                tx_done.send(()).unwrap();
            } else if let Event::Env(FakeEnvEvent::Send { id, value }) = msg {
                tx_send.send((id, value)).unwrap();
            } else {
                println!("LOG: {:?}", msg);
            }
        }
    });

    let script_1 = Script {
        rules: vec![
            Rule {
                conditions: vec![
                    Match {
                        source: vec![
                            GetterSelector::new()
                        ],
                        kind: ChannelKind::OnOff,
                        range: Range::Eq(Value::OnOff(OnOff::On)),
                        duration: None,
                        phantom: PhantomData
                    }
                ],
                execute: vec![
                    Statement {
                        destination: vec![
                            SetterSelector::new()
                        ],
                        value: Value::OnOff(OnOff::Off),
                        kind: ChannelKind::OnOff,
                        phantom: PhantomData,
                    }
                ],
                phantom: PhantomData
            }
        ],
        phantom: PhantomData,
    };

    let adapter_id_1 = Id::<AdapterId>::new("Adapter 1");
    let service_id_1 = Id::<ServiceId>::new("Service 1");
    let getter_id_1 = Id::<Getter>::new("Getter 1");
    let setter_id_1 = Id::<Setter>::new("Setter 1");

    println!("* We can start executing a trivial rule.");
    exec.start(env.clone(), script_1, tx_run).unwrap();

    println!("* Changing the structure of the network doesn't break the rule.");
    env.execute(Instruction::AddAdapters(vec![adapter_id_1.to_string()]));
    rx_done.recv().unwrap();

    env.execute(Instruction::AddServices(vec![
        Service {
            id: service_id_1.clone(),
            adapter: adapter_id_1.clone(),
            getters: HashMap::new(),
            setters: HashMap::new(),
            tags: HashSet::new(),
        }
    ]));
    rx_done.recv().unwrap();

    env.execute(Instruction::AddGetters(vec![
        Channel {
            id: getter_id_1.clone(),
            adapter: adapter_id_1.clone(),
            service: service_id_1.clone(),
            tags: HashSet::new(),
            last_seen: None,
            mechanism: Getter {
                watch: true,
                poll: None,
                updated: None,
                trigger: None,
                kind: ChannelKind::OnOff,
            }
        }
    ]));
    rx_done.recv().unwrap();

    env.execute(Instruction::AddSetters(vec![
        Channel {
            id: setter_id_1.clone(),
            adapter: adapter_id_1.clone(),
            service: service_id_1.clone(),
            last_seen: None,
            tags: HashSet::new(),
            mechanism: Setter {
                push: None,
                updated: None,
                kind: ChannelKind::OnOff,
            }
        }
    ]));
    rx_done.recv().unwrap();

    println!("* Injecting the expected value triggers the send.");
    env.execute(Instruction::InjectGetterValues(vec![
        (getter_id_1.clone(), Value::OnOff(OnOff::On))
    ]));

    rx_done.recv().unwrap();
    let (id, value) = rx_send.recv().unwrap();
    assert_eq!(id, setter_id_1);
    assert_eq!(value, Value::OnOff(OnOff::Off));

    println!("");
}<|endoftext|>


<commit_before>use filetime::FileTime;
use runtime::lua;
use runtime::Runtime;
use std::cell::RefCell;
use std::collections::{HashMap, LinkedList};
use std::error::Error as StdError;
use error::Error;
use std::fs;
use std::path::Path;
use std::rc::Rc;
use stdlib;

/// A single named build task.
pub struct Task {
    /// The name of the task.
    pub name: String,

    /// The task description.
    pub description: Option<String>,

    /// A list of tasks that must be ran before this task.
    pub deps: Vec<String>,

    /// Indicates if the task has been run.
    satisfied: bool,

    /// A reference to the Lua callback.
    func: lua::Reference,

    /// The runner the rule belongs to.
    runner: *mut Runner,
}

impl Task {
    pub fn is_satisfied(&self) -> bool {
        self.satisfied
    }

    pub fn run(&mut self, args: Vec<String>) -> Result<(), Box<Error>> {
        let runner = unsafe { &mut *self.runner };

        // Get the function reference onto the Lua stack.
        runner.runtime.state().raw_geti(lua::REGISTRYINDEX, self.func.value() as i64);

        // Push the given task arguments onto the stack.
        for string in &args {
            runner.runtime.state().push_string(string);
        }

        // Invoke the task function.
        runner.runtime.call(args.len() as i32, 0, 0).map(|_| ()).map_err(|e| e.into())
    }
}

/// A rule task that matches against files.
pub struct Rule {
    /// The file pattern to match.
    pub pattern: String,

    /// The rule description.
    pub description: Option<String>,

    /// A list of tasks that must be ran before this task.
    pub deps: Vec<String>,

    /// A reference to the Lua callback.
    func: Option<lua::Reference>,

    /// The runner the rule belongs to.
    runner: *mut Runner,
}

impl Rule {
    pub fn matches(&self, file_name: &str) -> bool {
        if let Some(index) = self.pattern.find("%") {
            let (prefix, suffix) = self.pattern.split_at(index);
            let suffix = &suffix[1..];

            file_name.starts_with(prefix) && file_name.ends_with(suffix)
        } else {
            file_name.ends_with(&self.pattern)
        }
    }

    pub fn is_satisfied(&self, file_name: &str) -> bool {
        let runner = unsafe { &*self.runner };

        if let Some(age) = Self::get_age(file_name) {
            for dependency in &self.deps {
                if let Some(dep_age) = runner.match_rule(dependency)
                                             .and(Self::get_age(dependency)) {
                    if dep_age > age {
                        return false;
                    }
                } else {
                    return false;
                }
            }

            true
        } else {
            false
        }
    }

    /// Runs the rule with a specified matching file name.
    pub fn run(&self, file_name: &str) -> Result<(), Box<Error>> {
        if self.func.is_none() {
            return Ok(());
        }

        let runner = unsafe { &mut *self.runner };

        // Get the function reference onto the Lua stack.
        runner.runtime
              .state()
              .raw_geti(lua::REGISTRYINDEX, self.func.unwrap().value() as i64);

        // Pass the file name in as the only argument.
        runner.runtime.state().push_string(file_name);

        // Invoke the task function.
        runner.runtime.call(1, 0, 0).map(|_| ()).map_err(|e| e.into())
    }

    fn get_age(file_name: &str) -> Option<FileTime> {
        fs::metadata(file_name)
            .map(|metadata| FileTime::from_last_modification_time(&metadata))
            .ok()
    }
}

/// A task runner object that holds the state for defined tasks, dependencies, and the scripting
/// runtime.
pub struct Runner {
    /// A map of all defined tasks.
    pub tasks: HashMap<String, Rc<RefCell<Task>>>,

    /// A vector of all defined file rules.
    pub rules: Vec<Rc<Rule>>,

    /// The name of the default task to run.
    pub default_task: Option<String>,

    /// Task execution stack.
    pub stack: LinkedList<String>,

    /// Indicates if actually running tasks should be skipped.
    dry_run: bool,

    /// The scripting runtime.
    runtime: Runtime,
}

impl Runner {
    /// Creates a new runner instance.
    ///
    /// The instance is placed inside a box to ensure the runner has a constant location in memory
    /// so that it can be referenced by native closures in the runtime.
    pub fn new(dry_run: bool) -> Result<Box<Runner>, Box<Error>> {
        let mut runner = Box::new(Runner {
            tasks: HashMap::new(),
            rules: Vec::new(),
            default_task: None,
            stack: LinkedList::new(),
            dry_run: dry_run,
            runtime: Runtime::new(),
        });

        // Set up the environment.
        runner.runtime.add_path("./components/?/?.lua");
        runner.runtime.add_cpath("./target/debug/lib?.so");

        // Set a pointer we can use to fetch the runner from within the runtime.
        runner.runtime.clone().reg_set("runner", &*runner as *const Runner as *mut Runner);

        runner.runtime.state().push_string(if cfg!(windows) {
            "windows"
        } else {
            "unix"
        });
        runner.runtime.state().set_global("OS");

        trace!("opening standard module...");
        stdlib::open_lib(runner.runtime.clone());
        try!(runner.runtime.eval(include_str!("dsl.lua")));

        Ok(runner)
    }

    /// Gets a reference to the runner that belongs to a given runtime.
    pub fn from_runtime<'a>(mut runtime: Runtime) -> &'a mut Self {
        trace!("fetching pointer to runner from registry");
        runtime.reg_get("runner").unwrap()
    }

    /// Creates a new task.
    pub fn create_task(&mut self,
                       name: String,
                       description: Option<String>,
                       deps: Vec<String>,
                       func: lua::Reference) {
        let task = Task {
            name: name,
            description: description,
            deps: deps,
            satisfied: false,
            func: func,
            runner: self,
        };

        // Add it to the master list of tasks.
        self.tasks.insert(task.name.clone(), Rc::new(RefCell::new(task)));
    }

    /// Creates a new rule.
    pub fn create_rule(&mut self,
                       pattern: String,
                       description: Option<String>,
                       deps: Vec<String>,
                       func: Option<lua::Reference>) {
        let rule = Rule {
            pattern: pattern,
            description: description,
            deps: deps,
            func: func,
            runner: self,
        };

        self.rules.push(Rc::new(rule));
    }

    /// Gets the default task to run, if any.
    pub fn default_task(&self) -> Option<Rc<RefCell<Task>>> {
        self.default_task
            .as_ref()
            .and_then(|name| self.tasks.get(name))
            .map(|rc| rc.clone())
        // ^ Look at that snazzy functional code.
    }

    /// Gets a task or a matching file rule by name.
    pub fn get_task(&self, name: &str) -> Option<Rc<RefCell<Task>>> {
        self.tasks
            .get(name)
            .map(|rc| rc.clone())
    }

    /// Gets a rule for a given file name, if any defined rules match.
    pub fn match_rule(&self, file_name: &str) -> Option<Rc<Rule>> {
        if self.tasks.contains_key(file_name) {
            return None;
        }

        for rule in &self.rules {
            if rule.matches(file_name) {
                return Some(rule.clone());
            }
        }

        None
    }

    /// Loads a build file script.
    pub fn load(&mut self, path: &Path) -> Result<(), Box<Error>> {
        self.runtime.load(path).map_err(|err: Box<StdError>| {
            err.into()
        })
    }

    /// Runs a task with a specified name.
    pub fn run(&mut self, name: &str, args: Vec<String>) -> Result<(), Box<Error>> {
        // If the default task is requested, fetch it.
        let name = if name == "default" {
            if let Some(ref task) = self.default_task {
                task.clone()
            } else {
                return Err(Box::new(Error::TaskNotFound(name.to_string())));
            }
        } else {
            name.to_string()
        };

        // Push the task onto the call stack.
        self.stack.push_front(name.clone());

        // If the name is a task, run it first.
        if let Some(task) = self.get_task(&name) {
            let satisfied = task.borrow().is_satisfied();
            if !satisfied {
                debug!("task not satisfied, running dependencies");
                try!(self.resolve_dependencies(&task.borrow().deps));

                if !self.dry_run {
                    try!(task.borrow_mut().run(args));
                } else {
                    info!("would run task \"{}\".", &name);
                }
            } else {
                info!("nothing to be done for task \"{}\".", &name);
            }
        } else if let Some(rule) = self.match_rule(&name) {
            if !rule.is_satisfied(&name) {
                try!(self.resolve_dependencies(&rule.deps));

                if !self.dry_run {
                    try!(rule.run(&name));
                } else {
                    info!("would run rule \"{}\" for file \"{}\".",
                             &rule.pattern,
                             &name);
                }
            } else {
                info!("nothing to be done for file \"{}\".", &name);
            }
        } else {
            return Err(Error::TaskNotFound(name).into());
        }

        // Pop the task off the call stack.
        self.stack.pop_front();

        Ok(())
    }

    fn resolve_dependencies(&mut self, dependencies: &Vec<String>) -> Result<(), Box<Error>> {
        for dependency in dependencies {
            try!(self.run(dependency, Vec::new()));
        }

        Ok(())
    }
}
<commit_msg>Update module paths<commit_after>use error::Error;
use filetime::FileTime;
use runtime::lua;
use runtime::Runtime;
use std::cell::RefCell;
use std::collections::{HashMap, LinkedList};
use std::env;
use std::error::Error as StdError;
use std::fs;
use std::path::Path;
use std::rc::Rc;
use stdlib;

/// A single named build task.
pub struct Task {
    /// The name of the task.
    pub name: String,

    /// The task description.
    pub description: Option<String>,

    /// A list of tasks that must be ran before this task.
    pub deps: Vec<String>,

    /// Indicates if the task has been run.
    satisfied: bool,

    /// A reference to the Lua callback.
    func: lua::Reference,

    /// The runner the rule belongs to.
    runner: *mut Runner,
}

impl Task {
    pub fn is_satisfied(&self) -> bool {
        self.satisfied
    }

    pub fn run(&mut self, args: Vec<String>) -> Result<(), Box<Error>> {
        let runner = unsafe { &mut *self.runner };

        // Get the function reference onto the Lua stack.
        runner.runtime.state().raw_geti(lua::REGISTRYINDEX, self.func.value() as i64);

        // Push the given task arguments onto the stack.
        for string in &args {
            runner.runtime.state().push_string(string);
        }

        // Invoke the task function.
        runner.runtime.call(args.len() as i32, 0, 0).map(|_| ()).map_err(|e| e.into())
    }
}

/// A rule task that matches against files.
pub struct Rule {
    /// The file pattern to match.
    pub pattern: String,

    /// The rule description.
    pub description: Option<String>,

    /// A list of tasks that must be ran before this task.
    pub deps: Vec<String>,

    /// A reference to the Lua callback.
    func: Option<lua::Reference>,

    /// The runner the rule belongs to.
    runner: *mut Runner,
}

impl Rule {
    pub fn matches(&self, file_name: &str) -> bool {
        if let Some(index) = self.pattern.find("%") {
            let (prefix, suffix) = self.pattern.split_at(index);
            let suffix = &suffix[1..];

            file_name.starts_with(prefix) && file_name.ends_with(suffix)
        } else {
            file_name.ends_with(&self.pattern)
        }
    }

    pub fn is_satisfied(&self, file_name: &str) -> bool {
        let runner = unsafe { &*self.runner };

        if let Some(age) = Self::get_age(file_name) {
            for dependency in &self.deps {
                if let Some(dep_age) = runner.match_rule(dependency)
                                             .and(Self::get_age(dependency)) {
                    if dep_age > age {
                        return false;
                    }
                } else {
                    return false;
                }
            }

            true
        } else {
            false
        }
    }

    /// Runs the rule with a specified matching file name.
    pub fn run(&self, file_name: &str) -> Result<(), Box<Error>> {
        if self.func.is_none() {
            return Ok(());
        }

        let runner = unsafe { &mut *self.runner };

        // Get the function reference onto the Lua stack.
        runner.runtime
              .state()
              .raw_geti(lua::REGISTRYINDEX, self.func.unwrap().value() as i64);

        // Pass the file name in as the only argument.
        runner.runtime.state().push_string(file_name);

        // Invoke the task function.
        runner.runtime.call(1, 0, 0).map(|_| ()).map_err(|e| e.into())
    }

    fn get_age(file_name: &str) -> Option<FileTime> {
        fs::metadata(file_name)
            .map(|metadata| FileTime::from_last_modification_time(&metadata))
            .ok()
    }
}

/// A task runner object that holds the state for defined tasks, dependencies, and the scripting
/// runtime.
pub struct Runner {
    /// A map of all defined tasks.
    pub tasks: HashMap<String, Rc<RefCell<Task>>>,

    /// A vector of all defined file rules.
    pub rules: Vec<Rc<Rule>>,

    /// The name of the default task to run.
    pub default_task: Option<String>,

    /// Task execution stack.
    pub stack: LinkedList<String>,

    /// Indicates if actually running tasks should be skipped.
    dry_run: bool,

    /// The scripting runtime.
    runtime: Runtime,
}

impl Runner {
    /// Creates a new runner instance.
    ///
    /// The instance is placed inside a box to ensure the runner has a constant location in memory
    /// so that it can be referenced by native closures in the runtime.
    pub fn new(dry_run: bool) -> Result<Box<Runner>, Box<Error>> {
        let mut runner = Box::new(Runner {
            tasks: HashMap::new(),
            rules: Vec::new(),
            default_task: None,
            stack: LinkedList::new(),
            dry_run: dry_run,
            runtime: Runtime::new(),
        });

        // Set up the environment.
        runner.runtime.add_path("./components/?.lua");
        runner.runtime.add_cpath("/usr/lib/rote/lib?.so");

        if let Some(mut path) = env::current_exe().ok()
            .and_then(|path| path.parent()
                .map(|p| p.to_path_buf())
            ) {
            path.push("lib?.so");
            runner.runtime.add_cpath(path.to_str().unwrap());
        }

        // Set a pointer we can use to fetch the runner from within the runtime.
        runner.runtime.clone().reg_set("runner", &*runner as *const Runner as *mut Runner);

        runner.runtime.state().push_string(if cfg!(windows) {
            "windows"
        } else {
            "unix"
        });
        runner.runtime.state().set_global("OS");

        trace!("opening standard module...");
        stdlib::open_lib(runner.runtime.clone());
        try!(runner.runtime.eval(include_str!("dsl.lua")));

        Ok(runner)
    }

    /// Gets a reference to the runner that belongs to a given runtime.
    pub fn from_runtime<'a>(mut runtime: Runtime) -> Option<&'a mut Self> {
        trace!("fetching pointer to runner from registry");
        runtime.reg_get("runner")
    }

    /// Creates a new task.
    pub fn create_task(&mut self,
                       name: String,
                       description: Option<String>,
                       deps: Vec<String>,
                       func: lua::Reference) {
        let task = Task {
            name: name,
            description: description,
            deps: deps,
            satisfied: false,
            func: func,
            runner: self,
        };

        // Add it to the master list of tasks.
        self.tasks.insert(task.name.clone(), Rc::new(RefCell::new(task)));
    }

    /// Creates a new rule.
    pub fn create_rule(&mut self,
                       pattern: String,
                       description: Option<String>,
                       deps: Vec<String>,
                       func: Option<lua::Reference>) {
        let rule = Rule {
            pattern: pattern,
            description: description,
            deps: deps,
            func: func,
            runner: self,
        };

        self.rules.push(Rc::new(rule));
    }

    /// Gets the default task to run, if any.
    pub fn default_task(&self) -> Option<Rc<RefCell<Task>>> {
        self.default_task
            .as_ref()
            .and_then(|name| self.tasks.get(name))
            .map(|rc| rc.clone())
        // ^ Look at that snazzy functional code.
    }

    /// Gets a task or a matching file rule by name.
    pub fn get_task(&self, name: &str) -> Option<Rc<RefCell<Task>>> {
        self.tasks
            .get(name)
            .map(|rc| rc.clone())
    }

    /// Gets a rule for a given file name, if any defined rules match.
    pub fn match_rule(&self, file_name: &str) -> Option<Rc<Rule>> {
        if self.tasks.contains_key(file_name) {
            return None;
        }

        for rule in &self.rules {
            if rule.matches(file_name) {
                return Some(rule.clone());
            }
        }

        None
    }

    /// Loads a build file script.
    pub fn load(&mut self, path: &Path) -> Result<(), Box<Error>> {
        self.runtime.load(path).map_err(|err: Box<StdError>| {
            err.into()
        })
    }

    /// Runs a task with a specified name.
    pub fn run(&mut self, name: &str, args: Vec<String>) -> Result<(), Box<Error>> {
        // If the default task is requested, fetch it.
        let name = if name == "default" {
            if let Some(ref task) = self.default_task {
                task.clone()
            } else {
                return Err(Box::new(Error::TaskNotFound(name.to_string())));
            }
        } else {
            name.to_string()
        };

        // Push the task onto the call stack.
        self.stack.push_front(name.clone());

        // If the name is a task, run it first.
        if let Some(task) = self.get_task(&name) {
            let satisfied = task.borrow().is_satisfied();
            if !satisfied {
                debug!("task not satisfied, running dependencies");
                try!(self.resolve_dependencies(&task.borrow().deps));

                if !self.dry_run {
                    try!(task.borrow_mut().run(args));
                } else {
                    info!("would run task \"{}\".", &name);
                }
            } else {
                info!("nothing to be done for task \"{}\".", &name);
            }
        } else if let Some(rule) = self.match_rule(&name) {
            if !rule.is_satisfied(&name) {
                try!(self.resolve_dependencies(&rule.deps));

                if !self.dry_run {
                    try!(rule.run(&name));
                } else {
                    info!("would run rule \"{}\" for file \"{}\".",
                             &rule.pattern,
                             &name);
                }
            } else {
                info!("nothing to be done for file \"{}\".", &name);
            }
        } else {
            return Err(Error::TaskNotFound(name).into());
        }

        // Pop the task off the call stack.
        self.stack.pop_front();

        Ok(())
    }

    fn resolve_dependencies(&mut self, dependencies: &Vec<String>) -> Result<(), Box<Error>> {
        for dependency in dependencies {
            try!(self.run(dependency, Vec::new()));
        }

        Ok(())
    }
}
<|endoftext|>