pyOpenSourceProjects 0.2.0__tar.gz → 0.2.2__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 (26) hide show
  1. {pyopensourceprojects-0.2.0 → pyopensourceprojects-0.2.2}/.github/workflows/build.yml +5 -7
  2. {pyopensourceprojects-0.2.0 → pyopensourceprojects-0.2.2}/PKG-INFO +2 -1
  3. pyopensourceprojects-0.2.2/osprojects/__init__.py +1 -0
  4. pyopensourceprojects-0.2.2/osprojects/checkos.py +334 -0
  5. pyopensourceprojects-0.2.2/osprojects/editor.py +132 -0
  6. {pyopensourceprojects-0.2.0 → pyopensourceprojects-0.2.2}/osprojects/osproject.py +1 -1
  7. {pyopensourceprojects-0.2.0 → pyopensourceprojects-0.2.2}/pyproject.toml +3 -1
  8. pyopensourceprojects-0.2.2/scripts/release +8 -0
  9. pyopensourceprojects-0.2.2/scripts/test +132 -0
  10. pyopensourceprojects-0.2.0/osprojects/__init__.py +0 -1
  11. pyopensourceprojects-0.2.0/osprojects/checkos.py +0 -138
  12. pyopensourceprojects-0.2.0/scripts/test +0 -9
  13. {pyopensourceprojects-0.2.0 → pyopensourceprojects-0.2.2}/.github/workflows/upload-to-pypi.yml +0 -0
  14. {pyopensourceprojects-0.2.0 → pyopensourceprojects-0.2.2}/.gitignore +0 -0
  15. {pyopensourceprojects-0.2.0 → pyopensourceprojects-0.2.2}/.project +0 -0
  16. {pyopensourceprojects-0.2.0 → pyopensourceprojects-0.2.2}/.pydevproject +0 -0
  17. {pyopensourceprojects-0.2.0 → pyopensourceprojects-0.2.2}/LICENSE +0 -0
  18. {pyopensourceprojects-0.2.0 → pyopensourceprojects-0.2.2}/README.md +0 -0
  19. {pyopensourceprojects-0.2.0 → pyopensourceprojects-0.2.2}/mkdocs.yml +0 -0
  20. {pyopensourceprojects-0.2.0 → pyopensourceprojects-0.2.2}/scripts/blackisort +0 -0
  21. {pyopensourceprojects-0.2.0 → pyopensourceprojects-0.2.2}/scripts/doc +0 -0
  22. {pyopensourceprojects-0.2.0 → pyopensourceprojects-0.2.2}/scripts/install +0 -0
  23. {pyopensourceprojects-0.2.0 → pyopensourceprojects-0.2.2}/scripts/installAndTest +0 -0
  24. {pyopensourceprojects-0.2.0 → pyopensourceprojects-0.2.2}/tests/__init__.py +0 -0
  25. {pyopensourceprojects-0.2.0 → pyopensourceprojects-0.2.2}/tests/basetest.py +0 -0
  26. {pyopensourceprojects-0.2.0 → pyopensourceprojects-0.2.2}/tests/test_osproject.py +0 -0
@@ -18,10 +18,10 @@ jobs:
18
18
  runs-on: ubuntu-latest
19
19
  strategy:
20
20
  matrix:
21
- os: [ubuntu-latest, macos-latest, windows-latest]
22
- #os: [ubuntu-latest]
23
- python-version: [3.9, "3.10", "3.11", "3.12"]
24
- #python-version: ["3.10"]
21
+ #os: [ubuntu-latest, macos-latest, windows-latest]
22
+ os: [ubuntu-latest]
23
+ #python-version: [ '3.9', '3.10', '3.11', '3.12' ]
24
+ python-version: ["3.10"]
25
25
 
26
26
  steps:
27
27
  - uses: actions/checkout@v4
@@ -32,10 +32,8 @@ jobs:
32
32
  - name: Install dependencies
33
33
  run: |
34
34
  python -m pip install --upgrade pip
35
- pip install sphinx
36
- pip install sphinx_rtd_theme
37
35
  scripts/install
38
36
  scripts/doc
39
37
  - name: Run tests
40
38
  run: |
41
- scripts/installAndTest
39
+ scripts/test
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: pyOpenSourceProjects
3
- Version: 0.2.0
3
+ Version: 0.2.2
4
4
  Dynamic: Summary
5
5
  Project-URL: Home, https://github.com/WolfgangFahl/pyOpenSourceProjects
6
6
  Project-URL: Documentation, http://wiki.bitplan.com/index.php/pyOpenSourceProjects
@@ -15,6 +15,7 @@ Classifier: Programming Language :: Python :: 3.10
15
15
  Classifier: Programming Language :: Python :: 3.11
16
16
  Classifier: Programming Language :: Python :: 3.12
17
17
  Requires-Python: >=3.9
18
+ Requires-Dist: packaging>=24.1
18
19
  Requires-Dist: py-3rdparty-mediawiki>=0.11.3
19
20
  Requires-Dist: pylodstorage>=0.11.6
20
21
  Requires-Dist: python-dateutil>=2.8.2
@@ -0,0 +1 @@
1
+ __version__ = "0.2.2"
@@ -0,0 +1,334 @@
1
+ #!/usr/bin/env python
2
+ """
3
+ Created on 2024-07-30
4
+
5
+ @author: wf
6
+ """
7
+ import argparse
8
+ import os
9
+ from argparse import Namespace
10
+ from dataclasses import dataclass
11
+ from typing import List
12
+ # original at ngwidgets - use redundant local copy ...
13
+ from osprojects.editor import Editor
14
+ from osprojects.osproject import GitHub, OsProject
15
+ import tomllib
16
+ import traceback
17
+ from packaging import version
18
+
19
+ @dataclass
20
+ class Check:
21
+ ok: bool = False
22
+ path: str = None
23
+ msg: str = ""
24
+ content: str = None
25
+
26
+ @property
27
+ def marker(self) -> str:
28
+ return f"✅" if self.ok else f"❌"
29
+
30
+ @classmethod
31
+ def file_exists(cls, path) -> "Check":
32
+ ok = os.path.exists(path)
33
+ content = None
34
+ if ok and os.path.isfile(path):
35
+ with open(path, "r") as f:
36
+ content = f.read()
37
+ check = Check(ok, path, msg=path, content=content)
38
+ return check
39
+
40
+ class CheckOS:
41
+ """
42
+ check the open source projects
43
+ """
44
+
45
+ def __init__(self, args: Namespace, project: OsProject):
46
+ self.args = args
47
+ self.verbose = args.verbose
48
+ self.workspace = args.workspace
49
+ 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
54
+
55
+ @property
56
+ def total(self) -> int:
57
+ return len(self.checks)
58
+
59
+ @property
60
+ def ok_checks(self) -> List[Check]:
61
+ ok_checks = [check for check in self.checks if check.ok]
62
+ return ok_checks
63
+
64
+ @property
65
+ def failed_checks(self) -> List[Check]:
66
+ failed_checks = [check for check in self.checks if not check.ok]
67
+ return failed_checks
68
+
69
+ def add_check(self, ok, msg:str="",path: str=None,negative:bool=False) -> Check:
70
+ if not path:
71
+ raise ValueError("path parameter missing")
72
+ marker=""
73
+ if negative:
74
+ ok=not ok
75
+ marker="⚠ ️"
76
+ check = Check(ok=ok, path=path, msg=f"{marker}{msg}{path}")
77
+ self.checks.append(check)
78
+ return check
79
+
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)
83
+ return check
84
+
85
+ def add_path_check(self, path) -> Check:
86
+ # Check if path exists
87
+ path_exists = Check.file_exists(path)
88
+ self.checks.append(path_exists)
89
+ return path_exists
90
+
91
+ def check_local(self) -> Check:
92
+ local = Check.file_exists(self.project_path)
93
+ return local
94
+
95
+ def check_github_workflows(self):
96
+ workflows_path = os.path.join(self.project_path, ".github", "workflows")
97
+ workflows_exist = self.add_path_check(workflows_path)
98
+
99
+ if workflows_exist.ok:
100
+ required_files = ["build.yml", "upload-to-pypi.yml"]
101
+ for file in required_files:
102
+ file_path = os.path.join(workflows_path, file)
103
+ file_exists = self.add_path_check(file_path)
104
+
105
+ if file_exists.ok:
106
+ content = file_exists.content
107
+
108
+ 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)
111
+ 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
+ self.add_content_check(
113
+ content,
114
+ python_versions,
115
+ file_path,
116
+ )
117
+ self.add_content_check(
118
+ content,
119
+ "os: [ubuntu-latest, macos-latest, windows-latest]",
120
+ file_path,
121
+ )
122
+ self.add_content_check(content, "uses: actions/checkout@v4", file_path)
123
+ self.add_content_check(
124
+ content,
125
+ "uses: actions/setup-python@v5",
126
+ file_path,
127
+ )
128
+
129
+ self.add_content_check(
130
+ content,
131
+ "sphinx",
132
+ file_path,
133
+ negative=True
134
+ )
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)
137
+
138
+ elif file == "upload-to-pypi.yml":
139
+ self.add_content_check(content, "id-token: write", file_path)
140
+ self.add_content_check(content, "uses: actions/checkout@v4", file_path)
141
+ self.add_content_check(
142
+ content,
143
+ "uses: actions/setup-python@v5",
144
+ file_path,
145
+ )
146
+ self.add_content_check(
147
+ content,
148
+ "uses: pypa/gh-action-pypi-publish@release/v1",
149
+ file_path,
150
+ )
151
+
152
+ def check_scripts(self):
153
+ scripts_path = os.path.join(self.project_path, "scripts")
154
+ scripts_exist = self.add_path_check(scripts_path)
155
+ if scripts_exist.ok:
156
+ required_files = ["blackisort", "test", "install", "doc", "release"]
157
+ for file in required_files:
158
+ file_path = os.path.join(scripts_path, file)
159
+ file_exists = self.add_path_check(file_path)
160
+ if file_exists.ok:
161
+ 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":
168
+ self.add_content_check(content, "scripts/doc -d", file_path)
169
+
170
+ def check_readme(self):
171
+ readme_path = os.path.join(self.project_path, "README.md")
172
+ readme_exists = self.add_path_check(readme_path)
173
+ if readme_exists.ok:
174
+ readme_content = readme_exists.content
175
+ badge_lines = [
176
+ "[![pypi](https://img.shields.io/pypi/pyversions/{self.project_name})](https://pypi.org/project/{self.project_name}/)",
177
+ "[![Github Actions Build](https://github.com/{self.project.fqid}/actions/workflows/build.yml/badge.svg)](https://github.com/{self.project.fqid}/actions/workflows/build.yml)",
178
+ "[![PyPI Status](https://img.shields.io/pypi/v/{self.project_name}.svg)](https://pypi.python.org/pypi/{self.project_name}/)",
179
+ "[![GitHub issues](https://img.shields.io/github/issues/{self.project.fqid}.svg)](https://github.com/{self.project.fqid}/issues)",
180
+ "[![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}/)",
182
+ "[![License](https://img.shields.io/github/license/{self.project.fqid}.svg)](https://www.apache.org/licenses/LICENSE-2.0)",
183
+ ]
184
+ for line in badge_lines:
185
+ formatted_line = line.format(self=self)
186
+ self.add_content_check(
187
+ content=readme_content,
188
+ needle=formatted_line,
189
+ path=readme_path,
190
+ )
191
+ self.add_content_check(readme_content, "readthedocs", readme_path, negative=True)
192
+
193
+ def check_pyproject_toml(self):
194
+ """
195
+ pyproject.toml
196
+ """
197
+ toml_path = os.path.join(self.project_path, "pyproject.toml")
198
+ toml_exists = self.add_path_check(toml_path)
199
+ if toml_exists.ok:
200
+ content=toml_exists.content
201
+ toml_dict = tomllib.loads(content)
202
+ project_check=self.add_check("project" in toml_dict, "[project]", toml_path)
203
+ 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)
206
+ if requires_python_check.ok:
207
+ 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}"
215
+ self.add_content_check(content, needle, toml_path)
216
+ self.add_content_check(content, "hatchling", toml_path)
217
+ self.add_content_check(content,"[tool.hatch.build.targets.wheel.sources]",toml_path)
218
+
219
+
220
+ def check(self,title:str):
221
+ """
222
+ Check the given project and print results
223
+ """
224
+ self.check_local()
225
+ self.check_pyproject_toml()
226
+ self.check_github_workflows()
227
+ self.check_readme()
228
+ self.check_scripts()
229
+
230
+
231
+ # ok_count=len(ok_checks)
232
+ 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}"
234
+ print(f"{title}{summary}:{self.project}→{self.project.url}")
235
+ if failed_count > 0:
236
+ # Sort checks by path
237
+ sorted_checks = sorted(self.checks, key=lambda c: c.path or "")
238
+
239
+ # Group checks by path
240
+ checks_by_path = {}
241
+ for check in sorted_checks:
242
+ if check.path not in checks_by_path:
243
+ checks_by_path[check.path] = []
244
+ checks_by_path[check.path].append(check)
245
+
246
+ # Display results
247
+ for path, path_checks in checks_by_path.items():
248
+ path_failed = sum(1 for c in path_checks if not c.ok)
249
+ if path_failed > 0 or self.args.debug:
250
+ print(f"❌ {path}: {path_failed}")
251
+ i=0
252
+ for check in path_checks:
253
+ show=not check.ok or self.args.debug
254
+ if show:
255
+ i+=1
256
+ print(f" {i:3}{check.marker}:{check.msg}")
257
+
258
+ if self.args.editor and path_failed > 0:
259
+ if os.path.isfile(path):
260
+ # @TODO Make editor configurable
261
+ Editor.open(path,default_editor_cmd="/usr/local/bin/atom")
262
+ else:
263
+ 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,132 @@
1
+ """
2
+ Created on 2022-11-27
3
+
4
+ @author: wf
5
+ """
6
+
7
+ import os
8
+ import platform
9
+ import subprocess
10
+ import tempfile
11
+ from pathlib import Path
12
+ from urllib.request import urlopen
13
+
14
+ from bs4 import BeautifulSoup
15
+
16
+
17
+ class Editor:
18
+ """
19
+ helper class to open the system defined editor
20
+
21
+ see https://stackoverflow.com/questions/1442841/lauch-default-editor-like-webbrowser-module
22
+ """
23
+
24
+ @classmethod
25
+ def open_filepath(cls, filepath: str):
26
+ if platform.system() == "Darwin": # macOS
27
+ subprocess.call(("open", filepath))
28
+ elif platform.system() == "Windows": # Windows
29
+ os.startfile(filepath, "open")
30
+ else: # linux variants
31
+ subprocess.call(("xdg-open", filepath))
32
+
33
+ @classmethod
34
+ def extract_text(cls, html_text: str) -> str:
35
+ """
36
+ extract the text from the given html_text
37
+
38
+ Args:
39
+ html_text(str): the input for the html text
40
+
41
+ Returns:
42
+ str: the plain text
43
+ """
44
+ soup = BeautifulSoup(html_text, features="html.parser")
45
+
46
+ # kill all script and style elements
47
+ for script in soup(["script", "style"]):
48
+ script.extract() # rip it out
49
+
50
+ # get text
51
+ text = soup.get_text()
52
+
53
+ # break into lines and remove leading and trailing space on each
54
+ lines = (line.strip() for line in text.splitlines())
55
+ # break multi-headlines into a line each
56
+ chunks = (phrase.strip() for line in lines for phrase in line.split(" "))
57
+ # drop blank lines
58
+ text = "\n".join(chunk for chunk in chunks if chunk)
59
+ return text
60
+
61
+ @classmethod
62
+ def open(
63
+ cls,
64
+ file_source: str,
65
+ extract_text: bool = True,
66
+ default_editor_cmd: str = "/usr/local/bin/atom",
67
+ ) -> str:
68
+ """
69
+ open an editor for the given file_source
70
+
71
+ Args:
72
+ file_source(str): the path to the file
73
+ extract_text(bool): if True extract the text from html sources
74
+
75
+ Returns:
76
+ str: the path to the file e.g. a temporary file if the file_source points to an url
77
+ """
78
+ # handle urls
79
+ # https://stackoverflow.com/a/45886824/1497139
80
+ if file_source.startswith("http"):
81
+ url_source = urlopen(file_source)
82
+ # https://stackoverflow.com/a/19156107/1497139
83
+ charset = url_source.headers.get_content_charset()
84
+ # if charset fails here you might want to set it to utf-8 as a default!
85
+ text = url_source.read().decode(charset)
86
+ if extract_text:
87
+ # https://stackoverflow.com/a/24618186/1497139
88
+ text = cls.extract_text(text)
89
+
90
+ return cls.open_tmp_text(text)
91
+
92
+ editor_cmd = None
93
+ editor_env = os.getenv("EDITOR")
94
+ if editor_env:
95
+ editor_cmd = editor_env
96
+ if platform.system() == "Darwin":
97
+ if not editor_env:
98
+ # https://stackoverflow.com/questions/22390709/how-can-i-open-the-atom-editor-from-the-command-line-in-os-x
99
+ editor_cmd = default_editor_cmd
100
+ if editor_cmd:
101
+ os_cmd = f"{editor_cmd} {file_source}"
102
+ os.system(os_cmd)
103
+ return file_source
104
+
105
+ @classmethod
106
+ def open_tmp_text(cls, text: str, file_name: str = None) -> str:
107
+ """
108
+ open an editor for the given text in a newly created temporary file
109
+
110
+ Args:
111
+ text(str): the text to write to a temporary file and then open
112
+ file_name(str): the name to use for the file
113
+
114
+ Returns:
115
+ str: the path to the temp file
116
+ """
117
+ # see https://stackoverflow.com/a/8577226/1497139
118
+ # https://stackoverflow.com/a/3924253/1497139
119
+ with tempfile.NamedTemporaryFile(delete=False) as tmp:
120
+ with open(tmp.name, "w") as tmp_file:
121
+ tmp_file.write(text)
122
+ tmp_file.close()
123
+ if file_name is None:
124
+ file_path = tmp.name
125
+ else:
126
+ # https://stackoverflow.com/questions/3167154/how-to-split-a-dos-path-into-its-components-in-python
127
+ path = Path(tmp.name)
128
+ # https://stackoverflow.com/a/49798311/1497139
129
+ file_path = path.parent / file_name
130
+ os.rename(tmp.name, file_path)
131
+
132
+ return cls.open(str(file_path))
@@ -333,7 +333,7 @@ class OsProject(object):
333
333
 
334
334
  @property
335
335
  def fqid(self):
336
- fqid=f"{self.owner}/{self.id}"
336
+ fqid = f"{self.owner}/{self.id}"
337
337
  return fqid
338
338
 
339
339
  def __str__(self):
@@ -18,7 +18,9 @@ dependencies = [
18
18
  "pyLodStorage>=0.11.6",
19
19
  "py-3rdparty-mediawiki>=0.11.3",
20
20
  # https://pypi.org/project/python-dateutil/
21
- "python-dateutil>=2.8.2"
21
+ "python-dateutil>=2.8.2",
22
+ # https://github.com/pypa/packaging
23
+ "packaging>=24.1"
22
24
  ]
23
25
  requires-python = ">=3.9"
24
26
 
@@ -0,0 +1,8 @@
1
+ #!/bin/bash
2
+ # WF 2024-07-31
3
+ # prepare a release
4
+ scripts/doc -d
5
+
6
+ # Commit with a message that includes the current ISO timestamp
7
+ git commit -a -m "release commit"
8
+ git push
@@ -0,0 +1,132 @@
1
+ #!/bin/bash
2
+ # WF 2022-08-20
3
+ # WF 2024-08-03
4
+
5
+ #ansi colors
6
+ #http://www.csc.uvic.ca/~sae/seng265/fall04/tips/s265s047-tips/bash-using-colors.html
7
+ blue='\033[0;34m'
8
+ red='\033[0;31m'
9
+ green='\033[0;32m' # '\e[1;32m' is too bright for white bg.
10
+ endColor='\033[0m'
11
+
12
+ #
13
+ # a colored message
14
+ # params:
15
+ # 1: l_color - the color of the message
16
+ # 2: l_msg - the message to display
17
+ #
18
+ color_msg() {
19
+ local l_color="$1"
20
+ local l_msg="$2"
21
+ echo -e "${l_color}$l_msg${endColor}"
22
+ }
23
+
24
+ #
25
+ # error
26
+ #
27
+ # show the given error message on stderr and exit
28
+ #
29
+ # params:
30
+ # 1: l_msg - the error message to display
31
+ #
32
+ error() {
33
+ local l_msg="$1"
34
+ # use ansi red for error
35
+ color_msg $red "Error:" 1>&2
36
+ color_msg $red "\t$l_msg" 1>&2
37
+ exit 1
38
+ }
39
+
40
+ #
41
+ # show a negative message
42
+ #
43
+ negative() {
44
+ local l_msg="$1"
45
+ color_msg $red "❌:$l_msg"
46
+ }
47
+
48
+ #
49
+ # show a positive message
50
+ #
51
+ positive() {
52
+ local l_msg="$1"
53
+ color_msg $green "✅:$l_msg"
54
+ }
55
+
56
+ # show usage
57
+ #
58
+ usage() {
59
+ echo "$0 [-g|--green|-m|--module|-t|--tox|-h|--help]"
60
+ echo "-t |--tox: run tests with tox"
61
+ echo "-g |--green: run tests with green"
62
+ echo "-m |--module: run modulewise test"
63
+ echo "-h |--help: show this usage"
64
+ echo "default is running tests with unittest discover"
65
+ exit 1
66
+ }
67
+
68
+ #
69
+ # check and optional install the given package
70
+ #
71
+ check_package() {
72
+ local l_package="$1"
73
+ pip show $l_package > /dev/null
74
+ if [ $? -ne 0 ]
75
+ then
76
+ negative "$l_package"
77
+ color_msg $blue "installing $l_package"
78
+ pip install $l_package
79
+ else
80
+ positive "$l_package"
81
+ fi
82
+ }
83
+
84
+ #
85
+ # test module by module
86
+ #
87
+ modulewise_test() {
88
+ foundErrors=0
89
+ foundTests=0
90
+ for testmodule in tests/test*.py
91
+ do
92
+ echo "testing $testmodule ..."
93
+ # see https://github.com/CleanCut/green/issues/263
94
+ #green $testmodule -s1
95
+ python -m unittest $testmodule
96
+ exit_code=$?
97
+ foundErrors=$((foundErrors+exit_code))
98
+ foundTests=$((foundTests+1))
99
+ done
100
+ echo "$foundErrors/$foundTests module unit tests failed" 1>&2
101
+ if [[ $foundErrors -gt 0 ]]
102
+ then
103
+ exit 1
104
+ fi
105
+ }
106
+
107
+ export PYTHON_PATH="."
108
+ while [ "$1" != "" ]
109
+ do
110
+ option="$1"
111
+ case $option in
112
+ -h|--help)
113
+ usage
114
+ exit 0
115
+ ;;
116
+ -g|--green)
117
+ check_package green
118
+ green tests -s 1
119
+ exit 0
120
+ ;;
121
+ -m|--module)
122
+ modulewise_test
123
+ exit 0
124
+ ;;
125
+ -t|--tox)
126
+ check_package tox
127
+ tox -e py
128
+ exit 0
129
+ ;;
130
+ esac
131
+ done
132
+ python3 -m unittest discover
@@ -1 +0,0 @@
1
- __version__ = "0.2.0"
@@ -1,138 +0,0 @@
1
- #!/usr/bin/env python
2
- """
3
- Created on 2024-07-30
4
-
5
- @author: wf
6
- """
7
- from dataclasses import dataclass
8
- import argparse
9
- from argparse import Namespace
10
- import os
11
- from typing import List
12
-
13
- from osprojects.osproject import GitHub, OsProject
14
-
15
- @dataclass
16
- class Check:
17
- ok: bool=False
18
- msg: str=""
19
-
20
- @property
21
- def marker(self) -> str:
22
- return f"✅" if self.ok else f"❌"
23
-
24
- class CheckOS:
25
- """
26
- check the open source projects
27
- """
28
-
29
- def __init__(self, args:Namespace,project:OsProject):
30
- self.args=args
31
- self.verbose= args.verbose
32
- self.workspace = args.workspace
33
- self.project=project
34
- self.project_path = os.path.join(self.workspace, project.id)
35
-
36
-
37
- def check_local(self)->Check:
38
- local=Check(ok=os.path.exists(self.project_path),msg=f"{self.project_path}")
39
- return local
40
-
41
- def check_readme(self) -> List[Check]:
42
- checks=[]
43
- readme_path = os.path.join(self.project_path, "README.md")
44
- readme_exists=Check(ok=os.path.exists(readme_path),msg=readme_path)
45
- checks.append(readme_exists)
46
- if readme_exists.ok:
47
- with open(readme_path, "r") as readme_file:
48
- readme_content = readme_file.read()
49
- badge_lines = [
50
- "[![pypi](https://img.shields.io/pypi/pyversions/{self.project.id})](https://pypi.org/project/{self.project.id}/)",
51
- "[![Github Actions Build](https://github.com/{self.project.fqid}/actions/workflows/build.yml/badge.svg)](https://github.com/{self.project.fqid}/actions/workflows/build.yml)",
52
- "[![PyPI Status](https://img.shields.io/pypi/v/{self.project.id}.svg)](https://pypi.python.org/pypi/{self.project.id}/)",
53
- "[![GitHub issues](https://img.shields.io/github/issues/{self.project.fqid}.svg)](https://github.com/{self.project.fqid}/issues)",
54
- "[![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)",
55
- "[![API Docs](https://img.shields.io/badge/API-Documentation-blue)](https://{self.project.owner}.github.io/{self.project.id}/)",
56
- "[![License](https://img.shields.io/github/license/{self.project.fqid}.svg)](https://www.apache.org/licenses/LICENSE-2.0)"
57
- ]
58
- for line in badge_lines:
59
- formatted_line = line.format(self=self)
60
- checks.append(Check(ok=formatted_line in readme_content, msg=formatted_line))
61
-
62
-
63
- return checks
64
-
65
- def check(self) -> List[Check]:
66
- """
67
- Check the given project and print results
68
- """
69
- checks = []
70
- checks.append(self.check_local())
71
- checks.extend(self.check_readme())
72
- total=len(checks)
73
- ok_checks = [check for check in checks if check.ok]
74
- failed_checks = [check for check in checks if not check.ok]
75
- #ok_count=len(ok_checks)
76
- failed_count=len(failed_checks)
77
- summary=f"❌ {failed_count}/{total}" if failed_count>0 else "✅"
78
- print(f"{self.project} {summary}: {self.project.url}")
79
- if failed_count>0:
80
- sorted_checks = ok_checks + failed_checks if self.verbose else failed_checks
81
-
82
- for i,check in enumerate(sorted_checks):
83
- print(f" {i+1:3}{check.marker}:{check.msg}")
84
- return checks
85
-
86
- def main(_argv=None):
87
- """
88
- main command line entry point
89
- """
90
- parser = argparse.ArgumentParser(description="Check open source projects")
91
- parser.add_argument(
92
- "-o", "--owner", help="project owner or organization", required=True
93
- )
94
- parser.add_argument("-p", "--project", help="name of the project")
95
- parser.add_argument("-l", "--language", help="filter projects by language")
96
- parser.add_argument(
97
- "--local", action="store_true", help="check only locally available projects"
98
- )
99
- parser.add_argument(
100
- "-v","--verbose", action="store_true", help="show verbose output"
101
- )
102
- parser.add_argument(
103
- "-ws",
104
- "--workspace",
105
- help="(Eclipse) workspace directory",
106
- default=os.path.expanduser("~/py-workspace"),
107
- )
108
-
109
- args = parser.parse_args(args=_argv)
110
-
111
- github = GitHub()
112
- if args.project:
113
- # Check specific project
114
- projects = [
115
- github.list_projects_as_os_projects(args.owner, project_name=args.project)
116
- ]
117
- else:
118
- # Check all projects
119
- projects = github.list_projects_as_os_projects(args.owner)
120
-
121
- if args.language:
122
- projects = [p for p in projects if p.language == args.language]
123
-
124
- if args.local:
125
- local_projects = []
126
- for project in projects:
127
- checker = CheckOS(args=args,project=project)
128
- if checker.check_local().ok:
129
- local_projects.append(project)
130
- projects=local_projects
131
-
132
-
133
- for project in projects:
134
- checker = CheckOS(args=args,project=project)
135
- checker.check()
136
-
137
- if __name__ == "__main__":
138
- main()
@@ -1,9 +0,0 @@
1
- #!/bin/bash
2
- # WF 2020-06-03
3
- #python3 -m unittest discover
4
- pip list | egrep "^green "
5
- if [ $? -ne 0 ]
6
- then
7
- pip install green
8
- fi
9
- green