From 7d74f75f6d422189b26c2977368aca47ac329cfa Mon Sep 17 00:00:00 2001
From: Aldo Cortesi <aldo@nullcube.com>
Date: Sun, 28 Oct 2012 17:07:16 +1300
Subject: [PATCH] Split Code and Reason into separate _Component objects.

---
 libpathod/language.py | 62 +++++++++++++++++++++++++++----------------
 test/test_language.py | 20 +++++++++-----
 2 files changed, 52 insertions(+), 30 deletions(-)

diff --git a/libpathod/language.py b/libpathod/language.py
index 446c18233..671a6ec74 100644
--- a/libpathod/language.py
+++ b/libpathod/language.py
@@ -89,7 +89,6 @@ DATATYPES = dict(
 )
 
 
-#v_integer = pp.Regex(r"[+-]?\d+")\
 v_integer = pp.Regex(r"\d+")\
     .setName("integer")\
     .setParseAction(lambda toks: int(toks[0]))
@@ -541,24 +540,36 @@ class InjectAt(_Action):
             )
 
 
-class Code:
-    def __init__(self, code, msg=None):
-        self.code, self.msg = code, msg
-        if msg is None:
-            self.msg = ValueLiteral(http_status.RESPONSES.get(self.code, "Unknown code"))
+class Code(_Component):
+    def __init__(self, code):
+        self.code = str(code)
 
     def accept(self, settings, r):
-        r.code = self.code
-        r.msg = self.msg.get_generator(settings)
+        r.code = self
 
     @classmethod
     def expr(klass):
-        e = v_integer
-        e = e + pp.Optional(
-            Value
-        )
+        e = v_integer.copy()
         return e.setParseAction(lambda x: klass(*x))
 
+    def values(self, settings):
+        return [LiteralGenerator(self.code)]
+
+
+class Reason(_Component):
+    def __init__(self, value):
+        self.value = value
+
+    def accept(self, settings, r):
+        r.reason = self
+
+    @classmethod
+    def expr(klass):
+        e = Value.copy()
+        return e.setParseAction(lambda x: klass(*x))
+
+    def values(self, settings):
+        return [self.value.get_generator(settings)]
 
 
 class Message:
@@ -673,8 +684,8 @@ class Message:
             # Careful not to log any VALUE specs without sanitizing them first. We truncate at 1k.
             if hasattr(v, "values"):
                 v = [x[:TRUNCATE] for x in v.values(settings)]
-                v = "".join(v)
-            if hasattr(v, "__len__"):
+                v = "".join(v).encode("string_escape")
+            elif hasattr(v, "__len__"):
                 v = v[:TRUNCATE]
                 v = v.encode("string_escape")
             ret[i] = v
@@ -694,14 +705,21 @@ class Response(Message):
         ShortcutLocation,
         Raw
     )
-    logattrs = ["code", "version", "body"]
+    logattrs = ["code", "reason", "version", "body"]
     def __init__(self):
         Message.__init__(self)
         self.code = None
-        self.msg = None
+        self.reason = None
 
     def preamble(self, settings):
-        return [self.version, " ", str(self.code), " ", self.msg]
+        l = [self.version, " "]
+        l.extend(self.code.values(settings))
+        l.append(" ")
+        if self.reason:
+            l.extend(self.reason.values(settings))
+        else:
+            l.append(LiteralGenerator(http_status.RESPONSES.get(int(self.code.code), "Unknown code")))
+        return l
 
     @classmethod
     def expr(klass):
@@ -710,16 +728,14 @@ class Response(Message):
         resp = pp.And(
             [
                 Code.expr(),
+                pp.Optional(Reason.expr()),
                 pp.ZeroOrMore(Sep + atom)
             ]
         )
         return resp
 
-    def __str__(self):
-        parts = [
-            "%s %s"%(self.code, self.msg[:])
-        ]
-        return "\n".join(parts)
+    def string(self, settings):
+        return "%s"%self.code
 
 
 class Request(Message):
@@ -796,7 +812,7 @@ class CraftedResponse(Response):
 class PathodErrorResponse(Response):
     def __init__(self, msg, body=None):
         Response.__init__(self)
-        self.code = 800
+        self.code = Code("800")
         self.msg = LiteralGenerator(msg)
         self.body = Body(ValueLiteral("pathod error: " + (body or msg)))
         self.headers = [
diff --git a/test/test_language.py b/test/test_language.py
index aba672744..9391ddda8 100644
--- a/test/test_language.py
+++ b/test/test_language.py
@@ -182,11 +182,12 @@ class TestMisc:
     def test_code(self):
         e = language.Code.expr()
         v = e.parseString("200")[0]
-        assert v.code == 200
+        assert v.string() == "200"
 
+    def _test_reason(self):
         v = e.parseString("404'msg'")[0]
-        assert v.code == 404
-        assert v.msg.val == "msg"
+        assert v.code.string() == "404"
+        assert v.reason == "msg"
 
         r = e.parseString("200'foo'")[0]
         assert r.msg.val == "foo"
@@ -499,18 +500,23 @@ class TestResponse:
         p = tutils.test_data.path("data")
         d = dict(staticdir=p)
         r = language.parse_response(d, "+response")
-        assert r.code == 202
+        assert r.code.string() == "202"
 
     def test_response(self):
         r = language.parse_response({}, "400'msg'")
-        assert r.code == 400
-        assert r.msg == "msg"
+        assert r.code.string() == "400"
+        assert r.reason.string() == "msg"
 
         r = language.parse_response({}, "400'msg':b@100b")
-        assert r.msg == "msg"
+        assert r.reason.string() == "msg"
         assert r.body.values({})
         assert str(r)
 
+        r = language.parse_response({}, "200")
+        assert r.code.string() == "200"
+        assert not r.reason
+        assert "OK" in [i[:] for i in r.preamble({})]
+
     def test_render(self):
         s = cStringIO.StringIO()
         r = language.parse_response({}, "400'msg'")