Uhh broken stuff????
This commit is contained in:
		
							parent
							
								
									cb63969424
								
							
						
					
					
						commit
						4bd53221bb
					
				
					 1 changed files with 260 additions and 86 deletions
				
			
		
							
								
								
									
										346
									
								
								ganarchy.py
									
										
									
									
									
								
							
							
						
						
									
										346
									
								
								ganarchy.py
									
										
									
									
									
								
							| 
						 | 
				
			
			@ -23,9 +23,106 @@ import subprocess
 | 
			
		|||
import hashlib
 | 
			
		||||
import hmac
 | 
			
		||||
import jinja2
 | 
			
		||||
import re
 | 
			
		||||
 | 
			
		||||
# default HTML, can be overridden in $XDG_DATA_HOME/ganarchy/template.html or the $XDG_DATA_DIRS (TODO)
 | 
			
		||||
TEMPLATE = """<!DOCTYPE html>
 | 
			
		||||
MIGRATIONS = {
 | 
			
		||||
        "gen-index": (
 | 
			
		||||
                (
 | 
			
		||||
                    """ALTER TABLE "config" ADD COLUMN "title" TEXT"""),
 | 
			
		||||
                (),
 | 
			
		||||
                "supports generating an index page"
 | 
			
		||||
            ),
 | 
			
		||||
        "better-project-management": (
 | 
			
		||||
                (
 | 
			
		||||
                    """ALTER TABLE "repos" ADD COLUMN "branch" TEXT""",
 | 
			
		||||
                    """ALTER TABLE "repos" ADD COLUMN "project" TEXT""",
 | 
			
		||||
                    """CREATE UNIQUE INDEX "repos_url_branch_project" ON "repos" ("url", "branch", "project")""",
 | 
			
		||||
                    """CREATE INDEX "repos_project" ON "repos" ("project")""",
 | 
			
		||||
                    """ALTER TABLE "repo_history" ADD COLUMN "branch" TEXT""",
 | 
			
		||||
                    """ALTER TABLE "repo_history" ADD COLUMN "project" TEXT""",
 | 
			
		||||
                    """CREATE INDEX "repo_history_url_branch_project" ON "repo_history" ("url", "branch", "project")"""),
 | 
			
		||||
                (
 | 
			
		||||
                    """DELETE FROM "repos" WHERE "branch" IS NOT NULL OR "project" IS NOT NULL""",
 | 
			
		||||
                    """DELETE FROM "repo_history" WHERE "branch" IS NOT NULL OR "project" IS NOT NULL"""),
 | 
			
		||||
                "supports multiple projects, and allows choosing non-default branches"
 | 
			
		||||
            ),
 | 
			
		||||
        "test": (
 | 
			
		||||
                ("""-- apply""",),
 | 
			
		||||
                ("""-- revert""",),
 | 
			
		||||
                "does nothing"
 | 
			
		||||
            )
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
data_home = os.environ.get('XDG_DATA_HOME', '')
 | 
			
		||||
if not data_home:
 | 
			
		||||
    data_home = os.environ['HOME'] + '/.local/share'
 | 
			
		||||
data_home = data_home + "/ganarchy"
 | 
			
		||||
 | 
			
		||||
cache_home = os.environ.get('XDG_CACHE_HOME', '')
 | 
			
		||||
if not cache_home:
 | 
			
		||||
    cache_home = os.environ['HOME'] + '/.cache'
 | 
			
		||||
cache_home = cache_home + "/ganarchy"
 | 
			
		||||
 | 
			
		||||
config_home = os.environ.get('XDG_CONFIG_HOME', '')
 | 
			
		||||
if not config_home:
 | 
			
		||||
    config_home = os.environ['HOME'] + '/.config'
 | 
			
		||||
config_home = config_home + "/ganarchy"
 | 
			
		||||
 | 
			
		||||
config_dirs = os.environ.get('XDG_CONFIG_DIRS', '')
 | 
			
		||||
if not config_dirs:
 | 
			
		||||
    config_dirs = '/etc/xdg'
 | 
			
		||||
# TODO check if this is correct
 | 
			
		||||
config_dirs = [config_dir + "/ganarchy" for config_dir in config_dirs.split(':')]
 | 
			
		||||
 | 
			
		||||
def get_template_loader():
 | 
			
		||||
    from jinja2 import DictLoader, FileSystemLoader, ChoiceLoader
 | 
			
		||||
    return ChoiceLoader([
 | 
			
		||||
        FileSystemLoader([config_home + "/templates"] + [config_dir + "/templates" for config_dir in config_dirs]),
 | 
			
		||||
        DictLoader({
 | 
			
		||||
            ## index.html
 | 
			
		||||
            'index.html': """<!DOCTYPE html>
 | 
			
		||||
<html lang="en">
 | 
			
		||||
    <head>
 | 
			
		||||
        <meta charset="utf-8" />
 | 
			
		||||
        <!--
 | 
			
		||||
        GAnarchy - project homepage generator
 | 
			
		||||
        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/>.
 | 
			
		||||
        -->
 | 
			
		||||
        <title>{{ ganarchy.title|e }}</title>
 | 
			
		||||
        <meta name="description" content="{{ ganarchy.title|e }}" />
 | 
			
		||||
        <!--if your browser doesn't like the following, use a different browser.-->
 | 
			
		||||
        <script type="application/javascript" src="/index.js"></script>
 | 
			
		||||
    </head>
 | 
			
		||||
    <body>
 | 
			
		||||
        <h1>{{ ganarchy.title|e }}</h1>
 | 
			
		||||
        <p>This is {{ ganarchy.title|e }}. Currently tracking the following projects:</p>
 | 
			
		||||
        <ul>
 | 
			
		||||
        {% for project in ganarchy.projects -%}
 | 
			
		||||
            <li><a href="/project/{{ project.commit|e }}">{{ project.title|e }}</a>: {{ project.description|e }}</li>
 | 
			
		||||
        {% endfor -%}
 | 
			
		||||
        </ul>
 | 
			
		||||
        <p>Powered by <a href="https://ganarchy.autistic.space/">GAnarchy</a>. AGPLv3-licensed. <a href="https://cybre.tech/SoniEx2/ganarchy">Source Code</a>.</p>
 | 
			
		||||
        <p>
 | 
			
		||||
            <a href="{{ ganarchy.base_url|e }}" onclick="event.preventDefault(); navigator.registerProtocolHandler('web+ganarchy', this.href + '?url=%s', 'GAnarchy');">Register web+ganarchy: URI handler</a>.
 | 
			
		||||
        </p>
 | 
			
		||||
    </body>
 | 
			
		||||
</html>
 | 
			
		||||
""",
 | 
			
		||||
            ## project.html FIXME
 | 
			
		||||
            'project.html': """<!DOCTYPE html>
 | 
			
		||||
<html lang="en">
 | 
			
		||||
    <head>
 | 
			
		||||
        <meta charset="utf-8" />
 | 
			
		||||
| 
						 | 
				
			
			@ -57,7 +154,7 @@ TEMPLATE = """<!DOCTYPE html>
 | 
			
		|||
        <ul>
 | 
			
		||||
        {% for url, msg, img, branch in repos -%}
 | 
			
		||||
            <li><a href="{{ url|e }}">{{ url|e }}</a>{% if branch %} <span class="branchname">[{{ branch|e }}]</span>{% endif %}: {{ msg|e }}</li>
 | 
			
		||||
        {%- endfor %}
 | 
			
		||||
        {% endfor -%}
 | 
			
		||||
        </ul>
 | 
			
		||||
        <p>Powered by <a href="https://ganarchy.autistic.space/">GAnarchy</a>. AGPLv3-licensed. <a href="https://cybre.tech/SoniEx2/ganarchy">Source Code</a>.</p>
 | 
			
		||||
        <p>
 | 
			
		||||
| 
						 | 
				
			
			@ -66,45 +163,12 @@ TEMPLATE = """<!DOCTYPE html>
 | 
			
		|||
        </p>
 | 
			
		||||
    </body>
 | 
			
		||||
</html>
 | 
			
		||||
"""
 | 
			
		||||
""",
 | 
			
		||||
            ## history.svg FIXME
 | 
			
		||||
            'history.svg': """""",
 | 
			
		||||
        })
 | 
			
		||||
    ])
 | 
			
		||||
 | 
			
		||||
MIGRATIONS = {
 | 
			
		||||
        "better-project-management": (
 | 
			
		||||
                (
 | 
			
		||||
                    """ALTER TABLE "repos" ADD COLUMN "branch" TEXT""",
 | 
			
		||||
                    """ALTER TABLE "repos" ADD COLUMN "project" TEXT""",
 | 
			
		||||
                    """CREATE UNIQUE INDEX "repos_url_branch_project" ON "repos" ("url", "branch", "project")""",
 | 
			
		||||
                    """CREATE INDEX "repos_project" ON "repos" ("project")""",
 | 
			
		||||
                    """ALTER TABLE "repo_history" ADD COLUMN "branch" TEXT""",
 | 
			
		||||
                    """ALTER TABLE "repo_history" ADD COLUMN "project" TEXT""",
 | 
			
		||||
                    """CREATE INDEX "repo_history_url_branch_project" ON "repo_history" ("url", "branch", "project")"""),
 | 
			
		||||
                (
 | 
			
		||||
                    """DELETE FROM "repos" WHERE "branch" IS NOT NULL OR "project" IS NOT NULL""",
 | 
			
		||||
                    """DELETE FROM "repo_history" WHERE "branch" IS NOT NULL OR "project" IS NOT NULL"""),
 | 
			
		||||
                "supports multiple projects, and allows choosing non-default branches"
 | 
			
		||||
            ),
 | 
			
		||||
        "test": (
 | 
			
		||||
                ("""-- apply""",),
 | 
			
		||||
                ("""-- revert""",),
 | 
			
		||||
                "does nothing"
 | 
			
		||||
            )
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
try:
 | 
			
		||||
    data_home = os.environ['XDG_DATA_HOME']
 | 
			
		||||
except KeyError:
 | 
			
		||||
    data_home = ''
 | 
			
		||||
if not data_home:
 | 
			
		||||
    data_home = os.environ['HOME'] + '/.local/share'
 | 
			
		||||
data_home = data_home + "/ganarchy"
 | 
			
		||||
 | 
			
		||||
try:
 | 
			
		||||
    cache_home = os.environ['XDG_CACHE_HOME']
 | 
			
		||||
except KeyError:
 | 
			
		||||
    cache_home = ''
 | 
			
		||||
if not cache_home:
 | 
			
		||||
    cache_home = os.environ['HOME'] + '/.cache'
 | 
			
		||||
cache_home = cache_home + "/ganarchy"
 | 
			
		||||
 | 
			
		||||
@click.group()
 | 
			
		||||
def ganarchy():
 | 
			
		||||
| 
						 | 
				
			
			@ -122,8 +186,8 @@ def initdb():
 | 
			
		|||
    c.execute('''CREATE INDEX "repos_active" ON "repos" ("active")''')
 | 
			
		||||
    c.execute('''CREATE TABLE "repo_history" ("entry" INTEGER PRIMARY KEY ASC AUTOINCREMENT, "url" TEXT, "count" INTEGER, "head_commit" TEXT, "branch" TEXT, "project" TEXT)''')
 | 
			
		||||
    c.execute('''CREATE INDEX "repo_history_url_branch_project" ON "repo_history" ("url", "branch", "project")''')
 | 
			
		||||
    c.execute('''CREATE TABLE "config" ("git_commit" TEXT, "base_url" TEXT)''')
 | 
			
		||||
    c.execute('''INSERT INTO "config" VALUES ('', '')''')
 | 
			
		||||
    c.execute('''CREATE TABLE "config" ("git_commit" TEXT, "base_url" TEXT, "title" TEXT)''')
 | 
			
		||||
    c.execute('''INSERT INTO "config" VALUES ('', '', '')''')
 | 
			
		||||
    conn.commit()
 | 
			
		||||
    conn.close()
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -131,7 +195,6 @@ def initdb():
 | 
			
		|||
@click.argument('commit')
 | 
			
		||||
def set_commit(commit):
 | 
			
		||||
    """Sets the commit that represents the project."""
 | 
			
		||||
    import re
 | 
			
		||||
    if not re.fullmatch("[a-fA-F0-9]{40}", commit):
 | 
			
		||||
        raise click.BadArgumentUsage("COMMIT must be a git commit hash")
 | 
			
		||||
    conn = sqlite3.connect(data_home + "/ganarchy.db")
 | 
			
		||||
| 
						 | 
				
			
			@ -143,13 +206,23 @@ def set_commit(commit):
 | 
			
		|||
@ganarchy.command()
 | 
			
		||||
@click.argument('base-url')
 | 
			
		||||
def set_base_url(base_url):
 | 
			
		||||
    """Sets the GAnarchy instance's base URL."""
 | 
			
		||||
    """Sets the GAnarchy instance's base URL. Used for the URI handler."""
 | 
			
		||||
    conn = sqlite3.connect(data_home + "/ganarchy.db")
 | 
			
		||||
    c = conn.cursor()
 | 
			
		||||
    c.execute('''UPDATE "config" SET "base_url"=?''', (base_url,))
 | 
			
		||||
    conn.commit()
 | 
			
		||||
    conn.close()
 | 
			
		||||
 | 
			
		||||
@ganarchy.command()
 | 
			
		||||
@click.argument('title')
 | 
			
		||||
def set_title(title):
 | 
			
		||||
    """Sets the GAnarchy instance's title. This title is displayed on the index."""
 | 
			
		||||
    conn = sqlite3.connect(data_home + "/ganarchy.db")
 | 
			
		||||
    c = conn.cursor()
 | 
			
		||||
    c.execute('''UPDATE "config" SET "title"=?''', (title,))
 | 
			
		||||
    conn.commit()
 | 
			
		||||
    conn.close()
 | 
			
		||||
 | 
			
		||||
# TODO move --branch into here?
 | 
			
		||||
@ganarchy.group()
 | 
			
		||||
def repo():
 | 
			
		||||
| 
						 | 
				
			
			@ -265,28 +338,51 @@ def migrations():
 | 
			
		|||
 | 
			
		||||
migrations()
 | 
			
		||||
 | 
			
		||||
@ganarchy.command()
 | 
			
		||||
@click.argument('project', required=False)
 | 
			
		||||
def cron_target(project):
 | 
			
		||||
    """Runs ganarchy as a cron target."""
 | 
			
		||||
    def handle_target(url, branch, project_commit):
 | 
			
		||||
class GitError(LookupError):
 | 
			
		||||
    """Raised when a git operation fails, generally due to a missing commit or branch, or network connection issues."""
 | 
			
		||||
    pass
 | 
			
		||||
 | 
			
		||||
class Repo:
 | 
			
		||||
    def __init__(self, dbconn, project, url, branch, list_metadata=False):
 | 
			
		||||
        self.url = url
 | 
			
		||||
        self.branch = branch
 | 
			
		||||
        self.project_commit = project.commit
 | 
			
		||||
 | 
			
		||||
        if not branch:
 | 
			
		||||
            branchname = "gan" + hashlib.sha256(url.encode("utf-8")).hexdigest()
 | 
			
		||||
            branch = "HEAD"
 | 
			
		||||
            self.branchname = "gan" + hashlib.sha256(url.encode("utf-8")).hexdigest()
 | 
			
		||||
            self.head = "HEAD"
 | 
			
		||||
        else:
 | 
			
		||||
            branchname = "gan" + hmac.new(branch.encode("utf-8"), url.encode("utf-8"), "sha256").hexdigest()
 | 
			
		||||
            branch = "refs/heads/" + branch
 | 
			
		||||
            self.branchname = "gan" + hmac.new(branch.encode("utf-8"), url.encode("utf-8"), "sha256").hexdigest()
 | 
			
		||||
            self.head = "refs/heads/" + branch
 | 
			
		||||
 | 
			
		||||
        try:
 | 
			
		||||
            pre_hash = subprocess.check_output(["git", "-C", cache_home, "show", branchname, "-s", "--format=%H", "--"], stderr=subprocess.DEVNULL).decode("utf-8").strip()
 | 
			
		||||
            self.hash = subprocess.check_output(["git", "-C", cache_home, "show", self.branchname, "-s", "--format=%H", "--"], stderr=subprocess.DEVNULL).decode("utf-8").strip()
 | 
			
		||||
        except subprocess.CalledProcessError:
 | 
			
		||||
            pre_hash = None
 | 
			
		||||
            self.hash = None
 | 
			
		||||
 | 
			
		||||
        self.message = None
 | 
			
		||||
        if list_metadata:
 | 
			
		||||
            try:
 | 
			
		||||
                self.fetch_metadata()
 | 
			
		||||
            except GitError:
 | 
			
		||||
                pass
 | 
			
		||||
 | 
			
		||||
    def fetch_metadata(self):
 | 
			
		||||
        try:
 | 
			
		||||
            subprocess.check_output(["git", "-C", cache_home, "fetch", "-q", url, "+" + branch + ":" + branchname], stderr=subprocess.STDOUT)
 | 
			
		||||
            self.message = subprocess.check_output(["git", "-C", cache_home, "show", self.branchname, "-s", "--format=%B", "--"], stderr=subprocess.DEVNULL).decode("utf-8", "replace")
 | 
			
		||||
        except subprocess.CalledProcessError as e:
 | 
			
		||||
            raise GitError from e
 | 
			
		||||
 | 
			
		||||
    def update(self): # FIXME?
 | 
			
		||||
        try:
 | 
			
		||||
            subprocess.check_output(["git", "-C", cache_home, "fetch", "-q", self.url, "+" + self.head + ":" + self.branchname], stderr=subprocess.STDOUT)
 | 
			
		||||
        except subprocess.CalledProcessError as e:
 | 
			
		||||
            # This may error for various reasons, but some are important: dead links, etc
 | 
			
		||||
            click.echo(e.output, err=True)
 | 
			
		||||
            return None
 | 
			
		||||
        post_hash = subprocess.check_output(["git", "-C", cache_home, "show", branchname, "-s", "--format=%H", "--"], stderr=subprocess.DEVNULL).decode("utf-8").strip()
 | 
			
		||||
        pre_hash = self.hash
 | 
			
		||||
        post_hash = subprocess.check_output(["git", "-C", cache_home, "show", self.branchname, "-s", "--format=%H", "--"], stderr=subprocess.DEVNULL).decode("utf-8").strip()
 | 
			
		||||
        self.hash = post_hash
 | 
			
		||||
        if not pre_hash:
 | 
			
		||||
            pre_hash = post_hash
 | 
			
		||||
        try:
 | 
			
		||||
| 
						 | 
				
			
			@ -294,16 +390,99 @@ def cron_target(project):
 | 
			
		|||
        except subprocess.CalledProcessError:
 | 
			
		||||
            count = 0  # force-pushed
 | 
			
		||||
        try:
 | 
			
		||||
            subprocess.check_call(["git", "-C", cache_home, "merge-base", "--is-ancestor", project_commit, branchname], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
 | 
			
		||||
            return count, post_hash, subprocess.check_output(["git", "-C", cache_home, "show", branchname, "-s", "--format=%B", "--"], stderr=subprocess.DEVNULL).decode("utf-8", "replace")
 | 
			
		||||
        except subprocess.CalledProcessError:
 | 
			
		||||
            subprocess.check_call(["git", "-C", cache_home, "merge-base", "--is-ancestor", self.project_commit, self.branchname], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
 | 
			
		||||
            self.fetch_metadata()
 | 
			
		||||
            return count, post_hash, self.message
 | 
			
		||||
        except (subprocess.CalledProcessError, GitError) as e:
 | 
			
		||||
            click.echo(e, err=True)
 | 
			
		||||
            return None
 | 
			
		||||
 | 
			
		||||
class Project:
 | 
			
		||||
    def __init__(self, dbconn, ganarchy, project_commit, list_repos=False):
 | 
			
		||||
        self.commit = project_commit
 | 
			
		||||
        if ganarchy.project_commit == project_commit:
 | 
			
		||||
            project_commit = None
 | 
			
		||||
        self.refresh_metadata()
 | 
			
		||||
        if list_repos:
 | 
			
		||||
            repos = []
 | 
			
		||||
            with dbconn:
 | 
			
		||||
                for (e, url, branch) in dbconn.execute('''SELECT "max"("e"), "url", "branch" FROM (SELECT "max"("T1"."entry") "e", "T1"."url", "T1"."branch" FROM "repo_history" "T1"
 | 
			
		||||
                                                                    WHERE (SELECT "active" FROM "repos" "T2" WHERE "url" = "T1"."url" AND "branch" IS "T1"."branch" AND "project" IS ?1)
 | 
			
		||||
                                                                    GROUP BY "T1"."url", "T1"."branch"
 | 
			
		||||
                                                                    UNION
 | 
			
		||||
                                                                    SELECT null, "T3"."url", "T3"."branch" FROM "repos" "T3" WHERE "active" AND "project" IS ?1)
 | 
			
		||||
                                           GROUP BY "url" ORDER BY "e"''', (project_commit,)):
 | 
			
		||||
                    repos.append(Repo(dbconn, self, url, branch))
 | 
			
		||||
            self.repos = repos
 | 
			
		||||
        else:
 | 
			
		||||
            self.repos = None
 | 
			
		||||
 | 
			
		||||
    def refresh_metadata(self):
 | 
			
		||||
        try:
 | 
			
		||||
            project = subprocess.check_output(["git", "-C", cache_home, "show", self.commit, "-s", "--format=%B", "--"], stderr=subprocess.DEVNULL).decode("utf-8", "replace")
 | 
			
		||||
            project_title, project_desc = (lambda x: x.groups() if x is not None else ('', None))(re.fullmatch('^\\[Project\\]\s+(.+?)(?:\n\n(.+))?$', project, flags=re.ASCII|re.DOTALL|re.IGNORECASE))
 | 
			
		||||
            if not project_title.strip():
 | 
			
		||||
                project_title, project_desc = ("Error parsing project commit",)*2
 | 
			
		||||
            if project_desc:
 | 
			
		||||
                project_desc = project_desc.strip()
 | 
			
		||||
            self.commit_body = project
 | 
			
		||||
            self.title = project_title
 | 
			
		||||
            self.description = project_desc
 | 
			
		||||
        except subprocess.CalledProcessError:
 | 
			
		||||
            self.commit_body = None
 | 
			
		||||
            self.title = None
 | 
			
		||||
            self.description = None
 | 
			
		||||
 | 
			
		||||
    def update(self):
 | 
			
		||||
        # TODO
 | 
			
		||||
        self.refresh_metadata()
 | 
			
		||||
 | 
			
		||||
class GAnarchy:
 | 
			
		||||
    def __init__(self, dbconn, list_projects=False):
 | 
			
		||||
        with dbconn:
 | 
			
		||||
            # TODO
 | 
			
		||||
            #(project_commit, base_url, title) = dbconn.execute('''SELECT "git_commit", "base_url", "title" FROM "config"''').fetchone()
 | 
			
		||||
            (project_commit, base_url) = dbconn.execute('''SELECT "git_commit", "base_url" FROM "config"''').fetchone()
 | 
			
		||||
            title = None
 | 
			
		||||
            self.project_commit = project_commit
 | 
			
		||||
            self.base_url = base_url
 | 
			
		||||
            if not base_url:
 | 
			
		||||
                pass ## TODO
 | 
			
		||||
            if not title:
 | 
			
		||||
                from urllib.parse import urlparse
 | 
			
		||||
                title = "GAnarchy on " + urlparse(base_url).hostname
 | 
			
		||||
            self.title = title
 | 
			
		||||
        if list_projects:
 | 
			
		||||
            projects = []
 | 
			
		||||
            with dbconn:
 | 
			
		||||
                for (project,) in dbconn.execute('''SELECT DISTINCT "project" FROM "repos" '''): # FIXME? *maybe* sort by activity in the future
 | 
			
		||||
                    if project == None:
 | 
			
		||||
                        project = self.project_commit
 | 
			
		||||
                    projects.append(Project(dbconn, ganarchy, project))
 | 
			
		||||
            projects.sort(key=lambda project: project.title)
 | 
			
		||||
            self.projects = projects
 | 
			
		||||
        else:
 | 
			
		||||
            self.projects = None
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ganarchy.command()
 | 
			
		||||
@click.argument('project', required=False)
 | 
			
		||||
def cron_target(project):
 | 
			
		||||
    """Runs ganarchy as a cron target."""
 | 
			
		||||
    # make sure the cache dir exists
 | 
			
		||||
    os.makedirs(cache_home, exist_ok=True)
 | 
			
		||||
    # make sure it is a git repo
 | 
			
		||||
    subprocess.call(["git", "-C", cache_home, "init", "-q"])
 | 
			
		||||
    conn = sqlite3.connect(data_home + "/ganarchy.db")
 | 
			
		||||
    c = conn.cursor()
 | 
			
		||||
    c.execute('''SELECT "git_commit", "base_url" FROM "config"''')
 | 
			
		||||
    (project_commit, base_url) = c.fetchone()
 | 
			
		||||
    instance = GAnarchy(conn, list_projects=project=="index")
 | 
			
		||||
    env = jinja2.Environment(loader=get_template_loader(), autoescape=False)
 | 
			
		||||
    if project == "index":
 | 
			
		||||
        # render the index
 | 
			
		||||
        template = env.get_template('index.html')
 | 
			
		||||
        click.echo(template.render(ganarchy = instance))
 | 
			
		||||
        return
 | 
			
		||||
    project_commit = instance.project_commit
 | 
			
		||||
    base_url = instance.base_url
 | 
			
		||||
    if not base_url or not (project or project_commit):
 | 
			
		||||
        click.echo("No base URL or project commit specified", err=True)
 | 
			
		||||
        return
 | 
			
		||||
| 
						 | 
				
			
			@ -313,17 +492,16 @@ def cron_target(project):
 | 
			
		|||
        project_commit = project
 | 
			
		||||
    entries = []
 | 
			
		||||
    generate_html = []
 | 
			
		||||
    for (e, url, branch) in c.execute('''SELECT "max"("e"), "url", "branch" FROM (SELECT "max"("T1"."entry") "e", "T1"."url", "T1"."branch" FROM "repo_history" "T1"
 | 
			
		||||
                                                        WHERE (SELECT "active" FROM "repos" "T2" WHERE "url" = "T1"."url" AND "branch" IS "T1"."branch" AND "project" IS ?1)
 | 
			
		||||
                                                        GROUP BY "T1"."url", "T1"."branch"
 | 
			
		||||
                                                        UNION
 | 
			
		||||
                                                        SELECT null, "T3"."url", "T3"."branch" FROM "repos" "T3" WHERE "active" AND "project" IS ?1)
 | 
			
		||||
                               GROUP BY "url" ORDER BY "e"''', (project,)):
 | 
			
		||||
        result = handle_target(url, branch, project_commit)
 | 
			
		||||
    c = conn.cursor()
 | 
			
		||||
    p = Project(conn, instance, project_commit, list_repos=True)
 | 
			
		||||
    # FIXME: this should be moved into Project.update()
 | 
			
		||||
    for repo in p.repos:
 | 
			
		||||
        result = repo.update()
 | 
			
		||||
        if result is not None:
 | 
			
		||||
            count, post_hash, msg = result
 | 
			
		||||
            entries.append((url, count, post_hash, branch, project))
 | 
			
		||||
            generate_html.append((url, msg, count, branch))
 | 
			
		||||
            entries.append((repo.url, count, post_hash, repo.branch, project))
 | 
			
		||||
            generate_html.append((repo.url, msg, count, repo.branch))
 | 
			
		||||
    p.refresh_metadata()
 | 
			
		||||
    # sort stuff twice because reasons
 | 
			
		||||
    entries.sort(key=lambda x: x[1], reverse=True)
 | 
			
		||||
    generate_html.sort(key=lambda x: x[2], reverse=True)
 | 
			
		||||
| 
						 | 
				
			
			@ -334,20 +512,16 @@ def cron_target(project):
 | 
			
		|||
        history = c.execute('''SELECT "count" FROM "repo_history" WHERE "url" = ? AND "branch" IS ? AND "project" IS ? ORDER BY "entry" ASC''', (url, branch, project)).fetchall()
 | 
			
		||||
        # TODO process history into SVG
 | 
			
		||||
        html_entries.append((url, msg, "", branch))
 | 
			
		||||
    template = jinja2.Template(TEMPLATE)
 | 
			
		||||
    import re
 | 
			
		||||
    project = subprocess.check_output(["git", "-C", cache_home, "show", project_commit, "-s", "--format=%B", "--"], stderr=subprocess.DEVNULL).decode("utf-8", "replace")
 | 
			
		||||
    project_title, project_desc = (lambda x: x.groups() if x is not None else ('', None))(re.fullmatch('^\\[Project\\]\s+(.+?)(?:\n\n(.+))?$', project, flags=re.ASCII|re.DOTALL|re.IGNORECASE))
 | 
			
		||||
    if not project_title.strip():
 | 
			
		||||
        project_title, project_desc = ("Error parsing project commit",)*2
 | 
			
		||||
    if project_desc:
 | 
			
		||||
        project_desc = project_desc.strip()
 | 
			
		||||
    click.echo(template.render(project_title  = project_title,
 | 
			
		||||
                               project_desc   = project_desc,
 | 
			
		||||
                               project_body   = project,
 | 
			
		||||
                               project_commit = project_commit,
 | 
			
		||||
    template = env.get_template('project.html')
 | 
			
		||||
    click.echo(template.render(project_title  = p.title,
 | 
			
		||||
                               project_desc   = p.description,
 | 
			
		||||
                               project_body   = p.commit_body,
 | 
			
		||||
                               project_commit = p.commit,
 | 
			
		||||
                               repos          = html_entries,
 | 
			
		||||
                               base_url       = base_url))
 | 
			
		||||
                               base_url       = base_url,
 | 
			
		||||
                               # I don't think this thing supports deprecating the above?
 | 
			
		||||
                               project        = p,
 | 
			
		||||
                               ganarchy       = instance))
 | 
			
		||||
 | 
			
		||||
if __name__ == "__main__":
 | 
			
		||||
    ganarchy()
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
	Add table
		
		Reference in a new issue