pygitpub 0.0.58__tar.gz → 0.0.60__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.
@@ -0,0 +1,280 @@
1
+ # generated
2
+ /toolchain/
3
+ /dist/
4
+ /out/
5
+ /debug/
6
+ /download/
7
+ *.out
8
+ /.mypy_cache/
9
+ /.ruff_cache/
10
+ /.rumdl_cache/
11
+
12
+ # folders to git ignore
13
+ /*.gi
14
+ /gi/
15
+
16
+ # gems
17
+ /gems/
18
+
19
+ # hive
20
+ derby.log
21
+ dfs/
22
+ metastore_db/
23
+
24
+ # c, c++ stuff
25
+ *.o
26
+ *.so
27
+ *.a
28
+ *.dis
29
+ /Makefile.bak
30
+ *.o.ur-safe
31
+ *.oo
32
+ *.elf
33
+ *.dep
34
+ /core.*
35
+ lib*.so.*
36
+ *.gch
37
+ *.pchi
38
+ *.gcda
39
+ *.map
40
+ *.bin
41
+
42
+ # kernel stuff
43
+ *.ko
44
+ # *.mod
45
+ *.mod.c
46
+ *.mod.cmd
47
+ .*.o.cmd
48
+ .*.ko.cmd
49
+ .*.o.tmp
50
+ .*.o.d
51
+ .tmp_versions
52
+ Module.symvers
53
+ modules.order
54
+ *.objdump
55
+ /src/kernel/.*.tmp
56
+ .modules.*.cmd
57
+ .Module.*.cmd
58
+ /flags.cfg
59
+ # device tree binary files and binary overlay files
60
+ *.dtb
61
+ *.dtbo
62
+
63
+ # sphinx
64
+ /docs/_build/
65
+ /docs/.doctrees/
66
+
67
+ # mdBook build output
68
+ /docs/book/
69
+
70
+ # installations
71
+ /external/
72
+ /install/
73
+ *.tar.gz
74
+
75
+ # python
76
+ __pycache__/
77
+ *.py[oc]
78
+ /wheel/
79
+ /.venv/
80
+ .coverage
81
+ .coverage.*
82
+ coverage.xml
83
+ *.egg-info/
84
+
85
+ # mine
86
+ # error files left by the bashy prompt plugins (.uv.sync.errors,
87
+ # .pydmt.build.errors, ...); unanchored because a sub-project's
88
+ # pyproject root can sit below the git root
89
+ .*.errors
90
+ /.pydmt/
91
+ *.stamp
92
+ *.stamp.*
93
+
94
+ # java
95
+ *.class
96
+ *.jar
97
+ *.war
98
+ *.ear
99
+ *.jsa
100
+ /.mtj.tmp/
101
+ /hs_err_pid*
102
+
103
+ # node modules
104
+ node_modules/
105
+
106
+ # ppt, pdf
107
+ /doc/odp/*.pdf
108
+ /doc/odp/*.ppt
109
+
110
+ # intel compiler pgo
111
+ *.dyn
112
+ *.dpi
113
+ # *.lock
114
+
115
+ # dwarf dynamic debug info
116
+ *.dwo
117
+
118
+ # locks by open office
119
+ .~lock.*
120
+
121
+ # haskell
122
+ *.hi
123
+
124
+ # clojure
125
+ .cpcache/
126
+
127
+ # repositries of deb packages
128
+ /deb/mypackage/DEBIAN/control
129
+
130
+ # generic
131
+ *.log
132
+ *.swp
133
+
134
+ # scons
135
+ .sconsign.dblite
136
+
137
+ # DS_Store
138
+ .DS_Store
139
+
140
+ # Java stuff
141
+ .gradle/
142
+ target/
143
+ build/
144
+
145
+ # Jupyter notebooks
146
+ .ipynb_checkpoints/
147
+
148
+ # c#
149
+ # **/obj/ and **/bin/ match at any depth, which is what .NET output needs
150
+ # (src/<proj>/bin, src/<proj>/obj). The un-globbed [Oo]bj/ and [Bb]in/ that
151
+ # used to follow were redundant with these two and are gone.
152
+ **/obj/
153
+ **/bin/
154
+ # perl-meta keeps real Perl sources under perl/bin/Meta/, not build output.
155
+ !/perl/bin/
156
+
157
+ # flash/air
158
+ *.swf
159
+ *.air
160
+ *.apk
161
+ *.ipa
162
+ bin-debug/
163
+ bin-release/
164
+
165
+ # vscode
166
+ .vscode/
167
+
168
+ # qemu
169
+ *.qcow2
170
+ *.iso
171
+
172
+ # jetbrains and eclipse
173
+ .cache
174
+ .settings
175
+ .settings/
176
+ .classpath
177
+ .project
178
+ .project.xml
179
+ .idea/
180
+ build.properties
181
+
182
+ # spark
183
+ spark-warehouse/
184
+
185
+ # terraform
186
+ .terraform*
187
+ /.terraform/
188
+ terraform.tfstate
189
+ terraform.tfstate.backup
190
+ /terraform.lock.hcl
191
+
192
+ # scala
193
+ # Anchored: sbt's build dir is at the repo root. Unanchored, this also
194
+ # swallowed source directories named project/ nested under src/.
195
+ /project/
196
+
197
+ # rust
198
+ **/*.rs.bk
199
+ **/mutants.out*/
200
+ *.pdb
201
+ # Anchored: this is the root debug build dir. Unanchored (and without a
202
+ # trailing slash) it matched any file or directory named debug at any depth,
203
+ # including authored example content.
204
+ /debug/
205
+ /.rsconstruct_cache.json
206
+
207
+ # claude
208
+ /.claude/
209
+ /claude/
210
+ .claude/projects/
211
+ .claude/settings.local.json
212
+
213
+ # static sites
214
+ /_site/
215
+ /public/
216
+ /media/
217
+ /static/build_info.toml
218
+ /static/shared-themes/
219
+ /build_info.json
220
+
221
+ # manim
222
+ /.manim_scratch/
223
+
224
+ # terms
225
+ /terms.ambiguous/
226
+ /terms.single_meaning/
227
+
228
+ # stm32 demos
229
+ /STM32CubeWL/
230
+ /exercises/*/*/.obj/
231
+ /exercises/*/*/app*.elf
232
+ /exercises/*/*/app*.map
233
+ /exercises/*/*/firmware*.bin
234
+ /exercises/common/.hal_obj_cm0/
235
+ /exercises/common/.hal_obj_cm4/
236
+ /exercises/common/.rsc_hal_obj_cm0/
237
+ /exercises/common/.rsc_hal_obj_cm4/
238
+ /exercises/common/libhal_cm0.a
239
+ /exercises/common/libhal_cm4.a
240
+ /exercises/common/libhal_rsc_cm0.a
241
+ /exercises/common/libhal_rsc_cm4.a
242
+
243
+ # secrets
244
+ creds.txt
245
+
246
+ # rsconstruct
247
+ /.rsconstruct/
248
+
249
+ # generated website pieces (built into the Pages artifact by CI)
250
+ /docs/output/
251
+ /docs/index.html
252
+ # sphinx html output: the 14 py* repos render docs/ from sphinx/ and publish
253
+ # it as the Pages artifact, so none of it is committed. Listed path by path
254
+ # rather than as /docs/ because the rs* repos keep hand-written mdBook
255
+ # sources there, and several others keep authored html.
256
+ /docs/_modules/
257
+ /docs/_sources/
258
+ /docs/_static/
259
+ /docs/.buildinfo
260
+ /docs/objects.inv
261
+ /docs/searchindex.js
262
+ /docs/.nojekyll
263
+ # sphinx's top-level html. Not a blanket /docs/*.html: js-jschess and the
264
+ # site-personal-* repos keep hand-authored html under docs/. The fixed pages
265
+ # (genindex, modules, search) and the authored-rst pages some repos render
266
+ # (architecture, notes, ...) are listed by name; the one-page-per-module
267
+ # output is /docs/py*.html, since every package here is named py*.
268
+ /docs/architecture.html
269
+ /docs/cli-reference.html
270
+ /docs/development.html
271
+ /docs/genindex.html
272
+ /docs/modules.html
273
+ /docs/notes.html
274
+ /docs/overview.html
275
+ /docs/py*.html
276
+ /docs/quota-guide.html
277
+ /docs/search.html
278
+
279
+ # per-machine tool resolution cache (absolute paths, not portable)
280
+ /.tools.versions
@@ -1,6 +1,6 @@
1
1
  MIT License
2
2
 
3
- Copyright (c) 2022 Mark Veltzer
3
+ Copyright (c) 2026 Mark Veltzer <mark.veltzer@gmail.com>
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.4
1
+ Metadata-Version: 2.5
2
2
  Name: pygitpub
3
- Version: 0.0.58
3
+ Version: 0.0.60
4
4
  Summary: Pygitpub will help you work with github
5
5
  Project-URL: Homepage, https://github.com/veltzer/pygitpub
6
6
  Project-URL: Bug Tracker, https://github.com/veltzer/pygitpub/issues
@@ -19,9 +19,10 @@ Classifier: Operating System :: OS Independent
19
19
  Classifier: Programming Language :: Python
20
20
  Classifier: Programming Language :: Python :: 3
21
21
  Classifier: Programming Language :: Python :: 3 :: Only
22
- Classifier: Programming Language :: Python :: 3.12
22
+ Classifier: Programming Language :: Python :: 3.14
23
23
  Classifier: Topic :: Utilities
24
- Requires-Python: >=3.12
24
+ Requires-Python: >=3.14
25
+ Requires-Dist: lupa
25
26
  Requires-Dist: pyapikey
26
27
  Requires-Dist: pygithub
27
28
  Requires-Dist: pylogconf
@@ -29,6 +30,9 @@ Requires-Dist: pytconf
29
30
  Requires-Dist: pyyaml
30
31
  Description-Content-Type: text/markdown
31
32
 
33
+ <!-- This file is generated. Do not edit it by hand: your changes will be
34
+ lost on the next build. Edit the template it is rendered from, or the
35
+ values that template reads, and build again. -->
32
36
  # *pygitpub* project by Mark Veltzer
33
37
 
34
38
  description: Pygitpub will help you work with github
@@ -37,7 +41,7 @@ project website: https://veltzer.github.io/pygitpub
37
41
 
38
42
  author: Mark Veltzer
39
43
 
40
- version: 0.0.58
44
+ version: 0.0.60
41
45
 
42
46
  ![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)
43
47
 
@@ -63,12 +67,10 @@ version: 0.0.58
63
67
  ![PyPI - Downloads](https://img.shields.io/pypi/dw/pygitpub)
64
68
  ![PyPI - Downloads](https://img.shields.io/pypi/dm/pygitpub)
65
69
 
66
-
67
-
68
70
  ## contact me
71
+
69
72
  [mailto](mailto:mark.veltzer@gmail.com)
70
- ![gitter](https://img.shields.io/gitter/room/veltzer/mark.veltzer)
71
73
  ![discord](https://img.shields.io/discord/719336281624281119)
72
74
  ![discord](https://img.shields.io/discord/719336282194444302)
73
75
 
74
- Mark Veltzer, Copyright © 2022, 2023, 2024, 2025
76
+ Mark Veltzer, Copyright © 2022, 2023, 2024, 2025, 2026
@@ -1,3 +1,6 @@
1
+ <!-- This file is generated. Do not edit it by hand: your changes will be
2
+ lost on the next build. Edit the template it is rendered from, or the
3
+ values that template reads, and build again. -->
1
4
  # *pygitpub* project by Mark Veltzer
2
5
 
3
6
  description: Pygitpub will help you work with github
@@ -6,7 +9,7 @@ project website: https://veltzer.github.io/pygitpub
6
9
 
7
10
  author: Mark Veltzer
8
11
 
9
- version: 0.0.58
12
+ version: 0.0.60
10
13
 
11
14
  ![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)
12
15
 
@@ -32,12 +35,10 @@ version: 0.0.58
32
35
  ![PyPI - Downloads](https://img.shields.io/pypi/dw/pygitpub)
33
36
  ![PyPI - Downloads](https://img.shields.io/pypi/dm/pygitpub)
34
37
 
35
-
36
-
37
38
  ## contact me
39
+
38
40
  [mailto](mailto:mark.veltzer@gmail.com)
39
- ![gitter](https://img.shields.io/gitter/room/veltzer/mark.veltzer)
40
41
  ![discord](https://img.shields.io/discord/719336281624281119)
41
42
  ![discord](https://img.shields.io/discord/719336282194444302)
42
43
 
43
- Mark Veltzer, Copyright © 2022, 2023, 2024, 2025
44
+ Mark Veltzer, Copyright © 2022, 2023, 2024, 2025, 2026
@@ -4,8 +4,8 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "pygitpub"
7
- version = "0.0.58"
8
- requires-python = ">=3.12"
7
+ version = "0.0.60"
8
+ requires-python = ">=3.14"
9
9
  authors = [
10
10
  { name = "Mark Veltzer", email = "mark.veltzer@gmail.com" }
11
11
  ]
@@ -26,7 +26,7 @@ classifiers = [
26
26
  "Programming Language :: Python",
27
27
  "Programming Language :: Python :: 3",
28
28
  "Programming Language :: Python :: 3 :: Only",
29
- "Programming Language :: Python :: 3.12",
29
+ "Programming Language :: Python :: 3.14",
30
30
  "Topic :: Utilities",
31
31
  "License :: OSI Approved :: MIT License",
32
32
  ]
@@ -36,6 +36,16 @@ dependencies = [
36
36
  "PyGithub",
37
37
  "pyapikey",
38
38
  "pyyaml",
39
+ "lupa",
40
+ ]
41
+
42
+ [dependency-groups]
43
+ dev = [
44
+ "mypy",
45
+ "pylint",
46
+ "pytest",
47
+ "ruff",
48
+ "sphinx",
39
49
  ]
40
50
 
41
51
  [project.urls]
@@ -74,10 +84,23 @@ exclude = [
74
84
  ]
75
85
 
76
86
  [tool.mypy]
77
- python_version = "3.12"
87
+ python_version = "3.14"
78
88
  warn_return_any = true
79
89
  warn_unused_configs = true
90
+ mypy_path = "src:python:scripts"
91
+ explicit_package_bases = true
92
+ namespace_packages = true
80
93
 
94
+ # Third-party libraries without type stubs; `foo.*` also matches `foo` itself.
95
+ [[tool.mypy.overrides]]
96
+ module = [
97
+ "github.*",
98
+ "lupa.*",
99
+ "pyapikey.*",
100
+ "pylogconf.*",
101
+ "pytconf.*",
102
+ ]
103
+ ignore_missing_imports = true
81
104
  [tool.hatch.envs.default]
82
105
  installer = "uv"
83
106
 
@@ -3,6 +3,14 @@ All configurations for pygitpub
3
3
  """
4
4
  from pytconf import Config, ParamCreator
5
5
 
6
+ # The values github accepts in the "affiliation" parameter of its
7
+ # "list repositories" api. They are sent over the wire verbatim.
8
+ AFFILIATIONS = [
9
+ "owner",
10
+ "collaborator",
11
+ "organization_member",
12
+ ]
13
+
6
14
 
7
15
  class ConfigGithub(Config):
8
16
  """
@@ -40,10 +48,19 @@ class ConfigAlgo(Config):
40
48
  help_string="Only include repos owned by this owner (None for dont mind)",
41
49
  default=None,
42
50
  )
51
+ affiliation = ParamCreator.create_str(
52
+ help_string="Comma separated list of how you are affiliated with the repos to work on"
53
+ f" (any of {','.join(AFFILIATIONS)})",
54
+ default="owner",
55
+ )
43
56
  show_extra = ParamCreator.create_bool(
44
57
  help_string="Show extra git repos lying around?",
45
58
  default=True,
46
59
  )
60
+ base_dir = ParamCreator.create_str(
61
+ help_string="Base directory under which repos are cloned",
62
+ default="~/git",
63
+ )
47
64
 
48
65
 
49
66
  class ConfigOutput(Config):
@@ -2,26 +2,52 @@
2
2
  main entry point to the program
3
3
  """
4
4
 
5
+ import json
5
6
  import os
6
7
  import subprocess
7
8
  import sys
8
- import json
9
-
10
- import pylogconf.core
11
- from pytconf import register_main, config_arg_parse_and_launch, register_endpoint
12
9
 
13
10
  import github
14
11
  import pyapikey
15
- from pygitpub.configs import ConfigGithub, ConfigOutput, ConfigAlgo
12
+ import pylogconf.core
13
+ from pytconf import config_arg_parse_and_launch, register_endpoint, register_main
14
+ from pytconf.pydoc import get_first_line
15
+ from pytconf.registry import the_registry
16
+
16
17
  import pygitpub.static
18
+ from pygitpub.configs import AFFILIATIONS, ConfigAlgo, ConfigGithub, ConfigOutput
19
+ from pygitpub.utils.lua import config_path, load_lua_file
17
20
  from pygitpub.utils.misc import delete, get_all_git_repos
18
- from pygitpub.utils.importlib import import_file
21
+
22
+
23
+ def get_base_dir() -> str:
24
+ base_dir: str = ConfigAlgo.base_dir
25
+ return os.path.expanduser(base_dir)
26
+
27
+
28
+ def get_repo_folder(repo) -> str:
29
+ return os.path.join(get_base_dir(), repo.name)
30
+
31
+
32
+ def get_affiliation() -> str:
33
+ """ validate the affiliation parameter and return it in the form github wants """
34
+ values = [x.strip() for x in ConfigAlgo.affiliation.split(",")]
35
+ values = [x for x in values if x != ""]
36
+ if not values:
37
+ raise ValueError("affiliation must not be empty")
38
+ for value in values:
39
+ if value not in AFFILIATIONS:
40
+ raise ValueError(f"bad affiliation [{value}], must be one of [{','.join(AFFILIATIONS)}]")
41
+ return ",".join(values)
19
42
 
20
43
 
21
44
  def yield_repos():
22
45
  apikey = pyapikey.get_key(ConfigGithub.apikey)
23
46
  g = github.Github(login_or_token=apikey)
24
- for repo in g.get_user().get_repos():
47
+ # github filters by affiliation server side, so repos we are not interested
48
+ # in (org repos, by default) are never fetched in the first place
49
+ affiliation = get_affiliation()
50
+ for repo in g.get_user().get_repos(affiliation=affiliation):
25
51
  reason = "no reason set"
26
52
  skip = False
27
53
  if not ConfigAlgo.fork and repo.fork:
@@ -53,32 +79,31 @@ def yield_repos():
53
79
  def fix_metadata() -> None:
54
80
  orig_folder = os.getcwd()
55
81
  for repo in yield_repos():
56
- owner = repo.owner.login
57
- folder = os.path.join(owner, repo.name)
82
+ folder = get_repo_folder(repo)
58
83
  if not os.path.isdir(folder):
59
84
  # print(f"folder [{folder}] does not exist, continuing...")
60
85
  continue
61
86
  os.chdir(folder)
62
87
  # print(f"doing [{folder}]...")
63
- file_path = "config/project.py"
88
+ file_path = config_path("project")
64
89
  if not os.path.isfile(file_path):
65
90
  os.chdir(orig_folder)
66
91
  continue
67
- mod = import_file(file_path)
68
- if not hasattr(mod, "description_short"):
92
+ project = load_lua_file(file_path)
93
+ if "DESCRIPTION_SHORT" not in project:
69
94
  os.chdir(orig_folder)
70
95
  continue
71
- description_short = getattr(mod, "description_short")
96
+ description_short = project["DESCRIPTION_SHORT"]
72
97
  if description_short != repo.description:
73
98
  print(f"in {folder}...")
74
99
  print(f"description_short is [{description_short}]")
75
100
  print(f"repo.description is [{repo.description}]")
76
101
  repo.edit(description=description_short)
77
- if not hasattr(mod, "keywords"):
102
+ if "KEYWORDS" not in project:
78
103
  print(f"{folder} no keywords")
79
104
  os.chdir(orig_folder)
80
105
  continue
81
- keywords = getattr(mod, "keywords")
106
+ keywords = project["KEYWORDS"]
82
107
  if set(keywords) != set(repo.get_topics()):
83
108
  print(f"in {folder}...")
84
109
  print(f"keywords is [{keywords}]")
@@ -273,14 +298,15 @@ def runs_show_not_success() -> None:
273
298
  description="Pull all projects from github",
274
299
  configs=[
275
300
  ConfigGithub,
301
+ ConfigAlgo,
276
302
  ],
277
303
  )
278
304
  def pull_all() -> None:
279
305
  orig_folder = os.getcwd()
306
+ base_dir = get_base_dir()
280
307
  for repo in yield_repos():
281
- owner = repo.owner.login
282
308
  project = repo.name
283
- folder = os.path.join(owner, repo.name)
309
+ folder = get_repo_folder(repo)
284
310
  if os.path.isdir(folder):
285
311
  if not os.path.isfile(os.path.join(folder, ".skip")):
286
312
  print(f"project [{project}] exists, pulling it...")
@@ -296,13 +322,15 @@ def pull_all() -> None:
296
322
  else:
297
323
  print(f"project [{project}] exists, skipping it because of .skip file...")
298
324
  else:
299
- print(f"project [{project}] does not exists, cloning it from [{repo.ssh_url}]...")
325
+ print(f"project [{project}] does not exists, cloning it from [{repo.ssh_url}] to [{folder}]...")
326
+ os.makedirs(base_dir, exist_ok=True)
300
327
  subprocess.check_call(
301
328
  [
302
329
  "git",
303
330
  "clone",
304
331
  repo.ssh_url,
305
- ]
332
+ ],
333
+ cwd=base_dir,
306
334
  )
307
335
 
308
336
 
@@ -315,10 +343,10 @@ def pull_all() -> None:
315
343
  )
316
344
  def clone_all() -> None:
317
345
  all_repo_folders = set()
346
+ base_dir = get_base_dir()
318
347
  for repo in yield_repos():
319
- owner = repo.owner.login
320
348
  project = repo.name
321
- folder = os.path.join(owner, repo.name)
349
+ folder = get_repo_folder(repo)
322
350
  all_repo_folders.add(folder)
323
351
  # print(f"considering [{project}] from [{repo.ssh_url}]...")
324
352
  if os.path.isfile(folder):
@@ -329,21 +357,20 @@ def clone_all() -> None:
329
357
  continue
330
358
  if ConfigAlgo.dryrun:
331
359
  continue
332
- print(f"cloning [{owner}:{project}] from [{repo.ssh_url}] to [{folder}]")
333
- if not os.path.isdir(owner):
334
- os.mkdir(owner)
360
+ print(f"cloning [{project}] from [{repo.ssh_url}] to [{folder}]")
361
+ os.makedirs(base_dir, exist_ok=True)
335
362
  subprocess.check_call(
336
363
  [
337
364
  "git",
338
365
  "clone",
339
366
  repo.ssh_url,
340
367
  ],
341
- cwd=owner,
368
+ cwd=base_dir,
342
369
  stdout=subprocess.DEVNULL,
343
370
  stderr=subprocess.DEVNULL,
344
371
  )
345
372
  if ConfigAlgo.show_extra:
346
- disk_repos = get_all_git_repos()
373
+ disk_repos = get_all_git_repos(base_dir)
347
374
  extra_repos = disk_repos - all_repo_folders
348
375
  if extra_repos:
349
376
  print("extra repos follow:")
@@ -368,6 +395,34 @@ def workflows_run() -> None:
368
395
  print(f"{ret}")
369
396
 
370
397
 
398
+ @register_endpoint(
399
+ description="Show all configuration options and their current values",
400
+ configs=[
401
+ ConfigGithub,
402
+ ConfigAlgo,
403
+ ConfigOutput,
404
+ ],
405
+ )
406
+ def config() -> None:
407
+ for config_class in [ConfigGithub, ConfigAlgo, ConfigOutput]:
408
+ print(f"{config_class.__name__}: {get_first_line(config_class, 'undocumented')}")
409
+ for name, param in the_registry.yield_name_data_for_config(config_class):
410
+ value = getattr(config_class, name)
411
+ if param.required and value is param.default:
412
+ value_str = "MANDATORY (not set)"
413
+ else:
414
+ value_str = param.t2s(value)
415
+ print(f" {name} [{param.get_type_name()}]: {value_str}")
416
+ print(f" {param.help_string}")
417
+ if not param.required:
418
+ print(f" default: {param.t2s(param.default)}")
419
+ more_help = param.more_help()
420
+ if more_help is not None:
421
+ print(f" {more_help}")
422
+ print()
423
+ print(f"repos are cloned to and pulled from: {get_base_dir()}/<repo-name>")
424
+
425
+
371
426
  @register_main(
372
427
  main_description=pygitpub.static.DESCRIPTION,
373
428
  app_name=pygitpub.static.APP_NAME,
@@ -1,5 +1,5 @@
1
1
  """ version which can be consumed from within the module """
2
- VERSION_STR = "0.0.58"
2
+ VERSION_STR = "0.0.60"
3
3
  DESCRIPTION = "Pygitpub will help you work with github"
4
4
  APP_NAME = "pygitpub"
5
5
  LOGGER_NAME = "pygitpub"
@@ -0,0 +1,68 @@
1
+ """
2
+ Load "config/*.lua" data files.
3
+
4
+ The config files used to be python modules loaded via importlib; they are now
5
+ lua data files (config/project.lua and friends). A file is run in a fresh lua
6
+ runtime and the globals it declares are returned as a plain dict.
7
+ """
8
+
9
+ import os.path
10
+ from typing import Any
11
+
12
+ # lupa exposes LuaRuntime through a module level __getattr__ that points at the
13
+ # bundled lua version (lupa.lua54, lupa.lua55, ...), so it cannot be imported
14
+ # by name. Going through the package attribute works for any bundled version.
15
+ import lupa
16
+
17
+ CONFIG_FOLDER = "config"
18
+
19
+
20
+ def lua_to_python(value: Any) -> Any:
21
+ """
22
+ Recursively convert a lua value into its python equivalent.
23
+
24
+ Lua has a single table type, so a table is a list if its keys are exactly
25
+ 1..n and a dict otherwise. An empty table becomes an empty list, which
26
+ matches how the config files use them.
27
+ """
28
+ if not hasattr(value, "values"):
29
+ return value
30
+ keys = list(value.keys())
31
+ if keys == list(range(1, len(keys) + 1)):
32
+ return [lua_to_python(x) for x in value.values()]
33
+ return {k: lua_to_python(value[k]) for k in keys}
34
+
35
+
36
+ def config_path(name: str) -> str:
37
+ """ return the path of a config file by its short name ("project") """
38
+ return os.path.join(CONFIG_FOLDER, f"{name}.lua")
39
+
40
+
41
+ def load_lua_file(path: str) -> dict[str, Any]:
42
+ """
43
+ Run a lua file and return the globals it declared as a dict.
44
+
45
+ Raises FileNotFoundError if the file does not exist.
46
+ """
47
+ if not os.path.isfile(path):
48
+ raise FileNotFoundError(path)
49
+ runtime = lupa.LuaRuntime()
50
+ with open(path, encoding="utf-8") as stream:
51
+ runtime.execute(stream.read())
52
+ globals_table = runtime.globals()
53
+ values = {}
54
+ for key in globals_table:
55
+ # skip the lua standard library, we only want what the file declared
56
+ if key in _LUA_BUILTINS:
57
+ continue
58
+ values[key] = lua_to_python(globals_table[key])
59
+ return values
60
+
61
+
62
+ _LUA_BUILTINS = frozenset({
63
+ "_G", "_VERSION", "assert", "collectgarbage", "coroutine", "debug", "dofile",
64
+ "error", "getmetatable", "io", "ipairs", "load", "loadfile", "math", "next",
65
+ "os", "package", "pairs", "pcall", "print", "python", "rawequal", "rawget",
66
+ "rawlen", "rawset", "require", "select", "setmetatable", "string", "table",
67
+ "tonumber", "tostring", "type", "utf8", "warn", "xpcall",
68
+ })
@@ -1,9 +1,9 @@
1
1
  """ misc.py """
2
2
 
3
+ import glob
3
4
  import logging
4
5
  import os
5
6
  import os.path
6
- import glob
7
7
 
8
8
  from pygitpub import LOGGER_NAME
9
9
 
@@ -28,8 +28,8 @@ def delete(workflow_run):
28
28
  return status == 204
29
29
 
30
30
 
31
- def get_all_git_repos() -> set[str]:
32
- """ we use */*/.git here with recursive=False (which is the default)
31
+ def get_all_git_repos(base_dir: str) -> set[str]:
32
+ """ we use */.git under base_dir with recursive=False (which is the default)
33
33
  because otherwise we would find submodules too
34
34
  """
35
- return {os.path.dirname(x) for x in glob.glob("*/*/.git")}
35
+ return {os.path.dirname(x) for x in glob.glob(os.path.join(base_dir, "*/.git"))}
@@ -1,163 +0,0 @@
1
- # generated
2
- /build/
3
- /toolchain/
4
- /dist/
5
- /target/
6
- /out/
7
- *.out
8
- /.mypy_cache/
9
-
10
- # folders to git ignore
11
- /*.gi
12
-
13
- # gems
14
- /gems/
15
-
16
- # hive
17
- derby.log
18
- dfs/
19
- metastore_db/
20
-
21
- # c, c++ stuff
22
- *.o
23
- *.so
24
- *.a
25
- *.dis
26
- /Makefile.bak
27
- *.o.ur-safe
28
- *.oo
29
- *.elf
30
- *.dep
31
- /core.*
32
- lib*.so.*
33
- *.gch
34
- *.pchi
35
- *.gcda
36
-
37
- # kernel stuff
38
- *.ko
39
- # *.mod
40
- *.mod.c
41
- *.mod.cmd
42
- .*.o.cmd
43
- .*.ko.cmd
44
- .*.o.tmp
45
- .*.o.d
46
- .tmp_versions
47
- Module.symvers
48
- modules.order
49
- *.objdump
50
- /src/kernel/.*.tmp
51
- .modules.*.cmd
52
- .Module.*.cmd
53
- /flags.cfg
54
- # device tree binary files and binary overlay files
55
- *.dtb
56
- *.dtbo
57
-
58
- # sphinx
59
- /docs/_build/
60
- /docs/.doctrees/environment.pickle
61
-
62
- # installations
63
- /external/
64
- /install/
65
- *.tar.gz
66
-
67
- # python
68
- __pycache__/
69
- *.py[oc]
70
- /wheel/
71
- /.venv/
72
- .coverage
73
- .coverage.*
74
- coverage.xml
75
- *.egg-info/
76
-
77
- # mine
78
- /.pydmt.build.errors
79
- /.pydmt/
80
- *.stamp
81
- *.stamp.*
82
-
83
- # java
84
- *.class
85
- *.jar
86
- *.war
87
- *.ear
88
- *.jsa
89
- /.mtj.tmp/
90
- /hs_err_pid*
91
-
92
- # node modules
93
- node_modules/
94
-
95
- # ppt, pdf
96
- /doc/odp/*.pdf
97
- /doc/odp/*.ppt
98
-
99
- # intel compiler pgo
100
- *.dyn
101
- *.dpi
102
- # *.lock
103
-
104
- # dwarf dynamic debug info
105
- *.dwo
106
-
107
- # locks by open office
108
- .~lock.*
109
-
110
- # haskell
111
- *.hi
112
-
113
- # repositries of deb packages
114
- /deb/mypackage/DEBIAN/control
115
-
116
- # generic
117
- *.log
118
-
119
- # scons
120
- .sconsign.dblite
121
-
122
- # DS_Store
123
- .DS_Store
124
-
125
- # Java stuff
126
- .gradle/
127
- target/
128
- build/
129
-
130
- # Jupyter notebooks
131
- .ipynb_checkpoints/
132
-
133
- # c#
134
- **/obj/
135
- **/bin/
136
-
137
- # vscode
138
- .vscode/
139
-
140
- # qemu
141
- *.qcow2
142
- *.iso
143
-
144
- # jetbrains and eclipse
145
- .cache
146
- .settings
147
- .classpath
148
- .project
149
- .project.xml
150
- .idea/
151
- build.properties
152
-
153
- # spark
154
- spark-warehouse/
155
-
156
- # terraform
157
- .terraform*
158
- terraform.tfstate
159
- terraform.tfstate.backup
160
-
161
- # scala
162
- target/
163
- project/
@@ -1,16 +0,0 @@
1
- """ importlib.py """
2
-
3
- import importlib.util
4
-
5
-
6
- def module_exists(module_name):
7
- """ return if a module exists or not """
8
- spec = importlib.util.find_spec(module_name)
9
- return spec is not None
10
-
11
-
12
- def import_file(file_path):
13
- spec = importlib.util.spec_from_file_location("foo", file_path)
14
- mod = importlib.util.module_from_spec(spec)
15
- spec.loader.exec_module(mod)
16
- return mod