Fully include simplexpr

This commit is contained in:
elkowar 2021-07-17 12:57:30 +02:00
parent 5899489250
commit 8405d01303
No known key found for this signature in database
GPG key ID: E321AD71B1D1F27F
12 changed files with 412 additions and 27 deletions

172
Cargo.lock generated
View file

@ -11,6 +11,15 @@ dependencies = [
"memchr",
]
[[package]]
name = "ansi_term"
version = "0.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2"
dependencies = [
"winapi",
]
[[package]]
name = "arrayref"
version = "0.3.6"
@ -128,6 +137,12 @@ version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc"
[[package]]
name = "convert_case"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e"
[[package]]
name = "crossbeam-utils"
version = "0.8.4"
@ -145,6 +160,29 @@ version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7"
[[package]]
name = "ctor"
version = "0.1.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5e98e2ad1a782e33928b96fc3948e7c355e5af34ba4de7670fe8bac2a3b2006d"
dependencies = [
"quote",
"syn",
]
[[package]]
name = "derive_more"
version = "0.99.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "40eebddd2156ce1bb37b20bbe5151340a31828b1f2d22ba4141f3531710e38df"
dependencies = [
"convert_case",
"proc-macro2",
"quote",
"rustc_version",
"syn",
]
[[package]]
name = "diff"
version = "0.1.12"
@ -194,13 +232,20 @@ name = "eww_config"
version = "0.1.0"
dependencies = [
"codespan-reporting",
"derive_more",
"insta",
"itertools",
"lalrpop",
"lalrpop-util",
"lazy_static",
"logos",
"maplit",
"pretty_assertions",
"regex",
"serde",
"serde_json",
"simplexpr",
"smart-default",
"thiserror",
]
@ -233,6 +278,15 @@ version = "0.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d7afe4a420e3fe79967a00898cc1f4db7c8a49a9333a29f8a4bd76a253d5cd04"
[[package]]
name = "heck"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6d621efb26863f0e9924c6ac577e8275e5e6b77455db64ffa6c65c904e9e132c"
dependencies = [
"unicode-segmentation",
]
[[package]]
name = "hermit-abi"
version = "0.1.18"
@ -383,6 +437,24 @@ version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e4a24736216ec316047a1fc4252e27dabb04218aa4a3f37c6e7ddbf1f9782b54"
[[package]]
name = "output_vt100"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "53cdc5b785b7a58c5aad8216b3dfa114df64b0b06ae6e1501cef91df2fbdf8f9"
dependencies = [
"winapi",
]
[[package]]
name = "pest"
version = "2.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "10f4872ae94d7b90ae48754df22fd42ad52ce740b8f370b03da4835417403e53"
dependencies = [
"ucd-trie",
]
[[package]]
name = "petgraph"
version = "0.5.1"
@ -414,6 +486,18 @@ version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c"
[[package]]
name = "pretty_assertions"
version = "0.7.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1cab0e7c02cf376875e9335e0ba1da535775beb5450d21e1dffca068818ed98b"
dependencies = [
"ansi_term",
"ctor",
"diff",
"output_vt100",
]
[[package]]
name = "proc-macro2"
version = "1.0.27"
@ -478,12 +562,39 @@ dependencies = [
"crossbeam-utils",
]
[[package]]
name = "rustc_version"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f0dfe2087c51c460008730de8b57e6a320782fbfb312e1f4d520e6c6fae155ee"
dependencies = [
"semver",
]
[[package]]
name = "ryu"
version = "1.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e"
[[package]]
name = "semver"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f301af10236f6df4160f7c3f04eec6dbc70ace82d23326abad5edee88801c6b6"
dependencies = [
"semver-parser",
]
[[package]]
name = "semver-parser"
version = "0.10.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "00b0bef5b7f9e0df16536d3961cfb6e84331c065b4066afb39768d0e319411f7"
dependencies = [
"pest",
]
[[package]]
name = "serde"
version = "1.0.126"
@ -533,12 +644,40 @@ version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1ad1d488a557b235fc46dae55512ffbfc429d2482b08b4d9435ab07384ca8aec"
[[package]]
name = "simplexpr"
version = "0.1.0"
dependencies = [
"codespan-reporting",
"itertools",
"lalrpop",
"lalrpop-util",
"logos",
"maplit",
"regex",
"serde",
"serde_json",
"strum",
"thiserror",
]
[[package]]
name = "siphasher"
version = "0.3.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cbce6d4507c7e4a3962091436e56e95290cb71fa302d0d270e32130b75fbff27"
[[package]]
name = "smart-default"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "133659a15339456eeeb07572eb02a91c91e9815e9cbc89566944d2c8d3efdbf6"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "string_cache"
version = "0.8.1"
@ -551,6 +690,27 @@ dependencies = [
"precomputed-hash",
]
[[package]]
name = "strum"
version = "0.21.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "aaf86bbcfd1fa9670b7a129f64fc0c9fcbbfe4f1bc4210e9e98fe71ffc12cde2"
dependencies = [
"strum_macros",
]
[[package]]
name = "strum_macros"
version = "0.21.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d06aaeeee809dbc59eb4556183dd927df67db1540de5be8d3ec0b6636358a5ec"
dependencies = [
"heck",
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "syn"
version = "1.0.73"
@ -621,6 +781,18 @@ dependencies = [
"crunchy",
]
[[package]]
name = "ucd-trie"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "56dee185309b50d1f11bfedef0fe6d036842e3fb77413abef29f8f8d1c5d4c1c"
[[package]]
name = "unicode-segmentation"
version = "1.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8895849a949e7845e06bd6dc1aa51731a103c42707010a5b591c0038fb73385b"
[[package]]
name = "unicode-width"
version = "0.1.8"

View file

@ -17,6 +17,16 @@ maplit = "1.0"
codespan-reporting = "0.11"
logos = "0.12"
derive_more = "0.99"
smart-default = "0.6"
serde = {version = "1.0", features = ["derive"]}
serde_json = "1.0"
lazy_static = "1.4"
pretty_assertions = "0.7"
simplexpr = { path = "../../projects/simplexpr" }
[build-dependencies]
lalrpop = "0.19.5"

View file

@ -3,7 +3,7 @@ use eww_config::{ast::*, config::*};
fn main() {
let mut files = codespan_reporting::files::SimpleFiles::new();
let input = "(12 :bar 22 (foo) (baz))";
let input = r#"(hi :bar 22 :baz {"hi" asdfasdf * 2} (foo) (baz))"#;
let file_id = files.add("foo.eww", input);
let ast = eww_config::parse_string(file_id, input);

View file

@ -1,4 +1,5 @@
use itertools::Itertools;
use simplexpr::ast::SimplExpr;
use std::collections::HashMap;
use crate::{config::FromAst, error::*};
@ -22,9 +23,11 @@ impl std::fmt::Debug for Span {
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
pub enum AstType {
List,
Array,
Keyword,
Symbol,
Value,
SimplExpr,
Comment,
}
@ -37,10 +40,11 @@ impl Display for AstType {
#[derive(PartialEq, Eq, Clone)]
pub enum Ast {
List(Span, Vec<Ast>),
// ArgList(Span, Vec<Ast>),
Array(Span, Vec<Ast>),
Keyword(Span, String),
Symbol(Span, String),
Value(Span, String),
SimplExpr(Span, SimplExpr),
Comment(Span),
}
@ -74,9 +78,11 @@ impl Ast {
pub fn expr_type(&self) -> AstType {
match self {
Ast::List(..) => AstType::List,
Ast::Array(..) => AstType::Array,
Ast::Keyword(..) => AstType::Keyword,
Ast::Symbol(..) => AstType::Symbol,
Ast::Value(..) => AstType::Value,
Ast::SimplExpr(..) => AstType::SimplExpr,
Ast::Comment(_) => AstType::Comment,
}
}
@ -84,9 +90,11 @@ impl Ast {
pub fn span(&self) -> Span {
match self {
Ast::List(span, _) => *span,
Ast::Array(span, _) => *span,
Ast::Keyword(span, _) => *span,
Ast::Symbol(span, _) => *span,
Ast::Value(span, _) => *span,
Ast::SimplExpr(span, _) => *span,
Ast::Comment(span) => *span,
}
}
@ -104,9 +112,11 @@ impl std::fmt::Display for Ast {
use Ast::*;
match self {
List(_, x) => write!(f, "({})", x.iter().map(|e| format!("{}", e)).join(" ")),
Array(_, x) => write!(f, "({})", x.iter().map(|e| format!("{}", e)).join(" ")),
Keyword(_, x) => write!(f, "{}", x),
Symbol(_, x) => write!(f, "{}", x),
Value(_, x) => write!(f, "{}", x),
SimplExpr(_, x) => write!(f, "{{{}}}", x),
Comment(_) => write!(f, ""),
}
}
@ -116,9 +126,11 @@ impl std::fmt::Debug for Ast {
use Ast::*;
match self {
List(span, x) => f.debug_tuple(&format!("List<{}>", span)).field(x).finish(),
Array(span, x) => f.debug_tuple(&format!("Array<{}>", span)).field(x).finish(),
Keyword(span, x) => write!(f, "Number<{}>({})", span, x),
Symbol(span, x) => write!(f, "Symbol<{}>({})", span, x),
Value(span, x) => write!(f, "Value<{}>({})", span, x),
SimplExpr(span, x) => write!(f, "SimplExpr<{}>({})", span, x),
Comment(span) => write!(f, "Comment<{}>", span),
}
}

View file

@ -57,7 +57,7 @@ mod test {
fn test() {
let parser = parser::AstParser::new();
insta::with_settings!({sort_maps => true}, {
let lexer = lexer::Lexer::new("(box :bar 12 :baz \"hi\" foo (bar))");
let lexer = lexer::Lexer::new(0, "(box :bar 12 :baz \"hi\" foo (bar))");
insta::assert_debug_snapshot!(
Element::<Ast, Ast>::from_ast(parser.parse(0, lexer).unwrap()).unwrap()
);

View file

@ -1,13 +1,13 @@
use crate::{
ast::{Ast, AstType, Span},
lexer,
lexer, parse_error,
};
use codespan_reporting::{diagnostic, files};
use thiserror::Error;
pub type AstResult<T> = Result<T, AstError>;
#[derive(Debug, PartialEq, Eq, Error)]
#[derive(Debug, Error)]
pub enum AstError {
#[error("Definition invalid")]
InvalidDefinition(Option<Span>),
@ -17,7 +17,7 @@ pub enum AstError {
WrongExprType(Option<Span>, AstType, AstType),
#[error("Parse error: {source}")]
ParseError { file_id: Option<usize>, source: lalrpop_util::ParseError<usize, lexer::Token, lexer::LexicalError> },
ParseError { file_id: Option<usize>, source: lalrpop_util::ParseError<usize, lexer::Token, parse_error::ParseError> },
}
impl AstError {
@ -39,21 +39,27 @@ impl AstError {
}
}
pub fn from_parse_error(file_id: usize, err: lalrpop_util::ParseError<usize, lexer::Token, lexer::LexicalError>) -> AstError {
pub fn from_parse_error(
file_id: usize,
err: lalrpop_util::ParseError<usize, lexer::Token, parse_error::ParseError>,
) -> AstError {
AstError::ParseError { file_id: Some(file_id), source: err }
}
}
fn get_parse_error_span(
file_id: usize,
err: &lalrpop_util::ParseError<usize, lexer::Token, lexer::LexicalError>,
err: &lalrpop_util::ParseError<usize, lexer::Token, parse_error::ParseError>,
) -> Option<Span> {
match err {
lalrpop_util::ParseError::InvalidToken { location } => Some(Span(*location, *location, file_id)),
lalrpop_util::ParseError::UnrecognizedEOF { location, expected } => Some(Span(*location, *location, file_id)),
lalrpop_util::ParseError::UnrecognizedToken { token, expected } => Some(Span(token.0, token.2, file_id)),
lalrpop_util::ParseError::ExtraToken { token } => Some(Span(token.0, token.2, file_id)),
lalrpop_util::ParseError::User { error } => None,
lalrpop_util::ParseError::User { error } => match error {
parse_error::ParseError::SimplExpr(span, error) => *span,
parse_error::ParseError::LexicalError(span) => Some(*span),
},
}
}

View file

@ -1,12 +1,17 @@
use logos::Logos;
use crate::{ast::Span, parse_error};
#[derive(Logos, Debug, PartialEq, Eq, Clone)]
pub enum Token {
#[token("(")]
LPren,
#[token(")")]
RPren,
#[token("[")]
LBrack,
#[token("]")]
RBrack,
#[token("true")]
True,
@ -26,6 +31,9 @@ pub enum Token {
#[regex(r#":\S+"#, |x| x.slice().to_string())]
Keyword(String),
#[regex(r#"\{[^}]*\}"#, |x| x.slice().to_string())]
SimplExpr(String),
#[regex(r#";.*"#)]
Comment,
@ -39,46 +47,41 @@ impl std::fmt::Display for Token {
match self {
Token::LPren => write!(f, "'('"),
Token::RPren => write!(f, "')'"),
Token::LBrack => write!(f, "'['"),
Token::RBrack => write!(f, "']'"),
Token::True => write!(f, "true"),
Token::False => write!(f, "false"),
Token::StrLit(x) => write!(f, "\"{}\"", x),
Token::NumLit(x) => write!(f, "{}", x),
Token::Symbol(x) => write!(f, "{}", x),
Token::Keyword(x) => write!(f, "{}", x),
Token::SimplExpr(x) => write!(f, "{{{}}}", x),
Token::Comment => write!(f, ""),
Token::Error => write!(f, ""),
}
}
}
#[derive(Debug, Eq, PartialEq, Copy, Clone)]
pub struct LexicalError(usize, usize);
impl std::fmt::Display for LexicalError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "Lexical error at {}..{}", self.0, self.1)
}
}
pub type SpannedResult<Tok, Loc, Error> = Result<(Loc, Tok, Loc), Error>;
pub struct Lexer<'input> {
file_id: usize,
lexer: logos::SpannedIter<'input, Token>,
}
impl<'input> Lexer<'input> {
pub fn new(text: &'input str) -> Self {
Lexer { lexer: logos::Lexer::new(text).spanned() }
pub fn new(file_id: usize, text: &'input str) -> Self {
Lexer { file_id, lexer: logos::Lexer::new(text).spanned() }
}
}
impl<'input> Iterator for Lexer<'input> {
type Item = SpannedResult<Token, usize, LexicalError>;
type Item = SpannedResult<Token, usize, parse_error::ParseError>;
fn next(&mut self) -> Option<Self::Item> {
let (token, range) = self.lexer.next()?;
if token == Token::Error {
Some(Err(LexicalError(range.start, range.end)))
Some(Err(parse_error::ParseError::LexicalError(Span(range.start, range.end, self.file_id))))
} else {
Some(Ok((range.start, token, range.end)))
}

View file

@ -6,6 +6,8 @@ pub mod ast;
pub mod config;
pub mod error;
mod lexer;
mod parse_error;
pub mod value;
use ast::Ast;
use error::{AstError, AstResult};
@ -18,7 +20,7 @@ use lalrpop_util::lalrpop_mod;
lalrpop_mod!(pub parser);
pub fn parse_string(file_id: usize, s: &str) -> AstResult<Ast> {
let lexer = lexer::Lexer::new(s);
let lexer = lexer::Lexer::new(file_id, s);
let parser = parser::AstParser::new();
Ok(parser.parse(file_id, lexer).map_err(|e| AstError::from_parse_error(file_id, e))?)
}
@ -30,7 +32,7 @@ macro_rules! test_parser {
::insta::with_settings!({sort_maps => true}, {
$(
::insta::assert_debug_snapshot!(p.parse(0, Lexer::new($text)));
::insta::assert_debug_snapshot!(p.parse(0, Lexer::new(0, $text)));
)*
});
}}

10
src/parse_error.rs Normal file
View file

@ -0,0 +1,10 @@
use crate::ast::Span;
#[derive(Debug, thiserror::Error)]
pub enum ParseError {
#[error("{1}")]
SimplExpr(Option<Span>, simplexpr::error::Error),
#[error("Unknown token")]
LexicalError(Span),
}

View file

@ -1,22 +1,28 @@
use std::str::FromStr;
use crate::lexer::{Token, LexicalError};
use crate::lexer::{Token};
use crate::ast::{Ast, Span};
use simplexpr::ast::SimplExpr;
use simplexpr;
use lalrpop_util::ParseError;
grammar(file_id: usize);
extern {
type Location = usize;
type Error = LexicalError;
type Error = crate::parse_error::ParseError;
enum Token {
"(" => Token::LPren,
")" => Token::RPren,
"[" => Token::LBrack,
"]" => Token::RBrack,
"true" => Token::True,
"false" => Token::False,
"string" => Token::StrLit(<String>),
"number" => Token::NumLit(<String>),
"symbol" => Token::Symbol(<String>),
"keyword" => Token::Keyword(<String>),
"simplexpr" => Token::SimplExpr(<String>),
"comment" => Token::Comment,
}
}
@ -24,6 +30,8 @@ extern {
pub Ast: Ast = {
<l:@L> "(" <elems:(<Ast>)+> ")" <r:@R> => Ast::List(Span(l, r, file_id), elems),
<l:@L> "[" <elems:(<Ast>)+> "]" <r:@R> => Ast::Array(Span(l, r, file_id), elems),
<l:@L> <expr:SimplExpr> <r:@R> => Ast::SimplExpr(Span(l, r, file_id), expr),
<x:Keyword> => x,
<x:Symbol> => x,
<l:@L> <x:Value> <r:@R> => Ast::Value(Span(l, r, file_id), x),
@ -45,6 +53,15 @@ StrLit: String = {
},
};
SimplExpr: SimplExpr = {
<l:@L> <x:"simplexpr"> =>? {
let expr = x[1..x.len() - 1].to_string();
simplexpr::parse_string(&expr).map_err(|e| {
let span = e.get_span().map(|simplexpr::Span(simpl_l, simpl_r)| Span(1 + l + simpl_l, 1 + l + simpl_r, file_id));
ParseError::User { error: crate::parse_error::ParseError::SimplExpr(span, e) }})
}
}
Num: String = <"number"> => <>.to_string();
Bool: String = {

112
src/value/coords.rs Normal file
View file

@ -0,0 +1,112 @@
use derive_more::*;
use serde::{Deserialize, Serialize};
use smart_default::SmartDefault;
use std::{fmt, str::FromStr};
#[derive(Debug, thiserror::Error)]
pub enum Error {
#[error("Failed to parse \"{0}\" as a length value")]
NumParseFailed(String),
#[error("Inalid unit \"{0}\", must be either % or px")]
InvalidUnit(String),
#[error("Invalid format. Coordinates must be formated like 200x100")]
MalformedCoords,
}
#[derive(Clone, Copy, PartialEq, Eq, Deserialize, Serialize, Display, DebugCustom, SmartDefault)]
pub enum NumWithUnit {
#[display(fmt = "{}%", .0)]
#[debug(fmt = "{}%", .0)]
Percent(i32),
#[display(fmt = "{}px", .0)]
#[debug(fmt = "{}px", .0)]
#[default]
Pixels(i32),
}
impl NumWithUnit {
pub fn relative_to(&self, max: i32) -> i32 {
match *self {
NumWithUnit::Percent(n) => ((max as f64 / 100.0) * n as f64) as i32,
NumWithUnit::Pixels(n) => n,
}
}
}
impl FromStr for NumWithUnit {
type Err = Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
lazy_static::lazy_static! {
static ref PATTERN: regex::Regex = regex::Regex::new("^(-?\\d+)(.*)$").unwrap();
};
let captures = PATTERN.captures(s).ok_or_else(|| Error::NumParseFailed(s.to_string()))?;
let value = captures.get(1).unwrap().as_str().parse::<i32>().map_err(|_| Error::NumParseFailed(s.to_string()))?;
match captures.get(2).unwrap().as_str() {
"px" | "" => Ok(NumWithUnit::Pixels(value)),
"%" => Ok(NumWithUnit::Percent(value)),
unit => Err(Error::InvalidUnit(unit.to_string())),
}
}
}
#[derive(Clone, Copy, PartialEq, Eq, Deserialize, Serialize, Display, Default)]
#[display(fmt = "{}*{}", x, y)]
pub struct Coords {
pub x: NumWithUnit,
pub y: NumWithUnit,
}
impl FromStr for Coords {
type Err = Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let (x, y) = s
.split_once(|x: char| x.to_ascii_lowercase() == 'x' || x.to_ascii_lowercase() == '*')
.ok_or_else(|| Error::MalformedCoords)?;
Coords::from_strs(x, y)
}
}
impl fmt::Debug for Coords {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "CoordsWithUnits({}, {})", self.x, self.y)
}
}
impl Coords {
pub fn from_pixels(x: i32, y: i32) -> Self {
Coords { x: NumWithUnit::Pixels(x), y: NumWithUnit::Pixels(y) }
}
/// parse a string for x and a string for y into a [`Coords`] object.
pub fn from_strs(x: &str, y: &str) -> Result<Coords, Error> {
Ok(Coords { x: x.parse()?, y: y.parse()? })
}
/// resolve the possibly relative coordinates relative to a given containers size
pub fn relative_to(&self, width: i32, height: i32) -> (i32, i32) {
(self.x.relative_to(width), self.y.relative_to(height))
}
}
#[cfg(test)]
mod test {
use super::*;
use pretty_assertions::assert_eq;
#[test]
fn test_parse_num_with_unit() {
assert_eq!(NumWithUnit::Pixels(55), NumWithUnit::from_str("55").unwrap());
assert_eq!(NumWithUnit::Pixels(55), NumWithUnit::from_str("55px").unwrap());
assert_eq!(NumWithUnit::Percent(55), NumWithUnit::from_str("55%").unwrap());
assert!(NumWithUnit::from_str("55pp").is_err());
}
#[test]
fn test_parse_coords() {
assert_eq!(Coords { x: NumWithUnit::Pixels(50), y: NumWithUnit::Pixels(60) }, Coords::from_str("50x60").unwrap());
assert!(Coords::from_str("5060").is_err());
}
}

41
src/value/mod.rs Normal file
View file

@ -0,0 +1,41 @@
use derive_more::*;
use serde::{Deserialize, Serialize};
pub mod coords;
pub use coords::*;
/// The name of a variable
#[repr(transparent)]
#[derive(Clone, Hash, PartialEq, Eq, Serialize, Deserialize, AsRef, From, FromStr, Display, DebugCustom)]
#[debug(fmt = "VarName({})", .0)]
pub struct VarName(pub String);
impl std::borrow::Borrow<str> for VarName {
fn borrow(&self) -> &str {
&self.0
}
}
impl From<&str> for VarName {
fn from(s: &str) -> Self {
VarName(s.to_owned())
}
}
/// The name of an attribute
#[repr(transparent)]
#[derive(Clone, Hash, PartialEq, Eq, Serialize, Deserialize, AsRef, From, FromStr, Display, DebugCustom)]
#[debug(fmt="AttrName({})", .0)]
pub struct AttrName(pub String);
impl std::borrow::Borrow<str> for AttrName {
fn borrow(&self) -> &str {
&self.0
}
}
impl From<&str> for AttrName {
fn from(s: &str) -> Self {
AttrName(s.to_owned())
}
}