mirror of https://github.com/deuill/fawkss.git
175 lines
5.2 KiB
Awk
Executable File
175 lines
5.2 KiB
Awk
Executable File
#!/usr/bin/awk -f
|
|
#
|
|
# Fawkss — The [ig]noble CSS preprocessor.
|
|
# ========================================
|
|
#
|
|
# Fawkss is a CSS preprocessor for people who dislike CSS preprocessors. It
|
|
# implements a subset of the SCSS syntax while remaining relatively simple.
|
|
#
|
|
# This documentation is built using Markdown syntax, and can be parsed out by
|
|
# running `make doc` in the project root. Please check the project's README file
|
|
# for additional information.
|
|
#
|
|
# Built-in functions
|
|
# ------------------
|
|
#
|
|
# This section contains global helper functions, used across different rules, as
|
|
# defined in the next section below.
|
|
#
|
|
# > Function `lastindex` returns the index of the last occurence of a substring
|
|
# > `s` in `str`, or -1 if the substring was not found.
|
|
function lastindex(str, s) {
|
|
match(str, ".*" s)
|
|
return max(RSTART + RLENGTH - 1, -1)
|
|
}
|
|
|
|
# > Function `max` finds and returns greatest between two numbers.
|
|
function max(x, y) {
|
|
return (x > y) ? x : y
|
|
}
|
|
|
|
# > Function `trim` removes characters (whitespace by default) off both ends of
|
|
# > the string passed and returns the modified string.
|
|
function trim(str, chars) {
|
|
t = (chars != "") ? chars : ":space:"
|
|
match(str, "[" t "]*[^[" t "]]+[" t "]*")
|
|
|
|
return substr(str, RSTART, RLENGTH)
|
|
}
|
|
|
|
# > Function `file_exists` checks if file pointed to by `filename` exists, and
|
|
# > returns `1` if true, `0` if false.
|
|
function file_exists(filename) {
|
|
return (system("[ -e '" filename "' ]") == 0) ? 1 : 0;
|
|
}
|
|
|
|
# Initialization
|
|
# --------------
|
|
#
|
|
# This block contains logic for initializing global variables used across Fawkss.
|
|
|
|
BEGIN {
|
|
# Error messages used across Fawkss.
|
|
errors["unused-variable"] = "ERROR: Use of undeclared variable '%s' on line %d\n"
|
|
|
|
# Rule definitions. Each rule corresponds to a regular expression matching
|
|
# that rule on any given line.
|
|
rules["comment"] = "[ ]*//.*$"
|
|
rules["comment-exception"] = "['\"][^//]*//[^'\"]*['\"]"
|
|
|
|
rules["variable-name"] = "\\$[a-zA-Z0-9_]+"
|
|
rules["variable-define"] = "^[ ]*" rules["variable-name"] "[ ]*:"
|
|
}
|
|
|
|
# Rule definitions
|
|
# ----------------
|
|
#
|
|
# This block contains rule definitions used across Fawkss. A rule is defined as
|
|
# an exclusive match against a single line which always contines on to the next
|
|
# line. As such, rules are not composable.
|
|
#
|
|
# > Match variable declarations, for example:
|
|
# >
|
|
# > $varname: "value";
|
|
# >
|
|
# > Only one variable declaration can appear on a single line. Redeclaring a
|
|
# > variable overrides the value set for that variable.
|
|
$0 ~ rules["variable-define"] {
|
|
# Split text in tokens.
|
|
split($0, token, ":")
|
|
|
|
# Get variable name and value.
|
|
name = trim(substr(token[1], index(token[1], "$")))
|
|
value = trim(substr(token[2], 0, lastindex(token[2], ";") - 1))
|
|
|
|
# Assign variable to the global variables table.
|
|
variables[name] = value
|
|
next
|
|
}
|
|
|
|
# Line manipulation
|
|
# -----------------
|
|
#
|
|
# This block contains line manipulation for rules defined in the previous section.
|
|
# Each line manipulation rule may modify the current line but should not continue
|
|
# to the next line, in order to allow for cascading effects.
|
|
#
|
|
# > Match inline comments, for example:
|
|
# >
|
|
# > // This is an inline comment.
|
|
# > :root{background: white;} // Another inline comment.
|
|
# >
|
|
# > As opposed to regular CSS comments (i.e. `/* */`), inline comments are removed
|
|
# > from the processed result. Inline comments inside strings are not removed.
|
|
$0 ~ rules["comment"] {
|
|
# Remove any special cases from the line.
|
|
while (match($0, rules["comment-exception"])) {
|
|
special[len += 1] = RSTART ":" substr($0, RSTART, RLENGTH)
|
|
$0 = substr($0, 0, RSTART - 1) substr($0, RSTART + RLENGTH, length($0))
|
|
}
|
|
|
|
# Remove inline comments from line.
|
|
while (match($0, rules["comment"])) {
|
|
$0 = substr($0, 0, RSTART - 1) substr($0, RSTART + RLENGTH, length($0))
|
|
}
|
|
|
|
# Reinsert special cases in their predefined positions.
|
|
for (i = len; i != 0; i--) {
|
|
pos = substr(special[i], 0, index(special[i], ":") - 1)
|
|
|
|
# Do not attempt to reinsert special case string if string has been
|
|
# truncated to less the original position of the string.
|
|
if (pos > length($0)) {
|
|
continue
|
|
}
|
|
|
|
str = substr(special[i], index(special[i], ":") + 1, length(special[i]))
|
|
$0 = substr($0, 0, pos - 1) str substr($0, pos, length($0))
|
|
}
|
|
|
|
len = 0
|
|
}
|
|
|
|
# > Match variable uses, for example:
|
|
# >
|
|
# > :root{background: $white;}
|
|
# >
|
|
# > Where `$white` is a previously declared variable. Attempting to use a variable
|
|
# > that has not been defined yet will throw a fatal error.
|
|
$0 ~ rules["variable-name"] {
|
|
# Replace each variable use with its concrete value.
|
|
while (match($0, rules["variable-name"])) {
|
|
name = substr($0, RSTART, RLENGTH)
|
|
|
|
# Throw error and exit if variable used has not been declared.
|
|
if (variables[name] == "") {
|
|
printf errors["unused-variable"], name, NR | "cat >&2"
|
|
exit
|
|
}
|
|
|
|
$0 = substr($0, 0, RSTART - 1) variables[name] substr($0, RSTART + RLENGTH, length($0))
|
|
}
|
|
}
|
|
|
|
# Line printing
|
|
# -------------
|
|
#
|
|
# This block contains line-printing rules, for results generated in the above
|
|
# blocks.
|
|
#
|
|
# > Match empty line. Consecutive empty lines do not print, and are instead
|
|
# > squashed down to a single line.
|
|
!NF {
|
|
if ((newlines += 1) < 2) {
|
|
print
|
|
}
|
|
|
|
next
|
|
}
|
|
|
|
# > Match non-black line. Resets newline count, used above.
|
|
{
|
|
newlines = 0
|
|
print
|
|
}
|