@@ -346,6 +346,26 @@ class Folder(FS): | |||
shutil.copytree(self.path, str(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, str(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, str(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 | |||
@@ -0,0 +1,68 @@ | |||
""" | |||
Contains data structures and utilities for hyde. | |||
""" | |||
class Expando(object): | |||
""" | |||
A generic expando class that creates attributes from the passed in dictionary. | |||
""" | |||
def __init__(self, d): | |||
super(Expando, self).__init__() | |||
d = d or {} | |||
for key, value in d.items(): | |||
setattr(self, key, Expando.transform(value)) | |||
@staticmethod | |||
def transform(primitive): | |||
""" | |||
Creates an expando object, a sequence of expando objects or just | |||
returns the primitive based on the primitive's type. | |||
""" | |||
if isinstance(primitive, dict): | |||
return Expando(primitive) | |||
elif isinstance(primitive, (tuple, list, set, frozenset)): | |||
return type(primitive)(Expando.transform(attr) for attr in primitive) | |||
else: | |||
return primitive | |||
from hyde.fs import File, Folder | |||
class Config(Expando): | |||
""" | |||
Represents the hyde configuration file | |||
""" | |||
def __init__(self, site_path, config_dict=None): | |||
default_config = dict( | |||
content_root = 'content', | |||
media_root = 'media', | |||
layout_root = 'layout', | |||
media_url = '/media', | |||
site_url = '/' | |||
) | |||
conf = dict(**default_config) | |||
if config_dict: | |||
conf.update(config_dict) | |||
super(Config, self).__init__(conf) | |||
self.site_path = Folder(site_path) | |||
@property | |||
def content_root_path(self): | |||
""" | |||
Derives the content root path from the site path | |||
""" | |||
return self.site_path.child_folder(self.content_root) | |||
@property | |||
def media_root_path(self): | |||
""" | |||
Derives the media root path from the site path | |||
""" | |||
return self.site_path.child_folder(self.media_root) | |||
@property | |||
def layout_root_path(self): | |||
""" | |||
Derives the layout root path from the site path | |||
""" | |||
return self.site_path.child_folder(self.layout_root) |
@@ -4,8 +4,10 @@ Parses & holds information about the site to be generated. | |||
""" | |||
from hyde.fs import File, Folder | |||
from hyde.exceptions import HydeException | |||
from hyde.fs import File, Folder | |||
from hyde.model import Config, Expando | |||
import logging | |||
import os | |||
@@ -223,16 +225,14 @@ class RootNode(Node): | |||
class Site(object): | |||
""" | |||
Represents the site to be generated | |||
Represents the site to be generated. | |||
""" | |||
def __init__(self, site_path): | |||
def __init__(self, site_path=None, config=None): | |||
super(Site, self).__init__() | |||
self.site_path = Folder(str(site_path)) | |||
# TODO: Get the value from config | |||
content_folder = self.site_path.child_folder('content') | |||
self.content = RootNode(content_folder, self) | |||
self.config = config if config else Config(self.site_path) | |||
self.content = RootNode(self.config.content_root_path, self ) | |||
self.node_map = {} | |||
self.resource_map = {} | |||
@@ -241,5 +241,4 @@ class Site(object): | |||
Walks the content and media folders to build up the sitemap. | |||
""" | |||
self.content.build() | |||
self.content.build() |
@@ -0,0 +1,7 @@ | |||
mode: development | |||
content_root: stuff # Relative path from site root | |||
media_root: media # Relative path from site root | |||
media_url: /media | |||
widgets: | |||
plugins: | |||
aggregators: |
@@ -1,9 +1,6 @@ | |||
site: | |||
mode: development | |||
media: | |||
root: | |||
path: media # Relative path from site root (the directory where this file exists) | |||
url: /media | |||
widgets: | |||
plugins: | |||
aggregators: | |||
mode: development | |||
media_root:: media # Relative path from site root (the directory where this file exists) | |||
media_url: /media | |||
widgets: | |||
plugins: | |||
aggregators: |
@@ -187,6 +187,38 @@ def test_copy_folder_contents(): | |||
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) | |||
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.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('junk').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ƒ' | |||
@@ -0,0 +1,73 @@ | |||
# -*- coding: utf-8 -*- | |||
""" | |||
Use nose | |||
`$ pip install nose` | |||
`$ nosetests` | |||
""" | |||
from hyde.model import Config, Expando | |||
from hyde.fs import * | |||
def test_expando_one_level(): | |||
d = {"a": 123, "b": "abc"} | |||
x = Expando(d) | |||
assert x.a == d['a'] | |||
assert x.b == d['b'] | |||
def test_expando_two_levels(): | |||
d = {"a": 123, "b": {"c": 456}} | |||
x = Expando(d) | |||
assert x.a == d['a'] | |||
assert x.b.c == d['b']['c'] | |||
def test_expando_three_levels(): | |||
d = {"a": 123, "b": {"c": 456, "d": {"e": "abc"}}} | |||
x = Expando(d) | |||
assert x.a == d['a'] | |||
assert x.b.c == d['b']['c'] | |||
assert x.b.d.e == d['b']['d']['e'] | |||
TEST_SITE_ROOT = File(__file__).parent.child_folder('sites/test_jinja') | |||
import yaml | |||
class TestConfig(object): | |||
@classmethod | |||
def setup_class(cls): | |||
cls.conf1 = """ | |||
mode: development | |||
content_root: stuff # Relative path from site root | |||
media_root: media # Relative path from site root | |||
media_url: /media | |||
widgets: | |||
plugins: | |||
aggregators: | |||
""" | |||
cls.conf2 = """ | |||
mode: development | |||
content_root: site/stuff # Relative path from site root | |||
media_root: mmm # Relative path from site root | |||
media_url: /media | |||
widgets: | |||
plugins: | |||
aggregators: | |||
""" | |||
def test_default_configuration(self): | |||
c = Config(site_path=TEST_SITE_ROOT) | |||
for root in ['content', 'layout', 'media']: | |||
name = root + '_root' | |||
path = name + '_path' | |||
assert hasattr(c, name) | |||
assert getattr(c, name) == root | |||
assert hasattr(c, path) | |||
assert getattr(c, path) == TEST_SITE_ROOT.child_folder(root) | |||
def test_conf1(self): | |||
c = Config(site_path=TEST_SITE_ROOT, config_dict=yaml.load(self.conf1)) | |||
assert c.content_root_path == TEST_SITE_ROOT.child_folder('stuff') | |||
def test_conf2(self): | |||
c = Config(site_path=TEST_SITE_ROOT, config_dict=yaml.load(self.conf2)) | |||
assert c.content_root_path == TEST_SITE_ROOT.child_folder('site/stuff') | |||
assert c.media_root_path == TEST_SITE_ROOT.child_folder('mmm') | |||
assert c.media_url == TEST_SITE_ROOT.child_folder('/media') |
@@ -4,9 +4,13 @@ Use nose | |||
`$ pip install nose` | |||
`$ nosetests` | |||
""" | |||
import yaml | |||
from hyde.fs import File, Folder | |||
from hyde.model import Config, Expando | |||
from hyde.site import Node, RootNode, Site | |||
from nose.tools import raises, with_setup, nottest | |||
TEST_SITE_ROOT = File(__file__).parent.child_folder('sites/test_jinja') | |||
@@ -60,3 +64,32 @@ def test_build(): | |||
assert resource | |||
assert resource.relative_path == path | |||
assert not s.content.resource_from_relative_path('/happy-festivus.html') | |||
class TestSiteWithConfig(object): | |||
@classmethod | |||
def setup_class(cls): | |||
cls.SITE_PATH = File(__file__).parent.child_folder('sites/test_jinja_with_config') | |||
cls.SITE_PATH.make() | |||
TEST_SITE_ROOT.copy_contents_to(cls.SITE_PATH) | |||
cls.config_file = File(cls.SITE_PATH.child('alternate.yaml')) | |||
with open(cls.config_file.path) as config: | |||
cls.config = Config(site_path=cls.SITE_PATH, config_dict=yaml.load(config)) | |||
cls.SITE_PATH.child_folder('content').rename_to(cls.config.content_root) | |||
@classmethod | |||
def teardown_class(cls): | |||
cls.SITE_PATH.delete() | |||
def test_build_with_config(self): | |||
s = Site(self.SITE_PATH, config = self.config) | |||
s.build() | |||
path = 'blog/2010/december' | |||
node = s.content.node_from_relative_path(path) | |||
assert node | |||
assert Folder(node.relative_path) == Folder(path) | |||
path += '/merry-christmas.html' | |||
resource = s.content.resource_from_relative_path(path) | |||
assert resource | |||
assert resource.relative_path == path | |||
assert not s.content.resource_from_relative_path('/happy-festivus.html') |