<commit_before>use protobuf::text_format::print_to_string;
use protobuf::Message;
use protobuf_test_common::*;

use super::test_fmt_text_format_pb::*;

#[test]
fn test_show() {
    let mut m = TestTypes::new();
    m.set_bool_singular(true);
    assert_eq!("bool_singular: true", &*format!("{:?}", m));
}

#[test]
fn test_pretty() {
    let mut tm = TestMessage::new();
    tm.set_value(23);
    let mut m = TestTypes::new();
    m.set_test_message_singular(tm);
    m.set_string_singular("abc".to_string());
    m.mut_string_repeated().push("def".to_string());
    m.mut_string_repeated().push("ghi".to_string());
    assert_eq!(
        "string_singular: \"abc\"\ntest_message_singular {\n  value: 23\n}\nstring_repeated: \"def\"\nstring_repeated: \"ghi\"\n",
        &*format!("{:#?}", m)
    );
}

#[test]
fn test_rust_identifier() {
    let mut m = TestTextFormatRustIdentifier::new();
    m.set_field_const(true);
    assert_eq!("const: true", &*format!("{:?}", m));
}

#[test]
fn test_empty() {
    test_text_format_str_descriptor("", &TestTypes::descriptor_static());
}

#[test]
fn test_enum() {
    test_text_format_str_descriptor("test_enum_singular: DARK", &TestTypes::descriptor_static());

    test_text_format_str_descriptor(
        "test_enum_repeated: DARK test_enum_repeated: LIGHT test_enum_repeated: LIGHT",
        &TestTypes::descriptor_static(),
    );
}

#[test]
fn test_int() {
    test_text_format_str_descriptor("uint32_singular: 98", &TestTypes::descriptor_static());
    test_text_format_str_descriptor("uint64_singular: 100", &TestTypes::descriptor_static());

    test_text_format_str_descriptor("int32_singular: 98", &TestTypes::descriptor_static());
    test_text_format_str_descriptor("int64_singular: 100", &TestTypes::descriptor_static());
    test_text_format_str_descriptor("int32_singular: -98", &TestTypes::descriptor_static());
    test_text_format_str_descriptor("int64_singular: -100", &TestTypes::descriptor_static());

    test_text_format_str_descriptor("fixed32_singular: 98", &TestTypes::descriptor_static());
    test_text_format_str_descriptor("fixed64_singular: 100", &TestTypes::descriptor_static());
    test_text_format_str_descriptor("sfixed32_singular: 98", &TestTypes::descriptor_static());
    test_text_format_str_descriptor("sfixed64_singular: 100", &TestTypes::descriptor_static());
    test_text_format_str_descriptor("sfixed32_singular: -98", &TestTypes::descriptor_static());
    test_text_format_str_descriptor("sfixed64_singular: -100", &TestTypes::descriptor_static());

    test_text_format_str_descriptor(
        "int32_repeated: 98 int32_repeated: -99",
        &TestTypes::descriptor_static(),
    );
}

#[test]
fn test_parse_float() {
    test_text_format_str_descriptor("float_singular: 98.5", &TestTypes::descriptor_static());
    test_text_format_str_descriptor("float_singular: -99.5", &TestTypes::descriptor_static());
    test_text_format_str_descriptor("float_singular: -99", &TestTypes::descriptor_static());
    test_text_format_str_descriptor("double_singular: 98.5", &TestTypes::descriptor_static());
    test_text_format_str_descriptor("double_singular: -99.5", &TestTypes::descriptor_static());
    test_text_format_str_descriptor("double_singular: 99", &TestTypes::descriptor_static());
}

#[test]
fn test_bool() {
    test_text_format_str_descriptor("bool_singular: true", &TestTypes::descriptor_static());
    test_text_format_str_descriptor("bool_singular: false", &TestTypes::descriptor_static());
    test_text_format_str_descriptor(
        "bool_repeated: false bool_repeated: false bool_repeated: true",
        &TestTypes::descriptor_static(),
    );
}

#[test]
fn test_string_bytes() {
    test_text_format_str_descriptor("string_singular: \"\"", &TestTypes::descriptor_static());
    test_text_format_str_descriptor("string_singular: \"a b\"", &TestTypes::descriptor_static());
    test_text_format_str_descriptor(
        "string_singular: \"a\\nb\"",
        &TestTypes::descriptor_static(),
    );

    test_text_format_str_descriptor("bytes_singular: \"\"", &TestTypes::descriptor_static());
    test_text_format_str_descriptor("bytes_singular: \"a b\"", &TestTypes::descriptor_static());
    test_text_format_str_descriptor("bytes_singular: \"a\\nb\"", &TestTypes::descriptor_static());
    test_text_format_str_descriptor(
        "bytes_singular: \"a\\xfeb\"",
        &TestTypes::descriptor_static(),
    );

    test_text_format_str_descriptor(
        "string_repeated: \"ab\" bytes_repeated: \"cd\" string_repeated: \"ef\"",
        &TestTypes::descriptor_static(),
    );

    test_text_format_str_descriptor(
        "string_singular: \"quote\\\"newline\\nbackslash\\\\del\\177\"",
        &TestTypes::descriptor_static(),
    );
}

#[test]
fn test_message() {
    test_text_format_str_descriptor("test_message_singular {}", &TestTypes::descriptor_static());

    test_text_format_str_descriptor(
        "test_message_singular { value: 10 }",
        &TestTypes::descriptor_static(),
    );

    test_text_format_str_descriptor(
        "test_message_repeated { value: 10 } test_message_repeated { value: 20 }",
        &TestTypes::descriptor_static(),
    );

    test_text_format_str_descriptor("test_message_singular <>", &TestTypes::descriptor_static());

    test_text_format_str_descriptor(
        "test_message_singular < value: 10 >",
        &TestTypes::descriptor_static(),
    );

    assert!(
        parse_using_rust_protobuf(
            "test_message_singular < value: 10 }",
            &TestTypes::descriptor_static()
        )
        .is_err(),
        "Parsing a message with mismatched message start and terminator symbols should fail."
    );

    assert!(
        parse_using_rust_protobuf(
            "test_message_singular { value: 10 >",
            &TestTypes::descriptor_static()
        )
        .is_err(),
        "Parsing a message with mismatched message start and terminator symbols should fail."
    );
}

#[test]
fn test_map_keys_sorted() {
    // TODO
    // When generating text format for a .proto, maps are sorted by key.
    // Numeric keys are sorted numerically.
}

#[test]
fn test_reflect() {
    for m in special_messages_typed::<TestTypes>() {
        test_text_format_message(&m);
        // This slow test is equivalent to the fast test below.
        // Do just one iteration as smoke test.
        // `break` statement can be commented out for easier debugging.
        break;
    }

    let mut l = TestTypesList::new();
    // TODO: make `ts` field public in codegen
    l.set_ts(special_messages_typed().into());
    test_text_format_message(&l);
}

#[test]
fn test_parse_error() {
    let e = protobuf::text_format::parse_from_str::<TestTypes>("nonexistent: 42").unwrap_err();
    let _error: &dyn std::error::Error = &e;
    assert_eq!(e.to_string(), "1:1: UnknownField(\"nonexistent\")");
}

fn t<F: FnMut(&mut TestTypes)>(expected: &str, mut setter: F) {
    let mut m = TestTypes::new();
    setter(&mut m);
    assert_eq!(&*print_to_string(&m), expected);
}

#[test]
fn test_singular() {
    t("int32_singular: 99", |m| m.set_int32_singular(99));
    t("double_singular: 99", |m| m.set_double_singular(99.0));
    t("float_singular: 99", |m| m.set_float_singular(99.0));
    t("int32_singular: 99", |m| m.set_int32_singular(99));
    t("int64_singular: 99", |m| m.set_int64_singular(99));
    t("uint32_singular: 99", |m| m.set_uint32_singular(99));
    t("uint64_singular: 99", |m| m.set_uint64_singular(99));
    t("sint32_singular: 99", |m| m.set_sint32_singular(99));
    t("sint64_singular: 99", |m| m.set_sint64_singular(99));
    t("fixed32_singular: 99", |m| m.set_fixed32_singular(99));
    t("fixed64_singular: 99", |m| m.set_fixed64_singular(99));
    t("sfixed32_singular: 99", |m| m.set_sfixed32_singular(99));
    t("sfixed64_singular: 99", |m| m.set_sfixed64_singular(99));
    t("bool_singular: true", |m| m.set_bool_singular(true));
    t("string_singular: \"abc\"", |m| {
        m.set_string_singular("abc".to_string())
    });
    t("bytes_singular: \"def\"", |m| {
        m.set_bytes_singular(b"def".to_vec())
    });
    t("test_enum_singular: DARK", |m| {
        m.set_test_enum_singular(TestEnum::DARK)
    });
    t("test_message_singular {}", |m| {
        m.test_message_singular.set_default();
    });
}

#[test]
fn test_repeated_one() {
    t("int32_repeated: 99", |m| m.int32_repeated.push(99));
    t("double_repeated: 99", |m| m.double_repeated.push(99.0));
    t("float_repeated: 99", |m| m.float_repeated.push(99.0));
    t("int32_repeated: 99", |m| m.int32_repeated.push(99));
    t("int64_repeated: 99", |m| m.int64_repeated.push(99));
    t("uint32_repeated: 99", |m| m.uint32_repeated.push(99));
    t("uint64_repeated: 99", |m| m.uint64_repeated.push(99));
    t("sint32_repeated: 99", |m| m.sint32_repeated.push(99));
    t("sint64_repeated: 99", |m| m.sint64_repeated.push(99));
    t("fixed32_repeated: 99", |m| m.fixed32_repeated.push(99));
    t("fixed64_repeated: 99", |m| m.fixed64_repeated.push(99));
    t("sfixed32_repeated: 99", |m| m.sfixed32_repeated.push(99));
    t("sfixed64_repeated: 99", |m| m.sfixed64_repeated.push(99));
    t("bool_repeated: false", |m| m.bool_repeated.push(false));
    t("string_repeated: \"abc\"", |m| {
        m.string_repeated.push("abc".to_string())
    });
    t("bytes_repeated: \"def\"", |m| {
        m.bytes_repeated.push(b"def".to_vec())
    });
    t("test_enum_repeated: DARK", |m| {
        m.test_enum_repeated.push(TestEnum::DARK.into())
    });
    t("test_message_repeated {}", |m| {
        m.test_message_repeated.push(Default::default());
    });
}

#[test]
fn test_repeated_multiple() {
    t(
        "uint32_singular: 30 int32_repeated: 10 int32_repeated: -20",
        |m| {
            m.set_uint32_singular(30);
            m.int32_repeated.push(10);
            m.int32_repeated.push(-20);
        },
    );
}

#[test]
fn test_complex_message() {
    t("test_message_singular {value: 30}", |m| {
        m.test_message_singular.mut_or_default().set_value(30)
    });
}

#[test]
fn test_string_escaped() {
    let mut m = TestTypes::new();
    m.set_string_singular("quote\"newline\nbackslash\\del\x7f".to_string());
    assert_eq!(
        "string_singular: \"quote\\\"newline\\nbackslash\\\\del\\177\"",
        &*format!("{:?}", m)
    );
}
<commit_msg>Fix test<commit_after>use protobuf::text_format::print_to_string;
use protobuf::Message;
use protobuf_test_common::*;

use super::test_fmt_text_format_pb::*;

#[test]
fn test_show() {
    let mut m = TestTypes::new();
    m.set_bool_singular(true);
    assert_eq!("bool_singular: true", &*format!("{:?}", m));
}

#[test]
fn test_pretty() {
    let mut tm = TestMessage::new();
    tm.set_value(23);
    let mut m = TestTypes::new();
    m.set_test_message_singular(tm);
    m.set_string_singular("abc".to_string());
    m.mut_string_repeated().push("def".to_string());
    m.mut_string_repeated().push("ghi".to_string());
    assert_eq!(
        "string_singular: \"abc\"\ntest_message_singular {\n  value: 23\n}\nstring_repeated: \"def\"\nstring_repeated: \"ghi\"\n",
        &*format!("{:#?}", m)
    );
}

#[test]
fn test_rust_identifier() {
    let mut m = TestTextFormatRustIdentifier::new();
    m.set_field_const(true);
    assert_eq!("const: true", &*format!("{:?}", m));
}

#[test]
fn test_empty() {
    test_text_format_str_descriptor("", &TestTypes::descriptor_static());
}

#[test]
fn test_enum() {
    test_text_format_str_descriptor("test_enum_singular: DARK", &TestTypes::descriptor_static());

    test_text_format_str_descriptor(
        "test_enum_repeated: DARK test_enum_repeated: LIGHT test_enum_repeated: LIGHT",
        &TestTypes::descriptor_static(),
    );
}

#[test]
fn test_int() {
    test_text_format_str_descriptor("uint32_singular: 98", &TestTypes::descriptor_static());
    test_text_format_str_descriptor("uint64_singular: 100", &TestTypes::descriptor_static());

    test_text_format_str_descriptor("int32_singular: 98", &TestTypes::descriptor_static());
    test_text_format_str_descriptor("int64_singular: 100", &TestTypes::descriptor_static());
    test_text_format_str_descriptor("int32_singular: -98", &TestTypes::descriptor_static());
    test_text_format_str_descriptor("int64_singular: -100", &TestTypes::descriptor_static());

    test_text_format_str_descriptor("fixed32_singular: 98", &TestTypes::descriptor_static());
    test_text_format_str_descriptor("fixed64_singular: 100", &TestTypes::descriptor_static());
    test_text_format_str_descriptor("sfixed32_singular: 98", &TestTypes::descriptor_static());
    test_text_format_str_descriptor("sfixed64_singular: 100", &TestTypes::descriptor_static());
    test_text_format_str_descriptor("sfixed32_singular: -98", &TestTypes::descriptor_static());
    test_text_format_str_descriptor("sfixed64_singular: -100", &TestTypes::descriptor_static());

    test_text_format_str_descriptor(
        "int32_repeated: 98 int32_repeated: -99",
        &TestTypes::descriptor_static(),
    );
}

#[test]
fn test_parse_float() {
    test_text_format_str_descriptor("float_singular: 98.5", &TestTypes::descriptor_static());
    test_text_format_str_descriptor("float_singular: -99.5", &TestTypes::descriptor_static());
    test_text_format_str_descriptor("float_singular: -99", &TestTypes::descriptor_static());
    test_text_format_str_descriptor("double_singular: 98.5", &TestTypes::descriptor_static());
    test_text_format_str_descriptor("double_singular: -99.5", &TestTypes::descriptor_static());
    test_text_format_str_descriptor("double_singular: 99", &TestTypes::descriptor_static());
}

#[test]
fn test_bool() {
    test_text_format_str_descriptor("bool_singular: true", &TestTypes::descriptor_static());
    test_text_format_str_descriptor("bool_singular: false", &TestTypes::descriptor_static());
    test_text_format_str_descriptor(
        "bool_repeated: false bool_repeated: false bool_repeated: true",
        &TestTypes::descriptor_static(),
    );
}

#[test]
fn test_string_bytes() {
    test_text_format_str_descriptor("string_singular: \"\"", &TestTypes::descriptor_static());
    test_text_format_str_descriptor("string_singular: \"a b\"", &TestTypes::descriptor_static());
    test_text_format_str_descriptor(
        "string_singular: \"a\\nb\"",
        &TestTypes::descriptor_static(),
    );

    test_text_format_str_descriptor("bytes_singular: \"\"", &TestTypes::descriptor_static());
    test_text_format_str_descriptor("bytes_singular: \"a b\"", &TestTypes::descriptor_static());
    test_text_format_str_descriptor("bytes_singular: \"a\\nb\"", &TestTypes::descriptor_static());
    test_text_format_str_descriptor(
        "bytes_singular: \"a\\xfeb\"",
        &TestTypes::descriptor_static(),
    );

    test_text_format_str_descriptor(
        "string_repeated: \"ab\" bytes_repeated: \"cd\" string_repeated: \"ef\"",
        &TestTypes::descriptor_static(),
    );

    test_text_format_str_descriptor(
        "string_singular: \"quote\\\"newline\\nbackslash\\\\del\\177\"",
        &TestTypes::descriptor_static(),
    );
}

#[test]
fn test_message() {
    test_text_format_str_descriptor("test_message_singular {}", &TestTypes::descriptor_static());

    test_text_format_str_descriptor(
        "test_message_singular { value: 10 }",
        &TestTypes::descriptor_static(),
    );

    test_text_format_str_descriptor(
        "test_message_repeated { value: 10 } test_message_repeated { value: 20 }",
        &TestTypes::descriptor_static(),
    );

    test_text_format_str_descriptor("test_message_singular <>", &TestTypes::descriptor_static());

    test_text_format_str_descriptor(
        "test_message_singular < value: 10 >",
        &TestTypes::descriptor_static(),
    );

    assert!(
        parse_using_rust_protobuf(
            "test_message_singular < value: 10 }",
            &TestTypes::descriptor_static()
        )
        .is_err(),
        "Parsing a message with mismatched message start and terminator symbols should fail."
    );

    assert!(
        parse_using_rust_protobuf(
            "test_message_singular { value: 10 >",
            &TestTypes::descriptor_static()
        )
        .is_err(),
        "Parsing a message with mismatched message start and terminator symbols should fail."
    );
}

#[test]
fn test_map_keys_sorted() {
    // TODO
    // When generating text format for a .proto, maps are sorted by key.
    // Numeric keys are sorted numerically.
}

#[test]
fn test_reflect() {
    for m in special_messages_typed::<TestTypes>() {
        test_text_format_message(&m);
        // This slow test is equivalent to the fast test below.
        // Do just one iteration as smoke test.
        // `break` statement can be commented out for easier debugging.
        break;
    }

    let mut l = TestTypesList::new();
    // TODO: make `ts` field public in codegen
    l.set_ts(special_messages_typed().into());
    test_text_format_message(&l);
}

#[test]
fn test_parse_error() {
    let e = protobuf::text_format::parse_from_str::<TestTypes>("nonexistent: 42").unwrap_err();
    let _error: &dyn std::error::Error = &e;
    assert_eq!(e.to_string(), "1:1: Unknown field: `nonexistent`");
}

fn t<F: FnMut(&mut TestTypes)>(expected: &str, mut setter: F) {
    let mut m = TestTypes::new();
    setter(&mut m);
    assert_eq!(&*print_to_string(&m), expected);
}

#[test]
fn test_singular() {
    t("int32_singular: 99", |m| m.set_int32_singular(99));
    t("double_singular: 99", |m| m.set_double_singular(99.0));
    t("float_singular: 99", |m| m.set_float_singular(99.0));
    t("int32_singular: 99", |m| m.set_int32_singular(99));
    t("int64_singular: 99", |m| m.set_int64_singular(99));
    t("uint32_singular: 99", |m| m.set_uint32_singular(99));
    t("uint64_singular: 99", |m| m.set_uint64_singular(99));
    t("sint32_singular: 99", |m| m.set_sint32_singular(99));
    t("sint64_singular: 99", |m| m.set_sint64_singular(99));
    t("fixed32_singular: 99", |m| m.set_fixed32_singular(99));
    t("fixed64_singular: 99", |m| m.set_fixed64_singular(99));
    t("sfixed32_singular: 99", |m| m.set_sfixed32_singular(99));
    t("sfixed64_singular: 99", |m| m.set_sfixed64_singular(99));
    t("bool_singular: true", |m| m.set_bool_singular(true));
    t("string_singular: \"abc\"", |m| {
        m.set_string_singular("abc".to_string())
    });
    t("bytes_singular: \"def\"", |m| {
        m.set_bytes_singular(b"def".to_vec())
    });
    t("test_enum_singular: DARK", |m| {
        m.set_test_enum_singular(TestEnum::DARK)
    });
    t("test_message_singular {}", |m| {
        m.test_message_singular.set_default();
    });
}

#[test]
fn test_repeated_one() {
    t("int32_repeated: 99", |m| m.int32_repeated.push(99));
    t("double_repeated: 99", |m| m.double_repeated.push(99.0));
    t("float_repeated: 99", |m| m.float_repeated.push(99.0));
    t("int32_repeated: 99", |m| m.int32_repeated.push(99));
    t("int64_repeated: 99", |m| m.int64_repeated.push(99));
    t("uint32_repeated: 99", |m| m.uint32_repeated.push(99));
    t("uint64_repeated: 99", |m| m.uint64_repeated.push(99));
    t("sint32_repeated: 99", |m| m.sint32_repeated.push(99));
    t("sint64_repeated: 99", |m| m.sint64_repeated.push(99));
    t("fixed32_repeated: 99", |m| m.fixed32_repeated.push(99));
    t("fixed64_repeated: 99", |m| m.fixed64_repeated.push(99));
    t("sfixed32_repeated: 99", |m| m.sfixed32_repeated.push(99));
    t("sfixed64_repeated: 99", |m| m.sfixed64_repeated.push(99));
    t("bool_repeated: false", |m| m.bool_repeated.push(false));
    t("string_repeated: \"abc\"", |m| {
        m.string_repeated.push("abc".to_string())
    });
    t("bytes_repeated: \"def\"", |m| {
        m.bytes_repeated.push(b"def".to_vec())
    });
    t("test_enum_repeated: DARK", |m| {
        m.test_enum_repeated.push(TestEnum::DARK.into())
    });
    t("test_message_repeated {}", |m| {
        m.test_message_repeated.push(Default::default());
    });
}

#[test]
fn test_repeated_multiple() {
    t(
        "uint32_singular: 30 int32_repeated: 10 int32_repeated: -20",
        |m| {
            m.set_uint32_singular(30);
            m.int32_repeated.push(10);
            m.int32_repeated.push(-20);
        },
    );
}

#[test]
fn test_complex_message() {
    t("test_message_singular {value: 30}", |m| {
        m.test_message_singular.mut_or_default().set_value(30)
    });
}

#[test]
fn test_string_escaped() {
    let mut m = TestTypes::new();
    m.set_string_singular("quote\"newline\nbackslash\\del\x7f".to_string());
    assert_eq!(
        "string_singular: \"quote\\\"newline\\nbackslash\\\\del\\177\"",
        &*format!("{:?}", m)
    );
}
<|endoftext|>


<commit_before>// Copyright (C) 2020 Julen Ruiz Aizpuru, https://github.com/julen

//! # Plurr
//!
//! A library for handling plurals/genders/conditionals.

use std::collections::HashMap;

mod plurals;
mod utils;

const _PLURAL: &str = "_PLURAL";

#[derive(Debug, PartialEq)]
pub enum PlurrError {
    EmptyPlaceholder,
    EmptyVariants,
    NotZeroOrPositiveValue,
    UnmatchedOpeningBrace,
    UnmatchedClosingBrace,
    UndefinedParameter,
    UndefinedParameterPairs,
}

type PlurrParams = HashMap<String, String>;

#[derive(Debug)]
pub struct Plurr<'a> {
    locale: &'a str,
    auto_plurals: bool,
    strict: bool,
    params: PlurrParams,
}

impl<'a> Default for Plurr<'a> {
    fn default() -> Self {
        Plurr::new()
    }
}

impl<'a> Plurr<'a> {
    pub fn new() -> Self {
        Plurr {
            locale: "en",
            auto_plurals: true,
            strict: true,
            params: PlurrParams::new(),
        }
    }

    /// Sets the locale for Plurr. If this is not called or an incompatible
    /// `locale_code` is provided, it defaults to English.
    pub fn locale(&mut self, locale_code: &'a str) -> &mut Plurr<'a> {
        self.locale = locale_code;
        self
    }

    /// Toggles the auto-plural behavior.
    /// * With auto-plurals, Plurr will automatically select the plural form for
    /// the current locale.
    /// * Without auto-plurals, you will need to manually provide an `N_PLURAL`
    /// parameter before calling `format()`.
    pub fn auto_plurals(&mut self, auto_plurals: bool) -> &mut Plurr<'a> {
        self.auto_plurals = auto_plurals;
        self
    }

    /// Toggles strict mode. In strict mode, errors are not skipped.
    pub fn strict(&mut self, strict: bool) -> &mut Plurr<'a> {
        self.strict = strict;
        self
    }

    /// Stores a parameter value. Parameters must be fed before calling
    /// `format()`.
    pub fn param(&mut self, key: &str, value: &str) -> &mut Plurr<'a> {
        self.params.insert(key.to_string(), value.to_string());
        self
    }

    /// Formats a Plurr string and returns the evaluated string.
    ///
    /// For the format spec please check out the project
    /// [README](https://github.com/loctools/plurr/blob/master/README.md).
    ///
    /// # Examples
    ///
    /// ```
    /// use plurr::Plurr;
    ///
    /// let mut p = Plurr::new();
    /// let s = "Delete {N_PLURAL:{N} file|{N} files}?";
    ///
    /// assert_eq!(
    ///     p.param("N", "5").format(s),
    ///     Ok("Delete 5 files?".to_string())
    /// );
    /// ```
    pub fn format(&mut self, value: &str) -> Result<String, PlurrError> {
        let mut bracket_count = 0;
        let mut blocks = vec!["".to_string()];

        for chunk in utils::split_string(value, "{", "}") {
            if chunk == "{" {
                bracket_count += 1;
                blocks.push("".to_string());
                continue;
            }

            if chunk == "}" {
                bracket_count -= 1;
                if bracket_count < 0 {
                    return Err(PlurrError::UnmatchedClosingBrace);
                }

                let block = blocks.pop().unwrap();
                let colon_pos_maybe = block.find(':');

                let name = match colon_pos_maybe {
                    Some(colon_pos) => {
                        if self.strict && colon_pos == 0 {
                            return Err(PlurrError::EmptyPlaceholder);
                        }
                        // Multiple choices
                        &block[0..colon_pos]
                    }
                    // Simple placeholder
                    None => &block,
                };

                if !self.params.contains_key(name) {
                    let p_pos = name.find(_PLURAL);
                    if self.auto_plurals
                        && p_pos.is_some()
                        && p_pos.unwrap() == name.len() - _PLURAL.len()
                    {
                        let prefix = name[0..p_pos.unwrap()].to_string();
                        if !self.params.contains_key(&prefix) && self.strict {
                            return Err(PlurrError::UndefinedParameterPairs);
                        }

                        // This is where the actual parameter replacing happens
                        let prefix_value_maybe = self.params.get(&prefix).unwrap();
                        let prefix_value;
                        match prefix_value_maybe.parse::<usize>() {
                            Ok(parsed_value) => prefix_value = parsed_value,
                            Err(_) => {
                                if self.strict {
                                    return Err(PlurrError::NotZeroOrPositiveValue);
                                }
                                prefix_value = 0;
                            }
                        }

                        self.params.insert(
                            name.to_owned(),
                            plurals::plural_func(&self.locale, prefix_value).to_string(),
                        );
                    } else if self.strict {
                        return Err(PlurrError::UndefinedParameter);
                    }
                }

                let result = match colon_pos_maybe {
                    Some(colon_pos) => {
                        // multiple choices
                        let block_len = block.len();

                        if self.strict && colon_pos == block_len - 1 {
                            return Err(PlurrError::EmptyVariants);
                        }

                        let value = self.params.get(name).unwrap();
                        let mut choice_idx: usize = value.parse().unwrap_or(0);

                        let content_start = colon_pos + 1;
                        let clean_block = &block[content_start..];

                        let parts: Vec<&str> = clean_block.split('|').collect();
                        if choice_idx >= parts.len() {
                            choice_idx = parts.len() - 1;
                        }
                        parts[choice_idx]
                    }
                    None => self.params.get(name).unwrap(),
                };

                let index = blocks.len() - 1;
                blocks[index].push_str(result);

                continue;
            }

            let index = blocks.len() - 1;
            blocks[index].push_str(&chunk);
        }

        if bracket_count > 0 {
            return Err(PlurrError::UnmatchedOpeningBrace);
        }

        // Clear params for subsequent calls
        self.params.clear();

        Ok(blocks[0].clone())
    }
}
<commit_msg>Cleanup: use `Self` to refer to current object<commit_after>// Copyright (C) 2020 Julen Ruiz Aizpuru, https://github.com/julen

//! # Plurr
//!
//! A library for handling plurals/genders/conditionals.

use std::collections::HashMap;

mod plurals;
mod utils;

const _PLURAL: &str = "_PLURAL";

#[derive(Debug, PartialEq)]
pub enum PlurrError {
    EmptyPlaceholder,
    EmptyVariants,
    NotZeroOrPositiveValue,
    UnmatchedOpeningBrace,
    UnmatchedClosingBrace,
    UndefinedParameter,
    UndefinedParameterPairs,
}

type PlurrParams = HashMap<String, String>;

#[derive(Debug)]
pub struct Plurr<'a> {
    locale: &'a str,
    auto_plurals: bool,
    strict: bool,
    params: PlurrParams,
}

impl<'a> Default for Plurr<'a> {
    fn default() -> Self {
        Plurr::new()
    }
}

impl<'a> Plurr<'a> {
    pub fn new() -> Self {
        Self {
            locale: "en",
            auto_plurals: true,
            strict: true,
            params: PlurrParams::new(),
        }
    }

    /// Sets the locale for Plurr. If this is not called or an incompatible
    /// `locale_code` is provided, it defaults to English.
    pub fn locale(&mut self, locale_code: &'a str) -> &mut Self {
        self.locale = locale_code;
        self
    }

    /// Toggles the auto-plural behavior.
    /// * With auto-plurals, Plurr will automatically select the plural form for
    /// the current locale.
    /// * Without auto-plurals, you will need to manually provide an `N_PLURAL`
    /// parameter before calling `format()`.
    pub fn auto_plurals(&mut self, auto_plurals: bool) -> &mut Self {
        self.auto_plurals = auto_plurals;
        self
    }

    /// Toggles strict mode. In strict mode, errors are not skipped.
    pub fn strict(&mut self, strict: bool) -> &mut Self {
        self.strict = strict;
        self
    }

    /// Stores a parameter value. Parameters must be fed before calling
    /// `format()`.
    pub fn param(&mut self, key: &str, value: &str) -> &mut Self {
        self.params.insert(key.to_string(), value.to_string());
        self
    }

    /// Formats a Plurr string and returns the evaluated string.
    ///
    /// For the format spec please check out the project
    /// [README](https://github.com/loctools/plurr/blob/master/README.md).
    ///
    /// # Examples
    ///
    /// ```
    /// use plurr::Plurr;
    ///
    /// let mut p = Plurr::new();
    /// let s = "Delete {N_PLURAL:{N} file|{N} files}?";
    ///
    /// assert_eq!(
    ///     p.param("N", "5").format(s),
    ///     Ok("Delete 5 files?".to_string())
    /// );
    /// ```
    pub fn format(&mut self, value: &str) -> Result<String, PlurrError> {
        let mut bracket_count = 0;
        let mut blocks = vec!["".to_string()];

        for chunk in utils::split_string(value, "{", "}") {
            if chunk == "{" {
                bracket_count += 1;
                blocks.push("".to_string());
                continue;
            }

            if chunk == "}" {
                bracket_count -= 1;
                if bracket_count < 0 {
                    return Err(PlurrError::UnmatchedClosingBrace);
                }

                let block = blocks.pop().unwrap();
                let colon_pos_maybe = block.find(':');

                let name = match colon_pos_maybe {
                    Some(colon_pos) => {
                        if self.strict && colon_pos == 0 {
                            return Err(PlurrError::EmptyPlaceholder);
                        }
                        // Multiple choices
                        &block[0..colon_pos]
                    }
                    // Simple placeholder
                    None => &block,
                };

                if !self.params.contains_key(name) {
                    let p_pos = name.find(_PLURAL);
                    if self.auto_plurals
                        && p_pos.is_some()
                        && p_pos.unwrap() == name.len() - _PLURAL.len()
                    {
                        let prefix = name[0..p_pos.unwrap()].to_string();
                        if !self.params.contains_key(&prefix) && self.strict {
                            return Err(PlurrError::UndefinedParameterPairs);
                        }

                        // This is where the actual parameter replacing happens
                        let prefix_value_maybe = self.params.get(&prefix).unwrap();
                        let prefix_value;
                        match prefix_value_maybe.parse::<usize>() {
                            Ok(parsed_value) => prefix_value = parsed_value,
                            Err(_) => {
                                if self.strict {
                                    return Err(PlurrError::NotZeroOrPositiveValue);
                                }
                                prefix_value = 0;
                            }
                        }

                        self.params.insert(
                            name.to_owned(),
                            plurals::plural_func(&self.locale, prefix_value).to_string(),
                        );
                    } else if self.strict {
                        return Err(PlurrError::UndefinedParameter);
                    }
                }

                let result = match colon_pos_maybe {
                    Some(colon_pos) => {
                        // multiple choices
                        let block_len = block.len();

                        if self.strict && colon_pos == block_len - 1 {
                            return Err(PlurrError::EmptyVariants);
                        }

                        let value = self.params.get(name).unwrap();
                        let mut choice_idx: usize = value.parse().unwrap_or(0);

                        let content_start = colon_pos + 1;
                        let clean_block = &block[content_start..];

                        let parts: Vec<&str> = clean_block.split('|').collect();
                        if choice_idx >= parts.len() {
                            choice_idx = parts.len() - 1;
                        }
                        parts[choice_idx]
                    }
                    None => self.params.get(name).unwrap(),
                };

                let index = blocks.len() - 1;
                blocks[index].push_str(result);

                continue;
            }

            let index = blocks.len() - 1;
            blocks[index].push_str(&chunk);
        }

        if bracket_count > 0 {
            return Err(PlurrError::UnmatchedOpeningBrace);
        }

        // Clear params for subsequent calls
        self.params.clear();

        Ok(blocks[0].clone())
    }
}
<|endoftext|>


<commit_before>//! Some lints that are only useful in the compiler or crates that use compiler internals, such as
//! Clippy.

use crate::hir::{HirId, Path, PathSegment, QPath, Ty, TyKind};
use crate::lint::{
    EarlyContext, EarlyLintPass, LateContext, LateLintPass, LintArray, LintContext, LintPass,
};
use errors::Applicability;
use rustc_data_structures::fx::FxHashMap;
use syntax::ast::Ident;

declare_lint! {
    pub DEFAULT_HASH_TYPES,
    Allow,
    "forbid HashMap and HashSet and suggest the FxHash* variants"
}

pub struct DefaultHashTypes {
    map: FxHashMap<String, String>,
}

impl DefaultHashTypes {
    pub fn new() -> Self {
        let mut map = FxHashMap::default();
        map.insert("HashMap".to_string(), "FxHashMap".to_string());
        map.insert("HashSet".to_string(), "FxHashSet".to_string());
        Self { map }
    }
}

impl LintPass for DefaultHashTypes {
    fn get_lints(&self) -> LintArray {
        lint_array!(DEFAULT_HASH_TYPES)
    }

    fn name(&self) -> &'static str {
        "DefaultHashTypes"
    }
}

impl EarlyLintPass for DefaultHashTypes {
    fn check_ident(&mut self, cx: &EarlyContext<'_>, ident: Ident) {
        let ident_string = ident.to_string();
        if let Some(replace) = self.map.get(&ident_string) {
            let msg = format!(
                "Prefer {} over {}, it has better performance",
                replace, ident_string
            );
            let mut db = cx.struct_span_lint(DEFAULT_HASH_TYPES, ident.span, &msg);
            db.span_suggestion(
                ident.span,
                "use",
                replace.to_string(),
                Applicability::MaybeIncorrect, // FxHashMap, ... needs another import
            );
            db.note(&format!(
                "a `use rustc_data_structures::fx::{}` may be necessary",
                replace
            ))
            .emit();
        }
    }
}

declare_lint! {
    pub USAGE_OF_TY_TYKIND,
    Allow,
    "Usage of `ty::TyKind` outside of the `ty::sty` module"
}

pub struct TyKindUsage;

impl LintPass for TyKindUsage {
    fn get_lints(&self) -> LintArray {
        lint_array!(USAGE_OF_TY_TYKIND)
    }

    fn name(&self) -> &'static str {
        "TyKindUsage"
    }
}

impl<'a, 'tcx> LateLintPass<'a, 'tcx> for TyKindUsage {
    fn check_path(&mut self, cx: &LateContext<'_, '_>, path: &'tcx Path, _: HirId) {
        let segments = path.segments.iter().rev().skip(1).rev();

        if let Some(last) = segments.last() {
            let span = path.span.with_hi(last.ident.span.hi());
            if lint_ty_kind_usage(cx, last) {
                cx.struct_span_lint(USAGE_OF_TY_TYKIND, span, "usage of `ty::TyKind::<kind>`")
                    .span_suggestion(
                        span,
                        "try using ty::<kind> directly",
                        "ty".to_string(),
                        Applicability::MaybeIncorrect, // ty maybe needs an import
                    )
                    .emit();
            }
        }
    }

    fn check_ty(&mut self, cx: &LateContext<'_, '_>, ty: &'tcx Ty) {
        if let TyKind::Path(qpath) = &ty.node {
            if let QPath::Resolved(_, path) = qpath {
                if let Some(last) = path.segments.iter().last() {
                    if lint_ty_kind_usage(cx, last) {
                        cx.struct_span_lint(USAGE_OF_TY_TYKIND, path.span, "usage of `ty::TyKind`")
                            .help("try using `ty::Ty` instead")
                            .emit();
                    }
                }
            }
        }
    }
}

fn lint_ty_kind_usage(cx: &LateContext<'_, '_>, segment: &PathSegment) -> bool {
    if segment.ident.as_str() == "TyKind" {
        if let Some(def) = segment.def {
            if let Some(did) = def.opt_def_id() {
                return did.match_path(cx.tcx, &["rustc", "ty", "sty", "TyKind"]);
            }
        }
    }

    false
}
<commit_msg>Use declare_lint_pass! and impl_lint_pass! in more places<commit_after>//! Some lints that are only useful in the compiler or crates that use compiler internals, such as
//! Clippy.

use crate::hir::{HirId, Path, PathSegment, QPath, Ty, TyKind};
use crate::lint::{
    EarlyContext, EarlyLintPass, LateContext, LateLintPass, LintArray, LintContext, LintPass,
};
use errors::Applicability;
use rustc_data_structures::fx::FxHashMap;
use syntax::ast::Ident;

declare_lint! {
    pub DEFAULT_HASH_TYPES,
    Allow,
    "forbid HashMap and HashSet and suggest the FxHash* variants"
}

pub struct DefaultHashTypes {
    map: FxHashMap<String, String>,
}

impl DefaultHashTypes {
    pub fn new() -> Self {
        let mut map = FxHashMap::default();
        map.insert("HashMap".to_string(), "FxHashMap".to_string());
        map.insert("HashSet".to_string(), "FxHashSet".to_string());
        Self { map }
    }
}

impl_lint_pass!(DefaultHashTypes => [DEFAULT_HASH_TYPES]);

impl EarlyLintPass for DefaultHashTypes {
    fn check_ident(&mut self, cx: &EarlyContext<'_>, ident: Ident) {
        let ident_string = ident.to_string();
        if let Some(replace) = self.map.get(&ident_string) {
            let msg = format!(
                "Prefer {} over {}, it has better performance",
                replace, ident_string
            );
            let mut db = cx.struct_span_lint(DEFAULT_HASH_TYPES, ident.span, &msg);
            db.span_suggestion(
                ident.span,
                "use",
                replace.to_string(),
                Applicability::MaybeIncorrect, // FxHashMap, ... needs another import
            );
            db.note(&format!(
                "a `use rustc_data_structures::fx::{}` may be necessary",
                replace
            ))
            .emit();
        }
    }
}

declare_lint! {
    pub USAGE_OF_TY_TYKIND,
    Allow,
    "Usage of `ty::TyKind` outside of the `ty::sty` module"
}

declare_lint_pass!(TyKindUsage => [USAGE_OF_TY_TYKIND]);

impl<'a, 'tcx> LateLintPass<'a, 'tcx> for TyKindUsage {
    fn check_path(&mut self, cx: &LateContext<'_, '_>, path: &'tcx Path, _: HirId) {
        let segments = path.segments.iter().rev().skip(1).rev();

        if let Some(last) = segments.last() {
            let span = path.span.with_hi(last.ident.span.hi());
            if lint_ty_kind_usage(cx, last) {
                cx.struct_span_lint(USAGE_OF_TY_TYKIND, span, "usage of `ty::TyKind::<kind>`")
                    .span_suggestion(
                        span,
                        "try using ty::<kind> directly",
                        "ty".to_string(),
                        Applicability::MaybeIncorrect, // ty maybe needs an import
                    )
                    .emit();
            }
        }
    }

    fn check_ty(&mut self, cx: &LateContext<'_, '_>, ty: &'tcx Ty) {
        if let TyKind::Path(qpath) = &ty.node {
            if let QPath::Resolved(_, path) = qpath {
                if let Some(last) = path.segments.iter().last() {
                    if lint_ty_kind_usage(cx, last) {
                        cx.struct_span_lint(USAGE_OF_TY_TYKIND, path.span, "usage of `ty::TyKind`")
                            .help("try using `ty::Ty` instead")
                            .emit();
                    }
                }
            }
        }
    }
}

fn lint_ty_kind_usage(cx: &LateContext<'_, '_>, segment: &PathSegment) -> bool {
    if segment.ident.as_str() == "TyKind" {
        if let Some(def) = segment.def {
            if let Some(did) = def.opt_def_id() {
                return did.match_path(cx.tcx, &["rustc", "ty", "sty", "TyKind"]);
            }
        }
    }

    false
}
<|endoftext|>


<commit_before>// Copyright 2014 The Rust Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.

use libc;
use cell::UnsafeCell;
use sync::atomic::{AtomicUsize, Ordering};

pub struct RWLock {
    inner: UnsafeCell<libc::pthread_rwlock_t>,
    write_locked: UnsafeCell<bool>, // guarded by the `inner` RwLock
    num_readers: AtomicUsize,
}

unsafe impl Send for RWLock {}
unsafe impl Sync for RWLock {}

impl RWLock {
    pub const fn new() -> RWLock {
        RWLock {
            inner: UnsafeCell::new(libc::PTHREAD_RWLOCK_INITIALIZER),
            write_locked: UnsafeCell::new(false),
            num_readers: AtomicUsize::new(0),
        }
    }
    #[inline]
    pub unsafe fn read(&self) {
        let r = libc::pthread_rwlock_rdlock(self.inner.get());

        // According to the pthread_rwlock_rdlock spec, this function **may**
        // fail with EDEADLK if a deadlock is detected. On the other hand
        // pthread mutexes will *never* return EDEADLK if they are initialized
        // as the "fast" kind (which ours always are). As a result, a deadlock
        // situation may actually return from the call to pthread_rwlock_rdlock
        // instead of blocking forever (as mutexes and Windows rwlocks do). Note
        // that not all unix implementations, however, will return EDEADLK for
        // their rwlocks.
        //
        // We roughly maintain the deadlocking behavior by panicking to ensure
        // that this lock acquisition does not succeed.
        //
        // We also check whether this lock is already write locked. This
        // is only possible if it was write locked by the current thread and
        // the implementation allows recursive locking. The POSIX standard
        // doesn't require recursively locking a rwlock to deadlock, but we can't
        // allow that because it could lead to aliasing issues.
        if r == libc::EAGAIN {
            panic!("rwlock maximum reader count exceeded");
        } else if r == libc::EDEADLK || (r == 0 && *self.write_locked.get()) {
            if r == 0 {
                self.raw_unlock();
            }
            panic!("rwlock read lock would result in deadlock");
        } else {
            debug_assert_eq!(r, 0);
            self.num_readers.fetch_add(1, Ordering::Relaxed);
        }
    }
    #[inline]
    pub unsafe fn try_read(&self) -> bool {
        let r = libc::pthread_rwlock_tryrdlock(self.inner.get());
        if r == 0 {
            if *self.write_locked.get() {
                self.raw_unlock();
                false
            } else {
                self.num_readers.fetch_add(1, Ordering::Relaxed);
                true
            }
        } else {
            false
        }
    }
    #[inline]
    pub unsafe fn write(&self) {
        let r = libc::pthread_rwlock_wrlock(self.inner.get());
        // See comments above for why we check for EDEADLK and write_locked. We
        // also need to check that num_readers is 0.
        if r == libc::EDEADLK || *self.write_locked.get() ||
           self.num_readers.load(Ordering::Relaxed) != 0 {
            if r == 0 {
                self.raw_unlock();
            }
            panic!("rwlock write lock would result in deadlock");
        } else {
            debug_assert_eq!(r, 0);
        }
        *self.write_locked.get() = true;
    }
    #[inline]
    pub unsafe fn try_write(&self) -> bool {
        let r = libc::pthread_rwlock_trywrlock(self.inner.get());
        if r == 0 {
            if *self.write_locked.get() || self.num_readers.load(Ordering::Relaxed) != 0 {
                self.raw_unlock();
                false
            } else {
                *self.write_locked.get() = true;
                true
            }
        } else {
            false
        }
    }
    #[inline]
    unsafe fn raw_unlock(&self) {
        let r = libc::pthread_rwlock_unlock(self.inner.get());
        debug_assert_eq!(r, 0);
    }
    #[inline]
    pub unsafe fn read_unlock(&self) {
        debug_assert!(!*self.write_locked.get());
        self.num_readers.fetch_sub(1, Ordering::Relaxed);
        self.raw_unlock();
    }
    #[inline]
    pub unsafe fn write_unlock(&self) {
        debug_assert_eq!(self.num_readers.load(Ordering::Relaxed), 0);
        debug_assert!(*self.write_locked.get());
        *self.write_locked.get() = false;
        self.raw_unlock();
    }
    #[inline]
    pub unsafe fn destroy(&self) {
        let r = libc::pthread_rwlock_destroy(self.inner.get());
        // On DragonFly pthread_rwlock_destroy() returns EINVAL if called on a
        // rwlock that was just initialized with
        // libc::PTHREAD_RWLOCK_INITIALIZER. Once it is used (locked/unlocked)
        // or pthread_rwlock_init() is called, this behaviour no longer occurs.
        if cfg!(target_os = "dragonfly") {
            debug_assert!(r == 0 || r == libc::EINVAL);
        } else {
            debug_assert_eq!(r, 0);
        }
    }
}
<commit_msg>do not skip return code check in release builds<commit_after>// Copyright 2014 The Rust Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.

use libc;
use cell::UnsafeCell;
use sync::atomic::{AtomicUsize, Ordering};

pub struct RWLock {
    inner: UnsafeCell<libc::pthread_rwlock_t>,
    write_locked: UnsafeCell<bool>, // guarded by the `inner` RwLock
    num_readers: AtomicUsize,
}

unsafe impl Send for RWLock {}
unsafe impl Sync for RWLock {}

impl RWLock {
    pub const fn new() -> RWLock {
        RWLock {
            inner: UnsafeCell::new(libc::PTHREAD_RWLOCK_INITIALIZER),
            write_locked: UnsafeCell::new(false),
            num_readers: AtomicUsize::new(0),
        }
    }
    #[inline]
    pub unsafe fn read(&self) {
        let r = libc::pthread_rwlock_rdlock(self.inner.get());

        // According to the pthread_rwlock_rdlock spec, this function **may**
        // fail with EDEADLK if a deadlock is detected. On the other hand
        // pthread mutexes will *never* return EDEADLK if they are initialized
        // as the "fast" kind (which ours always are). As a result, a deadlock
        // situation may actually return from the call to pthread_rwlock_rdlock
        // instead of blocking forever (as mutexes and Windows rwlocks do). Note
        // that not all unix implementations, however, will return EDEADLK for
        // their rwlocks.
        //
        // We roughly maintain the deadlocking behavior by panicking to ensure
        // that this lock acquisition does not succeed.
        //
        // We also check whether this lock is already write locked. This
        // is only possible if it was write locked by the current thread and
        // the implementation allows recursive locking. The POSIX standard
        // doesn't require recursively locking a rwlock to deadlock, but we can't
        // allow that because it could lead to aliasing issues.
        if r == libc::EAGAIN {
            panic!("rwlock maximum reader count exceeded");
        } else if r == libc::EDEADLK || (r == 0 && *self.write_locked.get()) {
            if r == 0 {
                self.raw_unlock();
            }
            panic!("rwlock read lock would result in deadlock");
        } else {
            assert_eq!(r, 0);
            self.num_readers.fetch_add(1, Ordering::Relaxed);
        }
    }
    #[inline]
    pub unsafe fn try_read(&self) -> bool {
        let r = libc::pthread_rwlock_tryrdlock(self.inner.get());
        if r == 0 {
            if *self.write_locked.get() {
                self.raw_unlock();
                false
            } else {
                self.num_readers.fetch_add(1, Ordering::Relaxed);
                true
            }
        } else {
            false
        }
    }
    #[inline]
    pub unsafe fn write(&self) {
        let r = libc::pthread_rwlock_wrlock(self.inner.get());
        // See comments above for why we check for EDEADLK and write_locked. We
        // also need to check that num_readers is 0.
        if r == libc::EDEADLK || *self.write_locked.get() ||
           self.num_readers.load(Ordering::Relaxed) != 0 {
            if r == 0 {
                self.raw_unlock();
            }
            panic!("rwlock write lock would result in deadlock");
        } else {
            debug_assert_eq!(r, 0);
        }
        *self.write_locked.get() = true;
    }
    #[inline]
    pub unsafe fn try_write(&self) -> bool {
        let r = libc::pthread_rwlock_trywrlock(self.inner.get());
        if r == 0 {
            if *self.write_locked.get() || self.num_readers.load(Ordering::Relaxed) != 0 {
                self.raw_unlock();
                false
            } else {
                *self.write_locked.get() = true;
                true
            }
        } else {
            false
        }
    }
    #[inline]
    unsafe fn raw_unlock(&self) {
        let r = libc::pthread_rwlock_unlock(self.inner.get());
        debug_assert_eq!(r, 0);
    }
    #[inline]
    pub unsafe fn read_unlock(&self) {
        debug_assert!(!*self.write_locked.get());
        self.num_readers.fetch_sub(1, Ordering::Relaxed);
        self.raw_unlock();
    }
    #[inline]
    pub unsafe fn write_unlock(&self) {
        debug_assert_eq!(self.num_readers.load(Ordering::Relaxed), 0);
        debug_assert!(*self.write_locked.get());
        *self.write_locked.get() = false;
        self.raw_unlock();
    }
    #[inline]
    pub unsafe fn destroy(&self) {
        let r = libc::pthread_rwlock_destroy(self.inner.get());
        // On DragonFly pthread_rwlock_destroy() returns EINVAL if called on a
        // rwlock that was just initialized with
        // libc::PTHREAD_RWLOCK_INITIALIZER. Once it is used (locked/unlocked)
        // or pthread_rwlock_init() is called, this behaviour no longer occurs.
        if cfg!(target_os = "dragonfly") {
            debug_assert!(r == 0 || r == libc::EINVAL);
        } else {
            debug_assert_eq!(r, 0);
        }
    }
}
<|endoftext|>


<commit_before>extern crate handlebars;
extern crate itertools;
extern crate serde;
#[macro_use] extern crate serde_derive;
extern crate serde_json;

use handlebars::Handlebars;
use itertools::Itertools;
use serde_json::Value;
use std::collections::BTreeMap;

trait Codegen {
    fn codegen(&self, handlebars: &Handlebars) -> String;
}

#[derive(Debug, Deserialize)]
pub struct AMQProtocolDefinition {
    pub name:          String,
    #[serde(rename="major-version")]
    pub major_version: u8,
    #[serde(rename="minor-version")]
    pub minor_version: u8,
    pub revision:      u8,
    pub port:          u32,
    pub copyright:     Vec<String>,
    pub domains:       Vec<AMQPDomain>,
    pub constants:     Vec<AMQPConstant>,
    pub classes:       Vec<AMQPClass>,
}

impl AMQProtocolDefinition {
    pub fn load() -> AMQProtocolDefinition {
        let specs = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/specs/amqp-rabbitmq-0.9.1.json"));

        serde_json::from_str::<AMQProtocolDefinition>(specs).expect("Failed to parse AMQP specs file")
    }

    pub fn codegen(&self, templates: &AMQPTemplates) -> String {
        let handlebars = register_templates(templates);
        let mut data   = BTreeMap::new();

        data.insert("name".to_string(),          self.name.clone());
        data.insert("major_version".to_string(), format!("{}", self.major_version));
        data.insert("minor_version".to_string(), format!("{}", self.minor_version));
        data.insert("revision".to_string(),      format!("{}", self.revision));
        data.insert("port".to_string(),          format!("{}", self.port));
        data.insert("copyright".to_string(),     self.copyright.iter().join(""));
        data.insert("domains".to_string(),       self.domains.iter().map(|domain| domain.codegen(&handlebars)).join("\n"));
        data.insert("constants".to_string(),     self.constants.iter().map(|constant| constant.codegen(&handlebars)).join("\n"));
        data.insert("classes".to_string(),       self.classes.iter().map(|klass| klass.codegen(&handlebars)).join("\n"));

        handlebars.render("main", &data).expect("Failed to render main template")
    }
}

fn register_templates(templates: &AMQPTemplates) -> Handlebars {
    let mut handlebars = Handlebars::new();

    handlebars.register_escape_fn(handlebars::no_escape);

    handlebars.register_template_string("main",     &templates.main).expect("Failed to register main template");
    handlebars.register_template_string("domain",   &templates.domain).expect("Failed to register domain template");
    handlebars.register_template_string("constant", &templates.constant).expect("Failed to register constant template");
    handlebars.register_template_string("class",    &templates.klass).expect("Failed to register class template");
    handlebars.register_template_string("method",   &templates.method).expect("Failed to register method template");
    handlebars.register_template_string("argument", &templates.argument).expect("Failed to register argument template");
    handlebars.register_template_string("property", &templates.property).expect("Failed to register property template");

    handlebars
}

pub struct AMQPTemplates {
    pub main:     String,
    pub domain:   String,
    pub constant: String,
    pub klass:    String,
    pub method:   String,
    pub argument: String,
    pub property: String,
}

#[derive(Debug, Deserialize)]
pub enum AMQPType {
    #[serde(rename="bit")]
    Bit,
    #[serde(rename="octet")]
    Octet,
    #[serde(rename="short")]
    Short,
    #[serde(rename="long")]
    Long,
    #[serde(rename="longlong")]
    LongLong,
    #[serde(rename="shortstr")]
    ShortStr,
    #[serde(rename="longstr")]
    LongStr,
    #[serde(rename="table")]
    Table,
    #[serde(rename="timestamp")]
    Timestamp,
}

impl AMQPType {
    fn to_string(&self) -> String {
        match *self {
            AMQPType::Bit       => "bit",
            AMQPType::Octet     => "octet",
            AMQPType::Short     => "short",
            AMQPType::Long      => "long",
            AMQPType::LongLong  => "longlong",
            AMQPType::ShortStr  => "shortstr",
            AMQPType::LongStr   => "longstr",
            AMQPType::Table     => "table",
            AMQPType::Timestamp => "timestamp",
        }.to_string()
    }
}

#[derive(Debug, Deserialize)]
pub struct AMQPDomain(String, AMQPType);

impl Codegen for AMQPDomain {
    fn codegen(&self, handlebars: &Handlebars) -> String {
        let mut data = BTreeMap::new();

        data.insert("name".to_string(), self.0.clone());
        data.insert("type".to_string(), self.1.to_string());

        handlebars.render("domain", &data).expect("Failed to render domain template")
    }
}

#[derive(Debug, Deserialize)]
pub struct AMQPConstant {
    pub name:  String,
    pub value: u16,
    #[serde(rename="class")]
    pub klass: Option<String>,
}

impl Codegen for AMQPConstant {
    fn codegen(&self, handlebars: &Handlebars) -> String {
        let mut data = BTreeMap::new();

        data.insert("name".to_string(),  self.name.clone());
        data.insert("value".to_string(), format!("{}", self.value));
        if let Some(ref klass) = self.klass {
            data.insert("class".to_string(), klass.clone());
        }

        handlebars.render("constant", &data).expect("Failed to render constant template")
    }
}

#[derive(Debug, Deserialize)]
pub struct AMQPClass {
    pub id:         u8,
    pub methods:    Vec<AMQPMethod>,
    pub name:       String,
    pub properties: Option<Vec<AMQPProperty>>,
}

impl Codegen for AMQPClass {
    fn codegen(&self, handlebars: &Handlebars) -> String {
        let mut data = BTreeMap::new();

        data.insert("id".to_string(),      format!("{}", self.id));
        data.insert("methods".to_string(), self.methods.iter().map(|method| method.codegen(&handlebars)).join("\n"));
        data.insert("name".to_string(),    self.name.clone());
        if let Some(ref properties) = self.properties {
            data.insert("properties".to_string(), properties.iter().map(|prop| prop.codegen(&handlebars)).join("\n"));
        }

        handlebars.render("class", &data).expect("Failed to render class template")
    }
}

#[derive(Debug, Deserialize)]
pub struct AMQPMethod {
    pub id:          u8,
    pub arguments:   Vec<AMQPArgument>,
    pub name:        String,
    pub synchronous: Option<bool>,
}

impl Codegen for AMQPMethod {
    fn codegen(&self, handlebars: &Handlebars) -> String {
        let mut data = BTreeMap::new();

        data.insert("id".to_string(),          format!("{}", self.id));
        data.insert("arguments".to_string(),   self.arguments.iter().map(|arg| arg.codegen(&handlebars)).join("\n"));
        data.insert("name".to_string(),        self.name.clone());
        data.insert("synchronous".to_string(), format!("{}", self.synchronous.unwrap_or(false)));

        handlebars.render("method", &data).expect("Failed to render method template")
    }
}

#[derive(Debug, Deserialize)]
pub struct AMQPArgument {
    #[serde(rename="type")]
    pub amqp_type:     Option<AMQPType>,
    pub name:          String,
    #[serde(rename="default-value")]
    pub default_value: Option<Value>,
    pub domain:        Option<String>,
}

impl Codegen for AMQPArgument {
    fn codegen(&self, handlebars: &Handlebars) -> String {
        let mut data = BTreeMap::new();

        if let Some(ref amqp_type) = self.amqp_type {
            data.insert("type".to_string(), amqp_type.to_string());
        }
        if let Some(ref default_value) = self.default_value {
            data.insert("default_value".to_string(), default_value.to_string());
        }
        data.insert("name".to_string(),   self.name.clone());
        if let Some(ref domain) = self.domain {
            data.insert("domain".to_string(), domain.clone());
        }

        handlebars.render("argument", &data).expect("Failed to render domain template")
    }
}

#[derive(Debug, Deserialize)]
pub struct AMQPProperty {
    #[serde(rename="type")]
    pub amqp_type: AMQPType,
    pub name:      String,
}

impl Codegen for AMQPProperty {
    fn codegen(&self, handlebars: &Handlebars) -> String {
        let mut data = BTreeMap::new();

        data.insert("type".to_string(), self.amqp_type.to_string());
        data.insert("name".to_string(), self.name.clone());

        handlebars.render("property", &data).expect("Failed to render domain template")
    }
}

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

    fn specs() -> AMQProtocolDefinition {
        AMQProtocolDefinition {
            name:          "AMQP".to_string(),
            major_version: 0,
            minor_version: 9,
            revision:      1,
            port:          5672,
            copyright:     vec!["Copyright 1".to_string(), "Copyright 2".to_string()],
            domains:       vec![AMQPDomain("domain1".to_string(), AMQPType::Octet)],
            constants:     vec![
                AMQPConstant {
                    name:  "constant1".to_string(),
                    value: 128,
                    klass: Some("class1".to_string()),
                }
            ],
            classes:       vec![
                AMQPClass {
                    id:         42,
                    methods:    vec![
                        AMQPMethod {
                            id:          64,
                            arguments:   vec![
                                AMQPArgument {
                                    amqp_type:     Some(AMQPType::LongStr),
                                    name:          "argument1".to_string(),
                                    default_value: Some(From::from("value1")),
                                    domain:        Some("domain1".to_string()),
                                }
                            ],
                            name:        "method1".to_string(),
                            synchronous: Some(true),
                        }
                    ],
                    name:       "class1".to_string(),
                    properties: Some(vec![
                        AMQPProperty {
                            amqp_type: AMQPType::LongStr,
                            name:      "property1".to_string(),
                        }
                    ]),
                }
            ],
        }
    }

    fn templates() -> AMQPTemplates {
        AMQPTemplates {
            main:     r#"
{{name}} - {{major_version}}.{{minor_version}}.{{revision}}
{{copyright}}
port {{port}}
{{domains}}
{{constants}}
{{classes}}
"#.to_string(),
            domain:   "{{name}}: {{type}}".to_string(),
            constant: "{{name}}({{class}}) = {{value}}".to_string(),
            klass:    r#"
{{id}} - {{name}}
{{properties}}
{{methods}}
"#.to_string(),
            method:   r#"
{{id}} - {{name}}
synchronous: {{synchronous}}
{{arguments}}
"#.to_string(),
            argument: "{{name}}({{domain}}): {{type}} = {{default_value}}".to_string(),
            property: "{{name}}: {{type}}".to_string(),
        }
    }

    #[test]
    fn main_template() {
        assert_eq!(specs().codegen(&templates()), r#"
AMQP - 0.9.1
Copyright 1
Copyright 2
port 5672
domain1: octet
constant1(class1) = 128

42 - class1
property1: longstr

64 - method1
synchronous: true
argument1(domain1): longstr = "value1"

"#);
    }
}
<commit_msg>codegen: inject more template vars for properties<commit_after>extern crate handlebars;
extern crate itertools;
extern crate serde;
#[macro_use] extern crate serde_derive;
extern crate serde_json;

use handlebars::Handlebars;
use itertools::Itertools;
use serde_json::Value;
use std::collections::BTreeMap;
use std::ascii::AsciiExt;

trait Codegen {
    fn codegen(&self, handlebars: &Handlebars) -> String;
}

#[derive(Debug, Deserialize)]
pub struct AMQProtocolDefinition {
    pub name:          String,
    #[serde(rename="major-version")]
    pub major_version: u8,
    #[serde(rename="minor-version")]
    pub minor_version: u8,
    pub revision:      u8,
    pub port:          u32,
    pub copyright:     Vec<String>,
    pub domains:       Vec<AMQPDomain>,
    pub constants:     Vec<AMQPConstant>,
    pub classes:       Vec<AMQPClass>,
}

impl AMQProtocolDefinition {
    pub fn load() -> AMQProtocolDefinition {
        let specs = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/specs/amqp-rabbitmq-0.9.1.json"));

        serde_json::from_str::<AMQProtocolDefinition>(specs).expect("Failed to parse AMQP specs file")
    }

    pub fn codegen(&self, templates: &AMQPTemplates) -> String {
        let handlebars = register_templates(templates);
        let mut data   = BTreeMap::new();

        data.insert("name".to_string(),          self.name.clone());
        data.insert("major_version".to_string(), format!("{}", self.major_version));
        data.insert("minor_version".to_string(), format!("{}", self.minor_version));
        data.insert("revision".to_string(),      format!("{}", self.revision));
        data.insert("port".to_string(),          format!("{}", self.port));
        data.insert("copyright".to_string(),     self.copyright.iter().join(""));
        data.insert("domains".to_string(),       self.domains.iter().map(|domain| domain.codegen(&handlebars)).join("\n"));
        data.insert("constants".to_string(),     self.constants.iter().map(|constant| constant.codegen(&handlebars)).join("\n"));
        data.insert("classes".to_string(),       self.classes.iter().map(|klass| klass.codegen(&handlebars)).join("\n"));

        handlebars.render("main", &data).expect("Failed to render main template")
    }
}

fn register_templates(templates: &AMQPTemplates) -> Handlebars {
    let mut handlebars = Handlebars::new();

    handlebars.register_escape_fn(handlebars::no_escape);

    handlebars.register_template_string("main",     &templates.main).expect("Failed to register main template");
    handlebars.register_template_string("domain",   &templates.domain).expect("Failed to register domain template");
    handlebars.register_template_string("constant", &templates.constant).expect("Failed to register constant template");
    handlebars.register_template_string("class",    &templates.klass).expect("Failed to register class template");
    handlebars.register_template_string("method",   &templates.method).expect("Failed to register method template");
    handlebars.register_template_string("argument", &templates.argument).expect("Failed to register argument template");
    handlebars.register_template_string("property", &templates.property).expect("Failed to register property template");

    handlebars
}

pub struct AMQPTemplates {
    pub main:     String,
    pub domain:   String,
    pub constant: String,
    pub klass:    String,
    pub method:   String,
    pub argument: String,
    pub property: String,
}

#[derive(Debug, Deserialize)]
pub enum AMQPType {
    #[serde(rename="bit")]
    Bit,
    #[serde(rename="octet")]
    Octet,
    #[serde(rename="short")]
    Short,
    #[serde(rename="long")]
    Long,
    #[serde(rename="longlong")]
    LongLong,
    #[serde(rename="shortstr")]
    ShortStr,
    #[serde(rename="longstr")]
    LongStr,
    #[serde(rename="table")]
    Table,
    #[serde(rename="timestamp")]
    Timestamp,
}

impl AMQPType {
    fn to_string(&self) -> String {
        match *self {
            AMQPType::Bit       => "bit",
            AMQPType::Octet     => "octet",
            AMQPType::Short     => "short",
            AMQPType::Long      => "long",
            AMQPType::LongLong  => "longlong",
            AMQPType::ShortStr  => "shortstr",
            AMQPType::LongStr   => "longstr",
            AMQPType::Table     => "table",
            AMQPType::Timestamp => "timestamp",
        }.to_string()
    }

    fn to_rust_type(&self) -> String {
        match *self {
            AMQPType::Bit       => "bool",
            AMQPType::Octet     => "u8",
            AMQPType::Short     => "u16",
            AMQPType::Long      => "u32",
            AMQPType::LongLong  => "u64",
            AMQPType::ShortStr  => "String",
            AMQPType::LongStr   => "String",
            AMQPType::Table     => "String", /* FIXME: add a custom type */
            AMQPType::Timestamp => "u64",
        }.to_string()
    }
}

#[derive(Debug, Deserialize)]
pub struct AMQPDomain(String, AMQPType);

impl Codegen for AMQPDomain {
    fn codegen(&self, handlebars: &Handlebars) -> String {
        let mut data = BTreeMap::new();

        data.insert("name".to_string(), self.0.clone());
        data.insert("type".to_string(), self.1.to_string());

        handlebars.render("domain", &data).expect("Failed to render domain template")
    }
}

#[derive(Debug, Deserialize)]
pub struct AMQPConstant {
    pub name:  String,
    pub value: u16,
    #[serde(rename="class")]
    pub klass: Option<String>,
}

impl Codegen for AMQPConstant {
    fn codegen(&self, handlebars: &Handlebars) -> String {
        let mut data = BTreeMap::new();

        data.insert("name".to_string(),  self.name.clone());
        data.insert("value".to_string(), format!("{}", self.value));
        if let Some(ref klass) = self.klass {
            data.insert("class".to_string(), klass.clone());
        }

        handlebars.render("constant", &data).expect("Failed to render constant template")
    }
}

#[derive(Debug, Deserialize)]
pub struct AMQPClass {
    pub id:         u8,
    pub methods:    Vec<AMQPMethod>,
    pub name:       String,
    pub properties: Option<Vec<AMQPProperty>>,
}

impl Codegen for AMQPClass {
    fn codegen(&self, handlebars: &Handlebars) -> String {
        let mut data = BTreeMap::new();

        data.insert("id".to_string(),      format!("{}", self.id));
        data.insert("methods".to_string(), self.methods.iter().map(|method| method.codegen(&handlebars)).join("\n"));
        data.insert("name".to_string(),    self.name.clone());
        if let Some(ref properties) = self.properties {
            data.insert("properties".to_string(), properties.iter().map(|prop| prop.codegen(&handlebars)).join("\n"));
        }

        handlebars.render("class", &data).expect("Failed to render class template")
    }
}

#[derive(Debug, Deserialize)]
pub struct AMQPMethod {
    pub id:          u8,
    pub arguments:   Vec<AMQPArgument>,
    pub name:        String,
    pub synchronous: Option<bool>,
}

impl Codegen for AMQPMethod {
    fn codegen(&self, handlebars: &Handlebars) -> String {
        let mut data = BTreeMap::new();

        data.insert("id".to_string(),          format!("{}", self.id));
        data.insert("arguments".to_string(),   self.arguments.iter().map(|arg| arg.codegen(&handlebars)).join("\n"));
        data.insert("name".to_string(),        self.name.clone());
        data.insert("synchronous".to_string(), format!("{}", self.synchronous.unwrap_or(false)));

        handlebars.render("method", &data).expect("Failed to render method template")
    }
}

#[derive(Debug, Deserialize)]
pub struct AMQPArgument {
    #[serde(rename="type")]
    pub amqp_type:     Option<AMQPType>,
    pub name:          String,
    #[serde(rename="default-value")]
    pub default_value: Option<Value>,
    pub domain:        Option<String>,
}

impl Codegen for AMQPArgument {
    fn codegen(&self, handlebars: &Handlebars) -> String {
        let mut data = BTreeMap::new();

        if let Some(ref amqp_type) = self.amqp_type {
            data.insert("type".to_string(), amqp_type.to_string());
        }
        if let Some(ref default_value) = self.default_value {
            data.insert("default_value".to_string(), default_value.to_string());
        }
        data.insert("name".to_string(),   self.name.clone());
        if let Some(ref domain) = self.domain {
            data.insert("domain".to_string(), domain.clone());
        }

        handlebars.render("argument", &data).expect("Failed to render domain template")
    }
}

#[derive(Debug, Deserialize)]
pub struct AMQPProperty {
    #[serde(rename="type")]
    pub amqp_type: AMQPType,
    pub name:      String,
}

impl AMQPProperty {
    pub fn struct_name(&self) -> String {
        let mut new_word: bool = true;
        self.name.chars().fold("".to_string(), |mut result, ch| {
            if ch == '-' || ch == '_' || ch == ' ' {
                new_word = true;
                result
            } else {
                result.push(if new_word { ch.to_ascii_uppercase() } else { ch.to_ascii_lowercase() });
                new_word = false;
                result
            }
        })
    }
}

impl Codegen for AMQPProperty {
    fn codegen(&self, handlebars: &Handlebars) -> String {
        let mut data = BTreeMap::new();

        data.insert("type".to_string(),        self.amqp_type.to_string());
        data.insert("rust_type".to_string(),   self.amqp_type.to_rust_type());
        data.insert("name".to_string(),        self.name.clone());
        data.insert("struct_name".to_string(), self.struct_name());

        handlebars.render("property", &data).expect("Failed to render domain template")
    }
}

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

    fn specs() -> AMQProtocolDefinition {
        AMQProtocolDefinition {
            name:          "AMQP".to_string(),
            major_version: 0,
            minor_version: 9,
            revision:      1,
            port:          5672,
            copyright:     vec!["Copyright 1".to_string(), "Copyright 2".to_string()],
            domains:       vec![AMQPDomain("domain1".to_string(), AMQPType::Octet)],
            constants:     vec![
                AMQPConstant {
                    name:  "constant1".to_string(),
                    value: 128,
                    klass: Some("class1".to_string()),
                }
            ],
            classes:       vec![
                AMQPClass {
                    id:         42,
                    methods:    vec![
                        AMQPMethod {
                            id:          64,
                            arguments:   vec![
                                AMQPArgument {
                                    amqp_type:     Some(AMQPType::LongStr),
                                    name:          "argument1".to_string(),
                                    default_value: Some(From::from("value1")),
                                    domain:        Some("domain1".to_string()),
                                }
                            ],
                            name:        "method1".to_string(),
                            synchronous: Some(true),
                        }
                    ],
                    name:       "class1".to_string(),
                    properties: Some(vec![
                        AMQPProperty {
                            amqp_type: AMQPType::LongStr,
                            name:      "property1".to_string(),
                        }
                    ]),
                }
            ],
        }
    }

    fn templates() -> AMQPTemplates {
        AMQPTemplates {
            main:     r#"
{{name}} - {{major_version}}.{{minor_version}}.{{revision}}
{{copyright}}
port {{port}}
{{domains}}
{{constants}}
{{classes}}
"#.to_string(),
            domain:   "{{name}}: {{type}}".to_string(),
            constant: "{{name}}({{class}}) = {{value}}".to_string(),
            klass:    r#"
{{id}} - {{name}}
{{properties}}
{{methods}}
"#.to_string(),
            method:   r#"
{{id}} - {{name}}
synchronous: {{synchronous}}
{{arguments}}
"#.to_string(),
            argument: "{{name}}({{domain}}): {{type}} = {{default_value}}".to_string(),
            property: "{{name}}: {{type}}".to_string(),
        }
    }

    #[test]
    fn main_template() {
        assert_eq!(specs().codegen(&templates()), r#"
AMQP - 0.9.1
Copyright 1
Copyright 2
port 5672
domain1: octet
constant1(class1) = 128

42 - class1
property1: longstr

64 - method1
synchronous: true
argument1(domain1): longstr = "value1"

"#);
    }
}
<|endoftext|>


<commit_before>extern crate handlebars;
extern crate itertools;
extern crate serde;
#[macro_use] extern crate serde_derive;
extern crate serde_json;

use handlebars::Handlebars;
use itertools::Itertools;
use serde_json::Value;
use std::collections::BTreeMap;

trait Codegen {
    fn codegen(&self, handlebars: &Handlebars) -> String;
}

#[derive(Debug, Deserialize)]
pub struct AMQProtocolDefinition {
    pub name:          String,
    #[serde(rename="major-version")]
    pub major_version: u8,
    #[serde(rename="minor-version")]
    pub minor_version: u8,
    pub revision:      u8,
    pub port:          u32,
    pub copyright:     Vec<String>,
    pub domains:       Vec<AMQPDomain>,
    pub constants:     Vec<AMQPConstant>,
    pub classes:       Vec<AMQPClass>,
}

impl AMQProtocolDefinition {
    pub fn codegen(&self, templates: &AMQPTemplates) -> String {
        let handlebars = register_templates(templates);
        let mut data   = BTreeMap::new();

        data.insert("name".to_string(),          self.name.clone());
        data.insert("major_version".to_string(), format!("{}", self.major_version));
        data.insert("minor_version".to_string(), format!("{}", self.minor_version));
        data.insert("revision".to_string(),      format!("{}", self.revision));
        data.insert("port".to_string(),          format!("{}", self.port));
        data.insert("copyright".to_string(),     self.copyright.iter().join("\n"));
        data.insert("domains".to_string(),       self.domains.iter().map(|domain| domain.codegen(&handlebars)).join("\n"));
        data.insert("constants".to_string(),     self.constants.iter().map(|constant| constant.codegen(&handlebars)).join("\n"));
        data.insert("classes".to_string(),       self.classes.iter().map(|klass| klass.codegen(&handlebars)).join("\n"));

        handlebars.render("main", &data).expect("Failed to render main template")
    }
}

fn register_templates(templates: &AMQPTemplates) -> Handlebars {
    let mut handlebars = Handlebars::new();

    handlebars.register_template_string("main",     &templates.main).expect("Failed to register main template");
    handlebars.register_template_string("domain",   &templates.main).expect("Failed to register domain template");
    handlebars.register_template_string("constant", &templates.constant).expect("Failed to register constant template");
    handlebars.register_template_string("class",    &templates.klass).expect("Failed to register class template");
    handlebars.register_template_string("method",   &templates.method).expect("Failed to register method template");
    handlebars.register_template_string("argument", &templates.argument).expect("Failed to register argument template");
    handlebars.register_template_string("property", &templates.property).expect("Failed to register property template");

    handlebars
}

pub struct AMQPTemplates {
    pub main:     String,
    pub domain:   String,
    pub constant: String,
    pub klass:    String,
    pub method:   String,
    pub argument: String,
    pub property: String,
}

#[derive(Debug, Deserialize)]
pub enum AMQPType {
    #[serde(rename="bit")]
    Bit,
    #[serde(rename="octet")]
    Octet,
    #[serde(rename="short")]
    Short,
    #[serde(rename="long")]
    Long,
    #[serde(rename="longlong")]
    LongLong,
    #[serde(rename="shortstr")]
    ShortStr,
    #[serde(rename="longstr")]
    LongStr,
    #[serde(rename="table")]
    Table,
    #[serde(rename="timestamp")]
    Timestamp,
}

impl AMQPType {
    fn to_string(&self) -> String {
        match *self {
            AMQPType::Bit       => "bit",
            AMQPType::Octet     => "octet",
            AMQPType::Short     => "short",
            AMQPType::Long      => "long",
            AMQPType::LongLong  => "longlong",
            AMQPType::ShortStr  => "shortstr",
            AMQPType::LongStr   => "longstr",
            AMQPType::Table     => "table",
            AMQPType::Timestamp => "timestamp",
        }.to_string()
    }
}

#[derive(Debug, Deserialize)]
pub struct AMQPDomain(String, AMQPType);

impl Codegen for AMQPDomain {
    fn codegen(&self, handlebars: &Handlebars) -> String {
        let mut data = BTreeMap::new();

        data.insert("name".to_string(), self.0.clone());
        data.insert("type".to_string(), self.1.to_string());

        handlebars.render("domain", &data).expect("Failed to render domain template")
    }
}

#[derive(Debug, Deserialize)]
pub struct AMQPConstant {
    pub name:  String,
    pub value: u16,
    #[serde(rename="class")]
    pub klass: Option<String>,
}

impl Codegen for AMQPConstant {
    fn codegen(&self, handlebars: &Handlebars) -> String {
        let mut data = BTreeMap::new();

        data.insert("name".to_string(),  self.name.clone());
        data.insert("value".to_string(), format!("{}", self.value));
        if let Some(ref klass) = self.klass {
            data.insert("class".to_string(), klass.clone());
        }

        handlebars.render("constant", &data).expect("Failed to render constant template")
    }
}

#[derive(Debug, Deserialize)]
pub struct AMQPClass {
    pub id:         u8,
    pub methods:    Vec<AMQPMethod>,
    pub name:       String,
    pub properties: Option<Vec<AMQPProperty>>,
}

impl Codegen for AMQPClass {
    fn codegen(&self, handlebars: &Handlebars) -> String {
        let mut data = BTreeMap::new();

        data.insert("id".to_string(),      format!("{}", self.id));
        data.insert("methods".to_string(), self.methods.iter().map(|method| method.codegen(&handlebars)).join("\n"));
        data.insert("name".to_string(),    self.name.clone());
        if let Some(ref properties) = self.properties {
            data.insert("properties".to_string(), properties.iter().map(|prop| prop.codegen(&handlebars)).join("\n"));
        }

        handlebars.render("class", &data).expect("Failed to render class template")
    }
}

#[derive(Debug, Deserialize)]
pub struct AMQPMethod {
    pub id:          u8,
    pub arguments:   Vec<AMQPArgument>,
    pub name:        String,
    pub synchronous: Option<bool>,
}

impl Codegen for AMQPMethod {
    fn codegen(&self, handlebars: &Handlebars) -> String {
        let mut data = BTreeMap::new();

        data.insert("id".to_string(),          format!("{}", self.id));
        data.insert("arguments".to_string(),   self.arguments.iter().map(|arg| arg.codegen(&handlebars)).join("\n"));
        data.insert("name".to_string(),        self.name.clone());
        data.insert("synchronous".to_string(), format!("{}", self.synchronous.unwrap_or(false)));

        handlebars.render("method", &data).expect("Failed to render method template")
    }
}

#[derive(Debug, Deserialize)]
pub struct AMQPArgument {
    #[serde(rename="type")]
    pub amqp_type:     Option<AMQPType>,
    pub name:          String,
    #[serde(rename="default-value")]
    pub default_value: Option<Value>,
    pub domain:        Option<String>,
}

impl Codegen for AMQPArgument {
    fn codegen(&self, handlebars: &Handlebars) -> String {
        let mut data = BTreeMap::new();

        if let Some(ref amqp_type) = self.amqp_type {
            data.insert("type".to_string(), amqp_type.to_string());
        }
        if let Some(ref default_value) = self.default_value {
            data.insert("default_value".to_string(), default_value.to_string());
        }
        data.insert("name".to_string(),   self.name.clone());
        if let Some(ref domain) = self.domain {
            data.insert("domain".to_string(), domain.clone());
        }

        handlebars.render("domain", &data).expect("Failed to render domain template")
    }
}

#[derive(Debug, Deserialize)]
pub struct AMQPProperty {
    #[serde(rename="type")]
    pub amqp_type: AMQPType,
    pub name:      String,
}

impl Codegen for AMQPProperty {
    fn codegen(&self, handlebars: &Handlebars) -> String {
        let mut data = BTreeMap::new();

        data.insert("type".to_string(), self.amqp_type.to_string());
        data.insert("name".to_string(), self.name.clone());

        handlebars.render("domain", &data).expect("Failed to render domain template")
    }
}

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

    fn specs() -> AMQProtocolDefinition {
        AMQProtocolDefinition {
            name:          "AMQP".to_string(),
            major_version: 0,
            minor_version: 9,
            revision:      1,
            port:          5672,
            copyright:     Vec::new(),
            domains:       Vec::new(),
            constants:     Vec::new(),
            classes:       Vec::new(),
        }
    }

    fn templates() -> AMQPTemplates {
        AMQPTemplates {
            main:     "{{name}} - {{major_version}}.{{minor_version}}.{{revision}}".to_string(),
            domain:   String::new(),
            constant: String::new(),
            klass:    String::new(),
            method:   String::new(),
            argument: String::new(),
            property: String::new(),
        }
    }

    #[test]
    fn main_template() {
        assert_eq!(specs().codegen(&templates()), "AMQP - 0.9.1");
    }
}
<commit_msg>codegen: use the right templates<commit_after>extern crate handlebars;
extern crate itertools;
extern crate serde;
#[macro_use] extern crate serde_derive;
extern crate serde_json;

use handlebars::Handlebars;
use itertools::Itertools;
use serde_json::Value;
use std::collections::BTreeMap;

trait Codegen {
    fn codegen(&self, handlebars: &Handlebars) -> String;
}

#[derive(Debug, Deserialize)]
pub struct AMQProtocolDefinition {
    pub name:          String,
    #[serde(rename="major-version")]
    pub major_version: u8,
    #[serde(rename="minor-version")]
    pub minor_version: u8,
    pub revision:      u8,
    pub port:          u32,
    pub copyright:     Vec<String>,
    pub domains:       Vec<AMQPDomain>,
    pub constants:     Vec<AMQPConstant>,
    pub classes:       Vec<AMQPClass>,
}

impl AMQProtocolDefinition {
    pub fn codegen(&self, templates: &AMQPTemplates) -> String {
        let handlebars = register_templates(templates);
        let mut data   = BTreeMap::new();

        data.insert("name".to_string(),          self.name.clone());
        data.insert("major_version".to_string(), format!("{}", self.major_version));
        data.insert("minor_version".to_string(), format!("{}", self.minor_version));
        data.insert("revision".to_string(),      format!("{}", self.revision));
        data.insert("port".to_string(),          format!("{}", self.port));
        data.insert("copyright".to_string(),     self.copyright.iter().join("\n"));
        data.insert("domains".to_string(),       self.domains.iter().map(|domain| domain.codegen(&handlebars)).join("\n"));
        data.insert("constants".to_string(),     self.constants.iter().map(|constant| constant.codegen(&handlebars)).join("\n"));
        data.insert("classes".to_string(),       self.classes.iter().map(|klass| klass.codegen(&handlebars)).join("\n"));

        handlebars.render("main", &data).expect("Failed to render main template")
    }
}

fn register_templates(templates: &AMQPTemplates) -> Handlebars {
    let mut handlebars = Handlebars::new();

    handlebars.register_template_string("main",     &templates.main).expect("Failed to register main template");
    handlebars.register_template_string("domain",   &templates.domain).expect("Failed to register domain template");
    handlebars.register_template_string("constant", &templates.constant).expect("Failed to register constant template");
    handlebars.register_template_string("class",    &templates.klass).expect("Failed to register class template");
    handlebars.register_template_string("method",   &templates.method).expect("Failed to register method template");
    handlebars.register_template_string("argument", &templates.argument).expect("Failed to register argument template");
    handlebars.register_template_string("property", &templates.property).expect("Failed to register property template");

    handlebars
}

pub struct AMQPTemplates {
    pub main:     String,
    pub domain:   String,
    pub constant: String,
    pub klass:    String,
    pub method:   String,
    pub argument: String,
    pub property: String,
}

#[derive(Debug, Deserialize)]
pub enum AMQPType {
    #[serde(rename="bit")]
    Bit,
    #[serde(rename="octet")]
    Octet,
    #[serde(rename="short")]
    Short,
    #[serde(rename="long")]
    Long,
    #[serde(rename="longlong")]
    LongLong,
    #[serde(rename="shortstr")]
    ShortStr,
    #[serde(rename="longstr")]
    LongStr,
    #[serde(rename="table")]
    Table,
    #[serde(rename="timestamp")]
    Timestamp,
}

impl AMQPType {
    fn to_string(&self) -> String {
        match *self {
            AMQPType::Bit       => "bit",
            AMQPType::Octet     => "octet",
            AMQPType::Short     => "short",
            AMQPType::Long      => "long",
            AMQPType::LongLong  => "longlong",
            AMQPType::ShortStr  => "shortstr",
            AMQPType::LongStr   => "longstr",
            AMQPType::Table     => "table",
            AMQPType::Timestamp => "timestamp",
        }.to_string()
    }
}

#[derive(Debug, Deserialize)]
pub struct AMQPDomain(String, AMQPType);

impl Codegen for AMQPDomain {
    fn codegen(&self, handlebars: &Handlebars) -> String {
        let mut data = BTreeMap::new();

        data.insert("name".to_string(), self.0.clone());
        data.insert("type".to_string(), self.1.to_string());

        handlebars.render("domain", &data).expect("Failed to render domain template")
    }
}

#[derive(Debug, Deserialize)]
pub struct AMQPConstant {
    pub name:  String,
    pub value: u16,
    #[serde(rename="class")]
    pub klass: Option<String>,
}

impl Codegen for AMQPConstant {
    fn codegen(&self, handlebars: &Handlebars) -> String {
        let mut data = BTreeMap::new();

        data.insert("name".to_string(),  self.name.clone());
        data.insert("value".to_string(), format!("{}", self.value));
        if let Some(ref klass) = self.klass {
            data.insert("class".to_string(), klass.clone());
        }

        handlebars.render("constant", &data).expect("Failed to render constant template")
    }
}

#[derive(Debug, Deserialize)]
pub struct AMQPClass {
    pub id:         u8,
    pub methods:    Vec<AMQPMethod>,
    pub name:       String,
    pub properties: Option<Vec<AMQPProperty>>,
}

impl Codegen for AMQPClass {
    fn codegen(&self, handlebars: &Handlebars) -> String {
        let mut data = BTreeMap::new();

        data.insert("id".to_string(),      format!("{}", self.id));
        data.insert("methods".to_string(), self.methods.iter().map(|method| method.codegen(&handlebars)).join("\n"));
        data.insert("name".to_string(),    self.name.clone());
        if let Some(ref properties) = self.properties {
            data.insert("properties".to_string(), properties.iter().map(|prop| prop.codegen(&handlebars)).join("\n"));
        }

        handlebars.render("class", &data).expect("Failed to render class template")
    }
}

#[derive(Debug, Deserialize)]
pub struct AMQPMethod {
    pub id:          u8,
    pub arguments:   Vec<AMQPArgument>,
    pub name:        String,
    pub synchronous: Option<bool>,
}

impl Codegen for AMQPMethod {
    fn codegen(&self, handlebars: &Handlebars) -> String {
        let mut data = BTreeMap::new();

        data.insert("id".to_string(),          format!("{}", self.id));
        data.insert("arguments".to_string(),   self.arguments.iter().map(|arg| arg.codegen(&handlebars)).join("\n"));
        data.insert("name".to_string(),        self.name.clone());
        data.insert("synchronous".to_string(), format!("{}", self.synchronous.unwrap_or(false)));

        handlebars.render("method", &data).expect("Failed to render method template")
    }
}

#[derive(Debug, Deserialize)]
pub struct AMQPArgument {
    #[serde(rename="type")]
    pub amqp_type:     Option<AMQPType>,
    pub name:          String,
    #[serde(rename="default-value")]
    pub default_value: Option<Value>,
    pub domain:        Option<String>,
}

impl Codegen for AMQPArgument {
    fn codegen(&self, handlebars: &Handlebars) -> String {
        let mut data = BTreeMap::new();

        if let Some(ref amqp_type) = self.amqp_type {
            data.insert("type".to_string(), amqp_type.to_string());
        }
        if let Some(ref default_value) = self.default_value {
            data.insert("default_value".to_string(), default_value.to_string());
        }
        data.insert("name".to_string(),   self.name.clone());
        if let Some(ref domain) = self.domain {
            data.insert("domain".to_string(), domain.clone());
        }

        handlebars.render("argument", &data).expect("Failed to render domain template")
    }
}

#[derive(Debug, Deserialize)]
pub struct AMQPProperty {
    #[serde(rename="type")]
    pub amqp_type: AMQPType,
    pub name:      String,
}

impl Codegen for AMQPProperty {
    fn codegen(&self, handlebars: &Handlebars) -> String {
        let mut data = BTreeMap::new();

        data.insert("type".to_string(), self.amqp_type.to_string());
        data.insert("name".to_string(), self.name.clone());

        handlebars.render("property", &data).expect("Failed to render domain template")
    }
}

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

    fn specs() -> AMQProtocolDefinition {
        AMQProtocolDefinition {
            name:          "AMQP".to_string(),
            major_version: 0,
            minor_version: 9,
            revision:      1,
            port:          5672,
            copyright:     Vec::new(),
            domains:       Vec::new(),
            constants:     Vec::new(),
            classes:       Vec::new(),
        }
    }

    fn templates() -> AMQPTemplates {
        AMQPTemplates {
            main:     "{{name}} - {{major_version}}.{{minor_version}}.{{revision}}".to_string(),
            domain:   String::new(),
            constant: String::new(),
            klass:    String::new(),
            method:   String::new(),
            argument: String::new(),
            property: String::new(),
        }
    }

    #[test]
    fn main_template() {
        assert_eq!(specs().codegen(&templates()), "AMQP - 0.9.1");
    }
}
<|endoftext|>