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