* 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
@@ -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 | |||
@@ -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) | |||
@@ -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): | |||
""" | |||
@@ -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): | |||
@@ -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') | |||
@@ -4,7 +4,8 @@ jpegoptim plugin | |||
""" | |||
from hyde.plugin import CLTransformer | |||
from hyde.fs import File | |||
from fswrap import File | |||
class JPEGOptimPlugin(CLTransformer): | |||
""" | |||
@@ -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): | |||
""" | |||
@@ -4,7 +4,8 @@ OPTIPNG plugin | |||
""" | |||
from hyde.plugin import CLTransformer | |||
from hyde.fs import File | |||
from fswrap import File | |||
class OptiPNGPlugin(CLTransformer): | |||
""" | |||
@@ -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 | |||
@@ -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 | |||
@@ -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. | |||
""" | |||
@@ -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): | |||
""" | |||
@@ -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): | |||
""" | |||
@@ -4,7 +4,8 @@ Uglify plugin | |||
""" | |||
from hyde.plugin import CLTransformer | |||
from hyde.fs import File | |||
from fswrap import File | |||
class UglifyPlugin(CLTransformer): | |||
""" | |||
@@ -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): | |||
""" | |||
@@ -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): | |||
@@ -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') | |||
@@ -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() | |||
@@ -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): | |||
@@ -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) |
@@ -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') | |||
@@ -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" | |||
@@ -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)) |
@@ -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): | |||
@@ -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,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 | |||
@@ -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. | |||
@@ -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 | |||
@@ -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. | |||
@@ -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') | |||
@@ -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') | |||
@@ -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') | |||
@@ -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,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 == '/' | |||
@@ -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 | |||
@@ -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 | |||
@@ -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') | |||
@@ -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') | |||
@@ -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 | |||
@@ -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') | |||
@@ -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): | |||
@@ -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') | |||
@@ -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): | |||
@@ -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') | |||
@@ -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", | |||
@@ -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') | |||
@@ -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') | |||
@@ -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') | |||
@@ -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 |
@@ -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): | |||
@@ -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,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,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) | |||
@@ -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 |
@@ -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"} | |||
@@ -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') | |||
@@ -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') | |||
@@ -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') | |||
@@ -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,3 +1,4 @@ | |||
fswrap==0.1.1 | |||
commando==0.3.2a | |||
PyYAML==3.10 | |||
Markdown==2.3.1 | |||
@@ -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', | |||