initial commit
This commit is contained in:
commit
e5dbe3a69b
1 changed files with 116 additions and 0 deletions
116
main.py
Executable file
116
main.py
Executable file
|
@ -0,0 +1,116 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
from dataclasses import dataclass
|
||||
import sys
|
||||
import subprocess
|
||||
import re
|
||||
import heapq
|
||||
|
||||
from os import mkdir, path, listdir
|
||||
from argparse import ArgumentParser
|
||||
from typing import Iterable
|
||||
from venv import EnvBuilder
|
||||
|
||||
parser = ArgumentParser(prog="py-setup", add_help=True)
|
||||
subparsers = parser.add_subparsers(dest="command")
|
||||
|
||||
new = subparsers.add_parser("new", add_help=True)
|
||||
new.add_argument("name")
|
||||
new.add_argument("--with-venv", metavar="path")
|
||||
|
||||
install = subparsers.add_parser("install", add_help=True)
|
||||
install.add_argument("-r", "--requirements", default="requirements.txt")
|
||||
install.add_argument("packages", nargs="+")
|
||||
|
||||
def create_new(args):
|
||||
try:
|
||||
mkdir(args.name)
|
||||
except FileExistsError:
|
||||
if len(listdir(args.name)) > 0:
|
||||
print(f"directory `{args.name}` exists and is not empty, exiting")
|
||||
sys.exit()
|
||||
|
||||
if args.with_venv is not None:
|
||||
venv_dir = path.join(args.name, args.with_venv)
|
||||
EnvBuilder().create(venv_dir)
|
||||
|
||||
print(f"created {args.name} at {path.realpath(args.name)}")
|
||||
|
||||
def grep(source, pattern):
|
||||
pattern = re.compile(pattern)
|
||||
return [line for line in source.split(b"\n") if pattern.search(line)]
|
||||
|
||||
def pip(command):
|
||||
return subprocess.check_output([sys.executable, "-m", "pip", *command.split(" ")])
|
||||
|
||||
def flatten(xss):
|
||||
return [x for xs in xss for x in xs]
|
||||
|
||||
def take_until(value: bytes, delim: Iterable[bytes]) -> Iterable[bytes]:
|
||||
for byte in value:
|
||||
if byte in delim:
|
||||
return
|
||||
yield byte
|
||||
|
||||
specifiers = ["~", "=", "!", ">", "<"]
|
||||
@dataclass(frozen=True)
|
||||
class Requirement:
|
||||
value: bytes
|
||||
|
||||
@property
|
||||
def name(self) -> bytes:
|
||||
return bytes(take_until(self.value, specifiers))
|
||||
|
||||
def __str__(self):
|
||||
return self.value.decode("utf-8")
|
||||
|
||||
def create_requirements(packages) -> Requirement:
|
||||
output = pip("freeze")
|
||||
requirements = [grep(output, bytes(package, "utf-8")) for package in packages]
|
||||
requirements = [req.replace(b"==", b"~=") for req in flatten(requirements)]
|
||||
return [Requirement(req) for req in requirements]
|
||||
|
||||
def read_requirements(path) -> Requirement:
|
||||
try:
|
||||
with open(path, "rb") as f:
|
||||
lines = [line.replace(b"\n", b"") for line in f.readlines() if line != b"\n"]
|
||||
return [Requirement(line) for line in lines]
|
||||
except:
|
||||
return []
|
||||
|
||||
def install_libraries(args):
|
||||
pip(f"install {" ".join(args.packages)}")
|
||||
|
||||
new_requirements = create_requirements(args.packages)
|
||||
|
||||
new_req_strs = [str(req) for req in new_requirements]
|
||||
print(f"installed {", ".join(new_req_strs)}")
|
||||
|
||||
existing_requirements = filter(
|
||||
lambda req: req not in new_requirements,
|
||||
read_requirements(args.requirements)
|
||||
)
|
||||
|
||||
requirements = heapq.merge(
|
||||
existing_requirements,
|
||||
new_requirements,
|
||||
key=lambda x: x.name
|
||||
)
|
||||
|
||||
requirements = [str(req) for req in requirements]
|
||||
|
||||
with open(args.requirements, "w") as f:
|
||||
f.write("\n".join(requirements))
|
||||
|
||||
count = len(new_requirements)
|
||||
entry_word = "entry" if count == 1 else "entries"
|
||||
print(f"updated {args.requirements} with {count} {entry_word}")
|
||||
|
||||
def main():
|
||||
args = parser.parse_args()
|
||||
match args.command:
|
||||
case "new": create_new(args)
|
||||
case "install": install_libraries(args)
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
Loading…
Add table
Reference in a new issue