Add support for grunt css from 29 onwards
[mdk.git] / mdk / commands / css.py
1 #!/usr/bin/env python
2 # -*- coding: utf-8 -*-
3
4 """
5 Moodle Development Kit
6
7 Copyright (c) 2014 Frédéric Massart - FMCorz.net
8
9 This program is free software: you can redistribute it and/or modify
10 it under the terms of the GNU General Public License as published by
11 the Free Software Foundation, either version 3 of the License, or
12 (at your option) any later version.
13
14 This program is distributed in the hope that it will be useful,
15 but WITHOUT ANY WARRANTY; without even the implied warranty of
16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 GNU General Public License for more details.
18
19 You should have received a copy of the GNU General Public License
20 along with this program. If not, see <http://www.gnu.org/licenses/>.
21
22 http://github.com/FMCorz/mdk
23 """
24
25 import logging
26 import os
27 import time
28 import watchdog.events
29 import watchdog.observers
30 from .. import css
31 from ..command import Command
32
33
34 class CssCommand(Command):
35
36 _arguments = [
37 (
38 ['-c', '--compile'],
39 {
40 'action': 'store_true',
41 'dest': 'compile',
42 'help': 'compile the theme less files'
43 }
44 ),
45 (
46 ['-s', '--sheets'],
47 {
48 'action': 'store',
49 'dest': 'sheets',
50 'default': None,
51 'help': 'the sheets to work on without their extensions. When not specified, it is guessed from the less folder.',
52 'nargs': '+'
53 }
54 ),
55 (
56 ['-t', '--theme'],
57 {
58 'action': 'store',
59 'dest': 'theme',
60 'default': None,
61 'help': 'the theme to work on. The default is \'bootstrapbase\' but is ignored if we are in a theme folder.',
62 }
63 ),
64 (
65 ['-d', '--debug'],
66 {
67 'action': 'store_true',
68 'dest': 'debug',
69 'help': 'produce an unminified debugging version with source maps'
70 }
71 ),
72 (
73 ['-w', '--watch'],
74 {
75 'action': 'store_true',
76 'dest': 'watch',
77 'help': 'watch the directory'
78 }
79 ),
80 (
81 ['names'],
82 {
83 'default': None,
84 'help': 'name of the instances',
85 'metavar': 'names',
86 'nargs': '*'
87 }
88 )
89 ]
90 _description = 'Wrapper for CSS functions'
91
92 def run(self, args):
93
94 Mlist = self.Wp.resolveMultiple(args.names)
95 if len(Mlist) < 1:
96 raise Exception('No instances to work on. Exiting...')
97
98 # Resolve the theme folder we are in.
99 if not args.theme:
100 mpath = os.path.join(Mlist[0].get('path'), 'theme')
101 cwd = os.path.realpath(os.path.abspath(os.getcwd()))
102 if cwd.startswith(mpath):
103 candidate = cwd.replace(mpath, '').strip('/')
104 while True:
105 (head, tail) = os.path.split(candidate)
106 if not head and tail:
107 # Found the theme.
108 args.theme = tail
109 logging.info('You are in the theme \'%s\', using that.' % (args.theme))
110 break
111 elif not head and not tail:
112 # Nothing, let's leave.
113 break
114 candidate = head
115
116 # We have not found anything, falling back on the default.
117 if not args.theme:
118 args.theme = 'bootstrapbase'
119
120 for M in Mlist:
121 if args.compile:
122 logging.info('Compiling theme \'%s\' on %s' % (args.theme, M.get('identifier')))
123 processor = css.Css(M)
124 processor.setDebug(args.debug)
125 if args.debug:
126 processor.setCompiler('lessc')
127 elif M.branch_compare(29, '<'):
128 # Grunt was only introduced for 2.9.
129 processor.setCompiler('recess')
130
131 processor.compile(theme=args.theme, sheets=args.sheets)
132
133 # Setting up watchdog. This code should be improved when we will have more than a compile option.
134 observer = None
135 if args.compile and args.watch:
136 observer = watchdog.observers.Observer()
137
138 for M in Mlist:
139 if args.watch and args.compile:
140 processor = css.Css(M)
141 processorArgs = {'theme': args.theme, 'sheets': args.sheets}
142 handler = LessWatcher(M, processor, processorArgs)
143 observer.schedule(handler, processor.getThemeLessPath(args.theme), recursive=True)
144 logging.info('Watchdog set up on %s/%s, waiting for changes...' % (M.get('identifier'), args.theme))
145
146 if observer and args.compile and args.watch:
147 observer.start()
148
149 try:
150 while True:
151 time.sleep(1)
152 except KeyboardInterrupt:
153 observer.stop()
154 finally:
155 observer.join()
156
157
158 class LessWatcher(watchdog.events.FileSystemEventHandler):
159
160 _processor = None
161 _args = None
162 _ext = '.less'
163 _M = None
164
165 def __init__(self, M, processor, args):
166 super(self.__class__, self).__init__()
167 self._M = M
168 self._processor = processor
169 self._args = args
170
171 def on_modified(self, event):
172 self.process(event)
173
174 def on_moved(self, event):
175 self.process(event)
176
177 def process(self, event):
178 if event.is_directory:
179 return
180 elif not event.src_path.endswith(self._ext):
181 return
182
183 filename = event.src_path.replace(self._processor.getThemeLessPath(self._args['theme']), '').strip('/')
184 logging.info('[%s] Changes detected in %s!' % (self._M.get('identifier'), filename))
185 self._processor.compile(**self._args)