diff --git a/doc/sphinxext/announce.py b/doc/sphinxext/announce.py index 66e999e251c5e..4769f3c6fdf53 100755 --- a/doc/sphinxext/announce.py +++ b/doc/sphinxext/announce.py @@ -57,6 +57,52 @@ def get_authors(revision_range): + """ + Get authors from git log. + + This function retrieves the authors from the git log within the specified + revision range. It compares the authors from the current release to those + from the previous release to determine the authors for the current release. + + Parameters + ---------- + revision_range : str + The revision range to get the authors from, specified in the format + 'previous_revision..current_revision'. + + Returns + ------- + authors : list + A list of authors sorted alphabetically, with new authors (not present + in the previous release) marked with a '+'. + + See Also + -------- + CONTRIBUTOR_MAPPING : dict + A mapping of contributors' names for renaming purposes. + + Notes + ----- + - The function assumes that the `this_repo` object is an instance of a Git + repository and that it has methods like `git.log` and `git.shortlog` to + fetch commit logs and short logs, respectively. + - The `revision_range` parameter should be a valid range in the format + 'previous_revision..current_revision'. If the current revision includes + `HEAD`, it should be specified as 'previous_revision..current_revision|HEAD'. + - The function handles `Co-authored-by` commits separately to account for + backported changes, which are typically merged by bots. + - The function discards contributions by automated merge bots (e.g., 'Homu') + to provide a cleaner list of human contributors. + - Contributor names are updated according to the `CONTRIBUTOR_MAPPING` to + ensure consistent naming. + + Examples + -------- + >>> get_authors('v1.0.0..v1.0.1') + ['Joris Van den Bossche', 'Tom Augspurger', 'Jeff Reback', 'Philip Cloud', 'Stephan Hoyer'] + >>> get_authors('v1.0.1..HEAD') + ['Joris Van den Bossche', 'Tom Augspurger', 'Jeff Reback', 'Philip Cloud', 'Stephan Hoyer', 'Simon Hawkins +'] + """ pat = "^.*\\t(.*)$" lst_release, cur_release = (r.strip() for r in revision_range.split("..")) @@ -109,6 +155,42 @@ def get_authors(revision_range): def get_pull_requests(repo, revision_range): + """ + Retrieve a list of pull requests merged in a given revision range of a repository. + + This function identifies pull requests from regular merges, Homu auto merges, + and fast forward squash-merges within the specified revision range, and returns + a list of pull request objects from the GitHub repository. + + Parameters + ---------- + repo : object + A GitHub repository object that provides the method `get_pull` to retrieve pull request data. + revision_range : str + The revision range to search for merged pull requests, specified in the format + 'start_revision..end_revision'. + + Returns + ------- + list + A list of pull request objects corresponding to the pull request numbers found in the specified + revision range. + + Notes + ----- + - The function assumes that the `this_repo` object is an instance of a Git repository + and that it has a method `git.log` to fetch commit logs. + - Pull request numbers are identified from commit messages that follow specific patterns: + - "Merge pull request #" + - "Auto merge of #" + - "fast forward squash-merge (#)" + - The function sorts the pull request numbers before retrieving the pull request objects. + + Examples + -------- + >>> get_pull_requests(repo, 'v1.0.0..v1.1.0') + [, , ] + """ prnums = [] # From regular merges @@ -134,6 +216,45 @@ def get_pull_requests(repo, revision_range): def build_components(revision_range, heading="Contributors"): + """ + Build components for the contributors section based on a revision range. + + This function generates the components needed for constructing a formatted + contributors section. It extracts the list of authors who contributed within + the specified revision range and prepares a dictionary with the heading, a + message about the authors, and the list of authors. + + Parameters + ---------- + revision_range : str + The revision range to get the contributors from, specified in the format + 'start_revision..end_revision'. + heading : str, optional + The heading for the contributors section. Default is "Contributors". + + Returns + ------- + dict + A dictionary with the following keys: + - "heading" : str + The heading for the contributors section. + - "author_message" : str + A message about the number of authors. + - "authors" : list + A list of authors who contributed within the specified revision range. + + Notes + ----- + - The function assumes that the `get_authors` function is available and returns a list of authors + for the given revision range. + - The `author_msg` string is assumed to be a template string that takes a single integer, representing + the number of authors. + + Examples + -------- + >>> build_components('v1.0.0..v1.1.0') + {'heading': 'Contributors', 'author_message': 'There are 5 contributors:', 'authors': ['Author One', 'Author Two']} + """ lst_release, cur_release = (r.strip() for r in revision_range.split("..")) authors = get_authors(revision_range) @@ -145,6 +266,39 @@ def build_components(revision_range, heading="Contributors"): def build_string(revision_range, heading="Contributors"): + """ + Build a formatted string of contributors for a given revision range. + + This function constructs a formatted string listing the contributors within the specified + revision range, including a heading and a list of authors. The heading is underlined for + emphasis, and the authors are listed with bullet points. + + Parameters + ---------- + revision_range : str + The revision range to get the contributors from, specified in the format + 'start_revision..end_revision'. + heading : str, optional + The heading for the contributors section. Default is "Contributors". + + Returns + ------- + str + A formatted string with the heading, underlined heading, a message, and a list of authors. + + Notes + ----- + - The function assumes that the `build_components` function is available and returns a dictionary + with keys "heading", "author_message", and "authors". + - The length of the underline is dynamically set to match the length of the heading. + - Authors are listed with bullet points, each preceded by an asterisk (*) and a newline character. + - The `textwrap.dedent` method is used to maintain proper formatting of the template string. + + Examples + -------- + >>> build_string('v1.0.0..v1.1.0') + 'Contributors\n============\n\nList of authors who contributed:\n* Author One\n* Author Two' + """ components = build_components(revision_range, heading=heading) components["uline"] = "=" * len(components["heading"]) components["authors"] = "* " + "\n* ".join(components["authors"]) @@ -162,7 +316,41 @@ def build_string(revision_range, heading="Contributors"): def main(revision_range): - # document authors + """ + Generate and print the contributors list for a given revision range. + + This function builds a formatted string of contributors who have made changes + within the specified revision range and prints it to the standard output. + + Parameters + ---------- + revision_range : str + The revision range to get the contributors from, specified in the format + 'start_revision..end_revision'. + + Returns + ------- + None + + Notes + ----- + - The function relies on `build_string` to generate the formatted contributors list. + - The contributors list includes a heading, an underlined heading, a message about the authors, + and a list of authors with new contributors marked. + + Examples + -------- + >>> main('v1.0.0..v1.1.0') + + Authors + ============ + - TomAugspurger + - gfyoung + - datapythonista + - jreback + - jschendel + - ... + """ text = build_string(revision_range) print(text) diff --git a/doc/sphinxext/contributors.py b/doc/sphinxext/contributors.py index 06f205b5cc3ce..32dc6f0d2423a 100644 --- a/doc/sphinxext/contributors.py +++ b/doc/sphinxext/contributors.py @@ -26,6 +26,18 @@ class ContributorsDirective(Directive): name = "contributors" def run(self): + """ + Execute the contributors directive. + + This method processes the provided revision range and generates the corresponding + list of contributors. + + Returns + ------- + list + A list of nodes representing the message indicating the number of contributors + and commits, and a bullet list containing each contributor individually. + """ range_ = self.arguments[0] if range_.endswith("x..HEAD"): return [nodes.paragraph(), nodes.bullet_list()] @@ -53,6 +65,25 @@ def run(self): def setup(app): + """ + Setup function for the Sphinx extension. + + This function registers the `contributors` directive with the Sphinx application. + + Parameters + ---------- + app : sphinx.application.Sphinx + The Sphinx application object. + + Returns + ------- + dict + A dictionary containing metadata about the extension. + + Notes + ----- + - This function is typically called by Sphinx during the initialization phase. + """ app.add_directive("contributors", ContributorsDirective) return {"version": "0.1", "parallel_read_safe": True, "parallel_write_safe": True}