rule.go (4951B)
1 package css 2 3 import ( 4 "fmt" 5 "strings" 6 ) 7 8 const ( 9 indentSpace = 2 10 ) 11 12 // RuleKind represents a Rule kind 13 type RuleKind int 14 15 // Rule kinds 16 const ( 17 QualifiedRule RuleKind = iota 18 AtRule 19 ) 20 21 // At Rules than have Rules inside their block instead of Declarations 22 var atRulesWithRulesBlock = []string{ 23 "@document", "@font-feature-values", "@keyframes", "@media", "@supports", 24 } 25 26 // Rule represents a parsed CSS rule 27 type 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 50 func NewRule(kind RuleKind) *Rule { 51 return &Rule{ 52 Kind: kind, 53 } 54 } 55 56 // Returns string representation of rule kind 57 func (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 69 func (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 82 func (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 117 func (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 167 func (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 211 func (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 222 func (rule *Rule) indentEndBlock() string { 223 result := "" 224 225 for i := 0; i < (rule.EmbedLevel * indentSpace); i++ { 226 result += " " 227 } 228 229 return result 230 }