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