Browse Source

Reuse `fswrap` and `commando.util` modules.

*   Remove `hyde.fs` use `fswrap` package instead.
*   Remove logging functions from `hyde.util`. Use `commando.util`
    instead.
*   Remove `hyde.loader`. Use `commando.util.load_python_object`
    instead.
main
Lakshmi Vyasarajan 11 years ago
parent
commit
bcc1346854
61 changed files with 150 additions and 1479 deletions
  1. +3
    -0
      CHANGELOG.rst
  2. +15
    -11
      hyde/engine.py
  3. +2
    -1
      hyde/ext/plugins/folders.py
  4. +0
    -1
      hyde/ext/plugins/git.py
  5. +0
    -4
      hyde/ext/plugins/grouper.py
  6. +2
    -1
      hyde/ext/plugins/jpegoptim.py
  7. +1
    -1
      hyde/ext/plugins/less.py
  8. +2
    -1
      hyde/ext/plugins/optipng.py
  9. +2
    -1
      hyde/ext/plugins/paginator.py
  10. +1
    -3
      hyde/ext/plugins/sorter.py
  11. +6
    -6
      hyde/ext/plugins/sphinx.py
  12. +1
    -1
      hyde/ext/plugins/stylus.py
  13. +4
    -8
      hyde/ext/plugins/tagger.py
  14. +2
    -1
      hyde/ext/plugins/uglify.py
  15. +2
    -4
      hyde/ext/plugins/urls.py
  16. +1
    -4
      hyde/ext/publishers/dvcs.py
  17. +3
    -2
      hyde/ext/publishers/pyfs.py
  18. +2
    -3
      hyde/ext/publishers/pypi.py
  19. +8
    -3
      hyde/ext/templates/jinja.py
  20. +0
    -618
      hyde/fs.py
  21. +3
    -3
      hyde/generator.py
  22. +1
    -1
      hyde/layout.py
  23. +0
    -49
      hyde/loader.py
  24. +3
    -3
      hyde/model.py
  25. +6
    -8
      hyde/plugin.py
  26. +1
    -2
      hyde/publisher.py
  27. +4
    -44
      hyde/server.py
  28. +4
    -4
      hyde/site.py
  29. +3
    -1
      hyde/template.py
  30. +2
    -2
      hyde/tests/ext/test_auto_extend.py
  31. +1
    -1
      hyde/tests/ext/test_blockdown.py
  32. +2
    -2
      hyde/tests/ext/test_combine.py
  33. +1
    -4
      hyde/tests/ext/test_depends.py
  34. +4
    -3
      hyde/tests/ext/test_flattener.py
  35. +1
    -1
      hyde/tests/ext/test_grouper.py
  36. +3
    -4
      hyde/tests/ext/test_images.py
  37. +2
    -1
      hyde/tests/ext/test_less.py
  38. +1
    -1
      hyde/tests/ext/test_markings.py
  39. +1
    -1
      hyde/tests/ext/test_meta.py
  40. +2
    -1
      hyde/tests/ext/test_optipng.py
  41. +2
    -2
      hyde/tests/ext/test_paginator.py
  42. +2
    -2
      hyde/tests/ext/test_sorter.py
  43. +2
    -2
      hyde/tests/ext/test_stylus.py
  44. +1
    -1
      hyde/tests/ext/test_syntext.py
  45. +2
    -3
      hyde/tests/ext/test_tagger.py
  46. +1
    -2
      hyde/tests/ext/test_textlinks.py
  47. +2
    -1
      hyde/tests/ext/test_uglify.py
  48. +2
    -3
      hyde/tests/ext/test_urlcleaner.py
  49. +0
    -457
      hyde/tests/test_fs.py
  50. +2
    -2
      hyde/tests/test_generate.py
  51. +2
    -1
      hyde/tests/test_initialize.py
  52. +7
    -6
      hyde/tests/test_jinja2template.py
  53. +4
    -3
      hyde/tests/test_layout.py
  54. +0
    -81
      hyde/tests/test_loader.py
  55. +2
    -1
      hyde/tests/test_model.py
  56. +1
    -3
      hyde/tests/test_plugin.py
  57. +5
    -5
      hyde/tests/test_simple_copy.py
  58. +2
    -3
      hyde/tests/test_site.py
  59. +7
    -91
      hyde/util.py
  60. +1
    -0
      requirements.txt
  61. +1
    -0
      setup.py

+ 3
- 0
CHANGELOG.rst View File

@@ -2,6 +2,9 @@ Version 0.8.5a16
============================================================

* Upgrade dependencies and setup for 0.8.5
* Remove `hyde.fs` use `fswrap` package instead.
* Remove logging functions from `hyde.util`. Use `commando.util` instead.
* Remove `hyde.loader`. Use `commando.util.load_python_object` instead.
* Bug fix: Use the released version of typogrify. (Issue #193)

Version 0.8.5a15


+ 15
- 11
hyde/engine.py View File

@@ -2,6 +2,12 @@
"""
Implements the hyde entry point commands
"""
from hyde.exceptions import HydeException
from hyde.layout import Layout, HYDE_DATA
from hyde.model import Config
from hyde.site import Site
from hyde.version import __version__

from commando import (
Application,
command,
@@ -10,19 +16,21 @@ from commando import (
true,
version
)
from hyde.exceptions import HydeException
from hyde.fs import FS, Folder
from hyde.layout import Layout, HYDE_DATA
from hyde.model import Config
from hyde.site import Site
from hyde.version import __version__
from hyde.util import getLoggerWithConsoleHandler
from commando.util import getLoggerWithConsoleHandler
from fswrap import FS, Folder

HYDE_LAYOUTS = "HYDE_LAYOUTS"


class Engine(Application):

def __init__(self, raise_exceptions=True):
logger = getLoggerWithConsoleHandler('hyde')
super(Engine, self).__init__(
raise_exceptions=raise_exceptions,
logger=logger
)

@command(description='hyde - a python static website generator',
epilog='Use %(prog)s {command} -h to get help on individual commands')
@true('-v', '--verbose', help="Show detailed information in console")
@@ -34,10 +42,6 @@ class Engine(Application):
to provide common parameters for the subcommands and some generic stuff
like version and metadata
"""
if args.verbose:
import logging
self.logger.setLevel(logging.DEBUG)

sitepath = Folder(args.sitepath).fully_expanded_path
return Folder(sitepath)



+ 2
- 1
hyde/ext/plugins/folders.py View File

@@ -4,7 +4,8 @@ Plugins related to folders and paths
"""

from hyde.plugin import Plugin
from hyde.fs import Folder

from fswrap import Folder

class FlattenerPlugin(Plugin):
"""


+ 0
- 1
hyde/ext/plugins/git.py View File

@@ -6,7 +6,6 @@ Contains classes and utilities to extract information from git repository
from hyde.plugin import Plugin

import subprocess
import traceback
from dateutil.parser import parse

class GitDatesPlugin(Plugin):


+ 0
- 4
hyde/ext/plugins/grouper.py View File

@@ -3,16 +3,12 @@
Contains classes and utilities related to grouping
resources and nodes in hyde.
"""
import re
from hyde.model import Expando
from hyde.plugin import Plugin
from hyde.site import Node, Resource
from hyde.util import add_method, add_property, pairwalk

from collections import namedtuple
from functools import partial
from itertools import ifilter, izip, tee, product
from operator import attrgetter


Grouper = namedtuple('Grouper', 'group resources')


+ 2
- 1
hyde/ext/plugins/jpegoptim.py View File

@@ -4,7 +4,8 @@ jpegoptim plugin
"""

from hyde.plugin import CLTransformer
from hyde.fs import File

from fswrap import File

class JPEGOptimPlugin(CLTransformer):
"""


+ 1
- 1
hyde/ext/plugins/less.py View File

@@ -4,11 +4,11 @@ Less css plugin
"""

from hyde.plugin import CLTransformer
from hyde.fs import File

import re
import subprocess

from fswrap import File

class LessCSSPlugin(CLTransformer):
"""


+ 2
- 1
hyde/ext/plugins/optipng.py View File

@@ -4,7 +4,8 @@ OPTIPNG plugin
"""

from hyde.plugin import CLTransformer
from hyde.fs import File

from fswrap import File

class OptiPNGPlugin(CLTransformer):
"""


+ 2
- 1
hyde/ext/plugins/paginator.py View File

@@ -5,11 +5,12 @@ each page to a copy of the original resource.
"""
import os

from hyde.fs import File
from hyde.plugin import Plugin
from hyde.site import Resource
from hyde.util import pairwalk

from fswrap import File

class Page:
def __init__(self, posts, number):
self.posts = posts


+ 1
- 3
hyde/ext/plugins/sorter.py View File

@@ -3,8 +3,6 @@
Contains classes and utilities related to sorting
resources and nodes in hyde.
"""
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
@@ -40,7 +38,7 @@ def attributes_checker(item, attributes=None):
Checks if the given list of attributes exist.
"""
try:
x = attrgetter(*attributes)(item)
attrgetter(*attributes)(item)
return True
except AttributeError:
return False


+ 6
- 6
hyde/ext/plugins/sphinx.py View File

@@ -41,16 +41,16 @@ hyde templating workflow. You would end up with::
from __future__ import absolute_import

import os
import sys
import json
import tempfile
import tempfile

from hyde.plugin import Plugin
from hyde.fs import File, Folder
from hyde.model import Expando
from hyde.ext.plugins.meta import MetaPlugin as _MetaPlugin

from hyde.util import getLoggerWithNullHandler
from commado.util import getLoggerWithNullHandler
from fswrap import File, Folder

logger = getLoggerWithNullHandler('hyde.ext.plugins.sphinx')

try:
@@ -101,7 +101,7 @@ class SphinxPlugin(Plugin):
def sphinx_config(self):
"""Configuration options for sphinx.

This is a lazily-generated property giving the options from the
This is a lazily-generated property giving the options from the
sphinx configuration file. It's generated by actualy executing
the config file, so don't do anything silly in there.
"""
@@ -276,7 +276,7 @@ class HydeJSONHTMLBuilder(JSONHTMLBuilder):
This is a Sphinx builder that serilises the generated HTML fragments into
a JSON docuent, so they can be later retrieved and dealt with at will.

The only customistion we do over the standard JSONHTMLBuilder is to
The only customistion we do over the standard JSONHTMLBuilder is to
reference documents with a .html suffix, so that internal link will
work correctly once things have been processed by Hyde.
"""


+ 1
- 1
hyde/ext/plugins/stylus.py View File

@@ -4,11 +4,11 @@ Less css plugin
"""

from hyde.plugin import CLTransformer
from hyde.fs import File

import re
import subprocess

from fswrap import File

class StylusPlugin(CLTransformer):
"""


+ 4
- 8
hyde/ext/plugins/tagger.py View File

@@ -3,19 +3,15 @@
Contains classes and utilities related to tagging
resources in hyde.
"""
import re
from hyde.fs import File, Folder

from hyde.model import Expando
from hyde.plugin import Plugin
from hyde.site import Node, Resource
from hyde.util import add_method, add_property, pairwalk
from hyde.site import Node
from hyde.util import add_method

from collections import namedtuple
from datetime import datetime
from functools import partial
from itertools import ifilter, izip, tee, product
from operator import attrgetter

from fswrap import File, Folder

class Tag(Expando):
"""


+ 2
- 1
hyde/ext/plugins/uglify.py View File

@@ -4,7 +4,8 @@ Uglify plugin
"""

from hyde.plugin import CLTransformer
from hyde.fs import File

from fswrap import File

class UglifyPlugin(CLTransformer):
"""


+ 2
- 4
hyde/ext/plugins/urls.py View File

@@ -2,14 +2,12 @@
"""
Contains classes and utilities related to hyde urls.
"""
import re

from hyde.fs import File, Folder
from hyde.model import Expando
from hyde.plugin import Plugin
from hyde.site import Site, Node, Resource
from hyde.site import Site

from functools import wraps
from fswrap import File

class UrlCleanerPlugin(Plugin):
"""


+ 1
- 4
hyde/ext/publishers/dvcs.py View File

@@ -3,12 +3,9 @@ Contains classes and utilities that help publishing a hyde website to
distributed version control systems.
"""

import sys
import abc

from hyde.fs import File, Folder
from hyde.publisher import Publisher

import abc
from subprocess import Popen, PIPE

class DVCS(Publisher):


+ 3
- 2
hyde/ext/publishers/pyfs.py View File

@@ -14,10 +14,11 @@ are valid URLs that can be used with this publisher:
import getpass
import hashlib

from hyde.fs import File, Folder
from hyde.publisher import Publisher

from hyde.util import getLoggerWithNullHandler
from commando.util import getLoggerWithNullHandler

logger = getLoggerWithNullHandler('hyde.ext.publishers.pyfs')




+ 2
- 3
hyde/ext/publishers/pypi.py View File

@@ -13,10 +13,9 @@ import urlparse
from base64 import standard_b64encode
import ConfigParser

from hyde.fs import File, Folder
from hyde.publisher import Publisher

from hyde.util import getLoggerWithNullHandler
from commando.util import getLoggerWithNullHandler
logger = getLoggerWithNullHandler('hyde.ext.publishers.pypi')


@@ -136,5 +135,5 @@ class PyPI(Publisher):
con.close()
finally:
tf.close()


+ 8
- 3
hyde/ext/templates/jinja.py View File

@@ -11,15 +11,20 @@ from urllib import quote, unquote

from hyde.model import Expando
from hyde.template import HtmlWrap, Template
from hyde.util import getLoggerWithNullHandler
from operator import attrgetter

from jinja2 import contextfunction, Environment
from jinja2 import FileSystemLoader, FileSystemBytecodeCache
from jinja2 import (
contextfunction,
Environment,
FileSystemLoader,
FileSystemBytecodeCache
)
from jinja2 import contextfilter, environmentfilter, Markup, Undefined, nodes
from jinja2.ext import Extension
from jinja2.exceptions import TemplateError

from commando.util import getLoggerWithNullHandler

logger = getLoggerWithNullHandler('hyde.engine.Jinja2')

class SilentUndefined(Undefined):


+ 0
- 618
hyde/fs.py View File

@@ -1,618 +0,0 @@
# -*- coding: utf-8 -*-
"""
Unified object oriented interface for interacting with file system objects.
File system operations in python are distributed across modules: os, os.path,
fnamtch, shutil and distutils. This module attempts to make the right choices
for common operations to provide a single interface.
"""

import codecs
from datetime import datetime
import mimetypes
import os
import shutil
from distutils import dir_util
import functools
import fnmatch

from hyde.util import getLoggerWithNullHandler

logger = getLoggerWithNullHandler('fs')

# pylint: disable-msg=E0611


__all__ = ['File', 'Folder']


class FS(object):
"""
The base file system object
"""

def __init__(self, path):
super(FS, self).__init__()
if path == os.sep:
self.path = path
else:
self.path = os.path.expandvars(os.path.expanduser(
unicode(path).strip().rstrip(os.sep)))

def __str__(self):
return self.path

def __repr__(self):
return self.path

def __eq__(self, other):
return unicode(self) == unicode(other)

def __ne__(self, other):
return unicode(self) != unicode(other)

@property
def fully_expanded_path(self):
"""
Returns the absolutely absolute path. Calls os.(
normpath, normcase, expandvars and expanduser).
"""
return os.path.abspath(
os.path.normpath(
os.path.normcase(
os.path.expandvars(
os.path.expanduser(self.path)))))

@property
def exists(self):
"""
Does the file system object exist?
"""
return os.path.exists(self.path)

@property
def name(self):
"""
Returns the name of the FS object with its extension
"""
return os.path.basename(self.path)

@property
def parent(self):
"""
The parent folder. Returns a `Folder` object.
"""
return Folder(os.path.dirname(self.path))

@property
def depth(self):
"""
Returns the number of ancestors of this directory.
"""
return len(self.path.rstrip(os.sep).split(os.sep))

def ancestors(self, stop=None):
"""
Generates the parents until stop or the absolute
root directory is reached.
"""
folder = self
while folder.parent != stop:
if folder.parent == folder:
return
yield folder.parent
folder = folder.parent

def is_descendant_of(self, ancestor):
"""
Checks if this folder is inside the given ancestor.
"""
stop = Folder(ancestor)
for folder in self.ancestors():
if folder == stop:
return True
if stop.depth > folder.depth:
return False
return False

def get_relative_path(self, root):
"""
Gets the fragment of the current path starting at root.
"""
if self.path == root:
return ''
ancestors = self.ancestors(stop=root)
return functools.reduce(lambda f, p: Folder(p.name).child(f),
ancestors,
self.name)

def get_mirror(self, target_root, source_root=None):
"""
Returns a File or Folder object that reperesents if the entire
fragment of this directory starting with `source_root` were copied
to `target_root`.

>>> Folder('/usr/local/hyde/stuff').get_mirror('/usr/tmp',
source_root='/usr/local/hyde')
Folder('/usr/tmp/stuff')
"""
fragment = self.get_relative_path(
source_root if source_root else self.parent)
return Folder(target_root).child(fragment)

@staticmethod
def file_or_folder(path):
"""
Returns a File or Folder object that would represent the given path.
"""
target = unicode(path)
return Folder(target) if os.path.isdir(target) else File(target)

def __get_destination__(self, destination):
"""
Returns a File or Folder object that would represent this entity
if it were copied or moved to `destination`.
"""
if isinstance(destination, File) or os.path.isfile(unicode(destination)):
return destination
else:
return FS.file_or_folder(Folder(destination).child(self.name))


class File(FS):
"""
The File object.
"""

def __init__(self, path):
super(File, self).__init__(path)

@property
def name_without_extension(self):
"""
Returns the name of the FS object without its extension
"""
return os.path.splitext(self.name)[0]

@property
def extension(self):
"""
File extension prefixed with a dot.
"""
return os.path.splitext(self.path)[1]

@property
def kind(self):
"""
File extension without dot prefix.
"""
return self.extension.lstrip(".")

@property
def size(self):
"""
Size of this file.
"""

if not self.exists:
return -1
return os.path.getsize(self.path)

@property
def mimetype(self):
"""
Gets the mimetype of this file.
"""
(mime, _) = mimetypes.guess_type(self.path)
return mime

@property
def is_binary(self):
"""Return true if this is a binary file."""
with open(self.path, 'rb') as fin:
CHUNKSIZE = 1024
while 1:
chunk = fin.read(CHUNKSIZE)
if '\0' in chunk:
return True
if len(chunk) < CHUNKSIZE:
break
return False

@property
def is_text(self):
"""Return true if this is a text file."""
return (not self.is_binary)

@property
def is_image(self):
"""Return true if this is an image file."""
return self.mimetype.split("/")[0] == "image"


@property
def last_modified(self):
"""
Returns a datetime object representing the last modified time.
Calls os.path.getmtime.

"""
return datetime.fromtimestamp(os.path.getmtime(self.path))

def has_changed_since(self, basetime):
"""
Returns True if the file has been changed since the given time.

"""
return self.last_modified > basetime

def older_than(self, another_file):
"""
Checks if this file is older than the given file. Uses last_modified to
determine age.

"""
return self.last_modified < File(unicode(another_file)).last_modified

@staticmethod
def make_temp(text):
"""
Creates a temprorary file and writes the `text` into it
"""
import tempfile
(handle, path) = tempfile.mkstemp(text=True)
os.close(handle)
afile = File(path)
afile.write(text)
return afile

def read_all(self, encoding='utf-8'):
"""
Reads from the file and returns the content as a string.
"""
logger.info("Reading everything from %s" % self)
with codecs.open(self.path, 'r', encoding) as fin:
read_text = fin.read()
return read_text

def write(self, text, encoding="utf-8"):
"""
Writes the given text to the file using the given encoding.
"""
logger.info("Writing to %s" % self)
with codecs.open(self.path, 'w', encoding) as fout:
fout.write(text)

def copy_to(self, destination):
"""
Copies the file to the given destination. Returns a File
object that represents the target file. `destination` must
be a File or Folder object.
"""
target = self.__get_destination__(destination)
logger.info("Copying %s to %s" % (self, target))
shutil.copy(self.path, unicode(destination))
return target

def delete(self):
"""
Delete the file if it exists.
"""
if self.exists:
os.remove(self.path)


class FSVisitor(object):
"""
Implements syntactic sugar for walking and listing folders
"""

def __init__(self, folder, pattern=None):
super(FSVisitor, self).__init__()
self.folder = folder
self.pattern = pattern

def folder_visitor(self, function):
"""
Decorator for `visit_folder` protocol
"""
self.visit_folder = function
return function

def file_visitor(self, function):
"""
Decorator for `visit_file` protocol
"""
self.visit_file = function
return function

def finalizer(self, function):
"""
Decorator for `visit_complete` protocol
"""
self.visit_complete = function
return function

def __enter__(self):
return self

def __exit__(self, exc_type, exc_val, exc_tb):
pass


class FolderWalker(FSVisitor):
"""
Walks the entire hirearchy of this directory starting with itself.

If a pattern is provided, only the files that match the pattern are
processed.
"""

def walk(self, walk_folders=False, walk_files=False):
"""
A simple generator that yields a File or Folder object based on
the arguments.
"""

if not walk_files and not walk_folders:
return

for root, _, a_files in os.walk(self.folder.path, followlinks=True):
folder = Folder(root)
if walk_folders:
yield folder
if walk_files:
for a_file in a_files:
if (not self.pattern or
fnmatch.fnmatch(a_file, self.pattern)):
yield File(folder.child(a_file))

def walk_all(self):
"""
Yield both Files and Folders as the tree is walked.
"""

return self.walk(walk_folders=True, walk_files=True)

def walk_files(self):
"""
Yield only Files.
"""
return self.walk(walk_folders=False, walk_files=True)

def walk_folders(self):
"""
Yield only Folders.
"""
return self.walk(walk_folders=True, walk_files=False)

def __exit__(self, exc_type, exc_val, exc_tb):
"""
Automatically walk the folder when the context manager is exited.

Calls self.visit_folder first and then calls self.visit_file for
any files found. After all files and folders have been exhausted
self.visit_complete is called.

If visitor.visit_folder returns False, the files in the folder are not
processed.
"""

def __visit_folder__(folder):
process_folder = True
if hasattr(self, 'visit_folder'):
process_folder = self.visit_folder(folder)
# If there is no return value assume true
#
if process_folder is None:
process_folder = True
return process_folder

def __visit_file__(a_file):
if hasattr(self, 'visit_file'):
self.visit_file(a_file)

def __visit_complete__():
if hasattr(self, 'visit_complete'):
self.visit_complete()

for root, dirs, a_files in os.walk(self.folder.path, followlinks=True):
folder = Folder(root)
if not __visit_folder__(folder):
dirs[:] = []
continue
for a_file in a_files:
if not self.pattern or fnmatch.fnmatch(a_file, self.pattern):
__visit_file__(File(folder.child(a_file)))
__visit_complete__()


class FolderLister(FSVisitor):
"""
Lists the contents of this directory.

If a pattern is provided, only the files that match the pattern are
processed.
"""

def list(self, list_folders=False, list_files=False):
"""
A simple generator that yields a File or Folder object based on
the arguments.
"""

a_files = os.listdir(self.folder.path)
for a_file in a_files:
path = self.folder.child(a_file)
if os.path.isdir(path):
if list_folders:
yield Folder(path)
elif list_files:
if not self.pattern or fnmatch.fnmatch(a_file, self.pattern):
yield File(path)

def list_all(self):
"""
Yield both Files and Folders as the folder is listed.
"""

return self.list(list_folders=True, list_files=True)

def list_files(self):
"""
Yield only Files.
"""
return self.list(list_folders=False, list_files=True)

def list_folders(self):
"""
Yield only Folders.
"""
return self.list(list_folders=True, list_files=False)

def __exit__(self, exc_type, exc_val, exc_tb):
"""
Automatically list the folder contents when the context manager
is exited.

Calls self.visit_folder first and then calls self.visit_file for
any files found. After all files and folders have been exhausted
self.visit_complete is called.
"""

a_files = os.listdir(self.folder.path)
for a_file in a_files:
path = self.folder.child(a_file)
if os.path.isdir(path) and hasattr(self, 'visit_folder'):
self.visit_folder(Folder(path))
elif hasattr(self, 'visit_file'):
if not self.pattern or fnmatch.fnmatch(a_file, self.pattern):
self.visit_file(File(path))
if hasattr(self, 'visit_complete'):
self.visit_complete()


class Folder(FS):
"""
Represents a directory.
"""

def __init__(self, path):
super(Folder, self).__init__(path)

def child_folder(self, fragment):
"""
Returns a folder object by combining the fragment to this folder's path
"""
return Folder(os.path.join(self.path, Folder(fragment).path))

def child(self, fragment):
"""
Returns a path of a child item represented by `fragment`.
"""
return os.path.join(self.path, FS(fragment).path)

def make(self):
"""
Creates this directory and any of the missing directories in the path.
Any errors that may occur are eaten.
"""
try:
if not self.exists:
logger.info("Creating %s" % self.path)
os.makedirs(self.path)
except os.error:
pass
return self

def delete(self):
"""
Deletes the directory if it exists.
"""
if self.exists:
logger.info("Deleting %s" % self.path)
shutil.rmtree(self.path)

def copy_to(self, destination):
"""
Copies this directory to the given destination. Returns a Folder object
that represents the moved directory.
"""
target = self.__get_destination__(destination)
logger.info("Copying %s to %s" % (self, target))
shutil.copytree(self.path, unicode(target))
return target

def move_to(self, destination):
"""
Moves this directory to the given destination. Returns a Folder object
that represents the moved directory.
"""
target = self.__get_destination__(destination)
logger.info("Move %s to %s" % (self, target))
shutil.move(self.path, unicode(target))
return target

def rename_to(self, destination_name):
"""
Moves this directory to the given destination. Returns a Folder object
that represents the moved directory.
"""
target = self.parent.child_folder(destination_name)
logger.info("Rename %s to %s" % (self, target))
shutil.move(self.path, unicode(target))
return target

def _create_target_tree(self, target):
"""
There is a bug in dir_util that makes `copy_tree` crash if a folder in
the tree has been deleted before and readded now. To workaround the
bug, we first walk the tree and create directories that are needed.
"""
source = self
with source.walker as walker:

@walker.folder_visitor
def visit_folder(folder):
"""
Create the mirror directory
"""
if folder != source:
Folder(folder.get_mirror(target, source)).make()

def copy_contents_to(self, destination):
"""
Copies the contents of this directory to the given destination.
Returns a Folder object that represents the moved directory.
"""
logger.info("Copying contents of %s to %s" % (self, destination))
target = Folder(destination)
target.make()
self._create_target_tree(target)
dir_util.copy_tree(self.path, unicode(target))
return target

def get_walker(self, pattern=None):
"""
Return a `FolderWalker` object with a set pattern.
"""
return FolderWalker(self, pattern)

@property
def walker(self):
"""
Return a `FolderWalker` object
"""
return FolderWalker(self)

def get_lister(self, pattern=None):
"""
Return a `FolderLister` object with a set pattern.
"""
return FolderLister(self, pattern)

@property
def lister(self):
"""
Return a `FolderLister` object
"""
return FolderLister(self)

+ 3
- 3
hyde/generator.py View File

@@ -4,17 +4,17 @@ The generator class and related utility functions.
"""

from hyde.exceptions import HydeException
from hyde.fs import File, Folder
from fswrap import File, Folder
from hyde.model import Context, Dependents
from hyde.plugin import Plugin
from hyde.template import Template
from hyde.site import Node, Resource
from hyde.site import Resource

from contextlib import contextmanager
from datetime import datetime

from shutil import copymode
from hyde.util import getLoggerWithNullHandler
from commando.util import getLoggerWithNullHandler
logger = getLoggerWithNullHandler('hyde.engine')




+ 1
- 1
hyde/layout.py View File

@@ -4,7 +4,7 @@ Classes, functions and utilties related to hyde layouts
"""
import os

from hyde.fs import File, Folder
from fswrap import File, Folder

HYDE_DATA = "HYDE_DATA"
LAYOUTS = "layouts"


+ 0
- 49
hyde/loader.py View File

@@ -1,49 +0,0 @@
# -*- coding: utf-8 -*-
"""
Generic loader of extensions (plugins & templates)
"""
import sys

from hyde.exceptions import HydeException

from hyde.util import getLoggerWithNullHandler
logger = getLoggerWithNullHandler('hyde.engine')

plugins = {}
templates = {}


def load_python_object(name):
"""
Loads a python module from string
"""
(module_name, _, object_name) = name.rpartition(".")
if module_name == '':
(module_name, object_name) = (object_name, module_name)
try:
logger.debug('Loading module [%s]' % module_name)
module = __import__(module_name)
except ImportError:
raise HydeException("The given module name [%s] is invalid." %
module_name)

if object_name == '':
return module

try:
module = sys.modules[module_name]
except KeyError:
raise HydeException("Error occured when loading module [%s]" %
module_name)

try:
logger.debug('Getting object [%s] from module [%s]' %
(object_name, module_name))
return getattr(module, object_name)
except AttributeError:
raise HydeException("Cannot load the specified plugin [%s]. "
"The given module [%s] does not contain the "
"desired object [%s]. Please fix the "
"configuration or ensure that the module is "
"installed properly" %
(name, module_name, object_name))

+ 3
- 3
hyde/model.py View File

@@ -2,14 +2,14 @@
"""
Contains data structures and utilities for hyde.
"""
from hyde.fs import File, Folder

import codecs
import yaml
from datetime import datetime
from UserDict import IterableUserDict

from hyde.util import getLoggerWithNullHandler
from commando.util import getLoggerWithNullHandler
from fswrap import File, Folder

logger = getLoggerWithNullHandler('hyde.engine')

class Expando(object):


+ 6
- 8
hyde/plugin.py View File

@@ -2,23 +2,21 @@
"""
Contains definition for a plugin protocol and other utiltities.
"""
import abc

from hyde import loader

from hyde.exceptions import HydeException
from hyde.fs import File
from hyde.util import getLoggerWithNullHandler, first_match, discover_executable
from hyde.util import first_match, discover_executable
from hyde.model import Expando

import abc
from functools import partial
import fnmatch

import os
import re
import subprocess
import traceback

from commando.util import getLoggerWithNullHandler, load_python_object
from fswrap import File

logger = getLoggerWithNullHandler('hyde.engine')

class PluginProxy(object):
@@ -263,7 +261,7 @@ class Plugin(object):
Loads plugins based on the configuration. Assigns the plugins to
'site.plugins'
"""
site.plugins = [loader.load_python_object(name)(site)
site.plugins = [load_python_object(name)(site)
for name in site.config.plugins]

@staticmethod


+ 1
- 2
hyde/publisher.py View File

@@ -1,8 +1,7 @@
import abc
from operator import attrgetter

from hyde.util import getLoggerWithNullHandler
from hyde.loader import load_python_object
from commando.util import getLoggerWithNullHandler, load_python_object

"""
Contains abstract classes and utilities that help publishing a website to a


+ 4
- 44
hyde/server.py View File

@@ -3,8 +3,6 @@
Contains classes and utilities for serving a site
generated from hyde.
"""
import os
import select
import threading
import urlparse
import urllib
@@ -12,12 +10,12 @@ import traceback
from datetime import datetime
from SimpleHTTPServer import SimpleHTTPRequestHandler
from BaseHTTPServer import HTTPServer
from hyde.fs import File, Folder
from hyde.site import Site

from hyde.generator import Generator
from hyde.exceptions import HydeException

from hyde.util import getLoggerWithNullHandler
from fswrap import File, Folder

from commando.util import getLoggerWithNullHandler
logger = getLoggerWithNullHandler('hyde.server')

class HydeRequestHandler(SimpleHTTPRequestHandler):
@@ -146,44 +144,6 @@ class HydeWebServer(HTTPServer):
ext = "." + extension if not extension == 'default' else ''
HydeRequestHandler.extensions_map[ext] = type


####### Code from python 2.7.1: Socket server
####### Duplicated to make sure shutdown works in Python v > 2.6
#######

def serve_forever(self, poll_interval=0.5):
"""Handle one request at a time until shutdown.

Polls for shutdown every poll_interval seconds. Ignores
self.timeout. If you need to do periodic tasks, do them in
another thread.
"""
self.__is_shut_down.clear()
try:
while not self.__shutdown_request:
# XXX: Consider using another file descriptor or
# connecting to the socket to wake this up instead of
# polling. Polling reduces our responsiveness to a
# shutdown request and wastes cpu at all other times.
r, w, e = select.select([self], [], [], poll_interval)
if self in r:
self._handle_request_noblock()
finally:
self.__shutdown_request = False
self.__is_shut_down.set()

def shutdown(self):
"""Stops the serve_forever loop.

Blocks until the loop has finished. This must be called while
serve_forever() is running in another thread, or it will
deadlock.
"""
self.__shutdown_request = True
self.__is_shut_down.wait()

############## Duplication End.

def regenerate(self):
"""
Regenerates the entire site.


+ 4
- 4
hyde/site.py View File

@@ -10,10 +10,10 @@ from functools import wraps
from urllib import quote

from hyde.exceptions import HydeException
from hyde.fs import FS, File, Folder
from hyde.model import Config
from hyde.util import getLoggerWithNullHandler

from commando.util import getLoggerWithNullHandler
from fswrap import FS, File, Folder

def path_normalized(f):
@wraps(f)
@@ -51,7 +51,7 @@ class Processable(object):
Gets the source path of this node.
"""
return self.source.path
def get_relative_deploy_path(self):
"""
Gets the path where the file will be created
@@ -109,7 +109,7 @@ class Resource(Processable):
Gets the path relative to the root folder (Content)
"""
return self.source_file.get_relative_path(self.node.root.source_folder)
@property
def slug(self):
#TODO: Add a more sophisticated slugify method


+ 3
- 1
hyde/template.py View File

@@ -4,10 +4,12 @@
Abstract classes and utilities for template engines
"""
from hyde.exceptions import HydeException
from hyde.util import getLoggerWithNullHandler

import abc

from commando.util import getLoggerWithNullHandler


class HtmlWrap(object):
"""
A wrapper class for raw html.


+ 2
- 2
hyde/tests/ext/test_auto_extend.py View File

@@ -4,13 +4,13 @@ Use nose
`$ pip install nose`
`$ nosetests`
"""
from hyde.fs import File, Folder
from hyde.generator import Generator
from hyde.site import Site

from pyquery import PyQuery

from fswrap import File
from nose.tools import nottest
from pyquery import PyQuery

TEST_SITE = File(__file__).parent.parent.child_folder('_test')



+ 1
- 1
hyde/tests/ext/test_blockdown.py View File

@@ -4,10 +4,10 @@ Use nose
`$ pip install nose`
`$ nosetests`
"""
from hyde.fs import File, Folder
from hyde.generator import Generator
from hyde.site import Site

from fswrap import File
from pyquery import PyQuery

TEST_SITE = File(__file__).parent.parent.child_folder('_test')


+ 2
- 2
hyde/tests/ext/test_combine.py View File

@@ -4,11 +4,11 @@ Use nose
`$ pip install nose`
`$ nosetests`
"""
from hyde.fs import File, Folder
from hyde.model import Expando
from hyde.generator import Generator
from hyde.site import Site

from fswrap import File, Folder

COMBINE_SOURCE = File(__file__).parent.child_folder('combine')
TEST_SITE = File(__file__).parent.parent.child_folder('_test')



+ 1
- 4
hyde/tests/ext/test_depends.py View File

@@ -4,13 +4,10 @@ Use nose
`$ pip install nose`
`$ nosetests`
"""
from hyde.fs import File, Folder
from hyde.generator import Generator
from hyde.site import Site

from pyquery import PyQuery

from nose.tools import nottest
from fswrap import File

TEST_SITE = File(__file__).parent.parent.child_folder('_test')



+ 4
- 3
hyde/tests/ext/test_flattener.py View File

@@ -4,11 +4,12 @@ Use nose
`$ pip install nose`
`$ nosetests`
"""
from hyde.fs import File, Folder
from hyde.generator import Generator
from hyde.site import Site
from hyde.model import Expando, Config
from hyde.model import Config

from fswrap import File

TEST_SITE = File(__file__).parent.parent.child_folder('_test')

@@ -60,7 +61,7 @@ class TestFlattner(object):
gen = Generator(s)
gen.generate_all()
blog_node = s.content.node_from_relative_path('blog')
assert blog_node
assert blog_node.url == '/'



+ 1
- 1
hyde/tests/ext/test_grouper.py View File

@@ -7,11 +7,11 @@ Use nose
from hyde.ext.plugins.meta import MetaPlugin
from hyde.ext.plugins.sorter import SorterPlugin
from hyde.ext.plugins.grouper import GrouperPlugin
from hyde.fs import File, Folder
from hyde.generator import Generator
from hyde.site import Site
from hyde.model import Config, Expando

from fswrap import File
from hyde.tests.util import assert_html_equals
import yaml



+ 3
- 4
hyde/tests/ext/test_images.py View File

@@ -6,11 +6,11 @@ Use nose

Requires PIL
"""
from hyde.fs import File, Folder
from hyde.generator import Generator
from hyde.site import Site

from pyquery import PyQuery
from fswrap import File

TEST_SITE = File(__file__).parent.parent.child_folder('_test')
IMAGE_SOURCE = File(__file__).parent.child_folder('optipng')
@@ -87,8 +87,7 @@ class TestImageSizer(object):

def test_size_image_multiline(self):
text = u"""
<img
src="/media/img/%s">
<img src="/media/img/%s">
""" % IMAGE_NAME
html = self._generic_test_image(text)
assert ' width="%d"' % IMAGE_SIZE[0] in html


+ 2
- 1
hyde/tests/ext/test_less.py View File

@@ -4,10 +4,11 @@ Use nose
`$ pip install nose`
`$ nosetests`
"""
from hyde.fs import File, Folder
from hyde.generator import Generator
from hyde.site import Site

from fswrap import File, Folder

LESS_SOURCE = File(__file__).parent.child_folder('less')
TEST_SITE = File(__file__).parent.parent.child_folder('_test')



+ 1
- 1
hyde/tests/ext/test_markings.py View File

@@ -4,10 +4,10 @@ Use nose
`$ pip install nose`
`$ nosetests`
"""
from hyde.fs import File, Folder
from hyde.generator import Generator
from hyde.site import Site

from fswrap import File
from pyquery import PyQuery

TEST_SITE = File(__file__).parent.parent.child_folder('_test')


+ 1
- 1
hyde/tests/ext/test_meta.py View File

@@ -4,10 +4,10 @@ Use nose
`$ pip install nose`
`$ nosetests`
"""
from hyde.fs import File, Folder
from hyde.generator import Generator
from hyde.site import Site

from fswrap import File, Folder
from pyquery import PyQuery
import yaml



+ 2
- 1
hyde/tests/ext/test_optipng.py View File

@@ -4,11 +4,12 @@ Use nose
`$ pip install nose`
`$ nosetests`
"""
from hyde.fs import File, Folder
from hyde.model import Expando
from hyde.generator import Generator
from hyde.site import Site

from fswrap import File, Folder

OPTIPNG_SOURCE = File(__file__).parent.child_folder('optipng')
TEST_SITE = File(__file__).parent.parent.child_folder('_test')



+ 2
- 2
hyde/tests/ext/test_paginator.py View File

@@ -6,11 +6,11 @@ Use nose
"""
from textwrap import dedent

from hyde.fs import File, Folder
from hyde.generator import Generator
from hyde.model import Expando
from hyde.site import Site

from fswrap import File

TEST_SITE = File(__file__).parent.parent.child_folder('_test')

class TestPaginator(object):


+ 2
- 2
hyde/tests/ext/test_sorter.py View File

@@ -6,13 +6,13 @@ Use nose
"""
from hyde.ext.plugins.meta import MetaPlugin
from hyde.ext.plugins.sorter import SorterPlugin
from hyde.fs import File, Folder
from hyde.generator import Generator
from hyde.site import Site
from hyde.model import Config, Expando

from fswrap import File, Folder
import yaml

from operator import attrgetter

TEST_SITE = File(__file__).parent.parent.child_folder('_test')



+ 2
- 2
hyde/tests/ext/test_stylus.py View File

@@ -4,15 +4,15 @@ Use nose
`$ pip install nose`
`$ nosetests`
"""
from hyde.fs import File, Folder
from hyde.model import Expando
from hyde.generator import Generator
from hyde.site import Site

from fswrap import File, Folder

STYLUS_SOURCE = File(__file__).parent.child_folder('stylus')
TEST_SITE = File(__file__).parent.parent.child_folder('_test')


class TestStylus(object):

def setUp(self):


+ 1
- 1
hyde/tests/ext/test_syntext.py View File

@@ -4,10 +4,10 @@ Use nose
`$ pip install nose`
`$ nosetests`
"""
from hyde.fs import File, Folder
from hyde.generator import Generator
from hyde.site import Site

from fswrap import File
from pyquery import PyQuery

TEST_SITE = File(__file__).parent.parent.child_folder('_test')


+ 2
- 3
hyde/tests/ext/test_tagger.py View File

@@ -4,11 +4,10 @@ Use nose
`$ pip install nose`
`$ nosetests`
"""
from hyde.fs import File
from hyde.generator import Generator
from hyde.site import Site

from fswrap import File

TEST_SITE = File(__file__).parent.parent.child_folder('_test')

@@ -152,7 +151,7 @@ class TestTagger(object):
assert tag
assert not hasattr(tag, "emotions")

def test_tagger_metadata(self):
def test_tagger_sorted(self):
conf = {
"tagger":{
"sorter": "time",


+ 1
- 2
hyde/tests/ext/test_textlinks.py View File

@@ -4,12 +4,11 @@ Use nose
`$ pip install nose`
`$ nosetests`
"""
from hyde.fs import File, Folder
from hyde.generator import Generator
from hyde.site import Site
from urllib import quote

from pyquery import PyQuery
from fswrap import File

TEST_SITE = File(__file__).parent.parent.child_folder('_test')



+ 2
- 1
hyde/tests/ext/test_uglify.py View File

@@ -4,11 +4,12 @@ Use nose
`$ pip install nose`
`$ nosetests`
"""
from hyde.fs import File, Folder
from hyde.model import Expando
from hyde.generator import Generator
from hyde.site import Site

from fswrap import File, Folder

UGLIFY_SOURCE = File(__file__).parent.child_folder('uglify')
TEST_SITE = File(__file__).parent.parent.child_folder('_test')



+ 2
- 3
hyde/tests/ext/test_urlcleaner.py View File

@@ -4,14 +4,13 @@ Use nose
`$ pip install nose`
`$ nosetests`
"""
from hyde.fs import File, Folder
from hyde.generator import Generator
from hyde.model import Config
from hyde.site import Site

from fswrap import File, Folder
import yaml

from hyde.model import Config

TEST_SITE = File(__file__).parent.parent.child_folder('_test')




+ 0
- 457
hyde/tests/test_fs.py View File

@@ -1,457 +0,0 @@
# -*- coding: utf-8 -*-
"""
Use nose
`$ pip install nose`
`$ nosetests`
"""

from hyde.fs import FS, File, Folder
import codecs
import os
import shutil

from nose.tools import raises, with_setup, nottest

def test_representation():
f = FS(__file__)
assert f.path == __file__
assert unicode(f) == __file__
assert repr(f) == __file__

def test_name():
f = FS(__file__)
assert f.name == os.path.basename(__file__)

def test_equals():
f = FS('/blog/2010/december')
g = FS('/blog/2010/december')
h = FS('/blog/2010/december/')
i = FS('/blog/2010/november')
assert f == f.path
assert f == g
assert f == h
assert f != i

def test_name_without_extension():
f = File(__file__)
assert f.name_without_extension == "test_fs"

def test_extension():
f = File(__file__)
assert f.extension == os.path.splitext(__file__)[1]
f = File("abc")
assert f.extension == ''

def test_kind():
f = File(__file__)
assert f.kind == os.path.splitext(__file__)[1].lstrip('.')
f = File("abc")
assert f.kind == ''

def test_can_create_temp_file():
text = "A for apple"
f = File.make_temp(text)
assert f.exists
assert text == f.read_all()
f.delete()
assert not f.exists

def test_time_functions():
f1 = File(__file__)
t1 = f1.last_modified
f2 = File.make_temp("I am new")
t2 = f2.last_modified
assert t1 < t2
assert f2.has_changed_since(t1)
assert f1.older_than(f2)


def test_path_expands_user():
f = File("~/abc/def")
assert f.path == os.path.expanduser("~/abc/def")

def test_fully_expanded_path():
f = File(__file__).parent
n = f.child_folder('../' + f.name)
e = Folder(n.fully_expanded_path)
assert n != e
assert f == e

def test_parent():
f = File(__file__)
p = f.parent
assert hasattr(p, 'child_folder')
assert unicode(p) == os.path.dirname(__file__)

def test_child():
p = File(__file__).parent
c = p.child('data.dat')
assert c == os.path.join(os.path.dirname(__file__), 'data.dat')

def test_child_folder():
p = File(__file__).parent
c = p.child_folder('data')
assert hasattr(c, 'child_folder')
assert unicode(c) == os.path.join(os.path.dirname(__file__), 'data')

def test_exists():
p = FS(__file__)
assert p.exists
p = FS(__file__ + "_some_junk")
assert not p.exists
f = FS(__file__).parent.parent
assert f.exists
f = FS(__file__).parent.child_folder('templates')
assert f.exists

def test_create_folder():
f = FS(__file__).parent
assert f.exists
f.make()
assert True # No Exceptions
c = f.child_folder('__test__')
assert not c.exists
c.make()
assert c.exists
shutil.rmtree(unicode(c))
assert not c.exists

def test_remove_folder():
f = FS(__file__).parent
c = f.child_folder('__test__')
assert not c.exists
c.make()
assert c.exists
c.delete()
assert not c.exists

def test_can_remove_file():
f = FS(__file__).parent
c = f.child_folder('__test__')
c.make()
assert c.exists
txt = "abc"
abc = File(c.child('abc.txt'))
abc.write(txt)
assert abc.exists
abc.delete()
assert not abc.exists
abc.delete()
assert True # No Exception
c.delete()

def test_file_or_folder():
f = FS.file_or_folder(__file__)
assert isinstance(f, File)
f = FS.file_or_folder(File(__file__).parent)
assert isinstance(f, Folder)

DATA_ROOT = File(__file__).parent.child_folder('data')
TEMPLATE_ROOT = File(__file__).parent.child_folder('templates')
JINJA2 = TEMPLATE_ROOT.child_folder('jinja2')
HELPERS = File(JINJA2.child('helpers.html'))
INDEX = File(JINJA2.child('index.html'))
LAYOUT = File(JINJA2.child('layout.html'))
LOGO = File(TEMPLATE_ROOT.child('../../../resources/hyde-logo.png'))
XML = File(TEMPLATE_ROOT.child('../sites/test_jinja/content/crossdomain.xml'))

def test_ancestors():
depth = 0
next = JINJA2
for folder in INDEX.ancestors():
assert folder == next
depth += 1
next = folder.parent
assert depth == len(JINJA2.path.split(os.sep))

def test_ancestors_stop():
depth = 0
next = JINJA2
for folder in INDEX.ancestors(stop=TEMPLATE_ROOT.parent):
assert folder == next
depth += 1
next = folder.parent
assert depth == 2

def test_is_descendant_of():
assert INDEX.is_descendant_of(JINJA2)
assert JINJA2.is_descendant_of(TEMPLATE_ROOT)
assert INDEX.is_descendant_of(TEMPLATE_ROOT)
assert not INDEX.is_descendant_of(DATA_ROOT)

def test_get_relative_path():
assert INDEX.get_relative_path(TEMPLATE_ROOT) == Folder(JINJA2.name).child(INDEX.name)
assert INDEX.get_relative_path(TEMPLATE_ROOT.parent) == Folder(
TEMPLATE_ROOT.name).child_folder(JINJA2.name).child(INDEX.name)
assert JINJA2.get_relative_path(JINJA2) == ""

def test_get_mirror():
mirror = JINJA2.get_mirror(DATA_ROOT, source_root=TEMPLATE_ROOT)
assert mirror == DATA_ROOT.child_folder(JINJA2.name)
mirror = JINJA2.get_mirror(DATA_ROOT, source_root=TEMPLATE_ROOT.parent)
assert mirror == DATA_ROOT.child_folder(TEMPLATE_ROOT.name).child_folder(JINJA2.name)

def test_mimetype():
assert HELPERS.mimetype == 'text/html'
assert LOGO.mimetype == 'image/png'

def test_is_text():
assert HELPERS.is_text
assert not LOGO.is_text
assert XML.is_text

def test_is_image():
assert not HELPERS.is_image
assert LOGO.is_image

def test_file_size():
assert LOGO.size == 1942

@nottest
def setup_data():
DATA_ROOT.make()

@nottest
def cleanup_data():
DATA_ROOT.delete()

@with_setup(setup_data, cleanup_data)
def test_copy_file():
DATA_HELPERS = File(DATA_ROOT.child(HELPERS.name))
assert not DATA_HELPERS.exists
HELPERS.copy_to(DATA_ROOT)
assert DATA_HELPERS.exists

@with_setup(setup_data, cleanup_data)
def test_copy_folder():
assert DATA_ROOT.exists
DATA_JINJA2 = DATA_ROOT.child_folder(JINJA2.name)
assert not DATA_JINJA2.exists
JINJA2.copy_to(DATA_ROOT)
assert DATA_JINJA2.exists
for f in [HELPERS, INDEX, LAYOUT]:
assert File(DATA_JINJA2.child(f.name)).exists

@with_setup(setup_data, cleanup_data)
def test_copy_folder_target_missing():
DATA_ROOT.delete()
assert not DATA_ROOT.exists
DATA_JINJA2 = DATA_ROOT.child_folder(JINJA2.name)
assert not DATA_JINJA2.exists
JINJA2.copy_to(DATA_ROOT)
assert DATA_JINJA2.exists
for f in [HELPERS, INDEX, LAYOUT]:
assert File(DATA_JINJA2.child(f.name)).exists

@with_setup(setup_data, cleanup_data)
def test_copy_folder_contents():
for f in [HELPERS, INDEX, LAYOUT]:
assert not File(DATA_ROOT.child(f.name)).exists
JINJA2.copy_contents_to(DATA_ROOT)
for f in [HELPERS, INDEX, LAYOUT]:
assert File(DATA_ROOT.child(f.name)).exists

@with_setup(setup_data, cleanup_data)
def test_move_folder():
DATA_JUNK = DATA_ROOT.child_folder('junk')
assert not DATA_JUNK.exists
JINJA2.copy_contents_to(DATA_JUNK)
assert DATA_JUNK.exists
for f in [HELPERS, INDEX, LAYOUT]:
assert File(DATA_JUNK.child(f.name)).exists
DATA_JUNK2 = DATA_ROOT.child_folder('second_junk')
assert not DATA_JUNK2.exists
DATA_JUNK.move_to(DATA_JUNK2)
assert not DATA_JUNK.exists
assert DATA_JUNK2.exists
for f in [HELPERS, INDEX, LAYOUT]:
assert File(DATA_JUNK2.child_folder(
DATA_JUNK.name).child(
f.name)).exists

@with_setup(setup_data, cleanup_data)
def test_rename_folder():
DATA_JUNK = DATA_ROOT.child_folder('junk')
assert not DATA_JUNK.exists
JINJA2.copy_contents_to(DATA_JUNK)
for f in [HELPERS, INDEX, LAYOUT]:
assert File(DATA_JUNK.child(f.name)).exists
DATA_JUNK2 = DATA_ROOT.child_folder('junk2')
assert DATA_JUNK.exists
assert not DATA_JUNK2.exists
DATA_JUNK.rename_to('junk2')
assert not DATA_JUNK.exists
assert DATA_JUNK2.exists
for f in [HELPERS, INDEX, LAYOUT]:
assert File(DATA_JUNK2.child(f.name)).exists

@with_setup(setup_data, cleanup_data)
def test_read_all():
utxt = u'ĂĄĂźcdeĆ’'
path = DATA_ROOT.child('unicode.txt')
with codecs.open(path, 'w', 'utf-8') as f:
f.write(utxt)

txt = File(path).read_all()
assert txt == utxt

@with_setup(setup_data, cleanup_data)
def test_write():
utxt = u'ĂĄĂźcdeĆ’'
path = DATA_ROOT.child('unicode.txt')
File(path).write(utxt)
txt = File(path).read_all()
assert txt == utxt

def test_walker():
folders = []
files = []
complete = []

with TEMPLATE_ROOT.walker as walker:

@walker.folder_visitor
def visit_folder(f):
folders.append(f)

@walker.file_visitor
def visit_file(f):
files.append(f)

@walker.finalizer
def visit_complete():
assert folders[0] == TEMPLATE_ROOT
assert folders[1] == JINJA2
assert INDEX in files
assert HELPERS in files
assert LAYOUT in files
complete.append(True)

assert len(files) == 4
assert len(folders) == 2
assert len(complete) == 1

def test_walker_walk_all():
items = list(TEMPLATE_ROOT.walker.walk_all())
assert len(items) == 6
assert TEMPLATE_ROOT in items
assert JINJA2 in items
assert INDEX in items
assert HELPERS in items
assert LAYOUT in items

def test_walker_walk_files():
items = list(TEMPLATE_ROOT.walker.walk_files())
assert len(items) == 4
assert INDEX in items
assert HELPERS in items
assert LAYOUT in items

def test_walker_walk_folders():
items = list(TEMPLATE_ROOT.walker.walk_folders())
assert len(items) == 2
assert TEMPLATE_ROOT in items
assert JINJA2 in items

def test_walker_templates_just_root():
folders = []
files = []
complete = []

with TEMPLATE_ROOT.walker as walker:

@walker.folder_visitor
def visit_folder(f):
assert f == TEMPLATE_ROOT
folders.append(f)
return False

@walker.file_visitor
def visit_file(f):
files.append(f)

@walker.finalizer
def visit_complete():
complete.append(True)

assert len(files) == 0
assert len(folders) == 1
assert len(complete) == 1

def test_lister_templates():
folders = []
files = []
complete = []

with TEMPLATE_ROOT.lister as lister:

@lister.folder_visitor
def visit_folder(f):
assert f == JINJA2
folders.append(f)

@lister.file_visitor
def visit_file(f):
files.append(f)

@lister.finalizer
def visit_complete():
complete.append(True)

assert len(files) == 0
assert len(folders) == 1
assert len(complete) == 1


def test_lister_list_all():
items = list(TEMPLATE_ROOT.lister.list_all())
assert len(items) == 1
assert JINJA2 in items
items = list(JINJA2.lister.list_all())
assert len(items) == 4
assert INDEX in items
assert HELPERS in items
assert LAYOUT in items


def test_lister_list_files():
items = list(TEMPLATE_ROOT.lister.list_files())
assert len(items) == 0
items = list(JINJA2.lister.list_files())
assert len(items) == 4
assert INDEX in items
assert HELPERS in items
assert LAYOUT in items

def test_lister_list_folders():
items = list(TEMPLATE_ROOT.lister.list_folders())
assert len(items) == 1
assert JINJA2 in items
items = list(JINJA2.lister.list_folders())
assert len(items) == 0

def test_lister_jinja2():
folders = []
files = []
complete = []

with JINJA2.lister as lister:

@lister.folder_visitor
def visit_folder(f):
folders.append(f)

@lister.file_visitor
def visit_file(f):
files.append(f)

@lister.finalizer
def visit_complete():
assert INDEX in files
assert HELPERS in files
assert LAYOUT in files
complete.append(True)

assert len(files) == 4
assert len(folders) == 0
assert len(complete) == 1

+ 2
- 2
hyde/tests/test_generate.py View File

@@ -6,13 +6,13 @@ Use nose
"""

from hyde.generator import Generator
from hyde.fs import FS, File, Folder
from hyde.model import Config
from hyde.site import Site

from nose.tools import raises, with_setup, nottest
from pyquery import PyQuery

from fswrap import File, Folder

TEST_SITE = File(__file__).parent.child_folder('_test')

class TestGenerator(object):


+ 2
- 1
hyde/tests/test_initialize.py View File

@@ -8,8 +8,9 @@ Use nose

from hyde.engine import Engine
from hyde.exceptions import HydeException
from hyde.fs import File, Folder
from hyde.layout import Layout

from fswrap import File, Folder
from nose.tools import raises, with_setup, nottest

TEST_SITE = File(__file__).parent.child_folder('_test')


+ 7
- 6
hyde/tests/test_jinja2template.py View File

@@ -7,18 +7,19 @@ Use nose
Some code borrowed from rwbench.py from the jinja2 examples
"""
from datetime import datetime
from random import choice, randrange

from hyde.ext.templates.jinja import Jinja2Template
from hyde.fs import File
from hyde.site import Site
from hyde.generator import Generator
from hyde.model import Config

from fswrap import File
from jinja2.utils import generate_lorem_ipsum
from random import choice, randrange
import yaml

from pyquery import PyQuery
from nose.tools import nottest
from pyquery import PyQuery

import yaml

ROOT = File(__file__).parent
JINJA2 = ROOT.child_folder('templates/jinja2')
@@ -623,7 +624,7 @@ Hyde & Jinja.
assert "reference" not in html


def test_yaml_tag(salf):
def test_yaml_tag(self):

text = """
{% yaml test %}


+ 4
- 3
hyde/tests/test_layout.py View File

@@ -4,11 +4,12 @@ Use nose
`$ pip install nose`
`$ nosetests`
"""
import os

from hyde.fs import File, Folder
from hyde.layout import Layout, HYDE_DATA, LAYOUTS
from nose.tools import raises, with_setup, nottest
import os

from fswrap import File
from nose.tools import nottest, with_setup

DATA_ROOT = File(__file__).parent.child_folder('data')
LAYOUT_ROOT = DATA_ROOT.child_folder(LAYOUTS)


+ 0
- 81
hyde/tests/test_loader.py View File

@@ -1,81 +0,0 @@
# -*- coding: utf-8 -*-
"""
Use nose
`$ pip install nose`
`$ nosetests`
"""

from hyde.loader import load_python_object
from nose.tools import raises
import os

from hyde.exceptions import HydeException
from hyde.fs import File, Folder
from hyde.generator import Generator
from hyde.site import Site



def test_can_load_locals():

file_class = load_python_object('hyde.fs.File')
assert file_class

f = file_class(__file__)
assert f

assert f.name == os.path.basename(__file__)


def test_can_load_from_python_path():

markdown = load_python_object('markdown.markdown')
assert markdown

assert "<h3>h3</h3>" == markdown("### h3")

def test_can_load_module_without_dot():

yaml = load_python_object('yaml')

abc = yaml.load("""
d: efg
l: mno
""")

assert abc['d'] == 'efg'
assert abc['l'] == 'mno'

def test_can_load_site_specific_plugins():
TEST_SITE = File(__file__).parent.child_folder('_test')
TEST_SITE.make()
TEST_SITE.parent.child_folder(
'sites/test_jinja').copy_contents_to(TEST_SITE)
TEST_SITE.parent.child_folder(
'ssp').copy_contents_to(TEST_SITE)
s = Site(TEST_SITE)
gen = Generator(s)
gen.generate_all()
banner = """
<!--
This file was produced with infinite love, care & sweat.
Please dont copy. If you have to, please drop me a note.
-->
"""
with TEST_SITE.child_folder('deploy').get_walker('*.html') as walker:
@walker.file_visitor
def visit_file(f):
text = f.read_all()
assert text.strip().startswith(banner.strip())

@raises(HydeException)
def test_exception_raised_for_invalid_module():
load_python_object("junk.junk.junk")
assert False

@raises(HydeException)
def test_exception_raised_for_invalid_object():
load_python_object("markdown.junk")
assert False

+ 2
- 1
hyde/tests/test_model.py View File

@@ -5,7 +5,8 @@ Use nose
`$ nosetests`
"""
from hyde.model import Config, Expando
from hyde.fs import *

from fswrap import File, Folder

def test_expando_one_level():
d = {"a": 123, "b": "abc"}


+ 1
- 3
hyde/tests/test_plugin.py View File

@@ -5,16 +5,14 @@ Use nose
`$ nosetests`
"""

from hyde.exceptions import HydeException
from hyde.fs import File, Folder
from hyde.generator import Generator
from hyde.plugin import Plugin
from hyde.site import Site
from hyde.model import Expando

from mock import patch, Mock
from nose.tools import raises, nottest, with_setup

from fswrap import File, Folder

TEST_SITE = File(__file__).parent.child_folder('_test')



+ 5
- 5
hyde/tests/test_simple_copy.py View File

@@ -18,14 +18,14 @@ Use nose
`$ nosetests`
"""
import yaml
from urllib import quote

from hyde.fs import File, Folder
from hyde.model import Config, Expando
from hyde.site import Node, RootNode, Site
from hyde.model import Config
from hyde.site import Site
from hyde.generator import Generator

from nose.tools import raises, with_setup, nottest
from fswrap import File
from nose.tools import nottest


TEST_SITE_ROOT = File(__file__).parent.child_folder('sites/test_jinja')



+ 2
- 3
hyde/tests/test_site.py View File

@@ -7,11 +7,10 @@ Use nose
import yaml
from urllib import quote

from hyde.fs import File, Folder
from hyde.model import Config, Expando
from hyde.model import Config
from hyde.site import Node, RootNode, Site

from nose.tools import raises, with_setup, nottest
from fswrap import File, Folder

TEST_SITE_ROOT = File(__file__).parent.child_folder('sites/test_jinja')



+ 7
- 91
hyde/util.py View File

@@ -1,97 +1,10 @@
"""
Module for python 2.6 compatibility.
"""
import logging
import os
import sys
from itertools import ifilter, izip, tee
from functools import partial
from itertools import izip, tee

try:
from logging import NullHandler
except:
class NullHandler(logging.Handler):
"""
NOOP handler for libraries.
"""
def emit(self, record):
"""
/dev/null
"""
pass

def getLoggerWithConsoleHandler(logger_name):
logger = logging.getLogger(logger_name)
logger.setLevel(logging.INFO)
if not logger.handlers:
handler = logging.StreamHandler(sys.stdout)
if sys.platform == 'win32':
formatter = logging.Formatter(
fmt="%(asctime)s %(name)s %(message)s",
datefmt='%H:%M:%S')
else:
formatter = ColorFormatter(fmt="$RESET %(asctime)s "
"$BOLD$COLOR%(name)s$RESET "
"%(message)s", datefmt='%H:%M:%S')
handler.setFormatter(formatter)
logger.addHandler(handler)
return logger


def getLoggerWithNullHandler(logger_name):
"""
Gets the logger initialized with the `logger_name`
and a NullHandler.
"""
logger = logging.getLogger(logger_name)
if not logger.handlers:
logger.addHandler(NullHandler())
return logger


## Code stolen from :
## http://stackoverflow.com/questions/384076/how-can-i-make-the-python-logging-output-to-be-colored/2532931#2532931
##
BLACK, RED, GREEN, YELLOW, BLUE, MAGENTA, CYAN, WHITE = range(8)

COLORS = {
'WARNING' : YELLOW,
'INFO' : WHITE,
'DEBUG' : BLUE,
'CRITICAL' : YELLOW,
'ERROR' : RED,
'RED' : RED,
'GREEN' : GREEN,
'YELLOW' : YELLOW,
'BLUE' : BLUE,
'MAGENTA' : MAGENTA,
'CYAN' : CYAN,
'WHITE' : WHITE,
}

RESET_SEQ = "\033[0m"
COLOR_SEQ = "\033[1;%dm"
BOLD_SEQ = "\033[1m"

class ColorFormatter(logging.Formatter):

def __init__(self, *args, **kwargs):
# can't do super(...) here because Formatter is an old school class
logging.Formatter.__init__(self, *args, **kwargs)

def format(self, record):
levelname = record.levelname
color = COLOR_SEQ % (30 + COLORS[levelname])
message = logging.Formatter.format(self, record)
message = message.replace("$RESET", RESET_SEQ)\
.replace("$BOLD", BOLD_SEQ)\
.replace("$COLOR", color)
for k,v in COLORS.items():
message = message.replace("$" + k, COLOR_SEQ % (v+30))\
.replace("$BG" + k, COLOR_SEQ % (v+40))\
.replace("$BG-" + k, COLOR_SEQ % (v+40))
return message + RESET_SEQ

logging.ColorFormatter = ColorFormatter

def make_method(method_name, method_):
def method__(*args, **kwargs):
@@ -99,21 +12,23 @@ 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)
return izip(a, b)


def first_match(predicate, iterable):
"""
Gets the first element matched by the predicate
@@ -124,6 +39,7 @@ def first_match(predicate, iterable):
return item
return None


def discover_executable(name, sitepath):
"""
Finds an executable in the given sitepath or in the


+ 1
- 0
requirements.txt View File

@@ -1,3 +1,4 @@
fswrap==0.1.1
commando==0.3.2a
PyYAML==3.10
Markdown==2.3.1


+ 1
- 0
setup.py View File

@@ -116,6 +116,7 @@ setup(name=PROJECT,
packages=find_packages(),
requires=['python (>= 2.7)'],
install_requires=(
'fswrap==0.1.1',
'commando==0.3.2a',
'PyYAML==3.10',
'Markdown==2.3.1',


Loading…
Cancel
Save