| 1 |  |  | """ | 
            
                                                                                                            
                            
            
                                    
            
            
                | 2 |  |  | Documents Mandos commands. | 
            
                                                                                                            
                            
            
                                    
            
            
                | 3 |  |  | """ | 
            
                                                                                                            
                            
            
                                    
            
            
                | 4 |  |  |  | 
            
                                                                                                            
                            
            
                                    
            
            
                | 5 |  |  | from __future__ import annotations | 
            
                                                                                                            
                            
            
                                    
            
            
                | 6 |  |  |  | 
            
                                                                                                            
                            
            
                                    
            
            
                | 7 |  |  | from dataclasses import dataclass | 
            
                                                                                                            
                            
            
                                    
            
            
                | 8 |  |  | from pathlib import Path | 
            
                                                                                                            
                            
            
                                    
            
            
                | 9 |  |  | from textwrap import wrap | 
            
                                                                                                            
                            
            
                                    
            
            
                | 10 |  |  | from typing import Mapping, Optional, Sequence | 
            
                                                                                                            
                            
            
                                    
            
            
                | 11 |  |  |  | 
            
                                                                                                            
                            
            
                                    
            
            
                | 12 |  |  | import pandas as pd | 
                            
                    |  |  |  | 
                                                                                        
                                                                                     | 
            
                                                                                                            
                            
            
                                    
            
            
                | 13 |  |  | from pocketutils.core.exceptions import ContradictoryRequestError | 
                            
                    |  |  |  | 
                                                                                        
                                                                                     | 
            
                                                                                                            
                            
            
                                    
            
            
                | 14 |  |  | from pocketutils.misc.typer_utils import TyperUtils | 
                            
                    |  |  |  | 
                                                                                        
                                                                                     | 
            
                                                                                                            
                            
            
                                    
            
            
                | 15 |  |  | from typeddfs import FileFormat, TypedDfs | 
                            
                    |  |  |  | 
                                                                                        
                                                                                     | 
            
                                                                                                            
                            
            
                                    
            
            
                | 16 |  |  | from typeddfs.utils import Utils as TdfUtils | 
                            
                    |  |  |  | 
                                                                                        
                                                                                     | 
            
                                                                                                            
                            
            
                                    
            
            
                | 17 |  |  | from typer.models import CommandInfo | 
                            
                    |  |  |  | 
                                                                                        
                                                                                     | 
            
                                                                                                            
                            
            
                                    
            
            
                | 18 |  |  |  | 
            
                                                                                                            
                            
            
                                    
            
            
                | 19 |  |  | CommandDocDf = ( | 
            
                                                                                                            
                            
            
                                    
            
            
                | 20 |  |  |     TypedDfs.typed("CommandDocDf") | 
            
                                                                                                            
                            
            
                                    
            
            
                | 21 |  |  |     .require("command", dtype=str) | 
            
                                                                                                            
                            
            
                                    
            
            
                | 22 |  |  |     .reserve("description", "parameters", dtype=str) | 
            
                                                                                                            
                            
            
                                    
            
            
                | 23 |  |  |     .strict() | 
            
                                                                                                            
                            
            
                                    
            
            
                | 24 |  |  |     .secure() | 
            
                                                                                                            
                            
            
                                    
            
            
                | 25 |  |  | ).build() | 
            
                                                                                                            
                            
            
                                    
            
            
                | 26 |  |  |  | 
            
                                                                                                            
                            
            
                                    
            
            
                | 27 |  |  |  | 
            
                                                                                                            
                            
            
                                    
            
            
                | 28 |  |  | @dataclass(frozen=True, repr=True) | 
                            
                    |  |  |  | 
                                                                                        
                                                                                     | 
            
                                                                                                            
                            
            
                                    
            
            
                | 29 |  |  | class Doc: | 
            
                                                                                                            
                            
            
                                    
            
            
                | 30 |  |  |     command: str | 
            
                                                                                                            
                            
            
                                    
            
            
                | 31 |  |  |     description: Optional[str] | 
            
                                                                                                            
                            
            
                                    
            
            
                | 32 |  |  |     params: Optional[Mapping[str, str]] | 
            
                                                                                                            
                            
            
                                    
            
            
                | 33 |  |  |  | 
            
                                                                                                            
                            
            
                                    
            
            
                | 34 |  |  |     def as_lines(self) -> Sequence[str]: | 
                            
                    |  |  |  | 
                                                                                        
                                                                                     | 
            
                                                                                                            
                            
            
                                    
            
            
                | 35 |  |  |         lines = [self.command] | 
            
                                                                                                            
                            
            
                                    
            
            
                | 36 |  |  |         if self.description is not None: | 
            
                                                                                                            
                            
            
                                    
            
            
                | 37 |  |  |             lines.append(self.description) | 
            
                                                                                                            
                            
            
                                    
            
            
                | 38 |  |  |         if self.params is not None: | 
            
                                                                                                            
                            
            
                                    
            
            
                | 39 |  |  |             lines.extend(self.description) | 
            
                                                                                                            
                            
            
                                    
            
            
                | 40 |  |  |         return lines | 
            
                                                                                                            
                            
            
                                    
            
            
                | 41 |  |  |  | 
            
                                                                                                            
                            
            
                                    
            
            
                | 42 |  |  |     def as_dict(self) -> Mapping[str, str]: | 
                            
                    |  |  |  | 
                                                                                        
                                                                                     | 
            
                                                                                                            
                            
            
                                    
            
            
                | 43 |  |  |         dct = dict(command=self.command) | 
            
                                                                                                            
                            
            
                                    
            
            
                | 44 |  |  |         if self.description is not None: | 
            
                                                                                                            
                            
            
                                    
            
            
                | 45 |  |  |             dct["description"] = self.description | 
            
                                                                                                            
                            
            
                                    
            
            
                | 46 |  |  |         if self.params is not None: | 
            
                                                                                                            
                            
            
                                    
            
            
                | 47 |  |  |             dct["params"] = "\n".join(self.params.values()) | 
            
                                                                                                            
                            
            
                                    
            
            
                | 48 |  |  |         return dct | 
            
                                                                                                            
                            
            
                                    
            
            
                | 49 |  |  |  | 
            
                                                                                                            
                            
            
                                    
            
            
                | 50 |  |  |  | 
            
                                                                                                            
                            
            
                                    
            
            
                | 51 |  |  | @dataclass(frozen=True, repr=True) | 
                            
                    |  |  |  | 
                                                                                        
                                                                                     | 
            
                                                                                                            
                            
            
                                    
            
            
                | 52 |  |  | class Documenter: | 
            
                                                                                                            
                            
            
                                    
            
            
                | 53 |  |  |     level: int | 
            
                                                                                                            
                            
            
                                    
            
            
                | 54 |  |  |     main: bool | 
            
                                                                                                            
                            
            
                                    
            
            
                | 55 |  |  |     search: bool | 
            
                                                                                                            
                            
            
                                    
            
            
                | 56 |  |  |     hidden: bool | 
            
                                                                                                            
                            
            
                                    
            
            
                | 57 |  |  |     common: bool | 
            
                                                                                                            
                            
            
                                    
            
            
                | 58 |  |  |     width: Optional[int] | 
            
                                                                                                            
                            
            
                                    
            
            
                | 59 |  |  |  | 
            
                                                                                                            
                            
            
                                    
            
            
                | 60 |  |  |     def document(self, commands: Sequence[CommandInfo], to: Path, style: str) -> None: | 
                            
                    |  |  |  | 
                                                                                        
                                                                                            
                                                                                     | 
            
                                                                                                            
                            
            
                                    
            
            
                | 61 |  |  |         fmt = FileFormat.from_path_or_none(to) | 
            
                                                                                                            
                            
            
                                    
            
            
                | 62 |  |  |         if fmt is not None and not fmt.is_text and style != "table": | 
            
                                                                                                            
                            
            
                                    
            
            
                | 63 |  |  |             raise ContradictoryRequestError(f"Cannot write binary {fmt} with style {style}") | 
            
                                                                                                            
                            
            
                                    
            
            
                | 64 |  |  |         if style == "docs": | 
            
                                                                                                            
                            
            
                                    
            
            
                | 65 |  |  |             content = self.get_long_text(commands) | 
            
                                                                                                            
                            
            
                                    
            
            
                | 66 |  |  |             TdfUtils.write(to, content, encoding="utf8") | 
            
                                                                                                            
                            
            
                                    
            
            
                | 67 |  |  |             return | 
            
                                                                                                            
                            
            
                                    
            
            
                | 68 |  |  |         table = self.get_table(commands) | 
            
                                                                                                            
                            
            
                                    
            
            
                | 69 |  |  |         if style == "table": | 
            
                                                                                                            
                            
            
                                    
            
            
                | 70 |  |  |             table.write_file(to) | 
            
                                                                                                            
                            
            
                                    
            
            
                | 71 |  |  |         else: | 
            
                                                                                                            
                            
            
                                    
            
            
                | 72 |  |  |             content = table.pretty_print(style) | 
            
                                                                                                            
                            
            
                                    
            
            
                | 73 |  |  |             TdfUtils.write(to, content, encoding="utf8") | 
            
                                                                                                            
                            
            
                                    
            
            
                | 74 |  |  |  | 
            
                                                                                                            
                            
            
                                    
            
            
                | 75 |  |  |     def get_table(self, commands: Sequence[CommandInfo]) -> CommandDocDf: | 
                            
                    |  |  |  | 
                                                                                        
                                                                                     | 
            
                                                                                                            
                            
            
                                    
            
            
                | 76 |  |  |         docs = self.get_docs(commands) | 
            
                                                                                                            
                            
            
                                    
            
            
                | 77 |  |  |         return CommandDocDf.of([pd.Series(d.as_dict()) for d in docs]) | 
            
                                                                                                            
                            
            
                                    
            
            
                | 78 |  |  |  | 
            
                                                                                                            
                            
            
                                    
            
            
                | 79 |  |  |     def get_long_text(self, commands: Sequence[CommandInfo]) -> str: | 
                            
                    |  |  |  | 
                                                                                        
                                                                                     | 
            
                                                                                                            
                            
            
                                    
            
            
                | 80 |  |  |         if self.search and self.main: | 
            
                                                                                                            
                            
            
                                    
            
            
                | 81 |  |  |             title = "Main and search commands" | 
            
                                                                                                            
                            
            
                                    
            
            
                | 82 |  |  |         elif self.search: | 
            
                                                                                                            
                            
            
                                    
            
            
                | 83 |  |  |             title = "Search commands" | 
            
                                                                                                            
                            
            
                                    
            
            
                | 84 |  |  |         else: | 
            
                                                                                                            
                            
            
                                    
            
            
                | 85 |  |  |             title = "Main commands" | 
            
                                                                                                            
                            
            
                                    
            
            
                | 86 |  |  |         docs = self.get_long(commands) | 
            
                                                                                                            
                            
            
                                    
            
            
                | 87 |  |  |         width = max([len(title), *[len(s) for s in docs.keys()]]) | 
                            
                    |  |  |  | 
                                                                                        
                                                                                     | 
            
                                                                                                            
                            
            
                                    
            
            
                | 88 |  |  |         txt = title + "\n" + "=" * width + "\n\n" | 
            
                                                                                                            
                            
            
                                    
            
            
                | 89 |  |  |         for k, v in docs.items(): | 
                            
                    |  |  |  | 
                                                                                        
                                                                                     | 
            
                                                                                                            
                            
            
                                    
            
            
                | 90 |  |  |             txt += "\n\n" + k + "\n" + "#" * width + "\n" + v + "\n" | 
            
                                                                                                            
                            
            
                                    
            
            
                | 91 |  |  |         return txt | 
            
                                                                                                            
                            
            
                                    
            
            
                | 92 |  |  |  | 
            
                                                                                                            
                            
            
                                    
            
            
                | 93 |  |  |     def get_long(self, commands: Sequence[CommandInfo]) -> Mapping[str, str]: | 
                            
                    |  |  |  | 
                                                                                        
                                                                                     | 
            
                                                                                                            
                            
            
                                    
            
            
                | 94 |  |  |         docs = self.get_docs(commands) | 
            
                                                                                                            
                            
            
                                    
            
            
                | 95 |  |  |         results = {} | 
            
                                                                                                            
                            
            
                                    
            
            
                | 96 |  |  |         for doc in docs: | 
            
                                                                                                            
                            
            
                                    
            
            
                | 97 |  |  |             zz = [] | 
                            
                    |  |  |  | 
                                                                                        
                                                                                     | 
            
                                                                                                            
                            
            
                                    
            
            
                | 98 |  |  |             for line in doc.as_lines(): | 
            
                                                                                                            
                            
            
                                    
            
            
                | 99 |  |  |                 zz += line | 
                            
                    |  |  |  | 
                                                                                        
                                                                                     | 
            
                                                                                                            
                            
            
                                    
            
            
                | 100 |  |  |             results[doc.command] = "\n".join(zz) | 
            
                                                                                                            
                            
            
                                    
            
            
                | 101 |  |  |         return results | 
            
                                                                                                            
                            
            
                                    
            
            
                | 102 |  |  |  | 
            
                                                                                                            
                            
            
                                    
            
            
                | 103 |  |  |     def get_docs(self, commands: Sequence[CommandInfo]) -> Sequence[Doc]: | 
                            
                    |  |  |  | 
                                                                                        
                                                                                     | 
            
                                                                                                            
                            
            
                                    
            
            
                | 104 |  |  |         commands = self._commands(commands) | 
            
                                                                                                            
                            
            
                                    
            
            
                | 105 |  |  |         return [self._doc(cmd) for cmd in commands] | 
            
                                                                                                            
                            
            
                                    
            
            
                | 106 |  |  |  | 
            
                                                                                                            
                            
            
                                    
            
            
                | 107 |  |  |     def _commands(self, commands: Sequence[CommandInfo]): | 
            
                                                                                                            
                            
            
                                    
            
            
                | 108 |  |  |         cmds = [ | 
            
                                                                                                            
                            
            
                                    
            
            
                | 109 |  |  |             c | 
            
                                                                                                            
                            
            
                                    
            
            
                | 110 |  |  |             for c in commands | 
            
                                                                                                            
                            
            
                                    
            
            
                | 111 |  |  |             if (self.hidden or not c.hidden) | 
            
                                                                                                            
                            
            
                                    
            
            
                | 112 |  |  |             and (c.name.startswith(":") and self.main or not c.name.startswith(":") and self.search) | 
            
                                                                                                            
                            
            
                                    
            
            
                | 113 |  |  |         ] | 
            
                                                                                                            
                            
            
                                    
            
            
                | 114 |  |  |         return sorted(cmds, key=lambda c: c.name) | 
            
                                                                                                            
                            
            
                                    
            
            
                | 115 |  |  |  | 
            
                                                                                                            
                            
            
                                    
            
            
                | 116 |  |  |     def _doc(self, cmd: CommandInfo) -> Doc: | 
            
                                                                                                            
                            
            
                                    
            
            
                | 117 |  |  |         desc = self._wrap(self._desc(cmd)) | 
            
                                                                                                            
                            
            
                                    
            
            
                | 118 |  |  |         params = TyperUtils.get_help(cmd, hidden=self.hidden) | 
            
                                                                                                            
                            
            
                                    
            
            
                | 119 |  |  |         params = { | 
            
                                                                                                            
                            
            
                                    
            
            
                | 120 |  |  |             k: self._wrap(self._param(a)) | 
            
                                                                                                            
                            
            
                                    
            
            
                | 121 |  |  |             for k, a in params.items() | 
            
                                                                                                            
                            
            
                                    
            
            
                | 122 |  |  |             if self._include_arg(cmd.name, k) | 
            
                                                                                                            
                            
            
                                    
            
            
                | 123 |  |  |         } | 
            
                                                                                                            
                            
            
                                    
            
            
                | 124 |  |  |         return Doc(command=self._wrap(cmd.name), description=desc, params=params) | 
            
                                                                                                            
                            
            
                                    
            
            
                | 125 |  |  |  | 
            
                                                                                                            
                            
            
                                    
            
            
                | 126 |  |  |     def _desc(self, cmd: CommandInfo) -> Optional[str]: | 
            
                                                                                                            
                            
            
                                    
            
            
                | 127 |  |  |         cb = cmd.callback.__doc__ | 
                            
                    |  |  |  | 
                                                                                        
                                                                                     | 
            
                                                                                                            
                            
            
                                    
            
            
                | 128 |  |  |         desc_lines = self._split(cb) | 
            
                                                                                                            
                            
            
                                    
            
            
                | 129 |  |  |         if self.level >= 3: | 
                            
                    |  |  |  | 
                                                                                        
                                                                                     | 
            
                                                                                                            
                            
            
                                    
            
            
                | 130 |  |  |             return cb | 
            
                                                                                                            
                            
            
                                    
            
            
                | 131 |  |  |         elif self.level >= 2: | 
            
                                                                                                            
                            
            
                                    
            
            
                | 132 |  |  |             return "\n".join(desc_lines[:2]) | 
            
                                                                                                            
                            
            
                                    
            
            
                | 133 |  |  |         elif self.level >= 1: | 
            
                                                                                                            
                            
            
                                    
            
            
                | 134 |  |  |             return desc_lines[0] | 
            
                                                                                                            
                            
            
                                    
            
            
                | 135 |  |  |         return None | 
            
                                                                                                            
                            
            
                                    
            
            
                | 136 |  |  |  | 
            
                                                                                                            
                            
            
                                    
            
            
                | 137 |  |  |     def _param(self, param: str) -> Optional[str]: | 
            
                                                                                                            
                            
            
                                    
            
            
                | 138 |  |  |         lines = self._split(param) | 
            
                                                                                                            
                            
            
                                    
            
            
                | 139 |  |  |         if self.level >= 3: | 
            
                                                                                                            
                            
            
                                    
            
            
                | 140 |  |  |             return param | 
            
                                                                                                            
                            
            
                                    
            
            
                | 141 |  |  |         if self.level >= 2: | 
                            
                    |  |  |  | 
                                                                                        
                                                                                     | 
            
                                                                                                            
                            
            
                                    
            
            
                | 142 |  |  |             return lines[0] + "\n" + lines[1] | 
            
                                                                                                            
                            
            
                                    
            
            
                | 143 |  |  |         elif self.level >= 1: | 
            
                                                                                                            
                            
            
                                    
            
            
                | 144 |  |  |             return lines[0] | 
            
                                                                                                            
                            
            
                                    
            
            
                | 145 |  |  |         return None | 
            
                                                                                                            
                                                                
            
                                    
            
            
                | 146 |  |  |  | 
            
                                                                        
                            
            
                                    
            
            
                | 147 |  |  |     def _include_arg(self, command: str, arg: str) -> bool: | 
            
                                                                        
                            
            
                                    
            
            
                | 148 |  |  |         if self.common: | 
            
                                                                        
                            
            
                                    
            
            
                | 149 |  |  |             return True | 
            
                                                                        
                            
            
                                    
            
            
                | 150 |  |  |         _common = ["path", "--key", "--to", "--as-of", "--replace", "--proceed", "--check"] | 
            
                                                                        
                            
            
                                    
            
            
                | 151 |  |  |         return arg not in ["--stderr", "--log"] and (command.startswith(":") or arg not in _common) | 
            
                                                                                                            
                            
            
                                    
            
            
                | 152 |  |  |  | 
            
                                                                                                            
                            
            
                                    
            
            
                | 153 |  |  |     def _split(self, txt: str) -> Sequence[str]: | 
            
                                                                                                            
                            
            
                                    
            
            
                | 154 |  |  |         zs = [] | 
                            
                    |  |  |  | 
                                                                                        
                                                                                     | 
            
                                                                                                            
                            
            
                                    
            
            
                | 155 |  |  |         for s in txt.splitlines(): | 
                            
                    |  |  |  | 
                                                                                        
                                                                                     | 
            
                                                                                                            
                            
            
                                    
            
            
                | 156 |  |  |             s = self._clean(s) | 
                            
                    |  |  |  | 
                                                                                        
                                                                                     | 
            
                                                                                                            
                            
            
                                    
            
            
                | 157 |  |  |             if len(s) > 0: | 
            
                                                                                                            
                            
            
                                    
            
            
                | 158 |  |  |                 zs.append(s) | 
            
                                                                                                            
                            
            
                                    
            
            
                | 159 |  |  |         return zs | 
            
                                                                                                            
                            
            
                                    
            
            
                | 160 |  |  |  | 
            
                                                                                                            
                            
            
                                    
            
            
                | 161 |  |  |     def _clean(self, txt: str) -> str: | 
                            
                    |  |  |  | 
                                                                                        
                                                                                     | 
            
                                                                                                            
                            
            
                                    
            
            
                | 162 |  |  |         return TdfUtils.strip_control_chars(txt.replace("\n", " ").replace("\t", " ")).strip() | 
            
                                                                                                            
                            
            
                                    
            
            
                | 163 |  |  |  | 
            
                                                                                                            
                            
            
                                    
            
            
                | 164 |  |  |     def _wrap(self, txt: Optional[str]) -> Optional[str]: | 
            
                                                                                                            
                            
            
                                    
            
            
                | 165 |  |  |         if txt is None: | 
            
                                                                                                            
                            
            
                                    
            
            
                | 166 |  |  |             return None | 
            
                                                                                                            
                            
            
                                    
            
            
                | 167 |  |  |         if self.width is None: | 
            
                                                                                                            
                            
            
                                    
            
            
                | 168 |  |  |             return txt | 
            
                                                                                                            
                            
            
                                    
            
            
                | 169 |  |  |         return "\n".join(wrap(txt, width=self.width)) | 
            
                                                                                                            
                            
            
                                    
            
            
                | 170 |  |  |  | 
            
                                                                                                            
                            
            
                                    
            
            
                | 171 |  |  |  | 
            
                                                                                                            
                                                                
            
                                    
            
            
                | 172 |  |  | __all__ = ["Documenter", "Doc", "CommandDocDf"] | 
            
                                                        
            
                                    
            
            
                | 173 |  |  |  |