From a9f798eae170fd8c6c9d5b9b8fbdc6b5141d0ac7 Mon Sep 17 00:00:00 2001 From: Meir Kriheli Date: Sun, 15 Apr 2012 00:41:38 +0300 Subject: [PATCH 01/10] Document less css generator --- docs/index.rst | 1 + docs/settings.rst | 5 +++++ 2 files changed, 6 insertions(+) diff --git a/docs/index.rst b/docs/index.rst index 1389f132..6ad22670 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -23,6 +23,7 @@ Pelican currently supports: * Publication of articles in multiple languages * Atom/RSS feeds * Code syntax highlighting +* Compilation of less css (optional) * Import from WordPress, Dotclear, or RSS feeds * Integration with external tools: Twitter, Google Analytics, etc. (optional) diff --git a/docs/settings.rst b/docs/settings.rst index b7882075..48018a89 100644 --- a/docs/settings.rst +++ b/docs/settings.rst @@ -82,10 +82,15 @@ Setting name (default value) What does it do? generated HTML, using the `Typogrify `_ library +`LESS_GENERATOR` (``FALSE``) Set to True if you want to enable compiling less + files. Requires installtion of `less css`_. +`LESS_COMPILER` (``'/usr/bin/lessc'``) The path to less css compiler (`lessc`) ================================================ ===================================================== .. [#] Default is the system locale. +.. _less css: http://lesscss.org/ + URL settings ------------ From 50f2cd295f8da11218ecbdd4af7abdafdff2725e Mon Sep 17 00:00:00 2001 From: Meir Kriheli Date: Sun, 15 Apr 2012 02:20:20 +0300 Subject: [PATCH 02/10] Implement LessCSSGenerator --- pelican/__init__.py | 4 +++- pelican/generators.py | 36 ++++++++++++++++++++++++++++++++++++ pelican/settings.py | 2 ++ 3 files changed, 41 insertions(+), 1 deletion(-) diff --git a/pelican/__init__.py b/pelican/__init__.py index c2766646..bb9819cd 100644 --- a/pelican/__init__.py +++ b/pelican/__init__.py @@ -6,7 +6,7 @@ import logging import argparse from pelican.generators import (ArticlesGenerator, PagesGenerator, - StaticGenerator, PdfGenerator) + StaticGenerator, PdfGenerator, LessCSSGenerator) from pelican.log import init from pelican.settings import read_settings, _DEFAULT_CONFIG from pelican.utils import clean_output_dir, files_changed @@ -133,6 +133,8 @@ class Pelican(object): generators = [ArticlesGenerator, PagesGenerator, StaticGenerator] if self.settings['PDF_GENERATOR']: generators.append(PdfGenerator) + if self.settings['LESS_GENERATOR']: + generators.append(LessCSSGenerator) return generators def get_writer(self): diff --git a/pelican/generators.py b/pelican/generators.py index d7ebb0b0..5b90ad96 100644 --- a/pelican/generators.py +++ b/pelican/generators.py @@ -4,6 +4,7 @@ import math import random import logging import datetime +import subprocess from collections import defaultdict from functools import partial @@ -414,3 +415,38 @@ class PdfGenerator(Generator): for page in self.context['pages']: self._create_pdf(page, pdf_path) + + +class LessCSSGenerator(Generator): + """Compile less css files. This assumes we have `lessc` in our PATH.""" + + def generate_context(self): + pass + + def _compile(self, less_file, source_dir, dest_dir): + base = os.path.relpath(less_file, source_dir) + target = os.path.splitext( + os.path.join(self.output_path, 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 pdf output folder in " + + target_dir) + pass + + cmd = ' '.join([self.settings['LESS_COMPILER'], less_file, target]) + subprocess.call(cmd, shell=True) + logger.info(u' [ok] compiled %s' % base) + + def generate_output(self, writer=None): + logger.info(u' Compiling less css') + + 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) diff --git a/pelican/settings.py b/pelican/settings.py index c0e30815..418634a0 100644 --- a/pelican/settings.py +++ b/pelican/settings.py @@ -67,6 +67,8 @@ _DEFAULT_CONFIG = {'PATH': '.', 'DEFAULT_STATUS': 'published', 'ARTICLE_PERMALINK_STRUCTURE': '', 'TYPOGRIFY': False, + 'LESS_GENERATOR': False, + 'LESS_COMPILER': '/usr/bin/lessc', } From ddf57ca2957509e4b069a53da20e5842d5ae4dbe Mon Sep 17 00:00:00 2001 From: Meir Kriheli Date: Sun, 15 Apr 2012 02:52:19 +0300 Subject: [PATCH 03/10] Also compile less css files in theme static --- pelican/generators.py | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/pelican/generators.py b/pelican/generators.py index 5b90ad96..48d29f84 100644 --- a/pelican/generators.py +++ b/pelican/generators.py @@ -426,7 +426,7 @@ class LessCSSGenerator(Generator): def _compile(self, less_file, source_dir, dest_dir): base = os.path.relpath(less_file, source_dir) target = os.path.splitext( - os.path.join(self.output_path, base))[0] + '.css' + os.path.join(dest_dir, base))[0] + '.css' target_dir = os.path.dirname(target) if not os.path.exists(target_dir): @@ -435,7 +435,6 @@ class LessCSSGenerator(Generator): except OSError: logger.error("Couldn't create the pdf output folder in " + target_dir) - pass cmd = ' '.join([self.settings['LESS_COMPILER'], less_file, target]) subprocess.call(cmd, shell=True) @@ -444,9 +443,21 @@ class LessCSSGenerator(Generator): def generate_output(self, writer=None): logger.info(u' Compiling less css') + # 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) From 4793cdfab347835ba3119e97d2fe7e889a87249b Mon Sep 17 00:00:00 2001 From: Meir Kriheli Date: Mon, 16 Apr 2012 22:55:50 +0300 Subject: [PATCH 04/10] Fix typo in less generator docs --- docs/settings.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/settings.rst b/docs/settings.rst index 48018a89..22b4c744 100644 --- a/docs/settings.rst +++ b/docs/settings.rst @@ -83,7 +83,7 @@ Setting name (default value) What does it do? `_ library `LESS_GENERATOR` (``FALSE``) Set to True if you want to enable compiling less - files. Requires installtion of `less css`_. + files. Requires installation of `less css`_. `LESS_COMPILER` (``'/usr/bin/lessc'``) The path to less css compiler (`lessc`) ================================================ ===================================================== From d4e981f916c445bb13c30d651a3b80c79f79bb06 Mon Sep 17 00:00:00 2001 From: Meir Kriheli Date: Sat, 28 Apr 2012 02:41:48 +0300 Subject: [PATCH 05/10] unittest helper: Skip if exectuable not found --- tests/support.py | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/tests/support.py b/tests/support.py index 4eb07ec4..f2b4a075 100644 --- a/tests/support.py +++ b/tests/support.py @@ -4,6 +4,8 @@ __all__ = [ 'unittest', ] +import os +import subprocess from contextlib import contextmanager from tempfile import mkdtemp from shutil import rmtree @@ -35,3 +37,21 @@ def get_article(title, slug, content, lang, extra_metadata=None): if extra_metadata is not None: metadata.update(extra_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 From 1d3e38c5dd4d667ae1d702cfb9dfb32749ab8bf4 Mon Sep 17 00:00:00 2001 From: Meir Kriheli Date: Sat, 28 Apr 2012 03:03:53 +0300 Subject: [PATCH 06/10] Test less generator --- tests/test_generators.py | 53 ++++++++++++++++++++++++++++++++++++++-- 1 file changed, 51 insertions(+), 2 deletions(-) diff --git a/tests/test_generators.py b/tests/test_generators.py index bc5c8b73..ce1b9e7e 100644 --- a/tests/test_generators.py +++ b/tests/test_generators.py @@ -2,10 +2,12 @@ from mock import MagicMock import os +import re +import subprocess -from pelican.generators import ArticlesGenerator +from pelican.generators import ArticlesGenerator, LessCSSGenerator from pelican.settings import _DEFAULT_CONFIG -from .support import unittest +from .support import unittest, temporary_folder, skipIfNoExecutable CUR_DIR = os.path.dirname(__file__) @@ -46,3 +48,50 @@ class TestArticlesGenerator(unittest.TestCase): elif relfilepath == "article_without_category.rst": 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 + settings['LESS_COMPILER'] = 'lessc' + + # 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)) From 2b93d6d855f392327ea09bdfccd17075f3d8401e Mon Sep 17 00:00:00 2001 From: Meir Kriheli Date: Sat, 28 Apr 2012 03:04:58 +0300 Subject: [PATCH 07/10] Remove empty generate_context --- pelican/generators.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/pelican/generators.py b/pelican/generators.py index 16b4e434..4eecd9dc 100644 --- a/pelican/generators.py +++ b/pelican/generators.py @@ -420,9 +420,6 @@ class PdfGenerator(Generator): class LessCSSGenerator(Generator): """Compile less css files. This assumes we have `lessc` in our PATH.""" - def generate_context(self): - pass - def _compile(self, less_file, source_dir, dest_dir): base = os.path.relpath(less_file, source_dir) target = os.path.splitext( From f558389006e0f013dbc2f410ab6520cc1b2fbc66 Mon Sep 17 00:00:00 2001 From: Meir Kriheli Date: Sat, 28 Apr 2012 03:25:54 +0300 Subject: [PATCH 08/10] Remove redundant LESS_COMPILER setting --- docs/settings.rst | 6 +++--- pelican/__init__.py | 2 +- pelican/generators.py | 9 +++++++-- pelican/settings.py | 1 - tests/test_generators.py | 1 - 5 files changed, 11 insertions(+), 8 deletions(-) diff --git a/docs/settings.rst b/docs/settings.rst index 2a1cfe75..a3ac6606 100644 --- a/docs/settings.rst +++ b/docs/settings.rst @@ -82,9 +82,9 @@ Setting name (default value) What does it do? generated HTML, using the `Typogrify `_ library -`LESS_GENERATOR` (``FALSE``) Set to True if you want to enable compiling less - files. Requires installation of `less css`_. -`LESS_COMPILER` (``'/usr/bin/lessc'``) The path to less css compiler (`lessc`) +`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. diff --git a/pelican/__init__.py b/pelican/__init__.py index 1161f7e2..6b3d12fb 100644 --- a/pelican/__init__.py +++ b/pelican/__init__.py @@ -134,7 +134,7 @@ class Pelican(object): generators = [ArticlesGenerator, PagesGenerator, StaticGenerator] if self.settings['PDF_GENERATOR']: generators.append(PdfGenerator) - if self.settings['LESS_GENERATOR']: + if self.settings['LESS_GENERATOR']: # can be True or PATH to lessc generators.append(LessCSSGenerator) return generators diff --git a/pelican/generators.py b/pelican/generators.py index 4eecd9dc..37413ddb 100644 --- a/pelican/generators.py +++ b/pelican/generators.py @@ -418,7 +418,7 @@ class PdfGenerator(Generator): class LessCSSGenerator(Generator): - """Compile less css files. This assumes we have `lessc` in our PATH.""" + """Compile less css files.""" def _compile(self, less_file, source_dir, dest_dir): base = os.path.relpath(less_file, source_dir) @@ -433,13 +433,18 @@ class LessCSSGenerator(Generator): logger.error("Couldn't create the pdf output folder in " + target_dir) - cmd = ' '.join([self.settings['LESS_COMPILER'], less_file, target]) + cmd = ' '.join([self._lessc, less_file, target]) subprocess.call(cmd, shell=True) 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( diff --git a/pelican/settings.py b/pelican/settings.py index 8d8a7d75..771c72c6 100644 --- a/pelican/settings.py +++ b/pelican/settings.py @@ -68,7 +68,6 @@ _DEFAULT_CONFIG = {'PATH': '.', 'ARTICLE_PERMALINK_STRUCTURE': '', 'TYPOGRIFY': False, 'LESS_GENERATOR': False, - 'LESS_COMPILER': '/usr/bin/lessc', } diff --git a/tests/test_generators.py b/tests/test_generators.py index ce1b9e7e..ecaacfcd 100644 --- a/tests/test_generators.py +++ b/tests/test_generators.py @@ -68,7 +68,6 @@ class TestLessCSSGenerator(unittest.TestCase): settings = _DEFAULT_CONFIG.copy() settings['STATIC_PATHS'] = ['static'] settings['LESS_GENERATOR'] = True - settings['LESS_COMPILER'] = 'lessc' # we'll nest here for py < 2.7 compat with temporary_folder() as temp_content: From 0924a9dd736427ec6b5591170f9f306415e9cd4c Mon Sep 17 00:00:00 2001 From: Meir Kriheli Date: Sat, 28 Apr 2012 03:27:30 +0300 Subject: [PATCH 09/10] Not pdf, but less folder --- pelican/generators.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pelican/generators.py b/pelican/generators.py index 37413ddb..cc18e999 100644 --- a/pelican/generators.py +++ b/pelican/generators.py @@ -430,7 +430,7 @@ class LessCSSGenerator(Generator): try: os.makedirs(target_dir) except OSError: - logger.error("Couldn't create the pdf output folder in " + + logger.error("Couldn't create the less css output folder in " + target_dir) cmd = ' '.join([self._lessc, less_file, target]) From 17626b5474941259dcc4733e3efb1fe1e8b9f017 Mon Sep 17 00:00:00 2001 From: Meir Kriheli Date: Sat, 28 Apr 2012 03:46:43 +0300 Subject: [PATCH 10/10] Don't use shell=True --- pelican/generators.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/pelican/generators.py b/pelican/generators.py index cc18e999..86e48eb8 100644 --- a/pelican/generators.py +++ b/pelican/generators.py @@ -433,8 +433,7 @@ class LessCSSGenerator(Generator): logger.error("Couldn't create the less css output folder in " + target_dir) - cmd = ' '.join([self._lessc, less_file, target]) - subprocess.call(cmd, shell=True) + subprocess.call([self._lessc, less_file, target]) logger.info(u' [ok] compiled %s' % base) def generate_output(self, writer=None):