@@ -14,3 +14,4 @@ build | |||||
.idea | .idea | ||||
PYSMELLTAGS | PYSMELLTAGS | ||||
.noseids | .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** | # 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 | [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 | [hydedocs]: http://hyde.github.com/overview | ||||
[Here](http://groups.google.com/group/hyde-dev/browse_thread/thread/2a143bd2081b3322) is | [Here](http://groups.google.com/group/hyde-dev/browse_thread/thread/2a143bd2081b3322) is | ||||
@@ -15,31 +16,22 @@ the initial announcement of the project. | |||||
## Installation | ## 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 | ## 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. | will create a new hyde site using the test layout. | ||||
## Generating the hyde site | ## Generating the hyde site | ||||
cd ~/test_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. | 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 | ## 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 | and there is no extends statement, this plugin automatically adds | ||||
an extends statement to the top of the file. | an extends statement to the top of the file. | ||||
""" | """ | ||||
if not resource.uses_template: | |||||
return text | |||||
layout = None | layout = None | ||||
block = None | block = None | ||||
try: | try: | ||||
@@ -7,7 +7,7 @@ import re | |||||
from hyde.model import Expando | from hyde.model import Expando | ||||
from hyde.plugin import Plugin | from hyde.plugin import Plugin | ||||
from hyde.site import Node, Resource | 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 collections import namedtuple | ||||
from functools import partial | from functools import partial | ||||
@@ -42,6 +42,10 @@ class Group(Expando): | |||||
'walk_resources_grouped_by_%s' % self.name, | 'walk_resources_grouped_by_%s' % self.name, | ||||
Group.walk_resources, | Group.walk_resources, | ||||
group=self) | group=self) | ||||
add_property(Resource, | |||||
'%s_group' % self.name, | |||||
Group.get_resource_group, | |||||
group=self) | |||||
add_method(Resource, | add_method(Resource, | ||||
'walk_%s_groups' % self.name, | 'walk_%s_groups' % self.name, | ||||
Group.walk_resource_groups, | Group.walk_resource_groups, | ||||
@@ -57,6 +61,23 @@ class Group(Expando): | |||||
else: | else: | ||||
return super(Group, self).set_expando(key, value) | 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 | @staticmethod | ||||
def walk_resource_groups(resource, group): | def walk_resource_groups(resource, group): | ||||
""" | """ | ||||
@@ -4,6 +4,7 @@ Jinja template utilties | |||||
""" | """ | ||||
from hyde.fs import File, Folder | from hyde.fs import File, Folder | ||||
from hyde.model import Expando | |||||
from hyde.template import HtmlWrap, Template | from hyde.template import HtmlWrap, Template | ||||
from hyde.site import Resource | from hyde.site import Resource | ||||
from hyde.util import getLoggerWithNullHandler, getLoggerWithConsoleHandler | from hyde.util import getLoggerWithNullHandler, getLoggerWithConsoleHandler | ||||
@@ -58,11 +59,12 @@ def markdown(env, value): | |||||
d = {} | d = {} | ||||
if hasattr(env.config, 'markdown'): | if hasattr(env.config, 'markdown'): | ||||
d['extensions'] = getattr(env.config.markdown, 'extensions', []) | 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) | md = markdown.Markdown(**d) | ||||
return md.convert(output) | return md.convert(output) | ||||
@environmentfilter | @environmentfilter | ||||
def syntax(env, value, lexer=None, filename=None): | def syntax(env, value, lexer=None, filename=None): | ||||
""" | """ | ||||
@@ -81,7 +83,9 @@ def syntax(env, value, lexer=None, filename=None): | |||||
lexers.guess_lexer(value)) | lexers.guess_lexer(value)) | ||||
settings = {} | settings = {} | ||||
if hasattr(env.config, 'syntax'): | if hasattr(env.config, 'syntax'): | ||||
settings = getattr(env.config.syntax, 'options', {}) | |||||
settings = getattr(env.config.syntax, | |||||
'options', | |||||
Expando({})).to_dict() | |||||
formatter = formatters.HtmlFormatter(**settings) | formatter = formatters.HtmlFormatter(**settings) | ||||
code = pygments.highlight(value, pyg, formatter) | code = pygments.highlight(value, pyg, formatter) | ||||
@@ -117,6 +121,49 @@ class Markdown(Extension): | |||||
output = caller().strip() | output = caller().strip() | ||||
return markdown(self.environment, output) | 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): | def parse_kwargs(parser): | ||||
name = parser.stream.expect('name').value | name = parser.stream.expect('name').value | ||||
parser.stream.expect('assign') | parser.stream.expect('assign') | ||||
@@ -265,7 +312,13 @@ class Refer(Extension): | |||||
includeNode.ignore_missing = False | includeNode.ignore_missing = False | ||||
includeNode.template = template | includeNode.template = template | ||||
temp = parser.free_identifier(lineno) | |||||
return [ | return [ | ||||
nodes.Assign( | |||||
nodes.Name(temp.name, 'store'), | |||||
nodes.Name(MARKINGS, 'load') | |||||
).set_lineno(lineno), | |||||
nodes.Assign( | nodes.Assign( | ||||
nodes.Name(MARKINGS, 'store'), | nodes.Name(MARKINGS, 'store'), | ||||
nodes.Const({})).set_lineno(lineno), | nodes.Const({})).set_lineno(lineno), | ||||
@@ -295,6 +348,10 @@ class Refer(Extension): | |||||
nodes.Getitem(nodes.Name(namespace, 'load'), | nodes.Getitem(nodes.Name(namespace, 'load'), | ||||
nodes.Const('parent_resource'), 'load') | nodes.Const('parent_resource'), 'load') | ||||
).set_lineno(lineno), | ).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): | def _push_resource(self, namespace, site, resource, template, caller): | ||||
@@ -378,6 +435,7 @@ class Jinja2Template(Template): | |||||
Syntax, | Syntax, | ||||
Reference, | Reference, | ||||
Refer, | Refer, | ||||
YamlVar, | |||||
'jinja2.ext.do', | 'jinja2.ext.do', | ||||
'jinja2.ext.loopcontrols', | 'jinja2.ext.loopcontrols', | ||||
'jinja2.ext.with_']) | 'jinja2.ext.with_']) | ||||
@@ -13,7 +13,7 @@ | |||||
<!-- Always force latest IE rendering engine (even in intranet) & Chrome Frame | <!-- Always force latest IE rendering engine (even in intranet) & Chrome Frame | ||||
Remove this if you use the .htaccess --> | 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 --> | <!-- 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}}"> | <meta name="author" content="{{resource.meta.author}}"> | ||||
<!-- Mobile viewport optimized: j.mp/bplateviewport --> | <!-- 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 %} | {% block favicons %} | ||||
<!-- Place favicon.ico & apple-touch-icon.png in the root of your domain and delete these references --> | <!-- Place favicon.ico & apple-touch-icon.png in the root of your domain and delete these references --> | ||||
@@ -99,4 +99,4 @@ | |||||
{% endblock js %} | {% endblock js %} | ||||
</body> | </body> | ||||
</html> | </html> | ||||
{% endblock all %} | |||||
{% endblock all %} |
@@ -57,6 +57,16 @@ class Expando(object): | |||||
else: | else: | ||||
return primitive | 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): | class Context(object): | ||||
""" | """ | ||||
@@ -58,9 +58,13 @@ class HydeRequestHandler(SimpleHTTPRequestHandler): | |||||
logger.debug("Trying to load file based on request:[%s]" % result.path) | logger.debug("Trying to load file based on request:[%s]" % result.path) | ||||
path = result.path.lstrip('/') | path = result.path.lstrip('/') | ||||
if path.strip() == "" or File(path).kind.strip() == "": | 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: | 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()] | plugin_resources = [resource.name for resource in self.s.content.walk_resources_grouped_by_plugins()] | ||||
assert plugin_resources == self.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): | def test_resource_belongs_to(self): | ||||
groups = dict([(g.name, g) for g in self.s.grouper['section'].groups]) | 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() | gen.load_template_if_needed() | ||||
template = gen.template | template = gen.template | ||||
html = template.render(includer_text, {}).strip() | html = template.render(includer_text, {}).strip() | ||||
print html | |||||
assert html | assert html | ||||
q = PyQuery(html) | q = PyQuery(html) | ||||
assert "is_processable" not in html | assert "is_processable" not in html | ||||
@@ -200,7 +201,7 @@ class TestJinjaTemplate(object): | |||||
deps = list(t.get_dependencies('inc.md')) | deps = list(t.get_dependencies('inc.md')) | ||||
assert len(deps) == 1 | assert len(deps) == 1 | ||||
assert not deps[0] | assert not deps[0] | ||||
@@ -314,6 +315,52 @@ Hyde & Jinja. | |||||
assert "mark" not in html | assert "mark" not in html | ||||
assert "reference" 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): | def test_refer_with_var(self): | ||||
text = """ | text = """ | ||||
@@ -340,4 +387,45 @@ Hyde & Jinja. | |||||
""" | """ | ||||
html = assert_markdown_typogrify_processed_well(text, text2) | html = assert_markdown_typogrify_processed_well(text, text2) | ||||
assert "mark" not in html | 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.a == 789 | ||||
assert x.f == "opq" | 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') | TEST_SITE = File(__file__).parent.child_folder('_test') | ||||
import yaml | import yaml | ||||
@@ -92,11 +92,16 @@ def make_method(method_name, method_): | |||||
method__.__name__ = method_name | method__.__name__ = method_name | ||||
return method__ | 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): | def add_method(obj, method_name, method_, *args, **kwargs): | ||||
from functools import partial | from functools import partial | ||||
m = make_method(method_name, partial(method_, *args, **kwargs)) | m = make_method(method_name, partial(method_, *args, **kwargs)) | ||||
setattr(obj, method_name, m) | setattr(obj, method_name, m) | ||||
def pairwalk(iterable): | def pairwalk(iterable): | ||||
a, b = tee(iterable) | a, b = tee(iterable) | ||||
next(b, None) | next(b, None) |
@@ -3,4 +3,4 @@ | |||||
Handles hyde version | Handles hyde version | ||||
TODO: Use fabric like versioning scheme | 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 setuptools import setup, find_packages | ||||
from hyde.version import __version__ | 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__, | 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='Lakshmi Vyas', | ||||
author_email='lakshmi.vyas@gmail.com', | author_email='lakshmi.vyas@gmail.com', | ||||
url='http://ringce.com/hyde', | url='http://ringce.com/hyde', | ||||
packages=find_packages(), | packages=find_packages(), | ||||
dependency_links=[ | |||||
"https://github.com/hyde/typogrify/tarball/hyde-setup#egg=typogrify-hyde" | |||||
], | |||||
install_requires=( | install_requires=( | ||||
'argparse', | 'argparse', | ||||
'commando', | 'commando', | ||||
@@ -15,8 +125,21 @@ setup(name='hyde', | |||||
'pyYAML', | 'pyYAML', | ||||
'markdown', | 'markdown', | ||||
'smartypants', | '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'], | scripts=['main.py'], | ||||
entry_points={ | entry_points={ | ||||
'console_scripts': [ | 'console_scripts': [ | ||||
@@ -25,7 +148,7 @@ setup(name='hyde', | |||||
}, | }, | ||||
license='MIT', | license='MIT', | ||||
classifiers=[ | classifiers=[ | ||||
'Development Status :: 3 - Alpha', | |||||
'Development Status :: 4 - Beta', | |||||
'Environment :: Console', | 'Environment :: Console', | ||||
'Intended Audience :: End Users/Desktop', | 'Intended Audience :: End Users/Desktop', | ||||
'Intended Audience :: Developers', | 'Intended Audience :: Developers', | ||||
@@ -34,11 +157,13 @@ setup(name='hyde', | |||||
'Operating System :: MacOS :: MacOS X', | 'Operating System :: MacOS :: MacOS X', | ||||
'Operating System :: Unix', | 'Operating System :: Unix', | ||||
'Operating System :: POSIX', | 'Operating System :: POSIX', | ||||
'Operating System :: Microsoft :: Windows', | |||||
'Programming Language :: Python', | 'Programming Language :: Python', | ||||
'Programming Language :: Python :: 2.7', | |||||
'Topic :: Software Development', | 'Topic :: Software Development', | ||||
'Topic :: Software Development :: Build Tools', | '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, | |||||
) | ) |