Passed
Push — main ( da77b5...65730f )
by Douglas
02:28
created

mandos.entry.tools.docs.Documenter._doc_row()   B

Complexity

Conditions 7

Size

Total Lines 19
Code Lines 17

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 7
eloc 17
nop 2
dl 0
loc 19
rs 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 dataclasses import dataclass
10
from pathlib import Path
11
from textwrap import wrap
12
from typing import Mapping, Sequence
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 pocketutils.core.exceptions import ContradictoryRequestError
0 ignored issues
show
introduced by
Unable to import 'pocketutils.core.exceptions'
Loading history...
17
from typeddfs import FileFormat, TypedDfs
0 ignored issues
show
introduced by
Unable to import 'typeddfs'
Loading history...
18
from typeddfs.utils import Utils as TypedDfsUtils
0 ignored issues
show
introduced by
Unable to import 'typeddfs.utils'
Loading history...
19
from typer.models import CommandInfo
0 ignored issues
show
introduced by
Unable to import 'typer.models'
Loading history...
20
21
CommandDocDf = (
22
    TypedDfs.typed("CommandDocDf")
23
    .require("command", dtype=str)
24
    .reserve("description", "parameters", dtype=str)
25
    .strict(cols=False)
26
    .secure()
27
).build()
28
29
30
@dataclass(frozen=True, repr=True)
0 ignored issues
show
introduced by
Missing class docstring
Loading history...
31
class Documenter:
32
    level: int
33
    main: bool
34
    search: bool
35
    hidden: bool
36
    common: bool
37
    width: int
38
39
    def __post_init__(self):
40
        if self.main and self.search:
41
            raise ContradictoryRequestError("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
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...
introduced by
Missing function or method docstring
Loading history...
44
        fmt = FileFormat.from_path_or_none(to)
45
        if fmt is not None and not fmt.is_text and style != "table":
46
            raise ContradictoryRequestError(f"Cannot write binary {fmt} with style {style}")
47
        cmds = [c for c in commands if (self.hidden or not c.hidden)]
48
        if self.main:
49
            cmds = [c for c in cmds if c.name.startswith(":")]
50
        elif self.search:
51
            cmds = [c for c in cmds if not c.name.startswith(":")]
52
        cmds = sorted(cmds, key=lambda c: c.name)
53
        table = CommandDocDf([self._doc_row(c) for c in cmds])
54
        self._write(to, table, style)
55
56
    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...
57
        doc = c.callback.__doc__
58
        args = self._typer_param_docs(c)
59
        dct = dict(command=c.name)
60
        # descriptions
61
        if self.level >= 3:
62
            dct["description"] = doc
63
        elif self.level >= 1:
64
            dct["description"] = [s for s in doc.splitlines() if s.strip() != ""][0]
65
        # parameters
66
        if self.level >= 4:
67
            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...
68
                dct[f"parameter_{i}"] = f"{k} \n\n{v}"
69
        elif self.level >= 3:
70
            z = [f"{k}:: {v.splitlines()[0]}" for k, v in args.items()]
0 ignored issues
show
Coding Style Naming introduced by
Variable name "z" 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...
71
            dct["parameters"] = "\n\n".join(z)
72
        elif self.level == 2:
73
            dct["parameters"] = " ".join(args.keys())
74
        return pd.Series(dct)
75
76
    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...
77
        _args = inspect.signature(c.callback).parameters
78
        args = {}
79
        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...
80
            dtype = str(p.annotation)
81
            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...
82
            if not isinstance(v, (typer.models.ParameterInfo, typer.models.OptionInfo)):
83
                raise AssertionError(f"{p} can't be {v} on {c.name}!")
84
            if isinstance(v, typer.models.OptionInfo):
85
                k = "--" + k
86
            k = k.replace("_", "-")
87
            doc = f"[type: {dtype}] " + v.help
88
            if (
89
                (self.hidden or not v.hidden)
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
best-practice introduced by
Too many boolean expressions in if statement (7/5)
Loading history...
90
                and (self.common or k not in ["--verbose", "--quiet", "--log"])
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
91
                and (
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
92
                    self.common
93
                    or c.name.startswith(":")
94
                    or k not in ["path", "--key", "--to", "--as-of", "--check", "--no-setup"]
95
                )
96
            ):
97
                if v.show_default:
98
                    args[k] = doc + f"\n[default: {v.default}]"
99
                else:
100
                    args[k] = doc
101
        return args
102
103
    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...
104
        if style == "table":
105
            table.write_file(to)
106
        elif style in ["long", "doc", "long-form"]:
107
            content = "\n".join(self._long_doc_format(table))
108
            TypedDfsUtils.write(to, content)
109
        else:
110
            table = table.applymap(lambda s: self._format(str(s)))
111
            content = table.pretty_print(style)
112
            TypedDfsUtils.write(to, content)
113
114
    def _long_doc_format(self, table: pd.DataFrame) -> Sequence[str]:
115
        lines = []
116
        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...
117
            row = str(table.iat[r, 0])
118
            lines += [
119
                "\n",
120
                "\n",
121
                row.center(self.width),
122
                "\n",
123
                "=" * self.width,
124
                "\n",
125
            ]
126
            if "description" in table.columns:
127
                lines += [str(table.iat[r, 1]), "\n", "\n"]
128
            for c in range(2, 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...
129
                iat = str(table.iat[r, c])
130
                if iat != "nan":
131
                    lines += ["\n", "\n", "-" * self.width, "\n", iat]
132
        return lines
133
134
    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...
135
        s = s.strip()
136
        if s == "nan":
137
            return ""
138
        if self.width == 0:
139
            return s
140
        lines = []
141
        for line in s.split("\n\n"):
142
            lines.extend(wrap(line, width=self.width))
143
            lines.append(os.linesep)
144
        lines = [line.strip(" ").strip("\t") for line in lines]
145
        return os.linesep.join(lines).replace(os.linesep * 2, os.linesep)
146
147
148
__all__ = ["Documenter"]
149