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()
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)
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)