[web] use uiprops value to compile css transparently, handlig cache and reloading in debug mode
--- 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)
--- 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')
--- 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)
--- 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()
--- 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()):