Permalink
Browse files

Merge branch 'server-auto-regen' of github.com:robatron/wok

  • Loading branch information...
2 parents cb8ac93 + e63195e commit d2e659e428b30d0d85034bc45f529682a9f2dc51 @mythmon committed Feb 24, 2012
Showing with 152 additions and 53 deletions.
  1. +121 −0 wok/dev_server.py
  2. +0 −46 wok/devserver.py
  3. +31 −7 wok/engine.py
View
121 wok/dev_server.py
@@ -0,0 +1,121 @@
+''' Really simple HTTP *development* server
+
+Do *NOT* attempt to use this as anything resembling a production server. It is
+meant to be used as a development test server only.
+
+You might ask, "Why do I need a development server for static pages?" One
+hyphenated modifier: "root-relative." Since wok dumps all of the media files
+in the root output directory, pages that reside inside subdirectories still
+need to access these media files in a unified way.
+
+E.g., if you include `base.css` in your `base.html` template, `base.css` should
+be accessable to any page that uses `base.html`, even if it's a categorized
+page, and thus, goes into a subdirectory. This way, your CSS include tag could
+read `<link type='text/css' href='/base.css' />` (note the '/' in the `href`
+property) and `base.css` can be accessed from anywhere.
+'''
+
+import sys
+import os
+from BaseHTTPServer import HTTPServer
+from SimpleHTTPServer import SimpleHTTPRequestHandler
+
+class dev_server:
+
+ def __init__(self, serv_dir=None, host='', port=8000, dir_mon=False,
+ watch_dirs=[], change_handler=None):
+ '''
+ Initialize a new development server on `host`:`port`, and serve the
+ files in `serv_dir`. If `serv_dir` is not provided, it will use the
+ current working directory.
+
+ If `dir_mon` is set, the server will check for changes before handling
+ every request. If a change is detected, then wok will regenerate the
+ site.
+ '''
+ self.serv_dir = os.path.abspath(serv_dir)
+ self.host = host
+ self.port = port
+ self.dir_mon = dir_mon
+ self.watch_dirs = [os.path.abspath(d) for d in watch_dirs]
+ self.change_handler = change_handler
+
+ def run(self):
+ if self.serv_dir:
+ os.chdir(self.serv_dir)
+
+ if self.dir_mon:
+ wrap = RebuildHandlerWrapper(self.change_handler, self.watch_dirs)
+ req_handler = wrap.request_handler
+ else:
+ req_handler = SimpleHTTPRequestHandler
+
+ httpd = HTTPServer((self.host, self.port), req_handler)
+ socket_info = httpd.socket.getsockname()
+
+ print("Starting dev server on http://%s:%s... (Ctrl-C to stop)"
+ %(socket_info[0], socket_info[1]))
+ print "Serving files from", self.serv_dir
+
+ if self.dir_mon:
+ print "Monitoring the following directories for changes: "
+ for d in self.watch_dirs:
+ print "\t", d
+ else:
+ print "Directory monitoring is OFF"
+
+ try:
+ httpd.serve_forever()
+ except KeyboardInterrupt:
+ print "\nStopping development server..."
+
+
+class RebuildHandlerWrapper(object):
+
+ def __init__(wrap_self, rebuild, watch_dirs):
+ """
+ We can't pass arugments to HTTPRequestHandlers, because HTTPServer
+ calls __init__. So make a closure.
+ """
+ wrap_self.rebuild = rebuild
+ wrap_self.watch_dirs = watch_dirs
+
+ wrap_self._modtime_sum = None
+ wrap_self.changed()
+
+ class RebuildHandler(SimpleHTTPRequestHandler):
+ """Rebuild if something has changed."""
+
+ def handle(self):
+ """
+ Handle a request and, if anything has changed, rebuild the
+ site before responding.
+ """
+ if wrap_self.changed():
+ wrap_self.rebuild()
+
+ SimpleHTTPRequestHandler.handle(self)
+
+ wrap_self.request_handler = RebuildHandler
+
+ def changed(self):
+ """
+ Returns if the contents of the monitored directories have changed since
+ the last call. It will return always return false on first run.
+ """
+ last_modtime_sum = self._modtime_sum
+
+ # calculate simple sum of file modification times
+ self._modtime_sum = 0
+ for d in self.watch_dirs:
+ for root, dirs, files in os.walk(d):
+ for f in files:
+ abspath = os.path.join(root, f)
+ self._modtime_sum += os.stat(abspath).st_mtime
+
+ if last_modtime_sum is None:
+ # always return false on first run
+ return False
+ else:
+ # otherwise return if file modification sums changed since last run
+ return (last_modtime_sum != self._modtime_sum)
View
46 wok/devserver.py
@@ -1,46 +0,0 @@
-''' Really simple HTTP server for development.
-
-Do *NOT* attempt to use this as anything resembling a production server. It is
-meant to be used as a development test server only.
-
-You might ask, "Why do I need a development server for static pages?" One
-hyphenated modifier: "root-relative." Since wok dumps all of the media files
-in the root output directory, pages that reside inside subdirectories still
-need to access these media files in a unified way.
-
-E.g., if you include `base.css` in your `base.html` template, `base.css` should
-be accessable to any page that uses `base.html`, even if it's a categorized
-page, and thus, goes into a subdirectory. This way, your CSS include tag could
-read `<link type='text/css' href='/base.css' />` (note the '/' in the `href`
-property) and `base.css` can be accessed from anywhere.
-'''
-
-import sys
-import os
-from BaseHTTPServer import HTTPServer
-from SimpleHTTPServer import SimpleHTTPRequestHandler
-
-def run(address=None, port=None, serv_dir=None):
- ''' Run the development server on `address`:`port` and server the
- directory `serv_dir`. (If `serv_dir` is not provided, it will use
- the current working directory.)
- '''
- if not address:
- address = ''
- if not port:
- port = 8000
- else:
- port = int(port)
- if serv_dir:
- os.chdir(serv_dir)
- server_class = HTTPServer
- handler_class = SimpleHTTPRequestHandler
- httpd = server_class((address, port), handler_class)
- socketInfo = httpd.socket.getsockname()
- print "Development HTTP server running on http://%s:%s (Ctrl-c to stop)"\
- %(socketInfo[0], socketInfo[1])
- try:
- httpd.serve_forever()
- except KeyboardInterrupt:
- print "\nbye!"
- exit(0)
View
38 wok/engine.py
@@ -12,7 +12,7 @@
from wok.page import Page, Author
from wok import renderers
from wok import util
-from wok import devserver
+from wok.dev_server import dev_server
class Engine(object):
@@ -29,6 +29,7 @@ class Engine(object):
'url_pattern': '/{category}/{slug}{page}.{ext}',
'url_include_index': True,
}
+ SITE_ROOT = os.getcwd()
def __init__(self, output_lvl=1):
"""
@@ -92,6 +93,33 @@ def __init__(self, output_lvl=1):
# Action!
# -------
+ self.generate_site()
+
+ # Dev server
+ # ----------
+ if cli_options.runserver:
+ ''' Run the dev server if the user said to, and watch the specified
+ directories for changes. The server will regenerate the entire wok
+ site if changes are found after every request.
+ '''
+ output_dir = os.path.join(self.options['output_dir'])
+ host = '' if cli_options.address is None else cli_options.address
+ port = 8000 if cli_options.port is None else cli_options.port
+ server = dev_server(serv_dir=output_dir, host=host, port=port,
+ dir_mon=True,
+ watch_dirs=[
+ self.options['media_dir'],
+ self.options['template_dir'],
+ self.options['content_dir']
+ ],
+ change_handler=self.generate_site)
+ server.run()
+
+ def generate_site(self):
+ ''' Generate the wok site '''
+ orig_dir = os.getcwd()
+ os.chdir(self.SITE_ROOT)
+
self.all_pages = []
self.read_options()
@@ -107,12 +135,7 @@ def __init__(self, output_lvl=1):
self.run_hook('site.done')
- # Dev server
- # ----------
- # Run the dev server after generating pages if the user said to
- if cli_options.runserver:
- devserver.run(cli_options.address, cli_options.port,
- serv_dir=os.path.join(self.options['output_dir']))
+ os.chdir(orig_dir)
def read_options(self):
"""Load options from the config file."""
@@ -333,3 +356,4 @@ def render_site(self):
if __name__ == '__main__':
Engine()
+ exit(0)

0 comments on commit d2e659e

Please sign in to comment.