CSS compile watchdog handles moved files
[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 processor.compile(theme=args.theme, sheets=args.sheets)
128
129 # Setting up watchdog. This code should be improved when we will have more than a compile option.
130 observer = None
131 if args.compile and args.watch:
132 observer = watchdog.observers.Observer()
133
134 for M in Mlist:
135 if args.watch and args.compile:
136 processor = css.Css(M)
137 processorArgs = {'theme': args.theme, 'sheets': args.sheets}
138 handler = LessWatcher(M, processor, processorArgs)
139 observer.schedule(handler, processor.getThemeLessPath(args.theme), recursive=True)
140 logging.info('Watchdog set up on %s/%s, waiting for changes...' % (M.get('identifier'), args.theme))
141
142 if observer and args.compile and args.watch:
143 observer.start()
144
145 try:
146 while True:
147 time.sleep(1)
148 except KeyboardInterrupt:
149 observer.stop()
150 finally:
151 observer.join()
152
153
154 class LessWatcher(watchdog.events.FileSystemEventHandler):
155
156 _processor = None
157 _args = None
158 _ext = '.less'
159 _M = None
160
161 def __init__(self, M, processor, args):
162 super(self.__class__, self).__init__()
163 self._M = M
164 self._processor = processor
165 self._args = args
166
167 def on_modified(self, event):
168 self.process(event)
169
170 def on_moved(self, event):
171 self.process(event)
172
173 def process(self, event):
174 if event.is_directory:
175 return
176 elif not event.src_path.endswith(self._ext):
177 return
178
179 filename = event.src_path.replace(self._processor.getThemeLessPath(self._args['theme']), '').strip('/')
180 logging.info('[%s] Changes detected in %s!' % (self._M.get('identifier'), filename))
181 self._processor.compile(**self._args)