///|
/// Rules and at-rules.
///
/// Split from `write.mbt` only for size: it is the same printer, and the same
/// three modes.
///|
fn Printer::write_rule(self : Printer, r : @ast.CssRule) -> Unit {
match r {
Style(s) => {
self.write_selector_list(s.selectors[:])
self.write_body(s.body[:])
}
Media(m) => {
self.put("@media")
if m.queries.length() > 0 {
self.put(" ")
self.write_media_queries(m.queries[:])
}
self.write_body(m.body[:])
}
Supports(s) => {
self.put("@supports ")
self.write_condition(s.condition, top=true)
self.write_body(s.body[:])
}
Container(c) => {
self.put("@container")
match c.name {
Some(n) => {
self.put(" ")
self.put(n)
}
None => ()
}
self.put(" ")
self.write_condition(c.condition, top=true)
self.write_body(c.body[:])
}
Layer(l) => {
self.put("@layer")
if l.names.length() > 0 {
self.put(" ")
let mut first = true
for n in l.names {
if !first {
self.put(",")
self.sp()
}
first = false
self.write_layer_name(n)
}
}
match l.body {
Some(body) => self.write_body(body[:])
None => self.put(";")
}
}
Keyframes(k) => {
self.put("@keyframes ")
match k.name {
Ident(n) => self.put(n)
Str(s) => self.write_quoted(s)
}
self.write_keyframes_body(k.frames[:])
}
FontFace(b) => {
self.put("@font-face")
self.write_decl_body(b)
}
Page(p) => {
self.put("@page")
if p.selectors.length() > 0 {
self.put(" ")
let mut first = true
for s in p.selectors {
if !first {
self.put(",")
self.sp()
}
first = false
self.put(s)
}
}
self.write_body(p.body[:])
}
Property(p) => {
self.put("@property ")
self.put(p.name)
self.write_decl_body(p.decls)
}
Import(i) => {
self.put("@import ")
self.write_quoted(i.url)
match i.layer {
None => ()
Some(None) => self.put(" layer")
Some(Some(n)) => {
self.put(" layer(")
self.write_layer_name(n)
self.put(")")
}
}
match i.supports {
None => ()
Some(c) => {
self.put(" supports(")
self.write_condition(c, top=false)
self.put(")")
}
}
if i.media.length() > 0 {
self.put(" ")
self.write_media_queries(i.media[:])
}
self.put(";")
}
Charset(cs, _) => {
self.put("@charset ")
self.write_quoted(cs)
self.put(";")
}
Namespace(n) => {
self.put("@namespace ")
match n.prefix {
Some(p) => {
self.put(p)
self.put(" ")
}
None => ()
}
self.write_quoted(n.url)
self.put(";")
}
Scope(s) => {
self.put("@scope")
match s.start {
Some(sels) => {
self.put(" (")
self.write_selector_list(sels[:])
self.put(")")
}
None => ()
}
match s.end {
Some(sels) => {
self.put(" to (")
self.write_selector_list(sels[:])
self.put(")")
}
None => ()
}
self.write_body(s.body[:])
}
StartingStyle(body, _) => {
self.put("@starting-style")
self.write_body(body[:])
}
CounterStyle(name, b) => {
self.put("@counter-style ")
self.put(name)
self.write_decl_body(b)
}
Unknown(u) => {
self.put("@")
self.put(u.name)
if u.prelude.length() > 0 {
self.put(" ")
self.write_values(u.prelude[:])
}
match u.block {
Some(body) => self.write_body(body[:])
None => self.put(";")
}
}
Bogus(b) => self.put(b.text)
}
}
///|
fn Printer::write_layer_name(self : Printer, n : @ast.LayerName) -> Unit {
let mut first = true
for part in n.parts {
if !first {
self.put(".")
}
first = false
self.put(part)
}
}
///|
/// `@keyframes` bodies are not `BlockItem` lists -- every entry is a keyframe,
/// so they get their own loop rather than being forced through the general one.
fn Printer::write_keyframes_body(
self : Printer,
frames : ArrayView[@ast.KeyframeBlock],
) -> Unit {
self.sp()
self.put("{")
if frames.length() == 0 {
self.put("}")
return
}
self.depth = self.depth + 1
for f in frames {
self.nl()
let mut first = true
for s in f.selectors {
if !first {
self.put(",")
self.sp()
}
first = false
match s {
From => self.put("from")
To => self.put("to")
Percentage(n) => {
self.put(n.repr)
self.put("%")
}
}
}
self.write_decl_body(f.decls)
}
self.depth = self.depth - 1
self.nl()
self.put("}")
}
// ----------------------------------------------------------------- conditions
///|
fn Printer::write_media_queries(
self : Printer,
qs : ArrayView[@ast.MediaQuery],
) -> Unit {
let mut first = true
for q in qs {
if !first {
self.put(",")
self.sp()
}
first = false
self.write_media_query(q)
}
}
///|
fn Printer::write_media_query(self : Printer, q : @ast.MediaQuery) -> Unit {
let mut wrote = false
match q.qualifier {
Some(Only) => {
self.put("only")
wrote = true
}
Some(Not) => {
self.put("not")
wrote = true
}
None => ()
}
match q.media_type {
Some(t) => {
if wrote {
self.put(" ")
}
self.put(t)
wrote = true
}
None => ()
}
match q.condition {
Some(c) => {
if wrote {
self.put(" and ")
}
self.write_condition(c, top=true)
}
None => ()
}
}
///|
/// A condition.
///
/// `top` says whether this position already supplies parentheses. A feature
/// always writes its own, so `@media (width > 40rem)` is right; a nested
/// operand of `and`/`or` needs them only when it is itself an operation, which
/// is the one place the printer has to think.
fn Printer::write_condition(
self : Printer,
c : @ast.Condition,
top~ : Bool,
) -> Unit {
ignore(top)
match c {
Feature(f) => self.write_feature(f)
Decl(d) => {
self.put("(")
self.write_decl(d)
self.put(")")
}
SelectorFn(sels) => {
self.put("selector(")
self.write_selector_list(sels[:])
self.put(")")
}
Operation(Not, conds) => {
self.put("not ")
match conds {
[only] => self.write_operand(only)
_ =>
// `not` is unary; more than one operand is a malformed tree rather
// than a malformed stylesheet, so print them all and let the
// round-trip test catch it rather than silently dropping any.
for i, cc in conds {
if i > 0 {
self.put(" and ")
}
self.write_operand(cc)
}
}
}
Operation(op, conds) => {
let sep = match op {
And => " and "
Or => " or "
Not => " and "
}
let mut first = true
for cc in conds {
if !first {
self.put(sep)
}
first = false
self.write_operand(cc)
}
}
Unknown(vs) => {
self.put("(")
self.write_values(vs[:])
self.put(")")
}
Bogus(b) => self.put(b.text)
}
}
///|
/// An operand of `and`/`or`/`not`: parenthesised exactly when it is itself an
/// operation, because CSS forbids mixing operators without parentheses and a
/// nested one has to say where it begins.
fn Printer::write_operand(self : Printer, c : @ast.Condition) -> Unit {
match c {
Operation(_, _) => {
self.put("(")
self.write_condition(c, top=false)
self.put(")")
}
_ => self.write_condition(c, top=false)
}
}
///|
fn Printer::write_feature(self : Printer, f : @ast.Feature) -> Unit {
self.put("(")
match f {
Boolean(n) => self.put(n)
Plain(n, vs) => {
self.put(n)
self.put(":")
self.sp()
self.write_values(vs[:])
}
Range(n, op, vs) => {
self.put(n)
self.sp()
self.put(op.text())
self.sp()
self.write_values(vs[:])
}
Interval(lo, op1, n, op2, hi) => {
self.write_values(lo[:])
self.sp()
self.put(op1.text())
self.sp()
self.put(n)
self.sp()
self.put(op2.text())
self.sp()
self.write_values(hi[:])
}
}
self.put(")")
}