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