summary refs log tree commit diff stats
diff options
context:
space:
mode:
-rw-r--r--abdl/__init__.py (renamed from abdl.py)0
-rw-r--r--[-rwxr-xr-x]ganarchy/__init__.py (renamed from ganarchy.py)100
-rw-r--r--ganarchy/__main__.py23
-rw-r--r--ganarchy/config.py157
-rw-r--r--ganarchy/debug.py36
5 files changed, 221 insertions, 95 deletions
diff --git a/abdl.py b/abdl/__init__.py
index 8dde742..8dde742 100644
--- a/abdl.py
+++ b/abdl/__init__.py
diff --git a/ganarchy.py b/ganarchy/__init__.py
index 82f1a7b..0f13b44 100755..100644
--- a/ganarchy.py
+++ b/ganarchy/__init__.py
@@ -1,6 +1,4 @@
-#!/usr/bin/env python3
-
-# GAnarchy - project homepage generator
+# GAnarchy - decentralized project hub
 # Copyright (C) 2019  Soni L.
 #
 # This program is free software: you can redistribute it and/or modify
@@ -25,16 +23,17 @@ import re
 import sqlite3
 import subprocess
 
+import abdl
 import click
 import jinja2
 import qtoml
 import requests
 
-import abdl
-
 from collections import defaultdict
 from urllib.parse import urlparse
 
+import ganarchy.config
+
 MIGRATIONS = {
         "toml-config": (
                 (
@@ -440,75 +439,7 @@ class GAnarchy:
         else:
             self.projects = None
 
-class ConfigSource(abc.ABC):
-    @abc.abstractmethod
-    def update(self):
-        """Refreshes the config if necessary."""
-        pass
-
-    def is_domain_blocked(self, domain):
-        """Returns True if the given domain is blocked."""
-        return False
-
-    @abc.abstractmethod
-    def get_project_commit_tree_paths(self):
-        """Returns an iterator of (project, URI, branch, options) tuples.
-
-        project is the project commit hash, URI is the repo URI, branch is the branch name and
-        options are the options for the given project commit-tree path."""
-        pass
-
-    def __getitem__(self, key):
-        raise KeyError
-
-class FileConfigSource(ConfigSource):
-    def __init__(self, filename):
-        self.exists = False
-        self.last_updated = None
-        self.filename = filename
-        self.tomlobj = None
-        self.update()
-
-    def update(self):
-        try:
-            updtime = self.last_updated
-            self.last_updated = os.stat(self.filename).st_mtime
-            if not self.exists or updtime != self.last_updated:
-                with open(self.filename) as f:
-                    self.tomlobj = qtoml.load(f)
-            self.exists = True
-        except OSError:
-            return
-
-    def get_project_commit_tree_paths(self):
-        for r in Config.CONFIG_PATTERN_SANITIZE.match(self.tomlobj):
-            yield (v['commit'][0], v['url'][0], v['branch'][0], v['branch'][1])
-
-    def __getitem__(self, key):
-        if key in ('title', 'base_url', 'config_srcs'):
-            return self.tomlobj[key]
-        return super().__getitem__(self, key)
-
-class RemoteConfigSource(ConfigSource):
-    def __init__(self, uri):
-        self.uri = uri
-        self.tomlobj = None
-
-    def update(self):
-        raise NotImplementedError
-
-    def get_project_commit_tree_paths(self):
-        for r in Config.CONFIG_PATTERN_SANITIZE.match(self.tomlobj):
-            if v['branch'][1].get('active', False) in (True, False):
-                yield (v['commit'][0], v['url'][0], v['branch'][0], v['branch'][1])
-
 class Config:
-    # sanitize = skip invalid entries
-    # validate = error on invalid entries
-    CONFIG_PATTERN_SANITIZE = abdl.compile("->commit/[0-9a-fA-F]{40}|[0-9a-fA-F]{64}/?:?$dict->url:?$dict->branch:?$dict", {'dict': dict})
-    # TODO use a validating pattern instead?
-    CONFIG_PATTERN = abdl.compile("->commit->url->branch", {'dict': dict})
-
     def __init__(self, toml_file, base=None, remove=True):
         self.projects = defaultdict(lambda: defaultdict(lambda: defaultdict(lambda: defaultdict(dict))))
         config_data = qtoml.load(toml_file)
@@ -531,7 +462,7 @@ class Config:
         self._update_projects(projects, remove=remove)
 
     def _update_projects(self, projects, remove, sanitize=True):
-        m = (Config.CONFIG_PATTERN_SANITIZE if sanitize else Config.CONFIG_PATTERN).match(projects)
+        m = (ganarchy.config.CONFIG_PATTERN_SANITIZE if sanitize else ganarchy.config.CONFIG_PATTERN).match(projects)
         for v in m:
             commit, repo_url, branchname, options = v['commit'][0], v['url'][0], v['branch'][0], v['branch'][1]
             try:
@@ -556,24 +487,6 @@ class Config:
             branch = self.projects[commit][repo_url][branchname]
             branch['active'] = active or (branch.get('active', False) and not remove)
 
-def debug():
-    @ganarchy.group()
-    def debug():
-        pass
-
-    @debug.command()
-    def paths():
-        click.echo('Config home: {}'.format(config_home))
-        click.echo('Additional config search path: {}'.format(config_dirs))
-        click.echo('Cache home: {}'.format(cache_home))
-        click.echo('Data home: {}'.format(data_home))
-
-    @debug.command()
-    def configs():
-        pass
-
-debug()
-
 @ganarchy.command()
 @click.option('--skip-errors/--no-skip-errors', default=False)
 @click.argument('files', type=click.File('r', encoding='utf-8'), nargs=-1)
@@ -678,6 +591,3 @@ def cron_target(update, project):
                                # I don't think this thing supports deprecating the above?
                                project        = p,
                                ganarchy       = instance))
-
-if __name__ == "__main__":
-    ganarchy()
diff --git a/ganarchy/__main__.py b/ganarchy/__main__.py
new file mode 100644
index 0000000..b270251
--- /dev/null
+++ b/ganarchy/__main__.py
@@ -0,0 +1,23 @@
+# GAnarchy - decentralized project hub
+# Copyright (C) 2019  Soni L.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+# The base CLI
+import ganarchy
+
+# Additional CLI commands
+import ganarchy.debug
+
+ganarchy.ganarchy(prog_name='ganarchy')
diff --git a/ganarchy/config.py b/ganarchy/config.py
new file mode 100644
index 0000000..154447b
--- /dev/null
+++ b/ganarchy/config.py
@@ -0,0 +1,157 @@
+# This file is part of GAnarchy - decentralized project hub
+# Copyright (C) 2019  Soni L.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+import abc
+import os
+
+import abdl
+import qtoml
+
+from enum import Enum
+
+# sanitize = skip invalid entries
+# validate = error on invalid entries
+CONFIG_REPOS_SANITIZE = abdl.compile("""->'projects'?:?$dict
+                                          ->commit/[0-9a-fA-F]{40}|[0-9a-fA-F]{64}/?:?$dict
+                                            ->url:?$dict
+                                              ->branch:?$dict(->'active'?:?$bool)""", {'bool': bool, 'dict': dict})
+CONFIG_REPOS = abdl.compile("->'projects'->commit->url->branch", {'dict': dict})
+
+CONFIG_TITLE_SANITIZE = abdl.compile("""->title'title'?:?$str""", {'str': str})
+CONFIG_BASE_URL_SANITIZE = abdl.compile("""->base_url'base_url'?:?$str""", {'str': str})
+CONFIG_SRCS_SANITIZE = abdl.compile("""->'config_srcs'?:?$list->src:?$str""", {'list': list, 'str': str})
+
+CONFIG_TITLE_VALIDATE = abdl.compile("""->title'title':$str""", {'str': str})
+CONFIG_BASE_URL_VALIDATE = abdl.compile("""->base_url'base_url':$str""", {'str': str})
+CONFIG_SRCS_VALIDATE = abdl.compile("""->'config_srcs':$list->src:$str""", {'list': list, 'str': str})
+
+class ConfigProperty(Enum):
+    TITLE = 1
+    BASE_URL = 2
+
+class ConfigSource(abc.ABC):
+    @abc.abstractmethod
+    def update(self):
+        """Refreshes the config if necessary."""
+        pass
+
+    @abc.abstractmethod
+    def exists(self):
+        """Returns whether the config exists."""
+        pass
+
+    def is_domain_blocked(self, domain):
+        """Returns whether the given domain is blocked."""
+        return False
+
+    def get_remote_config_sources(self):
+        """Yields URI strings for additional configs.
+
+        Yields:
+            str: A remote config URI.
+
+        """
+        yield from ()
+
+    @abc.abstractmethod
+    def get_project_commit_tree_paths(self):
+        """Yields (project, URI, branch, options) tuples.
+
+        Yields:
+            tuple of (str, str, str, dict): A project commit-tree path.
+
+            Composed of a project commit hash, a repo URI, a branch name
+            and a dict of options respectively.
+
+        """
+        pass
+
+    def get_supported_properties(self):
+        """Returns an iterable of properties supported by this config source.
+
+        Returns:
+            Iterable of ConfigProperty: Supported properties.
+
+        """
+        return ()
+
+    def get_property_value(self, prop):
+        """Returns the value associated with the given property.
+
+        Args:
+            prop (ConfigProperty): The property.
+
+        Returns:
+            The value associated with the given property.
+
+        Raises:
+            ValueError: If the property is not supported by this config
+            source.
+
+        """
+        raise ValueError
+
+class FileConfigSource(ConfigSource):
+    SUPPORTED_PROPERTIES = {}
+
+    def __init__(self, filename):
+        self.file_exists = False
+        self.last_updated = None
+        self.filename = filename
+        self.tomlobj = None
+
+    def update(self):
+        try:
+            updtime = self.last_updated
+            self.last_updated = os.stat(self.filename).st_mtime
+            if not self.file_exists or updtime != self.last_updated:
+                with open(self.filename) as f:
+                    self.tomlobj = qtoml.load(f)
+            self.file_exists = True
+        except OSError:
+            return
+
+    def exists(self):
+        return self.file_exists
+
+    def get_remote_config_sources(self):
+        for r in CONFIG_SRCS_SANITIZE.match(self.tomlobj):
+            yield r['src'][1]
+
+    def get_project_commit_tree_paths(self):
+        for r in CONFIG_PATTERN_SANITIZE.match(self.tomlobj):
+            yield (r['commit'][0], r['url'][0], r['branch'][0], r['branch'][1])
+
+    @classmethod
+    def get_supported_properties(cls):
+        return cls.SUPPORTED_PROPERTIES
+
+class RemoteConfigSource(ConfigSource):
+    def __init__(self, uri):
+        self.uri = uri
+        self.tomlobj = None
+        self.remote_exists = False
+
+    def update(self):
+        raise NotImplementedError
+
+    def exists(self):
+        return self.remote_exists
+
+    def get_project_commit_tree_paths(self):
+        for r in CONFIG_PATTERN_SANITIZE.match(self.tomlobj):
+            yield (r['commit'][0], r['url'][0], r['branch'][0], r['branch'][1])
+
diff --git a/ganarchy/debug.py b/ganarchy/debug.py
new file mode 100644
index 0000000..1310549
--- /dev/null
+++ b/ganarchy/debug.py
@@ -0,0 +1,36 @@
+# This file is part of GAnarchy - decentralized project hub
+# Copyright (C) 2019  Soni L.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+import click
+
+import ganarchy
+import ganarchy.config
+
+@ganarchy.ganarchy.group()
+def debug():
+    pass
+
+@debug.command()
+def paths():
+    click.echo('Config home: {}'.format(ganarchy.config_home))
+    click.echo('Additional config search path: {}'.format(ganarchy.config_dirs))
+    click.echo('Cache home: {}'.format(ganarchy.cache_home))
+    click.echo('Data home: {}'.format(ganarchy.data_home))
+
+@debug.command()
+def configs():
+    pass
+