ex-cli 1.21.0

Command line tool to find, filter, sort and list files.
Documentation
#!/usr/bin/env python3
import os
import re
import subprocess
import sys
from pathlib import Path
from tempfile import NamedTemporaryFile
from typing import Any, TextIO, Union

type AnyIO = Union[TextIO, Any]

class CargoModifier:

    def __init__(self):
        self.depends = list()

    # noinspection PyProtectedMember
    def modify_file(self, path: Path):
        with open(path, 'r') as reader:
            with NamedTemporaryFile('w', dir=path.parent) as writer:
                self.modify_stream(reader, writer)
                writer._closer.delete = False
        os.rename(writer.name, path)

    # chrono = "0.4.38"
    # clap = { version = "4.5.4", features = ["cargo"] }

    def modify_stream(self, reader: TextIO, writer: AnyIO):
        target = None
        features = False
        for line in reader:
            if re.search(r'^\[dependencies]$', line):
                print(line, end='', file=writer)
                target = ''
            elif match := re.search(r'^\[(build|dev)-dependencies]$', line):
                print(line, end='', file=writer)
                target = match.group(1)
            elif match := re.search(r'^\[target\."(.+)"\.dependencies]$', line):
                print(line, end='', file=writer)
                target, = match.groups()
            elif target is not None:
                if match := re.search(r'^([\w-]+) = "\d+\.\d+\.\d+"$', line):
                    library, = match.groups()
                    self.depends.append((library, target, list()))
                elif match := re.search(r'^([\w-]+) = {(.+)}$', line):
                    library, options = match.groups()
                    library, options = self.create_options(library, options)
                    self.depends.append((library, target, options))
                elif re.search(r'^$', line):
                    print(line, end='', file=writer)
                    target = None
                else:
                    raise RuntimeError(f'Unexpected line: {line.rstrip()}')
            elif re.search(r'^\[features]$', line):
                print(line, end='', file=writer)
                features = True
            elif features:
                if re.search(r'^$', line):
                    print(line, end='', file=writer)
                    features = False
            else:
                print(line, end='', file=writer)

    @classmethod
    def create_options(cls, library: str, params: str) -> tuple[str, list[str]]:
        options = list()
        if match := re.search(r'\bfeatures = \[(.+?)]', params):
            features = re.findall(r'"(\S+)"', match.group(1))
            features = ','.join(sorted(features))
            options.extend(['--features', features])
        if match := re.search(r'\bpackage = "(\S+)"', params):
            package = match.group(1)
            library, package = package, library
            options.extend(['--rename', package])
        if re.search(r'\boptional = true\b', params):
            options.append('--optional')
        return library, options

    def run_cargo(self):
        for library, target, options in self.depends:
            command = ['cargo', 'add', library]
            if target == 'build':
                command.extend(['--build'])
            elif target == 'dev':
                command.extend(['--dev'])
            elif target:
                command.extend(['--target', target])
            command.extend(options)
            self.run_process(command)

    @classmethod
    def run_process(cls, command: list[str]) -> bool:
        process = subprocess.run(command)
        return process.returncode == 0

def run_main():
    path = Path(__file__).parent.with_name('Cargo.toml')
    modifier = CargoModifier()
    modifier.modify_file(path)
    modifier.run_cargo()

try:
    run_main()
except (OSError, RuntimeError, KeyboardInterrupt) as error:
    print(error, file=sys.stderr)