read/write processed man pages from sqlite
| 105 | |
| 106 | |
| 107 | class Store: |
| 108 | """read/write processed man pages from sqlite""" |
| 109 | |
| 110 | def __init__(self, db_path: str, read_only: bool = False) -> None: |
| 111 | logger.info("creating store, db_path = %r, read_only = %s", db_path, read_only) |
| 112 | # check_same_thread=False: the default sqlite3 driver raises if a |
| 113 | # connection is used from a thread other than the one that created it. |
| 114 | # Plain Store instances are expected to be used by one caller at a |
| 115 | # time. Production web serving uses CachingStore, which supplies |
| 116 | # per-thread read-only connections around the shared lookup cache. |
| 117 | if read_only: |
| 118 | self._conn = sqlite3.connect( |
| 119 | f"file:{db_path}?mode=ro", uri=True, check_same_thread=False |
| 120 | ) |
| 121 | self._conn.row_factory = sqlite3.Row |
| 122 | else: |
| 123 | self._conn = sqlite3.connect(db_path, check_same_thread=False) |
| 124 | self._conn.row_factory = sqlite3.Row |
| 125 | self._conn.execute("PRAGMA foreign_keys = ON") |
| 126 | |
| 127 | @classmethod |
| 128 | def create(cls, db_path: str) -> "Store": |
| 129 | """Create a new (or open an existing) writable database and return a Store.""" |
| 130 | s = cls(db_path) |
| 131 | s._conn.executescript(_CREATE_SCHEMA) |
| 132 | return s |
| 133 | |
| 134 | def close(self) -> None: |
| 135 | if self._conn: |
| 136 | self._conn.close() |
| 137 | self._conn = None |
| 138 | |
| 139 | def drop(self, confirm: bool = False) -> None: |
| 140 | if not confirm: |
| 141 | return |
| 142 | |
| 143 | logger.info("dropping mappings, parsed_manpages, manpages tables") |
| 144 | self._conn.executescript(""" |
| 145 | DELETE FROM mappings; |
| 146 | DELETE FROM parsed_manpages; |
| 147 | DELETE FROM manpages; |
| 148 | """) |
| 149 | self._conn.commit() |
| 150 | |
| 151 | def find_man_page( |
| 152 | self, name: str, distro: str | None = None, release: str | None = None |
| 153 | ) -> list[ParsedManpage]: |
| 154 | """find a man page by its name, everything following the last dot (.) in name, |
| 155 | is taken as the section of the man page |
| 156 | |
| 157 | we return the man page found with the highest score, and a list of |
| 158 | suggestions that also matched the given name (only the first item |
| 159 | is prepopulated with the option data) |
| 160 | |
| 161 | when distro and release are set, filter results to manpages whose |
| 162 | source starts with ``distro/release/``.""" |
| 163 | if name.endswith(".gz"): |
| 164 | logger.debug("name ends with .gz, looking up an exact match by source") |
no outgoing calls