diff options
| -rw-r--r-- | libpathod/language/base.py | 63 | ||||
| -rw-r--r-- | libpathod/language/http.py | 53 | ||||
| -rw-r--r-- | libpathod/language/websockets.py | 15 | ||||
| -rw-r--r-- | test/test_language_base.py (renamed from test/test_language.py) | 70 | ||||
| -rw-r--r-- | test/test_language_http.py | 8 | 
5 files changed, 113 insertions, 96 deletions
| diff --git a/libpathod/language/base.py b/libpathod/language/base.py index eceeae26..f6530d75 100644 --- a/libpathod/language/base.py +++ b/libpathod/language/base.py @@ -213,19 +213,6 @@ Offset = pp.MatchFirst(  ) -class Raw(_Token): -    @classmethod -    def expr(klass): -        e = pp.Literal("r").suppress() -        return e.setParseAction(lambda x: klass(*x)) - -    def spec(self): -        return "r" - -    def freeze(self, settings): -        return self - -  class _Component(_Token):      """          A value component of the primary specification of an HTTP message. @@ -386,7 +373,10 @@ class PathodSpec(_Token):          return PathodSpec(ValueLiteral(f.encode("string_escape"))) -class Path(_Component): +class SimpleValue(_Component): +    """ +        A simple value - i.e. one without a preface. +    """      def __init__(self, value):          if isinstance(value, basestring):              value = ValueLiteral(value) @@ -406,10 +396,13 @@ class Path(_Component):          return "%s"%(self.value.spec())      def freeze(self, settings): -        return Path(self.value.freeze(settings)) +        return self.__class__(self.value.freeze(settings)) -class _Token(_Component): +class CaselessLiteral(_Component): +    """ +        A caseless token that can take only one value. +    """      def __init__(self, value):          self.value = value @@ -429,28 +422,12 @@ class _Token(_Component):          return self -class WS(_Token): -    TOK = "ws" - - -class WF(_Token): -    TOK = "wf" - - -class Method(_Component): -    methods = [ -        "get", -        "head", -        "post", -        "put", -        "delete", -        "options", -        "trace", -        "connect", -    ] - +class OptionsOrValue(_Component): +    """ +        Can be any of a specified set of options, or a value specifier. +    """      def __init__(self, value): -        # If it's a string, we were passed one of the methods, so we upper-case +        # If it's a string, we were passed one of the options, so we upper-case          # it to be canonical. The user can specify a different case by using a          # string value literal.          if isinstance(value, basestring): @@ -459,7 +436,7 @@ class Method(_Component):      @classmethod      def expr(klass): -        parts = [pp.CaselessLiteral(i) for i in klass.methods] +        parts = [pp.CaselessLiteral(i) for i in klass.options]          m = pp.MatchFirst(parts)          spec = m | Value.copy()          spec = spec.setParseAction(lambda x: klass(*x)) @@ -472,12 +449,12 @@ class Method(_Component):      def spec(self):          s = self.value.spec() -        if s[1:-1].lower() in self.methods: +        if s[1:-1].lower() in self.options:              s = s[1:-1].lower()          return "%s"%s      def freeze(self, settings): -        return Method(self.value.freeze(settings)) +        return self.__class__(self.value.freeze(settings))  class Code(_Component): @@ -496,7 +473,7 @@ class Code(_Component):          return "%s"%(self.code)      def freeze(self, settings): -        return Code(self.code) +        return self  class Reason(_Component): @@ -653,10 +630,6 @@ class _Message(object):              return l[0]      @property -    def raw(self): -        return bool(self.tok(Raw)) - -    @property      def actions(self):          return self.toks(_Action) diff --git a/libpathod/language/http.py b/libpathod/language/http.py index df5d8ba8..7966b914 100644 --- a/libpathod/language/http.py +++ b/libpathod/language/http.py @@ -8,6 +8,31 @@ from netlib import http_status  from . import base, generators, exceptions +class WS(base.CaselessLiteral): +    TOK = "ws" + + +class Raw(base.CaselessLiteral): +    TOK = "r" + + +class Path(base.SimpleValue): +    pass + + +class Method(base.OptionsOrValue): +    options = [ +        "get", +        "head", +        "post", +        "put", +        "delete", +        "options", +        "trace", +        "connect", +    ] + +  def get_header(val, headers):      """          Header keys may be Values, so we have to "generate" them as we try the @@ -22,6 +47,10 @@ def get_header(val, headers):  class _HTTPMessage(base._Message):      version = "HTTP/1.1" +    @property +    def raw(self): +        return bool(self.tok(Raw)) +      @abc.abstractmethod      def preamble(self, settings): # pragma: no cover @@ -47,14 +76,14 @@ class Response(_HTTPMessage):          base.InjectAt,          base.ShortcutContentType,          base.ShortcutLocation, -        base.Raw, +        Raw,          base.Reason      )      logattrs = ["code", "reason", "version", "body"]      @property      def ws(self): -        return self.tok(base.WS) +        return self.tok(WS)      @property      def code(self): @@ -129,7 +158,7 @@ class Response(_HTTPMessage):              [                  pp.MatchFirst(                      [ -                        base.WS.expr() + pp.Optional( +                        WS.expr() + pp.Optional(                              base.Sep + base.Code.expr()                          ),                          base.Code.expr(), @@ -154,22 +183,22 @@ class Request(_HTTPMessage):          base.InjectAt,          base.ShortcutContentType,          base.ShortcutUserAgent, -        base.Raw, +        Raw,          base.PathodSpec,      )      logattrs = ["method", "path", "body"]      @property      def ws(self): -        return self.tok(base.WS) +        return self.tok(WS)      @property      def method(self): -        return self.tok(base.Method) +        return self.tok(Method)      @property      def path(self): -        return self.tok(base.Path) +        return self.tok(Path)      @property      def pathodspec(self): @@ -191,7 +220,7 @@ class Request(_HTTPMessage):              if not self.method:                  tokens.insert(                      1, -                    base.Method("get") +                    Method("get")                  )              for i in netlib.websockets.client_handshake_headers().lst:                  if not get_header(i[0], self.headers): @@ -232,14 +261,14 @@ class Request(_HTTPMessage):              [                  pp.MatchFirst(                      [ -                        base.WS.expr() + pp.Optional( -                            base.Sep + base.Method.expr() +                        WS.expr() + pp.Optional( +                            base.Sep + Method.expr()                          ), -                        base.Method.expr(), +                        Method.expr(),                      ]                  ),                  base.Sep, -                base.Path.expr(), +                Path.expr(),                  pp.ZeroOrMore(base.Sep + atom)              ]          ) diff --git a/libpathod/language/websockets.py b/libpathod/language/websockets.py index 29b7311c..7ec4a2b1 100644 --- a/libpathod/language/websockets.py +++ b/libpathod/language/websockets.py @@ -3,6 +3,19 @@ import netlib.websockets  import contrib.pyparsing as pp  from . import base, generators +""" +    wf:ctext:b'foo' +    wf:c15:r'foo' +    wf:fin:rsv1:rsv2:rsv3:mask +    wf:-fin:-rsv1:-rsv2:-rsv3:-mask +    wf:p234 +    wf:m"mask" +""" + + +class WF(base.CaselessLiteral): +    TOK = "wf" +  class WebsocketFrame(base._Message):      comps = ( @@ -19,7 +32,7 @@ class WebsocketFrame(base._Message):          atom = pp.MatchFirst(parts)          resp = pp.And(              [ -                base.WF.expr(), +                WF.expr(),                  base.Sep,                  pp.ZeroOrMore(base.Sep + atom)              ] diff --git a/test/test_language.py b/test/test_language_base.py index ffa5e82c..10f11d42 100644 --- a/test/test_language.py +++ b/test/test_language_base.py @@ -9,11 +9,12 @@ def parse_request(s):      return language.parse_requests(s)[0] -class TestWS: -    def test_expr(self): -        v = base.WS("foo") -        assert v.expr() -        assert v.values(language.Settings()) +def test_caseless_literal(): +    class CL(base.CaselessLiteral): +        TOK = "foo" +    v = CL("foo") +    assert v.expr() +    assert v.values(language.Settings())  class TestValueNakedLiteral: @@ -166,11 +167,11 @@ class TestMisc:          assert base.Value.parseString('"val"')[0].val == "val"          assert base.Value.parseString('"\'val\'"')[0].val == "'val'" -    def test_path(self): -        e = base.Path.expr() +    def test_simplevalue(self): +        e = base.SimpleValue.expr()          assert e.parseString('"/foo"')[0].value.val == "/foo" -        v = base.Path("/foo") +        v = base.SimpleValue("/foo")          assert v.value.val == "/foo"          v = e.parseString("@100")[0] @@ -182,32 +183,6 @@ class TestMisc:          s = v.spec()          assert s == v.expr().parseString(s)[0].spec() -    def test_method(self): -        e = base.Method.expr() -        assert e.parseString("get")[0].value.val == "GET" -        assert e.parseString("'foo'")[0].value.val == "foo" -        assert e.parseString("'get'")[0].value.val == "get" - -        assert e.parseString("get")[0].spec() == "get" -        assert e.parseString("'foo'")[0].spec() == "'foo'" - -        s = e.parseString("get")[0].spec() -        assert s == e.parseString(s)[0].spec() - -        s = e.parseString("'foo'")[0].spec() -        assert s == e.parseString(s)[0].spec() - -        v = e.parseString("@100")[0] -        v2 = v.freeze({}) -        v3 = v2.freeze({}) -        assert v2.value.val == v3.value.val - -    def test_raw(self): -        e = base.Raw.expr().parseString("r")[0] -        assert e -        assert e.spec() == "r" -        assert e.freeze({}).spec() == "r" -      def test_body(self):          e = base.Body.expr()          v = e.parseString("b'foo'")[0] @@ -470,3 +445,30 @@ class TestPauses:      def test_freeze(self):          l = base.PauseAt("r", 5)          assert l.freeze({}).spec() == l.spec() + + +def test_options_or_value(): +    class TT(base.OptionsOrValue): +        options = [ +            "one", +            "two", +            "three" +        ] +    e = TT.expr() +    assert e.parseString("one")[0].value.val == "ONE" +    assert e.parseString("'foo'")[0].value.val == "foo" +    assert e.parseString("'get'")[0].value.val == "get" + +    assert e.parseString("one")[0].spec() == "one" +    assert e.parseString("'foo'")[0].spec() == "'foo'" + +    s = e.parseString("one")[0].spec() +    assert s == e.parseString(s)[0].spec() + +    s = e.parseString("'foo'")[0].spec() +    assert s == e.parseString(s)[0].spec() + +    v = e.parseString("@100")[0] +    v2 = v.freeze({}) +    v3 = v2.freeze({}) +    assert v2.value.val == v3.value.val diff --git a/test/test_language_http.py b/test/test_language_http.py index 50473339..f2528da4 100644 --- a/test/test_language_http.py +++ b/test/test_language_http.py @@ -129,15 +129,15 @@ class TestRequest:          r = parse_request('ws:/path/')          res = r.resolve(language.Settings())          assert res.method.string().lower() == "get" -        assert res.tok(base.Path).value.val == "/path/" -        assert res.tok(base.Method).value.val.lower() == "get" +        assert res.tok(http.Path).value.val == "/path/" +        assert res.tok(http.Method).value.val.lower() == "get"          assert http.get_header("Upgrade", res.headers).value.val == "websocket"          r = parse_request('ws:put:/path/')          res = r.resolve(language.Settings())          assert r.method.string().lower() == "put" -        assert res.tok(base.Path).value.val == "/path/" -        assert res.tok(base.Method).value.val.lower() == "put" +        assert res.tok(http.Path).value.val == "/path/" +        assert res.tok(http.Method).value.val.lower() == "put"          assert http.get_header("Upgrade", res.headers).value.val == "websocket" | 
