about summary refs log tree commit diff
path: root/nixpkgs/pkgs/tools/networking/maubot/plugins/update.py
diff options
context:
space:
mode:
Diffstat (limited to 'nixpkgs/pkgs/tools/networking/maubot/plugins/update.py')
-rwxr-xr-xnixpkgs/pkgs/tools/networking/maubot/plugins/update.py200
1 files changed, 200 insertions, 0 deletions
diff --git a/nixpkgs/pkgs/tools/networking/maubot/plugins/update.py b/nixpkgs/pkgs/tools/networking/maubot/plugins/update.py
new file mode 100755
index 000000000000..a430753870fb
--- /dev/null
+++ b/nixpkgs/pkgs/tools/networking/maubot/plugins/update.py
@@ -0,0 +1,200 @@
+#!/usr/bin/env nix-shell
+#!nix-shell -i python3 -p git nurl "(python3.withPackages (ps: with ps; [ toml gitpython requests ruamel-yaml ]))"
+
+import git
+import json
+import os
+import subprocess
+import ruamel.yaml
+import sys
+import toml
+import zipfile
+
+from typing import Dict, List
+
+HOSTNAMES = {
+    'git.skeg1.se': 'gitlab',
+    'edugit.org': 'gitlab',
+    'codeberg.org': 'gitea',
+}
+PLUGINS: Dict[str, dict] = {}
+
+yaml = ruamel.yaml.YAML(typ='safe')
+
+TMP = os.environ.get('TEMPDIR', '/tmp')
+
+def process_repo(path: str, official: bool):
+    global PLUGINS
+    with open(path, 'rt') as f:
+        data = yaml.load(f)
+    name, repourl, license, desc = data['name'], data['repo'], data['license'], data['description']
+    origurl = repourl
+    if '/' in name or ' ' in name:
+        name = os.path.split(path)[-1].removesuffix('.yaml')
+    name = name.replace('_', '-')
+    if name in PLUGINS.keys():
+        raise ValueError(f'Duplicate plugin {name}, refusing to continue')
+    repodir = os.path.join(TMP, 'maubot-plugins', name)
+    plugindir = repodir
+    if '/tree/' in repourl:
+        repourl, rev_path = repourl.split('/tree/')
+        rev, subdir = rev_path.strip('/').split('/')
+        plugindir = os.path.join(plugindir, subdir)
+    else:
+        rev = None
+        subdir = None
+
+    if repourl.startswith('http:'):
+        repourl = 'https' + repourl[4:]
+    repourl = repourl.rstrip('/')
+    if not os.path.exists(repodir):
+        print('Fetching', name)
+        repo = git.Repo.clone_from(repourl + '.git', repodir)
+    else:
+        repo = git.Repo(repodir)
+    tags = sorted(repo.tags, key=lambda t: t.commit.committed_datetime)
+    tags = list(filter(lambda x: 'rc' not in str(x), tags))
+    if tags:
+        repo.git.checkout(tags[-1])
+        rev = str(tags[-1])
+    else:
+        rev = str(repo.commit('HEAD'))
+    ret: dict = {'attrs':{}}
+    if subdir:
+        ret['attrs']['postPatch'] = f'cd {subdir}'
+    domain, query = repourl.removeprefix('https://').split('/', 1)
+    hash = subprocess.run([
+        'nurl',
+        '--hash',
+        f'file://{repodir}',
+        rev
+    ], capture_output=True, check=True).stdout.decode('utf-8')
+    ret['attrs']['meta'] = {
+        'description': desc,
+        'homepage': origurl,
+    }
+    if domain.endswith('github.com'):
+        owner, repo = query.split('/')
+        ret['github'] = {
+            'owner': owner,
+            'repo': repo,
+            'rev': rev,
+            'hash': hash,
+        }
+        ret['attrs']['meta']['downloadPage'] = f'{repourl}/releases'
+        ret['attrs']['meta']['changelog'] = f'{repourl}/releases'
+        repobase = f'{repourl}/blob/{rev}'
+    elif HOSTNAMES.get(domain, 'gitea' if 'gitea.' in domain or 'forgejo.' in domain else None) == 'gitea':
+        owner, repo = query.split('/')
+        ret['gitea'] = {
+            'domain': domain,
+            'owner': owner,
+            'repo': repo,
+            'rev': rev,
+            'hash': hash,
+        }
+        repobase = f'{repourl}/src/commit/{rev}'
+        ret['attrs']['meta']['downloadPage'] = f'{repourl}/releases'
+        ret['attrs']['meta']['changelog'] = f'{repourl}/releases'
+    elif HOSTNAMES.get(domain, 'gitlab' if 'gitlab.' in domain else None) == 'gitlab':
+        owner, repo = query.split('/')
+        ret['gitlab'] = {
+            'owner': owner,
+            'repo': repo,
+            'rev': rev,
+            'hash': hash,
+        }
+        if domain != 'gitlab.com':
+            ret['gitlab']['domain'] = domain
+        repobase = f'{repourl}/-/blob/{rev}'
+    else:
+        raise ValueError(f'Is {domain} Gitea or Gitlab, or something else? Please specify in the Python script!')
+    if os.path.exists(os.path.join(plugindir, 'CHANGELOG.md')):
+        ret['attrs']['meta']['changelog'] = f'{repobase}/CHANGELOG.md'
+    if os.path.exists(os.path.join(plugindir, 'maubot.yaml')):
+        with open(os.path.join(plugindir, 'maubot.yaml'), 'rt') as f:
+            ret['manifest'] = yaml.load(f)
+    elif os.path.exists(os.path.join(plugindir, 'pyproject.toml')):
+        ret['isPoetry'] = True
+        with open(os.path.join(plugindir, 'pyproject.toml'), 'rt') as f:
+            data = toml.load(f)
+        deps = []
+        for key, val in data['tool']['poetry'].get('dependencies', {}).items():
+            if key in ['maubot', 'mautrix', 'python']:
+                continue
+            reqs = []
+            for req in val.split(','):
+                reqs.extend(poetry_to_pep(req))
+            deps.append(key + ', '.join(reqs))
+        ret['manifest'] = data['tool']['maubot']
+        ret['manifest']['id'] = data['tool']['poetry']['name']
+        ret['manifest']['version'] = data['tool']['poetry']['version']
+        ret['manifest']['license'] = data['tool']['poetry']['license']
+        if deps:
+            ret['manifest']['dependencies'] = deps
+    else:
+        raise ValueError(f'No maubot.yaml or pyproject.toml found in {repodir}')
+    # normalize non-spdx-conformant licenses this way
+    # (and fill out missing license info)
+    if 'license' not in ret['manifest'] or ret['manifest']['license'] in ['GPLv3', 'AGPL 3.0']:
+        ret['attrs']['meta']['license'] = license
+    elif ret['manifest']['license'] != license:
+        print(f"Warning: licenses for {repourl} don't match! {ret['manifest']['license']} != {license}")
+    if official:
+        ret['isOfficial'] = official
+    PLUGINS[name] = ret
+
+def next_incomp(ver_s: str) -> str:
+    ver = ver_s.split('.')
+    zero = False
+    for i in range(len(ver)):
+        try:
+            seg = int(ver[i])
+        except ValueError:
+            if zero:
+                ver = ver[:i]
+                break
+            continue
+        if zero:
+            ver[i] = '0'
+        elif seg:
+            ver[i] = str(seg + 1)
+            zero = True
+    return '.'.join(ver)
+
+def poetry_to_pep(ver_req: str) -> List[str]:
+    if '*' in ver_req:
+        raise NotImplementedError('Wildcard poetry versions not implemented!')
+    if ver_req.startswith('^'):
+        return ['>=' + ver_req[1:], '<' + next_incomp(ver_req[1:])]
+    if ver_req.startswith('~'):
+        return ['~=' + ver_req[1:]]
+    return [ver_req]
+
+def main():
+    cache_path = os.path.join(TMP, 'maubot-plugins')
+    if not os.path.exists(cache_path):
+        os.makedirs(cache_path)
+        git.Repo.clone_from('https://github.com/maubot/plugins.maubot.xyz', os.path.join(cache_path, '_repo'))
+    else:
+        pass
+
+    repodir = os.path.join(cache_path, '_repo')
+
+    for suffix, official in (('official', True), ('thirdparty', False)):
+        directory = os.path.join(repodir, 'data', 'plugins', suffix)
+        for plugin_name in os.listdir(directory):
+            process_repo(os.path.join(directory, plugin_name), official)
+
+    if os.path.isdir('pkgs/tools/networking/maubot/plugins'):
+        generated = 'pkgs/tools/networking/maubot/plugins/generated.json'
+    else:
+        script_dir = os.path.dirname(os.path.realpath(__file__))
+        generated = os.path.join(script_dir, 'generated.json')
+
+    with open(generated, 'wt') as file:
+        json.dump(PLUGINS, file, indent='  ', separators=(',', ': '), sort_keys=True)
+        file.write('\n')
+
+if __name__ == '__main__':
+    main()