pyOpenSourceProjects 0.5.2__tar.gz → 0.6.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {pyopensourceprojects-0.5.2 → pyopensourceprojects-0.6.0}/PKG-INFO +2 -1
- pyopensourceprojects-0.6.0/osprojects/__init__.py +1 -0
- {pyopensourceprojects-0.5.2 → pyopensourceprojects-0.6.0}/osprojects/git_api.py +20 -5
- {pyopensourceprojects-0.5.2 → pyopensourceprojects-0.6.0}/osprojects/osproject.py +68 -11
- {pyopensourceprojects-0.5.2 → pyopensourceprojects-0.6.0}/osprojects/version.py +2 -1
- {pyopensourceprojects-0.5.2 → pyopensourceprojects-0.6.0}/pyproject.toml +2 -0
- pyopensourceprojects-0.6.0/tests/test_gitlog2wiki.py +122 -0
- {pyopensourceprojects-0.5.2 → pyopensourceprojects-0.6.0}/tests/test_osproject.py +1 -1
- pyopensourceprojects-0.5.2/osprojects/__init__.py +0 -1
- {pyopensourceprojects-0.5.2 → pyopensourceprojects-0.6.0}/.github/workflows/build.yml +0 -0
- {pyopensourceprojects-0.5.2 → pyopensourceprojects-0.6.0}/.github/workflows/upload-to-pypi.yml +0 -0
- {pyopensourceprojects-0.5.2 → pyopensourceprojects-0.6.0}/.gitignore +0 -0
- {pyopensourceprojects-0.5.2 → pyopensourceprojects-0.6.0}/.project +0 -0
- {pyopensourceprojects-0.5.2 → pyopensourceprojects-0.6.0}/.pydevproject +0 -0
- {pyopensourceprojects-0.5.2 → pyopensourceprojects-0.6.0}/AGENTS.md +0 -0
- {pyopensourceprojects-0.5.2 → pyopensourceprojects-0.6.0}/LICENSE +0 -0
- {pyopensourceprojects-0.5.2 → pyopensourceprojects-0.6.0}/README.md +0 -0
- {pyopensourceprojects-0.5.2 → pyopensourceprojects-0.6.0}/mkdocs.yml +0 -0
- {pyopensourceprojects-0.5.2 → pyopensourceprojects-0.6.0}/osprojects/check_project.py +0 -0
- {pyopensourceprojects-0.5.2 → pyopensourceprojects-0.6.0}/osprojects/checkos.py +0 -0
- {pyopensourceprojects-0.5.2 → pyopensourceprojects-0.6.0}/osprojects/editor.py +0 -0
- {pyopensourceprojects-0.5.2 → pyopensourceprojects-0.6.0}/osprojects/github_api.py +0 -0
- {pyopensourceprojects-0.5.2 → pyopensourceprojects-0.6.0}/osprojects/gitlab_api.py +0 -0
- {pyopensourceprojects-0.5.2 → pyopensourceprojects-0.6.0}/scripts/blackisort +0 -0
- {pyopensourceprojects-0.5.2 → pyopensourceprojects-0.6.0}/scripts/doc +0 -0
- {pyopensourceprojects-0.5.2 → pyopensourceprojects-0.6.0}/scripts/install +0 -0
- {pyopensourceprojects-0.5.2 → pyopensourceprojects-0.6.0}/scripts/installAndTest +0 -0
- {pyopensourceprojects-0.5.2 → pyopensourceprojects-0.6.0}/scripts/release +0 -0
- {pyopensourceprojects-0.5.2 → pyopensourceprojects-0.6.0}/scripts/test +0 -0
- {pyopensourceprojects-0.5.2 → pyopensourceprojects-0.6.0}/tests/__init__.py +0 -0
- {pyopensourceprojects-0.5.2 → pyopensourceprojects-0.6.0}/tests/basetest.py +0 -0
- {pyopensourceprojects-0.5.2 → pyopensourceprojects-0.6.0}/tests/test_github.py +0 -0
- {pyopensourceprojects-0.5.2 → pyopensourceprojects-0.6.0}/tests/test_github_api.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pyOpenSourceProjects
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.6.0
|
|
4
4
|
Summary: Check python OpenSource Projects to follow standards for README, github workflow and pyprojec.toml - creates badges
|
|
5
5
|
Project-URL: Home, https://github.com/WolfgangFahl/pyOpenSourceProjects
|
|
6
6
|
Project-URL: Documentation, http://wiki.bitplan.com/index.php/pyOpenSourceProjects
|
|
@@ -20,6 +20,7 @@ Requires-Dist: beautifulsoup4>=4.14.2
|
|
|
20
20
|
Requires-Dist: gitpython
|
|
21
21
|
Requires-Dist: packaging>=24.1
|
|
22
22
|
Requires-Dist: py-3rdparty-mediawiki>=0.18.1
|
|
23
|
+
Requires-Dist: pybasemkit>=0.2.2
|
|
23
24
|
Requires-Dist: pylodstorage>=0.17.0
|
|
24
25
|
Requires-Dist: python-dateutil>=2.8.2
|
|
25
26
|
Requires-Dist: ratelimit>=2.2.1
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.6.0"
|
|
@@ -35,10 +35,24 @@ class GenericRepo:
|
|
|
35
35
|
Returns:
|
|
36
36
|
GenericRepo instance or None if the URL cannot be parsed.
|
|
37
37
|
"""
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
)
|
|
41
|
-
|
|
38
|
+
# Match HTTPS: https://host/owner/repo
|
|
39
|
+
https_pattern = r"https?://[^/]+/(?P<owner>[^/]+)/(?P<project_id>[^/.]+)"
|
|
40
|
+
match = re.match(https_pattern, url)
|
|
41
|
+
|
|
42
|
+
if not match:
|
|
43
|
+
# Match SSH: [user@]host:path - extract last two path components as owner/repo
|
|
44
|
+
ssh_pattern = r"(?:[^@]+@)?[^:]+:(?P<path>.+?)(?:\.git)?$"
|
|
45
|
+
ssh_match = re.match(ssh_pattern, url)
|
|
46
|
+
if ssh_match:
|
|
47
|
+
path = ssh_match.group("path")
|
|
48
|
+
# Split path and take last two components
|
|
49
|
+
parts = [p for p in path.split("/") if p]
|
|
50
|
+
if len(parts) >= 2:
|
|
51
|
+
owner = parts[-2]
|
|
52
|
+
project_id = parts[-1]
|
|
53
|
+
repo = cls(owner=owner, project_id=project_id, url=url)
|
|
54
|
+
return repo
|
|
55
|
+
|
|
42
56
|
repo = None
|
|
43
57
|
if match:
|
|
44
58
|
repo = cls(
|
|
@@ -50,7 +64,8 @@ class GenericRepo:
|
|
|
50
64
|
|
|
51
65
|
def projectUrl(self) -> str:
|
|
52
66
|
"""Return a browsable HTTPS project URL derived from the remote URL."""
|
|
53
|
-
|
|
67
|
+
# Match SSH URLs: user@host:path or host:path
|
|
68
|
+
ssh = re.match(r"(?:[^@]+@)?(?P<host>[^:]+):(?P<path>.+?)(?:\.git)?$", self.url)
|
|
54
69
|
if ssh:
|
|
55
70
|
url = f"https://{ssh.group('host')}/{ssh.group('path')}"
|
|
56
71
|
else:
|
|
@@ -13,6 +13,7 @@ import subprocess
|
|
|
13
13
|
import sys
|
|
14
14
|
from typing import Dict, Iterable, List, Optional, Set, Tuple
|
|
15
15
|
|
|
16
|
+
from basemkit.base_cmd import BaseCmd
|
|
16
17
|
from dateutil.parser import parse
|
|
17
18
|
from tqdm import tqdm
|
|
18
19
|
|
|
@@ -511,20 +512,18 @@ class OsProject:
|
|
|
511
512
|
"--no-pager",
|
|
512
513
|
"log",
|
|
513
514
|
"--reverse",
|
|
514
|
-
r'--pretty=format:{"name":"%cn","date":"%cI","hash":"%h"}',
|
|
515
|
+
r'--pretty=format:{"name":"%cn","date":"%cI","hash":"%h","subject":"%s"}',
|
|
515
516
|
]
|
|
516
|
-
gitLogCommitSubject = ["git", "log", "--format=%s", "-n", "1"]
|
|
517
517
|
rawCommitLogs = subprocess.check_output(gitlogCmd).decode()
|
|
518
518
|
for rawLog in rawCommitLogs.split("\n"):
|
|
519
|
+
if not rawLog.strip():
|
|
520
|
+
continue
|
|
519
521
|
log = json.loads(rawLog)
|
|
520
522
|
if log.get("date", None) is not None:
|
|
521
523
|
log["date"] = datetime.datetime.fromisoformat(log["date"])
|
|
522
524
|
log["project"] = self.project_id
|
|
523
525
|
log["host"] = self.projectUrl()
|
|
524
526
|
log["path"] = ""
|
|
525
|
-
log["subject"] = subprocess.check_output(
|
|
526
|
-
[*gitLogCommitSubject, log["hash"]]
|
|
527
|
-
)[:-1].decode() # seperate query to avoid json escaping issues
|
|
528
527
|
commit = Commit()
|
|
529
528
|
for k, v in log.items():
|
|
530
529
|
setattr(commit, k, v)
|
|
@@ -532,15 +531,73 @@ class OsProject:
|
|
|
532
531
|
return commits
|
|
533
532
|
|
|
534
533
|
|
|
534
|
+
class GitLog2WikiCmd(BaseCmd):
|
|
535
|
+
"""Command line interface for gitlog2wiki."""
|
|
536
|
+
|
|
537
|
+
def add_arguments(self, parser):
|
|
538
|
+
"""Add gitlog2wiki-specific arguments to the parser.
|
|
539
|
+
|
|
540
|
+
Args:
|
|
541
|
+
parser: The argument parser to extend.
|
|
542
|
+
"""
|
|
543
|
+
super().add_arguments(parser)
|
|
544
|
+
parser.add_argument(
|
|
545
|
+
"--filter",
|
|
546
|
+
help="Filter commits by date prefix, e.g. 2026, 2026-03, 2026-03-28",
|
|
547
|
+
default=None,
|
|
548
|
+
)
|
|
549
|
+
|
|
550
|
+
def handle_args(self, args):
|
|
551
|
+
"""Handle parsed arguments and run the command.
|
|
552
|
+
|
|
553
|
+
Args:
|
|
554
|
+
args: Parsed argument namespace.
|
|
555
|
+
|
|
556
|
+
Returns:
|
|
557
|
+
bool: True if handled.
|
|
558
|
+
"""
|
|
559
|
+
handled = super().handle_args(args)
|
|
560
|
+
result = False
|
|
561
|
+
if handled:
|
|
562
|
+
result = True
|
|
563
|
+
else:
|
|
564
|
+
osProject = OsProject.fromRepo()
|
|
565
|
+
if osProject.repo is None:
|
|
566
|
+
try:
|
|
567
|
+
url = subprocess.check_output(
|
|
568
|
+
["git", "config", "--get", "remote.origin.url"]
|
|
569
|
+
)
|
|
570
|
+
url = url.decode().strip("\n")
|
|
571
|
+
print(
|
|
572
|
+
f"Error: Could not parse git remote URL: {url}",
|
|
573
|
+
file=sys.stderr,
|
|
574
|
+
)
|
|
575
|
+
except subprocess.CalledProcessError:
|
|
576
|
+
print(
|
|
577
|
+
"Error: Not in a git repository or no remote.origin.url configured",
|
|
578
|
+
file=sys.stderr,
|
|
579
|
+
)
|
|
580
|
+
result = False
|
|
581
|
+
else:
|
|
582
|
+
commits = osProject.getCommits()
|
|
583
|
+
if args.filter:
|
|
584
|
+
date_filter = args.filter
|
|
585
|
+
commits = [
|
|
586
|
+
c for c in commits if str(c.date.date()).startswith(date_filter)
|
|
587
|
+
]
|
|
588
|
+
print("\n".join([c.toWikiMarkup() for c in commits]))
|
|
589
|
+
result = True
|
|
590
|
+
return result
|
|
591
|
+
|
|
592
|
+
|
|
535
593
|
def gitlog2wiki(_argv=None):
|
|
536
594
|
"""Cmdline interface to get gitlog entries in wiki markup."""
|
|
537
|
-
|
|
538
|
-
if _argv:
|
|
539
|
-
_args = parser.parse_args(args=_argv)
|
|
595
|
+
from osprojects.version import Version
|
|
540
596
|
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
597
|
+
argv = sys.argv[1:] if _argv is None else _argv
|
|
598
|
+
cmd = GitLog2WikiCmd(Version)
|
|
599
|
+
result = cmd.run(argv)
|
|
600
|
+
return result
|
|
544
601
|
|
|
545
602
|
|
|
546
603
|
def main(_argv=None):
|
|
@@ -12,5 +12,6 @@ class Version(object):
|
|
|
12
12
|
name = "pyOpenSourceProjects"
|
|
13
13
|
version = osprojects.__version__
|
|
14
14
|
date = "2024-07-31"
|
|
15
|
-
updated = "2026-03-
|
|
15
|
+
updated = "2026-03-28"
|
|
16
16
|
description = "Check python OpenSource Projects to follow standards for README, github workflow and pyproject.toml - creates badges"
|
|
17
|
+
doc_url = "http://wiki.bitplan.com/index.php/pyOpenSourceProjects"
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
"""Created on 2026-03-28.
|
|
2
|
+
|
|
3
|
+
@author: wf
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import datetime
|
|
7
|
+
import unittest
|
|
8
|
+
|
|
9
|
+
from osprojects.osproject import Commit, GitLog2WikiCmd, gitlog2wiki
|
|
10
|
+
from osprojects.version import Version
|
|
11
|
+
from tests.basetest import BaseTest
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class TestGitLog2WikiFilter(BaseTest):
|
|
15
|
+
"""Test the --filter option of gitlog2wiki."""
|
|
16
|
+
|
|
17
|
+
def _make_commit(self, date_iso: str) -> Commit:
|
|
18
|
+
"""Create a Commit with the given ISO date string.
|
|
19
|
+
|
|
20
|
+
Args:
|
|
21
|
+
date_iso: ISO 8601 date string, e.g. '2026-03-28T10:00:00+00:00'.
|
|
22
|
+
|
|
23
|
+
Returns:
|
|
24
|
+
A Commit instance with date and hash set.
|
|
25
|
+
"""
|
|
26
|
+
commit = Commit()
|
|
27
|
+
commit.date = datetime.datetime.fromisoformat(date_iso)
|
|
28
|
+
commit.hash = "abc1234"
|
|
29
|
+
commit.subject = "Test commit"
|
|
30
|
+
commit.name = "Tester"
|
|
31
|
+
commit.project = "testproject"
|
|
32
|
+
commit.host = "https://github.com/test/testproject"
|
|
33
|
+
commit.path = ""
|
|
34
|
+
return commit
|
|
35
|
+
|
|
36
|
+
def setUp(self, debug=False, profile=True):
|
|
37
|
+
"""Set up sample commits spanning multiple years/months/days."""
|
|
38
|
+
super().setUp(debug=debug, profile=profile)
|
|
39
|
+
self.commits = [
|
|
40
|
+
self._make_commit("2024-12-01T08:00:00+00:00"),
|
|
41
|
+
self._make_commit("2025-06-15T12:00:00+00:00"),
|
|
42
|
+
self._make_commit("2026-03-01T09:00:00+00:00"),
|
|
43
|
+
self._make_commit("2026-03-28T14:30:00+00:00"),
|
|
44
|
+
self._make_commit("2026-11-05T17:00:00+00:00"),
|
|
45
|
+
]
|
|
46
|
+
|
|
47
|
+
def _apply_filter(self, date_filter: str):
|
|
48
|
+
"""Apply the same filter logic as GitLog2WikiCmd.handle_args.
|
|
49
|
+
|
|
50
|
+
Args:
|
|
51
|
+
date_filter: Date prefix string, e.g. '2026', '2026-03', '2026-03-28'.
|
|
52
|
+
|
|
53
|
+
Returns:
|
|
54
|
+
Filtered list of Commit objects.
|
|
55
|
+
"""
|
|
56
|
+
result = [c for c in self.commits if str(c.date.date()).startswith(date_filter)]
|
|
57
|
+
return result
|
|
58
|
+
|
|
59
|
+
def test_filter_by_year(self):
|
|
60
|
+
"""Filter by year should return all commits in that year."""
|
|
61
|
+
filtered = self._apply_filter("2026")
|
|
62
|
+
dates = [str(c.date.date()) for c in filtered]
|
|
63
|
+
self.assertEqual(3, len(filtered))
|
|
64
|
+
self.assertIn("2026-03-01", dates)
|
|
65
|
+
self.assertIn("2026-03-28", dates)
|
|
66
|
+
self.assertIn("2026-11-05", dates)
|
|
67
|
+
|
|
68
|
+
def test_filter_by_month(self):
|
|
69
|
+
"""Filter by year-month should return only commits in that month."""
|
|
70
|
+
filtered = self._apply_filter("2026-03")
|
|
71
|
+
dates = [str(c.date.date()) for c in filtered]
|
|
72
|
+
self.assertEqual(2, len(filtered))
|
|
73
|
+
self.assertIn("2026-03-01", dates)
|
|
74
|
+
self.assertIn("2026-03-28", dates)
|
|
75
|
+
|
|
76
|
+
def test_filter_by_day(self):
|
|
77
|
+
"""Filter by exact day should return only commits on that day."""
|
|
78
|
+
filtered = self._apply_filter("2026-03-28")
|
|
79
|
+
self.assertEqual(1, len(filtered))
|
|
80
|
+
self.assertEqual("2026-03-28", str(filtered[0].date.date()))
|
|
81
|
+
|
|
82
|
+
def test_filter_no_match(self):
|
|
83
|
+
"""Filter with no matching commits should return an empty list."""
|
|
84
|
+
filtered = self._apply_filter("2023")
|
|
85
|
+
self.assertEqual(0, len(filtered))
|
|
86
|
+
|
|
87
|
+
def test_no_filter_returns_all(self):
|
|
88
|
+
"""Without a filter all commits are returned."""
|
|
89
|
+
filtered = self.commits # no filter applied
|
|
90
|
+
self.assertEqual(5, len(filtered))
|
|
91
|
+
|
|
92
|
+
def test_gitlog2wiki_filter_cmdline(self):
|
|
93
|
+
"""Test gitlog2wiki CLI with --filter passes through and filters
|
|
94
|
+
output."""
|
|
95
|
+
if self.inPublicCI():
|
|
96
|
+
return
|
|
97
|
+
output = self.captureOutput(gitlog2wiki, ["--filter", "2022"])
|
|
98
|
+
lines = [line for line in output.strip().split("\n") if line]
|
|
99
|
+
# The very first commit (hash 106254f) is from 2022-01-24
|
|
100
|
+
self.assertTrue(len(lines) >= 1)
|
|
101
|
+
for line in lines:
|
|
102
|
+
self.assertIn("2022", line)
|
|
103
|
+
|
|
104
|
+
def test_gitlog2wiki_no_filter_cmdline(self):
|
|
105
|
+
"""Test gitlog2wiki CLI without --filter returns all commits."""
|
|
106
|
+
if self.inPublicCI():
|
|
107
|
+
return
|
|
108
|
+
output_all = self.captureOutput(gitlog2wiki, [])
|
|
109
|
+
output_filtered = self.captureOutput(gitlog2wiki, ["--filter", "2022"])
|
|
110
|
+
lines_all = [l for l in output_all.strip().split("\n") if l]
|
|
111
|
+
lines_filtered = [l for l in output_filtered.strip().split("\n") if l]
|
|
112
|
+
self.assertGreater(len(lines_all), len(lines_filtered))
|
|
113
|
+
|
|
114
|
+
def test_gitlog2wiki_cmd_class(self):
|
|
115
|
+
"""Test GitLog2WikiCmd can be instantiated with Version."""
|
|
116
|
+
cmd = GitLog2WikiCmd(Version)
|
|
117
|
+
self.assertIsNotNone(cmd)
|
|
118
|
+
self.assertIsInstance(cmd, GitLog2WikiCmd)
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
if __name__ == "__main__":
|
|
122
|
+
unittest.main()
|
|
@@ -76,7 +76,7 @@ class TestOsProject(BaseTest):
|
|
|
76
76
|
return
|
|
77
77
|
commit = self.getSampleById(Commit, "hash", "106254f")
|
|
78
78
|
expectedCommitMarkup = commit.toWikiMarkup()
|
|
79
|
-
output = self.captureOutput(gitlog2wiki)
|
|
79
|
+
output = self.captureOutput(gitlog2wiki, [])
|
|
80
80
|
outputLines = output.split("\n")
|
|
81
81
|
self.assertTrue(expectedCommitMarkup in outputLines)
|
|
82
82
|
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "0.5.2"
|
|
File without changes
|
{pyopensourceprojects-0.5.2 → pyopensourceprojects-0.6.0}/.github/workflows/upload-to-pypi.yml
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|