|
3 | 3 |
|
4 | 4 | import json
|
5 | 5 | import pyperclip
|
| 6 | +import re |
6 | 7 | import toml
|
7 | 8 | import yaml
|
8 | 9 | from argparse import ArgumentParser
|
|
15 | 16 | from pygments.lexers import JsonLexer, TOMLLexer, YamlLexer
|
16 | 17 | from shutil import get_terminal_size
|
17 | 18 | from sys import stdin, stdout, stderr
|
18 |
| -from typing import Any, IO, Optional, Sequence |
| 19 | +from typing import Any, List, IO, Optional, Sequence, Union |
19 | 20 | from unittest.mock import patch
|
20 | 21 |
|
21 | 22 | __version__ = '0.2.4'
|
22 | 23 |
|
| 24 | +NUMERIC = re.compile(r'-?\d+$|-?\d+\.\d+$|^-?\d+\.?\d+e-?\d+$') |
| 25 | +DICT_OR_LIST = re.compile(r'^\{.*\}$|^\[.*\]$') |
| 26 | + |
23 | 27 |
|
24 | 28 | def print_inf(msg: Any):
|
25 | 29 | print(f'\033[1;94mjsonfmt:\033[0m \033[0;94m{msg}\033[0m', file=stdout)
|
@@ -69,6 +73,47 @@ def parse_to_pyobj(text: str, jpath: Optional[str]) -> tuple[Any, str]:
|
69 | 73 | return subelements, fmt
|
70 | 74 |
|
71 | 75 |
|
| 76 | +def forward_by_keys(py_obj: Any, keys: str) -> tuple[Any, Union[str, int]]: |
| 77 | + next_k = lambda obj, k: int(k) if isinstance(obj, list) else k |
| 78 | + |
| 79 | + _keys = keys.replace(']', '').replace('[', '.').split('.') |
| 80 | + for k in _keys[:-1]: |
| 81 | + py_obj = py_obj[next_k(py_obj, k)] |
| 82 | + else: |
| 83 | + return py_obj, next_k(py_obj, _keys[-1]) |
| 84 | + |
| 85 | + |
| 86 | +def load_value(value: str): |
| 87 | + if NUMERIC.match(value): |
| 88 | + return eval(value) |
| 89 | + elif DICT_OR_LIST.match(value): |
| 90 | + try: |
| 91 | + return eval(value) |
| 92 | + except Exception: |
| 93 | + return value |
| 94 | + else: |
| 95 | + return value |
| 96 | + |
| 97 | + |
| 98 | +def modify_pyobj(py_obj: Any, sets: List[str], pops: List[str]): |
| 99 | + for kv in sets: |
| 100 | + try: |
| 101 | + keys, value = kv.split('=') |
| 102 | + bottom, last_k = forward_by_keys(py_obj, keys) |
| 103 | + bottom[last_k] = load_value(value) |
| 104 | + except (IndexError, KeyError, ValueError): |
| 105 | + print_err(f'invalid key path: {kv}') |
| 106 | + continue |
| 107 | + |
| 108 | + for keys in pops: |
| 109 | + try: |
| 110 | + bottom, last_k = forward_by_keys(py_obj, keys) |
| 111 | + bottom.pop(last_k) |
| 112 | + except (IndexError, KeyError): |
| 113 | + print_err(f'invalid key path: {keys}') |
| 114 | + continue |
| 115 | + |
| 116 | + |
72 | 117 | def get_overview(py_obj: Any):
|
73 | 118 | def clip_value(value: Any):
|
74 | 119 | if isinstance(value, str):
|
@@ -136,11 +181,17 @@ def output(output_fp: IO, text: str, fmt: str, cp2clip: bool):
|
136 | 181 |
|
137 | 182 | def process(input_fp: IO, jpath: Optional[str], to_format: Optional[str], *,
|
138 | 183 | compact: bool, cp2clip: bool, escape: bool, indent: int,
|
139 |
| - overview: bool, overwrite: bool, sort_keys: bool): |
| 184 | + overview: bool, overwrite: bool, sort_keys: bool, |
| 185 | + sets: Optional[list], pops: Optional[list]): |
140 | 186 | # parse and format
|
141 | 187 | input_text = input_fp.read()
|
142 | 188 | py_obj, fmt = parse_to_pyobj(input_text, jpath)
|
143 | 189 |
|
| 190 | + if sets or pops: |
| 191 | + sets = sets or [] |
| 192 | + pops = pops or [] |
| 193 | + modify_pyobj(py_obj, sets, pops) |
| 194 | + |
144 | 195 | if overview:
|
145 | 196 | py_obj = get_overview(py_obj)
|
146 | 197 |
|
@@ -178,6 +229,10 @@ def parse_cmdline_args(args: Optional[Sequence[str]] = None):
|
178 | 229 | help='output part of the object via jsonpath')
|
179 | 230 | parser.add_argument('-s', dest='sort_keys', action='store_true',
|
180 | 231 | help='sort keys of objects on output')
|
| 232 | + parser.add_argument('--set', nargs='*', metavar="foo.k1=v1 k2[i]=v2", |
| 233 | + help='set the keys to values') |
| 234 | + parser.add_argument('--pop', nargs='*', metavar="k1 foo.k2 k3[i]", |
| 235 | + help='pop the specified keys') |
181 | 236 | parser.add_argument(dest='files', nargs='*',
|
182 | 237 | help='the files that will be processed')
|
183 | 238 | parser.add_argument('-v', dest='version', action='version',
|
@@ -208,7 +263,9 @@ def main():
|
208 | 263 | indent=args.indent,
|
209 | 264 | overview=args.overview,
|
210 | 265 | overwrite=args.overwrite,
|
211 |
| - sort_keys=args.sort_keys) |
| 266 | + sort_keys=args.sort_keys, |
| 267 | + sets=args.set, |
| 268 | + pops=args.pop) |
212 | 269 | except ParseError as err:
|
213 | 270 | print_err(err)
|
214 | 271 | except FileNotFoundError:
|
|
0 commit comments