@@ -346,6 +346,26 @@ class Folder(FS): | |||||
shutil.copytree(self.path, str(target)) | shutil.copytree(self.path, str(target)) | ||||
return 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): | def _create_target_tree(self, target): | ||||
""" | """ | ||||
There is a bug in dir_util that makes `copy_tree` crash if a folder in | 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.exceptions import HydeException | ||||
from hyde.fs import File, Folder | |||||
from hyde.model import Config, Expando | |||||
import logging | import logging | ||||
import os | import os | ||||
@@ -223,16 +225,14 @@ class RootNode(Node): | |||||
class Site(object): | 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__() | super(Site, self).__init__() | ||||
self.site_path = Folder(str(site_path)) | 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.node_map = {} | ||||
self.resource_map = {} | self.resource_map = {} | ||||
@@ -241,5 +241,4 @@ class Site(object): | |||||
Walks the content and media folders to build up the sitemap. | 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]: | for f in [HELPERS, INDEX, LAYOUT]: | ||||
assert File(DATA_ROOT.child(f.name)).exists | 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) | @with_setup(setup_data, cleanup_data) | ||||
def test_read_all(): | def test_read_all(): | ||||
utxt = u'ĂĄĂźcdeĆ’' | 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` | `$ pip install nose` | ||||
`$ nosetests` | `$ nosetests` | ||||
""" | """ | ||||
import yaml | |||||
from hyde.fs import File, Folder | from hyde.fs import File, Folder | ||||
from hyde.model import Config, Expando | |||||
from hyde.site import Node, RootNode, Site | 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') | TEST_SITE_ROOT = File(__file__).parent.child_folder('sites/test_jinja') | ||||
@@ -60,3 +64,32 @@ def test_build(): | |||||
assert resource | assert resource | ||||
assert resource.relative_path == path | assert resource.relative_path == path | ||||
assert not s.content.resource_from_relative_path('/happy-festivus.html') | 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') |