Skip to content

Commit 506661a

Browse files
committed
RTParser refactoring
1 parent a4bebd3 commit 506661a

File tree

2 files changed

+106
-103
lines changed

2 files changed

+106
-103
lines changed

Diff for: rtkit/parser.py

+101
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
from itertools import ifilterfalse
2+
import re
3+
from rtkit import comment
4+
5+
6+
class RTParser(object):
7+
HEADER = re.compile(r'^RT/(?P<v>.+)\s+(?P<s>(?P<i>\d+).+)')
8+
COMMENT = re.compile(r'^#\s+.+$')
9+
SECTION = re.compile(r'^--', re.M | re.U)
10+
11+
@classmethod
12+
def parse(cls, body, decoder):
13+
r""" Return a list of RFC5322-like section
14+
>>> decode = RTParser.decode
15+
>>> body = '''
16+
...
17+
... # c1
18+
... spam: 1
19+
... ham: 2,
20+
... 3
21+
... eggs:'''
22+
>>> RTParser.parse(body, decode)
23+
[[('spam', '1'), ('ham', '2, 3'), ('eggs', '')]]
24+
>>> RTParser.parse('# spam 1 does not exist.', decode)
25+
Traceback (most recent call last):
26+
...
27+
RTNotFoundError: spam 1 does not exist
28+
>>> RTParser.parse('# Spam 1 created.', decode)
29+
[[('id', 'spam/1')]]
30+
>>> RTParser.parse('No matching results.', decode)
31+
[]
32+
>>> decode = RTParser.decode_comment
33+
>>> RTParser.parse('# spam: 1\n# ham: 2', decode)
34+
[[('spam', '1'), ('ham', '2')]]
35+
"""
36+
section = cls.build(body)
37+
if len(section) == 1:
38+
try:
39+
comment.check(section[0])
40+
except comment.RTNoMatch:
41+
section = ''
42+
except comment.RTCreated as e:
43+
section = [['id: {0}'.format(e.id)]]
44+
return [decoder(lines) for lines in section]
45+
46+
@classmethod
47+
def decode(cls, lines):
48+
""" Return a list of 2-tuples parsing 'k: v' and skipping comments
49+
>>> RTParser.decode(['# c1: c2', 'spam: 1', 'ham: 2, 3', 'eggs:'])
50+
[('spam', '1'), ('ham', '2, 3'), ('eggs', '')]
51+
>>> RTParser.decode(['<!DOCTYPE HTML PUBLIC >', '<html><head>',])
52+
[]
53+
"""
54+
try:
55+
lines = ifilterfalse(cls.COMMENT.match, lines)
56+
return [(k, v.strip(' ')) for k, v in [l.split(':', 1) for l in lines]]
57+
except ValueError:
58+
return []
59+
60+
@classmethod
61+
def decode_comment(cls, lines):
62+
""" Return a list of 2-tuples parsing '# k: v'
63+
>>> RTParser.decode_comment(['# c1: c2', 'spam: 1', 'ham: 2, 3', 'eggs:'])
64+
[('c1', 'c2')]
65+
>>>
66+
"""
67+
lines = filter(cls.COMMENT.match, lines)
68+
return [(k.strip('# '), v.strip(' ')) for k, v in [l.split(':', 1) for l in lines]]
69+
70+
@classmethod
71+
def build(cls, body):
72+
""" Build logical lines from a RFC5322-like string
73+
>>> body = '''RT/1.2.3 200 Ok
74+
...
75+
... # a
76+
... b
77+
... spam: 1
78+
...
79+
... ham: 2,
80+
... 3
81+
... --
82+
... # c
83+
... spam: 4
84+
... ham:
85+
... --
86+
... a -- b
87+
... '''
88+
>>> RTParser.build(body)
89+
[['# a b', 'spam: 1', 'ham: 2, 3'], ['# c', 'spam: 4', 'ham:'], ['a -- b']]
90+
"""
91+
def build_section(section):
92+
logic_lines = []
93+
for line in filter(None, section.splitlines()):
94+
if cls.HEADER.match(line):
95+
continue
96+
if line[0].isspace():
97+
logic_lines[-1] += ' ' + line.strip(' ')
98+
else:
99+
logic_lines.append(line)
100+
return logic_lines
101+
return [build_section(b) for b in cls.SECTION.split(body)]

Diff for: rtkit/resource.py

+5-103
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,8 @@
1-
from itertools import ifilterfalse
21
import logging
3-
import re
42
import errors
53
import forms
6-
import comment
74
from urllib2 import Request, HTTPError
5+
from rtkit.parser import RTParser
86

97

108
class RTResource(object):
@@ -40,10 +38,6 @@ def request(self, method, path=None, payload=None, headers=None):
4038

4139

4240
class RTResponse(object):
43-
HEADER = re.compile(r'^RT/(?P<v>.+)\s+(?P<s>(?P<i>\d+).+)')
44-
COMMENT = re.compile(r'^#\s+.+$')
45-
SECTION = re.compile(r'^--', re.M | re.U)
46-
4741
def __init__(self, request, response):
4842
self.headers = response.headers
4943
self.body = response.read()
@@ -53,7 +47,7 @@ def __init__(self, request, response):
5347
self.logger.info(request.get_method())
5448
self.logger.info(request.get_full_url())
5549
self.logger.debug('HTTP_STATUS: {0}'.format(self.status))
56-
r = self.HEADER.match(self.body)
50+
r = RTParser.HEADER.match(self.body)
5751
if r:
5852
self.status = r.group('s')
5953
self.status_int = int(r.group('i'))
@@ -63,105 +57,13 @@ def __init__(self, request, response):
6357
self.status_int = 500
6458
self.logger.debug('%r' % self.body)
6559
try:
66-
decoder = self._decode
60+
decoder = RTParser.decode
6761
if self.status_int == 409:
68-
decoder = self._decode_comment
69-
self.parsed = self._parse(self.body, decoder)
62+
decoder = RTParser.decode_comment
63+
self.parsed = RTParser.parse(self.body, decoder)
7064
except errors.RTResourceError as e:
7165
self.parsed = []
7266
self.status_int = e.status_int
7367
self.status = '{0} {1}'.format(e.status_int, e.msg)
7468
self.logger.debug('RESOURCE_STATUS: {0}'.format(self.status))
7569
self.logger.info(self.parsed)
76-
77-
@classmethod
78-
def _parse(cls, body, decoder):
79-
r""" Return a list of RFC5322-like section
80-
>>> decode = RTResponse._decode
81-
>>> body = '''
82-
...
83-
... # c1
84-
... spam: 1
85-
... ham: 2,
86-
... 3
87-
... eggs:'''
88-
>>> RTResponse._parse(body, decode)
89-
[[('spam', '1'), ('ham', '2, 3'), ('eggs', '')]]
90-
>>> RTResponse._parse('# spam 1 does not exist.', decode)
91-
Traceback (most recent call last):
92-
...
93-
RTNotFoundError: spam 1 does not exist
94-
>>> RTResponse._parse('# Spam 1 created.', decode)
95-
[[('id', 'spam/1')]]
96-
>>> RTResponse._parse('No matching results.', decode)
97-
[]
98-
>>> decode = RTResponse._decode_comment
99-
>>> RTResponse._parse('# spam: 1\n# ham: 2', decode)
100-
[[('spam', '1'), ('ham', '2')]]
101-
"""
102-
section = cls._build(body)
103-
if len(section) == 1:
104-
try:
105-
comment.check(section[0])
106-
except comment.RTNoMatch:
107-
section = ''
108-
except comment.RTCreated as e:
109-
section = [['id: {0}'.format(e.id)]]
110-
return [decoder(lines) for lines in section]
111-
112-
@classmethod
113-
def _decode(cls, lines):
114-
""" Return a list of 2-tuples parsing 'k: v' and skipping comments
115-
>>> RTResponse._decode(['# c1: c2', 'spam: 1', 'ham: 2, 3', 'eggs:'])
116-
[('spam', '1'), ('ham', '2, 3'), ('eggs', '')]
117-
>>> RTResponse._decode(['<!DOCTYPE HTML PUBLIC >', '<html><head>',])
118-
[]
119-
"""
120-
try:
121-
lines = ifilterfalse(cls.COMMENT.match, lines)
122-
return [(k, v.strip(' ')) for k, v in [l.split(':', 1) for l in lines]]
123-
except ValueError:
124-
return []
125-
126-
@classmethod
127-
def _decode_comment(cls, lines):
128-
""" Return a list of 2-tuples parsing '# k: v'
129-
>>> RTResponse._decode_comment(['# c1: c2', 'spam: 1', 'ham: 2, 3', 'eggs:'])
130-
[('c1', 'c2')]
131-
>>>
132-
"""
133-
lines = filter(cls.COMMENT.match, lines)
134-
return [(k.strip('# '), v.strip(' ')) for k, v in [l.split(':', 1) for l in lines]]
135-
136-
@classmethod
137-
def _build(cls, body):
138-
""" Build logical lines from a RFC5322-like string
139-
>>> body = '''RT/1.2.3 200 Ok
140-
...
141-
... # a
142-
... b
143-
... spam: 1
144-
...
145-
... ham: 2,
146-
... 3
147-
... --
148-
... # c
149-
... spam: 4
150-
... ham:
151-
... --
152-
... a -- b
153-
... '''
154-
>>> RTResponse._build(body)
155-
[['# a b', 'spam: 1', 'ham: 2, 3'], ['# c', 'spam: 4', 'ham:'], ['a -- b']]
156-
"""
157-
def build_section(section):
158-
logic_lines = []
159-
for line in filter(None, section.splitlines()):
160-
if cls.HEADER.match(line):
161-
continue
162-
if line[0].isspace():
163-
logic_lines[-1] += ' ' + line.strip(' ')
164-
else:
165-
logic_lines.append(line)
166-
return logic_lines
167-
return [build_section(b) for b in cls.SECTION.split(body)]

0 commit comments

Comments
 (0)