@@ -0,0 +1,24 @@ | |||||
Hyde is primarily developed and maintained by [Lakshmi Vyasarajan][1]. The new | |||||
version of Hyde is sponsored by [Flowplayer][2] and [Tero Piirainen][3]. | |||||
This version would not exist without the contributions for the | |||||
[original hyde project][4]. | |||||
Contributors | |||||
============= | |||||
- @rfk | |||||
* Bug fixes | |||||
* Pyfs publisher with mimetype and etags support | |||||
- @tinnet | |||||
* Bug fixes (Default template, Syntax template tag) | |||||
[1]: http://twitter.com/lakshmivyas | |||||
[2]: http://flowplayer.org | |||||
[3]: http://cloudpanic.com | |||||
[4]: http://github.com/lakshmivyas/hyde |
@@ -1,3 +1,12 @@ | |||||
Version 0.8.1 | |||||
============= | |||||
Thanks to @rfk. | |||||
* Updated to use nose 1.0 | |||||
* Bug fixes to Less CSS | |||||
* PyFS publisher with mimetypes and etags support. | |||||
Version 0.8 | Version 0.8 | ||||
============== | ============== | ||||
@@ -1,4 +1,4 @@ | |||||
Version 0.8 | |||||
Version 0.8.1 | |||||
A brand new **hyde** | A brand new **hyde** | ||||
==================== | ==================== | ||||
@@ -112,8 +112,17 @@ Next Steps | |||||
- Text Compressor (CSS, JS, HTML) ✓ | - Text Compressor (CSS, JS, HTML) ✓ | ||||
- Image optimizer ✓ | - Image optimizer ✓ | ||||
Links | |||||
----- | |||||
1. `Changelog`_ | |||||
2. `Authors`_ | |||||
.. _hyde: https://github.com/lakshmivyas/hyde | .. _hyde: https://github.com/lakshmivyas/hyde | ||||
.. _here: http://hyde.github.com | .. _here: http://hyde.github.com | ||||
.. _Hyde Documentation: https://github.com/hyde/docs | .. _Hyde Documentation: https://github.com/hyde/docs | ||||
.. _Cloudpanic: https://github.com/tipiirai/cloudpanic | .. _Cloudpanic: https://github.com/tipiirai/cloudpanic | ||||
.. _Ringce: https://github.com/lakshmivyas/ringce/tree/v3.0 | |||||
.. _Ringce: https://github.com/lakshmivyas/ringce/tree/v3.0 | |||||
.. _Authors: https://github.com/hyde/hyde/blob/master/AUTHORS | |||||
.. _Changelog: https://github.com/hyde/hyde/blob/master/CHANGELOG.rst |
@@ -9,7 +9,7 @@ Jinja2==2.5.5 | |||||
pyquery==0.6.1 | pyquery==0.6.1 | ||||
unittest2==0.5.1 | unittest2==0.5.1 | ||||
mock==0.7.0b4 | mock==0.7.0b4 | ||||
nose==0.11.4 | |||||
nose==1.0.0 | |||||
pep8==0.6.1 | pep8==0.6.1 | ||||
pylint==0.22.0 | pylint==0.22.0 | ||||
pysmell==0.7.3 | pysmell==0.7.3 |
@@ -34,7 +34,7 @@ class LessCSSPlugin(CLTransformer): | |||||
""" | """ | ||||
if not resource.source_file.kind == 'less': | if not resource.source_file.kind == 'less': | ||||
return | |||||
return text | |||||
import_finder = re.compile( | import_finder = re.compile( | ||||
'^\\s*@import\s+(?:\'|\")([^\'\"]*)(?:\'|\")\s*\;\s*$', | '^\\s*@import\s+(?:\'|\")([^\'\"]*)(?:\'|\")\s*\;\s*$', | ||||
re.MULTILINE) | re.MULTILINE) | ||||
@@ -0,0 +1,99 @@ | |||||
""" | |||||
Contains classes and utilities that help publishing a hyde website to | |||||
a filesystem using PyFilesystem FS objects. | |||||
This publisher provides an easy way to publish to FTP, SFTP, WebDAV or other | |||||
servers by specifying a PyFS filesystem URL. For example, the following | |||||
are valid URLs that can be used with this publisher: | |||||
ftp://my.server.com/~username/my_blog/ | |||||
dav:https://username:password@my.server.com/path/to/my/site | |||||
""" | |||||
import getpass | |||||
import hashlib | |||||
from hyde.fs import File, Folder | |||||
from hyde.publisher import Publisher | |||||
from hyde.util import getLoggerWithNullHandler | |||||
logger = getLoggerWithNullHandler('hyde.ext.publishers.pyfs') | |||||
from fs.osfs import OSFS | |||||
from fs.path import pathjoin | |||||
from fs.opener import fsopendir | |||||
class PyFS(Publisher): | |||||
def initialize(self, settings): | |||||
self.settings = settings | |||||
self.url = settings.url | |||||
self.check_mtime = getattr(settings,"check_mtime",False) | |||||
self.check_etag = getattr(settings,"check_etag",False) | |||||
if self.check_etag and not isinstance(self.check_etag,basestring): | |||||
raise ValueError("check_etag must name the etag algorithm") | |||||
self.prompt_for_credentials() | |||||
self.fs = fsopendir(self.url) | |||||
def prompt_for_credentials(self): | |||||
credentials = {} | |||||
if "%(username)s" in self.url: | |||||
print "Username: ", | |||||
credentials["username"] = raw_input().strip() | |||||
if "%(password)s" in self.url: | |||||
credentials["password"] = getpass.getpass("Password: ") | |||||
if credentials: | |||||
self.url = self.url % credentials | |||||
def publish(self): | |||||
super(PyFS, self).publish() | |||||
deploy_fs = OSFS(self.site.config.deploy_root_path.path) | |||||
for (dirnm,local_filenms) in deploy_fs.walk(): | |||||
logger.info("Making directory: %s",dirnm) | |||||
self.fs.makedir(dirnm,allow_recreate=True) | |||||
remote_fileinfos = self.fs.listdirinfo(dirnm,files_only=True) | |||||
# Process each local file, to see if it needs updating. | |||||
for filenm in local_filenms: | |||||
filepath = pathjoin(dirnm,filenm) | |||||
# Try to find an existing remote file, to compare metadata. | |||||
for (nm,info) in remote_fileinfos: | |||||
if nm == filenm: | |||||
break | |||||
else: | |||||
info = {} | |||||
# Skip it if the etags match | |||||
if self.check_etag and "etag" in info: | |||||
with deploy_fs.open(filepath,"rb") as f: | |||||
local_etag = self._calculate_etag(f) | |||||
if info["etag"] == local_etag: | |||||
logger.info("Skipping file [etag]: %s",filepath) | |||||
continue | |||||
# Skip it if the mtime is more recent remotely. | |||||
if self.check_mtime and "modified_time" in info: | |||||
local_mtime = deploy_fs.getinfo(filepath)["modified_time"] | |||||
if info["modified_time"] > local_mtime: | |||||
logger.info("Skipping file [mtime]: %s",filepath) | |||||
continue | |||||
# Upload it to the remote filesystem. | |||||
logger.info("Uploading file: %s",filepath) | |||||
with deploy_fs.open(filepath,"rb") as f: | |||||
self.fs.setcontents(filepath,f) | |||||
# Process each remote file, to see if it needs deleting. | |||||
for (filenm,info) in remote_fileinfos: | |||||
filepath = pathjoin(dirnm,filenm) | |||||
if filenm not in local_filenms: | |||||
logger.info("Removing file: %s",filepath) | |||||
self.fs.remove(filepath) | |||||
def _calculate_etag(self,f): | |||||
hasher = getattr(hashlib,self.check_etag.lower())() | |||||
data = f.read(1024*64) | |||||
while data: | |||||
hasher.update(data) | |||||
data = f.read(1024*64) | |||||
return hasher.hexdigest() | |||||
@@ -131,6 +131,9 @@ def syntax(env, value, lexer=None, filename=None): | |||||
code = pygments.highlight(value, pyg, formatter) | code = pygments.highlight(value, pyg, formatter) | ||||
code = code.replace('\n\n', '\n \n').replace('\n', '<br />') | code = code.replace('\n\n', '\n \n').replace('\n', '<br />') | ||||
caption = filename if filename else pyg.name | caption = filename if filename else pyg.name | ||||
if hasattr(env.config, 'syntax'): | |||||
if not getattr(env.config.syntax, 'use_figure', True): | |||||
return Markup(code) | |||||
return Markup( | return Markup( | ||||
'<figure class="code">%s<figcaption>%s</figcaption></figure>\n\n' | '<figure class="code">%s<figcaption>%s</figcaption></figure>\n\n' | ||||
% (code, caption)) | % (code, caption)) | ||||
@@ -67,6 +67,10 @@ class Expando(object): | |||||
for k, v in d.iteritems(): | for k, v in d.iteritems(): | ||||
if isinstance(v, Expando): | if isinstance(v, Expando): | ||||
d[k] = v.to_dict() | d[k] = v.to_dict() | ||||
elif isinstance(v, (tuple, list, set, frozenset)): | |||||
seq = type(v) | |||||
d[k] = seq(item.to_dict() if isinstance(item, Expando) | |||||
else item for item in v) | |||||
return d | return d | ||||
@@ -85,6 +85,7 @@ def test_typogrify(): | |||||
""" | """ | ||||
t = Jinja2Template(JINJA2.path) | t = Jinja2Template(JINJA2.path) | ||||
t.configure(None) | t.configure(None) | ||||
t.env.filters['dateformat'] = dateformat | |||||
html = t.render(source, {}).strip() | html = t.render(source, {}).strip() | ||||
assert html == u'One <span class="amp">&</span> two' | assert html == u'One <span class="amp">&</span> two' | ||||
@@ -110,6 +111,7 @@ def test_spaceless(): | |||||
""" | """ | ||||
t = Jinja2Template(JINJA2.path) | t = Jinja2Template(JINJA2.path) | ||||
t.configure(None) | t.configure(None) | ||||
t.env.filters['dateformat'] = dateformat | |||||
html = t.render(source, {}).strip() | html = t.render(source, {}).strip() | ||||
expected = u""" | expected = u""" | ||||
<html><body><ul><li> | <html><body><ul><li> | ||||
@@ -146,6 +148,7 @@ def test_markdown_with_extensions(): | |||||
c = Config(JINJA2.path, config_dict=dict(markdown=dict(extensions=['headerid']))) | c = Config(JINJA2.path, config_dict=dict(markdown=dict(extensions=['headerid']))) | ||||
s.config = c | s.config = c | ||||
t.configure(s) | t.configure(s) | ||||
t.env.filters['dateformat'] = dateformat | |||||
html = t.render(source, {}).strip() | html = t.render(source, {}).strip() | ||||
assert html == u'<h3 id="heading_3">Heading 3</h3>' | assert html == u'<h3 id="heading_3">Heading 3</h3>' | ||||
@@ -162,6 +165,7 @@ def test_line_statements(): | |||||
c = Config(JINJA2.path, config_dict=dict(markdown=dict(extensions=['headerid']))) | c = Config(JINJA2.path, config_dict=dict(markdown=dict(extensions=['headerid']))) | ||||
s.config = c | s.config = c | ||||
t.configure(s) | t.configure(s) | ||||
t.env.filters['dateformat'] = dateformat | |||||
html = t.render(source, {}).strip() | html = t.render(source, {}).strip() | ||||
assert html == u'<h3 id="heading_3">Heading 3</h3>' | assert html == u'<h3 id="heading_3">Heading 3</h3>' | ||||
@@ -184,6 +188,7 @@ def test_line_statements_with_config(): | |||||
s = Site(JINJA2.path) | s = Site(JINJA2.path) | ||||
s.config = Config(JINJA2.path, config_dict=yaml.load(config)) | s.config = Config(JINJA2.path, config_dict=yaml.load(config)) | ||||
t.configure(s) | t.configure(s) | ||||
t.env.filters['dateformat'] = dateformat | |||||
html = t.render(source, {}).strip() | html = t.render(source, {}).strip() | ||||
assert html == u'<h3 id="heading_3">Heading 3</h3>' | assert html == u'<h3 id="heading_3">Heading 3</h3>' | ||||
@@ -221,6 +226,7 @@ class TestJinjaTemplate(object): | |||||
def test_depends(self): | def test_depends(self): | ||||
t = Jinja2Template(JINJA2.path) | t = Jinja2Template(JINJA2.path) | ||||
t.configure(None) | t.configure(None) | ||||
t.env.filters['dateformat'] = dateformat | |||||
deps = list(t.get_dependencies('index.html')) | deps = list(t.get_dependencies('index.html')) | ||||
assert len(deps) == 2 | assert len(deps) == 2 | ||||
@@ -258,6 +264,7 @@ class TestJinjaTemplate(object): | |||||
gen = Generator(site) | gen = Generator(site) | ||||
gen.load_template_if_needed() | gen.load_template_if_needed() | ||||
template = gen.template | template = gen.template | ||||
template.env.filters['dateformat'] = dateformat | |||||
html = template.render(text, {}).strip() | html = template.render(text, {}).strip() | ||||
assert html | assert html | ||||
@@ -538,6 +545,7 @@ two: | |||||
""" | """ | ||||
t = Jinja2Template(JINJA2.path) | t = Jinja2Template(JINJA2.path) | ||||
t.configure(None) | t.configure(None) | ||||
t.env.filters['dateformat'] = dateformat | |||||
html = t.render(text, {}).strip() | html = t.render(text, {}).strip() | ||||
actual = PyQuery(html) | actual = PyQuery(html) | ||||
assert actual("ul").length == 2 | assert actual("ul").length == 2 | ||||
@@ -580,6 +588,7 @@ item_list: | |||||
""" | """ | ||||
t = Jinja2Template(JINJA2.path) | t = Jinja2Template(JINJA2.path) | ||||
t.configure(None) | t.configure(None) | ||||
t.env.filters['dateformat'] = dateformat | |||||
html = t.render(text, {}).strip() | html = t.render(text, {}).strip() | ||||
print html | print html | ||||
actual = PyQuery(html) | actual = PyQuery(html) | ||||
@@ -3,4 +3,4 @@ | |||||
Handles hyde version | Handles hyde version | ||||
TODO: Use fabric like versioning scheme | TODO: Use fabric like versioning scheme | ||||
""" | """ | ||||
__version__ = '0.8' | |||||
__version__ = '0.8.1' |
@@ -5,4 +5,4 @@ Markdown | |||||
MarkupSafe | MarkupSafe | ||||
smartypants | smartypants | ||||
-e git://github.com/hyde/typogrify.git#egg=typogrify | -e git://github.com/hyde/typogrify.git#egg=typogrify | ||||
Jinja2 | |||||
Jinja2 |