Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: support Grass Sass compiler as a fallback #123

Merged
merged 4 commits into from
Jan 25, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 7 additions & 3 deletions ignis/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -233,16 +233,20 @@ def _setup(self, config_path: str) -> None:
self._config_path = config_path

def apply_css(
self, style_path: str, style_priority: StylePriority = "application"
self,
style_path: str,
style_priority: StylePriority = "application",
compiler: Literal["sass", "grass"] | None = None,
) -> None:
"""
theridane marked this conversation as resolved.
Show resolved Hide resolved
Apply a CSS/SCSS/SASS style from a path.
If ``style_path`` has a ``.sass`` or ``.scss`` extension, it will be automatically compiled.
Requires ``dart-sass`` for SASS/SCSS compilation.
Requires either ``dart-sass`` or ``grass-sass`` for SASS/SCSS compilation.

Args:
style_path: Path to the .css/.scss/.sass file.
style_priority: A priority of the CSS style. More info about style priorities: :obj:`Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION`.
compiler: The desired Sass compiler.

.. warning::
``style_priority`` won't affect a style applied to widgets using the ``style`` property,
Expand All @@ -268,7 +272,7 @@ def apply_css(
)

if style_path.endswith(".scss") or style_path.endswith(".sass"):
css_style = Utils.sass_compile(path=style_path)
css_style = Utils.sass_compile(path=style_path, compiler=compiler)
elif style_path.endswith(".css"):
with open(style_path) as file:
css_style = file.read()
Expand Down
11 changes: 6 additions & 5 deletions ignis/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -227,7 +227,7 @@ def plugin_package(self) -> str:

class SassCompilationError(Exception):
"""
Raised when Dart Sass compilation fails.
Raised when the Sass compilation fails.
"""

def __init__(self, stderr: str, *args: object) -> None:
Expand All @@ -239,19 +239,20 @@ def stderr(self) -> str:
"""
- required, read-only

The stderr output from Dart Sass.
The stderr output from the Sass compiler.
"""
return self._stderr


class DartSassNotFoundError(Exception):
class SassNotFoundError(Exception):
"""
Raised when Dart Sass is not found.
Raised when a compatible Sass compiler is not found.
"""

def __init__(self, *args: object) -> None:
super().__init__(
"Dart Sass not found! To compile SCSS/SASS, install dart-sass", *args
"Sass compiler not found! To compile SCSS/SASS, install either dart-sass or grass-sass",
*args,
)


Expand Down
49 changes: 37 additions & 12 deletions ignis/utils/sass.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,25 @@
import os
import shutil
import subprocess
from ignis.exceptions import SassCompilationError, DartSassNotFoundError
from typing import Literal
from ignis.exceptions import SassCompilationError, SassNotFoundError

TEMP_DIR = "/tmp/ignis"
COMPILED_CSS = f"{TEMP_DIR}/compiled.css"
os.makedirs(TEMP_DIR, exist_ok=True)

# resolve Sass compiler paths and pick a default one
# "sass" (dart-sass) is the default,
# "grass" is an API-compatible drop-in replacement
sass_compilers = {}
for cmd in ("sass", "grass"):
path = shutil.which(cmd)
if path:
sass_compilers[cmd] = path

def compile_file(path: str) -> str:
result = subprocess.run(["sass", path, COMPILED_CSS], capture_output=True)

def compile_file(path: str, compiler_path: str) -> str:
result = subprocess.run([compiler_path, path, COMPILED_CSS], capture_output=True)

if result.returncode != 0:
raise SassCompilationError(result.stderr.decode())
Expand All @@ -17,9 +28,9 @@ def compile_file(path: str) -> str:
return file.read()


def compile_string(string: str) -> str:
def compile_string(string: str, compiler_path: str) -> str:
process = subprocess.Popen(
["sass", "--stdin"],
[compiler_path, "--stdin"],
stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
Expand All @@ -32,28 +43,42 @@ def compile_string(string: str) -> str:
return stdout.decode()


def sass_compile(path: str | None = None, string: str | None = None) -> str:
def sass_compile(
path: str | None = None,
string: str | None = None,
compiler: Literal["sass", "grass"] | None = None,
) -> str:
"""
Compile a SASS/SCSS file or string.
Requires `Dart Sass <https://sass-lang.com/dart-sass/>`_.
Requires either `Dart Sass <https://sass-lang.com/dart-sass/>`_
or `Grass <https://github.com/connorskees/grass>`_.

Args:
path: The path to the SASS/SCSS file.
string: A string with SASS/SCSS style.
compiler: The desired Sass compiler, either ``sass`` or ``grass``.

Raises:
TypeError: If neither of the arguments is provided.
DartSassNotFoundError: If Dart Sass not found.
SassNotFoundError: If no Sass compiler is available.
SassCompilationError: If an error occurred while compiling SASS/SCSS.
"""
if not os.path.exists("/bin/sass"):
raise DartSassNotFoundError()
if not sass_compilers:
raise SassNotFoundError()

if compiler and compiler not in sass_compilers:
raise SassNotFoundError()

if compiler:
compiler_path = sass_compilers[compiler]
else:
compiler_path = next(iter(sass_compilers.values()))

if string:
return compile_string(string)
return compile_string(string, compiler_path)

elif path:
return compile_file(path)
return compile_file(path, compiler_path)

else:
raise TypeError("sass_compile() requires at least one positional argument")
Loading