Implement text analyzer core and CLI
This commit is contained in:
159
src/text_analyzer/cli.py
Normal file
159
src/text_analyzer/cli.py
Normal file
@@ -0,0 +1,159 @@
|
||||
"""Command-line interface for the text analyzer."""
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import sys
|
||||
from pathlib import Path
|
||||
from typing import Final
|
||||
|
||||
from text_analyzer.core import TextStatistics, analyze_file
|
||||
|
||||
DEFAULT_TOP: Final[int] = 10
|
||||
DEFAULT_MIN_LEN: Final[int] = 1
|
||||
DEFAULT_ENCODING: Final[str] = "utf-8"
|
||||
|
||||
|
||||
def build_parser() -> argparse.ArgumentParser:
|
||||
"""Build command-line parser."""
|
||||
parser = argparse.ArgumentParser(
|
||||
prog="text-analyzer",
|
||||
description="Analyze a text file.",
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
"path",
|
||||
type=Path,
|
||||
help="Path to the text file.",
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
"--top",
|
||||
type=int,
|
||||
default=DEFAULT_TOP,
|
||||
help="Number of most frequent words.",
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
"--min-len",
|
||||
type=int,
|
||||
default=DEFAULT_MIN_LEN,
|
||||
help="Minimum word length.",
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
"--format",
|
||||
choices=("text", "json"),
|
||||
default="text",
|
||||
help="Output format.",
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
"--encoding",
|
||||
default=DEFAULT_ENCODING,
|
||||
help="Input file encoding.",
|
||||
)
|
||||
|
||||
return parser
|
||||
|
||||
|
||||
def format_text(statistics: TextStatistics) -> str:
|
||||
"""Format statistics as text."""
|
||||
lines = [
|
||||
f"Lines: {statistics.lines}",
|
||||
f"Words: {statistics.words}",
|
||||
f"Characters: {statistics.characters}",
|
||||
"Top words:",
|
||||
]
|
||||
|
||||
if statistics.top_words:
|
||||
for word, count in statistics.top_words:
|
||||
lines.append(f" {word}: {count}")
|
||||
else:
|
||||
lines.append(" No words found.")
|
||||
|
||||
return "\n".join(lines)
|
||||
|
||||
|
||||
def format_json(statistics: TextStatistics) -> str:
|
||||
"""Format statistics as JSON."""
|
||||
data = {
|
||||
"lines": statistics.lines,
|
||||
"words": statistics.words,
|
||||
"characters": statistics.characters,
|
||||
"top_words": [
|
||||
{
|
||||
"word": word,
|
||||
"count": count,
|
||||
}
|
||||
for word, count in statistics.top_words
|
||||
],
|
||||
}
|
||||
|
||||
return json.dumps(
|
||||
data,
|
||||
ensure_ascii=True,
|
||||
indent=2,
|
||||
)
|
||||
|
||||
|
||||
def main() -> int:
|
||||
"""Run CLI application."""
|
||||
parser = build_parser()
|
||||
args = parser.parse_args()
|
||||
|
||||
if args.top < 0:
|
||||
print(
|
||||
"Error: --top must be non-negative.",
|
||||
file=sys.stderr,
|
||||
)
|
||||
return 2
|
||||
|
||||
if args.min_len < 1:
|
||||
print(
|
||||
"Error: --min-len must be at least 1.",
|
||||
file=sys.stderr,
|
||||
)
|
||||
return 2
|
||||
|
||||
try:
|
||||
statistics = analyze_file(
|
||||
args.path,
|
||||
top=args.top,
|
||||
min_len=args.min_len,
|
||||
encoding=args.encoding,
|
||||
)
|
||||
except FileNotFoundError:
|
||||
print(
|
||||
f"Error: file not found: {args.path}",
|
||||
file=sys.stderr,
|
||||
)
|
||||
return 1
|
||||
except UnicodeError:
|
||||
print(
|
||||
f"Error: cannot decode file using encoding: {args.encoding}",
|
||||
file=sys.stderr,
|
||||
)
|
||||
return 1
|
||||
except OSError as exc:
|
||||
print(
|
||||
f"Error: cannot read file: {exc}",
|
||||
file=sys.stderr,
|
||||
)
|
||||
return 1
|
||||
except ValueError as exc:
|
||||
print(
|
||||
f"Error: {exc}",
|
||||
file=sys.stderr,
|
||||
)
|
||||
return 2
|
||||
|
||||
if args.format == "json":
|
||||
print(format_json(statistics))
|
||||
else:
|
||||
print(format_text(statistics))
|
||||
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
Reference in New Issue
Block a user