123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145 |
- #!/usr/bin/env python3
- """Simple macro processor"""
- #
- # Copyright 2024 Odin Kroeger
- #
- # 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 ALL 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/>.
- #
- #
- # Modules
- #
- from contextlib import suppress
- from filecmp import cmp
- from getopt import getopt, GetoptError
- from os import remove, rename, strerror
- from os.path import dirname, basename, commonprefix, exists, join
- from shutil import copy
- from string import Template
- # pylint: disable=redefined-builtin
- from sys import argv, exit, stderr
- from typing import Callable, NoReturn
- import logging
- import re
- import sievemgr as mod
- #
- # Metadata
- #
- __author__ = 'Odin Kroeger'
- __copyright__ = '2024 Odin Kroeger'
- __version__ = '0.1'
- #
- # Functions
- #
- def error(*args, status: int = 1, **kwargs) -> NoReturn:
- """Log an err and :func:`exit <sys.exit>` with `status`.
- Arguments:
- args: Positional arguments for :func:`logging.error`.
- status: Exit status.
- kwargs: Keyword arguments for :func:`logging.error`.
- """
- logging.error(*args, **kwargs)
- exit(status)
- def showhelp(func: Callable) -> NoReturn:
- """Print the docstring of `func` and :func:`exit <sys.exit>`."""
- assert func.__doc__
- lines = func.__doc__.splitlines()
- indented = re.compile(r'\s+').match
- prefix = commonprefix(list(filter(indented, lines)))
- for line in lines[:-1]:
- print(line.removeprefix(prefix))
- exit()
- def showversion() -> NoReturn:
- """Print version to standard output and exit."""
- print(f"macrop {__version__}\nCopyright {__copyright__}")
- exit()
- #
- # Main
- #
- def main() -> NoReturn:
- """macrop - replace variables with dunder globals
- Usage: macrop template output
- Options:
- -V Show version information.
- -h Show this help screen.
- """
- progname = basename(argv[0])
- logging.basicConfig(format=f'{progname}: %(message)s')
- try:
- opts, args = getopt(argv[1:], 'hV', ['help', 'version'])
- except GetoptError as err:
- error(err, status=2)
- for opt, _ in opts:
- if opt in ('-h', '--help'):
- showhelp(main)
- if opt in ('-V', '--version'):
- showversion()
- try:
- source, target = args
- except ValueError:
- print(f'usage: {progname} [-h] source target', file=stderr)
- exit(2)
- swap = join(dirname(source), '.' + basename(source) + '.swp')
- macros = {k: v for k, v in mod.__dict__.items()
- if (re.fullmatch(r'__\w+__', k, re.A | re.I)
- and isinstance(v, (int, str)))}
- try:
- with open(swap, 'w') as swapfile:
- with open(source) as sourcefile:
- for line in sourcefile:
- sub = Template(line).safe_substitute(macros)
- print(sub, file=swapfile, end='')
- if exists(target):
- if cmp(swap, target):
- exit(0)
- copy(target, target + '.bak')
- rename(swap, target)
- except FileNotFoundError as err:
- error(f'{err.filename}: {strerror(err.errno)}')
- except OSError as err:
- error(strerror(err.errno))
- finally:
- with suppress(FileNotFoundError):
- remove(swap)
- exit(0)
- if __name__ == '__main__':
- main()
|