Skip to content

Commit

Permalink
Adding acknowlegements exctractor method
Browse files Browse the repository at this point in the history
  • Loading branch information
kamaal111 committed Dec 17, 2023
1 parent 89e2d18 commit d81e25b
Show file tree
Hide file tree
Showing 5 changed files with 372 additions and 0 deletions.
9 changes: 9 additions & 0 deletions justfile
Original file line number Diff line number Diff line change
Expand Up @@ -11,20 +11,29 @@ build:
rm -rf dist
. .venv/bin/activate
python3 -m build
just install-self

upload:
#!/bin/bash

. .venv/bin/activate
twine upload dist/*

test:
#!/bin/bash

. .venv/bin/activate
pytest

install-self:
#!/bin/bash

. .venv/bin/activate
pip install -e .

init-venv:
#!/bin/bash

python3 -m venv .venv
. .venv/bin/activate
just install-deps
Expand Down
1 change: 1 addition & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
build==0.10.0
click==8.1.6
pytest==7.4.3
twine==4.0.2
307 changes: 307 additions & 0 deletions src/xctools_kamaalio/actions/acknowledgments.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,307 @@
from dataclasses import asdict, dataclass
import json
import os
from pathlib import Path
import subprocess
import sys
from typing import TypedDict


def acknowledgments():
arguments = parse_arguments()

packages_directory = get_packages_directory(scheme=arguments["scheme"])
packages_licenses = get_packages_licenses(packages_directory=packages_directory)

package_file_content = decode_package_file()
packages = package_file_content_to_acknowledgments(
package_file_content=package_file_content, packages_licenses=packages_licenses
)

contributors_list = subprocess.getoutput(
f'git log "--pretty=format:%an <%ae>"'
).splitlines()
formatted_contributors = format_contributors(contributors_list=contributors_list)
acknowledgements = Acknowledgements(
packages=packages, contributors=formatted_contributors
)

output_path = Path(arguments["output"]) / "Acknowledgements.json"
output_path.write_text(acknowledgements.to_json(indent=2))

print("done writing acknowledgements ✨")


def format_contributors(contributors_list: list[str]):
contributor_names_mapped_by_emails: dict[str, list[str]] = {}
for contributor_entry in contributors_list:
splitted_contributor_entry = contributor_entry.split("<")
contributor_name = (
"".join(splitted_contributor_entry[:-1])
.strip()
.replace("<", "")
.replace(">", "")
)
email = splitted_contributor_entry[-1].strip().replace("<", "").replace(">", "")

contributor_names_mapped_by_emails[
email
] = contributor_names_mapped_by_emails.get(email, []) + [contributor_name]

contributors: list[Contributor] = []
for email, contributor_names in contributor_names_mapped_by_emails.items():
longest_contributor_name = ""

for contributor_name in contributor_names:
if contributor_name == "kamaal111" or contributor_name == "Kamaal":
contributor_name = "Kamaal Farah"
if len(contributor_name) > len(longest_contributor_name):
longest_contributor_name = contributor_name

contributors.append(
Contributor(
name=longest_contributor_name,
email=email,
contributions=len(contributor_names),
)
)

merged_contributors: list[Contributor] = []
for contributor in contributors:
contributor_first_names_names = map(
lambda contributor: contributor.first_name, merged_contributors
)
if contributor.first_name in contributor_first_names_names:
for index, merged_author in enumerate(merged_contributors):
first_name_is_the_same = (
contributor.first_name == merged_author.first_name
)
name_is_the_same = contributor.name == merged_author.name

one_of_authors_has_just_a_single_name = (
contributor.has_just_a_single_name
or merged_author.has_just_a_single_name
) and len(contributor.name_components) != len(
merged_author.name_components
)

if first_name_is_the_same and (
one_of_authors_has_just_a_single_name or name_is_the_same
):
if len(contributor.name) > len(merged_author.name):
longest_author_name = contributor.name
else:
longest_author_name = merged_author.name

merged_contributors[index] = Contributor(
name=longest_author_name,
email=None,
contributions=contributor.contributions
+ merged_author.contributions,
)
else:
contributor.email = None
merged_contributors.append(contributor)

return sorted(
sorted(merged_contributors, key=lambda contributor: contributor.name),
key=lambda contributor: contributor.contributions,
reverse=True,
)


def parse_arguments():
arguments: Arguments = {}

skip_next_value = False
for index, arg in enumerate(sys.argv[1:]):
if skip_next_value:
skip_next_value = False
continue

def get_next_value():
if index + 1 < len(sys.argv):
nonlocal skip_next_value
skip_next_value = True

return sys.argv[index + 2]

if arg == "--scheme" and (scheme := get_next_value()):
arguments["scheme"] = scheme
if arg == "--output" and (output := get_next_value()):
arguments["output"] = output

if arguments.get("scheme") is None:
raise Exception("Please provide a scheme with the --scheme flag")

if arguments.get("output") is None:
raise Exception("Please provide a output path with the --output flag")

return arguments


def get_path_from_root_ending_with(search_string: str) -> str | None:
current_work_directory = os.getcwd()
root_files = os.listdir(current_work_directory)

for file in root_files:
if file.endswith(search_string):
return file


def get_packages_licenses(packages_directory: str):
licenses: dict[str, str] = {}
for root, _, files in os.walk(packages_directory):
if "LICENSE" not in files:
continue

package_name = root.split("/")[-1]

license_path = os.path.join(root, "LICENSE")
with open(license_path, "r") as file:
licenses[package_name] = file.read()

return licenses


def package_file_content_to_acknowledgments(
package_file_content: "PackageFileContent", packages_licenses: dict[str, str]
):
packages: list[AcknowledgementPackage] = []
if package_object := package_file_content.get("object"):
for pin in package_object["pins"]:
package_name = pin["package"]
url = pin["repositoryURL"]
if url.endswith(".git"):
url = url[:-4]

author = url.split("/")[-2]

acknowledgement = AcknowledgementPackage(
name=package_name,
url=url,
author=author,
license=packages_licenses.get(package_name),
)
packages.append(acknowledgement)
else:
for pin in package_file_content["pins"]:
url = pin["location"]
if url.endswith(".git"):
url = url[:-4]

url_split_by_separator = url.split("/")
package_name = url_split_by_separator[-1]
author = url_split_by_separator[-2]
acknowledgement = AcknowledgementPackage(
name=package_name,
url=url,
author=author,
license=packages_licenses.get(package_name),
)
packages.append(acknowledgement)

return packages


def decode_package_file() -> "PackageFileContent":
if workspace_path := get_path_from_root_ending_with(search_string=".xcworkspace"):
path = Path(workspace_path) / "xcshareddata" / "swiftpm" / "Package.resolved"
return json.loads(path.read_text())

raise Exception("Workspace not found at root")


def get_packages_directory(scheme: str):
project_path = get_path_from_root_ending_with(search_string=".xcodeproj")
if project_path is None:
raise Exception("Project not found at root")

output = subprocess.getoutput(
f'xcodebuild -project {project_path} -target "{scheme}" -showBuildSettings'
)

output_search_line = " BUILD_DIR = "
for line in output.splitlines():
if output_search_line in line:
return line.replace(output_search_line, "").replace(
"Build/Products", "SourcePackages/checkouts"
)

raise Exception(f"Build directory not found from {output=}")


@dataclass
class Acknowledgements:
packages: list["AcknowledgementPackage"]
contributors: list["Contributor"]

def to_dict(self):
dictionary_to_return = {}
for key, value in asdict(self).items():
dictionary_to_return[key] = value
return dictionary_to_return

def to_json(self, indent: int | None = None):
return json.dumps(self.to_dict(), indent=indent)


@dataclass
class Contributor:
name: str
email: str | None
contributions: int

@property
def first_name(self):
return self.name_components[0]

@property
def name_components(self):
return self.name.split(" ")

@property
def has_just_a_single_name(self):
return len(self.name_components) == 1


@dataclass
class AcknowledgementPackage:
name: str
url: str
author: str | None
license: str | None


class Arguments(TypedDict):
scheme: str
output: str


class PackageFileContentObjectPinState(TypedDict):
branch: str | None
revision: str
version: str | None


class PackageFileContentObjectPin(TypedDict):
package: str
repositoryURL: str
state: PackageFileContentObjectPinState


class PackageFileContentObject(TypedDict):
pins: list[PackageFileContentObjectPin]


class PackageFileContentPin(TypedDict):
identity: str
kind: str
location: str
state: PackageFileContentObjectPinState


class PackageFileContent(TypedDict):
version: int
object: PackageFileContentObject | None
pins: list[PackageFileContentPin] | None
4 changes: 4 additions & 0 deletions src/xctools_kamaalio/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

from kamaaalpy.lists import removed, find_index

from xctools_kamaalio.actions.acknowledgments import acknowledgments
from xctools_kamaalio.actions.upload import upload
from xctools_kamaalio.actions.archive import archive
from xctools_kamaalio.actions.bump_version import bump_version
Expand All @@ -19,6 +20,7 @@
"trust-swift-plugins",
"test",
"build",
"acknowledgments",
]


Expand All @@ -44,6 +46,8 @@ def cli():
test()
if action == "build":
build()
if action == "acknowledgments":
acknowledgments()


class CLIException(Exception):
Expand Down
Loading

0 comments on commit d81e25b

Please sign in to comment.