This is an automated email from the ASF dual-hosted git repository.
sbp pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/tooling-trusted-releases.git
The following commit(s) were added to refs/heads/main by this push:
new 5115047 Make pluralisation more consistent throughout
5115047 is described below
commit 511504757e49161f19466f9c546c413d047c7c1d
Author: Sean B. Palmer <[email protected]>
AuthorDate: Mon Dec 29 15:08:10 2025 +0000
Make pluralisation more consistent throughout
---
atr/admin/__init__.py | 24 +++++++++++-------------
atr/form.py | 4 ++--
atr/get/checks.py | 6 +++---
atr/get/keys.py | 2 +-
atr/get/projects.py | 8 ++++----
atr/get/vote.py | 6 +++---
atr/manager.py | 3 ++-
atr/post/draft.py | 5 +----
atr/post/finish.py | 7 ++++---
atr/post/keys.py | 6 +++---
atr/post/upload.py | 8 +++-----
atr/shared/__init__.py | 8 ++++----
atr/storage/writers/keys.py | 4 ++--
atr/storage/writers/release.py | 6 +++---
atr/tabulate.py | 12 ++++--------
atr/tasks/checks/license.py | 2 +-
atr/tasks/checks/rat.py | 8 +++-----
atr/tasks/checks/signature.py | 2 +-
atr/tasks/checks/zipformat.py | 5 ++++-
atr/util.py | 34 ++++++++++++++++++----------------
20 files changed, 77 insertions(+), 83 deletions(-)
diff --git a/atr/admin/__init__.py b/atr/admin/__init__.py
index 63b5a62..3b54161 100644
--- a/atr/admin/__init__.py
+++ b/atr/admin/__init__.py
@@ -303,7 +303,8 @@ async def delete_committee_keys_post(
await data.commit()
await quart.flash(
- f"Removed {num_removed} key links for '{committee_name}'. Deleted
{unused_deleted} unused keys.",
+ f"Removed {util.plural(num_removed, 'key link')} for
'{committee_name}'. "
+ f"Deleted {util.plural(unused_deleted, 'unused key')}.",
"success",
)
@@ -389,9 +390,9 @@ async def delete_test_openpgp_keys_post(session:
web.Committer) -> web.Response:
delete_outcome = await wafc.keys.test_user_delete_all(test_uid)
deleted_count = delete_outcome.result_or_raise()
- suffix = "s" if (deleted_count != 1) else ""
await quart.flash(
- f"Successfully deleted {deleted_count} OpenPGP key{suffix} and
their associated links for test user.",
+ f"Successfully deleted {util.plural(deleted_count, 'OpenPGP key')}
"
+ "and their associated links for test user.",
"success",
)
except Exception as e:
@@ -717,10 +718,8 @@ async def tasks_recent(session: web.Committer, minutes:
int) -> str:
recent_tasks = (await data.execute(statement)).scalars().all()
page = htm.Block()
- minute_word = "minute" if (minutes == 1) else "minutes"
- page.h1[f"Tasks from the last {minutes} {minute_word}"]
- task_word = "task" if (len(recent_tasks) == 1) else "tasks"
- page.p[f"Found {len(recent_tasks)} {task_word}"]
+ page.h1[f"Tasks from the last {util.plural(minutes, 'minute')}"]
+ page.p[f"Found {util.plural(len(recent_tasks), 'task')}"]
if recent_tasks:
table = htm.Block(htpy.table, classes=".table.table-sm")
@@ -785,7 +784,7 @@ async def test(session: web.Committer) -> web.QuartResponse:
start = time.perf_counter_ns()
outcomes: outcome.List[types.Key] = await
wacm.keys.ensure_stored(keys_file_text)
end = time.perf_counter_ns()
- log.info(f"Upload of {outcomes.result_count} keys took {end - start}
ns")
+ log.info(f"Upload of {util.plural(outcomes.result_count, 'key')} took
{end - start} ns")
for ocr in outcomes.results():
log.info(f"Uploaded key: {type(ocr)} {ocr.key_model.fingerprint}")
for oce in outcomes.errors():
@@ -864,9 +863,9 @@ async def _check_keys(fix: bool = False) -> str:
if fix:
key.apache_uid = asf_uid
await data.commit()
- message = f"Checked {len(keys)} keys"
+ message = f"Checked {util.plural(len(keys), 'key')}"
if bad_keys:
- message += f"\nFound {len(bad_keys)} bad keys:\n{'\n'.join(bad_keys)}"
+ message += f"\nFound {util.plural(len(bad_keys), 'bad
key')}:\n{'\n'.join(bad_keys)}"
return message
@@ -943,12 +942,11 @@ async def _delete_releases(session: web.Committer,
releases_to_delete: list[str]
fail_count += 1
error_messages.append(f"{release_name}: Unexpected error ({e})")
- releases = "release" if (success_count == 1) else "releases"
if success_count > 0:
- await quart.flash(f"Successfully deleted {success_count} {releases}.",
"success")
+ await quart.flash(f"Successfully deleted {util.plural(success_count,
'release')}.", "success")
if fail_count > 0:
errors_str = "\n".join(error_messages)
- await quart.flash(f"Failed to delete {fail_count}
{releases}:\n{errors_str}", "error")
+ await quart.flash(f"Failed to delete {util.plural(fail_count,
'release')}:\n{errors_str}", "error")
def _format_exception_location(exc: BaseException) -> str:
diff --git a/atr/form.py b/atr/form.py
index 3fdcf58..6f40775 100644
--- a/atr/form.py
+++ b/atr/form.py
@@ -35,6 +35,7 @@ import quart_wtf.utils as utils
import atr.htm as htm
import atr.models.schema as schema
+import atr.util as util
if TYPE_CHECKING:
from collections.abc import Iterator
@@ -121,8 +122,7 @@ def flash_error_data(
def flash_error_summary(errors: list[pydantic_core.ErrorDetails], flash_data:
dict[str, Any]) -> markupsafe.Markup:
div = htm.Block(htm.div, classes=".atr-initial")
- plural = len(errors) > 1
- div.text(f"Please fix the following issue{'s' if plural else ''}:")
+ div.text(f"Please fix the following {util.plural(len(errors), 'issue',
include_count=False)}:")
with div.block(htm.ul, classes=".mt-2.mb-0") as ul:
for i, flash_datum in enumerate(flash_data.values()):
if i > 9:
diff --git a/atr/get/checks.py b/atr/get/checks.py
index f4f5dac..35b5585 100644
--- a/atr/get/checks.py
+++ b/atr/get/checks.py
@@ -479,9 +479,9 @@ def _render_summary(
f" {files_skipped} {skipped_word}." if (files_skipped > 0) else "",
]
- check_word = "check" if (totals.file_pass_after == 1) else "checks"
- warn_word = "warning" if (totals.file_warn_after == 1) else "warnings"
- err_word = "error" if (totals.file_err_after == 1) else "errors"
+ check_word = util.plural(totals.file_pass_after, "check",
include_count=False)
+ warn_word = util.plural(totals.file_warn_after, "warning",
include_count=False)
+ err_word = util.plural(totals.file_err_after, "error", include_count=False)
summary_div = htm.Block(htm.div, classes=".d-flex.flex-wrap.gap-4.mb-3")
summary_div.span(".text-success")[
diff --git a/atr/get/keys.py b/atr/get/keys.py
index 9951ee8..1b14e2a 100644
--- a/atr/get/keys.py
+++ b/atr/get/keys.py
@@ -115,7 +115,7 @@ async def details(session: web.Committer, fingerprint: str)
-> str:
expires_content = htm.span(".text-warning.fw-bold")[
expires_str,
" ",
- htm.span(".badge.bg-warning.text-dark.ms-2")[f"Expires in
{days_until_expiry} days"],
+ htm.span(".badge.bg-warning.text-dark.ms-2")[f"Expires in
{util.plural(days_until_expiry, 'day')}"],
]
else:
expires_content = expires_str
diff --git a/atr/get/projects.py b/atr/get/projects.py
index c1eb00b..e0d7485 100644
--- a/atr/get/projects.py
+++ b/atr/get/projects.py
@@ -453,7 +453,7 @@ async def _render_releases_sections(
title=f"View draft {project.name} {drf.version}",
)[
f"{project.name} {drf.version} ",
- htm.span(".badge.bg-secondary.ms-2")[f"{file_count}
{'file' if (file_count == 1) else 'files'}"],
+
htm.span(".badge.bg-secondary.ms-2")[util.plural(file_count, "file")],
]
)
sections.div(".d-flex.flex-wrap.gap-2.mb-4")[*draft_buttons]
@@ -470,7 +470,7 @@ async def _render_releases_sections(
title=f"View candidate {project.name} {cnd.version}",
)[
f"{project.name} {cnd.version} ",
- htm.span(".badge.bg-info.ms-2")[f"{file_count} {'file' if
(file_count == 1) else 'files'}"],
+ htm.span(".badge.bg-info.ms-2")[util.plural(file_count,
"file")],
]
)
sections.div(".d-flex.flex-wrap.gap-2.mb-4")[*candidate_buttons]
@@ -487,7 +487,7 @@ async def _render_releases_sections(
title=f"View preview {project.name} {prv.version}",
)[
f"{project.name} {prv.version} ",
- htm.span(".badge.bg-warning.ms-2")[f"{file_count} {'file'
if (file_count == 1) else 'files'}"],
+ htm.span(".badge.bg-warning.ms-2")[util.plural(file_count,
"file")],
]
)
sections.div(".d-flex.flex-wrap.gap-2.mb-4")[*preview_buttons]
@@ -504,7 +504,7 @@ async def _render_releases_sections(
title=f"View release {project.name} {rel.version}",
)[
f"{project.name} {rel.version} ",
- htm.span(".badge.bg-success.ms-2")[f"{file_count} {'file'
if (file_count == 1) else 'files'}"],
+ htm.span(".badge.bg-success.ms-2")[util.plural(file_count,
"file")],
]
)
sections.div(".d-flex.flex-wrap.gap-2.mb-4")[*release_buttons]
diff --git a/atr/get/vote.py b/atr/get/vote.py
index b2bdcd2..f5e4d20 100644
--- a/atr/get/vote.py
+++ b/atr/get/vote.py
@@ -354,9 +354,9 @@ def _render_section_checks(page: htm.Block, release:
sql.Release, file_totals: c
warn_count = file_totals.file_warn_after
err_count = file_totals.file_err_after
- check_word = "check" if (pass_count == 1) else "checks"
- warn_word = "warning" if (warn_count == 1) else "warnings"
- err_word = "error" if (err_count == 1) else "errors"
+ check_word = util.plural(pass_count, "check", include_count=False)
+ warn_word = util.plural(warn_count, "warning", include_count=False)
+ err_word = util.plural(err_count, "error", include_count=False)
checks_list = htm.Block(htm.div, classes=".d-flex.flex-wrap.gap-4.mb-3")
checks_list.span(".text-success")[
diff --git a/atr/manager.py b/atr/manager.py
index 37a6281..9d5ccec 100644
--- a/atr/manager.py
+++ b/atr/manager.py
@@ -32,6 +32,7 @@ import sqlmodel
import atr.db as db
import atr.log as log
import atr.models.sql as sql
+import atr.util as util
# Global debug flag to control worker process output capturing
global_worker_debug: bool = False
@@ -340,7 +341,7 @@ class WorkerManager:
log.error(f"Expected cursor result, got
{type(result)}")
return
if result.rowcount > 0:
- log.info(f"Reset {result.rowcount} tasks to state
'QUEUED' due to worker issues")
+ log.info(f"Reset {util.plural(result.rowcount,
'task')} to state 'QUEUED' due to worker issues")
except Exception as e:
log.error(f"Error resetting broken tasks: {e}")
diff --git a/atr/post/draft.py b/atr/post/draft.py
index 9060b73..dc81f94 100644
--- a/atr/post/draft.py
+++ b/atr/post/draft.py
@@ -94,10 +94,7 @@ async def delete_file(
success_message = f"File '{rel_path_to_delete.name}' deleted successfully"
if metadata_files_deleted:
- success_message += (
- f", and {metadata_files_deleted} associated metadata "
- f"file{'' if (metadata_files_deleted == 1) else 's'} deleted"
- )
+ success_message += f", and {util.plural(metadata_files_deleted,
'associated metadata file')} deleted"
return await session.redirect(
get.compose.selected, success=success_message,
project_name=project_name, version_name=version_name
)
diff --git a/atr/post/finish.py b/atr/post/finish.py
index 997f9ca..7408891 100644
--- a/atr/post/finish.py
+++ b/atr/post/finish.py
@@ -21,6 +21,7 @@ import atr.blueprints.post as post
import atr.log as log
import atr.shared as shared
import atr.storage as storage
+import atr.util as util
import atr.web as web
@@ -126,16 +127,16 @@ async def _remove_rc_tags(
if creation_error is not None:
return await respond(409, creation_error)
- items = "item" if (renamed_count == 1) else "items"
if error_messages:
status_ok = renamed_count > 0
# TODO: Ideally HTTP would have a general mixed status, like 207
but for anything
http_status = 200 if status_ok else 500
- msg = f"RC tags removed for {renamed_count} {items} with some
errors: {'; '.join(error_messages)}"
+ msg = f"RC tags removed for {util.plural(renamed_count, 'item')}"
+ msg += f" with some errors: {'; '.join(error_messages)}"
return await respond(http_status, msg)
if renamed_count > 0:
- return await respond(200, f"Successfully removed RC tags from
{renamed_count} {items}.")
+ return await respond(200, f"Successfully removed RC tags from
{util.plural(renamed_count, 'item')}.")
return await respond(200, "No items required RC tag removal or no
changes were made.")
diff --git a/atr/post/keys.py b/atr/post/keys.py
index 9dc03d7..9e5eb6f 100644
--- a/atr/post/keys.py
+++ b/atr/post/keys.py
@@ -134,9 +134,9 @@ async def import_selected_revision(
wacm = await write.as_project_committee_member(project_name)
outcomes: outcome.List[types.Key] = await
wacm.keys.import_keys_file(project_name, version_name)
- message = f"Uploaded {outcomes.result_count} keys"
+ message = f"Uploaded {util.plural(outcomes.result_count, 'key')}"
if outcomes.error_count > 0:
- message += f", failed to upload {outcomes.error_count} keys for
{wacm.committee_name}"
+ message += f", failed to upload {util.plural(outcomes.error_count,
'key')} for {wacm.committee_name}"
return await session.redirect(
get.compose.selected,
success=message,
@@ -251,7 +251,7 @@ async def _process_keys(keys_text: str, selected_committee:
str) -> str:
error_count = outcomes.error_count
total_count = success_count + error_count
- message = f"Processed {total_count} keys: {success_count} successful"
+ message = f"Processed {util.plural(total_count, 'key')}: {success_count}
successful"
if error_count > 0:
message += f", {error_count} failed"
diff --git a/atr/post/upload.py b/atr/post/upload.py
index a3565ca..7908bbb 100644
--- a/atr/post/upload.py
+++ b/atr/post/upload.py
@@ -66,8 +66,7 @@ async def finalise(
async with storage.write(session) as write:
wacp = await write.as_project_committee_participant(project_name)
number_of_files = len(staged_files)
- plural = "s" if (number_of_files != 1) else ""
- description = f"Upload of {number_of_files} file{plural} through
web interface"
+ description = f"Upload of {util.plural(number_of_files, 'file')}
through web interface"
async with wacp.release.create_and_manage_revision(project_name,
version_name, description) as creating:
for filename in staged_files:
@@ -79,7 +78,7 @@ async def finalise(
return await session.redirect(
get.compose.selected,
- success=f"{number_of_files} file{plural} added successfully",
+ success=f"{util.plural(number_of_files, 'file')} added
successfully",
project_name=project_name,
version_name=version_name,
)
@@ -157,10 +156,9 @@ async def _add_files(
wacp = await write.as_project_committee_participant(project_name)
number_of_files = await wacp.release.upload_files(project_name,
version_name, file_name, file_data)
- plural = number_of_files != 1
return await session.redirect(
get.compose.selected,
- success=f"{number_of_files} file{'s' if plural else ''} added
successfully",
+ success=f"{util.plural(number_of_files, 'file')} added
successfully",
project_name=project_name,
version_name=version_name,
)
diff --git a/atr/shared/__init__.py b/atr/shared/__init__.py
index f3427d9..4c9e400 100644
--- a/atr/shared/__init__.py
+++ b/atr/shared/__init__.py
@@ -241,11 +241,11 @@ def _render_checks_summary(info: types.PathInfo | None,
project_name: str, versi
file_content: list[htm.Element | str] = []
if error_count > 0:
- label = "error" if (error_count == 1) else "errors"
-
file_content.append(htpy.span(".badge.bg-danger.me-2")[f"{error_count}
{label}"])
+
file_content.append(htpy.span(".badge.bg-danger.me-2")[util.plural(error_count,
"error")])
if warning_count > 0:
- label = "warning" if (warning_count == 1) else "warnings"
-
file_content.append(htpy.span(".badge.bg-warning.text-dark.me-2")[f"{warning_count}
{label}"])
+ file_content.append(
+
htpy.span(".badge.bg-warning.text-dark.me-2")[util.plural(warning_count,
"warning")]
+ )
file_content.append(htpy.a(href=report_url)[htpy.strong[htpy.code[file_path]]])
files_div.div[*file_content]
diff --git a/atr/storage/writers/keys.py b/atr/storage/writers/keys.py
index 57a6f45..584db4f 100644
--- a/atr/storage/writers/keys.py
+++ b/atr/storage/writers/keys.py
@@ -558,7 +558,7 @@ class CommitteeParticipant(FoundationCommitter):
stmt.returning(via(sql.PublicSigningKey.fingerprint)),
)
key_inserts = {row.fingerprint for row in key_insert_result}
- log.info(f"Inserted or updated {len(key_inserts)} keys")
+ log.info(f"Inserted or updated {util.plural(len(key_inserts),
'key')}")
else:
# TODO: Warn the user about any keys that were already inserted
key_inserts = set()
@@ -585,7 +585,7 @@ class CommitteeParticipant(FoundationCommitter):
.returning(via(sql.KeyLink.key_fingerprint))
)
link_inserts = {row.key_fingerprint for row in link_insert_result}
- log.info(f"Inserted {len(link_inserts)} key links")
+ log.info(f"Inserted {util.plural(len(link_inserts), 'key link')}")
def replace_with_linked(key: types.Key) -> types.Key:
# nonlocal link_inserts
diff --git a/atr/storage/writers/release.py b/atr/storage/writers/release.py
index 30a8669..fdf4284 100644
--- a/atr/storage/writers/release.py
+++ b/atr/storage/writers/release.py
@@ -122,12 +122,12 @@ class CommitteeParticipant(FoundationCommitter):
tasks_to_delete = await
self.__data.task(project_name=release.project.name,
version_name=release.version).all()
for task in tasks_to_delete:
await self.__data.delete(task)
- log.debug(f"Deleted {len(tasks_to_delete)} tasks for {project_name}
{version}")
+ log.debug(f"Deleted {util.plural(len(tasks_to_delete), 'task')} for
{project_name} {version}")
checks_to_delete = await
self.__data.check_result(release_name=release.name).all()
for check in checks_to_delete:
await self.__data.delete(check)
- log.debug(f"Deleted {len(checks_to_delete)} check results for
{project_name} {version}")
+ log.debug(f"Deleted {util.plural(len(checks_to_delete), 'check
result')} for {project_name} {version}")
# TODO: Ensure that revisions are not deleted
# But this makes testing difficult
@@ -436,7 +436,7 @@ class CommitteeParticipant(FoundationCommitter):
) -> int:
"""Process and save the uploaded files into a new draft revision."""
number_of_files = len(files)
- description = f"Upload of {number_of_files} file{'' if
(number_of_files == 1) else 's'} through web interface"
+ description = f"Upload of {util.plural(number_of_files, 'file')}
through web interface"
async with self.create_and_manage_revision(project_name, version_name,
description) as creating:
# Save each uploaded file to the new revision directory
for file in files:
diff --git a/atr/tabulate.py b/atr/tabulate.py
index b7ea3ce..e4b1259 100644
--- a/atr/tabulate.py
+++ b/atr/tabulate.py
@@ -212,14 +212,10 @@ def _format_duration(duration_hours: float | int) -> str:
minutes = 0
parts: list[str] = []
- if hours == 1:
- parts.append("1 hour")
- elif hours > 1:
- parts.append(f"{hours} hours")
- if minutes == 1:
- parts.append("1 minute")
- elif minutes > 1:
- parts.append(f"{minutes} minutes")
+ if hours > 0:
+ parts.append(util.plural(hours, "hour"))
+ if minutes > 0:
+ parts.append(util.plural(minutes, "minute"))
if not parts:
return "less than 1 minute"
diff --git a/atr/tasks/checks/license.py b/atr/tasks/checks/license.py
index d4f9ce4..e67c00d 100644
--- a/atr/tasks/checks/license.py
+++ b/atr/tasks/checks/license.py
@@ -453,7 +453,7 @@ def _headers_check_core_logic(artifact_path: str,
ignore_lines: list[str]) -> It
yield ArtifactResult(
status=sql.CheckResultStatus.SUCCESS,
- message=f"Checked {artifact_data.files_checked} files,"
+ message=f"Checked {util.plural(artifact_data.files_checked, 'file')},"
f" found {artifact_data.files_with_valid_headers} with valid headers,"
f" {artifact_data.files_with_invalid_headers} with invalid headers,"
f" and {artifact_data.files_skipped} skipped",
diff --git a/atr/tasks/checks/rat.py b/atr/tasks/checks/rat.py
index fadae35..3bce4f5 100644
--- a/atr/tasks/checks/rat.py
+++ b/atr/tasks/checks/rat.py
@@ -196,7 +196,7 @@ def _check_core_logic(
raise ValueError("XML output path is None")
results = _check_core_logic_parse_output(xml_output_path,
extract_dir)
- log.info(f"Successfully parsed RAT output with
{results.get('total_files', 0)} files")
+ log.info(f"Successfully parsed RAT output with
{util.plural(results.get('total_files', 0), 'file')}")
# The unknown_license_files key may contain a list of dicts
# {"name": "./README.md", "license": "Unknown license"}
@@ -597,11 +597,9 @@ def _summary_message(valid: bool, unapproved_licenses:
int, unknown_licenses: in
if not valid:
message = "Found "
if unapproved_licenses > 0:
- files = "file" if (unapproved_licenses == 1) else "files"
- message += f"{unapproved_licenses} {files} with unapproved
licenses"
+ message += f"{util.plural(unapproved_licenses, 'file')} with
unapproved licenses"
if unknown_licenses > 0:
message += " and "
if unknown_licenses > 0:
- files = "file" if (unknown_licenses == 1) else "files"
- message += f"{unknown_licenses} {files} with unknown licenses"
+ message += f"{util.plural(unknown_licenses, 'file')} with unknown
licenses"
return message
diff --git a/atr/tasks/checks/signature.py b/atr/tasks/checks/signature.py
index 60d65d0..bc92e27 100644
--- a/atr/tasks/checks/signature.py
+++ b/atr/tasks/checks/signature.py
@@ -137,7 +137,7 @@ def _check_core_logic_verify_signature(
if not import_result.fingerprints:
log.warning("No fingerprints found after importing keys")
end = time.perf_counter_ns()
- log.info(f"Import {len(ascii_armored_keys)} keys took {(end - start) /
1000000} ms")
+ log.info(f"Import of {util.plural(len(ascii_armored_keys), 'key')}
took {(end - start) / 1000000} ms")
verified = gpg.verify_file(sig_file, str(artifact_path))
key_fp = verified.pubkey_fingerprint.lower() if
verified.pubkey_fingerprint else None
diff --git a/atr/tasks/checks/zipformat.py b/atr/tasks/checks/zipformat.py
index b1363db..cdce89e 100644
--- a/atr/tasks/checks/zipformat.py
+++ b/atr/tasks/checks/zipformat.py
@@ -23,6 +23,7 @@ from typing import Any
import atr.log as log
import atr.models.results as results
import atr.tasks.checks as checks
+import atr.util as util
async def integrity(args: checks.FunctionArguments) -> results.Results | None:
@@ -38,7 +39,9 @@ async def integrity(args: checks.FunctionArguments) ->
results.Results | None:
if result_data.get("error"):
await recorder.failure(result_data["error"], result_data)
else:
- await recorder.success(f"Zip archive integrity OK
({result_data['member_count']} members)", result_data)
+ await recorder.success(
+ f"Zip archive integrity OK
({util.plural(result_data['member_count'], 'member')})", result_data
+ )
except Exception as e:
await recorder.failure("Error checking zip integrity", {"error":
str(e)})
diff --git a/atr/util.py b/atr/util.py
index 0838124..c3281f6 100644
--- a/atr/util.py
+++ b/atr/util.py
@@ -284,14 +284,6 @@ def create_path_matcher(lines: Iterable[str], full_path:
pathlib.Path, base_dir:
return lambda file_path: gitignore_parser.handle_negation(file_path, rules)
-def is_dev_environment() -> bool:
- conf = config.get()
- for development_host in ("127.0.0.1", "localhost.apache.org"):
- if (conf.APP_HOST == development_host) or
conf.APP_HOST.startswith(f"{development_host}:"):
- return True
- return False
-
-
def email_from_uid(uid: str) -> str | None:
if m := re.search(r"<([^>]+)>", uid):
return m.group(1).lower()
@@ -524,6 +516,14 @@ async def has_files(release: sql.Release) -> bool:
return False
+def is_dev_environment() -> bool:
+ conf = config.get()
+ for development_host in ("127.0.0.1", "localhost.apache.org"):
+ if (conf.APP_HOST == development_host) or
conf.APP_HOST.startswith(f"{development_host}:"):
+ return True
+ return False
+
+
async def is_dir_resolve(path: pathlib.Path) -> pathlib.Path | None:
try:
resolved_path = await asyncio.to_thread(path.resolve)
@@ -688,6 +688,15 @@ def permitted_voting_recipients(asf_uid: str,
committee_name: str) -> list[str]:
]
+def plural(count: int, singular: str, plural_form: str | None = None, *,
include_count: bool = True) -> str:
+ if plural_form is None:
+ plural_form = singular + "s"
+ word = singular if (count == 1) else plural_form
+ if include_count:
+ return f"{count} {word}"
+ return word
+
+
async def read_file_for_viewer(full_path: pathlib.Path, max_size: int) ->
tuple[str | None, bool, bool, str | None]:
"""Read file content for viewer."""
content: str | None = None
@@ -759,13 +768,6 @@ def release_directory_base(release: sql.Release) ->
pathlib.Path:
return base_dir / project_name / version_name
-# def release_directory_eventual(release: models.Release) -> pathlib.Path:
-# """Return the path to the eventual destination of the release files."""
-# path_project = release.project.name
-# path_version = release.version
-# return get_finished_dir() / path_project / path_version
-
-
def release_directory_revision(release: sql.Release) -> pathlib.Path | None:
"""Return the path to the directory containing the active files for a
given release phase."""
path_project = release.project.name
@@ -1024,7 +1026,7 @@ def version_sort_key(version: str) -> bytes:
if version[i].isdigit():
# Find the end of this digit sequence
j = i
- while j < length and version[j].isdigit():
+ while (j < length) and version[j].isdigit():
j += 1
digit_sequence = version[i:j]
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]