Browse Source

Completely reworked configuration

SoniEx2 2 months ago
parent
commit
752f4d36ad
2 changed files with 226 additions and 186 deletions
  1. 225
    186
      ganarchy.py
  2. 1
    0
      requirements.txt

+ 225
- 186
ganarchy.py View File

@@ -24,31 +24,37 @@ import hashlib
24 24
 import hmac
25 25
 import jinja2
26 26
 import re
27
+import qtoml
28
+from collections import defaultdict
29
+from urllib.parse import urlparse
27 30
 
28 31
 MIGRATIONS = {
29
-        "gen-index": (
32
+        "toml-config": (
30 33
                 (
31
-                    """ALTER TABLE "config" ADD COLUMN "title" TEXT"""),
32
-                (),
33
-                "supports generating an index page"
34
+                    '''UPDATE "repo_history" SET "project" = (SELECT "git_commit" FROM "config") WHERE "project" IS NULL''',
35
+                    '''ALTER TABLE "repos" RENAME TO "repos_old"''',),
36
+                (
37
+                    '''UPDATE "repo_history" SET "project" = NULL WHERE "project" = (SELECT "git_commit" FROM "config")''',
38
+                    '''ALTER TABLE "repos_old" RENAME TO "repos"''',),
39
+                "switches to toml config format. the old 'repos' table is preserved as 'repos_old'"
34 40
             ),
35 41
         "better-project-management": (
36 42
                 (
37
-                    """ALTER TABLE "repos" ADD COLUMN "branch" TEXT""",
38
-                    """ALTER TABLE "repos" ADD COLUMN "project" TEXT""",
39
-                    """CREATE UNIQUE INDEX "repos_url_branch_project" ON "repos" ("url", "branch", "project")""",
40
-                    """CREATE INDEX "repos_project" ON "repos" ("project")""",
41
-                    """ALTER TABLE "repo_history" ADD COLUMN "branch" TEXT""",
42
-                    """ALTER TABLE "repo_history" ADD COLUMN "project" TEXT""",
43
-                    """CREATE INDEX "repo_history_url_branch_project" ON "repo_history" ("url", "branch", "project")"""),
43
+                    '''ALTER TABLE "repos" ADD COLUMN "branch" TEXT''',
44
+                    '''ALTER TABLE "repos" ADD COLUMN "project" TEXT''',
45
+                    '''CREATE UNIQUE INDEX "repos_url_branch_project" ON "repos" ("url", "branch", "project")''',
46
+                    '''CREATE INDEX "repos_project" ON "repos" ("project")''',
47
+                    '''ALTER TABLE "repo_history" ADD COLUMN "branch" TEXT''',
48
+                    '''ALTER TABLE "repo_history" ADD COLUMN "project" TEXT''',
49
+                    '''CREATE INDEX "repo_history_url_branch_project" ON "repo_history" ("url", "branch", "project")''',),
44 50
                 (
45
-                    """DELETE FROM "repos" WHERE "branch" IS NOT NULL OR "project" IS NOT NULL""",
46
-                    """DELETE FROM "repo_history" WHERE "branch" IS NOT NULL OR "project" IS NOT NULL"""),
51
+                    '''DELETE FROM "repos" WHERE "branch" IS NOT NULL OR "project" IS NOT NULL''',
52
+                    '''DELETE FROM "repo_history" WHERE "branch" IS NOT NULL OR "project" IS NOT NULL''',),
47 53
                 "supports multiple projects, and allows choosing non-default branches"
48 54
             ),
49 55
         "test": (
50
-                ("""-- apply""",),
51
-                ("""-- revert""",),
56
+                ('''-- apply''',),
57
+                ('''-- revert''',),
52 58
                 "does nothing"
53 59
             )
54 60
         }
@@ -120,6 +126,17 @@ def get_template_loader():
120 126
         </p>
121 127
     </body>
122 128
 </html>
129
+""",
130
+            ## index.toml
131
+            'index.toml': """# Generated by GAnarchy
132
+
133
+{%- for project, repos in config.projects.items() %}
134
+[projects.{{project}}]
135
+{%- for repo_url, branches in repos.items() %}{% for branch, options in branches.items() %}{% if options.active %}
136
+"{{repo_url|tomle}}".{% if branch %}"{{branch|tomle}}"{% else %}HEAD{% endif %} = { active=true }
137
+{%- endif %}{% endfor %}
138
+{%- endfor %}
139
+{% endfor -%}
123 140
 """,
124 141
             ## project.html FIXME
125 142
             'project.html': """<!DOCTYPE html>
@@ -169,6 +186,24 @@ def get_template_loader():
169 186
         })
170 187
     ])
171 188
 
189
+tomletrans = str.maketrans({
190
+    0: '\\u0000', 1: '\\u0001', 2: '\\u0002', 3: '\\u0003', 4: '\\u0004',
191
+    5: '\\u0005', 6: '\\u0006', 7: '\\u0007', 8: '\\b', 9: '\\t', 10: '\\n',
192
+    11: '\\u000B', 12: '\\f', 13: '\\r', 14: '\\u000E', 15: '\\u000F',
193
+    16: '\\u0010', 17: '\\u0011', 18: '\\u0012', 19: '\\u0013', 20: '\\u0014',
194
+    21: '\\u0015', 22: '\\u0016', 23: '\\u0017', 24: '\\u0018', 25: '\\u0019',
195
+    26: '\\u001A', 27: '\\u001B', 28: '\\u001C', 29: '\\u001D', 30: '\\u001E',
196
+    31: '\\u001F', '"': '\\"', '\\': '\\\\'
197
+    })
198
+def tomlescape(value):
199
+    return value.translate(tomletrans)
200
+
201
+def get_env():
202
+    env = jinja2.Environment(loader=get_template_loader(), autoescape=False)
203
+    env.filters['tomlescape'] = tomlescape
204
+    env.filters['tomle'] = env.filters['tomlescape']
205
+    return env
206
+
172 207
 
173 208
 @click.group()
174 209
 def ganarchy():
@@ -180,118 +215,8 @@ def initdb():
180 215
     os.makedirs(data_home, exist_ok=True)
181 216
     conn = sqlite3.connect(data_home + "/ganarchy.db")
182 217
     c = conn.cursor()
183
-    c.execute('''CREATE TABLE "repos" ("url" TEXT PRIMARY KEY, "active" INT, "branch" TEXT, "project" TEXT)''')
184
-    c.execute('''CREATE UNIQUE INDEX "repos_url_branch_project" ON "repos" ("url", "branch", "project")''')
185
-    c.execute('''CREATE INDEX "repos_project" ON "repos" ("project")''')
186
-    c.execute('''CREATE INDEX "repos_active" ON "repos" ("active")''')
187 218
     c.execute('''CREATE TABLE "repo_history" ("entry" INTEGER PRIMARY KEY ASC AUTOINCREMENT, "url" TEXT, "count" INTEGER, "head_commit" TEXT, "branch" TEXT, "project" TEXT)''')
188 219
     c.execute('''CREATE INDEX "repo_history_url_branch_project" ON "repo_history" ("url", "branch", "project")''')
189
-    c.execute('''CREATE TABLE "config" ("git_commit" TEXT, "base_url" TEXT, "title" TEXT)''')
190
-    c.execute('''INSERT INTO "config" VALUES ('', '', '')''')
191
-    conn.commit()
192
-    conn.close()
193
-
194
-@ganarchy.command()
195
-@click.argument('commit')
196
-def set_commit(commit):
197
-    """Sets the commit that represents the project."""
198
-    if not re.fullmatch("[a-fA-F0-9]{40}", commit):
199
-        raise click.BadArgumentUsage("COMMIT must be a git commit hash")
200
-    conn = sqlite3.connect(data_home + "/ganarchy.db")
201
-    c = conn.cursor()
202
-    c.execute('''UPDATE "config" SET "git_commit"=?''', (commit,))
203
-    conn.commit()
204
-    conn.close()
205
-
206
-@ganarchy.command()
207
-@click.argument('base-url')
208
-def set_base_url(base_url):
209
-    """Sets the GAnarchy instance's base URL. Used for the URI handler."""
210
-    conn = sqlite3.connect(data_home + "/ganarchy.db")
211
-    c = conn.cursor()
212
-    c.execute('''UPDATE "config" SET "base_url"=?''', (base_url,))
213
-    conn.commit()
214
-    conn.close()
215
-
216
-@ganarchy.command()
217
-@click.argument('title')
218
-def set_title(title):
219
-    """Sets the GAnarchy instance's title. This title is displayed on the index."""
220
-    conn = sqlite3.connect(data_home + "/ganarchy.db")
221
-    c = conn.cursor()
222
-    c.execute('''UPDATE "config" SET "title"=?''', (title,))
223
-    conn.commit()
224
-    conn.close()
225
-
226
-# TODO move --branch into here?
227
-@ganarchy.group()
228
-def repo():
229
-    """Modifies repos to track."""
230
-
231
-@repo.command()
232
-@click.option('--branch', default=None, help="Sets the branch to be used for the repo")
233
-@click.option('--project', default=None, help="Sets the project commit to be used for the repo")
234
-@click.option('--disabled', default=False, is_flag=True, help="Mark the repo as disabled")
235
-@click.argument('url')
236
-def add(branch, project, disabled, url):
237
-    """Adds a repo to track."""
238
-    conn = sqlite3.connect(data_home + "/ganarchy.db")
239
-    c = conn.cursor()
240
-    c.execute('''SELECT "git_commit", "base_url" FROM "config"''')
241
-    (project_commit, base_url) = c.fetchone()
242
-    if project_commit == project:
243
-        project = None
244
-    c.execute('''INSERT INTO "repos" ("url", "active", "branch", "project") VALUES (?, ?, ?, ?)''', (url, int(not disabled), branch, project))
245
-    conn.commit()
246
-    conn.close()
247
-
248
-@repo.command()
249
-@click.option('--branch', default=None, help="Sets the branch to be used for the repo")
250
-@click.option('--project', default=None, help="Sets the project commit to be used for the repo")
251
-@click.argument('url')
252
-def enable(branch, project, url):
253
-    """Enables tracking of a repo."""
254
-    conn = sqlite3.connect(data_home + "/ganarchy.db")
255
-    c = conn.cursor()
256
-    c.execute('''SELECT "git_commit", "base_url" FROM "config"''')
257
-    (project_commit, base_url) = c.fetchone()
258
-    if project_commit == project:
259
-        project = None
260
-    c.execute('''UPDATE "repos" SET "active"=1 WHERE "url"=? AND "branch" IS ? AND "project" IS ?''', (url, branch, project))
261
-    conn.commit()
262
-    conn.close()
263
-
264
-@repo.command()
265
-@click.option('--branch', default=None, help="Sets the branch to be used for the repo")
266
-@click.option('--project', default=None, help="Sets the project commit to be used for the repo")
267
-@click.argument('url')
268
-def disable(branch, project, url):
269
-    """Disables tracking of a repo."""
270
-    conn = sqlite3.connect(data_home + "/ganarchy.db")
271
-    c = conn.cursor()
272
-    c.execute('''SELECT "git_commit", "base_url" FROM "config"''')
273
-    (project_commit, base_url) = c.fetchone()
274
-    if project_commit == project:
275
-        project = None
276
-    c.execute('''UPDATE repos SET "active"=0 WHERE "url"=? AND "branch" IS ? AND "project" IS ?''', (url, branch, project))
277
-    conn.commit()
278
-    conn.close()
279
-
280
-@repo.command()
281
-@click.option('--branch', default=None, help="Sets the branch to be used for the repo")
282
-@click.option('--project', default=None, help="Sets the project commit to be used for the repo")
283
-@click.argument('url')
284
-def remove(branch, project, url):
285
-    """Stops tracking a repo."""
286
-    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.")
287
-    conn = sqlite3.connect(data_home + "/ganarchy.db")
288
-    c = conn.cursor()
289
-    c.execute('''SELECT "git_commit", "base_url" FROM "config"''')
290
-    (project_commit, base_url) = c.fetchone()
291
-    if project_commit == project:
292
-        project = None
293
-    c.execute('''DELETE FROM "repos" WHERE "url"=? AND "branch" IS ? AND "project" IS ?''', (url, branch, project))
294
-    c.execute('''DELETE FROM "repo_history" WHERE "url"=? AND "branch" IS ? AND "project" IS ?''', (url, branch, project))
295 220
     conn.commit()
296 221
     conn.close()
297 222
 
@@ -363,10 +288,11 @@ class Git:
363 288
 GIT = Git(cache_home)
364 289
 
365 290
 class Repo:
366
-    def __init__(self, dbconn, project, url, branch, head_commit, list_metadata=False):
291
+    def __init__(self, dbconn, project_commit, url, branch, head_commit, list_metadata=False):
367 292
         self.url = url
368 293
         self.branch = branch
369
-        self.project_commit = project.commit
294
+        self.project_commit = project_commit
295
+        self.erroring = False
370 296
 
371 297
         if not branch:
372 298
             self.branchname = "gan" + hashlib.sha256(url.encode("utf-8")).hexdigest()
@@ -378,9 +304,10 @@ class Repo:
378 304
         if head_commit:
379 305
             self.hash = head_commit
380 306
         else:
381
-            try:
307
+            try: # FIXME should we even do this?
382 308
                 self.hash = GIT.get_hash(self.branchname)
383 309
             except GitError:
310
+                self.erroring = True
384 311
                 self.hash = None
385 312
 
386 313
         self.message = None
@@ -388,6 +315,7 @@ class Repo:
388 315
             try:
389 316
                 self.update_metadata()
390 317
             except GitError:
318
+                self.erroring = True
391 319
                 pass
392 320
 
393 321
     def update_metadata(self):
@@ -402,14 +330,15 @@ class Repo:
402 330
         except subprocess.CalledProcessError as e:
403 331
             # This may error for various reasons, but some are important: dead links, etc
404 332
             click.echo(e.output, err=True)
333
+            self.erroring = True
405 334
             return None
406 335
         pre_hash = self.hash
407 336
         try:
408 337
             post_hash = GIT.get_hash(self.branchname)
409 338
         except GitError as e:
410 339
             # This should never happen, but maybe there's some edge cases?
411
-            # Can you force-push an empty branch?
412
-            # TODO
340
+            # TODO check
341
+            self.erroring = True
413 342
             return None
414 343
         self.hash = post_hash
415 344
         if not pre_hash:
@@ -421,30 +350,31 @@ class Repo:
421 350
         try:
422 351
             subprocess.check_call(["git", "-C", cache_home, "merge-base", "--is-ancestor", self.project_commit, self.branchname], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
423 352
             self.update_metadata()
424
-            return count, post_hash, self.message
353
+            return count
425 354
         except (subprocess.CalledProcessError, GitError) as e:
426 355
             click.echo(e, err=True)
356
+            self.erroring = True
427 357
             return None
428 358
 
429 359
 class Project:
430
-    def __init__(self, dbconn, ganarchy, project_commit, list_repos=False):
360
+    def __init__(self, dbconn, project_commit, list_repos=False):
431 361
         self.commit = project_commit
432
-        if ganarchy.project_commit == project_commit:
433
-            project_commit = None
434 362
         self.refresh_metadata()
363
+        self.repos = None
435 364
         if list_repos:
436
-            repos = []
437
-            with dbconn:
438
-                for (e, url, branch, head_commit) in dbconn.execute('''SELECT "max"("e"), "url", "branch", "head_commit" FROM (SELECT "max"("T1"."entry") "e", "T1"."url", "T1"."branch", "T1"."head_commit" FROM "repo_history" "T1"
439
-                                                                    WHERE (SELECT "active" FROM "repos" "T2" WHERE "url" = "T1"."url" AND "branch" IS "T1"."branch" AND "project" IS ?1)
440
-                                                                    GROUP BY "T1"."url", "T1"."branch"
441
-                                                                    UNION
442
-                                                                    SELECT null, "T3"."url", "T3"."branch", null FROM "repos" "T3" WHERE "active" AND "project" IS ?1)
443
-                                           GROUP BY "url" ORDER BY "e"''', (project_commit,)):
444
-                    repos.append(Repo(dbconn, self, url, branch, head_commit))
445
-            self.repos = repos
446
-        else:
447
-            self.repos = None
365
+            self.list_repos(dbconn)
366
+
367
+    def list_repos(self, dbconn):
368
+        repos = []
369
+        with dbconn:
370
+            for (e, url, branch, head_commit) in dbconn.execute('''SELECT "max"("e"), "url", "branch", "head_commit" FROM (SELECT "max"("T1"."entry") "e", "T1"."url", "T1"."branch", "T1"."head_commit" FROM "repo_history" "T1"
371
+                                                                WHERE (SELECT "active" FROM "repos" "T2" WHERE "url" = "T1"."url" AND "branch" IS "T1"."branch" AND "project" IS ?1)
372
+                                                                GROUP BY "T1"."url", "T1"."branch"
373
+                                                                UNION
374
+                                                                SELECT null, "T3"."url", "T3"."branch", null FROM "repos" "T3" WHERE "active" AND "project" IS ?1)
375
+                                       GROUP BY "url" ORDER BY "e"''', (self.commit,)):
376
+                repos.append(Repo(dbconn, self.commit, url, branch, head_commit))
377
+        self.repos = repos
448 378
 
449 379
     def refresh_metadata(self):
450 380
         try:
@@ -452,8 +382,8 @@ class Project:
452 382
             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))
453 383
             if not project_title.strip(): # FIXME
454 384
                 project_title, project_desc = ("Error parsing project commit",)*2
455
-            if project_desc: # FIXME
456
-                project_desc = project_desc.strip()
385
+            # if project_desc: # FIXME
386
+            #     project_desc = project_desc.strip()
457 387
             self.commit_body = project
458 388
             self.title = project_title
459 389
             self.description = project_desc
@@ -463,74 +393,183 @@ class Project:
463 393
             self.description = None
464 394
 
465 395
     def update(self):
466
-        # TODO
396
+        # TODO? check if working correctly
397
+        results = [(repo, repo.update()) for repo in self.repos]
467 398
         self.refresh_metadata()
399
+        return results
468 400
 
469 401
 class GAnarchy:
470
-    def __init__(self, dbconn, list_projects=False):
471
-        with dbconn:
472
-            # TODO
473
-            #(project_commit, base_url, title) = dbconn.execute('''SELECT "git_commit", "base_url", "title" FROM "config"''').fetchone()
474
-            (project_commit, base_url) = dbconn.execute('''SELECT "git_commit", "base_url" FROM "config"''').fetchone()
475
-            title = None
476
-            self.project_commit = project_commit
477
-            self.base_url = base_url
478
-            if not base_url:
479
-                pass ## TODO
480
-            if not title:
481
-                from urllib.parse import urlparse
482
-                title = "GAnarchy on " + urlparse(base_url).hostname
483
-            self.title = title
402
+    def __init__(self, dbconn, config, list_projects=False, list_repos=False):
403
+        base_url = config.base_url
404
+        title = config.title
405
+        if not base_url:
406
+            # FIXME use a more appropriate error type
407
+            raise ValueError
408
+        if not title:
409
+            title = "GAnarchy on " + urlparse(base_url).hostname
410
+        self.title = title
411
+        self.base_url = base_url
412
+        # load config onto DB
413
+        c = dbconn.cursor()
414
+        c.execute('''CREATE TEMPORARY TABLE "repos" ("url" TEXT PRIMARY KEY, "active" INT, "branch" TEXT, "project" TEXT)''')
415
+        c.execute('''CREATE UNIQUE INDEX "temp"."repos_url_branch_project" ON "repos" ("url", "branch", "project")''')
416
+        c.execute('''CREATE INDEX "temp"."repos_project" ON "repos" ("project")''')
417
+        c.execute('''CREATE INDEX "temp"."repos_active" ON "repos" ("active")''')
418
+        for (project_commit, repos) in config.projects.items():
419
+            for (repo_url, branches) in repos.items():
420
+                for (branchname, options) in branches.items():
421
+                    if options['active']: # no need to insert inactive repos since they get ignored anyway
422
+                        c.execute('''INSERT INTO "repos" VALUES (?, ?, ?, ?)''', (repo_url, 1, branchname, project_commit))
423
+        dbconn.commit()
484 424
         if list_projects:
485 425
             projects = []
486 426
             with dbconn:
487 427
                 for (project,) in dbconn.execute('''SELECT DISTINCT "project" FROM "repos" '''): # FIXME? *maybe* sort by activity in the future
488
-                    if project == None:
489
-                        project = self.project_commit
490
-                    projects.append(Project(dbconn, ganarchy, project))
491
-            projects.sort(key=lambda project: project.title)
428
+                    projects.append(Project(dbconn, project, list_repos=list_repos))
429
+            projects.sort(key=lambda project: project.title) # sort projects by title
492 430
             self.projects = projects
493 431
         else:
494 432
             self.projects = None
495 433
 
434
+class Config:
435
+    def __init__(self, toml_file, base=None, remove=True):
436
+        self.projects = defaultdict(lambda: defaultdict(lambda: defaultdict(lambda: defaultdict(dict))))
437
+        config_data = qtoml.load(toml_file)
438
+        self.title = config_data.get('title', '')
439
+        self.base_url = config_data.get('base_url', '')
440
+        # TODO blocked domains (but only read them from config_data if remove is True)
441
+        self.blocked_domains = []
442
+        self.blocked_domain_suffixes = []
443
+        self.blocked_domains.sort()
444
+        self.blocked_domain_suffixes.sort(key=lambda x: x[::-1])
445
+        # FIXME remove duplicates and process invalid entries
446
+        self.blocked_domains = tuple(self.blocked_domains)
447
+        self.blocked_domain_suffixes = tuple(self.blocked_domain_suffixes) # MUST be tuple
448
+        # TODO re.compile("(^" + "|^".join(map(re.escape, domains)) + "|" + "|".join(map(re.escape, suffixes) + ")$")
449
+        if base:
450
+            self._update_projects(base.projects, sanitize=False) # already sanitized
451
+        projects = config_data.get('projects', {})
452
+        self._update_projects(projects, remove=remove)
453
+
454
+    def _update_projects(self, projects, remove, sanitize=True):
455
+        for (project_commit, repos) in projects.items():
456
+            if sanitize and not isinstance(repos, dict):
457
+                # TODO emit warnings?
458
+                continue
459
+            if sanitize and not re.fullmatch("[0-9a-fA-F]{40}|[0-9a-fA-F]{64}", project_commit): # future-proofing: sha256 support
460
+                # TODO emit warnings?
461
+                continue
462
+            project = self.projects[project_commit]
463
+            for (repo_url, branches) in repos.items():
464
+                if sanitize and not isinstance(branches, dict):
465
+                    # TODO emit warnings?
466
+                    continue
467
+                try:
468
+                    u = urlparse(repo_url)
469
+                    if not u:
470
+                        raise ValueError
471
+                    getattr(u, 'port') # raises ValueError if port is invalid
472
+                    if u.scheme in ('file', ''):
473
+                        raise ValueError
474
+                    if (u.hostname in self.blocked_domains) or (u.hostname.endswith(self.blocked_domain_suffixes)):
475
+                        raise ValueError
476
+                except ValueError:
477
+                    if sanitize:
478
+                        # TODO emit warnings?
479
+                        continue
480
+                    else:
481
+                        raise
482
+                repo = project[repo_url]
483
+                for (branchname, options) in branches.items():
484
+                    if sanitize and not isinstance(options, dict):
485
+                        # TODO emit warnings?
486
+                        continue
487
+                    if branchname == "HEAD":
488
+                        if sanitize:
489
+                            # feels weird, but generally makes things easier
490
+                            # DO NOT emit warnings here. this is deliberate.
491
+                            branchname = None
492
+                        else:
493
+                            raise ValueError
494
+                    branch = repo[branchname]
495
+                    active = options.get('active', False)
496
+                    if active not in (True, False):
497
+                        if sanitize:
498
+                            # TODO emit warnings?
499
+                            continue
500
+                        else:
501
+                            raise ValueError
502
+                    ## | remove | branch.active | options.active | result |
503
+                    ## |    x   |     false     |     false      |  false |
504
+                    ## |    x   |     false     |     true       |  true  |
505
+                    ## |    x   |     true      |     true       |  true  |
506
+                    ## |  false |     true      |     false      |  true  |
507
+                    ## |  true  |     true      |     false      |  false |
508
+                    branch['active'] = branch.get('active', False) or active
509
+                    if remove and not active:
510
+                        branch['active'] = False
511
+
512
+@ganarchy.command()
513
+@click.option('--skip-errors/--no-skip-errors', default=False)
514
+@click.argument('files', type=click.File('r', encoding='utf-8'), nargs=-1)
515
+def merge_configs(skip_errors, files):
516
+    """Merges config files."""
517
+    config = None
518
+    for f in files:
519
+        try:
520
+            f.reconfigure(newline='')
521
+            config = Config(f, config, remove=False)
522
+        except (UnicodeDecodeError, qtoml.decoder.TOMLDecodeError):
523
+            if not skip_errors:
524
+                raise
525
+    if config:
526
+        env = get_env()
527
+        template = env.get_template('index.toml')
528
+        click.echo(template.render(config=config))
496 529
 
497 530
 @ganarchy.command()
498 531
 @click.argument('project', required=False)
499 532
 def cron_target(project):
500 533
     """Runs ganarchy as a cron target."""
534
+    conf = None
535
+    # reverse order is intentional
536
+    for d in reversed(config_dirs):
537
+        try:
538
+            conf = Config(open(d + "/config.toml", 'r', encoding='utf-8', newline=''), conf)
539
+        except (OSError, UnicodeDecodeError, qtoml.decoder.TOMLDecodeError):
540
+            pass
541
+    with open(config_home + "/config.toml", 'r', encoding='utf-8', newline='') as f:
542
+        conf = Config(f, conf)
543
+    env = get_env()
544
+    if project == "config":
545
+        # render the config
546
+        # doesn't have access to a GAnarchy object. this is deliberate.
547
+        template = env.get_template('index.toml')
548
+        click.echo(template.render(config = conf))
549
+        return
501 550
     # make sure the cache dir exists
502 551
     os.makedirs(cache_home, exist_ok=True)
503 552
     # make sure it is a git repo
504 553
     subprocess.call(["git", "-C", cache_home, "init", "-q"])
505 554
     conn = sqlite3.connect(data_home + "/ganarchy.db")
506
-    instance = GAnarchy(conn, list_projects=project=="index")
507
-    env = jinja2.Environment(loader=get_template_loader(), autoescape=False)
555
+    instance = GAnarchy(conn, conf, list_projects=project in ["index", "config"])
508 556
     if project == "index":
509 557
         # render the index
510 558
         template = env.get_template('index.html')
511 559
         click.echo(template.render(ganarchy = instance))
512 560
         return
513
-    project_commit = instance.project_commit
514
-    base_url = instance.base_url
515
-    if not base_url or not (project or project_commit):
561
+    if not instance.base_url or not project:
516 562
         click.echo("No base URL or project commit specified", err=True)
517 563
         return
518
-    if project_commit == project:
519
-        project = None
520
-    elif project is not None:
521
-        project_commit = project
522 564
     entries = []
523 565
     generate_html = []
524 566
     c = conn.cursor()
525
-    p = Project(conn, instance, project_commit, list_repos=True)
526
-    # FIXME: this should be moved into Project.update()
527
-    for repo in p.repos:
528
-        result = repo.update()
529
-        if result is not None:
530
-            count, post_hash, msg = result
531
-            entries.append((repo.url, count, post_hash, repo.branch, project))
532
-            generate_html.append((repo.url, msg, count, repo.branch))
533
-    p.refresh_metadata()
567
+    p = Project(conn, project, list_repos=True)
568
+    results = p.update()
569
+    for (repo, count) in results:
570
+        if count is not None:
571
+            entries.append((repo.url, count, repo.hash, repo.branch, project))
572
+            generate_html.append((repo.url, repo.message, count, repo.branch))
534 573
     # sort stuff twice because reasons
535 574
     entries.sort(key=lambda x: x[1], reverse=True)
536 575
     generate_html.sort(key=lambda x: x[2], reverse=True)
@@ -547,7 +586,7 @@ def cron_target(project):
547 586
                                project_body   = p.commit_body,
548 587
                                project_commit = p.commit,
549 588
                                repos          = html_entries,
550
-                               base_url       = base_url,
589
+                               base_url       = instance.base_url,
551 590
                                # I don't think this thing supports deprecating the above?
552 591
                                project        = p,
553 592
                                ganarchy       = instance))

+ 1
- 0
requirements.txt View File

@@ -1,2 +1,3 @@
1 1
 Click==7.0
2 2
 Jinja2==2.10.1
3
+qtoml==0.2.4

Loading…
Cancel
Save