rule.go 4.8 KB

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