diff --git a/lib50/_api.py b/lib50/_api.py index b66578d..f040b84 100644 --- a/lib50/_api.py +++ b/lib50/_api.py @@ -39,7 +39,7 @@ DEFAULT_FILE_LIMIT = 10000 -def push(tool, slug, config_loader, repo=None, data=None, prompt=lambda question, included, excluded: True, file_limit=DEFAULT_FILE_LIMIT): +def push(tool, slug, config_loader, repo=None, data=None, prompt=lambda question, included, excluded: True, file_limit=DEFAULT_FILE_LIMIT, auth_method=None): """ Pushes to Github in name of a tool. What should be pushed is configured by the tool and its configuration in the .cs50.yml file identified by the slug. @@ -61,6 +61,10 @@ def push(tool, slug, config_loader, repo=None, data=None, prompt=lambda question :type prompt: lambda str, list, list => bool, optional :param file_limit: maximum number of files to be matched by any globbing pattern. :type file_limit: int + :param auth_method: The authentication method to use. Accepts `"https"` or `"ssh"`. \ + If any other value is provided, attempts SSH \ + authentication first and fall back to HTTPS if SSH fails. + :type auth_method: str :return: GitHub username and the commit hash :type: tuple(str, str) @@ -89,7 +93,7 @@ def push(tool, slug, config_loader, repo=None, data=None, prompt=lambda question remote, (honesty, included, excluded) = connect(slug, config_loader, file_limit=DEFAULT_FILE_LIMIT) # Authenticate the user with GitHub, and prepare the submission - with authenticate(remote["org"], repo=repo) as user, prepare(tool, slug, user, included): + with authenticate(remote["org"], repo=repo, auth_method=auth_method) as user, prepare(tool, slug, user, included): # Show any prompt if specified if prompt(honesty, included, excluded): @@ -1020,23 +1024,46 @@ def _match_files(universe, pattern): def get_content(org, repo, branch, filepath): """Get all content from org/repo/branch/filepath at GitHub.""" - url = "https://github.com/{}/{}/raw/{}/{}".format(org, repo, branch, filepath) - try: - r = requests.get(url) - if not r.ok: - if r.status_code == 404: - raise InvalidSlugError(_("Invalid slug. Did you mean to submit something else?")) - else: - # Check if GitHub outage may be the source of the issue - check_github_status() - - # Otherwise raise a ConnectionError - raise ConnectionError(_("Could not connect to GitHub. Do make sure you are connected to the internet.")) - - except requests.exceptions.SSLError as e: + + def _handle_ssl_error(e): + """Handle SSL errors consistently.""" raise ConnectionError(_(f"Could not connect to GitHub due to a SSL error.\nPlease check GitHub's status at githubstatus.com.\nError: {e}")) + + def _handle_non_404_error(): + """Handle non-404 HTTP errors consistently.""" - return r.content + # Check if GitHub outage may be the source of the issue + check_github_status() + + # Otherwise raise a ConnectionError + raise ConnectionError(_("Could not connect to GitHub. Do make sure you are connected to the internet.")) + + def _make_request(url): + """Make a request and handle SSL errors.""" + try: + return requests.get(url) + except requests.exceptions.SSLError as e: + _handle_ssl_error(e) + + url = "https://github.com/{}/{}/raw/{}/{}".format(org, repo, branch, filepath) + r = _make_request(url) + + if r.ok: + return r.content + + if r.status_code == 404: + # Try fallback URL in case there were issues with github.com's redirect + fallback_url = "https://raw.githubusercontent.com/{}/{}/{}/{}".format(org, repo, branch, filepath) + r = _make_request(fallback_url) + + if r.ok: + return r.content + elif r.status_code == 404: + raise InvalidSlugError(_("Invalid slug. Did you mean to submit something else?")) + else: + _handle_non_404_error() + else: + _handle_non_404_error() def check_github_status(): diff --git a/lib50/authentication.py b/lib50/authentication.py index 71d1e46..cc98ab0 100644 --- a/lib50/authentication.py +++ b/lib50/authentication.py @@ -32,14 +32,18 @@ class User: init=False) @contextlib.contextmanager -def authenticate(org, repo=None): +def authenticate(org, repo=None, auth_method=None): """ - A contextmanager that authenticates a user with GitHub via SSH if possible, otherwise via HTTPS. + A contextmanager that authenticates a user with GitHub. :param org: GitHub organisation to authenticate with :type org: str :param repo: GitHub repo (part of the org) to authenticate with. Default is the user's GitHub login. :type repo: str, optional + :param auth_method: The authentication method to use. Accepts `"https"` or `"ssh"`. \ + If any other value is provided, attempts SSH \ + authentication first and fall back to HTTPS if SSH fails. + :type auth_method: str, optional :return: an authenticated user :type: lib50.User @@ -51,21 +55,34 @@ def authenticate(org, repo=None): print(user.name) """ - with api.ProgressBar(_("Authenticating")) as progress_bar: - # Both authentication methods can require user input, best stop the bar - progress_bar.stop() + def try_https(org, repo): + with _authenticate_https(org, repo=repo) as user: + return user - # Try auth through SSH + def try_ssh(org, repo): user = _authenticate_ssh(org, repo=repo) - - # SSH auth failed, fallback to HTTPS if user is None: - with _authenticate_https(org, repo=repo) as user: - yield user - # yield SSH user - else: - yield user + raise ConnectionError + return user + # Showcase the type of authentication based on input + method_label = f" ({auth_method.upper()})" if auth_method in ("https", "ssh") else "" + with api.ProgressBar(_("Authenticating{}").format(method_label)) as progress_bar: + # Both authentication methods can require user input, best stop the bar + progress_bar.stop() + + match auth_method: + case "https": + yield try_https(org, repo) + case "ssh": + yield try_ssh(org, repo) + case _: + # Try auth through SSH + try: + yield try_ssh(org, repo) + except ConnectionError: + # SSH auth failed, fallback to HTTPS + yield try_https(org, repo) def logout(): """ diff --git a/lib50/locale/vi/LC_MESSAGES/lib50.mo b/lib50/locale/vi/LC_MESSAGES/lib50.mo new file mode 100644 index 0000000..8a3918f Binary files /dev/null and b/lib50/locale/vi/LC_MESSAGES/lib50.mo differ diff --git a/lib50/locale/vi/LC_MESSAGES/lib50.po b/lib50/locale/vi/LC_MESSAGES/lib50.po new file mode 100644 index 0000000..995dd48 --- /dev/null +++ b/lib50/locale/vi/LC_MESSAGES/lib50.po @@ -0,0 +1,243 @@ +# Vietnamese translations for lib50. +# Copyright (C) 2025 ORGANIZATION +# This file is distributed under the same license as the lib50 project. +# FIRST AUTHOR , 2025. +# +msgid "" +msgstr "" +"Project-Id-Version: lib50 3.1.1\n" +"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" +"POT-Creation-Date: 2025-08-05 15:21-0400\n" +"PO-Revision-Date: 2025-08-05 15:21-0400\n" +"Last-Translator: FULL NAME \n" +"Language: vi\n" +"Language-Team: vi \n" +"Plural-Forms: nplurals=1; plural=0;\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Generated-By: Babel 2.17.0\n" + +#: lib50/_api.py:101 +msgid "No files were submitted." +msgstr "Không có tập tin nào để nộp." + +#: lib50/_api.py:158 +#, python-brace-format +msgid "{} does not exist at {}/{}" +msgstr "{} không tồn tại tại {}/{}" + +#: lib50/_api.py:286 +#, python-brace-format +msgid "" +"Cannot include/exclude paths outside the current directory, but such a " +"path ({}) was specified." +msgstr "" +"Không thể bao gồm/loại trừ các đường dẫn bên ngoài thư mục này, nhưng " +"đường dẫn ({}) đã được chỉ định." + +#: lib50/_api.py:355 +msgid "Connecting" +msgstr "Đang kết nối" + +#: lib50/_api.py:363 +#, python-brace-format +msgid "Invalid slug for {}. Did you mean something else?" +msgstr "Slug cho {} không hợp lệ." + +#: lib50/_api.py:372 +#, python-brace-format +msgid "Go to {results} to see your results." +msgstr "Vui lòng đến {results} để xem kết qủa của bạn." + +#: lib50/_api.py:385 +#, python-brace-format +msgid "No files in this directory are expected by {}." +msgstr "Thư mục này không có tập tin nào {} đang mong đợi." + +#: lib50/_api.py:424 +msgid "Verifying" +msgstr "Đang thảm tra" + +#: lib50/_api.py:436 +#, python-brace-format +msgid "" +"Make sure your username and/or personal access token are valid and {} is " +"enabled for your account. To enable {}, " +msgstr "" +"Hãy đảm bảo username và/hoặc mã truy cập cá nhân của bạn hợp lệ và {} đã " +"được bật cho tài khoản của bạn. Để bật {}, " + +#: lib50/_api.py:438 +msgid "please contact your instructor." +msgstr "vui lòng liên hệ với giáo viên." + +#: lib50/_api.py:440 +#, python-brace-format +msgid "please go to {} in your web browser and try again." +msgstr "vui lòng đến {} và thử lại." + +#: lib50/_api.py:447 +msgid "Preparing" +msgstr "Đang chuẩn bị" + +#: lib50/_api.py:510 +msgid "Uploading" +msgstr "Đang tải lên" + +#: lib50/_api.py:511 +#, python-brace-format +msgid "automated commit by {}" +msgstr "commit tự động bởi {}" + +#: lib50/_api.py:564 +#, python-brace-format +msgid "Invalid slug: {}. Did you mean something else?" +msgstr "Slug này không hợp lệ: {}. " + +#: lib50/_api.py:568 +#, python-brace-format +msgid "Invalid slug: {}. Multiple configurations (both .yaml and .yml) found." +msgstr "Slug này không hợp lệ: {} Đã tìm thấy nhiều cấu hình (cả .yaml và .yml)." + +#: lib50/_api.py:668 +msgid "You don't have git. Install git, then re-run!" +msgstr "Bạn không có git. Vui lòng cài đặt git và thử lại." + +#: lib50/_api.py:674 +msgid "You have an old version of git. Install version 2.7 or later, then re-run!" +msgstr "Bạn đang sử dụng phiên bản git cũ. Hãy cài đặt phiên bản 2.7 trở lên, sau đó thử lại." + +#: lib50/_api.py:758 lib50/_api.py:828 +msgid "Invalid slug" +msgstr "Slug này không hợp lệ." + +#: lib50/_api.py:786 +#, python-brace-format +msgid "Invalid slug: {}" +msgstr "Slug này không hợp lệ: {}" + +#: lib50/_api.py:792 +#, python-brace-format +msgid "Invalid slug. Did you mean {}, without the leading and trailing slashes?" +msgstr "Slug này không hợp lệ. Ý bạn là {}, không có dấu gạch chéo ở đầu và cuối phải không?" + +#: lib50/_api.py:795 +#, python-brace-format +msgid "Invalid slug. Did you mean {}, without the leading slash?" +msgstr "Slug này không hợp lệ. Ý bạn là {}, không có dấu gạch chéo ở đầu phải không?" + +#: lib50/_api.py:798 +#, python-brace-format +msgid "Invalid slug. Did you mean {}, without the trailing slash?" +msgstr "Slug này không hợp lệ. Ý bạn là {}, không có dấu gạch chéo ở cuối phải không?" + +#: lib50/_api.py:1028 +msgid "Invalid slug. Did you mean to submit something else?" +msgstr "Slug này không hợp lệ. Bạn có ý định nộp bài khác không?" + +#: lib50/_api.py:1034 lib50/_api.py:1056 +msgid "" +"Could not connect to GitHub. Do make sure you are connected to the " +"internet." +msgstr "" +"Không kết nối với GitHub được. Hãy đảm bảo bạn đã kết nối với " +"internet." + +#: lib50/_api.py:1066 +#, python-brace-format +msgid "" +"Could not connect to GitHub. It looks like GitHub is having some issues " +"with {}. Do check on https://www.githubstatus.com and try again later." +msgstr "" +"Không kết nối với GitHub được. GitHub có vẻ bị vấn đề về {}." +"Vui lòng kiểm tra tại https://www.githubstatus.com và thử lại sau." + +#: lib50/_api.py:1089 +#, python-brace-format +msgid "" +"These files are too large to be submitted:\n" +"{}\n" +"Remove these files from your directory and then re-run!" +msgstr "" +"Những tập tin này quá lớn để nộp:\n" +"{}\n" +"Hãy xóa những tập tin này khỏi thư mục, rồi thử lại." + +#: lib50/_api.py:1097 +#, python-brace-format +msgid "" +"These files are too large to be submitted:\n" +"{}\n" +"Install git-lfs (or remove these files from your directory) and then re-" +"run!" +msgstr "" +"Những tập tin này quá lớn để nộp:\n" +"{}\n" +"Vui lòng cài đặt git-lfs (hay xoá những tập tin này khỏi thư mục), rồi thử lại." + +#: lib50/_errors.py:54 +msgid "You seem to be missing these required files:" +msgstr "Bạn thiếu những tập tin bắt buộc này:" + +#: lib50/_errors.py:56 lib50/_errors.py:76 +#, python-brace-format +msgid "You are currently in: {}, did you perhaps intend another directory?" +msgstr "Bạn đang ở: {}, có lẽ bạn muốn chuyển đến thư mục khác phải không?" + +#: lib50/_errors.py:75 +#, python-brace-format +msgid "Looks like you are in a directory with too many (> {}) files." +msgstr "Bạn đang ở trong một thư mục có quá nhiều tập tin (> {})." + +#: lib50/authentication.py:54 +msgid "Authenticating" +msgstr "Đang xác nhận" + +#: lib50/authentication.py:110 +msgid "" +"Looks like you're trying to push to a branch that conflicts with an " +"existing one in the repository.\n" +msgstr "" + +#: lib50/authentication.py:115 +msgid "You are trying to push to a branch that is not allowed.\n" +msgstr "" + +#: lib50/authentication.py:265 +msgid "Enter username for GitHub: " +msgstr "Nhập username cho GitHub: " + +#: lib50/authentication.py:272 +msgid "Enter personal access token for GitHub: " +msgstr "Nhập mã truy cập cá nhân cho GitHub: " + +#: lib50/authentication.py:291 +msgid "" +"You might be using your GitHub password to log in, but that's no longer " +"possible. But you can still use check50 and submit50! See " +"https://cs50.readthedocs.io/github for instructions." +msgstr "" +"Bạn có thể đang sử dụng mật khẩu GitHub để đăng nhập, nhưng phương pháp này không còn khả thi nữa." +"Tuy nhiên, bạn vẫn có thể sử dụng check50 và submit50. Xem " +"https://cs50.readthedocs.io/github để biết hướng dẫn." + +#: lib50/config.py:50 +#, python-brace-format +msgid "Two config files (.cs50.yaml and .cs50.yml) found at {}" +msgstr "Hai tập tin cấu hình (.cs50.yaml và .cs50.yml) được tìm thấy tại {}" + +#: lib50/config.py:53 +#, python-brace-format +msgid "No config file (.cs50.yaml or .cs50.yml) found at {}" +msgstr "Không tìm thấy tập tin cấu hình (.cs50.yaml hoặc .cs50.yml) tại {}" + +#: lib50/config.py:192 +msgid "Config is not valid yaml." +msgstr "Cấu hình yaml không hợp lệ." + +#: lib50/config.py:274 +#, python-brace-format +msgid "{} is not a valid tag for {}" +msgstr "{} không phải là tag hợp lệ cho {}" + diff --git a/setup.py b/setup.py index 0654ac0..a23c3b1 100644 --- a/setup.py +++ b/setup.py @@ -27,6 +27,6 @@ python_requires=">= 3.8", packages=["lib50"], url="https://github.com/cs50/lib50", - version="3.1.1", + version="3.1.2", include_package_data=True )