Source code for seiscat.config.autocompletion

# -*- coding: utf8 -*-
# SPDX-License-Identifier: GPL-3.0-or-later
"""
Autocompletion helpers for CLI arguments.

:copyright:
    2022-2026 Claudio Satriano <satriano@ipgp.fr>
:license:
    GNU General Public License v3.0 or later
    (https://www.gnu.org/licenses/gpl-3.0-standalone.html)
"""
# Curated fallback names used when Matplotlib cannot be imported at
# completion time. Keep only canonical names here (no _r variants).
_FALLBACK_MATPLOTLIB_COLORMAP_BASE_NAMES = (
    'Accent', 'Blues', 'BrBG', 'BuGn', 'BuPu', 'CMRmap', 'Dark2', 'GnBu',
    'Grays', 'Greens', 'Greys', 'OrRd', 'Oranges', 'PRGn', 'Paired',
    'Pastel1', 'Pastel2', 'PiYG', 'PuBu', 'PuBuGn', 'PuOr', 'PuRd',
    'Purples', 'RdBu', 'RdGy', 'RdPu', 'RdYlBu', 'RdYlGn', 'Reds', 'Set1',
    'Set2', 'Set3', 'Spectral', 'Wistia', 'YlGn', 'YlGnBu', 'YlOrBr',
    'YlOrRd', 'afmhot', 'autumn', 'berlin', 'binary', 'bone', 'brg',
    'bwr', 'cividis', 'cool', 'coolwarm', 'copper', 'cubehelix', 'flag',
    'gist_earth', 'gist_gray', 'gist_grey', 'gist_heat', 'gist_ncar',
    'gist_rainbow', 'gist_stern', 'gist_yarg', 'gist_yerg', 'gnuplot',
    'gnuplot2', 'gray', 'grey', 'hot', 'hsv', 'inferno', 'jet', 'magma',
    'managua', 'nipy_spectral', 'ocean', 'pink', 'plasma', 'prism',
    'rainbow', 'seismic', 'spring', 'summer', 'tab10', 'tab20', 'tab20b',
    'tab20c', 'terrain', 'turbo', 'twilight', 'twilight_shifted',
    'vanimo', 'viridis', 'winter'
)
# Full fallback completion set: base names plus generated reversed variants.
_FALLBACK_MATPLOTLIB_COLORMAPS = tuple(sorted(
    set(_FALLBACK_MATPLOTLIB_COLORMAP_BASE_NAMES)
    | {f'{name}_r' for name in _FALLBACK_MATPLOTLIB_COLORMAP_BASE_NAMES}
))


def _get_db_cursor(configfile):
    """
    Get a cursor to the database.

    :param configfile: path to config file
    :return: cursor to the database
    """
    try:
        fp = open(configfile, 'r', encoding='utf-8')
    except FileNotFoundError:
        return None
    try:
        db_file = [
            line.split('=')[1].strip() for line in fp
            if line.startswith('db_file')][0]
    except IndexError:
        db_file = None
    db_files_to_try = (
        [db_file]
        if db_file is not None
        else ['seiscat_db.sqlite', 'seiscat.sqlite']
    )
    for db_file in db_files_to_try:
        try:
            open(db_file, 'r', encoding='utf-8')
            break
        except FileNotFoundError:
            continue
    else:
        return None
    # pylint: disable=import-outside-toplevel
    import sqlite3  # lazy import to speed up startup time
    conn = sqlite3.connect(db_file)
    return conn.cursor()


[docs] def evid_completer(prefix, parsed_args, **_kwargs): """ Completer for event IDs. :param prefix: prefix to complete :param parsed_args: parsed arguments :param kwargs: keyword arguments :return: list of event IDs """ if evid_completer.db_cursor is None: evid_completer.db_cursor = _get_db_cursor(parsed_args.configfile) if evid_completer.db_cursor is None: return [] # Count matching evids first to avoid overwhelming completion evid_completer.db_cursor.execute( 'SELECT COUNT(*) FROM events WHERE evid LIKE ?', (f'{prefix}%',) ) count = evid_completer.db_cursor.fetchone()[0] max_completions = 100 if count > max_completions: return [ f'[Too many events ({count}) for autocompletion' '\nUse exact EVID or --where to filter]' ] evid_completer.db_cursor.execute( 'SELECT evid FROM events WHERE evid LIKE ?', (f'{prefix}%',) ) return [row[0] for row in evid_completer.db_cursor.fetchall()]
evid_completer.db_cursor = None
[docs] def sortby_completer(prefix, parsed_args, **_kwargs): """ Completer for sortby field names. :param prefix: prefix to complete :param parsed_args: parsed arguments :param kwargs: keyword arguments :return: list of field names """ if sortby_completer.db_cursor is None: sortby_completer.db_cursor = _get_db_cursor(parsed_args.configfile) if sortby_completer.db_cursor is None: return [] # Get all field names from the events table try: sortby_completer.db_cursor.execute('PRAGMA table_info(events)') # Field names are in the second column (index 1) all_fields = [row[1] for row in sortby_completer.db_cursor.fetchall()] # Filter by prefix return [field for field in all_fields if field.startswith(prefix)] except Exception: # pylint: disable=broad-except return []
sortby_completer.db_cursor = None def _get_matplotlib_colormap_names(): """Return the exhaustive Matplotlib colormap registry when available.""" try: from matplotlib import colormaps # lazy import to keep startup fast return sorted(colormaps.keys()) except Exception: # pylint: disable=broad-except return list(_FALLBACK_MATPLOTLIB_COLORMAPS)
[docs] def colormap_completer(prefix, _parsed_args, **_kwargs): """Completer for Matplotlib colormap names.""" prefix_lower = prefix.lower() return [ name for name in _get_matplotlib_colormap_names() if name.lower().startswith(prefix_lower) ]