diff options
-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__.py | 23 | ||||
-rw-r--r-- | ganarchy/config.py | 157 | ||||
-rw-r--r-- | ganarchy/debug.py | 36 |
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 + |