For the next version of pw (the static site generator that runs this site) I want to support auto-regeneration with a built-in web server. It's handy. Edit the items in your favorite editor, refresh the web browser and that's it. No more asking the thing to regenerate it again and again in the command-line.
Given that pw is written in Python, that should be easy, right? It's just a matter of finding the best libraries for the task.
So, first step: what to use to monitor file changes? My answer is: watchdog. It's fast, it's cross-platform and the only dependency it needs is PyYaml which I'm already using anyway. It does have a few quirks, but more about that in another post.
So, problem solved, watchdog is easy, let's go shopping!.
Well, there is still the web server bit, but since Python does ship with a SimpleHTTPServer class, it should be a non-issue. It turns out that no, it isn't...
SimpleHTTPServer: first try
When you start python with:
python -m SimpleHTTPServer [port] it starts
the web server, with the current directory as the root. It works well for static
files. The problem is: That root directory is hardcoded. And not just hardcoded
in the constructor but in a method named translate-path. WTF?
An option would be to just use
os.chdir(root_directory) before starting
the web server, but that strikes me as a poor and inelegant solution. It would
probably be ok in another application, but in a static generator? No way.
But we can probably subclass SimpleHTTPRequestHandler and add our path...
class PathAbleRequestHandler(BaseHTTPRequestHandler): def __init__(self, path, request, client_address, server): + self.path = path # add it to the constructor ... def translate_path(self, path): ... + path = self.path # use it instead of os.getcwd() ...
Well, no, that won't work. It turns out that TCPServer's constructor expects a RequestHandler class, not an instance. Then, in its (inherited from BaseServer) method finish_request it instantiates the request handler itself. Of course, it won't use our new signature, so the whole thing will fail.
But... we can also subclass TCPServer and implement finish_request, right?
Yes, yes we can, but we shouldn't use the constructor to inject our path, because it may change in the future. Here is that constructor:
def __init__(self, server_address, RequestHandlerClass): """Constructor. May be extended, do not override.""" ...
SimpleHTTPServer: second try
There is still a way to fix it though... we can use a static class variable in our request handler, and keep the constructor untouched:
class PathAbleRequestHandler(BaseHTTPRequestHandler): path = '.' def translate_path(self, path): ... + path = self.path # use it instead of os.getcwd() ... # Change the path manually from outside: Handler = PathAbleRequestHandler Handler.path = "/this/is/not/os.cwd/" # Pass the class to TCPServer, keeping the original constructor: httpd = TCPServer(("", port), Handler) ...
That works. It's not exactly beautiful, and we have to copy
almost unchanged, but it works.
SimpleHTTPServer: third try
After thinking for a while, I've decided not to use the solution above. Instead,
I went for a lazy approach: actually run
python -m SimpleHTTPServer, but in a new
process started in the desired root.
Here is the implementation:
if sys.version_info < (3, 0): HTTPDMODULE = "SimpleHTTPServer" else: HTTPDMODULE = "http.server" class WebServer(object): def __init__(self, root = '.', port = 8080): self.root = root self.port = port self.httpd = None def start(self): """Start the web server process.""" if not self.httpd: try: self.httpd = subprocess.Popen( "%s -m %s %s" % (sys.executable, HTTPDMODULE, self.port), cwd = self.root, ) except OSError as err: ... error handling ... def restart(self, root = None, port = None): """Restart the web server, optionally changing root and port.""" if root: self.root = root if port: self.port = port self.stop() self.spawn() def stop(self): """Stop the httpd process if it's running.""" if self.httpd: self.httpd.terminate() self.httpd = None
It's simple, it works, and I don't have to deal with Python's stdlib idiosyncrasies. It's also trivial to restart the webserver on another port/path when the user configuration file changes. Finally, it probably means better performance on dual-core machines, since now the OS can distribute load.
No headaches anymore.
Today, rgrau asked an interesting question on irc, which I'll replicate here.
Given a list such as:
'((#1="foo" "bar") . #1#), is it possible to replace
the value of
"foo" while keeping the reference to the first value in the list?
Unfortunately, the obvious approach won't work:
$ (setq x '((#1="foo" "bar") . #1#)) => (("foo" "bar") . "foo") $ (setf (caar x) "quux") ;; use setf and call it a day => "quux" $ (print x) => (("quux" "bar") . "foo")
It still keeps the reference to
"foo", which makes sense, since we haven't
asked it to do otherwise. We can't easily mutate it and keep the reference, but
we can use read syntax again mixed with quasiquotation-magic to change just what
$ (setq x '((#1="foo" "bar") . #1#)) => (("foo" "bar") . "foo") $ (setq x `((#1="quux" ,(cadar x)) . #1#)) => (("quux" "bar") . "quux")
What if we want to do the same on a list of N elements, replacing only the first one and keeping the reference? Let's try again with quasiquotation:
$ (setq x '((#1="foo" "bar" "baz") . #1#)) => (("foo" "bar" "baz") . "foo") $ (setq x `(#1=,(cons "quux" (cdar x)) . #1#)) => (("quux" "bar" "baz") "quux" "bar" "baz")
Ooops, that didn't work.
#1# is now referencing all the evaluation from the
unquoted part as the first element. Fortunately, we can also fix this, while keeping
quux at the place it belongs:
$ (setq x '((#1="foo" "bar" "baz") . #1#)) => (("foo" "bar" "baz") . "foo") $ (setq x `((#1="quux" ,@(cdar x)) . #1#)) => (("quux" "bar" "baz") . "quux")
Today, I've been playing Violet, by Jeremy Freese.
It's no wonder that it won the 2008 IfComp, what a wonderful ride.
Here is the blurb:
Calm down. All you have to do is write a thousand words and everything will be fine. And you have all day, except it's already noon.
Violet is a single-room, short story that takes about 2 hours to complete. You play a student who must complete his dissertation but is constantly being distracted by various means.
The complete thing is narrated from the voice of Violet, the student's girlfriend
who encourages him to write. Even actions such as
about result in some
quirky - often hilarious - response from her.
What I liked
The first thing I noticed after playing the game is that Violet is never, not even once, physically described. I like that. Her dialogue doesn't ever get annoying or controlling and the procedurally-generated pet names (she calls you 'wallaroo', 'weet-bix' and a plethora of others) keep it fresh.
Despite the fact that there are no NPCs, there is plenty of information given to the player about the characters. As actions are commited or objects examined, Violet will comment on them, often leaving the room walls to expand descriptions. That made the relationship between the protagonist and Violet more palpable to me.
Although the puzzles are solved in this single room, half of them exist because of what's currently happening outside it (ie: people talking whose voices can be heard from the room). Since this gives Violet more opportunities to go offtopic, and "leave the room", it helps building empathy and inserting bits of backstory into play.
Finally, the implementation is rock solid. All the actions I've tried resulted in a proper, well-written response. An often genuinely funny response.
What I didn't like
First (with one exception that I'm aware of), the puzzles don't have multiple solutions.
Wouldn't be necessarily bad, except that Violet has the kind of puzzles that lean themselves and would benefit from them, given all the items in the room. It hurts replayability, which is quite low.
Secondly, I felt the ending was an afterthought but I won't talk about why. If you want spoilers, go play Violet right now!