Skip to content
Next Next commit
Change BaseCompiler API:
 - `compile_file` does not rely on `compile_source` - `compile_file` returns the relative path to compiled file rather than compiled code - remove `write_output` method - remove `postprocess` method. Compilers post-process the compiled code in `compile_file` Refactor `utils.convert_urls`: now it accepts full path to compiled file and updates the file in place
  • Loading branch information
Andrey Fedoseev committed Sep 28, 2015
commit 60a67b55ccbf16b25b447f5155aa70661a3b9513
24 changes: 23 additions & 1 deletion static_precompiler/compilers/babel.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import os

from static_precompiler import exceptions, utils

from . import base
Expand All @@ -19,7 +21,27 @@ def __init__(self, executable="babel", modules=None):
super(Babel, self).__init__()

def compile_file(self, source_path):
return self.compile_source(self.get_source(source_path))
args = [
self.executable
]

if self.modules is not None:
args.extend(["--modules", self.modules])

full_output_path = self.get_full_output_path(source_path)

full_output_dirname = os.path.dirname(full_output_path)
if not os.path.exists(full_output_dirname):
os.makedirs(full_output_dirname)

args.extend(["-o", full_output_path])
args.append(self.get_full_source_path(source_path))

out, errors = utils.run_command(args)
if errors:
raise exceptions.StaticCompilationError(errors)

return self.get_output_path(source_path)

def compile_source(self, source):
args = [
Expand Down
41 changes: 6 additions & 35 deletions static_precompiler/compilers/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -164,24 +164,6 @@ def get_source(self, source_path):
with open(self.get_full_source_path(source_path)) as source:
return source.read()

def write_output(self, output, source_path):
""" Write the compiled output to a file.

:param output: compiled code
:type output: str
:param source_path: relative path to a source file
:type source_path: str

"""
output_path = self.get_full_output_path(source_path)
output_dir = os.path.dirname(output_path)
if not os.path.exists(output_dir):
os.makedirs(output_dir)

compiled_file = open(output_path, "w+")
compiled_file.write(output)
compiled_file.close()

def compile(self, source_path, from_management=False):
""" Compile the given source path and return relative path to the compiled file.
Raise ValueError is the source file type is not supported.
Expand All @@ -198,18 +180,19 @@ def compile(self, source_path, from_management=False):
raise ValueError("'{0}' file type is not supported by '{1}'".format(
source_path, self.__class__.__name__
))

compliled_path = self.get_output_path(source_path)

if self.should_compile(source_path, from_management=from_management):

compiled = self.compile_file(source_path)
compiled = self.postprocess(compiled, source_path)
self.write_output(compiled, source_path)
compliled_path = self.compile_file(source_path)

if self.supports_dependencies:
self.update_dependencies(source_path, self.find_dependencies(source_path))

logging.info("Compiled: '{0}'".format(source_path))

return self.get_output_path(source_path)
return compliled_path

def compile_lazy(self, source_path):
""" Return a lazy object which, when translated to string, compiles the specified source path and returns
Expand All @@ -226,7 +209,7 @@ def compile_lazy(self, source_path):
compile_lazy = functional.lazy(compile_lazy, six.text_type)

def compile_file(self, source_path):
""" Compile the source file. Return the compiled code.
""" Compile the source file. Return the relative path to compiled file.
May raise a StaticCompilationError if something goes wrong with compilation.

:param source_path: path to the source file
Expand All @@ -247,18 +230,6 @@ def compile_source(self, source):
"""
raise NotImplementedError

# noinspection PyMethodMayBeStatic,PyUnusedLocal
def postprocess(self, compiled, source_path):
""" Post-process the compiled code.

:param compiled: compiled code
:type compiled: str
:param source_path: relative path to a source file
:type source_path: str
:returns: str
"""
return compiled

def find_dependencies(self, source_path):
""" Find the dependencies for the given source file.

Expand Down
13 changes: 12 additions & 1 deletion static_precompiler/compilers/coffeescript.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import os

from static_precompiler import exceptions, settings, utils

from . import base
Expand All @@ -18,7 +20,16 @@ def __init__(self, executable=settings.COFFEESCRIPT_EXECUTABLE):
super(CoffeeScript, self).__init__()

def compile_file(self, source_path):
return self.compile_source(self.get_source(source_path))
args = [
self.executable,
"-c",
"-o", os.path.dirname(self.get_full_output_path(source_path)),
self.get_full_source_path(source_path),
]
out, errors = utils.run_command(args)
if errors:
raise exceptions.StaticCompilationError(errors)
return self.get_output_path(source_path)

def compile_source(self, source):
args = [
Expand Down
13 changes: 7 additions & 6 deletions static_precompiler/compilers/less.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,18 +33,22 @@ def should_compile(self, source_path, from_management=False):

def compile_file(self, source_path):
full_source_path = self.get_full_source_path(source_path)
full_output_path = self.get_full_output_path(source_path)
args = [
self.executable,
full_source_path,
self.get_full_source_path(source_path),
full_output_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 = utils.run_command(args, None, cwd=cwd)
out, errors = utils.run_command(args, cwd=cwd)
if errors:
raise exceptions.StaticCompilationError(errors)

return out
utils.convert_urls(full_output_path, source_path)

return self.get_output_path(source_path)

def compile_source(self, source):
args = [
Expand All @@ -59,9 +63,6 @@ def compile_source(self, source):

return out

def postprocess(self, compiled, source_path):
return utils.convert_urls(compiled, source_path)

def find_imports(self, source):
""" Find the imported files in the source code.

Expand Down
21 changes: 16 additions & 5 deletions static_precompiler/compilers/scss.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,23 +35,37 @@ def should_compile(self, source_path, from_management=False):

def compile_file(self, source_path):
full_source_path = self.get_full_source_path(source_path)
full_output_path = self.get_full_output_path(source_path)
args = [
self.executable,
full_source_path,
"--sourcemap=none",
]

if self.is_compass_enabled:
args.append("--compass")

args.extend([
self.get_full_source_path(source_path),
full_output_path,
])

full_output_dirname = os.path.dirname(full_output_path)
if not os.path.exists(full_output_dirname):
os.makedirs(full_output_dirname)

# `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 = utils.run_command(args, None, cwd=cwd)

if errors:
if os.path.exists(full_output_path):
os.remove(full_output_path)
raise exceptions.StaticCompilationError(errors)

return out
utils.convert_urls(full_output_path, source_path)

return self.get_output_path(source_path)

def compile_source(self, source):
args = [
Expand All @@ -70,9 +84,6 @@ def compile_source(self, source):

return out

def postprocess(self, compiled, source_path):
return utils.convert_urls(compiled, source_path)

# noinspection PyMethodMayBeStatic
def parse_import_string(self, import_string):
""" Extract import items from import string.
Expand Down
12 changes: 8 additions & 4 deletions static_precompiler/compilers/stylus.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,12 +37,17 @@ def compile_source(self, source):

def compile_file(self, source_path):
full_source_path = self.get_full_source_path(source_path)
full_output_path = self.get_full_output_path(source_path)
args = [
self.executable,
"-p",
full_source_path,
"-o", os.path.dirname(full_output_path),
]

full_output_dirname = os.path.dirname(full_output_path)
if not os.path.exists(full_output_dirname):
os.makedirs(full_output_dirname)

# `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("/")))))
Expand All @@ -51,10 +56,9 @@ def compile_file(self, source_path):
if errors:
raise exceptions.StaticCompilationError(errors)

return out
utils.convert_urls(full_output_path, source_path)

def postprocess(self, compiled, source_path):
return utils.convert_urls(compiled, source_path)
return self.get_output_path(source_path)

def find_imports(self, source):
""" Find the imported files in the source code.
Expand Down
21 changes: 16 additions & 5 deletions static_precompiler/tests/test_babel.py
Original file line number Diff line number Diff line change
@@ -1,18 +1,29 @@
# coding: utf-8
import os

import pytest

from static_precompiler import compilers, exceptions

from .test_coffeescript import clean_javascript


def test_compile_file():
def test_compile_file(monkeypatch, tmpdir):
monkeypatch.setattr("static_precompiler.settings.ROOT", tmpdir.strpath)

compiler = compilers.Babel()

assert (
clean_javascript(compiler.compile_file("scripts/test.es6")) ==
""""use strict";\nconsole.log("Hello, World!");"""
)
assert compiler.compile_file("scripts/test.es6") == "COMPILED/scripts/test.js"

full_output_path = compiler.get_full_output_path("scripts/test.es6")

assert os.path.exists(full_output_path)

with open(full_output_path) as compiled:
assert compiled.read() == """"use strict";

console.log("Hello, World!");
"""


def test_compile_source():
Expand Down
36 changes: 1 addition & 35 deletions static_precompiler/tests/test_base_compiler.py
Original file line number Diff line number Diff line change
Expand Up @@ -142,38 +142,16 @@ def test_get_source():
assert compiler.get_source("scripts/test.coffee") == 'console.log "Hello, World!"'


def test_write_output(monkeypatch, tmpdir):
compiler = compilers.BaseCompiler()

output_path = os.path.join(tmpdir.dirname, "dummy.js")
assert os.path.exists(output_path) is False

monkeypatch.setattr(compiler, "get_full_output_path", lambda x: output_path)

compiler.write_output("compiled", "dummy.coffee")
assert os.path.exists(output_path) is True

with open(output_path) as output:
assert output.read() == "compiled"


def test_compile_source():
compiler = compilers.BaseCompiler()
with pytest.raises(NotImplementedError):
compiler.compile_source("source")


def test_postprocess():
compiler = compilers.BaseCompiler()
assert compiler.postprocess("compiled", "dummy.coffee") == "compiled"


def test_compile(monkeypatch):
compiler = compilers.BaseCompiler()

monkeypatch.setattr(compiler, "compile_file", pretend.call_recorder(lambda *args: "compiled"))
monkeypatch.setattr(compiler, "write_output", pretend.call_recorder(lambda *args: None))
monkeypatch.setattr(compiler, "postprocess", pretend.call_recorder(lambda compiled, source_path: compiled))
monkeypatch.setattr(compiler, "compile_file", pretend.call_recorder(lambda *args: "dummy.js"))
monkeypatch.setattr(compiler, "update_dependencies", pretend.call_recorder(lambda *args: None))
monkeypatch.setattr(compiler, "find_dependencies", pretend.call_recorder(lambda *args: ["A", "B"]))
monkeypatch.setattr(compiler, "get_output_path", lambda *args: "dummy.js")
Expand All @@ -185,31 +163,19 @@ def test_compile(monkeypatch):

# noinspection PyUnresolvedReferences
assert compiler.compile_file.calls == []
# noinspection PyUnresolvedReferences
assert compiler.write_output.calls == []
# noinspection PyUnresolvedReferences
assert compiler.postprocess.calls == []

monkeypatch.setattr(compiler, "is_supported", lambda *args: True)
monkeypatch.setattr(compiler, "should_compile", lambda *args, **kwargs: False)

assert compiler.compile("dummy.coffee") == "dummy.js"
# noinspection PyUnresolvedReferences
assert compiler.compile_file.calls == []
# noinspection PyUnresolvedReferences
assert compiler.write_output.calls == []
# noinspection PyUnresolvedReferences
assert compiler.postprocess.calls == []

monkeypatch.setattr(compiler, "should_compile", lambda *args, **kwargs: True)
assert compiler.compile("dummy.coffee") == "dummy.js"

# noinspection PyUnresolvedReferences
assert compiler.compile_file.calls == [pretend.call("dummy.coffee")]
# noinspection PyUnresolvedReferences
assert compiler.write_output.calls == [pretend.call("compiled", "dummy.coffee")]
# noinspection PyUnresolvedReferences
assert compiler.postprocess.calls == [pretend.call("compiled", "dummy.coffee")]

# noinspection PyUnresolvedReferences
assert compiler.update_dependencies.calls == []
Expand Down
14 changes: 9 additions & 5 deletions static_precompiler/tests/test_coffeescript.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
# coding: utf-8
import os

import pytest

from static_precompiler import compilers, exceptions
Expand All @@ -11,13 +13,15 @@ def clean_javascript(js):
)


def test_compile_file():
def test_compile_file(monkeypatch, tmpdir):
monkeypatch.setattr("static_precompiler.settings.ROOT", tmpdir.strpath)

compiler = compilers.CoffeeScript()

assert (
clean_javascript(compiler.compile_file("scripts/test.coffee")) ==
"""(function() {\n console.log("Hello, World!");\n}).call(this);"""
)
assert clean_javascript(compiler.compile_file("scripts/test.coffee")) == "COMPILED/scripts/test.js"
assert os.path.exists(compiler.get_full_output_path("scripts/test.coffee"))
with open(compiler.get_full_output_path("scripts/test.coffee")) as compiled:
assert clean_javascript(compiled.read()) == """(function() {\n console.log("Hello, World!");\n}).call(this);"""


def test_compile_source():
Expand Down
Loading