Files @ 8092a1881a49
Branch filter:

Location: majic-scripts/utils/mapping_generator.py - annotation

branko
[thebuggenie_hg_remote.py] Fixed linter errors:

- Mostly spacing-related.
- Use the range function instead of xrange (Python 2.7 -> Python 3.x).
- Script probably will not run correctly under Python 3.x, but at
least the linter will not get in the way when working on other
Python code in the project.
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
#
# Copyright (C) 2025 Branko Majic
#
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public
# License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later
# version.
#
# This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
# warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
# details.
#
# You should have received a copy of the GNU General Public License along with this program. If not, see
# <https://www.gnu.org/licenses/>.


import shutil
import math

import click

from defusedxml import ElementTree


# Allow use of short option for showing help.
CONTEXT_SETTINGS = dict(help_option_names=['-h', '--help'])


@click.group(context_settings=CONTEXT_SETTINGS)
def cli():
    """
    Generate input mapping cheatsheets for input devices using specially-crafted scalable vector graphics (SVG)
    templates.

    To prepare a template, create an SVG using Inkscape, and assign custom IDs to text elements that should be managed
    by the mapping generator.
    """
    pass


@cli.command()
@click.argument('template', type=click.File('r'))
@click.option('--search', '-s', default='', help='only show matching mappings')
def info(template, search):
    """
    Shows information about the passed-in SVG template. This currently includes:

      - List of mappable elements.
    """

    template_tree = ElementTree.fromstring(template.read())
    namespaces = {
        'svg': 'http://www.w3.org/2000/svg',
    }

    # Inkscape default ID starts with string 'text' unless user assigns custom ID.
    mappable_elements = [
        e for e in template_tree.findall('.//svg:text', namespaces) if not e.get('id').startswith('text')
    ]
    matched_mappables = sorted([e.get('id') for e in mappable_elements if search in e.get('id')])

    if not mappable_elements:
        click.echo('No mappables found in the specified template. Please check that you have specified correct file.')

    elif not matched_mappables:
        click.echo('Search term did not match any mappable in the specified template.')

    else:
        # Show mappables in multiple columns, while enforcing a minimum column height (element count). For example, if
        # there are only 5 mappables, show them in a single column instead of splitting them up into multiple ones.
        terminal_size = shutil.get_terminal_size((80, 20))
        column_spacing = 4
        row_threshold = math.floor(terminal_size.lines * 0.5)
        column_width = max([len(m) for m in matched_mappables])
        columns = math.floor((terminal_size.columns) / (column_width + column_spacing))
        page_threshold = row_threshold * columns

        # Expand the list with blank strings in order to have exact number of 'cells' when printing to
        # screeen. Simplifies the code dealing with printing a bit and avoid invalid index access.
        if len(matched_mappables) <= row_threshold:
            rows = len(matched_mappables)
        elif len(matched_mappables) <= page_threshold:
            rows = row_threshold
            matched_mappables.extend([''] * (page_threshold - len(matched_mappables)))
        else:
            rows = math.ceil(len(matched_mappables) / columns)
            matched_mappables.extend([''] * (columns * rows - len(matched_mappables)))

        click.echo('Mappables:\n')

        for i in range(rows):
            click.echo((" " * column_spacing).join([f'{m:<{column_width}}' for m in matched_mappables[i::rows]]))


if __name__ == '__main__':
    cli()