| @@ -49,7 +49,7 @@ class Engine(Application): | |||||
| The create command. Creates a new site from the template at the given | The create command. Creates a new site from the template at the given | ||||
| sitepath. | sitepath. | ||||
| """ | """ | ||||
| sitepath = Folder(args.sitepath) | |||||
| sitepath = Folder(Folder(args.sitepath).fully_expanded_path) | |||||
| if sitepath.exists and not args.overwrite: | if sitepath.exists and not args.overwrite: | ||||
| raise HydeException( | raise HydeException( | ||||
| "The given site path[%s] is not empty" % sitepath) | "The given site path[%s] is not empty" % sitepath) | ||||
| @@ -75,14 +75,41 @@ class Engine(Application): | |||||
| The generate command. Generates the site at the given | The generate command. Generates the site at the given | ||||
| deployment directory. | deployment directory. | ||||
| """ | """ | ||||
| sitepath = Folder(args.sitepath) | |||||
| site = self.make_site(args.sitepath, args.config) | |||||
| from hyde.generator import Generator | |||||
| gen = Generator(site) | |||||
| gen.generate_all() | |||||
| @subcommand('serve', help='Serve the website') | |||||
| @store('-a', '--address', default='localhost', dest='address', | |||||
| help='The address where the website must be served from.') | |||||
| @store('-p', '--port', type=int, default=8080, dest='port', | |||||
| help='The port where the website must be served from.') | |||||
| @store('-c', '--config-path', default='site.yaml', dest='config', | |||||
| help='The configuration used to generate the site') | |||||
| @store('-d', '--deploy-path', default='deploy', | |||||
| help='Where should the site be generated?') | |||||
| def serve(self, args): | |||||
| """ | |||||
| The serve command. Serves the site at the given | |||||
| deployment directory, address and port. Regenerates | |||||
| the entire site or specific files based on ths request. | |||||
| """ | |||||
| sitepath = Folder(Folder(args.sitepath).fully_expanded_path) | |||||
| config_file = sitepath.child(args.config) | config_file = sitepath.child(args.config) | ||||
| site = self.make_site(args.sitepath, args.config) | |||||
| from hyde.server import HydeWebServer | |||||
| server = HydeWebServer(site, args.address, args.port) | |||||
| server.serve_forever() | |||||
| def make_site(self, sitepath, config): | |||||
| """ | |||||
| Creates a site object from the given sitepath and the config file. | |||||
| """ | |||||
| sitepath = Folder(Folder(sitepath).fully_expanded_path) | |||||
| config_file = sitepath.child(config) | |||||
| logger.info("Reading site configuration from [%s]", config_file) | logger.info("Reading site configuration from [%s]", config_file) | ||||
| conf = {} | conf = {} | ||||
| with open(config_file) as stream: | with open(config_file) as stream: | ||||
| conf = yaml.load(stream) | conf = yaml.load(stream) | ||||
| site = Site(sitepath, Config(sitepath, conf)) | |||||
| from hyde.generator import Generator | |||||
| gen = Generator(site) | |||||
| gen.generate_all() | |||||
| return Site(sitepath, Config(sitepath, conf)) | |||||
| @@ -27,9 +27,11 @@ class FS(object): | |||||
| """ | """ | ||||
| The base file system object | The base file system object | ||||
| """ | """ | ||||
| def __init__(self, path): | def __init__(self, path): | ||||
| super(FS, self).__init__() | super(FS, self).__init__() | ||||
| self.path = os.path.expanduser(str(path).strip().rstrip(os.sep)) | |||||
| self.path = os.path.expandvars(os.path.expanduser( | |||||
| str(path).strip().rstrip(os.sep))) | |||||
| def __str__(self): | def __str__(self): | ||||
| return self.path | return self.path | ||||
| @@ -43,6 +45,18 @@ class FS(object): | |||||
| def __ne__(self, other): | def __ne__(self, other): | ||||
| return str(self) != str(other) | return str(self) != str(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 | @property | ||||
| def exists(self): | def exists(self): | ||||
| """ | """ | ||||
| @@ -142,6 +156,7 @@ class File(FS): | |||||
| """ | """ | ||||
| The File object. | The File object. | ||||
| """ | """ | ||||
| def __init__(self, path): | def __init__(self, path): | ||||
| super(File, self).__init__(path) | super(File, self).__init__(path) | ||||
| @@ -436,6 +451,7 @@ class Folder(FS): | |||||
| """ | """ | ||||
| Represents a directory. | Represents a directory. | ||||
| """ | """ | ||||
| def __init__(self, path): | def __init__(self, path): | ||||
| super(Folder, self).__init__(path) | super(Folder, self).__init__(path) | ||||
| @@ -510,6 +526,7 @@ class Folder(FS): | |||||
| """ | """ | ||||
| source = self | source = self | ||||
| with source.walker as walker: | with source.walker as walker: | ||||
| @walker.folder_visitor | @walker.folder_visitor | ||||
| def visit_folder(folder): | def visit_folder(folder): | ||||
| """ | """ | ||||
| @@ -81,7 +81,8 @@ class Generator(object): | |||||
| if not self.template: | if not self.template: | ||||
| logger.info("Generating site at [%s]" % self.site.sitepath) | logger.info("Generating site at [%s]" % self.site.sitepath) | ||||
| self.template = Template.find_template(self.site) | self.template = Template.find_template(self.site) | ||||
| logger.info("Using [%s] as the template", self.template) | |||||
| logger.info("Using [%s] as the template", | |||||
| self.template.__class__.__name__) | |||||
| logger.info("Configuring the template environment") | logger.info("Configuring the template environment") | ||||
| self.template.configure(self.site.config) | self.template.configure(self.site.config) | ||||
| @@ -2,6 +2,8 @@ mode: development | |||||
| media_root:: media # Relative path from site root (the directory where this file exists) | media_root:: media # Relative path from site root (the directory where this file exists) | ||||
| media_url: /media | media_url: /media | ||||
| template: hyde.ext.jinja2 | template: hyde.ext.jinja2 | ||||
| widgets: | |||||
| plugins: | plugins: | ||||
| aggregators: | |||||
| - hyde.ext.plugins.meta.MetaPlugin | |||||
| - hyde.ext.plugins.auto_extend.AutoExtendPlugin | |||||
| - hyde.ext.plugins.less.LessCSSPlugin | |||||
| - hyde.ext.plugins.blockdown.BlockdownPlugin | |||||
| @@ -51,6 +51,7 @@ class Config(Expando): | |||||
| layout_root='layout', | layout_root='layout', | ||||
| media_url='/media', | media_url='/media', | ||||
| site_url='/', | site_url='/', | ||||
| not_found='404.html', | |||||
| plugins = [] | plugins = [] | ||||
| ) | ) | ||||
| conf = dict(**default_config) | conf = dict(**default_config) | ||||
| @@ -0,0 +1,123 @@ | |||||
| """ | |||||
| Contains classes and utilities for serving a site | |||||
| generated from hyde. | |||||
| """ | |||||
| import os | |||||
| import urlparse | |||||
| import urllib | |||||
| from SimpleHTTPServer import SimpleHTTPRequestHandler | |||||
| from BaseHTTPServer import HTTPServer | |||||
| from hyde.fs import File, Folder | |||||
| from hyde.site import Site | |||||
| from hyde.generator import Generator | |||||
| import logging | |||||
| logger = logging.getLogger('hyde.server') | |||||
| import sys | |||||
| logger.addHandler(logging.StreamHandler(sys.stdout)) | |||||
| class HydeRequestHandler(SimpleHTTPRequestHandler): | |||||
| """ | |||||
| Serves files by regenerating the resource (or) | |||||
| everything when a request is issued. | |||||
| """ | |||||
| def do_GET(self): | |||||
| """ | |||||
| Idenitfy the requested path. If the query string | |||||
| contains `refresh`, regenerat the entire site. | |||||
| Otherwise, regenerate only the requested resource | |||||
| and serve. | |||||
| """ | |||||
| logger.info("Processing request:[%s]" % self.path) | |||||
| result = urlparse.urlparse(self.path) | |||||
| query = urlparse.parse_qs(result.query) | |||||
| if 'refresh' in query: | |||||
| self.server.regenerate() | |||||
| del query['refresh'] | |||||
| parts = tuple(result) | |||||
| parts[4] = urllib.urlencode(query) | |||||
| new_url = urlparse.urlunparse(parts) | |||||
| logger.info('Redirecting...[%s]' % new_url) | |||||
| self.redirect(new_url) | |||||
| else: | |||||
| try: | |||||
| SimpleHTTPRequestHandler.do_GET(self) | |||||
| except Exception, exception: | |||||
| logger.error(exception.message) | |||||
| site = self.server.site | |||||
| res = site.content.resource_from_relative_path( | |||||
| site.config.not_found) | |||||
| self.redirect("/" + res.relative_deploy_path) | |||||
| def translate_path(self, path): | |||||
| """ | |||||
| Finds the absolute path of the requested file by | |||||
| referring to the `site` variable in the server. | |||||
| """ | |||||
| site = self.server.site | |||||
| result = urlparse.urlparse(self.path) | |||||
| logger.info("Trying to load file based on request:[%s]" % result.path) | |||||
| path = result.path.lstrip('/') | |||||
| res = site.content.resource_from_relative_path(path) | |||||
| if not res: | |||||
| logger.info("Cannot load file:[%s]" % path) | |||||
| raise Exception("Cannot load file: [%s]" % path) | |||||
| else: | |||||
| self.server.generate_resource(res) | |||||
| new_path = site.config.deploy_root_path.child( | |||||
| res.relative_deploy_path) | |||||
| return new_path | |||||
| def redirect(self, path, temporary=True): | |||||
| """ | |||||
| Sends a redirect header with the new location. | |||||
| """ | |||||
| self.send_response(302 if temporary else 301) | |||||
| self.send_header('Location', path) | |||||
| self.end_headers() | |||||
| class HydeWebServer(HTTPServer): | |||||
| """ | |||||
| The hyde web server that regenerates the resource, node or site when | |||||
| a request is issued. | |||||
| """ | |||||
| def __init__(self, site, address, port): | |||||
| self.site = site | |||||
| self.site.load() | |||||
| self.generator = Generator(self.site) | |||||
| HTTPServer.__init__(self, (address, port), | |||||
| HydeRequestHandler) | |||||
| def __reinit__(self): | |||||
| self.generator = Generator(self.site) | |||||
| self.regenerate() | |||||
| def regenerate(self): | |||||
| """ | |||||
| Regenerates the entire site. | |||||
| """ | |||||
| try: | |||||
| logger.info('Regenerating the entire site') | |||||
| self.generator.generate_all() | |||||
| except Exception, exception: | |||||
| logger.error('Error occured when regenerating the site [%s]' | |||||
| % exception.message) | |||||
| self.__reinit__() | |||||
| def generate_resource(self, resource): | |||||
| """ | |||||
| Regenerates the entire site. | |||||
| """ | |||||
| try: | |||||
| logger.info('Generating resource [%]' % resource) | |||||
| self.generator.generate_resource(resource) | |||||
| except Exception, exception: | |||||
| logger.error('Error [%s] occured when generating the resource [%s]' | |||||
| % (resource, repr(exception))) | |||||
| self.__reinit__() | |||||
| @@ -311,7 +311,7 @@ class Site(object): | |||||
| def __init__(self, sitepath=None, config=None): | def __init__(self, sitepath=None, config=None): | ||||
| super(Site, self).__init__() | super(Site, self).__init__() | ||||
| self.sitepath = Folder(str(sitepath)) | |||||
| self.sitepath = Folder(Folder(sitepath).fully_expanded_path) | |||||
| self.config = config if config else Config(self.sitepath) | self.config = config if config else Config(self.sitepath) | ||||
| self.content = RootNode(self.config.content_root_path, self) | self.content = RootNode(self.config.content_root_path, self) | ||||
| self.plugins = [] | self.plugins = [] | ||||
| @@ -60,6 +60,13 @@ def test_path_expands_user(): | |||||
| f = File("~/abc/def") | f = File("~/abc/def") | ||||
| assert f.path == os.path.expanduser("~/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(): | def test_parent(): | ||||
| f = File(__file__) | f = File(__file__) | ||||
| p = f.parent | p = f.parent | ||||
| @@ -77,6 +77,7 @@ class TestConfig(object): | |||||
| assert hasattr(c, 'plugins') | assert hasattr(c, 'plugins') | ||||
| assert len(c.plugins) == 0 | assert len(c.plugins) == 0 | ||||
| assert c.deploy_root_path == TEST_SITE_ROOT.child_folder('deploy') | assert c.deploy_root_path == TEST_SITE_ROOT.child_folder('deploy') | ||||
| assert c.not_found == '404.html' | |||||
| def test_conf1(self): | def test_conf1(self): | ||||
| c = Config(sitepath=TEST_SITE_ROOT, config_dict=yaml.load(self.conf1)) | c = Config(sitepath=TEST_SITE_ROOT, config_dict=yaml.load(self.conf1)) | ||||