xkb-parser 0.1.0

Parses `.xkb` (X keyboard extension) files
Documentation
#![type_length_limit = "2893838"]

use from_pest::FromPest;
use log::{debug, error};
use pest::Parser;
use rayon::prelude::*;
use std::{
    error::Error as StdError,
    ffi::OsStr,
    path::{Path, PathBuf},
};
use walkdir::WalkDir;
use xkb_parser::{Rule, Xkb, XkbParser};

type Error = Box<dyn StdError + Send + Sync>;

#[test]
fn parse_custom_fixtures() -> Result<(), Error> {
    enable_logging();

    parse_files(
        WalkDir::new("tests/fixtures/custom")
            .into_iter()
            .filter_map(|x| x.ok())
            .map(|x| x.path().to_path_buf())
            .filter(|x| x.is_file())
            .filter(|x| x.extension() == Some(OsStr::new("xkb")))
            .collect(),
    )
}

#[test]
fn parse_kbdgen_fixtures() -> Result<(), Error> {
    enable_logging();

    parse_files(
        WalkDir::new("tests/fixtures/kbdgen")
            .into_iter()
            .filter_map(|x| x.ok())
            .map(|x| x.path().to_path_buf())
            .filter(|x| x.is_file())
            .filter(|x| x.extension() == Some(OsStr::new("xkb")))
            .collect(),
    )
}

#[test]
fn parse_x11_symbols() -> Result<(), Error> {
    enable_logging();

    parse_files(
        WalkDir::new("tests/fixtures/x11/symbols")
            .into_iter()
            .filter_map(|x| x.ok())
            .map(|x| x.path().to_path_buf())
            .filter(|x| x.is_file())
            .collect(),
    )
}

#[test]
fn parse_x11_keycodes() -> Result<(), Error> {
    enable_logging();

    parse_files(
        WalkDir::new("tests/fixtures/x11/keycodes")
            .into_iter()
            .filter_map(|x| x.ok())
            .map(|x| x.path().to_path_buf())
            .filter(|x| x.is_file())
            .collect(),
    )
}

#[test]
fn parse_x11_types() -> Result<(), Error> {
    enable_logging();

    parse_files(
        WalkDir::new("tests/fixtures/x11/types")
            .into_iter()
            .filter_map(|x| x.ok())
            .map(|x| x.path().to_path_buf())
            .filter(|x| x.is_file())
            .collect(),
    )
}

#[test]
fn parse_x11_geometry() -> Result<(), Error> {
    enable_logging();

    log::info!("geometry");

    parse_files(
        WalkDir::new("tests/fixtures/x11/geometry")
            .into_iter()
            .filter_map(|x| x.ok())
            .map(|x| x.path().to_path_buf())
            .filter(|x| x.is_file())
            .collect(),
    )
}

#[test]
#[ignore]
fn parse_x11_rules() -> Result<(), Error> {
    enable_logging();

    parse_files(
        WalkDir::new("tests/fixtures/x11/rules")
            .into_iter()
            .filter_map(|x| x.ok())
            .map(|x| x.path().to_path_buf())
            .filter(|x| x.is_file())
            .collect(),
    )
}

fn parse_files(xkb_files: Vec<PathBuf>) -> Result<(), Error> {
    let failed: usize = xkb_files
        .into_par_iter()
        .filter(|f| f.extension() != Some(OsStr::new("json")))
        .filter(|f| f.extension() != Some(OsStr::new("ron")))
        .filter(|f| f.extension() != Some(OsStr::new("xml")))
        .filter(|f| f.file_name() != Some(OsStr::new("README")))
        .map(|f: PathBuf| {
            debug!("next: {}", f.display());
            let res = parse_one_file(&f);
            (f, res)
        })
        .filter_map(|(f, x)| x.err().map(|e| (f, e)))
        .inspect(|(file_name, error)| {
            debug!("error parsing file {}:\n{}", file_name.display(), error);
            error!("failed: {}", file_name.display());
        })
        .count();

    if failed > 0 {
        panic!("{} tests failed -- Set `VERBOSE_PARSE_ERRORS=1` for error messages", failed);
    }
    Ok(())
}

fn parse_one_file(file_name: &Path) -> Result<(), Error> {
    let file = std::fs::read_to_string(&file_name)?;
    log::trace!("  read {}", file_name.display());
    let mut parse_tree = XkbParser::parse(Rule::file, &file)?;
    log::trace!("parsed {}", file_name.display());
    std::fs::write(file_name.with_extension("debug.ron"), format!("{:#?}", parse_tree))?;
    let _ =
        Xkb::from_pest(&mut parse_tree).map_err(|e| format!("ast generation failed: {:?}", e))?;
    log::trace!(" asted {}", file_name.display());
    Ok(())
}

fn enable_logging() {
    let verbosity = std::env::var("VERBOSE_PARSE_ERRORS").ok().and_then(|x| x.parse::<i16>().ok());
    let level = match verbosity {
        None | Some(0) => log::LevelFilter::Warn,
        Some(1) => log::LevelFilter::Debug,
        _ => log::LevelFilter::Trace,
    };

    let _ = env_logger::builder()
        .filter(None, level)
        .default_format_timestamp(false)
        .is_test(true)
        .try_init();
}