+
Skip to content

Fix #3655: Allow dots (.), hyphens (-), and underscores (_) in GitHub usernames #3658

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 8 commits into
base: dev
Choose a base branch
from
Open
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
### Modules

- Remove args stub from module template to satisfy language server ([#3403](https://github.com/nf-core/tools/pull/3403))
- Allow GitHub usernames with dots (`.`), hyphens (`-`), and underscores (`_`) in `nf-core modules create` to support usernames from GitLab and other providers. ([#3658](https://github.com/nf-core/tools/pull/3658))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
- Allow GitHub usernames with dots (`.`), hyphens (`-`), and underscores (`_`) in `nf-core modules create` to support usernames from GitLab and other providers. ([#3658](https://github.com/nf-core/tools/pull/3658))
- Allow usernames with dots (`.`), hyphens (`-`), and underscores (`_`) in `nf-core modules create` to support usernames from GitLab and other providers. ([#3658](https://github.com/nf-core/tools/pull/3658))

- Fix modules meta.yml file structure ([#3532](https://github.com/nf-core/tools/pull/3532))
- Fix wrong key when updating module outputs ([#3665](https://github.com/nf-core/tools/pull/3665))

Expand Down
43 changes: 34 additions & 9 deletions nf_core/components/create.py
Original file line number Diff line number Diff line change
Expand Up @@ -430,16 +430,41 @@ def _get_username(self):
author_default = f"@{gh_auth_user['login']}"
except Exception as e:
log.debug(f"Could not find GitHub username using 'gh' cli command: [red]{e}")
# Determine whether this repo is hosted on GitHub
repo_url = (
getattr(self.modules_repo, "remote_url", None)
or getattr(self.wf.dir, "git_origin_url", "")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i don't thing wf is an attribute for ComponentCreate instances

or ""
)
is_github = "github.com" in repo_url.lower()

# Regex to valid GitHub username: https://github.com/shinnn/github-username-regex
github_username_regex = re.compile(r"^@[a-zA-Z\d](?:[a-zA-Z\d]|-(?=[a-zA-Z\d])){0,38}$")
while self.author is None or not github_username_regex.match(self.author):
if self.author is not None and not github_username_regex.match(self.author):
log.warning("Does not look like a valid GitHub username (must start with an '@')!")
self.author = rich.prompt.Prompt.ask(
f"[violet]GitHub Username:[/]{' (@author)' if author_default is None else ''}",
default=author_default,
)
username_regex = re.compile(
r"^@[A-Za-z\d](?:[A-Za-z\d]|[._-](?=[A-Za-z\d])){0,38}$"
)

while True:
if self.author is None:
self.author = rich.prompt.Prompt.ask(
f"[violet]GitHub Username:[/]"
f"{' (@author)' if author_default is None else ''}",
default=author_default,
)

if is_github:
valid = bool(username_regex.match(self.author))
warn = (
"Invalid GitHub username—must start with '@' and use only "
"letters, numbers, '.', '_', or '-'"
)
else:
valid = self.author.startswith("@") and len(self.author) > 1
warn = "Invalid username—must start with '@'"

if valid:
break

log.warning(warn)
self.author = None

def _copy_old_files(self, component_old_path):
"""Copy files from old module to new module"""
Expand Down
43 changes: 43 additions & 0 deletions tests/modules/test_create.py
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,49 @@ def test_modules_migrate_symlink(self, mock_rich_ask):
# Check that symlink is deleted
assert not symlink_file.is_symlink()

@pytest.mark.parametrize("origin_url, username, should_pass", [
# GitHub uses strict regex
("git@github.com:nf-core/tools.git", "@john.doe", True),
("https://github.com/foo/bar.git", "@foo_bar-1", True),
("https://github.com/foo/bar.git", "@foo@bar", False),
# Non-GitHub is more relaxed
("git@gitlab.com:john.doe/proj.git", "@anything", True),
("ssh://gitea.example/user", "@user.name", True),
("ssh://gitlab.com/user", "user.name", False),
])
def test_username_validation(self, origin_url, username, should_pass, monkeypatch):
"""Test GitHub username validation in _get_username logic"""

class DummyWF:
def __init__(self, origin_url):
self.config = {}
self.dir = type("Dir", (), {"git_origin_url": origin_url})

class FakeModulesRepo:
no_pull_global = False
def __init__(self, *args, **kwargs):
pass
Comment on lines +180 to +188
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

see tests/test_modules.py on how we initiate test module repos


# Stub out all dependencies
monkeypatch.setattr(nf_core.utils, "run_cmd", lambda *a, **k: (b"", b""))
monkeypatch.setattr(nf_core.modules.modules_repo, "ModulesRepo", FakeModulesRepo)
monkeypatch.setattr(nf_core.components.components_utils, "get_repo_info", lambda d, p: (d, "pipeline", "org"))

# Create minimal ComponentCreate object
wf = DummyWF(origin_url)
cc = object.__new__(nf_core.components.create.ComponentCreate)
cc.wf = wf
cc.author = None

if should_pass:
monkeypatch.setattr("rich.prompt.Prompt.ask", lambda *a, **k: username)
cc._get_username()
assert cc.author == username
else:
monkeypatch.setattr("rich.prompt.Prompt.ask", lambda *a, **k: (_ for _ in ()).throw(SystemExit()))
with pytest.raises(SystemExit):
cc._get_username()

def test_modules_meta_yml_structure_biotools_meta(self):
"""Test the structure of the module meta.yml file when it was generated with INFORMATION from bio.tools and WITH a meta."""
with responses.RequestsMock() as rsps:
Expand Down
Loading
点击 这是indexloc提供的php浏览器服务,不要输入任何密码和下载