summaryrefslogtreecommitdiff
path: root/scripts/builder/frontendBuilder.py
Unidiff
Diffstat (limited to 'scripts/builder/frontendBuilder.py') (more/less context) (ignore whitespace changes)
-rw-r--r--scripts/builder/frontendBuilder.py398
1 files changed, 398 insertions, 0 deletions
diff --git a/scripts/builder/frontendBuilder.py b/scripts/builder/frontendBuilder.py
new file mode 100644
index 0000000..b796438
--- a/dev/null
+++ b/scripts/builder/frontendBuilder.py
@@ -0,0 +1,398 @@
1#!/usr/bin/python
2# -*- coding: UTF-8 -*-
3
4import sys, os, re
5import cssmin
6import jsmin
7import codecs
8import shutil
9import StringIO
10import urllib
11
12#from mercurial import ui, hg
13#from mercurial.node import hex
14from dulwich.repo import Repo
15
16import main
17
18
19
20class FrontendBuilder:
21
22 def __init__ (self, frontend, settings):
23 if '.' in frontend:
24 moduleComponents = frontend.split('.')
25 self.module = moduleComponents[0]
26 self.submodule = moduleComponents[1]
27 else:
28 self.module = frontend
29 self.submodule = frontend
30
31 self.settings = settings
32 self.projectDir = main.projectBaseDir()
33 self.processedFiles = {}
34
35
36 def mercurialRepositoryVersion (self):
37 repo = hg.repository(ui.ui(), self.projectDir)
38 context = repo['tip']
39 result = str(context)
40
41 return result
42
43
44 def gitRepositoryVersion (self):
45 repo = Repo(self.projectDir)
46 #if repo.is_dirty():
47 #print "WARNING: build run with dirty repository"
48 result = repo.refs['HEAD']
49
50 return result
51
52
53
54 def repositoryVersion (self):
55 cacheKey = 'repositoryVersion'
56 if not self.processedFiles.has_key(cacheKey):
57 #result = self.mercurialRepositoryVersion()
58 result = self.gitRepositoryVersion()
59 self.processedFiles[cacheKey] = result
60 else:
61 result = self.processedFiles[cacheKey]
62
63 return result
64
65
66 #def relativePath (self):
67 #return self.module
68 #
69
70 def log (self, message):
71 print "frontend [" + self.module + "]: " + message
72
73
74 def absolutePathForSourceFile (self, folder, basePath, file):
75 return folder + '/frontend/' + self.module + '/' + basePath + '/' + file
76
77
78 def absolutePathForTargetFile (self, folder, basePath, file):
79 return folder + '/' + self.module + '/' + basePath + '/' + file
80
81 def filterFiles (self, files):
82 result = []
83
84 for file in files:
85 if file.startswith('--'):
86 pass
87 else:
88 result.append(file)
89
90 return result
91
92
93 def copyResources (self, sourceFolder, destinationFolder, fileType):
94 for file in self.filterFiles(self.settings[fileType]):
95 src = self.absolutePathForSourceFile(sourceFolder, fileType, file)
96 dst = self.absolutePathForTargetFile(destinationFolder, fileType, file)
97 main.createFolder(os.path.dirname(dst))
98 shutil.copy2(src, dst)
99
100
101 def copyResourcesToTargetFolder (self, targetFolder):
102 self.copyResources(self.projectDir, targetFolder, 'css')
103 self.copyResources(self.projectDir, targetFolder, 'js')
104
105
106 def loadFilesContent (self, basePath, files):
107 result = ""
108
109 for file in self.filterFiles(files):
110 try:
111 fileHandler = codecs.open(self.absolutePathForSourceFile(self.projectDir, basePath, file), 'r', 'utf-8')
112 except:
113 print "FILE: " + file
114
115 result += fileHandler.read() + '\n'
116 fileHandler.close()
117
118 return result
119
120
121 def template (self):
122 processedFile = 'html_template'
123 if not self.processedFiles.has_key(processedFile):
124 self.processedFiles[processedFile] = self.loadFilesContent('html', ['index_template.html'])
125
126 return self.processedFiles[processedFile]
127
128
129 def cssminCompressor (self, css):
130 # package found here:
131 # - http://stackoverflow.com/questions/222581/python-script-for-minifying-css/2396777#2396777
132 # actual downloaded version: http://pypi.python.org/pypi/cssmin/0.1.4
133 return cssmin.cssmin(css)
134
135
136 def regexCssCompressor (self, css):
137 # http://stackoverflow.com/questions/222581/python-script-for-minifying-css/223689#223689
138
139 # remove comments - this will break a lot of hacks :-P
140 css = re.sub( r'\s*/\*\s*\*/', "$$HACK1$$", css ) # preserve IE<6 comment hack
141 css = re.sub( r'/\*[\s\S]*?\*/', "", css )
142 css = css.replace( "$$HACK1$$", '/**/' ) # preserve IE<6 comment hack
143
144 # url() doesn't need quotes
145 css = re.sub( r'url\((["\'])([^)]*)\1\)', r'url(\2)', css )
146
147 # spaces may be safely collapsed as generated content will collapse them anyway
148 css = re.sub( r'\s+', ' ', css )
149
150 # shorten collapsable colors: #aabbcc to #abc
151 css = re.sub( r'#([0-9a-f])\1([0-9a-f])\2([0-9a-f])\3(\s|;)', r'#\1\2\3\4', css )
152
153 # fragment values can loose zeros
154 css = re.sub( r':\s*0(\.\d+([cm]m|e[mx]|in|p[ctx]))\s*;', r':\1;', css )
155
156 for rule in re.findall( r'([^{]+){([^}]*)}', css ):
157
158 # we don't need spaces around operators
159 selectors = [re.sub( r'(?<=[\[\(>+=])\s+|\s+(?=[=~^$*|>+\]\)])', r'', selector.strip() ) for selector in rule[0].split( ',' )]
160
161 # order is important, but we still want to discard repetitions
162 properties = {}
163 porder = []
164 for prop in re.findall( '(.*?):(.*?)(;|$)', rule[1] ):
165 key = prop[0].strip().lower()
166 if key not in porder: porder.append( key )
167 properties[ key ] = prop[1].strip()
168
169 # output rule if it contains any declarations
170 if properties:
171 print "%s{%s}" % ( ','.join( selectors ), ''.join(['%s:%s;' % (key, properties[key]) for key in porder])[:-1] )
172
173 return css
174
175
176 def compressCSS (self, css):
177 self.log("compressing CSS")
178 #return self.regexCssCompressor(css)
179 return self.cssminCompressor(css)
180
181
182 #==========================================================================
183
184 def compressJS_jsmin (self, js):
185 self.log("compressing JS code")
186 original = StringIO.StringIO(js)
187 output = StringIO.StringIO()
188
189 jsMinifier = jsmin.JavascriptMinify()
190 jsMinifier.minify(original, output)
191
192 result = output.getvalue()
193
194 original.close()
195 output.close()
196
197 return result
198
199 def compressJS_closureCompiler (self, js):
200 #Googles Closure compiler
201 #java -jar compiler.jar --js=in1.js --js=in2.js ... --js_output_file=out.js
202
203 result = js
204
205 return result
206
207
208 def compressJS (self, js):
209 return self.compressJS_jsmin(js)
210 #return self.compressJS_closureCompiler(js)
211
212
213 #==========================================================================
214
215 def packBookmarklet (self, bookmakeletCode):
216 replacers = [
217 ('isLoginForm', 'ilf'),
218 ('findLoginForm', 'flf'),
219 ('findLoginForm', 'flf'),
220 ('formParameters', 'fp' ),
221 ('pageParameters', 'pp' ),
222 ('serializeJSON', 'sj' ),
223 ('reprString', 'rs' ),
224 ('logFormParameters', 'lfp'),
225 ('loadClipperzBookmarklet','lcb'),
226 ('loginForm', 'lf' ),
227 ('parameters', 'p' ),
228 ('inputElementValues', 'iev'),
229 ]
230 result = self.compressJS(bookmakeletCode)
231
232 result = re.sub('\n', ' ', result) #Fit all in a single line
233 # result = re.sub('\s+', ' ', result) #Collapse "redundant" spaces. WARNING: this could have some evil side effects on constant strings used inside to code!!
234 # result = re.sub('\s?([,\+=\(\)\{\};])\s?', '\\1', result)
235
236 for replacer in replacers:
237 result = re.sub(replacer[0], replacer[1], result)
238
239 # <!-- escaping required to handle the bookmarklet code within the javascript code -->
240 result = re.sub('\://', '%3a%2f%2f',result)
241 result = re.sub('/', '%2f', result)
242 # result = re.sub('"', '%22', result)
243 result = re.sub('"', '\\"', result)
244 result = re.sub('\"', '%22', result)
245 result = re.sub('\'', '%22', result)
246 result = re.sub('\\\\', '%5c', result)
247 result = result.strip()
248 result = 'javascript:' + result
249
250 # replacers = [
251 # ('aForm', '_1' ),
252 # ('inputFields', '_2' ),
253 # ('passwordFieldsFound','_3' ),
254 # ('aDocument', '_6' ),
255 # ('aLevel', '_7' ),
256 # # ('result', '_8' ),
257 # ('documentForms', '_9' ),
258 # ('iFrames', '_c' ),
259 # ('anInputElement', '_d' ),
260 # ('options', '_f' ),
261 # ('option', '_12'),
262 # ('aLoginForm', '_13'),
263 # # ('action', '_17'),
264 # ('radioValues', '_18'),
265 # ('radioValueName', '_19'),
266 # ('inputElement', '_1a'),
267 # ('elementValues', '_1b'),
268 # ('radioValue', '_1c'),
269 # ('values', '_1d'),
270 # ('objtype', '_21'),
271 # ('useKey', '_27'),
272 # ('bookmarkletDiv', '_28'),
273 # ('someParameters', '_29'),
274 # ('anException', '_2a'),
275 # ('newDiv', '_2b'),
276 # ('base_url', '_2c'),
277 # ('help_url', '_2d'),
278 # ('logo_image_url', '_2e'),
279 # ('background_image_url','_2f'),
280 # ('close_image_url', '_30'),
281 # #('bookmarklet_textarea','_31'),
282 # ('innerHTML', '_32'),
283 # ]
284 # for replacer in replacers:
285 # result = re.sub('([^\.])' + replacer[0], '\\1' + replacer[1], result)
286
287 # replacers = [
288 # ('headNode', '_1' ),
289 # ('clipperzScriptNode','_2' ),
290 # ]
291 # for replacer in replacers:
292 # result = re.sub('([^\.])' + replacer[0], '\\1' + replacer[1], result)
293
294 # result = re.sub(';', ';\n', result)
295
296 return result
297
298
299
300 def bookmarklet (self):
301 cacheKey = 'bookmarklet'
302 if not self.processedFiles.has_key(cacheKey):
303 result = 'bookmarklet="' + self.packBookmarklet(self.loadFilesContent('js', ['Bookmarklet.js'])) + '";bookmarklet_ie="' + self.packBookmarklet(self.loadFilesContent('js', ['Bookmarklet_IE.js'])) + '";'
304 self.processedFiles[cacheKey] = result
305 else:
306 result = self.processedFiles[cacheKey]
307
308 return result
309
310
311 def replaceTemplatePlaceholders (self, assemblyMode, pageTitle, copyright, css, code, version, versionType):
312 result = self.template()
313
314 result = result.replace('@page.title@', pageTitle, 1)
315 result = result.replace('@copyright@', copyright, 1)
316 result = result.replace('@css@', css, 1)
317 #result = result.replace('@bookmarklet@', bookmarklet,1)
318 result = result.replace('@application.version@', version, 1)
319 result = result.replace('@application.version.type@', versionType,1)
320 result = result.replace('@js_' + assemblyMode + '@', code, 1)
321
322 result = re.sub('@js_[^@]+@', '', result)
323
324 return result
325
326
327 def assembleCopyrightHeader (self):
328 processedFile = 'copyright'
329 if not self.processedFiles.has_key(processedFile):
330 #self.log("assembling copyright header")
331 copyrightValues = self.settings['copyright.values']
332 license = self.loadFilesContent('../../properties', ['license.txt'])
333 result = self.loadFilesContent('properties', ['creditsAndCopyrights.txt'])
334
335 result = re.sub('@clipperz.license@', license, result)
336 for key in copyrightValues:
337 result = re.sub('@'+key+'@', copyrightValues[key], result)
338
339 self.processedFiles[processedFile] = result
340
341 return self.processedFiles[processedFile]
342
343
344 def cssTagsForFiles (self, basePath, files):
345 #<link rel="stylesheet" type="text/css" href="./css/reset-min.css" />
346 return '\n'.join(map(lambda file: '<link rel="stylesheet" type="text/css" href="./' + basePath + '/' + file + '" />', files))
347
348
349 def cssTagForContent (self, content):
350 return '<style type="text/css">' + content + '</style>'
351
352
353 def scriptTagsForFiles (self, basePath, files):
354 #<script type='text/javascript' src='./js/src/bookmarklet.js'></script>
355 return '\n'.join(map(lambda file: '<script type="text/javascript" src="./' + basePath + '/' + file + '"></script>', files))
356
357
358 def scriptTagForContent (self, content):
359 return '<script>' + content + '</script>'
360
361
362 def assembleVersion (self, assemblyMode, pageTitle, copyright, css, js, version, versionType):
363 cacheKey = version + "-" + versionType
364 if not self.processedFiles.has_key(cacheKey):
365 result = self.replaceTemplatePlaceholders(assemblyMode, pageTitle, copyright, css, js, version, versionType)
366 self.processedFiles[cacheKey] = result
367 else:
368 result = self.processedFiles[cacheKey]
369
370 #self.log("# cacheKey:\n" + result)
371 return result
372
373
374 def assemble (self, assemblyMode='INSTALL', versionType='LIVE'):
375 pageTitle = "Clipperz - " + self.module
376 if versionType != 'LIVE':
377 pageTitle += " [" + versionType + " - " + assemblyMode +"]"
378
379 if assemblyMode == 'INSTALL':
380 css= self.cssTagForContent(self.compressCSS(self.loadFilesContent('css', self.settings['css'])))
381 js= self.scriptTagForContent(self.bookmarklet() + '\n' + self.compressJS(self.loadFilesContent('js', self.settings['js'])))
382 else:
383 css= self.cssTagsForFiles('css', self.filterFiles(self.settings['css']))
384 js= self.scriptTagForContent(self.bookmarklet()) + '\n' + self.scriptTagsForFiles('js', self.filterFiles(self.settings['js']))
385
386 return self.assembleVersion(
387 assemblyMode= assemblyMode,
388 pageTitle = pageTitle,
389 copyright = self.assembleCopyrightHeader(),
390 css = css,
391 js = js,
392 version = self.repositoryVersion(),
393 versionType = versionType
394 )
395
396
397
398