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