| @@ -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 | |||||