xkb-parser 0.1.0

Parses `.xkb` (X keyboard extension) files
Documentation
use crate::{
    ast::*,
    xkb::{Rule, XkbParser},
};
use from_pest::FromPest;
use pest::Parser;
use std::fmt::Debug;

#[test]
fn test_ast_ident() {
    enable_logging();

    assert_parse(Rule::ident, "foobar\n", Ident { content: "foobar" });
}

#[test]
fn test_ast_string() {
    enable_logging();

    assert_parse(
        Rule::string,
        r#""Czech (with <\|> key)""#,
        StringContent { content: r"Czech (with <\|> key)" },
    );
}

#[test]
fn test_ast_what() {
    enable_logging();

    assert_parse(
        Rule::block_modifiers,
        "default partial alphanumeric_keys modifier_keys\n",
        BlockModifiers {
            values: vec![
                BlockModifier { content: "default" },
                BlockModifier { content: "partial" },
                BlockModifier { content: "alphanumeric_keys" },
                BlockModifier { content: "modifier_keys" },
            ],
        },
    );
}

#[test]
fn test_ast_symbol() {
    enable_logging();

    assert_parse(
        Rule::xkb_symbols_item,
        "key <ESC>  {	[ Escape		]	};",
        XkbSymbolsItem::Key(Key {
            mode: None,
            id: Symbol { content: "ESC" },
            values: vec![KeyValue::KeyNames(KeyNames {
                values: vec![Ident { content: "Escape" }],
            })],
        }),
    );

    assert_parse(
        Rule::xkb_symbols_item,
        "override key <LSGT> {	[ less, greater, bar, brokenbar ] };",
        XkbSymbolsItem::Key(Key {
            mode: Some(KeyMode::KeyModeOverride(KeyModeOverride)),
            id: Symbol { content: "LSGT" },
            values: vec![KeyValue::KeyNames(KeyNames {
                values: vec![
                    Ident { content: "less" },
                    Ident { content: "greater" },
                    Ident { content: "bar" },
                    Ident { content: "brokenbar" },
                ],
            })],
        }),
    );

    assert_parse(
        Rule::xkb_symbols_item,
        std::str::from_utf8(b"key <AE01> { [ U10B78                 ] }; // \xf0\x90\xad\xb8\n\t")
            .unwrap(),
        XkbSymbolsItem::Key(Key {
            mode: None,
            id: Symbol { content: "AE01" },
            values: vec![KeyValue::KeyNames(KeyNames {
                values: vec![Ident { content: "U10B78" }],
            })],
        }),
    );

    assert_parse(
        Rule::xkb_symbols_item,
        std::str::from_utf8(
            b"key <KP7>  { [\tKP_Home,\t\tKP_7,\t\n\t\t\tonehalf,\t\tdead_horn\t] };",
        )
        .unwrap(),
        XkbSymbolsItem::Key(Key {
            mode: None,
            id: Symbol { content: "KP7" },
            values: vec![KeyValue::KeyNames(KeyNames {
                values: vec![
                    Ident { content: "KP_Home" },
                    Ident { content: "KP_7" },
                    Ident { content: "onehalf" },
                    Ident { content: "dead_horn" },
                ],
            })],
        }),
    );

    assert_parse(
        Rule::xkb_symbols_item,
        std::str::from_utf8(b"key  <KP7> {	[  KP_Home	],	overlay1=<KO7>	};").unwrap(),
        XkbSymbolsItem::Key(Key {
            mode: None,
            id: Symbol { content: "KP7" },
            values: vec![
                KeyValue::KeyNames(KeyNames { values: vec![Ident { content: "KP_Home" }] }),
                KeyValue::KeyDefs(KeyDef::OverlayDef(OverlayDef {
                    level: 1,
                    key: Symbol { content: "KO7" },
                })),
            ],
        }),
    );

    assert_parse(
        Rule::xkb_symbols_item,
        "key <PRSC> {\n\ttype= \"PC_ALT_LEVEL2\",\n\tsymbols[Group1]= [ Print, Sys_Req ]\n    };",
        XkbSymbolsItem::Key(Key {
            mode: None,
            id: Symbol { content: "PRSC" },
            values: vec![
                KeyValue::KeyDefs(KeyDef::TypeDef(TypeDef {
                    group: None,
                    content: StringContent { content: "PC_ALT_LEVEL2" },
                })),
                KeyValue::KeyDefs(KeyDef::SymbolDef(SymbolDef {
                    group: Group { content: "Group1" },
                    values: KeyNames {
                        values: vec![Ident { content: "Print" }, Ident { content: "Sys_Req" }],
                    },
                })),
            ],
        }),
    );

    assert_parse(
        Rule::xkb_symbols_item,
        r#"key <RALT>  { type[Group1]="TWO_LEVEL",
                  [ ISO_Level3_Shift, Multi_key ] };"#,
        XkbSymbolsItem::Key(Key {
            mode: None,
            id: Symbol { content: "RALT" },
            values: vec![
                KeyValue::KeyDefs(KeyDef::TypeDef(TypeDef {
                    group: Some(Group { content: "Group1" }),
                    content: StringContent { content: "TWO_LEVEL" },
                })),
                KeyValue::KeyNames(KeyNames {
                    values: vec![
                        Ident { content: "ISO_Level3_Shift" },
                        Ident { content: "Multi_key" },
                    ],
                }),
            ],
        }),
    );

    assert_parse(
            Rule::xkb_symbols_item,
            r#"key <AC01> { [ a,            A,              aogonek,         Aogonek    ], type[Group1] = "EIGHT_LEVEL_ALPHABETIC" };"#,
            XkbSymbolsItem::Key(Key {
                mode: None,
                id: Symbol { content: "AC01" },
                values: vec![
                    KeyValue::KeyNames(KeyNames {
                        values: vec![
                            Ident { content: "a" },
                            Ident { content: "A" },
                            Ident { content: "aogonek" },
                            Ident { content: "Aogonek" },
                        ],
                    }),
                    KeyValue::KeyDefs(KeyDef::TypeDef(TypeDef {
                        group: Some(Group { content: "Group1" }),
                        content: StringContent { content: "EIGHT_LEVEL_ALPHABETIC" },
                    })),
                ],
            }),
        );

    assert_parse(
        Rule::xkb_symbols_item,
        r#"replace key <CAPS> {
                type[Group1] = "ONE_LEVEL",
                symbols[Group1] = [ Caps_Lock ],
                actions[Group1] = [ SetMods(modifiers=Control) ]
            };"#,
        XkbSymbolsItem::Key(Key {
            mode: Some(KeyMode::KeyModeReplace(KeyModeReplace)),
            id: Symbol { content: "CAPS" },
            values: vec![
                KeyValue::KeyDefs(KeyDef::TypeDef(TypeDef {
                    group: Some(Group { content: "Group1" }),
                    content: StringContent { content: "ONE_LEVEL" },
                })),
                KeyValue::KeyDefs(KeyDef::SymbolDef(SymbolDef {
                    group: Group { content: "Group1" },
                    values: KeyNames { values: vec![Ident { content: "Caps_Lock" }] },
                })),
                KeyValue::KeyDefs(KeyDef::ActionsDef(ActionsDef {
                    group: Group { content: "Group1" },
                    values: vec![Action {
                        name: Ident { content: "SetMods" },
                        params: vec![ActionParam::ParamAssignment(ParamAssignment {
                            ident: Ident { content: "modifiers" },
                            expr: ParamExpression { content: "Control" },
                        })],
                    }],
                })),
            ],
        }),
    );

    assert_parse(
        Rule::xkb_symbols_item,
        r#"name[Group1]="Russian (Sweden, phonetic)";"#,
        XkbSymbolsItem::Name(Name {
            group: Group { content: "Group1" },
            name: StringContent { content: "Russian (Sweden, phonetic)" },
        }),
    );

    assert_parse(
        Rule::xkb_symbols_item,
        r#"key.type[group1]="ALPHABETIC";"#,
        XkbSymbolsItem::KeyType(KeyType {
            group: Some(Group { content: "group1" }),
            name: StringContent { content: "ALPHABETIC" },
        }),
    );

    assert_parse(
        Rule::xkb_symbols_item,
        r#"include "srvr_ctrl(fkey2vt)""#,
        XkbSymbolsItem::Include(Include { name: StringContent { content: "srvr_ctrl(fkey2vt)" } }),
    );

    assert_parse(
        Rule::xkb_symbols_item,
        "modifier_map Shift  { Shift_L, Shift_R };",
        XkbSymbolsItem::ModifierMap(ModifierMap {
            name: Ident { content: "Shift" },
            values: vec![
                Modifier::Ident(Ident { content: "Shift_L" }),
                Modifier::Ident(Ident { content: "Shift_R" }),
            ],
        }),
    );

    assert_parse(
        Rule::xkb_symbols_item,
        "modifier_map Mod4 { <META>, Meta_L, Meta_R };",
        XkbSymbolsItem::ModifierMap(ModifierMap {
            name: Ident { content: "Mod4" },
            values: vec![
                Modifier::KeyId(Symbol { content: "META" }),
                Modifier::Ident(Ident { content: "Meta_L" }),
                Modifier::Ident(Ident { content: "Meta_R" }),
            ],
        }),
    );
}

#[test]
fn test_ast_key_combo() {
    enable_logging();

    assert_parse(
        Rule::key_combo,
        "Shift + Lock + LevelThree + NumLock + LevelFive",
        KeyCombo {
            content: vec![
                Ident { content: "Shift" },
                Ident { content: "Lock" },
                Ident { content: "LevelThree" },
                Ident { content: "NumLock" },
                Ident { content: "LevelFive" },
            ],
        },
    );
}

#[test]
fn test_ast_modifiers() {
    enable_logging();

    assert_parse(
        Rule::type_component,
        "modifiers = Shift + Lock + LevelThree + NumLock + LevelFive;",
        TypeComponent::Modifiers(Modifiers {
            name: KeyCombo {
                content: vec![
                    Ident { content: "Shift" },
                    Ident { content: "Lock" },
                    Ident { content: "LevelThree" },
                    Ident { content: "NumLock" },
                    Ident { content: "LevelFive" },
                ],
            },
        }),
    );
}

#[test]
fn test_geometry_assigment() {
    enable_logging();

    assert_parse(
        Rule::geometry_assigment,
        r#"color= "blue";"#,
        GeometryAssigment { key: Ident { content: "color" }, subkey: None, value: "\"blue\"" },
    );

    assert_parse(
        Rule::geometry_assigment,
        "key.gap=  0.5;",
        GeometryAssigment {
            key: Ident { content: "key" },
            subkey: Some(Ident { content: "gap" }),
            value: "0.5",
        },
    );
}
#[test]
fn test_geometry_section() {
    enable_logging();

    // assert_parse(
    //     Rule::geometry_section_block,
    //     r##"section "LedPanel" { indicator.onColor  = "#00ff00";};"##,
    //     GeometryAssigment {
    //         key: Ident { content: "color" },
    //         subkey: None,
    //         value: "blue",
    //     },
    // );

    assert_parse(
        Rule::geometry_assigment,
        "key.gap=  0.5;",
        GeometryAssigment {
            key: Ident { content: "key" },
            subkey: Some(Ident { content: "gap" }),
            value: "0.5",
        },
    );
}

fn enable_logging() {
    let _ = env_logger::builder()
        .filter(None, log::LevelFilter::Trace)
        .default_format_timestamp(false)
        .is_test(true)
        .try_init();
}

fn assert_parse<'i, T>(r: Rule, input: &'i str, expected: T)
where
    T: FromPest<'i, Rule = Rule> + PartialEq + Debug,
    <T as FromPest<'i>>::FatalError: Debug,
{
    let mut parse_tree = match XkbParser::parse(r, input) {
        Ok(parse_tree) => {
            println!("parse tree = {:#?}", parse_tree);
            parse_tree
        }
        Err(e) => {
            panic!("Failed to parse `{}` as {:?}: `{}`", input, r, e);
        }
    };

    let syntax_tree = T::from_pest(&mut parse_tree).expect("infallible");
    println!("syntax tree = {:#?}", syntax_tree);

    assert_eq!(syntax_tree, expected);
}