From e75cefea33b6c9bf3e42f5c18523542825c6551e Mon Sep 17 00:00:00 2001 From: Lakshmi Vyasarajan Date: Fri, 17 Dec 2010 22:54:59 +0530 Subject: [PATCH] hyde init complete --- LICENSE | 3 +- hyde/engine.py | 48 ++++++----- hyde/exceptions.py | 5 ++ hyde/fs.py | 83 +++++++++++++++++-- hyde/layout.py | 41 ++++++++- {data => hyde}/layouts/basic/README.markdown | 0 {data => hyde}/layouts/basic/info.yaml | 0 .../layouts/basic/layout/analytics.html | 0 {data => hyde}/layouts/basic/layout/base.html | 0 .../layouts/basic/layout/devmode.html | 0 {data => hyde}/layouts/basic/layout/root.html | 0 hyde/{hyde.py => main.py} | 4 +- hyde/tests/data/unicode.txt | 1 - hyde/tests/test_fs.py | 72 +++++++++++++++- hyde/tests/test_initialize.py | 45 ++++++++++ hyde/tests/test_layout.py | 39 +++++++++ 16 files changed, 306 insertions(+), 35 deletions(-) create mode 100644 hyde/exceptions.py rename {data => hyde}/layouts/basic/README.markdown (100%) rename {data => hyde}/layouts/basic/info.yaml (100%) rename {data => hyde}/layouts/basic/layout/analytics.html (100%) rename {data => hyde}/layouts/basic/layout/base.html (100%) rename {data => hyde}/layouts/basic/layout/devmode.html (100%) rename {data => hyde}/layouts/basic/layout/root.html (100%) rename hyde/{hyde.py => main.py} (67%) delete mode 100644 hyde/tests/data/unicode.txt create mode 100644 hyde/tests/test_initialize.py create mode 100644 hyde/tests/test_layout.py diff --git a/LICENSE b/LICENSE index 4cd24d2..7481c70 100644 --- a/LICENSE +++ b/LICENSE @@ -18,5 +18,4 @@ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. - +THE SOFTWARE. \ No newline at end of file diff --git a/hyde/engine.py b/hyde/engine.py index b55ada1..d66df27 100644 --- a/hyde/engine.py +++ b/hyde/engine.py @@ -2,10 +2,15 @@ """ Implements the hyde entry point commands """ -import sys -from commando import Application, command, subcommand, param -from version import __version__ +from commando import * +from hyde.exceptions import HydeException +from hyde.fs import File, Folder +from hyde.layout import Layout +from hyde.version import __version__ +import os + +HYDE_LAYOUTS = "HYDE_LAYOUTS" class Engine(Application): """ @@ -14,9 +19,9 @@ class Engine(Application): @command(description='hyde - a python static website generator', epilog='Use %(prog)s {command} -h to get help on individual commands') - @param('-v', '--version', action='version', version='%(prog)s ' + __version__) - @param('-s', '--sitepath', action='store', default='.', help="Location of the hyde site") - def main(self, params): + @version('-v', '--version', version='%(prog)s ' + __version__) + @store('-s', '--sitepath', default='.', help="Location of the hyde site") + def main(self, args): """ Will not be executed. A sub command is required. This function exists to provide common parameters for the subcommands and some generic stuff like version and @@ -25,24 +30,21 @@ class Engine(Application): pass @subcommand('init', help='Create a new hyde site') - @param('-t', '--template', action='store', default='basic', dest='template', - help='Overwrite the current site if it exists') - @param('-f', '--force', action='store_true', default=False, dest='overwrite', - help='Overwrite the current site if it exists') - def init(self, params): + @store('-l', '--layout', default='basic', help='Layout for the new site') + @true('-f', '--force', default=False, dest='overwrite', + help='Overwrite the current site if it exists') + def init(self, args): """ The initialize command. Creates a new site from the template at the given sitepath. """ - print params.sitepath - print params.template - print params.overwrite - - def start(self): - """ - main() - """ - args = self.parse(sys.argv[1:]) - self.run(args) - - + sitepath = File(args.sitepath) + if sitepath.exists and not args.overwrite: + raise HydeException("The given site path[%s] is not empty" % sitepath) + layout = Layout.find_layout(args.layout) + if not layout.exists: + raise HydeException( + "The given layout is invalid. Please check if you have the `layout` " + "is in the right place and the environment variable has been setup" + "properly") + layout.copy_to(args.sitepath) \ No newline at end of file diff --git a/hyde/exceptions.py b/hyde/exceptions.py new file mode 100644 index 0000000..cdc3aac --- /dev/null +++ b/hyde/exceptions.py @@ -0,0 +1,5 @@ +class HydeException(Exception): + """ + Base class for exceptions from hyde + """ + pass diff --git a/hyde/fs.py b/hyde/fs.py index 7f613a7..70d290b 100644 --- a/hyde/fs.py +++ b/hyde/fs.py @@ -1,14 +1,15 @@ # -*- 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. +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 # import fnmatch import os -# import shutil +import shutil # from datetime import datetime # pylint: disable-msg=E0611 @@ -28,6 +29,19 @@ class FS(object): def __repr__(self): return self.path + def __eq__(self, other): + return str(self) == str(other) + + def __ne__(self, other): + return str(self) != str(other) + + @property + def exists(self): + """ + Does the file system object exist? + """ + return os.path.exists(self.path) + @property def name(self): """ @@ -42,6 +56,27 @@ class FS(object): """ return Folder(os.path.dirname(self.path)) + + @staticmethod + def file_or_folder(path): + """ + Returns a File or Folder object that would represent the given path. + """ + target = str(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`. `destination` must be + an instance of File or Folder. + """ + if os.path.isdir(str(destination)): + return FS.file_or_folder(Folder(destination).child(self.name)) + else: + return destination + + class File(FS): """ The File object. @@ -85,6 +120,16 @@ class File(FS): 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. + """ + shutil.copy(self.path, str(destination)) + return self.__get_destination__(destination) + + class Folder(FS): """ Represents a directory. @@ -102,4 +147,32 @@ class Folder(FS): """ Returns a path of a child item represented by `name`. """ - return os.path.join(self.path, name) \ No newline at end of file + return os.path.join(self.path, name) + + 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: + os.makedirs(self.path) + except os.error: + pass + return self + + def delete(self): + """ + Deletes the directory if it exists. + """ + if self.exists: + 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) + shutil.copytree(self.path, str(target)) + return target \ No newline at end of file diff --git a/hyde/layout.py b/hyde/layout.py index 7c68785..4c4d64c 100644 --- a/hyde/layout.py +++ b/hyde/layout.py @@ -1 +1,40 @@ -# -*- coding: utf-8 -*- \ No newline at end of file +# -*- coding: utf-8 -*- +""" +Classes, functions and utilties related to hyde layouts +""" +import os + +from hyde.fs import File, Folder + +HYDE_DATA = "HYDE_DATA" +LAYOUTS = "layouts" + +class Layout(object): + """ + Represents a layout package + """ + + @staticmethod + def find_layout(layout_name="basic"): + """ + Find the layout with a given name. + Search order: + 1. env(HYDE_DATA) + 2. /layouts/ + """ + layout_folder = None + if HYDE_DATA in os.environ: + layout_folder = Layout._get_layout_folder(os.environ[HYDE_DATA], layout_name) + if not layout_folder: + layout_folder = Layout._get_layout_folder(File(__file__).parent, layout_name) + return layout_folder + + @staticmethod + def _get_layout_folder(root, layout_name="basic"): + """ + Finds the layout folder from the given root folder. + If it does not exist, return None + """ + layout_folder = Folder(str(root)).child_folder(LAYOUTS).child_folder(layout_name) + return layout_folder if layout_folder.exists else None + diff --git a/data/layouts/basic/README.markdown b/hyde/layouts/basic/README.markdown similarity index 100% rename from data/layouts/basic/README.markdown rename to hyde/layouts/basic/README.markdown diff --git a/data/layouts/basic/info.yaml b/hyde/layouts/basic/info.yaml similarity index 100% rename from data/layouts/basic/info.yaml rename to hyde/layouts/basic/info.yaml diff --git a/data/layouts/basic/layout/analytics.html b/hyde/layouts/basic/layout/analytics.html similarity index 100% rename from data/layouts/basic/layout/analytics.html rename to hyde/layouts/basic/layout/analytics.html diff --git a/data/layouts/basic/layout/base.html b/hyde/layouts/basic/layout/base.html similarity index 100% rename from data/layouts/basic/layout/base.html rename to hyde/layouts/basic/layout/base.html diff --git a/data/layouts/basic/layout/devmode.html b/hyde/layouts/basic/layout/devmode.html similarity index 100% rename from data/layouts/basic/layout/devmode.html rename to hyde/layouts/basic/layout/devmode.html diff --git a/data/layouts/basic/layout/root.html b/hyde/layouts/basic/layout/root.html similarity index 100% rename from data/layouts/basic/layout/root.html rename to hyde/layouts/basic/layout/root.html diff --git a/hyde/hyde.py b/hyde/main.py similarity index 67% rename from hyde/hyde.py rename to hyde/main.py index d74200a..c5b10ac 100644 --- a/hyde/hyde.py +++ b/hyde/main.py @@ -3,7 +3,7 @@ """ The hyde executable """ -from engine import Engine +from hyde.engine import Engine if __name__ == "__main__": - Engine().start() \ No newline at end of file + Engine().run() \ No newline at end of file diff --git a/hyde/tests/data/unicode.txt b/hyde/tests/data/unicode.txt deleted file mode 100644 index 1c5eeab..0000000 --- a/hyde/tests/data/unicode.txt +++ /dev/null @@ -1 +0,0 @@ -åßcdeƒ \ No newline at end of file diff --git a/hyde/tests/test_fs.py b/hyde/tests/test_fs.py index 08868a9..2578d9c 100644 --- a/hyde/tests/test_fs.py +++ b/hyde/tests/test_fs.py @@ -8,7 +8,9 @@ Use nose 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__) @@ -49,9 +51,76 @@ def test_child_folder(): assert hasattr(c, 'child_folder') assert str(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(str(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_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')) + +@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_read_all(): utxt = u'åßcdeƒ' path = DATA_ROOT.child('unicode.txt') @@ -60,7 +129,8 @@ def test_read_all(): 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') diff --git a/hyde/tests/test_initialize.py b/hyde/tests/test_initialize.py new file mode 100644 index 0000000..ce0667d --- /dev/null +++ b/hyde/tests/test_initialize.py @@ -0,0 +1,45 @@ +# -*- coding: utf-8 -*- +""" +Use nose +`$ pip install nose` +`$ nosetests` +""" + + +from hyde.engine import Engine +from hyde.exceptions import HydeException +from hyde.fs import FS, File, Folder + +from nose.tools import raises, with_setup, nottest + +TEST_SITE = File(__file__).parent.child_folder('_test') + +@nottest +def create_test_site(): + TEST_SITE.make() + +@nottest +def delete_test_site(): + TEST_SITE.delete() + +@raises(HydeException) +@with_setup(create_test_site, delete_test_site) +def test_ensure_exception_when_sitepath_exists(): + e = Engine() + e.run(e.parse(['-s', str(TEST_SITE), 'init'])) + +@with_setup(create_test_site, delete_test_site) +def test_ensure_no_exception_when_sitepath_exists_when_forced(): + e = Engine() + e.run(e.parse(['-s', str(TEST_SITE), 'init', '-f'])) + assert True #No Exception + +@with_setup(create_test_site, delete_test_site) +def test_ensure_no_exception_when_sitepath_does_not_exist(): + e = Engine() + TEST_SITE.delete() + e.run(e.parse(['-s', str(TEST_SITE), 'init', '-f'])) + assert TEST_SITE.exists + assert TEST_SITE.child_folder('layout').exists + assert File(TEST_SITE.child('info.yaml')).exists + diff --git a/hyde/tests/test_layout.py b/hyde/tests/test_layout.py new file mode 100644 index 0000000..222facd --- /dev/null +++ b/hyde/tests/test_layout.py @@ -0,0 +1,39 @@ +# -*- coding: utf-8 -*- +""" +Use nose +`$ pip install nose` +`$ nosetests` +""" + +from hyde.fs import File, Folder +from hyde.layout import Layout, HYDE_DATA, LAYOUTS +from nose.tools import raises, with_setup, nottest +import os + +DATA_ROOT = File(__file__).parent.child_folder('data') +LAYOUT_ROOT = DATA_ROOT.child_folder(LAYOUTS) + +@nottest +def setup_data(): + DATA_ROOT.make() + +@nottest +def cleanup_data(): + DATA_ROOT.delete() + +def test_find_layout_from_package_dir(): + f = Layout.find_layout() + assert f.name == 'basic' + assert f.child_folder('layout').exists + +@with_setup(setup_data, cleanup_data) +def test_find_layout_from_env_var(): + f = Layout.find_layout() + LAYOUT_ROOT.make() + f.copy_to(LAYOUT_ROOT) + os.environ[HYDE_DATA] = str(DATA_ROOT) + f = Layout.find_layout() + assert f.parent == LAYOUT_ROOT + assert f.name == 'basic' + assert f.child_folder('layout').exists + del os.environ[HYDE_DATA]