<commit_before>//! Validation of char literals

use std::u32;

use arrayvec::ArrayString;

use crate::{
    ast::{self, AstNode},
    string_lexing::{self, StringComponentKind},
    TextRange,
    yellow::{
        SyntaxError,
        SyntaxErrorKind::*,
    },
};

pub(super) fn validate_char_node(node: ast::Char, errors: &mut Vec<SyntaxError>) {
    let literal_text = node.text();
    let literal_range = node.syntax().range();
    let mut components = string_lexing::parse_char_literal(literal_text);
    let mut len = 0;
    for component in &mut components {
        len += 1;
        let text = &literal_text[component.range];
        let range = component.range + literal_range.start();
        validate_char_component(text, component.kind, range, errors);
    }

    if !components.has_closing_quote {
        errors.push(SyntaxError::new(UnclosedChar, literal_range));
    }

    if let Some(range) = components.suffix {
        errors.push(SyntaxError::new(
            InvalidSuffix,
            range + literal_range.start(),
        ));
    }

    if len == 0 {
        errors.push(SyntaxError::new(EmptyChar, literal_range));
    }

    if len > 1 {
        errors.push(SyntaxError::new(OverlongChar, literal_range));
    }
}

pub(super) fn validate_char_component(
    text: &str,
    kind: StringComponentKind,
    range: TextRange,
    errors: &mut Vec<SyntaxError>,
) {
    // Validate escapes
    use self::StringComponentKind::*;
    match kind {
        AsciiEscape => validate_ascii_escape(text, range, errors),
        AsciiCodeEscape => validate_ascii_code_escape(text, range, errors),
        UnicodeEscape => validate_unicode_escape(text, range, errors),
        CodePoint => {
            // These code points must always be escaped
            if text == "\t" || text == "\r" || text == "\n" {
                errors.push(SyntaxError::new(UnescapedCodepoint, range));
            }
        }
        StringComponentKind::IgnoreNewline => { /* always valid */ }
    }
}

fn validate_ascii_escape(text: &str, range: TextRange, errors: &mut Vec<SyntaxError>) {
    if text.len() == 1 {
        // Escape sequence consists only of leading `\` (only occurs at EOF, otherwise e.g. '\' is treated as an unclosed char containing a single quote `'`)
        errors.push(SyntaxError::new(EmptyAsciiEscape, range));
    } else {
        let escape_code = text.chars().skip(1).next().unwrap();
        if !is_ascii_escape(escape_code) {
            errors.push(SyntaxError::new(InvalidAsciiEscape, range));
        }
    }
}

pub(super) fn is_ascii_escape(code: char) -> bool {
    match code {
        '\\' | '\'' | '"' | 'n' | 'r' | 't' | '0' => true,
        _ => false,
    }
}

fn validate_ascii_code_escape(text: &str, range: TextRange, errors: &mut Vec<SyntaxError>) {
    // An AsciiCodeEscape has 4 chars, example: `\xDD`
    if !text.is_ascii() {
        // TODO: Give a more precise error message (say what the invalid character was)
        errors.push(SyntaxError::new(AsciiCodeEscapeOutOfRange, range));
    }
    if text.len() < 4 {
        errors.push(SyntaxError::new(TooShortAsciiCodeEscape, range));
    } else {
        assert_eq!(
            text.len(),
            4,
            "AsciiCodeEscape cannot be longer than 4 chars, but text '{}' is",
            text,
        );

        match u8::from_str_radix(&text[2..], 16) {
            Ok(code) if code < 128 => { /* Escape code is valid */ }
            Ok(_) => errors.push(SyntaxError::new(AsciiCodeEscapeOutOfRange, range)),
            Err(_) => errors.push(SyntaxError::new(MalformedAsciiCodeEscape, range)),
        }
    }
}

fn validate_unicode_escape(text: &str, range: TextRange, errors: &mut Vec<SyntaxError>) {
    assert!(&text[..2] == "\\u", "UnicodeEscape always starts with \\u");

    if text.len() == 2 {
        // No starting `{`
        errors.push(SyntaxError::new(MalformedUnicodeEscape, range));
        return;
    }

    if text.len() == 3 {
        // Only starting `{`
        errors.push(SyntaxError::new(UnclosedUnicodeEscape, range));
        return;
    }

    let mut code = ArrayString::<[_; 6]>::new();
    let mut closed = false;
    for c in text[3..].chars() {
        assert!(!closed, "no characters after escape is closed");

        if c.is_digit(16) {
            if code.len() == 6 {
                errors.push(SyntaxError::new(OverlongUnicodeEscape, range));
                return;
            }

            code.push(c);
        } else if c == '_' {
            // Reject leading _
            if code.len() == 0 {
                errors.push(SyntaxError::new(MalformedUnicodeEscape, range));
                return;
            }
        } else if c == '}' {
            closed = true;
        } else {
            errors.push(SyntaxError::new(MalformedUnicodeEscape, range));
            return;
        }
    }

    if !closed {
        errors.push(SyntaxError::new(UnclosedUnicodeEscape, range))
    }

    if code.len() == 0 {
        errors.push(SyntaxError::new(EmptyUnicodeEcape, range));
        return;
    }

    match u32::from_str_radix(&code, 16) {
        Ok(code_u32) if code_u32 > 0x10FFFF => {
            errors.push(SyntaxError::new(UnicodeEscapeOutOfRange, range));
        }
        Ok(_) => {
            // Valid escape code
        }
        Err(_) => {
            errors.push(SyntaxError::new(MalformedUnicodeEscape, range));
        }
    }
}

#[cfg(test)]
mod test {
    use crate::SourceFileNode;

    fn build_file(literal: &str) -> SourceFileNode {
        let src = format!("const C: char = '{}';", literal);
        SourceFileNode::parse(&src)
    }

    fn assert_valid_char(literal: &str) {
        let file = build_file(literal);
        assert!(
            file.errors().len() == 0,
            "Errors for literal '{}': {:?}",
            literal,
            file.errors()
        );
    }

    fn assert_invalid_char(literal: &str) {
        let file = build_file(literal);
        assert!(file.errors().len() > 0);
    }

    #[test]
    fn test_ansi_codepoints() {
        for byte in 0..=255u8 {
            match byte {
                b'\n' | b'\r' | b'\t' => assert_invalid_char(&(byte as char).to_string()),
                b'\'' | b'\\' => { /* Ignore character close and backslash */ }
                _ => assert_valid_char(&(byte as char).to_string()),
            }
        }
    }

    #[test]
    fn test_unicode_codepoints() {
        let valid = ["Ƒ", "バ", "メ", "﷽"];
        for c in &valid {
            assert_valid_char(c);
        }
    }

    #[test]
    fn test_unicode_multiple_codepoints() {
        let invalid = ["नी", "👨👨"];
        for c in &invalid {
            assert_invalid_char(c);
        }
    }

    #[test]
    fn test_valid_ascii_escape() {
        let valid = [r"\'", "\"", "\\\\", "\\\"", r"\n", r"\r", r"\t", r"\0"];
        for c in &valid {
            assert_valid_char(c);
        }
    }

    #[test]
    fn test_invalid_ascii_escape() {
        let invalid = [r"\a", r"\?", r"\"];
        for c in &invalid {
            assert_invalid_char(c);
        }
    }

    #[test]
    fn test_valid_ascii_code_escape() {
        let valid = [r"\x00", r"\x7F", r"\x55"];
        for c in &valid {
            assert_valid_char(c);
        }
    }

    #[test]
    fn test_invalid_ascii_code_escape() {
        let invalid = [r"\x", r"\x7", r"\xF0"];
        for c in &invalid {
            assert_invalid_char(c);
        }
    }

    #[test]
    fn test_valid_unicode_escape() {
        let valid = [
            r"\u{FF}",
            r"\u{0}",
            r"\u{F}",
            r"\u{10FFFF}",
            r"\u{1_0__FF___FF_____}",
        ];
        for c in &valid {
            assert_valid_char(c);
        }
    }

    #[test]
    fn test_invalid_unicode_escape() {
        let invalid = [
            r"\u",
            r"\u{}",
            r"\u{",
            r"\u{FF",
            r"\u{FFFFFF}",
            r"\u{_F}",
            r"\u{00FFFFF}",
            r"\u{110000}",
        ];
        for c in &invalid {
            assert_invalid_char(c);
        }
    }
}
<commit_msg>Use an else if<commit_after>//! Validation of char literals

use std::u32;

use arrayvec::ArrayString;

use crate::{
    ast::{self, AstNode},
    string_lexing::{self, StringComponentKind},
    TextRange,
    yellow::{
        SyntaxError,
        SyntaxErrorKind::*,
    },
};

pub(super) fn validate_char_node(node: ast::Char, errors: &mut Vec<SyntaxError>) {
    let literal_text = node.text();
    let literal_range = node.syntax().range();
    let mut components = string_lexing::parse_char_literal(literal_text);
    let mut len = 0;
    for component in &mut components {
        len += 1;
        let text = &literal_text[component.range];
        let range = component.range + literal_range.start();
        validate_char_component(text, component.kind, range, errors);
    }

    if !components.has_closing_quote {
        errors.push(SyntaxError::new(UnclosedChar, literal_range));
    }

    if let Some(range) = components.suffix {
        errors.push(SyntaxError::new(
            InvalidSuffix,
            range + literal_range.start(),
        ));
    }

    if len == 0 {
        errors.push(SyntaxError::new(EmptyChar, literal_range));
    }

    if len > 1 {
        errors.push(SyntaxError::new(OverlongChar, literal_range));
    }
}

pub(super) fn validate_char_component(
    text: &str,
    kind: StringComponentKind,
    range: TextRange,
    errors: &mut Vec<SyntaxError>,
) {
    // Validate escapes
    use self::StringComponentKind::*;
    match kind {
        AsciiEscape => validate_ascii_escape(text, range, errors),
        AsciiCodeEscape => validate_ascii_code_escape(text, range, errors),
        UnicodeEscape => validate_unicode_escape(text, range, errors),
        CodePoint => {
            // These code points must always be escaped
            if text == "\t" || text == "\r" || text == "\n" {
                errors.push(SyntaxError::new(UnescapedCodepoint, range));
            }
        }
        StringComponentKind::IgnoreNewline => { /* always valid */ }
    }
}

fn validate_ascii_escape(text: &str, range: TextRange, errors: &mut Vec<SyntaxError>) {
    if text.len() == 1 {
        // Escape sequence consists only of leading `\` (only occurs at EOF, otherwise e.g. '\' is treated as an unclosed char containing a single quote `'`)
        errors.push(SyntaxError::new(EmptyAsciiEscape, range));
    } else {
        let escape_code = text.chars().skip(1).next().unwrap();
        if !is_ascii_escape(escape_code) {
            errors.push(SyntaxError::new(InvalidAsciiEscape, range));
        }
    }
}

pub(super) fn is_ascii_escape(code: char) -> bool {
    match code {
        '\\' | '\'' | '"' | 'n' | 'r' | 't' | '0' => true,
        _ => false,
    }
}

fn validate_ascii_code_escape(text: &str, range: TextRange, errors: &mut Vec<SyntaxError>) {
    // An AsciiCodeEscape has 4 chars, example: `\xDD`
    if !text.is_ascii() {
        // TODO: Give a more precise error message (say what the invalid character was)
        errors.push(SyntaxError::new(AsciiCodeEscapeOutOfRange, range));
    } else if text.chars().count() < 4 {
        errors.push(SyntaxError::new(TooShortAsciiCodeEscape, range));
    } else {
        assert_eq!(
            text.chars().count(),
            4,
            "AsciiCodeEscape cannot be longer than 4 chars, but text '{}' is",
            text,
        );

        match u8::from_str_radix(&text[2..], 16) {
            Ok(code) if code < 128 => { /* Escape code is valid */ }
            Ok(_) => errors.push(SyntaxError::new(AsciiCodeEscapeOutOfRange, range)),
            Err(_) => errors.push(SyntaxError::new(MalformedAsciiCodeEscape, range)),
        }
    }
}

fn validate_unicode_escape(text: &str, range: TextRange, errors: &mut Vec<SyntaxError>) {
    assert!(&text[..2] == "\\u", "UnicodeEscape always starts with \\u");

    if text.len() == 2 {
        // No starting `{`
        errors.push(SyntaxError::new(MalformedUnicodeEscape, range));
        return;
    }

    if text.len() == 3 {
        // Only starting `{`
        errors.push(SyntaxError::new(UnclosedUnicodeEscape, range));
        return;
    }

    let mut code = ArrayString::<[_; 6]>::new();
    let mut closed = false;
    for c in text[3..].chars() {
        assert!(!closed, "no characters after escape is closed");

        if c.is_digit(16) {
            if code.len() == 6 {
                errors.push(SyntaxError::new(OverlongUnicodeEscape, range));
                return;
            }

            code.push(c);
        } else if c == '_' {
            // Reject leading _
            if code.len() == 0 {
                errors.push(SyntaxError::new(MalformedUnicodeEscape, range));
                return;
            }
        } else if c == '}' {
            closed = true;
        } else {
            errors.push(SyntaxError::new(MalformedUnicodeEscape, range));
            return;
        }
    }

    if !closed {
        errors.push(SyntaxError::new(UnclosedUnicodeEscape, range))
    }

    if code.len() == 0 {
        errors.push(SyntaxError::new(EmptyUnicodeEcape, range));
        return;
    }

    match u32::from_str_radix(&code, 16) {
        Ok(code_u32) if code_u32 > 0x10FFFF => {
            errors.push(SyntaxError::new(UnicodeEscapeOutOfRange, range));
        }
        Ok(_) => {
            // Valid escape code
        }
        Err(_) => {
            errors.push(SyntaxError::new(MalformedUnicodeEscape, range));
        }
    }
}

#[cfg(test)]
mod test {
    use crate::SourceFileNode;

    fn build_file(literal: &str) -> SourceFileNode {
        let src = format!("const C: char = '{}';", literal);
        SourceFileNode::parse(&src)
    }

    fn assert_valid_char(literal: &str) {
        let file = build_file(literal);
        assert!(
            file.errors().len() == 0,
            "Errors for literal '{}': {:?}",
            literal,
            file.errors()
        );
    }

    fn assert_invalid_char(literal: &str) {
        let file = build_file(literal);
        assert!(file.errors().len() > 0);
    }

    #[test]
    fn test_ansi_codepoints() {
        for byte in 0..=255u8 {
            match byte {
                b'\n' | b'\r' | b'\t' => assert_invalid_char(&(byte as char).to_string()),
                b'\'' | b'\\' => { /* Ignore character close and backslash */ }
                _ => assert_valid_char(&(byte as char).to_string()),
            }
        }
    }

    #[test]
    fn test_unicode_codepoints() {
        let valid = ["Ƒ", "バ", "メ", "﷽"];
        for c in &valid {
            assert_valid_char(c);
        }
    }

    #[test]
    fn test_unicode_multiple_codepoints() {
        let invalid = ["नी", "👨👨"];
        for c in &invalid {
            assert_invalid_char(c);
        }
    }

    #[test]
    fn test_valid_ascii_escape() {
        let valid = [r"\'", "\"", "\\\\", "\\\"", r"\n", r"\r", r"\t", r"\0"];
        for c in &valid {
            assert_valid_char(c);
        }
    }

    #[test]
    fn test_invalid_ascii_escape() {
        let invalid = [r"\a", r"\?", r"\"];
        for c in &invalid {
            assert_invalid_char(c);
        }
    }

    #[test]
    fn test_valid_ascii_code_escape() {
        let valid = [r"\x00", r"\x7F", r"\x55"];
        for c in &valid {
            assert_valid_char(c);
        }
    }

    #[test]
    fn test_invalid_ascii_code_escape() {
        let invalid = [r"\x", r"\x7", r"\xF0"];
        for c in &invalid {
            assert_invalid_char(c);
        }
    }

    #[test]
    fn test_valid_unicode_escape() {
        let valid = [
            r"\u{FF}",
            r"\u{0}",
            r"\u{F}",
            r"\u{10FFFF}",
            r"\u{1_0__FF___FF_____}",
        ];
        for c in &valid {
            assert_valid_char(c);
        }
    }

    #[test]
    fn test_invalid_unicode_escape() {
        let invalid = [
            r"\u",
            r"\u{}",
            r"\u{",
            r"\u{FF",
            r"\u{FFFFFF}",
            r"\u{_F}",
            r"\u{00FFFFF}",
            r"\u{110000}",
        ];
        for c in &invalid {
            assert_invalid_char(c);
        }
    }
}
<|endoftext|>


<commit_before>//! Book keeping for keeping diagnostics easily in sync with the client.
pub(crate) mod to_proto;

use std::{mem, sync::Arc};

use ra_ide::FileId;
use rustc_hash::{FxHashMap, FxHashSet};

use crate::lsp_ext;

pub(crate) type CheckFixes = Arc<FxHashMap<FileId, Vec<Fix>>>;

#[derive(Debug, Default, Clone)]
pub struct DiagnosticsConfig {
    pub warnings_as_info: Vec<String>,
    pub warnings_as_hint: Vec<String>,
}

#[derive(Debug, Default, Clone)]
pub(crate) struct DiagnosticCollection {
    pub(crate) native: FxHashMap<FileId, Vec<lsp_types::Diagnostic>>,
    pub(crate) check: FxHashMap<FileId, Vec<lsp_types::Diagnostic>>,
    pub(crate) check_fixes: CheckFixes,
    changes: FxHashSet<FileId>,
}

#[derive(Debug, Clone)]
pub(crate) struct Fix {
    pub(crate) range: lsp_types::Range,
    pub(crate) action: lsp_ext::CodeAction,
}

impl DiagnosticCollection {
    pub(crate) fn clear_check(&mut self) {
        Arc::make_mut(&mut self.check_fixes).clear();
        self.changes.extend(self.check.drain().map(|(key, _value)| key))
    }

    pub(crate) fn add_check_diagnostic(
        &mut self,
        file_id: FileId,
        diagnostic: lsp_types::Diagnostic,
        fixes: Vec<lsp_ext::CodeAction>,
    ) {
        let diagnostics = self.check.entry(file_id).or_default();
        for existing_diagnostic in diagnostics.iter() {
            if are_diagnostics_equal(&existing_diagnostic, &diagnostic) {
                return;
            }
        }

        let check_fixes = Arc::make_mut(&mut self.check_fixes);
        check_fixes
            .entry(file_id)
            .or_default()
            .extend(fixes.into_iter().map(|action| Fix { range: diagnostic.range, action }));
        diagnostics.push(diagnostic);
        self.changes.insert(file_id);
    }

    pub(crate) fn set_native_diagnostics(
        &mut self,
        file_id: FileId,
        diagnostics: Vec<lsp_types::Diagnostic>,
    ) {
        self.native.insert(file_id, diagnostics);
        self.changes.insert(file_id);
    }

    pub(crate) fn diagnostics_for(
        &self,
        file_id: FileId,
    ) -> impl Iterator<Item = &lsp_types::Diagnostic> {
        let native = self.native.get(&file_id).into_iter().flatten();
        let check = self.check.get(&file_id).into_iter().flatten();
        native.chain(check)
    }

    pub(crate) fn take_changes(&mut self) -> Option<FxHashSet<FileId>> {
        if self.changes.is_empty() {
            return None;
        }
        Some(mem::take(&mut self.changes))
    }
}

fn are_diagnostics_equal(left: &lsp_types::Diagnostic, right: &lsp_types::Diagnostic) -> bool {
    left.source == right.source
        && left.severity == right.severity
        && left.range == right.range
        && left.message == right.message
}
<commit_msg>Add fixmes<commit_after>//! Book keeping for keeping diagnostics easily in sync with the client.
pub(crate) mod to_proto;

use std::{mem, sync::Arc};

use ra_ide::FileId;
use rustc_hash::{FxHashMap, FxHashSet};

use crate::lsp_ext;

pub(crate) type CheckFixes = Arc<FxHashMap<FileId, Vec<Fix>>>;

#[derive(Debug, Default, Clone)]
pub struct DiagnosticsConfig {
    pub warnings_as_info: Vec<String>,
    pub warnings_as_hint: Vec<String>,
}

#[derive(Debug, Default, Clone)]
pub(crate) struct DiagnosticCollection {
    // FIXME: should be FxHashMap<FileId, Vec<ra_id::Diagnostic>>
    pub(crate) native: FxHashMap<FileId, Vec<lsp_types::Diagnostic>>,
    // FIXME: should be Vec<flycheck::Diagnostic>
    pub(crate) check: FxHashMap<FileId, Vec<lsp_types::Diagnostic>>,
    pub(crate) check_fixes: CheckFixes,
    changes: FxHashSet<FileId>,
}

#[derive(Debug, Clone)]
pub(crate) struct Fix {
    pub(crate) range: lsp_types::Range,
    pub(crate) action: lsp_ext::CodeAction,
}

impl DiagnosticCollection {
    pub(crate) fn clear_check(&mut self) {
        Arc::make_mut(&mut self.check_fixes).clear();
        self.changes.extend(self.check.drain().map(|(key, _value)| key))
    }

    pub(crate) fn add_check_diagnostic(
        &mut self,
        file_id: FileId,
        diagnostic: lsp_types::Diagnostic,
        fixes: Vec<lsp_ext::CodeAction>,
    ) {
        let diagnostics = self.check.entry(file_id).or_default();
        for existing_diagnostic in diagnostics.iter() {
            if are_diagnostics_equal(&existing_diagnostic, &diagnostic) {
                return;
            }
        }

        let check_fixes = Arc::make_mut(&mut self.check_fixes);
        check_fixes
            .entry(file_id)
            .or_default()
            .extend(fixes.into_iter().map(|action| Fix { range: diagnostic.range, action }));
        diagnostics.push(diagnostic);
        self.changes.insert(file_id);
    }

    pub(crate) fn set_native_diagnostics(
        &mut self,
        file_id: FileId,
        diagnostics: Vec<lsp_types::Diagnostic>,
    ) {
        self.native.insert(file_id, diagnostics);
        self.changes.insert(file_id);
    }

    pub(crate) fn diagnostics_for(
        &self,
        file_id: FileId,
    ) -> impl Iterator<Item = &lsp_types::Diagnostic> {
        let native = self.native.get(&file_id).into_iter().flatten();
        let check = self.check.get(&file_id).into_iter().flatten();
        native.chain(check)
    }

    pub(crate) fn take_changes(&mut self) -> Option<FxHashSet<FileId>> {
        if self.changes.is_empty() {
            return None;
        }
        Some(mem::take(&mut self.changes))
    }
}

fn are_diagnostics_equal(left: &lsp_types::Diagnostic, right: &lsp_types::Diagnostic) -> bool {
    left.source == right.source
        && left.severity == right.severity
        && left.range == right.range
        && left.message == right.message
}
<|endoftext|>


<commit_before>//! The parser module is responsible for parsing FITS files.

use std::str;
use std::str::FromStr;
use super::types::Keyword;

named!(fits<&[u8], ((Vec<Keyword>, (&[u8], Vec<&[u8]>), Vec<Vec<&[u8]> >), Vec<&[u8]>) >,
       pair!(primary_header, many0!( take!(2880) )));

named!(primary_header<&[u8], (Vec<Keyword>, (&[u8], Vec<&[u8]>), Vec<Vec<&[u8]> >)>,
       tuple!(
           many0!(keyword_record),
           end_record,
           many0!(blank_record)
       ));

named!(keyword_record<&[u8], Keyword>,
       map!( // TODO should use map_res!
           tuple!(
               take!(8),
               tag!("="),
               take!(71)
           ), |(slice, _, _) : (&[u8], &[u8], &[u8])| {
               match str::from_utf8(slice) {
                   Ok(s) => {
                       match Keyword::from_str(s) {
                           Ok(keyword) => keyword,
                           Err(_) => Keyword::SIMPLE // TODO this is not correct
                       }
                   }
                   Err(_) => Keyword::SIMPLE // TODO this is not correct
               }
           }));

named!(keyword<&[u8], Keyword>,
       map_res!(
           map_res!(
               take!(8),
               str::from_utf8),
           Keyword::from_str
       ));

named!(end_record<&[u8],(&[u8], Vec<&[u8]>)>, pair!(tag!("END"), count!(tag!(" "), 77)));

named!(blank_record<&[u8],Vec<&[u8]> >, count!(tag!(" "), 80));

#[cfg(test)]
mod tests {
    use nom::{IResult};
    use super::super::types::Keyword;
    use super::{fits, primary_header, keyword_record, keyword, end_record, blank_record};

    #[test]
    fn it_should_parse_a_fits_file(){
        let data = include_bytes!("../../assets/images/k2-trappist1-unofficial-tpf-long-cadence.fits");

        let result = fits(data);

        match result {
            IResult::Done(_, (header, blocks)) => {
                assert_eq!(blocks.len(), 3675);
            },
            IResult::Error(_) => panic!("Did not expect an error"),
            IResult::Incomplete(_) => panic!("Did not expect to be incomplete")
        }
    }

    #[test]
    fn primary_header_should_parse_a_primary_header(){
        let data = include_bytes!("../../assets/images/k2-trappist1-unofficial-tpf-long-cadence.fits");

        let result = primary_header(&data[0..(2*2880)]);

        match result {
            IResult::Done(_, _) => assert!(true),
            IResult::Error(_) => panic!("Did not expect an error"),
            IResult::Incomplete(_) => panic!("Did not expect to be incomplete")
        }
    }

    #[test]
    fn keyword_record_should_parse_a_keyword_record(){
        let data = "OBJECT  = 'EPIC 200164267'     / string version of target id                    "
            .as_bytes();

        let result = keyword_record(data);

        match result {
            IResult::Done(_,k) => assert_eq!(k, Keyword::OBJECT),
            IResult::Error(_) => panic!("Did not expect an error"),
            IResult::Incomplete(_) => panic!("Did not expect to be incomplete")
        }
    }

    #[test]
    fn keyword_should_parse_a_keyword(){
        let data = "OBJECT  "
            .as_bytes();

        let result = keyword(data);

        match result {
            IResult::Done(_, keyword) => assert_eq!(keyword, Keyword::OBJECT),
            IResult::Error(_) => panic!("Did not expect an error"),
            IResult::Incomplete(_) => panic!("Did not expect to be incomplete")
        }
    }

    #[test]
    fn end_record_should_parse_an_END_record(){
        let data = "END                                                                             "
            .as_bytes();

        let result = end_record(data);

        match result {
            IResult::Done(_,_) => assert!(true),
            IResult::Error(_) => panic!("Did not expect an error"),
            IResult::Incomplete(_) => panic!("Did not expect to be incomplete")
        }
    }

    #[test]
    fn blank_record_should_parse_a_BLANK_record(){
        let data = "                                                                                "
            .as_bytes();

        let result = blank_record(data);

        match result {
            IResult::Done(_,_) => assert!(true),
            IResult::Error(_) => panic!("Did not expect an error"),
            IResult::Incomplete(_) => panic!("Did not expect to be incomplete")
        }
    }
}
<commit_msg>Map end_record to END keyword<commit_after>//! The parser module is responsible for parsing FITS files.

use std::str;
use std::str::FromStr;
use super::types::Keyword;

named!(fits<&[u8], ((Vec<Keyword>, Keyword, Vec<Vec<&[u8]> >), Vec<&[u8]>) >,
       pair!(primary_header, many0!( take!(2880) )));

named!(primary_header<&[u8], (Vec<Keyword>, Keyword, Vec<Vec<&[u8]> >)>,
       tuple!(
           many0!(keyword_record),
           end_record,
           many0!(blank_record)
       ));

named!(keyword_record<&[u8], Keyword>,
       map!( // TODO should use map_res!
           tuple!(
               take!(8),
               tag!("="),
               take!(71)
           ), |(slice, _, _) : (&[u8], &[u8], &[u8])| {
               let k: Keyword = match str::from_utf8(slice) {
                   Ok(s) => {
                       match Keyword::from_str(s) {
                           Ok(keyword) => keyword,
                           Err(_) => Keyword::SIMPLE // TODO this is not correct
                       }
                   }
                   Err(_) => Keyword::SIMPLE // TODO this is not correct
               };
               println!("{:?}", k);
               k
           }));

named!(keyword<&[u8], Keyword>,
       map_res!(
           map_res!(
               take!(8),
               str::from_utf8),
           Keyword::from_str
       ));

named!(end_record<&[u8], Keyword>,
       map!(
           pair!(tag!("END"), count!(tag!(" "), 77)),
           |_| { Keyword::END }
       ));

named!(blank_record<&[u8],Vec<&[u8]> >, count!(tag!(" "), 80));

#[cfg(test)]
mod tests {
    use nom::{IResult};
    use super::super::types::Keyword;
    use super::{fits, primary_header, keyword_record, keyword, end_record, blank_record};

    #[test]
    fn it_should_parse_a_fits_file(){
        let data = include_bytes!("../../assets/images/k2-trappist1-unofficial-tpf-long-cadence.fits");

        let result = fits(data);

        match result {
            IResult::Done(_, (header, blocks)) => {
                assert_eq!(blocks.len(), 3675);
            },
            IResult::Error(_) => panic!("Did not expect an error"),
            IResult::Incomplete(_) => panic!("Did not expect to be incomplete")
        }
    }

    #[test]
    fn primary_header_should_parse_a_primary_header(){
        let data = include_bytes!("../../assets/images/k2-trappist1-unofficial-tpf-long-cadence.fits");

        let result = primary_header(&data[0..(2*2880)]);

        match result {
            IResult::Done(_, _) => assert!(true),
            IResult::Error(_) => panic!("Did not expect an error"),
            IResult::Incomplete(_) => panic!("Did not expect to be incomplete")
        }
    }

    #[test]
    fn keyword_record_should_parse_a_keyword_record(){
        let data = "OBJECT  = 'EPIC 200164267'     / string version of target id                    "
            .as_bytes();

        let result = keyword_record(data);

        match result {
            IResult::Done(_,k) => assert_eq!(k, Keyword::OBJECT),
            IResult::Error(_) => panic!("Did not expect an error"),
            IResult::Incomplete(_) => panic!("Did not expect to be incomplete")
        }
    }

    #[test]
    fn keyword_should_parse_a_keyword(){
        let data = "OBJECT  "
            .as_bytes();

        let result = keyword(data);

        match result {
            IResult::Done(_, keyword) => assert_eq!(keyword, Keyword::OBJECT),
            IResult::Error(_) => panic!("Did not expect an error"),
            IResult::Incomplete(_) => panic!("Did not expect to be incomplete")
        }
    }

    #[test]
    fn end_record_should_parse_an_END_record(){
        let data = "END                                                                             "
            .as_bytes();

        let result = end_record(data);

        match result {
            IResult::Done(_, keyword) => assert_eq!(keyword, Keyword::END),
            IResult::Error(_) => panic!("Did not expect an error"),
            IResult::Incomplete(_) => panic!("Did not expect to be incomplete")
        }
    }

    #[test]
    fn blank_record_should_parse_a_BLANK_record(){
        let data = "                                                                                "
            .as_bytes();

        let result = blank_record(data);

        match result {
            IResult::Done(_,_) => assert!(true),
            IResult::Error(_) => panic!("Did not expect an error"),
            IResult::Incomplete(_) => panic!("Did not expect to be incomplete")
        }
    }
}
<|endoftext|>


<commit_before>//! The parser module is responsible for parsing FITS files.

use std::str;
use std::str::FromStr;
use super::types::{Fits, PrimaryHeader, KeywordRecord, Keyword, Value, BlankRecord};

named!(#[doc = "Will parse data from a FITS file into a `Fits` structure"], pub fits<&[u8], Fits>,
       do_parse!(
           ph: primary_header >>
               many0!(take!(2880)) >>
               (Fits::new(ph))
       ));

named!(primary_header<&[u8], PrimaryHeader>,
       do_parse!(
           records: many0!(keyword_record) >>
               end_record >>
               many0!(blank_record) >>
               (PrimaryHeader::new(records))
       ));

named!(keyword_record<&[u8], KeywordRecord>,
       do_parse!(
           key: keyword  >>
               tag!("= ") >>
           vc: valuecomment >>
               (KeywordRecord::new(key, vc.0, vc.1))
       ));

named!(keyword<&[u8], Keyword>,
       map_res!(
           map_res!(
               take!(8),
               str::from_utf8),
           Keyword::from_str
       ));

named!(valuecomment<&[u8], (&str, Option<&str>)>,
       flat_map!(
           take!(70),
           pair!(
               value,
               opt!(comment)
           )));

named!(value<&[u8], &str>,
       map_res!(
           is_not!("/"), // TODO Differentiate on the possible value types
           str::from_utf8
       )
);

named!(character_string<&[u8], Value>,
       map!(
           map_res!(
               delimited!(
                   tag!("'"),
                   take_while!(is_allowed_in_character_string),
                   tag!("'")
               ),
               str::from_utf8
           ),
           Value::CharacterString
       ));

fn is_allowed_in_character_string(chr: u8) -> bool {
    is_restricted_ascii(chr) && chr != 39
}

named!(comment<&[u8], &str>,
       map_res!(
           do_parse!(
               tag!("/") >>
                   comment: take_while!(is_comment_character) >>
                   (comment)
           ),
           str::from_utf8
       ));

fn is_comment_character(chr: u8) -> bool {
    is_restricted_ascii(chr)
}

fn is_restricted_ascii(chr: u8) -> bool {
    32u8 <= chr && chr <= 126u8
}

named!(end_record<&[u8], Keyword>,
       map!(
           flat_map!(
               take!(80),
               pair!(tag!("END"), many0!(tag!(" ")))
           ),
           |_| { Keyword::END }
       ));

named!(blank_record<&[u8], BlankRecord>,
       map!(
           count!(tag!(" "), 80),
           |_| { BlankRecord }
       ));

#[cfg(test)]
mod tests {
    use nom::{IResult};
    use super::super::types::{Fits, PrimaryHeader, KeywordRecord, Keyword, Value, BlankRecord};
    use super::{fits, primary_header, keyword_record, keyword, valuecomment, character_string, end_record, blank_record};

    #[test]
    fn it_should_parse_a_fits_file(){
        let data = include_bytes!("../../assets/images/k2-trappist1-unofficial-tpf-long-cadence.fits");

        let result = fits(data);

        match result {
            IResult::Done(_, f) => {
                assert_eq!(f, Fits::new(long_cadence_header()));
            },
            IResult::Error(_) => panic!("Did not expect an error"),
            IResult::Incomplete(_) => panic!("Did not expect to be incomplete")
        }
    }

    #[test]
    fn primary_header_should_parse_a_primary_header(){
        let data = include_bytes!("../../assets/images/k2-trappist1-unofficial-tpf-long-cadence.fits");

        let result = primary_header(&data[0..(2*2880)]);

        match result {
            IResult::Done(_, h) => assert_eq!(h, long_cadence_header()),
            IResult::Error(_) => panic!("Did not expect an error"),
            IResult::Incomplete(_) => panic!("Did not expect to be incomplete")
        }
    }

    fn long_cadence_header<'a>() -> PrimaryHeader<'a> {
        PrimaryHeader::new(vec!(
            KeywordRecord::new(Keyword::SIMPLE, "                   T ",
                               Option::Some(" conforms to FITS standards                     ")),
            KeywordRecord::new(Keyword::BITPIX, "                   8 ",
                               Option::Some(" array data type                                ")),
            KeywordRecord::new(Keyword::NAXIS, "                   0 ",
                               Option::Some(" number of array dimensions                     ")),
            KeywordRecord::new(Keyword::EXTEND, "                   T ",
                               Option::Some(" file contains extensions                       ")),
            KeywordRecord::new(Keyword::NEXTEND, "                   2 ",
                               Option::Some(" number of standard extensions                  ")),
            KeywordRecord::new(Keyword::EXTNAME, "'PRIMARY '           ",
                               Option::Some(" name of extension                              ")),
            KeywordRecord::new(Keyword::EXTVER, "                   1 ",
                               Option::Some(" extension version number (not format version)  ")),
            KeywordRecord::new(Keyword::ORIGIN, "'Unofficial data product' ",
                               Option::Some(" institution responsible for creating this ")),
            KeywordRecord::new(Keyword::DATE, "'2017-03-08'         ",
                               Option::Some(" file creation date.                            ")),
            KeywordRecord::new(Keyword::CREATOR, "'kadenza '           ",
                               Option::Some(" pipeline job and program u                     ")),
            KeywordRecord::new(Keyword::PROCVER, "'2.1.dev '           ",
                               Option::Some(" SW version                                     ")),
            KeywordRecord::new(Keyword::FILEVER, "'0.0     '           ",
                               Option::Some(" file format version                            ")),
            KeywordRecord::new(Keyword::TIMVERSN, "'' ",
                               Option::Some(" OGIP memo number for file format                                 ")),
            KeywordRecord::new(Keyword::TELESCOP, "'Kepler  '           ",
                               Option::Some(" telescope                                      ")),
            KeywordRecord::new(Keyword::INSTRUME, "'Kepler Photometer'  ",
                               Option::Some(" detector type                                  ")),
            KeywordRecord::new(Keyword::OBJECT, "'EPIC 200164267'     ",
                               Option::Some(" string version of target id                    ")),
            KeywordRecord::new(Keyword::KEPLERID, "           200164267 ",
                               Option::Some(" unique Kepler target identifier                ")),
            KeywordRecord::new(Keyword::CHANNEL, "                  68 ",
                               Option::Some(" CCD channel                                    ")),
            KeywordRecord::new(Keyword::MODULE, "                  19 ",
                               Option::Some(" CCD module                                     ")),
            KeywordRecord::new(Keyword::OUTPUT, "                   4 ",
                               Option::Some(" CCD output                                     ")),
            KeywordRecord::new(Keyword::CAMPAIGN, "'' ",
                               Option::Some(" Observing campaign number                                        ")),
            KeywordRecord::new(Keyword::DATA_REL, "'' ",
                               Option::Some(" data release version number                                      ")),
            KeywordRecord::new(Keyword::OBSMODE, "'long cadence'       ",
                               Option::Some(" observing mode                                 ")),
            KeywordRecord::new(Keyword::MISSION, "'K2      '           ",
                               Option::Some(" Mission name                                   ")),
            KeywordRecord::new(Keyword::TTABLEID, "'' ",
                               Option::Some(" target table id                                                  ")),
            KeywordRecord::new(Keyword::RADESYS, "'ICRS    '           ",
                               Option::Some(" reference frame of celestial coordinates       ")),
            KeywordRecord::new(Keyword::RA_OBJ, "'' ",
                               Option::Some(" [deg] right ascension                                            ")),
            KeywordRecord::new(Keyword::DEC_OBJ, "'' ",
                               Option::Some(" [deg] declination                                                ")),
            KeywordRecord::new(Keyword::EQUINOX, "              2000.0 ",
                               Option::Some(" equinox of celestial coordinate system         ")),
            KeywordRecord::new(Keyword::PMRA, " ",
                               Option::Some(" [arcsec/yr] RA proper motion                                       ")),
            KeywordRecord::new(Keyword::PMDEC, " ",
                               Option::Some(" [arcsec/yr] Dec proper motion                                      ")),
            KeywordRecord::new(Keyword::PMTOTAL, " ",
                               Option::Some(" [arcsec/yr] total proper motion                                    ")),
            KeywordRecord::new(Keyword::PARALLAX, " ",
                               Option::Some(" [arcsec] parallax                                                  ")),
            KeywordRecord::new(Keyword::GLON, " ",
                               Option::Some(" [deg] galactic longitude                                           ")),
            KeywordRecord::new(Keyword::GLAT, " ",
                               Option::Some(" [deg] galactic latitude                                            ")),
            KeywordRecord::new(Keyword::GMAG, " ",
                               Option::Some(" [mag] SDSS g band magnitude                                        ")),
            KeywordRecord::new(Keyword::RMAG, " ",
                               Option::Some(" [mag] SDSS r band magnitude                                        ")),
            KeywordRecord::new(Keyword::IMAG, " ",
                               Option::Some(" [mag] SDSS i band magnitude                                        ")),
            KeywordRecord::new(Keyword::ZMAG, " ",
                               Option::Some(" [mag] SDSS z band magnitude                                        ")),
            KeywordRecord::new(Keyword::JMAG, " ",
                               Option::Some(" [mag] J band magnitude from 2MASS                                  ")),
            KeywordRecord::new(Keyword::HMAG, " ",
                               Option::Some(" [mag] H band magnitude from 2MASS                                  ")),
            KeywordRecord::new(Keyword::KMAG, " ",
                               Option::Some(" [mag] K band magnitude from 2MASS                                  ")),
            KeywordRecord::new(Keyword::KEPMAG, " ",
                               Option::Some(" [mag] Kepler magnitude (Kp)                                        ")),
            KeywordRecord::new(Keyword::GRCOLOR, " ",
                               Option::Some(" [mag] (g-r) color, SDSS bands                                      ")),
            KeywordRecord::new(Keyword::JKCOLOR, " ",
                               Option::Some(" [mag] (J-K) color, 2MASS bands                                     ")),
            KeywordRecord::new(Keyword::GKCOLOR, " ",
                               Option::Some(" [mag] (g-K) color, SDSS g - 2MASS K                                ")),
            KeywordRecord::new(Keyword::TEFF, " ",
                               Option::Some(" [K] Effective temperature                                          ")),
            KeywordRecord::new(Keyword::LOGG, " ",
                               Option::Some(" [cm/s2] log10 surface gravity                                      ")),
            KeywordRecord::new(Keyword::FEH, " ",
                               Option::Some(" [log10([Fe/H])]  metallicity                                       ")),
            KeywordRecord::new(Keyword::EBMINUSV, " ",
                               Option::Some(" [mag] E(B-V) reddening                                             ")),
            KeywordRecord::new(Keyword::AV, " ",
                               Option::Some(" [mag] A_v extinction                                               ")),
            KeywordRecord::new(Keyword::RADIUS, " ",
                               Option::Some(" [solar radii] stellar radius                                       ")),
            KeywordRecord::new(Keyword::TMINDEX, " ",
                               Option::Some(" unique 2MASS catalog ID                                            ")),
            KeywordRecord::new(Keyword::CHECKSUM, "'7k7A7h637h697h69'   ",
                               Option::Some(" HDU checksum updated 2017-03-08T02:47:56       ")),
            KeywordRecord::new(Keyword::DATASUM, "'0       '           ",
                               Option::Some(" data unit checksum updated 2017-03-08T02:47:56 ")),
        ))
    }

    #[test]
    fn keyword_record_should_parse_a_keyword_record(){
        let data = "OBJECT  = 'EPIC 200164267'     / string version of target id                    "
            .as_bytes();

        let result = keyword_record(data);

        match result {
            IResult::Done(_,k) => {
                assert_eq!(k, KeywordRecord::new(
                    Keyword::OBJECT,
                    "'EPIC 200164267'     ",
                    Option::Some(" string version of target id                    ")
                ))
            },
            IResult::Error(_) => panic!("Did not expect an error"),
            IResult::Incomplete(_) => panic!("Did not expect to be incomplete")
        }
    }

    #[test]
    fn valuecomment_should_parse_a_valuecomment(){
        let data = "'EPIC 200164267'     / string version of target id                    "
            .as_bytes();

        let result = valuecomment(data);

        match result {
            IResult::Done(_, (value, comment)) => {
                assert_eq!(value, "'EPIC 200164267'     ");
                assert_eq!(comment, Option::Some(" string version of target id                    "));
            },
            IResult::Error(_) => panic!("Did not expect an error"),
            IResult::Incomplete(_) => panic!("Did not expect to be incomplete")
        }
    }

    #[test]
    fn character_string_should_parse_an_quote_delimited_string(){
        let data = "'EPIC 200164267'"
            .as_bytes();

        let result = character_string(data);

        match result {
            IResult::Done(_, value) => {
                assert_eq!(value, Value::CharacterString("EPIC 200164267"));
            },
            IResult::Error(_) => panic!("Did not expect an error"),
            IResult::Incomplete(_) => panic!("Did not expect to be incomplete")
        }
    }

    #[test]
    fn keyword_should_parse_a_keyword(){
        let data = "OBJECT  "
            .as_bytes();

        let result = keyword(data);

        match result {
            IResult::Done(_, keyword) => assert_eq!(keyword, Keyword::OBJECT),
            IResult::Error(_) => panic!("Did not expect an error"),
            IResult::Incomplete(_) => panic!("Did not expect to be incomplete")
        }
    }

    #[test]
    #[allow(non_snake_case)]
    fn end_record_should_parse_an_END_record(){
        let data = "END                                                                             "
            .as_bytes();

        let result = end_record(data);

        match result {
            IResult::Done(_, keyword) => assert_eq!(keyword, Keyword::END),
            IResult::Error(_) => panic!("Did not expect an error"),
            IResult::Incomplete(_) => panic!("Did not expect to be incomplete")
        }
    }

    #[test]
    #[allow(non_snake_case)]
    fn blank_record_should_parse_a_BLANK_record(){
        let data = "                                                                                "
            .as_bytes();

        let result = blank_record(data);

        match result {
            IResult::Done(_, record) => assert_eq!(record, BlankRecord),
            IResult::Error(_) => panic!("Did not expect an error"),
            IResult::Incomplete(_) => panic!("Did not expect to be incomplete")
        }
    }
}
<commit_msg>Allow whitespace around a character string<commit_after>//! The parser module is responsible for parsing FITS files.

use std::str;
use std::str::FromStr;
use super::types::{Fits, PrimaryHeader, KeywordRecord, Keyword, Value, BlankRecord};

named!(#[doc = "Will parse data from a FITS file into a `Fits` structure"], pub fits<&[u8], Fits>,
       do_parse!(
           ph: primary_header >>
               many0!(take!(2880)) >>
               (Fits::new(ph))
       ));

named!(primary_header<&[u8], PrimaryHeader>,
       do_parse!(
           records: many0!(keyword_record) >>
               end_record >>
               many0!(blank_record) >>
               (PrimaryHeader::new(records))
       ));

named!(keyword_record<&[u8], KeywordRecord>,
       do_parse!(
           key: keyword  >>
               tag!("= ") >>
           vc: valuecomment >>
               (KeywordRecord::new(key, vc.0, vc.1))
       ));

named!(keyword<&[u8], Keyword>,
       map_res!(
           map_res!(
               take!(8),
               str::from_utf8),
           Keyword::from_str
       ));

named!(valuecomment<&[u8], (&str, Option<&str>)>,
       flat_map!(
           take!(70),
           pair!(
               value,
               opt!(comment)
           )));

named!(value<&[u8], &str>,
       map_res!(
           is_not!("/"), // TODO Differentiate on the possible value types
           str::from_utf8
       )
);

named!(character_string<&[u8], Value>,
       map!(
           map_res!(
               ws!(delimited!(
                   tag!("'"),
                   take_while!(is_allowed_in_character_string),
                   tag!("'")
               )),
               str::from_utf8
           ),
           Value::CharacterString
       ));

fn is_allowed_in_character_string(chr: u8) -> bool {
    is_restricted_ascii(chr) && chr != 39
}

named!(comment<&[u8], &str>,
       map_res!(
           do_parse!(
               tag!("/") >>
                   comment: take_while!(is_comment_character) >>
                   (comment)
           ),
           str::from_utf8
       ));

fn is_comment_character(chr: u8) -> bool {
    is_restricted_ascii(chr)
}

fn is_restricted_ascii(chr: u8) -> bool {
    32u8 <= chr && chr <= 126u8
}

named!(end_record<&[u8], Keyword>,
       map!(
           flat_map!(
               take!(80),
               pair!(tag!("END"), many0!(tag!(" ")))
           ),
           |_| { Keyword::END }
       ));

named!(blank_record<&[u8], BlankRecord>,
       map!(
           count!(tag!(" "), 80),
           |_| { BlankRecord }
       ));

#[cfg(test)]
mod tests {
    use nom::{IResult};
    use super::super::types::{Fits, PrimaryHeader, KeywordRecord, Keyword, Value, BlankRecord};
    use super::{fits, primary_header, keyword_record, keyword, valuecomment, character_string, end_record, blank_record};

    #[test]
    fn it_should_parse_a_fits_file(){
        let data = include_bytes!("../../assets/images/k2-trappist1-unofficial-tpf-long-cadence.fits");

        let result = fits(data);

        match result {
            IResult::Done(_, f) => {
                assert_eq!(f, Fits::new(long_cadence_header()));
            },
            IResult::Error(_) => panic!("Did not expect an error"),
            IResult::Incomplete(_) => panic!("Did not expect to be incomplete")
        }
    }

    #[test]
    fn primary_header_should_parse_a_primary_header(){
        let data = include_bytes!("../../assets/images/k2-trappist1-unofficial-tpf-long-cadence.fits");

        let result = primary_header(&data[0..(2*2880)]);

        match result {
            IResult::Done(_, h) => assert_eq!(h, long_cadence_header()),
            IResult::Error(_) => panic!("Did not expect an error"),
            IResult::Incomplete(_) => panic!("Did not expect to be incomplete")
        }
    }

    fn long_cadence_header<'a>() -> PrimaryHeader<'a> {
        PrimaryHeader::new(vec!(
            KeywordRecord::new(Keyword::SIMPLE, "                   T ",
                               Option::Some(" conforms to FITS standards                     ")),
            KeywordRecord::new(Keyword::BITPIX, "                   8 ",
                               Option::Some(" array data type                                ")),
            KeywordRecord::new(Keyword::NAXIS, "                   0 ",
                               Option::Some(" number of array dimensions                     ")),
            KeywordRecord::new(Keyword::EXTEND, "                   T ",
                               Option::Some(" file contains extensions                       ")),
            KeywordRecord::new(Keyword::NEXTEND, "                   2 ",
                               Option::Some(" number of standard extensions                  ")),
            KeywordRecord::new(Keyword::EXTNAME, "'PRIMARY '           ",
                               Option::Some(" name of extension                              ")),
            KeywordRecord::new(Keyword::EXTVER, "                   1 ",
                               Option::Some(" extension version number (not format version)  ")),
            KeywordRecord::new(Keyword::ORIGIN, "'Unofficial data product' ",
                               Option::Some(" institution responsible for creating this ")),
            KeywordRecord::new(Keyword::DATE, "'2017-03-08'         ",
                               Option::Some(" file creation date.                            ")),
            KeywordRecord::new(Keyword::CREATOR, "'kadenza '           ",
                               Option::Some(" pipeline job and program u                     ")),
            KeywordRecord::new(Keyword::PROCVER, "'2.1.dev '           ",
                               Option::Some(" SW version                                     ")),
            KeywordRecord::new(Keyword::FILEVER, "'0.0     '           ",
                               Option::Some(" file format version                            ")),
            KeywordRecord::new(Keyword::TIMVERSN, "'' ",
                               Option::Some(" OGIP memo number for file format                                 ")),
            KeywordRecord::new(Keyword::TELESCOP, "'Kepler  '           ",
                               Option::Some(" telescope                                      ")),
            KeywordRecord::new(Keyword::INSTRUME, "'Kepler Photometer'  ",
                               Option::Some(" detector type                                  ")),
            KeywordRecord::new(Keyword::OBJECT, "'EPIC 200164267'     ",
                               Option::Some(" string version of target id                    ")),
            KeywordRecord::new(Keyword::KEPLERID, "           200164267 ",
                               Option::Some(" unique Kepler target identifier                ")),
            KeywordRecord::new(Keyword::CHANNEL, "                  68 ",
                               Option::Some(" CCD channel                                    ")),
            KeywordRecord::new(Keyword::MODULE, "                  19 ",
                               Option::Some(" CCD module                                     ")),
            KeywordRecord::new(Keyword::OUTPUT, "                   4 ",
                               Option::Some(" CCD output                                     ")),
            KeywordRecord::new(Keyword::CAMPAIGN, "'' ",
                               Option::Some(" Observing campaign number                                        ")),
            KeywordRecord::new(Keyword::DATA_REL, "'' ",
                               Option::Some(" data release version number                                      ")),
            KeywordRecord::new(Keyword::OBSMODE, "'long cadence'       ",
                               Option::Some(" observing mode                                 ")),
            KeywordRecord::new(Keyword::MISSION, "'K2      '           ",
                               Option::Some(" Mission name                                   ")),
            KeywordRecord::new(Keyword::TTABLEID, "'' ",
                               Option::Some(" target table id                                                  ")),
            KeywordRecord::new(Keyword::RADESYS, "'ICRS    '           ",
                               Option::Some(" reference frame of celestial coordinates       ")),
            KeywordRecord::new(Keyword::RA_OBJ, "'' ",
                               Option::Some(" [deg] right ascension                                            ")),
            KeywordRecord::new(Keyword::DEC_OBJ, "'' ",
                               Option::Some(" [deg] declination                                                ")),
            KeywordRecord::new(Keyword::EQUINOX, "              2000.0 ",
                               Option::Some(" equinox of celestial coordinate system         ")),
            KeywordRecord::new(Keyword::PMRA, " ",
                               Option::Some(" [arcsec/yr] RA proper motion                                       ")),
            KeywordRecord::new(Keyword::PMDEC, " ",
                               Option::Some(" [arcsec/yr] Dec proper motion                                      ")),
            KeywordRecord::new(Keyword::PMTOTAL, " ",
                               Option::Some(" [arcsec/yr] total proper motion                                    ")),
            KeywordRecord::new(Keyword::PARALLAX, " ",
                               Option::Some(" [arcsec] parallax                                                  ")),
            KeywordRecord::new(Keyword::GLON, " ",
                               Option::Some(" [deg] galactic longitude                                           ")),
            KeywordRecord::new(Keyword::GLAT, " ",
                               Option::Some(" [deg] galactic latitude                                            ")),
            KeywordRecord::new(Keyword::GMAG, " ",
                               Option::Some(" [mag] SDSS g band magnitude                                        ")),
            KeywordRecord::new(Keyword::RMAG, " ",
                               Option::Some(" [mag] SDSS r band magnitude                                        ")),
            KeywordRecord::new(Keyword::IMAG, " ",
                               Option::Some(" [mag] SDSS i band magnitude                                        ")),
            KeywordRecord::new(Keyword::ZMAG, " ",
                               Option::Some(" [mag] SDSS z band magnitude                                        ")),
            KeywordRecord::new(Keyword::JMAG, " ",
                               Option::Some(" [mag] J band magnitude from 2MASS                                  ")),
            KeywordRecord::new(Keyword::HMAG, " ",
                               Option::Some(" [mag] H band magnitude from 2MASS                                  ")),
            KeywordRecord::new(Keyword::KMAG, " ",
                               Option::Some(" [mag] K band magnitude from 2MASS                                  ")),
            KeywordRecord::new(Keyword::KEPMAG, " ",
                               Option::Some(" [mag] Kepler magnitude (Kp)                                        ")),
            KeywordRecord::new(Keyword::GRCOLOR, " ",
                               Option::Some(" [mag] (g-r) color, SDSS bands                                      ")),
            KeywordRecord::new(Keyword::JKCOLOR, " ",
                               Option::Some(" [mag] (J-K) color, 2MASS bands                                     ")),
            KeywordRecord::new(Keyword::GKCOLOR, " ",
                               Option::Some(" [mag] (g-K) color, SDSS g - 2MASS K                                ")),
            KeywordRecord::new(Keyword::TEFF, " ",
                               Option::Some(" [K] Effective temperature                                          ")),
            KeywordRecord::new(Keyword::LOGG, " ",
                               Option::Some(" [cm/s2] log10 surface gravity                                      ")),
            KeywordRecord::new(Keyword::FEH, " ",
                               Option::Some(" [log10([Fe/H])]  metallicity                                       ")),
            KeywordRecord::new(Keyword::EBMINUSV, " ",
                               Option::Some(" [mag] E(B-V) reddening                                             ")),
            KeywordRecord::new(Keyword::AV, " ",
                               Option::Some(" [mag] A_v extinction                                               ")),
            KeywordRecord::new(Keyword::RADIUS, " ",
                               Option::Some(" [solar radii] stellar radius                                       ")),
            KeywordRecord::new(Keyword::TMINDEX, " ",
                               Option::Some(" unique 2MASS catalog ID                                            ")),
            KeywordRecord::new(Keyword::CHECKSUM, "'7k7A7h637h697h69'   ",
                               Option::Some(" HDU checksum updated 2017-03-08T02:47:56       ")),
            KeywordRecord::new(Keyword::DATASUM, "'0       '           ",
                               Option::Some(" data unit checksum updated 2017-03-08T02:47:56 ")),
        ))
    }

    #[test]
    fn keyword_record_should_parse_a_keyword_record(){
        let data = "OBJECT  = 'EPIC 200164267'     / string version of target id                    "
            .as_bytes();

        let result = keyword_record(data);

        match result {
            IResult::Done(_,k) => {
                assert_eq!(k, KeywordRecord::new(
                    Keyword::OBJECT,
                    "'EPIC 200164267'     ",
                    Option::Some(" string version of target id                    ")
                ))
            },
            IResult::Error(_) => panic!("Did not expect an error"),
            IResult::Incomplete(_) => panic!("Did not expect to be incomplete")
        }
    }

    #[test]
    fn valuecomment_should_parse_a_valuecomment(){
        let data = "'EPIC 200164267'     / string version of target id                    "
            .as_bytes();

        let result = valuecomment(data);

        match result {
            IResult::Done(_, (value, comment)) => {
                assert_eq!(value, "'EPIC 200164267'     ");
                assert_eq!(comment, Option::Some(" string version of target id                    "));
            },
            IResult::Error(_) => panic!("Did not expect an error"),
            IResult::Incomplete(_) => panic!("Did not expect to be incomplete")
        }
    }

    #[test]
    fn character_string_should_parse_an_quote_delimited_string(){
        let data = "   'EPIC 200164267'   "
            .as_bytes();

        let result = character_string(data);

        match result {
            IResult::Done(_, value) => {
                assert_eq!(value, Value::CharacterString("EPIC 200164267"));
            },
            IResult::Error(_) => panic!("Did not expect an error"),
            IResult::Incomplete(_) => panic!("Did not expect to be incomplete")
        }
    }

    #[test]
    fn keyword_should_parse_a_keyword(){
        let data = "OBJECT  "
            .as_bytes();

        let result = keyword(data);

        match result {
            IResult::Done(_, keyword) => assert_eq!(keyword, Keyword::OBJECT),
            IResult::Error(_) => panic!("Did not expect an error"),
            IResult::Incomplete(_) => panic!("Did not expect to be incomplete")
        }
    }

    #[test]
    #[allow(non_snake_case)]
    fn end_record_should_parse_an_END_record(){
        let data = "END                                                                             "
            .as_bytes();

        let result = end_record(data);

        match result {
            IResult::Done(_, keyword) => assert_eq!(keyword, Keyword::END),
            IResult::Error(_) => panic!("Did not expect an error"),
            IResult::Incomplete(_) => panic!("Did not expect to be incomplete")
        }
    }

    #[test]
    #[allow(non_snake_case)]
    fn blank_record_should_parse_a_BLANK_record(){
        let data = "                                                                                "
            .as_bytes();

        let result = blank_record(data);

        match result {
            IResult::Done(_, record) => assert_eq!(record, BlankRecord),
            IResult::Error(_) => panic!("Did not expect an error"),
            IResult::Incomplete(_) => panic!("Did not expect to be incomplete")
        }
    }
}
<|endoftext|>


<commit_before>use std::io::BufReader;
use std::net::TcpStream;
#[cfg(unix)]
use std::os::unix::net::UnixStream;
use std::time::Duration;
#[cfg(unix)]
use url::Host;
use url::Url;

use error::MemcacheError;
use protocol::{AsciiProtocol, BinaryProtocol, Protocol};
use stream::Stream;
use stream::UdpStream;

/// a connection to the memcached server
pub struct Connection {
    pub protocol: Protocol,
    pub url: String,
}

impl Connection {
    pub(crate) fn connect(url: &Url) -> Result<Self, MemcacheError> {
        let parts: Vec<&str> = url.scheme().split("+").collect();
        if parts.len() != 1 && parts.len() != 2 || parts[0] != "memcache" {
            return Err(MemcacheError::ClientError(
                "memcache URL's scheme should start with 'memcache'".into(),
            ));
        }
        if parts.len() == 2 && !(parts[1] != "tcp" || parts[1] != "udp" || parts[1] != "unix") {
            return Err(MemcacheError::ClientError(
                "memcache URL's scheme should be 'memcache+tcp' or 'memcache+udp' or 'memcache+unix'".into(),
            ));
        }

        let is_ascii = url.query_pairs().any(|(ref k, ref v)| k == "protocol" && v == "ascii");

        let mut is_udp = url.query_pairs().any(|(ref k, ref v)| k == "udp" && v == "true");

        if parts.len() == 2 && parts[1] == "udp" {
            // scheme specify have high priority.
            is_udp = true;
        }
        if is_udp {
            let udp_stream = Stream::Udp(UdpStream::new(url.clone())?);
            if is_ascii {
                return Ok(Connection {
                    url: url.to_string(),
                    protocol: Protocol::Ascii(AsciiProtocol {
                        reader: BufReader::new(udp_stream)
                    }),
                });
            } else {
                return Ok(Connection {
                    url: url.to_string(),
                    protocol: Protocol::Binary(BinaryProtocol {
                        stream: udp_stream }),
                });
            }
        }

        #[cfg(unix)]
        {
            if url.host() == Some(Host::Domain("")) && url.port() == None {
                let unix_stream = Stream::Unix(UnixStream::connect(url.path())?);
                if is_ascii {
                    return Ok(Connection {
                        url: url.to_string(),
                        protocol: Protocol::Ascii(AsciiProtocol {
                            reader: BufReader::new(unix_stream),
                        }),
                    });
                } else {
                    return Ok(Connection {
                        url: url.to_string(),
                        protocol: Protocol::Binary(BinaryProtocol {
                            stream: unix_stream,
                        }),
                    });
                }
            }
        }

        let tcp_stream = TcpStream::connect(url.clone())?;

        let disable_tcp_nodelay = url
            .query_pairs()
            .any(|(ref k, ref v)| k == "tcp_nodelay" && v == "false");
        if !disable_tcp_nodelay {
            tcp_stream.set_nodelay(true)?;
        }

        let timeout = url
            .query_pairs()
            .find(|&(ref k, ref _v)| k == "timeout")
            .and_then(|(ref _k, ref v)| v.parse::<u64>().ok())
            .map(Duration::from_secs);
        if timeout.is_some() {
            tcp_stream.set_read_timeout(timeout)?;
            tcp_stream.set_write_timeout(timeout)?;
        }

        if is_ascii {
            return Ok(Connection {
                url: url.to_string(),
                protocol: Protocol::Ascii(AsciiProtocol {
                    reader: BufReader::new(Stream::Tcp(tcp_stream)),
                }),
            });
        }
        return Ok(Connection {
            url: url.to_string(),
            protocol: Protocol::Binary(BinaryProtocol {
                stream: Stream::Tcp(tcp_stream),
            }),
        });
    }
}
<commit_msg>:art: Format codes using cargo fmt<commit_after>use std::io::BufReader;
use std::net::TcpStream;
#[cfg(unix)]
use std::os::unix::net::UnixStream;
use std::time::Duration;
#[cfg(unix)]
use url::Host;
use url::Url;

use error::MemcacheError;
use protocol::{AsciiProtocol, BinaryProtocol, Protocol};
use stream::Stream;
use stream::UdpStream;

/// a connection to the memcached server
pub struct Connection {
    pub protocol: Protocol,
    pub url: String,
}

impl Connection {
    pub(crate) fn connect(url: &Url) -> Result<Self, MemcacheError> {
        let parts: Vec<&str> = url.scheme().split("+").collect();
        if parts.len() != 1 && parts.len() != 2 || parts[0] != "memcache" {
            return Err(MemcacheError::ClientError(
                "memcache URL's scheme should start with 'memcache'".into(),
            ));
        }
        if parts.len() == 2 && !(parts[1] != "tcp" || parts[1] != "udp" || parts[1] != "unix") {
            return Err(MemcacheError::ClientError(
                "memcache URL's scheme should be 'memcache+tcp' or 'memcache+udp' or 'memcache+unix'".into(),
            ));
        }

        let is_ascii = url.query_pairs().any(|(ref k, ref v)| k == "protocol" && v == "ascii");

        let mut is_udp = url.query_pairs().any(|(ref k, ref v)| k == "udp" && v == "true");

        if parts.len() == 2 && parts[1] == "udp" {
            // scheme specify have high priority.
            is_udp = true;
        }
        if is_udp {
            let udp_stream = Stream::Udp(UdpStream::new(url.clone())?);
            if is_ascii {
                return Ok(Connection {
                    url: url.to_string(),
                    protocol: Protocol::Ascii(AsciiProtocol {
                        reader: BufReader::new(udp_stream),
                    }),
                });
            } else {
                return Ok(Connection {
                    url: url.to_string(),
                    protocol: Protocol::Binary(BinaryProtocol { stream: udp_stream }),
                });
            }
        }

        #[cfg(unix)]
        {
            if url.host() == Some(Host::Domain("")) && url.port() == None {
                let unix_stream = Stream::Unix(UnixStream::connect(url.path())?);
                if is_ascii {
                    return Ok(Connection {
                        url: url.to_string(),
                        protocol: Protocol::Ascii(AsciiProtocol {
                            reader: BufReader::new(unix_stream),
                        }),
                    });
                } else {
                    return Ok(Connection {
                        url: url.to_string(),
                        protocol: Protocol::Binary(BinaryProtocol { stream: unix_stream }),
                    });
                }
            }
        }

        let tcp_stream = TcpStream::connect(url.clone())?;

        let disable_tcp_nodelay = url
            .query_pairs()
            .any(|(ref k, ref v)| k == "tcp_nodelay" && v == "false");
        if !disable_tcp_nodelay {
            tcp_stream.set_nodelay(true)?;
        }

        let timeout = url
            .query_pairs()
            .find(|&(ref k, ref _v)| k == "timeout")
            .and_then(|(ref _k, ref v)| v.parse::<u64>().ok())
            .map(Duration::from_secs);
        if timeout.is_some() {
            tcp_stream.set_read_timeout(timeout)?;
            tcp_stream.set_write_timeout(timeout)?;
        }

        if is_ascii {
            return Ok(Connection {
                url: url.to_string(),
                protocol: Protocol::Ascii(AsciiProtocol {
                    reader: BufReader::new(Stream::Tcp(tcp_stream)),
                }),
            });
        }
        return Ok(Connection {
            url: url.to_string(),
            protocol: Protocol::Binary(BinaryProtocol {
                stream: Stream::Tcp(tcp_stream),
            }),
        });
    }
}
<|endoftext|>


<commit_before>use ppu::{Color, SCREEN_BUFFER_SIZE, SCREEN_HEIGHT, SCREEN_WIDTH};
use screen::Screen;
use sdl2::{Sdl, VideoSubsystem};
use sdl2::render::{Renderer, Texture};
use sdl2::pixels::PixelFormatEnum;
use sdl2::rect::Rect;

#[allow(dead_code)]
pub struct SDLScreen<'a> {
    video: VideoSubsystem,
    renderer: Renderer<'a>,
    texture: Texture,
}

const SCALE: usize = 3;

impl<'a> SDLScreen<'a> {
    pub fn new(sdl_context: &Sdl) -> SDLScreen<'a> {
        let video_subsystem = sdl_context.video().unwrap();

        let window = video_subsystem.window("Corrosion",
                                            (SCREEN_WIDTH * SCALE) as u32,
                                            (SCREEN_HEIGHT * SCALE) as u32)
                                    .position_centered()
                                    .opengl()
                                    .build()
                                    .unwrap();

        let mut renderer = window.renderer().present_vsync().build().unwrap();
        renderer.set_logical_size(SCREEN_WIDTH as u32, SCREEN_HEIGHT as u32).unwrap();

        let texture = renderer.create_texture_streaming(PixelFormatEnum::RGB24,
                                                        SCREEN_WIDTH as u32,
                                                        SCREEN_HEIGHT as u32)
                              .unwrap();
        SDLScreen {
            video: video_subsystem,
            renderer: renderer,
            texture: texture,
        }
    }
}

// Using a hard-coded palette for now. Will add .pal file support later,
// maybe proper NTSC video decoding eventually.
#[cfg_attr(rustfmt, rustfmt_skip)]
static PALETTE: [u8; 192] = [
    84, 84, 84,       0, 30, 116,       8, 16, 144,       48, 0, 136,       68, 0, 100,       92, 0, 48,        84, 4, 0,         60, 24, 0,        32, 42, 0,        8, 58, 0,         0, 64, 0,         0, 60, 0,         0, 50, 60,        0, 0, 0,          0, 0, 0,    0, 0, 0,
    152, 150, 152,    8, 76, 196,       48, 50, 236,      92, 30, 228,      136, 20, 176,     160, 20, 100,     152, 34, 32,      120, 60, 0,       84, 90, 0,        40, 114, 0,       8, 124, 0,        0, 118, 40,       0, 102, 120,      0, 0, 0,          0, 0, 0,    0, 0, 0,
    236, 238, 236,    76, 154, 236,     120, 124, 236,    176, 98, 236,     228, 84, 236,     236, 88, 180,     236, 106, 100,    212, 136, 32,     160, 170, 0,      116, 196, 0,      76, 208, 32,      56, 204, 108,     56, 180, 204,     60, 60, 60,       0, 0, 0,    0, 0, 0,
    236, 238, 236,    168, 204, 236,    188, 188, 236,    212, 178, 236,    236, 174, 236,    236, 174, 212,    236, 180, 176,    228, 196, 144,    204, 210, 120,    180, 222, 120,    168, 226, 144,    152, 226, 180,    160, 214, 228,    160, 162, 160,    0, 0, 0,    0, 0, 0,
];

fn copy_to_texture(buf: &[Color; SCREEN_BUFFER_SIZE], buffer: &mut [u8], pitch: usize) {
    for y in 0..SCREEN_HEIGHT {
        for x in 0..SCREEN_HEIGHT {
            let nes_idx = y * SCREEN_WIDTH + x;
            let color = buf[nes_idx];
            let pal_idx = color.bits() as usize * 3;
            let offset = y * pitch + x * 3;
            buffer[offset + 0] = PALETTE[pal_idx + 0];
            buffer[offset + 1] = PALETTE[pal_idx + 1];
            buffer[offset + 2] = PALETTE[pal_idx + 2];
        }
    }
}

impl<'a> Screen for SDLScreen<'a> {
    fn draw(&mut self, buf: &[Color; SCREEN_BUFFER_SIZE]) {
        self.texture
            .with_lock(None, |buffer: &mut [u8], pitch: usize| {
                copy_to_texture(buf, buffer, pitch);
            })
            .unwrap();

        self.renderer.copy(&self.texture,
                           None,
                           Some(Rect::new(0, 0, SCREEN_WIDTH as u32, SCREEN_HEIGHT as u32)));
        self.renderer.present();
    }
}
<commit_msg>Fixed right side of screen not drawing.<commit_after>use ppu::{Color, SCREEN_BUFFER_SIZE, SCREEN_HEIGHT, SCREEN_WIDTH};
use screen::Screen;
use sdl2::{Sdl, VideoSubsystem};
use sdl2::render::{Renderer, Texture};
use sdl2::pixels::PixelFormatEnum;
use sdl2::rect::Rect;

#[allow(dead_code)]
pub struct SDLScreen<'a> {
    video: VideoSubsystem,
    renderer: Renderer<'a>,
    texture: Texture,
}

const SCALE: usize = 3;

impl<'a> SDLScreen<'a> {
    pub fn new(sdl_context: &Sdl) -> SDLScreen<'a> {
        let video_subsystem = sdl_context.video().unwrap();

        let window = video_subsystem.window("Corrosion",
                                            (SCREEN_WIDTH * SCALE) as u32,
                                            (SCREEN_HEIGHT * SCALE) as u32)
                                    .position_centered()
                                    .opengl()
                                    .build()
                                    .unwrap();

        let mut renderer = window.renderer().present_vsync().build().unwrap();
        renderer.set_logical_size(SCREEN_WIDTH as u32, SCREEN_HEIGHT as u32).unwrap();

        let texture = renderer.create_texture_streaming(PixelFormatEnum::RGB24,
                                                        SCREEN_WIDTH as u32,
                                                        SCREEN_HEIGHT as u32)
                              .unwrap();
        SDLScreen {
            video: video_subsystem,
            renderer: renderer,
            texture: texture,
        }
    }
}

// Using a hard-coded palette for now. Will add .pal file support later,
// maybe proper NTSC video decoding eventually.
#[cfg_attr(rustfmt, rustfmt_skip)]
static PALETTE: [u8; 192] = [
    84, 84, 84,       0, 30, 116,       8, 16, 144,       48, 0, 136,       68, 0, 100,       92, 0, 48,        84, 4, 0,         60, 24, 0,        32, 42, 0,        8, 58, 0,         0, 64, 0,         0, 60, 0,         0, 50, 60,        0, 0, 0,          0, 0, 0,    0, 0, 0,
    152, 150, 152,    8, 76, 196,       48, 50, 236,      92, 30, 228,      136, 20, 176,     160, 20, 100,     152, 34, 32,      120, 60, 0,       84, 90, 0,        40, 114, 0,       8, 124, 0,        0, 118, 40,       0, 102, 120,      0, 0, 0,          0, 0, 0,    0, 0, 0,
    236, 238, 236,    76, 154, 236,     120, 124, 236,    176, 98, 236,     228, 84, 236,     236, 88, 180,     236, 106, 100,    212, 136, 32,     160, 170, 0,      116, 196, 0,      76, 208, 32,      56, 204, 108,     56, 180, 204,     60, 60, 60,       0, 0, 0,    0, 0, 0,
    236, 238, 236,    168, 204, 236,    188, 188, 236,    212, 178, 236,    236, 174, 236,    236, 174, 212,    236, 180, 176,    228, 196, 144,    204, 210, 120,    180, 222, 120,    168, 226, 144,    152, 226, 180,    160, 214, 228,    160, 162, 160,    0, 0, 0,    0, 0, 0,
];

fn copy_to_texture(buf: &[Color; SCREEN_BUFFER_SIZE], buffer: &mut [u8], pitch: usize) {
    for y in 0..SCREEN_HEIGHT {
        for x in 0..SCREEN_WIDTH {
            let nes_idx = y * SCREEN_WIDTH + x;
            let color = buf[nes_idx];
            let pal_idx = color.bits() as usize * 3;
            let offset = y * pitch + x * 3;
            buffer[offset + 0] = PALETTE[pal_idx + 0];
            buffer[offset + 1] = PALETTE[pal_idx + 1];
            buffer[offset + 2] = PALETTE[pal_idx + 2];
        }
    }
}

impl<'a> Screen for SDLScreen<'a> {
    fn draw(&mut self, buf: &[Color; SCREEN_BUFFER_SIZE]) {
        self.texture
            .with_lock(None, |buffer: &mut [u8], pitch: usize| {
                copy_to_texture(buf, buffer, pitch);
            })
            .unwrap();

        self.renderer.copy(&self.texture,
                           None,
                           Some(Rect::new(0, 0, SCREEN_WIDTH as u32, SCREEN_HEIGHT as u32)));
        self.renderer.present();
    }
}
<|endoftext|>


<commit_before>use std::os::unix::process::CommandExt;
use std::process::Command;

// clap exposes the macro value_t!
use clap::value_t;

// chrono needs this to call Utc.datetime_from_str().
use chrono::offset::TimeZone;

const SQLITE3: &'static str = "/usr/bin/sqlite3";
const DB_PATH: &'static str = "/home/kalvin/Documents/personal/expenditures.db";

const PEREXP: &'static str = "perexp";
const ESSEXP: &'static str = "essexp";

const CLAP_AMOUNT: &'static str = "amount";
const CLAP_DESCRIPTION: &'static str = "description";
const CLAP_EXPENDITURE_TYPE: &'static str = "expenditure-type";
const CLAP_TARGET_DATE: &'static str = "target-date";

#[derive(Debug)]
enum ExpenditureType {
    Personal,
    Essential,
}

#[derive(Debug)]
struct SqlOptions {
    // Expenditure type is always required in transacting expenditures.
    expenditure_type: ExpenditureType,

    // Target date is always required in transacting expenditures.
    target_date: chrono::Date<chrono::Utc>,

    // Amount and description are required for insertion.
    // They are meaningless for read-only queries.
    amount: Option<f64>,
    description: Option<String>,
}

#[derive(Debug)]
enum SqlCommand {
    Edit,
    Show(SqlOptions),
    Insert(SqlOptions),
}

fn expenditure_type_from_clap(matches: &clap::ArgMatches) -> ExpenditureType {
    let symbolic_type = matches.value_of(CLAP_EXPENDITURE_TYPE).unwrap();

    match symbolic_type {
        "p" => return ExpenditureType::Personal,
        "e" => return ExpenditureType::Essential,
        _ => (),
    };
    panic!(format!(
        "invalid {} ``{}''",
        CLAP_EXPENDITURE_TYPE, symbolic_type
    ));
}

fn expenditure_target_date_from_clap(
    matches: &clap::ArgMatches,
) -> Option<chrono::Date<chrono::Utc>> {
    if let Some(cli_target_date) = matches.value_of(CLAP_TARGET_DATE) {
        return Some(
            chrono::Utc
                .datetime_from_str(
                    &format!("{} 00:00:00", cli_target_date).to_string(),
                    "%Y-%m-%d %H:%M:%S",
                )
                .unwrap()
                .date(),
        );
    }
    None
}

fn expenditure_amount_from_clap(matches: &clap::ArgMatches) -> f64 {
    clap::value_t!(matches, CLAP_AMOUNT, f64).unwrap_or_else(|e| e.exit())
}

fn expenditure_description_from_clap(matches: &clap::ArgMatches) -> String {
    clap::value_t!(matches, CLAP_DESCRIPTION, String).unwrap_or_else(|e| e.exit())
}

fn build_show_options(matches: &clap::ArgMatches) -> SqlOptions {
    let target_date = match expenditure_target_date_from_clap(matches) {
        Some(date) => date,
        // Aribtrary choice: peeks back 6 months.
        None => (chrono::Utc::now() - chrono::Duration::days(183)).date(),
    };

    SqlOptions {
        expenditure_type: expenditure_type_from_clap(matches),
        target_date: target_date,
        amount: None,
        description: None,
    }
}

fn build_insert_options(matches: &clap::ArgMatches) -> SqlOptions {
    let target_date = match expenditure_target_date_from_clap(matches) {
        Some(date) => date,
        None => chrono::Utc::now().date(),
    };

    SqlOptions {
        expenditure_type: expenditure_type_from_clap(matches),
        target_date: target_date,
        amount: Some(expenditure_amount_from_clap(matches)),
        description: Some(expenditure_description_from_clap(matches)),
    }
}

fn parse_clap_matches(matches: clap::ArgMatches) -> SqlCommand {
    match matches.subcommand() {
        ("edit", _) => return SqlCommand::Edit,
        ("show", Some(show_matches)) => return SqlCommand::Show(build_show_options(show_matches)),
        ("insert", Some(insert_matches)) => {
            return SqlCommand::Insert(build_insert_options(insert_matches))
        }
        (&_, _) => panic!("no subcommand"),
    }
}

fn parse_command_line() -> SqlCommand {
    let matches = clap::App::new("tangerine-tailleferre")
        .version("0.1.0")
        .author("j39m")
        .about("manipulates expenditures")
        .subcommand(clap::App::new("edit").about("opens sqlite3 directly"))
        .subcommand(
            clap::App::new("show")
                .about("shows expenditures")
                .arg(
                    clap::Arg::with_name(CLAP_TARGET_DATE)
                        .takes_value(true)
                        .short("t"),
                )
                .arg(clap::Arg::with_name(CLAP_EXPENDITURE_TYPE).required(true)),
        )
        .subcommand(
            clap::App::new("insert")
                .about("inserts an expenditure")
                .arg(
                    clap::Arg::with_name(CLAP_AMOUNT)
                        .takes_value(true)
                        .required(true)
                        .short("a"),
                )
                .arg(
                    clap::Arg::with_name(CLAP_DESCRIPTION)
                        .takes_value(true)
                        .required(true)
                        .short("d"),
                )
                .arg(
                    clap::Arg::with_name(CLAP_TARGET_DATE)
                        .takes_value(true)
                        .short("t"),
                )
                .arg(clap::Arg::with_name(CLAP_EXPENDITURE_TYPE).required(true)),
        )
        .get_matches();

    parse_clap_matches(matches)
}

fn expenditure_type_name_from_enum(type_: &ExpenditureType) -> &'static str {
    match type_ {
        ExpenditureType::Personal => return PEREXP,
        ExpenditureType::Essential => return ESSEXP,
    }
}

fn build_show_command(options: SqlOptions) -> String {
    let summation_command = format!(
        r#"select {} from {} where date >= date("{}");"#,
        "sum(amount)",
        expenditure_type_name_from_enum(&options.expenditure_type),
        options.target_date.format("%Y-%m-%d").to_string()
    );

    let wordy_display_command = format!(
        r#"select {} from {} where date >= date("{}");"#,
        "*",
        expenditure_type_name_from_enum(&options.expenditure_type),
        options.target_date.format("%Y-%m-%d").to_string()
    );

    format!("{} {}", summation_command, wordy_display_command)
}

fn build_insert_command(options: SqlOptions) -> String {
    format!(
        r#"insert into {} values(date("{}"), "{}", {});"#,
        expenditure_type_name_from_enum(&options.expenditure_type),
        options.target_date.format("%Y-%m-%d"),
        options.description.unwrap(),
        options.amount.unwrap()
    )
}

fn build_extra_sqlite_arg() -> Option<String> {
    let sql_command = parse_command_line();

    match sql_command {
        SqlCommand::Edit => return None,
        SqlCommand::Show(options) => return Some(build_show_command(options)),
        SqlCommand::Insert(options) => return Some(build_insert_command(options)),
    };
}

fn main() {
    let mut command = Command::new(SQLITE3);
    command.arg(DB_PATH);

    if let Some(extra_arg) = build_extra_sqlite_arg() {
        command.arg(extra_arg);
    }

    eprintln!("{:#?}", command);
    command.exec();
}
<commit_msg>tangerine-tailleferre: swap "show" command order<commit_after>use std::os::unix::process::CommandExt;
use std::process::Command;

// clap exposes the macro value_t!
use clap::value_t;

// chrono needs this to call Utc.datetime_from_str().
use chrono::offset::TimeZone;

const SQLITE3: &'static str = "/usr/bin/sqlite3";
const DB_PATH: &'static str = "/home/kalvin/Documents/personal/expenditures.db";

const PEREXP: &'static str = "perexp";
const ESSEXP: &'static str = "essexp";

const CLAP_AMOUNT: &'static str = "amount";
const CLAP_DESCRIPTION: &'static str = "description";
const CLAP_EXPENDITURE_TYPE: &'static str = "expenditure-type";
const CLAP_TARGET_DATE: &'static str = "target-date";

#[derive(Debug)]
enum ExpenditureType {
    Personal,
    Essential,
}

#[derive(Debug)]
struct SqlOptions {
    // Expenditure type is always required in transacting expenditures.
    expenditure_type: ExpenditureType,

    // Target date is always required in transacting expenditures.
    target_date: chrono::Date<chrono::Utc>,

    // Amount and description are required for insertion.
    // They are meaningless for read-only queries.
    amount: Option<f64>,
    description: Option<String>,
}

#[derive(Debug)]
enum SqlCommand {
    Edit,
    Show(SqlOptions),
    Insert(SqlOptions),
}

fn expenditure_type_from_clap(matches: &clap::ArgMatches) -> ExpenditureType {
    let symbolic_type = matches.value_of(CLAP_EXPENDITURE_TYPE).unwrap();

    match symbolic_type {
        "p" => return ExpenditureType::Personal,
        "e" => return ExpenditureType::Essential,
        _ => (),
    };
    panic!(format!(
        "invalid {} ``{}''",
        CLAP_EXPENDITURE_TYPE, symbolic_type
    ));
}

fn expenditure_target_date_from_clap(
    matches: &clap::ArgMatches,
) -> Option<chrono::Date<chrono::Utc>> {
    if let Some(cli_target_date) = matches.value_of(CLAP_TARGET_DATE) {
        return Some(
            chrono::Utc
                .datetime_from_str(
                    &format!("{} 00:00:00", cli_target_date).to_string(),
                    "%Y-%m-%d %H:%M:%S",
                )
                .unwrap()
                .date(),
        );
    }
    None
}

fn expenditure_amount_from_clap(matches: &clap::ArgMatches) -> f64 {
    clap::value_t!(matches, CLAP_AMOUNT, f64).unwrap_or_else(|e| e.exit())
}

fn expenditure_description_from_clap(matches: &clap::ArgMatches) -> String {
    clap::value_t!(matches, CLAP_DESCRIPTION, String).unwrap_or_else(|e| e.exit())
}

fn build_show_options(matches: &clap::ArgMatches) -> SqlOptions {
    let target_date = match expenditure_target_date_from_clap(matches) {
        Some(date) => date,
        // Aribtrary choice: peeks back 6 months.
        None => (chrono::Utc::now() - chrono::Duration::days(183)).date(),
    };

    SqlOptions {
        expenditure_type: expenditure_type_from_clap(matches),
        target_date: target_date,
        amount: None,
        description: None,
    }
}

fn build_insert_options(matches: &clap::ArgMatches) -> SqlOptions {
    let target_date = match expenditure_target_date_from_clap(matches) {
        Some(date) => date,
        None => chrono::Utc::now().date(),
    };

    SqlOptions {
        expenditure_type: expenditure_type_from_clap(matches),
        target_date: target_date,
        amount: Some(expenditure_amount_from_clap(matches)),
        description: Some(expenditure_description_from_clap(matches)),
    }
}

fn parse_clap_matches(matches: clap::ArgMatches) -> SqlCommand {
    match matches.subcommand() {
        ("edit", _) => return SqlCommand::Edit,
        ("show", Some(show_matches)) => return SqlCommand::Show(build_show_options(show_matches)),
        ("insert", Some(insert_matches)) => {
            return SqlCommand::Insert(build_insert_options(insert_matches))
        }
        (&_, _) => panic!("no subcommand"),
    }
}

fn parse_command_line() -> SqlCommand {
    let matches = clap::App::new("tangerine-tailleferre")
        .version("0.1.0")
        .author("j39m")
        .about("manipulates expenditures")
        .subcommand(clap::App::new("edit").about("opens sqlite3 directly"))
        .subcommand(
            clap::App::new("show")
                .about("shows expenditures")
                .arg(
                    clap::Arg::with_name(CLAP_TARGET_DATE)
                        .takes_value(true)
                        .short("t"),
                )
                .arg(clap::Arg::with_name(CLAP_EXPENDITURE_TYPE).required(true)),
        )
        .subcommand(
            clap::App::new("insert")
                .about("inserts an expenditure")
                .arg(
                    clap::Arg::with_name(CLAP_AMOUNT)
                        .takes_value(true)
                        .required(true)
                        .short("a"),
                )
                .arg(
                    clap::Arg::with_name(CLAP_DESCRIPTION)
                        .takes_value(true)
                        .required(true)
                        .short("d"),
                )
                .arg(
                    clap::Arg::with_name(CLAP_TARGET_DATE)
                        .takes_value(true)
                        .short("t"),
                )
                .arg(clap::Arg::with_name(CLAP_EXPENDITURE_TYPE).required(true)),
        )
        .get_matches();

    parse_clap_matches(matches)
}

fn expenditure_type_name_from_enum(type_: &ExpenditureType) -> &'static str {
    match type_ {
        ExpenditureType::Personal => return PEREXP,
        ExpenditureType::Essential => return ESSEXP,
    }
}

fn build_show_command(options: SqlOptions) -> String {
    let wordy_display_command = format!(
        r#"select {} from {} where date >= date("{}");"#,
        "*",
        expenditure_type_name_from_enum(&options.expenditure_type),
        options.target_date.format("%Y-%m-%d").to_string()
    );
    let summation_command = format!(
        r#"select {} from {} where date >= date("{}");"#,
        "sum(amount)",
        expenditure_type_name_from_enum(&options.expenditure_type),
        options.target_date.format("%Y-%m-%d").to_string()
    );

    format!("{} {}", wordy_display_command, summation_command)
}

fn build_insert_command(options: SqlOptions) -> String {
    format!(
        r#"insert into {} values(date("{}"), "{}", {});"#,
        expenditure_type_name_from_enum(&options.expenditure_type),
        options.target_date.format("%Y-%m-%d"),
        options.description.unwrap(),
        options.amount.unwrap()
    )
}

fn build_extra_sqlite_arg() -> Option<String> {
    let sql_command = parse_command_line();

    match sql_command {
        SqlCommand::Edit => return None,
        SqlCommand::Show(options) => return Some(build_show_command(options)),
        SqlCommand::Insert(options) => return Some(build_insert_command(options)),
    };
}

fn main() {
    let mut command = Command::new(SQLITE3);
    command.arg(DB_PATH);

    if let Some(extra_arg) = build_extra_sqlite_arg() {
        command.arg(extra_arg);
    }

    eprintln!("{:#?}", command);
    command.exec();
}
<|endoftext|>