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.
Files changed (33) hide show
  1. {pyopensourceprojects-0.5.2 → pyopensourceprojects-0.6.0}/PKG-INFO +2 -1
  2. pyopensourceprojects-0.6.0/osprojects/__init__.py +1 -0
  3. {pyopensourceprojects-0.5.2 → pyopensourceprojects-0.6.0}/osprojects/git_api.py +20 -5
  4. {pyopensourceprojects-0.5.2 → pyopensourceprojects-0.6.0}/osprojects/osproject.py +68 -11
  5. {pyopensourceprojects-0.5.2 → pyopensourceprojects-0.6.0}/osprojects/version.py +2 -1
  6. {pyopensourceprojects-0.5.2 → pyopensourceprojects-0.6.0}/pyproject.toml +2 -0
  7. pyopensourceprojects-0.6.0/tests/test_gitlog2wiki.py +122 -0
  8. {pyopensourceprojects-0.5.2 → pyopensourceprojects-0.6.0}/tests/test_osproject.py +1 -1
  9. pyopensourceprojects-0.5.2/osprojects/__init__.py +0 -1
  10. {pyopensourceprojects-0.5.2 → pyopensourceprojects-0.6.0}/.github/workflows/build.yml +0 -0
  11. {pyopensourceprojects-0.5.2 → pyopensourceprojects-0.6.0}/.github/workflows/upload-to-pypi.yml +0 -0
  12. {pyopensourceprojects-0.5.2 → pyopensourceprojects-0.6.0}/.gitignore +0 -0
  13. {pyopensourceprojects-0.5.2 → pyopensourceprojects-0.6.0}/.project +0 -0
  14. {pyopensourceprojects-0.5.2 → pyopensourceprojects-0.6.0}/.pydevproject +0 -0
  15. {pyopensourceprojects-0.5.2 → pyopensourceprojects-0.6.0}/AGENTS.md +0 -0
  16. {pyopensourceprojects-0.5.2 → pyopensourceprojects-0.6.0}/LICENSE +0 -0
  17. {pyopensourceprojects-0.5.2 → pyopensourceprojects-0.6.0}/README.md +0 -0
  18. {pyopensourceprojects-0.5.2 → pyopensourceprojects-0.6.0}/mkdocs.yml +0 -0
  19. {pyopensourceprojects-0.5.2 → pyopensourceprojects-0.6.0}/osprojects/check_project.py +0 -0
  20. {pyopensourceprojects-0.5.2 → pyopensourceprojects-0.6.0}/osprojects/checkos.py +0 -0
  21. {pyopensourceprojects-0.5.2 → pyopensourceprojects-0.6.0}/osprojects/editor.py +0 -0
  22. {pyopensourceprojects-0.5.2 → pyopensourceprojects-0.6.0}/osprojects/github_api.py +0 -0
  23. {pyopensourceprojects-0.5.2 → pyopensourceprojects-0.6.0}/osprojects/gitlab_api.py +0 -0
  24. {pyopensourceprojects-0.5.2 → pyopensourceprojects-0.6.0}/scripts/blackisort +0 -0
  25. {pyopensourceprojects-0.5.2 → pyopensourceprojects-0.6.0}/scripts/doc +0 -0
  26. {pyopensourceprojects-0.5.2 → pyopensourceprojects-0.6.0}/scripts/install +0 -0
  27. {pyopensourceprojects-0.5.2 → pyopensourceprojects-0.6.0}/scripts/installAndTest +0 -0
  28. {pyopensourceprojects-0.5.2 → pyopensourceprojects-0.6.0}/scripts/release +0 -0
  29. {pyopensourceprojects-0.5.2 → pyopensourceprojects-0.6.0}/scripts/test +0 -0
  30. {pyopensourceprojects-0.5.2 → pyopensourceprojects-0.6.0}/tests/__init__.py +0 -0
  31. {pyopensourceprojects-0.5.2 → pyopensourceprojects-0.6.0}/tests/basetest.py +0 -0
  32. {pyopensourceprojects-0.5.2 → pyopensourceprojects-0.6.0}/tests/test_github.py +0 -0
  33. {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.5.2
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
- pattern = (
39
- r"(?:https?://[^/]+/|git@[^:]+:)(?P<owner>[^/]+)/(?P<project_id>[^/.]+)"
40
- )
41
- match = re.match(pattern, url)
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
- ssh = re.match(r"git@(?P<host>[^:]+):(?P<path>.+?)(?:\.git)?$", self.url)
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
- parser = argparse.ArgumentParser(description="gitlog2wiki")
538
- if _argv:
539
- _args = parser.parse_args(args=_argv)
595
+ from osprojects.version import Version
540
596
 
541
- osProject = OsProject.fromRepo()
542
- commits = osProject.getCommits()
543
- print("\n".join([c.toWikiMarkup() for c in commits]))
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-16"
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"
@@ -16,6 +16,8 @@ readme = "README.md"
16
16
  license = "Apache-2.0"
17
17
 
18
18
  dependencies = [
19
+ # https://pypi.org/project/pybasemkit/
20
+ "pybasemkit>=0.2.2",
19
21
  # https://pypi.org/project/beautifulsoup4/
20
22
  "beautifulsoup4>=4.14.2",
21
23
  # https://pypi.org/project/GitPython/
@@ -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"