A => ingest/__init__.py +0 -0
A => ingest/__main__.py +11 -0
@@ 1,11 @@
+#!/usr/bin/env python3
+
+from . import parse
+
+for msg in parse.split_messages(parse.read_input())[1:]:
+ try:
+ parse.parse_message(msg)
+ except:
+ raise
+
+
A => ingest/__pycache__/__init__.cpython-38.pyc +0 -0
A => ingest/__pycache__/__main__.cpython-38.pyc +0 -0
A => ingest/__pycache__/message.cpython-38.pyc +0 -0
A => ingest/__pycache__/parse.cpython-38.pyc +0 -0
A => ingest/message.py +201 -0
@@ 1,201 @@
+#!/usr/bin/env python3
+
+"""The message object and API."""
+
+from typing import Optional
+
+class Message(object):
+ """Container for a message and metadata."""
+ def __init__(
+ self,
+ *,
+ hdr_subject: Optional[str] = None,
+ hdr_date: Optional[str] = None,
+ hdr_from: Optional[str] = None,
+ hdr_to: Optional[str] = None,
+ hdr_cc: Optional[str] = None,
+ hdr_message_id: Optional[str] = None,
+ content_type: Optional[str] = None,
+ content: Optional[str] = None,
+ ) -> None:
+ self._subject = hdr_subject
+ self._date = hdr_date
+ self._from = hdr_from
+ self._to = hdr_to
+ self._cc = hdr_cc
+ self._message_id = hdr_message_id
+ self._content_type = content_type
+ self._content = content
+ self._last_hdr = None
+
+ def __str__(self) -> str:
+ return (
+ f"Subject: {self._subject}\n"
+ f"Date: {self._date}\n"
+ f"To: {self._to}\n"
+ f"From: {self._from}\n"
+ f"Cc: {self._cc}\n"
+ f"Message-ID: {self._message_id}\n"
+ f"Content-Type: {self._content_type}\n"
+ )
+
+ @property
+ def hdr_subject(self) -> str:
+ if self._subject is not None:
+ return self._subject
+ else:
+ raise ValueError("no header `subject' set") from None
+ @hdr_subject.setter
+ def hdr_subject(self, value: str):
+ if self._subject is None:
+ self._subject = value
+ self._last_hdr = "_subject"
+ else:
+ raise ValueError("header `subject' already set") from None
+
+ @property
+ def hdr_date(self) -> str:
+ if self._date is not None:
+ return self._date
+ else:
+ raise ValueError("no header `date' set") from None
+ @hdr_date.setter
+ def hdr_date(self, value: str):
+ if self._date is None:
+ self._date = value
+ self._last_hdr = "_date"
+ else:
+ raise ValueError("header `date' already set") from None
+
+ @property
+ def hdr_from(self) -> str:
+ if self._from is not None:
+ return self._from
+ else:
+ raise ValueError("no header `from' set") from None
+ @hdr_from.setter
+ def hdr_from(self, value: str):
+ if self._from is None:
+ self._from = value
+ self._last_hdr = "_from"
+ else:
+ raise ValueError("header `from' already set") from None
+
+ @property
+ def hdr_to(self) -> str:
+ if self._to is not None:
+ return self._to
+ else:
+ raise ValueError("no header `to' set") from None
+ @hdr_to.setter
+ def hdr_to(self, value: str):
+ if self._to is None:
+ self._to = value
+ self._last_hdr = "_to"
+ else:
+ raise ValueError("header `to' already set") from None
+
+ @property
+ def hdr_cc(self) -> str:
+ if self._cc is not None:
+ return self._cc
+ else:
+ raise ValueError("no header `cc' set") from None
+ @hdr_cc.setter
+ def hdr_cc(self, value: str):
+ if self._cc is None:
+ self._cc = value
+ self._last_hdr = "_cc"
+ else:
+ raise ValueError("header `cc' already set") from None
+
+ @property
+ def hdr_message_id(self) -> str:
+ if self._message_id is not None:
+ return self._message_id
+ else:
+ raise ValueError("no header `message_id' set") from None
+ @hdr_message_id.setter
+ def hdr_message_id(self, value: str):
+ if self._message_id is None:
+ self._message_id = value
+ self._last_hdr = "_message_id"
+ else:
+ raise ValueError("header `message_id' already set") from None
+
+ @property
+ def content_type(self) -> str:
+ if self._content_type is not None:
+ return self._content_type
+ else:
+ raise ValueError("no `content_type' set") from None
+ @content_type.setter
+ def content_type(self, value: str):
+ if self._content_type is None:
+ self._content_type = value
+ self._last_hdr = "_content_type"
+ else:
+ raise ValueError("`content_type' already set") from None
+
+ def append_last(self, value: str):
+ if self._last_hdr is not None:
+ old = getattr(self, self._last_hdr)
+ try:
+ new = old + value
+ except:
+ # test for bad encoding
+ raise
+ setattr(self, self._last_hdr, new)
+ else:
+ raise ValueError("no header set") from None
+
+ def into_multipart(self):
+ return MultipartMessage(
+ hdr_subject=self._subject,
+ hdr_date=self._date,
+ hdr_from=self._from,
+ hdr_to=self._to,
+ hdr_cc=self._cc,
+ hdr_message_id=self._message_id,
+ content_type=self._content_type,
+ content=self._content,
+ )
+
+class MultipartMessage(Message):
+ """Container for a multi-part message and metadata."""
+ def __init__(
+ self,
+ *,
+ hdr_subject: Optional[str] = None,
+ hdr_date: Optional[str] = None,
+ hdr_from: Optional[str] = None,
+ hdr_to: Optional[str] = None,
+ hdr_cc: Optional[str] = None,
+ hdr_message_id: Optional[str] = None,
+ content_type: Optional[str] = None,
+ content: Optional[str] = None,
+ ) -> None:
+ self._subject = hdr_subject
+ self._date = hdr_date
+ self._from = hdr_from
+ self._to = hdr_to
+ self._cc = hdr_cc
+ self._message_id = hdr_message_id
+ self._content_type = content_type
+ self._content = content
+ self._last_hdr = None
+
+ self._parts = list()
+
+ def __str__(self) -> str:
+ return (
+ f"Subject: {self._subject}\n"
+ f"Date: {self._date}\n"
+ f"To: {self._to}\n"
+ f"From: {self._from}\n"
+ f"Cc: {self._cc}\n"
+ f"Message-ID: {self._message_id}\n"
+ f"Content-Type: {self._content_type}\n"
+ f"Parts: {len(self._parts)}\n"
+ )
+
A => ingest/parse.py +155 -0
@@ 1,155 @@
+#!/usr/bin/env python3
+
+import sys
+import re
+from typing import List
+
+from . import message
+
+RE_MESSAGE_BREAK = re.compile(r"^-* *$")
+RE_HEADER_LINE = re.compile(r"^(?:Date|From|Subject|To|Cc|Message-ID|Content-Type):")
+RE_BLANK_LINE = re.compile(r"^ *$")
+RE_SUBJECT_LINE = re.compile(r"^Subject: *(.*) *$")
+RE_DATE_LINE = re.compile(r"^Date: *(.*) *$")
+RE_FROM_LINE = re.compile(r"^From: *(.*) *$")
+RE_TO_LINE = re.compile(r"^To: *(.*) *$")
+RE_CC_LINE = re.compile(r"^Cc: *(.*) *$")
+RE_ID_LINE = re.compile(r"^Message-ID: *(.*) *$")
+RE_CONTENT_LINE = re.compile(r"^Content-Type: *(.*) *$")
+RE_RUNON = re.compile(r"^[ \t]+(.*) *$")
+RE_BOUNDARY = re.compile(r'.*boundary="(.*)".*')
+
+def split_messages(blob: List[bytes]) -> List[List[bytes]]:
+ """Split a blob into messages."""
+ message_breaks = list()
+ message_start = 0
+ messages = list()
+
+ # Find probable message breaks
+ for index, bytes_line in enumerate(blob):
+ try:
+ line = str(bytes_line)
+ except:
+ # test for bad encodings
+ raise
+ if RE_MESSAGE_BREAK.match(line):
+ message_breaks.append(index)
+
+ # Validate message breaks and copy text into split messages
+ # NOTE: message breaks are validated by checking for...
+ # 1) A blank line following the message break
+ # 2) A header line following the blank line
+ for index in message_breaks:
+ try:
+ line1 = str(blob[index+1])
+ line2 = str(blob[index+2])
+ except:
+ # test for bad encodings
+ raise
+
+ # If fails validation, skip to next probable break
+ if not RE_BLANK_LINE.match(line1):
+ continue
+ elif not RE_HEADER_LINE.match(line2):
+ continue
+
+ # Message spans from known start to line before break
+ messages.append(blob[message_start:index - 1])
+
+ # Next message starts on first header line
+ message_start = index + 2
+
+ # Handle remainder
+ messages.append(blob[message_start:])
+
+ return messages
+
+def split_message_parts(
+ blob: List[bytes],
+ boundary: str,
+) -> List[List[bytes]]:
+ """Split a blob into message parts."""
+ part_breaks = list()
+ parts = list()
+ part_start = 0
+
+ # NOTE: can use `in' operator with bytes and strings
+ for index, line in enumerate(blob):
+ if boundary in line:
+ part_breaks.append(index)
+
+ for index in part_breaks:
+ parts.append(blob[part_start:index - 1])
+ part_start = index + 1
+
+ parts.append(blob[part_start:])
+
+ return parts
+
+
+
+
+
+def parse_message(blob: List[bytes]):
+ """Parse a message blob for metadata and parts."""
+ msg = message.Message()
+ header_end = 0
+
+ # Parse the header
+ for bytes_line in blob:
+ header_end += 1
+ try:
+ line = str(bytes_line)
+ except:
+ # test for bad encodings
+ raise
+
+ if RE_BLANK_LINE.match(line):
+ break
+ elif match := RE_RUNON.match(line):
+ msg.append_last(line)
+
+ elif match := RE_SUBJECT_LINE.match(line):
+ msg.hdr_subject = match.group(1)
+ elif match := RE_DATE_LINE.match(line):
+ msg.hdr_date = match.group(1)
+ elif match := RE_FROM_LINE.match(line):
+ msg.hdr_from = match.group(1)
+ elif match := RE_TO_LINE.match(line):
+ msg.hdr_to = match.group(1)
+ elif match := RE_CC_LINE.match(line):
+ msg.hdr_cc = match.group(1)
+ elif match := RE_ID_LINE.match(line):
+ msg.hdr_message_id = match.group(1)
+ elif match := RE_CONTENT_LINE.match(line):
+ msg.content_type = match.group(1)
+
+ # store content
+ msg._content = blob[header_end + 1:]
+
+ # parse for parts
+ try:
+ if "multipart/" in msg.content_type:
+ msg = msg.into_multipart()
+ except:
+ pass
+ if isinstance(msg,message.MultipartMessage):
+ if match := RE_BOUNDARY.match(msg.content_type):
+ boundary = "".join(match.group(1).split())
+ else:
+ raise ValueError("no boundary for multipart content") from None
+ msg._parts = split_message_parts(msg._content, boundary)
+
+ print(msg)
+
+def read_input() -> List[bytes]:
+ """Read STDIN into a blob."""
+ textblob = list()
+ for line in sys.stdin:
+ try:
+ textblob.append(line.rstrip())
+ except:
+ # test for bad encoding
+ raise
+ return textblob
+
A => tests/static/python_dev_mail_01.txt +294 -0
@@ 1,294 @@
+Send Python-Dev mailing list submissions to
+ python-dev@python.org
+
+To subscribe or unsubscribe via the World Wide Web, visit
+ https://mail.python.org/mailman3/lists/python-dev.python.org/
+or, via email, send a message with subject or body 'help' to
+ python-dev-request@python.org
+
+You can reach the person managing the list at
+ python-dev-owner@python.org
+
+When replying, please edit your Subject line so it is more specific
+than "Re: Contents of Python-Dev digest..."
+
+Today's Topics:
+
+ 1. Re: PEP 626: Precise line numbers for debugging and other tools.
+ (Inada Naoki)
+ 2. Re: How to customize CPython to a minimal set (Huang, Yang)
+
+
+----------------------------------------------------------------------
+
+Date: Wed, 22 Jul 2020 22:56:55 +0900
+From: Inada Naoki <songofacandy@gmail.com>
+Subject: [Python-Dev] Re: PEP 626: Precise line numbers for debugging
+ and other tools.
+To: Antoine Pitrou <antoine@python.org>
+Cc: Python-Dev <python-dev@python.org>
+Message-ID:
+ <CAEfz+TyyPL8iut0YXHeUYLxjz40nVxPu9V_LYVqw0abqLXEHow@mail.gmail.com>
+Content-Type: text/plain; charset="UTF-8"
+
+On Wed, Jul 22, 2020 at 10:53 PM Antoine Pitrou <antoine@python.org> wrote:
+>
+>
+> Le 22/07/2020 à 15:48, Inada Naoki a écrit :
+> > On Wed, Jul 22, 2020 at 8:51 PM Antoine Pitrou <solipsis@pitrou.net> wrote:
+> >>
+> >>>
+> >>> I don't think all attempts are failed. Note that current CPython includes
+> >>> some optimization already.
+> >>
+> >> The set of compile-time optimizations has almost not changed since at
+> >> least 15 years ago.
+> >>
+> >
+> > Constant folding is rewritten and unused constants are removed from co_consts.
+> > That's one of what Victor did his project.
+>
+> Constant folding is not a new optimization, so this does not contradict
+> what I said. Also, constant folding is not precluded by Mark's
+> proposal, AFAIK.
+>
+
+Yes, this is tooooo off topic. Please stop it.
+
+--
+Inada Naoki <songofacandy@gmail.com>
+
+------------------------------
+
+Date: Wed, 22 Jul 2020 13:31:18 +0000
+From: "Huang, Yang" <yang.huang@intel.com>
+Subject: [Python-Dev] Re: How to customize CPython to a minimal set
+To: "guido@python.org" <guido@python.org>, Steve Dower
+ <steve.dower@python.org>, MRAB <python@mrabarnett.plus.com>
+Cc: "python-dev@python.org" <python-dev@python.org>
+Message-ID: <CY4PR11MB1894D722452F4E01EBDDC61E85790@CY4PR11MB1894.namp
+ rd11.prod.outlook.com>
+Content-Type: multipart/alternative; boundary="_000_CY4PR11MB1894D7
+ 22452F4E01EBDDC61E85790CY4PR11MB1894namp_"
+
+--_000_CY4PR11MB1894D722452F4E01EBDDC61E85790CY4PR11MB1894namp_
+Content-Type: text/plain; charset="utf-8"
+Content-Transfer-Encoding: base64
+
+VGhhbmsgeW91IGZvciBhbGwgeW91ciBjb21tZW50cy4NCkkgY2Fubm90IGFncmVlIGFueSBtb3Jl
+LiBJIGRpZCB0cnkgYnV0IHRoZXJlIHdlcmUgc28gbWFueSBkZXBlbmRlbmNpZXMuIE9uZSBjaGFu
+Z2UgYWxsIGNoYW5nZS4NCg0KTWljcm9weXRob24gaXMgYSBjaG9pY2UuIEJ1dCBub3Qgc3VyZSBp
+ZiBudW1weSBhbmQgc3FsaXRlMyBjYW4gYmUgc3VwcG9ydGVkIHdlbGwuIEFuZCB3aGF04oCZcyB0
+aGUgY29tcGF0aWJpbGl0eSBvZiB0aGUgbGlicyBpbiBQeXBpLg0KDQoNCkZyb206IEd1aWRvIHZh
+biBSb3NzdW0gPGd1aWRvQHB5dGhvbi5vcmc+DQpTZW50OiBUdWVzZGF5LCBKdWx5IDIxLCAyMDIw
+IDEwOjU3IFBNDQpUbzogSHVhbmcsIFlhbmcgPHlhbmcuaHVhbmdAaW50ZWwuY29tPg0KQ2M6IHB5
+dGhvbi1kZXZAcHl0aG9uLm9yZw0KU3ViamVjdDogUmU6IFtQeXRob24tRGV2XSBIb3cgdG8gY3Vz
+dG9taXplIENQeXRob24gdG8gYSBtaW5pbWFsIHNldA0KDQpJIGV4cGVjdCBpdCB3aWxsIGJlIHVu
+ZmVhc2libGUgdG8gc3RyaXAgQ1B5dGhvbi4gSWYgeW91IGRpc2FncmVlLCB0cnkgaXQuIDstKQ0K
+DQpPbiBNb24sIEp1bCAyMCwgMjAyMCBhdCAyMjozNSBIdWFuZywgWWFuZyA8eWFuZy5odWFuZ0Bp
+bnRlbC5jb208bWFpbHRvOnlhbmcuaHVhbmdAaW50ZWwuY29tPj4gd3JvdGU6DQpIaSwgR3VpZG8N
+Cg0KWWVzLiBNaWNyb3B5aHRvbiBpcyBhbHNvIGluIGNvbnNpZGVyYXRpb24uDQpCdXQgc3FsaXRl
+MyBpcyB0aGUgZmlyc3QgdXNhZ2UuIFRoZXJlIHNob3VsZCBiZSBzb21lIGFkZGl0aW9uYWwgZmVh
+dHVyZXMgbGlrZSBudW1weSwgc2NpcHkuLi4gTm90IHN1cmUgaWYgbWljcm9weXRob24gc3VwcG9y
+dHMgd2VsbD8NCg0KT3IgaXMgdGhlcmUgYSBmZWFzaWJsZSB3YXkgdG8gc3RyaXAgQ1B5dGhvbiA/
+DQoNClRoYW5rcy4NCg0KRnJvbTogR3VpZG8gdmFuIFJvc3N1bSA8Z3VpZG9AcHl0aG9uLm9yZzxt
+YWlsdG86Z3VpZG9AcHl0aG9uLm9yZz4+DQpTZW50OiBNb25kYXksIEp1bHkgMjAsIDIwMjAgMTA6
+NDUgUE0NClRvOiBIdWFuZywgWWFuZyA8eWFuZy5odWFuZ0BpbnRlbC5jb208bWFpbHRvOnlhbmcu
+aHVhbmdAaW50ZWwuY29tPj4NCkNjOiBweXRob24tZGV2QHB5dGhvbi5vcmc8bWFpbHRvOnB5dGhv
+bi1kZXZAcHl0aG9uLm9yZz4NClN1YmplY3Q6IFJlOiBbUHl0aG9uLURldl0gSG93IHRvIGN1c3Rv
+bWl6ZSBDUHl0aG9uIHRvIGEgbWluaW1hbCBzZXQNCg0KSGF2ZSB5b3UgY29uc2lkZXJlZCBzdGFy
+dGluZyB3aXRoIG1pY3JvcHl0aG9uPyBJdOKAmXMgbWFkZSBmb3IgZW1iZWRkZWQgc3lzdGVtcyBh
+bmQgZnVsbHkgc3VwcG9ydHMgUHl0aG9uIDMgc3ludGF4LiBBZGRpbmcgc3FsaXRlMyBzdXBwb3J0
+IHRvIGl0IHdpbGwgYmUgbGVzcyB3b3JrIHRoYW4gc3RyaXBwaW5nIGFsbCB0aGUgSS9PIGZyb20g
+Q1B5dGhvbi4NCg0K4oCUR3VpZG8NCg0KT24gTW9uLCBKdWwgMjAsIDIwMjAgYXQgMDY6NDggSHVh
+bmcsIFlhbmcgPHlhbmcuaHVhbmdAaW50ZWwuY29tPG1haWx0bzp5YW5nLmh1YW5nQGludGVsLmNv
+bT4+IHdyb3RlOg0KDQpIaSwgYWxsDQoNClRoZXJlIGlzIGEgcmVxdWVzdCB0byBydW4gcHl0aG9u
+IGluIGEgTGludXgtYmFzZWQgZW1iZWRkZWQgcmVzb3VyY2UgY29uc3RyYWluZWQgc3lzdGVtIHdp
+dGggc3FsaXRlMyBzdXBwb3J0Lg0KDQpTbyBtYW55IGZlYXR1cmVzIGFyZSBub3QgcmVxdWlyZWQs
+IGxpa2UgcG9zaXhtb2R1bGUsIHNpZ25hbG1vZHVsZSwgaGFzaHRhYmxlIC4uLg0KQnV0IHNlZW1z
+IHRoZXJlIGFyZSBzb21lIGRlcGVuZGVuY2llcyBhbW9uZyB0aGUgTW9kdWxlcy9QYXJzZXIvUHl0
+aG9uL09iamVjdHMvUHJvZ3JhbXMuLi4NCg0KSXMgdGhlcmUgYSB3YXkgdG8gdGFpbG9yIENQeXRo
+b24gMyB0byBhIG1pbmltYWwgc2V0IHdpdGggc3FsaXRlMyAodGhlIGxlc3Mgc3lzY2FsbHMgdGhl
+IGJldHRlcikgPw0KSXMgaXQgcG9zc2libGUgdG8gZG8gdGhhdD8NCg0KVGhhbmsgeW91Lg0KX19f
+X19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX18NClB5dGhvbi1EZXYg
+bWFpbGluZyBsaXN0IC0tIHB5dGhvbi1kZXZAcHl0aG9uLm9yZzxtYWlsdG86cHl0aG9uLWRldkBw
+eXRob24ub3JnPg0KVG8gdW5zdWJzY3JpYmUgc2VuZCBhbiBlbWFpbCB0byBweXRob24tZGV2LWxl
+YXZlQHB5dGhvbi5vcmc8bWFpbHRvOnB5dGhvbi1kZXYtbGVhdmVAcHl0aG9uLm9yZz4NCmh0dHBz
+Oi8vbWFpbC5weXRob24ub3JnL21haWxtYW4zL2xpc3RzL3B5dGhvbi1kZXYucHl0aG9uLm9yZy8N
+Ck1lc3NhZ2UgYXJjaGl2ZWQgYXQgaHR0cHM6Ly9tYWlsLnB5dGhvbi5vcmcvYXJjaGl2ZXMvbGlz
+dC9weXRob24tZGV2QHB5dGhvbi5vcmcvbWVzc2FnZS9FQ1BMS1hRNDJWTkxIRDVEUDNSRzU3TDNR
+VEo3N0ZVVC8NCkNvZGUgb2YgQ29uZHVjdDogaHR0cDovL3B5dGhvbi5vcmcvcHNmL2NvZGVvZmNv
+bmR1Y3QvDQotLQ0KLS1HdWlkbyAobW9iaWxlKQ0KLS0NCi0tR3VpZG8gKG1vYmlsZSkNCg==
+
+--_000_CY4PR11MB1894D722452F4E01EBDDC61E85790CY4PR11MB1894namp_
+Content-Type: text/html; charset="utf-8"
+Content-Transfer-Encoding: base64
+
+PGh0bWwgeG1sbnM6dj0idXJuOnNjaGVtYXMtbWljcm9zb2Z0LWNvbTp2bWwiIHhtbG5zOm89InVy
+bjpzY2hlbWFzLW1pY3Jvc29mdC1jb206b2ZmaWNlOm9mZmljZSIgeG1sbnM6dz0idXJuOnNjaGVt
+YXMtbWljcm9zb2Z0LWNvbTpvZmZpY2U6d29yZCIgeG1sbnM6bT0iaHR0cDovL3NjaGVtYXMubWlj
+cm9zb2Z0LmNvbS9vZmZpY2UvMjAwNC8xMi9vbW1sIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcv
+VFIvUkVDLWh0bWw0MCI+DQo8aGVhZD4NCjxtZXRhIGh0dHAtZXF1aXY9IkNvbnRlbnQtVHlwZSIg
+Y29udGVudD0idGV4dC9odG1sOyBjaGFyc2V0PXV0Zi04Ij4NCjxtZXRhIG5hbWU9IkdlbmVyYXRv
+ciIgY29udGVudD0iTWljcm9zb2Z0IFdvcmQgMTUgKGZpbHRlcmVkIG1lZGl1bSkiPg0KPHN0eWxl
+PjwhLS0NCi8qIEZvbnQgRGVmaW5pdGlvbnMgKi8NCkBmb250LWZhY2UNCgl7Zm9udC1mYW1pbHk6
+IkNhbWJyaWEgTWF0aCI7DQoJcGFub3NlLTE6MiA0IDUgMyA1IDQgNiAzIDIgNDt9DQpAZm9udC1m
+YWNlDQoJe2ZvbnQtZmFtaWx5OkRlbmdYaWFuOw0KCXBhbm9zZS0xOjIgMSA2IDAgMyAxIDEgMSAx
+IDE7fQ0KQGZvbnQtZmFjZQ0KCXtmb250LWZhbWlseTpDYWxpYnJpOw0KCXBhbm9zZS0xOjIgMTUg
+NSAyIDIgMiA0IDMgMiA0O30NCkBmb250LWZhY2UNCgl7Zm9udC1mYW1pbHk6IlxARGVuZ1hpYW4i
+Ow0KCXBhbm9zZS0xOjIgMSA2IDAgMyAxIDEgMSAxIDE7fQ0KLyogU3R5bGUgRGVmaW5pdGlvbnMg
+Ki8NCnAuTXNvTm9ybWFsLCBsaS5Nc29Ob3JtYWwsIGRpdi5Nc29Ob3JtYWwNCgl7bWFyZ2luOjBp
+bjsNCgltYXJnaW4tYm90dG9tOi4wMDAxcHQ7DQoJZm9udC1zaXplOjExLjBwdDsNCglmb250LWZh
+bWlseToiQ2FsaWJyaSIsc2Fucy1zZXJpZjt9DQphOmxpbmssIHNwYW4uTXNvSHlwZXJsaW5rDQoJ
+e21zby1zdHlsZS1wcmlvcml0eTo5OTsNCgljb2xvcjpibHVlOw0KCXRleHQtZGVjb3JhdGlvbjp1
+bmRlcmxpbmU7fQ0KYTp2aXNpdGVkLCBzcGFuLk1zb0h5cGVybGlua0ZvbGxvd2VkDQoJe21zby1z
+dHlsZS1wcmlvcml0eTo5OTsNCgljb2xvcjpwdXJwbGU7DQoJdGV4dC1kZWNvcmF0aW9uOnVuZGVy
+bGluZTt9DQpwLm1zb25vcm1hbDAsIGxpLm1zb25vcm1hbDAsIGRpdi5tc29ub3JtYWwwDQoJe21z
+by1zdHlsZS1uYW1lOm1zb25vcm1hbDsNCgltc28tbWFyZ2luLXRvcC1hbHQ6YXV0bzsNCgltYXJn
+aW4tcmlnaHQ6MGluOw0KCW1zby1tYXJnaW4tYm90dG9tLWFsdDphdXRvOw0KCW1hcmdpbi1sZWZ0
+OjBpbjsNCglmb250LXNpemU6MTEuMHB0Ow0KCWZvbnQtZmFtaWx5OiJDYWxpYnJpIixzYW5zLXNl
+cmlmO30NCnNwYW4uRW1haWxTdHlsZTE4DQoJe21zby1zdHlsZS10eXBlOnBlcnNvbmFsLXJlcGx5
+Ow0KCWZvbnQtZmFtaWx5OiJDYWxpYnJpIixzYW5zLXNlcmlmOw0KCWNvbG9yOndpbmRvd3RleHQ7
+fQ0KLk1zb0NocERlZmF1bHQNCgl7bXNvLXN0eWxlLXR5cGU6ZXhwb3J0LW9ubHk7DQoJZm9udC1z
+aXplOjEwLjBwdDsNCglmb250LWZhbWlseToiQ2FsaWJyaSIsc2Fucy1zZXJpZjt9DQpAcGFnZSBX
+b3JkU2VjdGlvbjENCgl7c2l6ZTo4LjVpbiAxMS4waW47DQoJbWFyZ2luOjEuMGluIDEuMGluIDEu
+MGluIDEuMGluO30NCmRpdi5Xb3JkU2VjdGlvbjENCgl7cGFnZTpXb3JkU2VjdGlvbjE7fQ0KLS0+
+PC9zdHlsZT48IS0tW2lmIGd0ZSBtc28gOV0+PHhtbD4NCjxvOnNoYXBlZGVmYXVsdHMgdjpleHQ9
+ImVkaXQiIHNwaWRtYXg9IjEwMjYiIC8+DQo8L3htbD48IVtlbmRpZl0tLT48IS0tW2lmIGd0ZSBt
+c28gOV0+PHhtbD4NCjxvOnNoYXBlbGF5b3V0IHY6ZXh0PSJlZGl0Ij4NCjxvOmlkbWFwIHY6ZXh0
+PSJlZGl0IiBkYXRhPSIxIiAvPg0KPC9vOnNoYXBlbGF5b3V0PjwveG1sPjwhW2VuZGlmXS0tPg0K
+PC9oZWFkPg0KPGJvZHkgbGFuZz0iRU4tVVMiIGxpbms9ImJsdWUiIHZsaW5rPSJwdXJwbGUiPg0K
+PGRpdiBjbGFzcz0iV29yZFNlY3Rpb24xIj4NCjxwIGNsYXNzPSJNc29Ob3JtYWwiPlRoYW5rIHlv
+dSBmb3IgYWxsIHlvdXIgY29tbWVudHMuIDxvOnA+PC9vOnA+PC9wPg0KPHAgY2xhc3M9Ik1zb05v
+cm1hbCI+SSBjYW5ub3QgYWdyZWUgYW55IG1vcmUuIEkgZGlkIHRyeSBidXQgdGhlcmUgd2VyZSBz
+byBtYW55IGRlcGVuZGVuY2llcy4gT25lIGNoYW5nZSBhbGwgY2hhbmdlLjxvOnA+PC9vOnA+PC9w
+Pg0KPHAgY2xhc3M9Ik1zb05vcm1hbCI+PG86cD4mbmJzcDs8L286cD48L3A+DQo8cCBjbGFzcz0i
+TXNvTm9ybWFsIj5NaWNyb3B5dGhvbiBpcyBhIGNob2ljZS4gQnV0IG5vdCBzdXJlIGlmIG51bXB5
+IGFuZCBzcWxpdGUzIGNhbiBiZSBzdXBwb3J0ZWQgd2VsbC4gQW5kIHdoYXTigJlzIHRoZSBjb21w
+YXRpYmlsaXR5IG9mIHRoZSBsaWJzIGluIFB5cGkuICZuYnNwOzxvOnA+PC9vOnA+PC9wPg0KPHAg
+Y2xhc3M9Ik1zb05vcm1hbCI+PG86cD4mbmJzcDs8L286cD48L3A+DQo8cCBjbGFzcz0iTXNvTm9y
+bWFsIj48bzpwPiZuYnNwOzwvbzpwPjwvcD4NCjxkaXYgc3R5bGU9ImJvcmRlcjpub25lO2JvcmRl
+ci1sZWZ0OnNvbGlkIGJsdWUgMS41cHQ7cGFkZGluZzowaW4gMGluIDBpbiA0LjBwdCI+DQo8ZGl2
+Pg0KPGRpdiBzdHlsZT0iYm9yZGVyOm5vbmU7Ym9yZGVyLXRvcDpzb2xpZCAjRTFFMUUxIDEuMHB0
+O3BhZGRpbmc6My4wcHQgMGluIDBpbiAwaW4iPg0KPHAgY2xhc3M9Ik1zb05vcm1hbCI+PGI+RnJv
+bTo8L2I+IEd1aWRvIHZhbiBSb3NzdW0gJmx0O2d1aWRvQHB5dGhvbi5vcmcmZ3Q7IDxicj4NCjxi
+PlNlbnQ6PC9iPiBUdWVzZGF5LCBKdWx5IDIxLCAyMDIwIDEwOjU3IFBNPGJyPg0KPGI+VG86PC9i
+PiBIdWFuZywgWWFuZyAmbHQ7eWFuZy5odWFuZ0BpbnRlbC5jb20mZ3Q7PGJyPg0KPGI+Q2M6PC9i
+PiBweXRob24tZGV2QHB5dGhvbi5vcmc8YnI+DQo8Yj5TdWJqZWN0OjwvYj4gUmU6IFtQeXRob24t
+RGV2XSBIb3cgdG8gY3VzdG9taXplIENQeXRob24gdG8gYSBtaW5pbWFsIHNldDxvOnA+PC9vOnA+
+PC9wPg0KPC9kaXY+DQo8L2Rpdj4NCjxwIGNsYXNzPSJNc29Ob3JtYWwiPjxvOnA+Jm5ic3A7PC9v
+OnA+PC9wPg0KPGRpdj4NCjxkaXY+DQo8cCBjbGFzcz0iTXNvTm9ybWFsIj5JIGV4cGVjdCBpdCB3
+aWxsIGJlIHVuZmVhc2libGUgdG8gc3RyaXAgQ1B5dGhvbi4gSWYgeW91IGRpc2FncmVlLCB0cnkg
+aXQuIDstKTxvOnA+PC9vOnA+PC9wPg0KPC9kaXY+DQo8L2Rpdj4NCjxkaXY+DQo8cCBjbGFzcz0i
+TXNvTm9ybWFsIj48bzpwPiZuYnNwOzwvbzpwPjwvcD4NCjxkaXY+DQo8ZGl2Pg0KPHAgY2xhc3M9
+Ik1zb05vcm1hbCI+T24gTW9uLCBKdWwgMjAsIDIwMjAgYXQgMjI6MzUgSHVhbmcsIFlhbmcgJmx0
+OzxhIGhyZWY9Im1haWx0bzp5YW5nLmh1YW5nQGludGVsLmNvbSI+eWFuZy5odWFuZ0BpbnRlbC5j
+b208L2E+Jmd0OyB3cm90ZTo8bzpwPjwvbzpwPjwvcD4NCjwvZGl2Pg0KPGJsb2NrcXVvdGUgc3R5
+bGU9ImJvcmRlcjpub25lO2JvcmRlci1sZWZ0OnNvbGlkICNDQ0NDQ0MgMS4wcHQ7cGFkZGluZzow
+aW4gMGluIDBpbiA2LjBwdDttYXJnaW4tbGVmdDo0LjhwdDttYXJnaW4tdG9wOjUuMHB0O21hcmdp
+bi1yaWdodDowaW47bWFyZ2luLWJvdHRvbTo1LjBwdCI+DQo8ZGl2Pg0KPGRpdj4NCjxwIGNsYXNz
+PSJNc29Ob3JtYWwiIHN0eWxlPSJtc28tbWFyZ2luLXRvcC1hbHQ6YXV0bzttc28tbWFyZ2luLWJv
+dHRvbS1hbHQ6YXV0byI+SGksIEd1aWRvPG86cD48L286cD48L3A+DQo8cCBjbGFzcz0iTXNvTm9y
+bWFsIiBzdHlsZT0ibXNvLW1hcmdpbi10b3AtYWx0OmF1dG87bXNvLW1hcmdpbi1ib3R0b20tYWx0
+OmF1dG8iPiZuYnNwOzxvOnA+PC9vOnA+PC9wPg0KPHAgY2xhc3M9Ik1zb05vcm1hbCIgc3R5bGU9
+Im1zby1tYXJnaW4tdG9wLWFsdDphdXRvO21zby1tYXJnaW4tYm90dG9tLWFsdDphdXRvIj5ZZXMu
+IE1pY3JvcHlodG9uIGlzIGFsc28gaW4gY29uc2lkZXJhdGlvbi4NCjxvOnA+PC9vOnA+PC9wPg0K
+PHAgY2xhc3M9Ik1zb05vcm1hbCIgc3R5bGU9Im1zby1tYXJnaW4tdG9wLWFsdDphdXRvO21zby1t
+YXJnaW4tYm90dG9tLWFsdDphdXRvIj5CdXQgc3FsaXRlMyBpcyB0aGUgZmlyc3QgdXNhZ2UuIFRo
+ZXJlIHNob3VsZCBiZSBzb21lIGFkZGl0aW9uYWwgZmVhdHVyZXMgbGlrZSBudW1weSwgc2NpcHku
+Li4gTm90IHN1cmUgaWYgbWljcm9weXRob24gc3VwcG9ydHMgd2VsbD8NCjxvOnA+PC9vOnA+PC9w
+Pg0KPHAgY2xhc3M9Ik1zb05vcm1hbCIgc3R5bGU9Im1zby1tYXJnaW4tdG9wLWFsdDphdXRvO21z
+by1tYXJnaW4tYm90dG9tLWFsdDphdXRvIj4mbmJzcDs8bzpwPjwvbzpwPjwvcD4NCjxwIGNsYXNz
+PSJNc29Ob3JtYWwiIHN0eWxlPSJtc28tbWFyZ2luLXRvcC1hbHQ6YXV0bzttc28tbWFyZ2luLWJv
+dHRvbS1hbHQ6YXV0byI+T3IgaXMgdGhlcmUgYSBmZWFzaWJsZSB3YXkgdG8gc3RyaXAgQ1B5dGhv
+biA/PG86cD48L286cD48L3A+DQo8cCBjbGFzcz0iTXNvTm9ybWFsIiBzdHlsZT0ibXNvLW1hcmdp
+bi10b3AtYWx0OmF1dG87bXNvLW1hcmdpbi1ib3R0b20tYWx0OmF1dG8iPiZuYnNwOzxvOnA+PC9v
+OnA+PC9wPg0KPHAgY2xhc3M9Ik1zb05vcm1hbCIgc3R5bGU9Im1zby1tYXJnaW4tdG9wLWFsdDph
+dXRvO21zby1tYXJnaW4tYm90dG9tLWFsdDphdXRvIj5UaGFua3MuDQo8bzpwPjwvbzpwPjwvcD4N
+CjwvZGl2Pg0KPC9kaXY+DQo8ZGl2Pg0KPGRpdj4NCjxwIGNsYXNzPSJNc29Ob3JtYWwiIHN0eWxl
+PSJtc28tbWFyZ2luLXRvcC1hbHQ6YXV0bzttc28tbWFyZ2luLWJvdHRvbS1hbHQ6YXV0byI+Jm5i
+c3A7PG86cD48L286cD48L3A+DQo8ZGl2IHN0eWxlPSJib3JkZXI6bm9uZTtib3JkZXItbGVmdDpz
+b2xpZCBibHVlIDEuNXB0O3BhZGRpbmc6MGluIDBpbiAwaW4gNC4wcHQiPg0KPGRpdj4NCjxkaXYg
+c3R5bGU9ImJvcmRlcjpub25lO2JvcmRlci10b3A6c29saWQgI0UxRTFFMSAxLjBwdDtwYWRkaW5n
+OjMuMHB0IDBpbiAwaW4gMGluIj4NCjxwIGNsYXNzPSJNc29Ob3JtYWwiIHN0eWxlPSJtc28tbWFy
+Z2luLXRvcC1hbHQ6YXV0bzttc28tbWFyZ2luLWJvdHRvbS1hbHQ6YXV0byI+PGI+RnJvbTo8L2I+
+IEd1aWRvIHZhbiBSb3NzdW0gJmx0OzxhIGhyZWY9Im1haWx0bzpndWlkb0BweXRob24ub3JnIiB0
+YXJnZXQ9Il9ibGFuayI+Z3VpZG9AcHl0aG9uLm9yZzwvYT4mZ3Q7DQo8YnI+DQo8Yj5TZW50Ojwv
+Yj4gTW9uZGF5LCBKdWx5IDIwLCAyMDIwIDEwOjQ1IFBNPGJyPg0KPGI+VG86PC9iPiBIdWFuZywg
+WWFuZyAmbHQ7PGEgaHJlZj0ibWFpbHRvOnlhbmcuaHVhbmdAaW50ZWwuY29tIiB0YXJnZXQ9Il9i
+bGFuayI+eWFuZy5odWFuZ0BpbnRlbC5jb208L2E+Jmd0Ozxicj4NCjxiPkNjOjwvYj4gPGEgaHJl
+Zj0ibWFpbHRvOnB5dGhvbi1kZXZAcHl0aG9uLm9yZyIgdGFyZ2V0PSJfYmxhbmsiPnB5dGhvbi1k
+ZXZAcHl0aG9uLm9yZzwvYT48YnI+DQo8Yj5TdWJqZWN0OjwvYj4gUmU6IFtQeXRob24tRGV2XSBI
+b3cgdG8gY3VzdG9taXplIENQeXRob24gdG8gYSBtaW5pbWFsIHNldDxvOnA+PC9vOnA+PC9wPg0K
+PC9kaXY+DQo8L2Rpdj4NCjxwIGNsYXNzPSJNc29Ob3JtYWwiIHN0eWxlPSJtc28tbWFyZ2luLXRv
+cC1hbHQ6YXV0bzttc28tbWFyZ2luLWJvdHRvbS1hbHQ6YXV0byI+Jm5ic3A7PG86cD48L286cD48
+L3A+DQo8ZGl2Pg0KPGRpdj4NCjxwIGNsYXNzPSJNc29Ob3JtYWwiIHN0eWxlPSJtc28tbWFyZ2lu
+LXRvcC1hbHQ6YXV0bzttc28tbWFyZ2luLWJvdHRvbS1hbHQ6YXV0byI+SGF2ZSB5b3UgY29uc2lk
+ZXJlZCBzdGFydGluZyB3aXRoIG1pY3JvcHl0aG9uPyBJdOKAmXMgbWFkZSBmb3IgZW1iZWRkZWQg
+c3lzdGVtcyBhbmQgZnVsbHkgc3VwcG9ydHMgUHl0aG9uIDMgc3ludGF4LiBBZGRpbmcgc3FsaXRl
+MyBzdXBwb3J0IHRvIGl0IHdpbGwgYmUgbGVzcyB3b3JrIHRoYW4gc3RyaXBwaW5nIGFsbA0KIHRo
+ZSBJL08gZnJvbSBDUHl0aG9uLjxvOnA+PC9vOnA+PC9wPg0KPC9kaXY+DQo8ZGl2Pg0KPHAgY2xh
+c3M9Ik1zb05vcm1hbCIgc3R5bGU9Im1zby1tYXJnaW4tdG9wLWFsdDphdXRvO21zby1tYXJnaW4t
+Ym90dG9tLWFsdDphdXRvIj4mbmJzcDs8bzpwPjwvbzpwPjwvcD4NCjwvZGl2Pg0KPGRpdj4NCjxw
+IGNsYXNzPSJNc29Ob3JtYWwiIHN0eWxlPSJtc28tbWFyZ2luLXRvcC1hbHQ6YXV0bzttc28tbWFy
+Z2luLWJvdHRvbS1hbHQ6YXV0byI+4oCUR3VpZG88bzpwPjwvbzpwPjwvcD4NCjwvZGl2Pg0KPC9k
+aXY+DQo8ZGl2Pg0KPHAgY2xhc3M9Ik1zb05vcm1hbCIgc3R5bGU9Im1zby1tYXJnaW4tdG9wLWFs
+dDphdXRvO21zby1tYXJnaW4tYm90dG9tLWFsdDphdXRvIj4mbmJzcDs8bzpwPjwvbzpwPjwvcD4N
+CjxkaXY+DQo8ZGl2Pg0KPHAgY2xhc3M9Ik1zb05vcm1hbCIgc3R5bGU9Im1zby1tYXJnaW4tdG9w
+LWFsdDphdXRvO21zby1tYXJnaW4tYm90dG9tLWFsdDphdXRvIj5PbiBNb24sIEp1bCAyMCwgMjAy
+MCBhdCAwNjo0OCBIdWFuZywgWWFuZyAmbHQ7PGEgaHJlZj0ibWFpbHRvOnlhbmcuaHVhbmdAaW50
+ZWwuY29tIiB0YXJnZXQ9Il9ibGFuayI+eWFuZy5odWFuZ0BpbnRlbC5jb208L2E+Jmd0OyB3cm90
+ZTo8bzpwPjwvbzpwPjwvcD4NCjwvZGl2Pg0KPGJsb2NrcXVvdGUgc3R5bGU9ImJvcmRlcjpub25l
+O2JvcmRlci1sZWZ0OnNvbGlkICNDQ0NDQ0MgMS4wcHQ7cGFkZGluZzowaW4gMGluIDBpbiA2LjBw
+dDttYXJnaW4tbGVmdDo0LjhwdDttYXJnaW4tdG9wOjUuMHB0O21hcmdpbi1yaWdodDowaW47bWFy
+Z2luLWJvdHRvbTo1LjBwdCI+DQo8cCBjbGFzcz0iTXNvTm9ybWFsIiBzdHlsZT0ibXNvLW1hcmdp
+bi10b3AtYWx0OmF1dG87bXNvLW1hcmdpbi1ib3R0b20tYWx0OmF1dG8iPjxicj4NCkhpLCBhbGw8
+YnI+DQo8YnI+DQpUaGVyZSBpcyBhIHJlcXVlc3QgdG8gcnVuIHB5dGhvbiBpbiBhIExpbnV4LWJh
+c2VkIGVtYmVkZGVkIHJlc291cmNlIGNvbnN0cmFpbmVkIHN5c3RlbSB3aXRoIHNxbGl0ZTMgc3Vw
+cG9ydC48YnI+DQo8YnI+DQpTbyBtYW55IGZlYXR1cmVzIGFyZSBub3QgcmVxdWlyZWQsIGxpa2Ug
+cG9zaXhtb2R1bGUsIHNpZ25hbG1vZHVsZSwgaGFzaHRhYmxlIC4uLjxicj4NCkJ1dCBzZWVtcyB0
+aGVyZSBhcmUgc29tZSBkZXBlbmRlbmNpZXMgYW1vbmcgdGhlIE1vZHVsZXMvUGFyc2VyL1B5dGhv
+bi9PYmplY3RzL1Byb2dyYW1zLi4uPGJyPg0KPGJyPg0KSXMgdGhlcmUgYSB3YXkgdG8gdGFpbG9y
+IENQeXRob24gMyB0byBhIG1pbmltYWwgc2V0IHdpdGggc3FsaXRlMyAodGhlIGxlc3Mgc3lzY2Fs
+bHMgdGhlIGJldHRlcikgPw0KPGJyPg0KSXMgaXQgcG9zc2libGUgdG8gZG8gdGhhdD88YnI+DQo8
+YnI+DQpUaGFuayB5b3UuPGJyPg0KX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19f
+X19fX19fX19fX188YnI+DQpQeXRob24tRGV2IG1haWxpbmcgbGlzdCAtLSA8YSBocmVmPSJtYWls
+dG86cHl0aG9uLWRldkBweXRob24ub3JnIiB0YXJnZXQ9Il9ibGFuayI+DQpweXRob24tZGV2QHB5
+dGhvbi5vcmc8L2E+PGJyPg0KVG8gdW5zdWJzY3JpYmUgc2VuZCBhbiBlbWFpbCB0byA8YSBocmVm
+PSJtYWlsdG86cHl0aG9uLWRldi1sZWF2ZUBweXRob24ub3JnIiB0YXJnZXQ9Il9ibGFuayI+DQpw
+eXRob24tZGV2LWxlYXZlQHB5dGhvbi5vcmc8L2E+PGJyPg0KPGEgaHJlZj0iaHR0cHM6Ly9tYWls
+LnB5dGhvbi5vcmcvbWFpbG1hbjMvbGlzdHMvcHl0aG9uLWRldi5weXRob24ub3JnLyIgdGFyZ2V0
+PSJfYmxhbmsiPmh0dHBzOi8vbWFpbC5weXRob24ub3JnL21haWxtYW4zL2xpc3RzL3B5dGhvbi1k
+ZXYucHl0aG9uLm9yZy88L2E+PGJyPg0KTWVzc2FnZSBhcmNoaXZlZCBhdCA8YSBocmVmPSJodHRw
+czovL21haWwucHl0aG9uLm9yZy9hcmNoaXZlcy9saXN0L3B5dGhvbi1kZXZAcHl0aG9uLm9yZy9t
+ZXNzYWdlL0VDUExLWFE0MlZOTEhENURQM1JHNTdMM1FUSjc3RlVULyIgdGFyZ2V0PSJfYmxhbmsi
+Pg0KaHR0cHM6Ly9tYWlsLnB5dGhvbi5vcmcvYXJjaGl2ZXMvbGlzdC9weXRob24tZGV2QHB5dGhv
+bi5vcmcvbWVzc2FnZS9FQ1BMS1hRNDJWTkxIRDVEUDNSRzU3TDNRVEo3N0ZVVC88L2E+PGJyPg0K
+Q29kZSBvZiBDb25kdWN0OiA8YSBocmVmPSJodHRwOi8vcHl0aG9uLm9yZy9wc2YvY29kZW9mY29u
+ZHVjdC8iIHRhcmdldD0iX2JsYW5rIj5odHRwOi8vcHl0aG9uLm9yZy9wc2YvY29kZW9mY29uZHVj
+dC88L2E+PG86cD48L286cD48L3A+DQo8L2Jsb2NrcXVvdGU+DQo8L2Rpdj4NCjwvZGl2Pg0KPHAg
+Y2xhc3M9Ik1zb05vcm1hbCIgc3R5bGU9Im1zby1tYXJnaW4tdG9wLWFsdDphdXRvO21zby1tYXJn
+aW4tYm90dG9tLWFsdDphdXRvIj4tLQ0KPG86cD48L286cD48L3A+DQo8ZGl2Pg0KPHAgY2xhc3M9
+Ik1zb05vcm1hbCIgc3R5bGU9Im1zby1tYXJnaW4tdG9wLWFsdDphdXRvO21zby1tYXJnaW4tYm90
+dG9tLWFsdDphdXRvIj4tLUd1aWRvIChtb2JpbGUpPG86cD48L286cD48L3A+DQo8L2Rpdj4NCjwv
+ZGl2Pg0KPC9kaXY+DQo8L2Rpdj4NCjwvYmxvY2txdW90ZT4NCjwvZGl2Pg0KPC9kaXY+DQo8cCBj
+bGFzcz0iTXNvTm9ybWFsIj4tLSA8bzpwPjwvbzpwPjwvcD4NCjxkaXY+DQo8cCBjbGFzcz0iTXNv
+Tm9ybWFsIj4tLUd1aWRvIChtb2JpbGUpPG86cD48L286cD48L3A+DQo8L2Rpdj4NCjwvZGl2Pg0K
+PC9kaXY+DQo8L2JvZHk+DQo8L2h0bWw+DQo=
+
+--_000_CY4PR11MB1894D722452F4E01EBDDC61E85790CY4PR11MB1894namp_--
+
+------------------------------
+
+Subject: Digest Footer
+
+_______________________________________________
+Python-Dev mailing list -- python-dev@python.org
+To unsubscribe send an email to python-dev-leave@python.org
+https://mail.python.org/mailman3/lists/python-dev.python.org/
+
+
+------------------------------
+
+End of Python-Dev Digest, Vol 204, Issue 123
+********************************************
A => tests/static/python_dev_mail_02.txt +480 -0
@@ 1,480 @@
+Send Python-Dev mailing list submissions to
+ python-dev@python.org
+
+To subscribe or unsubscribe via the World Wide Web, visit
+ https://mail.python.org/mailman3/lists/python-dev.python.org/
+or, via email, send a message with subject or body 'help' to
+ python-dev-request@python.org
+
+You can reach the person managing the list at
+ python-dev-owner@python.org
+
+When replying, please edit your Subject line so it is more specific
+than "Re: Contents of Python-Dev digest..."
+
+Today's Topics:
+
+ 1. Re: PEP 626: Precise line numbers for debugging and other tools.
+ (Guido van Rossum)
+ 2. Add mimetypes.mimesniff (Dong-hee Na)
+ 3. Re: How to customize CPython to a minimal set (Matthias Klose)
+
+
+----------------------------------------------------------------------
+
+Date: Wed, 22 Jul 2020 18:15:49 -0700
+From: Guido van Rossum <guido@python.org>
+Subject: [Python-Dev] Re: PEP 626: Precise line numbers for debugging
+ and other tools.
+To: "Gregory P. Smith" <greg@krypto.org>
+Cc: Python-Dev <python-dev@python.org>
+Message-ID:
+ <CAP7+vJKeKeQr3suo3YK7NoRgLtaOF2u0K=CVn+FQvXBmrsjycQ@mail.gmail.com>
+Content-Type: multipart/alternative;
+ boundary="00000000000075ecc005ab119986"
+
+--00000000000075ecc005ab119986
+Content-Type: text/plain; charset="UTF-8"
+Content-Transfer-Encoding: quoted-printable
+
+But on which line is the RETURN opcode if there is more than a docstring?
+Doesn=E2=80=99t it make sense to have it attached to the last line of the b=
+ody?
+(Too bad about pytype, that kind of change happens =E2=80=94 we had this ki=
+nd of
+thing for mypy too, when line numbers in the AST were fixed.)
+
+On Wed, Jul 22, 2020 at 17:29 Gregory P. Smith <greg@krypto.org> wrote:
+
+>
+>
+> On Wed, Jul 22, 2020 at 5:19 AM Mark Shannon <mark@hotpy.org> wrote:
+>
+>>
+>>
+>> On 21/07/2020 9:46 pm, Gregory P. Smith wrote:
+>> >
+>> >
+>> > On Fri, Jul 17, 2020 at 8:41 AM Ned Batchelder <ned@nedbatchelder.com
+>> > <mailto:ned@nedbatchelder.com>> wrote:
+>> >
+>> > https://www.python.org/dev/peps/pep-0626/ :)
+>> >
+>> > --Ned.
+>> >
+>> > On 7/17/20 10:48 AM, Mark Shannon wrote:
+>> > > Hi all,
+>> > >
+>> > > I'd like to announce a new PEP.
+>> > >
+>> > > It is mainly codifying that Python should do what you probably
+>> > already
+>> > > thought it did :)
+>> > >
+>> > > Should be uncontroversial, but all comments are welcome.
+>> > >
+>> > > Cheers,
+>> > > Mark.
+>> >
+>> >
+>> > """When a frame object is created, the f_lineno will be set to the lin=
+e
+>> > at which the function or class is defined. For modules it will be set
+>> to
+>> > zero."""
+>> >
+>> > Within this PEP it'd be good for us to be very pedantic. f_lineno is =
+a
+>> > single number. So which number is it given many class and function
+>> > definition statements can span multiple lines.
+>> >
+>> > Is it the line containing the class or def keyword? Or is it the line
+>> > containing the trailing :?
+>>
+>> The line of the `def`/`class`. It wouldn't change for the current
+>> behavior. I'll add that to the PEP.
+>>
+>> >
+>> > Q: Why can't we have the information about the entire span of lines
+>> > rather than consider a definition to be a "line"?
+>>
+>> Pretty much every profiler, coverage tool, and debugger ever expects
+>> lines to be natural numbers, not ranges of numbers.
+>> A lot of tooling would need to be changed.
+>>
+>> >
+>> > I think that question applies to later sections as well. Anywhere we
+>> > refer to a "line", it could actually mean a span of lines. (especially
+>> > when you consider \ continuation in situations you might not otherwise
+>> > think could span lines)
+>>
+>> Let's take an example:
+>> ```
+>> x =3D (
+>> a,
+>> b,
+>> )
+>> ```
+>>
+>> You would want the BUILD_TUPLE instruction to have a of span lines 1 to
+>> 4 (inclusive), rather just line 1?
+>> If you wanted to break on the BUILD_TUPLE where you tell pdb to break?
+>>
+>> I don't see that it would add much value, but it would add a lot of
+>> complexity.
+>>
+>
+> We should have the data about the range at bytecode compilation time,
+> correct? So why not keep it? sure, most existing tooling would just use
+> the start of the range as the line number as it always has. but some
+> tooling could find the range useful (ex: semantic code indexing for use i=
+n
+> display, search, editors, IDEs. Rendering lint errors more accurately
+> instead of just claiming a single line or resorting to parsing hacks to
+> come up with a range, etc.). The downside is that we'd be storing a seco=
+nd
+> number in bytecode making it slightly larger. Though it could be stored
+> efficiently as a prefixed delta so it'd likely average out as less than 2
+> bytes per line number stored. (i don't have a feeling for our current
+> format to know if that is significant or not - if it is, maybe this idea
+> just gets nixed)
+>
+> The reason the range concept was on my mind is due to something not quite
+> related but involving a changed idea of a line number in our current syst=
+em
+> that we recently ran into with pytype during a Python upgrade.
+>
+> """in 3.7, if a function body is a plain docstring, the line number of th=
+e
+> RETURN_VALUE opcode corresponds to the docstring, whereas in 3.6 it
+> corresponds to the function definition.""" (Thanks, Martin & Rebecca!)
+>
+> ```python
+> def no_op():
+> """docstring instead of pass."""
+> ```
+>
+> so the location of what *was* originally an end of line `# pytype:
+> disable=3Dbad-return-type` comment (to work around an issue not relevant
+> here) turned awkward and version dependent. pytype is bytecode based, th=
+us
+> that is where its line numbers come from. metadata comments in source ca=
+n
+> only be tied to bytecode via line numbers. making end of line directives
+> occasionally hard to match up.
+>
+> When there is no return statement, this opcode still exists. what line
+> number does it belong to? 3.6's answer made sense to me. 3.7's seems
+> wrong - a docstring isn't responsible for a return opcode. I didn't chec=
+k
+> what 3.8 and 3.9 do. An alternate answer after this PEP is that it
+> wouldn't have a line number when there is no return statement (pedantical=
+ly
+> correct, I approve! #win).
+>
+> -gps
+>
+>
+>>
+>> Cheers,
+>> Mark.
+>>
+>> >
+>> > -gps
+>>
+> _______________________________________________
+> Python-Dev mailing list -- python-dev@python.org
+> To unsubscribe send an email to python-dev-leave@python.org
+> https://mail.python.org/mailman3/lists/python-dev.python.org/
+> Message archived at
+> https://mail.python.org/archives/list/python-dev@python.org/message/H3YBK=
+275SUSCR5EHWHYBTJBF655UK7JG/
+> Code of Conduct: http://python.org/psf/codeofconduct/
+>
+--=20
+--Guido (mobile)
+
+--00000000000075ecc005ab119986
+Content-Type: text/html; charset="UTF-8"
+Content-Transfer-Encoding: quoted-printable
+
+<div><div><div dir=3D"auto">But on which line is the RETURN opcode if there=
+ is more than a docstring? Doesn=E2=80=99t it make sense to have it attache=
+d to the last line of the body? (Too bad about pytype, that kind of change =
+happens =E2=80=94 we had this kind of thing for mypy too, when line numbers=
+ in the AST were fixed.)</div></div></div><div><div><br><div class=3D"gmail=
+_quote"><div dir=3D"ltr" class=3D"gmail_attr">On Wed, Jul 22, 2020 at 17:29=
+ Gregory P. Smith <<a href=3D"mailto:greg@krypto.org" target=3D"_blank">=
+greg@krypto.org</a>> wrote:<br></div><blockquote class=3D"gmail_quote" s=
+tyle=3D"margin:0px 0px 0px 0.8ex;border-left-width:1px;border-left-style:so=
+lid;padding-left:1ex;border-left-color:rgb(204,204,204)"><div dir=3D"ltr"><=
+div dir=3D"ltr"><br></div><br><div class=3D"gmail_quote"></div></div><div d=
+ir=3D"ltr"><div class=3D"gmail_quote"><div dir=3D"ltr" class=3D"gmail_attr"=
+>On Wed, Jul 22, 2020 at 5:19 AM Mark Shannon <<a href=3D"mailto:mark@ho=
+tpy.org" target=3D"_blank">mark@hotpy.org</a>> wrote:<br></div><blockquo=
+te class=3D"gmail_quote" style=3D"margin:0px 0px 0px 0.8ex;border-left-widt=
+h:1px;border-left-style:solid;padding-left:1ex;border-left-color:rgb(204,20=
+4,204)"><br>
+<br>
+On 21/07/2020 9:46 pm, Gregory P. Smith wrote:<br>
+> <br>
+> <br>
+> On Fri, Jul 17, 2020 at 8:41 AM Ned Batchelder <<a href=3D"mailto:n=
+ed@nedbatchelder.com" target=3D"_blank">ned@nedbatchelder.com</a> <br>
+> <mailto:<a href=3D"mailto:ned@nedbatchelder.com" target=3D"_blank">=
+ned@nedbatchelder.com</a>>> wrote:<br>
+> <br>
+>=C2=A0 =C2=A0 =C2=A0<a href=3D"https://www.python.org/dev/peps/pep-0626=
+/" rel=3D"noreferrer" target=3D"_blank">https://www.python.org/dev/peps/pep=
+-0626/</a> :)<br>
+> <br>
+>=C2=A0 =C2=A0 =C2=A0--Ned.<br>
+> <br>
+>=C2=A0 =C2=A0 =C2=A0On 7/17/20 10:48 AM, Mark Shannon wrote:<br>
+>=C2=A0 =C2=A0 =C2=A0 > Hi all,<br>
+>=C2=A0 =C2=A0 =C2=A0 ><br>
+>=C2=A0 =C2=A0 =C2=A0 > I'd like to announce a new PEP.<br>
+>=C2=A0 =C2=A0 =C2=A0 ><br>
+>=C2=A0 =C2=A0 =C2=A0 > It is mainly codifying that Python should do =
+what you probably<br>
+>=C2=A0 =C2=A0 =C2=A0already<br>
+>=C2=A0 =C2=A0 =C2=A0 > thought it did :)<br>
+>=C2=A0 =C2=A0 =C2=A0 ><br>
+>=C2=A0 =C2=A0 =C2=A0 > Should be uncontroversial, but all comments a=
+re welcome.<br>
+>=C2=A0 =C2=A0 =C2=A0 ><br>
+>=C2=A0 =C2=A0 =C2=A0 > Cheers,<br>
+>=C2=A0 =C2=A0 =C2=A0 > Mark.<br>
+> <br>
+> <br>
+> """When a frame object is created, the f_lineno will be=
+ set to the line <br>
+> at which the function or class is defined. For modules it will be set =
+to <br>
+> zero."""<br>
+> <br>
+> Within this PEP it'd be good for us to be very pedantic.=C2=A0 f_l=
+ineno is a <br>
+> single number.=C2=A0 So which number is it given many class and functi=
+on <br>
+> definition statements can span multiple lines.<br>
+> <br>
+> Is it the line containing the class or def keyword?=C2=A0 Or is it the=
+ line <br>
+> containing the trailing :?<br>
+<br>
+The line of the `def`/`class`. It wouldn't change for the current <br>
+behavior. I'll add that to the PEP.<br>
+<br>
+> <br>
+> Q: Why can't we have the information about the entire span of line=
+s <br>
+> rather than consider a definition to be a "line"?<br>
+<br>
+Pretty much every profiler, coverage tool, and debugger ever expects <br>
+lines to be natural numbers, not ranges of numbers.<br>
+A lot of tooling would need to be changed.<br>
+<br>
+> <br>
+> I think that question applies to later sections as well.=C2=A0 Anywher=
+e we <br>
+> refer to a "line", it could actually mean a span of=C2=A0lin=
+es. (especially <br>
+> when you consider \ continuation in situations you might not otherwise=
+ <br>
+> think could span lines)<br>
+<br>
+Let's take an example:<br>
+```<br>
+x =3D (<br>
+=C2=A0 =C2=A0 =C2=A0a,<br>
+=C2=A0 =C2=A0 =C2=A0b,<br>
+)<br>
+```<br>
+<br>
+You would want the BUILD_TUPLE instruction to have a of span lines 1 to <br=
+>
+4 (inclusive), rather just line 1?<br>
+If you wanted to break on the BUILD_TUPLE where you tell pdb to break?<br>
+<br>
+I don't see that it would add much value, but it would add a lot of <br=
+>
+complexity.<br></blockquote><div><br></div></div></div><div dir=3D"ltr"><di=
+v class=3D"gmail_quote"><div><div>We should have the data about the range a=
+t bytecode compilation time, correct?=C2=A0 So why not keep it?=C2=A0 sure,=
+ most existing tooling would just use the start of the range as the line nu=
+mber as it always has.=C2=A0 but some tooling could find the range useful (=
+ex: semantic code indexing for use in display, search, editors, IDEs. Rende=
+ring lint errors more accurately instead of just claiming a single line or =
+resorting to parsing hacks to come up with a range, etc.).=C2=A0 The downsi=
+de is that we'd be storing a second number in bytecode making it slight=
+ly larger.=C2=A0 Though it could be stored efficiently as a prefixed delta =
+so it'd likely average out as less than 2 bytes per line number stored.=
+=C2=A0 (i don't have a feeling for our current format to know if that i=
+s significant or not - if it is, maybe this idea just gets nixed)</div><div=
+><br></div></div><div>The reason the range concept was on my mind is due to=
+ something not quite related but involving a changed idea of a line number =
+in our current system that we recently ran into with pytype during a Python=
+ upgrade.</div><div><br></div><div>"""in 3.7, if a function =
+body is a plain docstring, the line number of the RETURN_VALUE opcode corre=
+sponds to the docstring, whereas in 3.6 it corresponds to the function defi=
+nition.""" (Thanks, Martin & Rebecca!)</div><div><br></d=
+iv><div>```python</div><div>def no_op():</div><div>=C2=A0 """=
+;docstring instead of pass."""</div><div>```</div><div><br><=
+/div><div>so the location of what <i>was</i> originally an end of line `# p=
+ytype: disable=3Dbad-return-type` comment (to work around an issue not rele=
+vant here) turned awkward and version dependent.=C2=A0=C2=A0pytype is bytec=
+ode based, thus that is where its line numbers=C2=A0come from.=C2=A0 metada=
+ta comments in source can only be tied to bytecode via line numbers.=C2=A0 =
+making end of line directives occasionally hard to match up.</div><div><br>=
+</div><div>When there is no return statement, this opcode still exists.=C2=
+=A0 what line number does it belong to?=C2=A0 3.6's answer made sense t=
+o me.=C2=A0 3.7's seems wrong - a docstring isn't responsible for a=
+ return opcode.=C2=A0 I didn't check what 3.8 and 3.9 do.=C2=A0 An alte=
+rnate answer after this PEP is that it wouldn't have a line number when=
+ there is no return statement (pedantically correct, I approve! #win).</div=
+><div><br></div><div>-gps<br></div><div>=C2=A0</div><blockquote class=3D"gm=
+ail_quote" style=3D"margin:0px 0px 0px 0.8ex;border-left-width:1px;border-l=
+eft-style:solid;padding-left:1ex;border-left-color:rgb(204,204,204)">
+<br>
+Cheers,<br>
+Mark.<br>
+<br>
+> <br>
+> -gps<br>
+</blockquote></div></div>
+_______________________________________________<br>
+Python-Dev mailing list -- <a href=3D"mailto:python-dev@python.org" target=
+=3D"_blank">python-dev@python.org</a><br>
+To unsubscribe send an email to <a href=3D"mailto:python-dev-leave@python.o=
+rg" target=3D"_blank">python-dev-leave@python.org</a><br>
+<a href=3D"https://mail.python.org/mailman3/lists/python-dev.python.org/" r=
+el=3D"noreferrer" target=3D"_blank">https://mail.python.org/mailman3/lists/=
+python-dev.python.org/</a><br>
+Message archived at <a href=3D"https://mail.python.org/archives/list/python=
+-dev@python.org/message/H3YBK275SUSCR5EHWHYBTJBF655UK7JG/" rel=3D"noreferre=
+r" target=3D"_blank">https://mail.python.org/archives/list/python-dev@pytho=
+n.org/message/H3YBK275SUSCR5EHWHYBTJBF655UK7JG/</a><br>
+Code of Conduct: <a href=3D"http://python.org/psf/codeofconduct/" rel=3D"no=
+referrer" target=3D"_blank">http://python.org/psf/codeofconduct/</a><br>
+</blockquote></div></div>
+</div>-- <br><div dir=3D"ltr" class=3D"gmail_signature" data-smartmail=3D"g=
+mail_signature">--Guido (mobile)</div>
+
+--00000000000075ecc005ab119986--
+
+------------------------------
+
+Date: Thu, 23 Jul 2020 13:37:31 +0900
+From: Dong-hee Na <donghee.na92@gmail.com>
+Subject: [Python-Dev] Add mimetypes.mimesniff
+To: Python Dev <python-dev@python.org>
+Message-ID:
+ <CA+Z=B_m8wCYz1v=qeVgOe4m=+AMTtAnGChz2sn+-DsPMkxR24g@mail.gmail.com>
+Content-Type: text/plain; charset="UTF-8"
+
+Hi,
+
+A few weeks ago, I suggested adding mimetypes.mimesniff on stdlib.
+(https://bugs.python.org/issue40841,
+https://github.com/python/cpython/pull/20720)
+
+Detecting MIME types well is an important feature and we already have
+mimetypes detection library but AFAIK it is not good enough.
+
+Note that some of our stdlib module already use the sniffing algorithm
+(e.g imghdr)
+
+The question is how exactly the mime sniffing should be done in terms
+of file formats and algorithm. Luckily, WHATWG published the standard
+for mime sniffing, and I think we should follow it.
+(https://mimesniff.spec.whatwg.org/)
+
+So I created the issue on the bpo and implemented it.
+I 'd like to listen to all your opinions :)
+
+--
+Software Development Engineer at Kakao corp.
+
+Tel: +82 10-3353-9127
+Email: donghee.na92@gmail.com | denny.i@kakaocorp.com
+Linkedin: https://www.linkedin.com/in/dong-hee-na-2b713b49/
+
+------------------------------
+
+Date: Thu, 23 Jul 2020 11:57:33 +0200
+From: Matthias Klose <doko@ubuntu.com>
+Subject: [Python-Dev] Re: How to customize CPython to a minimal set
+To: "Huang, Yang" <yang.huang@intel.com>,
+ "python-dev@python.org" <python-dev@python.org>
+Message-ID: <17099346-f1b4-5b85-8693-062b11d39ce2@ubuntu.com>
+Content-Type: text/plain; charset=utf-8
+
+On 7/20/20 10:30 AM, Huang, Yang wrote:
+>
+> Hi, all
+>
+> There is a request to run python in a Linux-based embedded resource constrained system with sqlite3 support.
+>
+> So many features are not required, like posixmodule, signalmodule, hashtable ...
+> But seems there are some dependencies among the Modules/Parser/Python/Objects/Programs...
+>
+> Is there a way to tailor CPython 3 to a minimal set with sqlite3 (the less syscalls the better) ?
+> Is it possible to do that?
+
+CPython comes with the promise of all batteries included, however sometimes it
+feels like a complete power plant. For packaging purposes, most Linux distros
+make the decision to ship CPython as a set of runtime packages, and a set of
+packages used for development (and building C extensions). Breaking that down
+for the Debian/Ubuntu packages comes down to these sizes (unpacked,
+uncompressed, on x86_64):
+
+minimal:
+5516K debian/libpython3.9-minimal
+5856K debian/python3.9-minimal
+
+stdlib:
+8528K debian/libpython3.9-stdlib
+624K debian/python3.9
+
+development:
+19468K debian/libpython3.9-dev
+25804K debian/libpython3.9-testsuite
+1232K debian/python3-distutils
+668K debian/python3-lib2to3
+548K debian/python3.9-dev
+
+extra modules:
+1648K debian/idle-python3.9
+5208K debian/python3.9-examples
+
+132K debian/python3-gdbm
+844K debian/python3-tk
+
+What you don't see from the sizes, are the extra dependencies which add to the
+size, e.g. the X stack for tk, or readline/curses/crypto for stdlib.
+
+The "minimal" set may sound nice, however it's not used in practice, because the
+set is not well defined, and it's difficult to keep the minimal set as a
+self-contained set of stdlib modules, and the minimal usually keeps growing,
+never shrinks.
+
+It looks like this "minimal" set is even too much for your purposed, so you
+would have to scale down this set even further.
+
+Matthias
+
+------------------------------
+
+Subject: Digest Footer
+
+_______________________________________________
+Python-Dev mailing list -- python-dev@python.org
+To unsubscribe send an email to python-dev-leave@python.org
+https://mail.python.org/mailman3/lists/python-dev.python.org/
+
+
+------------------------------
+
+End of Python-Dev Digest, Vol 204, Issue 125
+********************************************
A => textwrap/Makefile +6 -0
@@ 1,6 @@
+clean:
+ rm -rf textwrap
+
+build:
+ go build
+
A => textwrap/main.go +81 -0
@@ 1,81 @@
+package main
+
+import (
+ "fmt"
+ "os"
+ "strings"
+ "bufio"
+ "regexp"
+ "flag"
+)
+
+const LENGTH = 40
+
+func print_break(length int) {
+ fmt.Printf("%s\n", strings.Repeat("-", length))
+}
+
+func print_wrapped(line string, length int, quote string) {
+ len_quote := len(quote)
+ buffer := quote
+ for index, rune := range line[len_quote:] {
+ buffer += string(rune)
+ if (index + 1) % (length - len_quote) == 0 {
+ fmt.Printf("%s\n", buffer)
+ buffer = quote
+ }
+ }
+ if buffer != "" {
+ fmt.Printf("%s\n", buffer)
+ }
+}
+
+func main() {
+ // Open STDIN as scanner
+ _, err := os.Stdin.Stat()
+ if err != nil {
+ fmt.Printf("%s\n", "cannot read input")
+ os.Exit(1)
+ }
+ input := bufio.NewScanner(os.Stdin)
+
+ // Look for arguments
+ var width = flag.Int("width", 80, "target width for output")
+ flag.Parse()
+
+ // Compile regular expressions
+ re_quote, err := regexp.Compile("^([> ]*)")
+ if err != nil {
+ fmt.Printf("internal error - %v\n", err)
+ os.Exit(1)
+ }
+ re_break, err := regexp.Compile("^(?:-{5,}|={5,})$")
+ if err != nil {
+ fmt.Printf("internal error - %v\n", err)
+ os.Exit(1)
+ }
+
+ // Scan line by line
+ for input.Scan() {
+ line := input.Text()
+ line = strings.TrimSpace(line)
+
+ if len(line) > *width {
+ if re_break.MatchString(line) {
+ print_break(*width)
+ } else {
+ quote := re_quote.FindString(line)
+ print_wrapped(line, *width, quote)
+ }
+ } else {
+ fmt.Printf("%s\n", line)
+ }
+ }
+
+ // Check for scanner errors
+ if err = input.Err(); err != nil {
+ fmt.Printf("internal error - %v\n", err)
+ os.Exit(1)
+ }
+}
+
A => textwrap/textwrap +0 -0