File: minitj.py
   1 #!/usr/bin/python3
   2 
   3 # The MIT License (MIT)
   4 #
   5 # Copyright © 2024 pacman64
   6 #
   7 # Permission is hereby granted, free of charge, to any person obtaining a copy
   8 # of this software and associated documentation files (the “Software”), to deal
   9 # in the Software without restriction, including without limitation the rights
  10 # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  11 # copies of the Software, and to permit persons to whom the Software is
  12 # furnished to do so, subject to the following conditions:
  13 #
  14 # The above copyright notice and this permission notice shall be included in
  15 # all copies or substantial portions of the Software.
  16 #
  17 # THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  18 # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  19 # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  20 # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  21 # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  22 # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
  23 # SOFTWARE.
  24 
  25 
  26 info = '''
  27 minitj [options...] [python expression] [files/URIs...]
  28 
  29 This is the MINImal version of the Transform Json tool.
  30 '''
  31 
  32 
  33 from io import TextIOWrapper
  34 from json import dump, load
  35 from sys import argv, exit, stderr, stdin, stdout
  36 from typing import Iterable
  37 
  38 
  39 if len(argv) < 2 or argv[1] in ('-h', '--h', '-help', '--help'):
  40     print(info.strip(), file=stderr)
  41     exit(0)
  42 
  43 
  44 class Skip:
  45     pass
  46 
  47 
  48 skip = Skip()
  49 
  50 
  51 def rescue(attempt, fallback = None):
  52     try:
  53         return attempt()
  54     except Exception as e:
  55         if callable(fallback):
  56             return fallback(e)
  57         return fallback
  58 
  59 catch = rescue
  60 catched = rescue
  61 caught = rescue
  62 recover = rescue
  63 recovered = rescue
  64 rescued = rescue
  65 
  66 
  67 def result_needs_fixing(x):
  68     if x is None or isinstance(x, (bool, int, float, str)):
  69         return False
  70     rec = result_needs_fixing
  71     if isinstance(x, dict):
  72         return any(rec(k) or rec(v) for k, v in x.items())
  73     if isinstance(x, (list, tuple)):
  74         return any(rec(e) for e in x)
  75     return True
  76 
  77 
  78 def fix_result(x, default):
  79     if x is type:
  80         return type(default).__name__
  81 
  82     # if expression results in a func, auto-call it with the original data
  83     if callable(x):
  84         x = x(default)
  85 
  86     if x is None or isinstance(x, (bool, int, float, str)):
  87         return x
  88 
  89     rec = fix_result
  90 
  91     if isinstance(x, dict):
  92         return {
  93             rec(k, default): rec(v, default) for k, v in x.items() if not
  94                 (isinstance(k, Skip) or isinstance(v, Skip))
  95         }
  96     if isinstance(x, Iterable):
  97         return tuple(rec(e, default) for e in x if not isinstance(e, Skip))
  98 
  99     if isinstance(x, Exception):
 100         raise x
 101 
 102     return None if isinstance(x, Skip) else str(x)
 103 
 104 
 105 def seems_url(path):
 106     protocols = ('https://', 'http://', 'file://', 'ftp://', 'data:')
 107     return any(path.startswith(p) for p in protocols)
 108 
 109 
 110 cr = '\r'
 111 crlf = '\r\n'
 112 dquo = '"'
 113 dquote = '"'
 114 empty = ''
 115 lcurly = '{'
 116 lf = '\n'
 117 rcurly = '}'
 118 s = ''
 119 squo = '\''
 120 squote = '\''
 121 utf8bom = '\xef\xbb\xbf'
 122 
 123 nil = None
 124 none = None
 125 null = None
 126 
 127 
 128 no_input_opts = (
 129     '=', '-n', '--n', '-nil', '--nil', '-none', '--none', '-null', '--null',
 130 )
 131 compact_output_opts = (
 132     '-c', '--c', '-compact', '--compact', '-j0', '--j0', '-json0', '--json0',
 133 )
 134 more_modules_opts = ('-mm', '--mm', '-more', '--more')
 135 
 136 args = argv[1:]
 137 no_input = False
 138 compact_output = False
 139 
 140 while len(args) > 0:
 141     if args[0] in no_input_opts:
 142         no_input = True
 143         args = args[1:]
 144         continue
 145 
 146     if args[0] in compact_output_opts:
 147         compact_output = True
 148         args = args[1:]
 149         continue
 150 
 151     if args[0] in more_modules_opts:
 152         import functools
 153         import itertools
 154         import math
 155         import random
 156         import statistics
 157         import string
 158         import time
 159         args = args[1:]
 160         continue
 161 
 162     break
 163 
 164 
 165 try:
 166     expr = 'data'
 167     if len(args) > 0:
 168         expr = args[0]
 169         args = args[1:]
 170 
 171     if expr == '.':
 172         expr = 'data'
 173 
 174     if len(args) > 1:
 175         raise Exception('can\'t use more than 1 input')
 176     path = '-' if len(args) == 0 else args[0]
 177 
 178     if no_input:
 179         data = None
 180     elif path == '-':
 181         data = load(stdin)
 182     elif seems_url(path):
 183         from urllib.request import urlopen
 184         with urlopen(path) as inp:
 185             with TextIOWrapper(inp, encoding='utf-8') as txt:
 186                 data = load(txt)
 187     else:
 188         with open(path, encoding='utf-8') as inp:
 189             data = load(inp)
 190 
 191     exec = None
 192     open = None
 193     v = value = d = data
 194     v = eval(expr)
 195     if result_needs_fixing(v):
 196         v = fix_result(v, data)
 197 
 198     if compact_output:
 199         dump(v, stdout, indent=None, separators=(',', ':'), allow_nan=False)
 200     else:
 201         dump(v, stdout, indent=2, separators=(',', ': '), allow_nan=False)
 202     print()
 203 except BrokenPipeError:
 204     exit(0)
 205 except Exception as e:
 206     print(f'\x1b[31m{str(e)}\x1b[0m', file=stderr)
 207     exit(1)