forge: more generic forge types registry
3 files changed, 103 insertions(+), 91 deletions(-)

M hgext3rd/confman/commands.py
M hgext3rd/confman/configuration.py
M hgext3rd/confman/forge.py
M hgext3rd/confman/commands.py +1 -1
@@ 94,7 94,7 @@ def ensureconf(ui, repo, *args, **opts):
                     section, snaps, opts.get("keep_descendant", False)
                 )
             else:
-                forge.checkout(ui, section, rev, conf)
+                forge.checkout(ui, confman.forgetypes, section, rev, conf)
 
     else:
 

          
M hgext3rd/confman/configuration.py +6 -2
@@ 113,14 113,18 @@ class configurationmanager(object):
 
     @property
     def sections(self):
-        return self.confs.sections()
+        return [s for s in self.confs.sections() if not s.startswith(':confman:')]
+
+    @property
+    def forgetypes(self):
+        return self.confs[":confman:forgetypes"]
 
     def filtered_sections(self):
         include = set(self.opts.get('include_conf') or ())
         exclude = set(self.opts.get('exclude_conf') or ()) | self.failed
         exactmatch = set(self.args) or ()
 
-        for section in self.confs.sections():
+        for section in self.sections:
             if not _filtersection(section, exactmatch, include, exclude):
                 continue
             yield section

          
M hgext3rd/confman/forge.py +96 -88
@@ 1,3 1,4 @@ 
+import collections
 import os
 import shutil
 import tarfile

          
@@ 10,112 11,92 @@ import urllib.request
 # https://github.com/orus-io/elm-spa/archive/8a97e89fbc2933f3f53037bae53d730d7e496df2.zip
 
 
-TRACK_CSET = "track-cset"
-TRACK_BRANCH = "track-branch"
-TRACK_TAG = "track-tag"
-
-
-def match_github(pulluri, track):
-    if pulluri.startswith("https://github.com") or pulluri.startswith(
-        "git@github.com:"
-    ):
-        path = (
-            pulluri.removeprefix("https://github.com/")
-            .removeprefix("git@github.com:")
-            .removesuffix(".git")
-        )
-        return {
-            "track": track,
-            "path": path,
-            "name": path.split("/")[-1],
-        }
-    return None
+TRACK_CSET = "cset"
+TRACK_BRANCH = "branch"
+TRACK_TAG = "tag"
 
 
-def match_orus_io_hg(pulluri, track):
-    if pulluri.endswith(".git"):
-        return None
-    if pulluri.startswith("https://orus.io/") or pulluri.startswith(
-        "ssh://hg@orus.io/"
-    ):
-        path = pulluri.removeprefix("https://orus.io/").removeprefix(
-            "ssh://hg@orus.io/"
-        )
-        return {
-            "track": track,
-            "path": path,
-            "name": path.split("/")[-1],
-            "token": "?private_token=" + os.environ.get("CI_JOB_TOKEN")
-            if "CI_JOB_TOKEN" in os.environ
-            else "",
-        }
-    return None
+def github_vars(pulluri, track):
+    return {
+        "track": track,
+        "path": pulluri.path.lstrip("/"),
+        "name": pulluri.path.split("/")[-1],
+    }
 
 
-def match_orus_io_git(pulluri, track):
-    if not pulluri.endswith(".git"):
-        return None
-    if pulluri.startswith("https://orus.io/") or pulluri.startswith(
-        "ssh://hg@orus.io/"
-    ):
-        path = (
-            pulluri.removeprefix("https://orus.io/")
-            .removeprefix("ssh://hg@orus.io/")
-            .removesuffix(".git")
-        )
-        return {
-            "track": track,
-            "path": path,
-            "name": path.split("/")[-1],
-            "token": "?private_token=" + os.environ.get("CI_JOB_TOKEN")
-            if "CI_JOB_TOKEN" in os.environ
-            else "",
-        }
-    return None
+def gitlab_vars(pulluri, track):
+    token_env_name = "CONFMAN_CI_" + pulluri.host.upper().replace(".", "_") + "_TOKEN"
+    return {
+        "track": track,
+        "path": pulluri.path.lstrip("/"),
+        "name": pulluri.path.split("/")[-1],
+        "token": "?private_token=" + os.environ.get(token_env_name)
+        if token_env_name in os.environ
+        else "",
+    }
 
 
 known_forges = {
-    "github.com": {
-        "match": match_github,
-        TRACK_CSET: {
+    "github.com": "github",
+    "gitlab.com": "gitlab",
+    "foss.heptapod.net": "heptapod",
+}
+
+
+registry = {
+    "github": {
+        "vars": github_vars,
+        "git.cset": {
             "url": "https://github.com/%(path)s/archive/%(track)s.tar.gz",
             "prefix": "%(name)s-%(track)s",
         },
-        TRACK_BRANCH: {
+        "git.branch": {
             "url": "https://github.com/%(path)s/archive/refs/heads/%(track)s.tar.gz",
             "prefix": "%(name)s-%(track)s",
         },
-        TRACK_TAG: {
+        "git.tag": {
             "url": "https://github.com/%(path)s/archive/refs/tags/%(track)s.tar.gz",
             "prefix": "%(name)s-%(track)s",
         },
     },
-    "orus.io-hg": {
-        "match": match_orus_io_hg,
-        TRACK_CSET: {
+    "gitlab": {
+        "vars": gitlab_vars,
+        "git.cset": {
             "url": "https://orus.io/%(path)s/-/archive/%(track)s/%(name)s-%(track)s.tar.gz%(token)s",
             "prefix": "%(name)s-%(track)s",
         },
-        TRACK_BRANCH: {
-            "url": "https://orus.io/%(path)s/-/archive/branch/%(track)s/%(name)s-branch-%(track)s.tar.gz%(token)s",
-            "prefix": "%(name)s-branch-%(track)s",
+        "git.branch": {
+            "url": "https://orus.io/%(path)s/-/archive/%(track)s/%(name)s-%(track)s.tar.gz%(token)s",
+            "prefix": "%(name)s-%(track)s",
         },
-        TRACK_TAG: {
+        "git.tag": {
             "url": "https://orus.io/%(path)s/-/archive/%(track)s/%(name)s-%(track)s.tar.gz%(token)s",
             "prefix": "%(name)s-%(track)s",
         },
     },
-    "orus.io-git": {
-        "match": match_orus_io_git,
-        TRACK_CSET: {
+    "heptapod": {
+        "vars": gitlab_vars,
+        "hg.cset": {
             "url": "https://orus.io/%(path)s/-/archive/%(track)s/%(name)s-%(track)s.tar.gz%(token)s",
             "prefix": "%(name)s-%(track)s",
         },
-        TRACK_BRANCH: {
+        "hg.branch": {
+            "url": "https://orus.io/%(path)s/-/archive/branch/%(track)s/%(name)s-branch-%(track)s.tar.gz%(token)s",
+            "prefix": "%(name)s-branch-%(track)s",
+        },
+        "hg.tag": {
             "url": "https://orus.io/%(path)s/-/archive/%(track)s/%(name)s-%(track)s.tar.gz%(token)s",
             "prefix": "%(name)s-%(track)s",
         },
-        TRACK_TAG: {
+        "git.cset": {
+            "url": "https://orus.io/%(path)s/-/archive/%(track)s/%(name)s-%(track)s.tar.gz%(token)s",
+            "prefix": "%(name)s-%(track)s",
+        },
+        "git.branch": {
+            "url": "https://orus.io/%(path)s/-/archive/%(track)s/%(name)s-%(track)s.tar.gz%(token)s",
+            "prefix": "%(name)s-%(track)s",
+        },
+        "git.tag": {
             "url": "https://orus.io/%(path)s/-/archive/%(track)s/%(name)s-%(track)s.tar.gz%(token)s",
             "prefix": "%(name)s-%(track)s",
         },

          
@@ 123,23 104,50 @@ known_forges = {
 }
 
 
-def download_url(pulluri, rev, kind=TRACK_CSET):
-    for forge, settings in known_forges.items():
-        m = settings["match"](pulluri, rev)
-        if m is not None:
-            return (
-                settings[kind]["url"] % m,
-                settings[kind]["prefix"] % m,
-            )
-    return None, None
+URI = collections.namedtuple(
+    'URI', ["vcs", "scheme", "host", "path", "user", "password"]
+)
+
+
+def parse_uri(uri):
+    vcs = "hg"
+    if uri.endswith(".git"):
+        vcs = "git"
+        uri = uri.removesuffix(".git")
+    u = urllib.parse.urlparse(uri)
+    if u.netloc != "":
+        return URI(vcs, u.scheme, u.netloc, u.path, u.username, u.password)
+    if vcs == "git":
+        # assume the uri matches "user@host:path
+        host = uri.split("@")[1].split(":")[0]
+        path = uri.split(":")[1]
+        return URI("git", "git", host, path)
+    raise RuntimeError("invalid uri: " + uri)
 
 
-def checkout(ui, section, rev, secconf):
+def checkout(ui, forgetypes, section, rev, secconf):
+    pulluri = parse_uri(secconf["pulluri"])
+    forgetype = forgetypes.get(pulluri.host) or known_forges.get(pulluri.host)
+    if forgetype is None:
+        raise RuntimeError(
+            "could not determine '%s' forge type. Please complete the '[:confman:forgetypes]' section of your .hgconf file"
+            % pulluri.host,
+        )
+    forge = registry.get(forgetype)
+    if not forge:
+        raise RuntimeError("unknown forge type: %s" % forgetype)
+
+    vcs = "hg"
+    if secconf["pulluri"].endswith(".git"):
+        vcs = "git"
+
+    forge_vars = forge['vars'](pulluri, rev)
+
     # poke the forge type (bb, gh, gl, heptapod)
     for k in (TRACK_CSET, TRACK_TAG, TRACK_BRANCH):
-        url, prefix = download_url(secconf["pulluri"], rev, k)
-        if url is None:
-            continue
+        url = forge["%s.%s" % (vcs, k)]["url"] % forge_vars
+        prefix = forge["%s.%s" % (vcs, k)]["prefix"] % forge_vars
+
         try:
             ui.write("fetching %s...\n" % (url.split("?")[0]))
             req = urllib.request.Request(