• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

Hekxsler / pudding / 25738751695

12 May 2026 01:48PM UTC coverage: 92.24% (-1.0%) from 93.254%
25738751695

Pull #5

github

web-flow
Merge bd9dfd2d9 into a91d4e7d1
Pull Request #5: Feature/specifiy output

1260 of 1366 relevant lines covered (92.24%)

0.92 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

70.77
pudding/_cli.py
1
"""Main script."""
2

3
import argparse
1✔
4
import datetime
1✔
5
import logging
1✔
6
import os
1✔
7
from collections.abc import Sequence
1✔
8
from pathlib import Path
1✔
9

10
from .util import convert_files
1✔
11
from .version import __version__
1✔
12
from .writer import __all__ as formats
1✔
13

14
DEFAULT_FORMAT = "json"
1✔
15
DESCRIPTION = """
1✔
16
Pudding converts text to a structured format, such as XML, JSON or YAML.
17
For more information see the documentation at https://pudding.readthedocs.io/latest.
18
"""
19

20
format_choices = [f.lower() for f in formats]
1✔
21
logger = logging.getLogger(__name__)
1✔
22

23

24
def build_parser() -> argparse.ArgumentParser:
1✔
25
    """Build argument parser."""
26
    parser = argparse.ArgumentParser(prog="pudding", description=DESCRIPTION)
1✔
27
    parser.add_argument(
1✔
28
        "filename", help="The file or files to convert.", metavar="FILE", nargs="+"
29
    )
30
    parser.add_argument(
1✔
31
        "-s",
32
        "--syntax",
33
        default=None,
34
        help="The file containing the syntax for parsing the input.",
35
        metavar="SYNTAX_FILE",
36
        required=True,
37
    )
38
    parser.add_argument(
1✔
39
        "-f",
40
        "--format",
41
        choices=format_choices,
42
        default=DEFAULT_FORMAT,
43
        help=(
44
            "The output format. "
45
            f"Choices are: {', '.join(format_choices)}. Default is `{DEFAULT_FORMAT}`."
46
        ),
47
        metavar="FORMAT",
48
    )
49
    parser.add_argument(
1✔
50
        "-o",
51
        "--output",
52
        help=(
53
            "If converting a single file this is the name of the output file. "
54
            "When converting multiple files this is the output directory."
55
        ),
56
        metavar="OUTPUT",
57
    )
58
    parser.add_argument("--debug", action="store_true", help="Print debug info.")
1✔
59
    parser.add_argument("-V", "--version", action="version", version=__version__)
1✔
60
    return parser
1✔
61

62

63
def is_valid_inpath(path: str) -> bool:
1✔
64
    """Check if a syntax file path is valid."""
65
    if not os.path.exists(path):
1✔
66
        logger.error("no such file or directory: %s", repr(path))
×
67
        return False
×
68
    if not os.path.isfile(path):
1✔
69
        logger.error("not a valid input file: %s", repr(path))
×
70
        return False
×
71
    return True
1✔
72

73

74
def main(argv: Sequence[str] | None = None) -> int:
1✔
75
    """Check cli arguments."""
76
    args = build_parser().parse_args(argv)
1✔
77

78
    log_level = logging.INFO
1✔
79
    if args.debug:
1✔
80
        log_level = logging.DEBUG
×
81
    logging.basicConfig(
1✔
82
        format="[%(asctime)s] %(levelname)s: %(message)s",
83
        datefmt="%Y-%m-%d %I:%M:%S",
84
        level=log_level,
85
    )
86

87
    if not is_valid_inpath(args.syntax):
1✔
88
        return 2
×
89

90
    ins: list[Path] = []
1✔
91
    outs: list[Path] = []
1✔
92
    if len(args.filename) == 1:
1✔
93
        f = args.filename[0]
1✔
94
        if not is_valid_inpath(f):
1✔
95
            return 2
×
96
        path, _ = os.path.splitext(f)
1✔
97
        ins.append(Path(f))
1✔
98
        if args.output:
1✔
99
            outs.append(Path(args.output))
1✔
100
        else:
101
            outs.append(Path(f"{path}.{args.format.lower()}"))
×
102
    else:
103
        for f in args.filename:
×
104
            if not is_valid_inpath(f):
×
105
                return 2
×
106
            path, _ = os.path.splitext(f)
×
107
            ins.append(Path(f))
×
108
            output_path = Path(f"{path}.{args.format.lower()}")
×
109
            if args.output:
×
110
                outs.append(Path(args.output) / output_path)
×
111
            else:
112
                outs.append(output_path)
×
113

114
    start = datetime.datetime.now()
1✔
115
    try:
1✔
116
        convert_files(Path(args.syntax), ins, outs, args.format)
1✔
117
    except ValueError as e:
×
118
        logger.error(e)
×
119
    logger.debug("Total: %s", str(datetime.datetime.now() - start))
1✔
120
    return 0
1✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc