Some helper scripts for the day to day work with Ubuntu in WSL2
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

368 lines
12 KiB

import click
import pathlib
import pyperclip
import shutil
import sys
from typing import Iterable
from dataclasses import dataclass
from datetime import datetime
Pathlike = str | pathlib.Path
PATH_WIN_DESKTOP = pathlib.Path("/mnt/c/Users/Holgi/Desktop")
TODAY = datetime.now().strftime("%y%m%d")
CRLF = "\r\n"
GROUPS = {
"QC": ["ASQC, HQC, MQC"],
"Print": ["Dry-1, Dry-2"],
"Production": ["Hyb", "Reg"],
}
GROUP_FOLDER_PREFIX = "Safeguard-MBP-"
GROUP_FOLDER_SUFFIX = "-Changes"
EXCEL_CHANGELOG_HEADERS = [
"Sheet\tWell\tContents\tComment",
"-----\t----\t--------\t-------",
"",
]
EXCEL_CHANGELOGS = {
"QC": {
"changes mbp qc asqc {version}.txt": "L1",
"changes mbp qc hqc {version}.txt": "J1",
"changes mbp qc mqc {version}.txt": "J1",
},
"Print": {
"changes mbp print dry-1 {version}.txt": "L1",
"changes mbp print dry-2 {version}.txt": "L1",
},
"Production": {
"changes mbp production hyb {version}.txt": "J1",
"changes mbp production reg {version}.txt": "J1",
},
}
## Exception classes
class MBPExcecption(Exception):
pass
# common functions and helper functions
def _to_int(text: str, default=0) -> int:
try:
return int(text)
except (ValueError, TypeError):
return default
def _folder_content(folder: Pathlike) -> Iterable[pathlib.Path]:
folder = pathlib.Path(folder)
nondotted = (i for i in folder.iterdir() if not i.stem.startswith("."))
return (i for i in nondotted if not i.stem.startswith("~"))
def _files_in_folder(folder: Pathlike, suffix: str) -> Iterable[pathlib.Path]:
folder = pathlib.Path(folder)
files = (f for f in _folder_content(folder) if f.is_file())
return (f for f in files if f.suffix == suffix)
def _get_workbook_folder(group: str) -> pathlib.Path:
path = pathlib.Path("/") / "mnt" / "e" / f"Safeguard MBP {group} Workbooks"
if not path.is_dir():
msg = "Workbook folder {path} does not exist"
raise MBPExcecption(msg)
return path
def _get_changelog_path(folder: Pathlike) -> pathlib.Path:
textfiles = _files_in_folder(folder, ".txt")
return next(f for f in textfiles if f.stem.lower().startswith("change"))
def _list_current_frms(group: str, build_version: str):
source = _get_workbook_folder(group)
search_version = build_version.removesuffix(TODAY)
all_folders = (f for f in source.iterdir() if f.is_dir())
all_frms = (f for f in all_folders if "frm" in f.name.lower())
return [f for f in all_frms if search_version in f.name]
def _get_issue_numbers(group: str, build_version: str):
print(list(_list_current_frms(group, build_version)))
for path in _list_current_frms(group, build_version):
rest, issue_info = path.name.lower().split("issue")
issue_info = issue_info.removeprefix("s") # might be "issues"
issue, *rest = issue_info.strip().split()
yield issue.strip(" ,")
def _extract_changes_from_log(
cwd: Pathlike, group: str, build_version: str
) -> Iterable[str]:
issue_numbers = set(_get_issue_numbers(group, build_version))
changelog = _get_changelog_path(cwd)
for line in changelog.read_text().splitlines():
for issue in issue_numbers:
if issue in line:
yield line
def _get_group_from_folder(folder: Pathlike) -> str:
name = pathlib.Path(folder).name
middle = name.removeprefix(GROUP_FOLDER_PREFIX).removesuffix(GROUP_FOLDER_SUFFIX)
if middle in GROUPS:
return middle
msg = f"Folder '{name}' is not an MBP group folder"
raise MBPExcecption(msg)
def _get_latest_version(folder: Pathlike) -> "Version":
dir_names = [i.name for i in pathlib.Path(folder).iterdir() if i.is_dir()]
version_names = [name for name in dir_names if name.startswith("v")]
versions = [Version.from_name(name) for name in version_names]
sorted_versions = sorted(versions, key=lambda x: x.to_tuple())
return sorted_versions[-1]
## data classes
@dataclass
class Version:
major: int
layout: int
minor: int
@classmethod
def from_name(cls, name: str) -> "Version":
parts = name.removeprefix("v").split(".") + [None, None]
args = tuple([_to_int(part) for part in parts[:3]])
return cls(*args)
def to_tuple(self) -> tuple[int, int, int]:
return (self.major, self.layout, self.minor)
def bump(self) -> None:
cls = type(self)
return cls(self.major, self.layout, self.minor + 1)
def __str__(self) -> str:
return f"v{self.major}.{self.layout}.{self.minor}"
## functions for `sg_mbp_build`
def copy_workbooks(group: str, destination: Pathlike, build_version: str) -> None:
source = _get_workbook_folder(group)
all_xls_files = _files_in_folder(source, ".xlsx")
mbp_files = (f for f in all_xls_files if f.name.lower().startswith("mbp"))
for excel_file in mbp_files:
new_name = f"{excel_file.stem} {build_version}{excel_file.suffix}"
new_path = destination / new_name.format(version=build_version)
print(excel_file.name, "->", new_path)
shutil.copyfile(excel_file, new_path)
def copy_frms(group: str, destination: Pathlike, build_version: str) -> None:
current_frms = _list_current_frms(group, build_version)
for folder in current_frms:
new_path = destination / folder.name
print(folder.name, "->", new_path)
shutil.copytree(folder, new_path)
def copy_workbook_changelogs(
cwd: Pathlike, destination: Pathlike, latest: Version, build_version: str
) -> None:
source = pathlib.Path(cwd) / str(latest)
textfiles = _files_in_folder(source, ".txt")
logs = (f for f in textfiles if f.stem.lower().startswith("change"))
for log_file in logs:
new_name = log_file.name.replace(str(latest), build_version)
new_path = destination / new_name
print(log_file.name, "->", new_path)
shutil.copyfile(log_file, new_path)
def copy_changelog(cwd: Pathlike, destination: Pathlike, build_version: str) -> None:
changelog = _get_changelog_path(cwd)
new_path = pathlib.Path(destination) / f"CHANGELOG {build_version}.txt"
print(changelog.name, "->", new_path)
shutil.copyfile(changelog, new_path)
def get_announcement_text(
cwd: Pathlike,
group: str,
latest: Version,
build_version: str,
new_folder_name: Pathlike,
) -> str:
changes = list(_extract_changes_from_log(cwd, group, build_version))
if len(changes) == 1:
change_msg = "Only one change was introduced:"
else:
change_msg = "The changes made:"
if not build_version.endswith(TODAY):
term = "build"
dev_version = build_version.split(".")[-1]
version_note = (
f"As indicated by the letter '{dev_version}' at the end,"
f" this {term} is intended for Freiburg only."
)
else:
term = "version"
version_note = f"This is an official release {term}."
text = [
f"# New MBP {group} {term.title()} {build_version}",
"Good News Everyone,",
f"there is a new MBP {group} {term} available: {build_version}",
version_note,
change_msg,
"\n".join(changes),
f"You can find this {term} at our Freiburg Shared Drive:",
(
"Google Drive / Shared drives / Freiburg / Workbooks /"
f" MBP Workbooks {group} /"
f" {latest} /"
f" {new_folder_name}"
),
"Cheers,\nHolgi",
]
return "\n\n".join(text)
@click.command()
@click.option(
"-d",
"--dev_version",
prompt="Dev version i.e. 'c'",
required=True,
default=TODAY,
)
def sg_mbp_build(dev_version):
"""
Before running this command:
\b
- create a new versions folder e.g. "v4.9.2"
- make the requiered edits to the workbooks
- note the changes in the excel changelogs in the created version folder
- edit the group changelog
The command will collect all data into one folder on the Desktop to be published
"""
try:
cwd = pathlib.Path.cwd()
group = _get_group_from_folder(cwd)
latest = _get_latest_version(cwd)
build_version = f"{latest}.{dev_version}"
new_folder_name = f"{TODAY} MBP {group} {build_version}"
new_folder_path = PATH_WIN_DESKTOP / new_folder_name
if new_folder_path.exists():
raise MBPExcecption(f"Folder exists on desktop: {new_folder_name}")
else:
new_folder_path.mkdir()
copy_workbooks(group, new_folder_path, build_version)
copy_frms(group, new_folder_path, build_version)
copy_workbook_changelogs(cwd, new_folder_path, latest, build_version)
copy_changelog(cwd, new_folder_path, build_version)
announcement = get_announcement_text(
cwd, group, latest, build_version, new_folder_name
)
pyperclip.copy(announcement)
print(announcement)
except MBPExcecption as e:
sys.exit(str(e))
## functions for `sg_mbp_new_version`
def get_next_version() -> Version:
try:
cwd = pathlib.Path.cwd()
_get_group_from_folder(cwd) # may raise an exception
latest = _get_latest_version(cwd)
return latest.bump()
except MBPExcecption as e:
sys.exit(str(e))
def create_new_version_folder(cwd: Pathlike, new_version: str) -> pathlib.Path:
new_folder_path = pathlib.Path(cwd) / new_version
if new_folder_path.exists():
msg = f"Folder for version {new_version} already exists"
raise MBPExcecption(msg)
new_folder_path.mkdir()
return new_folder_path
def create_excel_changelogs(folder: Pathlike, new_version: str) -> None:
folder = pathlib.Path(folder)
group = _get_group_from_folder(folder.parent)
for name, cell in EXCEL_CHANGELOGS[group].items():
new_file = folder / name.format(version=new_version)
with new_file.open("w") as fh:
data_line = "\t".join(["Settings", cell, new_version, ""])
content_lines = EXCEL_CHANGELOG_HEADERS + [data_line, "", ""]
fh.write(CRLF.join(content_lines))
def create_changelog_entry(cwd: Pathlike, new_version: str) -> None:
group = _get_group_from_folder(cwd)
workbooks = ", ".join(GROUPS[group])
changelog = _get_changelog_path(cwd)
content = []
with changelog.open("r") as fh:
stripped_lines = (line.rstrip() for line in fh)
for line in stripped_lines:
content.append(line)
if line.startswith("----"):
content.append("")
content.append(f"{new_version}, work in progress:")
content.append(
f" - The following Workbooks did not have any changes: {workbooks}"
)
with changelog.open("w") as fh:
fh.write(CRLF.join(content))
@click.command()
@click.option(
"-v",
"--version",
required=True,
prompt="new version",
default=get_next_version,
show_default="next minor version",
)
def sg_mbp_new_version(version):
"""
creates a new version folder, new excel changes files and modifies the overall changelog
in "E:\Safeguard-MBP-issues"
"""
cwd = pathlib.Path.cwd()
folder = create_new_version_folder(cwd, version)
create_excel_changelogs(folder, version)
create_changelog_entry(cwd, version)