From d7ea652f0f1f410ec0a89d428e539328f2a755a0 Mon Sep 17 00:00:00 2001 From: Frederic Massart Date: Tue, 18 Feb 2014 14:29:34 +0800 Subject: [PATCH] Improved CSS command to watch directories --- README.md | 12 ++++-- extra/bash_completion | 2 +- lib/commands/css.py | 107 +++++++++++++++++++++++++++++++++++++++++++++++++- lib/css.py | 35 +++++++++++++---- requirements.txt | 2 + 5 files changed, 146 insertions(+), 12 deletions(-) create mode 100644 requirements.txt diff --git a/README.md b/README.md index 63706aa..1056bf0 100644 --- a/README.md +++ b/README.md @@ -52,12 +52,18 @@ Manual installation cd /opt sudo git clone git://github.com/FMCorz/mdk.git moodle-sdk -### 2. Make executable and accessible +### 2. Install the dependencies + +You will need the tool [pip](http://www.pip-installer.org/en/latest/installing.html) to install the packages required by Python. + + sudo pip install -r /opt/moodle-sdk/requirements.txt + +### 3. Make executable and accessible sudo chmod +x /opt/moodle-sdk/mdk.py sudo ln -s /opt/moodle-sdk/mdk.py /usr/local/bin/mdk -### 3. Set up the basics +### 4. Set up the basics Assuming that you are using Apache, which is set up to serve the files from /var/www, leave the default values as they are in `mdk init`, except for your remote and the database passwords. @@ -65,7 +71,7 @@ Assuming that you are using Apache, which is set up to serve the files from /var sudo ln -s ~/www /var/www/m sudo mdk init -### 4. Done +### 5. Done Try the following command to create a typical Stable Master instance (this will take some time because the cache is still empty): diff --git a/extra/bash_completion b/extra/bash_completion index 4a2cd6a..c0af8d6 100644 --- a/extra/bash_completion +++ b/extra/bash_completion @@ -108,7 +108,7 @@ function _mdk() { fi ;; css) - OPTS="--compile" + OPTS="--compile --sheets --theme --watch" ;; fix) if [[ "$PREV" == "-n" || "$PREV" == "--name" ]]; then diff --git a/lib/commands/css.py b/lib/commands/css.py index 41381f2..5cac58c 100644 --- a/lib/commands/css.py +++ b/lib/commands/css.py @@ -23,6 +23,10 @@ http://github.com/FMCorz/mdk """ import logging +import os +import time +import watchdog.events +import watchdog.observers from lib.command import Command from lib import css @@ -39,6 +43,33 @@ class CssCommand(Command): } ), ( + ['-s', '--sheets'], + { + 'action': 'store', + 'dest': 'sheets', + 'default': None, + 'help': 'the sheets to work on without their extensions. When not specified, it is guessed from the less folder.', + 'nargs': '+' + } + ), + ( + ['-t', '--theme'], + { + 'action': 'store', + 'dest': 'theme', + 'default': None, + 'help': 'the theme to work on. The default is \'bootstrapbase\' but is ignored if we are in a theme folder.', + } + ), + ( + ['-w', '--watch'], + { + 'action': 'store_true', + 'dest': 'watch', + 'help': 'watch the directory' + } + ), + ( ['names'], { 'default': None, @@ -56,7 +87,81 @@ class CssCommand(Command): if len(Mlist) < 1: raise Exception('No instances to work on. Exiting...') + # Resolve the theme folder we are in. + if not args.theme: + mpath = os.path.join(Mlist[0].get('path'), 'theme') + cwd = os.path.realpath(os.path.abspath(os.getcwd())) + if cwd.startswith(mpath): + candidate = cwd.replace(mpath, '').strip('/') + while True: + (head, tail) = os.path.split(candidate) + if not head and tail: + # Found the theme. + args.theme = tail + logging.info('You are in the theme \'%s\', using that.' % (args.theme)) + break + elif not head and not tail: + # Nothing, let's leave. + break + candidate = head + + # We have not found anything, falling back on the default. + if not args.theme: + args.theme = 'bootstrapbase' + for M in Mlist: if args.compile: + logging.info('Compiling theme \'%s\' on %s' % (args.theme, M.get('identifier'))) + processor = css.Css(M) + processor.compile(theme=args.theme, sheets=args.sheets) + + # Setting up watchdog. This code should be improved when we will have more than a compile option. + observer = None + if args.compile and args.watch: + observer = watchdog.observers.Observer() + + for M in Mlist: + if args.watch and args.compile: processor = css.Css(M) - processor.compile() + processorArgs = {'theme': args.theme, 'sheets': args.sheets} + handler = LessWatcher(M, processor, processorArgs) + observer.schedule(handler, processor.getThemeLessPath(args.theme), recursive=True) + logging.info('Watchdog set up on %s/%s, waiting for changes...' % (M.get('identifier'), args.theme)) + + if observer and args.compile and args.watch: + observer.start() + + try: + while True: + time.sleep(1) + except KeyboardInterrupt: + observer.stop() + finally: + observer.join() + + +class LessWatcher(watchdog.events.FileSystemEventHandler): + + _processor = None + _args = None + _ext = '.less' + _M = None + + def __init__(self, M, processor, args): + super(self.__class__, self).__init__() + self._M = M + self._processor = processor + self._args = args + + def on_modified(self, event): + self.process(event) + + def process(self, event): + if event.is_directory: + return + elif not event.src_path.endswith(self._ext): + return + + filename = event.src_path.replace(self._processor.getThemeLessPath(self._args['theme']), '').strip('/') + logging.info('[%s] Changes detected in %s!' % (self._M.get('identifier'), filename)) + self._processor.compile(**self._args) diff --git a/lib/css.py b/lib/css.py index ad92a40..9bf57e7 100644 --- a/lib/css.py +++ b/lib/css.py @@ -38,15 +38,27 @@ class Css(object): def __init__(self, M): self._M = M - def compile(self, theme='bootstrapbase', sheets=['moodle', 'editor']): + def compile(self, theme='bootstrapbase', sheets=None): """Compile LESS sheets contained within a theme""" - cwd = os.path.join(self._M.get('path'), 'theme', theme, 'less') - if not os.path.isdir(cwd): + source = self.getThemeLessPath(theme) + dest = self.getThemeCssPath(theme) + if not os.path.isdir(source): raise Exception('Unknown theme %s, or less directory not found' % (theme)) - source = os.path.join(self._M.get('path'), 'theme', theme, 'less') - dest = os.path.join(self._M.get('path'), 'theme', theme, 'style') + if not sheets: + # Guess the sheets from the theme less folder. + sheets = [] + for candidate in os.listdir(source): + if os.path.isfile(os.path.join(source, candidate)) and candidate.endswith('.less'): + sheets.append(os.path.splitext(candidate)[0]) + elif type(sheets) != list: + sheets = [sheets] + + if len(sheets) < 1: + logging.warning('Could not find any sheets') + return False + hadErrors = False for name in sheets: @@ -59,17 +71,26 @@ class Css(object): continue try: - compiler = Recess(cwd, os.path.join(source, sheet), os.path.join(dest, destSheet)) + compiler = Recess(source, os.path.join(source, sheet), os.path.join(dest, destSheet)) compiler.execute() except CssCompileFailed: logging.warning('Failed compilation of %s' % (sheet)) hadErrors = True continue else: - logging.info('Compiled %s' % (sheet)) + logging.info('Compiled %s to %s' % (sheet, destSheet)) return not hadErrors + def getThemeCssPath(self, theme): + return os.path.join(self.getThemePath(theme), 'style') + + def getThemeLessPath(self, theme): + return os.path.join(self.getThemePath(theme), 'less') + + def getThemePath(self, theme): + return os.path.join(self._M.get('path'), 'theme', theme) + class Compiler(object): """LESS compiler abstract""" diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..350bf3e --- /dev/null +++ b/requirements.txt @@ -0,0 +1,2 @@ +keyring +watchdog -- 2.11.0