# HG changeset patch # User Sylvain Thénault # Date 1272622537 -7200 # Node ID 4467ed43d97da6d8614230c66deafb149c760787 # Parent f7fdb5dd82f63a8734aa96911da1a08d4b9f61fd [web] use uiprops value to compile css transparently, handlig cache and reloading in debug mode diff -r f7fdb5dd82f6 -r 4467ed43d97d etwist/server.py --- a/etwist/server.py Fri Apr 30 12:14:15 2010 +0200 +++ b/etwist/server.py Fri Apr 30 12:15:37 2010 +0200 @@ -210,6 +210,7 @@ """Render a page from the root resource""" # reload modified files in debug mode if self.config.debugmode: + self.config.uiprops.reload_if_needed() self.appli.vreg.reload_if_needed() if self.config['profile']: # default profiler don't trace threads return self.render_request(request) diff -r f7fdb5dd82f6 -r 4467ed43d97d web/data/uiprops.py --- a/web/data/uiprops.py Fri Apr 30 12:14:15 2010 +0200 +++ b/web/data/uiprops.py Fri Apr 30 12:15:37 2010 +0200 @@ -1,57 +1,57 @@ """define default ui properties""" # CSS stylesheets to include systematically in HTML headers -STYLESHEETS = ['%s/cubicweb.reset.css' % datadir_url, - '%s/cubicweb.css' % datadir_url] -STYLESHEETS_IE = ['%s/cubicweb.ie.css' % datadir_url] -STYLESHEETS_PRINT = ['%s/cubicweb.print.css' % datadir_url] +STYLESHEETS = [data('cubicweb.reset.css'), + data('cubicweb.css')] +STYLESHEETS_IE = [data('cubicweb.ie.css')] +STYLESHEETS_PRINT = [data('cubicweb.print.css')] # Javascripts files to include systematically in HTML headers -JAVASCRIPTS = ['%s/jquery.js' % datadir_url, - '%s/jquery.corner.js' % datadir_url, - '%s/jquery.json.js' % datadir_url, - '%s/cubicweb.compat.js' % datadir_url, - '%s/cubicweb.python.js' % datadir_url, - '%s/cubicweb.htmlhelpers.js' % datadir_url] +JAVASCRIPTS = [data('jquery.js'), + data('jquery.corner.js'), + data('jquery.json.js'), + data('cubicweb.compat.js'), + data('cubicweb.python.js'), + data('cubicweb.htmlhelpers.js')] # where is installed fckeditor FCKEDITOR_PATH = '/usr/share/fckeditor/' # favicon and logo for the instance -FAVICON = '%s/favicon.ico' % datadir_url -LOGO = '%s/logo.png' % datadir_url +FAVICON = data('favicon.ico') +LOGO = data('logo.png') # rss logo (link to get the rss view of a selection) -RSS_LOGO = '%s/rss.png' % datadir_url -RSS_LOGO_16 = '%s/feed-icon16x16.png' % datadir_url -RSS_LOGO_32 = '%s/feed-icon32x32.png' % datadir_url +RSS_LOGO = data('rss.png') +RSS_LOGO_16 = data('feed-icon16x16.png') +RSS_LOGO_32 = data('feed-icon32x32.png') # XXX cleanup resources below, some of them are probably not used # (at least entity types icons...) # images -HELP = '%s/help.png' % datadir_url -SEARCH_GO = '%s/go.png' % datadir_url -PUCE_UP = '%s/puce_up.png' % datadir_url -PUCE_DOWN = '%s/puce_down.png' % datadir_url +HELP = data('help.png') +SEARCH_GO = data('go.png') +PUCE_UP = data('puce_up.png') +PUCE_DOWN = data('puce_down.png') # button icons -OK_ICON = '%s/ok.png' % datadir_url -CANCEL_ICON = '%s/cancel.png' % datadir_url -APPLY_ICON = '%s/plus.png' % datadir_url -TRASH_ICON = '%s/trash_can_small.png' % datadir_url +OK_ICON = data('ok.png') +CANCEL_ICON = data('cancel.png') +APPLY_ICON = data('plus.png') +TRASH_ICON = data('trash_can_small.png') # icons for entity types -BOOKMARK_ICON = '%s/icon_bookmark.gif' % datadir_url -EMAILADDRESS_ICON = '%s/icon_emailaddress.gif' % datadir_url -EUSER_ICON = '%s/icon_euser.gif' % datadir_url -STATE_ICON = '%s/icon_state.gif' % datadir_url +BOOKMARK_ICON = data('icon_bookmark.gif') +EMAILADDRESS_ICON = data('icon_emailaddress.gif') +EUSER_ICON = data('icon_euser.gif') +STATE_ICON = data('icon_state.gif') # other icons -CALENDAR_ICON = '%s/calendar.gif' % datadir_url -CANCEL_EMAIL_ICON = '%s/sendcancel.png' % datadir_url -SEND_EMAIL_ICON = '%s/sendok.png' % datadir_url -DOWNLOAD_ICON = '%s/download.gif' % datadir_url -UPLOAD_ICON = '%s/upload.gif' % datadir_url -GMARKER_ICON = '%s/gmap_blue_marker.png' % datadir_url -UP_ICON = '%s/up.gif' % datadir_url +CALENDAR_ICON = data('calendar.gif') +CANCEL_EMAIL_ICON = data('sendcancel.png') +SEND_EMAIL_ICON = data('sendok.png') +DOWNLOAD_ICON = data('download.gif') +UPLOAD_ICON = data('upload.gif') +GMARKER_ICON = data('gmap_blue_marker.png') +UP_ICON = data('up.gif') diff -r f7fdb5dd82f6 -r 4467ed43d97d web/propertysheet.py --- a/web/propertysheet.py Fri Apr 30 12:14:15 2010 +0200 +++ b/web/propertysheet.py Fri Apr 30 12:15:37 2010 +0200 @@ -19,13 +19,81 @@ __docformat__ = "restructuredtext en" +import re +import os +import os.path as osp + + class PropertySheet(dict): - def __init__(self, **context): + def __init__(self, cache_directory, **context): + self._cache_directory = cache_directory self._context = context + self.reset() context['sheet'] = self + self._percent_rgx = re.compile('%(?!\()') + + def reset(self): + self.clear() + self._ordered_propfiles = [] + self._propfile_mtime = {} + self._sourcefile_mtime = {} + self._cache = {} def load(self, fpath): scriptglobals = self._context.copy() scriptglobals['__file__'] = fpath execfile(fpath, scriptglobals, self) + self._propfile_mtime[fpath] = os.stat(fpath)[-2] + self._ordered_propfiles.append(fpath) + def need_reload(self): + for fpath, mtime in self._propfile_mtime.iteritems(): + if os.stat(fpath)[-2] > mtime: + return True + for rid, (directory, mtime) in self._cache.items(): + if os.stat(osp.join(directory, rid))[-2] > mtime: + del self._cache[rid] + return False + + def reload(self): + ordered_files = self._ordered_propfiles + self.reset() + for fpath in ordered_files: + self.load(fpath) + + def reload_if_needed(self): + if self.need_reload(): + self.reload() + + def process_resource(self, rdirectory, rid): + try: + return self._cache[rid][0] + except KeyError: + cachefile = osp.join(self._cache_directory, rid) + self.debug('caching processed css %s/%s into %s', + rdirectory, rid, cachefile) + rcachedir = osp.dirname(cachefile) + if not osp.exists(rcachedir): + os.makedirs(rcachedir) + sourcefile = osp.join(rdirectory, rid) + content = file(sourcefile).read() + # XXX replace % not followed by a paren by %% to avoid having to do + # this in the source css file ? + try: + content = self.compile(content) + except ValueError, ex: + self.error("can't process %s/%s: %s", rdirectory, rid, ex) + else: + stream = file(cachefile, 'w') + stream.write(content) + stream.close() + rdirectory = self._cache_directory + self._cache[rid] = (rdirectory, os.stat(sourcefile)[-2]) + return rdirectory + + def compile(self, content): + return self._percent_rgx.sub('%%', content) % self + +from cubicweb.web import LOGGER +from logilab.common.logging_ext import set_log_methods +set_log_methods(PropertySheet, LOGGER) diff -r f7fdb5dd82f6 -r 4467ed43d97d web/test/unittest_propertysheet.py --- a/web/test/unittest_propertysheet.py Fri Apr 30 12:14:15 2010 +0200 +++ b/web/test/unittest_propertysheet.py Fri Apr 30 12:15:37 2010 +0200 @@ -6,7 +6,7 @@ class PropertySheetTC(TestCase): def test(self): - ps = PropertySheet(datadir_url='http://cwtest.com') + ps = PropertySheet(None, datadir_url='http://cwtest.com') ps.load(join(DATADIR, 'sheet1.py')) ps.load(join(DATADIR, 'sheet2.py')) # defined by sheet1 @@ -18,7 +18,8 @@ # defined by sheet1, extended by sheet2 self.assertEquals(ps['stylesheets'], ['http://cwtest.com/cubicweb.css', 'http://cwtest.com/mycube.css']) - + self.assertEquals(ps.compile('a {bgcolor: %(bgcolor)s; size: 1%;}'), + 'a {bgcolor: #FFFFFF; size: 1%;}') if __name__ == '__main__': unittest_main() diff -r f7fdb5dd82f6 -r 4467ed43d97d web/webconfig.py --- a/web/webconfig.py Fri Apr 30 12:14:15 2010 +0200 +++ b/web/webconfig.py Fri Apr 30 12:15:37 2010 +0200 @@ -272,6 +272,9 @@ path = [self.apphome] + self.cubes_path() + [join(self.shared_dir())] for directory in path: if exists(join(directory, rdirectory, rid)): + if rdirectory == 'data' and rid.endswith('.css'): + return self.uiprops.process_resource(join(directory, rdirectory), + rid) return join(directory, rdirectory) def locate_all_files(self, rid, rdirectory='wdoc'): @@ -309,7 +312,10 @@ def _build_ui_properties(self): # self.datadir_url[:-1] to remove trailing / from cubicweb.web.propertysheet import PropertySheet - self.uiprops = PropertySheet(datadir_url=self.datadir_url[:-1]) + self.uiprops = PropertySheet( + join(self.appdatahome, 'uicache'), + data=lambda x: self.datadir_url + x, + datadir_url=self.datadir_url[:-1]) libuiprops = join(self.shared_dir(), 'data', 'uiprops.py') self.uiprops.load(libuiprops) for path in reversed([self.apphome] + self.cubes_path()):