* 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', | |||