b1f86dacda2bf269094ecd86ef2761693e2a0178
[mdk.git] / mdk / 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 from .tools import process
28 from .config import Conf
29
30 C = Conf()
31
32
33 class Css(object):
34 """Class wrapping CSS related functions"""
35
36 _M = None
37
38 _debug = False
39 _compiler = 'recess'
40
41 def __init__(self, M):
42 self._M = M
43
44 def setCompiler(self, compiler):
45 self._compiler = compiler
46
47 def setDebug(self, debug):
48 self._debug = debug
49
50 def compile(self, theme='bootstrapbase', sheets=None):
51 """Compile LESS sheets contained within a theme"""
52
53 source = self.getThemeLessPath(theme)
54 dest = self.getThemeCssPath(theme)
55 if not os.path.isdir(source):
56 raise Exception('Unknown theme %s, or less directory not found' % (theme))
57
58 if not sheets:
59 # Guess the sheets from the theme less folder.
60 sheets = []
61 for candidate in os.listdir(source):
62 if os.path.isfile(os.path.join(source, candidate)) and candidate.endswith('.less'):
63 sheets.append(os.path.splitext(candidate)[0])
64 elif type(sheets) != list:
65 sheets = [sheets]
66
67 if len(sheets) < 1:
68 logging.warning('Could not find any sheets')
69 return False
70
71 hadErrors = False
72
73 for name in sheets:
74 sheet = name + '.less'
75 destSheet = name + '.css'
76
77 if not os.path.isfile(os.path.join(source, sheet)):
78 logging.warning('Could not find file %s' % (sheet))
79 hadErrors = True
80 continue
81
82 try:
83 if self._compiler == 'recess':
84 compiler = Recess(source, os.path.join(source, sheet), os.path.join(dest, destSheet))
85 elif self._compiler == 'lessc':
86 compiler = Lessc(self.getThemeDir(), os.path.join(source, sheet), os.path.join(dest, destSheet))
87
88 compiler.setDebug(self._debug)
89
90 compiler.execute()
91 except CssCompileFailed:
92 logging.warning('Failed compilation of %s' % (sheet))
93 hadErrors = True
94 continue
95 else:
96 logging.info('Compiled %s to %s' % (sheet, destSheet))
97
98 return not hadErrors
99
100 def getThemeCssPath(self, theme):
101 return os.path.join(self.getThemePath(theme), 'style')
102
103 def getThemeLessPath(self, theme):
104 return os.path.join(self.getThemePath(theme), 'less')
105
106 def getThemeDir(self):
107 return os.path.join(self._M.get('path'), 'theme')
108
109 def getThemePath(self, theme):
110 return os.path.join(self.getThemeDir(), theme)
111
112
113 class Compiler(object):
114 """LESS compiler abstract"""
115
116 _compress = True
117 _debug = False
118 _cwd = None
119 _source = None
120 _dest = None
121
122 def __init__(self, cwd, source, dest):
123 self._cwd = cwd
124 self._source = source
125 self._dest = dest
126
127 def execute(self):
128 raise Exception('Compiler does not implement execute() method')
129
130 def setCompress(self, compress):
131 self._compress = compress
132
133 def setDebug(self, debug):
134 self._debug = debug
135
136
137 class Recess(Compiler):
138 """Recess compiler"""
139
140 def execute(self):
141 executable = C.get('recess')
142 if not executable:
143 raise Exception('Could not find executable path')
144
145 cmd = [executable, self._source, '--compile']
146
147 if self._compress:
148 cmd.append('--compress')
149
150 (code, out, err) = process(cmd, self._cwd)
151 if code != 0 or len(out) == 0:
152 raise CssCompileFailed('Error during compile')
153
154 # Saving to destination
155 with open(self._dest, 'w') as f:
156 f.write(out)
157
158
159 class Lessc(Compiler):
160 """Lessc compiler"""
161
162 def execute(self):
163 executable = C.get('lessc')
164 if not executable:
165 raise Exception('Could not find executable path')
166
167 cmd = [executable]
168
169 sourcePath = os.path.relpath(self._source, self._cwd)
170 sourceDir = os.path.dirname(sourcePath)
171
172 if self._debug:
173 cmd.append('--source-map-rootpath=' + sourceDir)
174 cmd.append('--source-map-map-inline')
175 self.setCompress(False)
176
177 if self._compress:
178 cmd.append('--compress')
179
180 # Append the source and destination.
181 cmd.append(sourcePath)
182 cmd.append(os.path.relpath(self._dest, self._cwd))
183
184 (code, out, err) = process(cmd, self._cwd)
185 if code != 0 or len(out) != 0:
186 raise CssCompileFailed('Error during compile')
187
188
189 class CssCompileFailed(Exception):
190 pass