|
- #!/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)))
|