pyOpenSourceProjects 0.2.3__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.3/osprojects/checkos.py → pyopensourceprojects-0.3.0/osprojects/check_project.py +165 -178
- 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.3 → pyopensourceprojects-0.3.0}/pyproject.toml +11 -6
- {pyopensourceprojects-0.2.3 → 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.3/PKG-INFO +0 -43
- pyopensourceprojects-0.2.3/README.md +0 -18
- pyopensourceprojects-0.2.3/osprojects/__init__.py +0 -1
- pyopensourceprojects-0.2.3/osprojects/osproject.py +0 -564
- pyopensourceprojects-0.2.3/tests/test_osproject.py +0 -193
- {pyopensourceprojects-0.2.3 → pyopensourceprojects-0.3.0}/.github/workflows/build.yml +0 -0
- {pyopensourceprojects-0.2.3 → pyopensourceprojects-0.3.0}/.github/workflows/upload-to-pypi.yml +0 -0
- {pyopensourceprojects-0.2.3 → pyopensourceprojects-0.3.0}/.gitignore +0 -0
- {pyopensourceprojects-0.2.3 → pyopensourceprojects-0.3.0}/.project +0 -0
- {pyopensourceprojects-0.2.3 → pyopensourceprojects-0.3.0}/.pydevproject +0 -0
- {pyopensourceprojects-0.2.3 → pyopensourceprojects-0.3.0}/LICENSE +0 -0
- {pyopensourceprojects-0.2.3 → pyopensourceprojects-0.3.0}/mkdocs.yml +0 -0
- {pyopensourceprojects-0.2.3 → pyopensourceprojects-0.3.0}/osprojects/editor.py +0 -0
- {pyopensourceprojects-0.2.3 → pyopensourceprojects-0.3.0}/scripts/blackisort +0 -0
- {pyopensourceprojects-0.2.3 → pyopensourceprojects-0.3.0}/scripts/doc +0 -0
- {pyopensourceprojects-0.2.3 → pyopensourceprojects-0.3.0}/scripts/install +0 -0
- {pyopensourceprojects-0.2.3 → pyopensourceprojects-0.3.0}/scripts/installAndTest +0 -0
- {pyopensourceprojects-0.2.3 → pyopensourceprojects-0.3.0}/scripts/release +0 -0
- {pyopensourceprojects-0.2.3 → pyopensourceprojects-0.3.0}/scripts/test +0 -0
- {pyopensourceprojects-0.2.3 → 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,22 +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
|
|
10
|
+
from typing import List
|
|
11
|
+
|
|
11
12
|
from git import Repo
|
|
12
13
|
from git.exc import InvalidGitRepositoryError, NoSuchPathError
|
|
13
|
-
from
|
|
14
|
+
from packaging import version
|
|
15
|
+
|
|
16
|
+
from osprojects.github_api import GitHubAction
|
|
17
|
+
|
|
14
18
|
# original at ngwidgets - use redundant local copy ...
|
|
15
19
|
from osprojects.editor import Editor
|
|
16
|
-
|
|
17
|
-
import tomllib
|
|
18
|
-
import traceback
|
|
19
|
-
from packaging import version
|
|
20
|
+
|
|
20
21
|
|
|
21
22
|
@dataclass
|
|
22
23
|
class Check:
|
|
@@ -39,20 +40,22 @@ class Check:
|
|
|
39
40
|
check = Check(ok, path, msg=path, content=content)
|
|
40
41
|
return check
|
|
41
42
|
|
|
42
|
-
|
|
43
|
+
|
|
44
|
+
class CheckProject:
|
|
43
45
|
"""
|
|
44
|
-
|
|
46
|
+
Checker for an individual open source project
|
|
45
47
|
"""
|
|
46
48
|
|
|
47
|
-
def __init__(self,
|
|
48
|
-
self.
|
|
49
|
-
self.verbose = args.verbose
|
|
50
|
-
self.workspace = args.workspace
|
|
49
|
+
def __init__(self, parent, project, args):
|
|
50
|
+
self.parent = parent
|
|
51
51
|
self.project = project
|
|
52
|
-
self.
|
|
53
|
-
self.checks = []
|
|
54
|
-
|
|
55
|
-
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
|
|
56
59
|
|
|
57
60
|
@property
|
|
58
61
|
def total(self) -> int:
|
|
@@ -68,20 +71,28 @@ class CheckOS:
|
|
|
68
71
|
failed_checks = [check for check in self.checks if not check.ok]
|
|
69
72
|
return failed_checks
|
|
70
73
|
|
|
71
|
-
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:
|
|
72
81
|
if not path:
|
|
73
82
|
raise ValueError("path parameter missing")
|
|
74
|
-
marker=""
|
|
83
|
+
marker = ""
|
|
75
84
|
if negative:
|
|
76
|
-
ok=not ok
|
|
77
|
-
marker="⚠ ️"
|
|
85
|
+
ok = not ok
|
|
86
|
+
marker = "⚠ ️"
|
|
78
87
|
check = Check(ok=ok, path=path, msg=f"{marker}{msg}{path}")
|
|
79
88
|
self.checks.append(check)
|
|
80
89
|
return check
|
|
81
90
|
|
|
82
|
-
def add_content_check(
|
|
83
|
-
|
|
84
|
-
|
|
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)
|
|
85
96
|
return check
|
|
86
97
|
|
|
87
98
|
def add_path_check(self, path) -> Check:
|
|
@@ -111,8 +122,14 @@ class CheckOS:
|
|
|
111
122
|
content = file_exists.content
|
|
112
123
|
|
|
113
124
|
if file == "build.yml":
|
|
114
|
-
min_python_version_minor = int(
|
|
115
|
-
|
|
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
|
+
)
|
|
116
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)])} ]"""
|
|
117
134
|
self.add_content_check(
|
|
118
135
|
content,
|
|
@@ -124,7 +141,9 @@ class CheckOS:
|
|
|
124
141
|
"os: [ubuntu-latest, macos-latest, windows-latest]",
|
|
125
142
|
file_path,
|
|
126
143
|
)
|
|
127
|
-
self.add_content_check(
|
|
144
|
+
self.add_content_check(
|
|
145
|
+
content, "uses: actions/checkout@v4", file_path
|
|
146
|
+
)
|
|
128
147
|
self.add_content_check(
|
|
129
148
|
content,
|
|
130
149
|
"uses: actions/setup-python@v5",
|
|
@@ -132,17 +151,20 @@ class CheckOS:
|
|
|
132
151
|
)
|
|
133
152
|
|
|
134
153
|
self.add_content_check(
|
|
135
|
-
content,
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
154
|
+
content, "sphinx", file_path, negative=True
|
|
155
|
+
)
|
|
156
|
+
scripts_ok = (
|
|
157
|
+
"scripts/install" in content
|
|
158
|
+
and "scripts/test" in content
|
|
159
|
+
or "scripts/installAndTest" in content
|
|
139
160
|
)
|
|
140
|
-
scripts_ok
|
|
141
|
-
self.add_check(scripts_ok,"install and test", file_path)
|
|
161
|
+
self.add_check(scripts_ok, "install and test", file_path)
|
|
142
162
|
|
|
143
163
|
elif file == "upload-to-pypi.yml":
|
|
144
164
|
self.add_content_check(content, "id-token: write", file_path)
|
|
145
|
-
self.add_content_check(
|
|
165
|
+
self.add_content_check(
|
|
166
|
+
content, "uses: actions/checkout@v4", file_path
|
|
167
|
+
)
|
|
146
168
|
self.add_content_check(
|
|
147
169
|
content,
|
|
148
170
|
"uses: actions/setup-python@v5",
|
|
@@ -164,19 +186,27 @@ class CheckOS:
|
|
|
164
186
|
file_exists = self.add_path_check(file_path)
|
|
165
187
|
if file_exists.ok:
|
|
166
188
|
content = file_exists.content
|
|
167
|
-
if file=="doc":
|
|
168
|
-
self.add_content_check(
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
self.add_content_check(
|
|
172
|
-
|
|
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":
|
|
173
199
|
self.add_content_check(content, "scripts/doc -d", file_path)
|
|
174
200
|
|
|
175
201
|
def check_readme(self):
|
|
176
202
|
readme_path = os.path.join(self.project_path, "README.md")
|
|
177
203
|
readme_exists = self.add_path_check(readme_path)
|
|
178
204
|
if not hasattr(self, "project_name"):
|
|
179
|
-
self.add_check(
|
|
205
|
+
self.add_check(
|
|
206
|
+
False,
|
|
207
|
+
"project_name from pyproject.toml needed for README.md check",
|
|
208
|
+
self.project_path,
|
|
209
|
+
)
|
|
180
210
|
return
|
|
181
211
|
if readme_exists.ok:
|
|
182
212
|
readme_content = readme_exists.content
|
|
@@ -186,7 +216,7 @@ class CheckOS:
|
|
|
186
216
|
"[](https://pypi.python.org/pypi/{self.project_name}/)",
|
|
187
217
|
"[](https://github.com/{self.project.fqid}/issues)",
|
|
188
218
|
"[](https://github.com/{self.project.fqid}/issues/?q=is%3Aissue+is%3Aclosed)",
|
|
189
|
-
"[](https://{self.project.owner}.github.io/{self.project.
|
|
219
|
+
"[](https://{self.project.owner}.github.io/{self.project.project_id}/)",
|
|
190
220
|
"[](https://www.apache.org/licenses/LICENSE-2.0)",
|
|
191
221
|
]
|
|
192
222
|
for line in badge_lines:
|
|
@@ -196,81 +226,115 @@ class CheckOS:
|
|
|
196
226
|
needle=formatted_line,
|
|
197
227
|
path=readme_path,
|
|
198
228
|
)
|
|
199
|
-
self.add_content_check(
|
|
229
|
+
self.add_content_check(
|
|
230
|
+
readme_content, "readthedocs", readme_path, negative=True
|
|
231
|
+
)
|
|
200
232
|
|
|
201
|
-
def check_pyproject_toml(self)->bool:
|
|
233
|
+
def check_pyproject_toml(self) -> bool:
|
|
202
234
|
"""
|
|
203
235
|
pyproject.toml
|
|
204
236
|
"""
|
|
205
237
|
toml_path = os.path.join(self.project_path, "pyproject.toml")
|
|
206
238
|
toml_exists = self.add_path_check(toml_path)
|
|
207
239
|
if toml_exists.ok:
|
|
208
|
-
content=toml_exists.content
|
|
240
|
+
content = toml_exists.content
|
|
209
241
|
toml_dict = tomllib.loads(content)
|
|
210
|
-
project_check=self.add_check(
|
|
242
|
+
project_check = self.add_check(
|
|
243
|
+
"project" in toml_dict, "[project]", toml_path
|
|
244
|
+
)
|
|
211
245
|
if project_check.ok:
|
|
212
|
-
self.project_name=toml_dict["project"]["name"]
|
|
213
|
-
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
|
+
)
|
|
214
252
|
if requires_python_check.ok:
|
|
215
253
|
self.requires_python = toml_dict["project"]["requires-python"]
|
|
216
|
-
min_python_version = version.parse(
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
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}"
|
|
223
269
|
self.add_content_check(content, needle, toml_path)
|
|
224
270
|
self.add_content_check(content, "hatchling", toml_path)
|
|
225
|
-
self.add_content_check(
|
|
271
|
+
self.add_content_check(
|
|
272
|
+
content, "[tool.hatch.build.targets.wheel.sources]", toml_path
|
|
273
|
+
)
|
|
226
274
|
return toml_exists.ok
|
|
227
275
|
|
|
228
|
-
def check_git(self):
|
|
276
|
+
def check_git(self) -> bool:
|
|
229
277
|
"""
|
|
230
|
-
Check git repository information using
|
|
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
|
|
231
282
|
"""
|
|
283
|
+
owner_match = False
|
|
284
|
+
is_fork = False
|
|
232
285
|
try:
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
self.add_check(True, "Has remote origin", self.project_path)
|
|
243
|
-
|
|
244
|
-
# Extract owner and repository name from the URL
|
|
245
|
-
parts = remote_url.split('/')
|
|
246
|
-
git_owner = parts[-2]
|
|
247
|
-
git_repo = parts[-1].replace('.git', '')
|
|
248
|
-
|
|
249
|
-
# Compare with the project information we have
|
|
250
|
-
owner_match = git_owner.lower() == self.project.owner.lower()
|
|
251
|
-
self.add_check(owner_match, f"Git owner ({git_owner}) matches project owner ({self.project.owner})", self.project_path)
|
|
252
|
-
|
|
253
|
-
repo_match = git_repo.lower() == self.project.id.lower()
|
|
254
|
-
self.add_check(repo_match, f"Git repo name ({git_repo}) matches project id ({self.project.id})", self.project_path)
|
|
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
|
+
)
|
|
255
295
|
|
|
256
|
-
|
|
257
|
-
|
|
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
|
+
)
|
|
258
304
|
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
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
|
+
)
|
|
264
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
|
+
)
|
|
265
319
|
else:
|
|
266
|
-
self.add_check(
|
|
320
|
+
self.add_check(
|
|
321
|
+
False,
|
|
322
|
+
"No GitHub Actions runs found",
|
|
323
|
+
self.project.repo.ticketUrl(),
|
|
324
|
+
)
|
|
267
325
|
|
|
268
326
|
except InvalidGitRepositoryError:
|
|
269
327
|
self.add_check(False, "Not a valid git repository", self.project_path)
|
|
270
328
|
except NoSuchPathError:
|
|
271
|
-
self.add_check(
|
|
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)
|
|
272
334
|
|
|
273
|
-
|
|
335
|
+
return owner_match and not is_fork
|
|
336
|
+
|
|
337
|
+
def check(self, title: str):
|
|
274
338
|
"""
|
|
275
339
|
Check the given project and print results
|
|
276
340
|
"""
|
|
@@ -283,7 +347,11 @@ class CheckOS:
|
|
|
283
347
|
|
|
284
348
|
# ok_count=len(ok_checks)
|
|
285
349
|
failed_count = len(self.failed_checks)
|
|
286
|
-
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
|
+
)
|
|
287
355
|
print(f"{title}{summary}:{self.project}→{self.project.url}")
|
|
288
356
|
if failed_count > 0:
|
|
289
357
|
# Sort checks by path
|
|
@@ -301,97 +369,16 @@ class CheckOS:
|
|
|
301
369
|
path_failed = sum(1 for c in path_checks if not c.ok)
|
|
302
370
|
if path_failed > 0 or self.args.debug:
|
|
303
371
|
print(f"❌ {path}: {path_failed}")
|
|
304
|
-
i=0
|
|
372
|
+
i = 0
|
|
305
373
|
for check in path_checks:
|
|
306
|
-
show=not check.ok or self.args.debug
|
|
374
|
+
show = not check.ok or self.args.debug
|
|
307
375
|
if show:
|
|
308
|
-
i+=1
|
|
376
|
+
i += 1
|
|
309
377
|
print(f" {i:3}{check.marker}:{check.msg}")
|
|
310
378
|
|
|
311
379
|
if self.args.editor and path_failed > 0:
|
|
312
380
|
if os.path.isfile(path):
|
|
313
381
|
# @TODO Make editor configurable
|
|
314
|
-
Editor.open(path,default_editor_cmd="/usr/local/bin/atom")
|
|
382
|
+
Editor.open(path, default_editor_cmd="/usr/local/bin/atom")
|
|
315
383
|
else:
|
|
316
384
|
Editor.open_filepath(path)
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
def main(_argv=None):
|
|
320
|
-
"""
|
|
321
|
-
main command line entry point
|
|
322
|
-
"""
|
|
323
|
-
parser = argparse.ArgumentParser(description="Check open source projects")
|
|
324
|
-
parser.add_argument(
|
|
325
|
-
"-d",
|
|
326
|
-
"--debug",
|
|
327
|
-
action="store_true",
|
|
328
|
-
help="add debug output",
|
|
329
|
-
)
|
|
330
|
-
parser.add_argument(
|
|
331
|
-
"-e",
|
|
332
|
-
"--editor",
|
|
333
|
-
action="store_true",
|
|
334
|
-
help="open default editor on failed files",
|
|
335
|
-
)
|
|
336
|
-
parser.add_argument(
|
|
337
|
-
"-o", "--owner", help="project owner or organization", required=True
|
|
338
|
-
)
|
|
339
|
-
parser.add_argument("-p", "--project", help="name of the project")
|
|
340
|
-
parser.add_argument("-l", "--language", help="filter projects by language")
|
|
341
|
-
parser.add_argument(
|
|
342
|
-
"--local", action="store_true", help="check only locally available projects"
|
|
343
|
-
)
|
|
344
|
-
parser.add_argument(
|
|
345
|
-
"-v", "--verbose", action="store_true", help="show verbose output"
|
|
346
|
-
)
|
|
347
|
-
parser.add_argument(
|
|
348
|
-
"-ws",
|
|
349
|
-
"--workspace",
|
|
350
|
-
help="(Eclipse) workspace directory",
|
|
351
|
-
default=os.path.expanduser("~/py-workspace"),
|
|
352
|
-
)
|
|
353
|
-
|
|
354
|
-
args = parser.parse_args(args=_argv)
|
|
355
|
-
|
|
356
|
-
try:
|
|
357
|
-
github = GitHub()
|
|
358
|
-
if args.project:
|
|
359
|
-
# Check specific project
|
|
360
|
-
projects = github.list_projects_as_os_projects(
|
|
361
|
-
args.owner, project_name=args.project
|
|
362
|
-
)
|
|
363
|
-
else:
|
|
364
|
-
# Check all projects
|
|
365
|
-
projects = github.list_projects_as_os_projects(args.owner)
|
|
366
|
-
|
|
367
|
-
if args.language:
|
|
368
|
-
projects = [p for p in projects if p.language == args.language]
|
|
369
|
-
|
|
370
|
-
if args.local:
|
|
371
|
-
local_projects = []
|
|
372
|
-
for project in projects:
|
|
373
|
-
checker = CheckOS(args=args, project=project)
|
|
374
|
-
if checker.check_local().ok:
|
|
375
|
-
local_projects.append(project)
|
|
376
|
-
projects = local_projects
|
|
377
|
-
|
|
378
|
-
# filter for git ownership
|
|
379
|
-
filtered_projects = []
|
|
380
|
-
for project in projects:
|
|
381
|
-
checker = CheckOS(args=args, project=project)
|
|
382
|
-
checker.check_git()
|
|
383
|
-
git_owner_check = next((check for check in checker.checks if "Git owner" in check.msg), None)
|
|
384
|
-
if git_owner_check and git_owner_check.ok:
|
|
385
|
-
filtered_projects.append(project)
|
|
386
|
-
projects = filtered_projects
|
|
387
|
-
|
|
388
|
-
for i,project in enumerate(projects):
|
|
389
|
-
checker = CheckOS(args=args, project=project)
|
|
390
|
-
checker.check(f"{i+1:3}:")
|
|
391
|
-
except Exception as ex:
|
|
392
|
-
if args.debug:
|
|
393
|
-
print(traceback.format_exc())
|
|
394
|
-
raise ex
|
|
395
|
-
|
|
396
|
-
if __name__ == "__main__":
|
|
397
|
-
main()
|