| @@ -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') | |||||