#!/usr/bin/python3 # The MIT License (MIT) # # Copyright © 2024 pacman64 # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the “Software”), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. info = ''' tb [options...] [python expression] [filepath/URI...] This is the MINImal version of the Transform Bytes tool. ''' from sys import argv, exit, stderr, stdin, stdout from typing import Generator # no args or a leading help-option arg means show the help message and quit help_opts = ('-h', '--h', '-help', '--help') if len(argv) < 2 or (len(argv) == 2 and argv[1] in help_opts): print(info.strip(), file=stderr) exit(0) class Skip: pass skip = Skip() def recover(attempt, fallback = None): try: return attempt() except Exception as e: if callable(fallback): return fallback(e) return fallback recovered = recover rescue = recover rescued = recover no_input_opts = ( '=', '-n', '--n', '-nil', '--nil', '-none', '--none', '-null', '--null', ) args = argv[1:] load_input = True expression = None # handle all other leading options; the explicit help options are # handled earlier in the script while len(args) > 0: if args[0] in no_input_opts: load_input = False args = args[1:] continue break if len(args) > 0: expression = args[0] args = args[1:] if expression is None: print(info.strip(), file=stderr) exit(0) def make_open_read(open): 'Restrict the file-open func to a read-only-binary file-open func.' def open_readonly(name): return open(name, mode='rb') return open_readonly def seems_url(s): protocols = ('https://', 'http://', 'file://', 'ftp://', 'data:') return any(s.startswith(p) for p in protocols) def tobytes(x): if isinstance(x, bytes): return x if isinstance(x, (bool, int)): return bytes(int(x)) if isinstance(x, float): return bytes(str(x), encoding='utf-8') if isinstance(x, str): return bytes(x, encoding='utf-8') return bytes(x) def tointorbytes(x): return x if isinstance(x, int) else tobytes(x) open_read = make_open_read(open) open = open_read exec = None def adapt_result(x, default): if x is True: return default if x is False: return None if isinstance(x, Skip): return None if callable(x): return x(default) return x def emit_result(w, x): if x is None: return if isinstance(x, (list, tuple, range, Generator)): for e in x: w.write(tobytes(e)) return w.write(tobytes(x)) def eval_expr(expr, using): global v, val, value, d, dat, data # offer several aliases for the variable with the input bytes v = val = value = d = dat = data = using return adapt_result(eval(expr), using) import functools import itertools import math import random import statistics import string import time try: if not expression or expression == '.': expression = 'data' expression = compile(expression, expression, 'eval') got_stdin = False all_stdin = None dashes = args.count('-') if any(seems_url(name) for name in args): from urllib.request import urlopen data = None if load_input: for name in args: if name == '-': if dashes > 1: if not got_stdin: all_stdin = stdin.buffer.read() got_stdin = True data = all_stdin else: data = stdin.buffer.read() elif seems_url(name): with urlopen(name) as inp: data = inp.read() else: with open_read(name) as inp: data = inp.read() emit_result(stdout.buffer, eval_expr(expression, data)) if len(args) == 0: data = stdin.buffer.read() emit_result(stdout.buffer, eval_expr(expression, data)) else: emit_result(stdout.buffer, eval(expression)) except BrokenPipeError: # quit quietly, instead of showing a confusing error message stderr.close() except KeyboardInterrupt: exit(2) except Exception as e: s = str(e) s = s if s else '' print(f'\x1b[31m{s}\x1b[0m', file=stderr) exit(1)