|
@@ -0,0 +1,92 @@ |
|
|
|
|
|
#!/usr/bin/env python3 |
|
|
|
|
|
try: |
|
|
|
|
|
from contextlib import nullcontext |
|
|
|
|
|
except ImportError: |
|
|
|
|
|
# Python 3.6 and below |
|
|
|
|
|
# One-liner because I can. |
|
|
|
|
|
nullcontext = type("nc", (object,), {"__init__": (lambda self, v: setattr(self, "v", v)), "__enter__": (lambda self: self.v), "__exit__": (lambda self, *args, **kwargs: None)}) |
|
|
|
|
|
import json |
|
|
|
|
|
import sys |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def parse_dict(f): |
|
|
|
|
|
d = {} |
|
|
|
|
|
while True: |
|
|
|
|
|
c = f.read(1) |
|
|
|
|
|
if c == b'e': |
|
|
|
|
|
return d |
|
|
|
|
|
elif b'0' <= c <= b'9': |
|
|
|
|
|
key = parse_str(f, c) |
|
|
|
|
|
value = parse_any(f) |
|
|
|
|
|
d[key] = value |
|
|
|
|
|
else: |
|
|
|
|
|
raise ValueError(f'invalid bencode value: {c} instead of expected e or digit') |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def parse_list(f): |
|
|
|
|
|
l = [] |
|
|
|
|
|
while True: |
|
|
|
|
|
c = f.read(1) |
|
|
|
|
|
if c == b'e': |
|
|
|
|
|
return l |
|
|
|
|
|
l.append(parse_any(f, c)) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _parse_int_bytes(f, stopChar): |
|
|
|
|
|
# Read until encountering a non-decimal character; if it isn't stopChar, raise a ValueError |
|
|
|
|
|
v = b'' |
|
|
|
|
|
# Python 3.8+ (walrus) |
|
|
|
|
|
#while b'0' <= (c := f.read(1)) <= b'9': |
|
|
|
|
|
# v += c |
|
|
|
|
|
while True: |
|
|
|
|
|
c = f.read(1) |
|
|
|
|
|
if b'0' <= c <= b'9': |
|
|
|
|
|
v += c |
|
|
|
|
|
else: |
|
|
|
|
|
break |
|
|
|
|
|
if c == stopChar: |
|
|
|
|
|
return v |
|
|
|
|
|
raise ValueError(f'invalid bencode value: {c} instead of expected {stopChar}') |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def parse_int(f): |
|
|
|
|
|
return int(_parse_int_bytes(f, b'e')) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def parse_str(f, c): |
|
|
|
|
|
v = _parse_int_bytes(f, b':') |
|
|
|
|
|
length = int(c + v) |
|
|
|
|
|
read = 0 |
|
|
|
|
|
buf = [] |
|
|
|
|
|
while read < length: |
|
|
|
|
|
d = f.read(length - read) |
|
|
|
|
|
buf.append(d) |
|
|
|
|
|
read += len(d) |
|
|
|
|
|
buf = b''.join(buf) |
|
|
|
|
|
try: |
|
|
|
|
|
return buf.decode('utf-8', 'strict') |
|
|
|
|
|
except UnicodeDecodeError: |
|
|
|
|
|
return buf.decode('iso-8859-1') |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def parse_any(f, c = None): |
|
|
|
|
|
if c is None: |
|
|
|
|
|
c = f.read(1) |
|
|
|
|
|
if c == b'd': |
|
|
|
|
|
return parse_dict(f) |
|
|
|
|
|
elif c == b'l': |
|
|
|
|
|
return parse_list(f) |
|
|
|
|
|
elif c == b'i': |
|
|
|
|
|
return parse_int(f) |
|
|
|
|
|
elif b'0' <= c <= b'9': |
|
|
|
|
|
return parse_str(f, c) |
|
|
|
|
|
else: |
|
|
|
|
|
raise ValueError(f'invalid bencode value: {c} instead of expected d, l, i, or digit') |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if sys.argv[1:]: |
|
|
|
|
|
c = open(sys.argv[1], 'rb') |
|
|
|
|
|
else: |
|
|
|
|
|
c = nullcontext(sys.stdin.buffer) |
|
|
|
|
|
with c as fp: |
|
|
|
|
|
print(json.dumps(parse_any(fp))) |