12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378237923802381238223832384238523862387238823892390239123922393239423952396239723982399240024012402240324042405240624072408240924102411 |
- //! Implementation of `syn::parse::Parse` for styles, and associated helper data/functions.
- // TODO make all parsers use HyphenWord where appropriate.
- // TODO make all error messages nice
- // TODO 100% test coverage
- // TODO see if I can get https://github.com/rust-lang/rust/issues/67544 accepted. then change "em" to
- // em and "ex" to ex.
- // TODO Split out extra "Dynamic" layer for each type for use in proc macro (so we can have `{ <arbitary
- // rust code> }`)
- use crate::*;
- use proc_macro2::Span;
- use std::{
- cell::RefCell,
- collections::BTreeSet,
- fmt::{self, Write},
- ops::RangeBounds,
- str,
- };
- use syn::{
- ext::IdentExt,
- parse::{discouraged::Speculative, Parse, ParseStream},
- punctuated::Punctuated,
- spanned::Spanned,
- Ident, Token,
- };
- use super::{DynamicStyle, DynamicStyles, Styles};
- impl Parse for DynamicStyles {
- fn parse(s: ParseStream) -> syn::Result<Self> {
- let punc = s.parse_terminated::<_, Token![;]>(<DynamicStyle as Parse>::parse)?;
- Ok(DynamicStyles::from(punc.into_iter().collect::<Vec<_>>()))
- }
- }
- impl Parse for Styles {
- fn parse(s: ParseStream) -> syn::Result<Self> {
- let punc = s.parse_terminated::<_, Token![;]>(<Style as Parse>::parse)?;
- Ok(Styles::from(punc.into_iter().collect::<Vec<_>>()))
- }
- }
- impl Parse for DynamicStyle {
- fn parse(s: ParseStream) -> syn::Result<Self> {
- // Pass through brackets
- if s.peek(syn::token::Brace) {
- Ok(DynamicStyle::Dynamic(s.parse()?))
- } else {
- Ok(DynamicStyle::Literal(s.parse()?))
- }
- }
- }
- impl Parse for Style {
- fn parse(s: ParseStream) -> syn::Result<Self> {
- if s.peek(syn::LitStr) {
- let unchecked: syn::LitStr = s.parse()?;
- return Ok(Style::Unchecked(unchecked.value()));
- }
- let name: HyphenWord = s.parse()?;
- if name.try_match("dummy") {
- return Ok(Style::Dummy);
- }
- s.parse::<Token![:]>()?;
- let output = if name.try_match("align-content") {
- Style::AlignContent(s.parse()?)
- } else if name.try_match("align-items") {
- Style::AlignItems(s.parse()?)
- } else if name.try_match("align-self") {
- Style::AlignSelf(s.parse()?)
- // all
- // background
- } else if name.try_match("background-attachment") {
- Style::BackgroundAttachment(s.parse()?)
- } else if name.try_match("background-blend-mode") {
- Style::BackgroundBlendMode(s.parse()?)
- } else if name.try_match("background-clip") {
- Style::BackgroundClip(s.parse()?)
- } else if name.try_match("background-color") {
- Style::BackgroundColor(s.parse()?)
- } else if name.try_match("background-image") {
- Style::BackgroundImage(s.parse()?)
- } else if name.try_match("background-origin") {
- Style::BackgroundOrigin(s.parse()?)
- } else if name.try_match("background-position") {
- Style::BackgroundPosition(s.parse()?)
- } else if name.try_match("background-repeat") {
- Style::BackgroundRepeat(s.parse()?)
- } else if name.try_match("background-size") {
- Style::BackgroundSize(s.parse()?)
- } else if name.try_match("border") {
- Style::Border(s.parse()?)
- } else if name.try_match("border-bottom") {
- Style::BorderBottom(s.parse()?)
- } else if name.try_match("border-bottom-color") {
- Style::BorderBottomColor(s.parse()?)
- } else if name.try_match("border-bottom-left-radius") {
- Style::BorderBottomLeftRadius(s.parse()?)
- } else if name.try_match("border-bottom-right-radius") {
- Style::BorderBottomRightRadius(s.parse()?)
- } else if name.try_match("border-bottom-style") {
- Style::BorderBottomStyle(s.parse()?)
- } else if name.try_match("border-bottom-width") {
- Style::BorderBottomWidth(s.parse()?)
- } else if name.try_match("border-collapse") {
- Style::BorderCollapse(s.parse()?)
- } else if name.try_match("border-color") {
- Style::BorderColor(s.parse()?)
- // border-image
- // border-image-outset
- // border-image-repeat
- // border-image-slice
- // border-image-source
- // border-image-width
- } else if name.try_match("border-left") {
- Style::BorderLeft(s.parse()?)
- } else if name.try_match("border-left-color") {
- Style::BorderLeftColor(s.parse()?)
- } else if name.try_match("border-left-style") {
- Style::BorderLeftStyle(s.parse()?)
- } else if name.try_match("border-left-width") {
- Style::BorderLeftWidth(s.parse()?)
- } else if name.try_match("border-radius") {
- Style::BorderRadius(s.parse()?)
- } else if name.try_match("border-right") {
- Style::BorderRight(s.parse()?)
- } else if name.try_match("border-right-color") {
- Style::BorderRightColor(s.parse()?)
- } else if name.try_match("border-right-style") {
- Style::BorderRightStyle(s.parse()?)
- } else if name.try_match("border-right-width") {
- Style::BorderRightWidth(s.parse()?)
- // border-spacing
- } else if name.try_match("border-style") {
- Style::BorderStyle(s.parse()?)
- } else if name.try_match("border-top") {
- Style::BorderTop(s.parse()?)
- } else if name.try_match("border-top-color") {
- Style::BorderTopColor(s.parse()?)
- } else if name.try_match("border-top-left-radius") {
- Style::BorderTopLeftRadius(s.parse()?)
- } else if name.try_match("border-top-right-radius") {
- Style::BorderTopRightRadius(s.parse()?)
- } else if name.try_match("border-top-style") {
- Style::BorderTopStyle(s.parse()?)
- } else if name.try_match("border-top-width") {
- Style::BorderTopWidth(s.parse()?)
- } else if name.try_match("border-width") {
- Style::BorderWidth(s.parse()?)
- } else if name.try_match("bottom") {
- Style::Bottom(s.parse()?)
- // box-decoration-break
- } else if name.try_match("box-shadow") {
- Style::BoxShadow(s.parse()?)
- } else if name.try_match("box-sizing") {
- Style::BoxSizing(s.parse()?)
- // break-after
- // break-before
- // break-inside
- // caption-side
- // caret-color
- } else if name.try_match("clear") {
- Style::Clear(s.parse()?)
- // clip
- // clip-path
- // clip-rule
- } else if name.try_match("column-count") {
- Style::ColumnCount(s.parse()?)
- } else if name.try_match("color") {
- Style::Color(s.parse()?)
- // contain
- // content
- // counter-increment
- // counter-reset
- // cue
- // cue-after
- // cue-before
- } else if name.try_match("cursor") {
- Style::Cursor(s.parse()?)
- // direction
- } else if name.try_match("display") {
- Style::Display(s.parse()?)
- // elevation
- // empty-cells
- // flex
- } else if name.try_match("flex-basis") {
- Style::FlexBasis(s.parse()?)
- } else if name.try_match("flex-direction") {
- Style::FlexDirection(s.parse()?)
- // flex-flow
- } else if name.try_match("flex-grow") {
- let number: Number = s.parse()?;
- if !number.suffix.is_empty() {
- return Err(syn::Error::new(number.span, "expected number"));
- }
- Style::FlexGrow(number.value)
- } else if name.try_match("flex-shrink") {
- let number: Number = s.parse()?;
- if !number.suffix.is_empty() {
- return Err(syn::Error::new(number.span, "expected number"));
- }
- Style::FlexShrink(number.value)
- } else if name.try_match("flex-wrap") {
- Style::FlexWrap(s.parse()?)
- } else if name.try_match("float") {
- Style::Float(s.parse()?)
- // font
- } else if name.try_match("font-family") {
- Style::FontFamily(s.parse()?)
- // font-feature-settings
- // font-kerning
- } else if name.try_match("font-size") {
- Style::FontSize(s.parse()?)
- // font-size-adjust
- // font-stretch
- } else if name.try_match("font-style") {
- Style::FontStyle(s.parse()?)
- // font-synthesis
- // font-variant
- // font-variant-caps
- // font-variant-east-asian
- // font-variant-ligatures
- // font-variant-numeric
- // font-variant-position
- } else if name.try_match("font-weight") {
- Style::FontWeight(s.parse()?)
- // glyph-orientation-vertical
- // grid
- // grid-area
- // grid-auto-columns
- // grid-auto-flow
- // grid-auto-rows
- // grid-column
- // grid-column-end
- // grid-column-start
- // grid-row
- // grid-row-end
- // grid-row-start
- // grid-template
- // grid-template-areas
- // grid-template-columns
- // grid-template-rows
- } else if name.try_match("height") {
- Style::Height(s.parse()?)
- // image-orientation
- // image-rendering
- // isolation
- } else if name.try_match("justify-content") {
- Style::JustifyContent(s.parse()?)
- } else if name.try_match("left") {
- Style::Left(s.parse()?)
- // letter-spacing
- } else if name.try_match("line-height") {
- Style::LineHeight(s.parse()?)
- // list-style
- // list-style-image
- // list-style-position
- } else if name.try_match("list-style-type") {
- Style::ListStyleType(s.parse()?)
- } else if name.try_match("margin") {
- Style::Margin(s.parse()?)
- } else if name.try_match("margin-bottom") {
- Style::MarginBottom(s.parse()?)
- } else if name.try_match("margin-left") {
- Style::MarginLeft(s.parse()?)
- } else if name.try_match("margin-right") {
- Style::MarginRight(s.parse()?)
- } else if name.try_match("margin-top") {
- Style::MarginTop(s.parse()?)
- // mask
- // mask-border
- // mask-border-mode
- // mask-border-outset
- // mask-border-repeat
- // mask-border-slice
- // mask-border-source
- // mask-border-width
- // mask-clip
- // mask-composite
- // mask-image
- // mask-mode
- // mask-origin
- // mask-position
- // mask-repeat
- // mask-size
- // mask-type
- } else if name.try_match("max-height") {
- Style::MaxHeight(s.parse()?)
- } else if name.try_match("max-width") {
- Style::MaxWidth(s.parse()?)
- } else if name.try_match("min-height") {
- Style::MinHeight(s.parse()?)
- } else if name.try_match("min-width") {
- Style::MinWidth(s.parse()?)
- // mix-blend-mode
- } else if name.try_match("object-fit") {
- Style::ObjectFit(s.parse()?)
- // object-position
- // opacity
- // order
- // orphans
- // outline
- // outline-color
- // outline-offset
- // outline-style
- // outline-width
- } else if name.try_match("overflow") {
- Style::Overflow(s.parse()?)
- } else if name.try_match("overflow-x") {
- Style::OverflowX(s.parse()?)
- } else if name.try_match("overflow-y") {
- Style::OverflowY(s.parse()?)
- } else if name.try_match("padding") {
- Style::Padding(s.parse()?)
- } else if name.try_match("padding-bottom") {
- Style::PaddingBottom(s.parse()?)
- } else if name.try_match("padding-left") {
- Style::PaddingLeft(s.parse()?)
- } else if name.try_match("padding-right") {
- Style::PaddingRight(s.parse()?)
- } else if name.try_match("padding-top") {
- Style::PaddingTop(s.parse()?)
- // page-break-after
- // page-break-before
- // page-break-inside
- // pause
- // pause-after
- // pause-before
- // pitch
- // pitch-range
- // play-during
- } else if name.try_match("position") {
- Style::Position(s.parse()?)
- // quotes
- } else if name.try_match("resize") {
- Style::Resize(s.parse()?)
- // richness
- } else if name.try_match("right") {
- Style::Right(s.parse()?)
- // scroll-margin
- // scroll-margin-block
- // scroll-margin-block-end
- // scroll-margin-block-start
- // scroll-margin-bottom
- // scroll-margin-inline
- // scroll-margin-inline-end
- // scroll-margin-inline-start
- // scroll-margin-left
- // scroll-margin-right
- // scroll-margin-top
- // scroll-padding
- // scroll-padding-block
- // scroll-padding-block-end
- // scroll-padding-block-start
- // scroll-padding-bottom
- // scroll-padding-inline
- // scroll-padding-inline-end
- // scroll-padding-inline-start
- // scroll-padding-left
- // scroll-padding-right
- // scroll-padding-top
- // scroll-snap-align
- // scroll-snap-stop
- // scroll-snap-type
- // shape-image-threshold
- // shape-margin
- // shape-outside
- // speak
- // speak-header
- // speak-numeral
- // speak-punctuation
- // speech-rate
- // stress
- // table-layout
- } else if name.try_match("text-align") {
- Style::TextAlign(s.parse()?)
- // text-combine-upright
- // text-decoration
- // text-decoration-color
- // text-decoration-line
- // text-decoration-style
- // text-emphasis
- // text-emphasis-color
- // text-emphasis-position
- // text-emphasis-style
- // text-indent
- // text-orientation
- // text-overflow
- // text-shadow
- // text-transform
- // text-underline-position
- } else if name.try_match("top") {
- Style::Top(s.parse()?)
- // transform
- // transform-box
- // transform-origin
- // unicode-bidi
- // vertical-align
- // visibility
- // voice-family
- // volume
- } else if name.try_match("white-space") {
- Style::WhiteSpace(s.parse()?)
- } else if name.try_match("widows") {
- Style::Widows(integer(s, 1..)?)
- } else if name.try_match("width") {
- Style::Width(s.parse()?)
- // will-change
- // word-spacing
- // writing-mode
- // z-index
- } else {
- return Err(name.error());
- };
- if !finished_rule(s) {
- return Err(s.error("unexpected trailing tokens in style rule"));
- }
- Ok(output)
- }
- }
- impl Parse for AlignContent {
- fn parse(s: ParseStream) -> syn::Result<Self> {
- let name: HyphenWord = s.parse()?;
- if name.try_match("flex-start") {
- Ok(AlignContent::FlexStart)
- } else if name.try_match("flex-end") {
- Ok(AlignContent::FlexEnd)
- } else if name.try_match("center") {
- Ok(AlignContent::Center)
- } else if name.try_match("space-between") {
- Ok(AlignContent::SpaceBetween)
- } else if name.try_match("space-around") {
- Ok(AlignContent::SpaceAround)
- } else if name.try_match("stretch") {
- Ok(AlignContent::Stretch)
- } else {
- Err(name.error())
- }
- }
- }
- #[test]
- fn test_align_content() {
- for test in vec![
- "flex-start",
- "flex-end",
- "center",
- "space-between",
- "space-around",
- "stretch",
- ] {
- assert_eq!(
- &syn::parse_str::<AlignContent>(test).unwrap().to_string(),
- test
- );
- }
- assert_eq!(
- &syn::parse_str::<Style>("align-content:flex-start")
- .unwrap()
- .to_string(),
- "align-content:flex-start"
- );
- }
- impl Parse for AlignItems {
- fn parse(s: ParseStream) -> syn::Result<Self> {
- let word: HyphenWord = s.parse()?;
- if word.try_match("normal") {
- Ok(AlignItems::Normal)
- } else if word.try_match("stretch") {
- Ok(AlignItems::Stretch)
- } else if word.try_match("center") {
- Ok(AlignItems::Center)
- } else if word.try_match("start") {
- Ok(AlignItems::Start)
- } else if word.try_match("end") {
- Ok(AlignItems::End)
- } else if word.try_match("flex-start") {
- Ok(AlignItems::FlexStart)
- } else if word.try_match("flex-end") {
- Ok(AlignItems::FlexEnd)
- } else if word.try_match("baseline") {
- Ok(AlignItems::Baseline)
- } else if word.try_match("first") {
- let word: HyphenWord = s.parse()?;
- if word.try_match("baseline") {
- Ok(AlignItems::FirstBaseline)
- } else {
- Err(word.error())
- }
- } else if word.try_match("last") {
- let word: HyphenWord = s.parse()?;
- if word.try_match("baseline") {
- Ok(AlignItems::LastBaseline)
- } else {
- Err(word.error())
- }
- } else if word.try_match("safe") {
- let word: HyphenWord = s.parse()?;
- if word.try_match("center") {
- Ok(AlignItems::SafeCenter)
- } else {
- Err(word.error())
- }
- } else if word.try_match("unsafe") {
- let word: HyphenWord = s.parse()?;
- if word.try_match("center") {
- Ok(AlignItems::UnsafeCenter)
- } else {
- Err(word.error())
- }
- } else {
- Err(word.error())
- }
- }
- }
- impl Parse for AlignSelf {
- fn parse(s: ParseStream) -> syn::Result<Self> {
- let word: HyphenWord = s.parse()?;
- if word.try_match("auto") {
- Ok(AlignSelf::Auto)
- } else if word.try_match("normal") {
- Ok(AlignSelf::Normal)
- } else if word.try_match("center") {
- Ok(AlignSelf::Center)
- } else if word.try_match("start") {
- Ok(AlignSelf::Start)
- } else if word.try_match("self-start") {
- Ok(AlignSelf::SelfStart)
- } else if word.try_match("self-end") {
- Ok(AlignSelf::SelfEnd)
- } else if word.try_match("flex-start") {
- Ok(AlignSelf::FlexStart)
- } else if word.try_match("flex-end") {
- Ok(AlignSelf::FlexEnd)
- } else if word.try_match("baseline") {
- Ok(AlignSelf::Baseline)
- } else if word.try_match("first") {
- let word: HyphenWord = s.parse()?;
- if word.try_match("baseline") {
- Ok(AlignSelf::FirstBaseline)
- } else {
- Err(word.error())
- }
- } else if word.try_match("last") {
- let word: HyphenWord = s.parse()?;
- if word.try_match("baseline") {
- Ok(AlignSelf::LastBaseline)
- } else {
- Err(word.error())
- }
- } else if word.try_match("stretch") {
- Ok(AlignSelf::Stretch)
- } else if word.try_match("safe") {
- let word: HyphenWord = s.parse()?;
- if word.try_match("center") {
- Ok(AlignSelf::SafeCenter)
- } else {
- Err(word.error())
- }
- } else if word.try_match("unsafe") {
- let word: HyphenWord = s.parse()?;
- if word.try_match("center") {
- Ok(AlignSelf::UnsafeCenter)
- } else {
- Err(word.error())
- }
- } else {
- Err(word.error())
- }
- }
- }
- impl Parse for BackgroundAttachment {
- fn parse(s: ParseStream) -> syn::Result<Self> {
- let word: HyphenWord = s.parse()?;
- if word.try_match("scroll") {
- Ok(BackgroundAttachment::Scroll)
- } else if word.try_match("fixed") {
- Ok(BackgroundAttachment::Fixed)
- } else if word.try_match("local") {
- Ok(BackgroundAttachment::Local)
- } else {
- Err(word.error())
- }
- }
- }
- impl Parse for BlendMode {
- fn parse(s: ParseStream) -> syn::Result<Self> {
- let word: HyphenWord = s.parse()?;
- if word.try_match("normal") {
- Ok(BlendMode::Normal)
- } else if word.try_match("multiply") {
- Ok(BlendMode::Multiply)
- } else if word.try_match("screen") {
- Ok(BlendMode::Screen)
- } else if word.try_match("overlay") {
- Ok(BlendMode::Overlay)
- } else if word.try_match("darken") {
- Ok(BlendMode::Darken)
- } else if word.try_match("lighten") {
- Ok(BlendMode::Lighten)
- } else if word.try_match("color-dodge") {
- Ok(BlendMode::ColorDodge)
- } else if word.try_match("color-burn") {
- Ok(BlendMode::ColorBurn)
- } else if word.try_match("hard-light") {
- Ok(BlendMode::HardLight)
- } else if word.try_match("soft-light") {
- Ok(BlendMode::SoftLight)
- } else if word.try_match("difference") {
- Ok(BlendMode::Difference)
- } else if word.try_match("exclusion") {
- Ok(BlendMode::Exclusion)
- } else if word.try_match("hue") {
- Ok(BlendMode::Hue)
- } else if word.try_match("saturation") {
- Ok(BlendMode::Saturation)
- } else if word.try_match("color") {
- Ok(BlendMode::Color)
- } else if word.try_match("luminosity") {
- Ok(BlendMode::Luminosity)
- } else {
- Err(word.error())
- }
- }
- }
- impl Parse for BackgroundImage {
- fn parse(s: ParseStream) -> syn::Result<Self> {
- let peek = HyphenWord::peek_specific(s);
- if peek.as_ref().map(|s| s.as_str()) == Some("url") {
- let url;
- syn::parenthesized!(url in s);
- let url = url.parse::<syn::LitStr>()?;
- Ok(BackgroundImage::Url(url.value()))
- } else {
- let word: HyphenWord = s.parse()?;
- word.add_expected("url");
- if word.try_match("none") {
- Ok(BackgroundImage::None)
- } else {
- Err(word.error())
- }
- }
- }
- }
- impl Parse for BackgroundBox {
- fn parse(s: ParseStream) -> syn::Result<Self> {
- let word: HyphenWord = s.parse()?;
- if word.try_match("border-box") {
- Ok(BackgroundBox::BorderBox)
- } else if word.try_match("padding-box") {
- Ok(BackgroundBox::PaddingBox)
- } else if word.try_match("content-box") {
- Ok(BackgroundBox::ContentBox)
- } else {
- Err(word.error())
- }
- }
- }
- impl Parse for BackgroundPosition {
- fn parse(s: ParseStream) -> syn::Result<Self> {
- let word: HyphenWord = s.parse()?;
- if word.try_match("top") {
- Ok(BackgroundPosition::Top)
- } else if word.try_match("bottom") {
- Ok(BackgroundPosition::Bottom)
- } else if word.try_match("left") {
- Ok(BackgroundPosition::Left)
- } else if word.try_match("right") {
- Ok(BackgroundPosition::Right)
- } else if word.try_match("center") {
- Ok(BackgroundPosition::Center)
- } else {
- Err(word.error())
- }
- }
- }
- impl Parse for BackgroundRepeat {
- fn parse(s: ParseStream) -> syn::Result<Self> {
- let word: HyphenWord = s.parse()?;
- if word.try_match("repeat-x") {
- Ok(BackgroundRepeat::RepeatX)
- } else if word.try_match("repeat-y") {
- Ok(BackgroundRepeat::RepeatY)
- } else if let Ok(v) = s.parse() {
- Ok(BackgroundRepeat::SingleOrDouble(v))
- } else {
- word.add_expected("repeat");
- word.add_expected("space");
- word.add_expected("round");
- word.add_expected("no-repeat");
- Err(word.error())
- }
- }
- }
- impl Parse for BgRepeatPart {
- fn parse(s: ParseStream) -> syn::Result<Self> {
- let word: HyphenWord = s.parse()?;
- if word.try_match("repeat") {
- Ok(BgRepeatPart::Repeat)
- } else if word.try_match("space") {
- Ok(BgRepeatPart::Space)
- } else if word.try_match("round") {
- Ok(BgRepeatPart::Round)
- } else if word.try_match("no-repeat") {
- Ok(BgRepeatPart::NoRepeat)
- } else {
- Err(word.error())
- }
- }
- }
- impl Parse for BackgroundSize {
- fn parse(s: ParseStream) -> syn::Result<Self> {
- let word: HyphenWord = s.parse()?;
- if word.try_match("cover") {
- Ok(BackgroundSize::Cover)
- } else if word.try_match("contain") {
- Ok(BackgroundSize::Contain)
- } else if let Ok(v) = s.parse() {
- Ok(BackgroundSize::SingleOrDouble(v))
- } else {
- word.add_expected("<length>");
- word.add_expected("<percentage>");
- word.add_expected("auto");
- Err(word.error())
- }
- }
- }
- impl Parse for Border {
- fn parse(s: ParseStream) -> syn::Result<Self> {
- fn line_width_error(span: Span) -> syn::Error {
- syn::Error::new(span, "the border width was specified more than once")
- }
- fn line_style_error(span: Span) -> syn::Error {
- syn::Error::new(span, "the border style was specified more than once")
- }
- fn color_error(span: Span) -> syn::Error {
- syn::Error::new(span, "the border color was specified more than once")
- }
- let mut border = Border::new();
- while !(border.is_full() || finished_rule(s)) {
- let mut matched_something = false; // prevents an infinite loop when no matches
- let width_fork = s.fork();
- match width_fork.parse::<LineWidth>() {
- Ok(line_width) => {
- if border.has_line_width() {
- return Err(line_width_error(width_fork.cursor().span()));
- }
- matched_something = true;
- border.line_width = Some(line_width);
- s.advance_to(&width_fork);
- }
- Err(_) => (),
- }
- let style_fork = s.fork();
- match style_fork.parse::<LineStyle>() {
- Ok(line_style) => {
- if border.has_line_style() {
- return Err(line_style_error(style_fork.cursor().span()));
- }
- matched_something = true;
- border.line_style = Some(line_style);
- s.advance_to(&style_fork);
- }
- Err(_) => (),
- }
- let color_fork = s.fork();
- match color_fork.parse::<Color>() {
- Ok(color) => {
- if border.has_color() {
- return Err(color_error(color_fork.cursor().span()));
- }
- matched_something = true;
- border.color = Some(color);
- s.advance_to(&color_fork);
- }
- Err(_) => (),
- }
- if !(matched_something || finished_rule(s)) {
- return Err(syn::Error::new(
- s.cursor().span(),
- "unexpected input - expected one of border-width, border-style, color",
- ));
- }
- }
- Ok(border)
- }
- }
- #[test]
- fn test_border_color() {
- for (input, output) in vec![
- ("black", Rect::All(Color::Black)),
- (
- "#fff blue",
- Rect::VerticalHorizontal(Color::HexRGB(255, 255, 255), Color::Blue),
- ),
- (
- "blue hsl(20, 5%, 100%) white",
- Rect::TopHorizontalBottom(Color::Blue, Color::HSL(20.0, 5.0, 100.0), Color::White),
- ),
- (
- "hsla(20, 5%, 100%, 0.2) #fff #ccc white",
- Rect::TopRightBottomLeft(
- Color::HSLA(20.0, 5.0, 100.0, 0.2),
- Color::HexRGB(255, 255, 255),
- Color::HexRGB(204, 204, 204),
- Color::White,
- ),
- ),
- ] {
- assert_eq!(syn::parse_str::<Rect<Color>>(input).unwrap(), output);
- }
- }
- #[test]
- fn test_border_width() {
- for (input, output) in vec![
- ("1px", BorderWidth::All(LineWidth::Length(Length::Px(1.0)))),
- (
- "1px 2\"em\"",
- BorderWidth::VerticalHorizontal(
- LineWidth::Length(Length::Px(1.0)),
- LineWidth::Length(Length::Em(2.0)),
- ),
- ),
- (
- "2\"em\" medium thick",
- BorderWidth::TopHorizontalBottom(
- LineWidth::Length(Length::Em(2.0)),
- LineWidth::Medium,
- LineWidth::Thick,
- ),
- ),
- (
- "2\"em\" medium 1px thick",
- BorderWidth::TopRightBottomLeft(
- LineWidth::Length(Length::Em(2.0)),
- LineWidth::Medium,
- LineWidth::Length(Length::Px(1.0)),
- LineWidth::Thick,
- ),
- ),
- ] {
- assert_eq!(syn::parse_str::<BorderWidth>(input).unwrap(), output);
- }
- for input in vec!["thi", "1px 1px 1px 1px 1px"] {
- assert!(syn::parse_str::<BorderWidth>(input).is_err());
- }
- }
- impl Parse for BorderCollapse {
- fn parse(s: ParseStream) -> syn::Result<Self> {
- let word: HyphenWord = s.parse()?;
- if word.try_match("collapse") {
- Ok(BorderCollapse::Collapse)
- } else if word.try_match("separate") {
- Ok(BorderCollapse::Separate)
- } else {
- Err(word.error())
- }
- }
- }
- impl Parse for BoxShadow {
- fn parse(s: ParseStream) -> syn::Result<Self> {
- syn::custom_keyword!(none);
- if s.peek(none) {
- s.parse::<none>()?;
- Ok(BoxShadow::None)
- } else {
- Ok(BoxShadow::Shadows(s.parse()?))
- }
- }
- }
- impl Parse for BoxSizing {
- fn parse(s: ParseStream) -> syn::Result<Self> {
- let word: HyphenWord = s.parse()?;
- if word.try_match("border-box") {
- Ok(BoxSizing::BorderBox)
- } else if word.try_match("content-box") {
- Ok(BoxSizing::ContentBox)
- } else {
- Err(word.error())
- }
- }
- }
- impl Parse for Clear {
- fn parse(s: ParseStream) -> syn::Result<Self> {
- let word: HyphenWord = s.parse()?;
- if word.try_match("none") {
- Ok(Clear::None)
- } else if word.try_match("left") {
- Ok(Clear::Left)
- } else if word.try_match("right") {
- Ok(Clear::Right)
- } else if word.try_match("both") {
- Ok(Clear::Both)
- } else if word.try_match("inline-start") {
- Ok(Clear::InlineStart)
- } else if word.try_match("inline-end") {
- Ok(Clear::InlineEnd)
- } else {
- Err(word.error())
- }
- }
- }
- impl Parse for ColumnCount {
- fn parse(s: ParseStream) -> syn::Result<Self> {
- if s.peek(syn::LitInt) {
- Ok(ColumnCount::Fixed(s.parse::<Integer<u32>>()?.into_inner()))
- } else {
- let word: HyphenWord = s.parse()?;
- word.add_expected("integer");
- if word.try_match("auto") {
- Ok(ColumnCount::Auto)
- } else {
- Err(word.error())
- }
- }
- }
- }
- #[test]
- fn test_clear() {
- for (input, output) in vec![
- ("none", Clear::None),
- ("left", Clear::Left),
- ("right", Clear::Right),
- ("both", Clear::Both),
- ("inline-start", Clear::InlineStart),
- ("inline-end", Clear::InlineEnd),
- ] {
- assert_eq!(syn::parse_str::<Clear>(input).unwrap(), output);
- }
- }
- impl Parse for Cursor {
- fn parse(s: ParseStream) -> syn::Result<Self> {
- let word: HyphenWord = s.parse()?;
- if word.try_match("auto") {
- Ok(Cursor::Auto)
- } else if word.try_match("default") {
- Ok(Cursor::Default)
- } else if word.try_match("none") {
- Ok(Cursor::None)
- } else if word.try_match("context-menu") {
- Ok(Cursor::ContextMenu)
- } else if word.try_match("help") {
- Ok(Cursor::Help)
- } else if word.try_match("pointer") {
- Ok(Cursor::Pointer)
- } else if word.try_match("progress") {
- Ok(Cursor::Progress)
- } else if word.try_match("wait") {
- Ok(Cursor::Wait)
- } else if word.try_match("cell") {
- Ok(Cursor::Cell)
- } else if word.try_match("crosshair") {
- Ok(Cursor::Crosshair)
- } else if word.try_match("text") {
- Ok(Cursor::Text)
- } else if word.try_match("vertical-text") {
- Ok(Cursor::VerticalText)
- } else if word.try_match("alias") {
- Ok(Cursor::Alias)
- } else if word.try_match("copy") {
- Ok(Cursor::Copy)
- } else if word.try_match("move") {
- Ok(Cursor::Move)
- } else if word.try_match("no-drop") {
- Ok(Cursor::NoDrop)
- } else if word.try_match("not-allowed") {
- Ok(Cursor::NotAllowed)
- } else if word.try_match("grab") {
- Ok(Cursor::Grab)
- } else if word.try_match("grabbing") {
- Ok(Cursor::Grabbing)
- } else if word.try_match("e-resize") {
- Ok(Cursor::EResize)
- } else if word.try_match("n-resize") {
- Ok(Cursor::NResize)
- } else if word.try_match("ne-resize") {
- Ok(Cursor::NEResize)
- } else if word.try_match("nw-resize") {
- Ok(Cursor::NWResize)
- } else if word.try_match("s-resize") {
- Ok(Cursor::SResize)
- } else if word.try_match("se-resize") {
- Ok(Cursor::SEResize)
- } else if word.try_match("sw-resize") {
- Ok(Cursor::SWResize)
- } else if word.try_match("w-resize") {
- Ok(Cursor::WResize)
- } else if word.try_match("ew-resize") {
- Ok(Cursor::EWResize)
- } else if word.try_match("ns-resize") {
- Ok(Cursor::NSResize)
- } else if word.try_match("nesw-resize") {
- Ok(Cursor::NESWResize)
- } else if word.try_match("nwse-resize") {
- Ok(Cursor::NWSEResize)
- } else if word.try_match("col-resize") {
- Ok(Cursor::ColResize)
- } else if word.try_match("row-resize") {
- Ok(Cursor::RowResize)
- } else if word.try_match("all-scroll") {
- Ok(Cursor::AllScroll)
- } else if word.try_match("zoom-in") {
- Ok(Cursor::ZoomIn)
- } else if word.try_match("zoom-out") {
- Ok(Cursor::ZoomOut)
- } else {
- Err(word.error())
- }
- }
- }
- impl Parse for Display {
- fn parse(s: ParseStream) -> syn::Result<Self> {
- let word: HyphenWord = s.parse()?;
- if word.try_match("block") {
- Ok(Display::Block)
- } else if word.try_match("flex") {
- Ok(Display::Flex)
- } else if word.try_match("inline") {
- Ok(Display::Inline)
- } else {
- Err(word.error())
- }
- }
- }
- impl Parse for FlexBasis {
- fn parse(s: ParseStream) -> syn::Result<Self> {
- syn::custom_keyword!(content);
- if s.peek(content) {
- s.parse::<content>()?;
- Ok(FlexBasis::Content)
- } else {
- let w: Width21 = s.parse()?;
- Ok(FlexBasis::Width(w))
- }
- }
- }
- impl Parse for FlexDirection {
- fn parse(s: ParseStream) -> syn::Result<Self> {
- let word: HyphenWord = s.parse()?;
- if word.try_match("column") {
- Ok(FlexDirection::Column)
- } else if word.try_match("row") {
- Ok(FlexDirection::Row)
- } else {
- Err(word.error())
- }
- }
- }
- impl Parse for FlexWrap {
- fn parse(s: ParseStream) -> syn::Result<Self> {
- let word: HyphenWord = s.parse()?;
- if word.try_match("wrap") {
- Ok(FlexWrap::Wrap)
- } else if word.try_match("nowrap") {
- Ok(FlexWrap::Nowrap)
- } else {
- Err(word.error())
- }
- }
- }
- impl Parse for Float {
- fn parse(s: ParseStream) -> syn::Result<Self> {
- let word: HyphenWord = s.parse()?;
- if word.try_match("none") {
- Ok(Float::None)
- } else if word.try_match("left") {
- Ok(Float::Left)
- } else if word.try_match("right") {
- Ok(Float::Right)
- } else if word.try_match("inline-start") {
- Ok(Float::InlineStart)
- } else if word.try_match("inline-end") {
- Ok(Float::InlineEnd)
- } else {
- Err(word.error())
- }
- }
- }
- impl Parse for Font {
- fn parse(s: ParseStream) -> syn::Result<Self> {
- if s.peek(syn::LitStr) {
- Ok(Font::Named(s.parse::<syn::LitStr>()?.value()))
- } else {
- let name: HyphenWord = s.parse()?;
- name.add_expected("named font");
- if name.try_match("serif") {
- Ok(Font::Serif)
- } else if name.try_match("sans-serif") {
- Ok(Font::SansSerif)
- } else if name.try_match("cursive") {
- Ok(Font::Cursive)
- } else if name.try_match("fantasy") {
- Ok(Font::Fantasy)
- } else if name.try_match("monospace") {
- Ok(Font::Fantasy)
- } else {
- Err(name.error())
- }
- }
- }
- }
- #[test]
- fn test_font_family() {
- for (input, output) in vec![
- (
- "cursive",
- FontFamily {
- first: Font::Cursive,
- rest: vec![],
- },
- ),
- (
- "\"Amatic SC\", sans-serif",
- FontFamily {
- first: Font::Named("Amatic SC".to_string()),
- rest: vec![Font::SansSerif],
- },
- ),
- ] {
- assert_eq!(syn::parse_str::<FontFamily>(input).unwrap(), output);
- }
- for val in vec![
- "font-family:\"Font Awesome 5 Free\"",
- "font-family:\"Some Name\",\"Another Name\",serif",
- ] {
- assert_eq!(&syn::parse_str::<Style>(val).unwrap().to_string(), val);
- }
- }
- impl Parse for FontSize {
- fn parse(s: ParseStream) -> syn::Result<Self> {
- let word_fork = s.fork();
- let name: HyphenWord = word_fork.parse()?;
- if name.try_match("xx-small") {
- s.advance_to(&word_fork);
- Ok(FontSize::XXSmall)
- } else if name.try_match("x-small") {
- s.advance_to(&word_fork);
- Ok(FontSize::XSmall)
- } else if name.try_match("small") {
- s.advance_to(&word_fork);
- Ok(FontSize::Small)
- } else if name.try_match("medium") {
- s.advance_to(&word_fork);
- Ok(FontSize::Medium)
- } else if name.try_match("large") {
- s.advance_to(&word_fork);
- Ok(FontSize::Large)
- } else if name.try_match("x-large") {
- s.advance_to(&word_fork);
- Ok(FontSize::XLarge)
- } else if name.try_match("xx-large") {
- s.advance_to(&word_fork);
- Ok(FontSize::XXLarge)
- } else if name.try_match("xxx-large") {
- s.advance_to(&word_fork);
- Ok(FontSize::XXXLarge)
- } else if name.try_match("larger") {
- s.advance_to(&word_fork);
- Ok(FontSize::Larger)
- } else if name.try_match("smaller") {
- s.advance_to(&word_fork);
- Ok(FontSize::Smaller)
- } else {
- s.parse().map(FontSize::LengthPercentage).map_err(|_| {
- name.add_expected("length");
- name.add_expected("percentage");
- name.error()
- })
- }
- }
- }
- impl Parse for FontStyle {
- fn parse(s: ParseStream) -> syn::Result<Self> {
- let name: HyphenWord = s.parse()?;
- if name.try_match("normal") {
- Ok(FontStyle::Normal)
- } else if name.try_match("italic") {
- Ok(FontStyle::Italic)
- } else if name.try_match("oblique") {
- Ok(FontStyle::Oblique)
- } else {
- Err(name.error())
- }
- }
- }
- #[test]
- fn test_font_style() {
- for (input, output) in vec![
- ("normal", FontStyle::Normal),
- ("italic", FontStyle::Italic),
- ("oblique", FontStyle::Oblique),
- ] {
- assert_eq!(syn::parse_str::<FontStyle>(input).unwrap(), output);
- }
- for input in vec!["norma", "normal trailing"] {
- assert!(syn::parse_str::<FontStyle>(input).is_err());
- }
- }
- impl Parse for FontWeight {
- fn parse(s: ParseStream) -> syn::Result<Self> {
- let name: HyphenWord = s.parse()?;
- name.add_expected("number where 1 <= number <= 1000");
- if name.try_match("normal") {
- Ok(FontWeight::Normal)
- } else if name.try_match("bold") {
- Ok(FontWeight::Bold)
- } else if name.try_match("lighter") {
- Ok(FontWeight::Lighter)
- } else if name.try_match("bolder") {
- Ok(FontWeight::Bolder)
- } else {
- let n: Number = s.parse().map_err(|_| name.error())?;
- if n.suffix.is_empty() && n.value >= 1.0 && n.value <= 1000.0 {
- Ok(FontWeight::Number(n.value))
- } else {
- Err(name.error())
- }
- }
- }
- }
- #[test]
- fn test_font_weight() {
- for (input, output) in vec![
- ("normal", FontWeight::Normal),
- ("bold", FontWeight::Bold),
- ("lighter", FontWeight::Lighter),
- ("bolder", FontWeight::Bolder),
- ("1", FontWeight::Number(1.0)),
- ("1.0", FontWeight::Number(1.0)),
- ("1000", FontWeight::Number(1000.0)),
- ("1000.0", FontWeight::Number(1000.0)),
- ("246.15", FontWeight::Number(246.15)),
- ] {
- match syn::parse_str::<FontWeight>(input) {
- Ok(v) => assert_eq!(v, output),
- Err(e) => panic!("error parsing {}: {}", input, e),
- }
- }
- }
- impl Parse for JustifyContent {
- fn parse(s: ParseStream) -> syn::Result<Self> {
- let name: HyphenWord = s.parse()?;
- if name.try_match("flex-start") {
- Ok(JustifyContent::FlexStart)
- } else if name.try_match("flex-end") {
- Ok(JustifyContent::FlexEnd)
- } else if name.try_match("center") {
- Ok(JustifyContent::Center)
- } else if name.try_match("space-between") {
- Ok(JustifyContent::SpaceBetween)
- } else if name.try_match("space-around") {
- Ok(JustifyContent::SpaceAround)
- } else if name.try_match("start") {
- // - not in level 1 spec
- Ok(JustifyContent::FlexStart)
- } else if name.try_match("end") {
- // - not in level 1 spec
- Ok(JustifyContent::FlexEnd)
- } else {
- Err(name.error())
- }
- }
- }
- impl Parse for Length {
- fn parse(s: ParseStream) -> syn::Result<Self> {
- let neg = if s.peek(Token![-]) {
- s.parse::<Token![-]>()?;
- true
- } else {
- false
- };
- let n: Number = s.parse()?;
- Length::parse_from_number(n, neg)
- }
- }
- impl Length {
- fn parse_from_number(n: Number, neg: bool) -> syn::Result<Self> {
- let neg = if neg { -1.0 } else { 1.0 };
- if n.suffix == "em" {
- Ok(Length::Em(n.value * neg))
- } else if n.suffix == "ex" {
- Ok(Length::Ex(n.value * neg))
- } else if n.suffix == "in" {
- Ok(Length::In(n.value * neg))
- } else if n.suffix == "cm" {
- Ok(Length::Cm(n.value * neg))
- } else if n.suffix == "mm" {
- Ok(Length::Mm(n.value * neg))
- } else if n.suffix == "pt" {
- Ok(Length::Pt(n.value * neg))
- } else if n.suffix == "pc" {
- Ok(Length::Pc(n.value * neg))
- } else if n.suffix == "px" {
- Ok(Length::Px(n.value * neg))
- } else if n.suffix == "" && n.value == 0.0 {
- Ok(Length::Zero)
- } else {
- // No matches so return error
- Err(syn::Error::new(
- n.span,
- "expected one of `\"em\"`, `\"ex\"`, `in`, `cm`, `mm`, `pt`, `pc`, `px` after number, or 0",
- ))
- }
- }
- }
- impl Parse for LineStyle {
- fn parse(s: ParseStream) -> syn::Result<Self> {
- let name = s.parse::<HyphenWord>()?;
- if name.try_match("none") {
- Ok(LineStyle::None)
- } else if name.try_match("hidden") {
- Ok(LineStyle::Hidden)
- } else if name.try_match("dotted") {
- Ok(LineStyle::Dotted)
- } else if name.try_match("dashed") {
- Ok(LineStyle::Dashed)
- } else if name.try_match("solid") {
- Ok(LineStyle::Solid)
- } else if name.try_match("double") {
- Ok(LineStyle::Double)
- } else if name.try_match("groove") {
- Ok(LineStyle::Groove)
- } else if name.try_match("ridge") {
- Ok(LineStyle::Ridge)
- } else if name.try_match("inset") {
- Ok(LineStyle::Inset)
- } else if name.try_match("outset") {
- Ok(LineStyle::Outset)
- } else {
- Err(name.error())
- }
- }
- }
- impl Parse for LineWidth {
- fn parse(s: ParseStream) -> syn::Result<Self> {
- let name = s.parse::<HyphenWord>()?;
- if name.try_match("thin") {
- Ok(LineWidth::Thin)
- } else if name.try_match("medium") {
- Ok(LineWidth::Medium)
- } else if name.try_match("thick") {
- Ok(LineWidth::Thick)
- } else {
- match s.parse::<Length>() {
- Ok(l) => Ok(LineWidth::Length(l)),
- Err(_) => {
- name.add_expected("length");
- Err(name.error())
- }
- }
- }
- }
- }
- #[test]
- fn test_parse_line_width() {
- assert_eq!(
- syn::parse_str::<LineWidth>("thin").unwrap(),
- LineWidth::Thin
- );
- }
- impl Parse for LineHeight {
- fn parse(s: ParseStream) -> syn::Result<Self> {
- Ok(LineHeight(s.parse::<syn::LitFloat>()?.base10_parse()?))
- }
- }
- impl Parse for ListStyleType {
- fn parse(s: ParseStream) -> syn::Result<Self> {
- let name: HyphenWord = s.parse()?;
- if name.try_match("disc") {
- Ok(ListStyleType::Disc)
- } else if name.try_match("circle") {
- Ok(ListStyleType::Circle)
- } else if name.try_match("square") {
- Ok(ListStyleType::Square)
- } else if name.try_match("decimal") {
- Ok(ListStyleType::Decimal)
- } else if name.try_match("decimal-leading-zero") {
- Ok(ListStyleType::DecimalLeadingZero)
- } else if name.try_match("lower-roman") {
- Ok(ListStyleType::LowerRoman)
- } else if name.try_match("upper-roman") {
- Ok(ListStyleType::UpperRoman)
- } else if name.try_match("lower-greek") {
- Ok(ListStyleType::LowerGreek)
- } else if name.try_match("upper-greek") {
- Ok(ListStyleType::UpperGreek)
- } else if name.try_match("lower-latin") {
- Ok(ListStyleType::LowerLatin)
- } else if name.try_match("upper-latin") {
- Ok(ListStyleType::UpperLatin)
- } else if name.try_match("armenian") {
- Ok(ListStyleType::Armenian)
- } else if name.try_match("georgian") {
- Ok(ListStyleType::Georgian)
- } else if name.try_match("lower-alpha") {
- Ok(ListStyleType::LowerAlpha)
- } else if name.try_match("upper-alpha") {
- Ok(ListStyleType::UpperAlpha)
- } else if name.try_match("none") {
- Ok(ListStyleType::None)
- } else {
- Err(name.error())
- }
- }
- }
- impl Parse for MaxWidthHeight {
- fn parse(s: ParseStream) -> syn::Result<Self> {
- let name = s.parse::<HyphenWord>()?;
- name.add_expected("length");
- name.add_expected("percentage");
- if name.try_match("none") {
- Ok(MaxWidthHeight::None)
- } else if name.try_match("min-content") {
- Ok(MaxWidthHeight::MinContent)
- } else if name.try_match("max-content") {
- Ok(MaxWidthHeight::MaxContent)
- } else if name.try_match("fit-content") {
- let content;
- syn::parenthesized!(content in s);
- Ok(MaxWidthHeight::FitContent(content.parse()?))
- } else {
- s.parse()
- .map(|lp| MaxWidthHeight::LengthPercentage(lp))
- .map_err(|_| name.error())
- }
- }
- }
- #[test]
- fn test_max_width_height() {
- let style: Style = syn::parse_str("max-width: 200px").unwrap();
- assert_eq!(&style.to_string(), "max-width:200px");
- }
- impl<T> Parse for Rect<T>
- where
- T: Parse,
- {
- fn parse(s: ParseStream) -> syn::Result<Self> {
- let first = s.parse::<T>()?;
- let fork = s.fork();
- let second = match fork.parse::<T>() {
- Ok(v) => {
- s.advance_to(&fork);
- v
- }
- Err(_) => return Ok(Rect::All(first)),
- };
- let third = match fork.parse::<T>() {
- Ok(v) => {
- s.advance_to(&fork);
- v
- }
- Err(_) => return Ok(Rect::VerticalHorizontal(first, second)),
- };
- match fork.parse::<T>() {
- Ok(v) => {
- s.advance_to(&fork);
- Ok(Rect::TopRightBottomLeft(first, second, third, v))
- }
- Err(_) => Ok(Rect::TopHorizontalBottom(first, second, third)),
- }
- }
- }
- impl Parse for AutoLengthPercentage {
- fn parse(s: ParseStream) -> syn::Result<Self> {
- syn::custom_keyword!(auto);
- if s.peek(auto) {
- s.parse::<auto>()?;
- Ok(AutoLengthPercentage::Auto)
- } else {
- Ok(AutoLengthPercentage::LengthPercentage(s.parse()?))
- }
- }
- }
- impl Parse for ObjectFit {
- fn parse(s: ParseStream) -> syn::Result<Self> {
- let name: HyphenWord = s.parse()?;
- if name.try_match("fill") {
- Ok(ObjectFit::Fill)
- } else if name.try_match("none") {
- Ok(ObjectFit::None)
- } else if name.try_match("contain") {
- if s.is_empty() {
- Ok(ObjectFit::Contain { scale_down: false })
- } else {
- let scale_down_word: HyphenWord = s.parse()?;
- if scale_down_word.try_match("scale-down") {
- Ok(ObjectFit::Contain { scale_down: true })
- } else {
- Err(scale_down_word.error())
- }
- }
- } else if name.try_match("cover") {
- if HyphenWord::peek(s) {
- let scale_down_word: HyphenWord = s.parse()?;
- if scale_down_word.try_match("scale-down") {
- Ok(ObjectFit::Cover { scale_down: true })
- } else {
- Err(scale_down_word.error())
- }
- } else {
- Ok(ObjectFit::Cover { scale_down: false })
- }
- } else if name.try_match("scale-down") {
- if HyphenWord::peek(s) {
- let cover_contain: HyphenWord = s.parse()?;
- if cover_contain.try_match("cover") {
- Ok(ObjectFit::Cover { scale_down: true })
- } else if cover_contain.try_match("contain") {
- Ok(ObjectFit::Contain { scale_down: true })
- } else {
- Err(cover_contain.error())
- }
- } else {
- // defaults to contain when cover/contain not present
- Ok(ObjectFit::Contain { scale_down: true })
- }
- } else {
- Err(name.error())
- }
- }
- }
- impl Parse for Overflow {
- fn parse(s: ParseStream) -> syn::Result<Self> {
- let first = s.parse::<OverflowXY>()?;
- Ok(match s.parse::<OverflowXY>() {
- Ok(second) => Overflow::XY(first, second),
- Err(_) => Overflow::Both(first),
- })
- }
- }
- impl Parse for OverflowXY {
- fn parse(s: ParseStream) -> syn::Result<Self> {
- let name: HyphenWord = s.parse()?;
- if name.try_match("visible") {
- Ok(OverflowXY::Visible)
- } else if name.try_match("hidden") {
- Ok(OverflowXY::Hidden)
- } else if name.try_match("clip") {
- Ok(OverflowXY::Clip)
- } else if name.try_match("scroll") {
- Ok(OverflowXY::Scroll)
- } else if name.try_match("auto") {
- Ok(OverflowXY::Auto)
- } else {
- Err(name.error())
- }
- }
- }
- impl Parse for Position {
- fn parse(s: ParseStream) -> syn::Result<Self> {
- let name: HyphenWord = s.parse()?;
- if name.try_match("static") {
- Ok(Position::Static)
- } else if name.try_match("relative") {
- Ok(Position::Relative)
- } else if name.try_match("absolute") {
- Ok(Position::Absolute)
- } else if name.try_match("fixed") {
- Ok(Position::Fixed)
- } else {
- Err(name.error())
- }
- }
- }
- #[test]
- fn test_padding() {
- for (input, output) in vec![(
- "padding:1\"em\"",
- Style::Padding(Padding::All(Calc::Normal(LengthPercentage::Length(
- Length::Em(1.0),
- )))),
- )] {
- assert_eq!(syn::parse_str::<Style>(input).unwrap(), output);
- }
- }
- impl Parse for Percentage {
- fn parse(s: ParseStream) -> syn::Result<Self> {
- let n: Number = s.parse()?;
- if n.suffix == "%" {
- Ok(Percentage(n.value))
- } else {
- Err(syn::Error::new(n.span, "expected percentage"))
- }
- }
- }
- impl Parse for WhiteSpace {
- fn parse(s: ParseStream) -> syn::Result<Self> {
- let name: HyphenWord = s.parse()?;
- if name.try_match("normal") {
- Ok(WhiteSpace::Normal)
- } else if name.try_match("pre") {
- Ok(WhiteSpace::Pre)
- } else if name.try_match("nowrap") {
- Ok(WhiteSpace::Nowrap)
- } else if name.try_match("pre-wrap") {
- Ok(WhiteSpace::PreWrap)
- } else if name.try_match("pre-line") {
- Ok(WhiteSpace::PreLine)
- } else {
- Err(name.error())
- }
- }
- }
- impl Parse for Width21 {
- fn parse(s: ParseStream) -> syn::Result<Self> {
- syn::custom_keyword!(auto);
- if s.peek(auto) {
- s.parse::<auto>()?;
- Ok(Width21::Auto)
- } else {
- Ok(Width21::LengthPercentage(s.parse()?))
- }
- }
- }
- impl Parse for WidthHeight {
- fn parse(s: ParseStream) -> syn::Result<Self> {
- let fork = s.fork();
- let name: HyphenWord = fork.parse()?;
- if name.try_match("auto") {
- s.advance_to(&fork);
- Ok(WidthHeight::Auto)
- } else if name.try_match("min-content") {
- s.advance_to(&fork);
- Ok(WidthHeight::MinContent)
- } else if name.try_match("max-content") {
- s.advance_to(&fork);
- Ok(WidthHeight::MaxContent)
- } else if name.try_match("fit-content") {
- s.advance_to(&fork);
- let content;
- syn::parenthesized!(content in s);
- let lp = content.parse()?;
- if !content.is_empty() {
- Err(content.error("trailing tokens"))
- } else {
- Ok(WidthHeight::FitContent(lp))
- }
- } else {
- // todo error message
- Ok(WidthHeight::LengthPercentage(s.parse()?))
- }
- }
- }
- #[test]
- fn test_width_height() {
- for (input, output) in vec![
- ("0", "0"),
- ("1px", "1px"),
- ("1\"em\"", "1em"),
- ("calc(100% - 60px)", "calc(100% - 60px)"),
- ] {
- match syn::parse_str::<WidthHeight>(input) {
- Ok(v) => assert_eq!(&v.to_string(), output),
- Err(e) => panic!("Error in \"{}\": {}", input, e),
- }
- }
- }
- impl Parse for LengthPercentage {
- fn parse(s: ParseStream) -> syn::Result<Self> {
- if s.peek2(Token![%]) {
- Ok(LengthPercentage::Percentage(s.parse()?))
- } else {
- Ok(LengthPercentage::Length(s.parse()?))
- }
- }
- }
- #[test]
- fn test_length_percentage() {
- for (input, output) in vec![
- ("1\"em\"", LengthPercentage::Length(Length::Em(1.0))),
- ("1.0px", LengthPercentage::Length(Length::Px(1.0))),
- ("0", LengthPercentage::Length(Length::Zero)),
- ] {
- assert_eq!(syn::parse_str::<LengthPercentage>(input).unwrap(), output);
- }
- }
- impl Parse for Resize {
- fn parse(s: ParseStream) -> syn::Result<Self> {
- let name: HyphenWord = s.parse()?;
- if name.try_match("none") {
- Ok(Resize::None)
- } else if name.try_match("both") {
- Ok(Resize::Both)
- } else if name.try_match("horizontal") {
- Ok(Resize::Horizontal)
- } else if name.try_match("vertical") {
- Ok(Resize::Vertical)
- } else {
- Err(name.error())
- }
- }
- }
- impl Parse for Shadow {
- fn parse(s: ParseStream) -> syn::Result<Self> {
- syn::custom_keyword!(inset);
- let mut inset_val = false;
- let mut length: Option<ShadowLength> = None;
- let mut color: Option<Color> = None;
- // keep trying all three until we're done or there is an error
- loop {
- let mut parsed_something = false;
- // inset (easiest)
- if s.peek(inset) {
- let inset_tok = s.parse::<inset>()?;
- if inset_val {
- return Err(syn::Error::new(
- inset_tok.span(),
- "`inset` must be specified 0 or 1 times",
- ));
- }
- inset_val = true;
- parsed_something = true;
- }
- // color
- let fork = s.fork();
- if let Ok(parsed_color) = fork.parse::<Color>() {
- if color.is_some() {
- return Err(s.error("color must be specified 0 or 1 times"));
- }
- color = Some(parsed_color);
- s.advance_to(&fork);
- parsed_something = true;
- }
- // length
- let fork = s.fork();
- if let Ok(parsed_length) = fork.parse::<ShadowLength>() {
- if length.is_some() {
- return Err(s.error("shadow length must be specified once"));
- }
- length = Some(parsed_length);
- s.advance_to(&fork);
- parsed_something = true;
- }
- // if we've failed to parse anything, end the loop.
- if !parsed_something {
- break;
- }
- }
- if let Some(length) = length {
- Ok(Shadow {
- color,
- length,
- inset: inset_val,
- })
- } else {
- Err(s.error("expected color, length, or `inset`"))
- }
- }
- }
- impl Parse for ShadowLength {
- fn parse(s: ParseStream) -> syn::Result<Self> {
- let horizontal: Length = s.parse()?;
- let vertical: Length = s.parse()?;
- // blur
- let fork = s.fork();
- let blur = match fork.parse::<Length>() {
- Ok(blur) => {
- s.advance_to(&fork);
- blur
- }
- Err(_) => {
- return Ok(ShadowLength::Offsets {
- horizontal,
- vertical,
- });
- }
- };
- // spread
- let fork = s.fork();
- match fork.parse::<Length>() {
- Ok(spread) => {
- s.advance_to(&fork);
- Ok(ShadowLength::OffsetsBlurSpread {
- horizontal,
- vertical,
- blur,
- spread,
- })
- }
- Err(_) => Ok(ShadowLength::OffsetsBlur {
- horizontal,
- vertical,
- blur,
- }),
- }
- }
- }
- impl Parse for TextAlign {
- fn parse(s: ParseStream) -> syn::Result<Self> {
- let word: HyphenWord = s.parse()?;
- if word.try_match("left") {
- Ok(TextAlign::Left)
- } else if word.try_match("right") {
- Ok(TextAlign::Right)
- } else if word.try_match("center") {
- Ok(TextAlign::Center)
- } else if word.try_match("justify") {
- Ok(TextAlign::Justify)
- } else {
- Err(word.error())
- }
- }
- }
- // color
- // =====
- impl Parse for DynamicColor {
- fn parse(s: ParseStream) -> syn::Result<Self> {
- Ok(if s.peek(syn::token::Brace) {
- DynamicColor::Dynamic(s.parse()?)
- } else {
- DynamicColor::Literal(s.parse()?)
- })
- }
- }
- impl Parse for Color {
- fn parse(s: ParseStream) -> syn::Result<Self> {
- if s.peek(Token![#]) {
- return parse_hex_color(s);
- }
- let fn_name: HyphenWord = s.parse()?;
- if fn_name.try_match("hsl") {
- parse_hsl_color(s, false)
- } else if fn_name.try_match("hsla") {
- parse_hsl_color(s, true)
- } else {
- if let Some(name) = fn_name.word.as_ref() {
- if let Some(color) = Color::from_named(name) {
- return Ok(color);
- }
- }
- fn_name.add_expected("named color");
- Err(fn_name.error())
- }
- }
- }
- fn parse_hex_color(s: ParseStream) -> syn::Result<Color> {
- const ERR_MSG: &'static str = "to avoid confusing rust, please enclose hex colors in `\"`";
- s.parse::<Token![#]>()?;
- if !(s.peek(syn::LitStr) || s.peek(Ident)) {
- return Err(s.error(ERR_MSG));
- }
- if s.peek(syn::LitStr) {
- let hex_str: syn::LitStr = s.parse()?;
- color::parse_hex(&hex_str.value()).ok_or(syn::Error::new(hex_str.span(), ERR_MSG))
- } else {
- let hex_str: Ident = s.parse()?;
- color::parse_hex(&hex_str.to_string()).ok_or(syn::Error::new(hex_str.span(), ERR_MSG))
- }
- }
- fn parse_hsl_color(s: ParseStream, with_alpha: bool) -> syn::Result<Color> {
- let content;
- syn::parenthesized!(content in s);
- let n: Number = content.parse()?;
- n.empty_suffix()?;
- let hue = n.value;
- if hue < 0.0 || hue >= 360.0 {
- return Err(syn::Error::new(
- n.span,
- "hue should be in the range `0 <= hue < 360`",
- ));
- }
- content.parse::<Token![,]>()?;
- let n: Number = content.parse()?;
- if n.suffix != "%" {
- return Err(syn::Error::new(
- n.span,
- "saturation should be a percentage (followed by `%`)",
- ));
- }
- let sat = n.value;
- if sat < 0.0 || sat > 100.0 {
- return Err(syn::Error::new(
- n.span,
- "saturation should be in the range `0 <= sat < 100`",
- ));
- }
- content.parse::<Token![,]>()?;
- let n: Number = content.parse()?;
- if n.suffix != "%" {
- return Err(syn::Error::new(
- n.span,
- "saturation should be a percentage (followed by `%`)",
- ));
- }
- let light = n.value;
- if light < 0.0 || light > 100.0 {
- return Err(syn::Error::new(
- n.span,
- "lightness should be in the range `0 <= light < 100`",
- ));
- }
- // since we parse content in parentheses, we can assume no trailing characers
- if !with_alpha {
- return if content.is_empty() {
- Ok(Color::HSL(hue, sat, light))
- } else {
- Err(content.error("trailing characters"))
- };
- }
- // we are a hsla
- content.parse::<Token![,]>()?;
- let n: Number = content.parse()?;
- n.empty_suffix()?;
- let alpha = n.value;
- if alpha < 0.0 || alpha > 1.0 {
- return Err(syn::Error::new(
- n.span,
- "alpha should be in the range `0 <= alpha < 1`",
- ));
- }
- if content.is_empty() {
- Ok(Color::HSLA(hue, sat, light, alpha))
- } else {
- Err(content.error("unexpected trailing characters"))
- }
- }
- #[test]
- fn test_color() {
- for (input, output) in vec![
- ("#ffffffff", Color::HexRGBA(255, 255, 255, 255)),
- ("#ffffff", Color::HexRGB(255, 255, 255)),
- ("#fff", Color::HexRGB(255, 255, 255)),
- ("#\"fff\"", Color::HexRGB(255, 255, 255)),
- ("hsl(100, 50%, 50%)", Color::HSL(100.0, 50.0, 50.0)),
- ("hsla(60, 0%, 0%, 0.2)", Color::HSLA(60.0, 0.0, 0.0, 0.2)),
- ("black", Color::Black),
- ("yellow", Color::Yellow),
- ] {
- match syn::parse_str::<Color>(input) {
- Ok(c) => assert_eq!(c, output),
- Err(e) => panic!("error parsing color {}: {}", input, e),
- }
- }
- }
- // Util
- // ====
- impl<T> Parse for NonemptyCommaList<T>
- where
- T: Parse,
- {
- fn parse(s: ParseStream) -> syn::Result<Self> {
- let punctuated = Punctuated::<T, Token![,]>::parse_separated_nonempty(s)?;
- let mut iter = punctuated.into_iter();
- let first = iter.next().unwrap();
- Ok(Self {
- first,
- rest: iter.collect(),
- })
- }
- }
- impl<T> Parse for SingleOrDouble<T>
- where
- T: Parse,
- {
- fn parse(s: ParseStream) -> syn::Result<Self> {
- let first = T::parse(s)?;
- let fork = s.fork();
- Ok(match T::parse(&fork) {
- Ok(second) => {
- s.advance_to(&fork);
- SingleOrDouble::Double {
- vert: first,
- horiz: second,
- }
- }
- Err(_) => SingleOrDouble::Single(first),
- })
- }
- }
- /// Either a float or an int, converted in either case to f64.
- ///
- /// A trailing percent (`%`) character will be consumed if the number has no suffix. This is valid
- /// according to the CSS tokeniser spec.
- ///
- /// TODO This only works for floats for now. Although JS only supports floats, integer literals are
- /// used in css.
- #[derive(Debug)]
- struct Number {
- value: f64,
- suffix: String,
- span: Span,
- }
- impl Number {
- fn empty_suffix(&self) -> syn::Result<()> {
- if self.suffix != "" {
- Err(syn::Error::new(
- self.span,
- "unexpected characters after number",
- ))
- } else {
- Ok(())
- }
- }
- #[cfg(test)]
- fn check_value(&self, value: f64, suffix: &str) -> bool {
- self.value == value && self.suffix == suffix
- }
- }
- impl Parse for Number {
- fn parse(s: ParseStream) -> syn::Result<Number> {
- let lookahead = s.lookahead1();
- let (value, mut span, mut suffix) = if lookahead.peek(syn::LitFloat) {
- let tok = s.parse::<syn::LitFloat>()?;
- let num = tok.base10_parse()?;
- (num, tok.span(), tok.suffix().to_string())
- } else if lookahead.peek(syn::LitInt) {
- let tok = s.parse::<syn::LitInt>()?;
- // u32 chosen because it can be safely converted into f64
- let num = tok.base10_parse::<u32>()?;
- (num.into(), tok.span(), tok.suffix().to_string())
- } else {
- return Err(lookahead.error());
- };
- if suffix.is_empty() {
- // look for a `%` for the suffix
- if s.peek(Token![%]) {
- let tok = s.parse::<Token![%]>()?;
- if let Some(extra_span) = span.join(tok.span) {
- span = extra_span;
- }
- suffix.push('%');
- // work-around using literal strings because the lexer can't support suffixes beginning
- // with `e` for floats: https://github.com/rust-lang/rust/issues/67544
- } else if s.peek(syn::LitStr) {
- let tok = s.parse::<syn::LitStr>()?;
- if let Some(extra_span) = span.join(tok.span()) {
- span = extra_span;
- }
- suffix.push_str(&tok.value());
- }
- }
- Ok(Number {
- value,
- suffix,
- span,
- })
- }
- }
- #[test]
- fn test_number() {
- for (input, value, suffix) in vec![
- ("200", 200.0, ""),
- ("200.0", 200.0, ""),
- ("0", 0.0, ""),
- ("0in", 0.0, "in"),
- ] {
- assert!(syn::parse_str::<Number>(input)
- .unwrap()
- .check_value(value, suffix),)
- }
- }
- /// Something like `word-separated-hyphens`
- #[derive(Debug)]
- struct HyphenWord {
- pub span: Span,
- pub word: Option<String>,
- /// List of tried matches - for building error.
- tried: TryList,
- }
- impl HyphenWord {
- pub fn new(span: Span, word: String) -> Self {
- HyphenWord {
- span,
- word: Some(word),
- tried: TryList::new(),
- }
- }
- /// This allows HyphenWords to be empty. In this case the token cursor will not advance and the
- /// returned word will be blank.
- pub fn new_no_word(span: Span) -> Self {
- HyphenWord {
- span,
- word: None,
- tried: TryList::new(),
- }
- }
- pub fn try_match(&self, other: &str) -> bool {
- if Some(other) == self.word.as_ref().map(|s| s.as_str()) {
- true
- } else {
- self.tried.add_literal(other);
- false
- }
- }
- pub fn add_expected(&self, ty: &str) {
- self.tried.add(ty);
- }
- /// Panics if there were no calls to `try_match` before calling this function.
- pub fn error(&self) -> syn::Error {
- self.tried.to_error(self.span)
- }
- /// This is cheaper than peek-specific
- pub fn peek(s: ParseStream) -> bool {
- s.peek(Ident)
- }
- /// Peek the next HyphenWord without advancing the parser.
- pub fn peek_specific(s: ParseStream) -> Option<String> {
- let fork = s.fork();
- match HyphenWord::parse(&fork) {
- Ok(hw) => Some(hw.word.unwrap()),
- Err(_) => None,
- }
- }
- }
- impl Parse for HyphenWord {
- fn parse(s: ParseStream) -> syn::Result<Self> {
- let fork = s.fork();
- let first = match fork.call(Ident::parse_any) {
- Ok(v) => {
- s.advance_to(&fork);
- v
- }
- Err(_) => return Ok(HyphenWord::new_no_word(s.cursor().span())),
- };
- let mut word = first.to_string();
- let mut span = first.span();
- // This is potentially unbounded. Probably not be a problem but making a note anyway.
- while s.peek(Token![-]) {
- let hyphen = s.parse::<Token![-]>()?;
- if let Some(joined) = span.join(hyphen.span) {
- span = joined;
- }
- let part = s.call(Ident::parse_any)?;
- write!(word, "-{}", part).unwrap();
- if let Some(joined) = span.join(part.span()) {
- span = joined;
- }
- }
- Ok(HyphenWord::new(span, word))
- }
- }
- #[test]
- fn test_hyphen_word() {
- let word: HyphenWord = syn::parse_str("first-second-third").unwrap();
- assert_eq!(word.word, Some("first-second-third".to_string()));
- assert!(syn::parse_str::<HyphenWord>("first-second-").is_err());
- assert!(syn::parse_str::<HyphenWord>("a a").is_err());
- }
- /// Keeps track of a list of tokens that have been tried.
- #[derive(Debug)]
- pub struct TryList(RefCell<BTreeSet<String>>);
- impl TryList {
- pub fn new() -> Self {
- TryList(RefCell::new(BTreeSet::new()))
- }
- /// Same as add, but with quotes
- pub fn add_literal(&self, lit: &str) {
- self.add(format!("`{}`", lit));
- }
- pub fn add(&self, ty: impl Into<String>) {
- self.0.borrow_mut().insert(ty.into());
- }
- fn to_error(&self, span: Span) -> syn::Error {
- let tried = self.0.borrow();
- let mut iter = tried.iter();
- let start = iter.next().unwrap().to_owned();
- let list = iter.fold(start, |mut acc, itm| {
- write!(acc, ", {}", itm).unwrap();
- acc
- });
- let error_msg = format!("expected one of {}", list);
- syn::Error::new(span, error_msg)
- }
- }
- /// Whether we are at the end of a rule. Either the stream will be empty, or there will be a
- /// semi-colon.
- fn finished_rule(s: ParseStream) -> bool {
- s.is_empty() || s.peek(Token![;])
- }
- // Parsing integers
- #[derive(Debug, PartialEq)]
- struct Integer<T> {
- value: T,
- }
- impl<T> Integer<T> {
- fn into_inner(self) -> T {
- self.value
- }
- }
- impl<T> Parse for Integer<T>
- where
- T: str::FromStr + fmt::Display + PartialOrd<T>,
- <T as str::FromStr>::Err: fmt::Display,
- {
- fn parse(s: ParseStream) -> syn::Result<Self> {
- Ok(Integer {
- value: integer(s, ..)?,
- })
- }
- }
- /// Parse an integer, with an optional allowed range.
- fn integer<T, R>(s: ParseStream, range: R) -> syn::Result<T>
- where
- R: RangeBounds<T> + fmt::Debug,
- T: str::FromStr + fmt::Display + PartialOrd<T>,
- <T as str::FromStr>::Err: fmt::Display,
- {
- let fixed = s.parse::<syn::LitInt>()?;
- let span = fixed.span();
- if fixed.suffix().is_empty() {
- let fixed = fixed.base10_parse()?;
- if range.contains(&fixed) {
- Ok(fixed)
- } else {
- Err(syn::Error::new(
- span,
- format!(
- "expected a number in the range {:?}, found {}",
- range, fixed
- ),
- ))
- }
- } else {
- Err(syn::Error::new(span, "the number should not have a suffix"))
- }
- }
- #[test]
- fn test_parse_integer() {
- let x: Integer<u8> = syn::parse_str("123").unwrap();
- assert_eq!(x.into_inner(), 123);
- let x: syn::Result<Integer<u8>> = syn::parse_str("256");
- assert!(x.is_err());
- }
- // tests
- #[test]
- fn downstream_bug1() {
- let s: Styles = syn::parse_str(
- "display: flex;
- flex-direction: column;
- flex-grow: 1;
- flex-shrink: 0;",
- )
- .unwrap();
- assert_eq!(
- s.rules,
- vec![
- Style::Display(Display::Flex),
- Style::FlexDirection(FlexDirection::Column),
- Style::FlexGrow(1.0),
- Style::FlexShrink(0.0)
- ]
- )
- }
- #[test]
- #[ignore]
- fn inline_logic() {
- todo!()
- }
- #[cfg(test)]
- mod tests {
- use super::*;
- /// Use if you want to test that something parses, but not if it looks the same when
- /// stringified. Example "border: 1px" -> "border:1px" but we still might want to check that
- /// the former parses.
- fn parse(input: &str) -> Style {
- syn::parse_str(input).unwrap()
- }
- /// This function can be used to quickly write tests to check that a parse and a stringify are
- /// opposites.
- fn round_trip_style(input: &str) {
- assert_eq!(&parse(input).to_string(), input);
- }
- #[test]
- fn border_bottom_left_radius() {
- round_trip_style("border-bottom-left-radius:30% 3px");
- }
- #[test]
- fn border_bottom_right_radius() {
- round_trip_style("border-bottom-right-radius:0 0");
- }
- #[test]
- fn border_collapse() {
- round_trip_style("border-collapse:collapse");
- }
- #[test]
- fn border_width() {
- round_trip_style("border-width:1px");
- round_trip_style("border-width:0 2px 50pt 0");
- }
- }
|