rule.go

  1package css
  2
  3import (
  4	"fmt"
  5	"strings"
  6)
  7
  8const (
  9	indentSpace = 2
 10)
 11
 12// RuleKind represents a Rule kind
 13type RuleKind int
 14
 15// Rule kinds
 16const (
 17	QualifiedRule RuleKind = iota
 18	AtRule
 19)
 20
 21// At Rules than have Rules inside their block instead of Declarations
 22var atRulesWithRulesBlock = []string{
 23	"@document", "@font-feature-values", "@keyframes", "@media", "@supports",
 24}
 25
 26// Rule represents a parsed CSS rule
 27type Rule struct {
 28	Kind RuleKind
 29
 30	// At Rule name (eg: "@media")
 31	Name string
 32
 33	// Raw prelude
 34	Prelude string
 35
 36	// Qualified Rule selectors parsed from prelude
 37	Selectors []string
 38
 39	// Style properties
 40	Declarations []*Declaration
 41
 42	// At Rule embedded rules
 43	Rules []*Rule
 44
 45	// Current rule embedding level
 46	EmbedLevel int
 47}
 48
 49// NewRule instanciates a new Rule
 50func NewRule(kind RuleKind) *Rule {
 51	return &Rule{
 52		Kind: kind,
 53	}
 54}
 55
 56// Returns string representation of rule kind
 57func (kind RuleKind) String() string {
 58	switch kind {
 59	case QualifiedRule:
 60		return "Qualified Rule"
 61	case AtRule:
 62		return "At Rule"
 63	default:
 64		return "WAT"
 65	}
 66}
 67
 68// EmbedsRules returns true if this rule embeds another rules
 69func (rule *Rule) EmbedsRules() bool {
 70	if rule.Kind == AtRule {
 71		for _, atRuleName := range atRulesWithRulesBlock {
 72			if rule.Name == atRuleName {
 73				return true
 74			}
 75		}
 76	}
 77
 78	return false
 79}
 80
 81// Equal returns true if both rules are equals
 82func (rule *Rule) Equal(other *Rule) bool {
 83	if (rule.Kind != other.Kind) ||
 84		(rule.Prelude != other.Prelude) ||
 85		(rule.Name != other.Name) {
 86		return false
 87	}
 88
 89	if (len(rule.Selectors) != len(other.Selectors)) ||
 90		(len(rule.Declarations) != len(other.Declarations)) ||
 91		(len(rule.Rules) != len(other.Rules)) {
 92		return false
 93	}
 94
 95	for i, sel := range rule.Selectors {
 96		if sel != other.Selectors[i] {
 97			return false
 98		}
 99	}
100
101	for i, decl := range rule.Declarations {
102		if !decl.Equal(other.Declarations[i]) {
103			return false
104		}
105	}
106
107	for i, rule := range rule.Rules {
108		if !rule.Equal(other.Rules[i]) {
109			return false
110		}
111	}
112
113	return true
114}
115
116// Diff returns a string representation of rules differences
117func (rule *Rule) Diff(other *Rule) []string {
118	result := []string{}
119
120	if rule.Kind != other.Kind {
121		result = append(result, fmt.Sprintf("Kind: %s | %s", rule.Kind.String(), other.Kind.String()))
122	}
123
124	if rule.Prelude != other.Prelude {
125		result = append(result, fmt.Sprintf("Prelude: \"%s\" | \"%s\"", rule.Prelude, other.Prelude))
126	}
127
128	if rule.Name != other.Name {
129		result = append(result, fmt.Sprintf("Name: \"%s\" | \"%s\"", rule.Name, other.Name))
130	}
131
132	if len(rule.Selectors) != len(other.Selectors) {
133		result = append(result, fmt.Sprintf("Selectors: %v | %v", strings.Join(rule.Selectors, ", "), strings.Join(other.Selectors, ", ")))
134	} else {
135		for i, sel := range rule.Selectors {
136			if sel != other.Selectors[i] {
137				result = append(result, fmt.Sprintf("Selector: \"%s\" | \"%s\"", sel, other.Selectors[i]))
138			}
139		}
140	}
141
142	if len(rule.Declarations) != len(other.Declarations) {
143		result = append(result, fmt.Sprintf("Declarations Nb: %d | %d", len(rule.Declarations), len(other.Declarations)))
144	} else {
145		for i, decl := range rule.Declarations {
146			if !decl.Equal(other.Declarations[i]) {
147				result = append(result, fmt.Sprintf("Declaration: \"%s\" | \"%s\"", decl.String(), other.Declarations[i].String()))
148			}
149		}
150	}
151
152	if len(rule.Rules) != len(other.Rules) {
153		result = append(result, fmt.Sprintf("Rules Nb: %d | %d", len(rule.Rules), len(other.Rules)))
154	} else {
155
156		for i, rule := range rule.Rules {
157			if !rule.Equal(other.Rules[i]) {
158				result = append(result, fmt.Sprintf("Rule: \"%s\" | \"%s\"", rule.String(), other.Rules[i].String()))
159			}
160		}
161	}
162
163	return result
164}
165
166// Returns the string representation of a rule
167func (rule *Rule) String() string {
168	result := ""
169
170	if rule.Kind == QualifiedRule {
171		for i, sel := range rule.Selectors {
172			if i != 0 {
173				result += ", "
174			}
175			result += sel
176		}
177	} else {
178		// AtRule
179		result += fmt.Sprintf("%s", rule.Name)
180
181		if rule.Prelude != "" {
182			if result != "" {
183				result += " "
184			}
185			result += fmt.Sprintf("%s", rule.Prelude)
186		}
187	}
188
189	if (len(rule.Declarations) == 0) && (len(rule.Rules) == 0) {
190		result += ";"
191	} else {
192		result += " {\n"
193
194		if rule.EmbedsRules() {
195			for _, subRule := range rule.Rules {
196				result += fmt.Sprintf("%s%s\n", rule.indent(), subRule.String())
197			}
198		} else {
199			for _, decl := range rule.Declarations {
200				result += fmt.Sprintf("%s%s\n", rule.indent(), decl.String())
201			}
202		}
203
204		result += fmt.Sprintf("%s}", rule.indentEndBlock())
205	}
206
207	return result
208}
209
210// Returns identation spaces for declarations and rules
211func (rule *Rule) indent() string {
212	result := ""
213
214	for i := 0; i < ((rule.EmbedLevel + 1) * indentSpace); i++ {
215		result += " "
216	}
217
218	return result
219}
220
221// Returns identation spaces for end of block character
222func (rule *Rule) indentEndBlock() string {
223	result := ""
224
225	for i := 0; i < (rule.EmbedLevel * indentSpace); i++ {
226		result += " "
227	}
228
229	return result
230}