@@ -14,3 +14,4 @@ build | |||
.idea | |||
PYSMELLTAGS | |||
.noseids | |||
*.tar.gz |
@@ -0,0 +1,6 @@ | |||
exclude *.pyc .DS_Store .gitignore .noseids MANIFEST.in | |||
include setup.py | |||
include distribute_setup.py | |||
recursive-include hyde *.py | |||
recursive-include hyde/tests *.py | |||
recursive-include hyde/layouts *.* |
@@ -1,13 +1,14 @@ | |||
Version 0.7b1 | |||
# A brand new **hyde** | |||
This is the new version of hyde under active development. | |||
Incomplete documentation can be found [here][hydedocs]. | |||
[This][hyde1-0] should give a good understanding of the motivation behind this | |||
version. You can also take a look at the [cloudpanic source][cp] for a | |||
reference implementation. | |||
This is the new version of hyde under active development. Incomplete documentation | |||
can be found [here][hydedocs]. [This][hyde1-0] should give a good understanding of | |||
the motivation behind this version. You can also take a look at the | |||
[documentation source][docs] for a reference implementation. | |||
[hyde1-0]: http://groups.google.com/group/hyde-dev/web/hyde-1-0 | |||
[cp]: http://github.com/tipiirai/cloudpanic/tree/refactor | |||
[docs]: https://github.com/hyde/hyde/tree/master/hyde/layouts/doc | |||
[hydedocs]: http://hyde.github.com/overview | |||
[Here](http://groups.google.com/group/hyde-dev/browse_thread/thread/2a143bd2081b3322) is | |||
@@ -15,31 +16,22 @@ the initial announcement of the project. | |||
## Installation | |||
Hyde supports both python 2.7 and 2.6. | |||
pip install -r req-2.6.txt | |||
or | |||
pip install -r req-2.7.txt | |||
To get the latest released version: | |||
will install all the dependencies of hyde. | |||
pip install hyde | |||
You can choose to install hyde by running | |||
For the current trunk: | |||
python setup.py install | |||
pip install -e git://github.com/hyde/hyde.git#egg=hyde | |||
## Creating a new hyde site | |||
The new version of Hyde uses the `argparse` module and hence support subcommands. | |||
The following command: | |||
hyde -s ~/test_site create -l test | |||
hyde -s ~/test_site create -l doc | |||
will create a new hyde site using the test layout. | |||
## Generating the hyde site | |||
cd ~/test_site | |||
@@ -56,6 +48,11 @@ The server also regenerates on demand. As long as the server is running, | |||
you can make changes to your source and refresh the browser to view the changes. | |||
## Examples | |||
1. [Cloudpanic](https://github.com/tipiirai/cloudpanic) | |||
2. [Ringce](https://github.com/lakshmivyas/ringce/tree/v3.0) | |||
## A brief list of features | |||
@@ -0,0 +1,485 @@ | |||
#!python | |||
"""Bootstrap distribute installation | |||
If you want to use setuptools in your package's setup.py, just include this | |||
file in the same directory with it, and add this to the top of your setup.py:: | |||
from distribute_setup import use_setuptools | |||
use_setuptools() | |||
If you want to require a specific version of setuptools, set a download | |||
mirror, or use an alternate download directory, you can do so by supplying | |||
the appropriate options to ``use_setuptools()``. | |||
This file can also be run as a script to install or upgrade setuptools. | |||
""" | |||
import os | |||
import sys | |||
import time | |||
import fnmatch | |||
import tempfile | |||
import tarfile | |||
from distutils import log | |||
try: | |||
from site import USER_SITE | |||
except ImportError: | |||
USER_SITE = None | |||
try: | |||
import subprocess | |||
def _python_cmd(*args): | |||
args = (sys.executable,) + args | |||
return subprocess.call(args) == 0 | |||
except ImportError: | |||
# will be used for python 2.3 | |||
def _python_cmd(*args): | |||
args = (sys.executable,) + args | |||
# quoting arguments if windows | |||
if sys.platform == 'win32': | |||
def quote(arg): | |||
if ' ' in arg: | |||
return '"%s"' % arg | |||
return arg | |||
args = [quote(arg) for arg in args] | |||
return os.spawnl(os.P_WAIT, sys.executable, *args) == 0 | |||
DEFAULT_VERSION = "0.6.14" | |||
DEFAULT_URL = "http://pypi.python.org/packages/source/d/distribute/" | |||
SETUPTOOLS_FAKED_VERSION = "0.6c11" | |||
SETUPTOOLS_PKG_INFO = """\ | |||
Metadata-Version: 1.0 | |||
Name: setuptools | |||
Version: %s | |||
Summary: xxxx | |||
Home-page: xxx | |||
Author: xxx | |||
Author-email: xxx | |||
License: xxx | |||
Description: xxx | |||
""" % SETUPTOOLS_FAKED_VERSION | |||
def _install(tarball): | |||
# extracting the tarball | |||
tmpdir = tempfile.mkdtemp() | |||
log.warn('Extracting in %s', tmpdir) | |||
old_wd = os.getcwd() | |||
try: | |||
os.chdir(tmpdir) | |||
tar = tarfile.open(tarball) | |||
_extractall(tar) | |||
tar.close() | |||
# going in the directory | |||
subdir = os.path.join(tmpdir, os.listdir(tmpdir)[0]) | |||
os.chdir(subdir) | |||
log.warn('Now working in %s', subdir) | |||
# installing | |||
log.warn('Installing Distribute') | |||
if not _python_cmd('setup.py', 'install'): | |||
log.warn('Something went wrong during the installation.') | |||
log.warn('See the error message above.') | |||
finally: | |||
os.chdir(old_wd) | |||
def _build_egg(egg, tarball, to_dir): | |||
# extracting the tarball | |||
tmpdir = tempfile.mkdtemp() | |||
log.warn('Extracting in %s', tmpdir) | |||
old_wd = os.getcwd() | |||
try: | |||
os.chdir(tmpdir) | |||
tar = tarfile.open(tarball) | |||
_extractall(tar) | |||
tar.close() | |||
# going in the directory | |||
subdir = os.path.join(tmpdir, os.listdir(tmpdir)[0]) | |||
os.chdir(subdir) | |||
log.warn('Now working in %s', subdir) | |||
# building an egg | |||
log.warn('Building a Distribute egg in %s', to_dir) | |||
_python_cmd('setup.py', '-q', 'bdist_egg', '--dist-dir', to_dir) | |||
finally: | |||
os.chdir(old_wd) | |||
# returning the result | |||
log.warn(egg) | |||
if not os.path.exists(egg): | |||
raise IOError('Could not build the egg.') | |||
def _do_download(version, download_base, to_dir, download_delay): | |||
egg = os.path.join(to_dir, 'distribute-%s-py%d.%d.egg' | |||
% (version, sys.version_info[0], sys.version_info[1])) | |||
if not os.path.exists(egg): | |||
tarball = download_setuptools(version, download_base, | |||
to_dir, download_delay) | |||
_build_egg(egg, tarball, to_dir) | |||
sys.path.insert(0, egg) | |||
import setuptools | |||
setuptools.bootstrap_install_from = egg | |||
def use_setuptools(version=DEFAULT_VERSION, download_base=DEFAULT_URL, | |||
to_dir=os.curdir, download_delay=15, no_fake=True): | |||
# making sure we use the absolute path | |||
to_dir = os.path.abspath(to_dir) | |||
was_imported = 'pkg_resources' in sys.modules or \ | |||
'setuptools' in sys.modules | |||
try: | |||
try: | |||
import pkg_resources | |||
if not hasattr(pkg_resources, '_distribute'): | |||
if not no_fake: | |||
_fake_setuptools() | |||
raise ImportError | |||
except ImportError: | |||
return _do_download(version, download_base, to_dir, download_delay) | |||
try: | |||
pkg_resources.require("distribute>="+version) | |||
return | |||
except pkg_resources.VersionConflict: | |||
e = sys.exc_info()[1] | |||
if was_imported: | |||
sys.stderr.write( | |||
"The required version of distribute (>=%s) is not available,\n" | |||
"and can't be installed while this script is running. Please\n" | |||
"install a more recent version first, using\n" | |||
"'easy_install -U distribute'." | |||
"\n\n(Currently using %r)\n" % (version, e.args[0])) | |||
sys.exit(2) | |||
else: | |||
del pkg_resources, sys.modules['pkg_resources'] # reload ok | |||
return _do_download(version, download_base, to_dir, | |||
download_delay) | |||
except pkg_resources.DistributionNotFound: | |||
return _do_download(version, download_base, to_dir, | |||
download_delay) | |||
finally: | |||
if not no_fake: | |||
_create_fake_setuptools_pkg_info(to_dir) | |||
def download_setuptools(version=DEFAULT_VERSION, download_base=DEFAULT_URL, | |||
to_dir=os.curdir, delay=15): | |||
"""Download distribute from a specified location and return its filename | |||
`version` should be a valid distribute version number that is available | |||
as an egg for download under the `download_base` URL (which should end | |||
with a '/'). `to_dir` is the directory where the egg will be downloaded. | |||
`delay` is the number of seconds to pause before an actual download | |||
attempt. | |||
""" | |||
# making sure we use the absolute path | |||
to_dir = os.path.abspath(to_dir) | |||
try: | |||
from urllib.request import urlopen | |||
except ImportError: | |||
from urllib2 import urlopen | |||
tgz_name = "distribute-%s.tar.gz" % version | |||
url = download_base + tgz_name | |||
saveto = os.path.join(to_dir, tgz_name) | |||
src = dst = None | |||
if not os.path.exists(saveto): # Avoid repeated downloads | |||
try: | |||
log.warn("Downloading %s", url) | |||
src = urlopen(url) | |||
# Read/write all in one block, so we don't create a corrupt file | |||
# if the download is interrupted. | |||
data = src.read() | |||
dst = open(saveto, "wb") | |||
dst.write(data) | |||
finally: | |||
if src: | |||
src.close() | |||
if dst: | |||
dst.close() | |||
return os.path.realpath(saveto) | |||
def _no_sandbox(function): | |||
def __no_sandbox(*args, **kw): | |||
try: | |||
from setuptools.sandbox import DirectorySandbox | |||
if not hasattr(DirectorySandbox, '_old'): | |||
def violation(*args): | |||
pass | |||
DirectorySandbox._old = DirectorySandbox._violation | |||
DirectorySandbox._violation = violation | |||
patched = True | |||
else: | |||
patched = False | |||
except ImportError: | |||
patched = False | |||
try: | |||
return function(*args, **kw) | |||
finally: | |||
if patched: | |||
DirectorySandbox._violation = DirectorySandbox._old | |||
del DirectorySandbox._old | |||
return __no_sandbox | |||
def _patch_file(path, content): | |||
"""Will backup the file then patch it""" | |||
existing_content = open(path).read() | |||
if existing_content == content: | |||
# already patched | |||
log.warn('Already patched.') | |||
return False | |||
log.warn('Patching...') | |||
_rename_path(path) | |||
f = open(path, 'w') | |||
try: | |||
f.write(content) | |||
finally: | |||
f.close() | |||
return True | |||
_patch_file = _no_sandbox(_patch_file) | |||
def _same_content(path, content): | |||
return open(path).read() == content | |||
def _rename_path(path): | |||
new_name = path + '.OLD.%s' % time.time() | |||
log.warn('Renaming %s into %s', path, new_name) | |||
os.rename(path, new_name) | |||
return new_name | |||
def _remove_flat_installation(placeholder): | |||
if not os.path.isdir(placeholder): | |||
log.warn('Unkown installation at %s', placeholder) | |||
return False | |||
found = False | |||
for file in os.listdir(placeholder): | |||
if fnmatch.fnmatch(file, 'setuptools*.egg-info'): | |||
found = True | |||
break | |||
if not found: | |||
log.warn('Could not locate setuptools*.egg-info') | |||
return | |||
log.warn('Removing elements out of the way...') | |||
pkg_info = os.path.join(placeholder, file) | |||
if os.path.isdir(pkg_info): | |||
patched = _patch_egg_dir(pkg_info) | |||
else: | |||
patched = _patch_file(pkg_info, SETUPTOOLS_PKG_INFO) | |||
if not patched: | |||
log.warn('%s already patched.', pkg_info) | |||
return False | |||
# now let's move the files out of the way | |||
for element in ('setuptools', 'pkg_resources.py', 'site.py'): | |||
element = os.path.join(placeholder, element) | |||
if os.path.exists(element): | |||
_rename_path(element) | |||
else: | |||
log.warn('Could not find the %s element of the ' | |||
'Setuptools distribution', element) | |||
return True | |||
_remove_flat_installation = _no_sandbox(_remove_flat_installation) | |||
def _after_install(dist): | |||
log.warn('After install bootstrap.') | |||
placeholder = dist.get_command_obj('install').install_purelib | |||
_create_fake_setuptools_pkg_info(placeholder) | |||
def _create_fake_setuptools_pkg_info(placeholder): | |||
if not placeholder or not os.path.exists(placeholder): | |||
log.warn('Could not find the install location') | |||
return | |||
pyver = '%s.%s' % (sys.version_info[0], sys.version_info[1]) | |||
setuptools_file = 'setuptools-%s-py%s.egg-info' % \ | |||
(SETUPTOOLS_FAKED_VERSION, pyver) | |||
pkg_info = os.path.join(placeholder, setuptools_file) | |||
if os.path.exists(pkg_info): | |||
log.warn('%s already exists', pkg_info) | |||
return | |||
log.warn('Creating %s', pkg_info) | |||
f = open(pkg_info, 'w') | |||
try: | |||
f.write(SETUPTOOLS_PKG_INFO) | |||
finally: | |||
f.close() | |||
pth_file = os.path.join(placeholder, 'setuptools.pth') | |||
log.warn('Creating %s', pth_file) | |||
f = open(pth_file, 'w') | |||
try: | |||
f.write(os.path.join(os.curdir, setuptools_file)) | |||
finally: | |||
f.close() | |||
_create_fake_setuptools_pkg_info = _no_sandbox(_create_fake_setuptools_pkg_info) | |||
def _patch_egg_dir(path): | |||
# let's check if it's already patched | |||
pkg_info = os.path.join(path, 'EGG-INFO', 'PKG-INFO') | |||
if os.path.exists(pkg_info): | |||
if _same_content(pkg_info, SETUPTOOLS_PKG_INFO): | |||
log.warn('%s already patched.', pkg_info) | |||
return False | |||
_rename_path(path) | |||
os.mkdir(path) | |||
os.mkdir(os.path.join(path, 'EGG-INFO')) | |||
pkg_info = os.path.join(path, 'EGG-INFO', 'PKG-INFO') | |||
f = open(pkg_info, 'w') | |||
try: | |||
f.write(SETUPTOOLS_PKG_INFO) | |||
finally: | |||
f.close() | |||
return True | |||
_patch_egg_dir = _no_sandbox(_patch_egg_dir) | |||
def _before_install(): | |||
log.warn('Before install bootstrap.') | |||
_fake_setuptools() | |||
def _under_prefix(location): | |||
if 'install' not in sys.argv: | |||
return True | |||
args = sys.argv[sys.argv.index('install')+1:] | |||
for index, arg in enumerate(args): | |||
for option in ('--root', '--prefix'): | |||
if arg.startswith('%s=' % option): | |||
top_dir = arg.split('root=')[-1] | |||
return location.startswith(top_dir) | |||
elif arg == option: | |||
if len(args) > index: | |||
top_dir = args[index+1] | |||
return location.startswith(top_dir) | |||
if arg == '--user' and USER_SITE is not None: | |||
return location.startswith(USER_SITE) | |||
return True | |||
def _fake_setuptools(): | |||
log.warn('Scanning installed packages') | |||
try: | |||
import pkg_resources | |||
except ImportError: | |||
# we're cool | |||
log.warn('Setuptools or Distribute does not seem to be installed.') | |||
return | |||
ws = pkg_resources.working_set | |||
try: | |||
setuptools_dist = ws.find(pkg_resources.Requirement.parse('setuptools', | |||
replacement=False)) | |||
except TypeError: | |||
# old distribute API | |||
setuptools_dist = ws.find(pkg_resources.Requirement.parse('setuptools')) | |||
if setuptools_dist is None: | |||
log.warn('No setuptools distribution found') | |||
return | |||
# detecting if it was already faked | |||
setuptools_location = setuptools_dist.location | |||
log.warn('Setuptools installation detected at %s', setuptools_location) | |||
# if --root or --preix was provided, and if | |||
# setuptools is not located in them, we don't patch it | |||
if not _under_prefix(setuptools_location): | |||
log.warn('Not patching, --root or --prefix is installing Distribute' | |||
' in another location') | |||
return | |||
# let's see if its an egg | |||
if not setuptools_location.endswith('.egg'): | |||
log.warn('Non-egg installation') | |||
res = _remove_flat_installation(setuptools_location) | |||
if not res: | |||
return | |||
else: | |||
log.warn('Egg installation') | |||
pkg_info = os.path.join(setuptools_location, 'EGG-INFO', 'PKG-INFO') | |||
if (os.path.exists(pkg_info) and | |||
_same_content(pkg_info, SETUPTOOLS_PKG_INFO)): | |||
log.warn('Already patched.') | |||
return | |||
log.warn('Patching...') | |||
# let's create a fake egg replacing setuptools one | |||
res = _patch_egg_dir(setuptools_location) | |||
if not res: | |||
return | |||
log.warn('Patched done.') | |||
_relaunch() | |||
def _relaunch(): | |||
log.warn('Relaunching...') | |||
# we have to relaunch the process | |||
# pip marker to avoid a relaunch bug | |||
if sys.argv[:3] == ['-c', 'install', '--single-version-externally-managed']: | |||
sys.argv[0] = 'setup.py' | |||
args = [sys.executable] + sys.argv | |||
sys.exit(subprocess.call(args)) | |||
def _extractall(self, path=".", members=None): | |||
"""Extract all members from the archive to the current working | |||
directory and set owner, modification time and permissions on | |||
directories afterwards. `path' specifies a different directory | |||
to extract to. `members' is optional and must be a subset of the | |||
list returned by getmembers(). | |||
""" | |||
import copy | |||
import operator | |||
from tarfile import ExtractError | |||
directories = [] | |||
if members is None: | |||
members = self | |||
for tarinfo in members: | |||
if tarinfo.isdir(): | |||
# Extract directories with a safe mode. | |||
directories.append(tarinfo) | |||
tarinfo = copy.copy(tarinfo) | |||
tarinfo.mode = 448 # decimal for oct 0700 | |||
self.extract(tarinfo, path) | |||
# Reverse sort directories. | |||
if sys.version_info < (2, 4): | |||
def sorter(dir1, dir2): | |||
return cmp(dir1.name, dir2.name) | |||
directories.sort(sorter) | |||
directories.reverse() | |||
else: | |||
directories.sort(key=operator.attrgetter('name'), reverse=True) | |||
# Set correct owner, mtime and filemode on directories. | |||
for tarinfo in directories: | |||
dirpath = os.path.join(path, tarinfo.name) | |||
try: | |||
self.chown(tarinfo, dirpath) | |||
self.utime(tarinfo, dirpath) | |||
self.chmod(tarinfo, dirpath) | |||
except ExtractError: | |||
e = sys.exc_info()[1] | |||
if self.errorlevel > 1: | |||
raise | |||
else: | |||
self._dbg(1, "tarfile: %s" % e) | |||
def main(argv, version=DEFAULT_VERSION): | |||
"""Install or upgrade setuptools and EasyInstall""" | |||
tarball = download_setuptools() | |||
_install(tarball) | |||
if __name__ == '__main__': | |||
main(sys.argv[1:]) |
@@ -20,6 +20,9 @@ class AutoExtendPlugin(Plugin): | |||
and there is no extends statement, this plugin automatically adds | |||
an extends statement to the top of the file. | |||
""" | |||
if not resource.uses_template: | |||
return text | |||
layout = None | |||
block = None | |||
try: | |||
@@ -7,7 +7,7 @@ import re | |||
from hyde.model import Expando | |||
from hyde.plugin import Plugin | |||
from hyde.site import Node, Resource | |||
from hyde.util import add_method, pairwalk | |||
from hyde.util import add_method, add_property, pairwalk | |||
from collections import namedtuple | |||
from functools import partial | |||
@@ -42,6 +42,10 @@ class Group(Expando): | |||
'walk_resources_grouped_by_%s' % self.name, | |||
Group.walk_resources, | |||
group=self) | |||
add_property(Resource, | |||
'%s_group' % self.name, | |||
Group.get_resource_group, | |||
group=self) | |||
add_method(Resource, | |||
'walk_%s_groups' % self.name, | |||
Group.walk_resource_groups, | |||
@@ -57,6 +61,23 @@ class Group(Expando): | |||
else: | |||
return super(Group, self).set_expando(key, value) | |||
@staticmethod | |||
def get_resource_group(resource, group): | |||
""" | |||
This method gets attached to the resource object. | |||
Returns group and its ancestors that the resource | |||
belongs to, in that order. | |||
""" | |||
try: | |||
group_name = getattr(resource.meta, group.root.name) | |||
except AttributeError: | |||
group_name = None | |||
return next((g for g in group.walk_groups() | |||
if g.name == group_name), None) \ | |||
if group_name \ | |||
else None | |||
@staticmethod | |||
def walk_resource_groups(resource, group): | |||
""" | |||
@@ -4,6 +4,7 @@ Jinja template utilties | |||
""" | |||
from hyde.fs import File, Folder | |||
from hyde.model import Expando | |||
from hyde.template import HtmlWrap, Template | |||
from hyde.site import Resource | |||
from hyde.util import getLoggerWithNullHandler, getLoggerWithConsoleHandler | |||
@@ -58,11 +59,12 @@ def markdown(env, value): | |||
d = {} | |||
if hasattr(env.config, 'markdown'): | |||
d['extensions'] = getattr(env.config.markdown, 'extensions', []) | |||
d['extension_configs'] = getattr(env.config.markdown, 'extension_configs', {}) | |||
d['extension_configs'] = getattr(env.config.markdown, | |||
'extension_configs', | |||
Expando({})).to_dict() | |||
md = markdown.Markdown(**d) | |||
return md.convert(output) | |||
@environmentfilter | |||
def syntax(env, value, lexer=None, filename=None): | |||
""" | |||
@@ -81,7 +83,9 @@ def syntax(env, value, lexer=None, filename=None): | |||
lexers.guess_lexer(value)) | |||
settings = {} | |||
if hasattr(env.config, 'syntax'): | |||
settings = getattr(env.config.syntax, 'options', {}) | |||
settings = getattr(env.config.syntax, | |||
'options', | |||
Expando({})).to_dict() | |||
formatter = formatters.HtmlFormatter(**settings) | |||
code = pygments.highlight(value, pyg, formatter) | |||
@@ -117,6 +121,49 @@ class Markdown(Extension): | |||
output = caller().strip() | |||
return markdown(self.environment, output) | |||
class YamlVar(Extension): | |||
""" | |||
An extension that converts the content between the tags | |||
into an yaml object and sets the value in the given | |||
variable. | |||
""" | |||
tags = set(['yaml']) | |||
def parse(self, parser): | |||
""" | |||
Parses the contained data and defers to the callback to load it as | |||
yaml. | |||
""" | |||
lineno = parser.stream.next().lineno | |||
var = parser.stream.expect('name').value | |||
body = parser.parse_statements(['name:endyaml'], drop_needle=True) | |||
return [ | |||
nodes.Assign( | |||
nodes.Name(var, 'store'), | |||
nodes.Const({}) | |||
).set_lineno(lineno), | |||
nodes.CallBlock( | |||
self.call_method('_set_yaml', args=[nodes.Name(var, 'load')]), | |||
[], [], body).set_lineno(lineno) | |||
] | |||
def _set_yaml(self, var, caller=None): | |||
""" | |||
Loads the yaml data into the specified variable. | |||
""" | |||
if not caller: | |||
return '' | |||
try: | |||
import yaml | |||
except ImportError: | |||
return '' | |||
out = caller().strip() | |||
var.update(yaml.load(out)) | |||
return '' | |||
def parse_kwargs(parser): | |||
name = parser.stream.expect('name').value | |||
parser.stream.expect('assign') | |||
@@ -265,7 +312,13 @@ class Refer(Extension): | |||
includeNode.ignore_missing = False | |||
includeNode.template = template | |||
temp = parser.free_identifier(lineno) | |||
return [ | |||
nodes.Assign( | |||
nodes.Name(temp.name, 'store'), | |||
nodes.Name(MARKINGS, 'load') | |||
).set_lineno(lineno), | |||
nodes.Assign( | |||
nodes.Name(MARKINGS, 'store'), | |||
nodes.Const({})).set_lineno(lineno), | |||
@@ -295,6 +348,10 @@ class Refer(Extension): | |||
nodes.Getitem(nodes.Name(namespace, 'load'), | |||
nodes.Const('parent_resource'), 'load') | |||
).set_lineno(lineno), | |||
nodes.Assign( | |||
nodes.Name(MARKINGS, 'store'), | |||
nodes.Name(temp.name, 'load') | |||
).set_lineno(lineno), | |||
] | |||
def _push_resource(self, namespace, site, resource, template, caller): | |||
@@ -378,6 +435,7 @@ class Jinja2Template(Template): | |||
Syntax, | |||
Reference, | |||
Refer, | |||
YamlVar, | |||
'jinja2.ext.do', | |||
'jinja2.ext.loopcontrols', | |||
'jinja2.ext.with_']) | |||
@@ -13,7 +13,7 @@ | |||
<!-- Always force latest IE rendering engine (even in intranet) & Chrome Frame | |||
Remove this if you use the .htaccess --> | |||
<meta http-equiv="X-UA-Compatible" content="{{page.meta.compatibility|default('IE=edge,chrome=1')"> | |||
<meta http-equiv="X-UA-Compatible" content="{{page.meta.compatibility|default('IE=edge,chrome=1')}}"> | |||
<!-- encoding must be specified within the first 512 bytes www.whatwg.org/specs/web-apps/current-work/multipage/semantics.html#charset --> | |||
@@ -25,7 +25,7 @@ | |||
<meta name="author" content="{{resource.meta.author}}"> | |||
<!-- Mobile viewport optimized: j.mp/bplateviewport --> | |||
<meta name="viewport" content="{{page.meta.viewport|default('width=device-width, initial-scale=1.0')"> | |||
<meta name="viewport" content="{{page.meta.viewport|default('width=device-width, initial-scale=1.0')}}"> | |||
{% block favicons %} | |||
<!-- Place favicon.ico & apple-touch-icon.png in the root of your domain and delete these references --> | |||
@@ -99,4 +99,4 @@ | |||
{% endblock js %} | |||
</body> | |||
</html> | |||
{% endblock all %} | |||
{% endblock all %} |
@@ -57,6 +57,16 @@ class Expando(object): | |||
else: | |||
return primitive | |||
def to_dict(self): | |||
""" | |||
Reverse transform an expando to dict | |||
""" | |||
d = self.__dict__ | |||
for k, v in d.iteritems(): | |||
if isinstance(v, Expando): | |||
d[k] = v.to_dict() | |||
return d | |||
class Context(object): | |||
""" | |||
@@ -58,9 +58,13 @@ class HydeRequestHandler(SimpleHTTPRequestHandler): | |||
logger.debug("Trying to load file based on request:[%s]" % result.path) | |||
path = result.path.lstrip('/') | |||
if path.strip() == "" or File(path).kind.strip() == "": | |||
return site.config.deploy_root_path.child(path) | |||
res = site.content.resource_from_relative_deploy_path(path) | |||
deployed = site.config.deploy_root_path.child(path) | |||
deployed = Folder.file_or_folder(deployed) | |||
if isinstance(deployed, Folder): | |||
node = site.content.node_from_relative_path(path) | |||
res = node.get_resource('index.html') | |||
else: | |||
res = site.content.resource_from_relative_deploy_path(path) | |||
if not res: | |||
@@ -130,6 +130,19 @@ class TestGrouperSingleLevel(object): | |||
plugin_resources = [resource.name for resource in self.s.content.walk_resources_grouped_by_plugins()] | |||
assert plugin_resources == self.plugins | |||
def test_resource_group(self): | |||
groups = dict([(g.name, g) for g in self.s.grouper['section'].groups]) | |||
for name, group in groups.items(): | |||
pages = getattr(self, name) | |||
for page in pages: | |||
res = self.s.content.resource_from_relative_path('blog/' + page) | |||
assert hasattr(res, 'section_group') | |||
res_group = getattr(res, 'section_group') | |||
print "%s, %s=%s" % (page, group.name, res_group.name) | |||
assert res_group == group | |||
def test_resource_belongs_to(self): | |||
groups = dict([(g.name, g) for g in self.s.grouper['section'].groups]) | |||
@@ -128,6 +128,7 @@ def assert_markdown_typogrify_processed_well(include_text, includer_text): | |||
gen.load_template_if_needed() | |||
template = gen.template | |||
html = template.render(includer_text, {}).strip() | |||
print html | |||
assert html | |||
q = PyQuery(html) | |||
assert "is_processable" not in html | |||
@@ -200,7 +201,7 @@ class TestJinjaTemplate(object): | |||
deps = list(t.get_dependencies('inc.md')) | |||
assert len(deps) == 1 | |||
assert not deps[0] | |||
@@ -314,6 +315,52 @@ Hyde & Jinja. | |||
assert "mark" not in html | |||
assert "reference" not in html | |||
def test_two_level_refer_with_var(self): | |||
text = """ | |||
=== | |||
is_processable: False | |||
=== | |||
<div class="fulltext"> | |||
{% filter markdown|typogrify %} | |||
{% mark heading %} | |||
This is a heading | |||
================= | |||
{% endmark %} | |||
{% reference content %} | |||
Hyde & Jinja. | |||
{% endreference %} | |||
{% endfilter %} | |||
</div> | |||
""" | |||
text2 = """ | |||
{% set super = 'super.md' %} | |||
{% refer to super as sup %} | |||
<div class="justhead"> | |||
{% mark child %} | |||
{{ sup.heading }} | |||
{% endmark %} | |||
{% mark cont %} | |||
{{ sup.content }} | |||
{% endmark %} | |||
</div> | |||
""" | |||
text3 = """ | |||
{% set incu = 'inc.md' %} | |||
{% refer to incu as inc %} | |||
{% filter markdown|typogrify %} | |||
{{ inc.child }} | |||
{{ inc.cont }} | |||
{% endfilter %} | |||
""" | |||
superinc = File(TEST_SITE.child('content/super.md')) | |||
superinc.write(text) | |||
html = assert_markdown_typogrify_processed_well(text2, text3) | |||
assert "mark" not in html | |||
assert "reference" not in html | |||
def test_refer_with_var(self): | |||
text = """ | |||
@@ -340,4 +387,45 @@ Hyde & Jinja. | |||
""" | |||
html = assert_markdown_typogrify_processed_well(text, text2) | |||
assert "mark" not in html | |||
assert "reference" not in html | |||
assert "reference" not in html | |||
def test_yaml_tag(salf): | |||
text = """ | |||
{% yaml test %} | |||
one: | |||
- A | |||
- B | |||
- C | |||
two: | |||
- D | |||
- E | |||
- F | |||
{% endyaml %} | |||
{% for section, values in test.items() %} | |||
<ul class="{{ section }}"> | |||
{% for value in values %} | |||
<li>{{ value }}</li> | |||
{% endfor %} | |||
</ul> | |||
{% endfor %} | |||
""" | |||
t = Jinja2Template(JINJA2.path) | |||
t.configure(None) | |||
html = t.render(text, {}).strip() | |||
actual = PyQuery(html) | |||
assert actual("ul").length == 2 | |||
assert actual("ul.one").length == 1 | |||
assert actual("ul.two").length == 1 | |||
assert actual("li").length == 6 | |||
assert actual("ul.one li").length == 3 | |||
assert actual("ul.two li").length == 3 | |||
ones = [item.text for item in actual("ul.one li")] | |||
assert ones == ["A", "B", "C"] | |||
twos = [item.text for item in actual("ul.two li")] | |||
assert twos == ["D", "E", "F"] |
@@ -43,6 +43,26 @@ def test_expando_update(): | |||
assert x.a == 789 | |||
assert x.f == "opq" | |||
def test_expando_to_dict(): | |||
d = {"a": 123, "b": {"c": 456, "d": {"e": "abc"}}} | |||
x = Expando(d) | |||
assert d == x.to_dict() | |||
def test_expando_to_dict_with_update(): | |||
d1 = {"a": 123, "b": "abc"} | |||
x = Expando(d1) | |||
d = {"b": {"c": 456, "d": {"e": "abc"}}, "f": "lmn"} | |||
x.update(d) | |||
expected = {} | |||
expected.update(d1) | |||
expected.update(d) | |||
assert expected == x.to_dict() | |||
d2 = {"a": 789, "f": "opq"} | |||
y = Expando(d2) | |||
x.update(y) | |||
expected.update(d2) | |||
assert expected == x.to_dict() | |||
TEST_SITE = File(__file__).parent.child_folder('_test') | |||
import yaml | |||
@@ -92,11 +92,16 @@ def make_method(method_name, method_): | |||
method__.__name__ = method_name | |||
return method__ | |||
def add_property(obj, method_name, method_, *args, **kwargs): | |||
from functools import partial | |||
m = make_method(method_name, partial(method_, *args, **kwargs)) | |||
setattr(obj, method_name, property(m)) | |||
def add_method(obj, method_name, method_, *args, **kwargs): | |||
from functools import partial | |||
m = make_method(method_name, partial(method_, *args, **kwargs)) | |||
setattr(obj, method_name, m) | |||
def pairwalk(iterable): | |||
a, b = tee(iterable) | |||
next(b, None) |
@@ -3,4 +3,4 @@ | |||
Handles hyde version | |||
TODO: Use fabric like versioning scheme | |||
""" | |||
__version__ = '0.6.0' | |||
__version__ = '0.7b1' |
@@ -1,2 +0,0 @@ | |||
argparse | |||
-r req-2.7.txt |
@@ -1,7 +0,0 @@ | |||
commando==0.1.1a | |||
PyYAML==3.09 | |||
Markdown==2.0.3 | |||
MarkupSafe==0.11 | |||
smartypants==1.6.0.3 | |||
-e git://github.com/hyde/typogrify.git#egg=typogrify | |||
Jinja2==2.5.5 |
@@ -1 +1,8 @@ | |||
-r req-2.6.txt | |||
argparse | |||
commando | |||
PyYAML | |||
Markdown | |||
MarkupSafe | |||
smartypants | |||
-e git://github.com/hyde/typogrify.git#egg=typogrify | |||
Jinja2 |
@@ -1,13 +1,123 @@ | |||
# Bootstrap installation of Distribute | |||
import distribute_setup | |||
distribute_setup.use_setuptools() | |||
from setuptools import setup, find_packages | |||
from hyde.version import __version__ | |||
setup(name='hyde', | |||
from distutils.util import convert_path | |||
from fnmatch import fnmatchcase | |||
import os | |||
import sys | |||
PROJECT = 'hyde' | |||
try: | |||
long_description = open('README.markdown', 'rt').read() | |||
except IOError: | |||
long_description = '' | |||
################################################################################ | |||
# find_package_data is an Ian Bicking creation. | |||
# Provided as an attribute, so you can append to these instead | |||
# of replicating them: | |||
standard_exclude = ('*.py', '*.pyc', '*~', '.*', '*.bak', '*.swp*') | |||
standard_exclude_directories = ('.*', 'CVS', '_darcs', './build', | |||
'./dist', 'EGG-INFO', '*.egg-info') | |||
def find_package_data( | |||
where='.', package='', | |||
exclude=standard_exclude, | |||
exclude_directories=standard_exclude_directories, | |||
only_in_packages=True, | |||
show_ignored=False): | |||
""" | |||
Return a dictionary suitable for use in ``package_data`` | |||
in a distutils ``setup.py`` file. | |||
The dictionary looks like:: | |||
{'package': [files]} | |||
Where ``files`` is a list of all the files in that package that | |||
don't match anything in ``exclude``. | |||
If ``only_in_packages`` is true, then top-level directories that | |||
are not packages won't be included (but directories under packages | |||
will). | |||
Directories matching any pattern in ``exclude_directories`` will | |||
be ignored; by default directories with leading ``.``, ``CVS``, | |||
and ``_darcs`` will be ignored. | |||
If ``show_ignored`` is true, then all the files that aren't | |||
included in package data are shown on stderr (for debugging | |||
purposes). | |||
Note patterns use wildcards, or can be exact paths (including | |||
leading ``./``), and all searching is case-insensitive. | |||
This function is by Ian Bicking. | |||
""" | |||
out = {} | |||
stack = [(convert_path(where), '', package, only_in_packages)] | |||
while stack: | |||
where, prefix, package, only_in_packages = stack.pop(0) | |||
for name in os.listdir(where): | |||
fn = os.path.join(where, name) | |||
if os.path.isdir(fn): | |||
bad_name = False | |||
for pattern in exclude_directories: | |||
if (fnmatchcase(name, pattern) | |||
or fn.lower() == pattern.lower()): | |||
bad_name = True | |||
if show_ignored: | |||
print >> sys.stderr, ( | |||
"Directory %s ignored by pattern %s" | |||
% (fn, pattern)) | |||
break | |||
if bad_name: | |||
continue | |||
if os.path.isfile(os.path.join(fn, '__init__.py')): | |||
if not package: | |||
new_package = name | |||
else: | |||
new_package = package + '.' + name | |||
stack.append((fn, '', new_package, False)) | |||
else: | |||
stack.append((fn, prefix + name + '/', package, only_in_packages)) | |||
elif package or not only_in_packages: | |||
# is a file | |||
bad_name = False | |||
for pattern in exclude: | |||
if (fnmatchcase(name, pattern) | |||
or fn.lower() == pattern.lower()): | |||
bad_name = True | |||
if show_ignored: | |||
print >> sys.stderr, ( | |||
"File %s ignored by pattern %s" | |||
% (fn, pattern)) | |||
break | |||
if bad_name: | |||
continue | |||
out.setdefault(package, []).append(prefix+name) | |||
return out | |||
################################################################################ | |||
setup(name=PROJECT, | |||
version=__version__, | |||
description='hyde is a pythonic static website generator', | |||
description='hyde is a static website generator', | |||
long_description = long_description, | |||
author='Lakshmi Vyas', | |||
author_email='lakshmi.vyas@gmail.com', | |||
url='http://ringce.com/hyde', | |||
packages=find_packages(), | |||
dependency_links=[ | |||
"https://github.com/hyde/typogrify/tarball/hyde-setup#egg=typogrify-hyde" | |||
], | |||
install_requires=( | |||
'argparse', | |||
'commando', | |||
@@ -15,8 +125,21 @@ setup(name='hyde', | |||
'pyYAML', | |||
'markdown', | |||
'smartypants', | |||
'pygments' | |||
'pygments', | |||
'typogrify-hyde' | |||
), | |||
tests_require=( | |||
'nose', | |||
), | |||
test_suite='nose.collector', | |||
include_package_data = True, | |||
# Scan the input for package information | |||
# to grab any data files (text, images, etc.) | |||
# associated with sub-packages. | |||
package_data = find_package_data(PROJECT, | |||
package=PROJECT, | |||
only_in_packages=False, | |||
), | |||
scripts=['main.py'], | |||
entry_points={ | |||
'console_scripts': [ | |||
@@ -25,7 +148,7 @@ setup(name='hyde', | |||
}, | |||
license='MIT', | |||
classifiers=[ | |||
'Development Status :: 3 - Alpha', | |||
'Development Status :: 4 - Beta', | |||
'Environment :: Console', | |||
'Intended Audience :: End Users/Desktop', | |||
'Intended Audience :: Developers', | |||
@@ -34,11 +157,13 @@ setup(name='hyde', | |||
'Operating System :: MacOS :: MacOS X', | |||
'Operating System :: Unix', | |||
'Operating System :: POSIX', | |||
'Operating System :: Microsoft :: Windows', | |||
'Programming Language :: Python', | |||
'Programming Language :: Python :: 2.7', | |||
'Topic :: Software Development', | |||
'Topic :: Software Development :: Build Tools', | |||
'Topic :: Software Development :: Websites', | |||
'Topic :: Software Development :: Static Websites', | |||
'Topic :: Software Development :: Code Generators', | |||
'Topic :: Internet', | |||
'Topic :: Internet :: WWW/HTTP :: Site Management', | |||
], | |||
zip_safe=False, | |||
) |