Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/turbohtml/__main__.py: 66%
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1"""
2turbohtml.__main__: a thin ``python -m turbohtml`` front end over the public API.
4Every subcommand parses argv, reads one input (a file argument or stdin), calls the matching public function, and
5writes the result (stdout or ``-o``); the transforms themselves live in the C core, so this module only wires argv to
6them. It gives the toolkit the command-line surface ``html2text``, ``markdownify``, ``htmlmin``, ``inscriptis``,
7``minify-html``, ``charset-normalizer``, and ``courlan`` each ship and turbohtml lacked.
8"""
10from __future__ import annotations
12import argparse
13import sys
14from pathlib import Path
15from typing import TYPE_CHECKING
17from . import parse
18from ._html import Minify
19from .clean import CSSMinify, minify, minify_css, minify_js, sanitize
20from .detect import detect
22if TYPE_CHECKING:
23 from collections.abc import Sequence
25__all__ = ["main"]
28def main(argv: Sequence[str] | None = None) -> int:
29 """
30 Run the ``python -m turbohtml`` command line.
32 :param argv: the argument vector, excluding the program name; ``None`` reads ``sys.argv``.
33 :returns: the process exit code -- 0 on success, 1 when the library rejects the input or the file cannot be read.
34 """
35 args = _parser().parse_args(argv)
36 try:
37 return args.handler(args)
38 except (OSError, ValueError, LookupError) as error:
39 sys.stderr.write(f"turbohtml {args.command}: {error}\n")
40 return 1
43def _parser() -> argparse.ArgumentParser:
44 """Build the argument parser: one subcommand per public entry point, each sharing the input/output arguments."""
45 parser = argparse.ArgumentParser(prog="python -m turbohtml", description="Command-line front end for turbohtml.")
46 shared = argparse.ArgumentParser(add_help=False)
47 shared.add_argument("file", nargs="?", help="input file; omit or pass - to read stdin")
48 shared.add_argument("-o", "--output", metavar="FILE", help="write to this file instead of stdout")
49 commands = parser.add_subparsers(dest="command", required=True)
51 minify_parser = commands.add_parser("minify", parents=[shared], help="minify an HTML document")
52 minify_parser.add_argument(
53 "--minify-css", action="store_true", help="also minify <style> bodies and style attributes"
54 )
55 minify_parser.set_defaults(handler=_run_minify)
57 commands.add_parser("minify-css", parents=[shared], help="minify a CSS stylesheet").set_defaults(
58 handler=_run_minify_css
59 )
60 commands.add_parser("minify-js", parents=[shared], help="minify a JavaScript source").set_defaults(
61 handler=_run_minify_js
62 )
63 commands.add_parser("detect", parents=[shared], help="report the character encoding of bytes").set_defaults(
64 handler=_run_detect
65 )
66 commands.add_parser("to-markdown", parents=[shared], help="render HTML as Markdown").set_defaults(
67 handler=_run_to_markdown
68 )
69 commands.add_parser("to-text", parents=[shared], help="render HTML as layout-aware plain text").set_defaults(
70 handler=_run_to_text
71 )
72 commands.add_parser("sanitize", parents=[shared], help="sanitize HTML against the default policy").set_defaults(
73 handler=_run_sanitize
74 )
75 return parser
78def _run_minify(args: argparse.Namespace) -> int:
79 _write(minify(_read_text(args.file), Minify(minify_css=CSSMinify() if args.minify_css else None)), args.output)
80 return 0
83def _run_minify_css(args: argparse.Namespace) -> int:
84 _write(minify_css(_read_text(args.file)), args.output)
85 return 0
88def _run_minify_js(args: argparse.Namespace) -> int:
89 _write(minify_js(_read_text(args.file)), args.output)
90 return 0
93def _run_detect(args: argparse.Namespace) -> int:
94 if (match := detect(_read_bytes(args.file))).encoding is None:
95 sys.stderr.write("turbohtml detect: no encoding detected\n")
96 return 1
97 _write(f"{match.encoding}\n", args.output)
98 return 0
101def _run_to_markdown(args: argparse.Namespace) -> int:
102 _write(parse(_read_text(args.file)).to_markdown(), args.output)
103 return 0
106def _run_to_text(args: argparse.Namespace) -> int:
107 _write(parse(_read_text(args.file)).to_text(), args.output)
108 return 0
111def _run_sanitize(args: argparse.Namespace) -> int:
112 _write(sanitize(_read_text(args.file)), args.output)
113 return 0
116def _read_text(path: str | None) -> str:
117 """Read text input from ``path``, or stdin when it is ``None`` or ``-``."""
118 if path is None or path == "-":
119 return sys.stdin.read()
120 return Path(path).read_text(encoding="utf-8")
123def _read_bytes(path: str | None) -> bytes:
124 """Read binary input from ``path``, or stdin when it is ``None`` or ``-``."""
125 if path is None or path == "-":
126 return sys.stdin.buffer.read()
127 return Path(path).read_bytes()
130def _write(text: str, output: str | None) -> None:
131 """Write ``text`` to ``output``, or stdout when it is ``None`` or ``-``."""
132 if output is None or output == "-":
133 sys.stdout.write(text)
134 else:
135 Path(output).write_text(text, encoding="utf-8")
138if __name__ == "__main__":
139 raise SystemExit(main())