mirror of
https://github.com/getpelican/pelican.git
synced 2025-10-15 20:28:56 +02:00
Merge pull request #318 from MeirKriheli/master
Generator to compile less css files
This commit is contained in:
commit
4370b51f02
7 changed files with 128 additions and 3 deletions
|
|
@ -23,6 +23,7 @@ Pelican currently supports:
|
||||||
* Publication of articles in multiple languages
|
* Publication of articles in multiple languages
|
||||||
* Atom/RSS feeds
|
* Atom/RSS feeds
|
||||||
* Code syntax highlighting
|
* Code syntax highlighting
|
||||||
|
* Compilation of less css (optional)
|
||||||
* Import from WordPress, Dotclear, or RSS feeds
|
* Import from WordPress, Dotclear, or RSS feeds
|
||||||
* Integration with external tools: Twitter, Google Analytics, etc. (optional)
|
* Integration with external tools: Twitter, Google Analytics, etc. (optional)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -82,10 +82,15 @@ Setting name (default value) What does it do?
|
||||||
generated HTML, using the `Typogrify
|
generated HTML, using the `Typogrify
|
||||||
<http://static.mintchaos.com/projects/typogrify/>`_
|
<http://static.mintchaos.com/projects/typogrify/>`_
|
||||||
library
|
library
|
||||||
|
`LESS_GENERATOR` (``FALSE``) Set to True or complete path to `lessc` (if not
|
||||||
|
found in system PATH) to enable compiling less
|
||||||
|
css files. Requires installation of `less css`_.
|
||||||
================================================ =====================================================
|
================================================ =====================================================
|
||||||
|
|
||||||
.. [#] Default is the system locale.
|
.. [#] Default is the system locale.
|
||||||
|
|
||||||
|
.. _less css: http://lesscss.org/
|
||||||
|
|
||||||
|
|
||||||
URL settings
|
URL settings
|
||||||
------------
|
------------
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ import logging
|
||||||
import argparse
|
import argparse
|
||||||
|
|
||||||
from pelican.generators import (ArticlesGenerator, PagesGenerator,
|
from pelican.generators import (ArticlesGenerator, PagesGenerator,
|
||||||
StaticGenerator, PdfGenerator)
|
StaticGenerator, PdfGenerator, LessCSSGenerator)
|
||||||
from pelican.log import init
|
from pelican.log import init
|
||||||
from pelican.settings import read_settings, _DEFAULT_CONFIG
|
from pelican.settings import read_settings, _DEFAULT_CONFIG
|
||||||
from pelican.utils import clean_output_dir, files_changed
|
from pelican.utils import clean_output_dir, files_changed
|
||||||
|
|
@ -134,6 +134,8 @@ class Pelican(object):
|
||||||
generators = [ArticlesGenerator, PagesGenerator, StaticGenerator]
|
generators = [ArticlesGenerator, PagesGenerator, StaticGenerator]
|
||||||
if self.settings['PDF_GENERATOR']:
|
if self.settings['PDF_GENERATOR']:
|
||||||
generators.append(PdfGenerator)
|
generators.append(PdfGenerator)
|
||||||
|
if self.settings['LESS_GENERATOR']: # can be True or PATH to lessc
|
||||||
|
generators.append(LessCSSGenerator)
|
||||||
return generators
|
return generators
|
||||||
|
|
||||||
def get_writer(self):
|
def get_writer(self):
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ import math
|
||||||
import random
|
import random
|
||||||
import logging
|
import logging
|
||||||
import datetime
|
import datetime
|
||||||
|
import subprocess
|
||||||
|
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
from functools import partial
|
from functools import partial
|
||||||
|
|
@ -414,3 +415,50 @@ class PdfGenerator(Generator):
|
||||||
|
|
||||||
for page in self.context['pages']:
|
for page in self.context['pages']:
|
||||||
self._create_pdf(page, pdf_path)
|
self._create_pdf(page, pdf_path)
|
||||||
|
|
||||||
|
|
||||||
|
class LessCSSGenerator(Generator):
|
||||||
|
"""Compile less css files."""
|
||||||
|
|
||||||
|
def _compile(self, less_file, source_dir, dest_dir):
|
||||||
|
base = os.path.relpath(less_file, source_dir)
|
||||||
|
target = os.path.splitext(
|
||||||
|
os.path.join(dest_dir, base))[0] + '.css'
|
||||||
|
target_dir = os.path.dirname(target)
|
||||||
|
|
||||||
|
if not os.path.exists(target_dir):
|
||||||
|
try:
|
||||||
|
os.makedirs(target_dir)
|
||||||
|
except OSError:
|
||||||
|
logger.error("Couldn't create the less css output folder in " +
|
||||||
|
target_dir)
|
||||||
|
|
||||||
|
subprocess.call([self._lessc, less_file, target])
|
||||||
|
logger.info(u' [ok] compiled %s' % base)
|
||||||
|
|
||||||
|
def generate_output(self, writer=None):
|
||||||
|
logger.info(u' Compiling less css')
|
||||||
|
|
||||||
|
# store out compiler here, so it won't be evaulted on each run of
|
||||||
|
# _compile
|
||||||
|
lg = self.settings['LESS_GENERATOR']
|
||||||
|
self._lessc = lg if isinstance(lg, basestring) else 'lessc'
|
||||||
|
|
||||||
|
# walk static paths
|
||||||
|
for static_path in self.settings['STATIC_PATHS']:
|
||||||
|
for f in self.get_files(
|
||||||
|
os.path.join(self.path, static_path),
|
||||||
|
extensions=['less']):
|
||||||
|
|
||||||
|
self._compile(f, self.path, self.output_path)
|
||||||
|
|
||||||
|
# walk theme static paths
|
||||||
|
theme_output_path = os.path.join(self.output_path, 'theme')
|
||||||
|
|
||||||
|
for static_path in self.settings['THEME_STATIC_PATHS']:
|
||||||
|
theme_static_path = os.path.join(self.theme, static_path)
|
||||||
|
for f in self.get_files(
|
||||||
|
theme_static_path,
|
||||||
|
extensions=['less']):
|
||||||
|
|
||||||
|
self._compile(f, theme_static_path, theme_output_path)
|
||||||
|
|
|
||||||
|
|
@ -67,6 +67,7 @@ _DEFAULT_CONFIG = {'PATH': '.',
|
||||||
'DEFAULT_STATUS': 'published',
|
'DEFAULT_STATUS': 'published',
|
||||||
'ARTICLE_PERMALINK_STRUCTURE': '',
|
'ARTICLE_PERMALINK_STRUCTURE': '',
|
||||||
'TYPOGRIFY': False,
|
'TYPOGRIFY': False,
|
||||||
|
'LESS_GENERATOR': False,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,8 @@ __all__ = [
|
||||||
'unittest',
|
'unittest',
|
||||||
]
|
]
|
||||||
|
|
||||||
|
import os
|
||||||
|
import subprocess
|
||||||
from contextlib import contextmanager
|
from contextlib import contextmanager
|
||||||
from tempfile import mkdtemp
|
from tempfile import mkdtemp
|
||||||
from shutil import rmtree
|
from shutil import rmtree
|
||||||
|
|
@ -35,3 +37,21 @@ def get_article(title, slug, content, lang, extra_metadata=None):
|
||||||
if extra_metadata is not None:
|
if extra_metadata is not None:
|
||||||
metadata.update(extra_metadata)
|
metadata.update(extra_metadata)
|
||||||
return Article(content, metadata=metadata)
|
return Article(content, metadata=metadata)
|
||||||
|
|
||||||
|
|
||||||
|
def skipIfNoExecutable(executable, valid_exit_code=1):
|
||||||
|
"""Tries to run an executable to make sure it's in the path, Skips the tests
|
||||||
|
if not found.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# calling with no params the command should exit with 1
|
||||||
|
with open(os.devnull, 'w') as fnull:
|
||||||
|
try:
|
||||||
|
res = subprocess.call(executable, stdout=fnull, stderr=fnull)
|
||||||
|
except OSError:
|
||||||
|
res = None
|
||||||
|
|
||||||
|
if res != valid_exit_code:
|
||||||
|
return unittest.skip('{0} compiler not found'.format(executable))
|
||||||
|
|
||||||
|
return lambda func: func
|
||||||
|
|
|
||||||
|
|
@ -2,10 +2,12 @@
|
||||||
|
|
||||||
from mock import MagicMock
|
from mock import MagicMock
|
||||||
import os
|
import os
|
||||||
|
import re
|
||||||
|
import subprocess
|
||||||
|
|
||||||
from pelican.generators import ArticlesGenerator
|
from pelican.generators import ArticlesGenerator, LessCSSGenerator
|
||||||
from pelican.settings import _DEFAULT_CONFIG
|
from pelican.settings import _DEFAULT_CONFIG
|
||||||
from .support import unittest
|
from .support import unittest, temporary_folder, skipIfNoExecutable
|
||||||
|
|
||||||
CUR_DIR = os.path.dirname(__file__)
|
CUR_DIR = os.path.dirname(__file__)
|
||||||
|
|
||||||
|
|
@ -46,3 +48,49 @@ class TestArticlesGenerator(unittest.TestCase):
|
||||||
elif relfilepath == "article_without_category.rst":
|
elif relfilepath == "article_without_category.rst":
|
||||||
self.assertEquals(article.category.name, 'Default')
|
self.assertEquals(article.category.name, 'Default')
|
||||||
|
|
||||||
|
|
||||||
|
class TestLessCSSGenerator(unittest.TestCase):
|
||||||
|
|
||||||
|
LESS_CONTENT = """
|
||||||
|
@color: #4D926F;
|
||||||
|
|
||||||
|
#header {
|
||||||
|
color: @color;
|
||||||
|
}
|
||||||
|
h2 {
|
||||||
|
color: @color;
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
|
||||||
|
@skipIfNoExecutable('lessc')
|
||||||
|
def test_less_compiler(self):
|
||||||
|
|
||||||
|
settings = _DEFAULT_CONFIG.copy()
|
||||||
|
settings['STATIC_PATHS'] = ['static']
|
||||||
|
settings['LESS_GENERATOR'] = True
|
||||||
|
|
||||||
|
# we'll nest here for py < 2.7 compat
|
||||||
|
with temporary_folder() as temp_content:
|
||||||
|
with temporary_folder() as temp_output:
|
||||||
|
generator = LessCSSGenerator(None, settings, temp_content,
|
||||||
|
_DEFAULT_CONFIG['THEME'], temp_output, None)
|
||||||
|
|
||||||
|
# create a dummy less file
|
||||||
|
less_dir = os.path.join(temp_content, 'static', 'css')
|
||||||
|
less_filename = os.path.join(less_dir, 'test.less')
|
||||||
|
|
||||||
|
less_output = os.path.join(temp_output, 'static', 'css',
|
||||||
|
'test.css')
|
||||||
|
|
||||||
|
os.makedirs(less_dir)
|
||||||
|
with open(less_filename, 'w') as less_file:
|
||||||
|
less_file.write(self.LESS_CONTENT)
|
||||||
|
|
||||||
|
generator.generate_output()
|
||||||
|
|
||||||
|
# we have the file ?
|
||||||
|
self.assertTrue(os.path.exists(less_output))
|
||||||
|
|
||||||
|
# was it compiled ?
|
||||||
|
self.assertIsNotNone(re.search(r'^\s+color:\s*#4D926F;$',
|
||||||
|
open(less_output).read(), re.MULTILINE | re.IGNORECASE))
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue