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.
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):
fi
;;
css)
- OPTS="--compile"
+ OPTS="--compile --sheets --theme --watch"
;;
fix)
if [[ "$PREV" == "-n" || "$PREV" == "--name" ]]; then
"""
import logging
+import os
+import time
+import watchdog.events
+import watchdog.observers
from lib.command import Command
from lib import css
}
),
(
+ ['-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,
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)
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:
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"""
--- /dev/null
+keyring
+watchdog