perky 0.1.0

An application to permute and score keyboard layouts.
Documentation
use core::{
    array,
    convert::TryFrom,
    ops::{Deref, DerefMut},
};

use serde_json::Value;

pub struct Table<const C: usize, const R: usize, T>(pub [[Option<T>; C]; R]);

impl<const C: usize, const R: usize, T> Deref for Table<C, R, T> {
    type Target = [[Option<T>; C]; R];

    fn deref(&self) -> &Self::Target {
        &self.0
    }
}

impl<const C: usize, const R: usize, T> DerefMut for Table<C, R, T> {
    fn deref_mut(&mut self) -> &mut Self::Target {
        &mut self.0
    }
}

impl<const C: usize, const R: usize, T: Copy> From<&Table<C, R, T>> for Value
where
    Value: From<T>,
{
    fn from(value: &Table<C, R, T>) -> Self {
        use Value::*;
        let mut rows = value
            .0
            .iter()
            .map(|row| {
                let mut columns = row
                    .iter()
                    .map(|opt_value| match opt_value {
                        None => Value::Null,
                        Some(value) => Value::from(*value),
                    })
                    .collect::<Vec<_>>();
                while matches!(columns.last(), Some(Null)) {
                    columns.pop();
                }
                Array(columns)
            })
            .collect::<Vec<_>>();
        while matches!(rows.last(), Some(Array(slice)) if slice.is_empty()) {
            rows.pop();
        }
        Array(rows)
    }
}

impl<const C: usize, const R: usize, T> TryFrom<&Value> for Table<C, R, T>
where
    T: for<'a> TryFrom<&'a Value, Error = String> + Copy,
{
    type Error = String;

    fn try_from(value: &Value) -> Result<Self, Self::Error> {
        let rows = value.as_array().ok_or("Table must be an array")?;
        if rows.len() > R {
            return Err(format!("Table has too many rows (maximum is {})", R));
        }
        let mut table = Self::default();
        for (r, row) in rows.iter().enumerate() {
            let row = row
                .as_array()
                .ok_or_else(|| format!("Row {} must be an array", r))?;
            if row.len() > C {
                return Err(format!("Row {} has too many columns (maximum is {})", r, C));
            }
            for (c, cell) in row.iter().enumerate() {
                table.0[r][c] = if cell.is_null() {
                    None
                } else {
                    Some(
                        T::try_from(cell)
                            .map_err(|e| format!("Invalid cell ({}, {}): {}", r, c, e))?,
                    )
                }
            }
        }
        Ok(table)
    }
}

impl<const C: usize, const R: usize, T> Default for Table<C, R, T> {
    fn default() -> Self {
        Self(array::from_fn(|_| array::from_fn(|_| None)))
    }
}