#!/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 = ''' minitl [options...] [python expression] [files/URIs...] This is the MINImal version of the Transform Lines tool. ''' from io import TextIOWrapper from json import dumps from sys import argv, exit, stderr, stdin from typing import Generator if len(argv) < 2 or argv[1] in ('-h', '--h', '-help', '--help'): print(info.strip(), file=stderr) exit(0) def handle_no_input(expr): res = eval(expr) if isinstance(res, (list, range, tuple, Generator)): for e in res: if not isinstance(e, Skip): print(e) return res = adapt_result(res, None) if not (res is None): print(res) def handle_lines(src, expr): # `comprehension` expressions seem to ignore local variables: even # lambda-based workarounds fail global i, l, line i = 0 for e in src: l = e.rstrip('\r\n').rstrip('\n') if i == 0: l = l.lstrip('\xef\xbb\xbf') line = l res = eval(expr) i += 1 if isinstance(res, (list, range, tuple, Generator)): for e in res: if not isinstance(e, Skip): print(e) continue res = adapt_result(res, line) if not (res is None): print(res) def hold_lines(src, lines): for e in src: lines.append(e) yield e def adapt_result(res, fallback): if callable(res): return res(fallback) if res is None or res is False or isinstance(res, Skip): return None if res is True: return fallback if isinstance(res, dict): return dumps(res, allow_nan=False) return str(res) class Skip: pass skip = Skip() def rescue(attempt, fallback = None): try: return attempt() except Exception as e: if callable(fallback): return fallback(e) return fallback catch = rescue catched = rescue caught = rescue recover = rescue recovered = rescue rescued = rescue def make_open_utf8(open): def open_utf8_readonly(path): return open(path, encoding='utf-8') return open_utf8_readonly def seems_url(path): protocols = ('https://', 'http://', 'file://', 'ftp://', 'data:') return any(path.startswith(p) for p in protocols) cr = '\r' crlf = '\r\n' dquo = '"' dquote = '"' empty = '' lcurly = '{' lf = '\n' rcurly = '}' s = '' squo = '\'' squote = '\'' utf8bom = '\xef\xbb\xbf' nil = None none = None null = None exec = None open_utf8 = make_open_utf8(open) open = open_utf8 no_input_opts = ( '=', '--n', '-nil', '--nil', '-none', '--none', '-null', '--null', ) more_modules_opts = ('-mm', '--mm', '-more', '--more') args = argv[1:] if any(seems_url(e) for e in args): from urllib.request import urlopen no_input = False while len(args) > 0: if args[0] in no_input_opts: no_input = True args = args[1:] continue if args[0] in more_modules_opts: import functools import itertools import math import random import statistics import string import time args = args[1:] continue break try: expr = '.' if len(args) > 0: expr = args[0] args = args[1:] # if expr in globals().keys(): # v = globals().get(expr) # if callable(v): # expr = f'{expr}(line)' if expr == '.' and no_input: print(info.strip(), file=stderr) exit(0) if expr == '.': expr = 'line' expr = compile(expr, expr, 'eval') if no_input: handle_no_input(expr) exit(0) if len(args) == 0: handle_lines(stdin, expr) exit(0) got_stdin = False all_stdin = None dashes = args.count('-') for path in args: if path == '-': if dashes > 1: if not got_stdin: handle_lines(hold_lines(stdin, all_stdin), expr) got_stdin = True else: handle_lines(all_stdin, expr) else: handle_lines(stdin, expr) continue if seems_url(path): with urlopen(path) as inp: with TextIOWrapper(inp, encoding='utf-8') as txt: handle_lines(txt, expr) continue with open_utf8(path) as txt: handle_lines(txt, expr) except BrokenPipeError: exit(0) except Exception as e: print(f'\x1b[31m{str(e)}\x1b[0m', file=stderr) exit(1)