2019-05-07 20:51:21 +02:00
#!/usr/bin/env python3
2019-04-17 03:58:00 +02:00
# 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/>.
import sqlite3
import click
import os
import subprocess
import hashlib
2019-05-19 15:17:04 +02:00
import hmac
2019-04-17 03:58:00 +02:00
import jinja2
2019-05-28 02:14:09 +02:00
import re
2019-04-17 03:58:00 +02:00
2019-05-28 02:14:09 +02:00
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>
2019-04-17 03:58:00 +02:00
< html lang = " en " >
2019-04-20 22:12:39 +02:00
< 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 > { { project_title | e } } < / title >
{ % if project_desc % } < meta name = " description " content = " {{ project_desc|e }} " / > { % endif % }
2019-05-19 15:17:04 +02:00
< style type = " text/css " > . branchname { color : #808080; font-style: italic; }</style>
2019-04-20 22:12:39 +02:00
< / head >
< body >
2019-04-21 00:27:50 +02:00
< h1 > { { project_title | e } } < / h1 >
2019-04-23 11:40:23 +02:00
< p > Tracking < span id = " project_commit " > < a href = " web+ganarchy: {{ project_commit }} " > { { project_commit } } < / a > < / span > < / p >
2019-04-21 00:27:50 +02:00
< div id = " project_body " > < p > { { project_body | e | replace ( " \n \n " , " </p><p> " ) } } < / p > < / div >
2019-04-20 22:12:39 +02:00
< ul >
2019-05-19 15:17:04 +02:00
{ % 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 >
2019-05-28 02:14:09 +02:00
{ % endfor - % }
2019-04-20 22:12:39 +02:00
< / ul >
2019-04-22 23:59:02 +02:00
< 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 = " / " > Main page < / a > .
2019-04-28 23:50:52 +02:00
< a href = " {{ base_url|e }} " onclick = " event.preventDefault(); navigator.registerProtocolHandler( ' web+ganarchy ' , this.href + ' ?url= %s ' , ' GAnarchy ' ); " > Register web + ganarchy : URI handler < / a > .
2019-04-22 23:59:02 +02:00
< / p >
2019-04-20 22:12:39 +02:00
< / body >
2019-04-17 03:58:00 +02:00
< / html >
2019-05-28 02:14:09 +02:00
""" ,
## history.svg FIXME
' history.svg ' : """ """ ,
} )
] )
2019-04-17 03:58:00 +02:00
@click.group ( )
def ganarchy ( ) :
pass
@ganarchy.command ( )
def initdb ( ) :
2019-05-03 00:14:09 +02:00
""" Initializes the ganarchy database. """
2019-04-17 03:58:00 +02:00
os . makedirs ( data_home , exist_ok = True )
conn = sqlite3 . connect ( data_home + " /ganarchy.db " )
c = conn . cursor ( )
2019-05-19 15:17:04 +02:00
c . execute ( ''' CREATE TABLE " repos " ( " url " TEXT PRIMARY KEY, " active " INT, " branch " TEXT, " project " TEXT) ''' )
c . execute ( ''' CREATE UNIQUE INDEX " repos_url_branch_project " ON " repos " ( " url " , " branch " , " project " ) ''' )
c . execute ( ''' CREATE INDEX " repos_project " ON " repos " ( " project " ) ''' )
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 " ) ''' )
2019-05-28 02:14:09 +02:00
c . execute ( ''' CREATE TABLE " config " ( " git_commit " TEXT, " base_url " TEXT, " title " TEXT) ''' )
c . execute ( ''' INSERT INTO " config " VALUES ( ' ' , ' ' , ' ' ) ''' )
2019-04-17 03:58:00 +02:00
conn . commit ( )
conn . close ( )
@ganarchy.command ( )
@click.argument ( ' commit ' )
def set_commit ( commit ) :
2019-05-03 00:14:09 +02:00
""" Sets the commit that represents the project. """
2019-04-17 03:58:00 +02:00
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 " )
c = conn . cursor ( )
2019-05-19 15:17:04 +02:00
c . execute ( ''' UPDATE " config " SET " git_commit " =? ''' , ( commit , ) )
2019-04-17 03:58:00 +02:00
conn . commit ( )
conn . close ( )
2019-04-22 23:59:02 +02:00
@ganarchy.command ( )
@click.argument ( ' base-url ' )
def set_base_url ( base_url ) :
2019-05-28 02:14:09 +02:00
""" Sets the GAnarchy instance ' s base URL. Used for the URI handler. """
2019-04-22 23:59:02 +02:00
conn = sqlite3 . connect ( data_home + " /ganarchy.db " )
c = conn . cursor ( )
2019-05-19 15:17:04 +02:00
c . execute ( ''' UPDATE " config " SET " base_url " =? ''' , ( base_url , ) )
2019-04-22 23:59:02 +02:00
conn . commit ( )
conn . close ( )
2019-05-28 02:14:09 +02:00
@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 ( )
2019-05-19 15:17:04 +02:00
# TODO move --branch into here?
2019-04-17 03:58:00 +02:00
@ganarchy.group ( )
def repo ( ) :
2019-05-03 00:14:09 +02:00
""" Modifies repos to track. """
2019-04-17 03:58:00 +02:00
@repo.command ( )
2019-05-19 15:17:04 +02:00
@click.option ( ' --branch ' , default = None , help = " Sets the branch to be used for the repo " )
@click.option ( ' --project ' , default = None , help = " Sets the project commit to be used for the repo " )
@click.option ( ' --disabled ' , default = False , is_flag = True , help = " Mark the repo as disabled " )
2019-04-17 03:58:00 +02:00
@click.argument ( ' url ' )
2019-05-19 15:17:04 +02:00
def add ( branch , project , disabled , url ) :
2019-05-03 00:14:09 +02:00
""" Adds a repo to track. """
2019-04-17 03:58:00 +02:00
conn = sqlite3 . connect ( data_home + " /ganarchy.db " )
c = conn . cursor ( )
2019-05-19 15:17:04 +02:00
c . execute ( ''' SELECT " git_commit " , " base_url " FROM " config " ''' )
( project_commit , base_url ) = c . fetchone ( )
if project_commit == project :
project = None
c . execute ( ''' INSERT INTO " repos " ( " url " , " active " , " branch " , " project " ) VALUES (?, ?, ?, ?) ''' , ( url , int ( not disabled ) , branch , project ) )
2019-04-17 03:58:00 +02:00
conn . commit ( )
conn . close ( )
@repo.command ( )
2019-05-19 15:17:04 +02:00
@click.option ( ' --branch ' , default = None , help = " Sets the branch to be used for the repo " )
@click.option ( ' --project ' , default = None , help = " Sets the project commit to be used for the repo " )
2019-04-17 03:58:00 +02:00
@click.argument ( ' url ' )
2019-05-19 15:17:04 +02:00
def enable ( branch , project , url ) :
2019-05-03 00:14:09 +02:00
""" Enables tracking of a repo. """
2019-04-17 03:58:00 +02:00
conn = sqlite3 . connect ( data_home + " /ganarchy.db " )
c = conn . cursor ( )
2019-05-19 15:17:04 +02:00
c . execute ( ''' SELECT " git_commit " , " base_url " FROM " config " ''' )
( project_commit , base_url ) = c . fetchone ( )
if project_commit == project :
project = None
c . execute ( ''' UPDATE " repos " SET " active " =1 WHERE " url " =? AND " branch " IS ? AND " project " IS ? ''' , ( url , branch , project ) )
2019-04-17 03:58:00 +02:00
conn . commit ( )
conn . close ( )
@repo.command ( )
2019-05-19 15:17:04 +02:00
@click.option ( ' --branch ' , default = None , help = " Sets the branch to be used for the repo " )
@click.option ( ' --project ' , default = None , help = " Sets the project commit to be used for the repo " )
2019-04-17 03:58:00 +02:00
@click.argument ( ' url ' )
2019-05-19 15:17:04 +02:00
def disable ( branch , project , url ) :
2019-05-03 00:14:09 +02:00
""" Disables tracking of a repo. """
2019-04-17 03:58:00 +02:00
conn = sqlite3 . connect ( data_home + " /ganarchy.db " )
c = conn . cursor ( )
2019-05-19 15:17:04 +02:00
c . execute ( ''' SELECT " git_commit " , " base_url " FROM " config " ''' )
( project_commit , base_url ) = c . fetchone ( )
if project_commit == project :
project = None
c . execute ( ''' UPDATE repos SET " active " =0 WHERE " url " =? AND " branch " IS ? AND " project " IS ? ''' , ( url , branch , project ) )
2019-04-17 03:58:00 +02:00
conn . commit ( )
conn . close ( )
@repo.command ( )
2019-05-19 15:17:04 +02:00
@click.option ( ' --branch ' , default = None , help = " Sets the branch to be used for the repo " )
@click.option ( ' --project ' , default = None , help = " Sets the project commit to be used for the repo " )
2019-04-17 03:58:00 +02:00
@click.argument ( ' url ' )
2019-05-19 15:17:04 +02:00
def remove ( branch , project , url ) :
2019-05-03 00:14:09 +02:00
""" Stops tracking a repo. """
2019-04-17 03:58:00 +02:00
click . confirm ( " WARNING: This operation does not delete the commits associated with the given repo! Are you sure you want to continue? This operation cannot be undone. " )
conn = sqlite3 . connect ( data_home + " /ganarchy.db " )
c = conn . cursor ( )
2019-05-19 15:17:04 +02:00
c . execute ( ''' SELECT " git_commit " , " base_url " FROM " config " ''' )
( project_commit , base_url ) = c . fetchone ( )
if project_commit == project :
project = None
c . execute ( ''' DELETE FROM " repos " WHERE " url " =? AND " branch " IS ? AND " project " IS ? ''' , ( url , branch , project ) )
c . execute ( ''' DELETE FROM " repo_history " WHERE " url " =? AND " branch " IS ? AND " project " IS ? ''' , ( url , branch , project ) )
2019-04-17 03:58:00 +02:00
conn . commit ( )
conn . close ( )
2019-05-03 00:14:09 +02:00
def migrations ( ) :
2019-05-04 01:19:24 +02:00
@ganarchy.group ( )
def migrations ( ) :
""" Modifies the DB to work with a newer/older version.
WARNING : THIS COMMAND CAN BE EXTREMELY DESTRUCTIVE ! """
@migrations.command ( )
@click.argument ( ' migration ' )
def apply ( migration ) :
""" Applies the migration with the given name. """
conn = sqlite3 . connect ( data_home + " /ganarchy.db " )
c = conn . cursor ( )
click . echo ( MIGRATIONS [ migration ] [ 0 ] )
2019-05-19 15:17:04 +02:00
for migration in MIGRATIONS [ migration ] [ 0 ] :
c . execute ( migration )
2019-05-04 01:19:24 +02:00
conn . commit ( )
conn . close ( )
@click.argument ( ' migration ' )
@migrations.command ( )
def revert ( migration ) :
""" Reverts the migration with the given name. """
conn = sqlite3 . connect ( data_home + " /ganarchy.db " )
c = conn . cursor ( )
click . echo ( MIGRATIONS [ migration ] [ 1 ] )
2019-05-19 15:17:04 +02:00
for migration in MIGRATIONS [ migration ] [ 1 ] :
c . execute ( migration )
2019-05-04 01:19:24 +02:00
conn . commit ( )
conn . close ( )
@click.argument ( ' migration ' , required = False )
@migrations.command ( )
def info ( migration ) :
""" Shows information about the migration with the given name. """
if not migration :
# TODO could be improved
click . echo ( MIGRATIONS . keys ( ) )
else :
click . echo ( MIGRATIONS [ migration ] [ 2 ] )
migrations ( )
2019-05-03 00:14:09 +02:00
2019-05-28 02:14:09 +02:00
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
2019-05-19 15:17:04 +02:00
if not branch :
2019-05-28 02:14:09 +02:00
self . branchname = " gan " + hashlib . sha256 ( url . encode ( " utf-8 " ) ) . hexdigest ( )
self . head = " HEAD "
2019-05-19 15:17:04 +02:00
else :
2019-05-28 02:14:09 +02:00
self . branchname = " gan " + hmac . new ( branch . encode ( " utf-8 " ) , url . encode ( " utf-8 " ) , " sha256 " ) . hexdigest ( )
self . head = " refs/heads/ " + branch
2019-04-17 03:58:00 +02:00
try :
2019-05-28 02:14:09 +02:00
self . hash = subprocess . check_output ( [ " git " , " -C " , cache_home , " show " , self . branchname , " -s " , " --format= % H " , " -- " ] , stderr = subprocess . DEVNULL ) . decode ( " utf-8 " ) . strip ( )
2019-04-17 03:58:00 +02:00
except subprocess . CalledProcessError :
2019-05-28 02:14:09 +02:00
self . hash = None
self . message = None
if list_metadata :
try :
self . fetch_metadata ( )
except GitError :
pass
def fetch_metadata ( self ) :
try :
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?
2019-04-17 03:58:00 +02:00
try :
2019-05-28 02:14:09 +02:00
subprocess . check_output ( [ " git " , " -C " , cache_home , " fetch " , " -q " , self . url , " + " + self . head + " : " + self . branchname ] , stderr = subprocess . STDOUT )
2019-04-17 03:58:00 +02:00
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
2019-05-28 02:14:09 +02:00
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
2019-04-17 03:58:00 +02:00
if not pre_hash :
pre_hash = post_hash
try :
2019-04-17 04:06:16 +02:00
count = int ( subprocess . check_output ( [ " git " , " -C " , cache_home , " rev-list " , " --count " , pre_hash + " .. " + post_hash , " -- " ] ) . decode ( " utf-8 " ) . strip ( ) )
2019-04-17 03:58:00 +02:00
except subprocess . CalledProcessError :
count = 0 # force-pushed
try :
2019-05-28 02:14:09 +02:00
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 )
2019-04-17 03:58:00 +02:00
return None
2019-05-28 02:14:09 +02:00
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
2019-04-17 03:58:00 +02:00
os . makedirs ( cache_home , exist_ok = True )
2019-05-28 02:14:09 +02:00
# make sure it is a git repo
2019-04-17 03:58:00 +02:00
subprocess . call ( [ " git " , " -C " , cache_home , " init " , " -q " ] )
conn = sqlite3 . connect ( data_home + " /ganarchy.db " )
2019-05-28 02:14:09 +02:00
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
2019-05-19 15:17:04 +02:00
if not base_url or not ( project or project_commit ) :
2019-04-22 23:59:02 +02:00
click . echo ( " No base URL or project commit specified " , err = True )
return
2019-05-19 15:17:04 +02:00
if project_commit == project :
project = None
elif project is not None :
project_commit = project
2019-04-17 03:58:00 +02:00
entries = [ ]
generate_html = [ ]
2019-05-28 02:14:09 +02:00
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 ( )
2019-04-17 03:58:00 +02:00
if result is not None :
count , post_hash , msg = result
2019-05-28 02:14:09 +02:00
entries . append ( ( repo . url , count , post_hash , repo . branch , project ) )
generate_html . append ( ( repo . url , msg , count , repo . branch ) )
p . refresh_metadata ( )
2019-04-19 21:42:59 +02:00
# sort stuff twice because reasons
entries . sort ( key = lambda x : x [ 1 ] , reverse = True )
generate_html . sort ( key = lambda x : x [ 2 ] , reverse = True )
2019-05-19 15:17:04 +02:00
c . executemany ( ''' INSERT INTO " repo_history " ( " url " , " count " , " head_commit " , " branch " , " project " ) VALUES (?, ?, ?, ?, ?) ''' , entries )
2019-04-17 03:58:00 +02:00
conn . commit ( )
html_entries = [ ]
2019-05-19 15:17:04 +02:00
for ( url , msg , count , branch ) in generate_html :
history = c . execute ( ''' SELECT " count " FROM " repo_history " WHERE " url " = ? AND " branch " IS ? AND " project " IS ? ORDER BY " entry " ASC ''' , ( url , branch , project ) ) . fetchall ( )
2019-04-17 03:58:00 +02:00
# TODO process history into SVG
2019-05-19 15:17:04 +02:00
html_entries . append ( ( url , msg , " " , branch ) )
2019-05-28 02:14:09 +02:00
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 ,
2019-04-22 23:59:02 +02:00
repos = html_entries ,
2019-05-28 02:14:09 +02:00
base_url = base_url ,
# I don't think this thing supports deprecating the above?
project = p ,
ganarchy = instance ) )
2019-04-17 03:58:00 +02:00
if __name__ == " __main__ " :
ganarchy ( )