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-release.git


The following commit(s) were added to refs/heads/main by this push:
     new 706f5ff  Align project name validation with observed naming patterns
706f5ff is described below

commit 706f5ffd8e0e783cd4310a77d4fb22c1e25b00e5
Author: Sean B. Palmer <[email protected]>
AuthorDate: Fri Jun 13 15:04:57 2025 +0100

    Align project name validation with observed naming patterns
---
 atr/routes/projects.py | 43 ++++++++++++++++++++++++++++++++++++++-----
 1 file changed, 38 insertions(+), 5 deletions(-)

diff --git a/atr/routes/projects.py b/atr/routes/projects.py
index e13ae04..0ccd10c 100644
--- a/atr/routes/projects.py
+++ b/atr/routes/projects.py
@@ -19,6 +19,7 @@
 
 import datetime
 import http.client
+import re
 from typing import Final, Protocol
 
 import asfquart.base as base
@@ -469,12 +470,10 @@ async def _project_add(form: AddFormProtocol, asf_id: 
str) -> response.Response:
 
 async def _project_add_validate(form: AddFormProtocol) -> tuple[str, str, str] 
| None:
     committee_name = str(form.committee_name.data)
+    # Normalise spaces in the display name, then validate
     display_name = str(form.display_name.data).strip()
-    if not display_name.startswith("Apache "):
-        await quart.flash("Display name must start with 'Apache '", "error")
-        return None
-    if not all(word[:1].isupper() for word in display_name.split(" ")):
-        await quart.flash("Display name must be in title case", "error")
+    display_name = re.sub(r"  +", " ", display_name)
+    if not await _project_add_validate_display_name(display_name):
         return None
     # Hidden criterion!
     # $ sqlite3 state/atr.db 'select full_name from project;' | grep -- 
'[^A-Za-z0-9 ]'
@@ -509,6 +508,40 @@ async def _project_add_validate(form: AddFormProtocol) -> 
tuple[str, str, str] |
     return (committee_name, display_name, label)
 
 
+async def _project_add_validate_display_name(display_name: str) -> bool:
+    # We have three criteria for display names
+    must_start_apache = "The first display name word must be 'Apache'."
+    must_have_two_words = "The display name must have at least two words."
+    must_use_correct_case = "Display name words must be in PascalCase, 
camelCase, or mod_ case."
+
+    # First criterion, the first word must be "Apache"
+    display_name_words = display_name.split(" ")
+    if display_name_words[0] != "Apache":
+        await quart.flash(must_start_apache, "error")
+        return False
+
+    # Second criterion, the display name must have two or more words
+    if not display_name_words[1:]:
+        await quart.flash(must_have_two_words, "error")
+        return False
+
+    # Third criterion, the display name must use the correct case
+    allowed_irregular_words = {".NET", "C++", "Empire-db", "Lucene.NET", 
"for", "jclouds"}
+    r_pascal_case = re.compile(r"^([A-Z][0-9a-z]*)+$")
+    r_camel_case = re.compile(r"^[a-z]*([A-Z][0-9a-z]*)+$")
+    r_mod_case = re.compile(r"^mod(_[0-9a-z]+)+$")
+    for display_name_word in display_name_words[1:]:
+        if display_name_word in allowed_irregular_words:
+            continue
+        is_pascal_case = r_pascal_case.match(display_name_word)
+        is_camel_case = r_camel_case.match(display_name_word)
+        is_mod_case = r_mod_case.match(display_name_word)
+        if not (is_pascal_case or is_camel_case or is_mod_case):
+            await quart.flash(must_use_correct_case, "error")
+            return False
+    return True
+
+
 def _set_default_fields(form: ReleasePolicyForm, project: models.Project, 
release_policy: models.ReleasePolicy) -> None:
     # Handle start_vote_template
     submitted_start_template = str(util.unwrap(form.start_vote_template.data))


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to