The little things give you away... A collection of various small helper stuff
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

93 lines
1.9 KiB

  1. #!/usr/bin/env python3
  2. try:
  3. from contextlib import nullcontext
  4. except ImportError:
  5. # Python 3.6 and below
  6. # One-liner because I can.
  7. nullcontext = type("nc", (object,), {"__init__": (lambda self, v: setattr(self, "v", v)), "__enter__": (lambda self: self.v), "__exit__": (lambda self, *args, **kwargs: None)})
  8. import json
  9. import sys
  10. def parse_dict(f):
  11. d = {}
  12. while True:
  13. c = f.read(1)
  14. if c == b'e':
  15. return d
  16. elif b'0' <= c <= b'9':
  17. key = parse_str(f, c)
  18. value = parse_any(f)
  19. d[key] = value
  20. else:
  21. raise ValueError(f'invalid bencode value: {c} instead of expected e or digit')
  22. def parse_list(f):
  23. l = []
  24. while True:
  25. c = f.read(1)
  26. if c == b'e':
  27. return l
  28. l.append(parse_any(f, c))
  29. def _parse_int_bytes(f, stopChar):
  30. # Read until encountering a non-decimal character; if it isn't stopChar, raise a ValueError
  31. v = b''
  32. # Python 3.8+ (walrus)
  33. #while b'0' <= (c := f.read(1)) <= b'9':
  34. # v += c
  35. while True:
  36. c = f.read(1)
  37. if b'0' <= c <= b'9':
  38. v += c
  39. else:
  40. break
  41. if c == stopChar:
  42. return v
  43. raise ValueError(f'invalid bencode value: {c} instead of expected {stopChar}')
  44. def parse_int(f):
  45. return int(_parse_int_bytes(f, b'e'))
  46. def parse_str(f, c):
  47. v = _parse_int_bytes(f, b':')
  48. length = int(c + v)
  49. read = 0
  50. buf = []
  51. while read < length:
  52. d = f.read(length - read)
  53. buf.append(d)
  54. read += len(d)
  55. buf = b''.join(buf)
  56. try:
  57. return buf.decode('utf-8', 'strict')
  58. except UnicodeDecodeError:
  59. return buf.decode('iso-8859-1')
  60. def parse_any(f, c = None):
  61. if c is None:
  62. c = f.read(1)
  63. if c == b'd':
  64. return parse_dict(f)
  65. elif c == b'l':
  66. return parse_list(f)
  67. elif c == b'i':
  68. return parse_int(f)
  69. elif b'0' <= c <= b'9':
  70. return parse_str(f, c)
  71. else:
  72. raise ValueError(f'invalid bencode value: {c} instead of expected d, l, i, or digit')
  73. if sys.argv[1:]:
  74. c = open(sys.argv[1], 'rb')
  75. else:
  76. c = nullcontext(sys.stdin.buffer)
  77. with c as fp:
  78. print(json.dumps(parse_any(fp)))