mirror of
https://github.com/servo/servo.git
synced 2025-06-08 16:43:28 +00:00
style: Allow to derive Parse/ToCss/SpecifiedValueInfo on bitflags
We keep getting this pattern of properties that have a set of joint and disjoint flags, and copy-pasting or writing the same parsing and serialization code in slightly different ways. container-type is one such type, and I think we should have a single way of dealing with this, thus implement deriving for various traits for bitflags, with an attribute that says which flags are single vs mixed. See docs and properties I ported. The remaining ones I left TODOs with, they are a bit trickier but can be ported with some care. Differential Revision: https://phabricator.services.mozilla.com/D142418
This commit is contained in:
parent
19a43aa7da
commit
f30837baf1
8 changed files with 222 additions and 312 deletions
|
@ -373,6 +373,11 @@ pub fn to_css_identifier(mut camel_case: &str) -> String {
|
||||||
result
|
result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Transforms foo-bar to FOO_BAR.
|
||||||
|
pub fn to_scream_case(css_case: &str) -> String {
|
||||||
|
css_case.to_uppercase().replace('-', "_")
|
||||||
|
}
|
||||||
|
|
||||||
/// Given "FooBar", returns "Foo" and sets `camel_case` to "Bar".
|
/// Given "FooBar", returns "Foo" and sets `camel_case` to "Bar".
|
||||||
fn split_camel_segment<'input>(camel_case: &mut &'input str) -> Option<&'input str> {
|
fn split_camel_segment<'input>(camel_case: &mut &'input str) -> Option<&'input str> {
|
||||||
let index = match camel_case.chars().next() {
|
let index = match camel_case.chars().next() {
|
||||||
|
|
|
@ -17,7 +17,6 @@ use crate::values::{CustomIdent, KeyframesName, TimelineName};
|
||||||
use crate::Atom;
|
use crate::Atom;
|
||||||
use cssparser::Parser;
|
use cssparser::Parser;
|
||||||
use num_traits::FromPrimitive;
|
use num_traits::FromPrimitive;
|
||||||
use selectors::parser::SelectorParseErrorKind;
|
|
||||||
use std::fmt::{self, Debug, Formatter, Write};
|
use std::fmt::{self, Debug, Formatter, Write};
|
||||||
use style_traits::{CssWriter, KeywordsCollectFn, ParseError};
|
use style_traits::{CssWriter, KeywordsCollectFn, ParseError};
|
||||||
use style_traits::{SpecifiedValueInfo, StyleParseErrorKind, ToCss};
|
use style_traits::{SpecifiedValueInfo, StyleParseErrorKind, ToCss};
|
||||||
|
@ -1213,9 +1212,8 @@ impl Parse for WillChange {
|
||||||
|
|
||||||
bitflags! {
|
bitflags! {
|
||||||
/// Values for the `touch-action` property.
|
/// Values for the `touch-action` property.
|
||||||
#[derive(MallocSizeOf, SpecifiedValueInfo, ToComputedValue, ToResolvedValue, ToShmem)]
|
#[derive(MallocSizeOf, SpecifiedValueInfo, ToComputedValue, ToCss, ToResolvedValue, ToShmem, Parse)]
|
||||||
/// These constants match Gecko's `NS_STYLE_TOUCH_ACTION_*` constants.
|
#[css(bitflags(single = "none,auto,manipulation", mixed = "pan-x,pan-y,pinch-zoom"))]
|
||||||
#[value_info(other_values = "auto,none,manipulation,pan-x,pan-y,pinch-zoom")]
|
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
pub struct TouchAction: u8 {
|
pub struct TouchAction: u8 {
|
||||||
/// `none` variant
|
/// `none` variant
|
||||||
|
@ -1241,82 +1239,9 @@ impl TouchAction {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ToCss for TouchAction {
|
|
||||||
fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
|
|
||||||
where
|
|
||||||
W: Write,
|
|
||||||
{
|
|
||||||
if self.contains(TouchAction::AUTO) {
|
|
||||||
return dest.write_str("auto");
|
|
||||||
}
|
|
||||||
if self.contains(TouchAction::NONE) {
|
|
||||||
return dest.write_str("none");
|
|
||||||
}
|
|
||||||
if self.contains(TouchAction::MANIPULATION) {
|
|
||||||
return dest.write_str("manipulation");
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut has_any = false;
|
|
||||||
macro_rules! maybe_write_value {
|
|
||||||
($ident:path => $str:expr) => {
|
|
||||||
if self.contains($ident) {
|
|
||||||
if has_any {
|
|
||||||
dest.write_str(" ")?;
|
|
||||||
}
|
|
||||||
has_any = true;
|
|
||||||
dest.write_str($str)?;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
maybe_write_value!(TouchAction::PAN_X => "pan-x");
|
|
||||||
maybe_write_value!(TouchAction::PAN_Y => "pan-y");
|
|
||||||
maybe_write_value!(TouchAction::PINCH_ZOOM => "pinch-zoom");
|
|
||||||
|
|
||||||
debug_assert!(has_any);
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Parse for TouchAction {
|
|
||||||
/// auto | none | [ pan-x || pan-y || pinch-zoom ] | manipulation
|
|
||||||
fn parse<'i, 't>(
|
|
||||||
_context: &ParserContext,
|
|
||||||
input: &mut Parser<'i, 't>,
|
|
||||||
) -> Result<TouchAction, ParseError<'i>> {
|
|
||||||
let mut result = TouchAction::empty();
|
|
||||||
while let Ok(name) = input.try_parse(|i| i.expect_ident_cloned()) {
|
|
||||||
let flag = match_ignore_ascii_case! { &name,
|
|
||||||
"pan-x" => Some(TouchAction::PAN_X),
|
|
||||||
"pan-y" => Some(TouchAction::PAN_Y),
|
|
||||||
"pinch-zoom" => Some(TouchAction::PINCH_ZOOM),
|
|
||||||
"none" if result.is_empty() => return Ok(TouchAction::NONE),
|
|
||||||
"manipulation" if result.is_empty() => return Ok(TouchAction::MANIPULATION),
|
|
||||||
"auto" if result.is_empty() => return Ok(TouchAction::AUTO),
|
|
||||||
_ => None
|
|
||||||
};
|
|
||||||
|
|
||||||
let flag = match flag {
|
|
||||||
Some(flag) if !result.contains(flag) => flag,
|
|
||||||
_ => {
|
|
||||||
return Err(
|
|
||||||
input.new_custom_error(SelectorParseErrorKind::UnexpectedIdent(name))
|
|
||||||
);
|
|
||||||
},
|
|
||||||
};
|
|
||||||
result.insert(flag);
|
|
||||||
}
|
|
||||||
|
|
||||||
if !result.is_empty() {
|
|
||||||
Ok(result)
|
|
||||||
} else {
|
|
||||||
Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bitflags! {
|
bitflags! {
|
||||||
#[derive(MallocSizeOf, SpecifiedValueInfo, ToComputedValue, ToResolvedValue, ToShmem)]
|
#[derive(MallocSizeOf, Parse, SpecifiedValueInfo, ToComputedValue, ToCss, ToResolvedValue, ToShmem)]
|
||||||
#[value_info(other_values = "none,strict,content,size,layout,paint")]
|
#[css(bitflags(single = "none,strict,content", mixed="size,layout,paint"))]
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
/// Constants for contain: https://drafts.csswg.org/css-contain/#contain-property
|
/// Constants for contain: https://drafts.csswg.org/css-contain/#contain-property
|
||||||
pub struct Contain: u8 {
|
pub struct Contain: u8 {
|
||||||
|
@ -1329,90 +1254,13 @@ bitflags! {
|
||||||
/// `paint` variant, turns on paint containment
|
/// `paint` variant, turns on paint containment
|
||||||
const PAINT = 1 << 2;
|
const PAINT = 1 << 2;
|
||||||
/// `strict` variant, turns on all types of containment
|
/// `strict` variant, turns on all types of containment
|
||||||
const STRICT = 1 << 3;
|
const STRICT = 1 << 3 | Contain::LAYOUT.bits | Contain::PAINT.bits | Contain::SIZE.bits;
|
||||||
/// 'content' variant, turns on layout and paint containment
|
/// 'content' variant, turns on layout and paint containment
|
||||||
const CONTENT = 1 << 4;
|
const CONTENT = 1 << 4 | Contain::LAYOUT.bits | Contain::PAINT.bits;
|
||||||
/// variant with all the bits that contain: strict turns on
|
|
||||||
const STRICT_BITS = Contain::LAYOUT.bits | Contain::PAINT.bits | Contain::SIZE.bits;
|
|
||||||
/// variant with all the bits that contain: content turns on
|
|
||||||
const CONTENT_BITS = Contain::LAYOUT.bits | Contain::PAINT.bits;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ToCss for Contain {
|
/// https://drafts.csswg.org/css-contain-2/#content-visibility
|
||||||
fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
|
|
||||||
where
|
|
||||||
W: Write,
|
|
||||||
{
|
|
||||||
if self.is_empty() {
|
|
||||||
return dest.write_str("none");
|
|
||||||
}
|
|
||||||
if self.contains(Contain::STRICT) {
|
|
||||||
return dest.write_str("strict");
|
|
||||||
}
|
|
||||||
if self.contains(Contain::CONTENT) {
|
|
||||||
return dest.write_str("content");
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut has_any = false;
|
|
||||||
macro_rules! maybe_write_value {
|
|
||||||
($ident:path => $str:expr) => {
|
|
||||||
if self.contains($ident) {
|
|
||||||
if has_any {
|
|
||||||
dest.write_str(" ")?;
|
|
||||||
}
|
|
||||||
has_any = true;
|
|
||||||
dest.write_str($str)?;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
maybe_write_value!(Contain::SIZE => "size");
|
|
||||||
maybe_write_value!(Contain::LAYOUT => "layout");
|
|
||||||
maybe_write_value!(Contain::PAINT => "paint");
|
|
||||||
|
|
||||||
debug_assert!(has_any);
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Parse for Contain {
|
|
||||||
/// none | strict | content | [ size || layout || paint ]
|
|
||||||
fn parse<'i, 't>(
|
|
||||||
_context: &ParserContext,
|
|
||||||
input: &mut Parser<'i, 't>,
|
|
||||||
) -> Result<Contain, ParseError<'i>> {
|
|
||||||
let mut result = Contain::empty();
|
|
||||||
while let Ok(name) = input.try_parse(|i| i.expect_ident_cloned()) {
|
|
||||||
let flag = match_ignore_ascii_case! { &name,
|
|
||||||
"size" => Some(Contain::SIZE),
|
|
||||||
"layout" => Some(Contain::LAYOUT),
|
|
||||||
"paint" => Some(Contain::PAINT),
|
|
||||||
"strict" if result.is_empty() => return Ok(Contain::STRICT | Contain::STRICT_BITS),
|
|
||||||
"content" if result.is_empty() => return Ok(Contain::CONTENT | Contain::CONTENT_BITS),
|
|
||||||
"none" if result.is_empty() => return Ok(result),
|
|
||||||
_ => None
|
|
||||||
};
|
|
||||||
|
|
||||||
let flag = match flag {
|
|
||||||
Some(flag) if !result.contains(flag) => flag,
|
|
||||||
_ => {
|
|
||||||
return Err(
|
|
||||||
input.new_custom_error(SelectorParseErrorKind::UnexpectedIdent(name))
|
|
||||||
);
|
|
||||||
},
|
|
||||||
};
|
|
||||||
result.insert(flag);
|
|
||||||
}
|
|
||||||
|
|
||||||
if !result.is_empty() {
|
|
||||||
Ok(result)
|
|
||||||
} else {
|
|
||||||
Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[allow(missing_docs)]
|
|
||||||
#[cfg_attr(feature = "servo", derive(Deserialize, Serialize))]
|
#[cfg_attr(feature = "servo", derive(Deserialize, Serialize))]
|
||||||
#[derive(
|
#[derive(
|
||||||
Clone,
|
Clone,
|
||||||
|
@ -2101,9 +1949,9 @@ impl Overflow {
|
||||||
}
|
}
|
||||||
|
|
||||||
bitflags! {
|
bitflags! {
|
||||||
#[derive(MallocSizeOf, SpecifiedValueInfo, ToComputedValue, ToResolvedValue, ToShmem)]
|
#[derive(MallocSizeOf, SpecifiedValueInfo, ToCss, ToComputedValue, ToResolvedValue, ToShmem, Parse)]
|
||||||
#[value_info(other_values = "auto,stable,both-edges")]
|
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
|
#[css(bitflags(single = "auto", mixed = "stable,both-edges", validate_mixed="Self::has_stable"))]
|
||||||
/// Values for scrollbar-gutter:
|
/// Values for scrollbar-gutter:
|
||||||
/// <https://drafts.csswg.org/css-overflow-3/#scrollbar-gutter-property>
|
/// <https://drafts.csswg.org/css-overflow-3/#scrollbar-gutter-property>
|
||||||
pub struct ScrollbarGutter: u8 {
|
pub struct ScrollbarGutter: u8 {
|
||||||
|
@ -2116,56 +1964,9 @@ bitflags! {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ToCss for ScrollbarGutter {
|
impl ScrollbarGutter {
|
||||||
fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
|
#[inline]
|
||||||
where
|
fn has_stable(self) -> bool {
|
||||||
W: Write,
|
self.intersects(Self::STABLE)
|
||||||
{
|
|
||||||
if self.is_empty() {
|
|
||||||
return dest.write_str("auto");
|
|
||||||
}
|
|
||||||
|
|
||||||
debug_assert!(
|
|
||||||
self.contains(ScrollbarGutter::STABLE),
|
|
||||||
"We failed to parse the syntax!"
|
|
||||||
);
|
|
||||||
dest.write_str("stable")?;
|
|
||||||
if self.contains(ScrollbarGutter::BOTH_EDGES) {
|
|
||||||
dest.write_str(" both-edges")?;
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Parse for ScrollbarGutter {
|
|
||||||
/// auto | stable && both-edges?
|
|
||||||
fn parse<'i, 't>(
|
|
||||||
_context: &ParserContext,
|
|
||||||
input: &mut Parser<'i, 't>,
|
|
||||||
) -> Result<ScrollbarGutter, ParseError<'i>> {
|
|
||||||
if input.try_parse(|i| i.expect_ident_matching("auto")).is_ok() {
|
|
||||||
return Ok(ScrollbarGutter::AUTO);
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut result = ScrollbarGutter::empty();
|
|
||||||
while let Ok(ident) = input.try_parse(|i| i.expect_ident_cloned()) {
|
|
||||||
let flag = match_ignore_ascii_case! { &ident,
|
|
||||||
"stable" => Some(ScrollbarGutter::STABLE),
|
|
||||||
"both-edges" => Some(ScrollbarGutter::BOTH_EDGES),
|
|
||||||
_ => None
|
|
||||||
};
|
|
||||||
|
|
||||||
match flag {
|
|
||||||
Some(flag) if !result.contains(flag) => result.insert(flag),
|
|
||||||
_ => return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if result.contains(ScrollbarGutter::STABLE) {
|
|
||||||
Ok(result)
|
|
||||||
} else {
|
|
||||||
Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -556,6 +556,7 @@ impl From<MasonryAutoFlow> for u8 {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: Can be derived with some care.
|
||||||
impl Parse for GridAutoFlow {
|
impl Parse for GridAutoFlow {
|
||||||
/// [ row | column ] || dense
|
/// [ row | column ] || dense
|
||||||
fn parse<'i, 't>(
|
fn parse<'i, 't>(
|
||||||
|
|
|
@ -232,8 +232,8 @@ impl ToComputedValue for TextOverflow {
|
||||||
}
|
}
|
||||||
|
|
||||||
bitflags! {
|
bitflags! {
|
||||||
#[derive(MallocSizeOf, Serialize, SpecifiedValueInfo, ToComputedValue, ToResolvedValue, ToShmem)]
|
#[derive(MallocSizeOf, Parse, Serialize, SpecifiedValueInfo, ToCss, ToComputedValue, ToResolvedValue, ToShmem)]
|
||||||
#[value_info(other_values = "none,underline,overline,line-through,blink")]
|
#[css(bitflags(single = "none", mixed = "underline,overline,line-through,blink"))]
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
/// Specified keyword values for the text-decoration-line property.
|
/// Specified keyword values for the text-decoration-line property.
|
||||||
pub struct TextDecorationLine: u8 {
|
pub struct TextDecorationLine: u8 {
|
||||||
|
@ -265,94 +265,6 @@ impl Default for TextDecorationLine {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Parse for TextDecorationLine {
|
|
||||||
/// none | [ underline || overline || line-through || blink ]
|
|
||||||
fn parse<'i, 't>(
|
|
||||||
_context: &ParserContext,
|
|
||||||
input: &mut Parser<'i, 't>,
|
|
||||||
) -> Result<Self, ParseError<'i>> {
|
|
||||||
let mut result = TextDecorationLine::empty();
|
|
||||||
|
|
||||||
// NOTE(emilio): this loop has this weird structure because we run this
|
|
||||||
// code to parse the text-decoration shorthand as well, so we need to
|
|
||||||
// ensure we don't return an error if we don't consume the whole thing
|
|
||||||
// because we find an invalid identifier or other kind of token.
|
|
||||||
loop {
|
|
||||||
let flag: Result<_, ParseError<'i>> = input.try_parse(|input| {
|
|
||||||
let flag = try_match_ident_ignore_ascii_case! { input,
|
|
||||||
"none" if result.is_empty() => TextDecorationLine::NONE,
|
|
||||||
"underline" => TextDecorationLine::UNDERLINE,
|
|
||||||
"overline" => TextDecorationLine::OVERLINE,
|
|
||||||
"line-through" => TextDecorationLine::LINE_THROUGH,
|
|
||||||
"blink" => TextDecorationLine::BLINK,
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(flag)
|
|
||||||
});
|
|
||||||
|
|
||||||
let flag = match flag {
|
|
||||||
Ok(flag) => flag,
|
|
||||||
Err(..) => break,
|
|
||||||
};
|
|
||||||
|
|
||||||
if flag.is_empty() {
|
|
||||||
return Ok(TextDecorationLine::NONE);
|
|
||||||
}
|
|
||||||
|
|
||||||
if result.contains(flag) {
|
|
||||||
return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError));
|
|
||||||
}
|
|
||||||
|
|
||||||
result.insert(flag)
|
|
||||||
}
|
|
||||||
|
|
||||||
if !result.is_empty() {
|
|
||||||
Ok(result)
|
|
||||||
} else {
|
|
||||||
Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ToCss for TextDecorationLine {
|
|
||||||
fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
|
|
||||||
where
|
|
||||||
W: Write,
|
|
||||||
{
|
|
||||||
if self.is_empty() {
|
|
||||||
return dest.write_str("none");
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "gecko")]
|
|
||||||
{
|
|
||||||
if *self == TextDecorationLine::COLOR_OVERRIDE {
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut writer = SequenceWriter::new(dest, " ");
|
|
||||||
let mut any = false;
|
|
||||||
|
|
||||||
macro_rules! maybe_write {
|
|
||||||
($ident:ident => $str:expr) => {
|
|
||||||
if self.contains(TextDecorationLine::$ident) {
|
|
||||||
any = true;
|
|
||||||
writer.raw_item($str)?;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
maybe_write!(UNDERLINE => "underline");
|
|
||||||
maybe_write!(OVERLINE => "overline");
|
|
||||||
maybe_write!(LINE_THROUGH => "line-through");
|
|
||||||
maybe_write!(BLINK => "blink");
|
|
||||||
|
|
||||||
debug_assert!(any);
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl TextDecorationLine {
|
impl TextDecorationLine {
|
||||||
#[inline]
|
#[inline]
|
||||||
/// Returns the initial value of text-decoration-line
|
/// Returns the initial value of text-decoration-line
|
||||||
|
@ -399,6 +311,7 @@ impl TextTransform {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: This can be simplified by deriving it.
|
||||||
impl Parse for TextTransform {
|
impl Parse for TextTransform {
|
||||||
fn parse<'i, 't>(
|
fn parse<'i, 't>(
|
||||||
_context: &ParserContext,
|
_context: &ParserContext,
|
||||||
|
@ -1194,6 +1107,7 @@ bitflags! {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: This can be derived with some care.
|
||||||
impl Parse for TextUnderlinePosition {
|
impl Parse for TextUnderlinePosition {
|
||||||
fn parse<'i, 't>(
|
fn parse<'i, 't>(
|
||||||
_context: &ParserContext,
|
_context: &ParserContext,
|
||||||
|
|
|
@ -2,14 +2,14 @@
|
||||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
use crate::to_css::CssVariantAttrs;
|
use crate::to_css::{CssBitflagAttrs, CssVariantAttrs};
|
||||||
use darling::FromField;
|
use darling::FromField;
|
||||||
use darling::FromVariant;
|
use darling::FromVariant;
|
||||||
use derive_common::cg;
|
use derive_common::cg;
|
||||||
use proc_macro2::TokenStream;
|
use proc_macro2::{TokenStream, Span};
|
||||||
use quote::quote;
|
use quote::{quote, TokenStreamExt};
|
||||||
use syn::parse_quote;
|
use syn::parse_quote;
|
||||||
use syn::{self, DeriveInput, Path};
|
use syn::{self, Ident, DeriveInput, Path};
|
||||||
use synstructure::{Structure, VariantInfo};
|
use synstructure::{Structure, VariantInfo};
|
||||||
|
|
||||||
#[derive(Default, FromVariant)]
|
#[derive(Default, FromVariant)]
|
||||||
|
@ -25,6 +25,70 @@ pub struct ParseFieldAttrs {
|
||||||
field_bound: bool,
|
field_bound: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn parse_bitflags(bitflags: &CssBitflagAttrs) -> TokenStream {
|
||||||
|
let mut match_arms = TokenStream::new();
|
||||||
|
for (rust_name, css_name) in bitflags.single_flags() {
|
||||||
|
let rust_ident = Ident::new(&rust_name, Span::call_site());
|
||||||
|
match_arms.append_all(quote! {
|
||||||
|
#css_name if result.is_empty() => {
|
||||||
|
single_flag = true;
|
||||||
|
Self::#rust_ident
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
for (rust_name, css_name) in bitflags.mixed_flags() {
|
||||||
|
let rust_ident = Ident::new(&rust_name, Span::call_site());
|
||||||
|
match_arms.append_all(quote! {
|
||||||
|
#css_name => Self::#rust_ident,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut validate_condition = quote! { !result.is_empty() };
|
||||||
|
if let Some(ref function) = bitflags.validate_mixed {
|
||||||
|
validate_condition.append_all(quote! {
|
||||||
|
&& #function(result)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// NOTE(emilio): this loop has this weird structure because we run this code
|
||||||
|
// to parse stuff like text-decoration-line in the text-decoration
|
||||||
|
// shorthand, so we need to be a bit careful that we don't error if we don't
|
||||||
|
// consume the whole thing because we find an invalid identifier or other
|
||||||
|
// kind of token. Instead, we should leave it unconsumed.
|
||||||
|
quote! {
|
||||||
|
let mut result = Self::empty();
|
||||||
|
loop {
|
||||||
|
let mut single_flag = false;
|
||||||
|
let flag: Result<_, style_traits::ParseError<'i>> = input.try_parse(|input| {
|
||||||
|
Ok(try_match_ident_ignore_ascii_case! { input,
|
||||||
|
#match_arms
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
let flag = match flag {
|
||||||
|
Ok(flag) => flag,
|
||||||
|
Err(..) => break,
|
||||||
|
};
|
||||||
|
|
||||||
|
if single_flag {
|
||||||
|
return Ok(flag);
|
||||||
|
}
|
||||||
|
|
||||||
|
if result.intersects(flag) {
|
||||||
|
return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError));
|
||||||
|
}
|
||||||
|
|
||||||
|
result.insert(flag);
|
||||||
|
}
|
||||||
|
if #validate_condition {
|
||||||
|
Ok(result)
|
||||||
|
} else {
|
||||||
|
Err(input.new_custom_error(style_traits::StyleParseErrorKind::UnspecifiedError))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn parse_non_keyword_variant(
|
fn parse_non_keyword_variant(
|
||||||
where_clause: &mut Option<syn::WhereClause>,
|
where_clause: &mut Option<syn::WhereClause>,
|
||||||
name: &syn::Ident,
|
name: &syn::Ident,
|
||||||
|
@ -46,6 +110,13 @@ fn parse_non_keyword_variant(
|
||||||
let binding_ast = &bindings[0].ast();
|
let binding_ast = &bindings[0].ast();
|
||||||
let ty = &binding_ast.ty;
|
let ty = &binding_ast.ty;
|
||||||
|
|
||||||
|
if let Some(ref bitflags) = variant_attrs.bitflags {
|
||||||
|
assert!(skip_try, "Should be the only variant");
|
||||||
|
assert!(parse_attrs.condition.is_none(), "Should be the only variant");
|
||||||
|
assert!(where_clause.is_none(), "Generic bitflags?");
|
||||||
|
return parse_bitflags(bitflags)
|
||||||
|
}
|
||||||
|
|
||||||
let field_attrs = cg::parse_field_attrs::<ParseFieldAttrs>(binding_ast);
|
let field_attrs = cg::parse_field_attrs::<ParseFieldAttrs>(binding_ast);
|
||||||
if field_attrs.field_bound {
|
if field_attrs.field_bound {
|
||||||
cg::add_predicate(where_clause, parse_quote!(#ty: crate::parser::Parse));
|
cg::add_predicate(where_clause, parse_quote!(#ty: crate::parser::Parse));
|
||||||
|
|
|
@ -71,7 +71,14 @@ pub fn derive(mut input: DeriveInput) -> TokenStream {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
Data::Struct(ref s) => {
|
Data::Struct(ref s) => {
|
||||||
if !derive_struct_fields(&s.fields, &mut types, &mut values) {
|
if let Some(ref bitflags) = css_attrs.bitflags {
|
||||||
|
for (_rust_name, css_name) in bitflags.single_flags() {
|
||||||
|
values.push(css_name)
|
||||||
|
}
|
||||||
|
for (_rust_name, css_name) in bitflags.mixed_flags() {
|
||||||
|
values.push(css_name)
|
||||||
|
}
|
||||||
|
} else if !derive_struct_fields(&s.fields, &mut types, &mut values) {
|
||||||
values.push(input_name());
|
values.push(input_name());
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
@ -7,13 +7,64 @@ use darling::FromDeriveInput;
|
||||||
use darling::FromField;
|
use darling::FromField;
|
||||||
use darling::FromVariant;
|
use darling::FromVariant;
|
||||||
use derive_common::cg;
|
use derive_common::cg;
|
||||||
use proc_macro2::TokenStream;
|
use proc_macro2::{Span, TokenStream};
|
||||||
use quote::quote;
|
use quote::quote;
|
||||||
use quote::{ToTokens, TokenStreamExt};
|
use quote::{ToTokens, TokenStreamExt};
|
||||||
use syn::parse_quote;
|
use syn::parse_quote;
|
||||||
use syn::{self, Data, Path, WhereClause};
|
use syn::{self, Data, Ident, Path, WhereClause};
|
||||||
use synstructure::{BindingInfo, Structure, VariantInfo};
|
use synstructure::{BindingInfo, Structure, VariantInfo};
|
||||||
|
|
||||||
|
fn derive_bitflags(input: &syn::DeriveInput, bitflags: &CssBitflagAttrs) -> TokenStream {
|
||||||
|
let name = &input.ident;
|
||||||
|
let mut body = TokenStream::new();
|
||||||
|
for (rust_name, css_name) in bitflags.single_flags() {
|
||||||
|
let rust_ident = Ident::new(&rust_name, Span::call_site());
|
||||||
|
body.append_all(quote! {
|
||||||
|
if *self == Self::#rust_ident {
|
||||||
|
return dest.write_str(#css_name);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
body.append_all(quote! {
|
||||||
|
let mut has_any = false;
|
||||||
|
});
|
||||||
|
|
||||||
|
for (rust_name, css_name) in bitflags.mixed_flags() {
|
||||||
|
let rust_ident = Ident::new(&rust_name, Span::call_site());
|
||||||
|
body.append_all(quote! {
|
||||||
|
if self.intersects(Self::#rust_ident) {
|
||||||
|
if has_any {
|
||||||
|
dest.write_char(' ')?;
|
||||||
|
}
|
||||||
|
has_any = true;
|
||||||
|
dest.write_str(#css_name)?;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
body.append_all(quote! {
|
||||||
|
debug_assert!(has_any, "Shouldn't have parsed empty");
|
||||||
|
Ok(())
|
||||||
|
});
|
||||||
|
|
||||||
|
quote! {
|
||||||
|
impl style_traits::ToCss for #name {
|
||||||
|
#[allow(unused_variables)]
|
||||||
|
#[inline]
|
||||||
|
fn to_css<W>(
|
||||||
|
&self,
|
||||||
|
dest: &mut style_traits::CssWriter<W>,
|
||||||
|
) -> std::fmt::Result
|
||||||
|
where
|
||||||
|
W: std::fmt::Write,
|
||||||
|
{
|
||||||
|
#body
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn derive(mut input: syn::DeriveInput) -> TokenStream {
|
pub fn derive(mut input: syn::DeriveInput) -> TokenStream {
|
||||||
let mut where_clause = input.generics.where_clause.take();
|
let mut where_clause = input.generics.where_clause.take();
|
||||||
for param in input.generics.type_params() {
|
for param in input.generics.type_params() {
|
||||||
|
@ -21,12 +72,21 @@ pub fn derive(mut input: syn::DeriveInput) -> TokenStream {
|
||||||
}
|
}
|
||||||
|
|
||||||
let input_attrs = cg::parse_input_attrs::<CssInputAttrs>(&input);
|
let input_attrs = cg::parse_input_attrs::<CssInputAttrs>(&input);
|
||||||
if let Data::Enum(_) = input.data {
|
if matches!(input.data, Data::Enum(..)) || input_attrs.bitflags.is_some() {
|
||||||
assert!(
|
assert!(
|
||||||
input_attrs.function.is_none(),
|
input_attrs.function.is_none(),
|
||||||
"#[css(function)] is not allowed on enums"
|
"#[css(function)] is not allowed on enums or bitflags"
|
||||||
);
|
);
|
||||||
assert!(!input_attrs.comma, "#[css(comma)] is not allowed on enums");
|
assert!(
|
||||||
|
!input_attrs.comma,
|
||||||
|
"#[css(comma)] is not allowed on enums or bitflags"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(ref bitflags) = input_attrs.bitflags {
|
||||||
|
assert!(!input_attrs.derive_debug, "Bitflags can derive debug on their own");
|
||||||
|
assert!(where_clause.is_none(), "Generic bitflags?");
|
||||||
|
return derive_bitflags(&input, bitflags);
|
||||||
}
|
}
|
||||||
|
|
||||||
let match_body = {
|
let match_body = {
|
||||||
|
@ -249,6 +309,32 @@ fn derive_single_field_expr(
|
||||||
expr
|
expr
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Default, FromMeta)]
|
||||||
|
pub struct CssBitflagAttrs {
|
||||||
|
/// Flags that can only go on their own, comma-separated.
|
||||||
|
pub single: String,
|
||||||
|
/// Flags that can go mixed with each other, comma-separated.
|
||||||
|
pub mixed: String,
|
||||||
|
/// Extra validation of the resulting mixed flags.
|
||||||
|
#[darling(default)]
|
||||||
|
pub validate_mixed: Option<Path>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CssBitflagAttrs {
|
||||||
|
/// Returns a vector of (rust_name, css_name) of a given flag list.
|
||||||
|
fn names(s: &str) -> Vec<(String, String)> {
|
||||||
|
s.split(',').map(|css_name| (cg::to_scream_case(css_name), css_name.to_owned())).collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn single_flags(&self) -> Vec<(String, String)> {
|
||||||
|
Self::names(&self.single)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn mixed_flags(&self) -> Vec<(String, String)> {
|
||||||
|
Self::names(&self.mixed)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Default, FromDeriveInput)]
|
#[derive(Default, FromDeriveInput)]
|
||||||
#[darling(attributes(css), default)]
|
#[darling(attributes(css), default)]
|
||||||
pub struct CssInputAttrs {
|
pub struct CssInputAttrs {
|
||||||
|
@ -257,6 +343,7 @@ pub struct CssInputAttrs {
|
||||||
pub function: Option<Override<String>>,
|
pub function: Option<Override<String>>,
|
||||||
// Here because structs variants are also their whole type definition.
|
// Here because structs variants are also their whole type definition.
|
||||||
pub comma: bool,
|
pub comma: bool,
|
||||||
|
pub bitflags: Option<CssBitflagAttrs>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default, FromVariant)]
|
#[derive(Default, FromVariant)]
|
||||||
|
@ -266,6 +353,7 @@ pub struct CssVariantAttrs {
|
||||||
// Here because structs variants are also their whole type definition.
|
// Here because structs variants are also their whole type definition.
|
||||||
pub derive_debug: bool,
|
pub derive_debug: bool,
|
||||||
pub comma: bool,
|
pub comma: bool,
|
||||||
|
pub bitflags: Option<CssBitflagAttrs>,
|
||||||
pub dimension: bool,
|
pub dimension: bool,
|
||||||
pub keyword: Option<String>,
|
pub keyword: Option<String>,
|
||||||
pub skip: bool,
|
pub skip: bool,
|
||||||
|
|
|
@ -44,6 +44,29 @@ use std::fmt::{self, Write};
|
||||||
/// * `#[css(represents_keyword)]` can be used on bool fields in order to
|
/// * `#[css(represents_keyword)]` can be used on bool fields in order to
|
||||||
/// serialize the field name if the field is true, or nothing otherwise. It
|
/// serialize the field name if the field is true, or nothing otherwise. It
|
||||||
/// also collects those keywords for `SpecifiedValueInfo`.
|
/// also collects those keywords for `SpecifiedValueInfo`.
|
||||||
|
/// * `#[css(bitflags(single="", mixed="", validate="")]` can be used to derive
|
||||||
|
/// parse / serialize / etc on bitflags. The rules for parsing bitflags are
|
||||||
|
/// the following:
|
||||||
|
///
|
||||||
|
/// * `single` flags can only appear on their own. It's common that bitflags
|
||||||
|
/// properties at least have one such value like `none` or `auto`.
|
||||||
|
/// * `mixed` properties can appear mixed together, but not along any other
|
||||||
|
/// flag that shares a bit with itself. For example, if you have three
|
||||||
|
/// bitflags like:
|
||||||
|
///
|
||||||
|
/// FOO = 1 << 0;
|
||||||
|
/// BAR = 1 << 1;
|
||||||
|
/// BAZ = 1 << 2;
|
||||||
|
/// BAZZ = BAR | BAZ;
|
||||||
|
///
|
||||||
|
/// Then the following combinations won't be valid:
|
||||||
|
///
|
||||||
|
/// * foo foo: (every flag shares a bit with itself)
|
||||||
|
/// * bar bazz: (bazz shares a bit with bar)
|
||||||
|
///
|
||||||
|
/// But `bar baz` will be valid, as they don't share bits, and so would
|
||||||
|
/// `foo` with any other flag, or `bazz` on its own.
|
||||||
|
///
|
||||||
/// * finally, one can put `#[css(derive_debug)]` on the whole type, to
|
/// * finally, one can put `#[css(derive_debug)]` on the whole type, to
|
||||||
/// implement `Debug` by a single call to `ToCss::to_css`.
|
/// implement `Debug` by a single call to `ToCss::to_css`.
|
||||||
pub trait ToCss {
|
pub trait ToCss {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue