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}