mquery-toolkit 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
mquery_toolkit/cli.py ADDED
@@ -0,0 +1,118 @@
1
+ """Command line interface with dry-run source edits by default."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import argparse
6
+ import json
7
+ from collections.abc import Callable
8
+ from pathlib import Path
9
+ from typing import Any
10
+
11
+ from .core import (
12
+ MQueryError,
13
+ check,
14
+ dependencies,
15
+ format_source,
16
+ parse,
17
+ rename,
18
+ replace_source,
19
+ update_file,
20
+ )
21
+
22
+
23
+ def _source(path: Path) -> str:
24
+ limit = 10 * 1024 * 1024
25
+ if path.stat().st_size > limit:
26
+ raise MQueryError("input exceeds 10 MiB")
27
+ with path.open("rb") as handle:
28
+ data = handle.read(limit + 1)
29
+ if len(data) > limit:
30
+ raise MQueryError("input exceeds 10 MiB")
31
+ return data.decode("utf-8", "strict")
32
+
33
+
34
+ def _print(value: Any, as_json: bool) -> None:
35
+ if as_json or not isinstance(value, str):
36
+ print(json.dumps(value, sort_keys=True, default=lambda item: item.as_dict()))
37
+ else:
38
+ print(value, end="" if value.endswith("\n") else "\n")
39
+
40
+
41
+ def main(argv: list[str] | None = None) -> int:
42
+ parser = argparse.ArgumentParser(prog="mquery")
43
+ parser.add_argument(
44
+ "command",
45
+ choices=[
46
+ "parse",
47
+ "format",
48
+ "check",
49
+ "dependencies",
50
+ "rename",
51
+ "replace-source",
52
+ ],
53
+ )
54
+ parser.add_argument("file", type=Path)
55
+ parser.add_argument("--json", action="store_true")
56
+ parser.add_argument(
57
+ "--write",
58
+ action="store_true",
59
+ help="atomically replace source after validation",
60
+ )
61
+ parser.add_argument("--old")
62
+ parser.add_argument("--new")
63
+ parser.add_argument(
64
+ "--source", help="complete replacement source for replace-source"
65
+ )
66
+ args = parser.parse_args(argv)
67
+ try:
68
+ if args.command == "parse":
69
+ _print(parse(_source(args.file)), True)
70
+ return 0
71
+ if args.command == "check":
72
+ diagnostics = check(_source(args.file), str(args.file))
73
+ if args.json:
74
+ _print([item.as_dict() for item in diagnostics], True)
75
+ else:
76
+ for item in diagnostics:
77
+ print(
78
+ f"{item.file}:{item.line}:{item.column}: "
79
+ f"{item.severity} {item.code}: {item.message}"
80
+ )
81
+ return 2 if any(item.severity == "error" for item in diagnostics) else 0
82
+ if args.command == "dependencies":
83
+ _print(dependencies(_source(args.file)), True)
84
+ return 0
85
+ transform: Callable[[str], str]
86
+ if args.command == "format":
87
+ transform = format_source
88
+ elif args.command == "rename":
89
+ if args.old is None or args.new is None:
90
+ raise MQueryError("rename requires --old and --new")
91
+ old, new = args.old, args.new
92
+
93
+ def transform(text: str) -> str:
94
+ return rename(text, old, new)
95
+
96
+ else:
97
+ if args.source is None:
98
+ raise MQueryError("replace-source requires --source")
99
+ replacement = args.source
100
+
101
+ def transform(text: str) -> str:
102
+ return replace_source(text, replacement)
103
+
104
+ _print(update_file(args.file, transform, write=args.write), args.json)
105
+ return 0
106
+ except (OSError, UnicodeDecodeError, MQueryError) as error:
107
+ code = getattr(error, "code", "M_IO_ERROR")
108
+ _print(
109
+ {"code": code, "message": str(error)}
110
+ if args.json
111
+ else f"error {code}: {error}",
112
+ args.json,
113
+ )
114
+ return 2
115
+
116
+
117
+ if __name__ == "__main__":
118
+ raise SystemExit(main())