-
Notifications
You must be signed in to change notification settings - Fork 60
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
18 changed files
with
279 additions
and
7 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
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,6 +2,12 @@ | |
Changes | ||
======= | ||
|
||
Dev | ||
=== | ||
|
||
- Add `Stylus <http://learnboost.github.io/stylus/>` compiler | ||
|
||
|
||
0.9 | ||
=== | ||
|
||
|
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
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,136 @@ | ||
from static_precompiler.utils import run_command, convert_urls | ||
from static_precompiler.exceptions import StaticCompilationError | ||
from static_precompiler.compilers.base import BaseCompiler | ||
import os | ||
import posixpath | ||
import re | ||
|
||
|
||
class Stylus(BaseCompiler): | ||
|
||
name = "stylus" | ||
input_extension = "styl" | ||
output_extension = "css" | ||
|
||
IMPORT_RE = re.compile(r"@(?:import|require)\s+(.+?)\s*$", re.MULTILINE) | ||
|
||
def __init__(self, executable="stylus"): | ||
self.executable = executable | ||
super(Stylus, self).__init__() | ||
|
||
def compile_source(self, source): | ||
args = [ | ||
self.executable, | ||
"-p", | ||
] | ||
out, errors = run_command(args, input=source) | ||
|
||
if errors: | ||
raise StaticCompilationError(errors) | ||
|
||
return out | ||
|
||
def compile_file(self, source_path): | ||
full_source_path = self.get_full_source_path(source_path) | ||
args = [ | ||
self.executable, | ||
"-p", | ||
full_source_path, | ||
] | ||
|
||
# `cwd` is a directory containing `source_path`. Ex: source_path = '1/2/3', full_source_path = '/abc/1/2/3' -> cwd = '/abc' | ||
cwd = os.path.normpath(os.path.join(full_source_path, *([".."] * len(source_path.split("/"))))) | ||
out, errors = run_command(args, cwd=cwd) | ||
|
||
if errors: | ||
raise StaticCompilationError(errors) | ||
|
||
return out | ||
|
||
def postprocess(self, compiled, source_path): | ||
return convert_urls(compiled, source_path) | ||
|
||
def find_imports(self, source): | ||
""" Find the imported files in the source code. | ||
:param source: source code | ||
:type source: str | ||
:returns: list of str | ||
""" | ||
imports = set() | ||
for import_string in self.IMPORT_RE.findall(source): | ||
import_string = import_string.strip("'").strip('"').strip() | ||
if not import_string: | ||
continue | ||
if import_string.startswith("url("): | ||
continue | ||
if import_string.endswith(".css"): | ||
continue | ||
if import_string.startswith("http://") or import_string.startswith("https://"): | ||
continue | ||
imports.add(import_string) | ||
return sorted(imports) | ||
|
||
def locate_imported_file(self, source_dir, import_path): | ||
""" Locate the imported file in the source directory. | ||
Return the path to the imported file relative to STATIC_ROOT | ||
:param source_dir: source directory | ||
:type source_dir: str | ||
:param import_path: path to the imported file | ||
:type import_path: str | ||
:returns: str | ||
""" | ||
path = posixpath.normpath(posixpath.join(source_dir, import_path)) | ||
|
||
try: | ||
self.get_full_source_path(path) | ||
except ValueError: | ||
raise StaticCompilationError( | ||
"Can't locate the imported file: {0}".format(import_path) | ||
) | ||
return path | ||
|
||
def find_dependencies(self, source_path): | ||
source = self.get_source(source_path) | ||
source_dir = posixpath.dirname(source_path) | ||
dependencies = set() | ||
imported_files = set() | ||
for import_path in self.find_imports(source): | ||
if import_path.endswith(".styl"): | ||
# @import "foo.styl" | ||
imported_files.add(self.locate_imported_file(source_dir, import_path)) | ||
elif import_path.endswith("/*"): | ||
# @import "foo/*" | ||
imported_dir = posixpath.join(source_dir, import_path[:-2]) | ||
try: | ||
imported_dir_full_path = self.get_full_source_path(imported_dir) | ||
except ValueError: | ||
raise StaticCompilationError( | ||
"Can't locate the imported directory: {0}".format(import_path) | ||
) | ||
if not os.path.isdir(imported_dir_full_path): | ||
raise StaticCompilationError( | ||
"Imported path is not a directory: {0}".format(import_path) | ||
) | ||
for filename in os.listdir(imported_dir_full_path): | ||
if filename.endswith(".styl"): | ||
imported_files.add(self.locate_imported_file(imported_dir, filename)) | ||
else: | ||
try: | ||
# @import "foo" -> @import "foo/index.styl" | ||
imported_dir = posixpath.join(source_dir, import_path) | ||
imported_dir_full_path = self.get_full_source_path(imported_dir) | ||
if os.path.isdir(imported_dir_full_path): | ||
imported_files.add(self.locate_imported_file(imported_dir, "index.styl")) | ||
except ValueError: | ||
# @import "foo" -> @import "foo.styl" | ||
imported_files.add(self.locate_imported_file(source_dir, import_path + ".styl")) | ||
|
||
dependencies.update(imported_files) | ||
for imported_file in imported_files: | ||
dependencies.update(self.find_dependencies(imported_file)) | ||
|
||
return sorted(dependencies) |
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 @@ | ||
@import 'B/*' |
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 @@ | ||
@import "../D.styl" |
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 @@ | ||
@import "E" |
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,2 @@ | ||
p | ||
color: red; |
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 @@ | ||
@import "F" |
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 @@ | ||
@import "non-existing/*" |
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 @@ | ||
@import "not-a-directory/*" |
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 @@ | ||
@import "empty-directory" |
Empty file.
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,97 @@ | ||
# coding: utf-8 | ||
from pretend import call_recorder, call | ||
from static_precompiler.compilers import Stylus | ||
from static_precompiler.exceptions import StaticCompilationError | ||
from static_precompiler.utils import normalize_path, fix_line_breaks | ||
import os | ||
import pytest | ||
|
||
|
||
def test_compile_file(): | ||
compiler = Stylus() | ||
|
||
assert fix_line_breaks(compiler.compile_file("styles/stylus/A.styl")) == "p {\n color: #f00;\n}\n" | ||
|
||
with pytest.raises(StaticCompilationError): | ||
assert compiler.compile_file("styles/stylus/broken1.styl") | ||
|
||
|
||
def test_compile_source(): | ||
compiler = Stylus() | ||
|
||
assert fix_line_breaks(compiler.compile_source("p\n color: red;")) == "p {\n color: #f00;\n}\n\n" | ||
|
||
with pytest.raises(StaticCompilationError): | ||
assert compiler.compile_source("broken") | ||
|
||
|
||
def test_postprocesss(monkeypatch): | ||
compiler = Stylus() | ||
convert_urls = call_recorder(lambda *args: "spam") | ||
monkeypatch.setattr("static_precompiler.compilers.stylus.convert_urls", convert_urls) | ||
assert compiler.postprocess("ham", "eggs") == "spam" | ||
assert convert_urls.calls == [call("ham", "eggs")] | ||
|
||
|
||
def test_find_imports(): | ||
source = """ | ||
@import " " | ||
@import "foo.styl" | ||
@import 'foo' | ||
@import "foo.css" | ||
@import "http://foo.com/bar" | ||
@import "https://foo.com/bar" | ||
@import url(foo) | ||
@import url(http://fonts.googleapis.com/css?family=Arvo:400,700,400italic,700italic) | ||
@import url("http://fonts.googleapis.com/css?family=Open+Sans:300italic,400italic,600italic,700italic,400,700,600,300") | ||
@require "foo.styl" | ||
@require "foo/*" | ||
""" | ||
|
||
expected = [ | ||
"foo", | ||
"foo.styl", | ||
"foo/*", | ||
] | ||
|
||
compiler = Stylus() | ||
assert compiler.find_imports(source) == expected | ||
|
||
|
||
def test_locate_imported_file(monkeypatch): | ||
compiler = Stylus() | ||
|
||
root = os.path.dirname(__file__) | ||
|
||
existing_files = set() | ||
for f in ("A/B.styl", "C.styl"): | ||
existing_files.add(os.path.join(root, "static", normalize_path(f))) | ||
|
||
monkeypatch.setattr("os.path.exists", lambda x: x in existing_files) | ||
|
||
assert compiler.locate_imported_file("A", "B.styl") == "A/B.styl" | ||
assert compiler.locate_imported_file("", "C.styl") == "C.styl" | ||
|
||
with pytest.raises(StaticCompilationError): | ||
compiler.locate_imported_file("", "Z.styl") | ||
|
||
|
||
def test_find_dependencies(): | ||
|
||
compiler = Stylus() | ||
|
||
assert compiler.find_dependencies("styles/stylus/A.styl") == [ | ||
"styles/stylus/B/C.styl", | ||
"styles/stylus/D.styl", | ||
"styles/stylus/E/F.styl", | ||
"styles/stylus/E/index.styl", | ||
] | ||
|
||
with pytest.raises(StaticCompilationError): | ||
compiler.find_dependencies("styles/stylus/broken1.styl") | ||
|
||
with pytest.raises(StaticCompilationError): | ||
compiler.find_dependencies("styles/stylus/broken2.styl") | ||
|
||
with pytest.raises(StaticCompilationError): | ||
compiler.find_dependencies("styles/stylus/broken3.styl") |