Show More
@@ -699,26 +699,16 b' wire protocol reserve the right to allow' | |||||
699 | formatting strings with additional formatters, hence why ``%%`` is |
|
699 | formatting strings with additional formatters, hence why ``%%`` is | |
700 | required to represent the literal ``%``. |
|
700 | required to represent the literal ``%``. | |
701 |
|
701 | |||
702 | The raw frame consists of a series of data structures representing |
|
702 | The frame payload consists of a CBOR array of CBOR maps. Each map | |
703 | textual atoms to print. Each atom begins with a struct defining the |
|
703 | defines an *atom* of text data to print. Each *atom* has the following | |
704 | size of the data that follows: |
|
704 | bytestring keys: | |
705 |
|
705 | |||
706 | * A 16-bit little endian unsigned integer denoting the length of the |
|
706 | msg | |
707 | formatting string. |
|
707 | (bytestring) The formatting string. Content MUST be ASCII. | |
708 | * An 8-bit unsigned integer denoting the number of label strings |
|
708 | args (optional) | |
709 | that follow. |
|
709 | Array of bytestrings defining arguments to the formatting string. | |
710 | * An 8-bit unsigned integer denoting the number of formatting string |
|
710 | labels (optional) | |
711 | arguments strings that follow. |
|
711 | Array of bytestrings defining labels to apply to this atom. | |
712 | * An array of 8-bit unsigned integers denoting the lengths of |
|
|||
713 | *labels* data. |
|
|||
714 | * An array of 16-bit unsigned integers denoting the lengths of |
|
|||
715 | formatting strings. |
|
|||
716 | * The formatting string, encoded as UTF-8. |
|
|||
717 | * 0 or more ASCII strings defining labels to apply to this atom. |
|
|||
718 | * 0 or more UTF-8 strings that will be used as arguments to the |
|
|||
719 | formatting string. |
|
|||
720 |
|
||||
721 | TODO use ASCII for formatting string. |
|
|||
722 |
|
712 | |||
723 | All data to be printed MUST be encoded into a single frame: this frame |
|
713 | All data to be printed MUST be encoded into a single frame: this frame | |
724 | does not support spanning data across multiple frames. |
|
714 | does not support spanning data across multiple frames. |
@@ -395,7 +395,8 b' def createerrorframe(stream, requestid, ' | |||||
395 | flags=flags, |
|
395 | flags=flags, | |
396 | payload=msg) |
|
396 | payload=msg) | |
397 |
|
397 | |||
398 |
def createtextoutputframe(stream, requestid, atoms |
|
398 | def createtextoutputframe(stream, requestid, atoms, | |
|
399 | maxframesize=DEFAULT_MAX_FRAME_SIZE): | |||
399 | """Create a text output frame to render text to people. |
|
400 | """Create a text output frame to render text to people. | |
400 |
|
401 | |||
401 | ``atoms`` is a 3-tuple of (formatting string, args, labels). |
|
402 | ``atoms`` is a 3-tuple of (formatting string, args, labels). | |
@@ -405,15 +406,9 b' def createtextoutputframe(stream, reques' | |||||
405 | formatters to be applied at rendering time. In terms of the ``ui`` |
|
406 | formatters to be applied at rendering time. In terms of the ``ui`` | |
406 | class, each atom corresponds to a ``ui.write()``. |
|
407 | class, each atom corresponds to a ``ui.write()``. | |
407 | """ |
|
408 | """ | |
408 | bytesleft = DEFAULT_MAX_FRAME_SIZE |
|
409 | atomdicts = [] | |
409 | atomchunks = [] |
|
|||
410 |
|
410 | |||
411 | for (formatting, args, labels) in atoms: |
|
411 | for (formatting, args, labels) in atoms: | |
412 | if len(args) > 255: |
|
|||
413 | raise ValueError('cannot use more than 255 formatting arguments') |
|
|||
414 | if len(labels) > 255: |
|
|||
415 | raise ValueError('cannot use more than 255 labels') |
|
|||
416 |
|
||||
417 | # TODO look for localstr, other types here? |
|
412 | # TODO look for localstr, other types here? | |
418 |
|
413 | |||
419 | if not isinstance(formatting, bytes): |
|
414 | if not isinstance(formatting, bytes): | |
@@ -425,8 +420,8 b' def createtextoutputframe(stream, reques' | |||||
425 | if not isinstance(label, bytes): |
|
420 | if not isinstance(label, bytes): | |
426 | raise ValueError('must use bytes for labels') |
|
421 | raise ValueError('must use bytes for labels') | |
427 |
|
422 | |||
428 |
# Formatting string must be |
|
423 | # Formatting string must be ASCII. | |
429 |
formatting = formatting.decode(r' |
|
424 | formatting = formatting.decode(r'ascii', r'replace').encode(r'ascii') | |
430 |
|
425 | |||
431 | # Arguments must be UTF-8. |
|
426 | # Arguments must be UTF-8. | |
432 | args = [a.decode(r'utf-8', r'replace').encode(r'utf-8') for a in args] |
|
427 | args = [a.decode(r'utf-8', r'replace').encode(r'utf-8') for a in args] | |
@@ -435,36 +430,23 b' def createtextoutputframe(stream, reques' | |||||
435 | labels = [l.decode(r'ascii', r'strict').encode(r'ascii') |
|
430 | labels = [l.decode(r'ascii', r'strict').encode(r'ascii') | |
436 | for l in labels] |
|
431 | for l in labels] | |
437 |
|
432 | |||
438 | if len(formatting) > 65535: |
|
433 | atom = {b'msg': formatting} | |
439 | raise ValueError('formatting string cannot be longer than 64k') |
|
434 | if args: | |
440 |
|
435 | atom[b'args'] = args | ||
441 | if any(len(a) > 65535 for a in args): |
|
436 | if labels: | |
442 | raise ValueError('argument string cannot be longer than 64k') |
|
437 | atom[b'labels'] = labels | |
443 |
|
||||
444 | if any(len(l) > 255 for l in labels): |
|
|||
445 | raise ValueError('label string cannot be longer than 255 bytes') |
|
|||
446 |
|
438 | |||
447 | chunks = [ |
|
439 | atomdicts.append(atom) | |
448 | struct.pack(r'<H', len(formatting)), |
|
|||
449 | struct.pack(r'<BB', len(labels), len(args)), |
|
|||
450 | struct.pack(r'<' + r'B' * len(labels), *map(len, labels)), |
|
|||
451 | struct.pack(r'<' + r'H' * len(args), *map(len, args)), |
|
|||
452 | ] |
|
|||
453 | chunks.append(formatting) |
|
|||
454 | chunks.extend(labels) |
|
|||
455 | chunks.extend(args) |
|
|||
456 |
|
440 | |||
457 | atom = b''.join(chunks) |
|
441 | payload = cbor.dumps(atomdicts, canonical=True) | |
458 | atomchunks.append(atom) |
|
|||
459 | bytesleft -= len(atom) |
|
|||
460 |
|
442 | |||
461 | if bytesleft < 0: |
|
443 | if len(payload) > maxframesize: | |
462 | raise ValueError('cannot encode data in a single frame') |
|
444 | raise ValueError('cannot encode data in a single frame') | |
463 |
|
445 | |||
464 | yield stream.makeframe(requestid=requestid, |
|
446 | yield stream.makeframe(requestid=requestid, | |
465 | typeid=FRAME_TYPE_TEXT_OUTPUT, |
|
447 | typeid=FRAME_TYPE_TEXT_OUTPUT, | |
466 | flags=0, |
|
448 | flags=0, | |
467 |
payload= |
|
449 | payload=payload) | |
468 |
|
450 | |||
469 | class stream(object): |
|
451 | class stream(object): | |
470 | """Represents a logical unidirectional series of frames.""" |
|
452 | """Represents a logical unidirectional series of frames.""" |
@@ -136,22 +136,6 b' class FrameTests(unittest.TestCase):' | |||||
136 | ffs(b'1 1 0 command-data eos %s' % data.getvalue()), |
|
136 | ffs(b'1 1 0 command-data eos %s' % data.getvalue()), | |
137 | ]) |
|
137 | ]) | |
138 |
|
138 | |||
139 | def testtextoutputexcessiveargs(self): |
|
|||
140 | """At most 255 formatting arguments are allowed.""" |
|
|||
141 | with self.assertRaisesRegexp(ValueError, |
|
|||
142 | 'cannot use more than 255 formatting'): |
|
|||
143 | args = [b'x' for i in range(256)] |
|
|||
144 | list(framing.createtextoutputframe(None, 1, |
|
|||
145 | [(b'bleh', args, [])])) |
|
|||
146 |
|
||||
147 | def testtextoutputexcessivelabels(self): |
|
|||
148 | """At most 255 labels are allowed.""" |
|
|||
149 | with self.assertRaisesRegexp(ValueError, |
|
|||
150 | 'cannot use more than 255 labels'): |
|
|||
151 | labels = [b'l' for i in range(256)] |
|
|||
152 | list(framing.createtextoutputframe(None, 1, |
|
|||
153 | [(b'bleh', [], labels)])) |
|
|||
154 |
|
||||
155 | def testtextoutputformattingstringtype(self): |
|
139 | def testtextoutputformattingstringtype(self): | |
156 | """Formatting string must be bytes.""" |
|
140 | """Formatting string must be bytes.""" | |
157 | with self.assertRaisesRegexp(ValueError, 'must use bytes formatting '): |
|
141 | with self.assertRaisesRegexp(ValueError, 'must use bytes formatting '): | |
@@ -168,31 +152,14 b' class FrameTests(unittest.TestCase):' | |||||
168 | list(framing.createtextoutputframe(None, 1, [ |
|
152 | list(framing.createtextoutputframe(None, 1, [ | |
169 | (b'foo', [], [b'foo'.decode('ascii')])])) |
|
153 | (b'foo', [], [b'foo'.decode('ascii')])])) | |
170 |
|
154 | |||
171 | def testtextoutputtoolongformatstring(self): |
|
|||
172 | with self.assertRaisesRegexp(ValueError, |
|
|||
173 | 'formatting string cannot be longer than'): |
|
|||
174 | list(framing.createtextoutputframe(None, 1, [ |
|
|||
175 | (b'x' * 65536, [], [])])) |
|
|||
176 |
|
||||
177 | def testtextoutputtoolongargumentstring(self): |
|
|||
178 | with self.assertRaisesRegexp(ValueError, |
|
|||
179 | 'argument string cannot be longer than'): |
|
|||
180 | list(framing.createtextoutputframe(None, 1, [ |
|
|||
181 | (b'bleh', [b'x' * 65536], [])])) |
|
|||
182 |
|
||||
183 | def testtextoutputtoolonglabelstring(self): |
|
|||
184 | with self.assertRaisesRegexp(ValueError, |
|
|||
185 | 'label string cannot be longer than'): |
|
|||
186 | list(framing.createtextoutputframe(None, 1, [ |
|
|||
187 | (b'bleh', [], [b'x' * 65536])])) |
|
|||
188 |
|
||||
189 | def testtextoutput1simpleatom(self): |
|
155 | def testtextoutput1simpleatom(self): | |
190 | stream = framing.stream(1) |
|
156 | stream = framing.stream(1) | |
191 | val = list(framing.createtextoutputframe(stream, 1, [ |
|
157 | val = list(framing.createtextoutputframe(stream, 1, [ | |
192 | (b'foo', [], [])])) |
|
158 | (b'foo', [], [])])) | |
193 |
|
159 | |||
194 | self.assertEqual(val, [ |
|
160 | self.assertEqual(val, [ | |
195 |
ffs(b |
|
161 | ffs(b'1 1 stream-begin text-output 0 ' | |
|
162 | b"cbor:[{b'msg': b'foo'}]"), | |||
196 | ]) |
|
163 | ]) | |
197 |
|
164 | |||
198 | def testtextoutput2simpleatoms(self): |
|
165 | def testtextoutput2simpleatoms(self): | |
@@ -203,8 +170,8 b' class FrameTests(unittest.TestCase):' | |||||
203 | ])) |
|
170 | ])) | |
204 |
|
171 | |||
205 | self.assertEqual(val, [ |
|
172 | self.assertEqual(val, [ | |
206 |
ffs(b |
|
173 | ffs(b'1 1 stream-begin text-output 0 ' | |
207 | br'\x03\x00\x00\x00foo\x03\x00\x00\x00bar'), |
|
174 | b"cbor:[{b'msg': b'foo'}, {b'msg': b'bar'}]") | |
208 | ]) |
|
175 | ]) | |
209 |
|
176 | |||
210 | def testtextoutput1arg(self): |
|
177 | def testtextoutput1arg(self): | |
@@ -214,8 +181,8 b' class FrameTests(unittest.TestCase):' | |||||
214 | ])) |
|
181 | ])) | |
215 |
|
182 | |||
216 | self.assertEqual(val, [ |
|
183 | self.assertEqual(val, [ | |
217 |
ffs(b |
|
184 | ffs(b'1 1 stream-begin text-output 0 ' | |
218 | br'\x06\x00\x00\x01\x04\x00foo %sval1'), |
|
185 | b"cbor:[{b'msg': b'foo %s', b'args': [b'val1']}]") | |
219 | ]) |
|
186 | ]) | |
220 |
|
187 | |||
221 | def testtextoutput2arg(self): |
|
188 | def testtextoutput2arg(self): | |
@@ -225,8 +192,8 b' class FrameTests(unittest.TestCase):' | |||||
225 | ])) |
|
192 | ])) | |
226 |
|
193 | |||
227 | self.assertEqual(val, [ |
|
194 | self.assertEqual(val, [ | |
228 |
ffs(b |
|
195 | ffs(b'1 1 stream-begin text-output 0 ' | |
229 | br'\x09\x00\x00\x02\x03\x00\x05\x00foo %s %svalvalue'), |
|
196 | b"cbor:[{b'msg': b'foo %s %s', b'args': [b'val', b'value']}]") | |
230 | ]) |
|
197 | ]) | |
231 |
|
198 | |||
232 | def testtextoutput1label(self): |
|
199 | def testtextoutput1label(self): | |
@@ -236,8 +203,8 b' class FrameTests(unittest.TestCase):' | |||||
236 | ])) |
|
203 | ])) | |
237 |
|
204 | |||
238 | self.assertEqual(val, [ |
|
205 | self.assertEqual(val, [ | |
239 |
ffs(b |
|
206 | ffs(b'1 1 stream-begin text-output 0 ' | |
240 | br'\x03\x00\x01\x00\x05foolabel'), |
|
207 | b"cbor:[{b'msg': b'foo', b'labels': [b'label']}]") | |
241 | ]) |
|
208 | ]) | |
242 |
|
209 | |||
243 | def testargandlabel(self): |
|
210 | def testargandlabel(self): | |
@@ -247,8 +214,9 b' class FrameTests(unittest.TestCase):' | |||||
247 | ])) |
|
214 | ])) | |
248 |
|
215 | |||
249 | self.assertEqual(val, [ |
|
216 | self.assertEqual(val, [ | |
250 |
ffs(b |
|
217 | ffs(b'1 1 stream-begin text-output 0 ' | |
251 | br'\x06\x00\x01\x01\x05\x03\x00foo %slabelarg'), |
|
218 | b"cbor:[{b'msg': b'foo %s', b'args': [b'arg'], " | |
|
219 | b"b'labels': [b'label']}]") | |||
252 | ]) |
|
220 | ]) | |
253 |
|
221 | |||
254 | class ServerReactorTests(unittest.TestCase): |
|
222 | class ServerReactorTests(unittest.TestCase): |
General Comments 0
You need to be logged in to leave comments.
Login now