diff --git a/hyde/ext/plugins/images.py b/hyde/ext/plugins/images.py
new file mode 100644
index 0000000..86ad7f8
--- /dev/null
+++ b/hyde/ext/plugins/images.py
@@ -0,0 +1,86 @@
+# -*- coding: utf-8 -*-
+"""
+Contains classes to handle images related things
+"""
+
+from hyde.plugin import Plugin
+
+import re
+import Image
+from BeautifulSoup import BeautifulSoup
+
+class ImageSizerPlugin(Plugin):
+ """
+ Each HTML page is modified to add width and height for images if
+ they are not already specified.
+ """
+
+ def __init__(self, site):
+ super(ImageSizerPlugin, self).__init__(site)
+ self.cache = {}
+
+ def text_resource_complete(self, resource, text):
+ """
+ When the resource is generated, search for img tag and specify
+ their sizes.
+ """
+ try:
+ mode = self.site.config.mode
+ except AttributeError:
+ mode = "production"
+
+ if not resource.source_file.kind == 'html':
+ return
+
+ if mode.startswith('dev'):
+ self.logger.debug("Skipping sizer in development mode.")
+ return
+
+ soup = BeautifulSoup(text)
+ for img in soup.findAll('img'):
+ if img.has_key('width') and img.has_key('height'):
+ continue
+ if not img.has_key('src'):
+ self.logger.warn("[%s] has an img tag without src attribute" % resource)
+ continue
+ if not img['src'] in self.cache:
+ if not re.match(r"(/[^/]|[^/]).*", img['src']):
+ # Not a local link
+ continue
+ if img['src'].startswith("/"):
+ # Absolute resource
+ path = img['src'].lstrip("/")
+ image = self.site.content.resource_from_relative_deploy_path(path)
+ else:
+ # Relative resource
+ path = resource.node.source_folder.child(img['src'])
+ image = self.site.content.resource_from_path(path)
+ if image is None:
+ self.logger.warn(
+ "[%s] has an unknown image" % resource)
+ continue
+ if image.source_file.kind not in ['png', 'jpg', 'jpeg', 'gif']:
+ self.logger.warn(
+ "[%s] has an img tag not linking to an image" % resource)
+ continue
+ # Now, get the size of the image
+ try:
+ self.cache[img['src']] = Image.open(image.path).size
+ except IOError:
+ self.logger.warn(
+ "Unable to process image [%s]" % image)
+ self.cache[img['src']] = (None, None)
+ continue
+ self.logger.debug("Image [%s] is %s" % (img['src'],
+ self.cache[img['src']]))
+ width, height = self.cache[img['src']]
+ if width is None:
+ continue
+ if img.has_key('width'):
+ height = int(img['width'])*height/width
+ width = int(img['width'])
+ elif img.has_key('height'):
+ width = int(img['height'])*width/height
+ height = int(img['height'])
+ img['height'], img['width'] = height, width
+ return unicode(soup)
diff --git a/hyde/tests/ext/test_images.py b/hyde/tests/ext/test_images.py
new file mode 100644
index 0000000..08f9c3b
--- /dev/null
+++ b/hyde/tests/ext/test_images.py
@@ -0,0 +1,84 @@
+# -*- coding: utf-8 -*-
+"""
+Use nose
+`$ pip install nose`
+`$ nosetests`
+"""
+from hyde.fs import File, Folder
+from hyde.generator import Generator
+from hyde.site import Site
+
+from pyquery import PyQuery
+
+TEST_SITE = File(__file__).parent.parent.child_folder('_test')
+IMAGE_SOURCE = File(__file__).parent.child_folder('optipng')
+IMAGE_NAME = "hyde-lt-b.png"
+IMAGE_SIZE = (538, 132)
+
+class TestImageSizer(object):
+
+ def setUp(self):
+ TEST_SITE.make()
+ TEST_SITE.parent.child_folder(
+ 'sites/test_jinja').copy_contents_to(TEST_SITE)
+ IMAGES = TEST_SITE.child_folder('content/media/img')
+ IMAGES.make()
+ IMAGE_SOURCE.copy_contents_to(IMAGES)
+
+ def tearDown(self):
+ TEST_SITE.delete()
+
+ def _generic_test_image(self, text):
+ site = Site(TEST_SITE)
+ site.config.mode = "production"
+ site.config.plugins = ['hyde.ext.plugins.images.ImageSizerPlugin']
+ tlink = File(site.content.source_folder.child('timg.html'))
+ tlink.write(text)
+ gen = Generator(site)
+ gen.generate_all()
+ f = File(site.config.deploy_root_path.child(tlink.name))
+ assert f.exists
+ html = f.read_all()
+ assert html
+ print html
+ return html
+
+ def test_size_image(self):
+ text = u"""
+
+""" % IMAGE_NAME
+ html = self._generic_test_image(text)
+ assert ' width="%d"' % IMAGE_SIZE[0] in html
+ assert ' height="%d"' % IMAGE_SIZE[1] in html
+
+ def test_size_image_relative(self):
+ text = u"""
+
+""" % IMAGE_NAME
+ html = self._generic_test_image(text)
+ assert ' width="%d"' % IMAGE_SIZE[0] in html
+ assert ' height="%d"' % IMAGE_SIZE[1] in html
+
+ def test_size_image_no_resize(self):
+ text = u"""
+
+""" % IMAGE_NAME
+ html = self._generic_test_image(text)
+ assert ' width="%d"' % IMAGE_SIZE[0] not in html
+ assert ' height="%d"' % IMAGE_SIZE[1] not in html
+
+ def test_size_image_size_proportional(self):
+ text = u"""
+
+""" % (IMAGE_NAME, IMAGE_SIZE[0]*2)
+ html = self._generic_test_image(text)
+ assert ' width="%d"' % (IMAGE_SIZE[0]*2) in html
+ assert ' height="%d"' % (IMAGE_SIZE[1]*2) in html
+
+ def test_size_image_not_exists(self):
+ text = u"""
+
+"""
+ html = self._generic_test_image(text)
+
+