Passed
Push — main ( 5006f2...cee75c )
by Douglas
04:00
created

mandos.entries.docs.Documenter._format()   A

Complexity

Conditions 4

Size

Total Lines 12
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 4
eloc 12
nop 2
dl 0
loc 12
rs 9.8
c 0
b 0
f 0
1
"""
2
Documents Mandos commands.
3
"""
4
5
from __future__ import annotations
6
7
import inspect
8
import os
9
from pathlib import Path
10
from typing import Sequence, Mapping
11
from dataclasses import dataclass
12
from textwrap import wrap
13
14
import pandas as pd
0 ignored issues
show
introduced by
Unable to import 'pandas'
Loading history...
15
import typer
0 ignored issues
show
introduced by
Unable to import 'typer'
Loading history...
16
from typeddfs import TypedDfs
0 ignored issues
show
introduced by
Unable to import 'typeddfs'
Loading history...
17
from typeddfs.utils import Utils as TypedDfsUtils
0 ignored issues
show
introduced by
Unable to import 'typeddfs.utils'
Loading history...
18
from typeddfs.file_formats import FileFormat
0 ignored issues
show
introduced by
Unable to import 'typeddfs.file_formats'
Loading history...
19
from typer.models import CommandInfo
0 ignored issues
show
introduced by
Unable to import 'typer.models'
Loading history...
20
21
22
DocFrame = (
23
    TypedDfs.typed("DocFrame")
24
    .require("command", dtype=str)
25
    .reserve("description", "parameters", dtype=str)
26
).build()
27
28
29
@dataclass(frozen=True, repr=True)
0 ignored issues
show
introduced by
Missing class docstring
Loading history...
30
class Documenter:
31
    level: int
32
    main: bool
33
    search: bool
34
    hidden: bool
35
    common: bool
36
    width: int
37
    flatten: bool
38
39
    def __post_init__(self):
40
        if self.main and self.search:
41
            raise ValueError("Cannot provide both --only-main and --only-search")
42
43
    def document(self, commands: Sequence[CommandInfo], to: Path, style: str) -> None:
0 ignored issues
show
introduced by
Missing function or method docstring
Loading history...
Coding Style Naming introduced by
Argument name "to" doesn't conform to snake_case naming style ('([^\\W\\dA-Z][^\\WA-Z]2,|_[^\\WA-Z]*|__[^\\WA-Z\\d_][^\\WA-Z]+__)$' pattern)

This check looks for invalid names for a range of different identifiers.

You can set regular expressions to which the identifiers must conform if the defaults do not match your requirements.

If your project includes a Pylint configuration file, the settings contained in that file take precedence.

To find out more about Pylint, please refer to their site.

Loading history...
44
        cmds = [c for c in commands if (self.hidden or not c.hidden)]
45
        if self.main:
46
            cmds = [c for c in cmds if c.name.startswith(":")]
47
        elif self.search:
48
            cmds = [c for c in cmds if not c.name.startswith(":")]
49
        table = DocFrame([self._doc_row(c) for c in cmds])
50
        self._write(to, table, style)
51
52
    def _doc_row(self, c: CommandInfo) -> pd.Series:
0 ignored issues
show
Coding Style Naming introduced by
Argument name "c" doesn't conform to snake_case naming style ('([^\\W\\dA-Z][^\\WA-Z]2,|_[^\\WA-Z]*|__[^\\WA-Z\\d_][^\\WA-Z]+__)$' pattern)

This check looks for invalid names for a range of different identifiers.

You can set regular expressions to which the identifiers must conform if the defaults do not match your requirements.

If your project includes a Pylint configuration file, the settings contained in that file take precedence.

To find out more about Pylint, please refer to their site.

Loading history...
53
        doc = c.callback.__doc__
54
        args = self._typer_param_docs(c)
55
        dct = dict(command=c.name)
56
        # descriptions
57
        if self.level >= 3:
58
            dct["description"] = doc
59
        elif self.level >= 1:
60
            dct["description"] = doc.splitlines()[0]
61
        # parameters
62
        if self.level >= 4:
63
            for i, (k, v) in enumerate(args.items()):
0 ignored issues
show
Coding Style Naming introduced by
Variable name "v" doesn't conform to snake_case naming style ('([^\\W\\dA-Z][^\\WA-Z]2,|_[^\\WA-Z]*|__[^\\WA-Z\\d_][^\\WA-Z]+__)$' pattern)

This check looks for invalid names for a range of different identifiers.

You can set regular expressions to which the identifiers must conform if the defaults do not match your requirements.

If your project includes a Pylint configuration file, the settings contained in that file take precedence.

To find out more about Pylint, please refer to their site.

Loading history...
64
                dct[f"parameter_{i}"] = f"{k}:: \n\n{v}"
65
        elif self.level >= 3:
66
            for i, (k, v) in enumerate(args.items()):
0 ignored issues
show
Coding Style Naming introduced by
Variable name "v" doesn't conform to snake_case naming style ('([^\\W\\dA-Z][^\\WA-Z]2,|_[^\\WA-Z]*|__[^\\WA-Z\\d_][^\\WA-Z]+__)$' pattern)

This check looks for invalid names for a range of different identifiers.

You can set regular expressions to which the identifiers must conform if the defaults do not match your requirements.

If your project includes a Pylint configuration file, the settings contained in that file take precedence.

To find out more about Pylint, please refer to their site.

Loading history...
67
                dct[f"parameter_{i}"] = f"{k}:: {v.splitlines()[0]}"
68
        elif self.level == 2:
69
            dct["parameters"] = " ".join(args.keys())
70
        return pd.Series(dct)
71
72
    def _typer_param_docs(self, c: CommandInfo) -> Mapping[str, str]:
0 ignored issues
show
Coding Style Naming introduced by
Argument name "c" doesn't conform to snake_case naming style ('([^\\W\\dA-Z][^\\WA-Z]2,|_[^\\WA-Z]*|__[^\\WA-Z\\d_][^\\WA-Z]+__)$' pattern)

This check looks for invalid names for a range of different identifiers.

You can set regular expressions to which the identifiers must conform if the defaults do not match your requirements.

If your project includes a Pylint configuration file, the settings contained in that file take precedence.

To find out more about Pylint, please refer to their site.

Loading history...
73
        _args = inspect.signature(c.callback).parameters
74
        args = {}
75
        for k, p in _args.items():
0 ignored issues
show
Coding Style Naming introduced by
Variable name "p" doesn't conform to snake_case naming style ('([^\\W\\dA-Z][^\\WA-Z]2,|_[^\\WA-Z]*|__[^\\WA-Z\\d_][^\\WA-Z]+__)$' pattern)

This check looks for invalid names for a range of different identifiers.

You can set regular expressions to which the identifiers must conform if the defaults do not match your requirements.

If your project includes a Pylint configuration file, the settings contained in that file take precedence.

To find out more about Pylint, please refer to their site.

Loading history...
76
            dtype = str(p.annotation)  # TODO: bad
0 ignored issues
show
Coding Style introduced by
TODO and FIXME comments should generally be avoided.
Loading history...
77
            v = p.default
0 ignored issues
show
Coding Style Naming introduced by
Variable name "v" doesn't conform to snake_case naming style ('([^\\W\\dA-Z][^\\WA-Z]2,|_[^\\WA-Z]*|__[^\\WA-Z\\d_][^\\WA-Z]+__)$' pattern)

This check looks for invalid names for a range of different identifiers.

You can set regular expressions to which the identifiers must conform if the defaults do not match your requirements.

If your project includes a Pylint configuration file, the settings contained in that file take precedence.

To find out more about Pylint, please refer to their site.

Loading history...
78
            if not isinstance(v, (typer.models.ParameterInfo, typer.models.OptionInfo)):
79
                raise AssertionError(f"{p} can't be {v} on {c.name}!")
80
            if isinstance(v, typer.models.OptionInfo):
81
                k = "--" + k
82
            k = k.replace("_", "-")
83
            k = k.replace("kind", "type")  # TODO: bad
0 ignored issues
show
Coding Style introduced by
TODO and FIXME comments should generally be avoided.
Loading history...
84
            doc = f"[type: {dtype}] " + v.help
85
            if self.hidden or not v.hidden:
86
                if self.common or k not in ["--verbose", "--quiet", "--log"]:
87
                    if (
88
                        self.common
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
89
                        or c.name.startswith(":")
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
90
                        or k not in ["path", "--key", "--to", "--as-of", "--check", "--no-setup"]
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
91
                    ):
92
                        if v.show_default:
93
                            args[k] = doc + f"\n[default: {v.default}]"
94
                        else:
95
                            args[k] = doc
96
        return args
97
98
    def _write(self, to: Path, table: pd.DataFrame, style: str) -> None:
0 ignored issues
show
Coding Style Naming introduced by
Argument name "to" doesn't conform to snake_case naming style ('([^\\W\\dA-Z][^\\WA-Z]2,|_[^\\WA-Z]*|__[^\\WA-Z\\d_][^\\WA-Z]+__)$' pattern)

This check looks for invalid names for a range of different identifiers.

You can set regular expressions to which the identifiers must conform if the defaults do not match your requirements.

If your project includes a Pylint configuration file, the settings contained in that file take precedence.

To find out more about Pylint, please refer to their site.

Loading history...
99
        if FileFormat.from_path(to) is FileFormat.lines and style != "none":
0 ignored issues
show
unused-code introduced by
Too many nested blocks (6/5)
Loading history...
100
            if self.flatten:
101
                rows = []
102
                for r in range(len(table)):
0 ignored issues
show
Coding Style Naming introduced by
Variable name "r" doesn't conform to snake_case naming style ('([^\\W\\dA-Z][^\\WA-Z]2,|_[^\\WA-Z]*|__[^\\WA-Z\\d_][^\\WA-Z]+__)$' pattern)

This check looks for invalid names for a range of different identifiers.

You can set regular expressions to which the identifiers must conform if the defaults do not match your requirements.

If your project includes a Pylint configuration file, the settings contained in that file take precedence.

To find out more about Pylint, please refer to their site.

Loading history...
103
                    row = table.iat[r, 0]
104
                    for c in range(1, len(table.columns)):
0 ignored issues
show
Coding Style Naming introduced by
Variable name "c" doesn't conform to snake_case naming style ('([^\\W\\dA-Z][^\\WA-Z]2,|_[^\\WA-Z]*|__[^\\WA-Z\\d_][^\\WA-Z]+__)$' pattern)

This check looks for invalid names for a range of different identifiers.

You can set regular expressions to which the identifiers must conform if the defaults do not match your requirements.

If your project includes a Pylint configuration file, the settings contained in that file take precedence.

To find out more about Pylint, please refer to their site.

Loading history...
105
                        iat = str(table.iat[r, c])
106
                        if iat != "nan":
107
                            if c > 1:
108
                                row += "\n\n" + "." * int(self.width * 0.75) + "\n\n"
109
                            row += iat
110
                    rows.append(row)
111
                table = DocFrame(rows, columns=["command"])
112
            table = table.applymap(lambda s: self._format(str(s)))
113
            content = table.pretty_print(style)
114
            TypedDfsUtils.write(to, content)
115
        else:
116
            table.write_file(to)
117
118
    def _format(self, s: str) -> str:
0 ignored issues
show
Coding Style Naming introduced by
Argument name "s" doesn't conform to snake_case naming style ('([^\\W\\dA-Z][^\\WA-Z]2,|_[^\\WA-Z]*|__[^\\WA-Z\\d_][^\\WA-Z]+__)$' pattern)

This check looks for invalid names for a range of different identifiers.

You can set regular expressions to which the identifiers must conform if the defaults do not match your requirements.

If your project includes a Pylint configuration file, the settings contained in that file take precedence.

To find out more about Pylint, please refer to their site.

Loading history...
119
        s = s.strip()
120
        if s == "nan":
121
            return ""
122
        if self.width == 0:
123
            return s
124
        lines = []
125
        for line in s.split("\n\n"):
126
            lines.extend(wrap(line, width=self.width))
127
            lines.append(os.linesep)
128
        lines = [line.strip(" ").strip("\t") for line in lines]
129
        return os.linesep.join(lines).replace(os.linesep * 2, os.linesep)
130
131
132
__all__ = ["Documenter"]
133