Skip to content

Commit ac13699

Browse files
authored
Merge pull request #159 from kaj/refactor-css-dest
Refactor css output.
2 parents bb2bac7 + 05660d4 commit ac13699

17 files changed

+649
-407
lines changed

Diff for: CHANGELOG.md

+3
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,9 @@ project adheres to
2323
* Moved the `SourcePos` type into the `input` module and converted it from
2424
keeping a copy of the relevant line to keeping a range with a (reference
2525
counted) `SourceFile` (PR #158).
26+
* Changed css creation from just writing to a text buffer to building
27+
a tree representation of the css (and serialize it to text as a
28+
final step) (PR #159).
2629
* Changed `css::Item::AtRule` to wrap the new type `css::AtRule`.
2730
* Changed handling of `hue` arguments to color functions, to allow
2831
different angle units, matching updates in sass-spec.

Diff for: src/css/atrule.rs

+28-11
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use super::{Comment, Import, Property, Rule};
1+
use super::{Comment, Import, Property, Rule, Value};
22
use crate::output::CssBuf;
33
use std::io::{self, Write};
44

@@ -9,28 +9,37 @@ use std::io::{self, Write};
99
#[derive(Clone, Debug)]
1010
pub struct AtRule {
1111
name: String,
12-
args: String, // TODO: More typed? Value? Or separate types for @media etc?
13-
body: Vec<AtRuleBodyItem>,
12+
args: Value,
13+
// Some<[]> outputs "{}", None outputs ";".
14+
body: Option<Vec<AtRuleBodyItem>>,
1415
}
1516

1617
impl AtRule {
1718
pub(crate) fn new(
1819
name: String,
19-
args: String,
20-
body: Vec<AtRuleBodyItem>,
20+
args: Value,
21+
body: Option<Vec<AtRuleBodyItem>>,
2122
) -> Self {
2223
AtRule { name, args, body }
2324
}
25+
pub(crate) fn no_body(&self) -> bool {
26+
self.body.is_none()
27+
}
2428
pub(crate) fn write(&self, buf: &mut CssBuf) -> io::Result<()> {
29+
buf.do_indent_no_nl();
2530
write!(buf, "@{}", self.name)?;
26-
if !self.args.is_empty() {
27-
write!(buf, " {}", self.args)?;
31+
if !self.args.is_null() {
32+
write!(buf, " {}", self.args.format(buf.format()))?;
2833
}
29-
buf.start_block();
30-
for item in &self.body {
31-
item.write(buf)?;
34+
if let Some(body) = &self.body {
35+
buf.start_block();
36+
for item in body {
37+
item.write(buf)?;
38+
}
39+
buf.end_block();
40+
} else {
41+
buf.add_one(";\n", ";");
3242
}
33-
buf.end_block();
3443
Ok(())
3544
}
3645
}
@@ -46,6 +55,8 @@ pub enum AtRuleBodyItem {
4655
Rule(Rule),
4756
/// A raw property.
4857
Property(Property),
58+
/// An `@` rule.
59+
AtRule(AtRule),
4960
}
5061

5162
impl AtRuleBodyItem {
@@ -55,6 +66,7 @@ impl AtRuleBodyItem {
5566
AtRuleBodyItem::Comment(comment) => comment.write(buf),
5667
AtRuleBodyItem::Rule(rule) => rule.write(buf)?,
5768
AtRuleBodyItem::Property(property) => property.write(buf),
69+
AtRuleBodyItem::AtRule(rule) => rule.write(buf)?,
5870
}
5971
Ok(())
6072
}
@@ -79,3 +91,8 @@ impl From<Property> for AtRuleBodyItem {
7991
AtRuleBodyItem::Property(rule)
8092
}
8193
}
94+
impl From<AtRule> for AtRuleBodyItem {
95+
fn from(rule: AtRule) -> Self {
96+
AtRuleBodyItem::AtRule(rule)
97+
}
98+
}

Diff for: src/css/item.rs

+3
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ pub enum Item {
1313
Rule(Rule),
1414
/// An `@` rule, e.g. `@media ... { ... }`
1515
AtRule(AtRule),
16+
/// An extra newline for grouping (unless compressed format).
17+
Separator,
1618
}
1719

1820
impl Item {
@@ -22,6 +24,7 @@ impl Item {
2224
Item::Import(import) => import.write(buf)?,
2325
Item::Rule(rule) => rule.write(buf)?,
2426
Item::AtRule(atrule) => atrule.write(buf)?,
27+
Item::Separator => buf.opt_nl(),
2528
}
2629
Ok(())
2730
}

Diff for: src/css/rule.rs

+16-10
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use super::{Comment, CssString, Import, Selectors, Value};
1+
use super::{AtRule, Comment, CssString, Import, Selectors, Value};
22
use crate::output::CssBuf;
33
use std::io::{self, Write};
44

@@ -55,6 +55,8 @@ pub enum BodyItem {
5555
CustomProperty(String, CssString),
5656
/// A comment
5757
Comment(Comment),
58+
/// Empty at-rules are allowed in a rule body.
59+
ARule(AtRule),
5860
}
5961

6062
impl BodyItem {
@@ -80,6 +82,7 @@ impl BodyItem {
8082
)?;
8183
buf.add_one(";\n", ";");
8284
}
85+
BodyItem::ARule(rule) => rule.write(buf)?,
8386
}
8487
Ok(())
8588
}
@@ -101,6 +104,18 @@ impl From<Property> for BodyItem {
101104
}
102105
}
103106

107+
impl TryFrom<AtRule> for BodyItem {
108+
type Error = AtRule;
109+
110+
fn try_from(value: AtRule) -> Result<Self, Self::Error> {
111+
if value.no_body() {
112+
Ok(BodyItem::ARule(value))
113+
} else {
114+
Err(value)
115+
}
116+
}
117+
}
118+
104119
/// A css property; a name and [Value].
105120
#[derive(Clone, Debug)]
106121
pub struct Property {
@@ -113,15 +128,6 @@ impl Property {
113128
pub fn new(name: String, value: Value) -> Self {
114129
Property { name, value }
115130
}
116-
/// Return this property but with a prefix.
117-
///
118-
/// The prefix is separated from the old name with a dash.
119-
pub fn prefix(self, prefix: &str) -> Self {
120-
Property {
121-
name: format!("{}-{}", prefix, self.name),
122-
value: self.value,
123-
}
124-
}
125131
pub(crate) fn write(&self, buf: &mut CssBuf) {
126132
buf.do_indent_no_nl();
127133
buf.add_str(&self.name);

Diff for: src/css/value.rs

+4
Original file line numberDiff line numberDiff line change
@@ -208,6 +208,10 @@ impl Value {
208208
format,
209209
}
210210
}
211+
/// Format this value for error messages.
212+
pub fn introspect(&self) -> Formatted<Value> {
213+
self.format(Format::introspect())
214+
}
211215
}
212216

213217
/// Some Values are equal according to spec even with some

Diff for: src/error.rs

+36
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,14 @@ pub enum Invalid {
156156
DuplicateArgument,
157157
/// Positional arguments must come before keyword arguments.
158158
PositionalArgAfterNamed,
159+
/// Declarations may only be used within style rules.
160+
DeclarationOutsideRule,
161+
/// Only properties are valid inside namespace rules.
162+
InNsRule,
163+
/// Global custom property not allowed.
164+
GlobalCustomProperty,
165+
/// Global namespaced property not allowed.
166+
GlobalNsProperty,
159167
/// Some invalid scope operation.
160168
InScope(ScopeError),
161169
/// An `@error` reached.
@@ -194,8 +202,36 @@ impl fmt::Display for Invalid {
194202
"Positional arguments must come before keyword arguments."
195203
.fmt(out)
196204
}
205+
Invalid::DeclarationOutsideRule => {
206+
"Declarations may only be used within style rules.".fmt(out)
207+
}
208+
Invalid::InNsRule => {
209+
"Only properties are valid inside namespace rules.".fmt(out)
210+
}
211+
Invalid::GlobalCustomProperty => {
212+
"Global custom property not allowed.".fmt(out)
213+
}
214+
Invalid::GlobalNsProperty => {
215+
"Global namespaced property not allowed.".fmt(out)
216+
}
197217
Invalid::InScope(err) => err.fmt(out),
198218
Invalid::AtError(msg) => msg.fmt(out),
199219
}
200220
}
201221
}
222+
223+
pub(crate) trait ResultPos<T> {
224+
/// Someting bad happened at a given source position.
225+
fn at(self, pos: &SourcePos) -> Result<T, Error>;
226+
/// Something bad happened, but we don't know the position.
227+
/// This is probably room for improvement in rsass error handling.
228+
fn no_pos(self) -> Result<T, Error>;
229+
}
230+
impl<T> ResultPos<T> for Result<T, Invalid> {
231+
fn at(self, pos: &SourcePos) -> Result<T, Error> {
232+
self.map_err(|e| e.at(pos.clone()))
233+
}
234+
fn no_pos(self) -> Result<T, Error> {
235+
self.map_err(|e| Error::error(e.to_string()))
236+
}
237+
}

Diff for: src/input/context.rs

+5-12
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use super::{
22
CargoLoader, FsLoader, LoadError, Loader, SourceFile, SourceKind,
33
};
4-
use crate::output::{handle_parsed, CssBuf, CssHead, Format};
4+
use crate::output::{handle_parsed, CssData, Format};
55
use crate::{Error, ScopeRef};
66
use std::{borrow::Cow, collections::BTreeMap, fmt, path::Path};
77
use tracing::instrument;
@@ -146,18 +146,11 @@ impl<AnyLoader: Loader> Context<AnyLoader> {
146146
.clone()
147147
.unwrap_or_else(|| ScopeRef::new_global(Default::default()));
148148
self.lock_loading(&file, false)?;
149-
let mut head = CssHead::new();
150-
let mut body = CssBuf::new(scope.get_format());
151-
handle_parsed(
152-
file.parse()?,
153-
&mut head,
154-
None,
155-
&mut body,
156-
scope,
157-
&mut self,
158-
)?;
149+
let mut css = CssData::new();
150+
let format = scope.get_format();
151+
handle_parsed(file.parse()?, &mut css, scope, &mut self)?;
159152
self.unlock_loading(&file);
160-
Ok(head.combine_final(body))
153+
css.into_buffer(format)
161154
}
162155

163156
/// Set the output format for this context.

0 commit comments

Comments
 (0)