1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
use cssparser::*;
use crate::properties::PropertyId;
use crate::vendor_prefix::VendorPrefix;
use crate::targets::Browsers;
use crate::prefixes::Feature;
use crate::error::ParserError;

#[derive(Debug, Clone, PartialEq)]
pub struct CustomProperty {
  pub name: String,
  pub value: String
}

impl CustomProperty {
  pub fn parse<'i, 't>(
    name: CowRcStr<'i>,
    input: &mut Parser<'i, 't>,
  ) -> Result<Self, ParseError<'i, ParserError<'i>>> {
    let value = parse_unknown_value(input)?;
    Ok(CustomProperty {
      name: name.as_ref().into(),
      value
    })
  }
}

#[derive(Debug, Clone, PartialEq)]
pub struct UnparsedProperty {
  pub property_id: PropertyId,
  pub value: String
}

impl UnparsedProperty {
  pub fn parse<'i, 't>(
    property_id: PropertyId,
    input: &mut Parser<'i, 't>
  ) -> Result<Self, ParseError<'i, ParserError<'i>>> {
    let value = parse_unknown_value(input)?;
    Ok(UnparsedProperty {
      property_id,
      value
    })
  }

  pub fn get_prefixed(&self, targets: Option<Browsers>, feature: Feature) -> UnparsedProperty {
    let mut clone = self.clone();
    if self.property_id.prefix().contains(VendorPrefix::None) {
      if let Some(targets) = targets {
        clone.property_id = clone.property_id.with_prefix(feature.prefixes_for(targets))
      }
    }
    clone
  }

  pub fn with_property_id(&self, property_id: PropertyId) -> UnparsedProperty {
    UnparsedProperty {
      property_id,
      value: self.value.clone()
    }
  }
}

fn parse_unknown_value<'i, 't>(input: &mut Parser<'i, 't>) -> Result<String, ParseError<'i, ParserError<'i>>> {
  input.parse_until_before(Delimiter::Bang | Delimiter::Semicolon, |input| {
    // Need at least one token
    let before_first = input.position();
    let first_is_whitespace = {
      let first = input.next_including_whitespace()?;
      matches!(first, Token::WhiteSpace(_))
    };

    let after_first = input.position();
    let mut has_two = false;
    loop {
      match input.next_including_whitespace_and_comments() {
        Ok(_) => {
          has_two = true;
        },
        Err(..) => {
          // If there is only one token, preserve it, even if it is whitespace.
          // e.g. `--foo: ;` is valid. 
          let mut slice = if !has_two || !first_is_whitespace {
            input.slice_from(before_first)
          } else {
            input.slice_from(after_first)
          };
          if has_two {
            slice = slice.trim_end();
          }
          return Ok(slice.into())
        },
      };
    }
  })
}