-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Create and implement the specificity utility
- Loading branch information
Showing
5 changed files
with
185 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,63 @@ | ||
/** | ||
* Gets the points from a rule | ||
* @param {string} rule The rule to evaluate | ||
* @returns {number} The number of points of that CSS selector | ||
*/ | ||
const getRulePoints = (rule) => { | ||
const firstCharacter = rule.charAt(0); | ||
switch (firstCharacter) { | ||
case ".": | ||
return 10; | ||
case "#": | ||
return 100; | ||
|
||
default: | ||
return firstCharacter.match(/[a-z]/i) ? 1 : 0; | ||
} | ||
}; | ||
|
||
/** | ||
* Calculates the points of a CSS instruction | ||
* @param {string} instruction The CSS instruction to evaluate | ||
* @returns {number} The total points | ||
*/ | ||
const getSpecificityPoints = (instruction) => { | ||
const selectors = instruction.split(" "); | ||
const points = selectors.reduce( | ||
(prev, curr) => prev + getRulePoints(curr), | ||
0 | ||
); | ||
return points; | ||
}; | ||
|
||
/** | ||
* Evaluates multiple rules at once | ||
* @param {Array} rules All of the rules to evaluate | ||
* @param {boolean?} ascending If it will be ascending, it won't by default | ||
* @param {boolean?} with_scores Will it return the scores, it will by default | ||
* @returns {Array} The same array sorted | ||
*/ | ||
const evaluateRules = ({ rules, ascending = false, withScores = true }) => { | ||
const rulesWithScores = rules.map((rule) => [ | ||
rule, | ||
getSpecificityPoints(rule), | ||
]); | ||
|
||
let sortedRules = rulesWithScores.sort((a, b) => { | ||
if (a == b) return 0; | ||
|
||
// If the condition is true, it will be a one, if it's not, it will be converted to -1 | ||
let result = ascending ? Number(a < b) : Number(a > b); | ||
if (result == 0) result = -1; | ||
|
||
return result; | ||
}); | ||
|
||
if (!withScores) { | ||
sortedRules = sortedRules.map((combination) => combination[0]); | ||
} | ||
|
||
return sortedRules; | ||
}; | ||
|
||
export default { evaluateRules, getRulePoints, getSpecificityPoints }; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,103 @@ | ||
# Typing | ||
from typing import List, Tuple | ||
# Regex | ||
import re | ||
# List utilities | ||
from functools import reduce | ||
|
||
|
||
def get_rule_points( | ||
rule: str | ||
) -> int: | ||
""" | ||
Gets the points from a rule | ||
rule : str | ||
The rule to evaluate | ||
returns number The number of points of that CSS selector | ||
""" | ||
if not rule: | ||
return 0 | ||
|
||
first_character = rule[0] | ||
|
||
if first_character == ".": | ||
# classes | ||
return 10 | ||
elif first_character == "#": | ||
# ids | ||
return 100 | ||
elif re.match(r'[a-z]', first_character, re.IGNORECASE): | ||
# html tags | ||
return 1 | ||
|
||
return 0 | ||
|
||
|
||
def get_specificity_points( | ||
instruction: str | ||
) -> int: | ||
""" | ||
Calculates the points of a CSS instruction | ||
instruction : str | ||
The CSS instruction to evaluate | ||
returns number The total points | ||
""" | ||
selectors = instruction.split(" ") | ||
points = reduce( | ||
lambda prev, curr: prev + get_rule_points(curr), | ||
selectors, | ||
0 | ||
) | ||
return points | ||
|
||
|
||
def evaluate_css_rules( | ||
rules: List[str], | ||
ascending: bool = False, | ||
with_scores=True, | ||
) -> List[Tuple[str, int]]: | ||
""" | ||
Evaluates multiple CSS rules and sorts them | ||
rules : List[str] | ||
The rules to evaluate | ||
ascending : bool | ||
If it will be ascending, it won't by default | ||
with_scores : bool | ||
Will it return the scores, it will by default | ||
returns List[Tuple[str, int]] | ||
""" | ||
rules_with_specificity = map( | ||
lambda rule: (rule, get_specificity_points(rule)), | ||
rules | ||
) | ||
sorted_rules = sorted( | ||
rules_with_specificity, | ||
key=lambda x: x[1], | ||
reverse=not ascending | ||
) | ||
|
||
if not with_scores: | ||
sorted_rules = map(lambda rule, _: rule, sorted_rules) | ||
|
||
return sorted_rules | ||
|
||
|
||
# checkout my css specificity calculator for a more interactive utility | ||
# https://github.com/jofaval/css-specificity-calculator | ||
if __name__ == '__main__': | ||
# should evaluate them all together | ||
assert evaluate_css_rules( | ||
rules=[ | ||
"my-lonely-html-tag", | ||
"#unique-id > .my-class + my-cool-html-tag", | ||
] | ||
) == [ | ||
("#unique-id > .my-class + my-cool-html-tag", 111), | ||
("my-lonely-html-tag", 1), | ||
] |