Skip to content

Commit 6d4e02b

Browse files
committed
Add first step for a python ynh-dev
1 parent 0e77bd1 commit 6d4e02b

File tree

9 files changed

+882
-125
lines changed

9 files changed

+882
-125
lines changed

.gitignore

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,9 @@
1+
# Python stuff
2+
__pycache__/
3+
.*_cache/
4+
uv.lock
5+
.python-version
6+
17
# Apps
28
*_ynh
39

@@ -11,6 +17,7 @@ Vagrantfile
1117
moulinette
1218
yunohost
1319
yunohost-admin
20+
yunohost-portal
1421
ssowat
1522

1623
# Folders

custom-catalog/catalog_manager.py

Lines changed: 0 additions & 125 deletions
This file was deleted.

pyproject.toml

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
[project]
2+
name = "ynh-dev"
3+
version = "2.0"
4+
description = "Yunohost dev environment manager"
5+
readme = "README.md"
6+
authors = [
7+
{name = "YunoHost", email = "yunohost@yunohost.org"}
8+
]
9+
requires-python = ">=3.11"
10+
11+
dependencies = [
12+
"pyinotify",
13+
]
14+
15+
16+
[project.scripts]
17+
ynh-dev = "ynh-dev:main"
18+
19+
[dependency-groups]
20+
lint = [
21+
"ruff",
22+
"mypy>=1.18",
23+
]
24+
25+
[tool.uv]
26+
package = false
27+
28+
[tool.ruff]
29+
line-length = 120
30+
31+
[tool.ruff.lint]
32+
select = [
33+
"YTT", # flake8-2020
34+
"ANN", # flake8-annotations
35+
"BLE", # flake8-blind-except
36+
"B", # flake8-bugbear
37+
"A", # flake8-builtins
38+
"C4", # flake8-comprehensions
39+
"DTZ", # flake8-datetimez
40+
"ISC", # flake8-implicit-str-concat
41+
"ICN", # flake8-import-conventions
42+
# "LOG", # flake8-logging
43+
# "G", # flake8-logging-format
44+
"PIE", # flake8-pie
45+
"Q", # flake8-quotes
46+
"RET", # flake8-return
47+
"SIM", # flake8-simplify
48+
"SLOT", # flake8-slots
49+
"PTH", # flake8-use-pathlib
50+
"FLY", # flynt
51+
"I", # isort
52+
"N", # pep8-naming
53+
"PERF", # Perflint
54+
"E", # pycodestyle
55+
"W", # pycodestyle
56+
"F", # Pyflakes
57+
"PL", # Pylint
58+
"UP", # pyupgrade
59+
"FURB", # refurb
60+
"RUF", # Ruff-specific rules
61+
"TRY", # tryceratops
62+
# "D",
63+
]
64+
ignore = [
65+
"ANN401", # any-type
66+
"COM812", # missing-trailing-comma
67+
"D203", # incorrect-blank-line-before-class
68+
"PLR0911", # too-many-return-statements
69+
"PLR0912", # too-many-branches
70+
"PLR0913", # too-many-arguments
71+
"PLR0915", # too-many-statements
72+
"PLR2004", # magic-value-comparison
73+
"UP009", # utf8-encoding-declaration
74+
"TRY003", # raise-vanilla-args
75+
"D100", # undocumented-public-module
76+
"D104", # undocumented-public-package
77+
"D105", # undocumented-magic-method
78+
"D200", # unnecessary-multiline-docstring
79+
"D212", # multi-line-summary-first-line
80+
"D401", # non-imperative-mood
81+
]

ynh-dev.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
#!/usr/bin/env python3
2+
3+
import os
4+
5+
6+
def main() -> None:
7+
if "container" in os.environ:
8+
from ynh_dev.ynh_dev_guest import main_container # noqa: PLC0415
9+
main_container()
10+
else:
11+
from ynh_dev.ynh_dev_host import main_host # noqa: PLC0415
12+
main_host()
13+
14+
15+
if __name__ == "__main__":
16+
main()

ynh_dev/catalog_manager.py

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
#!/usr/bin/python3
2+
3+
import json
4+
import os
5+
import sys
6+
import time
7+
from collections import OrderedDict
8+
from pathlib import Path
9+
from typing import Any
10+
11+
import toml
12+
import yaml
13+
14+
my_env = os.environ.copy().update({"GIT_TERMINAL_PROMPT": "0"})
15+
16+
17+
class Catalog:
18+
def __init__(self) -> None:
19+
self.CATALOG_LIST_PATH = Path("/etc/yunohost/apps_catalog.yml")
20+
self.DEFAULT_APPS_FOLDER = Path("/ynh-dev/custom-catalog/")
21+
self.DEFAULT_APP_BRANCH = "master"
22+
self.DEFAULT_CATALOG = [{"id": "default", "url": "https://app.yunohost.org/default/"}]
23+
# assert self.CATALOG_LIST_PATH.exists(), f"Catalog list yaml file '{self.CATALOG_LIST_PATH} does not exists"
24+
25+
def build(self, folder: Path | None = None) -> None:
26+
folder = folder or self.DEFAULT_APPS_FOLDER
27+
assert folder.exists(), f"'{folder}' doesn't exist."
28+
apps_list_path = folder / "apps.json"
29+
assert apps_list_path.exists(), "no 'apps.json' app list found."
30+
apps_list = json.load(apps_list_path.open())
31+
32+
apps = {}
33+
fail = False
34+
35+
for app, infos in apps_list.items():
36+
app = app.lower()
37+
try:
38+
app_dict = self.build_app_dict(app, infos, folder)
39+
except Exception as e:
40+
print(f"[\033[1m\033[31mFAIL\033[00m] Processing {app} failed: {e!s}")
41+
fail = True
42+
continue
43+
44+
apps[app_dict["id"]] = app_dict
45+
46+
# We also remove the app install question and resources parts which aint needed anymore by webadmin etc (or at least we think ;P)
47+
for app in apps.values():
48+
if "manifest" in app and "install" in app["manifest"]:
49+
del app["manifest"]["install"]
50+
if "manifest" in app and "resources" in app["manifest"]:
51+
del app["manifest"]["resources"]
52+
53+
data = {
54+
"apps": apps,
55+
"from_api_version": 3,
56+
}
57+
58+
output_file = folder / "catalog.json"
59+
json.dump(data, output_file.open("w"), sort_keys=True, indent=2)
60+
61+
if fail:
62+
sys.exit(1)
63+
64+
def build_app_dict(self, app: str, infos: dict[str, Any], folder: Path) -> dict[str, Any]:
65+
app_folder = folder / f"{app}_ynh"
66+
67+
# Build the dict with all the infos
68+
manifest_toml = app_folder / "manifest.toml"
69+
manifest_json = app_folder / "manifest.json"
70+
if manifest_toml.exists():
71+
manifest = toml.load(manifest_toml.open(), _dict=OrderedDict)
72+
else:
73+
manifest = json.load(manifest_json.open(), _dict=OrderedDict)
74+
75+
return {
76+
"id": app,
77+
"git": {
78+
"branch": infos.get("branch", self.DEFAULT_APP_BRANCH),
79+
"revision": infos.get("revision", "HEAD"),
80+
"url": f"file://{app_folder}",
81+
},
82+
"lastUpdate": time.time(),
83+
"manifest": manifest,
84+
"state": infos.get("state", "notworking"),
85+
"level": infos.get("level", -1),
86+
"maintained": infos.get("maintained", True),
87+
# "high_quality": infos.get("high_quality", False),
88+
# "featured": infos.get("featured", False),
89+
"category": infos.get("category"),
90+
"subtags": infos.get("subtags", []),
91+
"potential_alternative_to": infos.get("potential_alternative_to", []),
92+
"antifeatures": list(set(list(manifest.get("antifeatures", {}).keys()) + infos.get("antifeatures", []))),
93+
}
94+
95+
def reset(self) -> None:
96+
yaml.safe_dump(self.DEFAULT_CATALOG, self.CATALOG_LIST_PATH.open("w"), default_flow_style=False)
97+
98+
def add(self) -> None:
99+
if not self.CATALOG_LIST_PATH.exists():
100+
self.reset()
101+
catalog_list = yaml.safe_load(self.CATALOG_LIST_PATH.open())
102+
ids = [catalog["id"] for catalog in catalog_list]
103+
if "custom" not in ids:
104+
catalog_list.append({"id": "custom", "url": None})
105+
yaml.safe_dump(catalog_list, self.CATALOG_LIST_PATH.open("w"), default_flow_style=False)
106+
107+
def override(self) -> None:
108+
catalog_list = [{"id": "custom", "url": None}]
109+
yaml.safe_dump(catalog_list, self.CATALOG_LIST_PATH.open("w"), default_flow_style=False)

0 commit comments

Comments
 (0)