diff --git a/.gitignore b/.gitignore
index dd77799..62103ed 100644
--- a/.gitignore
+++ b/.gitignore
@@ -3,8 +3,9 @@
*.pyc
*_flymake
.coverage
-.sass-cache/
.ropeproject/
+.sass-cache/
+.tox/
nose.cfg
MANIFEST
_build/
diff --git a/.travis.yml b/.travis.yml
index 7493fd0..61eb826 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,7 +1,7 @@
language: python
python:
- "2.7"
- - "2.6"
+ - "3.3"
before_install:
# Dependencies to build PIL
- sudo apt-get update -qq
diff --git a/README.rst b/README.rst
index 4dedc16..1ca4854 100644
--- a/README.rst
+++ b/README.rst
@@ -18,7 +18,7 @@ The idea behind Sigal is to ease the use of the javascript librairies like
`galleria`_. These librairies do a great job to display the images, Sigal does
what is missing: resize images, create thumbnails, generate html pages.
-Sigal is currently compatible only with Python 2.
+Sigal is compatible with Python 2.7 and 3.3.
Links :
diff --git a/docs/changelog.rst b/docs/changelog.rst
index 9faf201..9cf35d6 100644
--- a/docs/changelog.rst
+++ b/docs/changelog.rst
@@ -2,6 +2,13 @@
Changelog
===========
+Version 0.6.dev
+~~~~~~~~~~~~~~~
+
+Released on 2013-xx-xx.
+
+- Add support for Python 3.3
+
Version 0.5.1
~~~~~~~~~~~~~
diff --git a/setup.py b/setup.py
index f591bba..2f2adb1 100644
--- a/setup.py
+++ b/setup.py
@@ -22,8 +22,10 @@ with open('docs/changelog.rst') as f:
# Load package meta from the pkgmeta module without loading the package.
pkgmeta = {}
-execfile(os.path.join(os.path.dirname(__file__), 'sigal', 'pkgmeta.py'),
- pkgmeta)
+pkgmeta_file = os.path.join(os.path.dirname(__file__), 'sigal', 'pkgmeta.py')
+with open(pkgmeta_file) as f:
+ code = compile(f.read(), 'pkgmeta.py', 'exec')
+ exec(code, pkgmeta)
setup(
name='sigal',
@@ -46,8 +48,10 @@ setup(
'Environment :: Console',
'License :: OSI Approved :: MIT License',
'Operating System :: OS Independent',
- 'Programming Language :: Python :: 2.6',
+ 'Programming Language :: Python :: 2',
'Programming Language :: Python :: 2.7',
+ 'Programming Language :: Python :: 3',
+ 'Programming Language :: Python :: 3.3',
'Topic :: Internet :: WWW/HTTP',
'Topic :: Multimedia :: Graphics :: Viewers',
'Topic :: Software Development :: Libraries :: Python Modules',
diff --git a/sigal/__init__.py b/sigal/__init__.py
index c4e1ec4..eb60a00 100644
--- a/sigal/__init__.py
+++ b/sigal/__init__.py
@@ -30,9 +30,9 @@ sigal is yet another python script to prepare a static gallery of images:
* generate html pages.
"""
-from __future__ import absolute_import
+from __future__ import absolute_import, print_function
-import codecs
+import io
import logging
import os
import sys
@@ -72,9 +72,9 @@ def init():
from pkg_resources import resource_string
conf = resource_string(__name__, 'templates/sigal.conf.py')
- with codecs.open('sigal.conf.py', 'w', 'utf-8') as f:
+ with io.open('sigal.conf.py', 'w', 'utf-8') as f:
f.write(conf)
- print "Sample config file created: sigal.conf.py"
+ print("Sample config file created: sigal.conf.py")
@arg('source', nargs='?', help='Input directory')
@@ -97,7 +97,7 @@ def build(source, destination, debug=False, verbose=False, force=False,
settings_file = config or _DEFAULT_CONFIG_FILE
if not os.path.isfile(settings_file):
- logger.error("Settings file not found (%s)", settings_file)
+ logger.error("Settings file not found: %s", settings_file)
sys.exit(1)
settings = read_settings(settings_file)
@@ -108,8 +108,7 @@ def build(source, destination, debug=False, verbose=False, force=False,
logger.info("Input : %s", settings['source'])
if not settings['source'] or not os.path.isdir(settings['source']):
- logger.error("Input directory '%s' does not exist.",
- settings['source'])
+ logger.error("Input directory not found: %s", settings['source'])
sys.exit(1)
logger.info("Output : %s", settings['destination'])
@@ -138,7 +137,7 @@ def serve(path):
Handler = SimpleHTTPServer.SimpleHTTPRequestHandler
httpd = SocketServer.TCPServer(("", PORT), Handler, False)
- print " * Running on http://127.0.0.1:%i/" % PORT
+ print(" * Running on http://127.0.0.1:{}/".format(PORT))
try:
httpd.allow_reuse_address = True
@@ -146,13 +145,14 @@ def serve(path):
httpd.server_activate()
httpd.serve_forever()
except KeyboardInterrupt:
- print '\nAll done!'
+ print('\nAll done!')
else:
sys.stderr.write("The '%s' directory doesn't exist.\n" % path)
def main():
- parser = ArghParser(description='Simple static gallery generator.',
- version=__version__)
+ parser = ArghParser(description='Simple static gallery generator.')
parser.add_commands([init, build, serve])
+ parser.add_argument('--version', action='version',
+ version='%(prog)s {}'.format(__version__))
parser.dispatch()
diff --git a/sigal/compat.py b/sigal/compat.py
new file mode 100644
index 0000000..ad1002b
--- /dev/null
+++ b/sigal/compat.py
@@ -0,0 +1,14 @@
+# -*- coding: utf-8 -*-
+
+import sys
+
+PY2 = sys.version_info[0] == 2
+
+if not PY2:
+ text_type = str
+ string_types = (str,)
+ unichr = chr
+else:
+ text_type = unicode # NOQA
+ string_types = (str, unicode) # NOQA
+ unichr = unichr
diff --git a/sigal/gallery.py b/sigal/gallery.py
index 2b4b9a7..2be0f4e 100644
--- a/sigal/gallery.py
+++ b/sigal/gallery.py
@@ -38,9 +38,13 @@ from PIL import Image as PILImage
import sigal.image
import sigal.video
+from . import compat
from .settings import get_thumb
from .writer import Writer
+if not compat.PY2:
+ from functools import reduce
+
DESCRIPTION_FILE = "index.md"
# Label with for the progress bar. The max value is 48 character = 80 - 32 for
@@ -69,7 +73,7 @@ class PathsDb(object):
# basepath must to be a unicode string so that os.walk will return
# unicode dirnames and filenames. If basepath is a str, we must
# convert it to unicode.
- if isinstance(path, str):
+ if compat.PY2 and isinstance(path, str):
enc = locale.getpreferredencoding()
self.basepath = path.decode(enc)
else:
@@ -96,12 +100,18 @@ class PathsDb(object):
}
# get information for each directory
- for path, dirnames, filenames in os.walk(self.basepath, followlinks=True):
+ for path, dirnames, filenames in os.walk(self.basepath,
+ followlinks=True):
relpath = os.path.relpath(path, self.basepath)
# sort images and sub-albums by name
- filenames.sort(cmp=locale.strcoll)
- dirnames.sort(cmp=locale.strcoll)
+ if compat.PY2:
+ filenames.sort(cmp=locale.strcoll)
+ dirnames.sort(cmp=locale.strcoll)
+ else:
+ from functools import cmp_to_key
+ filenames.sort(key=cmp_to_key(locale.strcoll))
+ dirnames.sort(key=cmp_to_key(locale.strcoll))
self.db['paths_list'].append(relpath)
self.db[relpath] = {
diff --git a/sigal/image.py b/sigal/image.py
index 3ea069a..f505521 100644
--- a/sigal/image.py
+++ b/sigal/image.py
@@ -40,6 +40,8 @@ from pilkit.processors import Transpose, Adjust
from pilkit.utils import save_image
from datetime import datetime
+from . import compat
+
def _has_exif_tags(img):
return hasattr(img, 'info') and 'exif' in img.info
@@ -91,8 +93,7 @@ def generate_image(source, outname, settings, options=None):
outformat = img.format or original_format or 'JPEG'
logger.debug(u'Save resized image to {0} ({1})'.format(outname, outformat))
- with open(outname, 'w') as fp:
- save_image(img, fp, outformat, options=options, autoconvert=True)
+ save_image(img, outname, outformat, options=options, autoconvert=True)
def generate_thumbnail(source, outname, box, fit=True, options=None):
@@ -109,8 +110,7 @@ def generate_thumbnail(source, outname, box, fit=True, options=None):
outformat = img.format or original_format or 'JPEG'
logger.debug(u'Save thumnail image: {0} ({1})'.format(outname, outformat))
- with open(outname, 'w') as fp:
- save_image(img, fp, outformat, options=options, autoconvert=True)
+ save_image(img, outname, outformat, options=options, autoconvert=True)
def add_copyright(img, text):
@@ -185,7 +185,12 @@ def get_exif_tags(source):
# Remove null bytes at the end if necessary
date = data['DateTimeOriginal'].rsplit('\x00')[0]
dt = datetime.strptime(date, '%Y:%m:%d %H:%M:%S')
- simple['datetime'] = dt
+ dt = dt.strftime('%A, %d. %B %Y')
+
+ if compat.PY2:
+ simple['datetime'] = dt.decode('utf8')
+ else:
+ simple['datetime'] = dt
except (ValueError, TypeError) as e:
msg = u'Could not parse DateTimeOriginal of %s: %s' % (source, e)
logger.warning(msg)
diff --git a/sigal/pkgmeta.py b/sigal/pkgmeta.py
index af3426a..0c76bc3 100644
--- a/sigal/pkgmeta.py
+++ b/sigal/pkgmeta.py
@@ -1,8 +1,8 @@
# -*- coding:utf-8 -*-
__title__ = 'sigal'
-__author__ = u"Simon Conseil"
-__version__ = "0.5.1"
-__license__ = "MIT"
+__author__ = 'Simon Conseil'
+__version__ = '0.6.0-dev'
+__license__ = 'MIT'
__url__ = 'https://github.com/saimn/sigal'
__all__ = ['__title__', '__author__', '__version__', '__license__', '__url__']
diff --git a/sigal/settings.py b/sigal/settings.py
index 32a18b8..b3af3e2 100644
--- a/sigal/settings.py
+++ b/sigal/settings.py
@@ -60,6 +60,7 @@ def get_thumb(settings, filename):
"""Return the path to the thumb.
examples:
+ >>> default_settings = create_settings()
>>> get_thumb(default_settings, "bar/foo.jpg")
"bar/thumbnails/foo.jpg"
>>> get_thumb(default_settings, "bar/foo.png")
@@ -96,8 +97,12 @@ def read_settings(filename=None):
if filename:
logger.debug("Settings file: %s", filename)
tempdict = {}
- execfile(filename, tempdict)
- settings.update((k, v) for k, v in tempdict.iteritems()
+
+ with open(filename) as f:
+ code = compile(f.read(), filename, 'exec')
+ exec(code, tempdict)
+
+ settings.update((k, v) for k, v in tempdict.items()
if k not in ['__builtins__'])
# Make the paths relative to the settings file
diff --git a/sigal/themes/colorbox/templates/index.html b/sigal/themes/colorbox/templates/index.html
index f338175..bad0c7c 100644
--- a/sigal/themes/colorbox/templates/index.html
+++ b/sigal/themes/colorbox/templates/index.html
@@ -78,7 +78,7 @@
{% if media.big %} data-big="{{ media.big }}"{% endif %}
{% if media.exif %}
{% if media.exif.datetime %}
- data-date=", {{ media.exif.datetime.strftime('%d %B %Y') }}"
+ data-date=", {{ media.exif.datetime }}"
{% endif %}
{% endif %}
{%- endmacro %}
diff --git a/sigal/themes/galleria/templates/index.html b/sigal/themes/galleria/templates/index.html
index 1f22380..979a918 100644
--- a/sigal/themes/galleria/templates/index.html
+++ b/sigal/themes/galleria/templates/index.html
@@ -67,7 +67,7 @@
{% if media.exif.exposure %}Exposure: {{ media.exif.exposure }}, {% endif %}
{% if media.exif.fstop %}Fstop: {{ media.exif.fstop }}{% endif %}
{% if media.exif.datetime %}
-
Date: {{ media.exif.datetime.strftime('%A, %d. %B %Y') }}
+
Date: {{ media.exif.datetime }}
{% endif %}
{% endif %}
{%- endmacro %}
diff --git a/sigal/video.py b/sigal/video.py
index 5ef4eb0..c3b2aad 100644
--- a/sigal/video.py
+++ b/sigal/video.py
@@ -27,6 +27,8 @@ import re
import shutil
import sigal.image
+from . import compat
+
def vid_size(source):
"""Returns the dimensions of the video"""
@@ -35,6 +37,10 @@ def vid_size(source):
stdout=subprocess.PIPE,
stderr=subprocess.PIPE)
stdout, stderr = p.communicate()
+
+ if not compat.PY2:
+ stderr = stderr.decode('utf8')
+
match = pattern.search(stderr)
if match:
x, y = int(match.groups()[0]), int(match.groups()[1])
diff --git a/tox.ini b/tox.ini
new file mode 100644
index 0000000..c313f00
--- /dev/null
+++ b/tox.ini
@@ -0,0 +1,14 @@
+[tox]
+envlist = py27,py33
+
+[testenv]
+commands = py.test
+deps =
+
+[testenv:py27]
+deps =
+ pytest
+
+[testenv:py33]
+deps =
+ pytest