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.
Files changed (32) hide show
  1. pyopensourceprojects-0.3.0/PKG-INFO +42 -0
  2. pyopensourceprojects-0.3.0/README.md +15 -0
  3. pyopensourceprojects-0.3.0/osprojects/__init__.py +1 -0
  4. pyopensourceprojects-0.2.2/osprojects/checkos.py → pyopensourceprojects-0.3.0/osprojects/check_project.py +188 -138
  5. pyopensourceprojects-0.3.0/osprojects/checkos.py +137 -0
  6. pyopensourceprojects-0.3.0/osprojects/github_api.py +353 -0
  7. pyopensourceprojects-0.3.0/osprojects/osproject.py +580 -0
  8. {pyopensourceprojects-0.2.2 → pyopensourceprojects-0.3.0}/pyproject.toml +13 -6
  9. {pyopensourceprojects-0.2.2 → pyopensourceprojects-0.3.0}/tests/basetest.py +6 -0
  10. pyopensourceprojects-0.3.0/tests/test_github.py +120 -0
  11. pyopensourceprojects-0.3.0/tests/test_github_api.py +87 -0
  12. pyopensourceprojects-0.3.0/tests/test_osproject.py +85 -0
  13. pyopensourceprojects-0.2.2/PKG-INFO +0 -42
  14. pyopensourceprojects-0.2.2/README.md +0 -18
  15. pyopensourceprojects-0.2.2/osprojects/__init__.py +0 -1
  16. pyopensourceprojects-0.2.2/osprojects/osproject.py +0 -564
  17. pyopensourceprojects-0.2.2/tests/test_osproject.py +0 -193
  18. {pyopensourceprojects-0.2.2 → pyopensourceprojects-0.3.0}/.github/workflows/build.yml +0 -0
  19. {pyopensourceprojects-0.2.2 → pyopensourceprojects-0.3.0}/.github/workflows/upload-to-pypi.yml +0 -0
  20. {pyopensourceprojects-0.2.2 → pyopensourceprojects-0.3.0}/.gitignore +0 -0
  21. {pyopensourceprojects-0.2.2 → pyopensourceprojects-0.3.0}/.project +0 -0
  22. {pyopensourceprojects-0.2.2 → pyopensourceprojects-0.3.0}/.pydevproject +0 -0
  23. {pyopensourceprojects-0.2.2 → pyopensourceprojects-0.3.0}/LICENSE +0 -0
  24. {pyopensourceprojects-0.2.2 → pyopensourceprojects-0.3.0}/mkdocs.yml +0 -0
  25. {pyopensourceprojects-0.2.2 → pyopensourceprojects-0.3.0}/osprojects/editor.py +0 -0
  26. {pyopensourceprojects-0.2.2 → pyopensourceprojects-0.3.0}/scripts/blackisort +0 -0
  27. {pyopensourceprojects-0.2.2 → pyopensourceprojects-0.3.0}/scripts/doc +0 -0
  28. {pyopensourceprojects-0.2.2 → pyopensourceprojects-0.3.0}/scripts/install +0 -0
  29. {pyopensourceprojects-0.2.2 → pyopensourceprojects-0.3.0}/scripts/installAndTest +0 -0
  30. {pyopensourceprojects-0.2.2 → pyopensourceprojects-0.3.0}/scripts/release +0 -0
  31. {pyopensourceprojects-0.2.2 → pyopensourceprojects-0.3.0}/scripts/test +0 -0
  32. {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** | [![Github Actions Build](https://github.com/WolfgangFahl/pyOpenSourceProjects/actions/workflows/build.yml/badge.svg)](https://github.com/WolfgangFahl/pyOpenSourceProjects/actions/workflows/build.yml) [![GitHub issues](https://img.shields.io/github/issues/WolfgangFahl/pyOpenSourceProjects.svg)](https://github.com/WolfgangFahl/pyOpenSourceProjects/issues) [![GitHub closed issues](https://img.shields.io/github/issues-closed/WolfgangFahl/pyOpenSourceProjects.svg)](https://github.com/WolfgangFahl/pyOpenSourceProjects/issues/?q=is%3Aissue+is%3Aclosed) |
34
+ | **PyPi** | [![PyPI Status](https://img.shields.io/pypi/v/pyOpenSourceProjects.svg)](https://pypi.python.org/pypi/pyOpenSourceProjects/) [![License](https://img.shields.io/github/license/WolfgangFahl/pyOpenSourceProjects.svg)](https://www.apache.org/licenses/LICENSE-2.0) [![pypi](https://img.shields.io/pypi/pyversions/pyOpenSourceProjects)](https://pypi.org/project/pyOpenSourceProjects/) |
35
+ | **Docs** | [![API Docs](https://img.shields.io/badge/API-Documentation-blue)](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** | [![Github Actions Build](https://github.com/WolfgangFahl/pyOpenSourceProjects/actions/workflows/build.yml/badge.svg)](https://github.com/WolfgangFahl/pyOpenSourceProjects/actions/workflows/build.yml) [![GitHub issues](https://img.shields.io/github/issues/WolfgangFahl/pyOpenSourceProjects.svg)](https://github.com/WolfgangFahl/pyOpenSourceProjects/issues) [![GitHub closed issues](https://img.shields.io/github/issues-closed/WolfgangFahl/pyOpenSourceProjects.svg)](https://github.com/WolfgangFahl/pyOpenSourceProjects/issues/?q=is%3Aissue+is%3Aclosed) |
7
+ | **PyPi** | [![PyPI Status](https://img.shields.io/pypi/v/pyOpenSourceProjects.svg)](https://pypi.python.org/pypi/pyOpenSourceProjects/) [![License](https://img.shields.io/github/license/WolfgangFahl/pyOpenSourceProjects.svg)](https://www.apache.org/licenses/LICENSE-2.0) [![pypi](https://img.shields.io/pypi/pyversions/pyOpenSourceProjects)](https://pypi.org/project/pyOpenSourceProjects/) |
8
+ | **Docs** | [![API Docs](https://img.shields.io/badge/API-Documentation-blue)](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-07-30
2
+ Created on 2024-08-28
4
3
 
5
4
  @author: wf
6
5
  """
7
- import argparse
6
+
8
7
  import os
9
- from argparse import Namespace
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
- from osprojects.osproject import GitHub, OsProject
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
- class CheckOS:
43
+
44
+ class CheckProject:
41
45
  """
42
- check the open source projects
46
+ Checker for an individual open source project
43
47
  """
44
48
 
45
- def __init__(self, args: Namespace, project: OsProject):
46
- self.args = args
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.project_path = os.path.join(self.workspace, project.id)
51
- self.checks = []
52
- # python 3.12 is max version
53
- self.max_python_version_minor=12
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 add_check(self, ok, msg:str="",path: str=None,negative:bool=False) -> Check:
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(self, content: str, needle: str, path: str, negative:bool=False) -> Check:
81
- ok=needle in content
82
- check=self.add_check(ok, msg=f"{needle} in ", path=path,negative=negative)
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(self.requires_python.split('.')[-1])
110
- self.add_check(min_python_version_minor==self.min_python_version_minor,msg=f"{min_python_version_minor} (build.yml)!={self.min_python_version_minor} (pyprojec.toml)",path=file_path)
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(content, "uses: actions/checkout@v4", file_path)
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="scripts/install" in content and "scripts/test" in content or "scripts/installAndTest" in content
136
- self.add_check(scripts_ok,"install and test", file_path)
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(content, "uses: actions/checkout@v4", file_path)
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(content, "sphinx", file_path, negative=True)
164
- self.add_content_check(content,"WF 2024-07-30 - updated",file_path)
165
- if file=="test":
166
- self.add_content_check(content,"WF 2024-08-03",file_path)
167
- if file=="release":
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
  "[![PyPI Status](https://img.shields.io/pypi/v/{self.project_name}.svg)](https://pypi.python.org/pypi/{self.project_name}/)",
179
217
  "[![GitHub issues](https://img.shields.io/github/issues/{self.project.fqid}.svg)](https://github.com/{self.project.fqid}/issues)",
180
218
  "[![GitHub closed issues](https://img.shields.io/github/issues-closed/{self.project.fqid}.svg)](https://github.com/{self.project.fqid}/issues/?q=is%3Aissue+is%3Aclosed)",
181
- "[![API Docs](https://img.shields.io/badge/API-Documentation-blue)](https://{self.project.owner}.github.io/{self.project.id}/)",
219
+ "[![API Docs](https://img.shields.io/badge/API-Documentation-blue)](https://{self.project.owner}.github.io/{self.project.project_id}/)",
182
220
  "[![License](https://img.shields.io/github/license/{self.project.fqid}.svg)](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(readme_content, "readthedocs", readme_path, negative=True)
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("project" in toml_dict, "[project]", toml_path)
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("requires-python" in toml_dict["project"], "requires-python", toml_path)
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(self.requires_python.replace(">=", ""))
209
- min_version_needed="3.9"
210
- version_ok=min_python_version >= version.parse(min_version_needed)
211
- self.add_check(version_ok, f"requires-python>={min_version_needed}", toml_path)
212
- self.min_python_version_minor=int(str(min_python_version).split('.')[-1])
213
- for minor_version in range(self.min_python_version_minor, self.max_python_version_minor+1):
214
- needle=f"Programming Language :: Python :: 3.{minor_version}"
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(content,"[tool.hatch.build.targets.wheel.sources]",toml_path)
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.check_pyproject_toml()
226
- self.check_github_workflows()
227
- self.check_readme()
228
- self.check_scripts()
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 = f"❌ {failed_count:2}/{self.total:2}" if failed_count > 0 else f"✅ {self.total:2}/{self.total:2}"
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()