pyOpenSourceProjects 0.2.2__tar.gz → 0.3.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.3.0/PKG-INFO +42 -0
- pyopensourceprojects-0.3.0/README.md +15 -0
- pyopensourceprojects-0.3.0/osprojects/__init__.py +1 -0
- pyopensourceprojects-0.2.2/osprojects/checkos.py → pyopensourceprojects-0.3.0/osprojects/check_project.py +188 -138
- pyopensourceprojects-0.3.0/osprojects/checkos.py +137 -0
- pyopensourceprojects-0.3.0/osprojects/github_api.py +353 -0
- pyopensourceprojects-0.3.0/osprojects/osproject.py +580 -0
- {pyopensourceprojects-0.2.2 → pyopensourceprojects-0.3.0}/pyproject.toml +13 -6
- {pyopensourceprojects-0.2.2 → pyopensourceprojects-0.3.0}/tests/basetest.py +6 -0
- pyopensourceprojects-0.3.0/tests/test_github.py +120 -0
- pyopensourceprojects-0.3.0/tests/test_github_api.py +87 -0
- pyopensourceprojects-0.3.0/tests/test_osproject.py +85 -0
- pyopensourceprojects-0.2.2/PKG-INFO +0 -42
- pyopensourceprojects-0.2.2/README.md +0 -18
- pyopensourceprojects-0.2.2/osprojects/__init__.py +0 -1
- pyopensourceprojects-0.2.2/osprojects/osproject.py +0 -564
- pyopensourceprojects-0.2.2/tests/test_osproject.py +0 -193
- {pyopensourceprojects-0.2.2 → pyopensourceprojects-0.3.0}/.github/workflows/build.yml +0 -0
- {pyopensourceprojects-0.2.2 → pyopensourceprojects-0.3.0}/.github/workflows/upload-to-pypi.yml +0 -0
- {pyopensourceprojects-0.2.2 → pyopensourceprojects-0.3.0}/.gitignore +0 -0
- {pyopensourceprojects-0.2.2 → pyopensourceprojects-0.3.0}/.project +0 -0
- {pyopensourceprojects-0.2.2 → pyopensourceprojects-0.3.0}/.pydevproject +0 -0
- {pyopensourceprojects-0.2.2 → pyopensourceprojects-0.3.0}/LICENSE +0 -0
- {pyopensourceprojects-0.2.2 → pyopensourceprojects-0.3.0}/mkdocs.yml +0 -0
- {pyopensourceprojects-0.2.2 → pyopensourceprojects-0.3.0}/osprojects/editor.py +0 -0
- {pyopensourceprojects-0.2.2 → pyopensourceprojects-0.3.0}/scripts/blackisort +0 -0
- {pyopensourceprojects-0.2.2 → pyopensourceprojects-0.3.0}/scripts/doc +0 -0
- {pyopensourceprojects-0.2.2 → pyopensourceprojects-0.3.0}/scripts/install +0 -0
- {pyopensourceprojects-0.2.2 → pyopensourceprojects-0.3.0}/scripts/installAndTest +0 -0
- {pyopensourceprojects-0.2.2 → pyopensourceprojects-0.3.0}/scripts/release +0 -0
- {pyopensourceprojects-0.2.2 → pyopensourceprojects-0.3.0}/scripts/test +0 -0
- {pyopensourceprojects-0.2.2 → pyopensourceprojects-0.3.0}/tests/__init__.py +0 -0
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: pyOpenSourceProjects
|
|
3
|
+
Version: 0.3.0
|
|
4
|
+
Dynamic: Summary
|
|
5
|
+
Project-URL: Home, https://github.com/WolfgangFahl/pyOpenSourceProjects
|
|
6
|
+
Project-URL: Documentation, http://wiki.bitplan.com/index.php/pyOpenSourceProjects
|
|
7
|
+
Project-URL: Source, https://github.com/WolfgangFahl/pyOpenSourceProjects
|
|
8
|
+
Author-email: Wolfgang Fahl <wf@bitplan.com>
|
|
9
|
+
Maintainer-email: Wolfgang Fahl <wf@bitplan.com>
|
|
10
|
+
License-Expression: Apache-2.0
|
|
11
|
+
License-File: LICENSE
|
|
12
|
+
Classifier: Programming Language :: Python
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
17
|
+
Requires-Python: >=3.10
|
|
18
|
+
Requires-Dist: gitpython
|
|
19
|
+
Requires-Dist: packaging>=24.1
|
|
20
|
+
Requires-Dist: py-3rdparty-mediawiki>=0.17.0
|
|
21
|
+
Requires-Dist: pylodstorage>=0.17.0
|
|
22
|
+
Requires-Dist: python-dateutil>=2.8.2
|
|
23
|
+
Requires-Dist: requests
|
|
24
|
+
Requires-Dist: tqdm>=4.66.5
|
|
25
|
+
Provides-Extra: test
|
|
26
|
+
Description-Content-Type: text/markdown
|
|
27
|
+
|
|
28
|
+
# pyOpenSourceProjects
|
|
29
|
+
Helper Library to organize open source Projects
|
|
30
|
+
|
|
31
|
+
| | |
|
|
32
|
+
| :--- | :--- |
|
|
33
|
+
| **GitHub** | [](https://github.com/WolfgangFahl/pyOpenSourceProjects/actions/workflows/build.yml) [](https://github.com/WolfgangFahl/pyOpenSourceProjects/issues) [](https://github.com/WolfgangFahl/pyOpenSourceProjects/issues/?q=is%3Aissue+is%3Aclosed) |
|
|
34
|
+
| **PyPi** | [](https://pypi.python.org/pypi/pyOpenSourceProjects/) [](https://www.apache.org/licenses/LICENSE-2.0) [](https://pypi.org/project/pyOpenSourceProjects/) |
|
|
35
|
+
| **Docs** | [](https://WolfgangFahl.github.io/pyOpenSourceProjects/) |
|
|
36
|
+
|
|
37
|
+
## Documentation
|
|
38
|
+
[Wiki](https://wiki.bitplan.com/index.php/PyOpenSourceProjects)
|
|
39
|
+
|
|
40
|
+
### Authors
|
|
41
|
+
* [Tim Holzheim](https://www.semantic-mediawiki.org/wiki/Tim_Holzheim)
|
|
42
|
+
* [Wolfgang Fahl](http://www.bitplan.com/Wolfgang_Fahl)
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# pyOpenSourceProjects
|
|
2
|
+
Helper Library to organize open source Projects
|
|
3
|
+
|
|
4
|
+
| | |
|
|
5
|
+
| :--- | :--- |
|
|
6
|
+
| **GitHub** | [](https://github.com/WolfgangFahl/pyOpenSourceProjects/actions/workflows/build.yml) [](https://github.com/WolfgangFahl/pyOpenSourceProjects/issues) [](https://github.com/WolfgangFahl/pyOpenSourceProjects/issues/?q=is%3Aissue+is%3Aclosed) |
|
|
7
|
+
| **PyPi** | [](https://pypi.python.org/pypi/pyOpenSourceProjects/) [](https://www.apache.org/licenses/LICENSE-2.0) [](https://pypi.org/project/pyOpenSourceProjects/) |
|
|
8
|
+
| **Docs** | [](https://WolfgangFahl.github.io/pyOpenSourceProjects/) |
|
|
9
|
+
|
|
10
|
+
## Documentation
|
|
11
|
+
[Wiki](https://wiki.bitplan.com/index.php/PyOpenSourceProjects)
|
|
12
|
+
|
|
13
|
+
### Authors
|
|
14
|
+
* [Tim Holzheim](https://www.semantic-mediawiki.org/wiki/Tim_Holzheim)
|
|
15
|
+
* [Wolfgang Fahl](http://www.bitplan.com/Wolfgang_Fahl)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.3.0"
|
|
@@ -1,20 +1,23 @@
|
|
|
1
|
-
#!/usr/bin/env python
|
|
2
1
|
"""
|
|
3
|
-
Created on 2024-
|
|
2
|
+
Created on 2024-08-28
|
|
4
3
|
|
|
5
4
|
@author: wf
|
|
6
5
|
"""
|
|
7
|
-
|
|
6
|
+
|
|
8
7
|
import os
|
|
9
|
-
|
|
8
|
+
import tomllib
|
|
10
9
|
from dataclasses import dataclass
|
|
11
10
|
from typing import List
|
|
11
|
+
|
|
12
|
+
from git import Repo
|
|
13
|
+
from git.exc import InvalidGitRepositoryError, NoSuchPathError
|
|
14
|
+
from packaging import version
|
|
15
|
+
|
|
16
|
+
from osprojects.github_api import GitHubAction
|
|
17
|
+
|
|
12
18
|
# original at ngwidgets - use redundant local copy ...
|
|
13
19
|
from osprojects.editor import Editor
|
|
14
|
-
|
|
15
|
-
import tomllib
|
|
16
|
-
import traceback
|
|
17
|
-
from packaging import version
|
|
20
|
+
|
|
18
21
|
|
|
19
22
|
@dataclass
|
|
20
23
|
class Check:
|
|
@@ -37,20 +40,22 @@ class Check:
|
|
|
37
40
|
check = Check(ok, path, msg=path, content=content)
|
|
38
41
|
return check
|
|
39
42
|
|
|
40
|
-
|
|
43
|
+
|
|
44
|
+
class CheckProject:
|
|
41
45
|
"""
|
|
42
|
-
|
|
46
|
+
Checker for an individual open source project
|
|
43
47
|
"""
|
|
44
48
|
|
|
45
|
-
def __init__(self,
|
|
46
|
-
self.
|
|
47
|
-
self.verbose = args.verbose
|
|
48
|
-
self.workspace = args.workspace
|
|
49
|
+
def __init__(self, parent, project, args):
|
|
50
|
+
self.parent = parent
|
|
49
51
|
self.project = project
|
|
50
|
-
self.
|
|
51
|
-
self.checks = []
|
|
52
|
-
|
|
53
|
-
self.
|
|
52
|
+
self.args = args
|
|
53
|
+
self.checks: List[Check] = []
|
|
54
|
+
self.project_path = project.folder
|
|
55
|
+
self.project_name = None
|
|
56
|
+
self.requires_python = None
|
|
57
|
+
self.min_python_version_minor = None
|
|
58
|
+
self.max_python_version_minor = 13 # python 3.13 is max version
|
|
54
59
|
|
|
55
60
|
@property
|
|
56
61
|
def total(self) -> int:
|
|
@@ -66,20 +71,28 @@ class CheckOS:
|
|
|
66
71
|
failed_checks = [check for check in self.checks if not check.ok]
|
|
67
72
|
return failed_checks
|
|
68
73
|
|
|
69
|
-
def
|
|
74
|
+
def add_error(self, ex, path: str):
|
|
75
|
+
self.parent.handle_exception(ex)
|
|
76
|
+
self.add_check(False, msg=f"{str(ex)}", path=path)
|
|
77
|
+
|
|
78
|
+
def add_check(
|
|
79
|
+
self, ok, msg: str = "", path: str = None, negative: bool = False
|
|
80
|
+
) -> Check:
|
|
70
81
|
if not path:
|
|
71
82
|
raise ValueError("path parameter missing")
|
|
72
|
-
marker=""
|
|
83
|
+
marker = ""
|
|
73
84
|
if negative:
|
|
74
|
-
ok=not ok
|
|
75
|
-
marker="⚠ ️"
|
|
85
|
+
ok = not ok
|
|
86
|
+
marker = "⚠ ️"
|
|
76
87
|
check = Check(ok=ok, path=path, msg=f"{marker}{msg}{path}")
|
|
77
88
|
self.checks.append(check)
|
|
78
89
|
return check
|
|
79
90
|
|
|
80
|
-
def add_content_check(
|
|
81
|
-
|
|
82
|
-
|
|
91
|
+
def add_content_check(
|
|
92
|
+
self, content: str, needle: str, path: str, negative: bool = False
|
|
93
|
+
) -> Check:
|
|
94
|
+
ok = needle in content
|
|
95
|
+
check = self.add_check(ok, msg=f"{needle} in ", path=path, negative=negative)
|
|
83
96
|
return check
|
|
84
97
|
|
|
85
98
|
def add_path_check(self, path) -> Check:
|
|
@@ -93,6 +106,9 @@ class CheckOS:
|
|
|
93
106
|
return local
|
|
94
107
|
|
|
95
108
|
def check_github_workflows(self):
|
|
109
|
+
"""
|
|
110
|
+
check the github workflow files
|
|
111
|
+
"""
|
|
96
112
|
workflows_path = os.path.join(self.project_path, ".github", "workflows")
|
|
97
113
|
workflows_exist = self.add_path_check(workflows_path)
|
|
98
114
|
|
|
@@ -106,8 +122,14 @@ class CheckOS:
|
|
|
106
122
|
content = file_exists.content
|
|
107
123
|
|
|
108
124
|
if file == "build.yml":
|
|
109
|
-
min_python_version_minor = int(
|
|
110
|
-
|
|
125
|
+
min_python_version_minor = int(
|
|
126
|
+
self.requires_python.split(".")[-1]
|
|
127
|
+
)
|
|
128
|
+
self.add_check(
|
|
129
|
+
min_python_version_minor == self.min_python_version_minor,
|
|
130
|
+
msg=f"{min_python_version_minor} (build.yml)!={self.min_python_version_minor} (pyprojec.toml)",
|
|
131
|
+
path=file_path,
|
|
132
|
+
)
|
|
111
133
|
python_versions = f"""python-version: [ {', '.join([f"'3.{i}'" for i in range(self.min_python_version_minor, self.max_python_version_minor+1)])} ]"""
|
|
112
134
|
self.add_content_check(
|
|
113
135
|
content,
|
|
@@ -119,7 +141,9 @@ class CheckOS:
|
|
|
119
141
|
"os: [ubuntu-latest, macos-latest, windows-latest]",
|
|
120
142
|
file_path,
|
|
121
143
|
)
|
|
122
|
-
self.add_content_check(
|
|
144
|
+
self.add_content_check(
|
|
145
|
+
content, "uses: actions/checkout@v4", file_path
|
|
146
|
+
)
|
|
123
147
|
self.add_content_check(
|
|
124
148
|
content,
|
|
125
149
|
"uses: actions/setup-python@v5",
|
|
@@ -127,17 +151,20 @@ class CheckOS:
|
|
|
127
151
|
)
|
|
128
152
|
|
|
129
153
|
self.add_content_check(
|
|
130
|
-
content,
|
|
131
|
-
"sphinx",
|
|
132
|
-
file_path,
|
|
133
|
-
negative=True
|
|
154
|
+
content, "sphinx", file_path, negative=True
|
|
134
155
|
)
|
|
135
|
-
scripts_ok=
|
|
136
|
-
|
|
156
|
+
scripts_ok = (
|
|
157
|
+
"scripts/install" in content
|
|
158
|
+
and "scripts/test" in content
|
|
159
|
+
or "scripts/installAndTest" in content
|
|
160
|
+
)
|
|
161
|
+
self.add_check(scripts_ok, "install and test", file_path)
|
|
137
162
|
|
|
138
163
|
elif file == "upload-to-pypi.yml":
|
|
139
164
|
self.add_content_check(content, "id-token: write", file_path)
|
|
140
|
-
self.add_content_check(
|
|
165
|
+
self.add_content_check(
|
|
166
|
+
content, "uses: actions/checkout@v4", file_path
|
|
167
|
+
)
|
|
141
168
|
self.add_content_check(
|
|
142
169
|
content,
|
|
143
170
|
"uses: actions/setup-python@v5",
|
|
@@ -159,17 +186,28 @@ class CheckOS:
|
|
|
159
186
|
file_exists = self.add_path_check(file_path)
|
|
160
187
|
if file_exists.ok:
|
|
161
188
|
content = file_exists.content
|
|
162
|
-
if file=="doc":
|
|
163
|
-
self.add_content_check(
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
self.add_content_check(
|
|
167
|
-
|
|
189
|
+
if file == "doc":
|
|
190
|
+
self.add_content_check(
|
|
191
|
+
content, "sphinx", file_path, negative=True
|
|
192
|
+
)
|
|
193
|
+
self.add_content_check(
|
|
194
|
+
content, "WF 2024-07-30 - updated", file_path
|
|
195
|
+
)
|
|
196
|
+
if file == "test":
|
|
197
|
+
self.add_content_check(content, "WF 2024-08-03", file_path)
|
|
198
|
+
if file == "release":
|
|
168
199
|
self.add_content_check(content, "scripts/doc -d", file_path)
|
|
169
200
|
|
|
170
201
|
def check_readme(self):
|
|
171
202
|
readme_path = os.path.join(self.project_path, "README.md")
|
|
172
203
|
readme_exists = self.add_path_check(readme_path)
|
|
204
|
+
if not hasattr(self, "project_name"):
|
|
205
|
+
self.add_check(
|
|
206
|
+
False,
|
|
207
|
+
"project_name from pyproject.toml needed for README.md check",
|
|
208
|
+
self.project_path,
|
|
209
|
+
)
|
|
210
|
+
return
|
|
173
211
|
if readme_exists.ok:
|
|
174
212
|
readme_content = readme_exists.content
|
|
175
213
|
badge_lines = [
|
|
@@ -178,7 +216,7 @@ class CheckOS:
|
|
|
178
216
|
"[](https://pypi.python.org/pypi/{self.project_name}/)",
|
|
179
217
|
"[](https://github.com/{self.project.fqid}/issues)",
|
|
180
218
|
"[](https://github.com/{self.project.fqid}/issues/?q=is%3Aissue+is%3Aclosed)",
|
|
181
|
-
"[](https://{self.project.owner}.github.io/{self.project.
|
|
219
|
+
"[](https://{self.project.owner}.github.io/{self.project.project_id}/)",
|
|
182
220
|
"[](https://www.apache.org/licenses/LICENSE-2.0)",
|
|
183
221
|
]
|
|
184
222
|
for line in badge_lines:
|
|
@@ -188,49 +226,132 @@ class CheckOS:
|
|
|
188
226
|
needle=formatted_line,
|
|
189
227
|
path=readme_path,
|
|
190
228
|
)
|
|
191
|
-
self.add_content_check(
|
|
229
|
+
self.add_content_check(
|
|
230
|
+
readme_content, "readthedocs", readme_path, negative=True
|
|
231
|
+
)
|
|
192
232
|
|
|
193
|
-
def check_pyproject_toml(self):
|
|
233
|
+
def check_pyproject_toml(self) -> bool:
|
|
194
234
|
"""
|
|
195
235
|
pyproject.toml
|
|
196
236
|
"""
|
|
197
237
|
toml_path = os.path.join(self.project_path, "pyproject.toml")
|
|
198
238
|
toml_exists = self.add_path_check(toml_path)
|
|
199
239
|
if toml_exists.ok:
|
|
200
|
-
content=toml_exists.content
|
|
240
|
+
content = toml_exists.content
|
|
201
241
|
toml_dict = tomllib.loads(content)
|
|
202
|
-
project_check=self.add_check(
|
|
242
|
+
project_check = self.add_check(
|
|
243
|
+
"project" in toml_dict, "[project]", toml_path
|
|
244
|
+
)
|
|
203
245
|
if project_check.ok:
|
|
204
|
-
self.project_name=toml_dict["project"]["name"]
|
|
205
|
-
requires_python_check=self.add_check(
|
|
246
|
+
self.project_name = toml_dict["project"]["name"]
|
|
247
|
+
requires_python_check = self.add_check(
|
|
248
|
+
"requires-python" in toml_dict["project"],
|
|
249
|
+
"requires-python",
|
|
250
|
+
toml_path,
|
|
251
|
+
)
|
|
206
252
|
if requires_python_check.ok:
|
|
207
253
|
self.requires_python = toml_dict["project"]["requires-python"]
|
|
208
|
-
min_python_version = version.parse(
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
254
|
+
min_python_version = version.parse(
|
|
255
|
+
self.requires_python.replace(">=", "")
|
|
256
|
+
)
|
|
257
|
+
min_version_needed = "3.9"
|
|
258
|
+
version_ok = min_python_version >= version.parse(min_version_needed)
|
|
259
|
+
self.add_check(
|
|
260
|
+
version_ok, f"requires-python>={min_version_needed}", toml_path
|
|
261
|
+
)
|
|
262
|
+
self.min_python_version_minor = int(
|
|
263
|
+
str(min_python_version).split(".")[-1]
|
|
264
|
+
)
|
|
265
|
+
for minor_version in range(
|
|
266
|
+
self.min_python_version_minor, self.max_python_version_minor + 1
|
|
267
|
+
):
|
|
268
|
+
needle = f"Programming Language :: Python :: 3.{minor_version}"
|
|
215
269
|
self.add_content_check(content, needle, toml_path)
|
|
216
270
|
self.add_content_check(content, "hatchling", toml_path)
|
|
217
|
-
self.add_content_check(
|
|
271
|
+
self.add_content_check(
|
|
272
|
+
content, "[tool.hatch.build.targets.wheel.sources]", toml_path
|
|
273
|
+
)
|
|
274
|
+
return toml_exists.ok
|
|
275
|
+
|
|
276
|
+
def check_git(self) -> bool:
|
|
277
|
+
"""
|
|
278
|
+
Check git repository information using GitHub class
|
|
279
|
+
|
|
280
|
+
Returns:
|
|
281
|
+
bool: True if git owner matches project owner and the repo is not a fork
|
|
282
|
+
"""
|
|
283
|
+
owner_match = False
|
|
284
|
+
is_fork = False
|
|
285
|
+
try:
|
|
286
|
+
local_owner = self.project.owner
|
|
287
|
+
remote_owner = self.project.repo_info["owner"]["login"]
|
|
288
|
+
is_fork = self.project.repo_info["fork"]
|
|
289
|
+
owner_match = local_owner.lower() == remote_owner.lower() and not is_fork
|
|
290
|
+
self.add_check(
|
|
291
|
+
owner_match,
|
|
292
|
+
f"Git owner ({remote_owner}) matches project owner ({local_owner}) and is not a fork",
|
|
293
|
+
self.project_path,
|
|
294
|
+
)
|
|
295
|
+
|
|
296
|
+
local_project_id = self.project.project_id
|
|
297
|
+
remote_repo_name = self.project.repo_info["name"]
|
|
298
|
+
repo_match = local_project_id.lower() == remote_repo_name.lower()
|
|
299
|
+
self.add_check(
|
|
300
|
+
repo_match,
|
|
301
|
+
f"Git repo name ({remote_repo_name}) matches project id ({local_project_id})",
|
|
302
|
+
self.project_path,
|
|
303
|
+
)
|
|
218
304
|
|
|
305
|
+
# Check if there are uncommitted changes (this still requires local git access)
|
|
306
|
+
local_repo = Repo(self.project_path)
|
|
307
|
+
self.add_check(
|
|
308
|
+
not local_repo.is_dirty(), "uncomitted changes for", self.project_path
|
|
309
|
+
)
|
|
310
|
+
|
|
311
|
+
# Check latest GitHub Actions workflow run
|
|
312
|
+
latest_run = GitHubAction.get_latest_workflow_run(self.project)
|
|
313
|
+
if latest_run:
|
|
314
|
+
self.add_check(
|
|
315
|
+
latest_run["conclusion"] == "success",
|
|
316
|
+
f"Latest GitHub Actions run: {latest_run['conclusion']}",
|
|
317
|
+
latest_run["html_url"],
|
|
318
|
+
)
|
|
319
|
+
else:
|
|
320
|
+
self.add_check(
|
|
321
|
+
False,
|
|
322
|
+
"No GitHub Actions runs found",
|
|
323
|
+
self.project.repo.ticketUrl(),
|
|
324
|
+
)
|
|
325
|
+
|
|
326
|
+
except InvalidGitRepositoryError:
|
|
327
|
+
self.add_check(False, "Not a valid git repository", self.project_path)
|
|
328
|
+
except NoSuchPathError:
|
|
329
|
+
self.add_check(
|
|
330
|
+
False, "Git repository path does not exist", self.project_path
|
|
331
|
+
)
|
|
332
|
+
except Exception as ex:
|
|
333
|
+
self.add_error(ex, self.project_path)
|
|
334
|
+
|
|
335
|
+
return owner_match and not is_fork
|
|
219
336
|
|
|
220
|
-
def check(self,title:str):
|
|
337
|
+
def check(self, title: str):
|
|
221
338
|
"""
|
|
222
339
|
Check the given project and print results
|
|
223
340
|
"""
|
|
224
341
|
self.check_local()
|
|
225
|
-
self.
|
|
226
|
-
self.
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
342
|
+
self.check_git()
|
|
343
|
+
if self.check_pyproject_toml():
|
|
344
|
+
self.check_github_workflows()
|
|
345
|
+
self.check_readme()
|
|
346
|
+
self.check_scripts()
|
|
230
347
|
|
|
231
348
|
# ok_count=len(ok_checks)
|
|
232
349
|
failed_count = len(self.failed_checks)
|
|
233
|
-
summary =
|
|
350
|
+
summary = (
|
|
351
|
+
f"❌ {failed_count:2}/{self.total:2}"
|
|
352
|
+
if failed_count > 0
|
|
353
|
+
else f"✅ {self.total:2}/{self.total:2}"
|
|
354
|
+
)
|
|
234
355
|
print(f"{title}{summary}:{self.project}→{self.project.url}")
|
|
235
356
|
if failed_count > 0:
|
|
236
357
|
# Sort checks by path
|
|
@@ -248,87 +369,16 @@ class CheckOS:
|
|
|
248
369
|
path_failed = sum(1 for c in path_checks if not c.ok)
|
|
249
370
|
if path_failed > 0 or self.args.debug:
|
|
250
371
|
print(f"❌ {path}: {path_failed}")
|
|
251
|
-
i=0
|
|
372
|
+
i = 0
|
|
252
373
|
for check in path_checks:
|
|
253
|
-
show=not check.ok or self.args.debug
|
|
374
|
+
show = not check.ok or self.args.debug
|
|
254
375
|
if show:
|
|
255
|
-
i+=1
|
|
376
|
+
i += 1
|
|
256
377
|
print(f" {i:3}{check.marker}:{check.msg}")
|
|
257
378
|
|
|
258
379
|
if self.args.editor and path_failed > 0:
|
|
259
380
|
if os.path.isfile(path):
|
|
260
381
|
# @TODO Make editor configurable
|
|
261
|
-
Editor.open(path,default_editor_cmd="/usr/local/bin/atom")
|
|
382
|
+
Editor.open(path, default_editor_cmd="/usr/local/bin/atom")
|
|
262
383
|
else:
|
|
263
384
|
Editor.open_filepath(path)
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
def main(_argv=None):
|
|
267
|
-
"""
|
|
268
|
-
main command line entry point
|
|
269
|
-
"""
|
|
270
|
-
parser = argparse.ArgumentParser(description="Check open source projects")
|
|
271
|
-
parser.add_argument(
|
|
272
|
-
"-d",
|
|
273
|
-
"--debug",
|
|
274
|
-
action="store_true",
|
|
275
|
-
help="add debug output",
|
|
276
|
-
)
|
|
277
|
-
parser.add_argument(
|
|
278
|
-
"-e",
|
|
279
|
-
"--editor",
|
|
280
|
-
action="store_true",
|
|
281
|
-
help="open default editor on failed files",
|
|
282
|
-
)
|
|
283
|
-
parser.add_argument(
|
|
284
|
-
"-o", "--owner", help="project owner or organization", required=True
|
|
285
|
-
)
|
|
286
|
-
parser.add_argument("-p", "--project", help="name of the project")
|
|
287
|
-
parser.add_argument("-l", "--language", help="filter projects by language")
|
|
288
|
-
parser.add_argument(
|
|
289
|
-
"--local", action="store_true", help="check only locally available projects"
|
|
290
|
-
)
|
|
291
|
-
parser.add_argument(
|
|
292
|
-
"-v", "--verbose", action="store_true", help="show verbose output"
|
|
293
|
-
)
|
|
294
|
-
parser.add_argument(
|
|
295
|
-
"-ws",
|
|
296
|
-
"--workspace",
|
|
297
|
-
help="(Eclipse) workspace directory",
|
|
298
|
-
default=os.path.expanduser("~/py-workspace"),
|
|
299
|
-
)
|
|
300
|
-
|
|
301
|
-
args = parser.parse_args(args=_argv)
|
|
302
|
-
|
|
303
|
-
try:
|
|
304
|
-
github = GitHub()
|
|
305
|
-
if args.project:
|
|
306
|
-
# Check specific project
|
|
307
|
-
projects = github.list_projects_as_os_projects(
|
|
308
|
-
args.owner, project_name=args.project
|
|
309
|
-
)
|
|
310
|
-
else:
|
|
311
|
-
# Check all projects
|
|
312
|
-
projects = github.list_projects_as_os_projects(args.owner)
|
|
313
|
-
|
|
314
|
-
if args.language:
|
|
315
|
-
projects = [p for p in projects if p.language == args.language]
|
|
316
|
-
|
|
317
|
-
if args.local:
|
|
318
|
-
local_projects = []
|
|
319
|
-
for project in projects:
|
|
320
|
-
checker = CheckOS(args=args, project=project)
|
|
321
|
-
if checker.check_local().ok:
|
|
322
|
-
local_projects.append(project)
|
|
323
|
-
projects = local_projects
|
|
324
|
-
|
|
325
|
-
for i,project in enumerate(projects):
|
|
326
|
-
checker = CheckOS(args=args, project=project)
|
|
327
|
-
checker.check(f"{i+1:3}:")
|
|
328
|
-
except Exception as ex:
|
|
329
|
-
if args.debug:
|
|
330
|
-
print(traceback.format_exc())
|
|
331
|
-
raise ex
|
|
332
|
-
|
|
333
|
-
if __name__ == "__main__":
|
|
334
|
-
main()
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
"""
|
|
3
|
+
Created on 2024-07-30
|
|
4
|
+
|
|
5
|
+
@author: wf
|
|
6
|
+
"""
|
|
7
|
+
import argparse
|
|
8
|
+
import logging
|
|
9
|
+
import os
|
|
10
|
+
import traceback
|
|
11
|
+
from argparse import Namespace
|
|
12
|
+
|
|
13
|
+
from osprojects.check_project import CheckProject
|
|
14
|
+
from osprojects.osproject import OsProjects
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class CheckOS:
|
|
18
|
+
"""
|
|
19
|
+
checker for a set of open source projects
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
def __init__(
|
|
23
|
+
self, args: Namespace, osprojects: OsProjects, max_python_version_minor=12
|
|
24
|
+
):
|
|
25
|
+
self.args = args
|
|
26
|
+
self.verbose = args.verbose
|
|
27
|
+
self.workspace = args.workspace
|
|
28
|
+
self.osprojects = osprojects
|
|
29
|
+
self.checks = []
|
|
30
|
+
# python 3.12 is max version
|
|
31
|
+
self.max_python_version_minor = max_python_version_minor
|
|
32
|
+
|
|
33
|
+
@classmethod
|
|
34
|
+
def from_args(cls, args: Namespace):
|
|
35
|
+
osprojects = OsProjects.from_folder(args.workspace, with_progress=True)
|
|
36
|
+
return cls(args, osprojects)
|
|
37
|
+
|
|
38
|
+
def select_projects(self):
|
|
39
|
+
try:
|
|
40
|
+
if self.args.project:
|
|
41
|
+
if self.args.owners:
|
|
42
|
+
return self.osprojects.select_projects(
|
|
43
|
+
owners=self.args.owners, project_id=self.args.project
|
|
44
|
+
)
|
|
45
|
+
elif self.args.local:
|
|
46
|
+
return self.osprojects.select_projects(
|
|
47
|
+
project_id=self.args.project, local_only=True
|
|
48
|
+
)
|
|
49
|
+
else:
|
|
50
|
+
raise ValueError("--local or --owner needed with --project")
|
|
51
|
+
elif self.args.owners:
|
|
52
|
+
return self.osprojects.select_projects(owners=self.args.owners)
|
|
53
|
+
elif self.args.local:
|
|
54
|
+
return self.osprojects.select_projects(local_only=True)
|
|
55
|
+
else:
|
|
56
|
+
raise ValueError(
|
|
57
|
+
"Please provide --owner and --project, or use --local option."
|
|
58
|
+
)
|
|
59
|
+
except ValueError as e:
|
|
60
|
+
print(f"Error: {str(e)}")
|
|
61
|
+
return []
|
|
62
|
+
|
|
63
|
+
def filter_projects(self):
|
|
64
|
+
if self.args.language:
|
|
65
|
+
self.osprojects.filter_projects(language=self.args.language)
|
|
66
|
+
if self.args.local:
|
|
67
|
+
self.osprojects.filter_projects(local_only=True)
|
|
68
|
+
|
|
69
|
+
def check_projects(self):
|
|
70
|
+
"""
|
|
71
|
+
Select, filter, and check all projects based on the provided arguments.
|
|
72
|
+
"""
|
|
73
|
+
self.select_projects()
|
|
74
|
+
self.filter_projects()
|
|
75
|
+
|
|
76
|
+
for i, (_url, project) in enumerate(
|
|
77
|
+
self.osprojects.selected_projects.items(), 1
|
|
78
|
+
):
|
|
79
|
+
checker = CheckProject(self, project, self.args)
|
|
80
|
+
checker.check(f"{i:3}:")
|
|
81
|
+
|
|
82
|
+
def handle_exception(self, ex: Exception):
|
|
83
|
+
CheckOS.show_exception(ex, self.args.debug)
|
|
84
|
+
|
|
85
|
+
@staticmethod
|
|
86
|
+
def show_exception(ex: Exception, debug: bool = False):
|
|
87
|
+
err_msg = f"Error: {str(ex)}"
|
|
88
|
+
logging.error(err_msg)
|
|
89
|
+
if debug:
|
|
90
|
+
print(traceback.format_exc())
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def main(_argv=None):
|
|
94
|
+
"""
|
|
95
|
+
main command line entry point
|
|
96
|
+
"""
|
|
97
|
+
parser = argparse.ArgumentParser(description="Check open source projects")
|
|
98
|
+
parser.add_argument(
|
|
99
|
+
"-d",
|
|
100
|
+
"--debug",
|
|
101
|
+
action="store_true",
|
|
102
|
+
help="add debug output",
|
|
103
|
+
)
|
|
104
|
+
parser.add_argument(
|
|
105
|
+
"-e",
|
|
106
|
+
"--editor",
|
|
107
|
+
action="store_true",
|
|
108
|
+
help="open default editor on failed files",
|
|
109
|
+
)
|
|
110
|
+
parser.add_argument("-o", "--owners", nargs="+", help="project owners")
|
|
111
|
+
parser.add_argument("-p", "--project", help="name of the project")
|
|
112
|
+
parser.add_argument("-l", "--language", help="filter projects by language")
|
|
113
|
+
parser.add_argument(
|
|
114
|
+
"--local", action="store_true", help="check only locally available projects"
|
|
115
|
+
)
|
|
116
|
+
parser.add_argument(
|
|
117
|
+
"-v", "--verbose", action="store_true", help="show verbose output"
|
|
118
|
+
)
|
|
119
|
+
parser.add_argument(
|
|
120
|
+
"-ws",
|
|
121
|
+
"--workspace",
|
|
122
|
+
help="(Eclipse) workspace directory",
|
|
123
|
+
default=os.path.expanduser("~/py-workspace"),
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
args = parser.parse_args(args=_argv)
|
|
127
|
+
|
|
128
|
+
try:
|
|
129
|
+
checker = CheckOS.from_args(args)
|
|
130
|
+
checker.check_projects()
|
|
131
|
+
except Exception as ex:
|
|
132
|
+
CheckOS.show_exception(ex, debug=args.debug)
|
|
133
|
+
raise ex
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
if __name__ == "__main__":
|
|
137
|
+
main()
|