pyOpenSourceProjects 0.2.0__tar.gz → 0.2.1__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.2.0 → pyopensourceprojects-0.2.1}/.github/workflows/build.yml +2 -4
- {pyopensourceprojects-0.2.0 → pyopensourceprojects-0.2.1}/PKG-INFO +2 -1
- pyopensourceprojects-0.2.1/osprojects/__init__.py +1 -0
- pyopensourceprojects-0.2.1/osprojects/checkos.py +320 -0
- pyopensourceprojects-0.2.1/osprojects/editor.py +132 -0
- {pyopensourceprojects-0.2.0 → pyopensourceprojects-0.2.1}/osprojects/osproject.py +1 -1
- {pyopensourceprojects-0.2.0 → pyopensourceprojects-0.2.1}/pyproject.toml +3 -1
- pyopensourceprojects-0.2.1/scripts/release +8 -0
- pyopensourceprojects-0.2.0/osprojects/__init__.py +0 -1
- pyopensourceprojects-0.2.0/osprojects/checkos.py +0 -138
- {pyopensourceprojects-0.2.0 → pyopensourceprojects-0.2.1}/.github/workflows/upload-to-pypi.yml +0 -0
- {pyopensourceprojects-0.2.0 → pyopensourceprojects-0.2.1}/.gitignore +0 -0
- {pyopensourceprojects-0.2.0 → pyopensourceprojects-0.2.1}/.project +0 -0
- {pyopensourceprojects-0.2.0 → pyopensourceprojects-0.2.1}/.pydevproject +0 -0
- {pyopensourceprojects-0.2.0 → pyopensourceprojects-0.2.1}/LICENSE +0 -0
- {pyopensourceprojects-0.2.0 → pyopensourceprojects-0.2.1}/README.md +0 -0
- {pyopensourceprojects-0.2.0 → pyopensourceprojects-0.2.1}/mkdocs.yml +0 -0
- {pyopensourceprojects-0.2.0 → pyopensourceprojects-0.2.1}/scripts/blackisort +0 -0
- {pyopensourceprojects-0.2.0 → pyopensourceprojects-0.2.1}/scripts/doc +0 -0
- {pyopensourceprojects-0.2.0 → pyopensourceprojects-0.2.1}/scripts/install +0 -0
- {pyopensourceprojects-0.2.0 → pyopensourceprojects-0.2.1}/scripts/installAndTest +0 -0
- {pyopensourceprojects-0.2.0 → pyopensourceprojects-0.2.1}/scripts/test +0 -0
- {pyopensourceprojects-0.2.0 → pyopensourceprojects-0.2.1}/tests/__init__.py +0 -0
- {pyopensourceprojects-0.2.0 → pyopensourceprojects-0.2.1}/tests/basetest.py +0 -0
- {pyopensourceprojects-0.2.0 → pyopensourceprojects-0.2.1}/tests/test_osproject.py +0 -0
|
@@ -20,7 +20,7 @@ jobs:
|
|
|
20
20
|
matrix:
|
|
21
21
|
os: [ubuntu-latest, macos-latest, windows-latest]
|
|
22
22
|
#os: [ubuntu-latest]
|
|
23
|
-
python-version: [3.9,
|
|
23
|
+
python-version: [ 3.9, '3.10', '3.11', '3.12' ]
|
|
24
24
|
#python-version: ["3.10"]
|
|
25
25
|
|
|
26
26
|
steps:
|
|
@@ -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/
|
|
39
|
+
scripts/test
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: pyOpenSourceProjects
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.1
|
|
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.1"
|
|
@@ -0,0 +1,320 @@
|
|
|
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
|
+
|
|
53
|
+
@property
|
|
54
|
+
def total(self) -> int:
|
|
55
|
+
return len(self.checks)
|
|
56
|
+
|
|
57
|
+
@property
|
|
58
|
+
def ok_checks(self) -> List[Check]:
|
|
59
|
+
ok_checks = [check for check in self.checks if check.ok]
|
|
60
|
+
return ok_checks
|
|
61
|
+
|
|
62
|
+
@property
|
|
63
|
+
def failed_checks(self) -> List[Check]:
|
|
64
|
+
failed_checks = [check for check in self.checks if not check.ok]
|
|
65
|
+
return failed_checks
|
|
66
|
+
|
|
67
|
+
def add_check(self, ok, msg:str="",path: str=None,negative:bool=False) -> Check:
|
|
68
|
+
if not path:
|
|
69
|
+
raise ValueError("path parameter missing")
|
|
70
|
+
marker=""
|
|
71
|
+
if negative:
|
|
72
|
+
ok=not ok
|
|
73
|
+
marker="⚠ ️"
|
|
74
|
+
check = Check(ok=ok, path=path, msg=f"{marker}{msg}{path}")
|
|
75
|
+
self.checks.append(check)
|
|
76
|
+
return check
|
|
77
|
+
|
|
78
|
+
def add_content_check(self, content: str, needle: str, path: str, negative:bool=False) -> Check:
|
|
79
|
+
ok=needle in content
|
|
80
|
+
check=self.add_check(ok, msg=f"{needle} in ", path=path,negative=negative)
|
|
81
|
+
return check
|
|
82
|
+
|
|
83
|
+
def add_path_check(self, path) -> Check:
|
|
84
|
+
# Check if path exists
|
|
85
|
+
path_exists = Check.file_exists(path)
|
|
86
|
+
self.checks.append(path_exists)
|
|
87
|
+
return path_exists
|
|
88
|
+
|
|
89
|
+
def check_local(self) -> Check:
|
|
90
|
+
local = Check.file_exists(self.project_path)
|
|
91
|
+
return local
|
|
92
|
+
|
|
93
|
+
def check_github_workflows(self):
|
|
94
|
+
workflows_path = os.path.join(self.project_path, ".github", "workflows")
|
|
95
|
+
workflows_exist = self.add_path_check(workflows_path)
|
|
96
|
+
|
|
97
|
+
if workflows_exist.ok:
|
|
98
|
+
required_files = ["build.yml", "upload-to-pypi.yml"]
|
|
99
|
+
for file in required_files:
|
|
100
|
+
file_path = os.path.join(workflows_path, file)
|
|
101
|
+
file_exists = self.add_path_check(file_path)
|
|
102
|
+
|
|
103
|
+
if file_exists.ok:
|
|
104
|
+
content = file_exists.content
|
|
105
|
+
|
|
106
|
+
if file == "build.yml":
|
|
107
|
+
min_version = int(self.requires_python.split('.')[-1])
|
|
108
|
+
python_versions = f"""python-version: [ {', '.join([f"'3.{i}'" for i in range(min_version, 13)])} ]"""
|
|
109
|
+
self.add_content_check(
|
|
110
|
+
content,
|
|
111
|
+
python_versions,
|
|
112
|
+
file_path,
|
|
113
|
+
)
|
|
114
|
+
self.add_content_check(
|
|
115
|
+
content,
|
|
116
|
+
"os: [ubuntu-latest, macos-latest, windows-latest]",
|
|
117
|
+
file_path,
|
|
118
|
+
)
|
|
119
|
+
self.add_content_check(content, "uses: actions/checkout@v4", file_path)
|
|
120
|
+
self.add_content_check(
|
|
121
|
+
content,
|
|
122
|
+
"uses: actions/setup-python@v5",
|
|
123
|
+
file_path,
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
self.add_content_check(
|
|
127
|
+
content,
|
|
128
|
+
"sphinx",
|
|
129
|
+
file_path,
|
|
130
|
+
negative=True
|
|
131
|
+
)
|
|
132
|
+
scripts_ok="scripts/install" in content and "scripts/test" in content or "scripts/installAndTest" in content
|
|
133
|
+
self.add_check(scripts_ok,"install and test", file_path)
|
|
134
|
+
|
|
135
|
+
elif file == "upload-to-pypi.yml":
|
|
136
|
+
self.add_content_check(content, "id-token: write", file_path)
|
|
137
|
+
self.add_content_check(content, "uses: actions/checkout@v4", file_path)
|
|
138
|
+
self.add_content_check(
|
|
139
|
+
content,
|
|
140
|
+
"uses: actions/setup-python@v5",
|
|
141
|
+
file_path,
|
|
142
|
+
)
|
|
143
|
+
self.add_content_check(
|
|
144
|
+
content,
|
|
145
|
+
"uses: pypa/gh-action-pypi-publish@release/v1",
|
|
146
|
+
file_path,
|
|
147
|
+
)
|
|
148
|
+
|
|
149
|
+
def check_scripts(self):
|
|
150
|
+
scripts_path = os.path.join(self.project_path, "scripts")
|
|
151
|
+
scripts_exist = self.add_path_check(scripts_path)
|
|
152
|
+
if scripts_exist.ok:
|
|
153
|
+
required_files = ["blackisort", "test", "install", "doc", "release"]
|
|
154
|
+
for file in required_files:
|
|
155
|
+
file_path = os.path.join(scripts_path, file)
|
|
156
|
+
file_exists = self.add_path_check(file_path)
|
|
157
|
+
if file_exists.ok:
|
|
158
|
+
content = file_exists.content
|
|
159
|
+
if file=="doc":
|
|
160
|
+
self.add_content_check(content, "sphinx", file_path, negative=True)
|
|
161
|
+
self.add_content_check(content,"WF 2024-07-30 - updated",file_path)
|
|
162
|
+
if file=="release":
|
|
163
|
+
self.add_content_check(content, "scripts/docs -d", file_path, negative=True)
|
|
164
|
+
|
|
165
|
+
def check_readme(self):
|
|
166
|
+
readme_path = os.path.join(self.project_path, "README.md")
|
|
167
|
+
readme_exists = self.add_path_check(readme_path)
|
|
168
|
+
if readme_exists.ok:
|
|
169
|
+
readme_content = readme_exists.content
|
|
170
|
+
badge_lines = [
|
|
171
|
+
"[](https://pypi.org/project/{self.project_name}/)",
|
|
172
|
+
"[](https://github.com/{self.project.fqid}/actions/workflows/build.yml)",
|
|
173
|
+
"[](https://pypi.python.org/pypi/{self.project_name}/)",
|
|
174
|
+
"[](https://github.com/{self.project.fqid}/issues)",
|
|
175
|
+
"[](https://github.com/{self.project.fqid}/issues/?q=is%3Aissue+is%3Aclosed)",
|
|
176
|
+
"[](https://{self.project.owner}.github.io/{self.project.id}/)",
|
|
177
|
+
"[](https://www.apache.org/licenses/LICENSE-2.0)",
|
|
178
|
+
]
|
|
179
|
+
for line in badge_lines:
|
|
180
|
+
formatted_line = line.format(self=self)
|
|
181
|
+
self.add_content_check(
|
|
182
|
+
content=readme_content,
|
|
183
|
+
needle=formatted_line,
|
|
184
|
+
path=readme_path,
|
|
185
|
+
)
|
|
186
|
+
self.add_content_check(readme_content, "readthedocs", readme_path, negative=True)
|
|
187
|
+
|
|
188
|
+
def check_pyproject_toml(self):
|
|
189
|
+
toml_path = os.path.join(self.project_path, "pyproject.toml")
|
|
190
|
+
toml_exists = self.add_path_check(toml_path)
|
|
191
|
+
if toml_exists.ok:
|
|
192
|
+
content=toml_exists.content
|
|
193
|
+
toml_dict = tomllib.loads(content)
|
|
194
|
+
project_check=self.add_check("project" in toml_dict, "[project]", toml_path)
|
|
195
|
+
if project_check.ok:
|
|
196
|
+
self.project_name=toml_dict["project"]["name"]
|
|
197
|
+
requires_python_check=self.add_check("requires-python" in toml_dict["project"], "requires-python", toml_path)
|
|
198
|
+
if requires_python_check.ok:
|
|
199
|
+
self.requires_python = toml_dict["project"]["requires-python"]
|
|
200
|
+
self.min_python_version = version.parse(self.requires_python.replace(">=", ""))
|
|
201
|
+
min_version_needed="3.9"
|
|
202
|
+
version_ok=self.min_python_version >= version.parse(min_version_needed)
|
|
203
|
+
self.add_check(version_ok, f"requires-python>={min_version_needed}", toml_path)
|
|
204
|
+
self.add_content_check(content, "hatchling", toml_path)
|
|
205
|
+
self.add_content_check(content,"[tool.hatch.build.targets.wheel.sources]",toml_path)
|
|
206
|
+
|
|
207
|
+
def check(self):
|
|
208
|
+
"""
|
|
209
|
+
Check the given project and print results
|
|
210
|
+
"""
|
|
211
|
+
self.check_local()
|
|
212
|
+
self.check_pyproject_toml()
|
|
213
|
+
self.check_readme()
|
|
214
|
+
self.check_scripts()
|
|
215
|
+
self.check_github_workflows()
|
|
216
|
+
|
|
217
|
+
# ok_count=len(ok_checks)
|
|
218
|
+
failed_count = len(self.failed_checks)
|
|
219
|
+
summary = f"❌ {failed_count}/{self.total}" if failed_count > 0 else f"✅ {self.total}/{self.total}"
|
|
220
|
+
print(f"{self.project} {summary}: {self.project.url}")
|
|
221
|
+
if failed_count > 0:
|
|
222
|
+
# Sort checks by path
|
|
223
|
+
sorted_checks = sorted(self.checks, key=lambda c: c.path or "")
|
|
224
|
+
|
|
225
|
+
# Group checks by path
|
|
226
|
+
checks_by_path = {}
|
|
227
|
+
for check in sorted_checks:
|
|
228
|
+
if check.path not in checks_by_path:
|
|
229
|
+
checks_by_path[check.path] = []
|
|
230
|
+
checks_by_path[check.path].append(check)
|
|
231
|
+
|
|
232
|
+
# Display results
|
|
233
|
+
for path, path_checks in checks_by_path.items():
|
|
234
|
+
path_failed = sum(1 for c in path_checks if not c.ok)
|
|
235
|
+
if path_failed > 0 or self.args.debug:
|
|
236
|
+
print(f"❌ {path}: {path_failed}")
|
|
237
|
+
i=0
|
|
238
|
+
for check in path_checks:
|
|
239
|
+
show=not check.ok or self.args.debug
|
|
240
|
+
if show:
|
|
241
|
+
i+=1
|
|
242
|
+
print(f" {i:3}{check.marker}:{check.msg}")
|
|
243
|
+
|
|
244
|
+
if self.args.editor and path_failed > 0:
|
|
245
|
+
if os.path.isfile(path):
|
|
246
|
+
# @TODO Make editor configurable
|
|
247
|
+
Editor.open(path,default_editor_cmd="/usr/local/bin/atom")
|
|
248
|
+
else:
|
|
249
|
+
Editor.open_filepath(path)
|
|
250
|
+
|
|
251
|
+
|
|
252
|
+
def main(_argv=None):
|
|
253
|
+
"""
|
|
254
|
+
main command line entry point
|
|
255
|
+
"""
|
|
256
|
+
parser = argparse.ArgumentParser(description="Check open source projects")
|
|
257
|
+
parser.add_argument(
|
|
258
|
+
"-d",
|
|
259
|
+
"--debug",
|
|
260
|
+
action="store_true",
|
|
261
|
+
help="add debug output",
|
|
262
|
+
)
|
|
263
|
+
parser.add_argument(
|
|
264
|
+
"-e",
|
|
265
|
+
"--editor",
|
|
266
|
+
action="store_true",
|
|
267
|
+
help="open default editor on failed files",
|
|
268
|
+
)
|
|
269
|
+
parser.add_argument(
|
|
270
|
+
"-o", "--owner", help="project owner or organization", required=True
|
|
271
|
+
)
|
|
272
|
+
parser.add_argument("-p", "--project", help="name of the project")
|
|
273
|
+
parser.add_argument("-l", "--language", help="filter projects by language")
|
|
274
|
+
parser.add_argument(
|
|
275
|
+
"--local", action="store_true", help="check only locally available projects"
|
|
276
|
+
)
|
|
277
|
+
parser.add_argument(
|
|
278
|
+
"-v", "--verbose", action="store_true", help="show verbose output"
|
|
279
|
+
)
|
|
280
|
+
parser.add_argument(
|
|
281
|
+
"-ws",
|
|
282
|
+
"--workspace",
|
|
283
|
+
help="(Eclipse) workspace directory",
|
|
284
|
+
default=os.path.expanduser("~/py-workspace"),
|
|
285
|
+
)
|
|
286
|
+
|
|
287
|
+
args = parser.parse_args(args=_argv)
|
|
288
|
+
|
|
289
|
+
try:
|
|
290
|
+
github = GitHub()
|
|
291
|
+
if args.project:
|
|
292
|
+
# Check specific project
|
|
293
|
+
projects = github.list_projects_as_os_projects(
|
|
294
|
+
args.owner, project_name=args.project
|
|
295
|
+
)
|
|
296
|
+
else:
|
|
297
|
+
# Check all projects
|
|
298
|
+
projects = github.list_projects_as_os_projects(args.owner)
|
|
299
|
+
|
|
300
|
+
if args.language:
|
|
301
|
+
projects = [p for p in projects if p.language == args.language]
|
|
302
|
+
|
|
303
|
+
if args.local:
|
|
304
|
+
local_projects = []
|
|
305
|
+
for project in projects:
|
|
306
|
+
checker = CheckOS(args=args, project=project)
|
|
307
|
+
if checker.check_local().ok:
|
|
308
|
+
local_projects.append(project)
|
|
309
|
+
projects = local_projects
|
|
310
|
+
|
|
311
|
+
for project in projects:
|
|
312
|
+
checker = CheckOS(args=args, project=project)
|
|
313
|
+
checker.check()
|
|
314
|
+
except Exception as ex:
|
|
315
|
+
if args.debug:
|
|
316
|
+
print(traceback.format_exc())
|
|
317
|
+
raise ex
|
|
318
|
+
|
|
319
|
+
if __name__ == "__main__":
|
|
320
|
+
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))
|
|
@@ -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
|
|
|
@@ -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
|
-
"[](https://pypi.org/project/{self.project.id}/)",
|
|
51
|
-
"[](https://github.com/{self.project.fqid}/actions/workflows/build.yml)",
|
|
52
|
-
"[](https://pypi.python.org/pypi/{self.project.id}/)",
|
|
53
|
-
"[](https://github.com/{self.project.fqid}/issues)",
|
|
54
|
-
"[](https://github.com/{self.project.fqid}/issues/?q=is%3Aissue+is%3Aclosed)",
|
|
55
|
-
"[](https://{self.project.owner}.github.io/{self.project.id}/)",
|
|
56
|
-
"[](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()
|
{pyopensourceprojects-0.2.0 → pyopensourceprojects-0.2.1}/.github/workflows/upload-to-pypi.yml
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|