File: listing.py

package info (click to toggle)
python-sshoot 1.6.0-2
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 344 kB
  • sloc: python: 1,579; makefile: 23; sh: 1
file content (180 lines) | stat: -rw-r--r-- 5,378 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
"""Helpers for listing output."""

from collections import OrderedDict
from csv import DictWriter
from io import StringIO
import json
from typing import (
    cast,
    Iterable,
    List,
    Optional,
    Protocol,
    Tuple,
)

from prettytable import (
    HEADER,
    PrettyTable,
)

from .config import yaml_dump
from .i18n import _
from .manager import Manager
from .profile import Profile

# Map names to profile fields
_FIELDS_MAP = OrderedDict(
    [
        (_("Remote host"), "remote"),
        (_("Subnets"), "subnets"),
        (_("Auto hosts"), "auto_hosts"),
        (_("Auto nets"), "auto_nets"),
        (_("DNS forward"), "dns"),
        (_("Exclude subnets"), "exclude_subnets"),
        (_("Seed hosts"), "seed_hosts"),
        (_("Extra options"), "extra_opts"),
    ]
)

NAME_FIELD = _("Name")
STATUS_FIELD = _("Status")


class InvalidFormat(Exception):
    def __init__(self, name: str):
        super().__init__(_("Invalid output format: {name}").format(name=name))


ProfileIterator = Iterable[Tuple[str, Profile]]


class Formatter(Protocol):
    def __call__(
        self, profile_iter: ProfileIterator, verbose: bool = False
    ) -> str:
        ...  # pragma: nocoverage


class ProfileListing:
    """List details for details in the specified format."""

    def __init__(self, manager: Manager):
        self.manager = manager

    @classmethod
    def supported_formats(cls) -> List[str]:
        return sorted(
            attr[8:] for attr in dir(cls) if attr.startswith("_format_")
        )

    def get_output(self, _format: str, verbose: bool = False) -> str:
        """Return a string with listing in the specified format."""
        formatter: Optional[Formatter] = getattr(
            self, f"_format_{_format}", None
        )
        if formatter is None:
            raise InvalidFormat(_format)
        profiles_iter = self.manager.get_profiles().items()
        return formatter(profiles_iter, verbose=verbose)

    def _format_table(
        self, profiles_iter: ProfileIterator, verbose: bool = False
    ) -> str:
        """Format profiles data as a table."""
        titles = ["", NAME_FIELD]
        titles.extend(_FIELDS_MAP)
        columns = list(_FIELDS_MAP.values())
        if not verbose:
            # Only basic info
            titles = titles[:4]
            columns = columns[:2]

        table = PrettyTable(titles)
        table.align = "l"
        table.vertical_char = " "
        table.junction_char = table.horizontal_char
        table.padding_width = 0
        table.left_padding_width = 0
        table.right_padding_width = 1
        table.hrules = HEADER

        for name, profile in profiles_iter:
            row = ["*" if self.manager.is_running(name) else "", name]
            row.extend(
                _format_value(getattr(profile, column)) for column in columns
            )
            table.add_row(row)
        return cast(str, table.get_string(sortby=NAME_FIELD)) + "\n"

    def _format_csv(
        self, profiles_iter: ProfileIterator, verbose: bool = False
    ) -> str:
        """Format profiles data as CSV."""
        titles = [NAME_FIELD, STATUS_FIELD]
        titles.extend(_FIELDS_MAP)

        buf = StringIO()
        writer = DictWriter(buf, fieldnames=titles)
        writer.writeheader()

        for name, profile in profiles_iter:
            row = {
                NAME_FIELD: name,
                STATUS_FIELD: _profile_status(self.manager, name),
            }
            row.update(
                {
                    title: getattr(profile, _FIELDS_MAP[title])
                    for title in titles[2:]
                }
            )
            writer.writerow(row)
        return buf.getvalue()

    def _format_json(
        self, profiles_iter: ProfileIterator, verbose: bool = False
    ) -> str:
        """Format profiles data as JSON."""
        data = {}
        for name, profile in profiles_iter:
            config = profile.config()
            # config['active'] = manager.is_running(name)
            data[name] = config

        return json.dumps(data)

    def _format_yaml(
        self, profiles_iter: ProfileIterator, verbose: bool = False
    ) -> str:
        """Format profiles data as YAML."""
        data = {name: profile.config() for name, profile in profiles_iter}
        return cast(str, yaml_dump(data))


def profile_details(manager: Manager, name: str) -> str:
    """Return a string with details about a profile, formatted as a table."""
    profile = manager.get_profile(name)
    table = PrettyTable(
        field_names=["key", "value"], header=False, border=False
    )
    table.align["key"] = table.align["value"] = "l"
    table.add_row((f"{NAME_FIELD}:", name))
    table.add_row((f"{STATUS_FIELD}:", _profile_status(manager, name)))
    for name, field in _FIELDS_MAP.items():
        table.add_row((f"{name}:", _format_value(getattr(profile, field))))
    return cast(str, table.get_string())


def _profile_status(manager: Manager, name: str) -> str:
    """Return a string with the status of a profile."""
    return _("ACTIVE") if manager.is_running(name) else _("STOPPED")


def _format_value(value) -> str:
    """Convert value to string, handling special cases."""
    if isinstance(value, (list, tuple)):
        return " ".join(value)
    if value is None:
        return ""
    return str(value)