├── hyde ├── __init__.py ├── ext │ ├── __init__.py │ ├── plugins │ │ ├── __init__.py │ │ ├── blog.py │ │ ├── depends.py │ │ ├── urls.py │ │ ├── languages.py │ │ └── vcs.py │ ├── publishers │ │ ├── __init__.py │ │ ├── ssh.py │ │ ├── dvcs.py │ │ └── pyfs.py │ └── templates │ │ └── __init__.py ├── lib │ ├── __init__.py │ └── pygments │ │ ├── __init__.py │ │ └── rst_directive.py ├── tests │ ├── __init__.py │ ├── ext │ │ ├── __init__.py │ │ ├── combine │ │ │ ├── script.1.js │ │ │ ├── script.2.js │ │ │ └── script.3.js │ │ ├── less │ │ │ ├── inc │ │ │ │ ├── vars.less │ │ │ │ ├── reset.css │ │ │ │ └── mixin.less │ │ │ ├── site.less │ │ │ └── expected-site.css │ │ ├── stylus │ │ │ ├── inc │ │ │ │ ├── vars.styl │ │ │ │ ├── reset.css │ │ │ │ └── mixin.styl │ │ │ ├── expected-site-compressed.css │ │ │ ├── site.styl │ │ │ └── expected-site.css │ │ ├── scss │ │ │ ├── inc │ │ │ │ ├── vars.scss │ │ │ │ ├── reset.css │ │ │ │ └── mixin.scss │ │ │ ├── expected-site.css │ │ │ └── site.scss │ │ ├── requirejs │ │ │ ├── main.js │ │ │ ├── app.js │ │ │ ├── lib │ │ │ │ ├── less.js │ │ │ │ └── more.js │ │ │ └── rjs.conf │ │ ├── images │ │ │ ├── portrait.jpg │ │ │ └── landscape.jpg │ │ ├── optipng │ │ │ └── hyde-lt-b.png │ │ ├── test_requirejs.py │ │ ├── test_syntext.py │ │ ├── test_less.py │ │ ├── test_optipng.py │ │ ├── test_scss.py │ │ ├── test_blockdown.py │ │ ├── test_depends.py │ │ ├── test_urlcleaner.py │ │ ├── test_textlinks.py │ │ ├── test_flattener.py │ │ ├── test_drafts.py │ │ ├── test_auto_extend.py │ │ ├── test_stylus.py │ │ ├── test_uglify.py │ │ ├── test_markings.py │ │ ├── test_combine.py │ │ └── test_paginator.py │ ├── ssp │ │ ├── ext │ │ │ ├── __init__.py │ │ │ └── banner.py │ │ └── site.yaml │ ├── sites │ │ ├── test_grouper │ │ │ ├── content │ │ │ │ ├── blog │ │ │ │ │ ├── overview.html │ │ │ │ │ ├── templating.html │ │ │ │ │ ├── installation.html │ │ │ │ │ ├── tags.html │ │ │ │ │ ├── plugins.html │ │ │ │ │ └── meta.yaml │ │ │ │ ├── media │ │ │ │ │ └── css │ │ │ │ │ │ └── site.css │ │ │ │ ├── favicon.ico │ │ │ │ ├── apple-touch-icon.png │ │ │ │ ├── robots.txt │ │ │ │ ├── about.html │ │ │ │ ├── 404.html │ │ │ │ └── crossdomain.xml │ │ │ ├── layout │ │ │ │ ├── root.html │ │ │ │ ├── blog │ │ │ │ │ └── post.html │ │ │ │ └── base.html │ │ │ ├── info.yaml │ │ │ ├── alternate.yaml │ │ │ └── site.yaml │ │ ├── test_jinja │ │ │ ├── layout │ │ │ │ ├── root.html │ │ │ │ ├── blog │ │ │ │ │ └── post.html │ │ │ │ └── base.html │ │ │ ├── content │ │ │ │ ├── media │ │ │ │ │ └── css │ │ │ │ │ │ └── site.css │ │ │ │ ├── favicon.ico │ │ │ │ ├── apple-touch-icon.png │ │ │ │ ├── robots.txt │ │ │ │ ├── about.html │ │ │ │ ├── blog │ │ │ │ │ └── 2010 │ │ │ │ │ │ └── december │ │ │ │ │ │ └── merry-christmas.html │ │ │ │ ├── 404.html │ │ │ │ └── crossdomain.xml │ │ │ ├── info.yaml │ │ │ ├── site.yaml │ │ │ └── alternate.yaml │ │ ├── test_paginator │ │ │ ├── layout │ │ │ │ └── root.j2 │ │ │ ├── content │ │ │ │ ├── pages_of_one.txt │ │ │ │ ├── pages_of_ten.txt │ │ │ │ ├── custom_file_pattern.txt │ │ │ │ └── blog │ │ │ │ │ ├── another-sad-post.html │ │ │ │ │ ├── sad-post.html │ │ │ │ │ └── angry-post.html │ │ │ └── site.yaml │ │ ├── test_sorter │ │ │ ├── content │ │ │ │ ├── media │ │ │ │ │ └── css │ │ │ │ │ │ └── site.css │ │ │ │ ├── favicon.ico │ │ │ │ ├── apple-touch-icon.png │ │ │ │ ├── robots.txt │ │ │ │ ├── about.html │ │ │ │ ├── blog │ │ │ │ │ ├── 2010 │ │ │ │ │ │ └── december │ │ │ │ │ │ │ └── merry-christmas.html │ │ │ │ │ ├── another-sad-post.html │ │ │ │ │ ├── sad-post.html │ │ │ │ │ └── angry-post.html │ │ │ │ ├── 404.html │ │ │ │ └── crossdomain.xml │ │ │ ├── layout │ │ │ │ ├── root.j2 │ │ │ │ └── tagged_posts.j2 │ │ │ └── site.yaml │ │ └── test_tagger │ │ │ ├── layout │ │ │ ├── root.j2 │ │ │ └── tagged_posts.j2 │ │ │ ├── site.yaml │ │ │ └── content │ │ │ └── blog │ │ │ ├── another-sad-post.html │ │ │ └── sad-post.html │ ├── templates │ │ └── jinja2 │ │ │ ├── helpers.html │ │ │ ├── layout.html │ │ │ └── index.html │ ├── README.rst │ ├── test_layout.py │ ├── util.py │ ├── test_initialize.py │ └── test_simple_copy.py ├── layouts │ ├── basic │ │ ├── README.markdown │ │ ├── content │ │ │ ├── media │ │ │ │ ├── meta.yaml │ │ │ │ ├── js │ │ │ │ │ └── libs │ │ │ │ │ │ └── meta.yaml │ │ │ │ ├── images │ │ │ │ │ ├── dark.png │ │ │ │ │ └── airport.png │ │ │ │ └── css │ │ │ │ │ └── syntax.css │ │ │ ├── blog │ │ │ │ ├── meta.yaml │ │ │ │ ├── atom.xml │ │ │ │ ├── index.html │ │ │ │ ├── excerpts.xml │ │ │ │ └── sad-post.html │ │ │ ├── favicon.ico │ │ │ ├── apple-touch-icon.png │ │ │ ├── about.html │ │ │ ├── portfolio │ │ │ │ └── index.html │ │ │ └── index.html │ │ ├── info.yaml │ │ ├── layout │ │ │ ├── devmode.j2 │ │ │ ├── listing.j2 │ │ │ ├── analytics.j2 │ │ │ ├── tagged_posts.j2 │ │ │ ├── macros.j2 │ │ │ ├── atom.j2 │ │ │ ├── blog.j2 │ │ │ └── base.j2 │ │ └── site.yaml │ └── starter │ │ ├── content │ │ ├── meta.yaml │ │ ├── advanced │ │ │ ├── meta.yaml │ │ │ ├── grouper.html │ │ │ ├── tagger.html │ │ │ ├── overview.html │ │ │ └── sorter.html │ │ ├── media │ │ │ ├── img │ │ │ │ └── background.jpg │ │ │ └── css │ │ │ │ └── style.css │ │ ├── index.html │ │ ├── about.html │ │ └── first-steps.html │ │ ├── layout │ │ ├── ga.j2 │ │ ├── macros.j2 │ │ └── base.j2 │ │ └── site.yaml ├── version.py ├── main.py ├── exceptions.py ├── layout.py ├── util.py ├── publisher.py └── template.py ├── resources ├── hyde-lt.png ├── hyde-logo.png └── hyde-lt-b.png ├── dev-req.txt ├── h ├── requirements.txt ├── .gitignore ├── MANIFEST.in ├── LICENSE └── README.rst /hyde/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /hyde/ext/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /hyde/lib/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /hyde/tests/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /hyde/ext/plugins/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /hyde/tests/ext/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /hyde/ext/publishers/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /hyde/ext/templates/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /hyde/lib/pygments/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /hyde/tests/ssp/ext/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /hyde/tests/ext/combine/script.1.js: -------------------------------------------------------------------------------- 1 | var a = 1 + 2; 2 | -------------------------------------------------------------------------------- /hyde/tests/ext/combine/script.2.js: -------------------------------------------------------------------------------- 1 | var b = a + 3; 2 | -------------------------------------------------------------------------------- /hyde/layouts/basic/README.markdown: -------------------------------------------------------------------------------- 1 | Basic template for hyde. -------------------------------------------------------------------------------- /hyde/layouts/basic/content/media/meta.yaml: -------------------------------------------------------------------------------- 1 | extends: false -------------------------------------------------------------------------------- /hyde/layouts/starter/content/meta.yaml: -------------------------------------------------------------------------------- 1 | level: basic 2 | 3 | -------------------------------------------------------------------------------- /hyde/tests/sites/test_grouper/content/blog/overview.html: -------------------------------------------------------------------------------- 1 | # Overview -------------------------------------------------------------------------------- /hyde/layouts/basic/content/media/js/libs/meta.yaml: -------------------------------------------------------------------------------- 1 | uses_template: false -------------------------------------------------------------------------------- /hyde/tests/ext/less/inc/vars.less: -------------------------------------------------------------------------------- 1 | @the-border: 1px; 2 | @base-color: #111; -------------------------------------------------------------------------------- /hyde/tests/sites/test_grouper/content/blog/templating.html: -------------------------------------------------------------------------------- 1 | # Templating -------------------------------------------------------------------------------- /hyde/tests/ext/stylus/inc/vars.styl: -------------------------------------------------------------------------------- 1 | the-border = 1px 2 | base-color = #111 3 | -------------------------------------------------------------------------------- /hyde/tests/sites/test_jinja/layout/root.html: -------------------------------------------------------------------------------- 1 | {% block all %}{% endblock all %} -------------------------------------------------------------------------------- /hyde/tests/ext/scss/inc/vars.scss: -------------------------------------------------------------------------------- 1 | $the-border: 1px; 2 | $base-color: #111; 3 | 4 | -------------------------------------------------------------------------------- /hyde/tests/sites/test_grouper/content/blog/installation.html: -------------------------------------------------------------------------------- 1 | # Installation Guide -------------------------------------------------------------------------------- /hyde/tests/sites/test_grouper/layout/root.html: -------------------------------------------------------------------------------- 1 | {% block all %}{% endblock all %} -------------------------------------------------------------------------------- /hyde/tests/ext/combine/script.3.js: -------------------------------------------------------------------------------- 1 | {% set five = 5 %} 2 | var c = a + {{ five }}; 3 | -------------------------------------------------------------------------------- /resources/hyde-lt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jakevdp/hyde/master/resources/hyde-lt.png -------------------------------------------------------------------------------- /dev-req.txt: -------------------------------------------------------------------------------- 1 | -r requirements.txt 2 | pyquery 3 | docutils 4 | mock 5 | nose 6 | pillow 7 | pyscss -------------------------------------------------------------------------------- /hyde/tests/sites/test_grouper/content/blog/tags.html: -------------------------------------------------------------------------------- 1 | === 2 | section: plugins 3 | === 4 | # Tags -------------------------------------------------------------------------------- /hyde/tests/sites/test_paginator/layout/root.j2: -------------------------------------------------------------------------------- 1 | {% block content -%} 2 | 3 | {%- endblock %} 4 | -------------------------------------------------------------------------------- /resources/hyde-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jakevdp/hyde/master/resources/hyde-logo.png -------------------------------------------------------------------------------- /resources/hyde-lt-b.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jakevdp/hyde/master/resources/hyde-lt-b.png -------------------------------------------------------------------------------- /hyde/layouts/basic/content/blog/meta.yaml: -------------------------------------------------------------------------------- 1 | extends: blog.j2 2 | default_block: post 3 | listable: true -------------------------------------------------------------------------------- /hyde/tests/ext/less/inc/reset.css: -------------------------------------------------------------------------------- 1 | * { 2 | border: 0; 3 | padding: 0; 4 | margin: 0; 5 | } -------------------------------------------------------------------------------- /hyde/tests/ext/stylus/inc/reset.css: -------------------------------------------------------------------------------- 1 | * { 2 | border: 0; 3 | padding: 0; 4 | margin: 0; 5 | } -------------------------------------------------------------------------------- /hyde/tests/sites/test_grouper/content/blog/plugins.html: -------------------------------------------------------------------------------- 1 | === 2 | section: plugins 3 | === 4 | # Plugins -------------------------------------------------------------------------------- /hyde/tests/ext/requirejs/main.js: -------------------------------------------------------------------------------- 1 | require(['lib/more'], function(more){ 2 | return more("require"); 3 | }); 4 | -------------------------------------------------------------------------------- /hyde/tests/ext/scss/inc/reset.css: -------------------------------------------------------------------------------- 1 | * { 2 | border: 0; 3 | padding: 0; 4 | margin: 0; 5 | } 6 | 7 | -------------------------------------------------------------------------------- /hyde/tests/sites/test_grouper/content/media/css/site.css: -------------------------------------------------------------------------------- 1 | body{ 2 | margin: 0 auto; 3 | width: 960px; 4 | } -------------------------------------------------------------------------------- /hyde/tests/sites/test_jinja/content/media/css/site.css: -------------------------------------------------------------------------------- 1 | body{ 2 | margin: 0 auto; 3 | width: 960px; 4 | } -------------------------------------------------------------------------------- /hyde/tests/sites/test_sorter/content/media/css/site.css: -------------------------------------------------------------------------------- 1 | body{ 2 | margin: 0 auto; 3 | width: 960px; 4 | } -------------------------------------------------------------------------------- /hyde/version.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Handles hyde version. 4 | """ 5 | __version__ = '0.8.8' 6 | -------------------------------------------------------------------------------- /hyde/layouts/starter/content/advanced/meta.yaml: -------------------------------------------------------------------------------- 1 | extends: base.j2 2 | default_block: content 3 | level: advanced 4 | -------------------------------------------------------------------------------- /hyde/tests/ext/images/portrait.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jakevdp/hyde/master/hyde/tests/ext/images/portrait.jpg -------------------------------------------------------------------------------- /hyde/tests/ext/requirejs/app.js: -------------------------------------------------------------------------------- 1 | require(["lib/more"],function(e){return e("require")}),define("main",function(){}); -------------------------------------------------------------------------------- /hyde/tests/sites/test_grouper/content/blog/meta.yaml: -------------------------------------------------------------------------------- 1 | extends: base.html 2 | default_block: main 3 | section: start 4 | -------------------------------------------------------------------------------- /hyde/tests/ext/images/landscape.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jakevdp/hyde/master/hyde/tests/ext/images/landscape.jpg -------------------------------------------------------------------------------- /hyde/tests/ext/optipng/hyde-lt-b.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jakevdp/hyde/master/hyde/tests/ext/optipng/hyde-lt-b.png -------------------------------------------------------------------------------- /hyde/tests/ext/requirejs/lib/less.js: -------------------------------------------------------------------------------- 1 | define(['lib/more'], function(more){ 2 | return more(s) + " or less"; 3 | }); 4 | -------------------------------------------------------------------------------- /hyde/layouts/basic/content/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jakevdp/hyde/master/hyde/layouts/basic/content/favicon.ico -------------------------------------------------------------------------------- /hyde/tests/ext/requirejs/lib/more.js: -------------------------------------------------------------------------------- 1 | define(function(){ 2 | return function(s){ 3 | return s + " more"; 4 | }; 5 | }); 6 | -------------------------------------------------------------------------------- /hyde/layouts/basic/content/blog/atom.xml: -------------------------------------------------------------------------------- 1 | === 2 | title: BASIC blog full text feed 3 | extends: atom.j2 4 | listable: false 5 | === 6 | -------------------------------------------------------------------------------- /hyde/layouts/basic/info.yaml: -------------------------------------------------------------------------------- 1 | author: Lakshmi Vyasarajan 2 | description: A basic layout for hyde. Based on html5 boilerplate 3 | version: 0.1 -------------------------------------------------------------------------------- /hyde/tests/ext/requirejs/rjs.conf: -------------------------------------------------------------------------------- 1 | ({ 2 | name: 'main', 3 | include: ['lib/more'], 4 | exclude: ['lib/less'], 5 | logLevel: 3 6 | }) 7 | -------------------------------------------------------------------------------- /h: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | if __name__ == "__main__": 4 | from hyde.engine import Engine 5 | Engine().run() 6 | -------------------------------------------------------------------------------- /hyde/layouts/basic/content/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jakevdp/hyde/master/hyde/layouts/basic/content/apple-touch-icon.png -------------------------------------------------------------------------------- /hyde/layouts/basic/content/media/images/dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jakevdp/hyde/master/hyde/layouts/basic/content/media/images/dark.png -------------------------------------------------------------------------------- /hyde/tests/sites/test_grouper/content/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jakevdp/hyde/master/hyde/tests/sites/test_grouper/content/favicon.ico -------------------------------------------------------------------------------- /hyde/tests/sites/test_jinja/content/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jakevdp/hyde/master/hyde/tests/sites/test_jinja/content/favicon.ico -------------------------------------------------------------------------------- /hyde/tests/sites/test_jinja/info.yaml: -------------------------------------------------------------------------------- 1 | author: Lakshmi Vyasarajan 2 | description: A test layout for hyde. 3 | template: jinja2 (2.6) 4 | version: 0.1 -------------------------------------------------------------------------------- /hyde/tests/sites/test_sorter/content/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jakevdp/hyde/master/hyde/tests/sites/test_sorter/content/favicon.ico -------------------------------------------------------------------------------- /hyde/layouts/basic/content/blog/index.html: -------------------------------------------------------------------------------- 1 | --- 2 | title: A basic blog 3 | extends: listing.j2 4 | default_block: test 5 | listable: false 6 | --- 7 | -------------------------------------------------------------------------------- /hyde/layouts/basic/content/media/images/airport.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jakevdp/hyde/master/hyde/layouts/basic/content/media/images/airport.png -------------------------------------------------------------------------------- /hyde/tests/sites/test_grouper/info.yaml: -------------------------------------------------------------------------------- 1 | author: Lakshmi Vyasarajan 2 | description: A test layout for hyde. 3 | template: jinja2 (2.6) 4 | version: 0.1 -------------------------------------------------------------------------------- /hyde/layouts/starter/content/media/img/background.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jakevdp/hyde/master/hyde/layouts/starter/content/media/img/background.jpg -------------------------------------------------------------------------------- /hyde/tests/sites/test_sorter/layout/root.j2: -------------------------------------------------------------------------------- 1 | 2 | 3 | {% block content -%} 4 | 5 | {%- endblock %} 6 | 7 | -------------------------------------------------------------------------------- /hyde/tests/sites/test_tagger/layout/root.j2: -------------------------------------------------------------------------------- 1 | 2 | 3 | {% block content -%} 4 | 5 | {%- endblock %} 6 | 7 | -------------------------------------------------------------------------------- /hyde/layouts/basic/content/blog/excerpts.xml: -------------------------------------------------------------------------------- 1 | === 2 | title: BASIC blog excerpts feed 3 | extends: atom.j2 4 | excerpts_only: true 5 | listable: false 6 | === 7 | -------------------------------------------------------------------------------- /hyde/tests/ext/stylus/inc/mixin.styl: -------------------------------------------------------------------------------- 1 | rounded(radius = 5px) 2 | -webkit-border-radius radius 3 | -moz-border-radius radius 4 | border-radius radius 5 | -------------------------------------------------------------------------------- /hyde/tests/sites/test_jinja/content/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jakevdp/hyde/master/hyde/tests/sites/test_jinja/content/apple-touch-icon.png -------------------------------------------------------------------------------- /hyde/layouts/basic/content/about.html: -------------------------------------------------------------------------------- 1 | --- 2 | extends: base.j2 3 | default_block: main 4 | title: About BASIC 5 | description: The default hyde template 6 | --- 7 | -------------------------------------------------------------------------------- /hyde/tests/sites/test_grouper/content/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jakevdp/hyde/master/hyde/tests/sites/test_grouper/content/apple-touch-icon.png -------------------------------------------------------------------------------- /hyde/tests/sites/test_sorter/content/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jakevdp/hyde/master/hyde/tests/sites/test_sorter/content/apple-touch-icon.png -------------------------------------------------------------------------------- /hyde/tests/sites/test_jinja/content/robots.txt: -------------------------------------------------------------------------------- 1 | # www.robotstxt.org/ 2 | # www.google.com/support/webmasters/bin/answer.py?hl=en&answer=156449 3 | 4 | User-agent: * 5 | 6 | -------------------------------------------------------------------------------- /hyde/tests/sites/test_grouper/content/robots.txt: -------------------------------------------------------------------------------- 1 | # www.robotstxt.org/ 2 | # www.google.com/support/webmasters/bin/answer.py?hl=en&answer=156449 3 | 4 | User-agent: * 5 | 6 | -------------------------------------------------------------------------------- /hyde/tests/sites/test_sorter/content/robots.txt: -------------------------------------------------------------------------------- 1 | # www.robotstxt.org/ 2 | # www.google.com/support/webmasters/bin/answer.py?hl=en&answer=156449 3 | 4 | User-agent: * 5 | 6 | -------------------------------------------------------------------------------- /hyde/tests/ext/less/inc/mixin.less: -------------------------------------------------------------------------------- 1 | .rounded (@radius: 5px){ 2 | -webkit-border-radius: @radius; 3 | -moz-border-radius: @radius; 4 | border-radius: @radius; 5 | } -------------------------------------------------------------------------------- /hyde/layouts/basic/content/portfolio/index.html: -------------------------------------------------------------------------------- 1 | --- 2 | extends: base.j2 3 | default_block: main 4 | title: BASIC - Portfolio 5 | description: Portfolio page in the BASIC hyde template 6 | --- 7 | -------------------------------------------------------------------------------- /hyde/tests/ext/scss/inc/mixin.scss: -------------------------------------------------------------------------------- 1 | @mixin rounded ($radius: 5px){ 2 | -webkit-border-radius: $radius; 3 | -moz-border-radius: $radius; 4 | border-radius: $radius; 5 | } 6 | 7 | -------------------------------------------------------------------------------- /hyde/tests/sites/test_jinja/site.yaml: -------------------------------------------------------------------------------- 1 | mode: development 2 | media_root:: media # Relative path from site root (the directory where this file exists) 3 | media_url: /media 4 | template: hyde.ext.jinja2 -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | fswrap==0.1.2 2 | commando==0.3.4 3 | PyYAML==3.10 4 | Markdown==2.3.1 5 | MarkupSafe==0.18 6 | Pygments==1.6 7 | typogrify==2.0.0 8 | smartypants<1.8 9 | Jinja2==2.7.1 10 | -------------------------------------------------------------------------------- /hyde/layouts/basic/layout/devmode.j2: -------------------------------------------------------------------------------- 1 | {%if not site.config.mode == "production" %} 2 | 3 | 4 | {% endif %} -------------------------------------------------------------------------------- /hyde/tests/sites/test_jinja/alternate.yaml: -------------------------------------------------------------------------------- 1 | mode: development 2 | content_root: stuff # Relative path from site root 3 | media_root: media # Relative path from site root 4 | media_url: /media 5 | widgets: 6 | plugins: 7 | aggregators: -------------------------------------------------------------------------------- /hyde/tests/sites/test_grouper/alternate.yaml: -------------------------------------------------------------------------------- 1 | mode: development 2 | content_root: stuff # Relative path from site root 3 | media_root: media # Relative path from site root 4 | media_url: /media 5 | widgets: 6 | plugins: 7 | aggregators: -------------------------------------------------------------------------------- /hyde/tests/sites/test_grouper/content/about.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block main %} 4 | Hi! 5 | 6 | I am a test template to make sure jinja2 generation works well with hyde. 7 | {{resource.name}} 8 | {% endblock %} -------------------------------------------------------------------------------- /hyde/tests/sites/test_grouper/site.yaml: -------------------------------------------------------------------------------- 1 | mode: development 2 | media_root:: media # Relative path from site root (the directory where this file exists) 3 | media_url: /media 4 | template: hyde.ext.jinja2 5 | meta: 6 | nodemeta: meta.yaml -------------------------------------------------------------------------------- /hyde/tests/sites/test_jinja/content/about.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block main %} 4 | Hi! 5 | 6 | I am a test template to make sure jinja2 generation works well with hyde. 7 | {{resource.name}} 8 | {% endblock %} -------------------------------------------------------------------------------- /hyde/tests/sites/test_sorter/content/about.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block main %} 4 | Hi! 5 | 6 | I am a test template to make sure jinja2 generation works well with hyde. 7 | {{resource.name}} 8 | {% endblock %} -------------------------------------------------------------------------------- /hyde/tests/ext/stylus/expected-site-compressed.css: -------------------------------------------------------------------------------- 1 | *{border:0;padding:0;margin:0} 2 | #header{color:#333;border-left:1px;border-right:2px} 3 | #footer{color:#333} 4 | #content{-webkit-border-radius:10px;-moz-border-radius:10px;border-radius:10px} -------------------------------------------------------------------------------- /hyde/tests/sites/test_jinja/content/blog/2010/december/merry-christmas.html: -------------------------------------------------------------------------------- 1 | {% extends "blog/post.html" %} 2 | 3 | {% block article %} 4 | {{ lipsum() }} 5 | {% endblock %} 6 | 7 | {% block aside %} 8 | {{ lipsum() }} 9 | {% endblock %} -------------------------------------------------------------------------------- /hyde/tests/sites/test_sorter/content/blog/2010/december/merry-christmas.html: -------------------------------------------------------------------------------- 1 | {% extends "blog/post.html" %} 2 | 3 | {% block article %} 4 | {{ lipsum() }} 5 | {% endblock %} 6 | 7 | {% block aside %} 8 | {{ lipsum() }} 9 | {% endblock %} -------------------------------------------------------------------------------- /hyde/main.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | """ 4 | The hyde executable 5 | """ 6 | from hyde.engine import Engine 7 | 8 | def main(): 9 | """Main""" 10 | Engine().run() 11 | 12 | if __name__ == "__main__": 13 | main() -------------------------------------------------------------------------------- /hyde/tests/sites/test_grouper/layout/blog/post.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block main %} 4 |
5 | {% block article %}{% endblock %} 6 |
7 | 10 | {% endblock %} -------------------------------------------------------------------------------- /hyde/tests/sites/test_jinja/layout/blog/post.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block main %} 4 |
5 | {% block article %}{% endblock %} 6 |
7 | 10 | {% endblock %} -------------------------------------------------------------------------------- /hyde/tests/sites/test_sorter/layout/tagged_posts.j2: -------------------------------------------------------------------------------- 1 |

Posts tagged: {{ tag }} in {{ node.name|title }}

2 | -------------------------------------------------------------------------------- /hyde/tests/sites/test_tagger/layout/tagged_posts.j2: -------------------------------------------------------------------------------- 1 |

Posts tagged: {{ tag }} in {{ node.name|title }}

2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *~ 2 | *.pyc 3 | .DS_Store 4 | deploy 5 | deploy/* 6 | *.cprof 7 | *.profile 8 | *.log 9 | pylint* 10 | *.tmproj 11 | test_site 12 | dist 13 | build 14 | *egg* 15 | .idea 16 | PYSMELLTAGS 17 | .noseids 18 | *.tar.gz 19 | .hyde_deps 20 | src 21 | .tm_properties -------------------------------------------------------------------------------- /hyde/exceptions.py: -------------------------------------------------------------------------------- 1 | class HydeException(Exception): 2 | """ 3 | Base class for exceptions from hyde 4 | """ 5 | 6 | @staticmethod 7 | def reraise(message, exc_info): 8 | _, _, tb = exc_info 9 | raise HydeException(message), None, tb 10 | 11 | 12 | -------------------------------------------------------------------------------- /hyde/tests/sites/test_paginator/content/pages_of_one.txt: -------------------------------------------------------------------------------- 1 | --- 2 | paginator: 3 | sorter: time 4 | size: 1 5 | --- 6 | {% for res in resource.page.posts %} 7 | {{ res.meta.title }} 8 | {% endfor %} 9 | {{ resource.page.previous.resource.url }} 10 | {{ resource.page.next.resource.url }} 11 | -------------------------------------------------------------------------------- /hyde/tests/sites/test_paginator/content/pages_of_ten.txt: -------------------------------------------------------------------------------- 1 | --- 2 | paginator: 3 | sorter: time 4 | size: 10 5 | --- 6 | {% for res in resource.page.posts %} 7 | {{ res.meta.title }} 8 | {% endfor %} 9 | {{ resource.page.previous.resource.url -}} 10 | {{ resource.page.next.resource.url -}} 11 | -------------------------------------------------------------------------------- /hyde/tests/ext/stylus/site.styl: -------------------------------------------------------------------------------- 1 | @import "inc/mixin" 2 | @import "inc/vars" 3 | @import "inc/reset.css" 4 | 5 | #header 6 | color base-color * 3 7 | border-left the-border 8 | border-right the-border * 2 9 | 10 | #footer 11 | color (base-color + #111) * 1.5 12 | 13 | #content 14 | rounded 10px -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | exclude *.pyc .DS_Store .gitignore .noseids MANIFEST.in 2 | include setup.py 3 | include distribute_setup.py 4 | include LICENSE 5 | include README.rst 6 | include AUTHORS.rst 7 | include CHANGELOG.rst 8 | recursive-include hyde *.py 9 | recursive-include hyde/tests *.py 10 | recursive-include hyde/layouts *.* 11 | -------------------------------------------------------------------------------- /hyde/tests/sites/test_paginator/content/custom_file_pattern.txt: -------------------------------------------------------------------------------- 1 | --- 2 | paginator: 3 | sorter: time 4 | size: 2 5 | file_pattern: $FILE-$PAGE$EXT 6 | --- 7 | {% for res in resource.page.posts %} 8 | {{ res.meta.title }} 9 | {% endfor %} 10 | {{ resource.page.previous.resource.url }} 11 | {{ resource.page.next.resource.url }} 12 | -------------------------------------------------------------------------------- /hyde/tests/ext/less/site.less: -------------------------------------------------------------------------------- 1 | @import "inc/mixin"; 2 | @import "inc/vars"; 3 | @import "inc/reset.css"; 4 | 5 | #header { 6 | color: @base-color * 3; 7 | border-left: @the-border; 8 | border-right: @the-border * 2; 9 | } 10 | 11 | #footer { 12 | color: (@base-color + #111) * 1.5; 13 | } 14 | 15 | #content { 16 | .rounded(10px); 17 | } -------------------------------------------------------------------------------- /hyde/tests/ext/stylus/expected-site.css: -------------------------------------------------------------------------------- 1 | * { 2 | border: 0; 3 | padding: 0; 4 | margin: 0; 5 | } 6 | #header { 7 | color: #333; 8 | border-left: 1px; 9 | border-right: 2px; 10 | } 11 | #footer { 12 | color: #333; 13 | } 14 | #content { 15 | -webkit-border-radius: 10px; 16 | -moz-border-radius: 10px; 17 | border-radius: 10px; 18 | } 19 | -------------------------------------------------------------------------------- /hyde/tests/ext/less/expected-site.css: -------------------------------------------------------------------------------- 1 | * { 2 | border: 0; 3 | padding: 0; 4 | margin: 0; 5 | } 6 | #header { 7 | color: #333333; 8 | border-left: 1px; 9 | border-right: 2px; 10 | } 11 | #footer { 12 | color: #333333; 13 | } 14 | #content { 15 | -webkit-border-radius: 10px; 16 | -moz-border-radius: 10px; 17 | border-radius: 10px; 18 | } 19 | -------------------------------------------------------------------------------- /hyde/tests/ext/scss/expected-site.css: -------------------------------------------------------------------------------- 1 | * { 2 | border: 0; 3 | padding: 0; 4 | margin: 0; 5 | } 6 | #header { 7 | color: #333333; 8 | border-left: 1px; 9 | border-right: 2px; 10 | } 11 | #footer { 12 | color: #333333; 13 | } 14 | #content { 15 | -webkit-border-radius: 10px; 16 | -moz-border-radius: 10px; 17 | border-radius: 10px; 18 | } 19 | -------------------------------------------------------------------------------- /hyde/tests/ext/scss/site.scss: -------------------------------------------------------------------------------- 1 | @option compress: no; 2 | 3 | @import "inc/mixin"; 4 | @import "inc/vars"; 5 | @import "inc/reset.css"; 6 | 7 | #header { 8 | color: $base-color * 3; 9 | border-left: $the-border; 10 | border-right: $the-border * 2; 11 | } 12 | 13 | #footer { 14 | color: ($base-color + #111) * 1.5; 15 | } 16 | 17 | #content { 18 | @include rounded(10px); 19 | } 20 | 21 | -------------------------------------------------------------------------------- /hyde/layouts/basic/layout/listing.j2: -------------------------------------------------------------------------------- 1 | {% extends "base.j2" %} 2 | {% from "macros.j2" import render_excerpt with context %} 3 | {% block main %} 4 | {% block page_title %}

{{ resource.meta.title }}

{% endblock %} 5 | 6 | 13 | {% endblock %} -------------------------------------------------------------------------------- /hyde/tests/ssp/ext/banner.py: -------------------------------------------------------------------------------- 1 | from hyde.plugin import Plugin 2 | 3 | 4 | class BannerPlugin(Plugin): 5 | """ 6 | Adds a comment banner to all generated html files 7 | """ 8 | 9 | def text_resource_complete(self, resource, text): 10 | banner = """ 11 | 15 | """ 16 | if resource.source.kind == "html": 17 | text = banner + text 18 | return text -------------------------------------------------------------------------------- /hyde/tests/templates/jinja2/helpers.html: -------------------------------------------------------------------------------- 1 | {% macro input_field(name, value='', type='text') -%} 2 | 3 | {%- endmacro %} 4 | 5 | {% macro textarea(name, value='', rows=10, cols=40) -%} 6 | 8 | {%- endmacro %} 9 | 10 | {% macro form(action='', method='post') -%} 11 |
{{ caller() }}
12 | {%- endmacro %} 13 | -------------------------------------------------------------------------------- /hyde/layouts/starter/layout/ga.j2: -------------------------------------------------------------------------------- 1 | 14 | -------------------------------------------------------------------------------- /hyde/layouts/basic/content/index.html: -------------------------------------------------------------------------------- 1 | --- 2 | extends: base.j2 3 | default_block: main 4 | title: BASIC - A hyde website 5 | description: Home page for the BASIC hyde template 6 | --- 7 | 8 | {% from "macros.j2" import render_excerpt with context %} 9 |
10 | {% set latest = site.content.walk_resources_sorted_by_time()|first %} 11 | 12 | {% if latest -%} 13 | {{ render_excerpt(latest, 'post') }} 14 | 15 | Read more… 17 | 18 | {%- endif %} 19 | 20 |
-------------------------------------------------------------------------------- /hyde/tests/sites/test_sorter/site.yaml: -------------------------------------------------------------------------------- 1 | mode: development 2 | media_root: media # Relative path from content folder. 3 | media_url: /media # URL where the media files are served from. 4 | base_url: / # The base url for autogenerated links. 5 | plugins: 6 | - hyde.ext.plugins.meta.MetaPlugin 7 | - hyde.ext.plugins.meta.AutoExtendPlugin 8 | - hyde.ext.plugins.meta.SorterPlugin 9 | - hyde.ext.plugins.text.TextlinksPlugin 10 | meta: 11 | nodemeta: meta.yaml 12 | created: !!timestamp 2010-01-01 00:00:00 13 | extends: root.j2 14 | default_block: content 15 | -------------------------------------------------------------------------------- /hyde/layouts/basic/layout/analytics.j2: -------------------------------------------------------------------------------- 1 | {% if site.config.mode == "production" %} 2 | 4 | 14 | {% endif %} -------------------------------------------------------------------------------- /hyde/tests/sites/test_paginator/site.yaml: -------------------------------------------------------------------------------- 1 | mode: development 2 | media_root: media # Relative path from content folder. 3 | media_url: /media # URL where the media files are served from. 4 | base_url: / # The base url for autogenerated links. 5 | plugins: 6 | - hyde.ext.plugins.meta.MetaPlugin 7 | - hyde.ext.plugins.meta.SorterPlugin 8 | - hyde.ext.plugins.structure.PaginatorPlugin 9 | meta: 10 | nodemeta: meta.yaml 11 | created: !!timestamp 2010-01-01 00:00:00 12 | extends: root.j2 13 | default_block: content 14 | sorter: 15 | time: 16 | attr: 17 | - meta.created 18 | reverse: true 19 | filters: 20 | source.kind: html 21 | -------------------------------------------------------------------------------- /hyde/layouts/starter/content/index.html: -------------------------------------------------------------------------------- 1 | {# Use base.j2 template as a base and only provide values for placeholders 2 | in this file. 3 | #} 4 | {% extends "base.j2" %} 5 | 6 | {# Markdown main content block. #} 7 | {% block content %} 8 | What is this all about 9 | ====================== 10 | 11 | Starter Kit introduces you to Hyde's many options and possibilities. 12 | Finding your way for the first time can be a bit confusing, so this 13 | template tries to ease your way in by exposing only core features and 14 | technologies step by step. Those are: 15 | 16 | * basic Hyde site structure 17 | * configuration file 18 | * Jinja templates 19 | * Markdown 20 | * basic metadata and plugins 21 | 22 | Are you ready for your [first steps](first-steps.html)? 23 | {% endblock %} 24 | -------------------------------------------------------------------------------- /hyde/tests/sites/test_grouper/content/404.html: -------------------------------------------------------------------------------- 1 | 2 | not found 3 | 4 | 15 | 16 | 17 | 18 | 19 |
20 |

Not found

21 |

:(

22 |
-------------------------------------------------------------------------------- /hyde/tests/sites/test_jinja/content/404.html: -------------------------------------------------------------------------------- 1 | 2 | not found 3 | 4 | 15 | 16 | 17 | 18 | 19 |
20 |

Not found

21 |

:(

22 |
-------------------------------------------------------------------------------- /hyde/tests/sites/test_sorter/content/404.html: -------------------------------------------------------------------------------- 1 | 2 | not found 3 | 4 | 15 | 16 | 17 | 18 | 19 |
20 |

Not found

21 |

:(

22 |
-------------------------------------------------------------------------------- /hyde/layouts/basic/layout/tagged_posts.j2: -------------------------------------------------------------------------------- 1 | {% extends "base.j2" %} 2 | 3 | {% block main %} 4 |
5 |

{{ tag.name }}

6 | 27 |
28 | {% endblock %} 29 | -------------------------------------------------------------------------------- /hyde/tests/sites/test_grouper/content/crossdomain.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 19 | 24 | 25 | -------------------------------------------------------------------------------- /hyde/tests/sites/test_jinja/content/crossdomain.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 19 | 24 | 25 | -------------------------------------------------------------------------------- /hyde/tests/sites/test_sorter/content/crossdomain.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 19 | 24 | 25 | -------------------------------------------------------------------------------- /hyde/tests/sites/test_tagger/site.yaml: -------------------------------------------------------------------------------- 1 | mode: development 2 | media_root: media # Relative path from content folder. 3 | media_url: /media # URL where the media files are served from. 4 | base_url: / # The base url for autogenerated links. 5 | plugins: 6 | - hyde.ext.plugins.meta.MetaPlugin 7 | - hyde.ext.plugins.meta.AutoExtendPlugin 8 | - hyde.ext.plugins.meta.SorterPlugin 9 | - hyde.ext.plugins.meta.TaggerPlugin 10 | - hyde.ext.plugins.text.TextlinksPlugin 11 | meta: 12 | nodemeta: meta.yaml 13 | created: !!timestamp 2010-01-01 00:00:00 14 | extends: root.j2 15 | default_block: content 16 | sorter: 17 | time: 18 | attr: 19 | - meta.created 20 | reverse: true 21 | filters: 22 | source.kind: html 23 | tagger: 24 | sorter: time 25 | archives: 26 | blog: 27 | template: tagged_posts.j2 28 | source: blog 29 | target: blog/tags 30 | extension: html 31 | -------------------------------------------------------------------------------- /hyde/tests/templates/jinja2/layout.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | {% block page_title %}{% endblock %} | RealWorld Benchmark 5 | 6 | 7 | 8 |
9 |
10 |

RealWorld Benchmark

11 |

12 | A less stupid benchmark for Mako and Jinja2 to get an impression how 13 | code changes affect runtime performance. 14 |

15 |
16 | 21 |
22 | {% block body %}{% endblock %} 23 |
24 | 27 |
28 | 29 | 30 | -------------------------------------------------------------------------------- /hyde/ext/plugins/blog.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | 4 | Plugins that are useful to blogs hosted with hyde. 5 | 6 | """ 7 | 8 | from hyde.plugin import Plugin 9 | 10 | 11 | class DraftsPlugin(Plugin): 12 | 13 | 14 | def begin_site(self): 15 | 16 | in_production = self.site.config.mode.startswith('prod') 17 | if not in_production: 18 | self.logger.info( 19 | 'Generating draft posts as the site is not in production mode.') 20 | return 21 | 22 | for resource in self.site.content.walk_resources(): 23 | if not resource.is_processable: 24 | continue 25 | 26 | try: 27 | is_draft = resource.meta.is_draft 28 | except AttributeError: 29 | is_draft = False 30 | 31 | if is_draft: 32 | resource.is_processable = False 33 | 34 | self.logger.info( 35 | '%s is%s draft' % (resource, 36 | '' if is_draft else ' not')) -------------------------------------------------------------------------------- /hyde/tests/README.rst: -------------------------------------------------------------------------------- 1 | Requirements 2 | ============ 3 | 4 | All the python requirements are enumerated in dev-req.txt. You can install them 5 | with: 6 | 7 | :: 8 | pip install -r dev-req.txt 9 | 10 | 11 | Apart from these requirements the following are required by plugins if you 12 | choose to run the corresponding tests. Some of the comands use the Mac OS X 13 | package manager `homebrew` - please use the package manager corresponding to 14 | your operating system. 15 | 16 | 17 | :: 18 | # stylus 19 | npm install -g stylus 20 | 21 | #uglifyjs 22 | npm install -g uglify-js 23 | 24 | #asciidoc 25 | brew install asciidoc 26 | cd /usr/local/Cellar/asciidoc/8.6.8/bin 27 | curl -O https://asciidoc.googlecode.com/hg/asciidocapi.py 28 | 29 | #optipng 30 | brew install optipng 31 | 32 | 33 | Ensure that `asciidoc`_ python api is available in the python path. 34 | 35 | For example: 36 | 37 | :: 38 | export PYTHONPATH=/usr/local/Cellar/asciidoc/8.6.8/bin:$PYTHONPATH 39 | 40 | 41 | Run the tests 42 | ============= 43 | 44 | :: 45 | nosetests hyde/tests -------------------------------------------------------------------------------- /hyde/tests/test_layout.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Use nose 4 | `$ pip install nose` 5 | `$ nosetests` 6 | """ 7 | import os 8 | 9 | from hyde.layout import Layout, HYDE_DATA, LAYOUTS 10 | 11 | from fswrap import File 12 | from nose.tools import nottest, with_setup 13 | 14 | DATA_ROOT = File(__file__).parent.child_folder('data') 15 | LAYOUT_ROOT = DATA_ROOT.child_folder(LAYOUTS) 16 | 17 | @nottest 18 | def setup_data(): 19 | DATA_ROOT.make() 20 | 21 | @nottest 22 | def cleanup_data(): 23 | DATA_ROOT.delete() 24 | 25 | def test_find_layout_from_package_dir(): 26 | f = Layout.find_layout() 27 | assert f.name == 'basic' 28 | assert f.child_folder('layout').exists 29 | 30 | @with_setup(setup_data, cleanup_data) 31 | def test_find_layout_from_env_var(): 32 | f = Layout.find_layout() 33 | LAYOUT_ROOT.make() 34 | f.copy_to(LAYOUT_ROOT) 35 | os.environ[HYDE_DATA] = unicode(DATA_ROOT) 36 | f = Layout.find_layout() 37 | assert f.parent == LAYOUT_ROOT 38 | assert f.name == 'basic' 39 | assert f.child_folder('layout').exists 40 | del os.environ[HYDE_DATA] 41 | -------------------------------------------------------------------------------- /hyde/layouts/starter/content/about.html: -------------------------------------------------------------------------------- 1 | --- 2 | extends: base.j2 3 | title: About 4 | default_block: content 5 | --- 6 | 7 | About 8 | ===== 9 | 10 | This beginner's tutorial was created by [Merlin Rebrović][0] for the 11 | [Hyde project][1]. It is included in Hyde as a layout named _starter_. 12 | If you have a [default Hyde installation][2], all you have to do is 13 | write: 14 | 15 | hyde -s folder_name create -l starter 16 | 17 | To build and serve the template, type: 18 | 19 | cd folder_name 20 | hyde gen 21 | hyde serve 22 | 23 | To check for the newest version, download it separately, report a bug 24 | or contribute, please visit Hyde Starter Kit's [GitHub page][3]. 25 | 26 | 27 | Attributions 28 | ------------ 29 | 30 | The [photo][4] used for part of the background. 31 | 32 | 33 | {# You can use Jinja tags in Markdown content also; they get processed 34 | before the content gets to the Markdown filter. 35 | #} 36 | [0]: {{ author.url }} 37 | [1]: {{ project.url }} 38 | [2]: {{ project.install }} 39 | [3]: {{ layout.url }} 40 | [4]: http://www.flickr.com/photos/batintherain/5613841957/ 41 | -------------------------------------------------------------------------------- /hyde/tests/templates/jinja2/index.html: -------------------------------------------------------------------------------- 1 | {% extends "layout.html" %} 2 | {% from "helpers.html" import input_field, textarea, form %} 3 | {% block page_title %}Index Page{% endblock %} 4 | {% block body %} 5 | {%- for article in articles if article.published %} 6 |
7 |

{{ article.title|e }}

8 |

written by {{ article.user.username|e }} on {{ article.pub_date|dateformat }}

10 |
{{ article.body }}
11 |
12 | {%- endfor %} 13 | {%- call form() %} 14 |
15 |
Name
16 |
{{ input_field('name') }}
17 |
E-Mail
18 |
{{ input_field('email') }}
19 |
URL
20 |
{{ input_field('url') }}
21 |
Comment
22 |
{{ textarea('comment') }}
23 |
Captcha
24 |
{{ input_field('captcha') }}
25 |
26 | {{ input_field(type='submit', value='Submit') }} 27 | {{ input_field('cancel', type='submit', value='Cancel') }} 28 | {%- endcall %} 29 | {% endblock %} 30 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License 2 | 3 | Copyright (c) 2009 - 2013 Lakshmi Vyasarajan, Ringce.com 4 | Copyright (c) 2009 - 2013 Hyde contributors. 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in 14 | all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /hyde/tests/util.py: -------------------------------------------------------------------------------- 1 | import re 2 | import difflib 3 | 4 | def strip_spaces_between_tags(value): 5 | """ 6 | Stolen from `django.util.html` 7 | Returns the given HTML with spaces between tags removed. 8 | """ 9 | return re.sub(r'>\s+<', '><', unicode(value)) 10 | 11 | def assert_no_diff(expected, out): 12 | diff = [l for l in difflib.unified_diff(expected.splitlines(True), 13 | out.splitlines(True), 14 | n=3)] 15 | assert not diff, ''.join(diff) 16 | 17 | 18 | def assert_html_equals(expected, actual, sanitize=None): 19 | expected = strip_spaces_between_tags(expected.strip()) 20 | actual = strip_spaces_between_tags(actual.strip()) 21 | if sanitize: 22 | expected = sanitize(expected) 23 | actual = sanitize(actual) 24 | assert expected == actual 25 | 26 | def trap_exit_fail(f): 27 | def test_wrapper(*args): 28 | try: 29 | f(*args) 30 | except SystemExit: 31 | assert False 32 | test_wrapper.__name__ = f.__name__ 33 | return test_wrapper 34 | 35 | def trap_exit_pass(f): 36 | def test_wrapper(*args): 37 | try: 38 | f(*args) 39 | except SystemExit: 40 | pass 41 | test_wrapper.__name__ = f.__name__ 42 | return test_wrapper -------------------------------------------------------------------------------- /hyde/tests/ext/test_requirejs.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Use nose 4 | `$ pip install nose` 5 | `$ nosetests` 6 | """ 7 | from hyde.generator import Generator 8 | from hyde.site import Site 9 | 10 | from fswrap import File, Folder 11 | 12 | RJS_SOURCE = File(__file__).parent.child_folder('requirejs') 13 | TEST_SITE = File(__file__).parent.parent.child_folder('_test') 14 | 15 | class TestRequireJS(object): 16 | def setUp(self): 17 | TEST_SITE.make() 18 | TEST_SITE.parent.child_folder('sites/test_jinja').copy_contents_to(TEST_SITE) 19 | RJS_SOURCE.copy_contents_to(TEST_SITE.child('content/media/js')) 20 | File(TEST_SITE.child('content/media/js/app.js')).delete() 21 | 22 | def tearDown(self): 23 | TEST_SITE.delete() 24 | 25 | def test_can_execute_rjs(self): 26 | s = Site(TEST_SITE) 27 | s.config.plugins = ['hyde.ext.plugins.js.RequireJSPlugin'] 28 | source = TEST_SITE.child('content/media/js/rjs.conf') 29 | target = File(Folder(s.config.deploy_root_path).child('media/js/app.js')) 30 | gen = Generator(s) 31 | gen.generate_resource_at_path(source) 32 | 33 | assert target.exists 34 | text = target.read_all() 35 | expected_text = File(RJS_SOURCE.child('app.js')).read_all() 36 | 37 | assert text == expected_text 38 | return 39 | -------------------------------------------------------------------------------- /hyde/tests/ext/test_syntext.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Use nose 4 | `$ pip install nose` 5 | `$ nosetests` 6 | """ 7 | from hyde.generator import Generator 8 | from hyde.site import Site 9 | 10 | from fswrap import File 11 | from pyquery import PyQuery 12 | 13 | TEST_SITE = File(__file__).parent.parent.child_folder('_test') 14 | 15 | 16 | class TestSyntext(object): 17 | 18 | def setUp(self): 19 | TEST_SITE.make() 20 | TEST_SITE.parent.child_folder( 21 | 'sites/test_jinja').copy_contents_to(TEST_SITE) 22 | 23 | def tearDown(self): 24 | TEST_SITE.delete() 25 | 26 | 27 | 28 | def test_syntext(self): 29 | text = u""" 30 | ~~~~~~~~css~~~~~~~ 31 | .body{ 32 | background-color: white; 33 | } 34 | ~~~~~~~~~~~~~~~~~~ 35 | """ 36 | site = Site(TEST_SITE) 37 | site.config.plugins = [ 38 | 'hyde.ext.plugins.meta.MetaPlugin', 39 | 'hyde.ext.plugins.text.SyntextPlugin'] 40 | syn = File(site.content.source_folder.child('syn.html')) 41 | syn.write(text) 42 | gen = Generator(site) 43 | gen.generate_all() 44 | f = File(site.config.deploy_root_path.child(syn.name)) 45 | assert f.exists 46 | html = f.read_all() 47 | assert html 48 | q = PyQuery(html) 49 | assert q('figure.code').length == 1 50 | -------------------------------------------------------------------------------- /hyde/layout.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Classes, functions and utilties related to hyde layouts 4 | """ 5 | import os 6 | 7 | from fswrap import File, Folder 8 | 9 | HYDE_DATA = "HYDE_DATA" 10 | LAYOUTS = "layouts" 11 | 12 | 13 | class Layout(object): 14 | """ 15 | Represents a layout package 16 | """ 17 | 18 | @staticmethod 19 | def find_layout(layout_name='basic'): 20 | """ 21 | Find the layout with a given name. 22 | Search order: 23 | 1. env(HYDE_DATA) 24 | 2. /layouts/ 25 | """ 26 | layout_folder = None 27 | if HYDE_DATA in os.environ: 28 | layout_folder = Layout._get_layout_folder( 29 | os.environ[HYDE_DATA], layout_name) 30 | if not layout_folder: 31 | layout_folder = Layout._get_layout_folder( 32 | File(__file__).parent, layout_name) 33 | return layout_folder 34 | 35 | @staticmethod 36 | def _get_layout_folder(root, layout_name='basic'): 37 | """ 38 | Finds the layout folder from the given root folder. 39 | If it does not exist, return None 40 | """ 41 | layouts_folder = Folder(unicode(root)).child_folder(LAYOUTS) 42 | layout_folder = layouts_folder.child_folder(layout_name) 43 | return layout_folder if layout_folder.exists else None 44 | -------------------------------------------------------------------------------- /hyde/tests/ext/test_less.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Use nose 4 | `$ pip install nose` 5 | `$ nosetests` 6 | """ 7 | from hyde.generator import Generator 8 | from hyde.site import Site 9 | 10 | from fswrap import File, Folder 11 | 12 | LESS_SOURCE = File(__file__).parent.child_folder('less') 13 | TEST_SITE = File(__file__).parent.parent.child_folder('_test') 14 | 15 | 16 | class TestLess(object): 17 | 18 | def setUp(self): 19 | TEST_SITE.make() 20 | TEST_SITE.parent.child_folder( 21 | 'sites/test_jinja').copy_contents_to(TEST_SITE) 22 | LESS_SOURCE.copy_contents_to(TEST_SITE.child('content/media/css')) 23 | File(TEST_SITE.child('content/media/css/site.css')).delete() 24 | 25 | 26 | def tearDown(self): 27 | TEST_SITE.delete() 28 | 29 | def test_can_execute_less(self): 30 | s = Site(TEST_SITE) 31 | s.config.plugins = ['hyde.ext.plugins.css.LessCSSPlugin'] 32 | source = TEST_SITE.child('content/media/css/site.less') 33 | target = File(Folder(s.config.deploy_root_path).child('media/css/site.css')) 34 | gen = Generator(s) 35 | gen.generate_resource_at_path(source) 36 | 37 | assert target.exists 38 | text = target.read_all() 39 | expected_text = File(LESS_SOURCE.child('expected-site.css')).read_all() 40 | 41 | assert text == expected_text 42 | return 43 | -------------------------------------------------------------------------------- /hyde/tests/ext/test_optipng.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Use nose 4 | `$ pip install nose` 5 | `$ nosetests` 6 | """ 7 | from hyde.model import Expando 8 | from hyde.generator import Generator 9 | from hyde.site import Site 10 | 11 | from fswrap import File, Folder 12 | 13 | OPTIPNG_SOURCE = File(__file__).parent.child_folder('optipng') 14 | TEST_SITE = File(__file__).parent.parent.child_folder('_test') 15 | 16 | 17 | class TestOptipng(object): 18 | 19 | def setUp(self): 20 | TEST_SITE.make() 21 | TEST_SITE.parent.child_folder( 22 | 'sites/test_jinja').copy_contents_to(TEST_SITE) 23 | IMAGES = TEST_SITE.child_folder('content/media/images') 24 | IMAGES.make() 25 | OPTIPNG_SOURCE.copy_contents_to(IMAGES) 26 | 27 | 28 | def tearDown(self): 29 | TEST_SITE.delete() 30 | 31 | def test_can_execute_optipng(self): 32 | s = Site(TEST_SITE) 33 | s.config.mode = "production" 34 | s.config.plugins = ['hyde.ext.plugins.images.OptiPNGPlugin'] 35 | s.config.optipng = Expando(dict(args=dict(quiet=""))) 36 | source =File(TEST_SITE.child('content/media/images/hyde-lt-b.png')) 37 | target = File(Folder(s.config.deploy_root_path).child('media/images/hyde-lt-b.png')) 38 | gen = Generator(s) 39 | gen.generate_resource_at_path(source) 40 | assert target.exists 41 | assert target.size < source.size 42 | -------------------------------------------------------------------------------- /hyde/tests/ext/test_scss.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Use nose 4 | `$ pip install nose` 5 | `$ nosetests` 6 | """ 7 | from hyde.generator import Generator 8 | from hyde.site import Site 9 | from hyde.tests.util import assert_no_diff 10 | 11 | from fswrap import File, Folder 12 | 13 | SCSS_SOURCE = File(__file__).parent.child_folder('scss') 14 | TEST_SITE = File(__file__).parent.parent.child_folder('_test') 15 | 16 | 17 | class TestSassyCSS(object): 18 | 19 | def setUp(self): 20 | TEST_SITE.make() 21 | TEST_SITE.parent.child_folder( 22 | 'sites/test_jinja').copy_contents_to(TEST_SITE) 23 | SCSS_SOURCE.copy_contents_to(TEST_SITE.child('content/media/css')) 24 | File(TEST_SITE.child('content/media/css/site.css')).delete() 25 | 26 | 27 | def tearDown(self): 28 | TEST_SITE.delete() 29 | 30 | 31 | def test_scss(self): 32 | s = Site(TEST_SITE) 33 | s.config.mode = 'prod' 34 | s.config.plugins = ['hyde.ext.plugins.css.SassyCSSPlugin'] 35 | source = TEST_SITE.child('content/media/css/site.scss') 36 | target = File(Folder(s.config.deploy_root_path).child('media/css/site.css')) 37 | gen = Generator(s) 38 | gen.generate_resource_at_path(source) 39 | 40 | assert target.exists 41 | text = target.read_all() 42 | expected_text = File(SCSS_SOURCE.child('expected-site.css')).read_all() 43 | assert_no_diff(expected_text, text) 44 | 45 | -------------------------------------------------------------------------------- /hyde/tests/ext/test_blockdown.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Use nose 4 | `$ pip install nose` 5 | `$ nosetests` 6 | """ 7 | from hyde.generator import Generator 8 | from hyde.site import Site 9 | 10 | from fswrap import File 11 | from pyquery import PyQuery 12 | 13 | TEST_SITE = File(__file__).parent.parent.child_folder('_test') 14 | 15 | 16 | class TestBlockdown(object): 17 | 18 | def setUp(self): 19 | TEST_SITE.make() 20 | TEST_SITE.parent.child_folder( 21 | 'sites/test_jinja').copy_contents_to(TEST_SITE) 22 | 23 | def tearDown(self): 24 | TEST_SITE.delete() 25 | 26 | def test_can_parse_blockdown(self): 27 | s = Site(TEST_SITE) 28 | s.config.plugins = ['hyde.ext.plugins.text.BlockdownPlugin'] 29 | txt ="This template tests to make sure blocks can be replaced with markdownish syntax." 30 | templ = """ 31 | {%% extends "base.html" %%} 32 | =====title======== 33 | %s 34 | ====/title========""" 35 | 36 | content = (templ.strip() % txt).strip() 37 | bd = File(TEST_SITE.child('content/blockdown.html')) 38 | bd.write(content) 39 | gen = Generator(s) 40 | gen.generate_resource_at_path(bd.path) 41 | res = s.content.resource_from_path(bd.path) 42 | target = File(s.config.deploy_root_path.child(res.relative_deploy_path)) 43 | assert target.exists 44 | text = target.read_all() 45 | q = PyQuery(text) 46 | assert q('title').text().strip() == txt.strip() 47 | -------------------------------------------------------------------------------- /hyde/layouts/basic/layout/macros.j2: -------------------------------------------------------------------------------- 1 | {% macro render_excerpt(res, class=None) %} 2 | {% refer to res.relative_path as post %} 3 |
8 | Posted: {{ res.meta.created.strftime('%a, %d %b %Y') }} 9 | 10 |
11 | {% endmacro %} 12 | 13 | {% macro render_nav(menu, cls=None) -%} 14 | {% if menu -%} 15 | 39 | {%- endif %} 40 | {%- endmacro %} -------------------------------------------------------------------------------- /hyde/layouts/starter/content/advanced/grouper.html: -------------------------------------------------------------------------------- 1 | --- 2 | index: 3 3 | title: Grouping 4 | tags: 5 | - sort 6 | - group 7 | learning_order: 2 8 | --- 9 | 10 | Grouping 11 | ======== 12 | 13 | Content is very often grouped by theme, size, location or any other 14 | conceivable measure. Groups can be traversed in a few ways in Hyde, and 15 | [sorted](sorter.html) at the same time. Here are two common ways: 16 | 17 | Walking all groups and subgroups 18 | -------------------------------- 19 | 20 | {# Resources are sorted by defining a sorter in the configuration file. #} 21 | 22 | {% for grp, res_walker in site.content.walk_level_groups() %} 23 | *{{ grp.description }}* 24 | 25 | {% for res in res_walker %} 26 | * [{{ res.slug|capitalize|replace("-"," ") }}]({{ res.full_url }}) 27 | ({{ res.name }}) 28 | {% endfor %} 29 | 30 | {% endfor %} 31 | 32 | {# The above code layout is not arbitrary. Remember that we're building 33 | a Markdown page so every space or line ending has a purpose. 34 | #} 35 | 36 | Listing only the specific (sub)group 37 | ------------------------------------ 38 | 39 | {% for res in site.content.walk_resources_grouped_by_advanced() %} 40 | * [{{ res.slug|capitalize|replace("-"," ") }}]({{ res.full_url }}) 41 | ({{ res.name }}) 42 | {% endfor %} 43 | 44 | {# You can also call the top level group "level" to get all resources that 45 | are in the group. Or you can list all resources of the same group in the 46 | current node with "resource.node.walk_resource_grouped_by_advanced()". 47 | #} 48 | 49 | {{ macros.render_bottom_article_nav() }} 50 | -------------------------------------------------------------------------------- /hyde/layouts/starter/content/advanced/tagger.html: -------------------------------------------------------------------------------- 1 | --- 2 | index: 4 3 | title: Tagging 4 | tags: 5 | - sort 6 | - tag 7 | learning_order: 3 8 | --- 9 | 10 | Tagging 11 | ======= 12 | 13 | It seems that human beings want to tag everything. You can do it with 14 | Hyde also. In this example **tags** are used to represent technologies 15 | used to build a particular advanced page. So you can see that the 16 | **sorting** was needed for all advanced topics, but **grouping** was 17 | used only for overview and grouping pages. 18 | 19 | Listing by tags 20 | --------------- 21 | 22 | {# You can grab the list of all tags ... #} 23 | {% for tag, meta in site.tagger.tags %} 24 | *{{ tag }}* 25 | 26 | {# ... and get all resurces tagged with that node. #} 27 | {% for res in resource.node.walk_resources_tagged_with(tag) %} 28 | * [{{ res.slug|capitalize|replace("-"," ") }}]({{ res.full_url }}) 29 | ({{ res.name }}) 30 | {% endfor %} 31 | 32 | {% endfor %} 33 | 34 | {# Another way to walk through resources tagged with a specific tag is 35 | to use a method that contains that tag's name. 36 | 37 | {% for res in resource.node.walk_resources_tagged_with_sort() %} 38 | {% endfor %} 39 | #} 40 | 41 | Tag combination 42 | --------------- 43 | 44 | You can also search for combination of tags. If you search for a 45 | resource that has **sort**, **tag** and **group** tags, only an 46 | {% for res in resource.node.walk_resources_tagged_with('sort+tag+group') -%} 47 | [{{ res.slug }}]({{ res.full_url }}) 48 | {%- endfor %} 49 | will be returned. 50 | 51 | {{ macros.render_bottom_article_nav() }} 52 | -------------------------------------------------------------------------------- /hyde/layouts/starter/layout/macros.j2: -------------------------------------------------------------------------------- 1 | {# Generates the main and basic menu from context data in the site's 2 | configuration file. 3 | #} 4 | {% macro render_basic_menu() -%} 5 | 11 | {%- endmacro %} 12 | 13 | 14 | {# Generates the advanced menu from all files located in the content/advanced 15 | folder. Only advanced section files have 'index' metadata, so we can be 16 | certain that no other files will creep in. 17 | #} 18 | {% macro render_advanced_menu() -%} 19 | 25 | {%- endmacro %} 26 | 27 | 28 | {# Advanced topics macro. Renders navigation at the end of an advanced 29 | article. It also depends on 'index' metadata. 30 | #} 31 | {% macro render_bottom_article_nav() %} 32 |
33 | {% if resource.next_by_index is not none -%} 34 | 37 | {%- endif %} 38 | 39 | {% if resource.prev_by_index is not none -%} 40 | 43 | {%- endif %} 44 |
45 | {% endmacro %} 46 | -------------------------------------------------------------------------------- /hyde/util.py: -------------------------------------------------------------------------------- 1 | """ 2 | Module for python 2.6 compatibility. 3 | """ 4 | import os 5 | from functools import partial 6 | from itertools import izip, tee 7 | 8 | 9 | def make_method(method_name, method_): 10 | def method__(*args, **kwargs): 11 | return method_(*args, **kwargs) 12 | method__.__name__ = method_name 13 | return method__ 14 | 15 | 16 | def add_property(obj, method_name, method_, *args, **kwargs): 17 | m = make_method(method_name, partial(method_, *args, **kwargs)) 18 | setattr(obj, method_name, property(m)) 19 | 20 | 21 | def add_method(obj, method_name, method_, *args, **kwargs): 22 | m = make_method(method_name, partial(method_, *args, **kwargs)) 23 | setattr(obj, method_name, m) 24 | 25 | 26 | def pairwalk(iterable): 27 | a, b = tee(iterable) 28 | next(b, None) 29 | return izip(a, b) 30 | 31 | 32 | def first_match(predicate, iterable): 33 | """ 34 | Gets the first element matched by the predicate 35 | in the iterable. 36 | """ 37 | for item in iterable: 38 | if predicate(item): 39 | return item 40 | return None 41 | 42 | 43 | def discover_executable(name, sitepath): 44 | """ 45 | Finds an executable in the given sitepath or in the 46 | path list provided by the PATH environment variable. 47 | """ 48 | 49 | # Check if an executable can be found in the site path first. 50 | # If not check the os $PATH for its presence. 51 | 52 | paths = [unicode(sitepath)] + os.environ['PATH'].split(os.pathsep) 53 | for path in paths: 54 | full_name = os.path.join(path, name) 55 | if os.path.exists(full_name): 56 | return full_name 57 | return None -------------------------------------------------------------------------------- /hyde/layouts/starter/content/advanced/overview.html: -------------------------------------------------------------------------------- 1 | --- 2 | index: 1 3 | title: Advanced topics 4 | tags: 5 | - sort 6 | - group 7 | - tag 8 | learning_order: 4 9 | --- 10 | 11 | More advanced topics 12 | ==================== 13 | 14 | If you have read and understood all basic topics covered in 15 | {% for res in site.content.walk_resources_grouped_by_basic()|reverse %} 16 | [{{ res.slug|capitalize|replace("-"," ") }}]({{ res.full_url }}) 17 | {% endfor %} 18 | then you are ready for some more advanced features. They are explained in 19 | the same way as the basic part, building on the knowledge of the previous, 20 | so it is recommended that you follow them in the listed order. 21 | 22 | 23 | {# List all resources from a group, sort them by index and list their tags. 24 | 25 | Sometimes you'll have to add HTML to a Markdown file for styling 26 | or adding some special features, and Markdown is OK with that. 27 | #} 28 |
    29 | {% for res in resource.node.walk_resources_sorted_by_learning_order() %} 30 | {# 31 | res.slug|capitalize|replace("-"," ") is just an example of how different 32 | commands can be chained together, but many times it will be much easier 33 | just to use meta data if a resource has it, like here -> res.meta.title 34 | #} 35 |
  1. {{ res.meta.title }} 36 | {% if res.name == "overview.html" %}(this file) {% endif %} 37 | 38 | tags: 39 | {% for tag in res.meta.tags %} 40 | {# 41 | After wring the tag name, check if that is the last tag in the list. If 42 | it is, don't append the comma at the end. 43 | #} 44 | {{ tag }}{% if tag != res.meta.tags[-1] %},{% endif %} 45 | {% endfor %} 46 | 47 |
  2. 48 | {% endfor %} 49 |
50 | 51 | {{ macros.render_bottom_article_nav() }} 52 | -------------------------------------------------------------------------------- /hyde/tests/ext/test_depends.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Use nose 4 | `$ pip install nose` 5 | `$ nosetests` 6 | """ 7 | from hyde.generator import Generator 8 | from hyde.site import Site 9 | 10 | from fswrap import File 11 | 12 | TEST_SITE = File(__file__).parent.parent.child_folder('_test') 13 | 14 | 15 | class TestDepends(object): 16 | 17 | def setUp(self): 18 | TEST_SITE.make() 19 | TEST_SITE.parent.child_folder( 20 | 'sites/test_jinja').copy_contents_to(TEST_SITE) 21 | TEST_SITE.parent.child_folder( 22 | 'templates/jinja2').copy_contents_to( 23 | TEST_SITE.child_folder('content')) 24 | 25 | def tearDown(self): 26 | TEST_SITE.delete() 27 | 28 | def test_depends(self): 29 | s = Site(TEST_SITE) 30 | s.config.plugins = ['hyde.ext.plugins.meta.MetaPlugin', 31 | 'hyde.ext.plugins.depends.DependsPlugin'] 32 | text = """ 33 | === 34 | depends: index.html 35 | === 36 | 37 | """ 38 | inc = File(TEST_SITE.child('content/inc.md')) 39 | inc.write(text) 40 | gen = Generator(s) 41 | gen.load_site_if_needed() 42 | gen.load_template_if_needed() 43 | def dateformat(x): 44 | return x.strftime('%Y-%m-%d') 45 | gen.template.env.filters['dateformat'] = dateformat 46 | gen.generate_resource_at_path(inc.name) 47 | res = s.content.resource_from_relative_path(inc.name) 48 | assert len(res.depends) == 1 49 | assert 'index.html' in res.depends 50 | deps = list(gen.get_dependencies(res)) 51 | assert len(deps) == 3 52 | 53 | assert 'helpers.html' in deps 54 | assert 'layout.html' in deps 55 | assert 'index.html' in deps -------------------------------------------------------------------------------- /hyde/tests/ext/test_urlcleaner.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Use nose 4 | `$ pip install nose` 5 | `$ nosetests` 6 | """ 7 | from hyde.generator import Generator 8 | from hyde.model import Config 9 | from hyde.site import Site 10 | 11 | from fswrap import File, Folder 12 | import yaml 13 | 14 | TEST_SITE = File(__file__).parent.parent.child_folder('_test') 15 | 16 | 17 | class TestUrlCleaner(object): 18 | 19 | def setUp(self): 20 | TEST_SITE.make() 21 | TEST_SITE.parent.child_folder( 22 | 'sites/test_jinja').copy_contents_to(TEST_SITE) 23 | 24 | def tearDown(self): 25 | TEST_SITE.delete() 26 | 27 | def test_url_cleaner(self): 28 | s = Site(TEST_SITE) 29 | cfg = """ 30 | plugins: 31 | - hyde.ext.plugins.urls.UrlCleanerPlugin 32 | urlcleaner: 33 | index_file_names: 34 | - about.html 35 | strip_extensions: 36 | - html 37 | append_slash: true 38 | """ 39 | s.config = Config(TEST_SITE, config_dict=yaml.load(cfg)) 40 | text = """ 41 | {% extends "base.html" %} 42 | 43 | {% block main %} 44 | 45 | 46 | {% endblock %} 47 | """ 48 | 49 | about2 = File(TEST_SITE.child('content/test.html')) 50 | about2.write(text) 51 | gen = Generator(s) 52 | gen.generate_all() 53 | 54 | from pyquery import PyQuery 55 | target = File(Folder(s.config.deploy_root_path).child('test.html')) 56 | text = target.read_all() 57 | q = PyQuery(text) 58 | assert q('a#index').attr("href") == '/' 59 | assert q('a#blog').attr("href") == '/blog/2010/december/merry-christmas' -------------------------------------------------------------------------------- /hyde/tests/ssp/site.yaml: -------------------------------------------------------------------------------- 1 | mode: development 2 | media_root: media # Relative path from content folder. 3 | media_url: /media # URL where the media files are served from. 4 | base_url: / # The base url for autogenerated links. 5 | plugins: 6 | - hyde.ext.plugins.meta.MetaPlugin 7 | - hyde.ext.plugins.meta.AutoExtendPlugin 8 | - hyde.ext.plugins.meta.SorterPlugin 9 | - hyde.ext.plugins.meta.TaggerPlugin 10 | - hyde.ext.plugins.text.SyntextPlugin 11 | - hyde.ext.plugins.text.TextlinksPlugin 12 | - ext.banner.BannerPlugin 13 | context: 14 | data: 15 | tweet_via: ringce 16 | menu: 17 | - 18 | name: Home 19 | description: Home Page 20 | css_class: home 21 | type: page 22 | url: index.html 23 | - 24 | name: Portfolio 25 | description: Portfolio 26 | css_class: portfolio 27 | type: node 28 | url: portfolio 29 | - 30 | name: Blog 31 | description: Blog 32 | css_class: blog 33 | type: node 34 | url: blog 35 | - 36 | name: About 37 | description: About 38 | css_class: about 39 | type: page 40 | url: about.html 41 | meta: 42 | nodemeta: meta.yaml 43 | created: !!timestamp 2010-01-01 00:00:00 44 | author: Lakshmi Vyasarajan 45 | sorter: 46 | time: 47 | attr: 48 | - meta.created 49 | reverse: true 50 | filters: 51 | source.kind: html 52 | meta.listable: true 53 | tagger: 54 | sorter: time 55 | archives: 56 | blog: 57 | source: blog 58 | target: blog/tags 59 | template: tagged_posts.j2 60 | archive_extension: html 61 | -------------------------------------------------------------------------------- /hyde/layouts/starter/layout/base.j2: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | Hyde Starter Kit {% if resource.meta.title-%} 7 | | {{ resource.meta.title}}{%-endif %} 8 | 9 | 10 | 11 | 12 |
13 |

Hyde Starter Kit

14 |

Know your tool

15 |
16 | 25 |
26 | {# Main content block. Notice it has to pass through the 27 | Markdown filter to generate HTML. If a block in content 28 | pages contains only markup, you can omit the filter. 29 | #} 30 | {% filter markdown -%} 31 | {% block content %}{% endblock %} 32 | {%- endfilter %} 33 |
34 | 35 | {# Some parts of the web are not needed for development and can 36 | wait for production (e.g. analytics). They can be included in 37 | the final production build. Create a new config file, extend 38 | the original one and override the "mode" property; then build 39 | the site with the new "production configuration". 40 | #} 41 | {% if site.config.mode == "production" -%} 42 | {% include "ga.j2" %} 43 | {%- endif %} 44 | 45 | 46 | -------------------------------------------------------------------------------- /hyde/tests/ext/test_textlinks.py: -------------------------------------------------------------------------------- 1 | """ 2 | Use nose 3 | `$ pip install nose` 4 | `$ nosetests` 5 | """ 6 | from hyde.generator import Generator 7 | from hyde.site import Site 8 | from urllib import quote 9 | 10 | from fswrap import File 11 | 12 | TEST_SITE = File(__file__).parent.parent.child_folder('_test') 13 | 14 | 15 | class TestTextlinks(object): 16 | 17 | def setUp(self): 18 | TEST_SITE.make() 19 | TEST_SITE.parent.child_folder( 20 | 'sites/test_jinja').copy_contents_to(TEST_SITE) 21 | 22 | def tearDown(self): 23 | TEST_SITE.delete() 24 | 25 | 26 | 27 | def test_textlinks(self): 28 | d = { 29 | 'objects': 'template/variables', 30 | 'plugins': 'plugins/metadata', 31 | 'sorter': 'plugins/sorter' 32 | } 33 | text = u""" 34 | {%% markdown %%} 35 | [[!!img/hyde-logo.png]] 36 | * [Rich object model][hyde objects] and 37 | [overridable hierarchical metadata]([[ %(plugins)s ]]) thats available for use in 38 | templates. 39 | * Configurable [sorting][], filtering and grouping support. 40 | 41 | [hyde objects]: [[ %(objects)s ]] 42 | [sorting]: [[%(sorter)s]] 43 | {%% endmarkdown %%} 44 | """ 45 | site = Site(TEST_SITE) 46 | site.config.plugins = ['hyde.ext.plugins.text.TextlinksPlugin'] 47 | site.config.base_url = 'http://example.com/' 48 | site.config.media_url = '/media' 49 | tlink = File(site.content.source_folder.child('tlink.html')) 50 | tlink.write(text % d) 51 | print tlink.read_all() 52 | gen = Generator(site) 53 | gen.generate_all() 54 | f = File(site.config.deploy_root_path.child(tlink.name)) 55 | assert f.exists 56 | html = f.read_all() 57 | assert html 58 | for name, path in d.items(): 59 | 60 | assert site.config.base_url + quote(path) in html 61 | assert '/media/img/hyde-logo.png' in html 62 | -------------------------------------------------------------------------------- /hyde/layouts/basic/site.yaml: -------------------------------------------------------------------------------- 1 | mode: development 2 | media_root: media # Relative path from content folder. 3 | media_url: /media # URL where the media files are served from. 4 | base_url: / # The base url for autogenerated links. 5 | plugins: 6 | - hyde.ext.plugins.meta.MetaPlugin 7 | - hyde.ext.plugins.meta.AutoExtendPlugin 8 | - hyde.ext.plugins.meta.SorterPlugin 9 | - hyde.ext.plugins.meta.TaggerPlugin 10 | - hyde.ext.plugins.text.SyntextPlugin 11 | - hyde.ext.plugins.text.TextlinksPlugin 12 | context: 13 | data: 14 | tweet_via: ringce 15 | menu: 16 | - 17 | name: Home 18 | description: Home Page 19 | css_class: home 20 | type: page 21 | url: index.html 22 | - 23 | name: Portfolio 24 | description: Portfolio 25 | css_class: portfolio 26 | type: node 27 | url: portfolio 28 | - 29 | name: Blog 30 | description: Blog 31 | css_class: blog 32 | type: node 33 | url: blog 34 | - 35 | name: About 36 | description: About 37 | css_class: about 38 | type: page 39 | url: about.html 40 | meta: 41 | nodemeta: meta.yaml 42 | created: !!timestamp 2010-01-01 00:00:00 43 | author: Lakshmi Vyasarajan 44 | sorter: 45 | time: 46 | attr: 47 | - meta.created 48 | reverse: true 49 | filters: 50 | source.kind: html 51 | meta.listable: true 52 | tagger: 53 | sorter: time 54 | archives: 55 | blog: 56 | source: blog 57 | target: blog/tags 58 | template: tagged_posts.j2 59 | archive_extension: html 60 | meta: 61 | listable: false 62 | -------------------------------------------------------------------------------- /hyde/tests/ext/test_flattener.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Use nose 4 | `$ pip install nose` 5 | `$ nosetests` 6 | """ 7 | 8 | from hyde.generator import Generator 9 | from hyde.site import Site 10 | from hyde.model import Config 11 | 12 | from fswrap import File 13 | 14 | TEST_SITE = File(__file__).parent.parent.child_folder('_test') 15 | 16 | 17 | class TestFlattner(object): 18 | 19 | def setUp(self): 20 | TEST_SITE.make() 21 | TEST_SITE.parent.child_folder( 22 | 'sites/test_jinja').copy_contents_to(TEST_SITE) 23 | 24 | def tearDown(self): 25 | TEST_SITE.delete() 26 | 27 | def test_can_flatten(self): 28 | s = Site(TEST_SITE) 29 | cfg = """ 30 | plugins: 31 | - hyde.ext.plugins.structure.FlattenerPlugin 32 | flattener: 33 | items: 34 | - 35 | source: blog 36 | target: '' 37 | """ 38 | import yaml 39 | s.config = Config(TEST_SITE, config_dict=yaml.load(cfg)) 40 | s.load() 41 | gen = Generator(s) 42 | gen.generate_all() 43 | 44 | assert not s.config.deploy_root_path.child_folder('blog').exists 45 | assert File(s.config.deploy_root_path.child('merry-christmas.html')).exists 46 | 47 | def test_flattener_fixes_nodes(self): 48 | s = Site(TEST_SITE) 49 | cfg = """ 50 | plugins: 51 | - hyde.ext.plugins.structure.FlattenerPlugin 52 | flattener: 53 | items: 54 | - 55 | source: blog 56 | target: '' 57 | """ 58 | import yaml 59 | s.config = Config(TEST_SITE, config_dict=yaml.load(cfg)) 60 | s.load() 61 | gen = Generator(s) 62 | gen.generate_all() 63 | blog_node = s.content.node_from_relative_path('blog') 64 | 65 | assert blog_node 66 | assert blog_node.url == '/' 67 | 68 | 69 | -------------------------------------------------------------------------------- /hyde/layouts/basic/layout/atom.j2: -------------------------------------------------------------------------------- 1 | {% from "macros.j2" import render_excerpt, render_post with context %} 2 | 3 | 4 | 5 | {% block title %}{{ resource.meta.title|default(feed_title) }}{% endblock %} 6 | 7 | {% block self_url %} 8 | 9 | {% endblock %} 10 | 11 | {% block site_url %} 12 | 13 | {% endblock %} 14 | 15 | {% block feed_extra %} 16 | {% endblock %} 17 | 18 | {{ time_now|xmldatetime }} 19 | 20 | {{ content_url(resource.url) }}/ 21 | 22 | {% for res in resource.node.walk_resources_sorted_by_time() %} 23 | 24 | {{ res.meta.title|forceescape }} 25 | {{ res.meta.author }} 26 | 27 | {{ res.meta.created|xmldatetime }} 28 | {{ res.meta.created|xmldatetime }} 29 | {{ content_url(res.url) }} 30 | {% for tag in res.meta.tags %} 31 | 34 | {% endfor %} 35 | 36 | 37 | {% refer to res.relative_path as article -%} 38 | {% filter forceescape -%} 39 | {% if resource.meta.excerpts_only -%} 40 | {{ article.image|markdown|typogrify }} 41 | {{ article.excerpt|markdown|typogrify }} 42 | {%- else %} 43 | {{ article.post|markdown|typogrify }} 44 | {%- endif %} 45 | {%- endfilter %} 46 | 47 | 48 | {% endfor %} 49 | 50 | -------------------------------------------------------------------------------- /hyde/ext/publishers/ssh.py: -------------------------------------------------------------------------------- 1 | """ 2 | SSH publisher 3 | ============= 4 | Contains classes and utilities that help publishing a hyde website 5 | via ssh/rsync. 6 | 7 | Usage 8 | ----- 9 | In site.yaml, add the following lines 10 | 11 | publisher: 12 | ssh: 13 | type: hyde.ext.publishers.ssh.SSH 14 | username: username 15 | server: ssh.server.com 16 | target: /www/username/mysite/ 17 | command: rsync 18 | opts: -r -e ssh 19 | 20 | Note that the final two settings (command and opts) are optional, and the 21 | values shown are the default. Username is also optional. 22 | With this set, generate and publish the site as follows: 23 | 24 | >$ hyde gen 25 | >$ hyde publish -p ssh 26 | 27 | For the above options, this will lead to execution of the following command 28 | within the ``deploy/`` directory: 29 | 30 | rsync -r -e ssh ./ username@ssh.server.com:/www/username/mysite/ 31 | 32 | """ 33 | from hyde.publisher import Publisher 34 | 35 | from subprocess import Popen, PIPE 36 | 37 | class SSH(Publisher): 38 | def initialize(self, settings): 39 | self.settings = settings 40 | self.username = settings.username 41 | self.server = settings.server 42 | self.target = settings.target 43 | self.command = getattr(settings, 'command', 'rsync') 44 | self.opts = getattr(settings, 'opts', '-r -e ssh') 45 | 46 | def publish(self): 47 | command = "{command} {opts} ./ {username}{server}:{target}".format( 48 | command=self.command, 49 | opts=self.opts, 50 | username=self.username+'@' if self.username else '', 51 | server=self.server, 52 | target=self.target) 53 | deploy_path = self.site.config.deploy_root_path.path 54 | 55 | cmd = Popen(command, cwd=unicode(deploy_path), stdout=PIPE, shell=True) 56 | cmdresult = cmd.communicate()[0] 57 | if cmd.returncode: 58 | raise Exception(cmdresult) 59 | -------------------------------------------------------------------------------- /hyde/ext/plugins/depends.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Depends plugin 4 | 5 | /// Experimental: Not working yet. 6 | """ 7 | 8 | from hyde.plugin import Plugin 9 | 10 | class DependsPlugin(Plugin): 11 | """ 12 | The plugin class setting explicit dependencies. 13 | """ 14 | 15 | def __init__(self, site): 16 | super(DependsPlugin, self).__init__(site) 17 | 18 | def begin_site(self): 19 | """ 20 | Initialize dependencies. 21 | 22 | Go through all the nodes and resources to initialize 23 | dependencies at each level. 24 | """ 25 | for resource in self.site.content.walk_resources(): 26 | self._update_resource(resource) 27 | 28 | 29 | def _update_resource(self, resource): 30 | """ 31 | If the meta data for the resource contains a depends attribute, 32 | this plugin adds an entry to the depends property of the 33 | resource. 34 | 35 | The dependency can contain the following template variables: 36 | node, resource, site, context. 37 | 38 | The following strings are valid: 39 | '{node.module}/dependencies/{resource.source.name_without_extension}.inc' 40 | '{context.dependency_folder}/{resource.source.name_without_extension}.{site.meta.depext}' 41 | """ 42 | depends = [] 43 | try: 44 | depends = resource.meta.depends 45 | except AttributeError: 46 | return 47 | 48 | if not hasattr(resource, 'depends') or not resource.depends: 49 | resource.depends = [] 50 | 51 | if isinstance(depends, basestring): 52 | depends = [depends] 53 | 54 | for dep in depends: 55 | resource.depends.append(dep.format(node=resource.node, 56 | resource=resource, 57 | site=self.site, 58 | context=self.site.context)) 59 | resource.depends = list(set(resource.depends)) 60 | -------------------------------------------------------------------------------- /hyde/tests/ext/test_drafts.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Use nose 4 | `$ pip install nose` 5 | `$ nosetests` 6 | """ 7 | 8 | from hyde.generator import Generator 9 | from hyde.site import Site 10 | from hyde.model import Config 11 | 12 | from fswrap import File 13 | 14 | TEST_SITE = File(__file__).parent.parent.child_folder('_test') 15 | 16 | DRAFT_POST = """ 17 | 18 | --- 19 | is_draft: true 20 | --- 21 | 22 | A draft post. 23 | 24 | """ 25 | 26 | class TestDrafts(object): 27 | 28 | def setUp(self): 29 | TEST_SITE.make() 30 | TEST_SITE.parent.child_folder( 31 | 'sites/test_jinja').copy_contents_to(TEST_SITE) 32 | draft = TEST_SITE.child_file('content/blog/2013/may/draft-post.html') 33 | draft.parent.make() 34 | draft.write(DRAFT_POST) 35 | 36 | def tearDown(self): 37 | TEST_SITE.delete() 38 | 39 | def test_drafts_are_skipped_in_production(self): 40 | s = Site(TEST_SITE) 41 | cfg = """ 42 | mode: production 43 | plugins: 44 | - hyde.ext.plugins.meta.MetaPlugin 45 | - hyde.ext.plugins.blog.DraftsPlugin 46 | """ 47 | import yaml 48 | s.config = Config(TEST_SITE, config_dict=yaml.load(cfg)) 49 | s.load() 50 | gen = Generator(s) 51 | gen.generate_all() 52 | assert not s.config.deploy_root_path.child_file( 53 | 'blog/2013/may/draft-post.html').exists 54 | 55 | def test_drafts_are_published_in_development(self): 56 | s = Site(TEST_SITE) 57 | cfg = """ 58 | mode: development 59 | plugins: 60 | - hyde.ext.plugins.meta.MetaPlugin 61 | - hyde.ext.plugins.blog.DraftsPlugin 62 | """ 63 | import yaml 64 | s.config = Config(TEST_SITE, config_dict=yaml.load(cfg)) 65 | s.load() 66 | gen = Generator(s) 67 | gen.generate_all() 68 | assert s.config.deploy_root_path.child_file( 69 | 'blog/2013/may/draft-post.html').exists 70 | 71 | 72 | -------------------------------------------------------------------------------- /hyde/layouts/starter/content/advanced/sorter.html: -------------------------------------------------------------------------------- 1 | --- 2 | index: 2 3 | title: Sorting 4 | tags: 5 | - sort 6 | learning_order: 1 7 | --- 8 | 9 | Sorting 10 | ======= 11 | 12 | There will come a time when you will need to list and sort resources. Hyde 13 | allows you to walk the site tree and sort the resources by the predefined 14 | settings in your configuration file. 15 | 16 | You can list and sort by name all your content files. 17 | 18 | {# With every sorter defined in the configuration file, nodes get a method 19 | to call. Notice that in the first and last example the method is called 20 | on the whole content of the site, while the second example shows how to 21 | invoke it only on one specific node (in this case the current one). 22 | 23 | Also, some new Jinja filters were used to style the output. 24 | #} 25 | {% for res in site.content.walk_resources_sorted_by_name() %} 26 | * [{{ res.slug|capitalize|replace("-"," ") }}]({{ res.full_url }}) 27 | ({{ res.name }}) 28 | {% endfor %} 29 | 30 | Or list only those in the current node (folder). In this case that would be 31 | all advanced topics. 32 | 33 | {# Have in mind that using the next example in a content page (like here) or 34 | using it in a layout (Jinja template that is extended or include by 35 | content pages) will yield very different results. 36 | 37 | In this case it will be called only once, for this resource, and shown 38 | only on this page. If it was in a layout, it would be called for EVERY 39 | resource that uses that layout. In that case the context would be 40 | different, the parent node of the resource could be different and the 41 | results will probably be different too. 42 | #} 43 | {% for res in resource.node.walk_resources_sorted_by_index() %} 44 | {{ loop.index }}. [{{ res.slug|capitalize }}]({{ res.full_url }}) 45 | {% endfor %} 46 | 47 | Or sort files by type and then by size. 48 | 49 | {% for res in site.content.walk_resources_sorted_by_file_type() %} 50 | * [{{ res.source_file.kind|upper }}] {{ res.name }} 51 | {% endfor %} 52 | 53 | {{ macros.render_bottom_article_nav() }} 54 | -------------------------------------------------------------------------------- /hyde/layouts/basic/layout/blog.j2: -------------------------------------------------------------------------------- 1 | {% extends "base.j2" %} 2 | 3 | {% block main -%} 4 |
5 | 38 |

39 | 40 | {{ resource.meta.title }} 41 | 42 |

43 | 46 | 47 | {% if resource.meta.tags %} 48 | 57 | {% endif %} 58 | {% filter markdown|typogrify -%} 59 | {% mark post -%} 60 | {% block post -%}{%- endblock %} 61 | {%- endmark %} 62 | {%- endfilter %} 63 |
64 | 65 | {%- endblock %} -------------------------------------------------------------------------------- /hyde/publisher.py: -------------------------------------------------------------------------------- 1 | import abc 2 | from operator import attrgetter 3 | 4 | from commando.util import getLoggerWithNullHandler, load_python_object 5 | 6 | """ 7 | Contains abstract classes and utilities that help publishing a website to a 8 | server. 9 | """ 10 | 11 | class Publisher(object): 12 | """ 13 | The abstract base class for publishers. 14 | """ 15 | 16 | __metaclass__ = abc.ABCMeta 17 | 18 | def __init__(self, site, settings, message): 19 | super(Publisher, self).__init__() 20 | self.logger = getLoggerWithNullHandler( 21 | 'hyde.engine.%s' % self.__class__.__name__) 22 | self.site = site 23 | self.message = message 24 | self.initialize(settings) 25 | 26 | @abc.abstractmethod 27 | def initialize(self, settings): pass 28 | 29 | @abc.abstractmethod 30 | def publish(self): 31 | if not self.site.config.deploy_root_path.exists: 32 | raise Exception("Please generate the site first") 33 | 34 | @staticmethod 35 | def load_publisher(site, publisher, message): 36 | logger = getLoggerWithNullHandler('hyde.engine.publisher') 37 | try: 38 | settings = attrgetter("publisher.%s" % publisher)(site.config) 39 | except AttributeError: 40 | settings = False 41 | 42 | if not settings: 43 | # Find the first configured publisher 44 | try: 45 | publisher = site.config.publisher.__dict__.iterkeys().next() 46 | logger.warning("No default publisher configured. Using: %s" % publisher) 47 | settings = attrgetter("publisher.%s" % publisher)(site.config) 48 | except (AttributeError, StopIteration): 49 | logger.error( 50 | "Cannot find the publisher configuration: %s" % publisher) 51 | raise 52 | 53 | if not hasattr(settings, 'type'): 54 | logger.error( 55 | "Publisher type not specified: %s" % publisher) 56 | raise Exception("Please specify the publisher type in config.") 57 | 58 | pub_class = load_python_object(settings.type) 59 | return pub_class(site, settings, message) -------------------------------------------------------------------------------- /hyde/tests/sites/test_jinja/layout/base.html: -------------------------------------------------------------------------------- 1 | {% extends "root.html" %} 2 | {% block all %} 3 | 4 | 5 | 6 | {% block starthead %}{% endblock starthead %} 7 | 8 | 9 | 10 | {% block title %}{{resource.meta.title}}{% endblock %} 11 | 12 | 13 | 14 | 15 | 16 | 17 | {% block favicons %} 18 | 19 | 20 | 21 | {% endblock favicons %} 22 | 23 | {% block css %} 24 | 25 | {% endblock css %} 26 | {% block endhead %}{% endblock endhead %} 27 | 28 | 29 | {% block content %} 30 |
31 | {% block container %} 32 |
33 | {% block header %}{% endblock header %} 34 |
35 |
36 | {% block main %}{% endblock main %} 37 |
38 |
39 | {% block footer %}{% endblock %} 40 |
41 | {% endblock container%} 42 |
43 | {% endblock content%} 44 | 45 | {% block js %} 46 | 47 | {% block jquery %} 48 | 49 | 50 | {% endblock jquery %} 51 | 52 | {% block scripts %}{% endblock scripts %} 53 | {%endblock js %} 54 | 55 | 56 | 57 | {% endblock all %} -------------------------------------------------------------------------------- /hyde/tests/sites/test_grouper/layout/base.html: -------------------------------------------------------------------------------- 1 | {% extends "root.html" %} 2 | {% block all %} 3 | 4 | 5 | 6 | {% block starthead %}{% endblock starthead %} 7 | 8 | 9 | 10 | {% block title %}{{resource.meta.title}}{% endblock %} 11 | 12 | 13 | 14 | 15 | 16 | 17 | {% block favicons %} 18 | 19 | 20 | 21 | {% endblock favicons %} 22 | 23 | {% block css %} 24 | 25 | {% endblock css %} 26 | {% block endhead %}{% endblock endhead %} 27 | 28 | 29 | {% block content %} 30 |
31 | {% block container %} 32 |
33 | {% block header %}{% endblock header %} 34 |
35 |
36 | {% block main %}{% endblock main %} 37 |
38 |
39 | {% block footer %}{% endblock %} 40 |
41 | {% endblock container%} 42 |
43 | {% endblock content%} 44 | 45 | {% block js %} 46 | 47 | {% block jquery %} 48 | 49 | 50 | {% endblock jquery %} 51 | 52 | {% block scripts %}{% endblock scripts %} 53 | {%endblock js %} 54 | 55 | 56 | 57 | {% endblock all %} -------------------------------------------------------------------------------- /hyde/tests/ext/test_auto_extend.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Use nose 4 | `$ pip install nose` 5 | `$ nosetests` 6 | """ 7 | from hyde.generator import Generator 8 | from hyde.site import Site 9 | 10 | 11 | from fswrap import File 12 | from nose.tools import nottest 13 | from pyquery import PyQuery 14 | 15 | TEST_SITE = File(__file__).parent.parent.child_folder('_test') 16 | 17 | 18 | class TestAutoExtend(object): 19 | 20 | def setUp(self): 21 | TEST_SITE.make() 22 | TEST_SITE.parent.child_folder( 23 | 'sites/test_jinja').copy_contents_to(TEST_SITE) 24 | 25 | def tearDown(self): 26 | TEST_SITE.delete() 27 | 28 | @nottest 29 | def assert_extended(self, s, txt, templ): 30 | content = (templ.strip() % txt).strip() 31 | bd = File(TEST_SITE.child('content/auto_extend.html')) 32 | bd.write(content) 33 | gen = Generator(s) 34 | gen.generate_resource_at_path(bd.path) 35 | res = s.content.resource_from_path(bd.path) 36 | target = File(s.config.deploy_root_path.child(res.relative_deploy_path)) 37 | assert target.exists 38 | text = target.read_all() 39 | q = PyQuery(text) 40 | assert q('title').text().strip() == txt.strip() 41 | 42 | def test_can_auto_extend(self): 43 | s = Site(TEST_SITE) 44 | s.config.plugins = ['hyde.ext.plugins.meta.MetaPlugin', 45 | 'hyde.ext.plugins.meta.AutoExtendPlugin', 46 | 'hyde.ext.plugins.text.BlockdownPlugin'] 47 | txt ="This template tests to make sure blocks can be replaced with markdownish syntax." 48 | templ = """ 49 | --- 50 | extends: base.html 51 | --- 52 | =====title======== 53 | %s 54 | ====/title========""" 55 | self.assert_extended(s, txt, templ) 56 | 57 | 58 | 59 | def test_can_auto_extend_with_default_blocks(self): 60 | s = Site(TEST_SITE) 61 | s.config.plugins = ['hyde.ext.plugins.meta.MetaPlugin', 62 | 'hyde.ext.plugins.meta.AutoExtendPlugin', 63 | 'hyde.ext.plugins.text.BlockdownPlugin'] 64 | txt ="This template tests to make sure blocks can be replaced with markdownish syntax." 65 | templ = """ 66 | --- 67 | extends: base.html 68 | default_block: title 69 | --- 70 | %s 71 | """ 72 | self.assert_extended(s, txt, templ) 73 | -------------------------------------------------------------------------------- /hyde/tests/ext/test_stylus.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Use nose 4 | `$ pip install nose` 5 | `$ nosetests` 6 | """ 7 | from hyde.model import Expando 8 | from hyde.generator import Generator 9 | from hyde.site import Site 10 | 11 | from fswrap import File, Folder 12 | 13 | STYLUS_SOURCE = File(__file__).parent.child_folder('stylus') 14 | TEST_SITE = File(__file__).parent.parent.child_folder('_test') 15 | 16 | class TestStylus(object): 17 | 18 | def setUp(self): 19 | TEST_SITE.make() 20 | TEST_SITE.parent.child_folder( 21 | 'sites/test_jinja').copy_contents_to(TEST_SITE) 22 | STYLUS_SOURCE.copy_contents_to(TEST_SITE.child('content/media/css')) 23 | File(TEST_SITE.child('content/media/css/site.css')).delete() 24 | 25 | 26 | def tearDown(self): 27 | TEST_SITE.delete() 28 | 29 | def test_can_execute_stylus(self): 30 | s = Site(TEST_SITE) 31 | s.config.plugins = ['hyde.ext.plugins.css.StylusPlugin'] 32 | paths = ['/usr/local/share/npm/bin/stylus'] 33 | for path in paths: 34 | if File(path).exists: 35 | s.config.stylus = Expando(dict(app=path)) 36 | source = TEST_SITE.child('content/media/css/site.styl') 37 | target = File(Folder(s.config.deploy_root_path).child('media/css/site.css')) 38 | gen = Generator(s) 39 | gen.generate_resource_at_path(source) 40 | 41 | assert target.exists 42 | text = target.read_all() 43 | expected_text = File(STYLUS_SOURCE.child('expected-site.css')).read_all() 44 | assert text.strip() == expected_text.strip() 45 | 46 | def test_can_compress_with_stylus(self): 47 | s = Site(TEST_SITE) 48 | s.config.mode = "production" 49 | s.config.plugins = ['hyde.ext.plugins.css.StylusPlugin'] 50 | paths = ['/usr/local/share/npm/bin/stylus'] 51 | for path in paths: 52 | if File(path).exists: 53 | s.config.stylus = Expando(dict(app=path)) 54 | source = TEST_SITE.child('content/media/css/site.styl') 55 | target = File(Folder(s.config.deploy_root_path).child('media/css/site.css')) 56 | gen = Generator(s) 57 | gen.generate_resource_at_path(source) 58 | 59 | assert target.exists 60 | text = target.read_all() 61 | expected_text = File(STYLUS_SOURCE.child('expected-site-compressed.css')).read_all() 62 | assert text.strip() == expected_text.strip() 63 | -------------------------------------------------------------------------------- /hyde/ext/plugins/urls.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Contains classes and utilities related to hyde urls. 4 | """ 5 | from hyde.plugin import Plugin 6 | from hyde.site import Site 7 | 8 | from functools import wraps 9 | from fswrap import File 10 | 11 | class UrlCleanerPlugin(Plugin): 12 | """ 13 | Url Cleaner plugin for hyde. Adds to hyde the ability to generate clean 14 | urls. 15 | 16 | Configuration example 17 | --------------------- 18 | #yaml 19 | urlcleaner: 20 | index_file_names: 21 | # Identifies the files that represents a directory listing. 22 | # These file names are automatically stripped away when 23 | # the content_url function is called. 24 | - index.html 25 | strip_extensions: 26 | # The following extensions are automatically removed when 27 | # generating the urls using content_url function. 28 | - html 29 | # This option will append a slash to the end of directory paths 30 | append_slash: true 31 | """ 32 | 33 | def __init__(self, site): 34 | super(UrlCleanerPlugin, self).__init__(site) 35 | 36 | def begin_site(self): 37 | """ 38 | Replace the content_url method in the site object with a custom method 39 | that cleans urls based on the given configuration. 40 | """ 41 | config = self.site.config 42 | 43 | if not hasattr(config, 'urlcleaner'): 44 | return 45 | 46 | if (hasattr(Site, '___url_cleaner_patched___')): 47 | return 48 | 49 | settings = config.urlcleaner 50 | 51 | def clean_url(urlgetter): 52 | @wraps(urlgetter) 53 | def wrapper(site, path, safe=None): 54 | url = urlgetter(site, path, safe) 55 | index_file_names = getattr(settings, 56 | 'index_file_names', 57 | ['index.html']) 58 | rep = File(url) 59 | if rep.name in index_file_names: 60 | url = rep.parent.path.rstrip('/') 61 | if hasattr(settings, 'append_slash') and \ 62 | settings.append_slash: 63 | url += '/' 64 | elif hasattr(settings, 'strip_extensions'): 65 | if rep.kind in settings.strip_extensions: 66 | url = rep.parent.child(rep.name_without_extension) 67 | return url or '/' 68 | return wrapper 69 | 70 | Site.___url_cleaner_patched___ = True 71 | Site.content_url = clean_url(Site.content_url) 72 | -------------------------------------------------------------------------------- /hyde/ext/plugins/languages.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Contains classes to help manage multi-language pages. 4 | """ 5 | 6 | from hyde.plugin import Plugin 7 | 8 | class LanguagePlugin(Plugin): 9 | """ 10 | Each page should be tagged with a language using `language` meta 11 | data. Each page should also have an UUID stored in `uuid` meta 12 | data. Pages with different languages but the same UUID will be 13 | considered a translation of each other. 14 | 15 | For example, consider that you have the following content tree: 16 | - `en/` 17 | - `fr/` 18 | 19 | In `en/meta.yaml`, you set `language: en` and in `fr/meta.yaml`, you 20 | set `language: fr`. In `en/my-first-page.html`, you put something like 21 | this:: 22 | ------- 23 | uuid: page1 24 | ------- 25 | My first page 26 | 27 | And in `fr/ma-premiere-page.html`, you put something like this:: 28 | ------- 29 | uuid: page1 30 | ------- 31 | Ma première page 32 | 33 | You'll get a `translations` attribute attached to the resource 34 | with the list of resources that are a translation of this one. 35 | """ 36 | 37 | def __init__(self, site): 38 | super(LanguagePlugin, self).__init__(site) 39 | self.languages = {} # Associate a UUID to the list of resources available 40 | 41 | def begin_site(self): 42 | """ 43 | Initialize plugin. Build the language tree for each node 44 | """ 45 | # Build association between UUID and list of resources 46 | for node in self.site.content.walk(): 47 | for resource in node.resources: 48 | try: 49 | uuid = resource.meta.uuid 50 | language = resource.meta.language 51 | except AttributeError: 52 | continue 53 | if uuid not in self.languages: 54 | self.languages[uuid] = [] 55 | self.languages[uuid].append(resource) 56 | # Add back the information about other languages 57 | for uuid, resources in self.languages.items(): 58 | for resource in resources: 59 | language = resource.meta.language 60 | resource.translations = \ 61 | [r for r in resources 62 | if r.meta.language != language] 63 | translations = ",".join([t.meta.language for t in resource.translations]) 64 | self.logger.debug( 65 | "Adding translations for resource [%s] from %s to %s" % (resource, 66 | language, 67 | translations)) 68 | -------------------------------------------------------------------------------- /hyde/lib/pygments/rst_directive.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | The Pygments reStructuredText directive 4 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 5 | 6 | This fragment is a Docutils_ 0.5 directive that renders source code 7 | (to HTML only, currently) via Pygments. 8 | 9 | To use it, adjust the options below and copy the code into a module 10 | that you import on initialization. The code then automatically 11 | registers a ``sourcecode`` directive that you can use instead of 12 | normal code blocks like this:: 13 | 14 | .. sourcecode:: python 15 | 16 | My code goes here. 17 | 18 | If you want to have different code styles, e.g. one with line numbers 19 | and one without, add formatters with their names in the VARIANTS dict 20 | below. You can invoke them instead of the DEFAULT one by using a 21 | directive option:: 22 | 23 | .. sourcecode:: python 24 | :linenos: 25 | 26 | My code goes here. 27 | 28 | Look at the `directive documentation`_ to get all the gory details. 29 | 30 | .. _Docutils: http://docutils.sf.net/ 31 | .. _directive documentation: 32 | http://docutils.sourceforge.net/docs/howto/rst-directives.html 33 | 34 | :copyright: Copyright 2006-2011 by the Pygments team, see AUTHORS. 35 | :license: BSD, see LICENSE for details. 36 | """ 37 | 38 | # Options 39 | # ~~~~~~~ 40 | 41 | # Set to True if you want inline CSS styles instead of classes 42 | INLINESTYLES = False 43 | 44 | from pygments.formatters import HtmlFormatter 45 | 46 | # The default formatter 47 | DEFAULT = HtmlFormatter(noclasses=INLINESTYLES) 48 | 49 | # Add name -> formatter pairs for every variant you want to use 50 | VARIANTS = { 51 | 'linenos': HtmlFormatter(noclasses=INLINESTYLES, linenos=True), 52 | } 53 | 54 | 55 | from docutils import nodes 56 | from docutils.parsers.rst import directives, Directive 57 | 58 | from pygments import highlight 59 | from pygments.lexers import get_lexer_by_name, TextLexer 60 | 61 | class Pygments(Directive): 62 | """ Source code syntax hightlighting. 63 | """ 64 | required_arguments = 1 65 | optional_arguments = 0 66 | final_argument_whitespace = True 67 | option_spec = dict([(key, directives.flag) for key in VARIANTS]) 68 | has_content = True 69 | 70 | def run(self): 71 | self.assert_has_content() 72 | try: 73 | lexer = get_lexer_by_name(self.arguments[0]) 74 | except ValueError: 75 | # no lexer found - use the text one instead of an exception 76 | lexer = TextLexer() 77 | # take an arbitrary option if more than one is given 78 | formatter = self.options and VARIANTS[self.options.keys()[0]] or DEFAULT 79 | parsed = highlight(u'\n'.join(self.content), lexer, formatter) 80 | return [nodes.raw('', parsed, format='html')] 81 | 82 | directives.register_directive('sourcecode', Pygments) 83 | -------------------------------------------------------------------------------- /hyde/tests/ext/test_uglify.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Use nose 4 | `$ pip install nose` 5 | `$ nosetests` 6 | """ 7 | from hyde.model import Expando 8 | from hyde.generator import Generator 9 | from hyde.site import Site 10 | 11 | from fswrap import File, Folder 12 | from hyde.tests.util import assert_no_diff 13 | 14 | UGLIFY_SOURCE = File(__file__).parent.child_folder('uglify') 15 | TEST_SITE = File(__file__).parent.parent.child_folder('_test') 16 | 17 | 18 | class TestUglify(object): 19 | 20 | def setUp(self): 21 | TEST_SITE.make() 22 | TEST_SITE.parent.child_folder( 23 | 'sites/test_jinja').copy_contents_to(TEST_SITE) 24 | JS = TEST_SITE.child_folder('content/media/js') 25 | JS.make() 26 | UGLIFY_SOURCE.copy_contents_to(JS) 27 | 28 | 29 | def tearDown(self): 30 | TEST_SITE.delete() 31 | 32 | def test_can_uglify(self): 33 | s = Site(TEST_SITE) 34 | s.config.plugins = ['hyde.ext.plugins.js.UglifyPlugin'] 35 | s.config.mode = "production" 36 | source = TEST_SITE.child('content/media/js/jquery.js') 37 | target = File(Folder(s.config.deploy_root_path).child('media/js/jquery.js')) 38 | gen = Generator(s) 39 | gen.generate_resource_at_path(source) 40 | 41 | assert target.exists 42 | expected = UGLIFY_SOURCE.child_file('expected-jquery.js').read_all() 43 | # TODO: Very fragile. Better comparison needed. 44 | text = target.read_all() 45 | assert_no_diff(expected, text) 46 | 47 | def test_uglify_with_extra_options(self): 48 | s = Site(TEST_SITE) 49 | s.config.plugins = ['hyde.ext.plugins.js.UglifyPlugin'] 50 | s.config.mode = "production" 51 | s.config.uglify = Expando(dict(args={"comments":"/http\:\/\/jquery.org\/license/"})) 52 | source = TEST_SITE.child('content/media/js/jquery.js') 53 | target = File(Folder(s.config.deploy_root_path).child('media/js/jquery.js')) 54 | gen = Generator(s) 55 | gen.generate_resource_at_path(source) 56 | 57 | assert target.exists 58 | expected = UGLIFY_SOURCE.child_file('expected-jquery-nc.js').read_all() 59 | # TODO: Very fragile. Better comparison needed. 60 | text = target.read_all() 61 | assert_no_diff(expected, text) 62 | 63 | def test_no_uglify_in_dev_mode(self): 64 | s = Site(TEST_SITE) 65 | s.config.plugins = ['hyde.ext.plugins.js.UglifyPlugin'] 66 | s.config.mode = "dev" 67 | source = TEST_SITE.child('content/media/js/jquery.js') 68 | target = File(Folder(s.config.deploy_root_path).child('media/js/jquery.js')) 69 | gen = Generator(s) 70 | gen.generate_resource_at_path(source) 71 | 72 | assert target.exists 73 | expected = UGLIFY_SOURCE.child_file('jquery.js').read_all() 74 | # TODO: Very fragile. Better comparison needed. 75 | text = target.read_all() 76 | assert_no_diff(expected, text) 77 | 78 | 79 | -------------------------------------------------------------------------------- /hyde/layouts/basic/content/media/css/syntax.css: -------------------------------------------------------------------------------- 1 | .c, .cm { color: #998; font-style: italic } /* Comments */ 2 | .err { color: #a61717; background-color: #e3d2d2 } /* Error */ 3 | .k { font-weight: bold } /* Keyword */ 4 | .o { font-weight: bold } /* Operator */ 5 | .cp { color: #999; font-weight: bold } /* Comment.Preproc */ 6 | .c1 { color: #998; font-style: italic } /* Comment.Single */ 7 | .cs { color: #999; font-weight: bold; font-style: italic } /* Comment.Special */ 8 | .gd { color: #000; background-color: #ffdddd } /* Generic.Deleted */ 9 | .gd .x { color: #000; background-color: #ffaaaa } /* Generic.Deleted.Specific */ 10 | .ge { font-style: italic } /* Generic.Emph */ 11 | .gr { color: #a00 } /* Generic.Error */ 12 | .gh { color: #999 } /* Generic.Heading */ 13 | .gi { color: #000; background-color: #ddffdd } /* Generic.Inserted */ 14 | .gi .x { color: #000; background-color: #aaffaa } /* Generic.Inserted.Specific */ 15 | .go { color: #888 } /* Generic.Output */ 16 | .gp { color: #555 } /* Generic.Prompt */ 17 | .gs { font-weight: bold } /* Generic.Strong */ 18 | .gu { color: #aaaaaa } /* Generic.Subheading */ 19 | .gt { color: #a00 } /* Generic.Traceback */ 20 | .kc { font-weight: bold } /* Keyword.Constant */ 21 | .kd { font-weight: bold } /* Keyword.Declaration */ 22 | .kp { font-weight: bold } /* Keyword.Pseudo */ 23 | .kr { font-weight: bold } /* Keyword.Reserved */ 24 | .kt { color: #445588; font-weight: bold } /* Keyword.Type */ 25 | .m { color: #099 } /* Literal.Number */ 26 | .s { color: #d14 } /* Literal.String */ 27 | .na { color: #008080 } /* Name.Attribute */ 28 | .nb { color: #0086B3 } /* Name.Builtin */ 29 | .nc { color: #445588; font-weight: bold } /* Name.Class */ 30 | .no { color: #008080 } /* Name.Constant */ 31 | .ni { color: #800080 } /* Name.Entity */ 32 | .ne { color: #900; font-weight: bold } /* Name.Exception */ 33 | .nf { color: #900; font-weight: bold } /* Name.Function */ 34 | .nn { color: #555 } /* Name.Namespace */ 35 | .nt { color: #000080 } /* Name.Tag */ 36 | .nv { color: #008080 } /* Name.Variable */ 37 | .ow { font-weight: bold } /* Operator.Word */ 38 | .w { color: #bbb } /* Text.Whitespace */ 39 | .mf { color: #099 } /* Literal.Number.Float */ 40 | .mh { color: #099 } /* Literal.Number.Hex */ 41 | .mi { color: #099 } /* Literal.Number.Integer */ 42 | .mo { color: #099 } /* Literal.Number.Oct */ 43 | .sb { color: #d14 } /* Literal.String.Backtick */ 44 | .sc { color: #d14 } /* Literal.String.Char */ 45 | .sd { color: #d14 } /* Literal.String.Doc */ 46 | .s2 { color: #d14 } /* Literal.String.Double */ 47 | .se { color: #d14 } /* Literal.String.Escape */ 48 | .sh { color: #d14 } /* Literal.String.Heredoc */ 49 | .si { color: #d14 } /* Literal.String.Interpol */ 50 | .sx { color: #d14 } /* Literal.String.Other */ 51 | .sr { color: #009926 } /* Literal.String.Regex */ 52 | .s1 { color: #d14 } /* Literal.String.Single */ 53 | .ss { color: #990073 } /* Literal.String.Symbol */ 54 | .bp { color: #999 } /* Name.Builtin.Pseudo */ 55 | .vc { color: #008080 } /* Name.Variable.Class */ 56 | .vg { color: #008080 } /* Name.Variable.Global */ 57 | .vi { color: #008080 } /* Name.Variable.Instance */ 58 | .il { color: #099 } /* Literal.Number.Integer.Long */ 59 | 60 | -------------------------------------------------------------------------------- /hyde/tests/ext/test_markings.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Use nose 4 | `$ pip install nose` 5 | `$ nosetests` 6 | """ 7 | from hyde.generator import Generator 8 | from hyde.site import Site 9 | 10 | from fswrap import File 11 | from pyquery import PyQuery 12 | 13 | TEST_SITE = File(__file__).parent.parent.child_folder('_test') 14 | 15 | def assert_valid_conversion(html): 16 | assert html 17 | q = PyQuery(html) 18 | assert "is_processable" not in html 19 | assert q("h1") 20 | assert "This is a" in q("h1").text() 21 | assert "heading" in q("h1").text() 22 | assert q(".amp").length == 1 23 | assert "mark" not in html 24 | assert "reference" not in html 25 | assert '.' not in q.text() 26 | assert '/' not in q.text() 27 | 28 | class TestMarkings(object): 29 | 30 | def setUp(self): 31 | TEST_SITE.make() 32 | TEST_SITE.parent.child_folder( 33 | 'sites/test_jinja').copy_contents_to(TEST_SITE) 34 | 35 | def tearDown(self): 36 | TEST_SITE.delete() 37 | 38 | 39 | 40 | def test_mark(self): 41 | text = u""" 42 | === 43 | is_processable: False 44 | === 45 | {% filter markdown|typogrify %} 46 | §§ heading 47 | This is a heading 48 | ================= 49 | §§ /heading 50 | 51 | §§ content 52 | Hyde & Jinja 53 | §§ / 54 | 55 | {% endfilter %} 56 | """ 57 | 58 | text2 = """ 59 | {% refer to "inc.md" as inc %} 60 | {% filter markdown|typogrify %} 61 | {{ inc.heading }} 62 | {{ inc.content }} 63 | {% endfilter %} 64 | """ 65 | site = Site(TEST_SITE) 66 | site.config.plugins = [ 67 | 'hyde.ext.plugins.meta.MetaPlugin', 68 | 'hyde.ext.plugins.text.MarkingsPlugin'] 69 | inc = File(TEST_SITE.child('content/inc.md')) 70 | inc.write(text) 71 | site.load() 72 | gen = Generator(site) 73 | gen.load_template_if_needed() 74 | 75 | template = gen.template 76 | html = template.render(text2, {}).strip() 77 | assert_valid_conversion(html) 78 | 79 | def test_reference(self): 80 | text = u""" 81 | === 82 | is_processable: False 83 | === 84 | {% filter markdown|typogrify %} 85 | §§ heading 86 | This is a heading 87 | ================= 88 | §§ /heading 89 | 90 | §§ content 91 | Hyde & Jinja 92 | §§ / 93 | 94 | {% endfilter %} 95 | """ 96 | 97 | text2 = u""" 98 | ※ inc.md as inc 99 | {% filter markdown|typogrify %} 100 | {{ inc.heading }} 101 | {{ inc.content }} 102 | {% endfilter %} 103 | """ 104 | site = Site(TEST_SITE) 105 | site.config.plugins = [ 106 | 'hyde.ext.plugins.meta.MetaPlugin', 107 | 'hyde.ext.plugins.text.MarkingsPlugin', 108 | 'hyde.ext.plugins.text.ReferencePlugin'] 109 | inc = File(site.content.source_folder.child('inc.md')) 110 | inc.write(text.strip()) 111 | src = File(site.content.source_folder.child('src.html')) 112 | src.write(text2.strip()) 113 | gen = Generator(site) 114 | gen.generate_all() 115 | f = File(site.config.deploy_root_path.child(src.name)) 116 | assert f.exists 117 | html = f.read_all() 118 | assert_valid_conversion(html) 119 | -------------------------------------------------------------------------------- /hyde/layouts/starter/site.yaml: -------------------------------------------------------------------------------- 1 | mode: learning 2 | media_root: media 3 | media_url: /media 4 | base_url: / 5 | # If your site is nested inside of a bigger one, you can use media_url and 6 | # base_url to properly generate links on your site. For example, if your URL 7 | # will be some.domain.com/starter/, use: 8 | # media_url: /starter/media 9 | # base_url: /starter/ 10 | template: hyde.ext.templates.jinja.Jinja2Template 11 | plugins: 12 | - hyde.ext.plugins.meta.MetaPlugin 13 | - hyde.ext.plugins.meta.AutoExtendPlugin 14 | # Plugins needed for the advances section. 15 | - hyde.ext.plugins.meta.SorterPlugin 16 | - hyde.ext.plugins.meta.GrouperPlugin 17 | - hyde.ext.plugins.meta.TaggerPlugin 18 | context: 19 | data: 20 | author: 21 | name: Merlin Rebrović 22 | url: "http://merlin.rebrovic.net" 23 | layout: 24 | name: Hyde Starter Kit 25 | url: "https://github.com/merlinrebrovic/hyde-starter-kit" 26 | project: 27 | name: Hyde 28 | url: "http://hyde.github.com" 29 | install: "https://github.com/hyde/hyde#installation" 30 | menu: 31 | - title: Home 32 | url: index.html 33 | - title: First steps 34 | url: first-steps.html 35 | - title: About 36 | url: about.html 37 | 38 | ### Advanced part ### 39 | 40 | # This defines meta data on the whole site. 41 | meta: 42 | # 'nodemeta' will tell Hyde what file to look for inside a folder from 43 | # which to apply meta data to all files (resources) inside it. This is 44 | # a great way of simply adding or modifying properties of a very large 45 | # number of files. 46 | nodemeta: meta.yaml 47 | ga_tracking_code: XX-XXXXXXXX-X 48 | 49 | sorter: 50 | name: # the name of the sorter (no pun intended) 51 | attr: name # by which attributes will resources be sorted 52 | filters: 53 | source_file.kind: html 54 | # You can include only files from a certain folder. 55 | #resource.node: (name of the folder) 56 | #reverse: True # if you need the list backwards 57 | file_type: 58 | attr: 59 | - source_file.kind 60 | - source_file.size 61 | index: 62 | attr: meta.index 63 | filters: 64 | source_file.kind: html 65 | learning_order: 66 | attr: meta.learning_order 67 | filters: 68 | source_file.kind: html 69 | 70 | grouper: 71 | level: 72 | sorter: name 73 | description: Difficulty levels 74 | groups: 75 | - name: basic 76 | description: Basic 77 | - name: advanced 78 | description: Advanced 79 | # You can have more than one group section, depending on your needs. 80 | # For example: "categories", "menu sections", etc. 81 | #category: 82 | # description: To which category a blog post belongs to. 83 | # groups: 84 | # - name: software 85 | # description: Software engineering 86 | # - name: web 87 | # description: Web technologies 88 | # - name: seo 89 | # description: Search Engine Optimization 90 | 91 | tagger: 92 | sorter: name 93 | -------------------------------------------------------------------------------- /hyde/layouts/starter/content/media/css/style.css: -------------------------------------------------------------------------------- 1 | * { 2 | margin: 0; 3 | padding: 0; 4 | } 5 | 6 | html, body { 7 | color: #ddd; 8 | background-color: black; 9 | } 10 | 11 | body { 12 | margin: 0 auto; 13 | width: 980px; 14 | min-height: 700px; 15 | background: url('../img/background.jpg') top left no-repeat; 16 | position: relative; 17 | font-size: 16px; 18 | font-family: Tahoma, sans-serif; 19 | } 20 | 21 | strong { 22 | color: white; 23 | } 24 | 25 | a { 26 | color: #f1ee00; 27 | } 28 | 29 | a:hover, a:focus { 30 | text-decoration: none; 31 | } 32 | 33 | p, ul, ol { 34 | margin-bottom: 22px; 35 | } 36 | 37 | ul, ol { 38 | margin-left: 28px; 39 | } 40 | 41 | header { 42 | position: absolute; 43 | top: 120px; 44 | right: 120px; 45 | font-family: Anton, Tahoma, sans-serif; 46 | text-transform: uppercase; 47 | font-weight: none; 48 | } 49 | 50 | header h1 { 51 | font-size: 50px; 52 | font-weight: normal; 53 | text-shadow: -1px 2px 1px black; 54 | color: white; 55 | } 56 | 57 | header h1 span { 58 | color: #f1ee00; 59 | } 60 | 61 | header p { 62 | font-size: 28px; 63 | font-weight: normal; 64 | letter-spacing: 11px; 65 | text-shadow: -1px 2px 1px black; 66 | color: white; 67 | } 68 | 69 | nav { 70 | position: absolute; 71 | top: 340px; 72 | right: 120px; 73 | width: 250px; 74 | text-align: right; 75 | font-family: Anton, Tahoma, sans-serif; 76 | } 77 | 78 | nav a, 79 | nav a:visited { 80 | text-decoration: none; 81 | color: white; 82 | text-transform: uppercase; 83 | font-size: 28px; 84 | } 85 | 86 | nav a.selected { 87 | color: #888; 88 | } 89 | 90 | nav a:hover { 91 | color: #f1ee00; 92 | } 93 | 94 | nav ul li { 95 | list-style-type: none; 96 | } 97 | 98 | nav, aside { 99 | /* to line up with the header */ 100 | padding-right: 5px; 101 | } 102 | 103 | #content { 104 | line-height: 1.5em; 105 | position: absolute; 106 | top: 350px; 107 | left: 180px; 108 | width: 410px; 109 | padding-bottom: 100px; 110 | } 111 | 112 | #content h1 { 113 | font-weight: normal; 114 | font-size: 28px; 115 | font-family: Anton, Tahoma, sans-serif; 116 | text-transform: uppercase; 117 | margin-bottom: 22px; 118 | color: white; 119 | } 120 | 121 | #content h2 { 122 | font-weight: bold; 123 | font-size: 18px; 124 | font-family: Tahoma, sans-serif; 125 | margin-bottom: 22px; 126 | border-bottom: 1px dashed #888; 127 | } 128 | 129 | #content code { 130 | display: block; 131 | font-family: monospace; 132 | font-size: 12px; 133 | background-color: #222; 134 | padding: 5px 10px; 135 | margin-bottom: 20px; 136 | border-top: 1px solid #444; 137 | border-right: 1px solid #333; 138 | border-bottom: 1px solid #222; 139 | border-left: 1px solid #111; 140 | } 141 | 142 | #content .tags { 143 | font-size: 14px; 144 | color: #888; 145 | } 146 | 147 | #content .bottom_article_nav { 148 | border-top: 1px dashed #888; 149 | overflow: hidden; 150 | margin-top: 40px; 151 | padding-top: 10px; 152 | } 153 | 154 | #content .bottom_article_nav .next { 155 | float: right; 156 | } 157 | -------------------------------------------------------------------------------- /hyde/layouts/starter/content/first-steps.html: -------------------------------------------------------------------------------- 1 | --- 2 | extends: base.j2 3 | title: First steps 4 | --- 5 | {# In-file metadata. Supplements data from the site's configuration file 6 | and node (folder) level data in "meta.yaml" files. 7 | 8 | Use the AutoExtendPlugin to extend templates from metadata. In this 9 | case the metadata and "extends" statement has to be on the top of the 10 | file. 11 | #} 12 | 13 | {% block content %} 14 | Walk this way if you're a beginner 15 | ================================== 16 | 17 | This template was created to look at its code. So you should spend about 18 | 5% of your time looking at the web from the outside and the other 95% 19 | tinkering with things under the hood. 20 | 21 | The template is not perfect nor does it contain the best practices. It 22 | is not even consistent. It is designed to help you with the learning 23 | process. If you follow the steps below, you'll get a pretty good picture of 24 | how things work. Every step adds some new features and builds upon the 25 | previous one. 26 | 27 | 28 | 1. Site structure and configuration file 29 | ---------------------------------------- 30 | 31 | The site is made of two folders and a [Hyde configuration][hyde_config] 32 | file. The folders are **content** and **layout**. 33 | 34 | **content** contains all your page content, blog articles, pictures and 35 | resources like CSS and JavaScript. Everything that is unique is here. 36 | 37 | **layout** contains templates and macros. Everything that you'll want to 38 | reuse is here. 39 | 40 | 41 | 2. Jinja2 template 42 | ------------------ 43 | 44 | **base.j2** is a very short and simple Jinja2 template. This way you can 45 | concentrate on some of the basic features. Notice meta and context 46 | variables invocation inside curly braces, dynamic media path generation 47 | and running all content through the Markdown filter. 48 | 49 | **macros.j2** contains macros for common and repetitive tasks. 50 | 51 | For more information or to try something new, visit the extensive [Jinja2 52 | documentation][jinja2_docs]. 53 | 54 | 55 | 3. Content 56 | ---------- 57 | 58 | Look at the three files in this order: [index.html](index.html), 59 | [first-steps.html](first-steps.html) and [about.html](about.html). 60 | 61 | [Index](index.html) extends the base layout in the classic Jinja way and 62 | fills the content block with some simple Markdown data. 63 | 64 | [First steps](first-steps.html) goes a step furher. It introduces the 65 | in-file metadata that plugins will use to extend and fill the layout. It 66 | also uses some new Markdown features. 67 | 68 | [About](about.html) has a **default_block** metadata and mixes Markdown 69 | content with Jinja templates. 70 | 71 | [hyde_config]: http://hyde.github.com/config.html "Hyde configuration" 72 | [jinja2_docs]: http://jinja.pocoo.org/docs/templates/ "Jinja2 documentation" 73 | 74 | 75 | 4. Advanced sections 76 | -------------------- 77 | 78 | While searching and navigating this template you'll find more files and 79 | sections than mentioned on this page (something like **meta.yaml** 80 | files, the **content/advanced** folder or other Jinja templates). Those 81 | files are needed for the [advanced topics](advanced/overview.html) so 82 | just ignore them at the beginning. They will start to make sense while 83 | you're working through the template or will be explicitly explained when 84 | the right time comes. 85 | {% endblock %} 86 | -------------------------------------------------------------------------------- /hyde/ext/publishers/dvcs.py: -------------------------------------------------------------------------------- 1 | """ 2 | Contains classes and utilities that help publishing a hyde website to 3 | distributed version control systems. 4 | """ 5 | 6 | from hyde.publisher import Publisher 7 | 8 | import abc 9 | from subprocess import Popen, PIPE 10 | 11 | class DVCS(Publisher): 12 | __metaclass__ = abc.ABCMeta 13 | 14 | def initialize(self, settings): 15 | self.settings = settings 16 | self.path = self.site.sitepath.child_folder(settings.path) 17 | self.url = settings.url 18 | self.branch = getattr(settings, 'branch', 'master') 19 | self.switch(self.branch) 20 | 21 | @abc.abstractmethod 22 | def pull(self): pass 23 | 24 | @abc.abstractmethod 25 | def push(self): pass 26 | 27 | @abc.abstractmethod 28 | def commit(self, message): pass 29 | 30 | @abc.abstractmethod 31 | def switch(self, branch): pass 32 | 33 | @abc.abstractmethod 34 | def add(self, path="."): pass 35 | 36 | @abc.abstractmethod 37 | def merge(self, branch): pass 38 | 39 | 40 | def publish(self): 41 | super(DVCS, self).publish() 42 | if not self.path.exists: 43 | raise Exception("The destination repository must exist.") 44 | self.site.config.deploy_root_path.copy_contents_to(self.path) 45 | self.add() 46 | self.commit(self.message) 47 | self.push() 48 | 49 | 50 | 51 | class Git(DVCS): 52 | """ 53 | Acts as a publisher to a git repository. Can be used to publish to 54 | github pages. 55 | """ 56 | 57 | def add(self, path="."): 58 | cmd = Popen('git add "%s"' % path, 59 | cwd=unicode(self.path), stdout=PIPE, shell=True) 60 | cmdresult = cmd.communicate()[0] 61 | if cmd.returncode: 62 | raise Exception(cmdresult) 63 | 64 | def pull(self): 65 | self.switch(self.branch) 66 | cmd = Popen("git pull origin %s" % self.branch, 67 | cwd=unicode(self.path), 68 | stdout=PIPE, 69 | shell=True) 70 | cmdresult = cmd.communicate()[0] 71 | if cmd.returncode: 72 | raise Exception(cmdresult) 73 | 74 | def push(self): 75 | cmd = Popen("git push origin %s" % self.branch, 76 | cwd=unicode(self.path), stdout=PIPE, 77 | shell=True) 78 | cmdresult = cmd.communicate()[0] 79 | if cmd.returncode: 80 | raise Exception(cmdresult) 81 | 82 | 83 | def commit(self, message): 84 | cmd = Popen('git commit -a -m"%s"' % message, 85 | cwd=unicode(self.path), stdout=PIPE, shell=True) 86 | cmdresult = cmd.communicate()[0] 87 | if cmd.returncode: 88 | raise Exception(cmdresult) 89 | 90 | def switch(self, branch): 91 | self.branch = branch 92 | cmd = Popen('git checkout %s' % branch, 93 | cwd=unicode(self.path), stdout=PIPE, shell=True) 94 | cmdresult = cmd.communicate()[0] 95 | if cmd.returncode: 96 | raise Exception(cmdresult) 97 | 98 | def merge(self, branch): 99 | cmd = Popen('git merge %s' % branch, 100 | cwd=unicode(self.path), stdout=PIPE, shell=True) 101 | cmdresult = cmd.communicate()[0] 102 | if cmd.returncode: 103 | raise Exception(cmdresult) -------------------------------------------------------------------------------- /hyde/tests/sites/test_sorter/content/blog/another-sad-post.html: -------------------------------------------------------------------------------- 1 | --- 2 | title: Another Sad Post 3 | description: > 4 | Something else sad happened. 5 | created: !!timestamp '2011-03-01 10:00:00' 6 | index: 2 7 | --- 8 | 9 | {% mark excerpt -%} 10 | 11 | I went and dressed sadly. It will show you pretty well how pipped I was when I 12 | tell you that I near as a toucher put on a white tie with a dinner-jacket. I 13 | sallied out for a bit of food more to pass the time than because I wanted it. 14 | It seemed brutal to be wading into the bill of fare with poor old Bicky headed 15 | for the breadline. 16 | 17 | {%- endmark %} 18 | 19 | When I got back old Chiswick had gone to bed, but Bicky was there, hunched up 20 | in an arm-chair, brooding pretty tensely, with a cigarette hanging out of the 21 | corner of his mouth and a more or less glassy stare in his eyes. He had the 22 | aspect of one who had been soaked with what the newspaper chappies call "some 23 | blunt instrument." 24 | 25 | "This is a bit thick, old thing—what!" I said. 26 | 27 | He picked up his glass and drained it feverishly, overlooking the fact that it 28 | hadn't anything in it. 29 | 30 | "I'm done, Bertie!" he said. 31 | 32 | He had another go at the glass. It didn't seem to do him any good. 33 | 34 | "If only this had happened a week later, Bertie! My next month's money was due 35 | to roll in on Saturday. I could have worked a wheeze I've been reading about 36 | in the magazine advertisements. It seems that you can make a dashed amount of 37 | money if you can only collect a few dollars and start a chicken-farm. Jolly 38 | sound scheme, Bertie! Say you buy a hen—call it one hen for the sake of 39 | argument. It lays an egg every day of the week. You sell the eggs seven for 40 | twenty-five cents. Keep of hen costs nothing. Profit practically twenty-five 41 | cents on every seven eggs. Or look at it another way: Suppose you have a dozen 42 | eggs. Each of the hens has a dozen chickens. The chickens grow up and have 43 | more chickens. Why, in no time you'd have the place covered knee-deep in hens, 44 | all laying eggs, at twenty-five cents for every seven. You'd make a fortune. 45 | Jolly life, too, keeping hens!" He had begun to get quite worked up at the 46 | thought of it, but he slopped back in his chair at this juncture with a good 47 | deal of gloom. "But, of course, it's no good," he said, "because I haven't the 48 | cash." 49 | 50 | "You've only to say the word, you know, Bicky, old top." 51 | 52 | "Thanks awfully, Bertie, but I'm not going to sponge on you." 53 | 54 | That's always the way in this world. The chappies you'd like to lend money to 55 | won't let you, whereas the chappies you don't want to lend it to will do 56 | everything except actually stand you on your head and lift the specie out of 57 | your pockets. As a lad who has always rolled tolerably free in the right 58 | stuff, I've had lots of experience of the second class. Many's the time, back 59 | in London, I've hurried along Piccadilly and felt the hot breath of the 60 | toucher on the back of my neck and heard his sharp, excited yapping as he 61 | closed in on me. I've simply spent my life scattering largesse to blighters I 62 | didn't care a hang for; yet here was I now, dripping doubloons and pieces of 63 | eight and longing to hand them over, and Bicky, poor fish, absolutely on his 64 | uppers, not taking any at any price. 65 | 66 | "Well, there's only one hope, then." 67 | 68 | "What's that?" 69 | 70 | "Jeeves." 71 | 72 | "Sir?" 73 | 74 | There was Jeeves, standing behind me, full of zeal. In this matter of 75 | shimmering into rooms the chappie is rummy to a degree. You're sitting in the 76 | old armchair, thinking of this and that, and then suddenly you look up, and 77 | there he is. He moves from point to point with as little uproar as a jelly 78 | fish. The thing startled poor old Bicky considerably. He rose from his seat 79 | like a rocketing pheasant. I'm used to Jeeves now, but often in the days when 80 | he first came to me I've bitten my tongue freely on finding him unexpectedly 81 | in my midst. 82 | 83 | [My Man Jeeves by PG Wodehouse][MMJ] 84 | 85 | [MMJ]: http://www.gutenberg.org/cache/epub/8164/pg8164.html 86 | -------------------------------------------------------------------------------- /hyde/tests/sites/test_paginator/content/blog/another-sad-post.html: -------------------------------------------------------------------------------- 1 | --- 2 | title: Another Sad Post 3 | description: > 4 | Something else sad happened. 5 | created: !!timestamp '2011-03-01 10:00:00' 6 | tags: 7 | - sad 8 | - events 9 | --- 10 | 11 | {% mark excerpt -%} 12 | 13 | I went and dressed sadly. It will show you pretty well how pipped I was when I 14 | tell you that I near as a toucher put on a white tie with a dinner-jacket. I 15 | sallied out for a bit of food more to pass the time than because I wanted it. 16 | It seemed brutal to be wading into the bill of fare with poor old Bicky headed 17 | for the breadline. 18 | 19 | {%- endmark %} 20 | 21 | When I got back old Chiswick had gone to bed, but Bicky was there, hunched up 22 | in an arm-chair, brooding pretty tensely, with a cigarette hanging out of the 23 | corner of his mouth and a more or less glassy stare in his eyes. He had the 24 | aspect of one who had been soaked with what the newspaper chappies call "some 25 | blunt instrument." 26 | 27 | "This is a bit thick, old thing—what!" I said. 28 | 29 | He picked up his glass and drained it feverishly, overlooking the fact that it 30 | hadn't anything in it. 31 | 32 | "I'm done, Bertie!" he said. 33 | 34 | He had another go at the glass. It didn't seem to do him any good. 35 | 36 | "If only this had happened a week later, Bertie! My next month's money was due 37 | to roll in on Saturday. I could have worked a wheeze I've been reading about 38 | in the magazine advertisements. It seems that you can make a dashed amount of 39 | money if you can only collect a few dollars and start a chicken-farm. Jolly 40 | sound scheme, Bertie! Say you buy a hen—call it one hen for the sake of 41 | argument. It lays an egg every day of the week. You sell the eggs seven for 42 | twenty-five cents. Keep of hen costs nothing. Profit practically twenty-five 43 | cents on every seven eggs. Or look at it another way: Suppose you have a dozen 44 | eggs. Each of the hens has a dozen chickens. The chickens grow up and have 45 | more chickens. Why, in no time you'd have the place covered knee-deep in hens, 46 | all laying eggs, at twenty-five cents for every seven. You'd make a fortune. 47 | Jolly life, too, keeping hens!" He had begun to get quite worked up at the 48 | thought of it, but he slopped back in his chair at this juncture with a good 49 | deal of gloom. "But, of course, it's no good," he said, "because I haven't the 50 | cash." 51 | 52 | "You've only to say the word, you know, Bicky, old top." 53 | 54 | "Thanks awfully, Bertie, but I'm not going to sponge on you." 55 | 56 | That's always the way in this world. The chappies you'd like to lend money to 57 | won't let you, whereas the chappies you don't want to lend it to will do 58 | everything except actually stand you on your head and lift the specie out of 59 | your pockets. As a lad who has always rolled tolerably free in the right 60 | stuff, I've had lots of experience of the second class. Many's the time, back 61 | in London, I've hurried along Piccadilly and felt the hot breath of the 62 | toucher on the back of my neck and heard his sharp, excited yapping as he 63 | closed in on me. I've simply spent my life scattering largesse to blighters I 64 | didn't care a hang for; yet here was I now, dripping doubloons and pieces of 65 | eight and longing to hand them over, and Bicky, poor fish, absolutely on his 66 | uppers, not taking any at any price. 67 | 68 | "Well, there's only one hope, then." 69 | 70 | "What's that?" 71 | 72 | "Jeeves." 73 | 74 | "Sir?" 75 | 76 | There was Jeeves, standing behind me, full of zeal. In this matter of 77 | shimmering into rooms the chappie is rummy to a degree. You're sitting in the 78 | old armchair, thinking of this and that, and then suddenly you look up, and 79 | there he is. He moves from point to point with as little uproar as a jelly 80 | fish. The thing startled poor old Bicky considerably. He rose from his seat 81 | like a rocketing pheasant. I'm used to Jeeves now, but often in the days when 82 | he first came to me I've bitten my tongue freely on finding him unexpectedly 83 | in my midst. 84 | 85 | [My Man Jeeves by PG Wodehouse][MMJ] 86 | 87 | [MMJ]: http://www.gutenberg.org/cache/epub/8164/pg8164.html -------------------------------------------------------------------------------- /hyde/tests/sites/test_tagger/content/blog/another-sad-post.html: -------------------------------------------------------------------------------- 1 | --- 2 | title: Another Sad Post 3 | description: > 4 | Something else sad happened. 5 | created: !!timestamp '2011-03-01 10:00:00' 6 | tags: 7 | - sad 8 | - events 9 | --- 10 | 11 | {% mark excerpt -%} 12 | 13 | I went and dressed sadly. It will show you pretty well how pipped I was when I 14 | tell you that I near as a toucher put on a white tie with a dinner-jacket. I 15 | sallied out for a bit of food more to pass the time than because I wanted it. 16 | It seemed brutal to be wading into the bill of fare with poor old Bicky headed 17 | for the breadline. 18 | 19 | {%- endmark %} 20 | 21 | When I got back old Chiswick had gone to bed, but Bicky was there, hunched up 22 | in an arm-chair, brooding pretty tensely, with a cigarette hanging out of the 23 | corner of his mouth and a more or less glassy stare in his eyes. He had the 24 | aspect of one who had been soaked with what the newspaper chappies call "some 25 | blunt instrument." 26 | 27 | "This is a bit thick, old thing—what!" I said. 28 | 29 | He picked up his glass and drained it feverishly, overlooking the fact that it 30 | hadn't anything in it. 31 | 32 | "I'm done, Bertie!" he said. 33 | 34 | He had another go at the glass. It didn't seem to do him any good. 35 | 36 | "If only this had happened a week later, Bertie! My next month's money was due 37 | to roll in on Saturday. I could have worked a wheeze I've been reading about 38 | in the magazine advertisements. It seems that you can make a dashed amount of 39 | money if you can only collect a few dollars and start a chicken-farm. Jolly 40 | sound scheme, Bertie! Say you buy a hen—call it one hen for the sake of 41 | argument. It lays an egg every day of the week. You sell the eggs seven for 42 | twenty-five cents. Keep of hen costs nothing. Profit practically twenty-five 43 | cents on every seven eggs. Or look at it another way: Suppose you have a dozen 44 | eggs. Each of the hens has a dozen chickens. The chickens grow up and have 45 | more chickens. Why, in no time you'd have the place covered knee-deep in hens, 46 | all laying eggs, at twenty-five cents for every seven. You'd make a fortune. 47 | Jolly life, too, keeping hens!" He had begun to get quite worked up at the 48 | thought of it, but he slopped back in his chair at this juncture with a good 49 | deal of gloom. "But, of course, it's no good," he said, "because I haven't the 50 | cash." 51 | 52 | "You've only to say the word, you know, Bicky, old top." 53 | 54 | "Thanks awfully, Bertie, but I'm not going to sponge on you." 55 | 56 | That's always the way in this world. The chappies you'd like to lend money to 57 | won't let you, whereas the chappies you don't want to lend it to will do 58 | everything except actually stand you on your head and lift the specie out of 59 | your pockets. As a lad who has always rolled tolerably free in the right 60 | stuff, I've had lots of experience of the second class. Many's the time, back 61 | in London, I've hurried along Piccadilly and felt the hot breath of the 62 | toucher on the back of my neck and heard his sharp, excited yapping as he 63 | closed in on me. I've simply spent my life scattering largesse to blighters I 64 | didn't care a hang for; yet here was I now, dripping doubloons and pieces of 65 | eight and longing to hand them over, and Bicky, poor fish, absolutely on his 66 | uppers, not taking any at any price. 67 | 68 | "Well, there's only one hope, then." 69 | 70 | "What's that?" 71 | 72 | "Jeeves." 73 | 74 | "Sir?" 75 | 76 | There was Jeeves, standing behind me, full of zeal. In this matter of 77 | shimmering into rooms the chappie is rummy to a degree. You're sitting in the 78 | old armchair, thinking of this and that, and then suddenly you look up, and 79 | there he is. He moves from point to point with as little uproar as a jelly 80 | fish. The thing startled poor old Bicky considerably. He rose from his seat 81 | like a rocketing pheasant. I'm used to Jeeves now, but often in the days when 82 | he first came to me I've bitten my tongue freely on finding him unexpectedly 83 | in my midst. 84 | 85 | [My Man Jeeves by PG Wodehouse][MMJ] 86 | 87 | [MMJ]: http://www.gutenberg.org/cache/epub/8164/pg8164.html -------------------------------------------------------------------------------- /hyde/tests/ext/test_combine.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Use nose 4 | `$ pip install nose` 5 | `$ nosetests` 6 | """ 7 | from hyde.generator import Generator 8 | from hyde.site import Site 9 | 10 | from fswrap import File, Folder 11 | 12 | COMBINE_SOURCE = File(__file__).parent.child_folder('combine') 13 | TEST_SITE = File(__file__).parent.parent.child_folder('_test') 14 | 15 | class CombineTester(object): 16 | def _test_combine(self, content): 17 | s = Site(TEST_SITE) 18 | s.config.plugins = [ 19 | 'hyde.ext.plugins.meta.MetaPlugin', 20 | 'hyde.ext.plugins.structure.CombinePlugin'] 21 | source = TEST_SITE.child('content/media/js/script.js') 22 | target = File(Folder(s.config.deploy_root_path).child('media/js/script.js')) 23 | File(source).write(content) 24 | 25 | gen = Generator(s) 26 | gen.generate_resource_at_path(source) 27 | 28 | assert target.exists 29 | text = target.read_all() 30 | return text, s 31 | 32 | class TestCombine(CombineTester): 33 | 34 | def setUp(self): 35 | TEST_SITE.make() 36 | TEST_SITE.parent.child_folder( 37 | 'sites/test_jinja').copy_contents_to(TEST_SITE) 38 | TEST_SITE.child_folder('content/media/js').make() 39 | COMBINE_SOURCE.copy_contents_to(TEST_SITE.child('content/media/js')) 40 | 41 | def tearDown(self): 42 | TEST_SITE.delete() 43 | 44 | def test_combine_top(self): 45 | text, _ = self._test_combine(""" 46 | --- 47 | combine: 48 | files: script.*.js 49 | where: top 50 | --- 51 | 52 | Last line""") 53 | assert text == """var a = 1 + 2; 54 | var b = a + 3; 55 | var c = a + 5; 56 | Last line""" 57 | return 58 | 59 | def test_combine_bottom(self): 60 | text, _ = self._test_combine(""" 61 | --- 62 | combine: 63 | files: script.*.js 64 | where: bottom 65 | --- 66 | 67 | First line 68 | """) 69 | expected = """First line 70 | var a = 1 + 2; 71 | var b = a + 3; 72 | var c = a + 5; 73 | """ 74 | 75 | assert text.strip() == expected.strip() 76 | return 77 | 78 | def test_combine_bottom_unsorted(self): 79 | text, _ = self._test_combine(""" 80 | --- 81 | combine: 82 | sort: false 83 | files: 84 | - script.3.js 85 | - script.1.js 86 | - script.2.js 87 | where: bottom 88 | --- 89 | 90 | First line 91 | """) 92 | expected = """First line 93 | var c = a + 5; 94 | var a = 1 + 2; 95 | var b = a + 3; 96 | """ 97 | 98 | assert text.strip() == expected.strip() 99 | return 100 | 101 | def test_combine_remove(self): 102 | _, s = self._test_combine(""" 103 | --- 104 | combine: 105 | files: script.*.js 106 | remove: yes 107 | --- 108 | 109 | First line""") 110 | for i in range(1,4): 111 | assert not File(Folder(s.config.deploy_root_path). 112 | child('media/js/script.%d.js' % i)).exists 113 | 114 | 115 | class TestCombinePaths(CombineTester): 116 | 117 | def setUp(self): 118 | TEST_SITE.make() 119 | TEST_SITE.parent.child_folder( 120 | 'sites/test_jinja').copy_contents_to(TEST_SITE) 121 | TEST_SITE.child_folder('content/media/js').make() 122 | JS = TEST_SITE.child_folder('content/scripts').make() 123 | S1 = JS.child_folder('s1').make() 124 | S2 = JS.child_folder('s2').make() 125 | S3 = JS.child_folder('s3').make() 126 | File(COMBINE_SOURCE.child('script.1.js')).copy_to(S1) 127 | File(COMBINE_SOURCE.child('script.2.js')).copy_to(S2) 128 | File(COMBINE_SOURCE.child('script.3.js')).copy_to(S3) 129 | 130 | def tearDown(self): 131 | TEST_SITE.delete() 132 | 133 | def test_combine_top(self): 134 | text, _ = self._test_combine(""" 135 | --- 136 | combine: 137 | root: scripts 138 | recurse: true 139 | files: script.*.js 140 | where: top 141 | --- 142 | 143 | Last line""") 144 | assert text == """var a = 1 + 2; 145 | var b = a + 3; 146 | var c = a + 5; 147 | Last line""" 148 | return 149 | -------------------------------------------------------------------------------- /hyde/tests/sites/test_sorter/content/blog/sad-post.html: -------------------------------------------------------------------------------- 1 | --- 2 | title: A Sad Post 3 | description: > 4 | Something sad happened. 5 | created: !!timestamp '2010-12-01 10:00:00' 6 | --- 7 | 8 | {% mark image -%} 9 | 10 | ![A Dark Image]([[!!images/dark.png]]) 11 | 12 | {%- endmark %} 13 | 14 | {% mark excerpt -%} 15 | 16 | I went and dressed sadly. It will show you pretty well how pipped I was when I 17 | tell you that I near as a toucher put on a white tie with a dinner-jacket. I 18 | sallied out for a bit of food more to pass the time than because I wanted it. 19 | It seemed brutal to be wading into the bill of fare with poor old Bicky headed 20 | for the breadline. 21 | 22 | {%- endmark %} 23 | 24 | When I got back old Chiswick had gone to bed, but Bicky was there, hunched up 25 | in an arm-chair, brooding pretty tensely, with a cigarette hanging out of the 26 | corner of his mouth and a more or less glassy stare in his eyes. He had the 27 | aspect of one who had been soaked with what the newspaper chappies call "some 28 | blunt instrument." 29 | 30 | "This is a bit thick, old thing—what!" I said. 31 | 32 | He picked up his glass and drained it feverishly, overlooking the fact that it 33 | hadn't anything in it. 34 | 35 | "I'm done, Bertie!" he said. 36 | 37 | He had another go at the glass. It didn't seem to do him any good. 38 | 39 | "If only this had happened a week later, Bertie! My next month's money was due 40 | to roll in on Saturday. I could have worked a wheeze I've been reading about 41 | in the magazine advertisements. It seems that you can make a dashed amount of 42 | money if you can only collect a few dollars and start a chicken-farm. Jolly 43 | sound scheme, Bertie! Say you buy a hen—call it one hen for the sake of 44 | argument. It lays an egg every day of the week. You sell the eggs seven for 45 | twenty-five cents. Keep of hen costs nothing. Profit practically twenty-five 46 | cents on every seven eggs. Or look at it another way: Suppose you have a dozen 47 | eggs. Each of the hens has a dozen chickens. The chickens grow up and have 48 | more chickens. Why, in no time you'd have the place covered knee-deep in hens, 49 | all laying eggs, at twenty-five cents for every seven. You'd make a fortune. 50 | Jolly life, too, keeping hens!" He had begun to get quite worked up at the 51 | thought of it, but he slopped back in his chair at this juncture with a good 52 | deal of gloom. "But, of course, it's no good," he said, "because I haven't the 53 | cash." 54 | 55 | "You've only to say the word, you know, Bicky, old top." 56 | 57 | "Thanks awfully, Bertie, but I'm not going to sponge on you." 58 | 59 | That's always the way in this world. The chappies you'd like to lend money to 60 | won't let you, whereas the chappies you don't want to lend it to will do 61 | everything except actually stand you on your head and lift the specie out of 62 | your pockets. As a lad who has always rolled tolerably free in the right 63 | stuff, I've had lots of experience of the second class. Many's the time, back 64 | in London, I've hurried along Piccadilly and felt the hot breath of the 65 | toucher on the back of my neck and heard his sharp, excited yapping as he 66 | closed in on me. I've simply spent my life scattering largesse to blighters I 67 | didn't care a hang for; yet here was I now, dripping doubloons and pieces of 68 | eight and longing to hand them over, and Bicky, poor fish, absolutely on his 69 | uppers, not taking any at any price. 70 | 71 | "Well, there's only one hope, then." 72 | 73 | "What's that?" 74 | 75 | "Jeeves." 76 | 77 | "Sir?" 78 | 79 | There was Jeeves, standing behind me, full of zeal. In this matter of 80 | shimmering into rooms the chappie is rummy to a degree. You're sitting in the 81 | old armchair, thinking of this and that, and then suddenly you look up, and 82 | there he is. He moves from point to point with as little uproar as a jelly 83 | fish. The thing startled poor old Bicky considerably. He rose from his seat 84 | like a rocketing pheasant. I'm used to Jeeves now, but often in the days when 85 | he first came to me I've bitten my tongue freely on finding him unexpectedly 86 | in my midst. 87 | 88 | [My Man Jeeves by PG Wodehouse][MMJ] 89 | 90 | [MMJ]: http://www.gutenberg.org/cache/epub/8164/pg8164.html 91 | -------------------------------------------------------------------------------- /hyde/tests/test_initialize.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Use nose 4 | `$ pip install nose` 5 | `$ nosetests` 6 | """ 7 | 8 | 9 | from hyde.engine import Engine 10 | from hyde.exceptions import HydeException 11 | from hyde.layout import Layout 12 | 13 | from fswrap import File, Folder 14 | from nose.tools import raises, with_setup, nottest 15 | 16 | TEST_SITE = File(__file__).parent.child_folder('_test') 17 | TEST_SITE_AT_USER = Folder('~/_test') 18 | 19 | @nottest 20 | def create_test_site(): 21 | TEST_SITE.make() 22 | 23 | @nottest 24 | def delete_test_site(): 25 | TEST_SITE.delete() 26 | 27 | @nottest 28 | def create_test_site_at_user(): 29 | TEST_SITE_AT_USER.make() 30 | 31 | @nottest 32 | def delete_test_site_at_user(): 33 | TEST_SITE_AT_USER.delete() 34 | 35 | @raises(HydeException) 36 | @with_setup(create_test_site, delete_test_site) 37 | def test_ensure_exception_when_site_yaml_exists(): 38 | e = Engine(raise_exceptions=True) 39 | File(TEST_SITE.child('site.yaml')).write("Hey") 40 | e.run(e.parse(['-s', unicode(TEST_SITE), 'create'])) 41 | 42 | @raises(HydeException) 43 | @with_setup(create_test_site, delete_test_site) 44 | def test_ensure_exception_when_content_folder_exists(): 45 | e = Engine(raise_exceptions=True) 46 | TEST_SITE.child_folder('content').make() 47 | e.run(e.parse(['-s', unicode(TEST_SITE), 'create'])) 48 | 49 | @raises(HydeException) 50 | @with_setup(create_test_site, delete_test_site) 51 | def test_ensure_exception_when_layout_folder_exists(): 52 | e = Engine(raise_exceptions=True) 53 | TEST_SITE.child_folder('layout').make() 54 | e.run(e.parse(['-s', unicode(TEST_SITE), 'create'])) 55 | 56 | @with_setup(create_test_site, delete_test_site) 57 | def test_ensure_no_exception_when_empty_site_exists(): 58 | e = Engine(raise_exceptions=True) 59 | e.run(e.parse(['-s', unicode(TEST_SITE), 'create'])) 60 | verify_site_contents(TEST_SITE, Layout.find_layout()) 61 | 62 | @with_setup(create_test_site, delete_test_site) 63 | def test_ensure_no_exception_when_forced(): 64 | e = Engine(raise_exceptions=True) 65 | TEST_SITE.child_folder('layout').make() 66 | e.run(e.parse(['-s', unicode(TEST_SITE), 'create', '-f'])) 67 | verify_site_contents(TEST_SITE, Layout.find_layout()) 68 | TEST_SITE.delete() 69 | TEST_SITE.child_folder('content').make() 70 | e.run(e.parse(['-s', unicode(TEST_SITE), 'create', '-f'])) 71 | verify_site_contents(TEST_SITE, Layout.find_layout()) 72 | TEST_SITE.delete() 73 | TEST_SITE.make() 74 | File(TEST_SITE.child('site.yaml')).write("Hey") 75 | e.run(e.parse(['-s', unicode(TEST_SITE), 'create', '-f'])) 76 | verify_site_contents(TEST_SITE, Layout.find_layout()) 77 | 78 | @with_setup(create_test_site, delete_test_site) 79 | def test_ensure_no_exception_when_sitepath_does_not_exist(): 80 | e = Engine(raise_exceptions=True) 81 | TEST_SITE.delete() 82 | e.run(e.parse(['-s', unicode(TEST_SITE), 'create', '-f'])) 83 | verify_site_contents(TEST_SITE, Layout.find_layout()) 84 | 85 | @with_setup(create_test_site_at_user, delete_test_site_at_user) 86 | def test_ensure_can_create_site_at_user(): 87 | e = Engine(raise_exceptions=True) 88 | TEST_SITE_AT_USER.delete() 89 | e.run(e.parse(['-s', unicode(TEST_SITE_AT_USER), 'create', '-f'])) 90 | verify_site_contents(TEST_SITE_AT_USER, Layout.find_layout()) 91 | 92 | @nottest 93 | def verify_site_contents(site, layout): 94 | assert site.exists 95 | assert site.child_folder('layout').exists 96 | assert File(site.child('info.yaml')).exists 97 | 98 | expected = map(lambda f: f.get_relative_path(layout), layout.walker.walk_all()) 99 | actual = map(lambda f: f.get_relative_path(site), site.walker.walk_all()) 100 | assert actual 101 | assert expected 102 | 103 | expected.sort() 104 | actual.sort() 105 | assert actual == expected 106 | 107 | @raises(HydeException) 108 | @with_setup(create_test_site, delete_test_site) 109 | def test_ensure_exception_when_layout_is_invalid(): 110 | e = Engine(raise_exceptions=True) 111 | e.run(e.parse(['-s', unicode(TEST_SITE), 'create', '-l', 'junk'])) 112 | 113 | -------------------------------------------------------------------------------- /hyde/layouts/basic/content/blog/sad-post.html: -------------------------------------------------------------------------------- 1 | --- 2 | title: A Sad Post 3 | description: > 4 | Something sad happened. 5 | created: !!timestamp '2010-12-01 10:00:00' 6 | tags: 7 | - sad 8 | - thoughts 9 | --- 10 | 11 | {% mark image -%} 12 | 13 | ![A Dark Image]([[!!images/dark.png]]) 14 | 15 | {%- endmark %} 16 | 17 | {% mark excerpt -%} 18 | 19 | I went and dressed sadly. It will show you pretty well how pipped I was when I 20 | tell you that I near as a toucher put on a white tie with a dinner-jacket. I 21 | sallied out for a bit of food more to pass the time than because I wanted it. 22 | It seemed brutal to be wading into the bill of fare with poor old Bicky headed 23 | for the breadline. 24 | 25 | {%- endmark %} 26 | 27 | When I got back old Chiswick had gone to bed, but Bicky was there, hunched up 28 | in an arm-chair, brooding pretty tensely, with a cigarette hanging out of the 29 | corner of his mouth and a more or less glassy stare in his eyes. He had the 30 | aspect of one who had been soaked with what the newspaper chappies call "some 31 | blunt instrument." 32 | 33 | "This is a bit thick, old thing—what!" I said. 34 | 35 | He picked up his glass and drained it feverishly, overlooking the fact that it 36 | hadn't anything in it. 37 | 38 | "I'm done, Bertie!" he said. 39 | 40 | He had another go at the glass. It didn't seem to do him any good. 41 | 42 | "If only this had happened a week later, Bertie! My next month's money was due 43 | to roll in on Saturday. I could have worked a wheeze I've been reading about 44 | in the magazine advertisements. It seems that you can make a dashed amount of 45 | money if you can only collect a few dollars and start a chicken-farm. Jolly 46 | sound scheme, Bertie! Say you buy a hen—call it one hen for the sake of 47 | argument. It lays an egg every day of the week. You sell the eggs seven for 48 | twenty-five cents. Keep of hen costs nothing. Profit practically twenty-five 49 | cents on every seven eggs. Or look at it another way: Suppose you have a dozen 50 | eggs. Each of the hens has a dozen chickens. The chickens grow up and have 51 | more chickens. Why, in no time you'd have the place covered knee-deep in hens, 52 | all laying eggs, at twenty-five cents for every seven. You'd make a fortune. 53 | Jolly life, too, keeping hens!" He had begun to get quite worked up at the 54 | thought of it, but he slopped back in his chair at this juncture with a good 55 | deal of gloom. "But, of course, it's no good," he said, "because I haven't the 56 | cash." 57 | 58 | "You've only to say the word, you know, Bicky, old top." 59 | 60 | "Thanks awfully, Bertie, but I'm not going to sponge on you." 61 | 62 | That's always the way in this world. The chappies you'd like to lend money to 63 | won't let you, whereas the chappies you don't want to lend it to will do 64 | everything except actually stand you on your head and lift the specie out of 65 | your pockets. As a lad who has always rolled tolerably free in the right 66 | stuff, I've had lots of experience of the second class. Many's the time, back 67 | in London, I've hurried along Piccadilly and felt the hot breath of the 68 | toucher on the back of my neck and heard his sharp, excited yapping as he 69 | closed in on me. I've simply spent my life scattering largesse to blighters I 70 | didn't care a hang for; yet here was I now, dripping doubloons and pieces of 71 | eight and longing to hand them over, and Bicky, poor fish, absolutely on his 72 | uppers, not taking any at any price. 73 | 74 | "Well, there's only one hope, then." 75 | 76 | "What's that?" 77 | 78 | "Jeeves." 79 | 80 | "Sir?" 81 | 82 | There was Jeeves, standing behind me, full of zeal. In this matter of 83 | shimmering into rooms the chappie is rummy to a degree. You're sitting in the 84 | old armchair, thinking of this and that, and then suddenly you look up, and 85 | there he is. He moves from point to point with as little uproar as a jelly 86 | fish. The thing startled poor old Bicky considerably. He rose from his seat 87 | like a rocketing pheasant. I'm used to Jeeves now, but often in the days when 88 | he first came to me I've bitten my tongue freely on finding him unexpectedly 89 | in my midst. 90 | 91 | [My Man Jeeves by PG Wodehouse][MMJ] 92 | 93 | [MMJ]: http://www.gutenberg.org/cache/epub/8164/pg8164.html -------------------------------------------------------------------------------- /hyde/tests/sites/test_paginator/content/blog/sad-post.html: -------------------------------------------------------------------------------- 1 | --- 2 | title: A Sad Post 3 | description: > 4 | Something sad happened. 5 | created: !!timestamp '2010-12-01 10:00:00' 6 | tags: 7 | - sad 8 | - thoughts 9 | --- 10 | 11 | {% mark image -%} 12 | 13 | ![A Dark Image]([[!!images/dark.png]]) 14 | 15 | {%- endmark %} 16 | 17 | {% mark excerpt -%} 18 | 19 | I went and dressed sadly. It will show you pretty well how pipped I was when I 20 | tell you that I near as a toucher put on a white tie with a dinner-jacket. I 21 | sallied out for a bit of food more to pass the time than because I wanted it. 22 | It seemed brutal to be wading into the bill of fare with poor old Bicky headed 23 | for the breadline. 24 | 25 | {%- endmark %} 26 | 27 | When I got back old Chiswick had gone to bed, but Bicky was there, hunched up 28 | in an arm-chair, brooding pretty tensely, with a cigarette hanging out of the 29 | corner of his mouth and a more or less glassy stare in his eyes. He had the 30 | aspect of one who had been soaked with what the newspaper chappies call "some 31 | blunt instrument." 32 | 33 | "This is a bit thick, old thing—what!" I said. 34 | 35 | He picked up his glass and drained it feverishly, overlooking the fact that it 36 | hadn't anything in it. 37 | 38 | "I'm done, Bertie!" he said. 39 | 40 | He had another go at the glass. It didn't seem to do him any good. 41 | 42 | "If only this had happened a week later, Bertie! My next month's money was due 43 | to roll in on Saturday. I could have worked a wheeze I've been reading about 44 | in the magazine advertisements. It seems that you can make a dashed amount of 45 | money if you can only collect a few dollars and start a chicken-farm. Jolly 46 | sound scheme, Bertie! Say you buy a hen—call it one hen for the sake of 47 | argument. It lays an egg every day of the week. You sell the eggs seven for 48 | twenty-five cents. Keep of hen costs nothing. Profit practically twenty-five 49 | cents on every seven eggs. Or look at it another way: Suppose you have a dozen 50 | eggs. Each of the hens has a dozen chickens. The chickens grow up and have 51 | more chickens. Why, in no time you'd have the place covered knee-deep in hens, 52 | all laying eggs, at twenty-five cents for every seven. You'd make a fortune. 53 | Jolly life, too, keeping hens!" He had begun to get quite worked up at the 54 | thought of it, but he slopped back in his chair at this juncture with a good 55 | deal of gloom. "But, of course, it's no good," he said, "because I haven't the 56 | cash." 57 | 58 | "You've only to say the word, you know, Bicky, old top." 59 | 60 | "Thanks awfully, Bertie, but I'm not going to sponge on you." 61 | 62 | That's always the way in this world. The chappies you'd like to lend money to 63 | won't let you, whereas the chappies you don't want to lend it to will do 64 | everything except actually stand you on your head and lift the specie out of 65 | your pockets. As a lad who has always rolled tolerably free in the right 66 | stuff, I've had lots of experience of the second class. Many's the time, back 67 | in London, I've hurried along Piccadilly and felt the hot breath of the 68 | toucher on the back of my neck and heard his sharp, excited yapping as he 69 | closed in on me. I've simply spent my life scattering largesse to blighters I 70 | didn't care a hang for; yet here was I now, dripping doubloons and pieces of 71 | eight and longing to hand them over, and Bicky, poor fish, absolutely on his 72 | uppers, not taking any at any price. 73 | 74 | "Well, there's only one hope, then." 75 | 76 | "What's that?" 77 | 78 | "Jeeves." 79 | 80 | "Sir?" 81 | 82 | There was Jeeves, standing behind me, full of zeal. In this matter of 83 | shimmering into rooms the chappie is rummy to a degree. You're sitting in the 84 | old armchair, thinking of this and that, and then suddenly you look up, and 85 | there he is. He moves from point to point with as little uproar as a jelly 86 | fish. The thing startled poor old Bicky considerably. He rose from his seat 87 | like a rocketing pheasant. I'm used to Jeeves now, but often in the days when 88 | he first came to me I've bitten my tongue freely on finding him unexpectedly 89 | in my midst. 90 | 91 | [My Man Jeeves by PG Wodehouse][MMJ] 92 | 93 | [MMJ]: http://www.gutenberg.org/cache/epub/8164/pg8164.html -------------------------------------------------------------------------------- /hyde/tests/sites/test_tagger/content/blog/sad-post.html: -------------------------------------------------------------------------------- 1 | --- 2 | title: A Sad Post 3 | description: > 4 | Something sad happened. 5 | created: !!timestamp '2010-12-01 10:00:00' 6 | tags: 7 | - sad 8 | - thoughts 9 | - transform 10 | --- 11 | 12 | {% mark image -%} 13 | 14 | ![A Dark Image]([[!!images/dark.png]]) 15 | 16 | {%- endmark %} 17 | 18 | {% mark excerpt -%} 19 | 20 | I went and dressed sadly. It will show you pretty well how pipped I was when I 21 | tell you that I near as a toucher put on a white tie with a dinner-jacket. I 22 | sallied out for a bit of food more to pass the time than because I wanted it. 23 | It seemed brutal to be wading into the bill of fare with poor old Bicky headed 24 | for the breadline. 25 | 26 | {%- endmark %} 27 | 28 | When I got back old Chiswick had gone to bed, but Bicky was there, hunched up 29 | in an arm-chair, brooding pretty tensely, with a cigarette hanging out of the 30 | corner of his mouth and a more or less glassy stare in his eyes. He had the 31 | aspect of one who had been soaked with what the newspaper chappies call "some 32 | blunt instrument." 33 | 34 | "This is a bit thick, old thing—what!" I said. 35 | 36 | He picked up his glass and drained it feverishly, overlooking the fact that it 37 | hadn't anything in it. 38 | 39 | "I'm done, Bertie!" he said. 40 | 41 | He had another go at the glass. It didn't seem to do him any good. 42 | 43 | "If only this had happened a week later, Bertie! My next month's money was due 44 | to roll in on Saturday. I could have worked a wheeze I've been reading about 45 | in the magazine advertisements. It seems that you can make a dashed amount of 46 | money if you can only collect a few dollars and start a chicken-farm. Jolly 47 | sound scheme, Bertie! Say you buy a hen—call it one hen for the sake of 48 | argument. It lays an egg every day of the week. You sell the eggs seven for 49 | twenty-five cents. Keep of hen costs nothing. Profit practically twenty-five 50 | cents on every seven eggs. Or look at it another way: Suppose you have a dozen 51 | eggs. Each of the hens has a dozen chickens. The chickens grow up and have 52 | more chickens. Why, in no time you'd have the place covered knee-deep in hens, 53 | all laying eggs, at twenty-five cents for every seven. You'd make a fortune. 54 | Jolly life, too, keeping hens!" He had begun to get quite worked up at the 55 | thought of it, but he slopped back in his chair at this juncture with a good 56 | deal of gloom. "But, of course, it's no good," he said, "because I haven't the 57 | cash." 58 | 59 | "You've only to say the word, you know, Bicky, old top." 60 | 61 | "Thanks awfully, Bertie, but I'm not going to sponge on you." 62 | 63 | That's always the way in this world. The chappies you'd like to lend money to 64 | won't let you, whereas the chappies you don't want to lend it to will do 65 | everything except actually stand you on your head and lift the specie out of 66 | your pockets. As a lad who has always rolled tolerably free in the right 67 | stuff, I've had lots of experience of the second class. Many's the time, back 68 | in London, I've hurried along Piccadilly and felt the hot breath of the 69 | toucher on the back of my neck and heard his sharp, excited yapping as he 70 | closed in on me. I've simply spent my life scattering largesse to blighters I 71 | didn't care a hang for; yet here was I now, dripping doubloons and pieces of 72 | eight and longing to hand them over, and Bicky, poor fish, absolutely on his 73 | uppers, not taking any at any price. 74 | 75 | "Well, there's only one hope, then." 76 | 77 | "What's that?" 78 | 79 | "Jeeves." 80 | 81 | "Sir?" 82 | 83 | There was Jeeves, standing behind me, full of zeal. In this matter of 84 | shimmering into rooms the chappie is rummy to a degree. You're sitting in the 85 | old armchair, thinking of this and that, and then suddenly you look up, and 86 | there he is. He moves from point to point with as little uproar as a jelly 87 | fish. The thing startled poor old Bicky considerably. He rose from his seat 88 | like a rocketing pheasant. I'm used to Jeeves now, but often in the days when 89 | he first came to me I've bitten my tongue freely on finding him unexpectedly 90 | in my midst. 91 | 92 | [My Man Jeeves by PG Wodehouse][MMJ] 93 | 94 | [MMJ]: http://www.gutenberg.org/cache/epub/8164/pg8164.html -------------------------------------------------------------------------------- /hyde/tests/ext/test_paginator.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Use nose 4 | `$ pip install nose` 5 | `$ nosetests` 6 | """ 7 | from textwrap import dedent 8 | 9 | from hyde.generator import Generator 10 | from hyde.site import Site 11 | 12 | from fswrap import File 13 | 14 | TEST_SITE = File(__file__).parent.parent.child_folder('_test') 15 | 16 | class TestPaginator(object): 17 | 18 | def setUp(self): 19 | TEST_SITE.make() 20 | TEST_SITE.parent.child_folder( 21 | 'sites/test_paginator').copy_contents_to(TEST_SITE) 22 | self.s = Site(TEST_SITE) 23 | self.deploy = TEST_SITE.child_folder('deploy') 24 | 25 | self.gen = Generator(self.s) 26 | self.gen.load_site_if_needed() 27 | self.gen.load_template_if_needed() 28 | self.gen.generate_all() 29 | 30 | 31 | def tearDown(self): 32 | TEST_SITE.delete() 33 | 34 | 35 | def test_pages_of_one(self): 36 | pages = ['pages_of_one.txt', 'page2/pages_of_one.txt', 37 | 'page3/pages_of_one.txt', 'page4/pages_of_one.txt'] 38 | files = [File(self.deploy.child(p)) for p in pages] 39 | for f in files: 40 | assert f.exists 41 | 42 | page5 = File(self.deploy.child('page5/pages_of_one.txt')) 43 | assert not page5.exists 44 | 45 | 46 | def test_pages_of_one_content(self): 47 | expected_page1_content = dedent('''\ 48 | Another Sad Post 49 | 50 | /page2/pages_of_one.txt''') 51 | expected_page2_content = dedent('''\ 52 | A Happy Post 53 | /pages_of_one.txt 54 | /page3/pages_of_one.txt''') 55 | expected_page3_content = dedent('''\ 56 | An Angry Post 57 | /page2/pages_of_one.txt 58 | /page4/pages_of_one.txt''') 59 | expected_page4_content = dedent('''\ 60 | A Sad Post 61 | /page3/pages_of_one.txt 62 | ''') 63 | 64 | page1 = self.deploy.child('pages_of_one.txt') 65 | content = File(page1).read_all() 66 | assert expected_page1_content == content 67 | 68 | page2 = self.deploy.child('page2/pages_of_one.txt') 69 | content = File(page2).read_all() 70 | assert expected_page2_content == content 71 | 72 | page3 = self.deploy.child('page3/pages_of_one.txt') 73 | content = File(page3).read_all() 74 | assert expected_page3_content == content 75 | 76 | page4 = self.deploy.child('page4/pages_of_one.txt') 77 | content = File(page4).read_all() 78 | assert expected_page4_content == content 79 | 80 | 81 | def test_pages_of_ten(self): 82 | page1 = self.deploy.child('pages_of_ten.txt') 83 | page2 = self.deploy.child('page2/pages_of_ten.txt') 84 | 85 | assert File(page1).exists 86 | assert not File(page2).exists 87 | 88 | 89 | def test_pages_of_ten_depends(self): 90 | depends = self.gen.deps['pages_of_ten.txt'] 91 | 92 | assert depends 93 | assert len(depends) == 4 94 | assert 'blog/sad-post.html' in depends 95 | assert 'blog/another-sad-post.html' in depends 96 | assert 'blog/angry-post.html' in depends 97 | assert 'blog/happy-post.html' in depends 98 | 99 | 100 | def test_pages_of_ten_content(self): 101 | expected_content = dedent('''\ 102 | Another Sad Post 103 | A Happy Post 104 | An Angry Post 105 | A Sad Post 106 | ''') 107 | 108 | page = self.deploy.child('pages_of_ten.txt') 109 | content = File(page).read_all() 110 | assert expected_content == content 111 | 112 | 113 | def test_pages_of_one_depends(self): 114 | depends = self.gen.deps['pages_of_one.txt'] 115 | 116 | assert depends 117 | assert len(depends) == 4 118 | assert 'blog/sad-post.html' in depends 119 | assert 'blog/another-sad-post.html' in depends 120 | assert 'blog/angry-post.html' in depends 121 | assert 'blog/happy-post.html' in depends 122 | 123 | 124 | def test_custom_file_pattern(self): 125 | page1 = self.deploy.child('custom_file_pattern.txt') 126 | page2 = self.deploy.child('custom_file_pattern-2.txt') 127 | 128 | assert File(page1).exists 129 | assert File(page2).exists 130 | -------------------------------------------------------------------------------- /hyde/layouts/basic/layout/base.j2: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | {% block starthead %}{% endblock starthead %} 10 | 11 | 12 | 14 | 15 | 16 | 18 | 19 | 22 | 25 | 26 | {% block title %}{{ resource.meta.title }}{% endblock %} 27 | 28 | 29 | 30 | 31 | 32 | 33 | {% block favicons %} 34 | 36 | 37 | 38 | {% endblock favicons %} 39 | 40 | {% block css %} 41 | 42 | 43 | {% endblock css %} 44 | 45 | {% block headjs %} 46 | 48 | 49 | {% endblock headjs %} 50 | {% block endhead %}{% endblock endhead %} 51 | 52 | 53 | {% block content %} 54 |
55 | {% block container %} 56 |
57 | 67 |
68 | {% block main %} 69 | {% endblock main %} 70 |
71 |
72 | {% endblock container %} 73 |
74 | 77 | {% endblock content%} 78 | {% block js %} 79 | 80 | {% block jquery %} 81 | 82 | 83 | 84 | {% endblock jquery %} 85 | 86 | {% block scripts %} 87 | {% endblock scripts %} 88 | 89 | 90 | 94 | 95 | {% block analytics %} 96 | {% include "analytics.j2" %} 97 | {% endblock analytics %} 98 | 99 | {% endblock js %} 100 | 101 | -------------------------------------------------------------------------------- /hyde/ext/plugins/vcs.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Contains classes and utilities to extract information from repositories 4 | """ 5 | 6 | from hyde.plugin import Plugin 7 | 8 | from datetime import datetime 9 | from dateutil.parser import parse 10 | import os.path 11 | import subprocess 12 | 13 | 14 | class VCSDatesPlugin(Plugin): 15 | """ 16 | Base class for getting resource timestamps from VCS. 17 | """ 18 | def __init__(self, site, vcs_name='vcs'): 19 | super(VCSDatesPlugin, self).__init__(site) 20 | self.vcs_name = vcs_name 21 | 22 | def begin_site(self): 23 | for node in self.site.content.walk(): 24 | for resource in node.resources: 25 | created = None 26 | modified = None 27 | try: 28 | created = resource.meta.created 29 | modified = resource.meta.modified 30 | except AttributeError: 31 | pass 32 | 33 | # Everything is already overrided 34 | if created != self.vcs_name and modified != self.vcs_name: 35 | continue 36 | 37 | date_created, date_modified = self.get_dates(resource) 38 | 39 | if created == "git": 40 | created = date_created or \ 41 | datetime.utcfromtimestamp( 42 | os.path.getctime(resource.path)) 43 | created = created.replace(tzinfo=None) 44 | resource.meta.created = created 45 | 46 | if modified == "git": 47 | modified = date_modified or resource.source.last_modified 48 | modified = modified.replace(tzinfo=None) 49 | resource.meta.modified = modified 50 | 51 | 52 | def get_dates(self): 53 | """ 54 | Extract creation and last modification date from the vcs and include 55 | them in the meta data if they are set to "". Creation date 56 | is put in `created` and last modification date in `modified`. 57 | """ 58 | return None, None 59 | 60 | # 61 | # Git Dates 62 | # 63 | class GitDatesPlugin(VCSDatesPlugin): 64 | def __init__(self, site): 65 | super(GitDatesPlugin, self).__init__(site, 'git') 66 | 67 | def get_dates(self, resource): 68 | """ 69 | Retrieve dates from git 70 | """ 71 | # Run git log --pretty=%ai 72 | try: 73 | commits = subprocess.check_output([ 74 | "git", 75 | "log", 76 | "--pretty=%ai", 77 | resource.path 78 | ]).split("\n") 79 | commits = commits[:-1] 80 | except subprocess.CalledProcessError: 81 | self.logger.warning("Unable to get git history for [%s]" % resource) 82 | commits = None 83 | 84 | if commits: 85 | created = parse(commits[-1].strip()) 86 | modified = parse(commits[0].strip()) 87 | else: 88 | self.logger.warning("No git history for [%s]" % resource) 89 | created, modified = None, None 90 | 91 | return created, modified 92 | 93 | # 94 | # Mercurial Dates 95 | # 96 | class MercurialDatesPlugin(VCSDatesPlugin): 97 | 98 | def __init__(self, site): 99 | super(MercurialDatesPlugin, self).__init__(site, 'hg') 100 | 101 | def get_dates(self, resource): 102 | """ 103 | Retrieve dates from mercurial 104 | """ 105 | # Run hg log --template={date|isodatesec} 106 | try: 107 | commits = subprocess.check_output([ 108 | "hg", "log", "--template={date|isodatesec}\n", 109 | resource.path]).split('\n') 110 | commits = commits[:-1] 111 | except subprocess.CalledProcessError: 112 | self.logger.warning("Unable to get mercurial history for [%s]" 113 | % resource) 114 | commits = None 115 | 116 | if not commits: 117 | self.logger.warning("No mercurial history for [%s]" % resource) 118 | return None, None 119 | 120 | created = parse(commits[-1].strip()) 121 | modified = parse(commits[0].strip()) 122 | return created, modified 123 | -------------------------------------------------------------------------------- /hyde/ext/publishers/pyfs.py: -------------------------------------------------------------------------------- 1 | """ 2 | Contains classes and utilities that help publishing a hyde website to 3 | a filesystem using PyFilesystem FS objects. 4 | 5 | This publisher provides an easy way to publish to FTP, SFTP, WebDAV or other 6 | servers by specifying a PyFS filesystem URL. For example, the following 7 | are valid URLs that can be used with this publisher: 8 | 9 | ftp://my.server.com/~username/my_blog/ 10 | dav:https://username:password@my.server.com/path/to/my/site 11 | 12 | """ 13 | 14 | import getpass 15 | import hashlib 16 | 17 | 18 | from hyde.publisher import Publisher 19 | 20 | from commando.util import getLoggerWithNullHandler 21 | 22 | logger = getLoggerWithNullHandler('hyde.ext.publishers.pyfs') 23 | 24 | 25 | try: 26 | from fs.osfs import OSFS 27 | from fs.path import pathjoin 28 | from fs.opener import fsopendir 29 | except ImportError: 30 | logger.error("The PyFS publisher requires PyFilesystem v0.4 or later.") 31 | logger.error("`pip install -U fs` to get it.") 32 | raise 33 | 34 | 35 | 36 | class PyFS(Publisher): 37 | 38 | def initialize(self, settings): 39 | self.settings = settings 40 | self.url = settings.url 41 | self.check_mtime = getattr(settings,"check_mtime",False) 42 | self.check_etag = getattr(settings,"check_etag",False) 43 | if self.check_etag and not isinstance(self.check_etag,basestring): 44 | raise ValueError("check_etag must name the etag algorithm") 45 | self.prompt_for_credentials() 46 | self.fs = fsopendir(self.url) 47 | 48 | def prompt_for_credentials(self): 49 | credentials = {} 50 | if "%(username)s" in self.url: 51 | print "Username: ", 52 | credentials["username"] = raw_input().strip() 53 | if "%(password)s" in self.url: 54 | credentials["password"] = getpass.getpass("Password: ") 55 | if credentials: 56 | self.url = self.url % credentials 57 | 58 | def publish(self): 59 | super(PyFS, self).publish() 60 | deploy_fs = OSFS(self.site.config.deploy_root_path.path) 61 | for (dirnm,local_filenms) in deploy_fs.walk(): 62 | logger.info("Making directory: %s",dirnm) 63 | self.fs.makedir(dirnm,allow_recreate=True) 64 | remote_fileinfos = self.fs.listdirinfo(dirnm,files_only=True) 65 | # Process each local file, to see if it needs updating. 66 | for filenm in local_filenms: 67 | filepath = pathjoin(dirnm,filenm) 68 | # Try to find an existing remote file, to compare metadata. 69 | for (nm,info) in remote_fileinfos: 70 | if nm == filenm: 71 | break 72 | else: 73 | info = {} 74 | # Skip it if the etags match 75 | if self.check_etag and "etag" in info: 76 | with deploy_fs.open(filepath,"rb") as f: 77 | local_etag = self._calculate_etag(f) 78 | if info["etag"] == local_etag: 79 | logger.info("Skipping file [etag]: %s",filepath) 80 | continue 81 | # Skip it if the mtime is more recent remotely. 82 | if self.check_mtime and "modified_time" in info: 83 | local_mtime = deploy_fs.getinfo(filepath)["modified_time"] 84 | if info["modified_time"] > local_mtime: 85 | logger.info("Skipping file [mtime]: %s",filepath) 86 | continue 87 | # Upload it to the remote filesystem. 88 | logger.info("Uploading file: %s",filepath) 89 | with deploy_fs.open(filepath,"rb") as f: 90 | self.fs.setcontents(filepath,f) 91 | # Process each remote file, to see if it needs deleting. 92 | for (filenm,info) in remote_fileinfos: 93 | filepath = pathjoin(dirnm,filenm) 94 | if filenm not in local_filenms: 95 | logger.info("Removing file: %s",filepath) 96 | self.fs.remove(filepath) 97 | 98 | def _calculate_etag(self,f): 99 | hasher = getattr(hashlib,self.check_etag.lower())() 100 | data = f.read(1024*64) 101 | while data: 102 | hasher.update(data) 103 | data = f.read(1024*64) 104 | return hasher.hexdigest() 105 | 106 | -------------------------------------------------------------------------------- /hyde/tests/test_simple_copy.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Tests the simple copy feature. 4 | 5 | In order to mark some files to simply be copied to the 6 | destination without any processing what so ever add this 7 | to the config (site.yaml for example): 8 | simple_copy: 9 | - media/css/*.css 10 | - media/js/*.js 11 | - **/*.js 12 | 13 | Matching is done with `fnmatch` module. So any `glob` that fnmatch 14 | can process is a valid pattern. 15 | 16 | Use nose 17 | `$ pip install nose` 18 | `$ nosetests` 19 | """ 20 | import yaml 21 | 22 | from hyde.model import Config 23 | from hyde.site import Site 24 | from hyde.generator import Generator 25 | 26 | from fswrap import File 27 | from nose.tools import nottest 28 | 29 | 30 | TEST_SITE_ROOT = File(__file__).parent.child_folder('sites/test_jinja') 31 | 32 | class TestSimpleCopy(object): 33 | @classmethod 34 | def setup_class(cls): 35 | cls.SITE_PATH = File(__file__).parent.child_folder('sites/test_jinja_with_config') 36 | cls.SITE_PATH.make() 37 | TEST_SITE_ROOT.copy_contents_to(cls.SITE_PATH) 38 | 39 | @classmethod 40 | def teardown_class(cls): 41 | cls.SITE_PATH.delete() 42 | 43 | @nottest 44 | def setup_config(self, passthru): 45 | self.config_file = File(self.SITE_PATH.child('site.yaml')) 46 | with open(self.config_file.path) as config: 47 | conf = yaml.load(config) 48 | conf['simple_copy'] = passthru 49 | self.config = Config(sitepath=self.SITE_PATH, config_dict=conf) 50 | 51 | def test_simple_copy_basic(self): 52 | self.setup_config([ 53 | 'about.html' 54 | ]) 55 | s = Site(self.SITE_PATH, config=self.config) 56 | s.load() 57 | res = s.content.resource_from_relative_path('about.html') 58 | assert res 59 | assert res.simple_copy 60 | 61 | def test_simple_copy_directory(self): 62 | self.setup_config([ 63 | '**/*.html' 64 | ]) 65 | s = Site(self.SITE_PATH, config=self.config) 66 | s.load() 67 | res = s.content.resource_from_relative_path('about.html') 68 | assert res 69 | assert not res.simple_copy 70 | res = s.content.resource_from_relative_path('blog/2010/december/merry-christmas.html') 71 | assert res 72 | assert res.simple_copy 73 | 74 | def test_simple_copy_multiple(self): 75 | self.setup_config([ 76 | '**/*.html', 77 | 'media/css/*.css' 78 | ]) 79 | s = Site(self.SITE_PATH, config=self.config) 80 | s.load() 81 | res = s.content.resource_from_relative_path('about.html') 82 | assert res 83 | assert not res.simple_copy 84 | res = s.content.resource_from_relative_path('blog/2010/december/merry-christmas.html') 85 | assert res 86 | assert res.simple_copy 87 | res = s.content.resource_from_relative_path('media/css/site.css') 88 | assert res 89 | assert res.simple_copy 90 | 91 | def test_generator(self): 92 | self.setup_config([ 93 | '**/*.html', 94 | 'media/css/*.css' 95 | ]) 96 | s = Site(self.SITE_PATH, self.config) 97 | g = Generator(s) 98 | g.generate_all() 99 | source = s.content.resource_from_relative_path('blog/2010/december/merry-christmas.html') 100 | target = File(s.config.deploy_root_path.child(source.relative_deploy_path)) 101 | left = source.source_file.read_all() 102 | right = target.read_all() 103 | assert left == right 104 | 105 | def test_plugins(self): 106 | 107 | text = """ 108 | --- 109 | title: Hey 110 | author: Me 111 | twitter: @me 112 | --- 113 | {%% extends "base.html" %%} 114 | 115 | {%% block main %%} 116 | Hi! 117 | 118 | I am a test template to make sure jinja2 generation works well with hyde. 119 | {{resource.meta.title}} 120 | {{resource.meta.author}} 121 | 122 | {%% endblock %%} 123 | """ 124 | index = File(self.SITE_PATH.child('content/blog/index.html')) 125 | index.write(text) 126 | self.setup_config([ 127 | '**/*.html', 128 | 'media/css/*.css' 129 | ]) 130 | conf = {'plugins': ['hyde.ext.plugins.meta.MetaPlugin']} 131 | conf.update(self.config.to_dict()) 132 | s = Site(self.SITE_PATH, Config(sitepath=self.SITE_PATH, config_dict=conf)) 133 | g = Generator(s) 134 | g.generate_all() 135 | source = s.content.resource_from_relative_path('blog/index.html') 136 | target = File(s.config.deploy_root_path.child(source.relative_deploy_path)) 137 | left = source.source_file.read_all() 138 | right = target.read_all() 139 | assert left == right -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | Version 0.8.8 2 | 3 | A brand new **hyde** 4 | ==================== 5 | 6 | This is the new version of `hyde`_ under active development. 7 | `Hyde documentation`_ is a work in progress. 8 | 9 | `Hyde starter kit`_ by `merlinrebrovic`_ is a really nice way to get started 10 | with hyde. 11 | 12 | `Hyde layout for bootstrap`_ by `auzigog`_ is also a good alternative if you 13 | like Twitter's `bootstrap framework`_. 14 | 15 | You can also take a look at `Hyde Powered Websites`_ for inspiration and reference. 16 | 17 | Installation 18 | ------------ 19 | 20 | To get the latest released version: 21 | 22 | :: 23 | 24 | pip install hyde 25 | 26 | For the current trunk: 27 | 28 | :: 29 | 30 | pip install -e git://github.com/hyde/hyde.git#egg=hyde 31 | 32 | Creating a new hyde site 33 | ------------------------ 34 | 35 | The following command: 36 | 37 | :: 38 | 39 | hyde -s ~/test_site create 40 | 41 | will create a new hyde site using the test layout. 42 | 43 | Generating the hyde site 44 | ------------------------ 45 | 46 | :: 47 | 48 | cd ~/test_site 49 | hyde gen 50 | 51 | Serving the website 52 | ------------------- 53 | 54 | :: 55 | 56 | cd ~/test_site 57 | hyde serve 58 | open http://localhost:8080 59 | 60 | Publishing the website 61 | ---------------------- 62 | 63 | :: 64 | 65 | cd ~/test_site 66 | hyde publish -p github 67 | 68 | 69 | Hyde supports extensible publishers. 70 | 71 | Github 72 | ~~~~~~~ 73 | 74 | The hyde documentation is published to github pages using this command with 75 | the following configuration: 76 | 77 | :: 78 | 79 | publisher: 80 | github: 81 | type: hyde.ext.publishers.dvcs.Git 82 | path: ../hyde.github.com 83 | url: git@github.com:hyde/hyde.github.com.git 84 | 85 | .. Note:: Currently, the initial path must have clone of the repository 86 | already in place for this command to work. 87 | 88 | PyFS 89 | ~~~~~~~ 90 | 91 | Hyde also has a publisher that acts as a frontend to the awesome 92 | `PyFS library`_ (thanks to `rfk`_). Here are a few configuration 93 | options for some PyFS backends: 94 | 95 | :: 96 | 97 | publisher: 98 | zip: 99 | type: hyde.ext.publishers.pyfs.PyFS 100 | url: zip://~/deploy/hyde/docs.zip 101 | s3: 102 | type: hyde.ext.publishers.pyfs.PyFS 103 | url: s3://hyde/docs 104 | sftp: 105 | type: hyde.ext.publishers.pyfs.PyFS 106 | url: sftp:hydeuser:hydepassword@hydedocs.org 107 | 108 | .. Note:: PyFS is not installed with hyde. In order to use the 109 | PyFS publisher, you need to install pyfs separately. 110 | 111 | Any PyFS dependencies (Example: `boto` for S3 publishing) 112 | need to be installed separately as well. 113 | 114 | :: 115 | 116 | pip install fs 117 | pip install boto 118 | 119 | To get additional help on PyFS backends, you can run the following 120 | command once PyFS is installed: 121 | 122 | :: 123 | 124 | fsls --listopeners 125 | 126 | Examples 127 | -------- 128 | 129 | 1. `Hyde Documentation Source`_ 130 | 2. `Cloudpanic`_ 131 | 3. `Ringce`_ 132 | 133 | A brief list of features 134 | -------------------------- 135 | 136 | 1. Evented Plugins: The Plugin hooks allow plugins to listen to events 137 | that occur during different times in the lifecycle and respond 138 | accordingly. 139 | 2. Metadata: Hyde now supports hierarchical metadata. You can specify 140 | and override variables at the site, node or the page level and access 141 | them in the templates. 142 | 3. Organization: The sorter, grouper and tagger plugins provide rich 143 | meta-data driven organizational capabilities to hyde sites. 144 | 4. Publishing: Hyde sites can be published to variety of targets including 145 | github pages, Amazon S3 & SFTP. 146 | 147 | Links 148 | ----- 149 | 150 | 1. `Changelog`_ 151 | 2. `Authors`_ 152 | 153 | 154 | .. _hyde: https://github.com/lakshmivyas/hyde 155 | .. _Hyde documentation: http://hyde.github.com 156 | .. _Hyde Documentation Source: https://github.com/hyde/docs 157 | .. _Cloudpanic: https://github.com/tipiirai/cloudpanic 158 | .. _Ringce: https://github.com/lakshmivyas/ringce/tree/v3.0 159 | .. _Authors: https://github.com/hyde/hyde/blob/master/AUTHORS.rst 160 | .. _Changelog: https://github.com/hyde/hyde/blob/master/CHANGELOG.rst 161 | .. _Hyde starter kit: http://merlin.rebrovic.net/hyde-starter-kit/about.html 162 | .. _merlinrebrovic: https://github.com/merlinrebrovic 163 | .. _rfk: https://github.com/rfk 164 | .. _PyFS library: http://packages.python.org/fs/ 165 | .. _Hyde layout for bootstrap: https://github.com/auzigog/hyde-bootstrap 166 | .. _auzigog: https://github.com/auzigog 167 | .. _bootstrap framework: http://twitter.github.com/bootstrap/ 168 | .. _Hyde Powered Websites: https://github.com/hyde/hyde/wiki/Hyde-Powered 169 | -------------------------------------------------------------------------------- /hyde/template.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # pylint: disable-msg=W0104,E0602,W0613,R0201 3 | """ 4 | Abstract classes and utilities for template engines 5 | """ 6 | from hyde.exceptions import HydeException 7 | 8 | import abc 9 | 10 | from commando.util import getLoggerWithNullHandler 11 | 12 | 13 | class HtmlWrap(object): 14 | """ 15 | A wrapper class for raw html. 16 | 17 | Provides pyquery interface if available. 18 | Otherwise raw html access. 19 | """ 20 | 21 | def __init__(self, html): 22 | super(HtmlWrap, self).__init__() 23 | self.raw = html 24 | try: 25 | from pyquery import PyQuery 26 | except: 27 | PyQuery = False 28 | if PyQuery: 29 | self.q = PyQuery(html) 30 | 31 | def __unicode__(self): 32 | return self.raw 33 | 34 | def __call__(self, selector=None): 35 | if not self.q: 36 | return self.raw 37 | return self.q(selector).html() 38 | 39 | class Template(object): 40 | """ 41 | Interface for hyde template engines. To use a different template engine, 42 | the following interface must be implemented. 43 | """ 44 | 45 | __metaclass__ = abc.ABCMeta 46 | 47 | def __init__(self, sitepath): 48 | self.sitepath = sitepath 49 | self.logger = getLoggerWithNullHandler(self.__class__.__name__) 50 | 51 | @abc.abstractmethod 52 | def configure(self, site, engine): 53 | 54 | """ 55 | The site object should contain a config attribute. The config object 56 | is a simple YAML object with required settings. The template 57 | implementations are responsible for transforming this object to match 58 | the `settings` required for the template engines. 59 | 60 | The engine is an informal protocol to provide access to some 61 | hyde internals. 62 | 63 | The preprocessor attribute must contain the function that trigger the 64 | hyde plugins to preprocess the template after load. 65 | 66 | A context_for_path attribute must contain the function that returns 67 | the context object that is populated with the appropriate variables 68 | for the given path. 69 | """ 70 | return 71 | 72 | def clear_caches(self): 73 | """ 74 | Clear all caches to prepare for regeneration 75 | """ 76 | return 77 | 78 | def get_dependencies(self, text): 79 | """ 80 | Finds the dependencies based on the included 81 | files. 82 | """ 83 | return None 84 | 85 | @abc.abstractmethod 86 | def render_resource(self, resource, context): 87 | """ 88 | This function must load the file represented by the resource 89 | object and return the rendered text. 90 | """ 91 | return '' 92 | 93 | @abc.abstractmethod 94 | def render(self, text, context): 95 | """ 96 | Given the text, and the context, this function must return the 97 | rendered string. 98 | """ 99 | 100 | return '' 101 | 102 | @abc.abstractproperty 103 | def exception_class(self): 104 | return HydeException 105 | 106 | @abc.abstractproperty 107 | def patterns(self): 108 | """ 109 | Patterns for matching selected template statements. 110 | """ 111 | return {} 112 | 113 | @abc.abstractmethod 114 | def get_include_statement(self, path_to_include): 115 | """ 116 | Returns an include statement for the current template, 117 | given the path to include. 118 | """ 119 | return '{%% include \'%s\' %%}' % path_to_include 120 | 121 | @abc.abstractmethod 122 | def get_extends_statement(self, path_to_extend): 123 | """ 124 | Returns an extends statement for the current template, 125 | given the path to extend. 126 | """ 127 | return '{%% extends \'%s\' %%}' % path_to_extend 128 | 129 | @abc.abstractmethod 130 | def get_open_tag(self, tag, params): 131 | """ 132 | Returns an open tag statement. 133 | """ 134 | return '{%% %s %s %%}' % (tag, params) 135 | 136 | @abc.abstractmethod 137 | def get_close_tag(self, tag, params): 138 | """ 139 | Returns an open tag statement. 140 | """ 141 | return '{%% end%s %%}' % tag 142 | 143 | @abc.abstractmethod 144 | def get_content_url_statement(self, url): 145 | """ 146 | Returns the content url statement. 147 | """ 148 | return '/' + url 149 | 150 | @abc.abstractmethod 151 | def get_media_url_statement(self, url): 152 | """ 153 | Returns the media url statement. 154 | """ 155 | return '/media/' + url 156 | 157 | @staticmethod 158 | def find_template(site): 159 | """ 160 | Reads the configuration to find the appropriate template. 161 | """ 162 | # TODO: Find the appropriate template environment 163 | from hyde.ext.templates.jinja import Jinja2Template 164 | template = Jinja2Template(site.sitepath) 165 | return template 166 | -------------------------------------------------------------------------------- /hyde/tests/sites/test_sorter/content/blog/angry-post.html: -------------------------------------------------------------------------------- 1 | --- 2 | title: An Angry Post 3 | description: > 4 | Temper. Temper. Temper. 5 | created: !!timestamp '2011-01-01 10:00:00' 6 | index: 1 7 | --- 8 | 9 | {% mark excerpt -%} 10 | 11 | To complete the character-study of Mr. Worple, he was a man of extremely 12 | uncertain temper, and his general tendency was to think that Corky was a poor 13 | chump and that whatever step he took in any direction on his own account, was 14 | just another proof of his innate idiocy. I should imagine Jeeves feels very 15 | much the same about me. 16 | 17 | {%- endmark %} 18 | 19 | So when Corky trickled into my apartment one afternoon, shooing a girl in 20 | front of him, and said, "Bertie, I want you to meet my fiancée, Miss Singer," 21 | the aspect of the matter which hit me first was precisely the one which he had 22 | come to consult me about. The very first words I spoke were, "Corky, how about 23 | your uncle?" 24 | 25 | The poor chap gave one of those mirthless laughs. He was looking anxious and 26 | worried, like a man who has done the murder all right but can't think what the 27 | deuce to do with the body. 28 | 29 | "We're so scared, Mr. Wooster," said the girl. "We were hoping that you might 30 | suggest a way of breaking it to him." 31 | 32 | Muriel Singer was one of those very quiet, appealing girls who have a way of 33 | looking at you with their big eyes as if they thought you were the greatest 34 | thing on earth and wondered that you hadn't got on to it yet yourself. She sat 35 | there in a sort of shrinking way, looking at me as if she were saying to 36 | herself, "Oh, I do hope this great strong man isn't going to hurt me." She 37 | gave a fellow a protective kind of feeling, made him want to stroke her hand 38 | and say, "There, there, little one!" or words to that effect. She made me feel 39 | that there was nothing I wouldn't do for her. She was rather like one of those 40 | innocent-tasting American drinks which creep imperceptibly into your system so 41 | that, before you know what you're doing, you're starting out to reform the 42 | world by force if necessary and pausing on your way to tell the large man in 43 | the corner that, if he looks at you like that, you will knock his head off. 44 | What I mean is, she made me feel alert and dashing, like a jolly old 45 | knight-errant or something of that kind. I felt that I was with her in this 46 | thing to the limit. 47 | 48 | "I don't see why your uncle shouldn't be most awfully bucked," I said to 49 | Corky. "He will think Miss Singer the ideal wife for you." 50 | 51 | Corky declined to cheer up. 52 | 53 | "You don't know him. Even if he did like Muriel he wouldn't admit it. That's 54 | the sort of pig-headed guy he is. It would be a matter of principle with him 55 | to kick. All he would consider would be that I had gone and taken an important 56 | step without asking his advice, and he would raise Cain automatically. He's 57 | always done it." 58 | 59 | I strained the old bean to meet this emergency. 60 | 61 | "You want to work it so that he makes Miss Singer's acquaintance without 62 | knowing that you know her. Then you come along" 63 | 64 | "But how can I work it that way?" 65 | 66 | I saw his point. That was the catch. 67 | 68 | "There's only one thing to do," I said. 69 | 70 | "What's that?" 71 | 72 | "Leave it to Jeeves." 73 | 74 | And I rang the bell. 75 | 76 | "Sir?" said Jeeves, kind of manifesting himself. One of the rummy things about 77 | Jeeves is that, unless you watch like a hawk, you very seldom see him come 78 | into a room. He's like one of those weird chappies in India who dissolve 79 | themselves into thin air and nip through space in a sort of disembodied way 80 | and assemble the parts again just where they want them. I've got a cousin 81 | who's what they call a Theosophist, and he says he's often nearly worked the 82 | thing himself, but couldn't quite bring it off, probably owing to having fed 83 | in his boyhood on the flesh of animals slain in anger and pie. 84 | 85 | The moment I saw the man standing there, registering respectful attention, a 86 | weight seemed to roll off my mind. I felt like a lost child who spots his 87 | father in the offing. There was something about him that gave me confidence. 88 | 89 | Jeeves is a tallish man, with one of those dark, shrewd faces. His eye gleams 90 | with the light of pure intelligence. 91 | 92 | "Jeeves, we want your advice." 93 | 94 | "Very good, sir." 95 | 96 | I boiled down Corky's painful case into a few well-chosen words. 97 | 98 | "So you see what it amount to, Jeeves. We want you to suggest some way by 99 | which Mr. Worple can make Miss Singer's acquaintance without getting on to the 100 | fact that Mr. Corcoran already knows her. Understand?" 101 | 102 | "Perfectly, sir." 103 | 104 | "Well, try to think of something." 105 | 106 | "I have thought of something already, sir." 107 | 108 | "You have!" 109 | 110 | "The scheme I would suggest cannot fail of success, but it has what may seem 111 | to you a drawback, sir, in that it requires a certain financial outlay." 112 | 113 | "He means," I translated to Corky, "that he has got a pippin of an idea, but 114 | it's going to cost a bit." 115 | 116 | Naturally the poor chap's face dropped, for this seemed to dish the whole 117 | thing. But I was still under the influence of the girl's melting gaze, and I 118 | saw that this was where I started in as a knight-errant. 119 | 120 | "You can count on me for all that sort of thing, Corky," I said. "Only too 121 | glad. Carry on, Jeeves." 122 | 123 | "I would suggest, sir, that Mr. Corcoran take advantage of Mr. Worple's 124 | attachment to ornithology." 125 | 126 | "How on earth did you know that he was fond of birds?" 127 | 128 | 129 | [My Man Jeeves by PG Wodehouse][MMJ] 130 | 131 | [MMJ]: http://www.gutenberg.org/cache/epub/8164/pg8164.html 132 | -------------------------------------------------------------------------------- /hyde/tests/sites/test_paginator/content/blog/angry-post.html: -------------------------------------------------------------------------------- 1 | --- 2 | title: An Angry Post 3 | description: > 4 | Temper. Temper. Temper. 5 | created: !!timestamp '2011-01-01 10:00:00' 6 | tags: 7 | - angry 8 | - thoughts 9 | --- 10 | 11 | {% mark excerpt -%} 12 | 13 | To complete the character-study of Mr. Worple, he was a man of extremely 14 | uncertain temper, and his general tendency was to think that Corky was a poor 15 | chump and that whatever step he took in any direction on his own account, was 16 | just another proof of his innate idiocy. I should imagine Jeeves feels very 17 | much the same about me. 18 | 19 | {%- endmark %} 20 | 21 | So when Corky trickled into my apartment one afternoon, shooing a girl in 22 | front of him, and said, "Bertie, I want you to meet my fiancée, Miss Singer," 23 | the aspect of the matter which hit me first was precisely the one which he had 24 | come to consult me about. The very first words I spoke were, "Corky, how about 25 | your uncle?" 26 | 27 | The poor chap gave one of those mirthless laughs. He was looking anxious and 28 | worried, like a man who has done the murder all right but can't think what the 29 | deuce to do with the body. 30 | 31 | "We're so scared, Mr. Wooster," said the girl. "We were hoping that you might 32 | suggest a way of breaking it to him." 33 | 34 | Muriel Singer was one of those very quiet, appealing girls who have a way of 35 | looking at you with their big eyes as if they thought you were the greatest 36 | thing on earth and wondered that you hadn't got on to it yet yourself. She sat 37 | there in a sort of shrinking way, looking at me as if she were saying to 38 | herself, "Oh, I do hope this great strong man isn't going to hurt me." She 39 | gave a fellow a protective kind of feeling, made him want to stroke her hand 40 | and say, "There, there, little one!" or words to that effect. She made me feel 41 | that there was nothing I wouldn't do for her. She was rather like one of those 42 | innocent-tasting American drinks which creep imperceptibly into your system so 43 | that, before you know what you're doing, you're starting out to reform the 44 | world by force if necessary and pausing on your way to tell the large man in 45 | the corner that, if he looks at you like that, you will knock his head off. 46 | What I mean is, she made me feel alert and dashing, like a jolly old 47 | knight-errant or something of that kind. I felt that I was with her in this 48 | thing to the limit. 49 | 50 | "I don't see why your uncle shouldn't be most awfully bucked," I said to 51 | Corky. "He will think Miss Singer the ideal wife for you." 52 | 53 | Corky declined to cheer up. 54 | 55 | "You don't know him. Even if he did like Muriel he wouldn't admit it. That's 56 | the sort of pig-headed guy he is. It would be a matter of principle with him 57 | to kick. All he would consider would be that I had gone and taken an important 58 | step without asking his advice, and he would raise Cain automatically. He's 59 | always done it." 60 | 61 | I strained the old bean to meet this emergency. 62 | 63 | "You want to work it so that he makes Miss Singer's acquaintance without 64 | knowing that you know her. Then you come along" 65 | 66 | "But how can I work it that way?" 67 | 68 | I saw his point. That was the catch. 69 | 70 | "There's only one thing to do," I said. 71 | 72 | "What's that?" 73 | 74 | "Leave it to Jeeves." 75 | 76 | And I rang the bell. 77 | 78 | "Sir?" said Jeeves, kind of manifesting himself. One of the rummy things about 79 | Jeeves is that, unless you watch like a hawk, you very seldom see him come 80 | into a room. He's like one of those weird chappies in India who dissolve 81 | themselves into thin air and nip through space in a sort of disembodied way 82 | and assemble the parts again just where they want them. I've got a cousin 83 | who's what they call a Theosophist, and he says he's often nearly worked the 84 | thing himself, but couldn't quite bring it off, probably owing to having fed 85 | in his boyhood on the flesh of animals slain in anger and pie. 86 | 87 | The moment I saw the man standing there, registering respectful attention, a 88 | weight seemed to roll off my mind. I felt like a lost child who spots his 89 | father in the offing. There was something about him that gave me confidence. 90 | 91 | Jeeves is a tallish man, with one of those dark, shrewd faces. His eye gleams 92 | with the light of pure intelligence. 93 | 94 | "Jeeves, we want your advice." 95 | 96 | "Very good, sir." 97 | 98 | I boiled down Corky's painful case into a few well-chosen words. 99 | 100 | "So you see what it amount to, Jeeves. We want you to suggest some way by 101 | which Mr. Worple can make Miss Singer's acquaintance without getting on to the 102 | fact that Mr. Corcoran already knows her. Understand?" 103 | 104 | "Perfectly, sir." 105 | 106 | "Well, try to think of something." 107 | 108 | "I have thought of something already, sir." 109 | 110 | "You have!" 111 | 112 | "The scheme I would suggest cannot fail of success, but it has what may seem 113 | to you a drawback, sir, in that it requires a certain financial outlay." 114 | 115 | "He means," I translated to Corky, "that he has got a pippin of an idea, but 116 | it's going to cost a bit." 117 | 118 | Naturally the poor chap's face dropped, for this seemed to dish the whole 119 | thing. But I was still under the influence of the girl's melting gaze, and I 120 | saw that this was where I started in as a knight-errant. 121 | 122 | "You can count on me for all that sort of thing, Corky," I said. "Only too 123 | glad. Carry on, Jeeves." 124 | 125 | "I would suggest, sir, that Mr. Corcoran take advantage of Mr. Worple's 126 | attachment to ornithology." 127 | 128 | "How on earth did you know that he was fond of birds?" 129 | 130 | 131 | [My Man Jeeves by PG Wodehouse][MMJ] 132 | 133 | [MMJ]: http://www.gutenberg.org/cache/epub/8164/pg8164.html --------------------------------------------------------------------------------