@@ -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 | |||
============== | |||
@@ -1,4 +1,4 @@ | |||
Version 0.8 | |||
Version 0.8.1 | |||
A brand new **hyde** | |||
==================== | |||
@@ -112,8 +112,17 @@ Next Steps | |||
- Text Compressor (CSS, JS, HTML) ✓ | |||
- Image optimizer ✓ | |||
Links | |||
----- | |||
1. `Changelog`_ | |||
2. `Authors`_ | |||
.. _hyde: https://github.com/lakshmivyas/hyde | |||
.. _here: http://hyde.github.com | |||
.. _Hyde Documentation: https://github.com/hyde/docs | |||
.. _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 | |||
unittest2==0.5.1 | |||
mock==0.7.0b4 | |||
nose==0.11.4 | |||
nose==1.0.0 | |||
pep8==0.6.1 | |||
pylint==0.22.0 | |||
pysmell==0.7.3 |
@@ -34,7 +34,7 @@ class LessCSSPlugin(CLTransformer): | |||
""" | |||
if not resource.source_file.kind == 'less': | |||
return | |||
return text | |||
import_finder = re.compile( | |||
'^\\s*@import\s+(?:\'|\")([^\'\"]*)(?:\'|\")\s*\;\s*$', | |||
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 = code.replace('\n\n', '\n \n').replace('\n', '<br />') | |||
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( | |||
'<figure class="code">%s<figcaption>%s</figcaption></figure>\n\n' | |||
% (code, caption)) | |||
@@ -67,6 +67,10 @@ class Expando(object): | |||
for k, v in d.iteritems(): | |||
if isinstance(v, Expando): | |||
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 | |||
@@ -85,6 +85,7 @@ def test_typogrify(): | |||
""" | |||
t = Jinja2Template(JINJA2.path) | |||
t.configure(None) | |||
t.env.filters['dateformat'] = dateformat | |||
html = t.render(source, {}).strip() | |||
assert html == u'One <span class="amp">&</span> two' | |||
@@ -110,6 +111,7 @@ def test_spaceless(): | |||
""" | |||
t = Jinja2Template(JINJA2.path) | |||
t.configure(None) | |||
t.env.filters['dateformat'] = dateformat | |||
html = t.render(source, {}).strip() | |||
expected = u""" | |||
<html><body><ul><li> | |||
@@ -146,6 +148,7 @@ def test_markdown_with_extensions(): | |||
c = Config(JINJA2.path, config_dict=dict(markdown=dict(extensions=['headerid']))) | |||
s.config = c | |||
t.configure(s) | |||
t.env.filters['dateformat'] = dateformat | |||
html = t.render(source, {}).strip() | |||
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']))) | |||
s.config = c | |||
t.configure(s) | |||
t.env.filters['dateformat'] = dateformat | |||
html = t.render(source, {}).strip() | |||
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.config = Config(JINJA2.path, config_dict=yaml.load(config)) | |||
t.configure(s) | |||
t.env.filters['dateformat'] = dateformat | |||
html = t.render(source, {}).strip() | |||
assert html == u'<h3 id="heading_3">Heading 3</h3>' | |||
@@ -221,6 +226,7 @@ class TestJinjaTemplate(object): | |||
def test_depends(self): | |||
t = Jinja2Template(JINJA2.path) | |||
t.configure(None) | |||
t.env.filters['dateformat'] = dateformat | |||
deps = list(t.get_dependencies('index.html')) | |||
assert len(deps) == 2 | |||
@@ -258,6 +264,7 @@ class TestJinjaTemplate(object): | |||
gen = Generator(site) | |||
gen.load_template_if_needed() | |||
template = gen.template | |||
template.env.filters['dateformat'] = dateformat | |||
html = template.render(text, {}).strip() | |||
assert html | |||
@@ -538,6 +545,7 @@ two: | |||
""" | |||
t = Jinja2Template(JINJA2.path) | |||
t.configure(None) | |||
t.env.filters['dateformat'] = dateformat | |||
html = t.render(text, {}).strip() | |||
actual = PyQuery(html) | |||
assert actual("ul").length == 2 | |||
@@ -580,6 +588,7 @@ item_list: | |||
""" | |||
t = Jinja2Template(JINJA2.path) | |||
t.configure(None) | |||
t.env.filters['dateformat'] = dateformat | |||
html = t.render(text, {}).strip() | |||
print html | |||
actual = PyQuery(html) | |||
@@ -3,4 +3,4 @@ | |||
Handles hyde version | |||
TODO: Use fabric like versioning scheme | |||
""" | |||
__version__ = '0.8' | |||
__version__ = '0.8.1' |
@@ -5,4 +5,4 @@ Markdown | |||
MarkupSafe | |||
smartypants | |||
-e git://github.com/hyde/typogrify.git#egg=typogrify | |||
Jinja2 | |||
Jinja2 |