Commit

Author:

Hash:

Timestamp:

+19860 -19748 +/-275 browse

Kevin Schoon [me@kevinschoon.com]

8549e2c25a44b9e8cbb41c275d06fd6852e4bd99

Sat, 16 Mar 2024 15:12:02 +0000 (1.3 years ago)

move main ayllu binary into subcrate
1diff --git a/Cargo.lock b/Cargo.lock
2index cf8671b..eb34e63 100644
3--- a/Cargo.lock
4+++ b/Cargo.lock
5 @@ -500,15 +500,11 @@ dependencies = [
6 "async-trait",
7 "axum",
8 "axum-extra",
9- "ayllu-build",
10- "ayllu-mail",
11- "ayllu-xmpp",
12 "ayllu_api",
13 "ayllu_config",
14 "ayllu_database",
15 "ayllu_git",
16 "ayllu_rpc",
17- "ayllu_scheduler",
18 "capnp",
19 "capnp-rpc",
20 "cc",
21 @@ -560,7 +556,6 @@ dependencies = [
22 "ayllu_database",
23 "ayllu_git",
24 "ayllu_rpc",
25- "ayllu_scheduler",
26 "capnp",
27 "capnp-rpc",
28 "clap 4.5.3",
29 diff --git a/Cargo.toml b/Cargo.toml
30index 973d7be..0ddcdf2 100644
31--- a/Cargo.toml
32+++ b/Cargo.toml
33 @@ -1,8 +1,3 @@
34- [package]
35- name = "ayllu"
36- version = "0.2.1"
37- edition = "2021"
38-
39 [workspace]
40 members = [
41 "crates/api",
42 @@ -11,80 +6,21 @@ members = [
43 "crates/rpc",
44 "crates/scheduler",
45 "crates/database",
46- "ayllu-mail",
47+ "ayllu",
48 "ayllu-build",
49+ "ayllu-mail",
50 "ayllu-xmpp"
51 ]
52
53- [[bin]]
54- name = "ayllu"
55-
56- [workspace.dependencies]
57- ayllu_api = { version = "0.2.1", path = "./crates/api"}
58- ayllu_config = { version = "0.2.1", path = "./crates/config"}
59- ayllu_database = { version = "0.2.1", path = "./crates/database"}
60- ayllu_git = { version = "0.2.1", path = "./crates/git"}
61- ayllu_rpc = { version = "0.2.1", path = "./crates/rpc"}
62- ayllu_scheduler = {version = "0.2.1", path = "./crates/scheduler"}
63- ayllu-xmpp = { version = "0.2.1", path = "ayllu-xmpp"}
64- ayllu-build = {version = "0.2.1", path = "ayllu-build"}
65- ayllu-mail = { version = "0.2.1", path = "ayllu-mail"}
66-
67- [dependencies]
68- ayllu_api = { workspace = true }
69- ayllu_config = { workspace = true }
70- ayllu_database = { workspace = true }
71- ayllu_git = { workspace = true }
72- ayllu_rpc = { workspace = true }
73- ayllu_scheduler = {workspace = true}
74- ayllu-mail = {workspace = true}
75- ayllu-build = {workspace = true}
76- ayllu-xmpp = {workspace = true}
77-
78- sqlx = { version = "0.7.3", features = [ "runtime-tokio-rustls", "sqlite", "macros", "time" ] }
79- git2 = "0.17.1"
80- serde = { version = "1.0", features = ["derive"] }
81- clap = { version = "4.3.0", features = ["derive"] }
82- toml = "0.7.4"
83- comrak = { version = "0.18.0"}
84- url = "2.3.1"
85- tree-sitter-highlight = "0.20.1"
86- tokei = "12.1.2"
87- time = "0.3.21"
88- futures = "0.3.28"
89- time-macros = "0.2.9"
90- file-mode = "0.1.2"
91- tera = "1.19.0"
92- lazy_static = "1.4.0"
93- mime_guess = "2.0.4"
94- serde_json = "1.0.97"
95- tree-sitter = "0.20.10"
96- plotters = "0.3.5"
97- rand = "0.8.5"
98- tabwriter = "1.2.1"
99- libloading = "0.8.0"
100- log = "0.4.19"
101- env_logger = "0.10.0"
102- rss = "2.0.5"
103- globwalk = "0.8.1"
104- anyhow = "1.0.75"
105- tokio = { version = "1.32.0", features = ["full"] }
106- axum = { version = "0.7.3", features = ["macros"] }
107- axum-extra = { version = "0.9.1", features = ["cookie"] }
108- tokio-util = { version = "0.7.9", features = ["io", "compat"] }
109- tower-http = { version = "0.5.0", features = ["tracing", "add-extension", "trace", "normalize-path"] }
110- tracing-subscriber = { version = "0.3.17", features = ["env-filter"] }
111- tracing = "0.1.40"
112- tower = { version = "0.4.13", features = ["util", "timeout", "tracing"] }
113- mime = "0.3.17"
114- capnp-rpc = "0.18.0"
115- capnp = "0.18.1"
116- async-trait = "0.1.74"
117- webfinger = "0.5.1"
118-
119- # NOTE: this must be cautiously updated along with sqlx and rusqlite.
120- [dependencies.libsqlite3-sys]
121- version = "0.27.0"
122+ resolver = "2"
123
124- [build-dependencies]
125- cc="*"
126+ # [workspace.dependencies]
127+ # ayllu_api = { version = "0.2.1", path = "./crates/api"}
128+ # ayllu_config = { version = "0.2.1", path = "./crates/config"}
129+ # ayllu_database = { version = "0.2.1", path = "./crates/database"}
130+ # ayllu_git = { version = "0.2.1", path = "./crates/git"}
131+ # ayllu_rpc = { version = "0.2.1", path = "./crates/rpc"}
132+ # ayllu_scheduler = {version = "0.2.1", path = "./crates/scheduler"}
133+ # ayllu-xmpp = { version = "0.2.1", path = "ayllu-xmpp"}
134+ # ayllu-build = {version = "0.2.1", path = "ayllu-build"}
135+ # ayllu-mail = { version = "0.2.1", path = "ayllu-mail"}
136 diff --git a/ayllu-build/Cargo.toml b/ayllu-build/Cargo.toml
137index e832950..72373d8 100644
138--- a/ayllu-build/Cargo.toml
139+++ b/ayllu-build/Cargo.toml
140 @@ -3,15 +3,18 @@ name = "ayllu-build"
141 version = "0.2.1"
142 edition = "2021"
143
144+ [[bin]]
145+ name = "ayllu-build"
146+
147 [dependencies]
148+ ayllu_api = {path = "../crates/api"}
149+ ayllu_config = {path = "../crates/config"}
150+ ayllu_database = {path = "../crates/database"}
151+ ayllu_rpc = {path = "../crates/rpc"}
152+ ayllu_git = {path = "../crates/git"}
153+
154 anyhow = "1.0.75"
155 clap = "4.4.8"
156- ayllu_api = {workspace = true}
157- ayllu_config = {workspace = true}
158- ayllu_database = {workspace = true}
159- ayllu_rpc = {workspace = true}
160- ayllu_git = {workspace = true}
161- ayllu_scheduler = {workspace = true}
162 nickel-lang-core = { version = "0.3.0", default-features = false }
163 petgraph = "0.6.4"
164 rand = "0.8.5"
165 diff --git a/ayllu-mail/Cargo.toml b/ayllu-mail/Cargo.toml
166index afb1c6b..855fa64 100644
167--- a/ayllu-mail/Cargo.toml
168+++ b/ayllu-mail/Cargo.toml
169 @@ -3,10 +3,14 @@ name = "ayllu-mail"
170 version = "0.2.1"
171 edition = "2021"
172
173+ [[bin]]
174+ name = "ayllu-mail"
175+
176 [dependencies]
177- ayllu_api = {workspace = true}
178- ayllu_config = {workspace = true}
179- ayllu_rpc = {workspace = true}
180+ ayllu_api = {path = "../crates/api"}
181+ ayllu_config = {path = "../crates/config"}
182+ ayllu_rpc = {path = "../crates/rpc"}
183+
184 serde = { version = "1.0.188", features = ["derive"] }
185 clap = { version = "4.4.6", features = ["derive"] }
186 tracing-subscriber = { version = "0.3.17", features = ["env-filter"] }
187 diff --git a/ayllu-xmpp/Cargo.toml b/ayllu-xmpp/Cargo.toml
188index 12652a1..54105eb 100644
189--- a/ayllu-xmpp/Cargo.toml
190+++ b/ayllu-xmpp/Cargo.toml
191 @@ -3,11 +3,15 @@ name = "ayllu-xmpp"
192 version = "0.2.1"
193 edition = "2021"
194
195+ [[bin]]
196+ name = "ayllu-xmpp"
197+
198 [dependencies]
199- ayllu_api = {workspace = true}
200- ayllu_config = {workspace = true}
201- ayllu_database = {workspace = true}
202- ayllu_rpc = {workspace = true}
203+ ayllu_api = {path = "../crates/api"}
204+ ayllu_config = {path = "../crates/config"}
205+ ayllu_database = {path = "../crates/database"}
206+ ayllu_rpc = {path = "../crates/rpc"}
207+
208 clap = "4.4.4"
209 serde = { version = "1.0", features = ["derive"] }
210 tokio = { version = "1.32.0", features = ["full"] }
211 diff --git a/ayllu/Cargo.toml b/ayllu/Cargo.toml
212new file mode 100644
213index 0000000..2b1b749
214--- /dev/null
215+++ b/ayllu/Cargo.toml
216 @@ -0,0 +1,71 @@
217+ [package]
218+ name = "ayllu"
219+ version = "0.2.1"
220+ edition = "2021"
221+
222+ [[bin]]
223+ name = "ayllu"
224+
225+ [dependencies]
226+ ayllu_api = { path = "../crates/api" }
227+ ayllu_rpc = { path = "../crates/rpc" }
228+ ayllu_git = { path = "../crates/git" }
229+ ayllu_config = { path = "../crates/config" }
230+ ayllu_database = { path = "../crates/database" }
231+
232+ # ayllu_config = { workspace = true }
233+ # ayllu_database = { workspace = true }
234+ # ayllu_git = { workspace = true }
235+ # ayllu_rpc = { workspace = true }
236+ # ayllu_scheduler = {workspace = true}
237+ # ayllu-mail = {workspace = true}
238+ # ayllu-build = {workspace = true}
239+ # ayllu-xmpp = {workspace = true}
240+
241+ sqlx = { version = "0.7.3", features = [ "runtime-tokio-rustls", "sqlite", "macros", "time" ] }
242+ git2 = "0.17.1"
243+ serde = { version = "1.0", features = ["derive"] }
244+ clap = { version = "4.3.0", features = ["derive"] }
245+ toml = "0.7.4"
246+ comrak = { version = "0.18.0"}
247+ url = "2.3.1"
248+ tree-sitter-highlight = "0.20.1"
249+ tokei = "12.1.2"
250+ time = "0.3.21"
251+ futures = "0.3.28"
252+ time-macros = "0.2.9"
253+ file-mode = "0.1.2"
254+ tera = "1.19.0"
255+ lazy_static = "1.4.0"
256+ mime_guess = "2.0.4"
257+ serde_json = "1.0.97"
258+ tree-sitter = "0.20.10"
259+ plotters = "0.3.5"
260+ rand = "0.8.5"
261+ tabwriter = "1.2.1"
262+ libloading = "0.8.0"
263+ log = "0.4.19"
264+ env_logger = "0.10.0"
265+ rss = "2.0.5"
266+ globwalk = "0.8.1"
267+ anyhow = "1.0.75"
268+ tokio = { version = "1.32.0", features = ["full"] }
269+ axum = { version = "0.7.3", features = ["macros"] }
270+ axum-extra = { version = "0.9.1", features = ["cookie"] }
271+ tokio-util = { version = "0.7.9", features = ["io", "compat"] }
272+ tower-http = { version = "0.5.0", features = ["tracing", "add-extension", "trace", "normalize-path"] }
273+ tracing-subscriber = { version = "0.3.17", features = ["env-filter"] }
274+ tracing = "0.1.40"
275+ tower = { version = "0.4.13", features = ["util", "timeout", "tracing"] }
276+ mime = "0.3.17"
277+ capnp-rpc = "0.18.0"
278+ capnp = "0.18.1"
279+ async-trait = "0.1.74"
280+ webfinger = "0.5.1"
281+
282+ # NOTE: this must be cautiously updated along with sqlx and rusqlite.
283+ [dependencies.libsqlite3-sys]
284+ version = "0.27.0"
285+
286+ [build-dependencies]
287+ cc="*"
288 diff --git a/ayllu/README.md b/ayllu/README.md
289new file mode 100644
290index 0000000..54f8bab
291--- /dev/null
292+++ b/ayllu/README.md
293 @@ -0,0 +1,3 @@
294+ # ayllu
295+
296+ This is the main binary for the Ayllu web interface and job system.
297 diff --git a/ayllu/migrations/20230602111216_init.sql b/ayllu/migrations/20230602111216_init.sql
298new file mode 100644
299index 0000000..d3754fe
300--- /dev/null
301+++ b/ayllu/migrations/20230602111216_init.sql
302 @@ -0,0 +1,51 @@
303+ CREATE TABLE authors (
304+ id INTEGER PRIMARY KEY NOT NULL,
305+ username TEXT NOT NULL,
306+ email TEXT NOT NULL,
307+ UNIQUE (username, email)
308+ ) STRICT;
309+
310+ CREATE TABLE languages (
311+ id INTEGER PRIMARY KEY NOT NULL,
312+ git_hash TEXT NOT NULL,
313+ repo_path TEXT NOT NULL,
314+ language TEXT NOT NULL,
315+ loc INTEGER NOT NULL,
316+ UNIQUE (git_hash, repo_path, language)
317+ ) STRICT;
318+
319+ CREATE TABLE jobs (
320+ id INTEGER PRIMARY KEY,
321+ created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,
322+ kind TEXT NOT NULL,
323+ repo_path TEXT,
324+ runtime INTEGER,
325+ success INTEGER
326+ ) STRICT;
327+
328+ CREATE TABLE job_tracking (
329+ id INTEGER PRIMARY KEY,
330+ job_id INTEGER REFERENCES jobs(id) ON DELETE CASCADE NOT NULL,
331+ repo_path TEXT NOT NULL,
332+ git_hash TEXT NOT NULL,
333+ kind TEXT NOT NULL,
334+ UNIQUE (repo_path, git_hash, kind)
335+ ) STRICT;
336+
337+ CREATE TABLE contributions (
338+ id INTEGER PRIMARY KEY,
339+ git_hash TEXT NOT NULL,
340+ repo_path TEXT NOT NULL,
341+ time TEXT NOT NULL,
342+ author_id INTEGER REFERENCES authors(id),
343+ lines_added INTEGER NOT NULL,
344+ lines_removed INTEGER NOT NULL
345+ ) STRICT;
346+
347+ CREATE TABLE contribution_tally (
348+ id INTEGER PRIMARY KEY,
349+ git_hash TEXT NOT NULL,
350+ repo_path TEXT NOT NULL,
351+ author_id INTEGER REFERENCES authors(id),
352+ total INTEGER NOT NULL
353+ ) STRICT;
354 diff --git a/ayllu/queries/authors_list_project.sql b/ayllu/queries/authors_list_project.sql
355new file mode 100644
356index 0000000..832e3de
357--- /dev/null
358+++ b/ayllu/queries/authors_list_project.sql
359 @@ -0,0 +1,30 @@
360+ WITH RECURSIVE max_git_id AS
361+ (SELECT contributions.id AS hash
362+ FROM contributions
363+ WHERE git_hash = ?
364+ AND repo_path = ?
365+ ORDER BY id DESC
366+ LIMIT 1),
367+ total_contributions AS
368+ (SELECT COUNT(*) AS contributions
369+ FROM contributions
370+ WHERE repo_path = ?
371+ AND id <=
372+ (SELECT hash
373+ FROM max_git_id) )
374+ SELECT authors.username,
375+ authors.email,
376+ COUNT(contributions.id) AS "count: i64",
377+ SUM(contributions.lines_added) AS "lines_added: i64",
378+ SUM(contributions.lines_removed) AS "lines_removed: i64",
379+ ROUND((COUNT(contributions.id)/CAST(
380+ (SELECT contributions
381+ FROM total_contributions) AS REAL))*100, 2) AS "percentage: f64"
382+ FROM contributions
383+ LEFT JOIN authors ON (contributions.author_id = authors.id)
384+ WHERE repo_path = ?
385+ AND contributions.id <=
386+ (SELECT hash
387+ FROM max_git_id)
388+ GROUP BY authors.email
389+ ORDER BY "count: i64" DESC
390 diff --git a/ayllu/queries/authors_upsert.sql b/ayllu/queries/authors_upsert.sql
391new file mode 100644
392index 0000000..1c55636
393--- /dev/null
394+++ b/ayllu/queries/authors_upsert.sql
395 @@ -0,0 +1,10 @@
396+ INSERT INTO authors
397+ (username, email)
398+ VALUES
399+ (?, ?)
400+ ON CONFLICT
401+ DO UPDATE
402+ SET
403+ username = username,
404+ email = email
405+ RETURNING id
406 diff --git a/ayllu/queries/commits_count.sql b/ayllu/queries/commits_count.sql
407new file mode 100644
408index 0000000..0088bb8
409--- /dev/null
410+++ b/ayllu/queries/commits_count.sql
411 @@ -0,0 +1,9 @@
412+ SELECT
413+ COUNT(id) AS count
414+ FROM contributions
415+ WHERE
416+ repo_path = ? AND
417+ id <= (
418+ SELECT id FROM contributions
419+ WHERE git_hash = ? AND repo_path = ?
420+ )
421 diff --git a/ayllu/queries/contribution_add.sql b/ayllu/queries/contribution_add.sql
422new file mode 100644
423index 0000000..e1085e3
424--- /dev/null
425+++ b/ayllu/queries/contribution_add.sql
426 @@ -0,0 +1,11 @@
427+ WITH previous(total) AS (
428+ SELECT total FROM contribution_tally
429+ WHERE
430+ author_id = ? AND repo_path = ?
431+ ORDER BY id DESC
432+ LIMIT 1
433+ )
434+ INSERT INTO contribution_tally
435+ (git_hash, repo_path, author_id, total)
436+ VALUES
437+ (?, ?, ?, COALESCE((SELECT(total) FROM previous), 0)+1)
438 diff --git a/ayllu/queries/contribution_delete.sql b/ayllu/queries/contribution_delete.sql
439new file mode 100644
440index 0000000..6fdbe5c
441--- /dev/null
442+++ b/ayllu/queries/contribution_delete.sql
443 @@ -0,0 +1,3 @@
444+ DELETE FROM contribution_tally
445+ WHERE
446+ repo_path = ?
447 diff --git a/ayllu/queries/contributions_add.sql b/ayllu/queries/contributions_add.sql
448new file mode 100644
449index 0000000..9883d94
450--- /dev/null
451+++ b/ayllu/queries/contributions_add.sql
452 @@ -0,0 +1,4 @@
453+ INSERT INTO contributions
454+ (author_id, git_hash, repo_path, time, lines_added, lines_removed)
455+ VALUES
456+ (?, ?, ?, ?, ?, ?)
457 diff --git a/ayllu/queries/contributions_bucket.sql b/ayllu/queries/contributions_bucket.sql
458new file mode 100644
459index 0000000..c4e2e55
460--- /dev/null
461+++ b/ayllu/queries/contributions_bucket.sql
462 @@ -0,0 +1,10 @@
463+ SELECT
464+ COUNT(id) AS "count!: i64",
465+ time,
466+ SUM(lines_added) AS "added!: i64",
467+ SUM(lines_removed) AS "removed!: i64"
468+ FROM contributions WHERE
469+ repo_path = ? AND
470+ id <= (SELECT id FROM contributions WHERE git_hash = ?) AND
471+ time <= ? AND time >= ?
472+ GROUP BY strftime(?, time)
473 diff --git a/ayllu/queries/contributions_delete.sql b/ayllu/queries/contributions_delete.sql
474new file mode 100644
475index 0000000..0ee5563
476--- /dev/null
477+++ b/ayllu/queries/contributions_delete.sql
478 @@ -0,0 +1,2 @@
479+ DELETE FROM contributions
480+ WHERE repo_path = ?
481 diff --git a/ayllu/queries/contributions_list.sql b/ayllu/queries/contributions_list.sql
482new file mode 100644
483index 0000000..fae5633
484--- /dev/null
485+++ b/ayllu/queries/contributions_list.sql
486 @@ -0,0 +1,42 @@
487+ WITH RECURSIVE
488+ tallys AS (
489+ SELECT
490+ authors.username AS name,
491+ authors.email AS email,
492+ MAX(contribution_tally.total) AS total
493+ FROM contribution_tally
494+ LEFT JOIN authors ON
495+ (authors.id = contribution_tally.author_id)
496+ WHERE
497+ contribution_tally.id < (
498+ SELECT id FROM contributions
499+ WHERE
500+ repo_path = ? AND git_hash = ?
501+ ORDER BY id DESC
502+ LIMIT 1
503+ ) AND
504+ contribution_tally.repo_path = ?
505+ GROUP BY authors.email
506+ ORDER BY contribution_tally.total DESC
507+ LIMIT 5),
508+ raw_commits AS (
509+ SELECT
510+ COUNT(*) as total
511+ FROM contributions
512+ WHERE
513+ contributions.id < (
514+ SELECT id FROM contributions
515+ WHERE
516+ repo_path = ? AND git_hash = ?
517+ ORDER BY id DESC
518+ LIMIT 1
519+ ) AND
520+ contributions.repo_path = ?
521+ )
522+ SELECT
523+ name,
524+ email,
525+ (
526+ CAST(tallys.total AS REAL)/(SELECT total FROM raw_commits)
527+ ) * 100 AS "percentage: i64"
528+ FROM tallys
529 diff --git a/ayllu/queries/job_tracking_add.sql b/ayllu/queries/job_tracking_add.sql
530new file mode 100644
531index 0000000..7e70756
532--- /dev/null
533+++ b/ayllu/queries/job_tracking_add.sql
534 @@ -0,0 +1,4 @@
535+ INSERT INTO job_tracking
536+ (repo_path, git_hash, kind, job_id)
537+ VALUES
538+ (?, ?, ?, ?)
539 diff --git a/ayllu/queries/job_tracking_delete.sql b/ayllu/queries/job_tracking_delete.sql
540new file mode 100644
541index 0000000..3aef33d
542--- /dev/null
543+++ b/ayllu/queries/job_tracking_delete.sql
544 @@ -0,0 +1,3 @@
545+ DELETE FROM job_tracking
546+ WHERE
547+ repo_path = ?
548 diff --git a/ayllu/queries/job_tracking_delete_by_repo.sql b/ayllu/queries/job_tracking_delete_by_repo.sql
549new file mode 100644
550index 0000000..ebd0363
551--- /dev/null
552+++ b/ayllu/queries/job_tracking_delete_by_repo.sql
553 @@ -0,0 +1,3 @@
554+ DELETE FROM jobs
555+ WHERE
556+ repo_path = ?
557 diff --git a/ayllu/queries/job_tracking_read.sql b/ayllu/queries/job_tracking_read.sql
558new file mode 100644
559index 0000000..823dfc5
560--- /dev/null
561+++ b/ayllu/queries/job_tracking_read.sql
562 @@ -0,0 +1,8 @@
563+ SELECT
564+ git_hash AS "git_hash!"
565+ FROM job_tracking
566+ WHERE
567+ repo_path = ? AND
568+ kind = ?
569+ ORDER BY id DESC
570+ LIMIT 1
571 diff --git a/ayllu/queries/jobs_create.sql b/ayllu/queries/jobs_create.sql
572new file mode 100644
573index 0000000..4a1bea5
574--- /dev/null
575+++ b/ayllu/queries/jobs_create.sql
576 @@ -0,0 +1,5 @@
577+ INSERT INTO jobs
578+ (created_at, repo_path, kind)
579+ VALUES
580+ (?, ?, ?)
581+ RETURNING id
582 diff --git a/ayllu/queries/jobs_delete.sql b/ayllu/queries/jobs_delete.sql
583new file mode 100644
584index 0000000..c70ba25
585--- /dev/null
586+++ b/ayllu/queries/jobs_delete.sql
587 @@ -0,0 +1,2 @@
588+ DELETE FROM jobs
589+ WHERE jobs.id = ?
590 diff --git a/ayllu/queries/jobs_list.sql b/ayllu/queries/jobs_list.sql
591new file mode 100644
592index 0000000..23b3209
593--- /dev/null
594+++ b/ayllu/queries/jobs_list.sql
595 @@ -0,0 +1,15 @@
596+ SELECT
597+ jobs.id AS "id!",
598+ created_at AS "created_at: OffsetDateTime",
599+ jobs.repo_path,
600+ jobs.kind,
601+ runtime AS "runtime: u32",
602+ success AS "success: bool",
603+ (
604+ SELECT COUNT(id) FROM job_tracking
605+ WHERE job_tracking.job_id = jobs.id
606+ ) AS "commits!: i32"
607+ FROM jobs
608+ WHERE
609+ jobs.repo_path = ? OR ? IS NULL
610+ ORDER BY DATETIME(created_at) ASC
611 diff --git a/ayllu/queries/jobs_update.sql b/ayllu/queries/jobs_update.sql
612new file mode 100644
613index 0000000..ea7e521
614--- /dev/null
615+++ b/ayllu/queries/jobs_update.sql
616 @@ -0,0 +1,6 @@
617+ UPDATE jobs
618+ SET
619+ runtime = ?,
620+ success = ?
621+ WHERE
622+ jobs.id = ?
623 diff --git a/ayllu/queries/languages_add.sql b/ayllu/queries/languages_add.sql
624new file mode 100644
625index 0000000..54551b3
626--- /dev/null
627+++ b/ayllu/queries/languages_add.sql
628 @@ -0,0 +1,4 @@
629+ INSERT INTO languages
630+ (git_hash, repo_path, language, loc)
631+ VALUES
632+ (?,?,?,?)
633 diff --git a/ayllu/queries/languages_delete.sql b/ayllu/queries/languages_delete.sql
634new file mode 100644
635index 0000000..06c4349
636--- /dev/null
637+++ b/ayllu/queries/languages_delete.sql
638 @@ -0,0 +1,3 @@
639+ DELETE FROM languages
640+ WHERE
641+ repo_path = ?
642 diff --git a/ayllu/queries/languages_list.sql b/ayllu/queries/languages_list.sql
643new file mode 100644
644index 0000000..608a719
645--- /dev/null
646+++ b/ayllu/queries/languages_list.sql
647 @@ -0,0 +1,13 @@
648+ SELECT
649+ language,
650+ loc,
651+ ROUND(
652+ CAST(loc AS REAL)/CAST(
653+ (SELECT SUM(loc) FROM languages
654+ WHERE repo_path = ? AND git_hash = ?) AS REAL)*100)
655+ AS "percentage: f64"
656+ FROM languages
657+ WHERE
658+ repo_path = ? AND git_hash = ?
659+ ORDER BY "percentage: f64" DESC
660+ LIMIT 5
661 diff --git a/ayllu/queries/latest_commit.sql b/ayllu/queries/latest_commit.sql
662new file mode 100644
663index 0000000..bd26eac
664--- /dev/null
665+++ b/ayllu/queries/latest_commit.sql
666 @@ -0,0 +1,7 @@
667+ SELECT
668+ git_hash
669+ FROM contributions
670+ WHERE
671+ repo_path = ?
672+ ORDER BY id DESC
673+ LIMIT 1
674 diff --git a/ayllu/src/config.rs b/ayllu/src/config.rs
675new file mode 100644
676index 0000000..2a0298e
677--- /dev/null
678+++ b/ayllu/src/config.rs
679 @@ -0,0 +1,383 @@
680+ use std::collections::HashMap;
681+ use std::error::Error;
682+ use std::fs::{metadata, read_dir};
683+ use std::num::NonZeroUsize;
684+ use std::thread::available_parallelism;
685+
686+ use comrak::ComrakOptions;
687+ use url::Url;
688+
689+ use ayllu_config::{data_dir, runtime_dir, Configurable};
690+
691+ use serde::{Deserialize, Serialize};
692+
693+ pub const EXAMPLE_CONFIG: &str = include_str!("../../config.example.toml");
694+
695+ // names that cannot be used in collections because they conflict with certain
696+ // internal urls within the service.
697+ const BANNED_COLLECTION_NAMES: &[&str] = &[
698+ "authors", "about", "api", "browse", "config", "rss", "static", "discuss",
699+ ];
700+
701+ fn default_bool() -> bool {
702+ false
703+ }
704+
705+ #[derive(Deserialize, Serialize, Clone, Debug)]
706+ pub struct Sites {
707+ pub enabled: bool,
708+ }
709+
710+ #[derive(Deserialize, Serialize, Clone, Debug)]
711+ pub struct Highlighting {
712+ pub names: Vec<String>,
713+ }
714+
715+ #[derive(Deserialize, Serialize, Clone, Debug)]
716+ pub struct Collection {
717+ pub name: String,
718+ pub description: Option<String>,
719+ pub path: String,
720+ pub hidden: Option<bool>,
721+ }
722+
723+ #[derive(Deserialize, Serialize, Clone, Debug)]
724+ pub struct List {
725+ pub address: String,
726+ pub description: Option<String>,
727+ }
728+
729+ #[derive(Deserialize, Serialize, Clone, Debug)]
730+ pub struct Web {
731+ #[serde(default = "Web::default_themes_path")]
732+ pub themes_path: String,
733+ #[serde(default = "Web::default_base_theme")]
734+ pub base_theme: String,
735+ #[serde(default = "Web::default_default_theme")]
736+ pub default_theme: String,
737+ #[serde(default = "Web::default_themes")]
738+ pub themes: Vec<String>,
739+ #[serde(default = "Web::default_unsafe_markdown")]
740+ pub unsafe_markdown: bool,
741+ }
742+
743+ impl Web {
744+ fn default_themes_path() -> String {
745+ String::from("themes")
746+ }
747+
748+ fn default_base_theme() -> String {
749+ String::from("default")
750+ }
751+
752+ fn default_themes() -> Vec<String> {
753+ Vec::new()
754+ }
755+
756+ fn default_default_theme() -> String {
757+ String::from("tokyonight")
758+ }
759+
760+ fn default_unsafe_markdown() -> bool {
761+ true
762+ }
763+ }
764+
765+ #[derive(Deserialize, Serialize, Clone, Debug)]
766+ pub struct Database {
767+ #[serde(default = "Database::path_default")]
768+ pub path: String,
769+ #[serde(default = "Database::migrate_default")]
770+ pub migrate: bool,
771+ }
772+
773+ impl Database {
774+ fn path_default() -> String {
775+ let mut data_path = data_dir();
776+ data_path.push("state.db");
777+ String::from(data_path.to_str().unwrap())
778+ }
779+
780+ fn migrate_default() -> bool {
781+ true
782+ }
783+ }
784+
785+ #[derive(Deserialize, Serialize, Clone, Debug)]
786+ pub struct Http {
787+ #[serde(default = "Http::default_address")]
788+ pub address: String,
789+ }
790+
791+ impl Http {
792+ fn default_address() -> String {
793+ String::from("localhost:8080")
794+ }
795+ }
796+
797+ #[derive(Deserialize, Serialize, Clone, Debug)]
798+ pub struct TreeSitterParser {
799+ pub language: String,
800+ pub shared_object: String,
801+ pub highlight_query: Option<String>,
802+ pub locals_query: Option<String>,
803+ pub injections_query: Option<String>,
804+ }
805+
806+ #[derive(Deserialize, Serialize, Clone, Debug)]
807+ pub struct TreeSitter {
808+ #[serde(default = "TreeSitter::default_base_path")]
809+ pub base_path: String,
810+ #[serde(default = "TreeSitter::default_queries_path")]
811+ pub queries_path: String,
812+ pub queries_extras_path: Option<String>,
813+ pub parsers: Option<Vec<TreeSitterParser>>,
814+ #[serde(default = "TreeSitter::default_keywords")]
815+ pub keywords: Vec<String>,
816+ }
817+
818+ impl TreeSitter {
819+ fn default_base_path() -> String {
820+ String::from("/usr/lib")
821+ }
822+
823+ fn default_queries_path() -> String {
824+ String::from("/usr/share/tree-sitter/queries")
825+ }
826+
827+ fn default_keywords() -> Vec<String> {
828+ Vec::new()
829+ }
830+ }
831+
832+ #[derive(Deserialize, Serialize, Clone, Debug)]
833+ pub struct Lfs {
834+ pub url_template: String,
835+ }
836+
837+ #[derive(Deserialize, Serialize, Clone, Debug, Default)]
838+ pub struct XmppChannel {
839+ pub jid: String,
840+ pub description: String,
841+ }
842+
843+ #[derive(Deserialize, Serialize, Clone, Debug, Default)]
844+ pub struct Xmpp {
845+ pub channels: Vec<XmppChannel>,
846+ #[serde(default = "Xmpp::default_socket_path")]
847+ pub socket_path: String,
848+ }
849+
850+ impl Xmpp {
851+ fn default_socket_path() -> String {
852+ runtime_dir().to_str().unwrap().to_string() + "/ayllu-xmpp.sock"
853+ }
854+ }
855+
856+ #[derive(Deserialize, Serialize, Clone, Debug, Default)]
857+ pub struct Mail {
858+ #[serde(default = "Mail::default_socket_path")]
859+ pub socket_path: String,
860+ }
861+
862+ impl Mail {
863+ fn default_socket_path() -> String {
864+ runtime_dir().to_str().unwrap().to_string() + "/ayllu-mail.sock"
865+ }
866+ }
867+
868+ #[derive(Deserialize, Serialize, Clone, Debug, Default)]
869+ pub struct Link {
870+ pub rel: String,
871+ pub href: Option<String>,
872+ pub template: Option<String>,
873+ pub mime_template: Option<String>,
874+ }
875+
876+ #[derive(Deserialize, Serialize, Clone, Debug, Default)]
877+ pub struct Author {
878+ pub email: String,
879+ pub links: Vec<Link>,
880+ }
881+
882+ #[derive(Deserialize, Serialize, Clone, Debug)]
883+ pub struct Language {
884+ pub name: String,
885+ pub extensions: Option<Vec<String>>,
886+ pub color: Option<String>,
887+ pub filenames: Option<Vec<String>>,
888+ }
889+
890+ #[derive(Deserialize, Serialize, Clone, Debug, Default)]
891+ pub struct Languages {
892+ pub mappings: HashMap<String, String>,
893+ pub extras: Vec<Language>,
894+ }
895+
896+ #[derive(Deserialize, Serialize, Clone, Debug)]
897+ pub struct Config {
898+ #[serde(default = "Config::default_site_name")]
899+ pub site_name: String,
900+ pub origin: String,
901+ pub domain: Option<String>,
902+ #[serde(default = "Config::default_worker_threads")]
903+ pub worker_threads: NonZeroUsize,
904+ #[serde(default = "Config::default_max_blocking_threads")]
905+ pub max_blocking_threads: NonZeroUsize,
906+ pub sysadmin: Option<String>,
907+ pub blurb: Option<String>,
908+ #[serde(default = "Config::default_robots_txt")]
909+ pub robots: String,
910+ #[serde(default = "default_bool")]
911+ pub subpath_mode: bool,
912+ #[serde(default = "Config::default_log_level")]
913+ pub log_level: String,
914+ pub git_clone_url: Option<String>,
915+ pub default_branch: Option<String>,
916+ pub rss_time_to_live: Option<i64>,
917+ pub web: Web,
918+ pub http: Http,
919+ #[serde(default = "Config::default_jobs_socket_path")]
920+ pub jobs_socket_path: String,
921+ #[serde(default = "Config::default_jobs_n_workers")]
922+ pub jobs_n_workers: NonZeroUsize,
923+ pub database: Database,
924+ #[serde(default = "Vec::new")]
925+ pub collections: Vec<Collection>,
926+ pub sites: Sites,
927+ #[serde(rename = "tree-sitter")]
928+ pub tree_sitter: Option<TreeSitter>,
929+ pub languages: Option<Languages>,
930+ pub lfs: Option<Lfs>,
931+ pub xmpp: Option<Xmpp>,
932+ pub mail: Option<Mail>,
933+ pub authors: Vec<Author>,
934+ }
935+
936+ impl Configurable for Config {
937+ fn validate(&mut self) -> Result<(), Box<dyn Error>> {
938+ // load themes from the file system when the configuration is loaded, they
939+ // are made available in the configuration page of the UI.
940+ let mut themes: Vec<String> = Vec::new();
941+
942+ match read_dir(&self.web.themes_path) {
943+ Ok(paths) => {
944+ for dir in paths.into_iter() {
945+ let dir_path = dir?.path();
946+ let filename = dir_path.file_name().unwrap();
947+ let theme_name = filename.to_str().unwrap();
948+ themes.push(theme_name.to_string());
949+ }
950+ }
951+ Err(e) => {
952+ return Err(format!(
953+ "failed to load themes from path: {} ({})",
954+ self.web.themes_path, e
955+ )
956+ .into())
957+ }
958+ }
959+
960+ let parsed_url = Url::parse(&self.origin)?;
961+ self.domain = parsed_url.domain().map(|domain| domain.to_string());
962+
963+ self.web.themes = themes;
964+ // verify collection names are all valid
965+ if let Some(collection) = self.collections.iter().find(|collection| {
966+ BANNED_COLLECTION_NAMES
967+ .iter()
968+ .any(|name| *name == collection.name.as_str())
969+ }) {
970+ return Err(format!("{} is a reserved name", collection.name)
971+ .as_str()
972+ .into());
973+ };
974+
975+ // validate that all collection directories exist
976+ for collection in self.collections.iter() {
977+ if let Err(err) = metadata(collection.path.clone()) {
978+ return Err(
979+ format!("could not load collection {}:\n{}", collection.name, err).into(),
980+ );
981+ }
982+ }
983+ Ok(())
984+ }
985+ }
986+
987+ impl Config {
988+ fn default_robots_txt() -> String {
989+ let robots = r#"
990+ User-agent: *
991+ Disallow: /*?*
992+ Disallow: /static/*
993+ Disallow: /assets/*
994+ Disallow: /config
995+ Disallow: /rss
996+ Disallow: /*/*/rss/*
997+ Disallow: /*/*/refs/archive/*
998+ Disallow: /*/*/blame/*
999+ Disallow: /*/*/log/*
1000+ Disallow: /*/*/tree/*
1001+ Disallow: /*/*/chart/*
1002+ "#;
1003+ String::from(robots.trim_start())
1004+ }
1005+
1006+ fn default_site_name() -> String {
1007+ String::from("🌄 ayllu")
1008+ }
1009+
1010+ fn default_log_level() -> String {
1011+ String::from("info")
1012+ }
1013+
1014+ fn default_worker_threads() -> NonZeroUsize {
1015+ available_parallelism().unwrap()
1016+ }
1017+
1018+ fn default_max_blocking_threads() -> NonZeroUsize {
1019+ NonZeroUsize::new(512).unwrap()
1020+ }
1021+
1022+ fn default_jobs_socket_path() -> String {
1023+ runtime_dir().to_str().unwrap().to_string() + "/ayllu.sock"
1024+ }
1025+
1026+ fn default_jobs_n_workers() -> NonZeroUsize {
1027+ available_parallelism().unwrap()
1028+ }
1029+
1030+ pub fn to_json(&self) -> String {
1031+ serde_json::to_string(self).unwrap()
1032+ }
1033+
1034+ pub fn markdown_render_options(&self) -> ComrakOptions {
1035+ let mut opts = ComrakOptions::default();
1036+ opts.extension.description_lists = true;
1037+ opts.extension.footnotes = true;
1038+ opts.extension.strikethrough = true;
1039+ opts.extension.superscript = true;
1040+ opts.extension.table = true;
1041+ opts.extension.tasklist = true;
1042+
1043+ opts.parse.smart = true;
1044+ opts.parse.relaxed_tasklist_matching = true;
1045+ // allow raw html in the markdown documents
1046+ opts.render.unsafe_ = self.web.unsafe_markdown;
1047+ // not relevent since we paint ourselves I think
1048+ // opts.render.github_pre_lang = false;
1049+ opts
1050+ }
1051+ }
1052+
1053+ #[cfg(test)]
1054+ mod tests {
1055+ use super::*;
1056+ use ayllu_config::Reader;
1057+
1058+ #[test]
1059+ fn test_example_config() {
1060+ Reader::<Config>::read(EXAMPLE_CONFIG).unwrap();
1061+ }
1062+ }
1063 diff --git a/ayllu/src/database_ext.rs b/ayllu/src/database_ext.rs
1064new file mode 100644
1065index 0000000..c19e804
1066--- /dev/null
1067+++ b/ayllu/src/database_ext.rs
1068 @@ -0,0 +1,411 @@
1069+ use async_trait::async_trait;
1070+ use futures::TryStreamExt;
1071+ use serde::Serialize;
1072+ use sqlx::Error;
1073+ use time::{format_description, OffsetDateTime};
1074+
1075+ use ayllu_database::Wrapper as Database;
1076+
1077+ // job related db methods
1078+
1079+ pub mod jobs {
1080+
1081+ use super::*;
1082+
1083+ #[derive(Clone)]
1084+ pub struct Job {
1085+ pub id: i64,
1086+ pub created_at: OffsetDateTime,
1087+ pub repo_path: Option<String>,
1088+ pub kind: String,
1089+ pub runtime: Option<u32>,
1090+ pub success: Option<bool>,
1091+ pub commits: i32,
1092+ }
1093+
1094+ #[async_trait]
1095+ pub trait JobsExt {
1096+ async fn create_job(&self, repo_path: Option<&str>, kind: &str) -> Result<i64, Error>;
1097+ async fn update_job(&self, job_id: i64, runtime: i64, success: bool) -> Result<(), Error>;
1098+ async fn list_jobs(&self, repo_path: Option<&str>) -> Result<Vec<Job>, Error>;
1099+ async fn delete_job(&self, job_id: i64) -> Result<(), Error>;
1100+ async fn purge(&self, repo_path: &str) -> Result<(), Error>;
1101+
1102+ // used to track job progress
1103+
1104+ async fn create_hash(
1105+ &self,
1106+ repo_path: &str,
1107+ git_hash: &str,
1108+ kind: &str,
1109+ job_id: i64,
1110+ ) -> Result<(), Error>;
1111+
1112+ async fn read_hash(&self, repo_path: &str, kind: &str) -> Result<Option<String>, Error>;
1113+ async fn latest_commit(&self, repo_path: &str) -> Result<Option<String>, Error>;
1114+ }
1115+
1116+ #[async_trait]
1117+ impl JobsExt for Database {
1118+ async fn create_job(&self, repo_path: Option<&str>, kind: &str) -> Result<i64, Error> {
1119+ let created_at = OffsetDateTime::now_utc();
1120+ let ret = sqlx::query_file!("queries/jobs_create.sql", created_at, repo_path, kind,)
1121+ .fetch_one(&self.pool)
1122+ .await?;
1123+ Ok(ret.id)
1124+ }
1125+
1126+ async fn update_job(&self, job_id: i64, runtime: i64, success: bool) -> Result<(), Error> {
1127+ sqlx::query_file!("queries/jobs_update.sql", runtime, success, job_id)
1128+ .execute(&self.pool)
1129+ .await?;
1130+ Ok(())
1131+ }
1132+
1133+ async fn list_jobs(&self, repo_path: Option<&str>) -> Result<Vec<Job>, Error> {
1134+ let jobs = sqlx::query_file_as!(Job, "queries/jobs_list.sql", repo_path, repo_path)
1135+ .fetch_all(&self.pool)
1136+ .await?;
1137+ Ok(jobs)
1138+ }
1139+
1140+ async fn delete_job(&self, job_id: i64) -> Result<(), Error> {
1141+ sqlx::query_file_as!(Job, "queries/jobs_delete.sql", job_id)
1142+ .execute(&self.pool)
1143+ .await?;
1144+ Ok(())
1145+ }
1146+
1147+ async fn purge(&self, repo_path: &str) -> Result<(), Error> {
1148+ sqlx::query_file!("queries/job_tracking_delete_by_repo.sql", repo_path)
1149+ .execute(&self.pool)
1150+ .await?;
1151+ sqlx::query_file!("queries/jobs_delete.sql", repo_path)
1152+ .execute(&self.pool)
1153+ .await?;
1154+ sqlx::query_file!("queries/languages_delete.sql", repo_path)
1155+ .execute(&self.pool)
1156+ .await?;
1157+ sqlx::query_file!("queries/contribution_delete.sql", repo_path)
1158+ .execute(&self.pool)
1159+ .await?;
1160+ sqlx::query_file!("queries/contributions_delete.sql", repo_path)
1161+ .execute(&self.pool)
1162+ .await?;
1163+ Ok(())
1164+ }
1165+
1166+ async fn create_hash(
1167+ &self,
1168+ repo_path: &str,
1169+ git_hash: &str,
1170+ kind: &str,
1171+ job_id: i64,
1172+ ) -> Result<(), Error> {
1173+ sqlx::query_file!(
1174+ "queries/job_tracking_add.sql",
1175+ repo_path,
1176+ git_hash,
1177+ kind,
1178+ job_id
1179+ )
1180+ .execute(&self.pool)
1181+ .await?;
1182+ Ok(())
1183+ }
1184+
1185+ // lookup the latest hash for the job kind
1186+ async fn read_hash(&self, repo_path: &str, kind: &str) -> Result<Option<String>, Error> {
1187+ let ret = sqlx::query_file!("queries/job_tracking_read.sql", repo_path, kind,)
1188+ .fetch_optional(&self.pool)
1189+ .await?;
1190+ Ok(ret.map(|record| record.git_hash))
1191+ }
1192+
1193+ async fn latest_commit(&self, repo_path: &str) -> Result<Option<String>, Error> {
1194+ let ret = sqlx::query_file!("queries/latest_commit.sql", repo_path)
1195+ .fetch_optional(&self.pool)
1196+ .await?;
1197+ Ok(ret.map(|record| record.git_hash))
1198+ }
1199+ }
1200+ }
1201+
1202+ // author and contributor helpers
1203+
1204+ pub mod contributors {
1205+ use super::*;
1206+
1207+ #[derive(Clone, Serialize)]
1208+ pub struct AuthorWithStats {
1209+ pub username: String,
1210+ pub email: String,
1211+ pub count: Option<i64>,
1212+ pub lines_added: Option<i64>,
1213+ pub lines_removed: Option<i64>,
1214+ pub percentage: Option<f64>,
1215+ }
1216+
1217+ #[async_trait]
1218+ pub trait ContributorsExt {
1219+ async fn upsert_author(&self, name: &str, email: &str) -> Result<i64, Error>;
1220+ async fn authors_list(
1221+ &self,
1222+ repo_path: &str,
1223+ git_hash: &str,
1224+ ) -> Result<Vec<AuthorWithStats>, Error>;
1225+ async fn contribution_add(
1226+ &self,
1227+ author_id: i64,
1228+ repo_path: &str,
1229+ git_hash: &str,
1230+ timestamp: OffsetDateTime,
1231+ lines_added: i64,
1232+ lines_removed: i64,
1233+ ) -> Result<(), Error>;
1234+ async fn contributors_list(
1235+ &self,
1236+ repo_path: &str,
1237+ git_hash: &str,
1238+ ) -> Result<Vec<(String, String, i64)>, Error>;
1239+ }
1240+
1241+ #[async_trait]
1242+ impl ContributorsExt for Database {
1243+ async fn upsert_author(&self, name: &str, email: &str) -> Result<i64, Error> {
1244+ let ret = sqlx::query_file!("queries/authors_upsert.sql", name, email)
1245+ .fetch_one(&self.pool)
1246+ .await?;
1247+ Ok(ret.id)
1248+ }
1249+
1250+ async fn authors_list(
1251+ &self,
1252+ repo_path: &str,
1253+ git_hash: &str,
1254+ ) -> Result<Vec<AuthorWithStats>, Error> {
1255+ sqlx::query_file_as!(
1256+ AuthorWithStats,
1257+ "queries/authors_list_project.sql",
1258+ git_hash,
1259+ repo_path,
1260+ repo_path,
1261+ repo_path,
1262+ )
1263+ .fetch_all(&self.pool)
1264+ .await
1265+ }
1266+
1267+ async fn contribution_add(
1268+ &self,
1269+ author_id: i64,
1270+ repo_path: &str,
1271+ git_hash: &str,
1272+ timestamp: OffsetDateTime,
1273+ lines_added: i64,
1274+ lines_removed: i64,
1275+ ) -> Result<(), Error> {
1276+ sqlx::query_file!(
1277+ "queries/contributions_add.sql",
1278+ author_id,
1279+ git_hash,
1280+ repo_path,
1281+ timestamp,
1282+ lines_added,
1283+ lines_removed
1284+ )
1285+ .execute(&self.pool)
1286+ .await?
1287+ .last_insert_rowid();
1288+ sqlx::query_file!(
1289+ "queries/contribution_add.sql",
1290+ author_id,
1291+ repo_path,
1292+ git_hash,
1293+ repo_path,
1294+ author_id,
1295+ )
1296+ .execute(&self.pool)
1297+ .await?;
1298+
1299+ Ok(())
1300+ }
1301+
1302+ async fn contributors_list(
1303+ &self,
1304+ repo_path: &str,
1305+ git_hash: &str,
1306+ ) -> Result<Vec<(String, String, i64)>, Error> {
1307+ let mut ret: Vec<(String, String, i64)> = Vec::new();
1308+ let mut rows = sqlx::query_file!(
1309+ "queries/contributions_list.sql",
1310+ repo_path,
1311+ git_hash,
1312+ repo_path,
1313+ repo_path,
1314+ git_hash,
1315+ repo_path,
1316+ )
1317+ .fetch(&self.pool);
1318+ while let Some(row) = rows.try_next().await? {
1319+ ret.push((row.name, row.email, row.percentage.unwrap()))
1320+ }
1321+ Ok(ret)
1322+ }
1323+ }
1324+ }
1325+
1326+ // forge and repository level statistics
1327+
1328+ pub mod stats {
1329+
1330+ use super::*;
1331+
1332+ #[allow(dead_code)]
1333+ pub enum Aggregation {
1334+ Day,
1335+ Week,
1336+ Month,
1337+ Year,
1338+ }
1339+
1340+ #[async_trait]
1341+ pub trait StatsExt {
1342+ async fn contribution_buckets_for_repo(
1343+ &self,
1344+ repo_path: &str,
1345+ git_hash: &str,
1346+ period: Aggregation,
1347+ offset: time::Duration,
1348+ ) -> Result<Vec<(i64, i64, i64)>, Error>;
1349+
1350+ async fn count_commits(&self, repo_path: &str, git_hash: &str) -> Result<i32, Error>;
1351+ }
1352+
1353+ #[async_trait]
1354+ impl StatsExt for Database {
1355+ async fn contribution_buckets_for_repo(
1356+ &self,
1357+ repo_path: &str,
1358+ git_hash: &str,
1359+ period: Aggregation,
1360+ offset: time::Duration,
1361+ ) -> Result<Vec<(i64, i64, i64)>, Error> {
1362+ let start = OffsetDateTime::now_utc();
1363+ let end = OffsetDateTime::now_utc().saturating_sub(offset);
1364+ let start_ts = start.unix_timestamp();
1365+ let end_ts = end.unix_timestamp();
1366+ let seconds = match period {
1367+ Aggregation::Day => 86400_i64,
1368+ Aggregation::Week => 604800_i64,
1369+ Aggregation::Month => 2592000_i64,
1370+ Aggregation::Year => 31536000_i64,
1371+ };
1372+ let n_buckets = (start_ts - end_ts) / seconds;
1373+ let mut buckets: Vec<(i64, i64, i64)> = vec![(0, 0, 0); n_buckets as usize];
1374+ let strftime = match period {
1375+ Aggregation::Day => "%d%m",
1376+ Aggregation::Week => "%w",
1377+ Aggregation::Month => "%m",
1378+ Aggregation::Year => "%y",
1379+ };
1380+ let rows = sqlx::query_file!(
1381+ "queries/contributions_bucket.sql",
1382+ repo_path,
1383+ git_hash,
1384+ start,
1385+ end,
1386+ strftime
1387+ )
1388+ .fetch_all(&self.pool)
1389+ .await?;
1390+ for row in rows.iter() {
1391+ let parsed = OffsetDateTime::parse(
1392+ row.time.as_str(),
1393+ &format_description::well_known::Rfc3339,
1394+ )
1395+ .unwrap();
1396+ let diff = ((start - parsed).as_seconds_f64() / seconds as f64).floor();
1397+ buckets[(diff - 1.0).abs() as usize] =
1398+ (row.count, row.added, (row.removed - row.removed * 2));
1399+ }
1400+ // buckets.reverse();
1401+ Ok(buckets)
1402+ }
1403+
1404+ async fn count_commits(&self, repo_path: &str, git_hash: &str) -> Result<i32, Error> {
1405+ let result =
1406+ sqlx::query_file!("queries/commits_count.sql", repo_path, git_hash, repo_path);
1407+ let result = result.fetch_one(&self.pool).await?;
1408+ Ok(result.count)
1409+ }
1410+ }
1411+ }
1412+
1413+ pub mod langauges {
1414+
1415+ use super::*;
1416+
1417+ #[async_trait]
1418+ pub trait LanguagesExt {
1419+ async fn language_create(
1420+ &self,
1421+ repo_path: &str,
1422+ git_hash: &str,
1423+ languages: Vec<(&str, i64)>,
1424+ ) -> Result<(), Error>;
1425+
1426+ async fn languages_list(
1427+ &self,
1428+ repo_path: &str,
1429+ git_hash: &str,
1430+ ) -> Result<Vec<(String, u8, u32)>, Error>;
1431+ }
1432+
1433+ #[async_trait]
1434+ impl LanguagesExt for Database {
1435+ async fn language_create(
1436+ &self,
1437+ repo_path: &str,
1438+ git_hash: &str,
1439+ languages: Vec<(&str, i64)>,
1440+ ) -> Result<(), Error> {
1441+ for language in languages {
1442+ sqlx::query_file!(
1443+ "queries/languages_add.sql",
1444+ git_hash,
1445+ repo_path,
1446+ language.0,
1447+ language.1,
1448+ )
1449+ .execute(&self.pool)
1450+ .await?;
1451+ }
1452+ Ok(())
1453+ }
1454+
1455+ async fn languages_list(
1456+ &self,
1457+ repo_path: &str,
1458+ git_hash: &str,
1459+ ) -> Result<Vec<(String, u8, u32)>, Error> {
1460+ let mut languages: Vec<(String, u8, u32)> = Vec::new();
1461+ let mut rows = sqlx::query_file!(
1462+ "queries/languages_list.sql",
1463+ repo_path,
1464+ git_hash,
1465+ repo_path,
1466+ git_hash
1467+ )
1468+ .fetch(&self.pool);
1469+ while let Some(row) = rows.try_next().await? {
1470+ languages.push((
1471+ row.language.clone(),
1472+ row.percentage.unwrap_or(0.0) as u8,
1473+ row.loc as u32,
1474+ ));
1475+ }
1476+ Ok(languages)
1477+ }
1478+ }
1479+ }
1480 diff --git a/ayllu/src/job_server/commands.rs b/ayllu/src/job_server/commands.rs
1481new file mode 100644
1482index 0000000..da7c748
1483--- /dev/null
1484+++ b/ayllu/src/job_server/commands.rs
1485 @@ -0,0 +1,134 @@
1486+ use std::fs::canonicalize;
1487+ use std::io::Write;
1488+ use std::path::{Path, PathBuf};
1489+
1490+ use anyhow::{format_err, Result};
1491+ use tabwriter::TabWriter;
1492+ use time::Duration;
1493+
1494+ use crate::config::Config;
1495+ use crate::time::friendly;
1496+ use ayllu_api::jobs_capnp::server::Client;
1497+ use ayllu_api::models::{Job, Kind};
1498+ use ayllu_git::git_dir;
1499+ use ayllu_rpc::Client as RpcHelper;
1500+
1501+ fn name(repo_path: Option<String>) -> String {
1502+ match repo_path {
1503+ Some(repo_path) => {
1504+ // return just the $collection/$name part of the path if applicable
1505+ let split: Vec<&str> = repo_path.split('/').collect();
1506+ if split.len() > 2 {
1507+ format!("{}/{}", split[split.len() - 2], split[split.len() - 1])
1508+ } else {
1509+ repo_path.to_string()
1510+ }
1511+ }
1512+ None => String::new(),
1513+ }
1514+ }
1515+
1516+ fn resolve_path(path: Option<&PathBuf>) -> Result<PathBuf> {
1517+ let repo_path = path.map_or_else(
1518+ || canonicalize(Path::new(".")),
1519+ |path| canonicalize(path.as_path()),
1520+ )?;
1521+ if !git_dir(repo_path.as_path())? {
1522+ Err(format_err!("this path does not look like a git repository"))
1523+ } else {
1524+ Ok(repo_path)
1525+ }
1526+ }
1527+
1528+ /// Run a job against a specific repository path, if kind is not specified all
1529+ /// jobs will be ran.
1530+ pub async fn run_one(
1531+ config: Config,
1532+ repo_path: Option<PathBuf>,
1533+ kind: Option<&str>,
1534+ _max_depth: Option<usize>,
1535+ ) -> Result<()> {
1536+ let kinds: Vec<Kind> = kind.map_or(vec![Kind::Cloc, Kind::Contributors], |kind| {
1537+ vec![kind.to_string().into()]
1538+ });
1539+ let repo_path = resolve_path(repo_path.as_ref())?;
1540+ RpcHelper::new(&config.jobs_socket_path)
1541+ .invoke(move |client: Client| async move {
1542+ for kind in kinds {
1543+ let mut request = client.submit_request();
1544+ // FIXME: set_repo_path must take Path/PathBuf
1545+ request
1546+ .get()
1547+ .set_repo_path(repo_path.display().to_string().as_str().into());
1548+ request.get().set_kind(kind.into());
1549+ request.send().promise.await?;
1550+ }
1551+ Ok(())
1552+ })
1553+ .await?;
1554+ Ok(())
1555+ }
1556+
1557+ pub async fn list(config: Config, repo_path: Option<PathBuf>) -> Result<()> {
1558+ let repo_path = repo_path
1559+ .map(|repo_path| resolve_path(Some(repo_path).as_ref()))
1560+ .transpose()?;
1561+ let jobs = RpcHelper::new(&config.jobs_socket_path)
1562+ .invoke(move |client: Client| async move {
1563+ let mut request = client.list_request();
1564+ if let Some(repo_path) = repo_path {
1565+ // FIXME: set_repo_path must take Path/PathBuf
1566+ request
1567+ .get()
1568+ .set_repo_path(repo_path.display().to_string().as_str().into());
1569+ }
1570+ let result = request.send().promise.await?;
1571+ let jobs: Vec<Job> = result
1572+ .get()?
1573+ .get_jobs()?
1574+ .iter()
1575+ .map(|item| Job {
1576+ id: item.get_id(),
1577+ repo_path: String::from_utf8(item.get_repo_path().unwrap().0.to_vec()).unwrap(),
1578+ created_at: item.get_created_at(),
1579+ runtime: item.get_runtime(),
1580+ kind: Kind::from(item.get_kind().unwrap()),
1581+ commits: item.get_commits(),
1582+ })
1583+ .collect();
1584+ Ok(jobs)
1585+ })
1586+ .await?;
1587+ let mut tw = TabWriter::new(vec![]);
1588+ writeln!(&mut tw, "name\ttime\truntime\tkind\tcommits")?;
1589+ for job in jobs {
1590+ writeln!(
1591+ &mut tw,
1592+ "{}\t{}\t{}\t{}\t{}",
1593+ name(Some(job.repo_path)),
1594+ friendly(job.created_at as u64),
1595+ Duration::milliseconds(job.runtime),
1596+ job.kind,
1597+ job.commits,
1598+ )?;
1599+ }
1600+ tw.flush()?;
1601+ println!("{}", String::from_utf8(tw.into_inner().unwrap()).unwrap());
1602+ Ok(())
1603+ }
1604+
1605+ pub async fn purge(config: Config, repo_path: Option<PathBuf>) -> Result<()> {
1606+ let repo_path = resolve_path(repo_path.as_ref())?;
1607+ RpcHelper::new(&config.jobs_socket_path)
1608+ .invoke(move |client: Client| async move {
1609+ let mut request = client.purge_request();
1610+ // FIXME: set_repo_path must take Path/PathBuf
1611+ request
1612+ .get()
1613+ .set_repo_path(repo_path.display().to_string().as_str().into());
1614+ request.send().promise.await?;
1615+ Ok(())
1616+ })
1617+ .await?;
1618+ Ok(())
1619+ }
1620 diff --git a/ayllu/src/job_server/jobs/cloc.rs b/ayllu/src/job_server/jobs/cloc.rs
1621new file mode 100644
1622index 0000000..73a1d64
1623--- /dev/null
1624+++ b/ayllu/src/job_server/jobs/cloc.rs
1625 @@ -0,0 +1,56 @@
1626+ use std::sync::Arc;
1627+ use std::time::{Duration, SystemTime};
1628+
1629+ use anyhow::Result;
1630+ use tokei::{Config as TokeiConfig, Languages};
1631+
1632+ use crate::database_ext::{jobs::JobsExt, langauges::LanguagesExt};
1633+ use ayllu_api::models::{Job as JobModel, Kind};
1634+ use ayllu_database::Wrapper as Database;
1635+ use ayllu_git::Wrapper as Repository;
1636+
1637+ pub struct Job {
1638+ pub repository: Repository,
1639+ pub database: Arc<Database>,
1640+ pub model: JobModel,
1641+ }
1642+
1643+ impl Job {
1644+ pub async fn invoke(&self, commits: Vec<String>) -> Result<Duration> {
1645+ let start = SystemTime::now();
1646+ let repo_path = self.repository.path();
1647+ for (i, commit) in commits.iter().enumerate() {
1648+ if i % 50 == 0 && i != 0 {
1649+ log::info!("processed {}/{} commits", i, commits.len());
1650+ }
1651+ let mut stats: Vec<(&str, i64)> = Vec::new();
1652+ let git_hash = commit.to_string();
1653+ self.repository
1654+ .with_worktree("/tmp", Some(git_hash.as_str()), |tree_path| {
1655+ let config = TokeiConfig::default();
1656+ let mut languages = Languages::new();
1657+ let paths = &[tree_path];
1658+ let exclude = &[];
1659+ languages.get_statistics(paths, exclude, &config);
1660+ for language in languages {
1661+ let name = language.0.name();
1662+ let loc = language.1.code;
1663+ stats.push((name, loc.try_into().unwrap()));
1664+ }
1665+ Ok(())
1666+ })?;
1667+ self.database
1668+ .language_create(&repo_path, git_hash.as_str(), stats)
1669+ .await?;
1670+ self.database
1671+ .create_hash(
1672+ &repo_path,
1673+ git_hash.as_str(),
1674+ Kind::Cloc.to_string().as_str(),
1675+ self.model.id,
1676+ )
1677+ .await?;
1678+ }
1679+ Ok(start.elapsed().unwrap())
1680+ }
1681+ }
1682 diff --git a/ayllu/src/job_server/jobs/contributors.rs b/ayllu/src/job_server/jobs/contributors.rs
1683new file mode 100644
1684index 0000000..cc5e9a4
1685--- /dev/null
1686+++ b/ayllu/src/job_server/jobs/contributors.rs
1687 @@ -0,0 +1,70 @@
1688+ use std::sync::Arc;
1689+ use std::time::{Duration, SystemTime};
1690+ use time::OffsetDateTime;
1691+
1692+ use anyhow::Result;
1693+
1694+ use crate::database_ext::{contributors::ContributorsExt, jobs::JobsExt};
1695+ use ayllu_api::models::{Job as JobModel, Kind};
1696+ use ayllu_database::Wrapper as Database;
1697+ use ayllu_git::Wrapper as Repository;
1698+
1699+ #[derive(Default)]
1700+ struct Contribution {
1701+ name: String,
1702+ email: String,
1703+ time: i64,
1704+ lines_added: usize,
1705+ lines_removed: usize,
1706+ }
1707+
1708+ pub struct Job {
1709+ pub repository: Repository,
1710+ pub database: Arc<Database>,
1711+ pub model: JobModel,
1712+ }
1713+
1714+ impl Job {
1715+ pub async fn invoke(&self, commits: Vec<String>) -> Result<Duration> {
1716+ let start = SystemTime::now();
1717+ let repo_path = self.repository.path();
1718+ for (i, commit) in commits.iter().enumerate() {
1719+ if i % 50 == 0 && i != 0 {
1720+ log::info!("processed {}/{} commits", i, commits.len());
1721+ }
1722+ let mut contribution = Contribution::default();
1723+ let git_hash = commit.to_string();
1724+ let commit = self.repository.commit(Some(git_hash.clone()))?.unwrap();
1725+ let stats = self.repository.stats(git_hash.clone().as_str())?;
1726+ contribution.name = commit.author_name;
1727+ contribution.email = commit.author_email;
1728+ contribution.time = commit.epoch;
1729+ contribution.lines_added = stats.insertions;
1730+ contribution.lines_removed = stats.deletions;
1731+ let timestamp = OffsetDateTime::from_unix_timestamp(contribution.time).unwrap();
1732+ let author_id = self
1733+ .database
1734+ .upsert_author(contribution.name.as_str(), contribution.email.as_str())
1735+ .await?;
1736+ self.database
1737+ .contribution_add(
1738+ author_id,
1739+ &repo_path,
1740+ git_hash.as_str(),
1741+ timestamp,
1742+ contribution.lines_added as i64,
1743+ contribution.lines_removed as i64,
1744+ )
1745+ .await?;
1746+ self.database
1747+ .create_hash(
1748+ &repo_path,
1749+ git_hash.as_str(),
1750+ Kind::Contributors.to_string().as_str(),
1751+ self.model.id,
1752+ )
1753+ .await?;
1754+ }
1755+ Ok(start.elapsed().unwrap())
1756+ }
1757+ }
1758 diff --git a/ayllu/src/job_server/jobs/mod.rs b/ayllu/src/job_server/jobs/mod.rs
1759new file mode 100644
1760index 0000000..eb3ca38
1761--- /dev/null
1762+++ b/ayllu/src/job_server/jobs/mod.rs
1763 @@ -0,0 +1,37 @@
1764+ use std::sync::Arc;
1765+ use std::time::Duration;
1766+
1767+ use anyhow::Result;
1768+
1769+ use ayllu_api::models::{Job, Kind};
1770+ use ayllu_database::Wrapper as Database;
1771+ use ayllu_git::Wrapper as Repository;
1772+
1773+ mod cloc;
1774+ mod contributors;
1775+
1776+ pub async fn invoke(
1777+ commits: Vec<String>,
1778+ job: Job,
1779+ repository: Repository,
1780+ database: Arc<Database>,
1781+ ) -> Result<Duration> {
1782+ match job.kind {
1783+ Kind::Contributors => {
1784+ let job = contributors::Job {
1785+ repository,
1786+ database,
1787+ model: job,
1788+ };
1789+ job.invoke(commits).await
1790+ }
1791+ Kind::Cloc => {
1792+ let job = cloc::Job {
1793+ repository,
1794+ database,
1795+ model: job,
1796+ };
1797+ job.invoke(commits).await
1798+ }
1799+ }
1800+ }
1801 diff --git a/ayllu/src/job_server/mod.rs b/ayllu/src/job_server/mod.rs
1802new file mode 100644
1803index 0000000..0cceca5
1804--- /dev/null
1805+++ b/ayllu/src/job_server/mod.rs
1806 @@ -0,0 +1,7 @@
1807+ pub use commands::*;
1808+ pub use server::serve;
1809+
1810+ mod commands;
1811+ mod jobs;
1812+ mod runner;
1813+ mod server;
1814 diff --git a/ayllu/src/job_server/runner.rs b/ayllu/src/job_server/runner.rs
1815new file mode 100644
1816index 0000000..b5a7570
1817--- /dev/null
1818+++ b/ayllu/src/job_server/runner.rs
1819 @@ -0,0 +1,86 @@
1820+ use std::path::Path;
1821+ use std::sync::Arc;
1822+
1823+ use anyhow::Result;
1824+ use tracing::log;
1825+
1826+ use crate::database_ext::jobs::JobsExt;
1827+ use crate::job_server::jobs::invoke;
1828+ use ayllu_api::models::{Job, Kind};
1829+ use ayllu_database::Wrapper as Database;
1830+ use ayllu_git::Wrapper as Repository;
1831+
1832+ pub struct Runner {
1833+ max_depth: Option<usize>,
1834+ database: Arc<Database>,
1835+ job: Job,
1836+ }
1837+
1838+ impl Runner {
1839+ pub fn new(job: Job, database: Arc<Database>) -> Self {
1840+ Runner {
1841+ max_depth: None,
1842+ database,
1843+ job,
1844+ }
1845+ }
1846+
1847+ async fn commits(&self, kind: Kind, repository: &Repository) -> Result<Vec<String>> {
1848+ let repo_path = repository.path();
1849+ let latest_db_hash = self
1850+ .database
1851+ .read_hash(&repo_path, kind.to_string().as_str())
1852+ .await?;
1853+ let latest_git_hash = repository.latest_hash()?;
1854+ if latest_git_hash.is_none() {
1855+ // empty repository
1856+ return Ok(Vec::new());
1857+ };
1858+ let latest_git_hash = latest_git_hash.unwrap();
1859+ let commits: Vec<String> = match latest_db_hash {
1860+ Some(db_hash) => {
1861+ if latest_git_hash == db_hash {
1862+ // nothing to do
1863+ return Ok(Vec::new());
1864+ }
1865+
1866+ repository.commits(Some(db_hash.as_str()), self.max_depth)?
1867+ }
1868+ None => repository.commits(None, self.max_depth)?,
1869+ };
1870+ Ok(commits)
1871+ }
1872+
1873+ pub async fn run(&self) -> Result<()> {
1874+ log::info!("running {}", self.job.kind.to_string());
1875+ // let name = self.repository.as_ref().map(|r| r.path());
1876+ let job_id = self
1877+ .database
1878+ .create_job(
1879+ Some(self.job.repo_path.as_str()),
1880+ self.job.kind.to_string().as_str(),
1881+ )
1882+ .await?;
1883+
1884+ let mut job = self.job.clone();
1885+ job.id = job_id;
1886+
1887+ let repository = Repository::new(Path::new(&job.repo_path))?;
1888+ let commits = self.commits(job.kind.clone(), &repository).await?;
1889+ log::info!("processing {} commits", commits.len());
1890+ if commits.is_empty() {
1891+ // nothing to do
1892+ log::debug!("deleting unused job");
1893+ self.database.delete_job(job_id).await?;
1894+ log::debug!("deleted job");
1895+ return Ok(());
1896+ }
1897+
1898+ let runtime = invoke(commits, job, repository, self.database.clone()).await?;
1899+
1900+ let duration = runtime.as_millis() as i64;
1901+ log::info!("runtime {}ms", duration);
1902+ self.database.update_job(job_id, duration, true).await?;
1903+ Ok(())
1904+ }
1905+ }
1906 diff --git a/ayllu/src/job_server/server.rs b/ayllu/src/job_server/server.rs
1907new file mode 100644
1908index 0000000..c66afcb
1909--- /dev/null
1910+++ b/ayllu/src/job_server/server.rs
1911 @@ -0,0 +1,112 @@
1912+ use std::error::Error as StdError;
1913+ use std::sync::Arc;
1914+
1915+ use capnp::{capability::Promise, Error};
1916+ use capnp_rpc::pry;
1917+
1918+ use crate::config::Config;
1919+ use crate::database_ext::jobs::JobsExt;
1920+ use crate::job_server::runner::Runner;
1921+ use ayllu_api::jobs_capnp::server::{
1922+ Client, ListParams, ListResults, PurgeParams, PurgeResults, Server, SubmitParams, SubmitResults,
1923+ };
1924+ use ayllu_api::models::{Job, Kind};
1925+ use ayllu_database::{Wrapper as Database, Builder};
1926+ use ayllu_rpc::Server as CapnpServerHelper;
1927+
1928+
1929+ #[derive(Clone)]
1930+ pub struct ServerImpl {
1931+ db: Arc<Database>,
1932+ }
1933+
1934+ impl Server for ServerImpl {
1935+ fn submit(
1936+ &mut self,
1937+ params: SubmitParams,
1938+ _: SubmitResults,
1939+ ) -> ::capnp::capability::Promise<(), ::capnp::Error> {
1940+ let repo_path = pry!(pry!(pry!(params.get()).get_repo_path()).to_string());
1941+ let kind = match pry!(pry!(params.get()).get_kind()) {
1942+ ayllu_api::jobs_capnp::Kind::Contributors => String::from("contributors"),
1943+ ayllu_api::jobs_capnp::Kind::Cloc => String::from("cloc"),
1944+ };
1945+ let db = self.db.clone();
1946+ Promise::from_future(async move {
1947+ let job = Job {
1948+ id: 0,
1949+ repo_path: repo_path.clone(),
1950+ created_at: 0,
1951+ runtime: 0,
1952+ kind: Kind::from(kind),
1953+ commits: 0,
1954+ };
1955+ match Runner::new(job, db).run().await {
1956+ Ok(()) => Ok(()),
1957+ Err(e) => Err(Error {
1958+ kind: capnp::ErrorKind::Failed,
1959+ extra: e.to_string(),
1960+ }),
1961+ }
1962+ })
1963+ }
1964+
1965+ fn list(
1966+ &mut self,
1967+ _: ListParams,
1968+ mut result: ListResults,
1969+ ) -> ::capnp::capability::Promise<(), ::capnp::Error> {
1970+ let db = self.db.clone();
1971+ Promise::from_future(async move {
1972+ match db.list_jobs(None).await {
1973+ Ok(jobs) => {
1974+ let mut jobs_result = result.get().init_jobs(jobs.len() as u32);
1975+ for (i, job) in jobs.iter().enumerate() {
1976+ let mut job_result = jobs_result.reborrow().get(i as u32);
1977+ job_result.set_created_at(job.created_at.unix_timestamp());
1978+ if job.repo_path.is_some() {
1979+ let repo_path = job.repo_path.as_ref().unwrap();
1980+ job_result.set_repo_path(repo_path.as_str().into())
1981+ }
1982+ if job.runtime.is_some() {
1983+ job_result.set_runtime(job.runtime.unwrap() as i64)
1984+ }
1985+ job_result.set_commits(job.commits as i64);
1986+ job_result.set_kind(Kind::from(job.kind.clone()).into());
1987+ job_result.set_success(job.success.is_some_and(|success| success))
1988+ }
1989+ Ok(())
1990+ }
1991+ Err(e) => Err(Error {
1992+ kind: capnp::ErrorKind::Failed,
1993+ extra: e.to_string(),
1994+ }),
1995+ }
1996+ })
1997+ }
1998+
1999+ fn purge(
2000+ &mut self,
2001+ params: PurgeParams,
2002+ _: PurgeResults,
2003+ ) -> ::capnp::capability::Promise<(), ::capnp::Error> {
2004+ let repo_path = pry!(pry!(pry!(params.get()).get_repo_path()).to_string());
2005+ let db = self.db.clone();
2006+ Promise::from_future(async move {
2007+ match db.purge(&repo_path).await {
2008+ Ok(_) => Ok(()),
2009+ Err(e) => Err(Error {
2010+ kind: capnp::ErrorKind::Failed,
2011+ extra: e.to_string(),
2012+ }),
2013+ }
2014+ })
2015+ }
2016+ }
2017+
2018+ pub async fn serve(cfg: &Config) -> Result<(), Box<dyn StdError>> {
2019+ let db = Builder::default().url(&cfg.database.path).build().await?;
2020+ let server = ServerImpl { db: Arc::new(db) };
2021+ let runtime = CapnpServerHelper::<Client, ServerImpl>::new(&cfg.jobs_socket_path, server);
2022+ runtime.serve().await.map_err(|e| e.into())
2023+ }
2024 diff --git a/ayllu/src/languages.rs b/ayllu/src/languages.rs
2025new file mode 100644
2026index 0000000..9b236c8
2027--- /dev/null
2028+++ b/ayllu/src/languages.rs
2029 @@ -0,0 +1,433 @@
2030+ use std::cmp::{Eq, PartialEq};
2031+ use std::collections::HashMap;
2032+ use std::hash::Hash;
2033+ use std::io::Read;
2034+ use std::path::Path;
2035+ use std::sync::RwLock;
2036+
2037+ use lazy_static::lazy_static;
2038+ use serde::{Deserialize, Serialize};
2039+
2040+ use crate::config::Language as ConfigLanguage;
2041+
2042+ /// Hint is the case-insensitive name of a programming language, .e.g
2043+ /// OCaml, ocaml, Rust, rust, RuSt
2044+ #[derive(Debug, Clone, Serialize, Deserialize)]
2045+ pub struct Hint(pub String);
2046+
2047+ impl Eq for Hint {}
2048+
2049+ impl PartialEq for Hint {
2050+ fn eq(&self, other: &Self) -> bool {
2051+ self.0.to_uppercase() == other.0.to_uppercase()
2052+ }
2053+ }
2054+
2055+ impl Hash for Hint {
2056+ fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
2057+ state.write(self.0.to_uppercase().as_bytes());
2058+ }
2059+ }
2060+
2061+ impl From<String> for Hint {
2062+ fn from(value: String) -> Self {
2063+ Hint(value)
2064+ }
2065+ }
2066+
2067+ impl From<&String> for Hint {
2068+ fn from(value: &String) -> Self {
2069+ Hint(value.to_string())
2070+ }
2071+ }
2072+
2073+ impl From<&str> for Hint {
2074+ fn from(value: &str) -> Self {
2075+ Hint(value.to_string())
2076+ }
2077+ }
2078+
2079+ #[derive(Deserialize, Clone, Debug)]
2080+ struct Language {
2081+ #[serde(default = "Language::black")]
2082+ pub color: String,
2083+ pub extensions: Option<Vec<String>>,
2084+ pub filenames: Option<Vec<String>>,
2085+ pub aliases: Option<Vec<String>>,
2086+ #[allow(dead_code)]
2087+ #[serde(rename = "codemirror_mime_type")]
2088+ pub mime_type: Option<String>,
2089+ }
2090+
2091+ impl Language {
2092+ fn black() -> String {
2093+ String::from("#000000")
2094+ }
2095+ }
2096+
2097+ pub struct Languages {
2098+ language_to_color: RwLock<HashMap<Hint, String>>,
2099+ extension_to_language: RwLock<HashMap<String, Hint>>,
2100+ filename_to_language: RwLock<HashMap<String, Hint>>,
2101+ alias_to_language: RwLock<HashMap<String, Hint>>,
2102+ mappings: RwLock<HashMap<Hint, Hint>>,
2103+ }
2104+
2105+ impl Languages {
2106+ fn new() -> Self {
2107+ let linguist_json = include_str!("../vendor/linguist.json");
2108+ let by_name: HashMap<String, Language> =
2109+ serde_json::from_str(linguist_json).expect("invalid languages file?");
2110+ let mut language_to_color: HashMap<Hint, String> = HashMap::new();
2111+ let mut extension_to_language: HashMap<String, Hint> = HashMap::new();
2112+ let mut filename_to_language: HashMap<String, Hint> = HashMap::new();
2113+ let mut alias_to_language: HashMap<String, Hint> = HashMap::new();
2114+ for (name, language) in by_name.iter() {
2115+ language_to_color.insert(Hint(name.to_string()), language.color.clone());
2116+ match &language.extensions {
2117+ Some(extensions) => {
2118+ for extension in extensions.iter() {
2119+ let extension = extension.trim_start_matches('.');
2120+ extension_to_language.insert(extension.to_string(), name.into());
2121+ }
2122+ }
2123+ None => {}
2124+ };
2125+ match &language.filenames {
2126+ Some(filenames) => {
2127+ for filename in filenames {
2128+ filename_to_language.insert(filename.to_string(), name.into());
2129+ }
2130+ }
2131+ None => {}
2132+ }
2133+ match &language.aliases {
2134+ Some(aliases) => {
2135+ for alias in aliases {
2136+ alias_to_language.insert(alias.to_string(), name.into());
2137+ }
2138+ }
2139+ None => {}
2140+ }
2141+ }
2142+ Languages {
2143+ language_to_color: RwLock::new(language_to_color),
2144+ extension_to_language: RwLock::new(extension_to_language),
2145+ filename_to_language: RwLock::new(filename_to_language),
2146+ alias_to_language: RwLock::new(alias_to_language),
2147+ mappings: RwLock::new(HashMap::new()),
2148+ }
2149+ }
2150+
2151+ pub fn set_mappings(&self, mappings: HashMap<Hint, Hint>) {
2152+ *self.mappings.write().unwrap() = mappings.clone();
2153+ }
2154+
2155+ pub fn set_languages(&self, languages: Vec<ConfigLanguage>) {
2156+ for language in languages {
2157+ match &language.color {
2158+ Some(color) => {
2159+ self.language_to_color
2160+ .write()
2161+ .unwrap()
2162+ .insert(Hint(language.name.clone()), color.to_string());
2163+ }
2164+ None => {}
2165+ };
2166+ match &language.extensions {
2167+ Some(extensions) => {
2168+ for extension in extensions {
2169+ let extension = extension.trim_start_matches('.');
2170+ let mut extensions = self.extension_to_language.write().unwrap();
2171+ extensions.insert(extension.to_string(), language.name.clone().into());
2172+ }
2173+ }
2174+ None => {}
2175+ }
2176+ match &language.filenames {
2177+ Some(filenames) => {
2178+ for filename in filenames {
2179+ let mut filenames = self.filename_to_language.write().unwrap();
2180+ filenames.insert(filename.to_string(), language.name.clone().into());
2181+ }
2182+ }
2183+ None => {}
2184+ }
2185+ }
2186+ }
2187+
2188+ pub fn color_from_language(&self, language: &Hint) -> String {
2189+ self.language_to_color
2190+ .read()
2191+ .unwrap()
2192+ .get(language)
2193+ .cloned()
2194+ .unwrap_or(String::from("#000000"))
2195+ }
2196+
2197+ fn guess_str(&self, code: &str) -> Option<Hint> {
2198+ let mut buf = [0; 512];
2199+ let mut b = code.as_bytes();
2200+ let _ = b.read(&mut buf);
2201+ let chunk = String::from_utf8(buf.to_vec()).unwrap();
2202+ for line in chunk.split('\n') {
2203+ if line.starts_with("#!") {
2204+ let mut split = line.split(' ');
2205+ let first = split.nth(0).unwrap();
2206+ let path = Path::new(first.trim_start_matches("#!"));
2207+ if let Some(name) = path.file_name() {
2208+ match name.to_str().unwrap() {
2209+ "env" => {
2210+ // special case for env shebang where the input
2211+ // uses an -S arg, see man env.1
2212+ // example:
2213+ // #!/usr/bin/env -C /tmp -S perl -w -T
2214+ if line.contains("-S") || line.contains("--split-line") {
2215+ loop {
2216+ match split.next() {
2217+ Some("-S") | Some("--split-line") => {
2218+ return split.next().map(|arg| Hint(arg.to_string()))
2219+ }
2220+ Some(_) => {} // some other arg
2221+ None => return None,
2222+ }
2223+ }
2224+ } else {
2225+ match split.nth(0) {
2226+ // TODO: Not always correct here, just guessing that the
2227+ // executable maps to the language name e.g. /usr/bin/python
2228+ Some(program) => return Some(Hint(program.to_string())),
2229+ None => return None,
2230+ }
2231+ }
2232+ }
2233+ name => return Some(Hint(name.to_string())),
2234+ }
2235+ }
2236+ }
2237+ }
2238+ None
2239+ }
2240+
2241+ fn guess_filename(&self, filename: &str) -> Option<Hint> {
2242+ match self.filename_to_language.read().unwrap().get(filename) {
2243+ Some(hint) => Some(hint.clone()),
2244+ None => {
2245+ let extension = Path::new(filename)
2246+ .extension()
2247+ .map(|extension| extension.to_str().unwrap().trim_start_matches('.'));
2248+ match extension {
2249+ Some(ext) => self.extension_to_language.read().unwrap().get(ext).cloned(),
2250+ None => None,
2251+ }
2252+ }
2253+ }
2254+ }
2255+
2256+ fn guess_alias(&self, alias: &str) -> Option<Hint> {
2257+ self.alias_to_language.read().unwrap().get(alias).cloned()
2258+ }
2259+
2260+ fn resolve(&self, hint: Option<&Hint>) -> Option<Hint> {
2261+ match hint {
2262+ Some(hint) => match self.mappings.read().unwrap().get(hint) {
2263+ Some(other) => Some(other.clone()),
2264+ None => Some(hint.clone()),
2265+ },
2266+ None => None,
2267+ }
2268+ }
2269+
2270+ pub fn guess(&self, code: &str, alias: Option<&str>, filename: Option<&str>) -> Option<Hint> {
2271+ let hint = match (alias, filename) {
2272+ (Some(alias), Some(filename)) => match self.guess_alias(alias) {
2273+ Some(hint) => Some(hint),
2274+ None => match self.guess_filename(filename) {
2275+ Some(hint) => Some(hint),
2276+ None => self.guess_str(code),
2277+ },
2278+ },
2279+ (Some(alias), None) => self.guess_alias(alias),
2280+ (None, Some(filename)) => match self.guess_filename(filename) {
2281+ Some(hint) => Some(hint),
2282+ None => self.guess_str(code),
2283+ },
2284+ (None, None) => self.guess_str(code),
2285+ };
2286+ self.resolve(hint.as_ref())
2287+ }
2288+ }
2289+
2290+ lazy_static! {
2291+ pub static ref LANGUAGE_TABLE: Languages = Languages::new();
2292+ }
2293+
2294+ #[cfg(test)]
2295+ mod tests {
2296+
2297+ use super::*;
2298+
2299+ const PYTHON_SCRIPT: &str = r"
2300+ #!/usr/bin/env python
2301+
2302+ def fuu()
2303+ print('bar')
2304+
2305+ if __name__ == '__main__'
2306+ fuu()
2307+ fuu()
2308+ ";
2309+
2310+ const PYTHON_SCRIPT_2: &str = r"
2311+ # some random text here with some comments
2312+ # woooo!
2313+
2314+ #!/usr/bin/python
2315+
2316+ def fuu()
2317+ print('bar')
2318+
2319+ if __name__ == '__main__'
2320+ fuu()
2321+ fuu()
2322+ ";
2323+
2324+ const PERL_SCRIPT: &str = r"
2325+ #!/usr/bin/perl
2326+
2327+ use strict;
2328+ use warnings;
2329+
2330+ print('Hello World\n');
2331+ ";
2332+
2333+ // example from env.1
2334+ const PERL_SCRIPT_2: &str = r"
2335+ #!/usr/bin/env -S perl -w -T
2336+
2337+ use strict;
2338+ use warnings;
2339+
2340+ print('Hello World\n');
2341+ ";
2342+
2343+ #[test]
2344+ fn test_extension_lookup() {
2345+ let table = Languages::new();
2346+ let result = table.guess("", None, Some("hello.py"));
2347+ assert!(result.is_some_and(|name| name == "Python".into()));
2348+ }
2349+
2350+ #[test]
2351+ fn test_extension_mapping_override() {
2352+ let table = Languages::new();
2353+ table.set_mappings(HashMap::from_iter(vec![(
2354+ Hint("Standard ML".to_string()),
2355+ Hint("OCaml".to_string()),
2356+ )]));
2357+ let result = table.guess("", None, Some("main.ml"));
2358+ assert!(result.is_some_and(|name| name == "OCaml".into()));
2359+ let result = table.guess("", None, Some("main.mli"));
2360+ assert!(result.is_some_and(|name| name == "OCaml".into()));
2361+ }
2362+
2363+ #[test]
2364+ fn test_extension_mapping_override_2() {
2365+ let table = Languages::new();
2366+ table.set_mappings(HashMap::from_iter(vec![(
2367+ Hint("Shell".to_string()),
2368+ Hint("Bash".to_string()),
2369+ )]));
2370+ let result = table.guess("", None, Some(".bashrc"));
2371+ assert!(result.is_some_and(|name| name == "Bash".into()));
2372+ }
2373+
2374+ #[test]
2375+ fn test_alias() {
2376+ let table = Languages::new();
2377+ let result = table.guess("", Some("sh"), None);
2378+ assert!(result.is_some_and(|name| name == "Shell".into()))
2379+ }
2380+
2381+ #[test]
2382+ fn test_shebang_python_1() {
2383+ let table = Languages::new();
2384+ let result = table.guess(PYTHON_SCRIPT, None, None);
2385+ assert!(result.is_some_and(|name| name == "Python".into()));
2386+ }
2387+
2388+ #[test]
2389+ fn test_shebang_python_2() {
2390+ let table = Languages::new();
2391+ let result = table.guess(PYTHON_SCRIPT_2, None, None);
2392+ assert!(result.is_some_and(|name| name == "Python".into()));
2393+ }
2394+
2395+ #[test]
2396+ fn test_shebang_perl() {
2397+ let table = Languages::new();
2398+ let result = table.guess(PERL_SCRIPT, None, None);
2399+ assert!(result.is_some_and(|name| name == "Perl".into()));
2400+ }
2401+
2402+ #[test]
2403+ fn test_shebang_perl_2() {
2404+ let table = Languages::new();
2405+ let result = table.guess(PERL_SCRIPT_2, None, None);
2406+ assert!(result.is_some_and(|name| name == "Perl".into()));
2407+ }
2408+
2409+ #[test]
2410+ fn test_color_lookup() {
2411+ let table = Languages::new();
2412+ let color = table.color_from_language(&"Go".into());
2413+ assert!(color == "#00ADD8");
2414+ }
2415+
2416+ #[test]
2417+ fn test_color_lookup_with_override() {
2418+ let table = Languages::new();
2419+ table.set_languages(vec![ConfigLanguage {
2420+ name: "Go".to_string(),
2421+ extensions: None,
2422+ color: Some("#FF1493".to_string()),
2423+ filenames: None,
2424+ }]);
2425+ let color = table.color_from_language(&"Go".into());
2426+ assert!(color == "#FF1493");
2427+ }
2428+
2429+ #[test]
2430+ fn test_custom_language() {
2431+ let table = Languages::new();
2432+ table.set_languages(vec![ConfigLanguage {
2433+ name: "FuuBar".to_string(),
2434+ extensions: Some(vec![".fuu".to_string(), ".bar".to_string()]),
2435+ color: Some("#FFFFFF".to_string()),
2436+ filenames: Some(vec![".fuuscript".to_string()]),
2437+ }]);
2438+ assert!(table
2439+ .guess("", None, Some("main.fuu"))
2440+ .is_some_and(|hint| hint == Hint("FuuBar".to_string())));
2441+ assert!(table
2442+ .guess("", None, Some(".fuuscript"))
2443+ .is_some_and(|hint| hint == Hint("FuuBar".to_string())))
2444+ }
2445+
2446+ #[test]
2447+ fn test_filename() {
2448+ let table = Languages::new();
2449+ assert!(table
2450+ .guess("", None, Some(".zshrc"))
2451+ .is_some_and(|hint| hint == Hint("Shell".to_string())));
2452+ assert!(table
2453+ .guess("", None, Some(".profile"))
2454+ .is_some_and(|hint| hint == Hint("Shell".to_string())));
2455+ assert!(table
2456+ .guess("", None, Some("PKGBUILD"))
2457+ .is_some_and(|hint| hint == Hint("Shell".to_string())));
2458+ }
2459+
2460+ #[test]
2461+ fn test_mime_lookup() {}
2462+ }
2463 diff --git a/ayllu/src/license.rs b/ayllu/src/license.rs
2464new file mode 100644
2465index 0000000..34a6444
2466--- /dev/null
2467+++ b/ayllu/src/license.rs
2468 @@ -0,0 +1,29 @@
2469+ use std::path::Path;
2470+
2471+ use ayllu_git::{Error, Wrapper};
2472+
2473+ // TODO: support an array of regexes
2474+ pub static LICENSES: &[(&str, &str)] = &[
2475+ ("LGPL-2.0", "GNU LIBRARY GENERAL PUBLIC LICENSE"),
2476+ ("AGPL-3.0", "GNU AFFERO GENERAL PUBLIC LICENSE"),
2477+ ("MIT", "MIT License"),
2478+ ("MPL-2.0", "Mozilla Public License Version 2.0"),
2479+ ];
2480+
2481+ static LICENSE_PATHS: &[&str] = &["LICENSE", "COPYING", "LICENSE.md"];
2482+
2483+ // TODO: super naive license detection
2484+ // see https://github.com/OpenSourceOrg/licenses
2485+ pub fn detect(repository: &Wrapper, commit: Option<String>) -> Result<Option<String>, Error> {
2486+ for path in LICENSE_PATHS {
2487+ let path = Path::new(path);
2488+ if let Some(content) = repository.read_string(path, commit.clone())? {
2489+ for license in LICENSES {
2490+ if content.contains(license.1) {
2491+ return Ok(Some(license.0.to_string()));
2492+ }
2493+ }
2494+ }
2495+ }
2496+ Ok(None)
2497+ }
2498 diff --git a/ayllu/src/main.rs b/ayllu/src/main.rs
2499new file mode 100644
2500index 0000000..0131ad1
2501--- /dev/null
2502+++ b/ayllu/src/main.rs
2503 @@ -0,0 +1,197 @@
2504+ use std::error::Error;
2505+ use std::io::stderr;
2506+ use std::path::{Path, PathBuf};
2507+ use std::thread::Builder as ThreadBuilder;
2508+
2509+ use clap::{Args, Parser, Subcommand};
2510+ use tokio::runtime::Builder;
2511+
2512+ use ayllu_config::Reader;
2513+ use ayllu_database::migrate;
2514+
2515+ mod job_server;
2516+ mod web2;
2517+
2518+ mod config;
2519+ mod database_ext;
2520+ mod languages;
2521+ mod license;
2522+ mod time;
2523+
2524+ #[derive(Parser, Debug)]
2525+ #[clap(version, about, long_about = "hyper performant code forge")]
2526+ struct Arguments {
2527+ #[clap(short, long, value_name = "FILE")]
2528+ config: Option<PathBuf>,
2529+ // FIXME: Parse to Level enum directly
2530+ /// logging level [ERROR,WARN,INFO,DEBUG,TRACE]
2531+ #[clap(short, long)]
2532+ level: Option<String>,
2533+ #[clap(subcommand)]
2534+ subcommand: Command,
2535+ }
2536+
2537+ #[derive(Args, Debug)]
2538+ struct JobArguments {
2539+ #[clap(short, long, default_value = "false")]
2540+ short: bool,
2541+ /// Path to a git repository
2542+ path: Option<PathBuf>,
2543+ /// Job kind specifier.
2544+ #[clap(short, long)]
2545+ kind: Option<String>,
2546+ }
2547+
2548+ #[derive(Subcommand, Debug)]
2549+ enum ConfigCommand {
2550+ /// Display the current configuration.
2551+ Display {
2552+ #[clap(long, default_value = "false")]
2553+ as_json: bool,
2554+ },
2555+ /// Generate a sample configuration file.
2556+ Generate,
2557+ }
2558+
2559+ #[derive(Subcommand, Debug)]
2560+ enum Command {
2561+ /// Configuration options.
2562+ #[clap(subcommand)]
2563+ Config(ConfigCommand),
2564+ /// Perform database migration.
2565+ Migrate,
2566+ /// Launch the main web server.
2567+ Serve,
2568+ /// Offline indexing and maintenance.
2569+ #[clap(subcommand)]
2570+ Jobs(JobsCommand),
2571+ }
2572+
2573+ #[derive(Parser, Debug)]
2574+ enum JobsCommand {
2575+ /// View the status of jobs.
2576+ List(JobArguments),
2577+ /// Run one or more job.
2578+ Run {
2579+ /// run all jobs across all repositories
2580+ #[clap(short, long, default_value = "false")]
2581+ all: bool,
2582+ #[clap(flatten)]
2583+ rest: JobArguments,
2584+ },
2585+ /// purge data from the database.
2586+ Purge(JobArguments),
2587+ }
2588+
2589+ fn init_config(
2590+ path: Option<&Path>,
2591+ level: Option<&str>,
2592+ ) -> Result<config::Config, ayllu_config::Error> {
2593+ let cfg: config::Config = Reader::load(path)?;
2594+ tracing_subscriber::fmt()
2595+ .compact()
2596+ .with_line_number(true)
2597+ .with_level(true)
2598+ .with_writer(stderr)
2599+ // tokei will spam the console unless this is set
2600+ .with_env_filter(format!(
2601+ "{},tokei::language::language_type=error",
2602+ level.unwrap_or(&cfg.log_level)
2603+ ))
2604+ .init();
2605+ log::info!("Logger initialized.");
2606+ Ok(cfg)
2607+ }
2608+
2609+ fn main() -> Result<(), Box<dyn Error>> {
2610+ let args: Arguments = Arguments::parse();
2611+ match args.subcommand {
2612+ Command::Config(subcommand) => match subcommand {
2613+ ConfigCommand::Display { as_json: true } => {
2614+ println!(
2615+ "{}",
2616+ init_config(args.config.as_deref(), args.level.as_deref())?.to_json()
2617+ );
2618+ Ok(())
2619+ }
2620+ ConfigCommand::Display { as_json: false } => {
2621+ println!(
2622+ "{:#?}",
2623+ init_config(args.config.as_deref(), args.level.as_deref())?
2624+ );
2625+ Ok(())
2626+ }
2627+ ConfigCommand::Generate => {
2628+ println!("{}", config::EXAMPLE_CONFIG);
2629+ Ok(())
2630+ }
2631+ },
2632+ Command::Migrate => {
2633+ let cfg = init_config(args.config.as_deref(), args.level.as_deref())?;
2634+ let runtime = Builder::new_current_thread().enable_all().build().unwrap();
2635+ runtime.block_on(migrate(&cfg.database.path, "./migrations"))?;
2636+ Ok(())
2637+ }
2638+ Command::Serve => {
2639+ let cfg = init_config(args.config.as_deref(), args.level.as_deref())?;
2640+ // launch the job server in a separate thread
2641+ let job_config = cfg.clone();
2642+ ThreadBuilder::new()
2643+ .name(String::from("ayllu-jobs-runtime"))
2644+ .spawn(move || {
2645+ let runtime = Builder::new_current_thread().enable_all().build().unwrap();
2646+ runtime
2647+ .block_on(async {
2648+ tokio::task::LocalSet::new()
2649+ .run_until(job_server::serve(&job_config))
2650+ .await
2651+ })
2652+ .unwrap();
2653+ })
2654+ .unwrap();
2655+ let runtime = Builder::new_multi_thread()
2656+ .worker_threads(cfg.worker_threads.into())
2657+ .thread_name("ayllu-web-runtime")
2658+ .max_blocking_threads(cfg.max_blocking_threads.into())
2659+ .enable_all()
2660+ .build()
2661+ .unwrap();
2662+ runtime.block_on(web2::runtime::serve(&cfg));
2663+ Ok(())
2664+ }
2665+ Command::Jobs(subcommand) => {
2666+ let cfg = init_config(args.config.as_deref(), args.level.as_deref())?;
2667+ let runtime = Builder::new_current_thread().enable_all().build().unwrap();
2668+ match subcommand {
2669+ JobsCommand::List(JobArguments { path, .. }) => {
2670+ runtime.block_on(async {
2671+ tokio::task::LocalSet::new()
2672+ .run_until(job_server::list(cfg, path))
2673+ .await
2674+ })?;
2675+ Ok(())
2676+ }
2677+ JobsCommand::Run {
2678+ all: _,
2679+ rest: JobArguments { path, short, kind },
2680+ } => {
2681+ let depth = if short { Some(1) } else { None };
2682+ runtime.block_on(async {
2683+ tokio::task::LocalSet::new()
2684+ .run_until(job_server::run_one(cfg, path, kind.as_deref(), depth))
2685+ .await
2686+ })?;
2687+ Ok(())
2688+ }
2689+ JobsCommand::Purge(JobArguments { path, .. }) => {
2690+ runtime.block_on(async {
2691+ tokio::task::LocalSet::new()
2692+ .run_until(job_server::purge(cfg, path))
2693+ .await
2694+ })?;
2695+ Ok(())
2696+ }
2697+ }
2698+ }
2699+ }
2700+ }
2701 diff --git a/ayllu/src/time.rs b/ayllu/src/time.rs
2702new file mode 100644
2703index 0000000..f0578dd
2704--- /dev/null
2705+++ b/ayllu/src/time.rs
2706 @@ -0,0 +1,54 @@
2707+ use std::time::SystemTime;
2708+
2709+ // apparently these exist in nightly rust?
2710+ const SECOND: u64 = 1;
2711+ const MINUTE: u64 = SECOND * 60;
2712+ const HOUR: u64 = MINUTE * 60;
2713+ const DAY: u64 = HOUR * 24;
2714+ const WEEK: u64 = DAY * 7;
2715+ const MONTH: u64 = DAY * 30;
2716+ const YEAR: u64 = DAY * 356;
2717+
2718+ fn pluralize(value: u64) -> String {
2719+ match value {
2720+ 1 => String::new(),
2721+ _ => String::from("s"),
2722+ }
2723+ }
2724+
2725+ // convert number of a seconds into a friendly string, e.g.
2726+ // recently, 1s ago, 1m ago, 1h ago, 1d ago, 1mo ago, 1y ago
2727+ // FIXME: why is this u64
2728+ pub fn friendly(seconds: u64) -> String {
2729+ let now = SystemTime::now()
2730+ .duration_since(SystemTime::UNIX_EPOCH)
2731+ .unwrap();
2732+ let seconds = now.as_secs() - seconds;
2733+ let message: String;
2734+ if seconds == 0 {
2735+ message = "recently".to_string();
2736+ } else if seconds < MINUTE {
2737+ message = format!("{} second{} ago", seconds, pluralize(seconds));
2738+ } else if seconds < HOUR {
2739+ message = format!(
2740+ "{} minute{} ago",
2741+ seconds / MINUTE,
2742+ pluralize(seconds / MINUTE),
2743+ )
2744+ } else if seconds < DAY {
2745+ message = format!("{} hour{} ago", seconds / HOUR, pluralize(seconds / HOUR),)
2746+ } else if seconds < WEEK {
2747+ message = format!("{} day{} ago", seconds / DAY, pluralize(seconds / DAY),)
2748+ } else if seconds < MONTH {
2749+ message = format!("{} week{} ago", seconds / WEEK, pluralize(seconds / WEEK),)
2750+ } else if seconds < YEAR {
2751+ message = format!(
2752+ "{} month{} ago",
2753+ seconds / MONTH,
2754+ pluralize(seconds / MONTH),
2755+ )
2756+ } else {
2757+ message = format!("{} year{} ago", seconds / YEAR, pluralize(seconds / YEAR),)
2758+ }
2759+ message
2760+ }
2761 diff --git a/ayllu/src/web2/charts.rs b/ayllu/src/web2/charts.rs
2762new file mode 100644
2763index 0000000..e331617
2764--- /dev/null
2765+++ b/ayllu/src/web2/charts.rs
2766 @@ -0,0 +1,222 @@
2767+ use std::i64;
2768+ use std::ops::Range;
2769+
2770+ use plotters::prelude::*;
2771+ use sqlx::Error;
2772+
2773+ use crate::languages::{Hint, LANGUAGE_TABLE};
2774+
2775+ const ALMOST_BLACK: &str = "#111111";
2776+
2777+ const LABEL_STYLE: (FontFamily, f64, FontStyle) = (FontFamily::Monospace, 14.0, FontStyle::Normal);
2778+ const TITLE_STYLE: (FontFamily, f64, FontStyle) = (FontFamily::Monospace, 16.0, FontStyle::Bold);
2779+
2780+ fn hex_to_rgb(input: &str) -> RGBColor {
2781+ let mut hex = input.to_string();
2782+ hex = hex.replace('#', "");
2783+ if hex.len() != 6 {
2784+ panic!("invalid color hex code");
2785+ }
2786+ let hex = hex.as_str();
2787+ let r = &hex[0..2];
2788+ let r = i64::from_str_radix(r, 16).unwrap();
2789+ let g = &hex[2..4];
2790+ let g = i64::from_str_radix(g, 16).unwrap();
2791+ let b = &hex[4..6];
2792+ let b = i64::from_str_radix(b, 16).unwrap();
2793+ RGBColor(r as u8, g as u8, b as u8)
2794+ }
2795+
2796+ // replace set primary color to currentColor to allow some level of CSS styling
2797+ fn filter_black(input: String) -> String {
2798+ let output = input.replace(
2799+ &format!("fill=\"{}\"", ALMOST_BLACK),
2800+ "fill=\"currentColor\"",
2801+ );
2802+ let output = output.replace(
2803+ &format!("stroke=\"{}\"", ALMOST_BLACK),
2804+ "stroke=\"currentColor\"",
2805+ );
2806+ let output = output.replace("stroke=\"#000000\"", "stroke=\"currentColor\"");
2807+
2808+ output.replace("fill=\"#000000\"", "fill=\"currentColor\"")
2809+ }
2810+
2811+ pub struct Renderer {
2812+ pub size: (u32, u32),
2813+ }
2814+
2815+ impl Renderer {
2816+ /// create a new chart renderer where the default size is (width, height)
2817+ pub fn new(size: (u32, u32)) -> Self {
2818+ Renderer { size }
2819+ }
2820+
2821+ pub fn languages(&self, data: Vec<(String, u8, u32)>) -> Result<String, Error> {
2822+ // total LOC for chart title
2823+ let loc = data.iter().fold(0, |accm, item| accm + item.2);
2824+ let mut output = String::new();
2825+ let n_items = match data.len() {
2826+ 0 => 0,
2827+ n => n - 1,
2828+ } as u32;
2829+ {
2830+ let root = SVGBackend::with_string(&mut output, self.size)
2831+ .into_drawing_area()
2832+ .titled(
2833+ format!("Composition [{} LOC]", loc).as_str(),
2834+ TextStyle::from((TITLE_STYLE.0, TITLE_STYLE.1).into_font()).color(&(BLACK)),
2835+ )
2836+ .unwrap();
2837+
2838+ let mut chart = ChartBuilder::on(&root)
2839+ .x_label_area_size(35)
2840+ .y_label_area_size(50)
2841+ .margin(5)
2842+ .build_cartesian_2d(0u32..105u32, (0u32..n_items).into_segmented())
2843+ .unwrap();
2844+
2845+ let names: Vec<String> = data.iter().map(|item| item.0.to_string()).collect();
2846+
2847+ let black: RGBAColor = hex_to_rgb(ALMOST_BLACK).into();
2848+
2849+ chart
2850+ .configure_mesh()
2851+ .bold_line_style(ShapeStyle {
2852+ color: black,
2853+ filled: false,
2854+ stroke_width: 1,
2855+ })
2856+ .x_label_style(
2857+ FontDesc::new(LABEL_STYLE.0, LABEL_STYLE.1, LABEL_STYLE.2).with_color(black),
2858+ )
2859+ .y_label_style(
2860+ FontDesc::new(LABEL_STYLE.0, LABEL_STYLE.1, LABEL_STYLE.2).with_color(black),
2861+ )
2862+ .y_labels(5)
2863+ .y_label_formatter(&|v| match *v {
2864+ SegmentValue::CenterOf(n) => match names.get(n as usize) {
2865+ Some(value) => value.to_string(),
2866+ None => String::new(),
2867+ },
2868+ _ => unreachable!("invalid chart data"),
2869+ })
2870+ .x_label_formatter(&|v| format!("{}%", v))
2871+ .draw()
2872+ .unwrap();
2873+
2874+ data.iter().enumerate().for_each(|(i, item)| {
2875+ let data = [item.1 as u32];
2876+ let color =
2877+ hex_to_rgb(&LANGUAGE_TABLE.color_from_language(&Hint(item.0.to_string())));
2878+ let hist = Histogram::horizontal(&chart)
2879+ .style(color.filled())
2880+ .data(data.iter().map(|x: &u32| (i as u32, *x)));
2881+ chart.draw_series(hist).unwrap();
2882+ });
2883+ }
2884+
2885+ Ok(filter_black(output))
2886+ }
2887+
2888+ pub fn activity(&self, data: Vec<(i64, i64, i64)>) -> Result<String, Error> {
2889+ let lines_added: Vec<i64> = data.iter().map(|item| item.1).collect();
2890+ let lines_removed: Vec<i64> = data.iter().map(|item| item.2).collect();
2891+ let y_max = match lines_added.iter().max_by(|x, y| x.cmp(y)) {
2892+ Some(i) => *i,
2893+ None => 90,
2894+ };
2895+ let y_min = match lines_removed.iter().min_by(|x, y| x.cmp(y)) {
2896+ Some(i) => *i,
2897+ None => 0,
2898+ };
2899+ let mut output = String::new();
2900+ {
2901+ let root = SVGBackend::with_string(&mut output, self.size)
2902+ .into_drawing_area()
2903+ .titled(
2904+ "Recent Activity (90 days)",
2905+ TextStyle::from((TITLE_STYLE.0, TITLE_STYLE.1).into_font()).color(&(BLACK)),
2906+ )
2907+ .unwrap();
2908+
2909+ let mut ctx = ChartBuilder::on(&root)
2910+ .margin(5)
2911+ .set_label_area_size(LabelAreaPosition::Left, 50)
2912+ .set_label_area_size(LabelAreaPosition::Bottom, 30)
2913+ .build_cartesian_2d(
2914+ Range {
2915+ start: 0,
2916+ end: data.len(),
2917+ }
2918+ .into_segmented(),
2919+ Range {
2920+ start: y_min,
2921+ end: y_max,
2922+ },
2923+ )
2924+ .unwrap();
2925+
2926+ let black = hex_to_rgb(ALMOST_BLACK);
2927+
2928+ ctx.configure_mesh()
2929+ .bold_line_style(ShapeStyle {
2930+ color: black.into(),
2931+ filled: false,
2932+ stroke_width: 1,
2933+ })
2934+ .x_label_style(FontDesc::new(LABEL_STYLE.0, LABEL_STYLE.1, LABEL_STYLE.2))
2935+ .y_label_style(FontDesc::new(LABEL_STYLE.0, LABEL_STYLE.1, LABEL_STYLE.2))
2936+ .draw()
2937+ .unwrap();
2938+
2939+ ctx.draw_series((0..).zip(lines_added.iter()).map(|(x, y)| {
2940+ let x0 = SegmentValue::Exact(x);
2941+ let x1 = SegmentValue::Exact(x + 1);
2942+ let mut bar = Rectangle::new([(x0, 0), (x1, *y)], GREEN.filled());
2943+ bar.set_margin(0, 0, 1, 1);
2944+ bar
2945+ }))
2946+ .unwrap();
2947+ ctx.draw_series((0..).zip(lines_removed.iter()).map(|(x, y)| {
2948+ let x0 = SegmentValue::Exact(x);
2949+ let x1 = SegmentValue::Exact(x + 1);
2950+ let mut bar = Rectangle::new([(x0, 0), (x1, *y)], RED.filled());
2951+ bar.set_margin(0, 0, 1, 1);
2952+ bar
2953+ }))
2954+ .unwrap();
2955+ }
2956+
2957+ Ok(filter_black(output))
2958+ }
2959+ }
2960+
2961+ // return a string like ▁▂▃▅▂▇▃▅▂▇▃▂▁▂▁▁▁ to illustrate usage activity
2962+ // idea inspired by https://git.zx2c4.com/spark/tree/spark.c
2963+ pub fn spark_lines(arr: Vec<i64>, chunks: usize) -> String {
2964+ let buckets: Vec<i64> = arr.chunks(chunks).map(|chunk| chunk.iter().sum()).collect();
2965+ let n_buckets = buckets.len() as f64;
2966+ let mut sorted = buckets.clone();
2967+ sorted.sort();
2968+ let line = buckets.iter().fold(String::new(), |accm, total| {
2969+ let (rank, _) = sorted
2970+ .iter()
2971+ .enumerate()
2972+ .find(|(_, other)| **other == *total)
2973+ .unwrap();
2974+ let bar = if rank < 1 {
2975+ String::from('▁')
2976+ } else if (rank as f64) < (n_buckets * 0.20) {
2977+ String::from('▂')
2978+ } else if (rank as f64) < (n_buckets * 0.40) {
2979+ String::from('▃')
2980+ } else if (rank as f64) < (n_buckets * 0.80) {
2981+ String::from('▅')
2982+ } else {
2983+ String::from('▇')
2984+ };
2985+ accm + &bar.to_string()
2986+ });
2987+ line
2988+ }
2989 diff --git a/ayllu/src/web2/config.rs b/ayllu/src/web2/config.rs
2990new file mode 100644
2991index 0000000..a8a2a87
2992--- /dev/null
2993+++ b/ayllu/src/web2/config.rs
2994 @@ -0,0 +1,8 @@
2995+ use serde::{Deserialize, Serialize};
2996+
2997+ // Client side configuration via cookies
2998+ #[derive(Clone, Deserialize, Serialize)]
2999+ pub struct Config {
3000+ pub theme: Option<String>,
3001+ pub items_per_page: usize,
3002+ }
3003 diff --git a/ayllu/src/web2/error.rs b/ayllu/src/web2/error.rs
3004new file mode 100644
3005index 0000000..38d078e
3006--- /dev/null
3007+++ b/ayllu/src/web2/error.rs
3008 @@ -0,0 +1,88 @@
3009+ use std::error::Error as StdError;
3010+ use std::fmt::Display;
3011+ use std::io::Error as IoError;
3012+
3013+ use axum::{body::Body, response::IntoResponse, response::Response};
3014+ use tera::Error as TeraError;
3015+
3016+ use ayllu_database::Error as SqlError;
3017+ use ayllu_git::Error as GitError;
3018+ use ayllu_rpc::Error as RpcError;
3019+
3020+ /// Error maps known error types into errors that can be translated into HTTP
3021+ /// status codes, e.g. Io::NotFound -> 404
3022+ #[derive(Debug, Clone)]
3023+ pub enum Error {
3024+ Message(String),
3025+ NotFound(String),
3026+ }
3027+
3028+ impl IntoResponse for Error {
3029+ fn into_response(self) -> Response {
3030+ Response::builder()
3031+ .extension(self)
3032+ .body(Body::empty())
3033+ .unwrap()
3034+ }
3035+ }
3036+
3037+ impl From<Box<dyn StdError>> for Error {
3038+ fn from(value: Box<dyn StdError>) -> Self {
3039+ Error::Message(value.to_string())
3040+ }
3041+ }
3042+
3043+ impl From<RpcError> for Error {
3044+ fn from(value: RpcError) -> Self {
3045+ Error::Message(format!("RPC: {:?}", value))
3046+ }
3047+ }
3048+
3049+ impl From<TeraError> for Error {
3050+ fn from(value: TeraError) -> Self {
3051+ Error::Message(format!("Templating: {:?}", value))
3052+ }
3053+ }
3054+
3055+ impl From<GitError> for Error {
3056+ fn from(value: GitError) -> Self {
3057+ if value.not_found() {
3058+ Error::NotFound(format!("GIT: {:?}", value))
3059+ } else {
3060+ Error::Message(format!("GIT: {:?}", value))
3061+ }
3062+ }
3063+ }
3064+
3065+ impl From<IoError> for Error {
3066+ fn from(value: IoError) -> Self {
3067+ match value.kind() {
3068+ std::io::ErrorKind::NotFound => Error::NotFound(format!("IO: {}", value)),
3069+ _ => Error::Message(format!("IO: {:?}", value)),
3070+ }
3071+ }
3072+ }
3073+
3074+ impl From<SqlError> for Error {
3075+ fn from(value: SqlError) -> Self {
3076+ Error::Message(format!("SQL: {:?}", value))
3077+ }
3078+ }
3079+
3080+ impl StdError for Error {
3081+ fn source(&self) -> Option<&(dyn StdError + 'static)> {
3082+ match self {
3083+ Error::Message(_) => None,
3084+ Error::NotFound(_) => None,
3085+ }
3086+ }
3087+ }
3088+
3089+ impl Display for Error {
3090+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
3091+ match self {
3092+ Error::Message(message) => write!(f, "{}", message),
3093+ Error::NotFound(message) => write!(f, "{}", message),
3094+ }
3095+ }
3096+ }
3097 diff --git a/ayllu/src/web2/extractors/config.rs b/ayllu/src/web2/extractors/config.rs
3098new file mode 100644
3099index 0000000..9af222e
3100--- /dev/null
3101+++ b/ayllu/src/web2/extractors/config.rs
3102 @@ -0,0 +1,34 @@
3103+ use axum::{
3104+ async_trait,
3105+ extract::FromRequestParts,
3106+ http::request::Parts,
3107+ response::{IntoResponse, Response},
3108+ };
3109+
3110+ use axum_extra::extract::CookieJar;
3111+
3112+ use crate::web2::config::Config;
3113+
3114+ pub struct ConfigReader(pub Config);
3115+
3116+ #[async_trait]
3117+ impl<S> FromRequestParts<S> for ConfigReader
3118+ where
3119+ S: Send + Sync,
3120+ {
3121+ type Rejection = Response;
3122+
3123+ async fn from_request_parts(parts: &mut Parts, state: &S) -> Result<Self, Self::Rejection> {
3124+ let cookies = CookieJar::from_request_parts(parts, state)
3125+ .await
3126+ .map_err(|err| err.into_response())?;
3127+ Ok(ConfigReader(Config {
3128+ theme: cookies
3129+ .get("theme")
3130+ .map(|cookie| cookie.value().to_string()),
3131+ items_per_page: cookies.get("items-per-page").map_or(100, |cookie| {
3132+ cookie.value().to_string().parse::<usize>().unwrap_or(100)
3133+ }),
3134+ }))
3135+ }
3136+ }
3137 diff --git a/ayllu/src/web2/extractors/mod.rs b/ayllu/src/web2/extractors/mod.rs
3138new file mode 100644
3139index 0000000..ef68c36
3140--- /dev/null
3141+++ b/ayllu/src/web2/extractors/mod.rs
3142 @@ -0,0 +1 @@
3143+ pub mod config;
3144 diff --git a/ayllu/src/web2/highlight.rs b/ayllu/src/web2/highlight.rs
3145new file mode 100644
3146index 0000000..462c804
3147--- /dev/null
3148+++ b/ayllu/src/web2/highlight.rs
3149 @@ -0,0 +1,352 @@
3150+ use std::collections::HashMap;
3151+
3152+ use std::fs;
3153+ use std::io::{Cursor, Error, Write};
3154+ use std::mem;
3155+ use std::path::Path;
3156+ use std::sync::RwLock;
3157+
3158+ use comrak::adapters::SyntaxHighlighterAdapter;
3159+ use lazy_static::lazy_static;
3160+ use log::debug;
3161+ use tera::escape_html;
3162+ use tree_sitter::Language;
3163+ use tree_sitter_highlight::{HighlightConfiguration, Highlighter as TSHighlighter, HtmlRenderer};
3164+
3165+ use crate::config::TreeSitterParser;
3166+ use crate::languages::{Hint, LANGUAGE_TABLE};
3167+
3168+ lazy_static! {
3169+ // global containing all language parsers and syntax highlighter definitions
3170+ // this needs to be initialized one time at startup
3171+ static ref LANGUAGES: RwLock<HashMap<Hint, Language>> = RwLock::new(HashMap::new());
3172+ // highlighter queries
3173+ static ref HIGHLIGHTS: RwLock<HashMap<Hint, String>> = RwLock::new(HashMap::new());
3174+ // local queries
3175+ static ref LOCALS: RwLock<HashMap<Hint, String>> = RwLock::new(HashMap::new());
3176+ // injection queries
3177+ static ref INJECTIONS: RwLock<HashMap<Hint, String>> = RwLock::new(HashMap::new());
3178+ }
3179+
3180+ unsafe fn load_language(path: &str, hint: &Hint) -> Language {
3181+ let lib = libloading::Library::new(path).unwrap();
3182+ let method_name = format!("tree_sitter_{}", hint.0.to_lowercase());
3183+ // NOTE: maybe, probably? some security auditing that needs to happen here?
3184+ debug!("attempting to load method: {}", method_name);
3185+ let func: libloading::Symbol<unsafe extern "C" fn() -> Language> =
3186+ lib.get(method_name.as_bytes()).unwrap();
3187+ let language = func();
3188+ mem::forget(lib);
3189+ language
3190+ }
3191+
3192+ pub struct Loader {
3193+ // typically /usr/lib
3194+ lib_path: String,
3195+ // typically /usr/share/tree-sitter
3196+ query_path: String,
3197+ dynamic_ok: bool,
3198+ extra_queries_path: Option<String>,
3199+ extra_parsers: Vec<TreeSitterParser>,
3200+ }
3201+
3202+ impl Loader {
3203+ pub fn new(lib_path: &str, query_path: &str) -> Self {
3204+ Loader {
3205+ lib_path: lib_path.to_string(),
3206+ query_path: query_path.to_string(),
3207+ dynamic_ok: false,
3208+ extra_queries_path: None,
3209+ extra_parsers: Vec::new(),
3210+ }
3211+ }
3212+
3213+ pub fn dynamic(mut self, value: bool) -> Self {
3214+ self.dynamic_ok = value;
3215+ self
3216+ }
3217+
3218+ pub fn extra_queries(mut self, path: &str) -> Self {
3219+ self.extra_queries_path = Some(path.to_string());
3220+ self
3221+ }
3222+
3223+ pub fn parsers(mut self, parsers: Vec<TreeSitterParser>) -> Self {
3224+ self.extra_parsers = parsers.clone();
3225+ self
3226+ }
3227+
3228+ fn try_query_load(&self, path: &str, hint: &Hint) -> Result<(), Error> {
3229+ let query_base_path = Path::new(path).join(hint.0.to_string().to_lowercase());
3230+ if let Ok(queries) = fs::read_dir(query_base_path) {
3231+ for query in queries {
3232+ let query = query?;
3233+ match query.file_name().into_string().unwrap().as_str() {
3234+ "highlights.scm" => {
3235+ let highlight_scm = fs::read_to_string(query.path())?;
3236+ HIGHLIGHTS
3237+ .write()
3238+ .unwrap()
3239+ .insert(hint.clone(), highlight_scm);
3240+ debug!("loaded [{:?}] {}", hint, query.path().display());
3241+ }
3242+ "locals.scm" => {
3243+ let locals_scm = fs::read_to_string(query.path())?;
3244+ LOCALS.write().unwrap().insert(hint.clone(), locals_scm);
3245+ debug!("loaded [{:?}] {}", hint, query.path().display());
3246+ }
3247+ "injections.scm" => {
3248+ let injections_scm = fs::read_to_string(query.path())?;
3249+ INJECTIONS
3250+ .write()
3251+ .unwrap()
3252+ .insert(hint.clone(), injections_scm);
3253+ debug!("loaded [{:?}] {}", hint, query.path().display());
3254+ }
3255+ name => {
3256+ debug!("ignoring query file: {}", name)
3257+ }
3258+ }
3259+ }
3260+ }
3261+ Ok(())
3262+ }
3263+
3264+ pub fn load(&self) -> Result<(), Error> {
3265+ let modules = fs::read_dir(Path::new(&self.lib_path))?;
3266+ for module in modules {
3267+ let fp = module?;
3268+ let file_name = fp.file_name().into_string().unwrap();
3269+ if file_name.starts_with("libtree-sitter-") && file_name.ends_with(".so") {
3270+ let language_name = file_name.replace("libtree-sitter-", "").replace(".so", "");
3271+
3272+ let hint = Hint(language_name);
3273+ let language = unsafe { load_language(fp.path().to_str().unwrap(), &hint) };
3274+ LANGUAGES.write().unwrap().insert(hint.clone(), language);
3275+ debug!(
3276+ "loaded tree-sitter shared module: [{:?}] {}",
3277+ hint,
3278+ fp.path().display()
3279+ );
3280+ self.try_query_load(self.query_path.as_str(), &hint)?;
3281+ // override any base queries with additional queries
3282+ self.extra_queries_path
3283+ .as_ref()
3284+ .map(|path| self.try_query_load(path.as_str(), &hint))
3285+ .transpose()?;
3286+ }
3287+ }
3288+ for parser in &self.extra_parsers {
3289+ let hint = Hint(parser.language.clone());
3290+ let language = unsafe { load_language(&parser.shared_object, &hint) };
3291+ LANGUAGES.write().unwrap().insert(hint.clone(), language);
3292+ parser
3293+ .highlight_query
3294+ .as_ref()
3295+ .map(|path| {
3296+ let highlight_scm = fs::read_to_string(path)?;
3297+ HIGHLIGHTS
3298+ .write()
3299+ .unwrap()
3300+ .insert(hint.clone(), highlight_scm);
3301+ Ok::<(), Error>(())
3302+ })
3303+ .transpose()?;
3304+ parser
3305+ .locals_query
3306+ .as_ref()
3307+ .map(|path| {
3308+ let locals_scm = fs::read_to_string(path)?;
3309+ LOCALS.write().unwrap().insert(hint.clone(), locals_scm);
3310+ Ok::<(), Error>(())
3311+ })
3312+ .transpose()?;
3313+ parser
3314+ .injections_query
3315+ .as_ref()
3316+ .map(|path| {
3317+ let injections_scm = fs::read_to_string(path)?;
3318+ INJECTIONS
3319+ .write()
3320+ .unwrap()
3321+ .insert(hint.clone(), injections_scm);
3322+ Ok::<(), Error>(())
3323+ })
3324+ .transpose()?;
3325+ }
3326+ Ok(())
3327+ }
3328+ }
3329+
3330+ fn render_lines(lines: Vec<&str>, show_line_numbers: bool) -> String {
3331+ let buf = Vec::new();
3332+ let mut file = Cursor::new(buf);
3333+ write!(&mut file, "<table>").unwrap();
3334+ for (i, line) in lines.into_iter().enumerate() {
3335+ if show_line_numbers {
3336+ write!(&mut file, "<tr><td class=line-number>{:?}</td>", i + 1).unwrap();
3337+ }
3338+ write!(&mut file, "<td class=line>{}</td></tr>", line).unwrap();
3339+ }
3340+ write!(&mut file, "</table>").unwrap();
3341+ let bytes = file.into_inner();
3342+ String::from_utf8(bytes).unwrap()
3343+ }
3344+
3345+ #[derive(Clone, Debug)]
3346+ pub struct Highlighter {
3347+ names: Vec<String>,
3348+ classes: Vec<String>,
3349+ }
3350+
3351+ impl Highlighter {
3352+ pub fn new(names: &[String]) -> Self {
3353+ let mut classes: Vec<String> = Vec::new();
3354+
3355+ for name in names.iter() {
3356+ classes.push(format!("class=\"ts_{}\"", name));
3357+ }
3358+ Self {
3359+ names: names.to_vec(),
3360+ classes,
3361+ }
3362+ }
3363+
3364+ pub fn highlight(
3365+ &self,
3366+ code: &str,
3367+ filepath: Option<&str>,
3368+ alias: Option<&str>,
3369+ hint: Option<Hint>,
3370+ show_line_numbers: bool,
3371+ ) -> (Option<Hint>, String) {
3372+ let buf = Vec::new();
3373+ let mut file = Cursor::new(buf);
3374+ write!(&mut file, "<table>").unwrap();
3375+
3376+ let hint = match hint {
3377+ Some(hint) => Some(hint),
3378+ None => LANGUAGE_TABLE.guess(code, alias, filepath),
3379+ };
3380+
3381+ debug!("language hint: {:?}", hint);
3382+
3383+ match hint {
3384+ Some(hint) => match (
3385+ LANGUAGES.read().unwrap().get(&hint),
3386+ HIGHLIGHTS.read().unwrap().get(&hint),
3387+ ) {
3388+ (Some(language), Some(syntax)) => {
3389+ debug!("painting syntax for language: {:?}", hint);
3390+ let mut highlighter = TSHighlighter::new();
3391+ let injections = INJECTIONS
3392+ .read()
3393+ .unwrap()
3394+ .get(&hint)
3395+ .cloned()
3396+ .unwrap_or_default();
3397+ let locals = LOCALS
3398+ .read()
3399+ .unwrap()
3400+ .get(&hint)
3401+ .cloned()
3402+ .unwrap_or_default();
3403+ let mut config =
3404+ HighlightConfiguration::new(*language, syntax, &injections, &locals)
3405+ .unwrap();
3406+
3407+ config.configure(&self.names);
3408+
3409+ let code = code.as_bytes();
3410+
3411+ let events = highlighter
3412+ .highlight(&config, code, None, |_| None)
3413+ .unwrap();
3414+
3415+ let mut renderer = HtmlRenderer::new();
3416+
3417+ renderer
3418+ .render(events, code, &move |highlight| {
3419+ let ret = match self.classes.get(highlight.0) {
3420+ Some(name) => name.as_bytes(),
3421+ None => "".as_bytes(),
3422+ };
3423+ ret
3424+ })
3425+ .unwrap();
3426+
3427+ (
3428+ Some(hint.clone()),
3429+ render_lines(renderer.lines().collect(), show_line_numbers),
3430+ )
3431+ }
3432+ _ => {
3433+ debug!("cannot paint syntax for language: {:?}", hint);
3434+ (
3435+ None,
3436+ render_lines(escape_html(code).lines().collect(), show_line_numbers),
3437+ )
3438+ }
3439+ },
3440+ None => {
3441+ debug!("cannot determine language");
3442+ (
3443+ None,
3444+ render_lines(code.to_string().lines().collect(), show_line_numbers),
3445+ )
3446+ }
3447+ }
3448+ }
3449+
3450+ pub fn highlight_vec(
3451+ &self,
3452+ content: Vec<u8>,
3453+ filepath: Option<&str>,
3454+ alias: Option<&str>,
3455+ hint: Option<Hint>,
3456+ show_line_numbers: bool,
3457+ ) -> (Option<Hint>, String) {
3458+ let content = String::from_utf8(content).unwrap();
3459+ self.highlight(content.as_str(), filepath, alias, hint, show_line_numbers)
3460+ }
3461+ }
3462+
3463+ #[derive(Debug, Clone)]
3464+ pub struct TreeSitterAdapter {
3465+ highlighter: Highlighter,
3466+ }
3467+
3468+ impl TreeSitterAdapter {
3469+ pub fn new(highlighter: Highlighter) -> Self {
3470+ TreeSitterAdapter { highlighter }
3471+ }
3472+ }
3473+
3474+ impl SyntaxHighlighterAdapter for TreeSitterAdapter {
3475+ fn write_highlighted(
3476+ &self,
3477+ output: &mut dyn std::io::Write,
3478+ lang: Option<&str>,
3479+ code: &str,
3480+ ) -> std::io::Result<()> {
3481+ let (_, highlighted) = self.highlighter.highlight(code, None, lang, None, false);
3482+ output.write_all(highlighted.as_bytes()).unwrap();
3483+ Ok(())
3484+ }
3485+
3486+ fn write_pre_tag(
3487+ &self,
3488+ _: &mut dyn std::io::Write,
3489+ _: std::collections::HashMap<String, String>,
3490+ ) -> std::io::Result<()> {
3491+ Ok(())
3492+ }
3493+
3494+ fn write_code_tag(
3495+ &self,
3496+ _: &mut dyn std::io::Write,
3497+ _: std::collections::HashMap<String, String>,
3498+ ) -> std::io::Result<()> {
3499+ Ok(())
3500+ }
3501+ }
3502 diff --git a/ayllu/src/web2/middleware/error.rs b/ayllu/src/web2/middleware/error.rs
3503new file mode 100644
3504index 0000000..956589e
3505--- /dev/null
3506+++ b/ayllu/src/web2/middleware/error.rs
3507 @@ -0,0 +1,55 @@
3508+ use std::sync::Arc;
3509+
3510+ use axum::{
3511+ extract,
3512+ http::StatusCode,
3513+ middleware::Next,
3514+ response::{Html, IntoResponse, Response},
3515+ };
3516+ use tera::Tera;
3517+
3518+ use crate::config::Config;
3519+ use crate::web2::error::Error;
3520+ use crate::web2::extractors::config::ConfigReader;
3521+ use crate::web2::terautil::{Loader, Options};
3522+
3523+ pub type State = Arc<(Config, Vec<(String, Tera)>)>;
3524+
3525+ pub async fn middleware(
3526+ extract::State(state): extract::State<State>,
3527+ ConfigReader(client_config): ConfigReader,
3528+ req: extract::Request,
3529+ next: Next,
3530+ ) -> Response {
3531+ let response = next.run(req).await;
3532+ if let Some(error) = response.extensions().get::<Error>() {
3533+ let status_code = match error {
3534+ Error::Message(_) => StatusCode::INTERNAL_SERVER_ERROR,
3535+ Error::NotFound(_) => StatusCode::NOT_FOUND,
3536+ };
3537+ // reload the theme since the middleware may not have ran yet
3538+ let loader = Loader {
3539+ templates: state.1.clone(),
3540+ default_theme: state.0.web.default_theme.clone(),
3541+ };
3542+ let (template, mut ctx) = loader.load(
3543+ Options {
3544+ origin: state.0.origin.clone(),
3545+ site_name: state.0.site_name.clone(),
3546+ ..Default::default()
3547+ },
3548+ client_config.theme,
3549+ );
3550+ ctx.insert("status_code", status_code.as_str());
3551+ ctx.insert("error_message", &error.to_string());
3552+ let template_str = if status_code == StatusCode::NOT_FOUND {
3553+ log::warn!("Not Found: {}", error.to_string());
3554+ template.render("404.html", &ctx).unwrap()
3555+ } else {
3556+ log::error!("Error: {}", error.to_string());
3557+ template.render("5xx.html", &ctx).unwrap()
3558+ };
3559+ return Html::from(template_str).into_response();
3560+ }
3561+ response
3562+ }
3563 diff --git a/ayllu/src/web2/middleware/mod.rs b/ayllu/src/web2/middleware/mod.rs
3564new file mode 100644
3565index 0000000..59371b3
3566--- /dev/null
3567+++ b/ayllu/src/web2/middleware/mod.rs
3568 @@ -0,0 +1,5 @@
3569+ pub mod error;
3570+ pub mod repository;
3571+ pub mod rpc_initiator;
3572+ pub mod sites;
3573+ pub mod template;
3574 diff --git a/ayllu/src/web2/middleware/repository.rs b/ayllu/src/web2/middleware/repository.rs
3575new file mode 100644
3576index 0000000..3c1c94a
3577--- /dev/null
3578+++ b/ayllu/src/web2/middleware/repository.rs
3579 @@ -0,0 +1,129 @@
3580+ use std::path::PathBuf;
3581+ use std::time::SystemTime;
3582+
3583+ use axum::{
3584+ extract::{Path, Request, State},
3585+ middleware::Next,
3586+ response::{IntoResponse, Response},
3587+ };
3588+
3589+ use serde::Deserialize;
3590+
3591+ use crate::config::Config;
3592+ use crate::web2::error::Error;
3593+ use ayllu_git::{Commit, Config as GitConfig, Wrapper as Repository};
3594+
3595+ #[derive(Deserialize)]
3596+ pub struct CommonParams {
3597+ pub collection: String,
3598+ pub name: String,
3599+ pub commitish: Option<String>,
3600+ pub file_path: Option<String>,
3601+ }
3602+
3603+ /// Preamble contains repository information useful for all "repository level"
3604+ /// pages, basically it's purpose is to reduce boilerplate.
3605+ #[derive(Clone)]
3606+ pub struct Preamble {
3607+ pub start_time: SystemTime,
3608+ pub repo_path: PathBuf,
3609+ pub is_empty: bool,
3610+ pub repo_name: String,
3611+ pub collection_name: String,
3612+ pub refname: String,
3613+ pub config: GitConfig,
3614+ pub file_path: Option<PathBuf>,
3615+ pub latest_commit: Option<Commit>,
3616+ pub latest_commit_id: Option<String>,
3617+ }
3618+
3619+ impl Preamble {
3620+ pub fn new(
3621+ system_config: &Config,
3622+ collection_name: String,
3623+ repo_name: String,
3624+ commitish: Option<String>,
3625+ file_path: Option<String>,
3626+ ) -> Result<Self, Error> {
3627+ let start_time = SystemTime::now();
3628+ let repo_path = match system_config
3629+ .collections
3630+ .iter()
3631+ .find(|collection| collection.name == collection_name)
3632+ {
3633+ Some(collection) => PathBuf::from(format!("{}/{}", collection.path, repo_name)),
3634+ None => {
3635+ return Err(Error::NotFound(String::from("collection not found")));
3636+ }
3637+ };
3638+ let repository = Repository::new(&repo_path)?;
3639+ let is_empty = repository.is_empty()?;
3640+ let config = repository.config()?;
3641+ let (refname, latest_commit) = if is_empty {
3642+ (String::new(), None)
3643+ } else if let Some(commitish) = commitish {
3644+ let commit = repository.resolve_commit(Some(commitish.as_str()))?;
3645+ (commitish.to_string(), commit)
3646+ } else if let Some(default_branch) = config.default_branch.clone() {
3647+ let commit = repository.resolve_commit(Some(default_branch.as_str()))?;
3648+ (default_branch.clone(), commit)
3649+ } else if let Some(system_default_branch) = &system_config.default_branch {
3650+ let commit = repository.resolve_commit(Some(system_default_branch.as_str()))?;
3651+ (system_default_branch.clone(), commit)
3652+ } else {
3653+ let default_branch = repository.default_ref()?;
3654+ let commit = repository.resolve_commit(Some(default_branch.as_str()))?;
3655+ (default_branch.clone(), commit)
3656+ };
3657+ Ok(Preamble {
3658+ start_time,
3659+ repo_path,
3660+ is_empty,
3661+ repo_name,
3662+ collection_name,
3663+ refname,
3664+ config,
3665+ file_path: file_path.map(PathBuf::from),
3666+ latest_commit: latest_commit.clone(),
3667+ latest_commit_id: latest_commit.map(|commit| commit.id),
3668+ })
3669+ }
3670+
3671+ pub fn repo_path_string(&self) -> String {
3672+ self.repo_path.to_str().unwrap().to_string()
3673+ }
3674+
3675+ pub fn file_path_string(&self) -> String {
3676+ self.file_path
3677+ .clone()
3678+ .map(|fp| fp.to_str().unwrap().to_string())
3679+ .unwrap_or_default()
3680+ }
3681+
3682+ pub fn file_name(&self) -> String {
3683+ self.file_path
3684+ .clone()
3685+ .map_or(String::new(), |fp| fp.to_str().unwrap().to_string())
3686+ }
3687+ }
3688+
3689+ pub async fn middleware(
3690+ State(config): State<Config>,
3691+ Path(CommonParams {
3692+ collection,
3693+ name,
3694+ commitish,
3695+ file_path,
3696+ }): Path<CommonParams>,
3697+ mut req: Request,
3698+ next: Next,
3699+ ) -> Response {
3700+ let preamble = Preamble::new(&config, collection, name, commitish, file_path);
3701+ match preamble {
3702+ Ok(preamble) => {
3703+ req.extensions_mut().insert(preamble);
3704+ }
3705+ Err(err) => return err.into_response(),
3706+ }
3707+ next.run(req).await
3708+ }
3709 diff --git a/ayllu/src/web2/middleware/rpc_initiator.rs b/ayllu/src/web2/middleware/rpc_initiator.rs
3710new file mode 100644
3711index 0000000..cdc455c
3712--- /dev/null
3713+++ b/ayllu/src/web2/middleware/rpc_initiator.rs
3714 @@ -0,0 +1,121 @@
3715+ use std::sync::Arc;
3716+
3717+ use crate::web2::terautil::{Loader, Options};
3718+ use axum::{
3719+ body::Body,
3720+ extract,
3721+ http::{header::CONTENT_TYPE, StatusCode},
3722+ middleware::Next,
3723+ response::Response,
3724+ };
3725+ use mime::TEXT_HTML_UTF_8;
3726+ use tera::Tera;
3727+ use tracing::log;
3728+
3729+ use crate::config::Config;
3730+ use crate::web2::config::Config as ClientConfig;
3731+ use crate::web2::extractors::config::ConfigReader;
3732+ use ayllu_rpc::Client;
3733+
3734+ pub type State = Arc<(Config, Vec<(String, Tera)>, &'static [Kind])>;
3735+
3736+ pub enum Kind {
3737+ Mail,
3738+ Xmpp,
3739+ }
3740+
3741+ #[derive(Clone, Default)]
3742+ pub struct Initiator {
3743+ mail: Option<Client>,
3744+ xmpp: Option<Client>,
3745+ }
3746+
3747+ impl Initiator {
3748+ pub fn client(self, kind: Kind) -> Option<Client> {
3749+ match kind {
3750+ Kind::Mail => self.mail,
3751+ Kind::Xmpp => self.xmpp,
3752+ }
3753+ }
3754+ }
3755+
3756+ fn plugin_not_enabled(
3757+ plugin_name: &str,
3758+ cfg: &Config,
3759+ client_config: ClientConfig,
3760+ templates: &[(String, Tera)],
3761+ ) -> Response {
3762+ log::warn!("returning error due to missing plugin: {}", plugin_name);
3763+ // reload the theme since the middleware may not have ran yet
3764+ let loader = Loader {
3765+ templates: templates.to_vec(),
3766+ default_theme: cfg.web.default_theme.clone(),
3767+ };
3768+ let (template, mut ctx) = loader.load(
3769+ Options {
3770+ origin: cfg.origin.clone(),
3771+ site_name: cfg.site_name.clone(),
3772+ ..Default::default()
3773+ },
3774+ client_config.theme,
3775+ );
3776+ ctx.insert("status_code", StatusCode::NOT_IMPLEMENTED.as_str());
3777+ ctx.insert(
3778+ "error_message",
3779+ &format!("Plugin: {} is not enabled", plugin_name),
3780+ );
3781+ let template_str = template.render("5xx.html", &ctx).unwrap();
3782+ Response::builder()
3783+ .header(CONTENT_TYPE, TEXT_HTML_UTF_8.as_ref())
3784+ .status(StatusCode::NOT_IMPLEMENTED)
3785+ .body(Body::new(template_str))
3786+ .unwrap()
3787+ }
3788+
3789+ /// configure an optinoal plugin for use in a handler
3790+ pub async fn optional(
3791+ extract::State(cfg): extract::State<Arc<Config>>,
3792+ mut req: extract::Request,
3793+ next: Next,
3794+ ) -> Response {
3795+ req.extensions_mut().insert(Initiator {
3796+ mail: cfg
3797+ .mail
3798+ .as_ref()
3799+ .map(|mail_cfg| Client::new(&mail_cfg.socket_path)),
3800+ xmpp: cfg
3801+ .xmpp
3802+ .as_ref()
3803+ .map(|xmpp_cfg| Client::new(&xmpp_cfg.socket_path)),
3804+ });
3805+ next.run(req).await
3806+ }
3807+
3808+ /// configure a required plugin for use in a handler, if it is not configured
3809+ /// an error page will be returned from the handler
3810+ pub async fn required(
3811+ extract::State(state): extract::State<State>,
3812+ ConfigReader(client_config): ConfigReader,
3813+ mut req: extract::Request,
3814+ next: Next,
3815+ ) -> Response {
3816+ let mut initiator = Initiator::default();
3817+ for kind in state.2.iter() {
3818+ match kind {
3819+ Kind::Mail => match &state.0.mail {
3820+ Some(mail_cfg) => {
3821+ initiator.mail = Some(Client::new(&mail_cfg.socket_path));
3822+ }
3823+ None => return plugin_not_enabled("ayllu-mail", &state.0, client_config, &state.1),
3824+ },
3825+ Kind::Xmpp => match &state.0.xmpp {
3826+ Some(xmpp_cfg) => {
3827+ initiator.xmpp = Some(Client::new(&xmpp_cfg.socket_path));
3828+ }
3829+ None => return plugin_not_enabled("ayllu-xmpp", &state.0, client_config, &state.1),
3830+ },
3831+ }
3832+ }
3833+ req.extensions_mut().insert(initiator);
3834+ next.run(req).await
3835+ }
3836 diff --git a/ayllu/src/web2/middleware/sites.rs b/ayllu/src/web2/middleware/sites.rs
3837new file mode 100644
3838index 0000000..c711298
3839--- /dev/null
3840+++ b/ayllu/src/web2/middleware/sites.rs
3841 @@ -0,0 +1,153 @@
3842+ use std::fs::read_dir;
3843+ use std::path::{Path, PathBuf};
3844+
3845+ use axum::{
3846+ body::Body,
3847+ extract,
3848+ http::{header, StatusCode},
3849+ middleware::Next,
3850+ response::{IntoResponse, Response},
3851+ };
3852+
3853+ use mime_guess::from_path;
3854+
3855+ use crate::config::{Collection, Config};
3856+ use crate::web2::error::Error;
3857+ use crate::web2::util;
3858+ use ayllu_git::Wrapper as Repository;
3859+
3860+ pub type State = extract::State<(Config, Vec<(String, (String, String))>)>;
3861+ pub type Sites = Vec<(String, (String, String))>;
3862+
3863+ // array of all the repositories in each collection
3864+ fn repositories(collections: Vec<Collection>) -> Result<Vec<PathBuf>, Error> {
3865+ let mut paths: Vec<PathBuf> = Vec::new();
3866+ for collection in collections.iter() {
3867+ let collection_path = Path::new(&collection.path);
3868+ let entries = read_dir(collection_path)?;
3869+ for entry in entries {
3870+ let entry = entry?;
3871+ if entry.file_type()?.is_dir() {
3872+ paths.push(entry.path());
3873+ }
3874+ }
3875+ }
3876+ Ok(paths)
3877+ }
3878+
3879+ pub fn sites(cfg: Config) -> Result<Sites, Error> {
3880+ let mut sites: Vec<(String, (String, String))> = Vec::new();
3881+ for path in repositories(cfg.collections.clone())? {
3882+ let repository = Repository::new(path.as_path())?;
3883+ let repo_config = repository.config()?;
3884+ if repo_config.sites.header.is_some() {
3885+ let domain = repo_config.sites.header.unwrap();
3886+ match domain.split_once('=') {
3887+ Some((key, value)) => {
3888+ let repo_path = path.to_str().unwrap().to_string();
3889+ sites.push((repo_path.to_string(), (key.to_string(), value.to_string())));
3890+ log::info!("serving static site {} -> {}", domain, repo_path);
3891+ }
3892+ None => panic!("bad key=value header"),
3893+ };
3894+ }
3895+ }
3896+ Ok(sites)
3897+ }
3898+
3899+ pub async fn middleware(
3900+ extract::State((cfg, sites)): State,
3901+ req: extract::Request,
3902+ next: Next,
3903+ ) -> Result<Response, Error> {
3904+ if !cfg.sites.enabled {
3905+ return Ok(next.run(req).await);
3906+ }
3907+
3908+ let mut repo_path: Option<String> = None;
3909+ let headers = req.headers();
3910+
3911+ let hostname = headers
3912+ .get("host")
3913+ .map(|header| header.to_str().unwrap_or(""));
3914+
3915+ for (other_repo_path, (key, value)) in sites {
3916+ log::debug!("checking site {} {}={}", other_repo_path, key, value);
3917+ if let Some(header_value) = headers.get(key.as_str()) {
3918+ if header_value.to_str().unwrap() == value {
3919+ log::debug!("sites header match: {}={}", key, value);
3920+ repo_path = Some(other_repo_path.to_string());
3921+ break;
3922+ // return self.serve(req, repo_path).await;
3923+ }
3924+ };
3925+ }
3926+
3927+ match repo_path {
3928+ Some(repo_path) => {
3929+ let repository = Repository::new(Path::new(&repo_path))?;
3930+ let config = repository.config()?;
3931+ let path = util::select_path(req.uri(), Some(0), None).map_or_else(
3932+ || PathBuf::from("index.html"),
3933+ |path| {
3934+ path.strip_prefix("/")
3935+ .unwrap_or(path.as_path())
3936+ .to_path_buf()
3937+ },
3938+ );
3939+ let blob_path = config
3940+ .sites
3941+ .content
3942+ .map_or(PathBuf::from("public").join(path.clone()), |base| {
3943+ PathBuf::from(base).join(path.clone())
3944+ });
3945+ // assuming path is GET /fuu
3946+ // try:
3947+ // GET /fuu
3948+ // GET /fuu/index.html
3949+ // GET /fuu/index.htm
3950+ // GET /fuu.html
3951+ // GET /fuu.htm
3952+ let mut paths: Vec<PathBuf> = vec![
3953+ blob_path.clone(),
3954+ blob_path.clone().join("index.html"),
3955+ blob_path.clone().join("index.htm"),
3956+ ];
3957+ if blob_path.extension().is_none() {
3958+ paths.push(blob_path.clone().with_extension("html"));
3959+ paths.push(blob_path.clone().with_extension("htm"));
3960+ };
3961+ log::debug!("trying paths: {:?}", paths);
3962+ for path in paths {
3963+ log::debug!("trying path {:?}", path);
3964+ if let Some(blob) = repository.blob(path.as_path(), config.sites.branch.clone())? {
3965+ log::debug!("got blob from path {:?}", path);
3966+ let mime_type = from_path(path.to_str().unwrap()).first_or_octet_stream();
3967+ let response = Response::builder()
3968+ .header(header::CONTENT_TYPE, mime_type.to_string())
3969+ .body(Body::from(blob.content))
3970+ .unwrap();
3971+ return Ok(response);
3972+ };
3973+ }
3974+
3975+ // special case if running a static site in-front of the forge
3976+ // where requests can fall through to the backend.
3977+ if hostname.is_some_and(|name| name == cfg.domain.unwrap()) {
3978+ let response = next.run(req).await;
3979+ Ok(response)
3980+ } else {
3981+ // TODO: allow custom 404 / error pages on static sites?
3982+ Ok((
3983+ StatusCode::NOT_FOUND,
3984+ format!("cannot find resource: {}", blob_path.to_str().unwrap()),
3985+ )
3986+ .into_response())
3987+ }
3988+ }
3989+ None => {
3990+ let response = next.run(req).await;
3991+ Ok(response)
3992+ }
3993+ }
3994+ }
3995 diff --git a/ayllu/src/web2/middleware/template.rs b/ayllu/src/web2/middleware/template.rs
3996new file mode 100644
3997index 0000000..58f93cc
3998--- /dev/null
3999+++ b/ayllu/src/web2/middleware/template.rs
4000 @@ -0,0 +1,47 @@
4001+ use serde::Deserialize;
4002+ use std::sync::Arc;
4003+ use tera::{Context as TeraContext, Tera};
4004+
4005+ use axum::{extract, middleware::Next, response::Response};
4006+
4007+ use crate::config::Config;
4008+ use crate::web2::extractors::config::ConfigReader;
4009+ use crate::web2::terautil::{Loader, Options};
4010+
4011+ pub type Template = (Tera, TeraContext);
4012+ pub type State = extract::State<Arc<(Config, Vec<(String, Tera)>)>>;
4013+
4014+ #[derive(Deserialize)]
4015+ pub struct CommonParams {
4016+ pub collection: Option<String>,
4017+ pub name: Option<String>,
4018+ }
4019+
4020+ pub async fn middleware(
4021+ extract::State(state): State,
4022+ ConfigReader(config): ConfigReader,
4023+ extract::Path(CommonParams { collection, name }): extract::Path<CommonParams>,
4024+ mut req: extract::Request,
4025+ next: Next,
4026+ ) -> Response {
4027+ let loader = Loader {
4028+ templates: state.1.clone(),
4029+ default_theme: state.0.web.default_theme.clone(),
4030+ };
4031+ let (template, ctx) = loader.load(
4032+ Options {
4033+ origin: state.0.origin.clone(),
4034+ collection,
4035+ name,
4036+ site_name: state.0.site_name.clone(),
4037+ url: req.uri().to_string(),
4038+ path: req.uri().path().to_string(),
4039+ subpath_mode: state.0.subpath_mode,
4040+ ..Options::default()
4041+ },
4042+ config.theme,
4043+ );
4044+ req.extensions_mut().insert((template, ctx));
4045+
4046+ next.run(req).await
4047+ }
4048 diff --git a/ayllu/src/web2/mod.rs b/ayllu/src/web2/mod.rs
4049new file mode 100644
4050index 0000000..3d0d739
4051--- /dev/null
4052+++ b/ayllu/src/web2/mod.rs
4053 @@ -0,0 +1,19 @@
4054+ mod charts;
4055+ mod config;
4056+ mod error;
4057+ mod extractors;
4058+ mod highlight;
4059+ mod middleware;
4060+ mod navigation;
4061+ mod routes;
4062+ mod server;
4063+ mod terautil;
4064+ mod util;
4065+
4066+ pub mod runtime {
4067+ use crate::config::Config;
4068+ use crate::web2::server;
4069+ pub async fn serve(cfg: &Config) {
4070+ server::serve(cfg).await.unwrap();
4071+ }
4072+ }
4073 diff --git a/ayllu/src/web2/navigation.rs b/ayllu/src/web2/navigation.rs
4074new file mode 100644
4075index 0000000..ff3e58e
4076--- /dev/null
4077+++ b/ayllu/src/web2/navigation.rs
4078 @@ -0,0 +1,125 @@
4079+ pub type Items = Vec<(String, String, bool)>;
4080+
4081+ pub fn global(current_page: &str, mail_visible: bool) -> Items {
4082+ let mut nav: Items = vec![
4083+ (
4084+ String::from("about"),
4085+ String::from("/about"),
4086+ current_page == "about",
4087+ ),
4088+ (
4089+ String::from("config"),
4090+ String::from("/config"),
4091+ current_page == "config",
4092+ ),
4093+ ];
4094+ if mail_visible {
4095+ nav.push((
4096+ String::from("mail"),
4097+ String::from("/mail"),
4098+ current_page == "mail",
4099+ ))
4100+ }
4101+ nav
4102+ }
4103+
4104+ pub fn primary(current_page: &str, collection: &str, name: &str) -> Items {
4105+ vec![
4106+ (
4107+ String::from("authors"),
4108+ format!("/{}/{}/authors", collection, name,),
4109+ current_page == "authors",
4110+ ),
4111+ (
4112+ String::from("charts"),
4113+ format!("/{}/{}/charts", collection, name),
4114+ current_page == "charts",
4115+ ),
4116+ (
4117+ String::from("log"),
4118+ format!("/{}/{}/log", collection, name),
4119+ current_page == "log",
4120+ ),
4121+ (
4122+ String::from("project"),
4123+ format!("/{}/{}", collection, name),
4124+ current_page == "project",
4125+ ),
4126+ (
4127+ String::from("refs"),
4128+ format!("/{}/{}/refs", collection, name),
4129+ current_page == "refs",
4130+ ),
4131+ ]
4132+ }
4133+
4134+ pub fn subnav(
4135+ current_page: &str,
4136+ collection: &str,
4137+ name: &str,
4138+ file_path: &str,
4139+ ref_name: &str,
4140+ commit_id: &str,
4141+ ) -> Items {
4142+ vec![
4143+ (
4144+ String::from("blob"),
4145+ format!("/{}/{}/blob/{}/{}", collection, name, ref_name, file_path,),
4146+ current_page == "blob",
4147+ ),
4148+ (
4149+ String::from("blame"),
4150+ format!("/{}/{}/blame/{}/{}", collection, name, ref_name, file_path),
4151+ current_page == "blame",
4152+ ),
4153+ (
4154+ String::from("log"),
4155+ format!("/{}/{}/log/{}/{}", collection, name, ref_name, file_path,),
4156+ current_page == "log",
4157+ ),
4158+ (
4159+ String::from("permalink"),
4160+ format!(
4161+ "/{}/{}/{}/{}/{}",
4162+ collection, name, current_page, commit_id, file_path
4163+ ),
4164+ false,
4165+ ),
4166+ (
4167+ String::from("raw"),
4168+ format!("/{}/{}/raw/{}/{}", collection, name, ref_name, file_path),
4169+ false,
4170+ ),
4171+ ]
4172+ }
4173+
4174+ pub fn chartnav(current_page: &str, collection: &str, name: &str, git_hash: Option<&str>) -> Items {
4175+ let commit_path = git_hash.map_or(String::new(), |hash| String::from("/") + hash);
4176+ vec![
4177+ (
4178+ String::from("activity"),
4179+ format!("/{}/{}/chart/activity{}", collection, name, commit_path),
4180+ current_page == "activity",
4181+ ),
4182+ (
4183+ String::from("languages"),
4184+ format!("/{}/{}/chart/languages{}", collection, name, commit_path),
4185+ current_page == "languages",
4186+ ),
4187+ ]
4188+ }
4189+
4190+ pub fn revnav(current_page: &str, collection: &str, name: &str) -> Items {
4191+ vec![
4192+ (
4193+ String::from("tags"),
4194+ format!("/{}/{}/refs/tags", collection, name),
4195+ current_page == "tags",
4196+ ),
4197+ (
4198+ String::from("branches"),
4199+ format!("/{}/{}/refs/branches", collection, name),
4200+ current_page == "branches",
4201+ ),
4202+ ]
4203+ }
4204 diff --git a/ayllu/src/web2/routes/about.rs b/ayllu/src/web2/routes/about.rs
4205new file mode 100644
4206index 0000000..86c3983
4207--- /dev/null
4208+++ b/ayllu/src/web2/routes/about.rs
4209 @@ -0,0 +1,32 @@
4210+ use comrak::{markdown_to_html_with_plugins, ComrakOptions, ComrakPlugins};
4211+
4212+ use axum::{extract::Extension, response::Html};
4213+
4214+ use crate::config::Config;
4215+ use crate::web2::error::Error;
4216+ use crate::web2::highlight::TreeSitterAdapter;
4217+ use crate::web2::middleware::template::Template;
4218+ use crate::web2::navigation;
4219+
4220+ pub async fn serve(
4221+ Extension(cfg): Extension<Config>,
4222+ Extension(adapter): Extension<TreeSitterAdapter>,
4223+ Extension((templates, mut ctx)): Extension<Template>,
4224+ ) -> Result<Html<String>, Error> {
4225+ ctx.insert("title", "about");
4226+ ctx.insert(
4227+ "nav_elements",
4228+ &navigation::global("about", cfg.mail.is_some()),
4229+ );
4230+ let options = ComrakOptions::default();
4231+ let mut plugins = ComrakPlugins::default();
4232+ plugins.render.codefence_syntax_highlighter = Some(&adapter);
4233+ let blurb = match &cfg.blurb {
4234+ Some(blurb) => blurb.clone(),
4235+ None => String::from(""),
4236+ };
4237+ let blurb = markdown_to_html_with_plugins(&blurb, &options, &plugins);
4238+ ctx.insert("blurb", &blurb);
4239+ let body = templates.render("about.html", &ctx)?;
4240+ Ok(Html(body))
4241+ }
4242 diff --git a/ayllu/src/web2/routes/assets.rs b/ayllu/src/web2/routes/assets.rs
4243new file mode 100644
4244index 0000000..976f19b
4245--- /dev/null
4246+++ b/ayllu/src/web2/routes/assets.rs
4247 @@ -0,0 +1,68 @@
4248+ use std::path::PathBuf;
4249+
4250+ use axum::{body::Body, extract::Path, http::header, response::Response, Extension};
4251+
4252+ use tokio::fs::File;
4253+ use tokio_util::io::ReaderStream;
4254+
4255+ use mime;
4256+ use mime_guess;
4257+
4258+ use crate::config::Config;
4259+ use crate::web2::error::Error;
4260+ use crate::web2::extractors::config::ConfigReader;
4261+
4262+ pub async fn serve_css(
4263+ Extension(system_config): Extension<Config>,
4264+ ConfigReader(user_config): ConfigReader,
4265+ ) -> Result<Response, Error> {
4266+ let themes_path = &system_config.web.themes_path.clone();
4267+ let mut file_path = PathBuf::from(themes_path);
4268+ file_path.push(user_config.theme.unwrap_or(system_config.web.default_theme));
4269+ file_path.push("main.min.css");
4270+ let file = tokio::fs::File::open(file_path).await?;
4271+ let stream = ReaderStream::new(file);
4272+ Ok(Response::builder()
4273+ .header(header::CONTENT_TYPE, mime::TEXT_CSS.as_ref())
4274+ .body(Body::from_stream(stream))
4275+ .unwrap())
4276+ }
4277+
4278+ pub async fn serve_asset(
4279+ Extension(system_config): Extension<Config>,
4280+ ConfigReader(user_config): ConfigReader,
4281+ Path(asset): Path<String>,
4282+ ) -> Result<Response, Error> {
4283+ let themes_path = &system_config.web.themes_path.clone();
4284+ let mut file_path = PathBuf::from(themes_path);
4285+ file_path.push(user_config.theme.unwrap_or(system_config.web.default_theme));
4286+ file_path.push("assets");
4287+ file_path.push(&asset);
4288+ let file_path = file_path.to_str().unwrap();
4289+ let file = {
4290+ match File::open(file_path).await {
4291+ Ok(file) => {
4292+ log::debug!("loaded asset from: {}", file_path);
4293+ file
4294+ }
4295+ Err(_) => {
4296+ let mut fallback_path = PathBuf::from(themes_path);
4297+ fallback_path.push(system_config.web.base_theme.clone());
4298+ fallback_path.push("assets");
4299+ fallback_path.push(&asset);
4300+ let fallback_path = fallback_path.to_str().unwrap();
4301+ let file = File::open(fallback_path).await?;
4302+ log::debug!("loaded asset from: {}", fallback_path);
4303+ file
4304+ }
4305+ }
4306+ };
4307+ let stream = ReaderStream::new(file);
4308+ let mime = mime_guess::from_path(file_path)
4309+ .first_or_octet_stream()
4310+ .to_string();
4311+ Ok(Response::builder()
4312+ .header("Content-Type", mime)
4313+ .body(Body::from_stream(stream))
4314+ .unwrap())
4315+ }
4316 diff --git a/ayllu/src/web2/routes/authors.rs b/ayllu/src/web2/routes/authors.rs
4317new file mode 100644
4318index 0000000..2c3a5a4
4319--- /dev/null
4320+++ b/ayllu/src/web2/routes/authors.rs
4321 @@ -0,0 +1,32 @@
4322+ use std::sync::Arc;
4323+
4324+ use axum::{extract::Extension, response::Html};
4325+
4326+ use crate::database_ext::contributors::ContributorsExt;
4327+ use crate::web2::error::Error;
4328+ use crate::web2::middleware::repository::Preamble;
4329+ use crate::web2::middleware::template::Template;
4330+ use crate::web2::navigation;
4331+ use ayllu_database::Wrapper as Database;
4332+
4333+ pub async fn serve(
4334+ Extension(db): Extension<Arc<Database>>,
4335+ Extension(preamble): Extension<Preamble>,
4336+ Extension((templates, mut ctx)): Extension<Template>,
4337+ ) -> Result<Html<String>, Error> {
4338+ // TODO: move to extension
4339+ let authors = db
4340+ .authors_list(
4341+ preamble.repo_path_string().as_str(),
4342+ preamble.latest_commit_id.clone().unwrap().as_str(),
4343+ )
4344+ .await?;
4345+ ctx.insert("title", &format!("{} Authors", preamble.repo_name.clone()));
4346+ ctx.insert(
4347+ "nav_elements",
4348+ &navigation::primary("authors", &preamble.collection_name, &preamble.repo_name),
4349+ );
4350+ ctx.insert("authors", &authors);
4351+ let body = templates.render("authors.html", &ctx)?;
4352+ Ok(Html(body))
4353+ }
4354 diff --git a/ayllu/src/web2/routes/blame.rs b/ayllu/src/web2/routes/blame.rs
4355new file mode 100644
4356index 0000000..4ff451b
4357--- /dev/null
4358+++ b/ayllu/src/web2/routes/blame.rs
4359 @@ -0,0 +1,84 @@
4360+ use serde::Serialize;
4361+
4362+ use axum::{extract::Extension, response::Html};
4363+
4364+ use crate::config::Config;
4365+ use crate::time as timeutil;
4366+ use crate::web2::error::Error;
4367+ use crate::web2::highlight::Highlighter;
4368+ use crate::web2::middleware::repository::Preamble;
4369+ use crate::web2::middleware::template::Template;
4370+ use crate::web2::navigation;
4371+ use ayllu_git::Wrapper;
4372+
4373+ #[derive(Serialize, Default, Clone)]
4374+ struct BlameItem {
4375+ start: usize,
4376+ end: usize,
4377+ author_name: String,
4378+ author_email: String,
4379+ timestamp: String,
4380+ commit_id: String,
4381+ }
4382+
4383+ pub async fn serve(
4384+ Extension(_cfg): Extension<Config>,
4385+ Extension(preamble): Extension<Preamble>,
4386+ Extension(highlighter): Extension<Highlighter>,
4387+ Extension((templates, mut ctx)): Extension<Template>,
4388+ ) -> Result<Html<String>, Error> {
4389+ let repository = Wrapper::new(preamble.repo_path.as_path())?;
4390+ let title = format!("blame: {}", preamble.file_name());
4391+ ctx.insert("title", &title);
4392+ ctx.insert(
4393+ "nav_elements",
4394+ &navigation::primary("", &preamble.collection_name, &preamble.repo_name),
4395+ );
4396+ ctx.insert("file_name", &preamble.file_name());
4397+ ctx.insert(
4398+ "subnav_elements",
4399+ &navigation::subnav(
4400+ "blame",
4401+ &preamble.collection_name,
4402+ &preamble.repo_name,
4403+ &preamble.file_path_string(),
4404+ &preamble.refname,
4405+ &preamble.latest_commit_id.clone().unwrap_or(String::new()),
4406+ ),
4407+ );
4408+ ctx.insert("subtitle", "blame");
4409+ let file_path = preamble.file_path.clone().unwrap();
4410+ if let Some(blob) = repository.blob(file_path.as_path(), preamble.latest_commit_id.clone())? {
4411+ if blob.is_binary {
4412+ ctx.insert("is_renderable", &false);
4413+ } else {
4414+ let content = highlighter.highlight_vec(
4415+ blob.content,
4416+ Some(file_path.to_str().unwrap()),
4417+ None,
4418+ None,
4419+ false,
4420+ );
4421+ ctx.insert("is_renderable", &true);
4422+ ctx.insert("content", &content);
4423+ let lines = repository.blame(
4424+ preamble.file_path.clone().unwrap().as_path(),
4425+ preamble.latest_commit_id.clone(),
4426+ )?;
4427+ let lines: Vec<BlameItem> = lines
4428+ .iter()
4429+ .map(|item| BlameItem {
4430+ start: item.start,
4431+ end: item.end,
4432+ author_name: item.author_name.clone(),
4433+ author_email: item.author_email.clone(),
4434+ timestamp: timeutil::friendly(item.timestamp as u64),
4435+ commit_id: item.commit_id.clone(),
4436+ })
4437+ .collect();
4438+ ctx.insert("blame_lines", &lines);
4439+ }
4440+ }
4441+ let body = templates.render("blame.html", &ctx)?;
4442+ Ok(Html(body))
4443+ }
4444 diff --git a/ayllu/src/web2/routes/blob.rs b/ayllu/src/web2/routes/blob.rs
4445new file mode 100644
4446index 0000000..0f7d68b
4447--- /dev/null
4448+++ b/ayllu/src/web2/routes/blob.rs
4449 @@ -0,0 +1,154 @@
4450+ use std::time::SystemTime;
4451+
4452+ use comrak::{markdown_to_html_with_plugins, ComrakPlugins};
4453+
4454+ use axum::{
4455+ body::Bytes,
4456+ extract::Extension,
4457+ http::{header, HeaderValue},
4458+ response::{Html, IntoResponse, Redirect, Response},
4459+ };
4460+
4461+ use crate::config::Config;
4462+ use crate::languages::LANGUAGE_TABLE;
4463+ use crate::web2::error::Error;
4464+ use crate::web2::highlight::{Highlighter, TreeSitterAdapter};
4465+ use crate::web2::middleware::repository::Preamble;
4466+ use crate::web2::middleware::template::Template;
4467+ use crate::web2::navigation;
4468+ use crate::web2::util;
4469+ use ayllu_git::Wrapper;
4470+
4471+ // TODO: considerable clean up is needed here
4472+ pub async fn serve(
4473+ Extension(cfg): Extension<Config>,
4474+ Extension(preamble): Extension<Preamble>,
4475+ Extension(highlighter): Extension<Highlighter>,
4476+ Extension(adapter): Extension<TreeSitterAdapter>,
4477+ Extension((templates, mut ctx)): Extension<Template>,
4478+ ) -> Result<Html<String>, Error> {
4479+ let repository = Wrapper::new(preamble.repo_path.as_path())?;
4480+ let blob = repository.blob(
4481+ preamble.file_path.clone().unwrap().as_path(),
4482+ preamble.latest_commit_id.clone(),
4483+ )?;
4484+ if blob.is_none() {
4485+ return Err(Error::NotFound(String::from("Not Found")));
4486+ }
4487+ let blob = blob.unwrap();
4488+ ctx.insert("title", &preamble.file_name());
4489+ ctx.insert("file_name", &preamble.file_name());
4490+ ctx.insert("blob", &blob.drop_content());
4491+ ctx.insert("file_size", &blob.size);
4492+ ctx.insert("file_mode", &blob.mode);
4493+ let mut is_markdown = false;
4494+ let mime_type = mime_guess::from_path(preamble.file_path_string()).first_or_octet_stream();
4495+ ctx.insert("mime_type", &mime_type.to_string());
4496+ ctx.insert("subtitle", "blob");
4497+ log::debug!("rendering blob with mime type: {}", mime_type.to_string());
4498+ if mime_type.type_() == "image" {
4499+ ctx.insert("is_image", &true);
4500+ ctx.insert("is_binary", &true);
4501+ ctx.insert("content", &None::<()>);
4502+ } else if mime_type.type_() == "video" {
4503+ ctx.insert("is_video", &true);
4504+ ctx.insert("is_binary", &true);
4505+ ctx.insert("content", &None::<()>);
4506+ } else if mime_type.type_() == "audio" {
4507+ ctx.insert("is_audio", &true);
4508+ ctx.insert("is_binary", &true);
4509+ ctx.insert("content", &None::<()>);
4510+ } else if mime_type.to_string().as_str() == "text/markdown" {
4511+ let mut plugins = ComrakPlugins::default();
4512+ plugins.render.codefence_syntax_highlighter = Some(&adapter);
4513+ let content = String::from_utf8(blob.content).unwrap();
4514+ let content = markdown_to_html_with_plugins(
4515+ content.as_str(),
4516+ &cfg.markdown_render_options(),
4517+ &plugins,
4518+ );
4519+ ctx.insert("content", &content);
4520+ is_markdown = true;
4521+ } else if blob.is_binary {
4522+ ctx.insert("content", &None::<()>);
4523+ } else {
4524+ let content = String::from_utf8(blob.content).unwrap();
4525+ let file_path = preamble.file_path_string();
4526+ let (hint, content) =
4527+ highlighter.highlight(&content, Some(file_path.as_str()), None, None, true);
4528+ if let Some(hint) = hint {
4529+ ctx.insert("color", &LANGUAGE_TABLE.color_from_language(&hint));
4530+ ctx.insert("hint", &hint);
4531+ }
4532+ ctx.insert("content", &content);
4533+ }
4534+ ctx.insert(
4535+ "nav_elements",
4536+ &navigation::primary("blob", &preamble.collection_name, &preamble.repo_name),
4537+ );
4538+ let subnav = navigation::subnav(
4539+ "blob",
4540+ &preamble.collection_name,
4541+ &preamble.repo_name,
4542+ &preamble.file_path_string(),
4543+ &preamble.refname,
4544+ &preamble.latest_commit_id.clone().unwrap_or(String::new()),
4545+ );
4546+ ctx.insert("raw_url", &subnav.get(4).unwrap().1);
4547+ ctx.insert("subnav_elements", &subnav);
4548+ ctx.insert("file_type", &mime_type.to_string());
4549+ ctx.insert("is_markdown", &is_markdown);
4550+ let end = SystemTime::now();
4551+ let duration = end.duration_since(preamble.start_time).unwrap();
4552+ ctx.insert("render_time", &duration.as_millis());
4553+ let body = templates.render("blob.html", &ctx)?;
4554+ Ok(Html(body))
4555+ }
4556+
4557+ pub async fn serve_raw(
4558+ Extension(cfg): Extension<Config>,
4559+ Extension(preamble): Extension<Preamble>,
4560+ ) -> Result<Response, Error> {
4561+ let repository = Wrapper::new(preamble.repo_path.as_path())?;
4562+ let blob = repository.blob(
4563+ preamble.file_path.clone().unwrap().as_path(),
4564+ preamble.latest_commit_id.clone(),
4565+ )?;
4566+ if blob.is_none() {
4567+ return Err(Error::NotFound(format!(
4568+ "{} not found",
4569+ preamble.file_path_string()
4570+ )));
4571+ }
4572+ let blob = blob.unwrap();
4573+ if blob.is_pointer {
4574+ let lfs_config = cfg.lfs.clone().unwrap();
4575+ let location = util::lfs_url(
4576+ lfs_config.url_template.as_str(),
4577+ preamble.collection_name.as_str(),
4578+ preamble.repo_name.as_str(),
4579+ blob.oid.as_str(),
4580+ );
4581+ log::debug!("redirecting blob to LFS server: {}", location);
4582+ Ok(Redirect::permanent(&location).into_response())
4583+ } else {
4584+ let mime = if blob.is_binary {
4585+ mime_guess::from_path(preamble.file_path_string())
4586+ .first_or_octet_stream()
4587+ .to_string()
4588+ } else {
4589+ mime_guess::mime::TEXT_PLAIN.to_string()
4590+ };
4591+ log::debug!(
4592+ "serving blob {} with mime {}",
4593+ preamble.file_path_string(),
4594+ mime.to_string()
4595+ );
4596+ let mut response = Bytes::from(blob.content.clone()).into_response();
4597+ response.headers_mut().insert(
4598+ header::CONTENT_TYPE,
4599+ HeaderValue::from_str(mime.as_ref()).unwrap(),
4600+ );
4601+ Ok(response)
4602+ }
4603+ }
4604 diff --git a/ayllu/src/web2/routes/builds.rs b/ayllu/src/web2/routes/builds.rs
4605new file mode 100644
4606index 0000000..ac7fe86
4607--- /dev/null
4608+++ b/ayllu/src/web2/routes/builds.rs
4609 @@ -0,0 +1,91 @@
4610+ use axum::{
4611+ body::Bytes,
4612+ extract::Extension,
4613+ http::header::{HeaderValue, CONTENT_TYPE},
4614+ response::{Html, IntoResponse, Response},
4615+ };
4616+ use serde::Serialize;
4617+
4618+ use crate::config::Config;
4619+ use crate::web2::error::Error;
4620+
4621+ use crate::web2::middleware::template::Template;
4622+
4623+
4624+ #[derive(Debug, Serialize)]
4625+ struct Build {
4626+ id: String,
4627+ timestamp: String,
4628+ runtime: f64,
4629+ success: bool,
4630+ }
4631+
4632+ #[derive(Debug, Serialize)]
4633+ struct Details {
4634+ build: Build,
4635+ steps: Vec<(String, String, String)>,
4636+ }
4637+
4638+ pub async fn serve(
4639+ Extension(_cfg): Extension<Config>,
4640+ Extension((templates, mut ctx)): Extension<Template>,
4641+ ) -> Result<Html<String>, Error> {
4642+ ctx.insert("title", "builds");
4643+ // TODO
4644+ let builds = vec![
4645+ Build {
4646+ id: String::from("1234"),
4647+ timestamp: String::from("1999-01-02"),
4648+ runtime: 25.0,
4649+ success: true,
4650+ },
4651+ Build {
4652+ id: String::from("4567"),
4653+ timestamp: String::from("1999-02-01"),
4654+ runtime: 29.0,
4655+ success: false,
4656+ },
4657+ ];
4658+ ctx.insert("builds", &builds);
4659+ let body = templates.render("builds.html", &ctx)?;
4660+ Ok(Html(body))
4661+ }
4662+
4663+ pub async fn details(
4664+ Extension(_cfg): Extension<Config>,
4665+ Extension((templates, mut ctx)): Extension<Template>,
4666+ ) -> Result<Html<String>, Error> {
4667+ ctx.insert("title", "builds");
4668+ ctx.insert(
4669+ "build",
4670+ &Build {
4671+ id: String::from("1234"),
4672+ timestamp: String::from("1999-01-01"),
4673+ runtime: 25.0,
4674+ success: true,
4675+ },
4676+ );
4677+ let build_steps: Vec<(String, String, String)> = vec![(
4678+ String::from("phase_1"),
4679+ String::from("stdout\n\nfuu\nbar\n"),
4680+ String::from("stderr\n\n\nerror\n"),
4681+ )];
4682+ ctx.insert("steps", &build_steps);
4683+ let body = templates.render("build.html", &ctx)?;
4684+ Ok(Html(body))
4685+ }
4686+
4687+ pub async fn badge(
4688+ Extension(_cfg): Extension<Config>,
4689+ Extension((templates, mut ctx)): Extension<Template>,
4690+ ) -> Result<Response, Error> {
4691+ ctx.insert("key", "build");
4692+ ctx.insert("value", "success");
4693+ let body = templates.render("badge.svg", &ctx)?;
4694+ let mut response = Bytes::from(body).into_response();
4695+ response.headers_mut().insert(
4696+ CONTENT_TYPE,
4697+ HeaderValue::from_str("image/svg+xml").unwrap(),
4698+ );
4699+ Ok(response)
4700+ }
4701 diff --git a/ayllu/src/web2/routes/chart.rs b/ayllu/src/web2/routes/chart.rs
4702new file mode 100644
4703index 0000000..3a60b2b
4704--- /dev/null
4705+++ b/ayllu/src/web2/routes/chart.rs
4706 @@ -0,0 +1,113 @@
4707+ use std::sync::Arc;
4708+
4709+ use axum::{
4710+ extract::{Extension, Path},
4711+ response::Html,
4712+ };
4713+ use serde::Deserialize;
4714+ use time::Duration;
4715+
4716+ use crate::database_ext::{
4717+ langauges::LanguagesExt,
4718+ stats::{Aggregation, StatsExt},
4719+ };
4720+ use crate::web2::charts::Renderer;
4721+ use crate::web2::error::Error;
4722+ use crate::web2::middleware::repository::Preamble;
4723+ use crate::web2::middleware::template::Template;
4724+ use crate::web2::navigation;
4725+ use ayllu_database::Wrapper as Database;
4726+
4727+ const MAXIMUM_SIZE: u32 = 2000;
4728+ const DEFAULT_HEIGHT: u32 = 450;
4729+ const DEFAULT_WIDTH: u32 = 600;
4730+
4731+ pub const EMPTY_IMAGE: &str = r#"
4732+ <?xml version="1.0" encoding="UTF-8"?><svg xmlns="http://www.w3.org/2000/svg" width="1" height="1"/>
4733+ "#;
4734+
4735+ #[derive(Deserialize)]
4736+ pub struct Params {
4737+ pub commit_id: Option<String>,
4738+ pub kind: Option<String>,
4739+ pub height: Option<u32>,
4740+ pub width: Option<u32>,
4741+ }
4742+
4743+ pub async fn serve(
4744+ Path(params): Path<Params>,
4745+ Extension(db): Extension<Arc<Database>>,
4746+ Extension(preamble): Extension<Preamble>,
4747+ Extension((templates, mut ctx)): Extension<Template>,
4748+ ) -> Result<Html<String>, Error> {
4749+ let git_hash = match params.commit_id.clone() {
4750+ Some(git_hash) => Some(git_hash),
4751+ None => preamble.latest_commit_id.clone(),
4752+ };
4753+
4754+ let chart_kind = params.kind.unwrap_or(String::from("activity"));
4755+ let width = params.width.map_or(DEFAULT_WIDTH, |width| {
4756+ if width > MAXIMUM_SIZE {
4757+ DEFAULT_WIDTH
4758+ } else {
4759+ width
4760+ }
4761+ });
4762+ let height = params.height.map_or(DEFAULT_HEIGHT, |height| {
4763+ if height > MAXIMUM_SIZE {
4764+ DEFAULT_HEIGHT
4765+ } else {
4766+ height
4767+ }
4768+ });
4769+
4770+ ctx.insert(
4771+ "nav_elements",
4772+ &navigation::primary("charts", &preamble.collection_name, &preamble.repo_name),
4773+ );
4774+ ctx.insert(
4775+ "chartnav",
4776+ &navigation::chartnav(
4777+ &chart_kind,
4778+ &preamble.collection_name,
4779+ &preamble.repo_name,
4780+ params.commit_id.as_deref(),
4781+ ),
4782+ );
4783+ if let Some(git_hash) = git_hash {
4784+ // DB based operations
4785+ // TODO: move to extension
4786+ let chart_builder = Renderer::new((width, height));
4787+ let xml = match chart_kind.as_str() {
4788+ "activity" => {
4789+ ctx.insert("title", "Activity");
4790+ ctx.insert("chart_title", "Activity");
4791+ let data = db
4792+ .contribution_buckets_for_repo(
4793+ &preamble.repo_path_string(),
4794+ &git_hash,
4795+ Aggregation::Day,
4796+ 90 * Duration::DAY,
4797+ )
4798+ .await?;
4799+ chart_builder.activity(data)?
4800+ }
4801+ "languages" => {
4802+ ctx.insert("title", "Languages");
4803+ ctx.insert("chart_title", "Language Composition");
4804+ let data = db
4805+ .languages_list(&preamble.repo_path_string(), &git_hash)
4806+ .await?;
4807+ chart_builder.languages(data)?
4808+ }
4809+ name => return Err(Error::Message(format!("unknown chart kind: {}", name))),
4810+ };
4811+ ctx.insert("chart", &xml);
4812+ } else {
4813+ ctx.insert("title", "empty");
4814+ ctx.insert("chart_title", "empty");
4815+ ctx.insert("chart", EMPTY_IMAGE);
4816+ };
4817+ let body = templates.render("chart.html", &ctx)?;
4818+ Ok(Html(body))
4819+ }
4820 diff --git a/ayllu/src/web2/routes/commit.rs b/ayllu/src/web2/routes/commit.rs
4821new file mode 100644
4822index 0000000..95f730a
4823--- /dev/null
4824+++ b/ayllu/src/web2/routes/commit.rs
4825 @@ -0,0 +1,38 @@
4826+ use axum::{
4827+ extract::{Extension, Path},
4828+ response::Html,
4829+ };
4830+
4831+ use crate::web2::error::Error;
4832+ use crate::web2::highlight::Highlighter;
4833+ use crate::web2::middleware::repository::Preamble;
4834+ use crate::web2::middleware::template::Template;
4835+ use crate::web2::navigation;
4836+ use ayllu_git::Wrapper;
4837+
4838+ pub async fn serve(
4839+ Path((_, _, commit_id)): Path<(String, String, String)>,
4840+ Extension(preamble): Extension<Preamble>,
4841+ Extension(highlighter): Extension<Highlighter>,
4842+ Extension((templates, mut ctx)): Extension<Template>,
4843+ ) -> Result<Html<String>, Error> {
4844+ let repository = Wrapper::new(preamble.repo_path.as_path())?;
4845+ let title = format!("commit {}", commit_id);
4846+ ctx.insert("title", &title);
4847+ ctx.insert(
4848+ "nav_elements",
4849+ &navigation::primary("", &preamble.collection_name, &preamble.repo_name),
4850+ );
4851+ ctx.insert("commit_hash", &commit_id);
4852+ let commit = repository.commit(Some(commit_id.to_string()))?.unwrap();
4853+ let (stats, diff) = repository.diff(&commit_id)?;
4854+ ctx.insert("commit", &commit);
4855+ ctx.insert("stats", &stats);
4856+ ctx.insert("distinct_author", &commit.distinct_author());
4857+ ctx.insert("extended_commit_message", &commit.extended_commit_message());
4858+ let formatted_diff =
4859+ highlighter.highlight(diff.as_str(), None, None, Some("diff".into()), true);
4860+ ctx.insert("diff", &formatted_diff);
4861+ let body = templates.render("commit.html", &ctx)?;
4862+ Ok(Html(body))
4863+ }
4864 diff --git a/ayllu/src/web2/routes/config.rs b/ayllu/src/web2/routes/config.rs
4865new file mode 100644
4866index 0000000..85d7184
4867--- /dev/null
4868+++ b/ayllu/src/web2/routes/config.rs
4869 @@ -0,0 +1,47 @@
4870+ use axum::{
4871+ extract::{Extension, Form},
4872+ response::{Html, Redirect},
4873+ };
4874+
4875+ use axum_extra::extract::cookie::{Cookie, CookieJar};
4876+
4877+ use crate::config::Config as SystemConfig;
4878+ use crate::web2::config::Config;
4879+ use crate::web2::error::Error;
4880+ use crate::web2::extractors::config::ConfigReader;
4881+ use crate::web2::middleware::template::Template;
4882+ use crate::web2::navigation;
4883+
4884+ pub async fn serve(
4885+ Extension(cfg): Extension<SystemConfig>,
4886+ ConfigReader(client_config): ConfigReader,
4887+ Extension((templates, mut ctx)): Extension<Template>,
4888+ ) -> Result<Html<String>, Error> {
4889+ ctx.insert("title", "config");
4890+ ctx.insert("config", &client_config);
4891+ ctx.insert("themes", &cfg.web.themes);
4892+ ctx.insert(
4893+ "nav_elements",
4894+ &navigation::global("config", cfg.mail.is_some()),
4895+ );
4896+ let body = templates.render("config.html", &ctx)?;
4897+ Ok(Html(body))
4898+ }
4899+
4900+ pub async fn update(
4901+ jar: CookieJar,
4902+ Extension(cfg): Extension<SystemConfig>,
4903+ Form(updates): Form<Config>,
4904+ ) -> Result<(CookieJar, Redirect), Error> {
4905+ Ok((
4906+ jar.add(Cookie::new(
4907+ "theme",
4908+ updates.theme.unwrap_or(cfg.web.default_theme),
4909+ ))
4910+ .add(Cookie::new(
4911+ "items-per-page",
4912+ updates.items_per_page.to_string(),
4913+ )),
4914+ Redirect::to("/config"),
4915+ ))
4916+ }
4917 diff --git a/ayllu/src/web2/routes/finger.rs b/ayllu/src/web2/routes/finger.rs
4918new file mode 100644
4919index 0000000..ab8c4d1
4920--- /dev/null
4921+++ b/ayllu/src/web2/routes/finger.rs
4922 @@ -0,0 +1,93 @@
4923+ use axum::{
4924+ extract::{Extension, Query},
4925+ http::StatusCode,
4926+ response::IntoResponse,
4927+ response::Json,
4928+ response::Response,
4929+ };
4930+ use serde::Deserialize;
4931+ use webfinger::{Link, Prefix, Resolver, ResolverError, Webfinger};
4932+
4933+ use std::sync::Arc;
4934+
4935+ use crate::config::Config;
4936+ // use crate::web2::error::Error;
4937+
4938+ #[derive(Clone)]
4939+ pub struct CResolver {
4940+ pub domain: &'static str,
4941+ pub config: Arc<Config>,
4942+ }
4943+
4944+ #[derive(Deserialize, Clone)]
4945+ pub struct FingerParams {
4946+ pub resource: String,
4947+ }
4948+
4949+ pub struct Error(ResolverError);
4950+
4951+ impl IntoResponse for Error {
4952+ fn into_response(self) -> Response {
4953+ let status_code = match self.0 {
4954+ ResolverError::InvalidResource => StatusCode::INTERNAL_SERVER_ERROR,
4955+ ResolverError::WrongDomain => StatusCode::INTERNAL_SERVER_ERROR,
4956+ ResolverError::NotFound => StatusCode::NOT_FOUND,
4957+ };
4958+ (status_code, format!("resolver: {:?}", self.0)).into_response()
4959+ }
4960+ }
4961+
4962+ impl Resolver<()> for CResolver {
4963+ fn instance_domain<'a>(&self) -> &'a str {
4964+ self.domain
4965+ }
4966+
4967+ fn find(
4968+ &self,
4969+ prefix: Prefix,
4970+ acct: String,
4971+ _resource_repo: (),
4972+ ) -> Result<Webfinger, ResolverError> {
4973+ match prefix {
4974+ Prefix::Acct => {
4975+ // TODO: expand this to use in addition to the static config
4976+ // objects also "author repositories" where each author has a
4977+ // repo that contains a static json response for this.
4978+ let author = self
4979+ .config
4980+ .authors
4981+ .iter()
4982+ .find(|author| author.email == acct);
4983+ match author {
4984+ Some(author) => Ok(Webfinger {
4985+ subject: acct.clone(),
4986+ aliases: vec![acct.clone()],
4987+ links: author
4988+ .links
4989+ .iter()
4990+ .map(|link| Link {
4991+ rel: link.rel.clone(),
4992+ href: link.href.clone(),
4993+ template: link.template.clone(),
4994+ mime_type: link.mime_template.clone(),
4995+ })
4996+ .collect(),
4997+ }),
4998+ None => Err(ResolverError::NotFound),
4999+ }
5000+ }
5001+ Prefix::Group => Err(ResolverError::InvalidResource),
5002+ Prefix::Custom(_) => Err(ResolverError::InvalidResource),
5003+ }
5004+ }
5005+ }
5006+
5007+ pub async fn serve(
5008+ Extension(resolver): Extension<CResolver>,
5009+ Query(params): Query<FingerParams>,
5010+ ) -> Result<Json<Webfinger>, Error> {
5011+ match resolver.find(webfinger::Prefix::Acct, params.resource, ()) {
5012+ Ok(result) => Ok(Json(result)),
5013+ Err(e) => Err(Error(e)),
5014+ }
5015+ }
5016 diff --git a/ayllu/src/web2/routes/index.rs b/ayllu/src/web2/routes/index.rs
5017new file mode 100644
5018index 0000000..ca1cf86
5019--- /dev/null
5020+++ b/ayllu/src/web2/routes/index.rs
5021 @@ -0,0 +1,134 @@
5022+ use std::convert::From;
5023+ use std::sync::Arc;
5024+
5025+ use serde::Serialize;
5026+
5027+ use axum::{
5028+ extract::{Extension, Path},
5029+ response::Html,
5030+ };
5031+
5032+ use crate::config::Config;
5033+ use crate::database_ext::stats::{Aggregation, StatsExt};
5034+ use crate::time as timeutil;
5035+ use crate::web2::charts;
5036+ use crate::web2::error::Error;
5037+ use crate::web2::middleware::template::Template;
5038+ use crate::web2::navigation;
5039+ use ayllu_database::Wrapper as Database;
5040+ use ayllu_git::{Scanner, Wrapper};
5041+
5042+ #[derive(Debug, Serialize)]
5043+ struct Collection {
5044+ name: String,
5045+ description: Option<String>,
5046+ repositories: Vec<Repository>,
5047+ }
5048+
5049+ #[derive(Debug, Serialize)]
5050+ struct Repository {
5051+ name: String,
5052+ description: String,
5053+ timestamp: Option<u64>,
5054+ age: String,
5055+ activity: String,
5056+ }
5057+
5058+ async fn load_repositories(collection_path: &str, db: &Database) -> Result<Vec<Repository>, Error> {
5059+ let mut repositories: Vec<Repository> = Vec::new();
5060+ let scanner = Scanner::from_path(collection_path)?;
5061+ for repo_path in scanner {
5062+ let repository = Wrapper::new(repo_path.as_path())?;
5063+ let git_config = repository.config()?;
5064+ // skip any repository that is marked "hidden"
5065+ if git_config.hidden.is_some_and(|hidden| hidden) {
5066+ continue;
5067+ }
5068+ let latest_hash = repository.latest_hash()?;
5069+ let name = repo_path.file_name().unwrap();
5070+ let name = String::from(name.to_str().unwrap());
5071+ let description = git_config
5072+ .description
5073+ .unwrap_or(String::from("no description present"));
5074+ let timestamp = repository.last_modified()?;
5075+ let age = timestamp.map_or(String::from("?"), timeutil::friendly);
5076+ // TODO move to plugin based system
5077+ let db_activity: Vec<(i64, i64, i64)> = match latest_hash {
5078+ Some(hash) => {
5079+ db.contribution_buckets_for_repo(
5080+ repo_path.to_str().unwrap(),
5081+ hash.as_str(),
5082+ Aggregation::Day,
5083+ time::Duration::DAY * 90,
5084+ )
5085+ .await?
5086+ }
5087+ None => Vec::new(),
5088+ };
5089+ let activity = charts::spark_lines(db_activity.iter().map(|item| item.0).collect(), 7);
5090+ repositories.push(Repository {
5091+ name,
5092+ description,
5093+ timestamp,
5094+ age,
5095+ activity,
5096+ });
5097+ }
5098+ repositories.sort_by(|a, b| {
5099+ let first = a.timestamp.unwrap_or(0);
5100+ let second = b.timestamp.unwrap_or(0);
5101+ second.cmp(&first)
5102+ });
5103+ Ok(repositories)
5104+ }
5105+
5106+ pub async fn index(
5107+ Extension(db): Extension<Arc<Database>>,
5108+ Extension(cfg): Extension<Config>,
5109+ Extension((templates, mut ctx)): Extension<Template>,
5110+ ) -> Result<Html<String>, Error> {
5111+ // database::test_cache(config.database.as_str()).await?;
5112+ ctx.insert("title", "repository listing");
5113+ let mut collections: Vec<Collection> = Vec::new();
5114+ for collection in cfg.collections.iter() {
5115+ if collection.hidden.is_some_and(|hidden| hidden) {
5116+ continue;
5117+ };
5118+ let repositories = load_repositories(collection.path.as_str(), &db).await?;
5119+ collections.push(Collection {
5120+ name: collection.name.clone(),
5121+ description: collection.description.clone(),
5122+ repositories,
5123+ });
5124+ }
5125+ ctx.insert("title", "ayllu");
5126+ ctx.insert("collections", &collections);
5127+ ctx.insert("nav_elements", &navigation::global("", cfg.mail.is_some()));
5128+ let body = templates.render("index.html", &ctx)?;
5129+ Ok(Html(body))
5130+ }
5131+
5132+ pub async fn collection(
5133+ Extension(db): Extension<Arc<Database>>,
5134+ Extension(cfg): Extension<Config>,
5135+ collection: Option<Path<String>>,
5136+ Extension((templates, mut ctx)): Extension<Template>,
5137+ ) -> Result<Html<String>, Error> {
5138+ let entry = cfg.collections.iter().find(|entry| {
5139+ collection
5140+ .as_ref()
5141+ .is_some_and(|collection| entry.name == collection.0)
5142+ });
5143+ if entry.is_none() {
5144+ return Err(Error::NotFound("collection not found".to_string()));
5145+ }
5146+ let entry = entry.unwrap();
5147+ let repositories = load_repositories(entry.path.as_str(), &db).await?;
5148+ ctx.insert("title", "ayllu");
5149+ ctx.insert("nav_elements", &navigation::global("", cfg.mail.is_some()));
5150+ ctx.insert("collection", &entry.clone());
5151+ ctx.insert("repositories", &repositories);
5152+ ctx.insert("is_hidden", &entry.hidden.is_some_and(|x| x));
5153+ let body = templates.render("collection.html", &ctx)?;
5154+ Ok(Html(body))
5155+ }
5156 diff --git a/ayllu/src/web2/routes/log.rs b/ayllu/src/web2/routes/log.rs
5157new file mode 100644
5158index 0000000..3cdf57d
5159--- /dev/null
5160+++ b/ayllu/src/web2/routes/log.rs
5161 @@ -0,0 +1,75 @@
5162+ use std::collections::HashMap;
5163+
5164+ use axum::{
5165+ debug_handler,
5166+ extract::{Extension, Query},
5167+ response::Html,
5168+ };
5169+
5170+ use crate::web2::error::Error;
5171+ use crate::web2::extractors::config::ConfigReader;
5172+ use crate::web2::middleware::repository::Preamble;
5173+ use crate::web2::middleware::template::Template;
5174+ use crate::web2::navigation;
5175+ use ayllu_git::{Selector, Wrapper};
5176+
5177+ #[debug_handler]
5178+ pub async fn serve(
5179+ Extension(preamble): Extension<Preamble>,
5180+ Extension((templates, mut ctx)): Extension<Template>,
5181+ ConfigReader(config): ConfigReader,
5182+ params: Option<Query<HashMap<String, String>>>,
5183+ ) -> Result<Html<String>, Error> {
5184+ let repository = Wrapper::new(preamble.repo_path.as_path())?;
5185+ ctx.insert(
5186+ "nav_elements",
5187+ &navigation::primary("log", &preamble.collection_name, &preamble.repo_name),
5188+ );
5189+ ctx.insert("file_view", &false);
5190+ match preamble.file_path.clone() {
5191+ Some(file_path) => {
5192+ let title = format!("log: {}", preamble.file_name());
5193+ ctx.insert("title", &title);
5194+ ctx.insert("subtitle", &title);
5195+ ctx.insert("file_view", &true);
5196+ ctx.insert(
5197+ "file_name",
5198+ &file_path
5199+ .file_name()
5200+ .map_or(String::new(), |item| item.to_str().unwrap().to_string()),
5201+ );
5202+ ctx.insert(
5203+ "subnav_elements",
5204+ &navigation::subnav(
5205+ "log",
5206+ &preamble.collection_name,
5207+ &preamble.repo_name,
5208+ &preamble.file_path_string(),
5209+ &preamble.refname,
5210+ &preamble.latest_commit_id.clone().unwrap_or(String::new()),
5211+ ),
5212+ );
5213+ let commits =
5214+ repository.log_path(file_path.as_path(), preamble.latest_commit_id.clone())?;
5215+ ctx.insert("commits", &commits);
5216+ }
5217+ None => {
5218+ let title = format!("{} log", preamble.repo_name.clone());
5219+ ctx.insert("title", &title);
5220+ let commits = repository.log(Selector {
5221+ commit: preamble.latest_commit_id.clone(),
5222+ username: params
5223+ .clone()
5224+ .and_then(|params| params.get("username").cloned()),
5225+ email: params
5226+ .clone()
5227+ .and_then(|params| params.get("email").cloned()),
5228+ limit: Some(config.items_per_page as i64),
5229+ })?;
5230+ ctx.insert("commits", &commits);
5231+ ctx.insert("has_more", &(commits.len() == config.items_per_page));
5232+ }
5233+ }
5234+ let body = templates.render("log.html", &ctx)?;
5235+ Ok(Html(body))
5236+ }
5237 diff --git a/ayllu/src/web2/routes/mail.rs b/ayllu/src/web2/routes/mail.rs
5238new file mode 100644
5239index 0000000..e6a4977
5240--- /dev/null
5241+++ b/ayllu/src/web2/routes/mail.rs
5242 @@ -0,0 +1,330 @@
5243+ use axum::{
5244+ body::Bytes,
5245+ debug_handler,
5246+ extract::{Extension, Path},
5247+ http::header::CONTENT_TYPE,
5248+ response::{Html, IntoResponse, Response},
5249+ };
5250+ use serde::{Deserialize, Serialize};
5251+
5252+ use crate::languages::Hint;
5253+ use crate::web2::error::Error;
5254+ use crate::web2::highlight::Highlighter;
5255+ use crate::web2::middleware::rpc_initiator::{Initiator, Kind as InitiatorKind};
5256+ use crate::web2::middleware::template::Template;
5257+ use crate::web2::navigation;
5258+ use ayllu_api::mail_capnp::server::Client as MailClient;
5259+ use ayllu_rpc::Client;
5260+
5261+ #[derive(Deserialize)]
5262+ pub struct Params {
5263+ pub list_id: String,
5264+ pub thread_id: Option<String>,
5265+ pub message_id: Option<String>,
5266+ }
5267+
5268+ #[derive(Debug, Serialize, Default)]
5269+ struct Thread {
5270+ pub id: i64,
5271+ pub message_id: String,
5272+ pub from: String,
5273+ pub subject: String,
5274+ pub n_replies: i64,
5275+ pub timestamp: i64,
5276+ }
5277+
5278+ #[derive(Debug, Serialize, Default)]
5279+ struct Message {
5280+ pub id: String,
5281+ pub message_id: String,
5282+ pub subject: String,
5283+ pub created_at: i64,
5284+ pub from_address: String,
5285+ pub body: String,
5286+ pub text: String,
5287+ pub is_patch: bool,
5288+ }
5289+
5290+ #[derive(Debug, Serialize, Default, PartialEq)]
5291+ struct List {
5292+ pub id: String,
5293+ pub name: String,
5294+ pub description: Option<String>,
5295+ pub address: String,
5296+ pub topics: Vec<String>,
5297+ pub request_address: String,
5298+ }
5299+
5300+ async fn read_list(mail_client: Client, list_id: String) -> Result<Option<List>, Error> {
5301+ let list = mail_client
5302+ .invoke(move |c: MailClient| async move {
5303+ let req = c.lists_request();
5304+ let result = req.send().promise.await?;
5305+ let rpc_lists = result.get()?.get_lists()?;
5306+ for list in rpc_lists.iter() {
5307+ let id = list.get_id()?.to_string().unwrap();
5308+ let topics: Vec<String> = list
5309+ .get_topics()?
5310+ .iter()
5311+ .map(|topic| topic.unwrap().to_string().unwrap())
5312+ .collect();
5313+ if list_id == id {
5314+ return Ok(Some(List {
5315+ id: list_id.clone(),
5316+ name: list.get_name()?.to_string().unwrap(),
5317+ description: None,
5318+ address: list.get_address()?.to_string().unwrap(),
5319+ topics,
5320+ request_address: list.get_request_address()?.to_string().unwrap(),
5321+ }));
5322+ }
5323+ }
5324+ Ok(None)
5325+ })
5326+ .await?;
5327+ Ok(list)
5328+ }
5329+
5330+ pub async fn lists(
5331+ Extension(initiator): Extension<Initiator>,
5332+ Extension((templates, mut ctx)): Extension<Template>,
5333+ ) -> Result<Html<String>, Error> {
5334+ ctx.insert("title", "lists");
5335+ ctx.insert("nav_elements", &navigation::global("mail", true));
5336+ let mail_client = initiator.client(InitiatorKind::Mail).unwrap();
5337+ let lists = mail_client
5338+ .invoke(move |c: MailClient| async move {
5339+ let mut lists: Vec<List> = Vec::new();
5340+ let req = c.lists_request();
5341+ let result = req.send().promise.await?;
5342+ let rpc_lists = result.get()?.get_lists()?;
5343+ for list in rpc_lists.iter() {
5344+ let description = if list.has_description() {
5345+ Some(list.get_description()?.to_string().unwrap())
5346+ } else {
5347+ None
5348+ };
5349+ let topics: Vec<String> = list
5350+ .get_topics()?
5351+ .iter()
5352+ .map(|topic| topic.unwrap().to_string().unwrap())
5353+ .collect();
5354+ lists.push(List {
5355+ id: list.get_id()?.to_string().unwrap(),
5356+ name: list.get_name()?.to_string().unwrap(),
5357+ description,
5358+ address: list.get_name()?.to_string().unwrap(),
5359+ topics,
5360+ request_address: list.get_request_address()?.to_string().unwrap(),
5361+ })
5362+ }
5363+ Ok(lists)
5364+ })
5365+ .await?;
5366+ ctx.insert("lists", &lists);
5367+ // ctx.insert("lists", &cfg.mail.unwrap().lists);
5368+ let body = templates.render("lists.html", &ctx)?;
5369+ Ok(Html(body))
5370+ }
5371+
5372+ #[debug_handler]
5373+ pub async fn threads(
5374+ Path(params): Path<Params>,
5375+ // Extension(db): Extension<Arc<Database>>,
5376+ Extension(initiator): Extension<Initiator>,
5377+ Extension((templates, mut ctx)): Extension<Template>,
5378+ ) -> Result<Html<String>, Error> {
5379+ let client = initiator.client(InitiatorKind::Mail).unwrap();
5380+ let mailing_list = read_list(client.clone(), params.list_id.clone()).await?;
5381+ if let Some(mailing_list) = mailing_list {
5382+ ctx.insert("title", &format!("list {}", mailing_list.name));
5383+ ctx.insert("nav_elements", &navigation::global("mail", true));
5384+ ctx.insert("list", &mailing_list);
5385+ ctx.insert("title", &format!("list {}", mailing_list.address));
5386+ ctx.insert("nav_elements", &navigation::global("mail", true));
5387+ ctx.insert("list", &mailing_list);
5388+ let mut threads = client
5389+ .invoke(move |c: MailClient| async move {
5390+ let mut threads: Vec<Thread> = Vec::new();
5391+ let mut req = c.list_threads_request();
5392+ req.get().set_id(params.list_id.as_str().into());
5393+ let result = req.send().promise.await?;
5394+ let rpc_threads = result.get()?.get_threads()?;
5395+ for thread in rpc_threads.iter() {
5396+ let message = thread.get_first()?;
5397+ let message_id = message.get_message_id()?.to_string().unwrap();
5398+ threads.push(Thread {
5399+ id: message.get_id(),
5400+ message_id,
5401+ from: message.get_from()?.to_string().unwrap(),
5402+ subject: message.get_subject()?.to_string().unwrap(),
5403+ n_replies: thread.get_n_replies(),
5404+ timestamp: thread.get_first().unwrap().get_timestamp(),
5405+ })
5406+ }
5407+ Ok(threads)
5408+ })
5409+ .await?;
5410+ threads.sort_by(|first, second| second.timestamp.cmp(&first.timestamp));
5411+ ctx.insert("threads", &threads);
5412+ ctx.insert("request_address", &mailing_list.request_address);
5413+ let body = templates.render("threads.html", &ctx)?;
5414+ Ok(Html(body))
5415+ } else {
5416+ Err(Error::NotFound(format!(
5417+ "no mailing list with id: {} exists",
5418+ params.list_id
5419+ )))
5420+ }
5421+ }
5422+
5423+ pub async fn thread(
5424+ Path(params): Path<Params>,
5425+ Extension(initiator): Extension<Initiator>,
5426+ Extension(highlighter): Extension<Highlighter>,
5427+ Extension((templates, mut ctx)): Extension<Template>,
5428+ ) -> Result<Html<String>, Error> {
5429+ let client = initiator.client(InitiatorKind::Mail).unwrap();
5430+ let mailing_list = read_list(client.clone(), params.list_id.clone()).await?;
5431+ if let Some(mailing_list) = mailing_list {
5432+ let thread_id = params.thread_id.clone();
5433+ let messages = client
5434+ .invoke(move |c: MailClient| async move {
5435+ let mut messages: Vec<Message> = Vec::new();
5436+ let mut req = c.read_thread_request();
5437+ req.get().set_id(params.list_id.as_str().into());
5438+ req.get().set_message_id(thread_id.unwrap().as_str().into());
5439+ let result = req.send().promise.await?;
5440+ for message in result.get()?.get_thread()? {
5441+ let text = message.get_text().unwrap().to_string().unwrap();
5442+ let text = if message.get_is_patch() {
5443+ let (_, diff) = highlighter.highlight(
5444+ &text,
5445+ None,
5446+ None,
5447+ Some(Hint::from("DIFF")),
5448+ false,
5449+ );
5450+ diff
5451+ } else {
5452+ text
5453+ };
5454+ messages.push(Message {
5455+ id: message.get_id().to_string(),
5456+ subject: message.get_subject()?.to_string().unwrap(),
5457+ message_id: message.get_message_id()?.to_string().unwrap(),
5458+ created_at: message.get_timestamp(),
5459+ from_address: message.get_address()?.to_string().unwrap(),
5460+ body: message.get_body()?.to_string().unwrap(),
5461+ text,
5462+ is_patch: message.get_is_patch(),
5463+ })
5464+ }
5465+ Ok(messages)
5466+ })
5467+ .await?;
5468+ let subject = messages.first().map(|message| message.subject.clone());
5469+ ctx.insert("title", &format!("list {}", mailing_list.address));
5470+ ctx.insert("nav_elements", &navigation::global("mail", true));
5471+ ctx.insert("list", &mailing_list);
5472+ ctx.insert("list_id", &mailing_list.id);
5473+ ctx.insert("thread_id", &params.thread_id.clone());
5474+ ctx.insert("messages", &messages);
5475+ ctx.insert("subject", &subject);
5476+ let body = templates.render("thread.html", &ctx)?;
5477+ Ok(Html(body))
5478+ } else {
5479+ Err(Error::NotFound(format!(
5480+ "no mailing list with id: {} exists",
5481+ params.list_id
5482+ )))
5483+ }
5484+ }
5485+
5486+ pub async fn message(
5487+ Path(params): Path<Params>,
5488+ Extension(initiator): Extension<Initiator>,
5489+ Extension(highlighter): Extension<Highlighter>,
5490+ Extension((templates, mut ctx)): Extension<Template>,
5491+ ) -> Result<Html<String>, Error> {
5492+ let client = initiator.client(InitiatorKind::Mail).unwrap();
5493+ let mailing_list = read_list(client.clone(), params.list_id.clone()).await?;
5494+ if let Some(mailing_list) = mailing_list {
5495+ let message = client
5496+ .invoke(move |c: MailClient| async move {
5497+ let mut req = c.read_post_request();
5498+ req.get().set_id(params.list_id.as_str().into());
5499+ req.get()
5500+ .set_message_id(params.message_id.unwrap().as_str().into());
5501+ // TODO: detect a missing message and return 404 appropriately
5502+ let result = req.send().promise.await?;
5503+ let message = result.get()?;
5504+ let text = message.get_text().unwrap().to_string().unwrap();
5505+ let text = if message.get_is_patch() {
5506+ let (_, diff) =
5507+ highlighter.highlight(&text, None, None, Some(Hint::from("DIFF")), false);
5508+ diff
5509+ } else {
5510+ text
5511+ };
5512+ Ok(Message {
5513+ id: message.get_id().to_string(),
5514+ subject: message.get_subject()?.to_string().unwrap(),
5515+ message_id: message.get_message_id()?.to_string().unwrap(),
5516+ created_at: message.get_timestamp(),
5517+ body: message.get_body()?.to_string().unwrap(),
5518+ text,
5519+ from_address: message.get_address()?.to_string().unwrap(),
5520+ is_patch: message.get_is_patch(),
5521+ })
5522+ })
5523+ .await?;
5524+ ctx.insert("title", &format!("list {}", mailing_list.name));
5525+ ctx.insert("nav_elements", &navigation::global("mail", true));
5526+ ctx.insert("list", &mailing_list);
5527+ ctx.insert("list_id", &mailing_list.id.clone());
5528+ ctx.insert("thread_id", &params.thread_id);
5529+ ctx.insert("message", &message);
5530+ let body = templates.render("post.html", &ctx)?;
5531+ Ok(Html(body))
5532+ } else {
5533+ Err(Error::NotFound(format!(
5534+ "no mailing list with id: {} exists",
5535+ params.list_id
5536+ )))
5537+ }
5538+ }
5539+
5540+ pub async fn export(
5541+ Path(params): Path<Params>,
5542+ Extension(initiator): Extension<Initiator>,
5543+ ) -> Result<Response, Error> {
5544+ let client = initiator.client(InitiatorKind::Mail).unwrap();
5545+ let mailing_list = read_list(client.clone(), params.list_id.clone()).await?;
5546+ if let Some(mailing_list) = mailing_list {
5547+ let list_id = mailing_list.id.clone();
5548+ let thread = client
5549+ .invoke(move |c: MailClient| async move {
5550+ let mut req = c.export_request();
5551+ req.get().set_id(list_id.as_str().into());
5552+ if let Some(message_id) = params.message_id {
5553+ req.get().set_message_id(message_id.as_str().into());
5554+ req.get().set_as_thread(true);
5555+ }
5556+ let result = req.send().promise.await?;
5557+ let post = result.get()?.get_thread()?;
5558+ Ok(post.to_vec())
5559+ })
5560+ .await?;
5561+ let mut response = Bytes::from(thread).into_response();
5562+ response
5563+ .headers_mut()
5564+ .insert(CONTENT_TYPE, "application/mbox".parse().unwrap());
5565+ Ok(response)
5566+ } else {
5567+ Err(Error::NotFound(format!(
5568+ "no mailing list with id: {} exists",
5569+ params.list_id
5570+ )))
5571+ }
5572+ }
5573 diff --git a/ayllu/src/web2/routes/mod.rs b/ayllu/src/web2/routes/mod.rs
5574new file mode 100644
5575index 0000000..98e70a1
5576--- /dev/null
5577+++ b/ayllu/src/web2/routes/mod.rs
5578 @@ -0,0 +1,18 @@
5579+ pub mod about;
5580+ pub mod assets;
5581+ pub mod authors;
5582+ pub mod blame;
5583+ pub mod blob;
5584+ pub mod builds;
5585+ pub mod chart;
5586+ pub mod commit;
5587+ pub mod config;
5588+ pub mod finger;
5589+ pub mod index;
5590+ pub mod log;
5591+ pub mod mail;
5592+ pub mod refs;
5593+ pub mod repo;
5594+ pub mod robots;
5595+ pub mod rss;
5596+ pub mod xmpp;
5597 diff --git a/ayllu/src/web2/routes/refs.rs b/ayllu/src/web2/routes/refs.rs
5598new file mode 100644
5599index 0000000..3483ce2
5600--- /dev/null
5601+++ b/ayllu/src/web2/routes/refs.rs
5602 @@ -0,0 +1,106 @@
5603+ use serde::Serialize;
5604+
5605+ use axum::{
5606+ body::Body,
5607+ extract::{Extension, Path},
5608+ http::header::CONTENT_TYPE,
5609+ response::{Html, Response},
5610+ };
5611+ use tokio_util::io::ReaderStream;
5612+
5613+ use crate::web2::error::Error;
5614+ use crate::web2::middleware::repository::Preamble;
5615+ use crate::web2::middleware::template::Template;
5616+ use crate::web2::navigation;
5617+ use ayllu_git::Wrapper;
5618+
5619+ const INVALID_EXTENSION_MESSAGE: &str = "archive requests must have a .tar.gz extension";
5620+
5621+ #[derive(Serialize, Default)]
5622+ struct Item {
5623+ author_name: String,
5624+ author_email: String,
5625+ name: String,
5626+ message: String,
5627+ timestamp: String,
5628+ short_hash: String,
5629+ commit_hash: String,
5630+ }
5631+
5632+ // TODO: need an individual branch page
5633+
5634+ pub async fn branches(
5635+ Extension(preamble): Extension<Preamble>,
5636+ Extension((templates, mut ctx)): Extension<Template>,
5637+ ) -> Result<Html<String>, Error> {
5638+ let repository = Wrapper::new(preamble.repo_path.as_path())?;
5639+ let branches = repository.branches()?;
5640+ ctx.insert("title", &preamble.repo_name);
5641+ ctx.insert("branches", &branches);
5642+ ctx.insert(
5643+ "nav_elements",
5644+ &navigation::primary(
5645+ "refs",
5646+ &preamble.collection_name,
5647+ &preamble.repo_name,
5648+ ),
5649+ );
5650+ ctx.insert(
5651+ "refnav",
5652+ &navigation::revnav(
5653+ "branches",
5654+ preamble.collection_name.as_str(),
5655+ preamble.repo_name.as_str(),
5656+ ),
5657+ );
5658+ let body = templates.render("branches.html", &ctx)?;
5659+ Ok(Html(body))
5660+ }
5661+
5662+ // TODO: need an individual ref page
5663+
5664+ pub async fn tags(
5665+ Extension(preamble): Extension<Preamble>,
5666+ Extension((templates, mut ctx)): Extension<Template>,
5667+ ) -> Result<Html<String>, Error> {
5668+ let repository = Wrapper::new(preamble.repo_path.as_path())?;
5669+ let tags = repository.tags()?;
5670+ ctx.insert("title", &preamble.repo_name);
5671+ ctx.insert("tags", &tags);
5672+ ctx.insert(
5673+ "nav_elements",
5674+ &navigation::primary(
5675+ "refs",
5676+ &preamble.collection_name,
5677+ &preamble.repo_name,
5678+ ),
5679+ );
5680+ ctx.insert(
5681+ "refnav",
5682+ &navigation::revnav(
5683+ "tags",
5684+ preamble.collection_name.as_str(),
5685+ preamble.repo_name.as_str(),
5686+ ),
5687+ );
5688+ let body = templates.render("tags.html", &ctx)?;
5689+ Ok(Html(body))
5690+ }
5691+
5692+ pub async fn archive(
5693+ Path((_, _, ref_name)): Path<(String, String, String)>,
5694+ Extension(preamble): Extension<Preamble>,
5695+ ) -> Result<Response, Error> {
5696+ if !ref_name.ends_with(".tar.gz") {
5697+ return Err(Error::Message(INVALID_EXTENSION_MESSAGE.to_string()));
5698+ }
5699+ let repository = Wrapper::new(preamble.repo_path.as_path())?;
5700+ let ref_name = ref_name.trim_end_matches(".tar.gz");
5701+ let mut child_proc = repository.archive(ref_name)?;
5702+ let stdout = child_proc.stdout.take().unwrap();
5703+ let response = Response::builder()
5704+ .header(CONTENT_TYPE, "application/gzip")
5705+ .body(Body::from_stream(ReaderStream::new(stdout)))
5706+ .unwrap();
5707+ Ok(response)
5708+ }
5709 diff --git a/ayllu/src/web2/routes/repo.rs b/ayllu/src/web2/routes/repo.rs
5710new file mode 100644
5711index 0000000..31b8779
5712--- /dev/null
5713+++ b/ayllu/src/web2/routes/repo.rs
5714 @@ -0,0 +1,387 @@
5715+ use std::path::PathBuf;
5716+ use std::sync::Arc;
5717+
5718+ use axum::{
5719+ debug_handler,
5720+ extract::{Extension, OriginalUri},
5721+ response::Html,
5722+ };
5723+ use comrak::{markdown_to_html_with_plugins, ComrakPlugins};
5724+ use serde::Serialize;
5725+ use tera::{to_value, Filter};
5726+ use time::Duration;
5727+
5728+ use crate::config::Config;
5729+ use crate::database_ext::{
5730+ contributors::ContributorsExt,
5731+ langauges::LanguagesExt,
5732+ stats::{Aggregation, StatsExt},
5733+ };
5734+ use crate::license;
5735+ use crate::web2::charts;
5736+ use crate::web2::error::Error;
5737+ use crate::web2::highlight::TreeSitterAdapter;
5738+ use crate::web2::middleware::repository::Preamble;
5739+ use crate::web2::middleware::rpc_initiator::{Initiator, Kind as InitiatorKind};
5740+ use crate::web2::middleware::template::Template;
5741+ use crate::web2::navigation;
5742+ use crate::web2::util;
5743+ use ayllu_api::xmpp_capnp::server::Client as XmppClient;
5744+ use ayllu_database::Wrapper as Database;
5745+ use ayllu_git::{ChatKind, Commit, Config as GitConfig, TreeEntry, Wrapper};
5746+
5747+ const README_FILES: [&str; 6] = [
5748+ "readme.md",
5749+ "README.md",
5750+ "readme",
5751+ "README",
5752+ "readme.txt",
5753+ "README.txt",
5754+ ];
5755+
5756+ /// materialized view with data needed to populate the repo page
5757+ #[derive(Default)]
5758+ struct View {
5759+ pub commit_count: i32,
5760+ pub authors: Vec<(String, String, i64)>,
5761+ pub buckets: Vec<(i64, i64, i64)>,
5762+ pub languages: Vec<(String, u8, u32)>,
5763+ }
5764+
5765+ async fn make_view(
5766+ db: &Database,
5767+ repo_path: &str,
5768+ commit_hash: Option<String>,
5769+ ) -> Result<View, Error> {
5770+ match commit_hash {
5771+ Some(commit_hash) => Ok(View {
5772+ commit_count: db.count_commits(repo_path, &commit_hash).await?,
5773+ buckets: db
5774+ .contribution_buckets_for_repo(
5775+ repo_path,
5776+ &commit_hash,
5777+ Aggregation::Day,
5778+ 90 * Duration::DAY,
5779+ )
5780+ .await?,
5781+ authors: db.contributors_list(repo_path, &commit_hash).await?,
5782+ languages: db.languages_list(repo_path, &commit_hash).await?,
5783+ }),
5784+ None => Ok(View::default()),
5785+ }
5786+ }
5787+
5788+ struct ItemBuilder {
5789+ base_url: PathBuf,
5790+ file_path: Option<PathBuf>,
5791+ commitish: String,
5792+ }
5793+
5794+ impl Filter for ItemBuilder {
5795+ fn filter(
5796+ &self,
5797+ value: &serde_json::Value,
5798+ args: &std::collections::HashMap<String, serde_json::Value>,
5799+ ) -> tera::Result<serde_json::Value> {
5800+ // TODO: should clean this up somehow
5801+ let kind = args.get("kind").unwrap().as_str().unwrap();
5802+ let kind = kind.to_lowercase();
5803+ let kind = if kind == "pointer" || kind == "submodule" {
5804+ "blob".to_string()
5805+ } else {
5806+ kind.clone()
5807+ };
5808+ let mut url = self.base_url.clone();
5809+ url.push(kind.clone());
5810+ url.push(self.commitish.clone());
5811+ match &self.file_path {
5812+ Some(fp) => {
5813+ let fp = fp.to_str().unwrap();
5814+ let fp = fp.trim_start_matches('/');
5815+ url.push(fp)
5816+ }
5817+ None => {}
5818+ }
5819+ let value = value.as_str().unwrap();
5820+ url.push(value);
5821+ let url = url.to_str().unwrap().to_string();
5822+ Ok(to_value(url).unwrap())
5823+ }
5824+ }
5825+
5826+ #[derive(Serialize, Clone)]
5827+ pub struct ChatLink {
5828+ pub kind: String,
5829+ pub url: String,
5830+ pub description: Option<String>,
5831+ pub users_online: Option<i64>,
5832+ }
5833+
5834+ async fn managed_chats(cfg: &Config, git_cfg: &GitConfig, initiator: Initiator) -> Vec<ChatLink> {
5835+ let managed_xmpp_channels: Vec<String> = cfg.xmpp.clone().map_or(Vec::new(), |link| {
5836+ link.channels.iter().map(|item| item.jid.clone()).collect()
5837+ });
5838+ let mut links: Vec<ChatLink> = Vec::new();
5839+ if git_cfg.chat.is_some() {
5840+ let mut to_lookup: Vec<String> = Vec::new();
5841+ for link in git_cfg.chat.clone().unwrap() {
5842+ match link.kind {
5843+ ChatKind::Xmpp => {
5844+ if managed_xmpp_channels.contains(&link.url) {
5845+ to_lookup.push(link.url.clone())
5846+ } else {
5847+ links.push(ChatLink {
5848+ kind: format!("{:?}", link.kind),
5849+ url: link.url.clone(),
5850+ description: None,
5851+ users_online: None,
5852+ })
5853+ };
5854+ }
5855+ ChatKind::Irc => links.push(ChatLink {
5856+ kind: format!("{:?}", link.kind),
5857+ url: link.url.clone(),
5858+ description: None,
5859+ users_online: None,
5860+ }),
5861+ }
5862+ }
5863+ let xmpp_client = initiator.client(InitiatorKind::Xmpp);
5864+ if let Some(xmpp_client) = xmpp_client {
5865+ match xmpp_client
5866+ .invoke(move |client: XmppClient| async move {
5867+ let mut remote_channels: Vec<ChatLink> = Vec::new();
5868+ let mut request = client.stats_request();
5869+ let mut channels = request
5870+ .get()
5871+ .init_channels(managed_xmpp_channels.len() as u32);
5872+ for (i, lookup) in to_lookup.iter().enumerate() {
5873+ channels.set(i as u32, lookup.as_str().into());
5874+ }
5875+ let status = request.send().promise.await?;
5876+ let results = status.get()?;
5877+ let stats = results.get_stats()?;
5878+ for stat in stats {
5879+ remote_channels.push(ChatLink {
5880+ kind: format!("{:?}", ChatKind::Xmpp),
5881+ url: stat.get_name()?.to_string().unwrap(),
5882+ description: None,
5883+ users_online: Some(stat.get_users_online()),
5884+ })
5885+ }
5886+ Ok(remote_channels)
5887+ })
5888+ .await
5889+ {
5890+ Ok(remote_links) => {
5891+ links.extend(remote_links);
5892+ }
5893+ Err(err) => {
5894+ log::error!(
5895+ "failed to resolve discussion group status: {:?}",
5896+ err.to_string()
5897+ )
5898+ }
5899+ };
5900+ }
5901+ }
5902+ links
5903+ }
5904+
5905+ #[derive(Serialize, Clone)]
5906+ pub struct EmailLink {
5907+ pub url: String,
5908+ pub description: Option<String>,
5909+ }
5910+
5911+ #[debug_handler]
5912+ pub async fn serve(
5913+ uri: OriginalUri,
5914+ Extension(cfg): Extension<Config>,
5915+ Extension(initiator): Extension<Initiator>,
5916+ Extension(db): Extension<Arc<Database>>,
5917+ Extension(preamble): Extension<Preamble>,
5918+ Extension(adapter): Extension<TreeSitterAdapter>,
5919+ Extension((mut templates, mut ctx)): Extension<Template>,
5920+ ) -> Result<Html<String>, Error> {
5921+ let repository = Wrapper::new(preamble.repo_path.as_path())?;
5922+
5923+ let license = license::detect(&repository, preamble.latest_commit_id.clone())?;
5924+
5925+ let tree = repository.tree(
5926+ preamble.file_path.clone(),
5927+ preamble.latest_commit_id.clone(),
5928+ )?;
5929+
5930+ let recent_commits = repository.resolve_tree(
5931+ preamble.file_path.clone(),
5932+ &tree,
5933+ preamble.latest_commit_id.clone(),
5934+ )?;
5935+
5936+ let entries_with_commits: Vec<(&TreeEntry, &Commit)> =
5937+ tree.iter().zip(recent_commits.iter()).collect();
5938+
5939+ // attempt to render readme if a suitable file is present
5940+
5941+ let mut readme: Option<String> = None;
5942+
5943+ let mut rendered_file_name: Option<String> = None;
5944+
5945+ for path in README_FILES {
5946+ let mut readme_path = preamble.file_path.clone().unwrap_or(PathBuf::new());
5947+ readme_path.push(path);
5948+ if let Some(content) =
5949+ repository.read_string(&readme_path, preamble.latest_commit_id.clone())?
5950+ {
5951+ rendered_file_name = Some(path.to_string());
5952+ readme = Some(content);
5953+ break;
5954+ }
5955+ }
5956+
5957+ let chat_links = managed_chats(&cfg, &preamble.config, initiator).await;
5958+ ctx.insert("chat_links", &chat_links);
5959+
5960+ // find any associated mailing lists associated with this repository
5961+ /*
5962+ let email_links = preamble.config.clone().mail.map(|mail| {
5963+ let links: Vec<EmailLink> = mail
5964+ .iter()
5965+ .map(|address| {
5966+ let git_address = address.0.clone();
5967+ let description = cfg
5968+ .mail
5969+ .clone()
5970+ .and_then(|mail_cfg| {
5971+ mail_cfg
5972+ .lists
5973+ .iter()
5974+ .find(|entry| entry.address == git_address)
5975+ .map(|entry| entry.address.clone())
5976+ });
5977+ EmailLink {
5978+ url: address.0.clone(),
5979+ description,
5980+ }
5981+ })
5982+ .collect();
5983+ links
5984+ });
5985+
5986+ ctx.insert("email_links", &email_links);
5987+ */
5988+
5989+ let materialized = make_view(
5990+ &db,
5991+ &preamble.repo_path_string(),
5992+ preamble.latest_commit_id.clone(),
5993+ )
5994+ .await?;
5995+ let chart_builder = charts::Renderer::new((320, 250));
5996+
5997+ let mut plugins = ComrakPlugins::default();
5998+ plugins.render.codefence_syntax_highlighter = Some(&adapter);
5999+
6000+ let readme = readme.map_or(String::new(), |readme| {
6001+ markdown_to_html_with_plugins(readme.as_str(), &cfg.markdown_render_options(), &plugins)
6002+ });
6003+
6004+ // Merge everything together and render
6005+ templates.register_filter(
6006+ "make_url",
6007+ ItemBuilder {
6008+ base_url: PathBuf::from(format!(
6009+ "/{}/{}",
6010+ preamble.collection_name, preamble.repo_name
6011+ )),
6012+ file_path: preamble.file_path.clone(),
6013+ commitish: preamble.refname.clone(),
6014+ },
6015+ );
6016+ ctx.insert("title", &preamble.repo_name);
6017+ ctx.insert("sites_url", &preamble.config.homepage);
6018+ ctx.insert("readme", &readme);
6019+ ctx.insert("file_path", &preamble.file_path_string());
6020+ ctx.insert(
6021+ "activity_chart",
6022+ &chart_builder.activity(materialized.buckets)?,
6023+ );
6024+ ctx.insert(
6025+ "language_chart",
6026+ &chart_builder.languages(materialized.languages)?,
6027+ );
6028+ ctx.insert("tree", &entries_with_commits);
6029+ ctx.insert("refname", &preamble.refname.clone());
6030+ ctx.insert("show_details", &preamble.file_path.is_none());
6031+ ctx.insert(
6032+ "nav_elements",
6033+ &navigation::primary(
6034+ "project",
6035+ &preamble.collection_name,
6036+ &preamble.repo_name,
6037+ ),
6038+ );
6039+ ctx.insert("commit_count", &materialized.commit_count);
6040+ ctx.insert("latest_commit", &preamble.latest_commit);
6041+ ctx.insert("authors", &materialized.authors);
6042+ ctx.insert("authors_count", &0);
6043+ ctx.insert("rendered_file_name", &rendered_file_name);
6044+ ctx.insert(
6045+ "rss_link_all",
6046+ &util::select_path(&uri, Some(0), None).map(|mut path| {
6047+ path.push("rss/firehose.xml");
6048+ path.to_str().unwrap().to_string()
6049+ }),
6050+ );
6051+ ctx.insert(
6052+ "rss_link_1d",
6053+ &util::select_path(&uri, Some(0), None).map(|mut path| {
6054+ path.push("rss/1d.xml");
6055+ path.to_str().unwrap().to_string()
6056+ }),
6057+ );
6058+ ctx.insert(
6059+ "rss_link_1w",
6060+ &util::select_path(&uri, Some(0), None).map(|mut path| {
6061+ path.push("rss/1w.xml");
6062+ path.to_str().unwrap().to_string()
6063+ }),
6064+ );
6065+ ctx.insert(
6066+ "rss_link_1m",
6067+ &util::select_path(&uri, Some(0), None).map(|mut path| {
6068+ path.push("rss/1m.xml");
6069+ path.to_str().unwrap().to_string()
6070+ }),
6071+ );
6072+ ctx.insert(
6073+ "http_clone_url",
6074+ &format!(
6075+ "{}/{}/{}",
6076+ cfg.origin, preamble.collection_name, preamble.repo_name
6077+ ),
6078+ );
6079+ let git_clone_url = match cfg.git_clone_url {
6080+ Some(base) => {
6081+ let clone_url = format!(
6082+ "{}:{}/{}.git",
6083+ base, preamble.collection_name, preamble.repo_name,
6084+ );
6085+ Some(clone_url)
6086+ }
6087+ None => None,
6088+ };
6089+ ctx.insert("git_clone_url", &git_clone_url);
6090+ match license {
6091+ Some(license) => {
6092+ ctx.insert("license", &license);
6093+ }
6094+ None => {
6095+ ctx.insert("license", &String::new());
6096+ }
6097+ };
6098+
6099+ let body = templates.render("repo.html", &ctx)?;
6100+ Ok(Html(body))
6101+ }
6102 diff --git a/ayllu/src/web2/routes/robots.rs b/ayllu/src/web2/routes/robots.rs
6103new file mode 100644
6104index 0000000..fe40c5e
6105--- /dev/null
6106+++ b/ayllu/src/web2/routes/robots.rs
6107 @@ -0,0 +1,15 @@
6108+ use axum::{response::IntoResponse, Extension};
6109+
6110+ use crate::config::Config;
6111+ use crate::web2::error::Error;
6112+
6113+ pub async fn serve(Extension(cfg): Extension<Config>) -> Result<impl IntoResponse, Error> {
6114+ let robots_txt = cfg.sysadmin.clone().map_or(cfg.robots.clone(), |sysadmin| {
6115+ format!(
6116+ "# contact {} with questions\n{}",
6117+ sysadmin,
6118+ cfg.robots.clone()
6119+ )
6120+ });
6121+ Ok(robots_txt)
6122+ }
6123 diff --git a/ayllu/src/web2/routes/rss.rs b/ayllu/src/web2/routes/rss.rs
6124new file mode 100644
6125index 0000000..ee16af4
6126--- /dev/null
6127+++ b/ayllu/src/web2/routes/rss.rs
6128 @@ -0,0 +1,751 @@
6129+ use std::path::PathBuf;
6130+
6131+ use axum::{
6132+ body::Body,
6133+ extract::{Extension, OriginalUri},
6134+ http::header::CONTENT_TYPE,
6135+ response::Response,
6136+ };
6137+ use mime::TEXT_XML;
6138+ use rss::{Channel, Guid, Item};
6139+ use serde::Serialize;
6140+ use tera::{Context, Tera};
6141+ use time::{format_description::well_known::Rfc2822, macros::time, Duration, OffsetDateTime};
6142+
6143+ use crate::config::{Collection, Config};
6144+ use crate::web2::error::Error;
6145+ use crate::web2::middleware::repository::Preamble;
6146+ use crate::web2::middleware::template::Template;
6147+ use crate::web2::util;
6148+ use ayllu_git::{Commit, Scanner, Tag, Wrapper};
6149+
6150+ fn stylesheet_hack(input: String) -> String {
6151+ const ORIGINAL: &str = r#"<?xml version="1.0" encoding="utf-8"?>"#;
6152+ const REPLACEMENT: &str = r#"<?xml version="1.0" encoding="utf-8"?><?xml-stylesheet href="/static/assets/feed.xsl" type="text/xsl"?>"#;
6153+ input.replacen(ORIGINAL, REPLACEMENT, 1)
6154+ }
6155+
6156+ fn html_commit(summary: String, message: String) -> String {
6157+ if summary == message.trim_end() {
6158+ message
6159+ } else {
6160+ format!(
6161+ "<pre>{}\n{}</pre>",
6162+ summary,
6163+ message.replacen(&summary, "", 1)
6164+ )
6165+ }
6166+ }
6167+
6168+ /// Timeframe specifies a chunk of time to summarize repository activity for.
6169+ #[derive(Clone, Copy)]
6170+ enum Timeframe {
6171+ Daily,
6172+ Weekly,
6173+ Monthly,
6174+ }
6175+
6176+ impl Timeframe {
6177+ /// "Clamp" a time period for the given timeframe, for instance the WEEKLY
6178+ /// timeframe for the current time of 2021-03-05 13:00:55 UTC will be
6179+ /// 2021-02-21 00:00:00 UTC -> 2021-02-28 00:00:00 UTC which is the previous
6180+ /// 7 day week from the "current" week.
6181+ pub fn clamp(self, current_time: OffsetDateTime) -> (OffsetDateTime, OffsetDateTime) {
6182+ match self {
6183+ Timeframe::Daily => {
6184+ // today at midnight
6185+ let end_time = current_time.replace_time(time!(00:00:00));
6186+ // 00:00:00 two days ago
6187+ let start_time = end_time.saturating_sub(24 * Duration::HOUR);
6188+ (start_time, end_time)
6189+ }
6190+ Timeframe::Weekly => {
6191+ // today at midnight
6192+ let current_time = current_time.replace_time(time!(00:00:00));
6193+ // days to previous sunday
6194+ let days_to_sunday = current_time.weekday().number_days_from_sunday();
6195+ let end_time = current_time.saturating_sub(days_to_sunday * Duration::DAY);
6196+ // one week prior to previous sunday
6197+ let start_time = end_time.saturating_sub(7 * Duration::DAY);
6198+ (start_time, end_time)
6199+ }
6200+ Timeframe::Monthly => {
6201+ // today at midnight
6202+ let current_time = current_time.replace_time(time!(00:00:00));
6203+ let days_to_previous_month = current_time.date().day();
6204+ let end_time = current_time.saturating_sub(days_to_previous_month * Duration::DAY);
6205+ let days_to_previous_month = end_time.date().day();
6206+ let start_time =
6207+ end_time.saturating_sub((days_to_previous_month - 1) * Duration::DAY);
6208+ (start_time, end_time)
6209+ }
6210+ }
6211+ }
6212+ }
6213+
6214+ #[derive(Serialize)]
6215+ struct Data {
6216+ name: String,
6217+ tags: Vec<Tag>,
6218+ commits: Vec<Commit>,
6219+ }
6220+
6221+ /// Channel builder for RSS feeds for on all repositories hosted on the server.
6222+ struct Builder {
6223+ templates: Tera,
6224+ context: Context,
6225+ origin: String,
6226+ title: String,
6227+ time_to_live: Option<Duration>,
6228+ current_time: OffsetDateTime,
6229+ }
6230+
6231+ impl Builder {
6232+ fn scan_repositories(
6233+ &self,
6234+ collections: Vec<Collection>,
6235+ ) -> Result<Vec<(PathBuf, String)>, Error> {
6236+ let mut entries: Vec<(PathBuf, String)> = Vec::new();
6237+ for collection in collections.iter() {
6238+ // NOTE: hide hidden collections from RSS feeds
6239+ if collection.hidden.is_some_and(|hidden| hidden) {
6240+ continue;
6241+ }
6242+ for repository_path in Scanner::from_path(&collection.path)? {
6243+ let file_name = repository_path.file_name().unwrap();
6244+ let name = file_name.to_str().unwrap();
6245+ entries.push((
6246+ repository_path.clone(),
6247+ format!("{}/{}/{}", self.origin, collection.name, name),
6248+ ));
6249+ }
6250+ }
6251+ Ok(entries)
6252+ }
6253+
6254+ fn channel(&self, items: Vec<Item>) -> Channel {
6255+ let mut channel = Channel::default();
6256+ channel.set_title(self.title.to_string());
6257+ channel.set_last_build_date(items.last().map(|item| {
6258+ item.pub_date()
6259+ .map_or(String::new(), |date| date.to_string())
6260+ }));
6261+ channel.set_items(items);
6262+ if let Some(ttl) = self.time_to_live {
6263+ channel.set_ttl(ttl.whole_minutes().to_string());
6264+ }
6265+ channel
6266+ }
6267+
6268+ fn firehose(&self, entries: Vec<(PathBuf, String)>, since: Duration) -> Result<Channel, Error> {
6269+ let start = self.current_time.saturating_sub(since);
6270+ let mut items: Vec<(Item, i64)> = Vec::new();
6271+ for entry in entries {
6272+ let repository = Wrapper::new(entry.0.as_path())?;
6273+ let config = repository.config()?;
6274+ if config.hidden.is_some_and(|hidden| hidden) {
6275+ continue;
6276+ };
6277+ for tag in repository.tags_range(Some((
6278+ start.unix_timestamp(),
6279+ self.current_time.unix_timestamp(),
6280+ )))? {
6281+ let mut item = rss::Item::default();
6282+ // FIXME: need a tag page before we can set a GUID/link
6283+ item.set_title(format!("Tag: {}", tag.name));
6284+ item.set_description(tag.summary);
6285+ item.set_author(tag.author_name);
6286+ items.push((item, tag.commit.epoch));
6287+ }
6288+ for commit in repository.commits_range(
6289+ None,
6290+ Some((start.unix_timestamp(), self.current_time.unix_timestamp())),
6291+ )? {
6292+ let mut item = rss::Item::default();
6293+ let link = format!("{}/commit/{}", entry.1, commit.id);
6294+ item.set_title(format!("Commit: {}", commit.summary));
6295+ item.set_author(commit.author_name);
6296+ item.set_link(Some(link.clone()));
6297+ item.set_guid(Some(Guid {
6298+ permalink: true,
6299+ value: link,
6300+ }));
6301+ item.set_description(html_commit(commit.summary, commit.message));
6302+ let pub_date = OffsetDateTime::from_unix_timestamp(commit.epoch).unwrap();
6303+ item.set_pub_date(pub_date.format(&Rfc2822).unwrap());
6304+ items.push((item, commit.epoch))
6305+ }
6306+ }
6307+ // reorder everything by time
6308+ items.sort_by(|a, b| a.1.cmp(&b.1));
6309+ items.reverse();
6310+ Ok(self.channel(items.iter().map(|item| item.0.clone()).collect()))
6311+ }
6312+
6313+ fn summary(
6314+ &self,
6315+ entries: Vec<(PathBuf, String)>,
6316+ timeframe: Timeframe,
6317+ ) -> Result<Channel, Error> {
6318+ let (start, end) = timeframe.clamp(self.current_time);
6319+ let mut items: Vec<Item> = Vec::new();
6320+ let mut all_data: Vec<Data> = Vec::new();
6321+ for entry in entries {
6322+ let repository = Wrapper::new(entry.0.as_path())?;
6323+ let config = repository.config()?;
6324+ if config.hidden.is_some_and(|hidden| hidden) {
6325+ continue;
6326+ };
6327+ let tags =
6328+ repository.tags_range(Some((start.unix_timestamp(), end.unix_timestamp())))?;
6329+ let commits = repository
6330+ .commits_range(None, Some((start.unix_timestamp(), end.unix_timestamp())))?;
6331+ if !tags.is_empty() || !commits.is_empty() {
6332+ all_data.push(Data {
6333+ name: repository.name(),
6334+ tags,
6335+ commits,
6336+ });
6337+ }
6338+ }
6339+
6340+ // don't generate a new rss item if there is nothing within it
6341+ if all_data.is_empty() {
6342+ return Ok(self.channel(vec![]));
6343+ }
6344+
6345+ let mut ctx = self.context.clone();
6346+
6347+ ctx.insert("origin", &self.origin);
6348+ ctx.insert("start_date", &start.date().to_string());
6349+ ctx.insert("end_date", &end.date().to_string());
6350+
6351+ let n_tags = all_data
6352+ .iter()
6353+ .fold(0, |accm, entries| accm + entries.tags.len());
6354+ ctx.insert("n_tags", &n_tags);
6355+ let n_commits = all_data
6356+ .iter()
6357+ .fold(0, |accm, entries| accm + entries.commits.len());
6358+ ctx.insert("n_commits", &n_commits);
6359+ ctx.insert("n_projects", &all_data.len());
6360+ ctx.insert("entries", &all_data);
6361+
6362+ let description = self.templates.render("rss_summary.html", &ctx).unwrap();
6363+ let mut item = rss::Item::default();
6364+ item.set_description(Some(description));
6365+ item.set_guid(Some(Guid {
6366+ value: format!(
6367+ "summary-{}-{}",
6368+ start.unix_timestamp(),
6369+ end.unix_timestamp()
6370+ ),
6371+ permalink: false,
6372+ }));
6373+ items.push(item);
6374+ Ok(self.channel(items))
6375+ }
6376+ }
6377+
6378+ pub async fn feed_firehose(
6379+ Extension(cfg): Extension<Config>,
6380+ Extension((templates, context)): Extension<Template>,
6381+ ) -> Result<Response, Error> {
6382+ let builder = Builder {
6383+ templates,
6384+ context,
6385+ origin: cfg.origin,
6386+ title: String::from("Firehose"),
6387+ time_to_live: cfg.rss_time_to_live.map(Duration::seconds),
6388+ current_time: OffsetDateTime::now_utc(),
6389+ };
6390+ let channel = builder.firehose(
6391+ builder.scan_repositories(cfg.collections)?,
6392+ Duration::days(7),
6393+ )?;
6394+ let response = Response::builder()
6395+ .header(CONTENT_TYPE, TEXT_XML.as_ref())
6396+ .body(Body::new(stylesheet_hack(channel.to_string())))
6397+ .unwrap();
6398+ Ok(response)
6399+ }
6400+
6401+ pub async fn feed_1d(
6402+ Extension(cfg): Extension<Config>,
6403+ Extension((templates, context)): Extension<Template>,
6404+ ) -> Result<Response, Error> {
6405+ let builder = Builder {
6406+ templates,
6407+ context,
6408+ origin: cfg.origin,
6409+ title: String::from("Daily Update Summary"),
6410+ time_to_live: cfg.rss_time_to_live.map(Duration::seconds),
6411+ current_time: OffsetDateTime::now_utc(),
6412+ };
6413+ let channel = builder.summary(
6414+ builder.scan_repositories(cfg.collections)?,
6415+ Timeframe::Daily,
6416+ )?;
6417+ let response = Response::builder()
6418+ .header(CONTENT_TYPE, TEXT_XML.as_ref())
6419+ .body(Body::new(stylesheet_hack(channel.to_string())))
6420+ .unwrap();
6421+ Ok(response)
6422+ }
6423+
6424+ pub async fn feed_1w(
6425+ Extension(cfg): Extension<Config>,
6426+ Extension((templates, context)): Extension<Template>,
6427+ ) -> Result<Response, Error> {
6428+ let builder = Builder {
6429+ templates,
6430+ context,
6431+ origin: cfg.origin,
6432+ title: String::from("Weekly Update Summary"),
6433+ time_to_live: cfg.rss_time_to_live.map(Duration::seconds),
6434+ current_time: OffsetDateTime::now_utc(),
6435+ };
6436+ let channel = builder.summary(
6437+ builder.scan_repositories(cfg.collections)?,
6438+ Timeframe::Weekly,
6439+ )?;
6440+ let response = Response::builder()
6441+ .header(CONTENT_TYPE, TEXT_XML.as_ref())
6442+ .body(Body::new(stylesheet_hack(channel.to_string())))
6443+ .unwrap();
6444+ Ok(response)
6445+ }
6446+
6447+ pub async fn feed_1m(
6448+ Extension(cfg): Extension<Config>,
6449+ Extension((templates, context)): Extension<Template>,
6450+ ) -> Result<Response, Error> {
6451+ let builder = Builder {
6452+ templates,
6453+ context,
6454+ origin: cfg.origin,
6455+ title: String::from("Monthly Update Summary"),
6456+ time_to_live: cfg.rss_time_to_live.map(Duration::seconds),
6457+ current_time: OffsetDateTime::now_utc(),
6458+ };
6459+ let channel = builder.summary(
6460+ builder.scan_repositories(cfg.collections)?,
6461+ Timeframe::Monthly,
6462+ )?;
6463+ let response = Response::builder()
6464+ .header(CONTENT_TYPE, TEXT_XML.as_ref())
6465+ .body(Body::new(stylesheet_hack(channel.to_string())))
6466+ .unwrap();
6467+ Ok(response)
6468+ }
6469+
6470+ pub async fn feed_repository_firehose(
6471+ uri: OriginalUri,
6472+ Extension(cfg): Extension<Config>,
6473+ Extension(preamble): Extension<Preamble>,
6474+ Extension((templates, context)): Extension<Template>,
6475+ ) -> Result<Response, Error> {
6476+ let project_url = util::project_url(&uri, cfg.origin.as_str());
6477+ let builder = Builder {
6478+ templates,
6479+ context,
6480+ origin: cfg.origin,
6481+ title: format!(
6482+ "Firehose for {}/{}",
6483+ preamble.collection_name, preamble.repo_name
6484+ ),
6485+ time_to_live: cfg.rss_time_to_live.map(Duration::seconds),
6486+ current_time: OffsetDateTime::now_utc(),
6487+ };
6488+ let channel = builder.firehose(vec![(preamble.repo_path, project_url)], Duration::days(7))?;
6489+ let response = Response::builder()
6490+ .header(CONTENT_TYPE, TEXT_XML.as_ref())
6491+ .body(Body::new(stylesheet_hack(channel.to_string())))
6492+ .unwrap();
6493+ Ok(response)
6494+ }
6495+
6496+ pub async fn feed_repository_1d(
6497+ uri: OriginalUri,
6498+ Extension(cfg): Extension<Config>,
6499+ Extension(preamble): Extension<Preamble>,
6500+ Extension((templates, context)): Extension<Template>,
6501+ ) -> Result<Response, Error> {
6502+ let project_url = util::project_url(&uri, cfg.origin.as_str());
6503+ let builder = Builder {
6504+ templates,
6505+ context,
6506+ origin: cfg.origin,
6507+ title: format!(
6508+ "Daily Update Summary for {}/{}",
6509+ preamble.collection_name, preamble.repo_name
6510+ ),
6511+ time_to_live: cfg.rss_time_to_live.map(Duration::seconds),
6512+ current_time: OffsetDateTime::now_utc(),
6513+ };
6514+ let channel = builder.summary(vec![(preamble.repo_path, project_url)], Timeframe::Daily)?;
6515+ let response = Response::builder()
6516+ .header(CONTENT_TYPE, TEXT_XML.as_ref())
6517+ .body(Body::new(stylesheet_hack(channel.to_string())))
6518+ .unwrap();
6519+ Ok(response)
6520+ }
6521+
6522+ pub async fn feed_repository_1w(
6523+ uri: OriginalUri,
6524+ Extension(cfg): Extension<Config>,
6525+ Extension(preamble): Extension<Preamble>,
6526+ Extension((templates, context)): Extension<Template>,
6527+ ) -> Result<Response, Error> {
6528+ let project_url = util::project_url(&uri, cfg.origin.as_str());
6529+ let builder = Builder {
6530+ templates,
6531+ context,
6532+ origin: cfg.origin,
6533+ title: format!(
6534+ "Weekly Update Summary for {}/{}",
6535+ preamble.collection_name, preamble.repo_name
6536+ ),
6537+ time_to_live: cfg.rss_time_to_live.map(Duration::seconds),
6538+ current_time: OffsetDateTime::now_utc(),
6539+ };
6540+ let channel = builder.summary(vec![(preamble.repo_path, project_url)], Timeframe::Weekly)?;
6541+ let response = Response::builder()
6542+ .header(CONTENT_TYPE, TEXT_XML.as_ref())
6543+ .body(Body::new(stylesheet_hack(channel.to_string())))
6544+ .unwrap();
6545+ Ok(response)
6546+ }
6547+
6548+ pub async fn feed_repository_1m(
6549+ uri: OriginalUri,
6550+ Extension(cfg): Extension<Config>,
6551+ Extension(preamble): Extension<Preamble>,
6552+ Extension((templates, context)): Extension<Template>,
6553+ ) -> Result<Response, Error> {
6554+ let project_url = util::project_url(&uri, cfg.origin.as_str());
6555+ let builder = Builder {
6556+ templates,
6557+ context,
6558+ origin: cfg.origin,
6559+ title: format!(
6560+ "Monthly Update Summary for {}/{}",
6561+ preamble.collection_name, preamble.repo_name
6562+ ),
6563+ time_to_live: cfg.rss_time_to_live.map(Duration::seconds),
6564+ current_time: OffsetDateTime::now_utc(),
6565+ };
6566+ let channel = builder.summary(vec![(preamble.repo_path, project_url)], Timeframe::Monthly)?;
6567+ let response = Response::builder()
6568+ .header(CONTENT_TYPE, TEXT_XML.as_ref())
6569+ .body(Body::new(stylesheet_hack(channel.to_string())))
6570+ .unwrap();
6571+ Ok(response)
6572+ }
6573+
6574+ #[cfg(test)]
6575+ mod tests {
6576+
6577+ use super::*;
6578+ use tera::Tera;
6579+ use time::{format_description::well_known::Rfc2822, macros::datetime, OffsetDateTime};
6580+
6581+ use ayllu_git::testing;
6582+
6583+ // Thu, 07 Apr 2005 22:13:13 +0200
6584+
6585+ /// all tests assume the current time is as below
6586+ const CURRENT_TIME: &str = "Tue, 19 Dec 2023 20:55:10 +0000";
6587+
6588+ #[test]
6589+ fn test_firehose_commits() {
6590+ let mut test_repo = testing::Builder::default().with_commands(vec![
6591+ // an old commit to be filtered out
6592+ format!(
6593+ "echo 'content' > file_1.txt && git add file_1.txt && {} git commit -m 'commit 1'",
6594+ testing::timestamp_envs("Tue, 14 Dec 2023 20:55:10 +0000")
6595+ )
6596+ .as_str(),
6597+ format!(
6598+ "echo 'content' > file_2.txt && git add file_2.txt && {} git commit -m 'commit 2'",
6599+ testing::timestamp_envs("Tue, 19 Dec 2023 20:00:00 +0000")
6600+ )
6601+ .as_str(),
6602+ format!(
6603+ "echo 'content' > file_3.txt && git add file_3.txt && {} git commit -m 'commit 3'",
6604+ testing::timestamp_envs("Tue, 19 Dec 2023 20:01:00 +0000")
6605+ )
6606+ .as_str(),
6607+ ]);
6608+ let (name, path) = test_repo.build().expect("failed to init repo");
6609+ let templates =
6610+ Tera::new("themes/default/templates/*.html").expect("failed to load templates");
6611+ let context = Context::new();
6612+ let builder = Builder {
6613+ templates,
6614+ context,
6615+ origin: String::from("localhost:8080"),
6616+ title: String::from("test"),
6617+ time_to_live: Some(Duration::HOUR),
6618+ current_time: OffsetDateTime::parse(CURRENT_TIME, &Rfc2822).unwrap(),
6619+ };
6620+ let channel = builder
6621+ .firehose(vec![(path, name)], Duration::days(1))
6622+ .expect("failed to build items");
6623+ assert!(channel.items.len() == 2);
6624+ assert!(channel.items[0]
6625+ .title
6626+ .as_ref()
6627+ .is_some_and(|title| title == "Commit: commit 3"));
6628+ assert!(channel.items[1]
6629+ .title
6630+ .as_ref()
6631+ .is_some_and(|title| title == "Commit: commit 2"));
6632+ test_repo.cleanup().expect("failed to cleanup repo");
6633+ }
6634+
6635+ #[test]
6636+ fn test_firehose_releases() {
6637+ let mut test_repo = testing::Builder::default().with_commands(vec![
6638+ format!(
6639+ "echo 'content' > file_1.txt && git add file_1.txt && {} git commit -m 'commit 1'",
6640+ testing::timestamp_envs("Tue Dec 19 20:00:00 2023 +0000")
6641+ )
6642+ .as_str(),
6643+ format!(
6644+ "echo 'content' > file_2.txt && git add file_2.txt && {} git commit -m 'commit 2'",
6645+ testing::timestamp_envs("Tue Dec 19 20:01:00 2023 +0000")
6646+ )
6647+ .as_str(),
6648+ // release a new version
6649+ format!(
6650+ "{} git tag -m 'release version 0.0.1!' v0.0.1",
6651+ testing::timestamp_envs("Tue Dec 19 20:02:00 2023 +0000")
6652+ )
6653+ .as_str(),
6654+ ]);
6655+ let (name, path) = test_repo.build().expect("failed to init repo");
6656+ let templates =
6657+ Tera::new("themes/default/templates/*.html").expect("failed to load templates");
6658+ let context = Context::new();
6659+ let builder = Builder {
6660+ templates,
6661+ context,
6662+ origin: String::from("localhost:8080"),
6663+ title: String::from("test"),
6664+ time_to_live: Some(Duration::HOUR),
6665+ current_time: OffsetDateTime::parse(CURRENT_TIME, &Rfc2822).unwrap(),
6666+ };
6667+ let channel = builder
6668+ .firehose(vec![(path, name)], Duration::days(1))
6669+ .expect("failed to build items");
6670+ assert!(channel.items.len() == 3);
6671+ assert!(channel.items[0]
6672+ .title
6673+ .as_ref()
6674+ .is_some_and(|title| title == "Commit: commit 2"));
6675+ assert!(channel.items[1]
6676+ .title
6677+ .as_ref()
6678+ .is_some_and(|title| title == "Tag: v0.0.1"));
6679+ assert!(channel.items[1]
6680+ .description
6681+ .as_ref()
6682+ .is_some_and(|description| description == "release version 0.0.1!\n"));
6683+ assert!(channel.items[2]
6684+ .title
6685+ .as_ref()
6686+ .is_some_and(|title| title == "Commit: commit 1"));
6687+ assert!(channel.items[0].guid.is_some());
6688+ // FIXME: assert!(channel.items[1].guid.is_some());
6689+ assert!(channel.items[2].guid.is_some());
6690+ test_repo.cleanup().expect("failed to cleanup repo");
6691+ }
6692+
6693+ #[test]
6694+ fn test_feed_1d() {
6695+ let mut test_repo = testing::Builder::default().with_commands(vec![
6696+ // older commit which is filtered
6697+ format!(
6698+ "echo 'content' > file_1.txt && git add file_1.txt && {} git commit -m 'commit 1'",
6699+ testing::timestamp_envs("Tue Dec 16 20:00:00 2023 +0000")
6700+ )
6701+ .as_str(),
6702+ format!(
6703+ "echo 'content' > file_2.txt && git add file_2.txt && {} git commit -m 'commit 2'",
6704+ testing::timestamp_envs("Tue Dec 18 20:01:00 2023 +0000")
6705+ )
6706+ .as_str(),
6707+ format!(
6708+ "echo 'content' > file_3.txt && git add file_3.txt && {} git commit -m 'commit 3'",
6709+ testing::timestamp_envs("Tue Dec 18 20:02:00 2023 +0000")
6710+ )
6711+ .as_str(),
6712+ ]);
6713+ let (name, path) = test_repo.build().expect("failed to init repo");
6714+ let templates =
6715+ Tera::new("themes/default/templates/*.html").expect("failed to load templates");
6716+ let context = Context::new();
6717+ let builder = Builder {
6718+ templates,
6719+ context,
6720+ origin: String::from("localhost:8080"),
6721+ title: String::from("test"),
6722+ time_to_live: Some(Duration::HOUR),
6723+ current_time: OffsetDateTime::parse(CURRENT_TIME, &Rfc2822).unwrap(),
6724+ };
6725+ let channel = builder
6726+ .summary(vec![(path, name)], Timeframe::Daily)
6727+ .expect("failed to build items");
6728+ assert!(channel.items.len() == 1);
6729+ assert!(channel.items[0]
6730+ .description
6731+ .as_ref()
6732+ .is_some_and(|content| content.contains("commit 2") && content.contains("commit 3")));
6733+ assert!(channel.items[0].guid.is_some());
6734+ assert!(channel.ttl.as_ref().is_some_and(|ttl| ttl == "60"))
6735+ }
6736+
6737+ #[test]
6738+ fn test_feed_1w() {
6739+ let mut test_repo = testing::Builder::default().with_commands(vec![
6740+ format!(
6741+ "echo 'content' > file_1.txt && git add file_1.txt && {} git commit -m 'commit 1'",
6742+ testing::timestamp_envs("Tue Dec 15 20:00:00 2023 +0000")
6743+ )
6744+ .as_str(),
6745+ format!(
6746+ "echo 'content' > file_2.txt && git add file_2.txt && {} git commit -m 'commit 2'",
6747+ testing::timestamp_envs("Tue Dec 16 20:01:00 2023 +0000")
6748+ )
6749+ .as_str(),
6750+ ]);
6751+ let (name, path) = test_repo.build().expect("failed to init repo");
6752+ let templates =
6753+ Tera::new("themes/default/templates/*.html").expect("failed to load templates");
6754+ let context = Context::new();
6755+ let builder = Builder {
6756+ templates,
6757+ context,
6758+ origin: String::from("localhost:8080"),
6759+ title: String::from("test"),
6760+ time_to_live: Some(Duration::HOUR),
6761+ current_time: OffsetDateTime::parse(CURRENT_TIME, &Rfc2822).unwrap(),
6762+ };
6763+ let channel = builder
6764+ .summary(vec![(path, name)], Timeframe::Weekly)
6765+ .expect("failed to build items");
6766+ assert!(channel.items.len() == 1);
6767+ assert!(channel.items[0]
6768+ .description
6769+ .as_ref()
6770+ .is_some_and(|content| content.contains("commit 1") && content.contains("commit 2")));
6771+ assert!(channel.items[0].guid.is_some());
6772+ assert!(channel.ttl.as_ref().is_some_and(|ttl| ttl == "60"))
6773+ }
6774+
6775+ #[test]
6776+ fn test_feed_1m() {
6777+ let mut test_repo = testing::Builder::default().with_commands(vec![
6778+ format!(
6779+ "echo 'content' > file_1.txt && git add file_1.txt && {} git commit -m 'commit 1'",
6780+ testing::timestamp_envs("Tue Nov 23 00:01:00 2023 +0000")
6781+ )
6782+ .as_str(),
6783+ format!(
6784+ "echo 'content' > file_2.txt && git add file_2.txt && {} git commit -m 'commit 2'",
6785+ testing::timestamp_envs("Tue Nov 23 00:01:00 2023 +0000")
6786+ )
6787+ .as_str(),
6788+ // a recent commit to be filtered out
6789+ format!(
6790+ "echo 'content' > file_3.txt && git add file_3.txt && {} git commit -m 'commit 3'",
6791+ testing::timestamp_envs("Tue Dec 18 00:01:00 2023 +0000")
6792+ )
6793+ .as_str(),
6794+ ]);
6795+ let (name, path) = test_repo.build().expect("failed to init repo");
6796+ let templates =
6797+ Tera::new("themes/default/templates/*.html").expect("failed to load templates");
6798+ let context = Context::new();
6799+ let builder = Builder {
6800+ templates,
6801+ context,
6802+ origin: String::from("localhost:8080"),
6803+ title: String::from("test"),
6804+ time_to_live: Some(Duration::HOUR),
6805+ current_time: OffsetDateTime::parse(CURRENT_TIME, &Rfc2822).unwrap(),
6806+ };
6807+ let channel = builder
6808+ .summary(vec![(path, name)], Timeframe::Monthly)
6809+ .expect("failed to build items");
6810+ assert!(channel.items.len() == 1);
6811+ assert!(channel.items[0]
6812+ .description
6813+ .as_ref()
6814+ .is_some_and(|content| content.contains("commit 1") && content.contains("commit 2")));
6815+ assert!(channel.items[0].guid.is_some());
6816+ assert!(channel.ttl.as_ref().is_some_and(|ttl| ttl == "60"))
6817+ }
6818+
6819+ #[test]
6820+ fn test_feed_1d_no_commits() {
6821+ let mut test_repo = testing::Builder::default().with_commands(vec![
6822+ // older commit which is filtered
6823+ format!(
6824+ "echo 'content' > file_1.txt && git add file_1.txt && {} git commit -m 'commit 1'",
6825+ testing::timestamp_envs("Tue Dec 16 20:00:00 2023 +0000")
6826+ )
6827+ .as_str(),
6828+ ]);
6829+ let (name, path) = test_repo.build().expect("failed to init repo");
6830+ let templates =
6831+ Tera::new("themes/default/templates/*.html").expect("failed to load templates");
6832+ let context = Context::new();
6833+ let builder = Builder {
6834+ templates,
6835+ context,
6836+ origin: String::from("localhost:8080"),
6837+ title: String::from("test"),
6838+ time_to_live: Some(Duration::HOUR),
6839+ current_time: OffsetDateTime::parse(CURRENT_TIME, &Rfc2822).unwrap(),
6840+ };
6841+ let channel = builder
6842+ .summary(vec![(path, name)], Timeframe::Daily)
6843+ .expect("failed to build items");
6844+ assert!(channel.items.is_empty());
6845+ assert!(channel.ttl.as_ref().is_some_and(|ttl| ttl == "60"))
6846+ }
6847+
6848+ #[test]
6849+ fn test_timeframe_1d() {
6850+ let now = datetime!(2021-03-05 13:00:55 UTC);
6851+ let (start, end) = Timeframe::Daily.clamp(now);
6852+ assert!(start == datetime!(2021-03-04 00:00:00 UTC));
6853+ assert!(end == datetime!(2021-03-05 00:00:00 UTC));
6854+ }
6855+
6856+ #[test]
6857+ fn test_timeframe_1w() {
6858+ let now = datetime!(2021-03-05 13:00:55 UTC);
6859+ let (start, end) = Timeframe::Weekly.clamp(now);
6860+ assert!(start == datetime!(2021-02-21 00:00:00 UTC));
6861+ assert!(end == datetime!(2021-02-28 00:00:00 UTC));
6862+ let now = datetime!(2021-03-07 21:34:05 UTC);
6863+ let (start, end) = Timeframe::Weekly.clamp(now);
6864+ assert!(start == datetime!(2021-02-28 00:00:00 UTC));
6865+ assert!(end == datetime!(2021-03-07 00:00:00 UTC));
6866+ }
6867+
6868+ #[test]
6869+ fn test_timeframe_1m() {
6870+ let now = datetime!(2021-03-05 13:00:55 UTC);
6871+ let (start, end) = Timeframe::Monthly.clamp(now);
6872+ assert!(start == datetime!(2021-02-01 00:00:00 UTC));
6873+ assert!(end == datetime!(2021-02-28 00:00:00 UTC));
6874+ let now = datetime!(2021-01-05 13:00:55 UTC);
6875+ let (start, end) = Timeframe::Monthly.clamp(now);
6876+ assert!(start == datetime!(2020-12-01 00:00:00 UTC));
6877+ assert!(end == datetime!(2020-12-31 00:00:00 UTC));
6878+ }
6879+ }
6880 diff --git a/ayllu/src/web2/routes/xmpp.rs b/ayllu/src/web2/routes/xmpp.rs
6881new file mode 100644
6882index 0000000..749bdce
6883--- /dev/null
6884+++ b/ayllu/src/web2/routes/xmpp.rs
6885 @@ -0,0 +1,101 @@
6886+ use axum::{
6887+ extract::{Extension, Path},
6888+ response::Html,
6889+ };
6890+ use serde::{Deserialize, Serialize};
6891+
6892+ use crate::config::Config;
6893+ use crate::web2::error::Error;
6894+ use crate::web2::extractors::config::ConfigReader;
6895+ use crate::web2::middleware::rpc_initiator::{Initiator, Kind as InitiatorKind};
6896+ use crate::web2::middleware::template::Template;
6897+ use crate::web2::navigation;
6898+ use ayllu_api::xmpp_capnp::server::Client as XmppClient;
6899+
6900+ #[derive(Debug, Serialize)]
6901+ struct ChannelWithStats {
6902+ pub name: String,
6903+ pub n_users: i64,
6904+ pub n_messages: i64,
6905+ pub online: bool,
6906+ }
6907+
6908+ #[derive(Debug, Serialize)]
6909+ struct XmppMessage {
6910+ pub id: String,
6911+ pub nickname: String,
6912+ pub timestamp: String,
6913+ pub body: String,
6914+ }
6915+ pub async fn channels(
6916+ Extension(_cfg): Extension<Config>,
6917+ Extension((templates, mut ctx)): Extension<Template>,
6918+ Extension(initiator): Extension<Initiator>,
6919+ ) -> Result<Html<String>, Error> {
6920+ ctx.insert("title", "Discussions");
6921+ ctx.insert("nav_elements", &navigation::global("xmpp", true));
6922+ let xmpp_client = initiator.client(InitiatorKind::Xmpp).unwrap();
6923+ let channels = xmpp_client
6924+ .invoke(move |c: XmppClient| async move {
6925+ let mut channels: Vec<ChannelWithStats> = Vec::new();
6926+ let req = c.stats_request();
6927+ let stats = req.send().promise.await?;
6928+ for stat in stats.get()?.get_stats()? {
6929+ channels.push(ChannelWithStats {
6930+ name: stat.get_name()?.to_string().unwrap(),
6931+ n_users: stat.get_users_online(),
6932+ n_messages: stat.get_n_messages(),
6933+ online: true,
6934+ })
6935+ }
6936+ Ok(channels)
6937+ })
6938+ .await?;
6939+ ctx.insert("channels", &channels);
6940+ let body = templates.render("channels.html", &ctx)?;
6941+ Ok(Html(body))
6942+ }
6943+
6944+ #[derive(Deserialize)]
6945+ pub struct ChannelParams {
6946+ pub channel: String,
6947+ pub last_message: Option<String>,
6948+ }
6949+
6950+ pub async fn channel(
6951+ Path(params): Path<ChannelParams>,
6952+ Extension(_cfg): Extension<Config>,
6953+ ConfigReader(config): ConfigReader,
6954+ Extension((templates, mut ctx)): Extension<Template>,
6955+ Extension(initiator): Extension<Initiator>,
6956+ ) -> Result<Html<String>, Error> {
6957+ ctx.insert("title", "lists");
6958+ ctx.insert("nav_elements", &navigation::global("xmpp", true));
6959+ ctx.insert("channel", &params.channel);
6960+ let xmpp_client = initiator.client(InitiatorKind::Xmpp).unwrap();
6961+ let messages = xmpp_client
6962+ .invoke(move |c: XmppClient| async move {
6963+ let mut xmpp_messages: Vec<XmppMessage> = Vec::new();
6964+ let mut req = c.messages_request();
6965+ req.get().set_channel_name(params.channel.as_str().into());
6966+ req.get().set_limit(config.items_per_page as i64);
6967+ if let Some(id) = params.last_message {
6968+ req.get().set_last_message(id.as_str().into());
6969+ }
6970+ let messages = req.send().promise.await?;
6971+ for message in messages.get()?.get_messages()? {
6972+ xmpp_messages.push(XmppMessage {
6973+ id: message.get_message_id()?.to_string().unwrap(),
6974+ nickname: message.get_nickname()?.to_string().unwrap(),
6975+ timestamp: message.get_timestamp()?.to_string().unwrap(),
6976+ body: message.get_body()?.to_string().unwrap(),
6977+ })
6978+ }
6979+ Ok(xmpp_messages)
6980+ })
6981+ .await?;
6982+ ctx.insert("messages", &messages);
6983+ // ctx.insert("lists", &cfg.mail.unwrap().lists);
6984+ let body = templates.render("channel.html", &ctx)?;
6985+ Ok(Html(body))
6986+ }
6987 diff --git a/ayllu/src/web2/server.rs b/ayllu/src/web2/server.rs
6988new file mode 100644
6989index 0000000..1738afe
6990--- /dev/null
6991+++ b/ayllu/src/web2/server.rs
6992 @@ -0,0 +1,299 @@
6993+ use std::collections::HashMap;
6994+ use std::error::Error;
6995+ use std::fs;
6996+ use std::net::SocketAddrV4;
6997+ use std::sync::Arc;
6998+
6999+ use axum::{
7000+ body::Body, extract::Request, middleware::from_fn_with_state, routing, Extension, Router,
7001+ ServiceExt,
7002+ };
7003+ use globwalk::glob_builder;
7004+ use tera::Tera;
7005+ use tokio::net::TcpListener;
7006+ use tower::Layer;
7007+ use tower_http::{
7008+ normalize_path::NormalizePathLayer,
7009+ trace::{DefaultOnResponse, TraceLayer},
7010+ };
7011+ use tracing::{Level, Span};
7012+
7013+ use crate::config::Config;
7014+ use crate::languages::{Hint, LANGUAGE_TABLE};
7015+ use crate::web2::highlight::{Highlighter, Loader, TreeSitterAdapter};
7016+ use crate::web2::middleware::error;
7017+ use crate::web2::middleware::repository;
7018+ use crate::web2::middleware::rpc_initiator;
7019+ use crate::web2::middleware::sites;
7020+ use crate::web2::middleware::template;
7021+ use crate::web2::routes::about;
7022+ use crate::web2::routes::assets;
7023+ use crate::web2::routes::authors;
7024+ use crate::web2::routes::blame;
7025+ use crate::web2::routes::blob;
7026+ use crate::web2::routes::builds;
7027+ use crate::web2::routes::chart;
7028+ use crate::web2::routes::commit;
7029+ use crate::web2::routes::config;
7030+ use crate::web2::routes::finger;
7031+ use crate::web2::routes::index;
7032+ use crate::web2::routes::log as log_route;
7033+ use crate::web2::routes::mail;
7034+ use crate::web2::routes::refs;
7035+ use crate::web2::routes::repo;
7036+ use crate::web2::routes::robots;
7037+ use crate::web2::routes::rss;
7038+ use crate::web2::routes::xmpp;
7039+ use crate::web2::terautil;
7040+ use ayllu_database::Builder;
7041+
7042+ pub async fn serve(cfg: &Config) -> Result<(), Box<dyn Error>> {
7043+ let keywords = match &cfg.tree_sitter {
7044+ Some(ts_config) => {
7045+ let mut loader = Loader::new(&ts_config.base_path, &ts_config.queries_path);
7046+ loader = loader.dynamic(true);
7047+ match &ts_config.queries_extras_path {
7048+ Some(extras_path) => {
7049+ loader = loader.extra_queries(extras_path.as_str());
7050+ }
7051+ None => {}
7052+ }
7053+ match &ts_config.parsers {
7054+ Some(parsers) => {
7055+ loader = loader.parsers(parsers.clone());
7056+ }
7057+ None => {}
7058+ }
7059+ loader.load()?;
7060+ ts_config.keywords.clone()
7061+ }
7062+ None => {
7063+ log::warn!("tree-sitter is not configured, syntax highlighting will be disabled");
7064+ Vec::new()
7065+ }
7066+ };
7067+
7068+ match &cfg.languages {
7069+ Some(languages) => {
7070+ LANGUAGE_TABLE.set_languages(languages.extras.clone());
7071+ LANGUAGE_TABLE.set_mappings(HashMap::from_iter(languages.mappings.iter().map(
7072+ |(key, value)| {
7073+ let kvs: (Hint, Hint) = (key.into(), value.into());
7074+ kvs
7075+ },
7076+ )))
7077+ }
7078+ None => {}
7079+ }
7080+
7081+ let highlighter = Highlighter::new(&keywords);
7082+ let adapter = TreeSitterAdapter::new(highlighter.clone());
7083+
7084+ let db = Builder::default()
7085+ .url(&cfg.database.path)
7086+ .log_queries(cfg.log_level == "DEBUG")
7087+ .read_only(true) // Web UI is 100% read-only
7088+ .build()
7089+ .await?;
7090+
7091+ // NOTE: files modified on the file system will see their changes
7092+ // immediately in the rendered server output however added new files
7093+ // will require that the server be restarted.
7094+ let templates_path = format!("{}/{}/templates/*", cfg.web.themes_path, cfg.web.base_theme);
7095+ // map of all loaded themes at startup, new themes require that the server
7096+ // be restarted for now.
7097+ let mut templates: Vec<(String, Tera)> = Vec::new();
7098+ let mut templates_base = Tera::new(&templates_path)?;
7099+ templates_base.register_filter("friendly_time", terautil::FriendlyTime {});
7100+ templates_base.register_filter("format_epoch", terautil::FormatEpoch {});
7101+ templates_base.register_filter("filemode", terautil::FileMode {});
7102+ templates_base.register_filter("human_bytes", terautil::HumanBytes {});
7103+ templates_base.register_filter("emoji", terautil::Emojis {});
7104+
7105+ for theme in cfg.web.themes.iter().filter(|item| *item != "default") {
7106+ let templates_base_path = format!("{}/{}/templates", cfg.web.themes_path, theme);
7107+ if fs::metadata(templates_base_path.clone()).is_err() {
7108+ // its valid to include a theme without any templates
7109+ continue;
7110+ }
7111+ let templates_path = format!("{}/*.html", templates_base_path);
7112+ let mut template_files: Vec<(String, Option<String>)> = Vec::new();
7113+ match glob_builder(templates_path).build() {
7114+ Ok(globwalker) => {
7115+ for entry in globwalker.into_iter() {
7116+ let entry = entry?;
7117+ let path = String::from(entry.path().to_str().unwrap());
7118+ let name = String::from(entry.file_name().to_str().unwrap());
7119+ log::info!("loaded template override {} {}", path, name);
7120+ template_files.push((path, Some(name)));
7121+ }
7122+ }
7123+ Err(_) => {
7124+ continue;
7125+ }
7126+ }
7127+ let mut tera_theme = templates_base.clone();
7128+ if !template_files.is_empty() {
7129+ log::info!(
7130+ "loaded {} template files for theme {}",
7131+ template_files.len(),
7132+ theme
7133+ );
7134+ tera_theme.add_template_files(template_files)?;
7135+ }
7136+ templates.push((theme.clone(), tera_theme));
7137+ }
7138+ templates.push((String::from("default"), templates_base));
7139+
7140+ let site_mapping = if cfg.sites.enabled {
7141+ sites::sites(cfg.clone())?
7142+ } else {
7143+ Vec::new()
7144+ };
7145+
7146+ let mail_required_plugins: &'static [rpc_initiator::Kind] = &[rpc_initiator::Kind::Mail];
7147+ let xmpp_required_plugins: &'static [rpc_initiator::Kind] = &[rpc_initiator::Kind::Xmpp];
7148+
7149+ let address: SocketAddrV4 = cfg.http.address.parse()?;
7150+ let app = NormalizePathLayer::trim_trailing_slash().layer(
7151+ Router::new()
7152+ .route("/robots.txt", routing::get(robots::serve))
7153+ .nest(
7154+ "/",
7155+ Router::new()
7156+ .route("/", routing::get(index::index))
7157+ .route("/browse", routing::get(index::index))
7158+ .route("/:collection", routing::get(index::collection))
7159+ .route("/rss/firehose.xml", routing::get(rss::feed_firehose))
7160+ .route("/rss/1d.xml", routing::get(rss::feed_1d))
7161+ .route("/rss/1w.xml", routing::get(rss::feed_1w))
7162+ .route("/rss/1m.xml", routing::get(rss::feed_1m))
7163+ .route("/about", routing::get(about::serve))
7164+ .route("/config", routing::get(config::serve).post(config::update))
7165+ .layer(from_fn_with_state(
7166+ Arc::new((cfg.clone(), templates.clone())),
7167+ template::middleware,
7168+ )),
7169+ )
7170+ .nest(
7171+ "/mail",
7172+ Router::new()
7173+ .route("/", routing::get(mail::lists))
7174+ .route("/:list_id", routing::get(mail::threads))
7175+ .route("/export/:list_id", routing::get(mail::export))
7176+ .route("/export/:list_id/:thread_id", routing::get(mail::export))
7177+ .route(
7178+ "/export/:list_id/:thread_id/:message_id",
7179+ routing::get(mail::export),
7180+ )
7181+ .route("/thread/:list_id/:thread_id", routing::get(mail::thread))
7182+ .route("/message/:list_id/:message_id", routing::get(mail::message))
7183+ .layer(from_fn_with_state(
7184+ Arc::new((cfg.clone(), templates.clone(), mail_required_plugins)),
7185+ rpc_initiator::required,
7186+ ))
7187+ .layer(from_fn_with_state(
7188+ Arc::new((cfg.clone(), templates.clone())),
7189+ template::middleware,
7190+ )),
7191+ )
7192+ .nest(
7193+ "/xmpp",
7194+ Router::new()
7195+ .route("/", routing::get(xmpp::channels))
7196+ .route("/:channel", routing::get(xmpp::channel))
7197+ .route("/:channel/:last_message", routing::get(xmpp::channel))
7198+ .layer(from_fn_with_state(
7199+ Arc::new((cfg.clone(), templates.clone(), xmpp_required_plugins)),
7200+ rpc_initiator::required,
7201+ ))
7202+ .layer(from_fn_with_state(
7203+ Arc::new((cfg.clone(), templates.clone())),
7204+ template::middleware,
7205+ )),
7206+ )
7207+ .nest(
7208+ "/:collection/:name",
7209+ Router::new()
7210+ .route("/", routing::get(repo::serve))
7211+ .route(
7212+ "/rss/firehose.xml",
7213+ routing::get(rss::feed_repository_firehose),
7214+ )
7215+ .route("/rss/1d.xml", routing::get(rss::feed_repository_1d))
7216+ .route("/rss/1w.xml", routing::get(rss::feed_repository_1w))
7217+ .route("/rss/1m.xml", routing::get(rss::feed_repository_1m))
7218+ .route("/commit/:commit_id", routing::get(commit::serve))
7219+ .route("/tree/:commitish", routing::get(repo::serve))
7220+ .route("/tree/:commitish/*file_path", routing::get(repo::serve))
7221+ .route("/log", routing::get(log_route::serve))
7222+ .route("/log/:commitish", routing::get(log_route::serve))
7223+ .route("/log/:commitish/*file_path", routing::get(log_route::serve))
7224+ .route("/charts", routing::get(chart::serve))
7225+ .route("/chart/:kind", routing::get(chart::serve))
7226+ .route("/chart/:kind/:commit_id", routing::get(chart::serve))
7227+ .route(
7228+ "/:kind/:commit_id/:width/:height",
7229+ routing::get(chart::serve),
7230+ )
7231+ .route("/authors", routing::get(authors::serve))
7232+ .route("/blame/:commitish/*file_path", routing::get(blame::serve))
7233+ .route("/blob/:commitish/*file_path", routing::get(blob::serve))
7234+ .route("/raw/:commitish/*file_path", routing::get(blob::serve_raw))
7235+ .route("/builds", routing::get(builds::serve))
7236+ .route("/builds/badge/:commitish", routing::get(builds::badge))
7237+ .route("/builds/:commit_id", routing::get(builds::details))
7238+ .route(
7239+ "/builds/:commit_id/:build_id",
7240+ routing::get(builds::details),
7241+ )
7242+ .route("/refs", routing::get(refs::tags))
7243+ .route("/refs/branches", routing::get(refs::branches))
7244+ .route("/refs/tags", routing::get(refs::tags))
7245+ .route("/refs/archive/:ref_id", routing::get(refs::archive))
7246+ .layer(from_fn_with_state(cfg.clone(), repository::middleware))
7247+ .layer(from_fn_with_state(
7248+ Arc::new(cfg.clone()),
7249+ rpc_initiator::optional,
7250+ ))
7251+ .layer(from_fn_with_state(
7252+ Arc::new((cfg.clone(), templates.clone())),
7253+ template::middleware,
7254+ )),
7255+ )
7256+ .route(
7257+ "/.well-known/webfinger",
7258+ routing::get(finger::serve).layer(Extension(finger::CResolver {
7259+ domain: "todo",
7260+ config: Arc::new(cfg.clone()),
7261+ })),
7262+ )
7263+ .route("/static/main.min.css", routing::get(assets::serve_css))
7264+ .route("/static/assets/:asset", routing::get(assets::serve_asset))
7265+ .layer(Extension(cfg.clone()))
7266+ .layer(Extension(Arc::new(db)))
7267+ .layer(Extension(highlighter))
7268+ .layer(Extension(adapter))
7269+ // error handling
7270+ .layer(from_fn_with_state(
7271+ Arc::new((cfg.clone(), templates.clone())),
7272+ error::middleware,
7273+ ))
7274+ // git hosted static sites
7275+ .layer(from_fn_with_state(
7276+ (cfg.clone(), site_mapping),
7277+ sites::middleware,
7278+ ))
7279+ .layer(
7280+ TraceLayer::new_for_http()
7281+ .on_request(|request: &Request<Body>, _span: &Span| {
7282+ tracing::info!("started {} {}", request.method(), request.uri().path())
7283+ })
7284+ .on_response(DefaultOnResponse::new().level(Level::INFO)),
7285+ ),
7286+ );
7287+ log::info!("listening @ {}", cfg.http.address);
7288+ let listener = TcpListener::bind(address).await?;
7289+ axum::serve(listener, ServiceExt::<Request>::into_make_service(app)).await?;
7290+ Ok(())
7291+ }
7292 diff --git a/ayllu/src/web2/terautil/filters.rs b/ayllu/src/web2/terautil/filters.rs
7293new file mode 100644
7294index 0000000..6eef1cc
7295--- /dev/null
7296+++ b/ayllu/src/web2/terautil/filters.rs
7297 @@ -0,0 +1,80 @@
7298+ use tera::{to_value, Filter, Result, Value};
7299+
7300+ use file_mode::Mode;
7301+ use time::{format_description::well_known, OffsetDateTime};
7302+
7303+ use crate::time as ctime;
7304+ use crate::web2::util;
7305+
7306+ pub struct FriendlyTime {}
7307+
7308+ impl Filter for FriendlyTime {
7309+ fn filter(&self, value: &Value, _: &std::collections::HashMap<String, Value>) -> Result<Value> {
7310+ let ts = ctime::friendly(value.as_u64().unwrap());
7311+ let result = to_value(ts)?;
7312+ Ok(result)
7313+ }
7314+ }
7315+
7316+ pub struct FormatEpoch {}
7317+
7318+ impl Filter for FormatEpoch {
7319+ fn filter(&self, value: &Value, _: &std::collections::HashMap<String, Value>) -> Result<Value> {
7320+ let ts = OffsetDateTime::from_unix_timestamp(value.as_i64().unwrap()).unwrap();
7321+ let formatted = ts.format(&well_known::Rfc2822).unwrap();
7322+ let result = to_value(formatted)?;
7323+ Ok(result)
7324+ }
7325+ }
7326+
7327+ pub struct FileMode {}
7328+
7329+ impl Filter for FileMode {
7330+ fn filter(&self, value: &Value, _: &std::collections::HashMap<String, Value>) -> Result<Value> {
7331+ let filemode = value.as_u64().unwrap() as u32;
7332+ let mode = Mode::new(filemode, 0o777);
7333+ let result = to_value(mode.to_string())?;
7334+ Ok(result)
7335+ }
7336+ }
7337+
7338+ pub struct HumanBytes {}
7339+
7340+ impl Filter for HumanBytes {
7341+ fn filter(&self, value: &Value, _: &std::collections::HashMap<String, Value>) -> Result<Value> {
7342+ let bytes = value.as_f64().unwrap();
7343+ let result = to_value(util::human_bytes(bytes))?;
7344+ Ok(result)
7345+ }
7346+ }
7347+
7348+ pub struct Emojis {}
7349+
7350+ impl Filter for Emojis {
7351+ fn filter(&self, value: &Value, _: &std::collections::HashMap<String, Value>) -> Result<Value> {
7352+ // TODO: load these dynamically with the rest of the theme assets
7353+ let name = value.as_str().unwrap();
7354+ let svg_content = match name {
7355+ "books" => include_str!("../../../themes/default/assets/books.svg"),
7356+ "building" => include_str!("../../../themes/default/assets/building.svg"),
7357+ "check" => include_str!("../../../themes/default/assets/check.svg"),
7358+ "chat" => include_str!("../../../themes/default/assets/chat.svg"),
7359+ "code" => include_str!("../../../themes/default/assets/code.svg"),
7360+ "feed" => include_str!("../../../themes/default/assets/feed.svg"),
7361+ "moon" => include_str!("../../../themes/default/assets/moon.svg"),
7362+ "textile-pattern-1" => {
7363+ include_str!("../../../themes/default/assets/textile-pattern-1.svg")
7364+ }
7365+ "textile-pattern-2" => {
7366+ include_str!("../../../themes/default/assets/textile-pattern-2.svg")
7367+ }
7368+ "question" => include_str!("../../../themes/default/assets/question.svg"),
7369+ "rss" => include_str!("../../../themes/default/assets/rss.svg"),
7370+ "scale" => include_str!("../../../themes/default/assets/scale.svg"),
7371+ "xmpp" => include_str!("../../../themes/default/assets/xmpp.svg"),
7372+ _ => "",
7373+ };
7374+ let result = to_value(svg_content)?;
7375+ Ok(result)
7376+ }
7377+ }
7378 diff --git a/ayllu/src/web2/terautil/loader.rs b/ayllu/src/web2/terautil/loader.rs
7379new file mode 100644
7380index 0000000..54d3933
7381--- /dev/null
7382+++ b/ayllu/src/web2/terautil/loader.rs
7383 @@ -0,0 +1,55 @@
7384+ use serde::{Deserialize, Serialize};
7385+ use tera::{Context, Tera};
7386+
7387+ const DEFAULT_NAV: &[(&str, &str)] = &[];
7388+
7389+ /// top-level theme options available in all pages
7390+ #[derive(Serialize, Deserialize, Debug, Default)]
7391+ pub struct Options {
7392+ pub origin: String,
7393+ pub site_name: String,
7394+ pub collection: Option<String>,
7395+ pub name: Option<String>,
7396+ pub url: String,
7397+ pub path: String,
7398+ pub fluid: bool,
7399+ pub subpath_mode: bool,
7400+ }
7401+
7402+ pub struct Loader {
7403+ pub templates: Vec<(String, Tera)>,
7404+ pub default_theme: String,
7405+ }
7406+
7407+ impl Loader {
7408+ pub fn load(&self, options: Options, theme_name: Option<String>) -> (Tera, Context) {
7409+ let _default_template = || {
7410+ self.templates
7411+ .iter()
7412+ .find(|template| template.0 == self.default_theme)
7413+ .unwrap()
7414+ .1
7415+ .clone()
7416+ };
7417+ let template = match theme_name {
7418+ Some(name) => match self.templates.iter().find(|template| template.0 == name) {
7419+ Some(template) => template.1.clone(),
7420+ None => _default_template(),
7421+ },
7422+ None => _default_template(),
7423+ };
7424+ let mut ctx = Context::new();
7425+ ctx.insert("title", "");
7426+ ctx.insert("origin", &options.origin);
7427+ ctx.insert("collection", &options.collection);
7428+ ctx.insert("name", &options.name);
7429+ ctx.insert("site_name", &options.site_name.clone());
7430+ ctx.insert("nav_elements", DEFAULT_NAV);
7431+ // let url = req.uri();
7432+ ctx.insert("url", &options.url);
7433+ ctx.insert("path", &options.path);
7434+ ctx.insert("fluid", &false);
7435+ ctx.insert("subpath_mode", &options.subpath_mode);
7436+ (template, ctx)
7437+ }
7438+ }
7439 diff --git a/ayllu/src/web2/terautil/mod.rs b/ayllu/src/web2/terautil/mod.rs
7440new file mode 100644
7441index 0000000..b06a8ca
7442--- /dev/null
7443+++ b/ayllu/src/web2/terautil/mod.rs
7444 @@ -0,0 +1,5 @@
7445+ mod filters;
7446+ mod loader;
7447+
7448+ pub use filters::*;
7449+ pub use loader::{Loader, Options};
7450 diff --git a/ayllu/src/web2/util.rs b/ayllu/src/web2/util.rs
7451new file mode 100644
7452index 0000000..5ad6260
7453--- /dev/null
7454+++ b/ayllu/src/web2/util.rs
7455 @@ -0,0 +1,100 @@
7456+ use std::path::PathBuf;
7457+
7458+ use axum::http::Uri;
7459+ use url::Url;
7460+
7461+ // select a segment of the path from the given url between start and end.
7462+ // e.g.
7463+ // http://fuu.bar/baz/qux 0 1 -> Some(/baz)
7464+ // http://fuu.bar/baz/qux 0 None -> Some(/baz/qux)
7465+ // http://fuu.bar/baz/qux 5 10 -> None
7466+ // http://fuu.bar/baz/qux/tree/some/path.txt 2 None -> Some(/some/path.txt)
7467+ pub fn select_path(uri: &Uri, start: Option<usize>, end: Option<usize>) -> Option<PathBuf> {
7468+ let mut path = PathBuf::from("/");
7469+ if let Some(stripped) = uri.path().strip_prefix('/') {
7470+ stripped.split('/').enumerate().for_each(|(i, segment)| {
7471+ match (start, end) {
7472+ (Some(start), Some(end)) => {
7473+ if i >= start && i <= end {
7474+ path.push(segment);
7475+ }
7476+ }
7477+ (Some(start), None) => {
7478+ if i >= start {
7479+ path.push(segment);
7480+ }
7481+ }
7482+ (None, Some(end)) => {
7483+ if i <= end {
7484+ path.push(segment)
7485+ }
7486+ }
7487+ (None, None) => {}
7488+ };
7489+ })
7490+ }
7491+ match path.iter().count() {
7492+ 0 | 1 => None,
7493+ _ => Some(path),
7494+ }
7495+ }
7496+
7497+ pub fn project_url(uri: &Uri, origin: &str) -> String {
7498+ let project_path = select_path(uri, Some(0), Some(1));
7499+ let project_path = project_path.unwrap_or(PathBuf::from("/"));
7500+ let mut url = Url::parse(origin).unwrap();
7501+ url.set_path(project_path.to_str().unwrap());
7502+ url.to_string()
7503+ }
7504+
7505+ /// construct an LFS url based on the template
7506+ pub fn lfs_url(template: &str, collection: &str, name: &str, oid: &str) -> String {
7507+ let url = template.to_string();
7508+ let url = url.replace("{collection}", collection);
7509+ let url = url.replace("{name}", name);
7510+ url.replace("{oid}", oid)
7511+ }
7512+
7513+ const UNIT: f64 = 1024.0;
7514+ const SUFFIX: [&str; 4] = ["B", "KiB", "MiB", "GiB"];
7515+
7516+ // return bytes as a human readable string
7517+ pub fn human_bytes(size: f64) -> String {
7518+ let base = size.log10() / UNIT.log10();
7519+ let result = format!("{:.1}", UNIT.powf(base - base.floor()),)
7520+ .trim_end_matches(".0")
7521+ .to_owned();
7522+ [&result, SUFFIX[base.floor() as usize]].join(" ")
7523+ }
7524+
7525+ #[cfg(test)]
7526+ mod tests {
7527+ use super::*;
7528+ #[test]
7529+ fn test_select_path() {
7530+ let as_string = |path: Option<PathBuf>| {
7531+ let output = path.unwrap().to_str().unwrap().to_string();
7532+ output
7533+ };
7534+
7535+ let input = &Uri::try_from("https://ayllu-forge.org").unwrap();
7536+ assert!(select_path(input, None, None,).is_none());
7537+ assert!(select_path(input, Some(0), Some(2),).is_none());
7538+
7539+ let input = &Uri::try_from("https://ayllu-forge.org/projects/ayllu").unwrap();
7540+ assert!(as_string(select_path(input, None, Some(1))) == "/projects/ayllu");
7541+
7542+ let input = &Uri::try_from("https://ayllu-forge.org/projects/ayllu/tree/main").unwrap();
7543+ assert!(as_string(select_path(input, Some(2), None)) == "/tree/main");
7544+
7545+ let input = &Uri::try_from("https://ayllu-forge.org/projects/ayllu/tree/main").unwrap();
7546+ assert!(select_path(input, Some(4), None).is_none());
7547+
7548+ let input = &Uri::try_from("https://ayllu-forge.org/projects/ayllu/tree/main/").unwrap();
7549+ assert!(select_path(input, Some(4), None).is_none());
7550+
7551+ let input =
7552+ &Uri::try_from("https://ayllu-forge.org/projects/ayllu/tree/main/src/web").unwrap();
7553+ assert!(as_string(select_path(input, Some(4), None)) == "/src/web");
7554+ }
7555+ }
7556 diff --git a/ayllu/themes/adwaita/main.min.css b/ayllu/themes/adwaita/main.min.css
7557new file mode 100644
7558index 0000000..254ac16
7559--- /dev/null
7560+++ b/ayllu/themes/adwaita/main.min.css
7561 @@ -0,0 +1,32 @@
7562+ span.ts_attribute{color:#e66100}span.ts_constant{color:#813d9c}span.ts_function.builtin{color:#1c71d8}span.ts_function{color:#1c71d8}span.ts_keyword{color:#e66100}span.ts_operator{color:#813d9c}span.ts_property{color:#99c1f1}span.ts_punctuation{color:#99c1f1}span.ts_punctuation.bracket{color:#99c1f1}span.ts_punctuation.delimiter{color:#99c1f1}span.ts_string{color:#5BC8AF}span.ts_string.special{color:#33B2A4}span.ts_tag{color:#5BC8AF}span.ts_type{color:#5BC8AF}span.ts_type.builtin{color:#99c1f1}span.ts_variable{color:#c0bfbc}span.ts_variable.builtin{color:#c0bfbc}span.ts_variable.parameter{color:#c0bfbc}@media (prefers-color-scheme: dark){span.label{color:#000}}/*!
7563+ * Pico CSS v1.5.10 (https://picocss.com)
7564+ * Copyright 2019-2023 - Licensed under MIT
7565+ */:root{--font-family: system-ui, -apple-system, "Segoe UI", "Roboto", "Ubuntu",
7566+ "Cantarell", "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji",
7567+ "Segoe UI Symbol", "Noto Color Emoji";--line-height: 1.5;--font-weight: 400;--font-size: 16px;--border-radius: 0.25rem;--border-width: 1px;--outline-width: 3px;--spacing: 1rem;--typography-spacing-vertical: 1.5rem;--block-spacing-vertical: calc(var(--spacing) * 2);--block-spacing-horizontal: var(--spacing);--grid-spacing-vertical: 0;--grid-spacing-horizontal: var(--spacing);--form-element-spacing-vertical: 0.75rem;--form-element-spacing-horizontal: 1rem;--nav-element-spacing-vertical: 1rem;--nav-element-spacing-horizontal: 0.5rem;--nav-link-spacing-vertical: 0.5rem;--nav-link-spacing-horizontal: 0.5rem;--form-label-font-weight: var(--font-weight);--transition: 0.2s ease-in-out;--modal-overlay-backdrop-filter: blur(0.25rem)}@media (min-width: 576px){:root{--font-size: 17px}}@media (min-width: 768px){:root{--font-size: 18px}}@media (min-width: 992px){:root{--font-size: 19px}}@media (min-width: 1200px){:root{--font-size: 20px}}@media (min-width: 576px){body>header,body>main,body>footer,section{--block-spacing-vertical: calc(var(--spacing) * 2.5)}}@media (min-width: 768px){body>header,body>main,body>footer,section{--block-spacing-vertical: calc(var(--spacing) * 3)}}@media (min-width: 992px){body>header,body>main,body>footer,section{--block-spacing-vertical: calc(var(--spacing) * 3.5)}}@media (min-width: 1200px){body>header,body>main,body>footer,section{--block-spacing-vertical: calc(var(--spacing) * 4)}}@media (min-width: 576px){article{--block-spacing-horizontal: calc(var(--spacing) * 1.25)}}@media (min-width: 768px){article{--block-spacing-horizontal: calc(var(--spacing) * 1.5)}}@media (min-width: 992px){article{--block-spacing-horizontal: calc(var(--spacing) * 1.75)}}@media (min-width: 1200px){article{--block-spacing-horizontal: calc(var(--spacing) * 2)}}dialog>article{--block-spacing-vertical: calc(var(--spacing) * 2);--block-spacing-horizontal: var(--spacing)}@media (min-width: 576px){dialog>article{--block-spacing-vertical: calc(var(--spacing) * 2.5);--block-spacing-horizontal: calc(var(--spacing) * 1.25)}}@media (min-width: 768px){dialog>article{--block-spacing-vertical: calc(var(--spacing) * 3);--block-spacing-horizontal: calc(var(--spacing) * 1.5)}}a{--text-decoration: none}a.secondary,a.contrast{--text-decoration: underline}small{--font-size: 0.875em}h1,h2,h3,h4,h5,h6{--font-weight: 700}h1{--font-size: 2rem;--typography-spacing-vertical: 3rem}h2{--font-size: 1.75rem;--typography-spacing-vertical: 2.625rem}h3{--font-size: 1.5rem;--typography-spacing-vertical: 2.25rem}h4{--font-size: 1.25rem;--typography-spacing-vertical: 1.874rem}h5{--font-size: 1.125rem;--typography-spacing-vertical: 1.6875rem}[type="checkbox"],[type="radio"]{--border-width: 2px}[type="checkbox"][role="switch"]{--border-width: 3px}thead th,thead td,tfoot th,tfoot td{--border-width: 3px}:not(thead,tfoot)>*>td{--font-size: 0.875em}pre,code,kbd,samp{--font-family: "Menlo", "Consolas", "Roboto Mono", "Ubuntu Monospace",
7568+ "Noto Mono", "Oxygen Mono", "Liberation Mono", monospace,
7569+ "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"}kbd{--font-weight: bolder}[data-theme="light"],:root:not([data-theme="dark"]){--background-color: #fff;--color: #655c53;--h1-color: #2d2925;--h2-color: #3b3630;--h3-color: #49433c;--h4-color: #574f47;--h5-color: #655c53;--h6-color: #73695e;--muted-color: #9a8f84;--muted-border-color: #f6f5f4;--primary: #62a0ea;--primary-hover: #3584e4;--primary-focus: rgba(98,160,234,0.125);--primary-inverse: #fff;--secondary: #81766a;--secondary-hover: #655c53;--secondary-focus: rgba(129,118,106,0.125);--secondary-inverse: #fff;--contrast: #2d2925;--contrast-hover: #77767b;--contrast-focus: rgba(129,118,106,0.125);--contrast-inverse: #fff;--mark-background-color: #ffb862;--mark-color: #44311c;--ins-color: #18b43a;--del-color: #751106;--blockquote-border-color: var(--muted-border-color);--blockquote-footer-color: var(--muted-color);--button-box-shadow: 0 0 0 rgba(0, 0, 0, 0);--button-hover-box-shadow: 0 0 0 rgba(0, 0, 0, 0);--form-element-background-color: transparent;--form-element-border-color: #c8c2bc;--form-element-color: var(--color);--form-element-placeholder-color: var(--muted-color);--form-element-active-background-color: transparent;--form-element-active-border-color: var(--primary);--form-element-focus-color: var(--primary-focus);--form-element-disabled-background-color: #ebe8e6;--form-element-disabled-border-color: #c8c2bc;--form-element-disabled-opacity: 0.5;--form-element-invalid-border-color: #751106;--form-element-invalid-active-border-color: #8d1407;--form-element-invalid-focus-color: rgba(141,20,7,0.125);--form-element-valid-border-color: #18b43a;--form-element-valid-active-border-color: #1bcb41;--form-element-valid-focus-color: rgba(27,203,65,0.125);--switch-background-color: #dfdcd8;--switch-color: var(--primary-inverse);--switch-checked-background-color: var(--primary);--range-border-color: #ebe8e6;--range-active-border-color: #dfdcd8;--range-thumb-border-color: var(--background-color);--range-thumb-color: var(--secondary);--range-thumb-hover-color: var(--secondary-hover);--range-thumb-active-color: var(--primary);--table-border-color: var(--muted-border-color);--table-row-stripped-background-color: #fbfafa;--code-background-color: #f6f5f4;--code-color: var(--muted-color);--code-kbd-background-color: var(--contrast);--code-kbd-color: var(--contrast-inverse);--code-tag-color: #b34d80;--code-property-color: #3d888f;--code-value-color: #986;--code-comment-color: #c8c2bc;--accordion-border-color: var(--muted-border-color);--accordion-close-summary-color: var(--color);--accordion-open-summary-color: var(--muted-color);--card-background-color: var(--background-color);--card-border-color: var(--muted-border-color);--card-box-shadow:
7570+ .0145rem .029rem .174rem rgba(45,41,37,0.01698),
7571+ .0335rem .067rem .402rem rgba(45,41,37,0.024),
7572+ .0625rem .125rem .75rem rgba(45,41,37,0.03),
7573+ .1125rem .225rem 1.35rem rgba(45,41,37,0.036),
7574+ .2085rem .417rem 2.502rem rgba(45,41,37,0.04302),
7575+ .5rem 1rem 6rem rgba(45,41,37,0.06),
7576+ 0 0 0 0.0625rem rgba(45,41,37,0.015);--card-sectionning-background-color: #fdfdfc;--dropdown-background-color: #fdfdfc;--dropdown-border-color: #f0efed;--dropdown-box-shadow: var(--card-box-shadow);--dropdown-color: var(--color);--dropdown-hover-background-color: #f6f5f4;--modal-overlay-background-color: rgba(235,232,230,0.7);--progress-background-color: #ebe8e6;--progress-color: var(--primary);--loading-spinner-opacity: 0.5;--tooltip-background-color: var(--contrast);--tooltip-color: var(--contrast-inverse);--icon-checkbox: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(255, 255, 255)' stroke-width='4' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='20 6 9 17 4 12'%3E%3C/polyline%3E%3C/svg%3E");--icon-chevron: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(101.2, 92, 82.8)' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'%3E%3C/polyline%3E%3C/svg%3E");--icon-chevron-button: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(255, 255, 255)' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'%3E%3C/polyline%3E%3C/svg%3E");--icon-chevron-button-inverse: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(255, 255, 255)' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'%3E%3C/polyline%3E%3C/svg%3E");--icon-close: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(154.2, 143, 131.8)' stroke-width='4' stroke-linecap='round' stroke-linejoin='round'%3E%3Cline x1='18' y1='6' x2='6' y2='18'%3E%3C/line%3E%3Cline x1='6' y1='6' x2='18' y2='18'%3E%3C/line%3E%3C/svg%3E");--icon-date: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(101.2, 92, 82.8)' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Crect x='3' y='4' width='18' height='18' rx='2' ry='2'%3E%3C/rect%3E%3Cline x1='16' y1='2' x2='16' y2='6'%3E%3C/line%3E%3Cline x1='8' y1='2' x2='8' y2='6'%3E%3C/line%3E%3Cline x1='3' y1='10' x2='21' y2='10'%3E%3C/line%3E%3C/svg%3E");--icon-invalid: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(116.9508196721, 16.8032786885, 6.0491803279)' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='12' cy='12' r='10'%3E%3C/circle%3E%3Cline x1='12' y1='8' x2='12' y2='12'%3E%3C/line%3E%3Cline x1='12' y1='16' x2='12.01' y2='16'%3E%3C/line%3E%3C/svg%3E");--icon-minus: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(255, 255, 255)' stroke-width='4' stroke-linecap='round' stroke-linejoin='round'%3E%3Cline x1='5' y1='12' x2='19' y2='12'%3E%3C/line%3E%3C/svg%3E");--icon-search: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(101.2, 92, 82.8)' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='11' cy='11' r='8'%3E%3C/circle%3E%3Cline x1='21' y1='21' x2='16.65' y2='16.65'%3E%3C/line%3E%3C/svg%3E");--icon-time: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(101.2, 92, 82.8)' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='12' cy='12' r='10'%3E%3C/circle%3E%3Cpolyline points='12 6 12 12 16 14'%3E%3C/polyline%3E%3C/svg%3E");--icon-valid: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(24.1535433071, 180.3464566929, 57.968503937)' stroke-width='3' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='20 6 9 17 4 12'%3E%3C/polyline%3E%3C/svg%3E");color-scheme:light}@media only screen and (prefers-color-scheme: dark){:root:not([data-theme]){--background-color: #494645;--color: #dfdcd8;--h1-color: #f6f5f4;--h2-color: #f0efed;--h3-color: #ebe8e6;--h4-color: #e5e2df;--h5-color: #dfdcd8;--h6-color: #d4cfca;--muted-color: #9a8f84;--muted-border-color: #342f2b;--primary: #62a0ea;--primary-hover: #99c1f1;--primary-focus: rgba(98,160,234,0.25);--primary-inverse: #fff;--secondary: #81766a;--secondary-hover: #9a8f84;--secondary-focus: rgba(154,143,132,0.25);--secondary-inverse: #fff;--contrast: #f6f5f4;--contrast-hover: #fff;--contrast-focus: rgba(154,143,132,0.25);--contrast-inverse: #77767b;--mark-background-color: #e4af6f;--mark-color: #494645;--ins-color: #18b43a;--del-color: #751106;--blockquote-border-color: var(--muted-border-color);--blockquote-footer-color: var(--muted-color);--button-box-shadow: 0 0 0 rgba(0, 0, 0, 0);--button-hover-box-shadow: 0 0 0 rgba(0, 0, 0, 0);--form-element-background-color: #494645;--form-element-border-color: #574f47;--form-element-color: var(--color);--form-element-placeholder-color: var(--muted-color);--form-element-active-background-color: var(--form-element-background-color);--form-element-active-border-color: var(--primary);--form-element-focus-color: var(--primary-focus);--form-element-disabled-background-color: #49433c;--form-element-disabled-border-color: #655c53;--form-element-disabled-opacity: 0.5;--form-element-invalid-border-color: #5d0d05;--form-element-invalid-active-border-color: #751106;--form-element-invalid-focus-color: rgba(117,17,6,0.25);--form-element-valid-border-color: #159e33;--form-element-valid-active-border-color: #18b43a;--form-element-valid-focus-color: rgba(24,180,58,0.25);--switch-background-color: #574f47;--switch-color: var(--primary-inverse);--switch-checked-background-color: var(--primary);--range-border-color: #3b3630;--range-active-border-color: #49433c;--range-thumb-border-color: var(--background-color);--range-thumb-color: var(--secondary);--range-thumb-hover-color: var(--secondary-hover);--range-thumb-active-color: var(--primary);--table-border-color: var(--muted-border-color);--table-row-stripped-background-color: rgba(154,143,132,0.05);--code-background-color: #363330;--code-color: var(--muted-color);--code-kbd-background-color: var(--contrast);--code-kbd-color: var(--contrast-inverse);--code-tag-color: #a65980;--code-property-color: #599fa6;--code-value-color: #8c8473;--code-comment-color: #73695e;--accordion-border-color: var(--muted-border-color);--accordion-active-summary-color: var(--primary);--accordion-close-summary-color: var(--color);--accordion-open-summary-color: var(--muted-color);--card-background-color: #403c3a;--card-border-color: var(--card-background-color);--card-box-shadow:
7577+ .0145rem .029rem .174rem rgba(119,118,123,0.01698),
7578+ .0335rem .067rem .402rem rgba(119,118,123,0.024),
7579+ .0625rem .125rem .75rem rgba(119,118,123,0.03),
7580+ .1125rem .225rem 1.35rem rgba(119,118,123,0.036),
7581+ .2085rem .417rem 2.502rem rgba(119,118,123,0.04302),
7582+ .5rem 1rem 6rem rgba(119,118,123,0.06),
7583+ 0 0 0 0.0625rem rgba(119,118,123,0.015);--card-sectionning-background-color: #363330;--dropdown-background-color: #2d2925;--dropdown-border-color: #3b3630;--dropdown-box-shadow: var(--card-box-shadow);--dropdown-color: var(--color);--dropdown-hover-background-color: rgba(59,54,48,0.75);--modal-overlay-background-color: rgba(59,54,48,0.8);--progress-background-color: #3b3630;--progress-color: var(--primary);--loading-spinner-opacity: 0.5;--tooltip-background-color: var(--contrast);--tooltip-color: var(--contrast-inverse);--icon-checkbox: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(255, 255, 255)' stroke-width='4' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='20 6 9 17 4 12'%3E%3C/polyline%3E%3C/svg%3E");--icon-chevron: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(200.1, 194, 187.9)' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'%3E%3C/polyline%3E%3C/svg%3E");--icon-chevron-button: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(255, 255, 255)' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'%3E%3C/polyline%3E%3C/svg%3E");--icon-chevron-button-inverse: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(119, 118, 123)' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'%3E%3C/polyline%3E%3C/svg%3E");--icon-close: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(154.2, 143, 131.8)' stroke-width='4' stroke-linecap='round' stroke-linejoin='round'%3E%3Cline x1='18' y1='6' x2='6' y2='18'%3E%3C/line%3E%3Cline x1='6' y1='6' x2='18' y2='18'%3E%3C/line%3E%3C/svg%3E");--icon-date: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(200.1, 194, 187.9)' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Crect x='3' y='4' width='18' height='18' rx='2' ry='2'%3E%3C/rect%3E%3Cline x1='16' y1='2' x2='16' y2='6'%3E%3C/line%3E%3Cline x1='8' y1='2' x2='8' y2='6'%3E%3C/line%3E%3Cline x1='3' y1='10' x2='21' y2='10'%3E%3C/line%3E%3C/svg%3E");--icon-invalid: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(92.7049180328, 13.3196721311, 4.7950819672)' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='12' cy='12' r='10'%3E%3C/circle%3E%3Cline x1='12' y1='8' x2='12' y2='12'%3E%3C/line%3E%3Cline x1='12' y1='16' x2='12.01' y2='16'%3E%3C/line%3E%3C/svg%3E");--icon-minus: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(255, 255, 255)' stroke-width='4' stroke-linecap='round' stroke-linejoin='round'%3E%3Cline x1='5' y1='12' x2='19' y2='12'%3E%3C/line%3E%3C/svg%3E");--icon-search: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(200.1, 194, 187.9)' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='11' cy='11' r='8'%3E%3C/circle%3E%3Cline x1='21' y1='21' x2='16.65' y2='16.65'%3E%3C/line%3E%3C/svg%3E");--icon-time: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(200.1, 194, 187.9)' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='12' cy='12' r='10'%3E%3C/circle%3E%3Cpolyline points='12 6 12 12 16 14'%3E%3C/polyline%3E%3C/svg%3E");--icon-valid: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(21.1417322835, 157.8582677165, 50.7401574803)' stroke-width='3' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='20 6 9 17 4 12'%3E%3C/polyline%3E%3C/svg%3E");color-scheme:dark}}[data-theme="dark"]{--background-color: #494645;--color: #dfdcd8;--h1-color: #f6f5f4;--h2-color: #f0efed;--h3-color: #ebe8e6;--h4-color: #e5e2df;--h5-color: #dfdcd8;--h6-color: #d4cfca;--muted-color: #9a8f84;--muted-border-color: #342f2b;--primary: #62a0ea;--primary-hover: #99c1f1;--primary-focus: rgba(98,160,234,0.25);--primary-inverse: #fff;--secondary: #81766a;--secondary-hover: #9a8f84;--secondary-focus: rgba(154,143,132,0.25);--secondary-inverse: #fff;--contrast: #f6f5f4;--contrast-hover: #fff;--contrast-focus: rgba(154,143,132,0.25);--contrast-inverse: #77767b;--mark-background-color: #e4af6f;--mark-color: #494645;--ins-color: #18b43a;--del-color: #751106;--blockquote-border-color: var(--muted-border-color);--blockquote-footer-color: var(--muted-color);--button-box-shadow: 0 0 0 rgba(0, 0, 0, 0);--button-hover-box-shadow: 0 0 0 rgba(0, 0, 0, 0);--form-element-background-color: #494645;--form-element-border-color: #574f47;--form-element-color: var(--color);--form-element-placeholder-color: var(--muted-color);--form-element-active-background-color: var(--form-element-background-color);--form-element-active-border-color: var(--primary);--form-element-focus-color: var(--primary-focus);--form-element-disabled-background-color: #49433c;--form-element-disabled-border-color: #655c53;--form-element-disabled-opacity: 0.5;--form-element-invalid-border-color: #5d0d05;--form-element-invalid-active-border-color: #751106;--form-element-invalid-focus-color: rgba(117,17,6,0.25);--form-element-valid-border-color: #159e33;--form-element-valid-active-border-color: #18b43a;--form-element-valid-focus-color: rgba(24,180,58,0.25);--switch-background-color: #574f47;--switch-color: var(--primary-inverse);--switch-checked-background-color: var(--primary);--range-border-color: #3b3630;--range-active-border-color: #49433c;--range-thumb-border-color: var(--background-color);--range-thumb-color: var(--secondary);--range-thumb-hover-color: var(--secondary-hover);--range-thumb-active-color: var(--primary);--table-border-color: var(--muted-border-color);--table-row-stripped-background-color: rgba(154,143,132,0.05);--code-background-color: #363330;--code-color: var(--muted-color);--code-kbd-background-color: var(--contrast);--code-kbd-color: var(--contrast-inverse);--code-tag-color: #a65980;--code-property-color: #599fa6;--code-value-color: #8c8473;--code-comment-color: #73695e;--accordion-border-color: var(--muted-border-color);--accordion-active-summary-color: var(--primary);--accordion-close-summary-color: var(--color);--accordion-open-summary-color: var(--muted-color);--card-background-color: #403c3a;--card-border-color: var(--card-background-color);--card-box-shadow:
7584+ .0145rem .029rem .174rem rgba(119,118,123,0.01698),
7585+ .0335rem .067rem .402rem rgba(119,118,123,0.024),
7586+ .0625rem .125rem .75rem rgba(119,118,123,0.03),
7587+ .1125rem .225rem 1.35rem rgba(119,118,123,0.036),
7588+ .2085rem .417rem 2.502rem rgba(119,118,123,0.04302),
7589+ .5rem 1rem 6rem rgba(119,118,123,0.06),
7590+ 0 0 0 0.0625rem rgba(119,118,123,0.015);--card-sectionning-background-color: #363330;--dropdown-background-color: #2d2925;--dropdown-border-color: #3b3630;--dropdown-box-shadow: var(--card-box-shadow);--dropdown-color: var(--color);--dropdown-hover-background-color: rgba(59,54,48,0.75);--modal-overlay-background-color: rgba(59,54,48,0.8);--progress-background-color: #3b3630;--progress-color: var(--primary);--loading-spinner-opacity: 0.5;--tooltip-background-color: var(--contrast);--tooltip-color: var(--contrast-inverse);--icon-checkbox: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(255, 255, 255)' stroke-width='4' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='20 6 9 17 4 12'%3E%3C/polyline%3E%3C/svg%3E");--icon-chevron: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(200.1, 194, 187.9)' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'%3E%3C/polyline%3E%3C/svg%3E");--icon-chevron-button: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(255, 255, 255)' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'%3E%3C/polyline%3E%3C/svg%3E");--icon-chevron-button-inverse: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(119, 118, 123)' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'%3E%3C/polyline%3E%3C/svg%3E");--icon-close: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(154.2, 143, 131.8)' stroke-width='4' stroke-linecap='round' stroke-linejoin='round'%3E%3Cline x1='18' y1='6' x2='6' y2='18'%3E%3C/line%3E%3Cline x1='6' y1='6' x2='18' y2='18'%3E%3C/line%3E%3C/svg%3E");--icon-date: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(200.1, 194, 187.9)' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Crect x='3' y='4' width='18' height='18' rx='2' ry='2'%3E%3C/rect%3E%3Cline x1='16' y1='2' x2='16' y2='6'%3E%3C/line%3E%3Cline x1='8' y1='2' x2='8' y2='6'%3E%3C/line%3E%3Cline x1='3' y1='10' x2='21' y2='10'%3E%3C/line%3E%3C/svg%3E");--icon-invalid: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(92.7049180328, 13.3196721311, 4.7950819672)' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='12' cy='12' r='10'%3E%3C/circle%3E%3Cline x1='12' y1='8' x2='12' y2='12'%3E%3C/line%3E%3Cline x1='12' y1='16' x2='12.01' y2='16'%3E%3C/line%3E%3C/svg%3E");--icon-minus: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(255, 255, 255)' stroke-width='4' stroke-linecap='round' stroke-linejoin='round'%3E%3Cline x1='5' y1='12' x2='19' y2='12'%3E%3C/line%3E%3C/svg%3E");--icon-search: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(200.1, 194, 187.9)' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='11' cy='11' r='8'%3E%3C/circle%3E%3Cline x1='21' y1='21' x2='16.65' y2='16.65'%3E%3C/line%3E%3C/svg%3E");--icon-time: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(200.1, 194, 187.9)' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='12' cy='12' r='10'%3E%3C/circle%3E%3Cpolyline points='12 6 12 12 16 14'%3E%3C/polyline%3E%3C/svg%3E");--icon-valid: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(21.1417322835, 157.8582677165, 50.7401574803)' stroke-width='3' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='20 6 9 17 4 12'%3E%3C/polyline%3E%3C/svg%3E");color-scheme:dark}progress,[type="checkbox"],[type="radio"],[type="range"]{accent-color:var(--primary)}*,*::before,*::after{box-sizing:border-box;background-repeat:no-repeat}::before,::after{text-decoration:inherit;vertical-align:inherit}:where(:root){-webkit-tap-highlight-color:transparent;-webkit-text-size-adjust:100%;text-size-adjust:100%;background-color:var(--background-color);color:var(--color);font-weight:var(--font-weight);font-size:var(--font-size);line-height:var(--line-height);font-family:var(--font-family);text-rendering:optimizeLegibility;overflow-wrap:break-word;cursor:default;tab-size:4}main{display:block}body{width:100%;margin:0}body>header,body>main,body>footer{width:100%;margin-right:auto;margin-left:auto;padding:var(--block-spacing-vertical) 0}.container,.container-fluid{width:100%;margin-right:auto;margin-left:auto;padding-right:var(--spacing);padding-left:var(--spacing)}@media (min-width: 576px){.container{max-width:510px;padding-right:0;padding-left:0}}@media (min-width: 768px){.container{max-width:700px}}@media (min-width: 992px){.container{max-width:920px}}@media (min-width: 1200px){.container{max-width:1130px}}section{margin-bottom:var(--block-spacing-vertical)}.grid{grid-column-gap:var(--grid-spacing-horizontal);grid-row-gap:var(--grid-spacing-vertical);display:grid;grid-template-columns:1fr;margin:0}@media (min-width: 992px){.grid{grid-template-columns:repeat(auto-fit, minmax(0%, 1fr))}}.grid>*{min-width:0}figure{display:block;margin:0;padding:0;overflow-x:auto}figure figcaption{padding:calc(var(--spacing) * 0.5) 0;color:var(--muted-color)}b,strong{font-weight:bolder}sub,sup{position:relative;font-size:0.75em;line-height:0;vertical-align:baseline}sub{bottom:-0.25em}sup{top:-0.5em}address,blockquote,dl,figure,form,ol,p,pre,table,ul{margin-top:0;margin-bottom:var(--typography-spacing-vertical);color:var(--color);font-style:normal;font-weight:var(--font-weight);font-size:var(--font-size)}a,[role="link"]{--color: var(--primary);--background-color: transparent;outline:none;background-color:var(--background-color);color:var(--color);text-decoration:var(--text-decoration);transition:background-color var(--transition),color var(--transition),text-decoration var(--transition),box-shadow var(--transition)}a:is([aria-current], :hover, :active, :focus),[role="link"]:is([aria-current], :hover, :active, :focus){--color: var(--primary-hover);--text-decoration: underline}a:focus,[role="link"]:focus{--background-color: var(--primary-focus)}a.secondary,[role="link"].secondary{--color: var(--secondary)}a.secondary:is([aria-current], :hover, :active, :focus),[role="link"].secondary:is([aria-current], :hover, :active, :focus){--color: var(--secondary-hover)}a.secondary:focus,[role="link"].secondary:focus{--background-color: var(--secondary-focus)}a.contrast,[role="link"].contrast{--color: var(--contrast)}a.contrast:is([aria-current], :hover, :active, :focus),[role="link"].contrast:is([aria-current], :hover, :active, :focus){--color: var(--contrast-hover)}a.contrast:focus,[role="link"].contrast:focus{--background-color: var(--contrast-focus)}h1,h2,h3,h4,h5,h6{margin-top:0;margin-bottom:var(--typography-spacing-vertical);color:var(--color);font-weight:var(--font-weight);font-size:var(--font-size);font-family:var(--font-family)}h1{--color: var(--h1-color)}h2{--color: var(--h2-color)}h3{--color: var(--h3-color)}h4{--color: var(--h4-color)}h5{--color: var(--h5-color)}h6{--color: var(--h6-color)}:where(address, blockquote, dl, figure, form, ol, p, pre, table, ul)~:is(h1, h2, h3, h4, h5, h6){margin-top:var(--typography-spacing-vertical)}hgroup,.headings{margin-bottom:var(--typography-spacing-vertical)}hgroup>*,.headings>*{margin-bottom:0}hgroup>*:last-child,.headings>*:last-child{--color: var(--muted-color);--font-weight: unset;font-size:1rem;font-family:unset}p{margin-bottom:var(--typography-spacing-vertical)}small{font-size:var(--font-size)}:where(dl, ol, ul){padding-right:0;padding-left:var(--spacing);padding-inline-start:var(--spacing);padding-inline-end:0}:where(dl, ol, ul) li{margin-bottom:calc(var(--typography-spacing-vertical) * 0.25)}:where(dl, ol, ul) :is(dl, ol, ul){margin:0;margin-top:calc(var(--typography-spacing-vertical) * 0.25)}ul li{list-style:square}mark{padding:0.125rem 0.25rem;background-color:var(--mark-background-color);color:var(--mark-color);vertical-align:baseline}blockquote{display:block;margin:var(--typography-spacing-vertical) 0;padding:var(--spacing);border-right:none;border-left:0.25rem solid var(--blockquote-border-color);border-inline-start:0.25rem solid var(--blockquote-border-color);border-inline-end:none}blockquote footer{margin-top:calc(var(--typography-spacing-vertical) * 0.5);color:var(--blockquote-footer-color)}abbr[title]{border-bottom:1px dotted;text-decoration:none;cursor:help}ins{color:var(--ins-color);text-decoration:none}del{color:var(--del-color)}::selection{background-color:var(--primary-focus)}:where(audio, canvas, iframe, img, svg, video){vertical-align:middle}audio,video{display:inline-block}audio:not([controls]){display:none;height:0}:where(iframe){border-style:none}img{max-width:100%;height:auto;border-style:none}:where(svg:not([fill])){fill:currentColor}svg:not(:root){overflow:hidden}button{margin:0;overflow:visible;font-family:inherit;text-transform:none}button,[type="button"],[type="reset"],[type="submit"]{-webkit-appearance:button}button{display:block;width:100%;margin-bottom:var(--spacing)}[role="button"]{display:inline-block;text-decoration:none}button,input[type="submit"],input[type="button"],input[type="reset"],[role="button"]{--background-color: var(--primary);--border-color: var(--primary);--color: var(--primary-inverse);--box-shadow: var(--button-box-shadow, 0 0 0 rgba(0, 0, 0, 0));padding:var(--form-element-spacing-vertical) var(--form-element-spacing-horizontal);border:var(--border-width) solid var(--border-color);border-radius:var(--border-radius);outline:none;background-color:var(--background-color);box-shadow:var(--box-shadow);color:var(--color);font-weight:var(--font-weight);font-size:1rem;line-height:var(--line-height);text-align:center;cursor:pointer;transition:background-color var(--transition),border-color var(--transition),color var(--transition),box-shadow var(--transition)}button:is([aria-current], :hover, :active, :focus),input[type="submit"]:is([aria-current], :hover, :active, :focus),input[type="button"]:is([aria-current], :hover, :active, :focus),input[type="reset"]:is([aria-current], :hover, :active, :focus),[role="button"]:is([aria-current], :hover, :active, :focus){--background-color: var(--primary-hover);--border-color: var(--primary-hover);--box-shadow: var(--button-hover-box-shadow, 0 0 0 rgba(0, 0, 0, 0));--color: var(--primary-inverse)}button:focus,input[type="submit"]:focus,input[type="button"]:focus,input[type="reset"]:focus,[role="button"]:focus{--box-shadow: var(--button-hover-box-shadow, 0 0 0 rgba(0, 0, 0, 0)),
7591+ 0 0 0 var(--outline-width) var(--primary-focus)}:is(button, input[type="submit"], input[type="button"], [role="button"]).secondary,input[type="reset"]{--background-color: var(--secondary);--border-color: var(--secondary);--color: var(--secondary-inverse);cursor:pointer}:is(button, input[type="submit"], input[type="button"], [role="button"]).secondary:is([aria-current], :hover, :active, :focus),input[type="reset"]:is([aria-current], :hover, :active, :focus){--background-color: var(--secondary-hover);--border-color: var(--secondary-hover);--color: var(--secondary-inverse)}:is(button, input[type="submit"], input[type="button"], [role="button"]).secondary:focus,input[type="reset"]:focus{--box-shadow: var(--button-hover-box-shadow, 0 0 0 rgba(0, 0, 0, 0)),
7592+ 0 0 0 var(--outline-width) var(--secondary-focus)}:is(button, input[type="submit"], input[type="button"], [role="button"]).contrast{--background-color: var(--contrast);--border-color: var(--contrast);--color: var(--contrast-inverse)}:is(button, input[type="submit"], input[type="button"], [role="button"]).contrast:is([aria-current], :hover, :active, :focus){--background-color: var(--contrast-hover);--border-color: var(--contrast-hover);--color: var(--contrast-inverse)}:is(button, input[type="submit"], input[type="button"], [role="button"]).contrast:focus{--box-shadow: var(--button-hover-box-shadow, 0 0 0 rgba(0, 0, 0, 0)),
7593+ 0 0 0 var(--outline-width) var(--contrast-focus)}:is(button, input[type="submit"], input[type="button"], [role="button"]).outline,input[type="reset"].outline{--background-color: transparent;--color: var(--primary)}:is(button, input[type="submit"], input[type="button"], [role="button"]).outline:is([aria-current], :hover, :active, :focus),input[type="reset"].outline:is([aria-current], :hover, :active, :focus){--background-color: transparent;--color: var(--primary-hover)}:is(button, input[type="submit"], input[type="button"], [role="button"]).outline.secondary,input[type="reset"].outline{--color: var(--secondary)}:is(button, input[type="submit"], input[type="button"], [role="button"]).outline.secondary:is([aria-current], :hover, :active, :focus),input[type="reset"].outline:is([aria-current], :hover, :active, :focus){--color: var(--secondary-hover)}:is(button, input[type="submit"], input[type="button"], [role="button"]).outline.contrast{--color: var(--contrast)}:is(button, input[type="submit"], input[type="button"], [role="button"]).outline.contrast:is([aria-current], :hover, :active, :focus){--color: var(--contrast-hover)}:where(button, [type="submit"], [type="button"], [type="reset"], [role="button"])[disabled],:where(fieldset[disabled]) :is(button, [type="submit"], [type="button"], [type="reset"], [role="button"]),a[role="button"]:not([href]){opacity:0.5;pointer-events:none}input,optgroup,select,textarea{margin:0;font-size:1rem;line-height:var(--line-height);font-family:inherit;letter-spacing:inherit}input{overflow:visible}select{text-transform:none}legend{max-width:100%;padding:0;color:inherit;white-space:normal}textarea{overflow:auto}[type="checkbox"],[type="radio"]{padding:0}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type="search"]{-webkit-appearance:textfield;outline-offset:-2px}[type="search"]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}::-moz-focus-inner{padding:0;border-style:none}:-moz-focusring{outline:none}:-moz-ui-invalid{box-shadow:none}::-ms-expand{display:none}[type="file"],[type="range"]{padding:0;border-width:0}input:not([type="checkbox"],[type="radio"],[type="range"]){height:calc( (1rem * var(--line-height)) + (var(--form-element-spacing-vertical) * 2) + (var(--border-width) * 2))}fieldset{margin:0;margin-bottom:var(--spacing);padding:0;border:0}label,fieldset legend{display:block;margin-bottom:calc(var(--spacing) * 0.25);font-weight:var(--form-label-font-weight, var(--font-weight))}input:not([type="checkbox"],[type="radio"]),select,textarea{width:100%}input:not([type="checkbox"],[type="radio"],[type="range"],[type="file"]),select,textarea{appearance:none;padding:var(--form-element-spacing-vertical) var(--form-element-spacing-horizontal)}input,select,textarea{--background-color: var(--form-element-background-color);--border-color: var(--form-element-border-color);--color: var(--form-element-color);--box-shadow: none;border:var(--border-width) solid var(--border-color);border-radius:var(--border-radius);outline:none;background-color:var(--background-color);box-shadow:var(--box-shadow);color:var(--color);font-weight:var(--font-weight);transition:background-color var(--transition),border-color var(--transition),color var(--transition),box-shadow var(--transition)}input:not([type="submit"],[type="button"],[type="reset"],[type="checkbox"],[type="radio"],[readonly]):is(:active, :focus),:where(select, textarea):is(:active, :focus){--background-color: var(--form-element-active-background-color)}input:not([type="submit"],[type="button"],[type="reset"],[role="switch"],[readonly]):is(:active, :focus),:where(select, textarea):is(:active, :focus){--border-color: var(--form-element-active-border-color)}input:not([type="submit"],[type="button"],[type="reset"],[type="range"],[type="file"],[readonly]):focus,select:focus,textarea:focus{--box-shadow: 0 0 0 var(--outline-width) var(--form-element-focus-color)}input:not([type="submit"],[type="button"],[type="reset"])[disabled],select[disabled],textarea[disabled],:where(fieldset[disabled]) :is(input:not([type="submit"], [type="button"], [type="reset"]), select, textarea){--background-color: var(--form-element-disabled-background-color);--border-color: var(--form-element-disabled-border-color);opacity:var(--form-element-disabled-opacity);pointer-events:none}:where(input, select, textarea):not([type="checkbox"],[type="radio"],[type="date"],[type="datetime-local"],[type="month"],[type="time"],[type="week"])[aria-invalid]{padding-right:calc( var(--form-element-spacing-horizontal) + 1.5rem) !important;padding-left:var(--form-element-spacing-horizontal);padding-inline-start:var(--form-element-spacing-horizontal) !important;padding-inline-end:calc( var(--form-element-spacing-horizontal) + 1.5rem) !important;background-position:center right 0.75rem;background-size:1rem auto;background-repeat:no-repeat}:where(input, select, textarea):not([type="checkbox"],[type="radio"],[type="date"],[type="datetime-local"],[type="month"],[type="time"],[type="week"])[aria-invalid="false"]{background-image:var(--icon-valid)}:where(input, select, textarea):not([type="checkbox"],[type="radio"],[type="date"],[type="datetime-local"],[type="month"],[type="time"],[type="week"])[aria-invalid="true"]{background-image:var(--icon-invalid)}:where(input, select, textarea)[aria-invalid="false"]{--border-color: var(--form-element-valid-border-color)}:where(input, select, textarea)[aria-invalid="false"]:is(:active, :focus){--border-color: var(--form-element-valid-active-border-color) !important;--box-shadow: 0 0 0 var(--outline-width) var(--form-element-valid-focus-color) !important}:where(input, select, textarea)[aria-invalid="true"]{--border-color: var(--form-element-invalid-border-color)}:where(input, select, textarea)[aria-invalid="true"]:is(:active, :focus){--border-color: var(--form-element-invalid-active-border-color) !important;--box-shadow: 0 0 0 var(--outline-width) var(--form-element-invalid-focus-color) !important}[dir="rtl"] :where(input, select, textarea):not([type="checkbox"],[type="radio"]):is([aria-invalid], [aria-invalid="true"], [aria-invalid="false"] ){background-position:center left 0.75rem}input::placeholder,input::-webkit-input-placeholder,textarea::placeholder,textarea::-webkit-input-placeholder,select:invalid{color:var(--form-element-placeholder-color);opacity:1}input:not([type="checkbox"],[type="radio"]),select,textarea{margin-bottom:var(--spacing)}select::-ms-expand{border:0;background-color:transparent}select:not([multiple],[size]){padding-right:calc(var(--form-element-spacing-horizontal) + 1.5rem);padding-left:var(--form-element-spacing-horizontal);padding-inline-start:var(--form-element-spacing-horizontal);padding-inline-end:calc(var(--form-element-spacing-horizontal) + 1.5rem);background-image:var(--icon-chevron);background-position:center right 0.75rem;background-size:1rem auto;background-repeat:no-repeat}[dir="rtl"] select:not([multiple],[size]){background-position:center left 0.75rem}:where(input, select, textarea, .grid)+small{display:block;width:100%;margin-top:calc(var(--spacing) * -0.75);margin-bottom:var(--spacing);color:var(--muted-color)}label>:where(input, select, textarea){margin-top:calc(var(--spacing) * 0.25)}[type="checkbox"],[type="radio"]{-webkit-appearance:none;-moz-appearance:none;appearance:none;width:1.25em;height:1.25em;margin-top:-0.125em;margin-right:0.375em;margin-left:0;margin-inline-start:0;margin-inline-end:0.375em;border-width:var(--border-width);font-size:inherit;vertical-align:middle;cursor:pointer}[type="checkbox"]::-ms-check,[type="radio"]::-ms-check{display:none}[type="checkbox"]:checked,[type="checkbox"]:checked:active,[type="checkbox"]:checked:focus,[type="radio"]:checked,[type="radio"]:checked:active,[type="radio"]:checked:focus{--background-color: var(--primary);--border-color: var(--primary);background-image:var(--icon-checkbox);background-position:center;background-size:0.75em auto;background-repeat:no-repeat}[type="checkbox"]~label,[type="radio"]~label{display:inline-block;margin-right:0.375em;margin-bottom:0;cursor:pointer}[type="checkbox"]:indeterminate{--background-color: var(--primary);--border-color: var(--primary);background-image:var(--icon-minus);background-position:center;background-size:0.75em auto;background-repeat:no-repeat}[type="radio"]{border-radius:50%}[type="radio"]:checked,[type="radio"]:checked:active,[type="radio"]:checked:focus{--background-color: var(--primary-inverse);border-width:0.35em;background-image:none}[type="checkbox"][role="switch"]{--background-color: var(--switch-background-color);--border-color: var(--switch-background-color);--color: var(--switch-color);width:2.25em;height:1.25em;border:var(--border-width) solid var(--border-color);border-radius:1.25em;background-color:var(--background-color);line-height:1.25em}[type="checkbox"][role="switch"]:focus{--background-color: var(--switch-background-color);--border-color: var(--switch-background-color)}[type="checkbox"][role="switch"]:checked{--background-color: var(--switch-checked-background-color);--border-color: var(--switch-checked-background-color)}[type="checkbox"][role="switch"]:before{display:block;width:calc(1.25em - (var(--border-width) * 2));height:100%;border-radius:50%;background-color:var(--color);content:"";transition:margin 0.1s ease-in-out}[type="checkbox"][role="switch"]:checked{background-image:none}[type="checkbox"][role="switch"]:checked::before{margin-left:calc(1.125em - var(--border-width));margin-inline-start:calc(1.125em - var(--border-width))}[type="checkbox"][aria-invalid="false"],[type="checkbox"]:checked[aria-invalid="false"],[type="radio"][aria-invalid="false"],[type="radio"]:checked[aria-invalid="false"],[type="checkbox"][role="switch"][aria-invalid="false"],[type="checkbox"][role="switch"]:checked[aria-invalid="false"]{--border-color: var(--form-element-valid-border-color)}[type="checkbox"][aria-invalid="true"],[type="checkbox"]:checked[aria-invalid="true"],[type="radio"][aria-invalid="true"],[type="radio"]:checked[aria-invalid="true"],[type="checkbox"][role="switch"][aria-invalid="true"],[type="checkbox"][role="switch"]:checked[aria-invalid="true"]{--border-color: var(--form-element-invalid-border-color)}[type="color"]::-webkit-color-swatch-wrapper{padding:0}[type="color"]::-moz-focus-inner{padding:0}[type="color"]::-webkit-color-swatch{border:0;border-radius:calc(var(--border-radius) * 0.5)}[type="color"]::-moz-color-swatch{border:0;border-radius:calc(var(--border-radius) * 0.5)}input:not([type="checkbox"],[type="radio"],[type="range"],[type="file"]):is([type="date"], [type="datetime-local"], [type="month"], [type="time"], [type="week"]){--icon-position: 0.75rem;--icon-width: 1rem;padding-right:calc(var(--icon-width) + var(--icon-position));background-image:var(--icon-date);background-position:center right var(--icon-position);background-size:var(--icon-width) auto;background-repeat:no-repeat}input:not([type="checkbox"],[type="radio"],[type="range"],[type="file"])[type="time"]{background-image:var(--icon-time)}[type="date"]::-webkit-calendar-picker-indicator,[type="datetime-local"]::-webkit-calendar-picker-indicator,[type="month"]::-webkit-calendar-picker-indicator,[type="time"]::-webkit-calendar-picker-indicator,[type="week"]::-webkit-calendar-picker-indicator{width:var(--icon-width);margin-right:calc(var(--icon-width) * -1);margin-left:var(--icon-position);opacity:0}[dir="rtl"] :is([type="date"], [type="datetime-local"], [type="month"], [type="time"], [type="week"]){text-align:right}@-moz-document url-prefix(){[type="date"],[type="datetime-local"],[type="month"],[type="time"],[type="week"]{padding-right:var(--form-element-spacing-horizontal) !important;background-image:none !important}}[type="file"]{--color: var(--muted-color);padding:calc(var(--form-element-spacing-vertical) * 0.5) 0;border:0;border-radius:0;background:none}[type="file"]::file-selector-button{--background-color: var(--secondary);--border-color: var(--secondary);--color: var(--secondary-inverse);margin-right:calc(var(--spacing) / 2);margin-left:0;margin-inline-start:0;margin-inline-end:calc(var(--spacing) / 2);padding:calc(var(--form-element-spacing-vertical) * 0.5) calc(var(--form-element-spacing-horizontal) * 0.5);border:var(--border-width) solid var(--border-color);border-radius:var(--border-radius);outline:none;background-color:var(--background-color);box-shadow:var(--box-shadow);color:var(--color);font-weight:var(--font-weight);font-size:1rem;line-height:var(--line-height);text-align:center;cursor:pointer;transition:background-color var(--transition),border-color var(--transition),color var(--transition),box-shadow var(--transition)}[type="file"]::file-selector-button:is(:hover, :active, :focus){--background-color: var(--secondary-hover);--border-color: var(--secondary-hover)}[type="file"]::-webkit-file-upload-button{--background-color: var(--secondary);--border-color: var(--secondary);--color: var(--secondary-inverse);margin-right:calc(var(--spacing) / 2);margin-left:0;margin-inline-start:0;margin-inline-end:calc(var(--spacing) / 2);padding:calc(var(--form-element-spacing-vertical) * 0.5) calc(var(--form-element-spacing-horizontal) * 0.5);border:var(--border-width) solid var(--border-color);border-radius:var(--border-radius);outline:none;background-color:var(--background-color);box-shadow:var(--box-shadow);color:var(--color);font-weight:var(--font-weight);font-size:1rem;line-height:var(--line-height);text-align:center;cursor:pointer;transition:background-color var(--transition),border-color var(--transition),color var(--transition),box-shadow var(--transition)}[type="file"]::-webkit-file-upload-button:is(:hover, :active, :focus){--background-color: var(--secondary-hover);--border-color: var(--secondary-hover)}[type="file"]::-ms-browse{--background-color: var(--secondary);--border-color: var(--secondary);--color: var(--secondary-inverse);margin-right:calc(var(--spacing) / 2);margin-left:0;margin-inline-start:0;margin-inline-end:calc(var(--spacing) / 2);padding:calc(var(--form-element-spacing-vertical) * 0.5) calc(var(--form-element-spacing-horizontal) * 0.5);border:var(--border-width) solid var(--border-color);border-radius:var(--border-radius);outline:none;background-color:var(--background-color);box-shadow:var(--box-shadow);color:var(--color);font-weight:var(--font-weight);font-size:1rem;line-height:var(--line-height);text-align:center;cursor:pointer;transition:background-color var(--transition),border-color var(--transition),color var(--transition),box-shadow var(--transition)}[type="file"]::-ms-browse:is(:hover, :active, :focus){--background-color: var(--secondary-hover);--border-color: var(--secondary-hover)}[type="range"]{-webkit-appearance:none;-moz-appearance:none;appearance:none;width:100%;height:1.25rem;background:none}[type="range"]::-webkit-slider-runnable-track{width:100%;height:.25rem;border-radius:var(--border-radius);background-color:var(--range-border-color);transition:background-color var(--transition),box-shadow var(--transition)}[type="range"]::-moz-range-track{width:100%;height:.25rem;border-radius:var(--border-radius);background-color:var(--range-border-color);transition:background-color var(--transition),box-shadow var(--transition)}[type="range"]::-ms-track{width:100%;height:.25rem;border-radius:var(--border-radius);background-color:var(--range-border-color);transition:background-color var(--transition),box-shadow var(--transition)}[type="range"]::-webkit-slider-thumb{-webkit-appearance:none;width:1.25rem;height:1.25rem;margin-top:-.5rem;border:2px solid var(--range-thumb-border-color);border-radius:50%;background-color:var(--range-thumb-color);cursor:pointer;transition:background-color var(--transition),transform var(--transition)}[type="range"]::-moz-range-thumb{-webkit-appearance:none;width:1.25rem;height:1.25rem;margin-top:-.5rem;border:2px solid var(--range-thumb-border-color);border-radius:50%;background-color:var(--range-thumb-color);cursor:pointer;transition:background-color var(--transition),transform var(--transition)}[type="range"]::-ms-thumb{-webkit-appearance:none;width:1.25rem;height:1.25rem;margin-top:-.5rem;border:2px solid var(--range-thumb-border-color);border-radius:50%;background-color:var(--range-thumb-color);cursor:pointer;transition:background-color var(--transition),transform var(--transition)}[type="range"]:hover,[type="range"]:focus{--range-border-color: var(--range-active-border-color);--range-thumb-color: var(--range-thumb-hover-color)}[type="range"]:active{--range-thumb-color: var(--range-thumb-active-color)}[type="range"]:active::-webkit-slider-thumb{transform:scale(1.25)}[type="range"]:active::-moz-range-thumb{transform:scale(1.25)}[type="range"]:active::-ms-thumb{transform:scale(1.25)}input:not([type="checkbox"],[type="radio"],[type="range"],[type="file"])[type="search"]{padding-inline-start:calc(var(--form-element-spacing-horizontal) + 1.75rem);border-radius:5rem;background-image:var(--icon-search);background-position:center left 1.125rem;background-size:1rem auto;background-repeat:no-repeat}input:not([type="checkbox"],[type="radio"],[type="range"],[type="file"])[type="search"][aria-invalid]{padding-inline-start:calc(var(--form-element-spacing-horizontal) + 1.75rem) !important;background-position:center left 1.125rem, center right 0.75rem}input:not([type="checkbox"],[type="radio"],[type="range"],[type="file"])[type="search"][aria-invalid="false"]{background-image:var(--icon-search),var(--icon-valid)}input:not([type="checkbox"],[type="radio"],[type="range"],[type="file"])[type="search"][aria-invalid="true"]{background-image:var(--icon-search),var(--icon-invalid)}[type="search"]::-webkit-search-cancel-button{-webkit-appearance:none;display:none}[dir="rtl"] :where(input):not([type="checkbox"],[type="radio"],[type="range"],[type="file"])[type="search"]{background-position:center right 1.125rem}[dir="rtl"] :where(input):not([type="checkbox"],[type="radio"],[type="range"],[type="file"])[type="search"][aria-invalid]{background-position:center right 1.125rem, center left 0.75rem}:where(table){width:100%;border-collapse:collapse;border-spacing:0;text-indent:0}th,td{padding:calc(var(--spacing) / 2) var(--spacing);border-bottom:var(--border-width) solid var(--table-border-color);color:var(--color);font-weight:var(--font-weight);font-size:var(--font-size);text-align:left;text-align:start}tfoot th,tfoot td{border-top:var(--border-width) solid var(--table-border-color);border-bottom:0}table[role="grid"] tbody tr:nth-child(odd){background-color:var(--table-row-stripped-background-color)}pre,code,kbd,samp{font-size:0.875em;font-family:var(--font-family)}pre{-ms-overflow-style:scrollbar;overflow:auto}pre,code,kbd{border-radius:var(--border-radius);background:var(--code-background-color);color:var(--code-color);font-weight:var(--font-weight);line-height:initial}code,kbd{display:inline-block;padding:0.375rem 0.5rem}pre{display:block;margin-bottom:var(--spacing);overflow-x:auto}pre>code{display:block;padding:var(--spacing);background:none;font-size:14px;line-height:var(--line-height)}code b{color:var(--code-tag-color);font-weight:var(--font-weight)}code i{color:var(--code-property-color);font-style:normal}code u{color:var(--code-value-color);text-decoration:none}code em{color:var(--code-comment-color);font-style:normal}kbd{background-color:var(--code-kbd-background-color);color:var(--code-kbd-color);vertical-align:baseline}hr{height:0;border:0;border-top:1px solid var(--muted-border-color);color:inherit}[hidden],template{display:none !important}canvas{display:inline-block}details{display:block;margin-bottom:var(--spacing);padding-bottom:var(--spacing);border-bottom:var(--border-width) solid var(--accordion-border-color)}details summary{line-height:1rem;list-style-type:none;cursor:pointer;transition:color var(--transition)}details summary:not([role]){color:var(--accordion-close-summary-color)}details summary::-webkit-details-marker{display:none}details summary::marker{display:none}details summary::-moz-list-bullet{list-style-type:none}details summary::after{display:block;width:1rem;height:1rem;margin-inline-start:calc(var(--spacing, 1rem) * 0.5);float:right;transform:rotate(-90deg);background-image:var(--icon-chevron);background-position:right center;background-size:1rem auto;background-repeat:no-repeat;content:"";transition:transform var(--transition)}details summary:focus{outline:none}details summary:focus:not([role="button"]){color:var(--accordion-active-summary-color)}details summary[role="button"]{width:100%;text-align:left}details summary[role="button"]::after{height:calc(1rem * var(--line-height, 1.5));background-image:var(--icon-chevron-button)}details summary[role="button"]:not(.outline).contrast::after{background-image:var(--icon-chevron-button-inverse)}details[open]>summary{margin-bottom:calc(var(--spacing))}details[open]>summary:not([role]):not(:focus){color:var(--accordion-open-summary-color)}details[open]>summary::after{transform:rotate(0)}[dir="rtl"] details summary{text-align:right}[dir="rtl"] details summary::after{float:left;background-position:left center}article{margin:var(--block-spacing-vertical) 0;padding:var(--block-spacing-vertical) var(--block-spacing-horizontal);border-radius:var(--border-radius);background:var(--card-background-color);box-shadow:var(--card-box-shadow)}article>header,article>footer{margin-right:calc(var(--block-spacing-horizontal) * -1);margin-left:calc(var(--block-spacing-horizontal) * -1);padding:calc(var(--block-spacing-vertical) * 0.66) var(--block-spacing-horizontal);background-color:var(--card-sectionning-background-color)}article>header{margin-top:calc(var(--block-spacing-vertical) * -1);margin-bottom:var(--block-spacing-vertical);border-bottom:var(--border-width) solid var(--card-border-color);border-top-right-radius:var(--border-radius);border-top-left-radius:var(--border-radius)}article>footer{margin-top:var(--block-spacing-vertical);margin-bottom:calc(var(--block-spacing-vertical) * -1);border-top:var(--border-width) solid var(--card-border-color);border-bottom-right-radius:var(--border-radius);border-bottom-left-radius:var(--border-radius)}:root{--scrollbar-width: 0px}dialog{display:flex;z-index:999;position:fixed;top:0;right:0;bottom:0;left:0;align-items:center;justify-content:center;width:inherit;min-width:100%;height:inherit;min-height:100%;padding:var(--spacing);border:0;backdrop-filter:var(--modal-overlay-backdrop-filter);background-color:var(--modal-overlay-background-color);color:var(--color)}dialog article{max-height:calc(100vh - var(--spacing) * 2);overflow:auto}@media (min-width: 576px){dialog article{max-width:510px}}@media (min-width: 768px){dialog article{max-width:700px}}dialog article>header,dialog article>footer{padding:calc(var(--block-spacing-vertical) * 0.5) var(--block-spacing-horizontal)}dialog article>header .close{margin:0;margin-left:var(--spacing);float:right}dialog article>footer{text-align:right}dialog article>footer [role="button"]{margin-bottom:0}dialog article>footer [role="button"]:not(:first-of-type){margin-left:calc(var(--spacing) * 0.5)}dialog article p:last-of-type{margin:0}dialog article .close{display:block;width:1rem;height:1rem;margin-top:calc(var(--block-spacing-vertical) * -0.5);margin-bottom:var(--typography-spacing-vertical);margin-left:auto;background-image:var(--icon-close);background-position:center;background-size:auto 1rem;background-repeat:no-repeat;opacity:0.5;transition:opacity var(--transition)}dialog article .close:is([aria-current], :hover, :active, :focus){opacity:1}dialog:not([open]),dialog[open="false"]{display:none}.modal-is-open{padding-right:var(--scrollbar-width, 0px);overflow:hidden;pointer-events:none;touch-action:none}.modal-is-open dialog{pointer-events:auto}:where(.modal-is-opening, .modal-is-closing) dialog,:where(.modal-is-opening, .modal-is-closing) dialog>article{animation-duration:.2s;animation-timing-function:ease-in-out;animation-fill-mode:both}:where(.modal-is-opening, .modal-is-closing) dialog{animation-duration:.8s;animation-name:modal-overlay}:where(.modal-is-opening, .modal-is-closing) dialog>article{animation-delay:.2s;animation-name:modal}.modal-is-closing dialog,.modal-is-closing dialog>article{animation-delay:0s;animation-direction:reverse}@keyframes modal-overlay{from{backdrop-filter:none;background-color:transparent}}@keyframes modal{from{transform:translateY(-100%);opacity:0}}:where(nav li)::before{float:left;content:"\200B"}nav,nav ul{display:flex}nav{justify-content:space-between}nav ol,nav ul{align-items:center;margin-bottom:0;padding:0;list-style:none}nav ol:first-of-type,nav ul:first-of-type{margin-left:calc(var(--nav-element-spacing-horizontal) * -1)}nav ol:last-of-type,nav ul:last-of-type{margin-right:calc(var(--nav-element-spacing-horizontal) * -1)}nav li{display:inline-block;margin:0;padding:var(--nav-element-spacing-vertical) var(--nav-element-spacing-horizontal)}nav li>*{--spacing: 0}nav :where(a, [role="link"]){display:inline-block;margin:calc(var(--nav-link-spacing-vertical) * -1) calc(var(--nav-link-spacing-horizontal) * -1);padding:var(--nav-link-spacing-vertical) var(--nav-link-spacing-horizontal);border-radius:var(--border-radius);text-decoration:none}nav :where(a, [role="link"]):is([aria-current], :hover, :active, :focus){text-decoration:none}nav[aria-label="breadcrumb"]{align-items:center;justify-content:start}nav[aria-label="breadcrumb"] ul li:not(:first-child){margin-inline-start:var(--nav-link-spacing-horizontal)}nav[aria-label="breadcrumb"] ul li:not(:last-child) ::after{position:absolute;width:calc(var(--nav-link-spacing-horizontal) * 2);margin-inline-start:calc(var(--nav-link-spacing-horizontal) / 2);content:"/";color:var(--muted-color);text-align:center}nav[aria-label="breadcrumb"] a[aria-current]{background-color:transparent;color:inherit;text-decoration:none;pointer-events:none}nav [role="button"]{margin-right:inherit;margin-left:inherit;padding:var(--nav-link-spacing-vertical) var(--nav-link-spacing-horizontal)}aside nav,aside ol,aside ul,aside li{display:block}aside li{padding:calc(var(--nav-element-spacing-vertical) * 0.5) var(--nav-element-spacing-horizontal)}aside li a{display:block}aside li [role="button"]{margin:inherit}[dir="rtl"] nav[aria-label="breadcrumb"] ul li:not(:last-child) ::after{content:"\\"}progress{display:inline-block;vertical-align:baseline}progress{-webkit-appearance:none;-moz-appearance:none;display:inline-block;appearance:none;width:100%;height:0.5rem;margin-bottom:calc(var(--spacing) * 0.5);overflow:hidden;border:0;border-radius:var(--border-radius);background-color:var(--progress-background-color);color:var(--progress-color)}progress::-webkit-progress-bar{border-radius:var(--border-radius);background:none}progress[value]::-webkit-progress-value{background-color:var(--progress-color)}progress::-moz-progress-bar{background-color:var(--progress-color)}@media (prefers-reduced-motion: no-preference){progress:indeterminate{background:var(--progress-background-color) linear-gradient(to right, var(--progress-color) 30%, var(--progress-background-color) 30%) top left/150% 150% no-repeat;animation:progress-indeterminate 1s linear infinite}progress:indeterminate[value]::-webkit-progress-value{background-color:transparent}progress:indeterminate::-moz-progress-bar{background-color:transparent}}@media (prefers-reduced-motion: no-preference){[dir="rtl"] progress:indeterminate{animation-direction:reverse}}@keyframes progress-indeterminate{0%{background-position:200% 0}100%{background-position:-200% 0}}details[role="list"],li[role="list"]{position:relative}details[role="list"] summary+ul,li[role="list"]>ul{display:flex;z-index:99;position:absolute;top:auto;right:0;left:0;flex-direction:column;margin:0;padding:0;border:var(--border-width) solid var(--dropdown-border-color);border-radius:var(--border-radius);border-top-right-radius:0;border-top-left-radius:0;background-color:var(--dropdown-background-color);box-shadow:var(--card-box-shadow);color:var(--dropdown-color);white-space:nowrap}details[role="list"] summary+ul li,li[role="list"]>ul li{width:100%;margin-bottom:0;padding:calc(var(--form-element-spacing-vertical) * 0.5) var(--form-element-spacing-horizontal);list-style:none}details[role="list"] summary+ul li:first-of-type,li[role="list"]>ul li:first-of-type{margin-top:calc(var(--form-element-spacing-vertical) * 0.5)}details[role="list"] summary+ul li:last-of-type,li[role="list"]>ul li:last-of-type{margin-bottom:calc(var(--form-element-spacing-vertical) * 0.5)}details[role="list"] summary+ul li a,li[role="list"]>ul li a{display:block;margin:calc(var(--form-element-spacing-vertical) * -0.5) calc(var(--form-element-spacing-horizontal) * -1);padding:calc(var(--form-element-spacing-vertical) * 0.5) var(--form-element-spacing-horizontal);overflow:hidden;color:var(--dropdown-color);text-decoration:none;text-overflow:ellipsis}details[role="list"] summary+ul li a:hover,li[role="list"]>ul li a:hover{background-color:var(--dropdown-hover-background-color)}details[role="list"] summary::after,li[role="list"]>a::after{display:block;width:1rem;height:calc(1rem * var(--line-height, 1.5));margin-inline-start:0.5rem;float:right;transform:rotate(0deg);background-position:right center;background-size:1rem auto;background-repeat:no-repeat;content:""}details[role="list"]{padding:0;border-bottom:none}details[role="list"] summary{margin-bottom:0}details[role="list"] summary:not([role]){height:calc( 1rem * var(--line-height) + var(--form-element-spacing-vertical) * 2 + var(--border-width) * 2);padding:var(--form-element-spacing-vertical) var(--form-element-spacing-horizontal);border:var(--border-width) solid var(--form-element-border-color);border-radius:var(--border-radius);background-color:var(--form-element-background-color);color:var(--form-element-placeholder-color);line-height:inherit;cursor:pointer;transition:background-color var(--transition),border-color var(--transition),color var(--transition),box-shadow var(--transition)}details[role="list"] summary:not([role]):active,details[role="list"] summary:not([role]):focus{border-color:var(--form-element-active-border-color);background-color:var(--form-element-active-background-color)}details[role="list"] summary:not([role]):focus{box-shadow:0 0 0 var(--outline-width) var(--form-element-focus-color)}details[role="list"][open] summary{border-bottom-right-radius:0;border-bottom-left-radius:0}details[role="list"][open] summary::before{display:block;z-index:1;position:fixed;top:0;right:0;bottom:0;left:0;background:none;content:"";cursor:default}nav details[role="list"] summary,nav li[role="list"] a{display:flex;direction:ltr}nav details[role="list"] summary+ul,nav li[role="list"]>ul{min-width:fit-content;border-radius:var(--border-radius)}nav details[role="list"] summary+ul li a,nav li[role="list"]>ul li a{border-radius:0}nav details[role="list"] summary,nav details[role="list"] summary:not([role]){height:auto;padding:var(--nav-link-spacing-vertical) var(--nav-link-spacing-horizontal)}nav details[role="list"][open] summary{border-radius:var(--border-radius)}nav details[role="list"] summary+ul{margin-top:var(--outline-width);margin-inline-start:0}nav details[role="list"] summary[role="link"]{margin-bottom:calc(var(--nav-link-spacing-vertical) * -1);line-height:var(--line-height)}nav details[role="list"] summary[role="link"]+ul{margin-top:calc(var(--nav-link-spacing-vertical) + var(--outline-width));margin-inline-start:calc(var(--nav-link-spacing-horizontal) * -1)}li[role="list"]:hover>ul,li[role="list"] a:active~ul,li[role="list"] a:focus~ul{display:flex}li[role="list"]>ul{display:none;margin-top:calc(var(--nav-link-spacing-vertical) + var(--outline-width));margin-inline-start:calc( var(--nav-element-spacing-horizontal) - var(--nav-link-spacing-horizontal))}li[role="list"]>a::after{background-image:var(--icon-chevron)}label>details[role="list"]{margin-top:calc(var(--spacing) * .25);margin-bottom:var(--spacing)}[aria-busy="true"]{cursor:progress}[aria-busy="true"]:not(input,select,textarea,html)::before{display:inline-block;width:1em;height:1em;border:0.1875em solid currentColor;border-radius:1em;border-right-color:transparent;content:"";vertical-align:text-bottom;vertical-align:-.125em;animation:spinner 0.75s linear infinite;opacity:var(--loading-spinner-opacity)}[aria-busy="true"]:not(input,select,textarea,html):not(:empty)::before{margin-right:calc(var(--spacing) * 0.5);margin-left:0;margin-inline-start:0;margin-inline-end:calc(var(--spacing) * 0.5)}[aria-busy="true"]:not(input,select,textarea,html):empty{text-align:center}button[aria-busy="true"],input[type="submit"][aria-busy="true"],input[type="button"][aria-busy="true"],input[type="reset"][aria-busy="true"],a[aria-busy="true"]{pointer-events:none}@keyframes spinner{to{transform:rotate(360deg)}}[data-tooltip]{position:relative}[data-tooltip]:not(a,button,input){border-bottom:1px dotted;text-decoration:none;cursor:help}[data-tooltip][data-placement="top"]::before,[data-tooltip][data-placement="top"]::after,[data-tooltip]::before,[data-tooltip]::after{display:block;z-index:99;position:absolute;bottom:100%;left:50%;padding:.25rem .5rem;overflow:hidden;transform:translate(-50%, -0.25rem);border-radius:var(--border-radius);background:var(--tooltip-background-color);content:attr(data-tooltip);color:var(--tooltip-color);font-style:normal;font-weight:var(--font-weight);font-size:.875rem;text-decoration:none;text-overflow:ellipsis;white-space:nowrap;opacity:0;pointer-events:none}[data-tooltip][data-placement="top"]::after,[data-tooltip]::after{padding:0;transform:translate(-50%, 0rem);border-top:.3rem solid;border-right:.3rem solid transparent;border-left:.3rem solid transparent;border-radius:0;background-color:transparent;content:"";color:var(--tooltip-background-color)}[data-tooltip][data-placement="bottom"]::before,[data-tooltip][data-placement="bottom"]::after{top:100%;bottom:auto;transform:translate(-50%, 0.25rem)}[data-tooltip][data-placement="bottom"]:after{transform:translate(-50%, -0.3rem);border:.3rem solid transparent;border-bottom:.3rem solid}[data-tooltip][data-placement="left"]::before,[data-tooltip][data-placement="left"]::after{top:50%;right:100%;bottom:auto;left:auto;transform:translate(-0.25rem, -50%)}[data-tooltip][data-placement="left"]:after{transform:translate(0.3rem, -50%);border:.3rem solid transparent;border-left:.3rem solid}[data-tooltip][data-placement="right"]::before,[data-tooltip][data-placement="right"]::after{top:50%;right:auto;bottom:auto;left:100%;transform:translate(0.25rem, -50%)}[data-tooltip][data-placement="right"]:after{transform:translate(-0.3rem, -50%);border:.3rem solid transparent;border-right:.3rem solid}[data-tooltip]:focus::before,[data-tooltip]:focus::after,[data-tooltip]:hover::before,[data-tooltip]:hover::after{opacity:1}@media (hover: hover) and (pointer: fine){[data-tooltip][data-placement="bottom"]:focus::before,[data-tooltip][data-placement="bottom"]:focus::after,[data-tooltip][data-placement="bottom"]:hover [data-tooltip]:focus::before,[data-tooltip][data-placement="bottom"]:hover [data-tooltip]:focus::after,[data-tooltip]:hover::before,[data-tooltip]:hover::after{animation-duration:.2s;animation-name:tooltip-slide-top}[data-tooltip][data-placement="bottom"]:focus::after,[data-tooltip][data-placement="bottom"]:hover [data-tooltip]:focus::after,[data-tooltip]:hover::after{animation-name:tooltip-caret-slide-top}[data-tooltip][data-placement="bottom"]:focus::before,[data-tooltip][data-placement="bottom"]:focus::after,[data-tooltip][data-placement="bottom"]:hover::before,[data-tooltip][data-placement="bottom"]:hover::after{animation-duration:.2s;animation-name:tooltip-slide-bottom}[data-tooltip][data-placement="bottom"]:focus::after,[data-tooltip][data-placement="bottom"]:hover::after{animation-name:tooltip-caret-slide-bottom}[data-tooltip][data-placement="left"]:focus::before,[data-tooltip][data-placement="left"]:focus::after,[data-tooltip][data-placement="left"]:hover::before,[data-tooltip][data-placement="left"]:hover::after{animation-duration:.2s;animation-name:tooltip-slide-left}[data-tooltip][data-placement="left"]:focus::after,[data-tooltip][data-placement="left"]:hover::after{animation-name:tooltip-caret-slide-left}[data-tooltip][data-placement="right"]:focus::before,[data-tooltip][data-placement="right"]:focus::after,[data-tooltip][data-placement="right"]:hover::before,[data-tooltip][data-placement="right"]:hover::after{animation-duration:.2s;animation-name:tooltip-slide-right}[data-tooltip][data-placement="right"]:focus::after,[data-tooltip][data-placement="right"]:hover::after{animation-name:tooltip-caret-slide-right}}@keyframes tooltip-slide-top{from{transform:translate(-50%, 0.75rem);opacity:0}to{transform:translate(-50%, -0.25rem);opacity:1}}@keyframes tooltip-caret-slide-top{from{opacity:0}50%{transform:translate(-50%, -0.25rem);opacity:0}to{transform:translate(-50%, 0rem);opacity:1}}@keyframes tooltip-slide-bottom{from{transform:translate(-50%, -0.75rem);opacity:0}to{transform:translate(-50%, 0.25rem);opacity:1}}@keyframes tooltip-caret-slide-bottom{from{opacity:0}50%{transform:translate(-50%, -0.5rem);opacity:0}to{transform:translate(-50%, -0.3rem);opacity:1}}@keyframes tooltip-slide-left{from{transform:translate(0.75rem, -50%);opacity:0}to{transform:translate(-0.25rem, -50%);opacity:1}}@keyframes tooltip-caret-slide-left{from{opacity:0}50%{transform:translate(0.05rem, -50%);opacity:0}to{transform:translate(0.3rem, -50%);opacity:1}}@keyframes tooltip-slide-right{from{transform:translate(-0.75rem, -50%);opacity:0}to{transform:translate(0.25rem, -50%);opacity:1}}@keyframes tooltip-caret-slide-right{from{opacity:0}50%{transform:translate(-0.05rem, -50%);opacity:0}to{transform:translate(-0.3rem, -50%);opacity:1}}[aria-controls]{cursor:pointer}[aria-disabled="true"],[disabled]{cursor:not-allowed}[aria-hidden="false"][hidden]{display:initial}[aria-hidden="false"][hidden]:not(:focus){clip:rect(0, 0, 0, 0);position:absolute}a,area,button,input,label,select,summary,textarea,[tabindex]{-ms-touch-action:manipulation}[dir="rtl"]{direction:rtl}@media (prefers-reduced-motion: reduce){*:not([aria-busy="true"]),:not([aria-busy="true"])::before,:not([aria-busy="true"])::after{background-attachment:initial !important;animation-duration:1ms !important;animation-delay:-1ms !important;animation-iteration-count:1 !important;scroll-behavior:auto !important;transition-delay:0s !important;transition-duration:0s !important}}.container,.container-fluid{width:100%;margin-right:auto;margin-left:auto;padding-right:var(--spacing);padding-left:var(--spacing)}@media (min-width: 576px){.container{max-width:510px;padding-right:0;padding-left:0}}@media (min-width: 768px){.container{max-width:700px}}@media (min-width: 992px){.container{max-width:920px}}@media (min-width: 1200px){.container{max-width:1130px}}@media (min-width: 1500px){.container{max-width:1480px}}@media (min-width: 2200px){.container{max-width:2190px}}::-webkit-scrollbar{display:none}:root{--spacing: 0;--font-family: monospace;--font-size: 14px}pre{white-space:pre-wrap;background-color:unset;color:unset;font-size:unset}table{margin-bottom:0px}table{overflow:hidden}table td{padding-left:2px;padding-right:2px}table tbody tr td{overflow:hidden;white-space:nowrap}table th{overflow:hidden;white-space:nowrap;font-weight:bold;text-decoration:underline}header.tree-preamble{font-size:smaller}.wide{display:flex;flex-direction:row;justify-content:space-between;row-gap:10px}.wide div:nth-child(2){margin-left:10px}nav.small a{font-size:small}ul.submenu{font-size:smaller;height:25px}pre{overflow:scroll}[role="button"]{padding:0;margin-top:2px;margin-bottom:2px}button.small{display:initial;width:initial}nav{border-radius:var(--border-radius);margin-left:10px;margin-right:10px}code{padding:0}code.highlighted{width:100%;overflow:scroll}footer.main{padding-top:2em;text-align:center;font-size:smaller}h1,h2,h3,h4,h5,h6{--typography-spacing-vertical: .1em}li.active>a{text-decoration:underline}.logo>svg{width:72px}@media (prefers-color-scheme: dark){.logo{filter:drop-shadow(0 0 4mm #fff)}}footer{height:2em}article{box-shadow:none}article.clone>input{font-size:smaller;height:30px;text-align:center}.blob-preview{text-align:center;border-style:solid;border-color:pink;margin-top:2em;padding:1em}.line-number{user-select:none;padding:0 2px;width:10px}.line{white-space:pre;padding-left:5px}.icon-header>h1,h2,h3,h4,h5{display:inline-block}.icon-header>h1,h2,h3,h4,h5>a{color:none;text-decoration:inherit}.icon-header>svg{height:2em;float:right;margin-top:2px;margin-left:2px}.icon>svg{height:1.5em}.icon-header.contrast>svg{border:2px solid}.emoji{float:right}.emoji>svg{height:30px}header.repo div{display:inline-block}article>header{padding:3px}ul.language-list li{list-style:none}ul.author-list li{list-style:none}span.right{float:right}span.tiny{font-size:.7em;font-weight:bold}span.center{text-align:center}span.tiny-text{font-size:smaller}span.hint{font-weight:bold;text-decoration:underline}span.h1{font-size:x-large;font-weight:bold}span.header-text{font-size:1.2em;font-weight:bold}span.sub-header{font-size:1em;font-weight:bold}span.header{font-size:large}span.timestamp{float:right}span.author{float:right}article.index-listing{margin-top:25px;margin-bottom:15px}article.index-listing>article{padding:3px}article.listing>article{padding:5px}span.labels{font-weight:bold}span.label{border:solid 2px}span.labels{border-radius:5px 5px 5px 5px}section.blame{display:grid;grid-template-columns:1fr 5fr}div.clone{display:flex;height:45px;font-size:smaller}div.clone-url>input{height:2em !important}div.clone>div{padding:5px;line-height:45px}div.clone-url{flex:auto}section.blame article{margin-top:0px !important}article.blame-right{padding:0;border-left:solid 1px}article.blame-left{padding:0;border-right:solid 1px}article.blame-left table tbody tr td{border-left:solid 1px;border-right:solid 1px;text-align:left}.readme ul{padding-right:revert;padding-left:revert;padding-inline-start:revert;padding-inline-end:revert}img.rss-icon-feed{height:4em}div.table{padding:.5em}div.readme{padding:1em}.term-width{word-wrap:break-word;width:600px}footer.tree{font-size:smaller}section.refs-container{display:grid;grid-template-columns:3fr 1fr}article.ref-right{margin-left:1em}@media screen and (max-width: 1000px){section.refs-container{grid-template-columns:1fr}article.ref-right{margin-left:0}}section.controls{text-align:center;border:1px}section.viewer{text-align:center;border:1px}div.repo-container{display:grid;grid-template-columns:3fr 1fr}div.user-box{display:grid;grid-template-columns:1fr 4fr}div.user-box>.name{font-weight:bold;text-decoration:underline}div.commit_message{padding-top:1em}div.user-box>.details{text-align:right}.expand-xl{display:none}div.tree{padding-right:5px;padding-left:5px}article.tree{margin-bottom:5px}section.repo-right{text-align:center}section.repo-right{margin-left:1em}section.repo-right>section{text-align:center}section.repo-right>article{display:block;text-align:center}.spaced>div:first-child{margin-left:2px}.panel{background:var(--card-background-color);padding:.3em}table.commits{width:100%}table.commits>tbody>tr>td.message{word-wrap:break-word;white-space:normal !important}section.thread-view>article{padding-top:1em;padding-bottom:1em}div.chart{text-align:center}@media (max-width: 768px){.collapse{display:none}section.repo-right{margin-top:1em;margin-left:unset}section.viewer>svg{width:400px !important}}@media (max-width: 1200px){section.viewer>svg{width:550px}section.repo-right{margin-top:1em;margin-left:unset}div.tree>table>thead>tr>th:nth-child(5){text-align:right}div.tree>table>tbody>tr>td:nth-child(5){text-align:right}article.repo{grid-template-columns:repeat(2, 1fr)}div.repo-container{grid-template-columns:1fr}}@media (min-width: 1200px){.expand-xl{display:revert}}article.blame-right{border-left:solid #dc8add 1px !important}article.blame-left{border-right:solid #dc8add 1px !important}article>header.highlighted{background-color:#dc8add}article>footer{background-color:unset}.icon-header.contrast>svg{background-color:#dc8add}article.repo-right{background-color:unset}article.repo-right>article{background-color:unset}article.repo-right>article>header{background-color:unset}.positive{color:#8ff0a4}.negative{color:#f66151}
7594 diff --git a/ayllu/themes/adwaita/templates/.gitkeep b/ayllu/themes/adwaita/templates/.gitkeep
7595new file mode 100644
7596index 0000000..e69de29
7597--- /dev/null
7598+++ b/ayllu/themes/adwaita/templates/.gitkeep
7599 diff --git a/ayllu/themes/adwaita/theme.scss b/ayllu/themes/adwaita/theme.scss
7600new file mode 100644
7601index 0000000..4716ec0
7602--- /dev/null
7603+++ b/ayllu/themes/adwaita/theme.scss
7604 @@ -0,0 +1,171 @@
7605+ // https://en.wikipedia.org/wiki/Adwaita_(design_language)
7606+ // https://developer.gnome.org/hig/reference/palette.html
7607+
7608+ $blue1: rgb(153, 193, 241);
7609+ $blue2: rgb(98, 160, 234 );
7610+ $blue3: rgb(53, 132, 228 );
7611+ $blue4: rgb(28, 113, 216 );
7612+ $blue5: rgb(26, 95, 180 );
7613+ $green1: rgb(143, 240, 164);
7614+ $green2: rgb(87, 227 ,137);
7615+ $green3: rgb(51, 209 ,122);
7616+ $green4: rgb(46, 194 ,126);
7617+ $green5: rgb(38, 162 ,105);
7618+ $yellow1: rgb(249, 240, 107);
7619+ $yellow2: rgb(248, 228, 92);
7620+ $yellow3: rgb(246, 211, 45);
7621+ $yellow4: rgb(245, 194, 17);
7622+ $yellow5: rgb(229, 165, 10);
7623+ $orange1: rgb(255, 190, 111);
7624+ $orange2: rgb(255, 163, 72);
7625+ $orange3: rgb(255, 120, 0);
7626+ $orange4: rgb(230, 97, 0);
7627+ $orange5: rgb(198, 70, 0);
7628+ $red1: rgb(246, 97, 81);
7629+ $red2: rgb(237, 51, 59);
7630+ $red3: rgb(224, 27, 36);
7631+ $red4: rgb(192, 28, 40);
7632+ $red5: rgb(165, 29, 45);
7633+ $purple1: rgb(220, 138, 221);
7634+ $purple2: rgb(192, 97, 203);
7635+ $purple3: rgb(145, 65, 172);
7636+ $purple4: rgb(129, 61, 156);
7637+ $purple5: rgb(97, 53, 131);
7638+ $brown1: rgb(205, 171, 143);
7639+ $brown2: rgb(181, 131, 90);
7640+ $brown3: rgb(152, 106, 68);
7641+ $brown4: rgb(134, 94, 60);
7642+ $brown5: rgb(99, 69, 44);
7643+ $light1: rgb(255, 255, 255);
7644+ $light2: rgb(246, 245, 244);
7645+ $light3: rgb(222, 221, 218);
7646+ $light4: rgb(192, 191, 188);
7647+ $light5: rgb(154, 153, 150);
7648+ $dark1: rgb(119, 118, 123);
7649+ $dark2: rgb(94, 92, 100);
7650+ $dark3: rgb(61, 56, 70);
7651+ $dark4: rgb(36, 31, 49);
7652+ $dark5: rgb(0, 0, 0);
7653+
7654+ // pico overrides
7655+ $grey-50: $light2; /// 96%
7656+ $grey-100: darken($grey-50, 5%);
7657+ $grey-200: darken($grey-50, 10%);
7658+ $grey-300: darken($grey-50, 20%);
7659+ $grey-400: darken($grey-50, 30%);
7660+ $grey-500: darken($grey-50, 40%);
7661+ $grey-600: darken($grey-50, 50%);
7662+ $grey-700: darken($grey-50, 60%);
7663+ $grey-800: darken($grey-50, 70%);
7664+ $grey-900: darken($grey-50, 80%);
7665+
7666+
7667+ // Light Blue
7668+ $primary-50: $blue1;
7669+ $primary-100: darken($primary-50, 5%);
7670+ $primary-200: darken($primary-50, 10%);
7671+ $primary-300: darken($primary-50, 20%);
7672+ $primary-400: darken($primary-50, 25%);
7673+ $primary-500: darken($primary-50, 30%);
7674+ $primary-600: darken($primary-50, 35%);
7675+ $primary-700: darken($primary-50, 40%);
7676+ $primary-800: darken($primary-50, 45%);
7677+ $primary-900: darken($primary-50, 50%);
7678+
7679+ // Black & White
7680+ $black: $dark1;
7681+ $white: $light1;
7682+
7683+ // Amber
7684+ $amber-50: $orange1;
7685+ $amber-100: darken($amber-50, 5%);
7686+ $amber-200: darken($amber-50, 10%);
7687+ $amber-300: darken($amber-50, 15%);
7688+ $amber-400: darken($amber-50, 20%);
7689+ $amber-500: darken($amber-50, 25%);
7690+ $amber-600: darken($amber-50, 30%);
7691+ $amber-700: darken($amber-50, 35%);
7692+ $amber-800: darken($amber-50, 40%);
7693+ $amber-900: darken($amber-50, 45%);
7694+
7695+ // Green
7696+ $green-50: $green1;
7697+ $green-100: darken($green-50, 5%);
7698+ $green-200: darken($green-50, 10%);
7699+ $green-300: darken($green-50, 15%);
7700+ $green-400: darken($green-50, 20%);
7701+ $green-500: darken($green-50, 25%);
7702+ $green-600: darken($green-50, 30%);
7703+ $green-700: darken($green-50, 35%);
7704+ $green-800: darken($green-50, 40%);
7705+ $green-900: darken($green-50, 45%);
7706+
7707+ // Red
7708+ $red-50: $red1;
7709+ $red-100: darken($red-50, 5%);
7710+ $red-200: darken($red-50, 10%);
7711+ $red-300: darken($red-50, 15%);
7712+ $red-400: darken($red-50, 20%);
7713+ $red-500: darken($red-50, 25%);
7714+ $red-600: darken($red-50, 30%);
7715+ $red-700: darken($red-50, 35%);
7716+ $red-800: darken($red-50, 40%);
7717+ $red-900: darken($red-50, 45%);
7718+
7719+ $primary-500: $blue1;
7720+ $primary-600: $blue2;
7721+ $primary-700: $blue3;
7722+
7723+ //
7724+ //
7725+ // tree sitter highlighting
7726+ // https://github.com/Mofiqul/adwaita.nvim
7727+ //
7728+ // extra colors for highlighting
7729+ $teal1: #93DDC2;
7730+ $teal2: #5BC8AF;
7731+ $teal3: #33B2A4;
7732+ $teal4: #26A1A2;
7733+ $teal5: #218787;
7734+ $violet2: #7D8AC7;
7735+ $violet3: #6362C8;
7736+ $violet4: #4E57BA;
7737+
7738+ span.ts_attribute {color: $orange4};
7739+ span.ts_constant {color: $purple4};
7740+ span.ts_function.builtin {color: $blue4};
7741+ span.ts_function {color: $blue4};
7742+ span.ts_keyword {color: $orange4};
7743+ span.ts_operator {color: $purple4};
7744+ span.ts_property {color: $blue1};
7745+ span.ts_punctuation {color: $blue1};
7746+ span.ts_punctuation.bracket {color: $blue1};
7747+ span.ts_punctuation.delimiter {color: $blue1};
7748+ span.ts_string {color: $teal2};
7749+ span.ts_string.special {color: $teal3};
7750+ span.ts_tag {color: $teal2};
7751+ span.ts_type {color: $teal2};
7752+ span.ts_type.builtin {color: $blue1};
7753+ span.ts_variable {color: $light4};
7754+ span.ts_variable.builtin {color: $light4};
7755+ span.ts_variable.parameter {color: $light4};
7756+
7757+ @media (prefers-color-scheme: dark) {
7758+ span.label {
7759+ color: $dark5;
7760+ }
7761+ }
7762+
7763+ $positive: $green1;
7764+ $negative: $red1;
7765+
7766+ $blame-border: $purple1;
7767+
7768+ $highlighted: $purple1;
7769+ $icon-background: $purple1;
7770+
7771+ $chart-color: $purple1;
7772+
7773+ @import "@picocss/pico/scss/pico";
7774+ @import "../default/layout.scss";
7775+ @import "../default/base.scss";
7776 diff --git a/ayllu/themes/default/assets/ayllu_logo.svg b/ayllu/themes/default/assets/ayllu_logo.svg
7777new file mode 100644
7778index 0000000..28124d3
7779--- /dev/null
7780+++ b/ayllu/themes/default/assets/ayllu_logo.svg
7781 @@ -0,0 +1,90 @@
7782+ <?xml version="1.0" encoding="UTF-8" standalone="no"?>
7783+ <svg xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:cc="http://creativecommons.org/ns#" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:svg="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg" xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" inkscape:version="1.0 (1.0+r73+1)" sodipodi:docname="textil-inca-pattern-3a.svg" id="svg159" version="1.1" viewBox="0 0 902 702">
7784+ <metadata id="metadata165">
7785+ <rdf:RDF>
7786+ <cc:Work rdf:about="">
7787+ <dc:format>image/svg+xml</dc:format>
7788+ <dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage"/>
7789+ </cc:Work>
7790+ </rdf:RDF>
7791+ </metadata>
7792+ <defs id="defs163"/>
7793+ <sodipodi:namedview inkscape:current-layer="svg159" inkscape:window-maximized="1" inkscape:window-y="27" inkscape:window-x="67" inkscape:cy="467.33333" inkscape:cx="600.66667" inkscape:zoom="0.75746799" showgrid="false" id="namedview161" inkscape:window-height="1025" inkscape:window-width="1853" inkscape:pageshadow="2" inkscape:pageopacity="0" guidetolerance="10" gridtolerance="10" objecttolerance="10" borderopacity="1" bordercolor="#666666" pagecolor="#ffffff"/>
7794+ <path style="fill:#5c451c;fill-opacity:1;fill-rule:evenodd;stroke:#5c451c;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="M 1,1 H 901 V 701 H 1 Z m 0,0" id="path4"/>
7795+ <path style="fill:#ffa300;fill-opacity:1;fill-rule:evenodd;stroke:#ffa300;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 641,341 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 z m 0,0" id="path6"/>
7796+ <path style="fill:#ffa300;fill-opacity:1;fill-rule:evenodd;stroke:#ffa300;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 121,341 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 z m 0,0" id="path8"/>
7797+ <path style="fill:#ffa300;fill-opacity:1;fill-rule:evenodd;stroke:#ffa300;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 401,341 h 20 v -20 h 20 v 20 h 20 v 20 h -20 v 20 h -20 v -20 h -20 z m 0,0" id="path10"/>
7798+ <path style="fill:#ffa300;fill-opacity:1;fill-rule:evenodd;stroke:#ffa300;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 441,341 h 20 v -20 h 20 v 20 h 20 v 20 h -20 v 20 h -20 v -20 h -20 z m 0,0" id="path12"/>
7799+ <path style="fill:#358794;fill-opacity:1;fill-rule:evenodd;stroke:#358794;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 701,221 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h -40 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 z m 0,0" id="path14"/>
7800+ <path style="fill:#358794;fill-opacity:1;fill-rule:evenodd;stroke:#358794;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 701,481 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h -40 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 z m 0,0" id="path16"/>
7801+ <path style="fill:#358794;fill-opacity:1;fill-rule:evenodd;stroke:#358794;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 621,361 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 40 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 z m 0,0" id="path18"/>
7802+ <path style="fill:#358794;fill-opacity:1;fill-rule:evenodd;stroke:#358794;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 621,341 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -40 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 z m 0,0" id="path20"/>
7803+ <path style="fill:#358794;fill-opacity:1;fill-rule:evenodd;stroke:#358794;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 201,221 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 H 81 v 20 H 61 v 20 h 40 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 z m 0,0" id="path22"/>
7804+ <path style="fill:#358794;fill-opacity:1;fill-rule:evenodd;stroke:#358794;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="M 201,481 H 181 V 461 H 161 V 441 H 141 V 421 H 121 V 401 H 101 V 381 H 81 V 361 H 61 v -20 h 40 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 z m 0,0" id="path24"/>
7805+ <path style="fill:#358794;fill-opacity:1;fill-rule:evenodd;stroke:#358794;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 281,361 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 40 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 z m 0,0" id="path26"/>
7806+ <path style="fill:#358794;fill-opacity:1;fill-rule:evenodd;stroke:#358794;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 281,341 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -40 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 z m 0,0" id="path28"/>
7807+ <path style="fill:#358794;fill-opacity:1;fill-rule:evenodd;stroke:#358794;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 441,121 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 40 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 z m 0,0" id="path30"/>
7808+ <path style="fill:#358794;fill-opacity:1;fill-rule:evenodd;stroke:#358794;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 261,361 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 40 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 z m 0,0" id="path32"/>
7809+ <path style="fill:#358794;fill-opacity:1;fill-rule:evenodd;stroke:#358794;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 461,121 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 40 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 z m 0,0" id="path34"/>
7810+ <path style="fill:#358794;fill-opacity:1;fill-rule:evenodd;stroke:#358794;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 641,361 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 40 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 z m 0,0" id="path36"/>
7811+ <path style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 701,161 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h -40 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 z m 0,0" id="path38"/>
7812+ <path style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 701,541 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h -40 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 z m 0,0" id="path40"/>
7813+ <path style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 441,61 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 40 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 z m 0,0" id="path42"/>
7814+ <path style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 441,641 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -40 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 z m 0,0" id="path44"/>
7815+ <path style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 461,61 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 40 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 z m 0,0" id="path46"/>
7816+ <path style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 621,421 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 40 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 z m 0,0" id="path48"/>
7817+ <path style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 621,281 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -40 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 z m 0,0" id="path50"/>
7818+ <path style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 281,281 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -40 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 z m 0,0" id="path52"/>
7819+ <path style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 201,161 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 H 81 v 20 H 61 v 20 H 41 v 20 H 21 v 20 H 1 v 20 h 40 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 z m 0,0" id="path54"/>
7820+ <path style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="M 201,541 H 181 V 521 H 161 V 501 H 141 V 481 H 121 V 461 H 101 V 441 H 81 V 421 H 61 V 401 H 41 V 381 H 21 V 361 H 1 v -20 h 40 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 z m 0,0" id="path56"/>
7821+ <path style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 461,641 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -40 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 z m 0,0" id="path58"/>
7822+ <path style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 281,421 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 40 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 z m 0,0" id="path60"/>
7823+ <path style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 701,181 h 20 v -80 h 100 v 40 h -40 v -20 h -20 v 40 h 80 V 81 H 701 Z m 0,0" id="path62"/>
7824+ <path style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="M 721,181 H 701 V 101 H 601 v 40 h 40 v -20 h 20 v 40 H 581 V 81 h 140 z m 0,0" id="path64"/>
7825+ <path style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 701,521 h 20 v 80 h 100 v -40 h -40 v 20 h -20 v -40 h 80 v 80 H 701 Z m 0,0" id="path66"/>
7826+ <path style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 721,521 h -20 v 80 H 601 v -40 h 40 v 20 h 20 v -40 h -80 v 80 h 140 z m 0,0" id="path68"/>
7827+ <path style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 181,521 h 20 v 80 h 100 v -40 h -40 v 20 h -20 v -40 h 80 v 80 H 181 Z m 0,0" id="path70"/>
7828+ <path style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 201,521 h -20 v 80 H 81 v -40 h 40 v 20 h 20 V 541 H 61 v 80 h 140 z m 0,0" id="path72"/>
7829+ <path style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 181,181 h 20 v -80 h 100 v 40 h -40 v -20 h -20 v 40 h 80 V 81 H 181 Z m 0,0" id="path74"/>
7830+ <path style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="M 201,181 H 181 V 101 H 81 v 40 h 40 v -20 h 20 v 40 H 61 V 81 h 140 z m 0,0" id="path76"/>
7831+ <path style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 441,181 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h -40 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 z m 0,0" id="path78"/>
7832+ <path style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 461,181 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h 40 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 z m 0,0" id="path80"/>
7833+ <path style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 441,521 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h -40 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 z m 0,0" id="path82"/>
7834+ <path style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 461,521 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h 40 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 z m 0,0" id="path84"/>
7835+ <path style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="M 1,21 H 881 V 61 H 1 Z m 0,0" id="path86"/>
7836+ <path style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 1,641 h 880 v 40 H 1 Z m 0,0" id="path88"/>
7837+ <path style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 421,281 h 20 v -20 h 20 v 20 h 20 v 20 h -20 v 20 h -20 v -20 h -20 z m 0,0" id="path90"/>
7838+ <path style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 681,341 h 20 v -20 h 20 v 20 h 20 v 20 h -20 v 20 h -20 v -20 h -20 z m 0,0" id="path92"/>
7839+ <path style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 161,341 h 20 v -20 h 20 v 20 h 20 v 20 h -20 v 20 h -20 v -20 h -20 z m 0,0" id="path94"/>
7840+ <path style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 421,401 h 20 v -20 h 20 v 20 h 20 v 20 h -20 v 20 h -20 v -20 h -20 z m 0,0" id="path96"/>
7841+ <path style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 401,301 h 20 v 20 h -20 z m 0,0" id="path98"/>
7842+ <path style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 381,321 h 20 v 20 h -20 z m 0,0" id="path100"/>
7843+ <path style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 361,341 h 20 v 20 h -20 z m 0,0" id="path102"/>
7844+ <path style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 381,361 h 20 v 20 h -20 z m 0,0" id="path104"/>
7845+ <path style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 401,381 h 20 v 20 h -20 z m 0,0" id="path106"/>
7846+ <path style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 501,321 h 20 v 20 h -20 z m 0,0" id="path108"/>
7847+ <path style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 521,341 h 20 v 20 h -20 z m 0,0" id="path110"/>
7848+ <path style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 501,361 h 20 v 20 h -20 z m 0,0" id="path112"/>
7849+ <path style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 481,381 h 20 v 20 h -20 z m 0,0" id="path114"/>
7850+ <path style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 421,341 h 20 v 20 h -20 z m 0,0" id="path116"/>
7851+ <path style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 461,341 h 20 v 20 h -20 z m 0,0" id="path118"/>
7852+ <path style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 881,21 h 20 v 40 h -20 z m 0,0" id="path120"/>
7853+ <path style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 881,641 h 20 v 40 h -20 z m 0,0" id="path122"/>
7854+ <path style="fill:#de3333;fill-opacity:1;fill-rule:evenodd;stroke:#de3333;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 801,201 h 60 V 81 h 40 v 220 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 z m 0,0" id="path124"/>
7855+ <path style="fill:#de3333;fill-opacity:1;fill-rule:evenodd;stroke:#de3333;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="M 101,201 H 41 V 81 H 1 v 220 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 z m 0,0" id="path126"/>
7856+ <path style="fill:#de3333;fill-opacity:1;fill-rule:evenodd;stroke:#de3333;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="M 101,501 H 41 V 621 H 1 V 401 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 z m 0,0" id="path128"/>
7857+ <path style="fill:#de3333;fill-opacity:1;fill-rule:evenodd;stroke:#de3333;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 801,501 h 60 v 120 h 40 V 401 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 z m 0,0" id="path130"/>
7858+ <path style="fill:#de3333;fill-opacity:1;fill-rule:evenodd;stroke:#de3333;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 381,341 h 20 v 20 h -20 z m 0,0" id="path132"/>
7859+ <path style="fill:#de3333;fill-opacity:1;fill-rule:evenodd;stroke:#de3333;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 401,321 h 20 v 20 h -20 z m 0,0" id="path134"/>
7860+ <path style="fill:#de3333;fill-opacity:1;fill-rule:evenodd;stroke:#de3333;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 421,301 h 20 v 20 h -20 z m 0,0" id="path136"/>
7861+ <path style="fill:#de3333;fill-opacity:1;fill-rule:evenodd;stroke:#de3333;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 461,301 h 20 v 20 h -20 z m 0,0" id="path138"/>
7862+ <path style="fill:#de3333;fill-opacity:1;fill-rule:evenodd;stroke:#de3333;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 481,321 h 20 v 20 h -20 z m 0,0" id="path140"/>
7863+ <path style="fill:#de3333;fill-opacity:1;fill-rule:evenodd;stroke:#de3333;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 501,341 h 20 v 20 h -20 z m 0,0" id="path142"/>
7864+ <path style="fill:#de3333;fill-opacity:1;fill-rule:evenodd;stroke:#de3333;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 481,361 h 20 v 20 h -20 z m 0,0" id="path144"/>
7865+ <path style="fill:#de3333;fill-opacity:1;fill-rule:evenodd;stroke:#de3333;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 461,381 h 20 v 20 h -20 z m 0,0" id="path146"/>
7866+ <path style="fill:#de3333;fill-opacity:1;fill-rule:evenodd;stroke:#de3333;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 441,361 h 20 v 20 h -20 z m 0,0" id="path148"/>
7867+ <path style="fill:#de3333;fill-opacity:1;fill-rule:evenodd;stroke:#de3333;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 421,381 h 20 v 20 h -20 z m 0,0" id="path150"/>
7868+ <path style="fill:#de3333;fill-opacity:1;fill-rule:evenodd;stroke:#de3333;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 401,361 h 20 v 20 h -20 z m 0,0" id="path152"/>
7869+ <path style="fill:#de3333;fill-opacity:1;fill-rule:evenodd;stroke:#de3333;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 881,81 h 20 v 220 h -20 z m 0,0" id="path154"/>
7870+ <path style="fill:#de3333;fill-opacity:1;fill-rule:evenodd;stroke:#de3333;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 881,401 h 20 v 220 h -20 z m 0,0" id="path156"/>
7871+ </svg>
7872 diff --git a/ayllu/themes/default/assets/books.svg b/ayllu/themes/default/assets/books.svg
7873new file mode 100644
7874index 0000000..ba393a5
7875--- /dev/null
7876+++ b/ayllu/themes/default/assets/books.svg
7877 @@ -0,0 +1,16 @@
7878+ <svg id="emoji" viewBox="0 0 72 72" xmlns="http://www.w3.org/2000/svg">
7879+ <g id="color">
7880+ <path fill="#fff" d="M61.2167,21.6411,37.911,15.0386a1.0068,1.0068,0,0,0-.5537.0019l-22.5947,6.627a.9484.9484,0,0,0-.1365.0719,5.3829,5.3829,0,0,0-1.8343,9.5426,5.3642,5.3642,0,0,0,.0663,8.6968,5.3776,5.3776,0,0,0,1.4856,9.5022l23.0146,6.7128a1.0034,1.0034,0,0,0,.5547.002l23.3057-6.6514a1.0008,1.0008,0,0,0,.7255-.9619V22.603A1,1,0,0,0,61.2167,21.6411Z"/>
7881+ <polygon fill="#92d3f5" points="37.638 15.976 60.944 22.579 37.638 29.231 15.044 22.603 37.638 15.976"/>
7882+ <path fill="#61b2e4" d="M15.0823,24.7275,37.0756,31.174a2,2,0,0,0,1.1113.0039l23.2947-6.7653a.613.613,0,0,0,.442-.5794l.0189-1.2508L37.6381,29.2547,15.08,22.6425a4.4,4.4,0,0,0-.4554,8.4813l23.014,6.713,24.2115-6.91.04-1.2423a.6255.6255,0,0,0-.7968-.6215L37.6442,35.7552l-22.4226-6.54a2.4,2.4,0,0,1-.1393-4.4873"/>
7883+ <path fill="#d22f27" d="M15.0823,33.4092l21.9933,6.4465a1.9988,1.9988,0,0,0,1.1113.004l23.2947-6.7653a.6131.6131,0,0,0,.442-.5794l.0189-1.2508L37.6381,37.9365,15.08,31.3243a4.4,4.4,0,0,0-.4554,8.4813l23.014,6.713,24.2115-6.91.04-1.2424a.6254.6254,0,0,0-.7968-.6214L37.6442,44.437l-22.4226-6.54a2.4,2.4,0,0,1-.1393-4.4873"/>
7884+ <path fill="#5c9e31" d="M15.0823,42.1549l21.9933,6.4465a2,2,0,0,0,1.1113.0039L61.4816,41.84a.613.613,0,0,0,.442-.5794l.0189-1.2507L37.6381,46.6821,15.08,40.07a4.4,4.4,0,0,0-.4554,8.4813l23.014,6.713,24.2115-6.91.04-1.2424a.6254.6254,0,0,0-.7968-.6214L37.6442,53.1827l-22.4226-6.54a2.4,2.4,0,0,1-.1393-4.4873"/>
7885+ <polygon fill="#61b2e4" points="44.32 17.794 38.594 16.172 16 22.799 21.81 24.504 44.32 17.794"/>
7886+ </g>
7887+ <g id="line">
7888+ <polygon fill="none" stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" points="37.638 16 60.944 22.603 37.638 29.255 15.044 22.627 37.638 16"/>
7889+ <path fill="none" stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15.08,22.6425a4.4,4.4,0,0,0-.4554,8.4813l23.0141,6.713,23.3057-6.6516"/>
7890+ <path fill="none" stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15.08,31.2942a4.4,4.4,0,0,0-.4554,8.4813l23.0141,6.713,23.3057-6.6516"/>
7891+ <path fill="none" stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15.08,40.04a4.4,4.4,0,0,0-.4554,8.4813l23.0141,6.713,23.3057-6.6516"/>
7892+ </g>
7893+ </svg>
7894 diff --git a/ayllu/themes/default/assets/building.svg b/ayllu/themes/default/assets/building.svg
7895new file mode 100644
7896index 0000000..f57946b
7897--- /dev/null
7898+++ b/ayllu/themes/default/assets/building.svg
7899 @@ -0,0 +1,26 @@
7900+ <svg id="emoji" viewBox="0 0 72 72" xmlns="http://www.w3.org/2000/svg">
7901+ <g id="color">
7902+ <rect x="18.54" y="26" width="34.92" height="25.95" fill="#9b9b9a"/>
7903+ <rect x="12" y="56" width="48" height="4" fill="#fff" stroke-miterlimit="10"/>
7904+ <rect x="14.13" y="22" width="43.74" height="4" fill="#fff" stroke-miterlimit="10"/>
7905+ <rect x="15.72" y="52" width="40.55" height="4" fill="#fff" stroke-miterlimit="10"/>
7906+ <rect x="18.5" y="26" width="4" height="26" fill="#fff" stroke-miterlimit="10"/>
7907+ <rect x="27.35" y="26.15" width="4" height="26" fill="#fff" stroke-miterlimit="10"/>
7908+ <rect x="49.52" y="25.95" width="3.941" height="26" fill="#fff" stroke-miterlimit="10"/>
7909+ <polygon fill="#fff" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" stroke-width="2" points="36 12 19 21.75 53 21.75"/>
7910+ <rect x="40.35" y="26.15" width="4" height="26" fill="#fff" stroke-miterlimit="10"/>
7911+ </g>
7912+ <g id="hair"/>
7913+ <g id="skin"/>
7914+ <g id="skin-shadow"/>
7915+ <g id="line">
7916+ <rect x="11.99" y="55.89" width="48.01" height="3.993" fill="none" stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" stroke-width="2"/>
7917+ <rect x="14.18" y="21.76" width="43.65" height="3.999" fill="none" stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" stroke-width="2.001"/>
7918+ <rect x="15.27" y="51.76" width="41.47" height="3.993" fill="none" stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" stroke-width="2"/>
7919+ <rect x="18.35" y="25.61" width="4.298" height="26.3" fill="none" stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" stroke-width="1.702"/>
7920+ <rect x="27.35" y="25.61" width="4.298" height="26.3" fill="none" stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" stroke-width="1.702"/>
7921+ <rect x="49.35" y="25.61" width="4.298" height="26.3" fill="none" stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" stroke-width="1.702"/>
7922+ <polygon transform="matrix(1.091 0 0 .9982 -3.283 .098)" fill="none" stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" stroke-width="1.916" points="36 12 19 21.75 53 21.75"/>
7923+ <rect x="40.35" y="25.61" width="4.298" height="26.3" fill="none" stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" stroke-width="1.702"/>
7924+ </g>
7925+ </svg>
7926 diff --git a/ayllu/themes/default/assets/chat.svg b/ayllu/themes/default/assets/chat.svg
7927new file mode 100644
7928index 0000000..e36c2f6
7929--- /dev/null
7930+++ b/ayllu/themes/default/assets/chat.svg
7931 @@ -0,0 +1,16 @@
7932+ <svg id="emoji" viewBox="0 0 72 72" xmlns="http://www.w3.org/2000/svg">
7933+ <g id="color">
7934+ <path fill="#D0CFCE" stroke="none" d="M15.042,14.9756c-2.2091,0-4,1.7909-4,4v11.7841l-0.0019,4.5314c-0.0002,0.2808,0.328,0.4336,0.5428,0.2527 l5.0257-4.389c0.1804-0.1518,0.4086-0.2351,0.6443-0.2351h30.9557c2.2091,0,4-1.7909,4-4v-7.9441c0-2.2091-1.7909-4-4-4H15.042z"/>
7935+ <path fill="#9B9B9A" stroke="none" d="M60.5096,60.5444c0.2147,0.1808,0.5427,0.0281,0.5426-0.2526l-0.0018-4.2707h0.0007V44.077 c0-2.2091-1.7909-4-4-4H24.103c-2.2091,0-4,1.7909-4,4v7.9441c0,2.2091,1.7909,4,4,4h30.8579c0.2357,0,0.4639,0.0833,0.6441,0.2351 L60.5096,60.5444z"/>
7936+ </g>
7937+ <g id="hair"/>
7938+ <g id="skin"/>
7939+ <g id="skin-shadow"/>
7940+ <g id="line">
7941+ <path fill="none" stroke="#000000" stroke-miterlimit="10" stroke-width="2" d="M15.042,14.9756c-2.2091,0-4,1.7909-4,4v11.7841 l-0.0019,4.5314c-0.0002,0.2808,0.328,0.4336,0.5428,0.2527l5.0257-4.389c0.1804-0.1518,0.4086-0.2351,0.6443-0.2351h30.9557 c2.2091,0,4-1.7909,4-4v-7.9441c0-2.2091-1.7909-4-4-4H15.042z"/>
7942+ <path fill="none" stroke="#000000" stroke-miterlimit="10" stroke-width="2" d="M60.5096,60.5444 c0.2147,0.1808,0.5427,0.0281,0.5426-0.2526l-0.0018-4.2707h0.0007V44.077c0-2.2091-1.7909-4-4-4H24.103c-2.2091,0-4,1.7909-4,4 v7.9441c0,2.2091,1.7909,4,4,4h30.8579c0.2357,0,0.4639,0.0833,0.6441,0.2351L60.5096,60.5444z"/>
7943+ <circle cx="31.9965" cy="48.0593" r="2" fill="#000000" stroke="none"/>
7944+ <circle cx="40.0081" cy="48.0593" r="2" fill="#000000" stroke="none"/>
7945+ <circle cx="48.0198" cy="48.0593" r="2" fill="#000000" stroke="none"/>
7946+ </g>
7947+ </svg>
7948 diff --git a/ayllu/themes/default/assets/check.svg b/ayllu/themes/default/assets/check.svg
7949new file mode 100644
7950index 0000000..631b6ab
7951--- /dev/null
7952+++ b/ayllu/themes/default/assets/check.svg
7953 @@ -0,0 +1,8 @@
7954+ <svg id="emoji" viewBox="0 0 72 72" xmlns="http://www.w3.org/2000/svg">
7955+ <g id="color">
7956+ <path fill="#b1cc33" d="m61.5 23.3-8.013-8.013-25.71 25.71-9.26-9.26-8.013 8.013 17.42 17.44z"/>
7957+ </g>
7958+ <g id="line">
7959+ <path fill="none" stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" stroke-width="2" d="m10.5 39.76 17.42 17.44 33.58-33.89-8.013-8.013-25.71 25.71-9.26-9.26z"/>
7960+ </g>
7961+ </svg>
7962 diff --git a/ayllu/themes/default/assets/code.svg b/ayllu/themes/default/assets/code.svg
7963new file mode 100644
7964index 0000000..f92f207
7965--- /dev/null
7966+++ b/ayllu/themes/default/assets/code.svg
7967 @@ -0,0 +1,17 @@
7968+ <svg id="emoji" viewBox="0 0 72 72" xmlns="http://www.w3.org/2000/svg">
7969+ <g id="color">
7970+ <rect x="11" y="16.0833" width="50" height="39.8333" fill="#d0cfce" stroke="none"/>
7971+ </g>
7972+ <g id="hair"/>
7973+ <g id="skin"/>
7974+ <g id="skin-shadow"/>
7975+ <g id="line">
7976+ <rect x="11" y="16.0009" width="50" height="39.9982" fill="none" stroke="#000000" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" stroke-width="2"/>
7977+ <polyline fill="none" stroke="#000000" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" stroke-width="2" points="16.3287,16.4792 16.3287,20.8542 11,20.8542 61,20.8542"/>
7978+ <line x1="28.8333" x2="21.9062" y1="30.3947" y2="37.3218" fill="none" stroke="#000000" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" stroke-width="2"/>
7979+ <line x1="28.8333" x2="21.9062" y1="44.3166" y2="37.3895" fill="none" stroke="#000000" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" stroke-width="2"/>
7980+ <line x1="38.1836" x2="32.8086" y1="28.1523" y2="46.25" fill="none" stroke="#000000" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" stroke-width="2"/>
7981+ <line x1="42.1588" x2="49.0859" y1="44.2515" y2="37.3244" fill="none" stroke="#000000" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" stroke-width="2"/>
7982+ <line x1="42.1588" x2="49.0859" y1="30.3296" y2="37.2567" fill="none" stroke="#000000" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" stroke-width="2"/>
7983+ </g>
7984+ </svg>
7985 diff --git a/ayllu/themes/default/assets/favicon.ico b/ayllu/themes/default/assets/favicon.ico
7986new file mode 100644
7987index 0000000..613ab78
7988 Binary files /dev/null and b/ayllu/themes/default/assets/favicon.ico differ
7989 diff --git a/ayllu/themes/default/assets/feed.svg b/ayllu/themes/default/assets/feed.svg
7990new file mode 100644
7991index 0000000..2fd8aa4
7992--- /dev/null
7993+++ b/ayllu/themes/default/assets/feed.svg
7994 @@ -0,0 +1,14 @@
7995+ <svg id="emoji" viewBox="0 0 72 72" xmlns="http://www.w3.org/2000/svg">
7996+ <g id="color">
7997+ <path fill="#F1B31C" d="M59.0349 60H12.9649C12.7092 59.9992 12.4642 59.8973 12.2834 59.7164C12.1026 59.5356 12.0007 59.2906 12 59.0349V12.9649C12.0008 12.7092 12.1027 12.4642 12.2836 12.2834C12.4644 12.1026 12.7094 12.0007 12.9651 12H59.0351C59.2908 12.0008 59.5358 12.1027 59.7166 12.2836C59.8974 12.4644 59.9993 12.7094 60 12.9651V59.0351C59.9992 59.2908 59.8973 59.5358 59.7164 59.7166C59.5356 59.8974 59.2906 59.9993 59.0349 60Z"/>
7998+ <circle cx="24.5" cy="47.5" r="4.5" fill="#ffffff"/>
7999+ <path fill="#ffffff" fill-rule="evenodd" d="M42 52C42 39.8497 32.1503 30 20 30V37C28.2843 37 35 43.7157 35 52H42Z" clip-rule="evenodd"/>
8000+ <path fill="#ffffff" fill-rule="evenodd" d="M52 52C52 34.3269 37.6731 20 20 20V27C33.8071 27 45 38.1929 45 52H52Z" clip-rule="evenodd"/>
8001+ </g>
8002+ <g id="line">
8003+ <path fill="none" stroke="#000000" stroke-linejoin="round" stroke-miterlimit="10" stroke-width="2" d="M59.0349 60H12.9649C12.7092 59.9992 12.4642 59.8973 12.2834 59.7164C12.1026 59.5356 12.0007 59.2906 12 59.0349V12.9649C12.0008 12.7092 12.1027 12.4642 12.2836 12.2834C12.4644 12.1026 12.7094 12.0007 12.9651 12H59.0351C59.2908 12.0008 59.5358 12.1027 59.7166 12.2836C59.8974 12.4644 59.9993 12.7094 60 12.9651V59.0351C59.9992 59.2908 59.8973 59.5358 59.7164 59.7166C59.5356 59.8974 59.2906 59.9993 59.0349 60V60Z"/>
8004+ <path fill="none" stroke="#000000" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M29 47.5C29 49.9853 26.9853 52 24.5 52C22.0147 52 20 49.9853 20 47.5C20 45.0147 22.0147 43 24.5 43C26.9853 43 29 45.0147 29 47.5Z"/>
8005+ <path fill="none" fill-rule="evenodd" stroke="#000" stroke-linejoin="round" stroke-width="2" d="M41 52C41 40.402 31.598 31 20 31V37C28.2843 37 35 43.7157 35 52H41Z" clip-rule="evenodd"/>
8006+ <path fill="none" fill-rule="evenodd" stroke="#000" stroke-linejoin="round" stroke-width="2" d="M52 52C52 34.3269 37.6731 20 20 20V26.0012C34.1062 26.1354 45.5 37.6121 45.5 51.75C45.5 51.8334 45.4996 51.9168 45.4988 52H52Z" clip-rule="evenodd"/>
8007+ </g>
8008+ </svg>
8009 diff --git a/ayllu/themes/default/assets/feed.xsl b/ayllu/themes/default/assets/feed.xsl
8010new file mode 100644
8011index 0000000..3d1cce4
8012--- /dev/null
8013+++ b/ayllu/themes/default/assets/feed.xsl
8014 @@ -0,0 +1,56 @@
8015+ <?xml version="1.0" encoding="utf-8"?>
8016+ <xsl:stylesheet version="3.0"
8017+ xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
8018+ xmlns:atom="http://www.w3.org/2005/Atom"
8019+ xmlns:dc="http://purl.org/dc/elements/1.1/"
8020+ xmlns:itunes="http://www.itunes.com/dtds/podcast-1.0.dtd">
8021+ <xsl:output method="html" version="1.0" encoding="UTF-8" indent="yes"/>
8022+ <xsl:template match="/">
8023+ <html lang="en">
8024+ <head>
8025+ <title>
8026+ <xsl:value-of select="/rss/channel/title"/>
8027+ </title>
8028+ <link rel="stylesheet" href="/static/main.min.css" />
8029+ </head>
8030+ <body>
8031+ <main class="container">
8032+ <article>
8033+ <header>
8034+ <h1>
8035+ <img class="rss-icon-feed" src="/static/assets/feed.svg"/>
8036+ <a>
8037+ <xsl:attribute name="href"> <xsl:value-of select="/rss/channel/link"/> </xsl:attribute>
8038+ <xsl:value-of select="/rss/channel/title"/>
8039+ </a>
8040+ </h1>
8041+ <h2><xsl:value-of select="/rss/channel/description"/></h2>
8042+ </header>
8043+ <p><strong>This is an <a href="https://en.wikipedia.org/wiki/RSS">RSS feed</a>.</strong></p>
8044+ <p>Copy the link from the address bar into your feed reader to receive regular updates.</p>
8045+ </article>
8046+ <xsl:for-each select="/rss/channel/item">
8047+ <article class="entry">
8048+ <header>
8049+ <h3>
8050+ <a>
8051+ <xsl:attribute name="href">
8052+ <xsl:value-of select="link"/>
8053+ </xsl:attribute>
8054+ <xsl:value-of select="title"/>
8055+ </a>
8056+ </h3>
8057+ </header>
8058+ <xsl:value-of select="description" disable-output-escaping="yes"/>
8059+ <footer>
8060+ <xsl:value-of select="pubDate"/>
8061+ <span class="right"><xsl:value-of select="author" /></span>
8062+ </footer>
8063+ </article>
8064+ </xsl:for-each>
8065+ </main>
8066+ </body>
8067+ </html>
8068+ </xsl:template>
8069+ </xsl:stylesheet>
8070+
8071 diff --git a/ayllu/themes/default/assets/logo.svg b/ayllu/themes/default/assets/logo.svg
8072new file mode 100644
8073index 0000000..8e43fd6
8074--- /dev/null
8075+++ b/ayllu/themes/default/assets/logo.svg
8076 @@ -0,0 +1,78 @@
8077+ <?xml version="1.0" encoding="UTF-8" standalone="no"?>
8078+ <svg
8079+ id="emoji"
8080+ viewBox="0 0 234.58891 59.331596"
8081+ version="1.1"
8082+ sodipodi:docname="ayllu.svg"
8083+ width="234.58891"
8084+ height="59.331596"
8085+ inkscape:export-filename="logo.png"
8086+ inkscape:export-xdpi="96"
8087+ inkscape:export-ydpi="96"
8088+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
8089+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
8090+ xmlns="http://www.w3.org/2000/svg"
8091+ xmlns:svg="http://www.w3.org/2000/svg">
8092+ <defs
8093+ id="defs4043">
8094+ <rect
8095+ x="67.923203"
8096+ y="12.228936"
8097+ width="188.06631"
8098+ height="68.492889"
8099+ id="rect7485" />
8100+ </defs>
8101+ <sodipodi:namedview
8102+ id="namedview4041"
8103+ pagecolor="#ffffff"
8104+ bordercolor="#999999"
8105+ borderopacity="1"
8106+ inkscape:showpageshadow="0"
8107+ inkscape:pageopacity="0"
8108+ inkscape:pagecheckerboard="0"
8109+ inkscape:deskcolor="#d1d1d1"
8110+ showgrid="true">
8111+ <inkscape:grid
8112+ type="xygrid"
8113+ id="grid7481"
8114+ originx="0"
8115+ originy="0" />
8116+ </sodipodi:namedview>
8117+ <g
8118+ id="color"
8119+ transform="translate(-6.3635189,-6.0201046)">
8120+ <path
8121+ fill="#fcea2b"
8122+ stroke="none"
8123+ d="m 7.3634,42.4095 c 4.5525,6.1703 11.874,10.1726 20.1303,10.1726 13.8071,0 25,-11.1929 25,-25 0,-8.5226 -4.2646,-16.0492 -10.7763,-20.5621 13.0383,2.8385 22.7812,14.4426 22.7812,28.3317 0,16.0163 -12.9837,29 -29,29 -13.5877,0 -24.9889,-9.3288 -28.1352,-21.9422 z"
8124+ id="path4029" />
8125+ <path
8126+ fill="#f1b31c"
8127+ stroke="none"
8128+ d="m 45.8373,9.2108 c 8.25,4.25 16.1946,11.8724 16.1946,24.6742 0,15.4494 -12.5242,27.9735 -27.9735,27.9735 -9.2431,0 -19.7524,-4.8353 -24.294,-15.5436 0,0 4.3805,18.6568 25.7189,18.665 C 54.8103,64.9873 63.5252,44.3581 63.5252,44.3581 70.033,12.3815 45.8373,9.2108 45.8373,9.2108 Z"
8129+ id="path4031" />
8130+ </g>
8131+ <g
8132+ id="line"
8133+ transform="translate(-6.3635189,-6.0201046)">
8134+ <path
8135+ fill="none"
8136+ stroke="#000000"
8137+ stroke-linecap="round"
8138+ stroke-linejoin="round"
8139+ stroke-miterlimit="10"
8140+ stroke-width="2"
8141+ d="m 7.3634,42.4095 c 4.5525,6.1703 11.874,10.1726 20.1303,10.1726 13.8071,0 25,-11.1929 25,-25 0,-8.5226 -4.2646,-16.0492 -10.7763,-20.5621 13.0383,2.8385 22.7812,14.4426 22.7812,28.3317 0,16.0163 -12.9837,29 -29,29 -13.5877,0 -24.9889,-9.3288 -28.1352,-21.9422 z"
8142+ id="path4037" />
8143+ </g>
8144+ <text
8145+ xml:space="preserve"
8146+ id="text7483"
8147+ style="font-style:normal;font-weight:normal;font-size:40px;line-height:1.25;font-family:sans-serif;white-space:pre;shape-inside:url(#rect7485);fill:#000000;fill-opacity:1;stroke:none"
8148+ transform="translate(-3.6949385,-4.8457367)"><tspan
8149+ x="67.923828"
8150+ y="48.623523"
8151+ id="tspan7896"><tspan
8152+ style="font-weight:bold;font-family:serif;-inkscape-font-specification:'serif Bold'"
8153+ id="tspan7894">ayllu</tspan></tspan></text>
8154+ </svg>
8155 diff --git a/ayllu/themes/default/assets/moon.svg b/ayllu/themes/default/assets/moon.svg
8156new file mode 100644
8157index 0000000..51596eb
8158--- /dev/null
8159+++ b/ayllu/themes/default/assets/moon.svg
8160 @@ -0,0 +1,12 @@
8161+ <svg id="emoji" viewBox="0 0 72 72" xmlns="http://www.w3.org/2000/svg">
8162+ <g id="color">
8163+ <path fill="#FCEA2B" stroke="none" d="M7.3634,42.4095c4.5525,6.1703,11.874,10.1726,20.1303,10.1726c13.8071,0,25-11.1929,25-25 c0-8.5226-4.2646-16.0492-10.7763-20.5621c13.0383,2.8385,22.7812,14.4426,22.7812,28.3317c0,16.0163-12.9837,29-29,29 C21.9109,64.3517,10.5097,55.0229,7.3634,42.4095z"/>
8164+ <path fill="#F1B31C" stroke="none" d="M45.8373,9.2108c8.25,4.25,16.1946,11.8724,16.1946,24.6742c0,15.4494-12.5242,27.9735-27.9735,27.9735 c-9.2431,0-19.7524-4.8353-24.294-15.5436c0,0,4.3805,18.6568,25.7189,18.665c19.327,0.0074,28.0419-20.6218,28.0419-20.6218 C70.033,12.3815,45.8373,9.2108,45.8373,9.2108z"/>
8165+ </g>
8166+ <g id="hair"/>
8167+ <g id="skin"/>
8168+ <g id="skin-shadow"/>
8169+ <g id="line">
8170+ <path fill="none" stroke="#000000" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" stroke-width="2" d="M7.3634,42.4095c4.5525,6.1703,11.874,10.1726,20.1303,10.1726c13.8071,0,25-11.1929,25-25 c0-8.5226-4.2646-16.0492-10.7763-20.5621c13.0383,2.8385,22.7812,14.4426,22.7812,28.3317c0,16.0163-12.9837,29-29,29 C21.9109,64.3517,10.5097,55.0229,7.3634,42.4095z"/>
8171+ </g>
8172+ </svg>
8173 diff --git a/ayllu/themes/default/assets/question.svg b/ayllu/themes/default/assets/question.svg
8174new file mode 100644
8175index 0000000..9ee75dd
8176--- /dev/null
8177+++ b/ayllu/themes/default/assets/question.svg
8178 @@ -0,0 +1,13 @@
8179+ <svg id="emoji" viewBox="0 0 72 72" xmlns="http://www.w3.org/2000/svg">
8180+ <g id="color">
8181+ <path fill="#fff" stroke-miterlimit="10" d="m48.72 20.15c0.3051 9.298-8.021 12.75-10.82 19.21v7.565c0 1.39-1.11 2.5-2.5 2.5-1.38 0-2.5-1.11-2.5-2.5v-9.865c3.493-6.142 10.38-7.469 10.67-17.06 0-5.66-6.543-6.151-7.988-6.175h-0.05c-5.767 1.187-6.135 4.99-7.289 9.549-0.6671 1.521-1.564 2.045-2.9 2.03-1.36-0.2401-2.26-1.54-2.02-2.9 0.5877-2.752 0.758-6.639 2.35-8.502 2.71-3.14 4.819-4.957 9.179-5.167 0.24 0 0.49-0.01 0.73-0.01 0.09 0 0.18 0 0.27 0.01 8.27 0.2401 12.6 2.992 12.87 11.32z"/>
8182+ <circle cx="34.95" cy="58.84" r="3" fill="#fff" stroke-miterlimit="10"/>
8183+ </g>
8184+ <g id="hair"/>
8185+ <g id="skin"/>
8186+ <g id="skin-shadow"/>
8187+ <g id="line">
8188+ <circle cx="35.3" cy="58.84" r="2.625" fill="none" stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" stroke-width="1.75"/>
8189+ <path d="m35.67 10.07c6.57 0 12.03 3.431 12.03 10.21 0 3.671-0.48 6.526-3.71 9.755-3.956 3.956-7.184 6.346-7.208 8.549-0.0651 5.826-5e-3 8.278-0.0044 8.36a1.476 1.476 0 0 1-1.464 1.487h-0.0115a1.476 1.476 0 0 1-1.476-1.464c-5e-4 -0.0819-0.0607-2.561 0.0044-8.416 0.0358-3.231 3.63-6.127 8.385-10.88 2.178-2.174 2.474-4.716 2.474-7.127 0-3.282-2.965-7.587-8.984-7.587-5.338 0-8.499 3.467-8.499 9.552a1.434 1.434 0 0 1-1.395 1.587 1.552 1.552 0 0 1-1.505-1.759c0-9.882 7.291-12.27 11.36-12.27m0-2c-4.964 0-13.36 3.005-13.36 14.27a3.519 3.519 0 0 0 3.505 3.759 3.418 3.418 0 0 0 3.395-3.587c0-3.445 1.128-7.552 6.499-7.552 4.817 0 6.984 3.267 6.984 5.587 0 2.364-0.3155 4.144-1.886 5.712-0.7515 0.75-1.474 1.454-2.157 2.12-3.933 3.833-6.775 6.602-6.815 10.15-0.055 4.946-0.0218 7.539-5e-3 8.424a3.476 3.476 0 1 0 6.952-0.0248l-0.0012-0.075c-0.0164-0.8595-0.0488-3.384 0.0056-8.248 0.0092-0.8384 2.102-2.822 3.948-4.572 0.8091-0.767 1.726-1.636 2.675-2.585 3.577-3.577 4.296-6.926 4.296-11.17 0-7.301-5.638-12.21-14.03-12.21z"/>
8190+ </g>
8191+ </svg>
8192 diff --git a/ayllu/themes/default/assets/rss.svg b/ayllu/themes/default/assets/rss.svg
8193new file mode 100644
8194index 0000000..b325149
8195--- /dev/null
8196+++ b/ayllu/themes/default/assets/rss.svg
8197 @@ -0,0 +1,18 @@
8198+ <?xml version="1.0"?>
8199+ <!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
8200+ <svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="128px" height="128px" id="RSSicon" viewBox="0 0 256 256">
8201+ <defs>
8202+ <linearGradient x1="0.085" y1="0.085" x2="0.915" y2="0.915" id="RSSg">
8203+ <stop offset="0.0" stop-color="#E3702D"/><stop offset="0.1071" stop-color="#EA7D31"/>
8204+ <stop offset="0.3503" stop-color="#F69537"/><stop offset="0.5" stop-color="#FB9E3A"/>
8205+ <stop offset="0.7016" stop-color="#EA7C31"/><stop offset="0.8866" stop-color="#DE642B"/>
8206+ <stop offset="1.0" stop-color="#D95B29"/>
8207+ </linearGradient>
8208+ </defs>
8209+ <rect width="256" height="256" rx="55" ry="55" x="0" y="0" fill="#CC5D15"/>
8210+ <rect width="246" height="246" rx="50" ry="50" x="5" y="5" fill="#F49C52"/>
8211+ <rect width="236" height="236" rx="47" ry="47" x="10" y="10" fill="url(#RSSg)"/>
8212+ <circle cx="68" cy="189" r="24" fill="#FFF"/>
8213+ <path d="M160 213h-34a82 82 0 0 0 -82 -82v-34a116 116 0 0 1 116 116z" fill="#FFF"/>
8214+ <path d="M184 213A140 140 0 0 0 44 73 V 38a175 175 0 0 1 175 175z" fill="#FFF"/>
8215+ </svg>
8216 diff --git a/ayllu/themes/default/assets/scale.svg b/ayllu/themes/default/assets/scale.svg
8217new file mode 100644
8218index 0000000..8ffe8c3
8219--- /dev/null
8220+++ b/ayllu/themes/default/assets/scale.svg
8221 @@ -0,0 +1,22 @@
8222+ <svg id="emoji" viewBox="0 0 72 72" xmlns="http://www.w3.org/2000/svg">
8223+ <g id="color">
8224+ <path fill="#9B9B9A" stroke="none" d="M48.0626,62.92c0-3.3137-5.5964-6-12.5-6s-12.5,2.6863-12.5,6H48.0626z"/>
8225+ <path fill="#D0CFCE" stroke="none" d="M24.9006,46.9656c0,2.1938-2.1985,3.9723-4.9106,3.9723s-4.9106-1.7785-4.9106-3.9723H24.9006z"/>
8226+ <path fill="#D0CFCE" stroke="none" d="M57.9006,46.9248c0,2.1938-2.1985,3.9723-4.9106,3.9723c-2.712,0-4.9106-1.7785-4.9106-3.9723H57.9006z"/>
8227+ </g>
8228+ <g id="hair"/>
8229+ <g id="skin"/>
8230+ <g id="skin-shadow"/>
8231+ <g id="line">
8232+ <path fill="none" stroke="#000000" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" stroke-width="2" d="M15.9996,25.1697c2.6667,0.0001,8.9544-5.3333,20.0002-5.3333S50.6666,23.8365,56,25.1697"/>
8233+ <line x1="35.9998" x2="35.9998" y1="23.9064" y2="51.9064" fill="none" stroke="#000000" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" stroke-width="2"/>
8234+ <circle cx="35.9998" cy="13.895" r="3" fill="#000000" stroke="none"/>
8235+ <path fill="none" stroke="#000000" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" stroke-width="2" d="M48,61.9064c0-3.3137-5.5964-6-12.5-6s-12.5,2.6863-12.5,6"/>
8236+ <path fill="none" stroke="#000000" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" stroke-width="2" d="M26,45.9064c0,3.3137-2.6863,6-6,6s-6-2.6863-6-6H26z"/>
8237+ <polygon fill="none" stroke="#000000" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" stroke-width="2" points="20,27.9064 15,45.9064 25,45.9064"/>
8238+ <line x1="20" x2="20" y1="27.9064" y2="45.9064" fill="none" stroke="#000000" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" stroke-width="2"/>
8239+ <path fill="none" stroke="#000000" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" stroke-width="2" d="M59,45.9064c0,3.3137-2.6863,6-6,6s-6-2.6863-6-6H59z"/>
8240+ <polygon fill="none" stroke="#000000" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" stroke-width="2" points="53,27.9064 48,45.9064 58,45.9064"/>
8241+ <line x1="53" x2="53" y1="27.9064" y2="45.9064" fill="none" stroke="#000000" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" stroke-width="2"/>
8242+ </g>
8243+ </svg>
8244 diff --git a/ayllu/themes/default/assets/textile-pattern-1.svg b/ayllu/themes/default/assets/textile-pattern-1.svg
8245new file mode 100644
8246index 0000000..28124d3
8247--- /dev/null
8248+++ b/ayllu/themes/default/assets/textile-pattern-1.svg
8249 @@ -0,0 +1,90 @@
8250+ <?xml version="1.0" encoding="UTF-8" standalone="no"?>
8251+ <svg xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:cc="http://creativecommons.org/ns#" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:svg="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg" xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" inkscape:version="1.0 (1.0+r73+1)" sodipodi:docname="textil-inca-pattern-3a.svg" id="svg159" version="1.1" viewBox="0 0 902 702">
8252+ <metadata id="metadata165">
8253+ <rdf:RDF>
8254+ <cc:Work rdf:about="">
8255+ <dc:format>image/svg+xml</dc:format>
8256+ <dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage"/>
8257+ </cc:Work>
8258+ </rdf:RDF>
8259+ </metadata>
8260+ <defs id="defs163"/>
8261+ <sodipodi:namedview inkscape:current-layer="svg159" inkscape:window-maximized="1" inkscape:window-y="27" inkscape:window-x="67" inkscape:cy="467.33333" inkscape:cx="600.66667" inkscape:zoom="0.75746799" showgrid="false" id="namedview161" inkscape:window-height="1025" inkscape:window-width="1853" inkscape:pageshadow="2" inkscape:pageopacity="0" guidetolerance="10" gridtolerance="10" objecttolerance="10" borderopacity="1" bordercolor="#666666" pagecolor="#ffffff"/>
8262+ <path style="fill:#5c451c;fill-opacity:1;fill-rule:evenodd;stroke:#5c451c;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="M 1,1 H 901 V 701 H 1 Z m 0,0" id="path4"/>
8263+ <path style="fill:#ffa300;fill-opacity:1;fill-rule:evenodd;stroke:#ffa300;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 641,341 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 z m 0,0" id="path6"/>
8264+ <path style="fill:#ffa300;fill-opacity:1;fill-rule:evenodd;stroke:#ffa300;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 121,341 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 z m 0,0" id="path8"/>
8265+ <path style="fill:#ffa300;fill-opacity:1;fill-rule:evenodd;stroke:#ffa300;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 401,341 h 20 v -20 h 20 v 20 h 20 v 20 h -20 v 20 h -20 v -20 h -20 z m 0,0" id="path10"/>
8266+ <path style="fill:#ffa300;fill-opacity:1;fill-rule:evenodd;stroke:#ffa300;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 441,341 h 20 v -20 h 20 v 20 h 20 v 20 h -20 v 20 h -20 v -20 h -20 z m 0,0" id="path12"/>
8267+ <path style="fill:#358794;fill-opacity:1;fill-rule:evenodd;stroke:#358794;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 701,221 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h -40 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 z m 0,0" id="path14"/>
8268+ <path style="fill:#358794;fill-opacity:1;fill-rule:evenodd;stroke:#358794;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 701,481 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h -40 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 z m 0,0" id="path16"/>
8269+ <path style="fill:#358794;fill-opacity:1;fill-rule:evenodd;stroke:#358794;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 621,361 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 40 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 z m 0,0" id="path18"/>
8270+ <path style="fill:#358794;fill-opacity:1;fill-rule:evenodd;stroke:#358794;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 621,341 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -40 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 z m 0,0" id="path20"/>
8271+ <path style="fill:#358794;fill-opacity:1;fill-rule:evenodd;stroke:#358794;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 201,221 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 H 81 v 20 H 61 v 20 h 40 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 z m 0,0" id="path22"/>
8272+ <path style="fill:#358794;fill-opacity:1;fill-rule:evenodd;stroke:#358794;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="M 201,481 H 181 V 461 H 161 V 441 H 141 V 421 H 121 V 401 H 101 V 381 H 81 V 361 H 61 v -20 h 40 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 z m 0,0" id="path24"/>
8273+ <path style="fill:#358794;fill-opacity:1;fill-rule:evenodd;stroke:#358794;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 281,361 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 40 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 z m 0,0" id="path26"/>
8274+ <path style="fill:#358794;fill-opacity:1;fill-rule:evenodd;stroke:#358794;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 281,341 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -40 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 z m 0,0" id="path28"/>
8275+ <path style="fill:#358794;fill-opacity:1;fill-rule:evenodd;stroke:#358794;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 441,121 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 40 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 z m 0,0" id="path30"/>
8276+ <path style="fill:#358794;fill-opacity:1;fill-rule:evenodd;stroke:#358794;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 261,361 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 40 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 z m 0,0" id="path32"/>
8277+ <path style="fill:#358794;fill-opacity:1;fill-rule:evenodd;stroke:#358794;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 461,121 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 40 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 z m 0,0" id="path34"/>
8278+ <path style="fill:#358794;fill-opacity:1;fill-rule:evenodd;stroke:#358794;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 641,361 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 40 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 z m 0,0" id="path36"/>
8279+ <path style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 701,161 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h -40 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 z m 0,0" id="path38"/>
8280+ <path style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 701,541 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h -40 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 z m 0,0" id="path40"/>
8281+ <path style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 441,61 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 40 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 z m 0,0" id="path42"/>
8282+ <path style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 441,641 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -40 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 z m 0,0" id="path44"/>
8283+ <path style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 461,61 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 40 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 z m 0,0" id="path46"/>
8284+ <path style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 621,421 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 40 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 z m 0,0" id="path48"/>
8285+ <path style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 621,281 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -40 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 z m 0,0" id="path50"/>
8286+ <path style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 281,281 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -40 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 z m 0,0" id="path52"/>
8287+ <path style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 201,161 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 H 81 v 20 H 61 v 20 H 41 v 20 H 21 v 20 H 1 v 20 h 40 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 z m 0,0" id="path54"/>
8288+ <path style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="M 201,541 H 181 V 521 H 161 V 501 H 141 V 481 H 121 V 461 H 101 V 441 H 81 V 421 H 61 V 401 H 41 V 381 H 21 V 361 H 1 v -20 h 40 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 z m 0,0" id="path56"/>
8289+ <path style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 461,641 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -40 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 z m 0,0" id="path58"/>
8290+ <path style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 281,421 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 40 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 z m 0,0" id="path60"/>
8291+ <path style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 701,181 h 20 v -80 h 100 v 40 h -40 v -20 h -20 v 40 h 80 V 81 H 701 Z m 0,0" id="path62"/>
8292+ <path style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="M 721,181 H 701 V 101 H 601 v 40 h 40 v -20 h 20 v 40 H 581 V 81 h 140 z m 0,0" id="path64"/>
8293+ <path style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 701,521 h 20 v 80 h 100 v -40 h -40 v 20 h -20 v -40 h 80 v 80 H 701 Z m 0,0" id="path66"/>
8294+ <path style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 721,521 h -20 v 80 H 601 v -40 h 40 v 20 h 20 v -40 h -80 v 80 h 140 z m 0,0" id="path68"/>
8295+ <path style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 181,521 h 20 v 80 h 100 v -40 h -40 v 20 h -20 v -40 h 80 v 80 H 181 Z m 0,0" id="path70"/>
8296+ <path style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 201,521 h -20 v 80 H 81 v -40 h 40 v 20 h 20 V 541 H 61 v 80 h 140 z m 0,0" id="path72"/>
8297+ <path style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 181,181 h 20 v -80 h 100 v 40 h -40 v -20 h -20 v 40 h 80 V 81 H 181 Z m 0,0" id="path74"/>
8298+ <path style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="M 201,181 H 181 V 101 H 81 v 40 h 40 v -20 h 20 v 40 H 61 V 81 h 140 z m 0,0" id="path76"/>
8299+ <path style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 441,181 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h -40 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 z m 0,0" id="path78"/>
8300+ <path style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 461,181 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h 40 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 z m 0,0" id="path80"/>
8301+ <path style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 441,521 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h -40 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 z m 0,0" id="path82"/>
8302+ <path style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 461,521 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h 40 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 z m 0,0" id="path84"/>
8303+ <path style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="M 1,21 H 881 V 61 H 1 Z m 0,0" id="path86"/>
8304+ <path style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 1,641 h 880 v 40 H 1 Z m 0,0" id="path88"/>
8305+ <path style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 421,281 h 20 v -20 h 20 v 20 h 20 v 20 h -20 v 20 h -20 v -20 h -20 z m 0,0" id="path90"/>
8306+ <path style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 681,341 h 20 v -20 h 20 v 20 h 20 v 20 h -20 v 20 h -20 v -20 h -20 z m 0,0" id="path92"/>
8307+ <path style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 161,341 h 20 v -20 h 20 v 20 h 20 v 20 h -20 v 20 h -20 v -20 h -20 z m 0,0" id="path94"/>
8308+ <path style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 421,401 h 20 v -20 h 20 v 20 h 20 v 20 h -20 v 20 h -20 v -20 h -20 z m 0,0" id="path96"/>
8309+ <path style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 401,301 h 20 v 20 h -20 z m 0,0" id="path98"/>
8310+ <path style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 381,321 h 20 v 20 h -20 z m 0,0" id="path100"/>
8311+ <path style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 361,341 h 20 v 20 h -20 z m 0,0" id="path102"/>
8312+ <path style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 381,361 h 20 v 20 h -20 z m 0,0" id="path104"/>
8313+ <path style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 401,381 h 20 v 20 h -20 z m 0,0" id="path106"/>
8314+ <path style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 501,321 h 20 v 20 h -20 z m 0,0" id="path108"/>
8315+ <path style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 521,341 h 20 v 20 h -20 z m 0,0" id="path110"/>
8316+ <path style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 501,361 h 20 v 20 h -20 z m 0,0" id="path112"/>
8317+ <path style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 481,381 h 20 v 20 h -20 z m 0,0" id="path114"/>
8318+ <path style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 421,341 h 20 v 20 h -20 z m 0,0" id="path116"/>
8319+ <path style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 461,341 h 20 v 20 h -20 z m 0,0" id="path118"/>
8320+ <path style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 881,21 h 20 v 40 h -20 z m 0,0" id="path120"/>
8321+ <path style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 881,641 h 20 v 40 h -20 z m 0,0" id="path122"/>
8322+ <path style="fill:#de3333;fill-opacity:1;fill-rule:evenodd;stroke:#de3333;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 801,201 h 60 V 81 h 40 v 220 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 z m 0,0" id="path124"/>
8323+ <path style="fill:#de3333;fill-opacity:1;fill-rule:evenodd;stroke:#de3333;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="M 101,201 H 41 V 81 H 1 v 220 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 z m 0,0" id="path126"/>
8324+ <path style="fill:#de3333;fill-opacity:1;fill-rule:evenodd;stroke:#de3333;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="M 101,501 H 41 V 621 H 1 V 401 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 z m 0,0" id="path128"/>
8325+ <path style="fill:#de3333;fill-opacity:1;fill-rule:evenodd;stroke:#de3333;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 801,501 h 60 v 120 h 40 V 401 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 z m 0,0" id="path130"/>
8326+ <path style="fill:#de3333;fill-opacity:1;fill-rule:evenodd;stroke:#de3333;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 381,341 h 20 v 20 h -20 z m 0,0" id="path132"/>
8327+ <path style="fill:#de3333;fill-opacity:1;fill-rule:evenodd;stroke:#de3333;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 401,321 h 20 v 20 h -20 z m 0,0" id="path134"/>
8328+ <path style="fill:#de3333;fill-opacity:1;fill-rule:evenodd;stroke:#de3333;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 421,301 h 20 v 20 h -20 z m 0,0" id="path136"/>
8329+ <path style="fill:#de3333;fill-opacity:1;fill-rule:evenodd;stroke:#de3333;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 461,301 h 20 v 20 h -20 z m 0,0" id="path138"/>
8330+ <path style="fill:#de3333;fill-opacity:1;fill-rule:evenodd;stroke:#de3333;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 481,321 h 20 v 20 h -20 z m 0,0" id="path140"/>
8331+ <path style="fill:#de3333;fill-opacity:1;fill-rule:evenodd;stroke:#de3333;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 501,341 h 20 v 20 h -20 z m 0,0" id="path142"/>
8332+ <path style="fill:#de3333;fill-opacity:1;fill-rule:evenodd;stroke:#de3333;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 481,361 h 20 v 20 h -20 z m 0,0" id="path144"/>
8333+ <path style="fill:#de3333;fill-opacity:1;fill-rule:evenodd;stroke:#de3333;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 461,381 h 20 v 20 h -20 z m 0,0" id="path146"/>
8334+ <path style="fill:#de3333;fill-opacity:1;fill-rule:evenodd;stroke:#de3333;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 441,361 h 20 v 20 h -20 z m 0,0" id="path148"/>
8335+ <path style="fill:#de3333;fill-opacity:1;fill-rule:evenodd;stroke:#de3333;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 421,381 h 20 v 20 h -20 z m 0,0" id="path150"/>
8336+ <path style="fill:#de3333;fill-opacity:1;fill-rule:evenodd;stroke:#de3333;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 401,361 h 20 v 20 h -20 z m 0,0" id="path152"/>
8337+ <path style="fill:#de3333;fill-opacity:1;fill-rule:evenodd;stroke:#de3333;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 881,81 h 20 v 220 h -20 z m 0,0" id="path154"/>
8338+ <path style="fill:#de3333;fill-opacity:1;fill-rule:evenodd;stroke:#de3333;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 881,401 h 20 v 220 h -20 z m 0,0" id="path156"/>
8339+ </svg>
8340 diff --git a/ayllu/themes/default/assets/textile-pattern-2.svg b/ayllu/themes/default/assets/textile-pattern-2.svg
8341new file mode 100644
8342index 0000000..f442f7a
8343--- /dev/null
8344+++ b/ayllu/themes/default/assets/textile-pattern-2.svg
8345 @@ -0,0 +1,308 @@
8346+ <?xml version="1.0" encoding="UTF-8" standalone="no"?>
8347+ <svg
8348+ xmlns:dc="http://purl.org/dc/elements/1.1/"
8349+ xmlns:cc="http://creativecommons.org/ns#"
8350+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
8351+ xmlns:svg="http://www.w3.org/2000/svg"
8352+ xmlns="http://www.w3.org/2000/svg"
8353+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
8354+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
8355+ width="1441.0001pt"
8356+ height="1441pt"
8357+ viewBox="0 0 1441.0001 1441"
8358+ version="1.1"
8359+ id="svg11"
8360+ sodipodi:docname="Manto-funerario-paracas-2.svg"
8361+ inkscape:version="1.0 (1.0+r73+1)">
8362+ <metadata
8363+ id="metadata17">
8364+ <rdf:RDF>
8365+ <cc:Work
8366+ rdf:about="">
8367+ <dc:format>image/svg+xml</dc:format>
8368+ <dc:type
8369+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
8370+ <dc:title />
8371+ </cc:Work>
8372+ </rdf:RDF>
8373+ </metadata>
8374+ <defs
8375+ id="defs15" />
8376+ <sodipodi:namedview
8377+ inkscape:document-rotation="0"
8378+ pagecolor="#ffffff"
8379+ bordercolor="#666666"
8380+ borderopacity="1"
8381+ objecttolerance="10"
8382+ gridtolerance="10"
8383+ guidetolerance="10"
8384+ inkscape:pageopacity="0"
8385+ inkscape:pageshadow="2"
8386+ inkscape:window-width="1853"
8387+ inkscape:window-height="1025"
8388+ id="namedview13"
8389+ showgrid="false"
8390+ inkscape:zoom="0.18438366"
8391+ inkscape:cx="471.3773"
8392+ inkscape:cy="745.57387"
8393+ inkscape:window-x="67"
8394+ inkscape:window-y="27"
8395+ inkscape:window-maximized="1"
8396+ inkscape:current-layer="svg11" />
8397+ <g
8398+ transform="translate(-0.49999149,719.5)"
8399+ id="g921">
8400+ <path
8401+ style="fill:#730033;fill-opacity:1;fill-rule:evenodd;stroke:#730033;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1"
8402+ d="M 1,1 H 361 V 361 H 1 Z m 0,0"
8403+ id="path4" />
8404+ <path
8405+ style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:8;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:10;stroke-opacity:1"
8406+ d="m 261,281 h -80 v 50 H 101 V 281 H 21 L 61,201 H 21 L 61,121 H 21 L 81,31 V 231 H 261 V 91 l -50,110 h -80 l -30,-70 v -30 l 50,-70 h 10 v 40 h 20 V 31 h 10 l 30,50 20,-50 h 40 l 60,100 h -60 l 60,100 h -60 l 60,100 h -80 z m 0,0"
8407+ id="path6" />
8408+ <path
8409+ style="fill:#730033;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:8;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:10;stroke-opacity:1"
8410+ d="m 141,131 20,-20 h 20 l 20,20 -20,40 h -20 z m 0,0"
8411+ id="path8" />
8412+ </g>
8413+ <g
8414+ id="g895"
8415+ transform="translate(384.03529,784.594)">
8416+ <path
8417+ id="path4-2"
8418+ d="M -383.53528,295.906 H -23.535268 V 655.90601 H -383.53528 Z m 0,0"
8419+ style="fill:#e15031;fill-opacity:1;fill-rule:evenodd;stroke:#730033;stroke-width:0.999997;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" />
8420+ <path
8421+ id="path6-9"
8422+ d="m -123.53527,575.90601 h -80 v 50 h -80 v -50 h -80.00001 l 40,-80 h -40 l 40,-80.00001 h -40 l 60,-90 v 200.00001 h 180.00001 V 385.906 l -50,110.00001 h -80.00001 L -283.53527,425.906 v -29.99999 l 50,-70.00001 h 9.99999 v 40 h 20.00001 v -40 h 9.99999 l 30,50 20,-50 h 40.00001 l 60.000002,100 h -60.000002 l 60.000002,100.00001 h -60.000002 l 60.000002,100 h -80.000002 z m 0,0"
8423+ style="fill:#ed2031;fill-opacity:1;fill-rule:evenodd;stroke:#730033;stroke-width:8.00002;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:10;stroke-opacity:1" />
8424+ <path
8425+ id="path8-1"
8426+ d="m -243.53528,425.906 20,-20 h 20.00001 l 20,20 -20,40 h -20.00001 z m 0,0"
8427+ style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:8.00002;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:10;stroke-opacity:1" />
8428+ </g>
8429+ <g
8430+ id="g944"
8431+ transform="translate(-198.02416,1142.587)">
8432+ <path
8433+ id="path4-27"
8434+ d="M 558.52418,-62.086991 H 918.52419 V 297.91302 H 558.52418 Z m 0,0"
8435+ style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:0.999997;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" />
8436+ <path
8437+ id="path6-0"
8438+ d="m 818.52419,217.91302 h -80 v 50 h -80 v -50 h -80.00001 l 40,-80 h -40 l 40,-80.000008 h -40 l 60.00001,-90.000003 V 167.91302 h 180 V 27.913013 l -50,110.000007 H 688.52418 L 658.52419,67.913012 v -29.99999 l 50,-70.000013 h 9.99999 V 7.9130143 h 20.00001 V -32.086991 h 9.99999 l 30,50.000006 20,-50.000006 h 40.00001 l 60,100.000003 h -60 l 60,100.000008 h -60 l 60,100 h -80 z m 0,0"
8439+ style="fill:#d66236;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:8.00002;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:10;stroke-opacity:1" />
8440+ <path
8441+ id="path8-9"
8442+ d="m 698.52418,67.913012 20,-20 h 20.00001 l 20,20 -20,39.999998 h -20.00001 z m 0,0"
8443+ style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:8.00002;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:10;stroke-opacity:1" />
8444+ </g>
8445+ <g
8446+ transform="translate(744.03529,424.59399)"
8447+ id="g895-3">
8448+ <path
8449+ style="fill:#e15031;fill-opacity:1;fill-rule:evenodd;stroke:#730033;stroke-width:0.999997;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1"
8450+ d="M -383.53528,295.906 H -23.535268 V 655.90601 H -383.53528 Z m 0,0"
8451+ id="path4-2-6" />
8452+ <path
8453+ style="fill:#ed2031;fill-opacity:1;fill-rule:evenodd;stroke:#730033;stroke-width:8.00002;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:10;stroke-opacity:1"
8454+ d="m -123.53527,575.90601 h -80 v 50 h -80 v -50 h -80.00001 l 40,-80 h -40 l 40,-80.00001 h -40 l 60,-90 v 200.00001 h 180.00001 V 385.906 l -50,110.00001 h -80.00001 L -283.53527,425.906 v -29.99999 l 50,-70.00001 h 9.99999 v 40 h 20.00001 v -40 h 9.99999 l 30,50 20,-50 h 40.00001 l 60.000002,100 h -60.000002 l 60.000002,100.00001 h -60.000002 l 60.000002,100 h -80.000002 z m 0,0"
8455+ id="path6-9-0" />
8456+ <path
8457+ style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:8.00002;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:10;stroke-opacity:1"
8458+ d="m -243.53528,425.906 20,-20 h 20.00001 l 20,20 -20,40 h -20.00001 z m 0,0"
8459+ id="path8-1-6" />
8460+ </g>
8461+ <g
8462+ id="g921-2"
8463+ transform="translate(359.50001,359.5)">
8464+ <path
8465+ id="path4-6"
8466+ d="M 1,1 H 361 V 361 H 1 Z m 0,0"
8467+ style="fill:#730033;fill-opacity:1;fill-rule:evenodd;stroke:#730033;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" />
8468+ <path
8469+ id="path6-1"
8470+ d="m 261,281 h -80 v 50 H 101 V 281 H 21 L 61,201 H 21 L 61,121 H 21 L 81,31 V 231 H 261 V 91 l -50,110 h -80 l -30,-70 v -30 l 50,-70 h 10 v 40 h 20 V 31 h 10 l 30,50 20,-50 h 40 l 60,100 h -60 l 60,100 h -60 l 60,100 h -80 z m 0,0"
8471+ style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:8;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:10;stroke-opacity:1" />
8472+ <path
8473+ id="path8-8"
8474+ d="m 141,131 20,-20 h 20 l 20,20 -20,40 h -20 z m 0,0"
8475+ style="fill:#730033;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:8;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:10;stroke-opacity:1" />
8476+ </g>
8477+ <g
8478+ id="g921-9"
8479+ transform="translate(719.50001,-0.5)">
8480+ <path
8481+ id="path4-7"
8482+ d="M 1,1 H 361 V 361 H 1 Z m 0,0"
8483+ style="fill:#730033;fill-opacity:1;fill-rule:evenodd;stroke:#730033;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" />
8484+ <path
8485+ id="path6-3"
8486+ d="m 261,281 h -80 v 50 H 101 V 281 H 21 L 61,201 H 21 L 61,121 H 21 L 81,31 V 231 H 261 V 91 l -50,110 h -80 l -30,-70 v -30 l 50,-70 h 10 v 40 h 20 V 31 h 10 l 30,50 20,-50 h 40 l 60,100 h -60 l 60,100 h -60 l 60,100 h -80 z m 0,0"
8487+ style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:8;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:10;stroke-opacity:1" />
8488+ <path
8489+ id="path8-6"
8490+ d="m 141,131 20,-20 h 20 l 20,20 -20,40 h -20 z m 0,0"
8491+ style="fill:#730033;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:8;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:10;stroke-opacity:1" />
8492+ </g>
8493+ <g
8494+ transform="translate(1104.0353,64.594)"
8495+ id="g895-1">
8496+ <path
8497+ style="fill:#e15031;fill-opacity:1;fill-rule:evenodd;stroke:#730033;stroke-width:0.999997;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1"
8498+ d="M -383.53528,295.906 H -23.535268 V 655.90601 H -383.53528 Z m 0,0"
8499+ id="path4-2-2" />
8500+ <path
8501+ style="fill:#ed2031;fill-opacity:1;fill-rule:evenodd;stroke:#730033;stroke-width:8.00002;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:10;stroke-opacity:1"
8502+ d="m -123.53527,575.90601 h -80 v 50 h -80 v -50 h -80.00001 l 40,-80 h -40 l 40,-80.00001 h -40 l 60,-90 v 200.00001 h 180.00001 V 385.906 l -50,110.00001 h -80.00001 L -283.53527,425.906 v -29.99999 l 50,-70.00001 h 9.99999 v 40 h 20.00001 v -40 h 9.99999 l 30,50 20,-50 h 40.00001 l 60.000002,100 h -60.000002 l 60.000002,100.00001 h -60.000002 l 60.000002,100 h -80.000002 z m 0,0"
8503+ id="path6-9-9" />
8504+ <path
8505+ style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:8.00002;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:10;stroke-opacity:1"
8506+ d="m -243.53528,425.906 20,-20 h 20.00001 l 20,20 -20,40 h -20.00001 z m 0,0"
8507+ id="path8-1-3" />
8508+ </g>
8509+ <g
8510+ transform="translate(521.97583,422.58699)"
8511+ id="g944-1">
8512+ <path
8513+ style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:0.999997;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1"
8514+ d="M 558.52418,-62.086991 H 918.52419 V 297.91302 H 558.52418 Z m 0,0"
8515+ id="path4-27-9" />
8516+ <path
8517+ style="fill:#d66236;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:8.00002;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:10;stroke-opacity:1"
8518+ d="m 818.52419,217.91302 h -80 v 50 h -80 v -50 h -80.00001 l 40,-80 h -40 l 40,-80.000008 h -40 l 60.00001,-90.000003 V 167.91302 h 180 V 27.913013 l -50,110.000007 H 688.52418 L 658.52419,67.913012 v -29.99999 l 50,-70.000013 h 9.99999 V 7.9130143 h 20.00001 V -32.086991 h 9.99999 l 30,50.000006 20,-50.000006 h 40.00001 l 60,100.000003 h -60 l 60,100.000008 h -60 l 60,100 h -80 z m 0,0"
8519+ id="path6-0-4" />
8520+ <path
8521+ style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:8.00002;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:10;stroke-opacity:1"
8522+ d="m 698.52418,67.913012 20,-20 h 20.00001 l 20,20 -20,39.999998 h -20.00001 z m 0,0"
8523+ id="path8-9-7" />
8524+ </g>
8525+ <g
8526+ id="g895-3-8"
8527+ transform="translate(1464.0353,-295.406)">
8528+ <path
8529+ id="path4-2-6-4"
8530+ d="M -383.53528,295.906 H -23.535268 V 655.90601 H -383.53528 Z m 0,0"
8531+ style="fill:#e15031;fill-opacity:1;fill-rule:evenodd;stroke:#730033;stroke-width:0.999997;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" />
8532+ <path
8533+ id="path6-9-0-5"
8534+ d="m -123.53527,575.90601 h -80 v 50 h -80 v -50 h -80.00001 l 40,-80 h -40 l 40,-80.00001 h -40 l 60,-90 v 200.00001 h 180.00001 V 385.906 l -50,110.00001 h -80.00001 L -283.53527,425.906 v -29.99999 l 50,-70.00001 h 9.99999 v 40 h 20.00001 v -40 h 9.99999 l 30,50 20,-50 h 40.00001 l 60.000002,100 h -60.000002 l 60.000002,100.00001 h -60.000002 l 60.000002,100 h -80.000002 z m 0,0"
8535+ style="fill:#ed2031;fill-opacity:1;fill-rule:evenodd;stroke:#730033;stroke-width:8.00002;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:10;stroke-opacity:1" />
8536+ <path
8537+ id="path8-1-6-0"
8538+ d="m -243.53528,425.906 20,-20 h 20.00001 l 20,20 -20,40 h -20.00001 z m 0,0"
8539+ style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:8.00002;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:10;stroke-opacity:1" />
8540+ </g>
8541+ <g
8542+ transform="translate(161.97582,782.587)"
8543+ id="g944-3">
8544+ <path
8545+ style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:0.999997;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1"
8546+ d="M 558.52418,-62.086991 H 918.52419 V 297.91302 H 558.52418 Z m 0,0"
8547+ id="path4-27-6" />
8548+ <path
8549+ style="fill:#d66236;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:8.00002;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:10;stroke-opacity:1"
8550+ d="m 818.52419,217.91302 h -80 v 50 h -80 v -50 h -80.00001 l 40,-80 h -40 l 40,-80.000008 h -40 l 60.00001,-90.000003 V 167.91302 h 180 V 27.913013 l -50,110.000007 H 688.52418 L 658.52419,67.913012 v -29.99999 l 50,-70.000013 h 9.99999 V 7.9130143 h 20.00001 V -32.086991 h 9.99999 l 30,50.000006 20,-50.000006 h 40.00001 l 60,100.000003 h -60 l 60,100.000008 h -60 l 60,100 h -80 z m 0,0"
8551+ id="path6-0-1" />
8552+ <path
8553+ style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:8.00002;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:10;stroke-opacity:1"
8554+ d="m 698.52418,67.913012 20,-20 h 20.00001 l 20,20 -20,39.999998 h -20.00001 z m 0,0"
8555+ id="path8-9-0" />
8556+ </g>
8557+ <g
8558+ id="g921-6"
8559+ transform="translate(719.50003,1079.5)">
8560+ <path
8561+ id="path4-3"
8562+ d="M 1,1 H 361 V 361 H 1 Z m 0,0"
8563+ style="fill:#730033;fill-opacity:1;fill-rule:evenodd;stroke:#730033;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" />
8564+ <path
8565+ id="path6-2"
8566+ d="m 261,281 h -80 v 50 H 101 V 281 H 21 L 61,201 H 21 L 61,121 H 21 L 81,31 V 231 H 261 V 91 l -50,110 h -80 l -30,-70 v -30 l 50,-70 h 10 v 40 h 20 V 31 h 10 l 30,50 20,-50 h 40 l 60,100 h -60 l 60,100 h -60 l 60,100 h -80 z m 0,0"
8567+ style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:8;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:10;stroke-opacity:1" />
8568+ <path
8569+ id="path8-0"
8570+ d="m 141,131 20,-20 h 20 l 20,20 -20,40 h -20 z m 0,0"
8571+ style="fill:#730033;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:8;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:10;stroke-opacity:1" />
8572+ </g>
8573+ <g
8574+ transform="translate(1079.5,719.5)"
8575+ id="g921-2-6">
8576+ <path
8577+ style="fill:#730033;fill-opacity:1;fill-rule:evenodd;stroke:#730033;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1"
8578+ d="M 1,1 H 361 V 361 H 1 Z m 0,0"
8579+ id="path4-6-1" />
8580+ <path
8581+ style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:8;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:10;stroke-opacity:1"
8582+ d="m 261,281 h -80 v 50 H 101 V 281 H 21 L 61,201 H 21 L 61,121 H 21 L 81,31 V 231 H 261 V 91 l -50,110 h -80 l -30,-70 v -30 l 50,-70 h 10 v 40 h 20 V 31 h 10 l 30,50 20,-50 h 40 l 60,100 h -60 l 60,100 h -60 l 60,100 h -80 z m 0,0"
8583+ id="path6-1-5" />
8584+ <path
8585+ style="fill:#730033;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:8;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:10;stroke-opacity:1"
8586+ d="m 141,131 20,-20 h 20 l 20,20 -20,40 h -20 z m 0,0"
8587+ id="path8-8-5" />
8588+ </g>
8589+ <g
8590+ transform="translate(-558.02418,422.58698)"
8591+ id="g944-4">
8592+ <path
8593+ style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:0.999997;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1"
8594+ d="M 558.52418,-62.086991 H 918.52419 V 297.91302 H 558.52418 Z m 0,0"
8595+ id="path4-27-7" />
8596+ <path
8597+ style="fill:#d66236;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:8.00002;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:10;stroke-opacity:1"
8598+ d="m 818.52419,217.91302 h -80 v 50 h -80 v -50 h -80.00001 l 40,-80 h -40 l 40,-80.000008 h -40 l 60.00001,-90.000003 V 167.91302 h 180 V 27.913013 l -50,110.000007 H 688.52418 L 658.52419,67.913012 v -29.99999 l 50,-70.000013 h 9.99999 V 7.9130143 h 20.00001 V -32.086991 h 9.99999 l 30,50.000006 20,-50.000006 h 40.00001 l 60,100.000003 h -60 l 60,100.000008 h -60 l 60,100 h -80 z m 0,0"
8599+ id="path6-0-6" />
8600+ <path
8601+ style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:8.00002;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:10;stroke-opacity:1"
8602+ d="m 698.52418,67.913012 20,-20 h 20.00001 l 20,20 -20,39.999998 h -20.00001 z m 0,0"
8603+ id="path8-9-5" />
8604+ </g>
8605+ <g
8606+ id="g944-3-6"
8607+ transform="translate(-198.02419,62.58699)">
8608+ <path
8609+ id="path4-27-6-9"
8610+ d="M 558.52418,-62.086991 H 918.52419 V 297.91302 H 558.52418 Z m 0,0"
8611+ style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:0.999997;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" />
8612+ <path
8613+ id="path6-0-1-3"
8614+ d="m 818.52419,217.91302 h -80 v 50 h -80 v -50 h -80.00001 l 40,-80 h -40 l 40,-80.000008 h -40 l 60.00001,-90.000003 V 167.91302 h 180 V 27.913013 l -50,110.000007 H 688.52418 L 658.52419,67.913012 v -29.99999 l 50,-70.000013 h 9.99999 V 7.9130143 h 20.00001 V -32.086991 h 9.99999 l 30,50.000006 20,-50.000006 h 40.00001 l 60,100.000003 h -60 l 60,100.000008 h -60 l 60,100 h -80 z m 0,0"
8615+ style="fill:#d66236;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:8.00002;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:10;stroke-opacity:1" />
8616+ <path
8617+ id="path8-9-0-7"
8618+ d="m 698.52418,67.913012 20,-20 h 20.00001 l 20,20 -20,39.999998 h -20.00001 z m 0,0"
8619+ style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:8.00002;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:10;stroke-opacity:1" />
8620+ </g>
8621+ <g
8622+ id="g895-1-4"
8623+ transform="translate(384.03528,-295.406)">
8624+ <path
8625+ id="path4-2-2-5"
8626+ d="M -383.53528,295.906 H -23.535268 V 655.90601 H -383.53528 Z m 0,0"
8627+ style="fill:#e15031;fill-opacity:1;fill-rule:evenodd;stroke:#730033;stroke-width:0.999997;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" />
8628+ <path
8629+ id="path6-9-9-2"
8630+ d="m -123.53527,575.90601 h -80 v 50 h -80 v -50 h -80.00001 l 40,-80 h -40 l 40,-80.00001 h -40 l 60,-90 v 200.00001 h 180.00001 V 385.906 l -50,110.00001 h -80.00001 L -283.53527,425.906 v -29.99999 l 50,-70.00001 h 9.99999 v 40 h 20.00001 v -40 h 9.99999 l 30,50 20,-50 h 40.00001 l 60.000002,100 h -60.000002 l 60.000002,100.00001 h -60.000002 l 60.000002,100 h -80.000002 z m 0,0"
8631+ style="fill:#ed2031;fill-opacity:1;fill-rule:evenodd;stroke:#730033;stroke-width:8.00002;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:10;stroke-opacity:1" />
8632+ <path
8633+ id="path8-1-3-5"
8634+ d="m -243.53528,425.906 20,-20 h 20.00001 l 20,20 -20,40 h -20.00001 z m 0,0"
8635+ style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:8.00002;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:10;stroke-opacity:1" />
8636+ </g>
8637+ <g
8638+ id="g895-1-47"
8639+ transform="translate(1464.0353,784.594)">
8640+ <path
8641+ id="path4-2-2-4"
8642+ d="M -383.53528,295.906 H -23.535268 V 655.90601 H -383.53528 Z m 0,0"
8643+ style="fill:#e15031;fill-opacity:1;fill-rule:evenodd;stroke:#730033;stroke-width:0.999997;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" />
8644+ <path
8645+ id="path6-9-9-4"
8646+ d="m -123.53527,575.90601 h -80 v 50 h -80 v -50 h -80.00001 l 40,-80 h -40 l 40,-80.00001 h -40 l 60,-90 v 200.00001 h 180.00001 V 385.906 l -50,110.00001 h -80.00001 L -283.53527,425.906 v -29.99999 l 50,-70.00001 h 9.99999 v 40 h 20.00001 v -40 h 9.99999 l 30,50 20,-50 h 40.00001 l 60.000002,100 h -60.000002 l 60.000002,100.00001 h -60.000002 l 60.000002,100 h -80.000002 z m 0,0"
8647+ style="fill:#ed2031;fill-opacity:1;fill-rule:evenodd;stroke:#730033;stroke-width:8.00002;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:10;stroke-opacity:1" />
8648+ <path
8649+ id="path8-1-3-3"
8650+ d="m -243.53528,425.906 20,-20 h 20.00001 l 20,20 -20,40 h -20.00001 z m 0,0"
8651+ style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:8.00002;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:10;stroke-opacity:1" />
8652+ </g>
8653+ </svg>
8654 diff --git a/ayllu/themes/default/assets/xmpp.svg b/ayllu/themes/default/assets/xmpp.svg
8655new file mode 100644
8656index 0000000..0c904a7
8657--- /dev/null
8658+++ b/ayllu/themes/default/assets/xmpp.svg
8659 @@ -0,0 +1,30 @@
8660+ <?xml version="1.0" encoding="UTF-8" standalone="no"?>
8661+ <!-- Generator: Adobe Illustrator 13.0.2, SVG Export Plug-In . SVG Version: 6.00 Build 14948) -->
8662+ <!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
8663+ <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" xml:space="preserve" viewBox="0 0 200 200" width="200px" height="200px" x="0px" y="0px" enable-background="new 0 0 200 200">
8664+
8665+ <linearGradient id="SVGID_right_" y2="1.279e-13" gradientUnits="userSpaceOnUse" x2="-1073.2" gradientTransform="translate(1196.604,15.368977)" y1="126.85" x1="-1073.2">
8666+ <stop stop-color="#1b3967" offset=".011"/>
8667+ <stop stop-color="#13b5ea" offset=".467"/>
8668+ <stop stop-color="#002b5c" offset=".9945"/>
8669+ </linearGradient>
8670+
8671+ <linearGradient id="SVGID_left_" y2="1.279e-13" gradientUnits="userSpaceOnUse" x2="-1073.2" gradientTransform="matrix(-1,0,0,1,-994.78801,15.367977)" y1="126.85" x1="-1073.2">
8672+ <stop stop-color="#1b3967" offset=".011"/>
8673+ <stop stop-color="#13b5ea" offset=".467"/>
8674+ <stop stop-color="#002b5c" offset=".9945"/>
8675+ </linearGradient>
8676+
8677+ <path d="m 151.80512,29.557978 c 0.077,1.313 -1.787,0.968 -1.787,2.293 0,38.551 -46.558,97.366012 -91.687985,108.730012 v 1.639 C 118.28313,136.69999 186.89012,74.419978 188.40012,15.369977 l -36.599,14.189001 z" style="fill:url(#SVGID_right_)"/>
8678+ <path d="m 133.67312,34.300978 c 0.076,1.313 0.12,2.63 0.12,3.957 0,38.551 -30.69898,90.497012 -75.826985,101.860012 v 1.639 c 59.044005,-2.79 105.809995,-63.024012 105.809995,-109.200012 0,-2.375 -0.128,-4.729 -0.371,-7.056 l -29.73,8.798 z" style="fill:#e96d1f"/>
8679+ <path d="m 163.69112,24.951978 -7.61699,2.722 c 0.041,0.962 0.066,2.254 0.066,3.225 0,41.219 -37.271,98.204012 -87.271995,107.120012 -3.24501,1.088 -7.53801,2.077 -10.932,2.931 v 1.638 C 123.19013,137.02799 169.03613,70.721978 163.69612,24.947978 Z" style="fill:#d9541e"/>
8680+
8681+ <path d="m 50.011,29.556978 c -0.077,1.313 1.787,0.968 1.787,2.293 0,38.551 46.558007,97.366012 91.68799,108.730012 v 1.639 C 83.533,136.69899 14.926,74.418978 13.416,15.368977 l 36.599,14.189001 z" style="fill:url(#SVGID_left_)"/>
8682+ <path d="m 68.143,34.299978 c -0.076,1.313 -0.12,2.63 -0.12,3.957 0,38.551 30.698995,90.497012 75.82699,101.860012 v 1.639 C 84.806,138.96599 38.04,78.731978 38.04,32.555978 c 0,-2.375 0.128,-4.729 0.371,-7.056 l 29.73,8.798 z" style="fill:#a0ce67"/>
8683+ <path d="m 38.125,24.950978 7.617,2.722 c -0.041,0.962 -0.066,2.254 -0.066,3.225 0,41.219 37.271,98.204012 87.27199,107.120012 3.245,1.088 7.538,2.077 10.932,2.931 v 1.638 C 78.626,137.02699 32.78,70.720978 38.12,24.946978 Z" style="fill:#439639"/>
8684+
8685+ <path d="m 25.988,172.07799 -13.388,-14.65 h 11.643 l 9.127,10.268 9.129,-10.268 h 11.643 l -13.387,14.646 14.401,14.728 h -12.09 l -9.697,-10.67 -9.693,10.67 H 11.584 l 14.404,-14.73 z"/>
8686+ <path d="m 58.508,157.42799 h 13.836 l 10.183,18.905 10.183,-18.905 h 13.83199 v 29.374 h -8.761983 v -21.096 h -0.08 L 85.893,186.80199 H 79.16 l -11.807,-21.096 h -0.082 v 21.096 h -8.764 v -29.37 z"/>
8687+ <path d="m 112.66199,157.42799 h 24.546 c 8.559,0 10.628,4.302 10.628,10.063 v 2.516 c 0,4.381 -1.908,9.41 -8.275,9.41 h -17.894 v 7.385 h -9.005 v -29.38 z m 9,14.69 h 13.997 c 2.10901,0 2.92401,-1.377 2.92401,-3.123 v -1.135 c 0,-1.99 -0.976,-3.127 -3.694,-3.127 h -13.227 v 7.38 z"/>
8688+ <path d="m 152.72199,157.42799 h 24.546 c 8.561,0 10.63,4.302 10.63,10.063 v 2.516 c 0,4.381 -1.907,9.41 -8.275,9.41 h -17.893 v 7.385 h -9.008 v -29.38 z m 9.01,14.69 h 13.996 c 2.11,0 2.922,-1.377 2.922,-3.123 v -1.135 c 0,-1.99 -0.974,-3.127 -3.693,-3.127 h -13.225 v 7.38 z"/>
8689+ </svg>
8690\ No newline at end of file
8691 diff --git a/ayllu/themes/default/base.scss b/ayllu/themes/default/base.scss
8692new file mode 100644
8693index 0000000..633a10c
8694--- /dev/null
8695+++ b/ayllu/themes/default/base.scss
8696 @@ -0,0 +1,558 @@
8697+ ::-webkit-scrollbar {
8698+ display: none;
8699+ }
8700+
8701+ :root {
8702+ --spacing: 0;
8703+ --font-family: monospace;
8704+ --font-size: 14px;
8705+ }
8706+
8707+ pre {
8708+ white-space: pre-wrap;
8709+ background-color: unset;
8710+ color: unset;
8711+ font-size: unset;
8712+ }
8713+
8714+ table {
8715+ margin-bottom: 0px;
8716+ }
8717+
8718+ table {
8719+ overflow: hidden;
8720+ }
8721+
8722+ table td {
8723+ padding-left: 2px;
8724+ padding-right: 2px;
8725+ }
8726+
8727+ table tbody tr td {
8728+ overflow: hidden;
8729+ white-space: nowrap;
8730+ }
8731+
8732+ table th {
8733+ overflow: hidden;
8734+ white-space: nowrap;
8735+ font-weight: bold;
8736+ text-decoration: underline;
8737+ }
8738+
8739+ header.tree-preamble {
8740+ font-size: smaller;
8741+ }
8742+
8743+ .wide {
8744+ display: flex;
8745+ flex-direction: row;
8746+ justify-content: space-between;
8747+ row-gap: 10px;
8748+ }
8749+
8750+ .wide {
8751+ div:nth-child(2) {
8752+ margin-left: 10px;
8753+ }
8754+ }
8755+
8756+ nav.small a {
8757+ font-size: small;
8758+ }
8759+
8760+ ul.submenu {
8761+ font-size: smaller;
8762+ height: 25px;
8763+ }
8764+
8765+ pre {
8766+ overflow: scroll;
8767+ }
8768+
8769+ [role="button"] {
8770+ padding: 0;
8771+ margin-top: 2px;
8772+ margin-bottom: 2px;
8773+ }
8774+
8775+ button.small {
8776+ display: initial;
8777+ width: initial;
8778+ }
8779+
8780+ nav {
8781+ border-radius: var(--border-radius);
8782+ margin-left: 10px;
8783+ margin-right: 10px;
8784+ }
8785+
8786+ code {
8787+ padding: 0;
8788+ }
8789+
8790+ code.highlighted {
8791+ width: 100%;
8792+ overflow: scroll;
8793+ }
8794+
8795+ footer.main {
8796+ padding-top: 2em;
8797+ text-align: center;
8798+ font-size: smaller;
8799+ }
8800+
8801+ h1, h2, h3, h4, h5, h6 {
8802+ --typography-spacing-vertical: .1em;
8803+ //margin-top: unset;
8804+ //margin-bottom: unset;
8805+ }
8806+
8807+ li.active > a {
8808+ text-decoration: underline;
8809+ }
8810+
8811+ .logo > svg {
8812+ width: 72px;
8813+ }
8814+
8815+ @media (prefers-color-scheme: dark) {
8816+ .logo {
8817+ filter: drop-shadow(0 0 4mm #fff);
8818+ }
8819+ }
8820+
8821+ footer {
8822+ height: 2em;
8823+ }
8824+
8825+ article {
8826+ box-shadow: none;
8827+ }
8828+
8829+ article.clone > input {
8830+ font-size: smaller;
8831+ height: 30px;
8832+ text-align: center;
8833+ }
8834+
8835+ .blob-preview {
8836+ text-align: center;
8837+ border-style: solid;
8838+ border-color: pink;
8839+ margin-top: 2em;
8840+ padding: 1em;
8841+ }
8842+
8843+ .line-number {
8844+ user-select: none;
8845+ // text-align: right;
8846+ padding: 0 2px;
8847+ width: 10px;
8848+ }
8849+
8850+ .line {
8851+ white-space: pre;
8852+ padding-left: 5px;
8853+ }
8854+
8855+ .icon-header > h1,h2,h3,h4,h5 {
8856+ display: inline-block;
8857+ }
8858+
8859+ .icon-header > h1,h2,h3,h4,h5 > a {
8860+ color: none;
8861+ text-decoration: inherit;
8862+ }
8863+
8864+ .icon-header > svg {
8865+ height: 2em;
8866+ // border: 2px solid;
8867+ float: right;
8868+ margin-top: 2px;
8869+ margin-left: 2px;
8870+ }
8871+
8872+ .icon > svg {
8873+ height: 1.5em;
8874+ }
8875+
8876+ .icon-header.contrast > svg {
8877+ border: 2px solid;
8878+ }
8879+
8880+ .emoji {
8881+ float: right;
8882+ }
8883+
8884+ .emoji > svg {
8885+ height: 30px;
8886+ }
8887+
8888+ header.repo div {
8889+ display: inline-block;
8890+ }
8891+
8892+ article > header {
8893+ padding: 3px;
8894+ }
8895+
8896+ ul.language-list li {
8897+ list-style: none;
8898+ }
8899+
8900+ ul.author-list li {
8901+ list-style: none;
8902+ }
8903+
8904+ span.right {
8905+ float: right;
8906+ };
8907+
8908+ span.tiny {
8909+ font-size: .7em;
8910+ font-weight: bold;
8911+ }
8912+
8913+ span.center {
8914+ text-align: center;
8915+ }
8916+
8917+ span.tiny-text {
8918+ font-size: smaller;
8919+ }
8920+
8921+ span.hint {
8922+ font-weight: bold;
8923+ text-decoration: underline;
8924+ }
8925+
8926+ span.h1 {
8927+ font-size: x-large;
8928+ font-weight: bold;
8929+ }
8930+
8931+ span.header-text {
8932+ font-size: 1.2em;
8933+ font-weight: bold;
8934+ }
8935+
8936+ span.sub-header {
8937+ font-size: 1em;
8938+ font-weight: bold;
8939+ }
8940+
8941+ span.header {
8942+ font-size: large;
8943+ }
8944+
8945+ span.timestamp {
8946+ float: right;
8947+ }
8948+
8949+ span.author {
8950+ float: right;
8951+ }
8952+
8953+ article.index-listing {
8954+ margin-top: 25px;
8955+ margin-bottom: 15px;
8956+ }
8957+
8958+ article.index-listing > article {
8959+ padding: 3px;
8960+ }
8961+
8962+ article.listing > article {
8963+ padding: 5px;
8964+ }
8965+
8966+ span.labels {
8967+ font-weight: bold;
8968+ }
8969+
8970+ span.label {
8971+ border: solid 2px;
8972+ }
8973+
8974+ span.labels {
8975+ border-radius: 5px 5px 5px 5px;
8976+ }
8977+
8978+ section.blame {
8979+ display: grid;
8980+ grid-template-columns: 1fr 5fr;
8981+ }
8982+
8983+ div.clone {
8984+ display: flex;
8985+ height: 45px;
8986+ font-size: smaller;
8987+ }
8988+
8989+ div.clone-url > input {
8990+ height: 2em !important;
8991+ }
8992+
8993+ div.clone > div {
8994+ padding: 5px;
8995+ line-height: 45px;
8996+ }
8997+
8998+ div.clone-url {
8999+ flex: auto;
9000+ }
9001+
9002+ section.blame {
9003+ article { margin-top: 0px !important; }
9004+ }
9005+
9006+ article.blame-right {
9007+ padding: 0;
9008+ border-left: solid 1px ;
9009+ }
9010+
9011+ article.blame-left {
9012+ padding: 0;
9013+ border-right: solid 1px;
9014+
9015+ table tbody tr td {
9016+ border-left: solid 1px;
9017+ border-right: solid 1px;
9018+ text-align: left;
9019+ }
9020+ }
9021+
9022+
9023+ .readme {
9024+ // fix markdown list rendering
9025+ ul {
9026+ padding-right: revert;
9027+ padding-left: revert;
9028+ padding-inline-start: revert;
9029+ padding-inline-end: revert;
9030+ }
9031+ }
9032+
9033+ img.rss-icon-feed {
9034+ height: 4em;
9035+ }
9036+
9037+ div.table {
9038+ padding: .5em;
9039+ }
9040+
9041+ div.readme {
9042+ padding: 1em;
9043+ }
9044+
9045+ .term-width {
9046+ word-wrap: break-word;
9047+ width: 600px;
9048+ }
9049+
9050+ footer.tree {
9051+ font-size: smaller;
9052+ }
9053+
9054+ // refs
9055+
9056+ section.refs-container {
9057+ display: grid;
9058+ grid-template-columns: 3fr 1fr;
9059+ }
9060+
9061+ article.ref-right {
9062+ margin-left: 1em;
9063+ }
9064+
9065+
9066+ @media screen and (max-width:1000px) {
9067+ section.refs-container {
9068+ grid-template-columns: 1fr;
9069+ }
9070+
9071+ article.ref-right {
9072+ margin-left: 0;
9073+ }
9074+ }
9075+
9076+ section.controls {
9077+ text-align: center;
9078+ border: 1px;
9079+ }
9080+
9081+ section.viewer {
9082+ text-align: center;
9083+ border: 1px;
9084+ }
9085+
9086+ // repo
9087+
9088+ div.repo-container {
9089+ display: grid;
9090+ grid-template-columns: 3fr 1fr;
9091+ }
9092+
9093+ div.user-box {
9094+ display: grid;
9095+ grid-template-columns: 1fr 4fr;
9096+ }
9097+
9098+ div.user-box > .name {
9099+ font-weight: bold;
9100+ text-decoration: underline;
9101+ }
9102+
9103+ div.commit_message {
9104+ padding-top: 1em;
9105+ }
9106+
9107+ div.user-box > .details {
9108+ text-align: right;
9109+ }
9110+
9111+ .expand-xl {display: none}
9112+
9113+ div.tree > table {
9114+ }
9115+
9116+ div.tree {
9117+ padding-right: 5px;
9118+ padding-left: 5px;
9119+ }
9120+
9121+ article.tree {
9122+ margin-bottom: 5px;
9123+ }
9124+
9125+ section.repo-right {
9126+ text-align: center;
9127+ }
9128+
9129+ section.repo-right {
9130+ margin-left: 1em;
9131+ }
9132+
9133+ section.repo-right > section {
9134+ text-align: center;
9135+ }
9136+
9137+ section.repo-right > article {
9138+ display: block;
9139+ text-align: center;
9140+ }
9141+
9142+ .spaced > div:first-child {
9143+ margin-left: 2px;
9144+ }
9145+
9146+ .panel {
9147+ background: var(--card-background-color);
9148+ padding: .3em;
9149+ }
9150+
9151+ table.commits {
9152+ width: 100%;
9153+ }
9154+
9155+ table.commits > tbody > tr > td.message {
9156+ word-wrap: break-word;
9157+ white-space: normal !important;
9158+ }
9159+
9160+ section.thread-view > article {
9161+ padding-top: 1em;
9162+ padding-bottom: 1em;
9163+ }
9164+
9165+ div.chart {
9166+ text-align: center;
9167+ }
9168+
9169+ // breakpoints and adjustments for different screen sizes
9170+ @if map-get($breakpoints, "md") {
9171+ @media (max-width: map-get($breakpoints, "md")) {
9172+ .collapse {display: none}
9173+ section.repo-right {
9174+ margin-top: 1em;
9175+ margin-left: unset;
9176+ }
9177+ section.viewer > svg {
9178+ width: 400px !important;
9179+ }
9180+ }
9181+ }
9182+
9183+ @if map-get($breakpoints, "xl") {
9184+ @media (max-width: map-get($breakpoints, "xl")) {
9185+ section.viewer > svg {
9186+ width: 550px;
9187+ }
9188+ section.repo-right {
9189+ margin-top: 1em;
9190+ margin-left: unset;
9191+ }
9192+ div.tree > table > thead > tr > th:nth-child(5) {
9193+ text-align: right;
9194+ }
9195+ div.tree > table > tbody > tr > td:nth-child(5) {
9196+ text-align: right;
9197+ }
9198+ article.repo {
9199+ grid-template-columns: repeat(2, 1fr);
9200+ }
9201+ div.repo-container {
9202+ grid-template-columns: 1fr;
9203+ }
9204+ }
9205+ }
9206+
9207+
9208+ @if map-get($breakpoints, "xl") {
9209+ @media (min-width: map-get($breakpoints, "xl")) {
9210+ .expand-xl {display: revert}
9211+ }
9212+ }
9213+
9214+ // code highlighting
9215+
9216+ article.blame-right {
9217+ border-left: solid $blame-border 1px !important;
9218+ }
9219+
9220+ article.blame-left {
9221+ border-right: solid $blame-border 1px !important;
9222+ }
9223+
9224+ article > header.highlighted {
9225+ background-color: $highlighted;
9226+ }
9227+
9228+ article > footer {
9229+ background-color: unset;
9230+ }
9231+
9232+ .icon-header.contrast > svg {
9233+ background-color: $icon-background;
9234+ }
9235+
9236+ article.repo-right {
9237+ background-color: unset;
9238+ }
9239+
9240+ article.repo-right > article {
9241+ background-color: unset;
9242+ }
9243+
9244+ article.repo-right > article > header {
9245+ background-color: unset;
9246+ }
9247+
9248+ .positive {
9249+ color: $positive;
9250+ }
9251+
9252+ .negative {
9253+ color: $negative;
9254+ }
9255 diff --git a/ayllu/themes/default/layout.scss b/ayllu/themes/default/layout.scss
9256new file mode 100644
9257index 0000000..de45bde
9258--- /dev/null
9259+++ b/ayllu/themes/default/layout.scss
9260 @@ -0,0 +1,70 @@
9261+ $breakpoints: (
9262+ xs: 0,
9263+ sm: 576px,
9264+ md: 768px,
9265+ lg: 992px,
9266+ xl: 1200px,
9267+ xxl: 1500px,
9268+ xxxl: 2200px
9269+ );
9270+
9271+ // Viewports
9272+ $viewports: (
9273+ // 'null' disable the viewport on a breakpoint
9274+ sm: 510px,
9275+ md: 700px,
9276+ lg: 920px,
9277+ xl: 1130px,
9278+ xxl: 1480px,
9279+ xxxl: 2190px
9280+ );
9281+
9282+
9283+ .container,
9284+ .container-fluid {
9285+ width: 100%;
9286+ margin-right: auto;
9287+ margin-left: auto;
9288+ padding-right: var(--spacing);
9289+ padding-left: var(--spacing);
9290+ }
9291+
9292+ .container {
9293+ @if map-get($breakpoints, "sm") {
9294+ @media (min-width: map-get($breakpoints, "sm")) {
9295+ max-width: map-get($viewports, "sm");
9296+ padding-right: 0;
9297+ padding-left: 0;
9298+ }
9299+ }
9300+
9301+ @if map-get($breakpoints, "md") {
9302+ @media (min-width: map-get($breakpoints, "md")) {
9303+ max-width: map-get($viewports, "md");
9304+ }
9305+ }
9306+
9307+ @if map-get($breakpoints, "lg") {
9308+ @media (min-width: map-get($breakpoints, "lg")) {
9309+ max-width: map-get($viewports, "lg");
9310+ }
9311+ }
9312+
9313+ @if map-get($breakpoints, "xl") {
9314+ @media (min-width: map-get($breakpoints, "xl")) {
9315+ max-width: map-get($viewports, "xl");
9316+ }
9317+ }
9318+
9319+ @if map-get($breakpoints, "xxl") {
9320+ @media (min-width: map-get($breakpoints, "xxl")) {
9321+ max-width: map-get($viewports, "xxl");
9322+ }
9323+ }
9324+
9325+ @if map-get($breakpoints, "xxxl") {
9326+ @media (min-width: map-get($breakpoints, "xxxl")) {
9327+ max-width: map-get($viewports, "xxxl");
9328+ }
9329+ }
9330+ }
9331 diff --git a/ayllu/themes/default/main.min.css b/ayllu/themes/default/main.min.css
9332new file mode 100644
9333index 0000000..1933d9b
9334--- /dev/null
9335+++ b/ayllu/themes/default/main.min.css
9336 @@ -0,0 +1,32 @@
9337+ /*!
9338+ * Pico CSS v1.5.10 (https://picocss.com)
9339+ * Copyright 2019-2023 - Licensed under MIT
9340+ */:root{--font-family: system-ui, -apple-system, "Segoe UI", "Roboto", "Ubuntu",
9341+ "Cantarell", "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji",
9342+ "Segoe UI Symbol", "Noto Color Emoji";--line-height: 1.5;--font-weight: 400;--font-size: 16px;--border-radius: 0.25rem;--border-width: 1px;--outline-width: 3px;--spacing: 1rem;--typography-spacing-vertical: 1.5rem;--block-spacing-vertical: calc(var(--spacing) * 2);--block-spacing-horizontal: var(--spacing);--grid-spacing-vertical: 0;--grid-spacing-horizontal: var(--spacing);--form-element-spacing-vertical: 0.75rem;--form-element-spacing-horizontal: 1rem;--nav-element-spacing-vertical: 1rem;--nav-element-spacing-horizontal: 0.5rem;--nav-link-spacing-vertical: 0.5rem;--nav-link-spacing-horizontal: 0.5rem;--form-label-font-weight: var(--font-weight);--transition: 0.2s ease-in-out;--modal-overlay-backdrop-filter: blur(0.25rem)}@media (min-width: 576px){:root{--font-size: 17px}}@media (min-width: 768px){:root{--font-size: 18px}}@media (min-width: 992px){:root{--font-size: 19px}}@media (min-width: 1200px){:root{--font-size: 20px}}@media (min-width: 576px){body>header,body>main,body>footer,section{--block-spacing-vertical: calc(var(--spacing) * 2.5)}}@media (min-width: 768px){body>header,body>main,body>footer,section{--block-spacing-vertical: calc(var(--spacing) * 3)}}@media (min-width: 992px){body>header,body>main,body>footer,section{--block-spacing-vertical: calc(var(--spacing) * 3.5)}}@media (min-width: 1200px){body>header,body>main,body>footer,section{--block-spacing-vertical: calc(var(--spacing) * 4)}}@media (min-width: 576px){article{--block-spacing-horizontal: calc(var(--spacing) * 1.25)}}@media (min-width: 768px){article{--block-spacing-horizontal: calc(var(--spacing) * 1.5)}}@media (min-width: 992px){article{--block-spacing-horizontal: calc(var(--spacing) * 1.75)}}@media (min-width: 1200px){article{--block-spacing-horizontal: calc(var(--spacing) * 2)}}dialog>article{--block-spacing-vertical: calc(var(--spacing) * 2);--block-spacing-horizontal: var(--spacing)}@media (min-width: 576px){dialog>article{--block-spacing-vertical: calc(var(--spacing) * 2.5);--block-spacing-horizontal: calc(var(--spacing) * 1.25)}}@media (min-width: 768px){dialog>article{--block-spacing-vertical: calc(var(--spacing) * 3);--block-spacing-horizontal: calc(var(--spacing) * 1.5)}}a{--text-decoration: none}a.secondary,a.contrast{--text-decoration: underline}small{--font-size: 0.875em}h1,h2,h3,h4,h5,h6{--font-weight: 700}h1{--font-size: 2rem;--typography-spacing-vertical: 3rem}h2{--font-size: 1.75rem;--typography-spacing-vertical: 2.625rem}h3{--font-size: 1.5rem;--typography-spacing-vertical: 2.25rem}h4{--font-size: 1.25rem;--typography-spacing-vertical: 1.874rem}h5{--font-size: 1.125rem;--typography-spacing-vertical: 1.6875rem}[type="checkbox"],[type="radio"]{--border-width: 2px}[type="checkbox"][role="switch"]{--border-width: 3px}thead th,thead td,tfoot th,tfoot td{--border-width: 3px}:not(thead,tfoot)>*>td{--font-size: 0.875em}pre,code,kbd,samp{--font-family: "Menlo", "Consolas", "Roboto Mono", "Ubuntu Monospace",
9343+ "Noto Mono", "Oxygen Mono", "Liberation Mono", monospace,
9344+ "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"}kbd{--font-weight: bolder}[data-theme="light"],:root:not([data-theme="dark"]){--background-color: #fff;--color: #415462;--h1-color: #1b2832;--h2-color: #23333e;--h3-color: #2c3d49;--h4-color: #374956;--h5-color: #415462;--h6-color: #4d606d;--muted-color: #73828c;--muted-border-color: #edf0f3;--primary: #596b78;--primary-hover: #415462;--primary-focus: rgba(89,107,120,0.125);--primary-inverse: #fff;--secondary: #596b78;--secondary-hover: #415462;--secondary-focus: rgba(89,107,120,0.125);--secondary-inverse: #fff;--contrast: #1b2832;--contrast-hover: #000;--contrast-focus: rgba(89,107,120,0.125);--contrast-inverse: #fff;--mark-background-color: #e1e6ea;--mark-color: #1b2832;--ins-color: #415462;--del-color: #2c3d49;--blockquote-border-color: var(--muted-border-color);--blockquote-footer-color: var(--muted-color);--button-box-shadow: 0 0 0 rgba(0, 0, 0, 0);--button-hover-box-shadow: 0 0 0 rgba(0, 0, 0, 0);--form-element-background-color: transparent;--form-element-border-color: #a2afb9;--form-element-color: var(--color);--form-element-placeholder-color: var(--muted-color);--form-element-active-background-color: transparent;--form-element-active-border-color: var(--primary);--form-element-focus-color: var(--primary-focus);--form-element-disabled-background-color: #d5dce2;--form-element-disabled-border-color: #a2afb9;--form-element-disabled-opacity: 0.5;--form-element-invalid-border-color: #2c3d49;--form-element-invalid-active-border-color: #415462;--form-element-invalid-focus-color: rgba(65,84,98,0.125);--form-element-valid-border-color: #415462;--form-element-valid-active-border-color: #596b78;--form-element-valid-focus-color: rgba(89,107,120,0.125);--switch-background-color: #bbc6ce;--switch-color: var(--primary-inverse);--switch-checked-background-color: var(--primary);--range-border-color: #d5dce2;--range-active-border-color: #bbc6ce;--range-thumb-border-color: var(--background-color);--range-thumb-color: var(--secondary);--range-thumb-hover-color: var(--secondary-hover);--range-thumb-active-color: var(--primary);--table-border-color: var(--muted-border-color);--table-row-stripped-background-color: #f6f8f9;--code-background-color: #edf0f3;--code-color: var(--muted-color);--code-kbd-background-color: var(--contrast);--code-kbd-color: var(--contrast-inverse);--code-tag-color: #b34d80;--code-property-color: #3d888f;--code-value-color: #986;--code-comment-color: #a2afb9;--accordion-border-color: var(--muted-border-color);--accordion-close-summary-color: var(--color);--accordion-open-summary-color: var(--muted-color);--card-background-color: var(--background-color);--card-border-color: var(--muted-border-color);--card-box-shadow:
9345+ .0145rem .029rem .174rem rgba(27,40,50,0.01698),
9346+ .0335rem .067rem .402rem rgba(27,40,50,0.024),
9347+ .0625rem .125rem .75rem rgba(27,40,50,0.03),
9348+ .1125rem .225rem 1.35rem rgba(27,40,50,0.036),
9349+ .2085rem .417rem 2.502rem rgba(27,40,50,0.04302),
9350+ .5rem 1rem 6rem rgba(27,40,50,0.06),
9351+ 0 0 0 0.0625rem rgba(27,40,50,0.015);--card-sectionning-background-color: #fafbfc;--dropdown-background-color: #fafbfc;--dropdown-border-color: #e1e6ea;--dropdown-box-shadow: var(--card-box-shadow);--dropdown-color: var(--color);--dropdown-hover-background-color: #edf0f3;--modal-overlay-background-color: rgba(213,220,226,0.7);--progress-background-color: #d5dce2;--progress-color: var(--primary);--loading-spinner-opacity: 0.5;--tooltip-background-color: var(--contrast);--tooltip-color: var(--contrast-inverse);--icon-checkbox: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(255, 255, 255)' stroke-width='4' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='20 6 9 17 4 12'%3E%3C/polyline%3E%3C/svg%3E");--icon-chevron: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(65.28, 84.32, 97.92)' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'%3E%3C/polyline%3E%3C/svg%3E");--icon-chevron-button: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(255, 255, 255)' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'%3E%3C/polyline%3E%3C/svg%3E");--icon-chevron-button-inverse: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(255, 255, 255)' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'%3E%3C/polyline%3E%3C/svg%3E");--icon-close: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(114.75, 129.625, 140.25)' stroke-width='4' stroke-linecap='round' stroke-linejoin='round'%3E%3Cline x1='18' y1='6' x2='6' y2='18'%3E%3C/line%3E%3Cline x1='6' y1='6' x2='18' y2='18'%3E%3C/line%3E%3C/svg%3E");--icon-date: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(65.28, 84.32, 97.92)' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Crect x='3' y='4' width='18' height='18' rx='2' ry='2'%3E%3C/rect%3E%3Cline x1='16' y1='2' x2='16' y2='6'%3E%3C/line%3E%3Cline x1='8' y1='2' x2='8' y2='6'%3E%3C/line%3E%3Cline x1='3' y1='10' x2='21' y2='10'%3E%3C/line%3E%3C/svg%3E");--icon-invalid: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(43.9875, 61.09375, 73.3125)' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='12' cy='12' r='10'%3E%3C/circle%3E%3Cline x1='12' y1='8' x2='12' y2='12'%3E%3C/line%3E%3Cline x1='12' y1='16' x2='12.01' y2='16'%3E%3C/line%3E%3C/svg%3E");--icon-minus: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(255, 255, 255)' stroke-width='4' stroke-linecap='round' stroke-linejoin='round'%3E%3Cline x1='5' y1='12' x2='19' y2='12'%3E%3C/line%3E%3C/svg%3E");--icon-search: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(65.28, 84.32, 97.92)' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='11' cy='11' r='8'%3E%3C/circle%3E%3Cline x1='21' y1='21' x2='16.65' y2='16.65'%3E%3C/line%3E%3C/svg%3E");--icon-time: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(65.28, 84.32, 97.92)' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='12' cy='12' r='10'%3E%3C/circle%3E%3Cpolyline points='12 6 12 12 16 14'%3E%3C/polyline%3E%3C/svg%3E");--icon-valid: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(65.28, 84.32, 97.92)' stroke-width='3' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='20 6 9 17 4 12'%3E%3C/polyline%3E%3C/svg%3E");color-scheme:light}@media only screen and (prefers-color-scheme: dark){:root:not([data-theme]){--background-color: #11191f;--color: #bbc6ce;--h1-color: #edf0f3;--h2-color: #e1e6ea;--h3-color: #d5dce2;--h4-color: #c8d1d8;--h5-color: #bbc6ce;--h6-color: #aebbc3;--muted-color: #73828c;--muted-border-color: #1f2d38;--primary: #596b78;--primary-hover: #73828c;--primary-focus: rgba(89,107,120,0.25);--primary-inverse: #fff;--secondary: #596b78;--secondary-hover: #73828c;--secondary-focus: rgba(115,130,140,0.25);--secondary-inverse: #fff;--contrast: #edf0f3;--contrast-hover: #fff;--contrast-focus: rgba(115,130,140,0.25);--contrast-inverse: #000;--mark-background-color: #a2afb9;--mark-color: #11191f;--ins-color: #415462;--del-color: #2c3d49;--blockquote-border-color: var(--muted-border-color);--blockquote-footer-color: var(--muted-color);--button-box-shadow: 0 0 0 rgba(0, 0, 0, 0);--button-hover-box-shadow: 0 0 0 rgba(0, 0, 0, 0);--form-element-background-color: #11191f;--form-element-border-color: #374956;--form-element-color: var(--color);--form-element-placeholder-color: var(--muted-color);--form-element-active-background-color: var(--form-element-background-color);--form-element-active-border-color: var(--primary);--form-element-focus-color: var(--primary-focus);--form-element-disabled-background-color: #2c3d49;--form-element-disabled-border-color: #415462;--form-element-disabled-opacity: 0.5;--form-element-invalid-border-color: #1b2832;--form-element-invalid-active-border-color: #2c3d49;--form-element-invalid-focus-color: rgba(44,61,73,0.25);--form-element-valid-border-color: #2c3d49;--form-element-valid-active-border-color: #415462;--form-element-valid-focus-color: rgba(65,84,98,0.25);--switch-background-color: #374956;--switch-color: var(--primary-inverse);--switch-checked-background-color: var(--primary);--range-border-color: #23333e;--range-active-border-color: #2c3d49;--range-thumb-border-color: var(--background-color);--range-thumb-color: var(--secondary);--range-thumb-hover-color: var(--secondary-hover);--range-thumb-active-color: var(--primary);--table-border-color: var(--muted-border-color);--table-row-stripped-background-color: rgba(115,130,140,0.05);--code-background-color: #17232c;--code-color: var(--muted-color);--code-kbd-background-color: var(--contrast);--code-kbd-color: var(--contrast-inverse);--code-tag-color: #a65980;--code-property-color: #599fa6;--code-value-color: #8c8473;--code-comment-color: #4d606d;--accordion-border-color: var(--muted-border-color);--accordion-active-summary-color: var(--primary);--accordion-close-summary-color: var(--color);--accordion-open-summary-color: var(--muted-color);--card-background-color: #141e25;--card-border-color: var(--card-background-color);--card-box-shadow:
9352+ .0145rem .029rem .174rem rgba(0,0,0,0.01698),
9353+ .0335rem .067rem .402rem rgba(0,0,0,0.024),
9354+ .0625rem .125rem .75rem rgba(0,0,0,0.03),
9355+ .1125rem .225rem 1.35rem rgba(0,0,0,0.036),
9356+ .2085rem .417rem 2.502rem rgba(0,0,0,0.04302),
9357+ .5rem 1rem 6rem rgba(0,0,0,0.06),
9358+ 0 0 0 0.0625rem rgba(0,0,0,0.015);--card-sectionning-background-color: #17232c;--dropdown-background-color: #1b2832;--dropdown-border-color: #23333e;--dropdown-box-shadow: var(--card-box-shadow);--dropdown-color: var(--color);--dropdown-hover-background-color: rgba(35,51,62,0.75);--modal-overlay-background-color: rgba(35,51,62,0.8);--progress-background-color: #23333e;--progress-color: var(--primary);--loading-spinner-opacity: 0.5;--tooltip-background-color: var(--contrast);--tooltip-color: var(--contrast-inverse);--icon-checkbox: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(255, 255, 255)' stroke-width='4' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='20 6 9 17 4 12'%3E%3C/polyline%3E%3C/svg%3E");--icon-chevron: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(161.976, 175.304, 184.824)' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'%3E%3C/polyline%3E%3C/svg%3E");--icon-chevron-button: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(255, 255, 255)' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'%3E%3C/polyline%3E%3C/svg%3E");--icon-chevron-button-inverse: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(0, 0, 0)' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'%3E%3C/polyline%3E%3C/svg%3E");--icon-close: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(114.75, 129.625, 140.25)' stroke-width='4' stroke-linecap='round' stroke-linejoin='round'%3E%3Cline x1='18' y1='6' x2='6' y2='18'%3E%3C/line%3E%3Cline x1='6' y1='6' x2='18' y2='18'%3E%3C/line%3E%3C/svg%3E");--icon-date: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(161.976, 175.304, 184.824)' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Crect x='3' y='4' width='18' height='18' rx='2' ry='2'%3E%3C/rect%3E%3Cline x1='16' y1='2' x2='16' y2='6'%3E%3C/line%3E%3Cline x1='8' y1='2' x2='8' y2='6'%3E%3C/line%3E%3Cline x1='3' y1='10' x2='21' y2='10'%3E%3C/line%3E%3C/svg%3E");--icon-invalid: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(26.775, 40.1625, 49.725)' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='12' cy='12' r='10'%3E%3C/circle%3E%3Cline x1='12' y1='8' x2='12' y2='12'%3E%3C/line%3E%3Cline x1='12' y1='16' x2='12.01' y2='16'%3E%3C/line%3E%3C/svg%3E");--icon-minus: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(255, 255, 255)' stroke-width='4' stroke-linecap='round' stroke-linejoin='round'%3E%3Cline x1='5' y1='12' x2='19' y2='12'%3E%3C/line%3E%3C/svg%3E");--icon-search: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(161.976, 175.304, 184.824)' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='11' cy='11' r='8'%3E%3C/circle%3E%3Cline x1='21' y1='21' x2='16.65' y2='16.65'%3E%3C/line%3E%3C/svg%3E");--icon-time: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(161.976, 175.304, 184.824)' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='12' cy='12' r='10'%3E%3C/circle%3E%3Cpolyline points='12 6 12 12 16 14'%3E%3C/polyline%3E%3C/svg%3E");--icon-valid: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(43.9875, 61.09375, 73.3125)' stroke-width='3' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='20 6 9 17 4 12'%3E%3C/polyline%3E%3C/svg%3E");color-scheme:dark}}[data-theme="dark"]{--background-color: #11191f;--color: #bbc6ce;--h1-color: #edf0f3;--h2-color: #e1e6ea;--h3-color: #d5dce2;--h4-color: #c8d1d8;--h5-color: #bbc6ce;--h6-color: #aebbc3;--muted-color: #73828c;--muted-border-color: #1f2d38;--primary: #596b78;--primary-hover: #73828c;--primary-focus: rgba(89,107,120,0.25);--primary-inverse: #fff;--secondary: #596b78;--secondary-hover: #73828c;--secondary-focus: rgba(115,130,140,0.25);--secondary-inverse: #fff;--contrast: #edf0f3;--contrast-hover: #fff;--contrast-focus: rgba(115,130,140,0.25);--contrast-inverse: #000;--mark-background-color: #a2afb9;--mark-color: #11191f;--ins-color: #415462;--del-color: #2c3d49;--blockquote-border-color: var(--muted-border-color);--blockquote-footer-color: var(--muted-color);--button-box-shadow: 0 0 0 rgba(0, 0, 0, 0);--button-hover-box-shadow: 0 0 0 rgba(0, 0, 0, 0);--form-element-background-color: #11191f;--form-element-border-color: #374956;--form-element-color: var(--color);--form-element-placeholder-color: var(--muted-color);--form-element-active-background-color: var(--form-element-background-color);--form-element-active-border-color: var(--primary);--form-element-focus-color: var(--primary-focus);--form-element-disabled-background-color: #2c3d49;--form-element-disabled-border-color: #415462;--form-element-disabled-opacity: 0.5;--form-element-invalid-border-color: #1b2832;--form-element-invalid-active-border-color: #2c3d49;--form-element-invalid-focus-color: rgba(44,61,73,0.25);--form-element-valid-border-color: #2c3d49;--form-element-valid-active-border-color: #415462;--form-element-valid-focus-color: rgba(65,84,98,0.25);--switch-background-color: #374956;--switch-color: var(--primary-inverse);--switch-checked-background-color: var(--primary);--range-border-color: #23333e;--range-active-border-color: #2c3d49;--range-thumb-border-color: var(--background-color);--range-thumb-color: var(--secondary);--range-thumb-hover-color: var(--secondary-hover);--range-thumb-active-color: var(--primary);--table-border-color: var(--muted-border-color);--table-row-stripped-background-color: rgba(115,130,140,0.05);--code-background-color: #17232c;--code-color: var(--muted-color);--code-kbd-background-color: var(--contrast);--code-kbd-color: var(--contrast-inverse);--code-tag-color: #a65980;--code-property-color: #599fa6;--code-value-color: #8c8473;--code-comment-color: #4d606d;--accordion-border-color: var(--muted-border-color);--accordion-active-summary-color: var(--primary);--accordion-close-summary-color: var(--color);--accordion-open-summary-color: var(--muted-color);--card-background-color: #141e25;--card-border-color: var(--card-background-color);--card-box-shadow:
9359+ .0145rem .029rem .174rem rgba(0,0,0,0.01698),
9360+ .0335rem .067rem .402rem rgba(0,0,0,0.024),
9361+ .0625rem .125rem .75rem rgba(0,0,0,0.03),
9362+ .1125rem .225rem 1.35rem rgba(0,0,0,0.036),
9363+ .2085rem .417rem 2.502rem rgba(0,0,0,0.04302),
9364+ .5rem 1rem 6rem rgba(0,0,0,0.06),
9365+ 0 0 0 0.0625rem rgba(0,0,0,0.015);--card-sectionning-background-color: #17232c;--dropdown-background-color: #1b2832;--dropdown-border-color: #23333e;--dropdown-box-shadow: var(--card-box-shadow);--dropdown-color: var(--color);--dropdown-hover-background-color: rgba(35,51,62,0.75);--modal-overlay-background-color: rgba(35,51,62,0.8);--progress-background-color: #23333e;--progress-color: var(--primary);--loading-spinner-opacity: 0.5;--tooltip-background-color: var(--contrast);--tooltip-color: var(--contrast-inverse);--icon-checkbox: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(255, 255, 255)' stroke-width='4' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='20 6 9 17 4 12'%3E%3C/polyline%3E%3C/svg%3E");--icon-chevron: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(161.976, 175.304, 184.824)' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'%3E%3C/polyline%3E%3C/svg%3E");--icon-chevron-button: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(255, 255, 255)' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'%3E%3C/polyline%3E%3C/svg%3E");--icon-chevron-button-inverse: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(0, 0, 0)' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'%3E%3C/polyline%3E%3C/svg%3E");--icon-close: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(114.75, 129.625, 140.25)' stroke-width='4' stroke-linecap='round' stroke-linejoin='round'%3E%3Cline x1='18' y1='6' x2='6' y2='18'%3E%3C/line%3E%3Cline x1='6' y1='6' x2='18' y2='18'%3E%3C/line%3E%3C/svg%3E");--icon-date: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(161.976, 175.304, 184.824)' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Crect x='3' y='4' width='18' height='18' rx='2' ry='2'%3E%3C/rect%3E%3Cline x1='16' y1='2' x2='16' y2='6'%3E%3C/line%3E%3Cline x1='8' y1='2' x2='8' y2='6'%3E%3C/line%3E%3Cline x1='3' y1='10' x2='21' y2='10'%3E%3C/line%3E%3C/svg%3E");--icon-invalid: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(26.775, 40.1625, 49.725)' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='12' cy='12' r='10'%3E%3C/circle%3E%3Cline x1='12' y1='8' x2='12' y2='12'%3E%3C/line%3E%3Cline x1='12' y1='16' x2='12.01' y2='16'%3E%3C/line%3E%3C/svg%3E");--icon-minus: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(255, 255, 255)' stroke-width='4' stroke-linecap='round' stroke-linejoin='round'%3E%3Cline x1='5' y1='12' x2='19' y2='12'%3E%3C/line%3E%3C/svg%3E");--icon-search: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(161.976, 175.304, 184.824)' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='11' cy='11' r='8'%3E%3C/circle%3E%3Cline x1='21' y1='21' x2='16.65' y2='16.65'%3E%3C/line%3E%3C/svg%3E");--icon-time: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(161.976, 175.304, 184.824)' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='12' cy='12' r='10'%3E%3C/circle%3E%3Cpolyline points='12 6 12 12 16 14'%3E%3C/polyline%3E%3C/svg%3E");--icon-valid: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(43.9875, 61.09375, 73.3125)' stroke-width='3' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='20 6 9 17 4 12'%3E%3C/polyline%3E%3C/svg%3E");color-scheme:dark}progress,[type="checkbox"],[type="radio"],[type="range"]{accent-color:var(--primary)}*,*::before,*::after{box-sizing:border-box;background-repeat:no-repeat}::before,::after{text-decoration:inherit;vertical-align:inherit}:where(:root){-webkit-tap-highlight-color:transparent;-webkit-text-size-adjust:100%;text-size-adjust:100%;background-color:var(--background-color);color:var(--color);font-weight:var(--font-weight);font-size:var(--font-size);line-height:var(--line-height);font-family:var(--font-family);text-rendering:optimizeLegibility;overflow-wrap:break-word;cursor:default;tab-size:4}main{display:block}body{width:100%;margin:0}body>header,body>main,body>footer{width:100%;margin-right:auto;margin-left:auto;padding:var(--block-spacing-vertical) 0}.container,.container-fluid{width:100%;margin-right:auto;margin-left:auto;padding-right:var(--spacing);padding-left:var(--spacing)}@media (min-width: 576px){.container{max-width:510px;padding-right:0;padding-left:0}}@media (min-width: 768px){.container{max-width:700px}}@media (min-width: 992px){.container{max-width:920px}}@media (min-width: 1200px){.container{max-width:1130px}}section{margin-bottom:var(--block-spacing-vertical)}.grid{grid-column-gap:var(--grid-spacing-horizontal);grid-row-gap:var(--grid-spacing-vertical);display:grid;grid-template-columns:1fr;margin:0}@media (min-width: 992px){.grid{grid-template-columns:repeat(auto-fit, minmax(0%, 1fr))}}.grid>*{min-width:0}figure{display:block;margin:0;padding:0;overflow-x:auto}figure figcaption{padding:calc(var(--spacing) * 0.5) 0;color:var(--muted-color)}b,strong{font-weight:bolder}sub,sup{position:relative;font-size:0.75em;line-height:0;vertical-align:baseline}sub{bottom:-0.25em}sup{top:-0.5em}address,blockquote,dl,figure,form,ol,p,pre,table,ul{margin-top:0;margin-bottom:var(--typography-spacing-vertical);color:var(--color);font-style:normal;font-weight:var(--font-weight);font-size:var(--font-size)}a,[role="link"]{--color: var(--primary);--background-color: transparent;outline:none;background-color:var(--background-color);color:var(--color);text-decoration:var(--text-decoration);transition:background-color var(--transition),color var(--transition),text-decoration var(--transition),box-shadow var(--transition)}a:is([aria-current], :hover, :active, :focus),[role="link"]:is([aria-current], :hover, :active, :focus){--color: var(--primary-hover);--text-decoration: underline}a:focus,[role="link"]:focus{--background-color: var(--primary-focus)}a.secondary,[role="link"].secondary{--color: var(--secondary)}a.secondary:is([aria-current], :hover, :active, :focus),[role="link"].secondary:is([aria-current], :hover, :active, :focus){--color: var(--secondary-hover)}a.secondary:focus,[role="link"].secondary:focus{--background-color: var(--secondary-focus)}a.contrast,[role="link"].contrast{--color: var(--contrast)}a.contrast:is([aria-current], :hover, :active, :focus),[role="link"].contrast:is([aria-current], :hover, :active, :focus){--color: var(--contrast-hover)}a.contrast:focus,[role="link"].contrast:focus{--background-color: var(--contrast-focus)}h1,h2,h3,h4,h5,h6{margin-top:0;margin-bottom:var(--typography-spacing-vertical);color:var(--color);font-weight:var(--font-weight);font-size:var(--font-size);font-family:var(--font-family)}h1{--color: var(--h1-color)}h2{--color: var(--h2-color)}h3{--color: var(--h3-color)}h4{--color: var(--h4-color)}h5{--color: var(--h5-color)}h6{--color: var(--h6-color)}:where(address, blockquote, dl, figure, form, ol, p, pre, table, ul)~:is(h1, h2, h3, h4, h5, h6){margin-top:var(--typography-spacing-vertical)}hgroup,.headings{margin-bottom:var(--typography-spacing-vertical)}hgroup>*,.headings>*{margin-bottom:0}hgroup>*:last-child,.headings>*:last-child{--color: var(--muted-color);--font-weight: unset;font-size:1rem;font-family:unset}p{margin-bottom:var(--typography-spacing-vertical)}small{font-size:var(--font-size)}:where(dl, ol, ul){padding-right:0;padding-left:var(--spacing);padding-inline-start:var(--spacing);padding-inline-end:0}:where(dl, ol, ul) li{margin-bottom:calc(var(--typography-spacing-vertical) * 0.25)}:where(dl, ol, ul) :is(dl, ol, ul){margin:0;margin-top:calc(var(--typography-spacing-vertical) * 0.25)}ul li{list-style:square}mark{padding:0.125rem 0.25rem;background-color:var(--mark-background-color);color:var(--mark-color);vertical-align:baseline}blockquote{display:block;margin:var(--typography-spacing-vertical) 0;padding:var(--spacing);border-right:none;border-left:0.25rem solid var(--blockquote-border-color);border-inline-start:0.25rem solid var(--blockquote-border-color);border-inline-end:none}blockquote footer{margin-top:calc(var(--typography-spacing-vertical) * 0.5);color:var(--blockquote-footer-color)}abbr[title]{border-bottom:1px dotted;text-decoration:none;cursor:help}ins{color:var(--ins-color);text-decoration:none}del{color:var(--del-color)}::selection{background-color:var(--primary-focus)}:where(audio, canvas, iframe, img, svg, video){vertical-align:middle}audio,video{display:inline-block}audio:not([controls]){display:none;height:0}:where(iframe){border-style:none}img{max-width:100%;height:auto;border-style:none}:where(svg:not([fill])){fill:currentColor}svg:not(:root){overflow:hidden}button{margin:0;overflow:visible;font-family:inherit;text-transform:none}button,[type="button"],[type="reset"],[type="submit"]{-webkit-appearance:button}button{display:block;width:100%;margin-bottom:var(--spacing)}[role="button"]{display:inline-block;text-decoration:none}button,input[type="submit"],input[type="button"],input[type="reset"],[role="button"]{--background-color: var(--primary);--border-color: var(--primary);--color: var(--primary-inverse);--box-shadow: var(--button-box-shadow, 0 0 0 rgba(0, 0, 0, 0));padding:var(--form-element-spacing-vertical) var(--form-element-spacing-horizontal);border:var(--border-width) solid var(--border-color);border-radius:var(--border-radius);outline:none;background-color:var(--background-color);box-shadow:var(--box-shadow);color:var(--color);font-weight:var(--font-weight);font-size:1rem;line-height:var(--line-height);text-align:center;cursor:pointer;transition:background-color var(--transition),border-color var(--transition),color var(--transition),box-shadow var(--transition)}button:is([aria-current], :hover, :active, :focus),input[type="submit"]:is([aria-current], :hover, :active, :focus),input[type="button"]:is([aria-current], :hover, :active, :focus),input[type="reset"]:is([aria-current], :hover, :active, :focus),[role="button"]:is([aria-current], :hover, :active, :focus){--background-color: var(--primary-hover);--border-color: var(--primary-hover);--box-shadow: var(--button-hover-box-shadow, 0 0 0 rgba(0, 0, 0, 0));--color: var(--primary-inverse)}button:focus,input[type="submit"]:focus,input[type="button"]:focus,input[type="reset"]:focus,[role="button"]:focus{--box-shadow: var(--button-hover-box-shadow, 0 0 0 rgba(0, 0, 0, 0)),
9366+ 0 0 0 var(--outline-width) var(--primary-focus)}:is(button, input[type="submit"], input[type="button"], [role="button"]).secondary,input[type="reset"]{--background-color: var(--secondary);--border-color: var(--secondary);--color: var(--secondary-inverse);cursor:pointer}:is(button, input[type="submit"], input[type="button"], [role="button"]).secondary:is([aria-current], :hover, :active, :focus),input[type="reset"]:is([aria-current], :hover, :active, :focus){--background-color: var(--secondary-hover);--border-color: var(--secondary-hover);--color: var(--secondary-inverse)}:is(button, input[type="submit"], input[type="button"], [role="button"]).secondary:focus,input[type="reset"]:focus{--box-shadow: var(--button-hover-box-shadow, 0 0 0 rgba(0, 0, 0, 0)),
9367+ 0 0 0 var(--outline-width) var(--secondary-focus)}:is(button, input[type="submit"], input[type="button"], [role="button"]).contrast{--background-color: var(--contrast);--border-color: var(--contrast);--color: var(--contrast-inverse)}:is(button, input[type="submit"], input[type="button"], [role="button"]).contrast:is([aria-current], :hover, :active, :focus){--background-color: var(--contrast-hover);--border-color: var(--contrast-hover);--color: var(--contrast-inverse)}:is(button, input[type="submit"], input[type="button"], [role="button"]).contrast:focus{--box-shadow: var(--button-hover-box-shadow, 0 0 0 rgba(0, 0, 0, 0)),
9368+ 0 0 0 var(--outline-width) var(--contrast-focus)}:is(button, input[type="submit"], input[type="button"], [role="button"]).outline,input[type="reset"].outline{--background-color: transparent;--color: var(--primary)}:is(button, input[type="submit"], input[type="button"], [role="button"]).outline:is([aria-current], :hover, :active, :focus),input[type="reset"].outline:is([aria-current], :hover, :active, :focus){--background-color: transparent;--color: var(--primary-hover)}:is(button, input[type="submit"], input[type="button"], [role="button"]).outline.secondary,input[type="reset"].outline{--color: var(--secondary)}:is(button, input[type="submit"], input[type="button"], [role="button"]).outline.secondary:is([aria-current], :hover, :active, :focus),input[type="reset"].outline:is([aria-current], :hover, :active, :focus){--color: var(--secondary-hover)}:is(button, input[type="submit"], input[type="button"], [role="button"]).outline.contrast{--color: var(--contrast)}:is(button, input[type="submit"], input[type="button"], [role="button"]).outline.contrast:is([aria-current], :hover, :active, :focus){--color: var(--contrast-hover)}:where(button, [type="submit"], [type="button"], [type="reset"], [role="button"])[disabled],:where(fieldset[disabled]) :is(button, [type="submit"], [type="button"], [type="reset"], [role="button"]),a[role="button"]:not([href]){opacity:0.5;pointer-events:none}input,optgroup,select,textarea{margin:0;font-size:1rem;line-height:var(--line-height);font-family:inherit;letter-spacing:inherit}input{overflow:visible}select{text-transform:none}legend{max-width:100%;padding:0;color:inherit;white-space:normal}textarea{overflow:auto}[type="checkbox"],[type="radio"]{padding:0}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type="search"]{-webkit-appearance:textfield;outline-offset:-2px}[type="search"]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}::-moz-focus-inner{padding:0;border-style:none}:-moz-focusring{outline:none}:-moz-ui-invalid{box-shadow:none}::-ms-expand{display:none}[type="file"],[type="range"]{padding:0;border-width:0}input:not([type="checkbox"],[type="radio"],[type="range"]){height:calc( (1rem * var(--line-height)) + (var(--form-element-spacing-vertical) * 2) + (var(--border-width) * 2))}fieldset{margin:0;margin-bottom:var(--spacing);padding:0;border:0}label,fieldset legend{display:block;margin-bottom:calc(var(--spacing) * 0.25);font-weight:var(--form-label-font-weight, var(--font-weight))}input:not([type="checkbox"],[type="radio"]),select,textarea{width:100%}input:not([type="checkbox"],[type="radio"],[type="range"],[type="file"]),select,textarea{appearance:none;padding:var(--form-element-spacing-vertical) var(--form-element-spacing-horizontal)}input,select,textarea{--background-color: var(--form-element-background-color);--border-color: var(--form-element-border-color);--color: var(--form-element-color);--box-shadow: none;border:var(--border-width) solid var(--border-color);border-radius:var(--border-radius);outline:none;background-color:var(--background-color);box-shadow:var(--box-shadow);color:var(--color);font-weight:var(--font-weight);transition:background-color var(--transition),border-color var(--transition),color var(--transition),box-shadow var(--transition)}input:not([type="submit"],[type="button"],[type="reset"],[type="checkbox"],[type="radio"],[readonly]):is(:active, :focus),:where(select, textarea):is(:active, :focus){--background-color: var(--form-element-active-background-color)}input:not([type="submit"],[type="button"],[type="reset"],[role="switch"],[readonly]):is(:active, :focus),:where(select, textarea):is(:active, :focus){--border-color: var(--form-element-active-border-color)}input:not([type="submit"],[type="button"],[type="reset"],[type="range"],[type="file"],[readonly]):focus,select:focus,textarea:focus{--box-shadow: 0 0 0 var(--outline-width) var(--form-element-focus-color)}input:not([type="submit"],[type="button"],[type="reset"])[disabled],select[disabled],textarea[disabled],:where(fieldset[disabled]) :is(input:not([type="submit"], [type="button"], [type="reset"]), select, textarea){--background-color: var(--form-element-disabled-background-color);--border-color: var(--form-element-disabled-border-color);opacity:var(--form-element-disabled-opacity);pointer-events:none}:where(input, select, textarea):not([type="checkbox"],[type="radio"],[type="date"],[type="datetime-local"],[type="month"],[type="time"],[type="week"])[aria-invalid]{padding-right:calc( var(--form-element-spacing-horizontal) + 1.5rem) !important;padding-left:var(--form-element-spacing-horizontal);padding-inline-start:var(--form-element-spacing-horizontal) !important;padding-inline-end:calc( var(--form-element-spacing-horizontal) + 1.5rem) !important;background-position:center right 0.75rem;background-size:1rem auto;background-repeat:no-repeat}:where(input, select, textarea):not([type="checkbox"],[type="radio"],[type="date"],[type="datetime-local"],[type="month"],[type="time"],[type="week"])[aria-invalid="false"]{background-image:var(--icon-valid)}:where(input, select, textarea):not([type="checkbox"],[type="radio"],[type="date"],[type="datetime-local"],[type="month"],[type="time"],[type="week"])[aria-invalid="true"]{background-image:var(--icon-invalid)}:where(input, select, textarea)[aria-invalid="false"]{--border-color: var(--form-element-valid-border-color)}:where(input, select, textarea)[aria-invalid="false"]:is(:active, :focus){--border-color: var(--form-element-valid-active-border-color) !important;--box-shadow: 0 0 0 var(--outline-width) var(--form-element-valid-focus-color) !important}:where(input, select, textarea)[aria-invalid="true"]{--border-color: var(--form-element-invalid-border-color)}:where(input, select, textarea)[aria-invalid="true"]:is(:active, :focus){--border-color: var(--form-element-invalid-active-border-color) !important;--box-shadow: 0 0 0 var(--outline-width) var(--form-element-invalid-focus-color) !important}[dir="rtl"] :where(input, select, textarea):not([type="checkbox"],[type="radio"]):is([aria-invalid], [aria-invalid="true"], [aria-invalid="false"] ){background-position:center left 0.75rem}input::placeholder,input::-webkit-input-placeholder,textarea::placeholder,textarea::-webkit-input-placeholder,select:invalid{color:var(--form-element-placeholder-color);opacity:1}input:not([type="checkbox"],[type="radio"]),select,textarea{margin-bottom:var(--spacing)}select::-ms-expand{border:0;background-color:transparent}select:not([multiple],[size]){padding-right:calc(var(--form-element-spacing-horizontal) + 1.5rem);padding-left:var(--form-element-spacing-horizontal);padding-inline-start:var(--form-element-spacing-horizontal);padding-inline-end:calc(var(--form-element-spacing-horizontal) + 1.5rem);background-image:var(--icon-chevron);background-position:center right 0.75rem;background-size:1rem auto;background-repeat:no-repeat}[dir="rtl"] select:not([multiple],[size]){background-position:center left 0.75rem}:where(input, select, textarea, .grid)+small{display:block;width:100%;margin-top:calc(var(--spacing) * -0.75);margin-bottom:var(--spacing);color:var(--muted-color)}label>:where(input, select, textarea){margin-top:calc(var(--spacing) * 0.25)}[type="checkbox"],[type="radio"]{-webkit-appearance:none;-moz-appearance:none;appearance:none;width:1.25em;height:1.25em;margin-top:-0.125em;margin-right:0.375em;margin-left:0;margin-inline-start:0;margin-inline-end:0.375em;border-width:var(--border-width);font-size:inherit;vertical-align:middle;cursor:pointer}[type="checkbox"]::-ms-check,[type="radio"]::-ms-check{display:none}[type="checkbox"]:checked,[type="checkbox"]:checked:active,[type="checkbox"]:checked:focus,[type="radio"]:checked,[type="radio"]:checked:active,[type="radio"]:checked:focus{--background-color: var(--primary);--border-color: var(--primary);background-image:var(--icon-checkbox);background-position:center;background-size:0.75em auto;background-repeat:no-repeat}[type="checkbox"]~label,[type="radio"]~label{display:inline-block;margin-right:0.375em;margin-bottom:0;cursor:pointer}[type="checkbox"]:indeterminate{--background-color: var(--primary);--border-color: var(--primary);background-image:var(--icon-minus);background-position:center;background-size:0.75em auto;background-repeat:no-repeat}[type="radio"]{border-radius:50%}[type="radio"]:checked,[type="radio"]:checked:active,[type="radio"]:checked:focus{--background-color: var(--primary-inverse);border-width:0.35em;background-image:none}[type="checkbox"][role="switch"]{--background-color: var(--switch-background-color);--border-color: var(--switch-background-color);--color: var(--switch-color);width:2.25em;height:1.25em;border:var(--border-width) solid var(--border-color);border-radius:1.25em;background-color:var(--background-color);line-height:1.25em}[type="checkbox"][role="switch"]:focus{--background-color: var(--switch-background-color);--border-color: var(--switch-background-color)}[type="checkbox"][role="switch"]:checked{--background-color: var(--switch-checked-background-color);--border-color: var(--switch-checked-background-color)}[type="checkbox"][role="switch"]:before{display:block;width:calc(1.25em - (var(--border-width) * 2));height:100%;border-radius:50%;background-color:var(--color);content:"";transition:margin 0.1s ease-in-out}[type="checkbox"][role="switch"]:checked{background-image:none}[type="checkbox"][role="switch"]:checked::before{margin-left:calc(1.125em - var(--border-width));margin-inline-start:calc(1.125em - var(--border-width))}[type="checkbox"][aria-invalid="false"],[type="checkbox"]:checked[aria-invalid="false"],[type="radio"][aria-invalid="false"],[type="radio"]:checked[aria-invalid="false"],[type="checkbox"][role="switch"][aria-invalid="false"],[type="checkbox"][role="switch"]:checked[aria-invalid="false"]{--border-color: var(--form-element-valid-border-color)}[type="checkbox"][aria-invalid="true"],[type="checkbox"]:checked[aria-invalid="true"],[type="radio"][aria-invalid="true"],[type="radio"]:checked[aria-invalid="true"],[type="checkbox"][role="switch"][aria-invalid="true"],[type="checkbox"][role="switch"]:checked[aria-invalid="true"]{--border-color: var(--form-element-invalid-border-color)}[type="color"]::-webkit-color-swatch-wrapper{padding:0}[type="color"]::-moz-focus-inner{padding:0}[type="color"]::-webkit-color-swatch{border:0;border-radius:calc(var(--border-radius) * 0.5)}[type="color"]::-moz-color-swatch{border:0;border-radius:calc(var(--border-radius) * 0.5)}input:not([type="checkbox"],[type="radio"],[type="range"],[type="file"]):is([type="date"], [type="datetime-local"], [type="month"], [type="time"], [type="week"]){--icon-position: 0.75rem;--icon-width: 1rem;padding-right:calc(var(--icon-width) + var(--icon-position));background-image:var(--icon-date);background-position:center right var(--icon-position);background-size:var(--icon-width) auto;background-repeat:no-repeat}input:not([type="checkbox"],[type="radio"],[type="range"],[type="file"])[type="time"]{background-image:var(--icon-time)}[type="date"]::-webkit-calendar-picker-indicator,[type="datetime-local"]::-webkit-calendar-picker-indicator,[type="month"]::-webkit-calendar-picker-indicator,[type="time"]::-webkit-calendar-picker-indicator,[type="week"]::-webkit-calendar-picker-indicator{width:var(--icon-width);margin-right:calc(var(--icon-width) * -1);margin-left:var(--icon-position);opacity:0}[dir="rtl"] :is([type="date"], [type="datetime-local"], [type="month"], [type="time"], [type="week"]){text-align:right}@-moz-document url-prefix(){[type="date"],[type="datetime-local"],[type="month"],[type="time"],[type="week"]{padding-right:var(--form-element-spacing-horizontal) !important;background-image:none !important}}[type="file"]{--color: var(--muted-color);padding:calc(var(--form-element-spacing-vertical) * 0.5) 0;border:0;border-radius:0;background:none}[type="file"]::file-selector-button{--background-color: var(--secondary);--border-color: var(--secondary);--color: var(--secondary-inverse);margin-right:calc(var(--spacing) / 2);margin-left:0;margin-inline-start:0;margin-inline-end:calc(var(--spacing) / 2);padding:calc(var(--form-element-spacing-vertical) * 0.5) calc(var(--form-element-spacing-horizontal) * 0.5);border:var(--border-width) solid var(--border-color);border-radius:var(--border-radius);outline:none;background-color:var(--background-color);box-shadow:var(--box-shadow);color:var(--color);font-weight:var(--font-weight);font-size:1rem;line-height:var(--line-height);text-align:center;cursor:pointer;transition:background-color var(--transition),border-color var(--transition),color var(--transition),box-shadow var(--transition)}[type="file"]::file-selector-button:is(:hover, :active, :focus){--background-color: var(--secondary-hover);--border-color: var(--secondary-hover)}[type="file"]::-webkit-file-upload-button{--background-color: var(--secondary);--border-color: var(--secondary);--color: var(--secondary-inverse);margin-right:calc(var(--spacing) / 2);margin-left:0;margin-inline-start:0;margin-inline-end:calc(var(--spacing) / 2);padding:calc(var(--form-element-spacing-vertical) * 0.5) calc(var(--form-element-spacing-horizontal) * 0.5);border:var(--border-width) solid var(--border-color);border-radius:var(--border-radius);outline:none;background-color:var(--background-color);box-shadow:var(--box-shadow);color:var(--color);font-weight:var(--font-weight);font-size:1rem;line-height:var(--line-height);text-align:center;cursor:pointer;transition:background-color var(--transition),border-color var(--transition),color var(--transition),box-shadow var(--transition)}[type="file"]::-webkit-file-upload-button:is(:hover, :active, :focus){--background-color: var(--secondary-hover);--border-color: var(--secondary-hover)}[type="file"]::-ms-browse{--background-color: var(--secondary);--border-color: var(--secondary);--color: var(--secondary-inverse);margin-right:calc(var(--spacing) / 2);margin-left:0;margin-inline-start:0;margin-inline-end:calc(var(--spacing) / 2);padding:calc(var(--form-element-spacing-vertical) * 0.5) calc(var(--form-element-spacing-horizontal) * 0.5);border:var(--border-width) solid var(--border-color);border-radius:var(--border-radius);outline:none;background-color:var(--background-color);box-shadow:var(--box-shadow);color:var(--color);font-weight:var(--font-weight);font-size:1rem;line-height:var(--line-height);text-align:center;cursor:pointer;transition:background-color var(--transition),border-color var(--transition),color var(--transition),box-shadow var(--transition)}[type="file"]::-ms-browse:is(:hover, :active, :focus){--background-color: var(--secondary-hover);--border-color: var(--secondary-hover)}[type="range"]{-webkit-appearance:none;-moz-appearance:none;appearance:none;width:100%;height:1.25rem;background:none}[type="range"]::-webkit-slider-runnable-track{width:100%;height:.25rem;border-radius:var(--border-radius);background-color:var(--range-border-color);transition:background-color var(--transition),box-shadow var(--transition)}[type="range"]::-moz-range-track{width:100%;height:.25rem;border-radius:var(--border-radius);background-color:var(--range-border-color);transition:background-color var(--transition),box-shadow var(--transition)}[type="range"]::-ms-track{width:100%;height:.25rem;border-radius:var(--border-radius);background-color:var(--range-border-color);transition:background-color var(--transition),box-shadow var(--transition)}[type="range"]::-webkit-slider-thumb{-webkit-appearance:none;width:1.25rem;height:1.25rem;margin-top:-.5rem;border:2px solid var(--range-thumb-border-color);border-radius:50%;background-color:var(--range-thumb-color);cursor:pointer;transition:background-color var(--transition),transform var(--transition)}[type="range"]::-moz-range-thumb{-webkit-appearance:none;width:1.25rem;height:1.25rem;margin-top:-.5rem;border:2px solid var(--range-thumb-border-color);border-radius:50%;background-color:var(--range-thumb-color);cursor:pointer;transition:background-color var(--transition),transform var(--transition)}[type="range"]::-ms-thumb{-webkit-appearance:none;width:1.25rem;height:1.25rem;margin-top:-.5rem;border:2px solid var(--range-thumb-border-color);border-radius:50%;background-color:var(--range-thumb-color);cursor:pointer;transition:background-color var(--transition),transform var(--transition)}[type="range"]:hover,[type="range"]:focus{--range-border-color: var(--range-active-border-color);--range-thumb-color: var(--range-thumb-hover-color)}[type="range"]:active{--range-thumb-color: var(--range-thumb-active-color)}[type="range"]:active::-webkit-slider-thumb{transform:scale(1.25)}[type="range"]:active::-moz-range-thumb{transform:scale(1.25)}[type="range"]:active::-ms-thumb{transform:scale(1.25)}input:not([type="checkbox"],[type="radio"],[type="range"],[type="file"])[type="search"]{padding-inline-start:calc(var(--form-element-spacing-horizontal) + 1.75rem);border-radius:5rem;background-image:var(--icon-search);background-position:center left 1.125rem;background-size:1rem auto;background-repeat:no-repeat}input:not([type="checkbox"],[type="radio"],[type="range"],[type="file"])[type="search"][aria-invalid]{padding-inline-start:calc(var(--form-element-spacing-horizontal) + 1.75rem) !important;background-position:center left 1.125rem, center right 0.75rem}input:not([type="checkbox"],[type="radio"],[type="range"],[type="file"])[type="search"][aria-invalid="false"]{background-image:var(--icon-search),var(--icon-valid)}input:not([type="checkbox"],[type="radio"],[type="range"],[type="file"])[type="search"][aria-invalid="true"]{background-image:var(--icon-search),var(--icon-invalid)}[type="search"]::-webkit-search-cancel-button{-webkit-appearance:none;display:none}[dir="rtl"] :where(input):not([type="checkbox"],[type="radio"],[type="range"],[type="file"])[type="search"]{background-position:center right 1.125rem}[dir="rtl"] :where(input):not([type="checkbox"],[type="radio"],[type="range"],[type="file"])[type="search"][aria-invalid]{background-position:center right 1.125rem, center left 0.75rem}:where(table){width:100%;border-collapse:collapse;border-spacing:0;text-indent:0}th,td{padding:calc(var(--spacing) / 2) var(--spacing);border-bottom:var(--border-width) solid var(--table-border-color);color:var(--color);font-weight:var(--font-weight);font-size:var(--font-size);text-align:left;text-align:start}tfoot th,tfoot td{border-top:var(--border-width) solid var(--table-border-color);border-bottom:0}table[role="grid"] tbody tr:nth-child(odd){background-color:var(--table-row-stripped-background-color)}pre,code,kbd,samp{font-size:0.875em;font-family:var(--font-family)}pre{-ms-overflow-style:scrollbar;overflow:auto}pre,code,kbd{border-radius:var(--border-radius);background:var(--code-background-color);color:var(--code-color);font-weight:var(--font-weight);line-height:initial}code,kbd{display:inline-block;padding:0.375rem 0.5rem}pre{display:block;margin-bottom:var(--spacing);overflow-x:auto}pre>code{display:block;padding:var(--spacing);background:none;font-size:14px;line-height:var(--line-height)}code b{color:var(--code-tag-color);font-weight:var(--font-weight)}code i{color:var(--code-property-color);font-style:normal}code u{color:var(--code-value-color);text-decoration:none}code em{color:var(--code-comment-color);font-style:normal}kbd{background-color:var(--code-kbd-background-color);color:var(--code-kbd-color);vertical-align:baseline}hr{height:0;border:0;border-top:1px solid var(--muted-border-color);color:inherit}[hidden],template{display:none !important}canvas{display:inline-block}details{display:block;margin-bottom:var(--spacing);padding-bottom:var(--spacing);border-bottom:var(--border-width) solid var(--accordion-border-color)}details summary{line-height:1rem;list-style-type:none;cursor:pointer;transition:color var(--transition)}details summary:not([role]){color:var(--accordion-close-summary-color)}details summary::-webkit-details-marker{display:none}details summary::marker{display:none}details summary::-moz-list-bullet{list-style-type:none}details summary::after{display:block;width:1rem;height:1rem;margin-inline-start:calc(var(--spacing, 1rem) * 0.5);float:right;transform:rotate(-90deg);background-image:var(--icon-chevron);background-position:right center;background-size:1rem auto;background-repeat:no-repeat;content:"";transition:transform var(--transition)}details summary:focus{outline:none}details summary:focus:not([role="button"]){color:var(--accordion-active-summary-color)}details summary[role="button"]{width:100%;text-align:left}details summary[role="button"]::after{height:calc(1rem * var(--line-height, 1.5));background-image:var(--icon-chevron-button)}details summary[role="button"]:not(.outline).contrast::after{background-image:var(--icon-chevron-button-inverse)}details[open]>summary{margin-bottom:calc(var(--spacing))}details[open]>summary:not([role]):not(:focus){color:var(--accordion-open-summary-color)}details[open]>summary::after{transform:rotate(0)}[dir="rtl"] details summary{text-align:right}[dir="rtl"] details summary::after{float:left;background-position:left center}article{margin:var(--block-spacing-vertical) 0;padding:var(--block-spacing-vertical) var(--block-spacing-horizontal);border-radius:var(--border-radius);background:var(--card-background-color);box-shadow:var(--card-box-shadow)}article>header,article>footer{margin-right:calc(var(--block-spacing-horizontal) * -1);margin-left:calc(var(--block-spacing-horizontal) * -1);padding:calc(var(--block-spacing-vertical) * 0.66) var(--block-spacing-horizontal);background-color:var(--card-sectionning-background-color)}article>header{margin-top:calc(var(--block-spacing-vertical) * -1);margin-bottom:var(--block-spacing-vertical);border-bottom:var(--border-width) solid var(--card-border-color);border-top-right-radius:var(--border-radius);border-top-left-radius:var(--border-radius)}article>footer{margin-top:var(--block-spacing-vertical);margin-bottom:calc(var(--block-spacing-vertical) * -1);border-top:var(--border-width) solid var(--card-border-color);border-bottom-right-radius:var(--border-radius);border-bottom-left-radius:var(--border-radius)}:root{--scrollbar-width: 0px}dialog{display:flex;z-index:999;position:fixed;top:0;right:0;bottom:0;left:0;align-items:center;justify-content:center;width:inherit;min-width:100%;height:inherit;min-height:100%;padding:var(--spacing);border:0;backdrop-filter:var(--modal-overlay-backdrop-filter);background-color:var(--modal-overlay-background-color);color:var(--color)}dialog article{max-height:calc(100vh - var(--spacing) * 2);overflow:auto}@media (min-width: 576px){dialog article{max-width:510px}}@media (min-width: 768px){dialog article{max-width:700px}}dialog article>header,dialog article>footer{padding:calc(var(--block-spacing-vertical) * 0.5) var(--block-spacing-horizontal)}dialog article>header .close{margin:0;margin-left:var(--spacing);float:right}dialog article>footer{text-align:right}dialog article>footer [role="button"]{margin-bottom:0}dialog article>footer [role="button"]:not(:first-of-type){margin-left:calc(var(--spacing) * 0.5)}dialog article p:last-of-type{margin:0}dialog article .close{display:block;width:1rem;height:1rem;margin-top:calc(var(--block-spacing-vertical) * -0.5);margin-bottom:var(--typography-spacing-vertical);margin-left:auto;background-image:var(--icon-close);background-position:center;background-size:auto 1rem;background-repeat:no-repeat;opacity:0.5;transition:opacity var(--transition)}dialog article .close:is([aria-current], :hover, :active, :focus){opacity:1}dialog:not([open]),dialog[open="false"]{display:none}.modal-is-open{padding-right:var(--scrollbar-width, 0px);overflow:hidden;pointer-events:none;touch-action:none}.modal-is-open dialog{pointer-events:auto}:where(.modal-is-opening, .modal-is-closing) dialog,:where(.modal-is-opening, .modal-is-closing) dialog>article{animation-duration:.2s;animation-timing-function:ease-in-out;animation-fill-mode:both}:where(.modal-is-opening, .modal-is-closing) dialog{animation-duration:.8s;animation-name:modal-overlay}:where(.modal-is-opening, .modal-is-closing) dialog>article{animation-delay:.2s;animation-name:modal}.modal-is-closing dialog,.modal-is-closing dialog>article{animation-delay:0s;animation-direction:reverse}@keyframes modal-overlay{from{backdrop-filter:none;background-color:transparent}}@keyframes modal{from{transform:translateY(-100%);opacity:0}}:where(nav li)::before{float:left;content:"\200B"}nav,nav ul{display:flex}nav{justify-content:space-between}nav ol,nav ul{align-items:center;margin-bottom:0;padding:0;list-style:none}nav ol:first-of-type,nav ul:first-of-type{margin-left:calc(var(--nav-element-spacing-horizontal) * -1)}nav ol:last-of-type,nav ul:last-of-type{margin-right:calc(var(--nav-element-spacing-horizontal) * -1)}nav li{display:inline-block;margin:0;padding:var(--nav-element-spacing-vertical) var(--nav-element-spacing-horizontal)}nav li>*{--spacing: 0}nav :where(a, [role="link"]){display:inline-block;margin:calc(var(--nav-link-spacing-vertical) * -1) calc(var(--nav-link-spacing-horizontal) * -1);padding:var(--nav-link-spacing-vertical) var(--nav-link-spacing-horizontal);border-radius:var(--border-radius);text-decoration:none}nav :where(a, [role="link"]):is([aria-current], :hover, :active, :focus){text-decoration:none}nav[aria-label="breadcrumb"]{align-items:center;justify-content:start}nav[aria-label="breadcrumb"] ul li:not(:first-child){margin-inline-start:var(--nav-link-spacing-horizontal)}nav[aria-label="breadcrumb"] ul li:not(:last-child) ::after{position:absolute;width:calc(var(--nav-link-spacing-horizontal) * 2);margin-inline-start:calc(var(--nav-link-spacing-horizontal) / 2);content:"/";color:var(--muted-color);text-align:center}nav[aria-label="breadcrumb"] a[aria-current]{background-color:transparent;color:inherit;text-decoration:none;pointer-events:none}nav [role="button"]{margin-right:inherit;margin-left:inherit;padding:var(--nav-link-spacing-vertical) var(--nav-link-spacing-horizontal)}aside nav,aside ol,aside ul,aside li{display:block}aside li{padding:calc(var(--nav-element-spacing-vertical) * 0.5) var(--nav-element-spacing-horizontal)}aside li a{display:block}aside li [role="button"]{margin:inherit}[dir="rtl"] nav[aria-label="breadcrumb"] ul li:not(:last-child) ::after{content:"\\"}progress{display:inline-block;vertical-align:baseline}progress{-webkit-appearance:none;-moz-appearance:none;display:inline-block;appearance:none;width:100%;height:0.5rem;margin-bottom:calc(var(--spacing) * 0.5);overflow:hidden;border:0;border-radius:var(--border-radius);background-color:var(--progress-background-color);color:var(--progress-color)}progress::-webkit-progress-bar{border-radius:var(--border-radius);background:none}progress[value]::-webkit-progress-value{background-color:var(--progress-color)}progress::-moz-progress-bar{background-color:var(--progress-color)}@media (prefers-reduced-motion: no-preference){progress:indeterminate{background:var(--progress-background-color) linear-gradient(to right, var(--progress-color) 30%, var(--progress-background-color) 30%) top left/150% 150% no-repeat;animation:progress-indeterminate 1s linear infinite}progress:indeterminate[value]::-webkit-progress-value{background-color:transparent}progress:indeterminate::-moz-progress-bar{background-color:transparent}}@media (prefers-reduced-motion: no-preference){[dir="rtl"] progress:indeterminate{animation-direction:reverse}}@keyframes progress-indeterminate{0%{background-position:200% 0}100%{background-position:-200% 0}}details[role="list"],li[role="list"]{position:relative}details[role="list"] summary+ul,li[role="list"]>ul{display:flex;z-index:99;position:absolute;top:auto;right:0;left:0;flex-direction:column;margin:0;padding:0;border:var(--border-width) solid var(--dropdown-border-color);border-radius:var(--border-radius);border-top-right-radius:0;border-top-left-radius:0;background-color:var(--dropdown-background-color);box-shadow:var(--card-box-shadow);color:var(--dropdown-color);white-space:nowrap}details[role="list"] summary+ul li,li[role="list"]>ul li{width:100%;margin-bottom:0;padding:calc(var(--form-element-spacing-vertical) * 0.5) var(--form-element-spacing-horizontal);list-style:none}details[role="list"] summary+ul li:first-of-type,li[role="list"]>ul li:first-of-type{margin-top:calc(var(--form-element-spacing-vertical) * 0.5)}details[role="list"] summary+ul li:last-of-type,li[role="list"]>ul li:last-of-type{margin-bottom:calc(var(--form-element-spacing-vertical) * 0.5)}details[role="list"] summary+ul li a,li[role="list"]>ul li a{display:block;margin:calc(var(--form-element-spacing-vertical) * -0.5) calc(var(--form-element-spacing-horizontal) * -1);padding:calc(var(--form-element-spacing-vertical) * 0.5) var(--form-element-spacing-horizontal);overflow:hidden;color:var(--dropdown-color);text-decoration:none;text-overflow:ellipsis}details[role="list"] summary+ul li a:hover,li[role="list"]>ul li a:hover{background-color:var(--dropdown-hover-background-color)}details[role="list"] summary::after,li[role="list"]>a::after{display:block;width:1rem;height:calc(1rem * var(--line-height, 1.5));margin-inline-start:0.5rem;float:right;transform:rotate(0deg);background-position:right center;background-size:1rem auto;background-repeat:no-repeat;content:""}details[role="list"]{padding:0;border-bottom:none}details[role="list"] summary{margin-bottom:0}details[role="list"] summary:not([role]){height:calc( 1rem * var(--line-height) + var(--form-element-spacing-vertical) * 2 + var(--border-width) * 2);padding:var(--form-element-spacing-vertical) var(--form-element-spacing-horizontal);border:var(--border-width) solid var(--form-element-border-color);border-radius:var(--border-radius);background-color:var(--form-element-background-color);color:var(--form-element-placeholder-color);line-height:inherit;cursor:pointer;transition:background-color var(--transition),border-color var(--transition),color var(--transition),box-shadow var(--transition)}details[role="list"] summary:not([role]):active,details[role="list"] summary:not([role]):focus{border-color:var(--form-element-active-border-color);background-color:var(--form-element-active-background-color)}details[role="list"] summary:not([role]):focus{box-shadow:0 0 0 var(--outline-width) var(--form-element-focus-color)}details[role="list"][open] summary{border-bottom-right-radius:0;border-bottom-left-radius:0}details[role="list"][open] summary::before{display:block;z-index:1;position:fixed;top:0;right:0;bottom:0;left:0;background:none;content:"";cursor:default}nav details[role="list"] summary,nav li[role="list"] a{display:flex;direction:ltr}nav details[role="list"] summary+ul,nav li[role="list"]>ul{min-width:fit-content;border-radius:var(--border-radius)}nav details[role="list"] summary+ul li a,nav li[role="list"]>ul li a{border-radius:0}nav details[role="list"] summary,nav details[role="list"] summary:not([role]){height:auto;padding:var(--nav-link-spacing-vertical) var(--nav-link-spacing-horizontal)}nav details[role="list"][open] summary{border-radius:var(--border-radius)}nav details[role="list"] summary+ul{margin-top:var(--outline-width);margin-inline-start:0}nav details[role="list"] summary[role="link"]{margin-bottom:calc(var(--nav-link-spacing-vertical) * -1);line-height:var(--line-height)}nav details[role="list"] summary[role="link"]+ul{margin-top:calc(var(--nav-link-spacing-vertical) + var(--outline-width));margin-inline-start:calc(var(--nav-link-spacing-horizontal) * -1)}li[role="list"]:hover>ul,li[role="list"] a:active~ul,li[role="list"] a:focus~ul{display:flex}li[role="list"]>ul{display:none;margin-top:calc(var(--nav-link-spacing-vertical) + var(--outline-width));margin-inline-start:calc( var(--nav-element-spacing-horizontal) - var(--nav-link-spacing-horizontal))}li[role="list"]>a::after{background-image:var(--icon-chevron)}label>details[role="list"]{margin-top:calc(var(--spacing) * .25);margin-bottom:var(--spacing)}[aria-busy="true"]{cursor:progress}[aria-busy="true"]:not(input,select,textarea,html)::before{display:inline-block;width:1em;height:1em;border:0.1875em solid currentColor;border-radius:1em;border-right-color:transparent;content:"";vertical-align:text-bottom;vertical-align:-.125em;animation:spinner 0.75s linear infinite;opacity:var(--loading-spinner-opacity)}[aria-busy="true"]:not(input,select,textarea,html):not(:empty)::before{margin-right:calc(var(--spacing) * 0.5);margin-left:0;margin-inline-start:0;margin-inline-end:calc(var(--spacing) * 0.5)}[aria-busy="true"]:not(input,select,textarea,html):empty{text-align:center}button[aria-busy="true"],input[type="submit"][aria-busy="true"],input[type="button"][aria-busy="true"],input[type="reset"][aria-busy="true"],a[aria-busy="true"]{pointer-events:none}@keyframes spinner{to{transform:rotate(360deg)}}[data-tooltip]{position:relative}[data-tooltip]:not(a,button,input){border-bottom:1px dotted;text-decoration:none;cursor:help}[data-tooltip][data-placement="top"]::before,[data-tooltip][data-placement="top"]::after,[data-tooltip]::before,[data-tooltip]::after{display:block;z-index:99;position:absolute;bottom:100%;left:50%;padding:.25rem .5rem;overflow:hidden;transform:translate(-50%, -0.25rem);border-radius:var(--border-radius);background:var(--tooltip-background-color);content:attr(data-tooltip);color:var(--tooltip-color);font-style:normal;font-weight:var(--font-weight);font-size:.875rem;text-decoration:none;text-overflow:ellipsis;white-space:nowrap;opacity:0;pointer-events:none}[data-tooltip][data-placement="top"]::after,[data-tooltip]::after{padding:0;transform:translate(-50%, 0rem);border-top:.3rem solid;border-right:.3rem solid transparent;border-left:.3rem solid transparent;border-radius:0;background-color:transparent;content:"";color:var(--tooltip-background-color)}[data-tooltip][data-placement="bottom"]::before,[data-tooltip][data-placement="bottom"]::after{top:100%;bottom:auto;transform:translate(-50%, 0.25rem)}[data-tooltip][data-placement="bottom"]:after{transform:translate(-50%, -0.3rem);border:.3rem solid transparent;border-bottom:.3rem solid}[data-tooltip][data-placement="left"]::before,[data-tooltip][data-placement="left"]::after{top:50%;right:100%;bottom:auto;left:auto;transform:translate(-0.25rem, -50%)}[data-tooltip][data-placement="left"]:after{transform:translate(0.3rem, -50%);border:.3rem solid transparent;border-left:.3rem solid}[data-tooltip][data-placement="right"]::before,[data-tooltip][data-placement="right"]::after{top:50%;right:auto;bottom:auto;left:100%;transform:translate(0.25rem, -50%)}[data-tooltip][data-placement="right"]:after{transform:translate(-0.3rem, -50%);border:.3rem solid transparent;border-right:.3rem solid}[data-tooltip]:focus::before,[data-tooltip]:focus::after,[data-tooltip]:hover::before,[data-tooltip]:hover::after{opacity:1}@media (hover: hover) and (pointer: fine){[data-tooltip][data-placement="bottom"]:focus::before,[data-tooltip][data-placement="bottom"]:focus::after,[data-tooltip][data-placement="bottom"]:hover [data-tooltip]:focus::before,[data-tooltip][data-placement="bottom"]:hover [data-tooltip]:focus::after,[data-tooltip]:hover::before,[data-tooltip]:hover::after{animation-duration:.2s;animation-name:tooltip-slide-top}[data-tooltip][data-placement="bottom"]:focus::after,[data-tooltip][data-placement="bottom"]:hover [data-tooltip]:focus::after,[data-tooltip]:hover::after{animation-name:tooltip-caret-slide-top}[data-tooltip][data-placement="bottom"]:focus::before,[data-tooltip][data-placement="bottom"]:focus::after,[data-tooltip][data-placement="bottom"]:hover::before,[data-tooltip][data-placement="bottom"]:hover::after{animation-duration:.2s;animation-name:tooltip-slide-bottom}[data-tooltip][data-placement="bottom"]:focus::after,[data-tooltip][data-placement="bottom"]:hover::after{animation-name:tooltip-caret-slide-bottom}[data-tooltip][data-placement="left"]:focus::before,[data-tooltip][data-placement="left"]:focus::after,[data-tooltip][data-placement="left"]:hover::before,[data-tooltip][data-placement="left"]:hover::after{animation-duration:.2s;animation-name:tooltip-slide-left}[data-tooltip][data-placement="left"]:focus::after,[data-tooltip][data-placement="left"]:hover::after{animation-name:tooltip-caret-slide-left}[data-tooltip][data-placement="right"]:focus::before,[data-tooltip][data-placement="right"]:focus::after,[data-tooltip][data-placement="right"]:hover::before,[data-tooltip][data-placement="right"]:hover::after{animation-duration:.2s;animation-name:tooltip-slide-right}[data-tooltip][data-placement="right"]:focus::after,[data-tooltip][data-placement="right"]:hover::after{animation-name:tooltip-caret-slide-right}}@keyframes tooltip-slide-top{from{transform:translate(-50%, 0.75rem);opacity:0}to{transform:translate(-50%, -0.25rem);opacity:1}}@keyframes tooltip-caret-slide-top{from{opacity:0}50%{transform:translate(-50%, -0.25rem);opacity:0}to{transform:translate(-50%, 0rem);opacity:1}}@keyframes tooltip-slide-bottom{from{transform:translate(-50%, -0.75rem);opacity:0}to{transform:translate(-50%, 0.25rem);opacity:1}}@keyframes tooltip-caret-slide-bottom{from{opacity:0}50%{transform:translate(-50%, -0.5rem);opacity:0}to{transform:translate(-50%, -0.3rem);opacity:1}}@keyframes tooltip-slide-left{from{transform:translate(0.75rem, -50%);opacity:0}to{transform:translate(-0.25rem, -50%);opacity:1}}@keyframes tooltip-caret-slide-left{from{opacity:0}50%{transform:translate(0.05rem, -50%);opacity:0}to{transform:translate(0.3rem, -50%);opacity:1}}@keyframes tooltip-slide-right{from{transform:translate(-0.75rem, -50%);opacity:0}to{transform:translate(0.25rem, -50%);opacity:1}}@keyframes tooltip-caret-slide-right{from{opacity:0}50%{transform:translate(-0.05rem, -50%);opacity:0}to{transform:translate(-0.3rem, -50%);opacity:1}}[aria-controls]{cursor:pointer}[aria-disabled="true"],[disabled]{cursor:not-allowed}[aria-hidden="false"][hidden]{display:initial}[aria-hidden="false"][hidden]:not(:focus){clip:rect(0, 0, 0, 0);position:absolute}a,area,button,input,label,select,summary,textarea,[tabindex]{-ms-touch-action:manipulation}[dir="rtl"]{direction:rtl}@media (prefers-reduced-motion: reduce){*:not([aria-busy="true"]),:not([aria-busy="true"])::before,:not([aria-busy="true"])::after{background-attachment:initial !important;animation-duration:1ms !important;animation-delay:-1ms !important;animation-iteration-count:1 !important;scroll-behavior:auto !important;transition-delay:0s !important;transition-duration:0s !important}}.container,.container-fluid{width:100%;margin-right:auto;margin-left:auto;padding-right:var(--spacing);padding-left:var(--spacing)}@media (min-width: 576px){.container{max-width:510px;padding-right:0;padding-left:0}}@media (min-width: 768px){.container{max-width:700px}}@media (min-width: 992px){.container{max-width:920px}}@media (min-width: 1200px){.container{max-width:1130px}}@media (min-width: 1500px){.container{max-width:1480px}}@media (min-width: 2200px){.container{max-width:2190px}}::-webkit-scrollbar{display:none}:root{--spacing: 0;--font-family: monospace;--font-size: 14px}pre{white-space:pre-wrap;background-color:unset;color:unset;font-size:unset}table{margin-bottom:0px}table{overflow:hidden}table td{padding-left:2px;padding-right:2px}table tbody tr td{overflow:hidden;white-space:nowrap}table th{overflow:hidden;white-space:nowrap;font-weight:bold;text-decoration:underline}header.tree-preamble{font-size:smaller}.wide{display:flex;flex-direction:row;justify-content:space-between;row-gap:10px}.wide div:nth-child(2){margin-left:10px}nav.small a{font-size:small}ul.submenu{font-size:smaller;height:25px}pre{overflow:scroll}[role="button"]{padding:0;margin-top:2px;margin-bottom:2px}button.small{display:initial;width:initial}nav{border-radius:var(--border-radius);margin-left:10px;margin-right:10px}code{padding:0}code.highlighted{width:100%;overflow:scroll}footer.main{padding-top:2em;text-align:center;font-size:smaller}h1,h2,h3,h4,h5,h6{--typography-spacing-vertical: .1em}li.active>a{text-decoration:underline}.logo>svg{width:72px}@media (prefers-color-scheme: dark){.logo{filter:drop-shadow(0 0 4mm #fff)}}footer{height:2em}article{box-shadow:none}article.clone>input{font-size:smaller;height:30px;text-align:center}.blob-preview{text-align:center;border-style:solid;border-color:pink;margin-top:2em;padding:1em}.line-number{user-select:none;padding:0 2px;width:10px}.line{white-space:pre;padding-left:5px}.icon-header>h1,h2,h3,h4,h5{display:inline-block}.icon-header>h1,h2,h3,h4,h5>a{color:none;text-decoration:inherit}.icon-header>svg{height:2em;float:right;margin-top:2px;margin-left:2px}.icon>svg{height:1.5em}.icon-header.contrast>svg{border:2px solid}.emoji{float:right}.emoji>svg{height:30px}header.repo div{display:inline-block}article>header{padding:3px}ul.language-list li{list-style:none}ul.author-list li{list-style:none}span.right{float:right}span.tiny{font-size:.7em;font-weight:bold}span.center{text-align:center}span.tiny-text{font-size:smaller}span.hint{font-weight:bold;text-decoration:underline}span.h1{font-size:x-large;font-weight:bold}span.header-text{font-size:1.2em;font-weight:bold}span.sub-header{font-size:1em;font-weight:bold}span.header{font-size:large}span.timestamp{float:right}span.author{float:right}article.index-listing{margin-top:25px;margin-bottom:15px}article.index-listing>article{padding:3px}article.listing>article{padding:5px}span.labels{font-weight:bold}span.label{border:solid 2px}span.labels{border-radius:5px 5px 5px 5px}section.blame{display:grid;grid-template-columns:1fr 5fr}div.clone{display:flex;height:45px;font-size:smaller}div.clone-url>input{height:2em !important}div.clone>div{padding:5px;line-height:45px}div.clone-url{flex:auto}section.blame article{margin-top:0px !important}article.blame-right{padding:0;border-left:solid 1px}article.blame-left{padding:0;border-right:solid 1px}article.blame-left table tbody tr td{border-left:solid 1px;border-right:solid 1px;text-align:left}.readme ul{padding-right:revert;padding-left:revert;padding-inline-start:revert;padding-inline-end:revert}img.rss-icon-feed{height:4em}div.table{padding:.5em}div.readme{padding:1em}.term-width{word-wrap:break-word;width:600px}footer.tree{font-size:smaller}section.refs-container{display:grid;grid-template-columns:3fr 1fr}article.ref-right{margin-left:1em}@media screen and (max-width: 1000px){section.refs-container{grid-template-columns:1fr}article.ref-right{margin-left:0}}section.controls{text-align:center;border:1px}section.viewer{text-align:center;border:1px}div.repo-container{display:grid;grid-template-columns:3fr 1fr}div.user-box{display:grid;grid-template-columns:1fr 4fr}div.user-box>.name{font-weight:bold;text-decoration:underline}div.commit_message{padding-top:1em}div.user-box>.details{text-align:right}.expand-xl{display:none}div.tree{padding-right:5px;padding-left:5px}article.tree{margin-bottom:5px}section.repo-right{text-align:center}section.repo-right{margin-left:1em}section.repo-right>section{text-align:center}section.repo-right>article{display:block;text-align:center}.spaced>div:first-child{margin-left:2px}.panel{background:var(--card-background-color);padding:.3em}table.commits{width:100%}table.commits>tbody>tr>td.message{word-wrap:break-word;white-space:normal !important}section.thread-view>article{padding-top:1em;padding-bottom:1em}div.chart{text-align:center}@media (max-width: 768px){.collapse{display:none}section.repo-right{margin-top:1em;margin-left:unset}section.viewer>svg{width:400px !important}}@media (max-width: 1200px){section.viewer>svg{width:550px}section.repo-right{margin-top:1em;margin-left:unset}div.tree>table>thead>tr>th:nth-child(5){text-align:right}div.tree>table>tbody>tr>td:nth-child(5){text-align:right}article.repo{grid-template-columns:repeat(2, 1fr)}div.repo-container{grid-template-columns:1fr}}@media (min-width: 1200px){.expand-xl{display:revert}}article.blame-right{border-left:solid #000 1px !important}article.blame-left{border-right:solid #000 1px !important}article>header.highlighted{background-color:#000}article>footer{background-color:unset}.icon-header.contrast>svg{background-color:#000}article.repo-right{background-color:unset}article.repo-right>article{background-color:unset}article.repo-right>article>header{background-color:unset}.positive{color:#000}.negative{color:#000}
9369 diff --git a/ayllu/themes/default/templates/404.html b/ayllu/themes/default/templates/404.html
9370new file mode 100644
9371index 0000000..d8bac44
9372--- /dev/null
9373+++ b/ayllu/themes/default/templates/404.html
9374 @@ -0,0 +1,8 @@
9375+ {% extends "base.html" %}
9376+ {% block content %}
9377+ <!-- https://openclipart.org/detail/310184/fair-labyrinth -->
9378+ <h1> Not Found ({{status_code}})</h1>
9379+ <p> Unable to find the resource you have requested, perhaps try looking somewhere else? </p>
9380+ <h4> Error Message: </h4>
9381+ <pre>{{ error_message }}</pre>
9382+ {% endblock %}
9383 diff --git a/ayllu/themes/default/templates/5xx.html b/ayllu/themes/default/templates/5xx.html
9384new file mode 100644
9385index 0000000..e010a62
9386--- /dev/null
9387+++ b/ayllu/themes/default/templates/5xx.html
9388 @@ -0,0 +1,7 @@
9389+ {% extends "base.html" %}
9390+ {% block content %}
9391+ <h1> Internal Service Error ({{status_code}})</h1>
9392+ <p> Something has gone wrong processing your request, please be patient. </p>
9393+ <h4> Error Message: </h4>
9394+ <pre>{{ error_message }}</pre>
9395+ {% endblock %}
9396 diff --git a/ayllu/themes/default/templates/about.html b/ayllu/themes/default/templates/about.html
9397new file mode 100644
9398index 0000000..e319b72
9399--- /dev/null
9400+++ b/ayllu/themes/default/templates/about.html
9401 @@ -0,0 +1,9 @@
9402+ {% extends "base.html" %}
9403+ {% block content %}
9404+ <section>
9405+ <article>
9406+ <header></header>
9407+ {{ blurb | safe }}
9408+ </article>
9409+ </section>
9410+ {% endblock %}
9411 diff --git a/ayllu/themes/default/templates/authors.html b/ayllu/themes/default/templates/authors.html
9412new file mode 100644
9413index 0000000..84eb9d4
9414--- /dev/null
9415+++ b/ayllu/themes/default/templates/authors.html
9416 @@ -0,0 +1,21 @@
9417+ {% extends "base.html" %}
9418+ {% block content %}
9419+ <section>
9420+ <article>
9421+ <header><h1>Authors</h1></header>
9422+ {% for author in authors %}
9423+ <article>
9424+ <div class="wide">
9425+ <div>
9426+ <a href="/{{collection}}/{{name}}/log?username={{author.username | urlencode}}&email={{author.email | urlencode}}">{{author.username}}</a>
9427+ </div>
9428+ <div>
9429+ <span class="positive">+{{author.lines_added}}</span>
9430+ <span class="negative">-{{author.lines_removed}}</span>
9431+ {{author.percentage}}% ({{author.count}})
9432+ </div>
9433+ </div>
9434+ </article>
9435+ {% endfor %}
9436+ </article>
9437+ {% endblock %}
9438 diff --git a/ayllu/themes/default/templates/badge.svg b/ayllu/themes/default/templates/badge.svg
9439new file mode 100644
9440index 0000000..895794b
9441--- /dev/null
9442+++ b/ayllu/themes/default/templates/badge.svg
9443 @@ -0,0 +1,29 @@
9444+ <svg xmlns="http://www.w3.org/2000/svg"
9445+ xmlns:xlink="http://www.w3.org/1999/xlink" width="108" height="20"
9446+ role="img" aria-label="{{key}}: {{value}}">
9447+ <title>{{key}}: {{value}}</title>
9448+ <linearGradient id="s" x2="0" y2="100%">
9449+ <stop offset="0" stop-color="#bbb" stop-opacity=".1" />
9450+ <stop offset="1" stop-opacity=".1" />
9451+ </linearGradient>
9452+ <clipPath id="r">
9453+ <rect width="108" height="20" rx="3" fill="#fff" />
9454+ </clipPath>
9455+ <g clip-path="url(#r)">
9456+ <rect width="55" height="20" fill="#555" />
9457+ <rect x="55" width="53" height="20" fill="#007ec6" />
9458+ <rect width="108" height="20" fill="url(#s)" />
9459+ </g>
9460+ <g fill="#fff" text-anchor="middle"
9461+ font-family="monospace"
9462+ text-rendering="geometricPrecision" font-size="110">
9463+ <text aria-hidden="true" x="285" y="150" fill="#010101"
9464+ fill-opacity=".3" transform="scale(.1)" textLength="450">{{key}}</text>
9465+ <text x="285" y="140" transform="scale(.1)" fill="#fff"
9466+ textLength="450">{{key}}</text>
9467+ <text aria-hidden="true" x="805" y="150" fill="#010101"
9468+ fill-opacity=".3" transform="scale(.1)" textLength="430">{{value}}</text>
9469+ <text x="805" y="140" transform="scale(.1)" fill="#fff"
9470+ textLength="430">{{value}}</text>
9471+ </g>
9472+ </svg>
9473 diff --git a/ayllu/themes/default/templates/base.html b/ayllu/themes/default/templates/base.html
9474new file mode 100644
9475index 0000000..191259d
9476--- /dev/null
9477+++ b/ayllu/themes/default/templates/base.html
9478 @@ -0,0 +1,32 @@
9479+ <!DOCTYPE html>
9480+ <html lang="en">
9481+ <head>
9482+ <meta charset="utf-8">
9483+ <meta name="viewport" content="width=device-width, initial-scale=1">
9484+ <title>
9485+ {% block title %}{{ title }}{% endblock %}
9486+ </title>
9487+ <link rel="stylesheet" href="/static/main.min.css" />
9488+ <link href="/static/assets/ayllu_logo.svg"
9489+ rel="icon"
9490+ type="image/svg+xml" />
9491+ {% block head %}{% endblock %}
9492+ </head>
9493+ <body>
9494+ <!-- https://bugzilla.mozilla.org/show_bug.cgi?id=1404468 -->
9495+ <script>0</script>
9496+ {% block navigation %}
9497+ {% include "nav.html" %}
9498+ {% endblock %}
9499+ {% if fluid %}
9500+ <main class="container-fluid">
9501+ {% else %}
9502+ <main class="container">
9503+ {% endif %}
9504+ {% block content %}{% endblock %}
9505+ </main>
9506+ <footer class="main">
9507+ {% if render_time %}rendered in {{ render_time }}ms{% endif %}
9508+ </footer>
9509+ </body>
9510+ </html>
9511 diff --git a/ayllu/themes/default/templates/blame.html b/ayllu/themes/default/templates/blame.html
9512new file mode 100644
9513index 0000000..9a0a83b
9514--- /dev/null
9515+++ b/ayllu/themes/default/templates/blame.html
9516 @@ -0,0 +1,33 @@
9517+ {% import "macros.html" as macros %}
9518+ {% extends "base.html" %}
9519+ {% block content %}
9520+ <article>
9521+ <header>
9522+ {{ macros::navigation(items=subnav_elements, title="Blame") }}
9523+ </header>
9524+ <section class="blame">
9525+ {% if is_renderable %}
9526+ <article class="blame-left">
9527+ <pre><table><tbody>
9528+ {%- for line in blame_lines -%}
9529+ {%- set n_lines = line.end - line.start -%}
9530+ <tr>
9531+ <td>{{line.author_name}} <a href="/{{collection}}/{{name}}/commit/{{line.commit_id}}">{{line.timestamp}}</a></td>
9532+ {%- for _ in range(end=n_lines-1) -%}
9533+ <tr><td>&nbsp;</td><td></td></tr>
9534+ {%- endfor -%}
9535+ {% endfor %}</tbody></table></pre>
9536+ </article>
9537+ <article class="blame-right">
9538+ <pre>{{ content | safe }}</pre>
9539+ </article>
9540+ {% else %}
9541+ <article>
9542+ <header>
9543+ <h2>This content is not blameable</h2>
9544+ </header>
9545+ </article>
9546+ {% endif %}
9547+ </section>
9548+ </article>
9549+ {% endblock %}
9550 diff --git a/ayllu/themes/default/templates/blob.html b/ayllu/themes/default/templates/blob.html
9551new file mode 100644
9552index 0000000..bc4d2e9
9553--- /dev/null
9554+++ b/ayllu/themes/default/templates/blob.html
9555 @@ -0,0 +1,40 @@
9556+ {% import "macros.html" as macros %}
9557+ {% extends "base.html" %}
9558+ {% block content %}
9559+ <section>
9560+ <article>
9561+ <header>
9562+ {{ macros::navigation(items=subnav_elements, title="Blob") }}
9563+ </br>
9564+ {%- if hint -%}
9565+ <span class="hint" style="color: {{color}}">{{hint}}</span></br>
9566+ {%- endif -%}
9567+ <span>{{file_name}}</span>
9568+ <span class="right"> {{ file_mode | filemode }} {{ file_size | human_bytes}} </span>
9569+ </header>
9570+ {% if is_binary %}
9571+ <div class="blob-preview">
9572+ {% if is_image %}
9573+ <img src="{{ raw_url }}"></img>
9574+ {% elif is_video %}
9575+ <video controls>
9576+ <source src="{{raw_url}}" type="{{file_type}}" />
9577+ <p><a href="{{raw_url}}">raw url</a></p>
9578+ </video>
9579+ {% elif is_audio %}
9580+ <audio src="{{raw_url}}" type="{{file_type}}"></audio>
9581+ {% else %}
9582+ <center><h3>Cannot render binary content</h3></center>
9583+ <a href="{{ raw_url }}">download</a>
9584+ {% endif %}
9585+ </div>
9586+ {% else %}
9587+ {% if is_markdown %}
9588+ <div class="readme">{{ content | safe }}</div>
9589+ {% else %}
9590+ <code class="highlighted">{{ content | safe }}</code>
9591+ {% endif %}
9592+ {% endif %}
9593+ </article>
9594+ </section>
9595+ {% endblock %}
9596 diff --git a/ayllu/themes/default/templates/branches.html b/ayllu/themes/default/templates/branches.html
9597new file mode 100644
9598index 0000000..84ddc1d
9599--- /dev/null
9600+++ b/ayllu/themes/default/templates/branches.html
9601 @@ -0,0 +1,26 @@
9602+ {% import "macros.html" as macros %}
9603+ {% extends "base.html" %}
9604+ {% block content %}
9605+ <section>
9606+ <article>
9607+ <header>
9608+ {{ macros::navigation(items=refnav, title="Branches") }}
9609+ </header>
9610+ {% for branch in branches %}
9611+ <article class="inner">
9612+ <header class="wide">
9613+ <div><h3><a href="/{{collection}}/{{name}}/tree/{{branch.name | urlencode }}">{{ branch.name }}</a></h3></div>
9614+ <div><h5>{{ branch.commit.epoch | friendly_time }}<h5></div>
9615+ </header>
9616+ <p>{{branch.commit.message}}</p>
9617+ <footer class="wide">
9618+ <div>{{ branch.commit.author_name }}</div>
9619+ <div>
9620+ <a href="/{{collection}}/{{name}}/commit/{{branch.commit.id}}" role="button">{{branch.commit.id | truncate(length=8, end="")}}</a>
9621+ </div>
9622+ </footer>
9623+ </article>
9624+ {% endfor %}
9625+ </article>
9626+ </section>
9627+ {% endblock %}
9628 diff --git a/ayllu/themes/default/templates/build.html b/ayllu/themes/default/templates/build.html
9629new file mode 100644
9630index 0000000..2f667cc
9631--- /dev/null
9632+++ b/ayllu/themes/default/templates/build.html
9633 @@ -0,0 +1,29 @@
9634+ {% extends "base.html" %}
9635+ {% block content %}
9636+ <section>
9637+ <article>
9638+ <header>
9639+ <h1>Build: {{ build.id }}</h1>
9640+ </header>
9641+ <table>
9642+ <thead>
9643+ <th> date </th>
9644+ <th> runtime </th>
9645+ <th> success </th>
9646+ </thead>
9647+ <tr>
9648+ <td> {{ build.timestamp }} </td>
9649+ <td> {{ build.runtime }} </td>
9650+ <td> {{ build.success }} </td>
9651+ </tr>
9652+ </table>
9653+ </article>
9654+ {% for step in steps %}
9655+ <article>
9656+ <header> {{ step.0 }} </header>
9657+ <code> {{ step.1 }} </code>
9658+ <code> {{ step.2 }} </code>
9659+ </article>
9660+ {% endfor %}
9661+ </section>
9662+ {% endblock %}
9663 diff --git a/ayllu/themes/default/templates/builds.html b/ayllu/themes/default/templates/builds.html
9664new file mode 100644
9665index 0000000..9e8e383
9666--- /dev/null
9667+++ b/ayllu/themes/default/templates/builds.html
9668 @@ -0,0 +1,26 @@
9669+ {% extends "base.html" %}
9670+ {% block content %}
9671+ <section>
9672+ <article>
9673+ <header>
9674+ <h1>Builds</h1>
9675+ </header>
9676+ <table>
9677+ <thead>
9678+ <th> id </th>
9679+ <th> date </th>
9680+ <th> runtime </th>
9681+ <th> success </th>
9682+ </thead>
9683+ {% for build in builds %}
9684+ <tr>
9685+ <td> <a href="/{{collection}}/{{name}}/builds/{{build.id}}">{{ build.id }}</a></td>
9686+ <td> {{ build.timestamp }} </td>
9687+ <td> {{ build.runtime }} </td>
9688+ <td> {{ build.success }} </td>
9689+ </tr>
9690+ {% endfor %}
9691+ </table>
9692+ </article>
9693+ </section>
9694+ {% endblock %}
9695 diff --git a/ayllu/themes/default/templates/channel.html b/ayllu/themes/default/templates/channel.html
9696new file mode 100644
9697index 0000000..81a29a2
9698--- /dev/null
9699+++ b/ayllu/themes/default/templates/channel.html
9700 @@ -0,0 +1,27 @@
9701+ {% import "macros.html" as macros %}
9702+ {% extends "base.html" %}
9703+ {% block content %}
9704+ <section>
9705+ <article>
9706+ <header>
9707+ {{ macros::navigation(items=discnav, title=channel) }}
9708+ </header>
9709+ <table>
9710+ <thead>
9711+ <th> nick </th>
9712+ <th> timestamp </th>
9713+ <th> message</th>
9714+ </thead>
9715+ <tbody>
9716+ {% for message in messages %}
9717+ <tr>
9718+ <td> {{ message.nickname }} </td>
9719+ <td> {{ message.timestamp }} </td>
9720+ <td>{{ message.body | escape }}</td>
9721+ </tr>
9722+ {% endfor %}
9723+ </tbody>
9724+ </table>
9725+ </article>
9726+ </section>
9727+ {% endblock %}
9728 diff --git a/ayllu/themes/default/templates/channels.html b/ayllu/themes/default/templates/channels.html
9729new file mode 100644
9730index 0000000..75e4a03
9731--- /dev/null
9732+++ b/ayllu/themes/default/templates/channels.html
9733 @@ -0,0 +1,27 @@
9734+ {% import "macros.html" as macros %}
9735+ {% extends "base.html" %}
9736+ {% block content %}
9737+ <section>
9738+ <article>
9739+ <header>
9740+ {{ macros::navigation(items=discnav, title="XMPP Channels") }}
9741+ </header>
9742+ <table>
9743+ <thead>
9744+ <th> name </th>
9745+ <th> online </th>
9746+ <th> messages </th>
9747+ </thead>
9748+ <tbody>
9749+ {% for channel in channels %}
9750+ <tr>
9751+ <td><a href="/discuss/xmpp/{{channel.name}}">{{ channel.name }}</a></td>
9752+ <td>{{ channel.n_users }}</td>
9753+ <td>{{ channel.n_messages }}</td>
9754+ </tr>
9755+ {% endfor %}
9756+ </tbody>
9757+ </table>
9758+ </article>
9759+ </section>
9760+ {% endblock %}
9761 diff --git a/ayllu/themes/default/templates/chart.html b/ayllu/themes/default/templates/chart.html
9762new file mode 100644
9763index 0000000..ed03896
9764--- /dev/null
9765+++ b/ayllu/themes/default/templates/chart.html
9766 @@ -0,0 +1,20 @@
9767+ {% extends "base.html" %}
9768+ {% block content %}
9769+ <section>
9770+ <article>
9771+ <header>
9772+ <nav>
9773+ <ul><h1>{{ chart_title }}</h1></ul>
9774+ <ul>
9775+ {% for item in chartnav %}
9776+ <li {% if item.2 %} class="active" {% endif %}> <a href="{{ item.1 }}">{{ item.0 }}</a></li>
9777+ {% endfor %}
9778+ </ul>
9779+ </nav>
9780+ </header>
9781+ <section class="viewer">
9782+ {{ chart | safe }}
9783+ </section>
9784+ </article>
9785+ </section>
9786+ {% endblock %}
9787 diff --git a/ayllu/themes/default/templates/collection.html b/ayllu/themes/default/templates/collection.html
9788new file mode 100644
9789index 0000000..0b8224b
9790--- /dev/null
9791+++ b/ayllu/themes/default/templates/collection.html
9792 @@ -0,0 +1,23 @@
9793+ {% extends "base.html" %}
9794+ {% block content %}
9795+ <section class="index">
9796+ <article>
9797+ <header>
9798+ <span class="h1">{{ collection.name }} </span>
9799+ <span class="right">{{ collection.description }}</b> {%- if is_hidden -%} <span class="negative">[hidden]</span> {%- endif -%}</span>
9800+ </header>
9801+ {% for repo in repositories %}
9802+ <article>
9803+ <span class="header">
9804+ <a href="/{{collection.name}}/{{repo.name}}">{{ repo.name }}</a>
9805+ </span>
9806+ <span class="right">{{repo.description}}</span>
9807+ <footer>
9808+ <span><small>{{repo.age}}</small></span>
9809+ <span class="right">{{repo.activity}}</span>
9810+ </footer>
9811+ </article>
9812+ {% endfor %}
9813+ </article>
9814+ </section>
9815+ {% endblock %}
9816 diff --git a/ayllu/themes/default/templates/commit.html b/ayllu/themes/default/templates/commit.html
9817new file mode 100644
9818index 0000000..b23617e
9819--- /dev/null
9820+++ b/ayllu/themes/default/templates/commit.html
9821 @@ -0,0 +1,61 @@
9822+ {% extends "base.html" %}
9823+ {% block content %}
9824+ <article>
9825+ <header class="wide">
9826+ <h1>Commit</h1>
9827+ <span class="right">{{ commit.epoch | friendly_time }}</span>
9828+ </header>
9829+ <article>
9830+ <div class="wide">
9831+ <div> <b> Author: </b> </div>
9832+ <div>
9833+ <a href="/{{collection}}/{{name}}/log?username={{commit.author_name | urlencode}}&email={{commit.author_email | urlencode}}">{{commit.author_name}}</a>
9834+ [<a href="mailto://{{commit.author_email}}">{{commit.author_email}}</a>]
9835+ </div>
9836+ </div>
9837+ {% if distinct_author %}
9838+ <div class="wide">
9839+ <div><b>Committer:</b></div>
9840+ <div>
9841+ <a href="#">{{commit.committer_name}}</a>
9842+ [<a href="mailto://{{commit.committer_email}}">{{commit.committer_email}}</a>]
9843+ {{ commit.committer_epoch | format_epoch }}
9844+ </div>
9845+ </div>
9846+ {% endif %}
9847+ <div class="wide">
9848+ <div><b>Hash:</b></div>
9849+ {% if commit.is_verified %}
9850+ <span class="positive">
9851+ {% else %}
9852+ <span class="negative">
9853+ {% endif %}
9854+ {{commit.id}}
9855+ </span>
9856+ </div>
9857+ <div class="wide">
9858+ <div> <b> Time: </b> </div>
9859+ <div> {{ commit.author_epoch | format_epoch }} </div>
9860+ </div>
9861+ <b> Message: </b>
9862+ </br>
9863+ <i>{{ commit.summary }}</i>
9864+ </article>
9865+ {% if extended_commit_message %}
9866+ <div class="commit_message">
9867+ <pre>{{ commit.message }}</pre>
9868+ </div>
9869+ {% endif %}
9870+ <footer>
9871+ <span class="positive">+{{stats.insertions}}</span>
9872+ <span class="negative">-{{stats.deletions}}</span>
9873+ <span>+/-{{stats.files_changed}}</span>
9874+ <span class="right">
9875+ <a href="/{{collection}}/{{name}}/tree/{{commit_hash}}" role="button">browse</a>
9876+ </span>
9877+ </footer>
9878+ </article>
9879+ <article>
9880+ <pre>{{diff | safe}}</pre>
9881+ </article>
9882+ {% endblock %}
9883 diff --git a/ayllu/themes/default/templates/config.html b/ayllu/themes/default/templates/config.html
9884new file mode 100644
9885index 0000000..349356a
9886--- /dev/null
9887+++ b/ayllu/themes/default/templates/config.html
9888 @@ -0,0 +1,25 @@
9889+ {% extends "base.html" %}
9890+ {% block content %}
9891+ <div class="container">
9892+ <article>
9893+ <header><h1>Site Configuration</h1></header>
9894+ <form action="/config" method="POST">
9895+ <label for="theme"><h5>items-per-page</h5></label>
9896+ <select id="items_per_page" name="items_per_page" required>
9897+ <option value="50" {%- if config.items_per_page == 50 -%} selected {%- endif -%}>50</option>
9898+ <option value="100" {%- if config.items_per_page == 100 -%} selected {%- endif -%}>100</option>
9899+ <option value="150" {%- if config.items_per_page == 150 -%} selected {%- endif -%}>150</option>
9900+ <option value="200" {%- if config.items_per_page == 200 -%} selected {%- endif -%}>200</option>
9901+ </select>
9902+ <label for="theme"><h5>theme</h5></label>
9903+ <select id="theme" name="theme" required>
9904+ {% for theme in themes %}
9905+ <option value="{{theme}}" {%- if theme == config.theme -%} selected {%- endif -%}>{{ theme }}</option>
9906+ {% endfor %}
9907+ </select>
9908+ <!-- Button -->
9909+ <button type="submit">Submit</button>
9910+ </form>
9911+ </article>
9912+ </div>
9913+ {% endblock %}
9914 diff --git a/ayllu/themes/default/templates/graphql.html b/ayllu/themes/default/templates/graphql.html
9915new file mode 100644
9916index 0000000..b8d9155
9917--- /dev/null
9918+++ b/ayllu/themes/default/templates/graphql.html
9919 @@ -0,0 +1,6 @@
9920+ {% extends "base.html" %}
9921+ {% block content %}
9922+ <p>
9923+ Graphql API
9924+ </p>
9925+ {% endblock %}
9926 diff --git a/ayllu/themes/default/templates/index.html b/ayllu/themes/default/templates/index.html
9927new file mode 100644
9928index 0000000..40ac8a5
9929--- /dev/null
9930+++ b/ayllu/themes/default/templates/index.html
9931 @@ -0,0 +1,37 @@
9932+ {% extends "base.html" %}
9933+ {% block content %}
9934+ <section class="index">
9935+ <header class="wide"><div><h3>Collections</h3></div>
9936+ <div>
9937+ <div class="icon-header">
9938+ <h3>Subscribe</h3>
9939+ {{ "feed" | emoji | safe }}
9940+ </div>
9941+ [<a href="/rss/firehose.xml">*</a>,
9942+ <a href="/rss/1d.xml">1d</a>,
9943+ <a href="/rss/1w.xml">1w</a>,
9944+ <a href="/rss/1m.xml">1m</a>]
9945+ </div>
9946+ </header>
9947+ {% for collection in collections %}
9948+ <article class="index-listing">
9949+ <header class="wide">
9950+ <div><h5><a href="/{{collection.name}}">{{ collection.name }}</a></h5></div>
9951+ <div><b>{{ collection.description }}</b></div>
9952+ </header>
9953+ {% for repo in collection.repositories %}
9954+ <article>
9955+ <header class="wide">
9956+ <div><a href="/{{collection.name}}/{{repo.name}}">{{ repo.name }}</a></div>
9957+ <div>{{repo.description}}</div>
9958+ </header>
9959+ <div class="wide">
9960+ <div>{{repo.age}}</div>
9961+ <div>{{repo.activity}}</div>
9962+ </footer>
9963+ </article>
9964+ {% endfor %}
9965+ </article>
9966+ {% endfor %}
9967+ </section>
9968+ {% endblock %}
9969 diff --git a/ayllu/themes/default/templates/lists.html b/ayllu/themes/default/templates/lists.html
9970new file mode 100644
9971index 0000000..afbcce5
9972--- /dev/null
9973+++ b/ayllu/themes/default/templates/lists.html
9974 @@ -0,0 +1,29 @@
9975+ {% import "macros.html" as macros %}
9976+ {% extends "base.html" %}
9977+ {% block content %}
9978+ <section>
9979+ <article>
9980+ <header>
9981+ <h1> Mailing Lists </h1>
9982+ </header>
9983+ <table>
9984+ <thead>
9985+ <th> id </th>
9986+ <th> name </th>
9987+ <th> description </th>
9988+ <th> address </th>
9989+ </thead>
9990+ <tbody>
9991+ {% for list in lists %}
9992+ <tr>
9993+ <td><a href="/mail/{{list.id}}">{{ list.id }}</a></td>
9994+ <td>{{ list.name }}</td>
9995+ <td>{{ list.description }}</td>
9996+ <td>{{ list.address }}</td>
9997+ </tr>
9998+ {% endfor %}
9999+ </tbody>
10000+ </table>
10001+ </article>
10002+ </section>
10003+ {% endblock %}
10004 diff --git a/ayllu/themes/default/templates/log.html b/ayllu/themes/default/templates/log.html
10005new file mode 100644
10006index 0000000..fe97650
10007--- /dev/null
10008+++ b/ayllu/themes/default/templates/log.html
10009 @@ -0,0 +1,61 @@
10010+ {% import "macros.html" as macros %}
10011+ {% extends "base.html" %}
10012+ {% block content %}
10013+ <section>
10014+ <article>
10015+ {% if file_view %}
10016+ <header>
10017+ {{ macros::navigation(items=subnav_elements, title="Log") }}
10018+ </header>
10019+ {% else %}
10020+ <header>
10021+ <h1>Log</h1>
10022+ </header>
10023+ {% endif %}
10024+ <table class="commits">
10025+ <thead>
10026+ <tr>
10027+ <th class="collapse">Author</th>
10028+ <th class="collapse">Signature</th>
10029+ <!--<th class="collapse">Hash</th>-->
10030+ <th>Message</th>
10031+ <th>Date</th>
10032+ </tr>
10033+ </thead>
10034+ <tbody>
10035+ {% for commit in commits %}
10036+ <tr>
10037+ <td class="collapse"><a href="/{{collection}}/{{name}}/log?username={{commit.author_name | urlencode}}&email={{commit.author_email | urlencode}}">{{commit.author_name}}</a></td>
10038+ <!-- <td class="collapse"> 🔒......</td>-->
10039+ <td class="collapse">
10040+ {% if commit.is_verified %}
10041+ <span class="positive">
10042+ {% else %}
10043+ <span class="negative">
10044+ {% endif %}
10045+ {{ commit.id | truncate(length=8, end="") }}
10046+ </span>
10047+ </td>
10048+ <td class="message">
10049+ <a href="/{{collection}}/{{name}}/commit/{{commit.id}}">
10050+ {{ commit.summary | truncate(length=80) }}</a>
10051+ {% if commit.is_extended %}
10052+ <br/>
10053+ <br/>
10054+ <pre>{{ commit.message }}</pre>
10055+ {% endif %}
10056+ </td>
10057+ <td> {{ commit.epoch | friendly_time }} </td>
10058+ </tr>
10059+ {% endfor %}
10060+ </tbody>
10061+ </table>
10062+ {% if has_more %}
10063+ <footer class="pagination">
10064+ {%- set last_commit = commits | last -%}
10065+ <span class="right"><a href="/{{collection}}/{{name}}/log/{{last_commit.id}}"><b>next</b></a></span>
10066+ </footer>
10067+ {% endif %}
10068+ </article>
10069+ </section>
10070+ {% endblock %}
10071 diff --git a/ayllu/themes/default/templates/macros.html b/ayllu/themes/default/templates/macros.html
10072new file mode 100644
10073index 0000000..c636ae9
10074--- /dev/null
10075+++ b/ayllu/themes/default/templates/macros.html
10076 @@ -0,0 +1,10 @@
10077+ {% macro navigation(items, title="") %}
10078+ <nav>
10079+ <ul><h1>{{title}}</h1></ul>
10080+ <ul>
10081+ {% for item in items %}
10082+ <li {% if item.2 %} class="active" {% endif %}> <a href="{{ item.1 }}">{{ item.0 }}</a></li>
10083+ {% endfor %}
10084+ </ul>
10085+ </nav>
10086+ {% endmacro navigation %}
10087 diff --git a/ayllu/themes/default/templates/nav.html b/ayllu/themes/default/templates/nav.html
10088new file mode 100644
10089index 0000000..2492a79
10090--- /dev/null
10091+++ b/ayllu/themes/default/templates/nav.html
10092 @@ -0,0 +1,16 @@
10093+ <nav>
10094+ <ul>
10095+ <li>
10096+ <a href="{%- if subpath_mode -%}/browse{%- else -%}/{%- endif -%}">
10097+ <div class="logo">{{ "textile-pattern-1" | emoji | safe }}
10098+ <h3>&emsp;</h3>
10099+ </div>
10100+ </a>
10101+ </li>
10102+ </ul>
10103+ <ul>
10104+ {% for item in nav_elements %}
10105+ <li {% if item.2 %} class="active" {% endif %}> <a href="{{ item.1 }}">{{ item.0 }}</a></li>
10106+ {% endfor %}
10107+ </ul>
10108+ </nav>
10109 diff --git a/ayllu/themes/default/templates/post.html b/ayllu/themes/default/templates/post.html
10110new file mode 100644
10111index 0000000..10a0e41
10112--- /dev/null
10113+++ b/ayllu/themes/default/templates/post.html
10114 @@ -0,0 +1,16 @@
10115+ {% extends "base.html" %}
10116+ {% block content %}
10117+ <section class="thread-view">
10118+ <article>
10119+ <header>
10120+ <h1>{{message.message_id}}</h1></br>
10121+ <h4> Export Message </h4>
10122+ <p> <a href="/mail/export/{{list_id}}/{{thread_id}}/{{message.message_id}}">mbox</a><p>
10123+ <b>From: {{ message.from_address }}</b></br>
10124+ <b>Subject: {{ message.subject }}</b></br>
10125+ <span class="right">{{ message.created_at | format_epoch }}</span>
10126+ </header>
10127+ <pre>{{ message.text | safe }}</pre>
10128+ </article>
10129+ </section>
10130+ {% endblock %}
10131 diff --git a/ayllu/themes/default/templates/repo.html b/ayllu/themes/default/templates/repo.html
10132new file mode 100644
10133index 0000000..631f670
10134--- /dev/null
10135+++ b/ayllu/themes/default/templates/repo.html
10136 @@ -0,0 +1,178 @@
10137+ {% extends "base.html" %}
10138+ {% block content %}
10139+ {% if show_details %}
10140+ <div class="repo-container">
10141+ {% endif %}
10142+ <section class="repo-left">
10143+ <article class=tree>
10144+ <header class="tree-preamble">
10145+ <div class="wide">
10146+ <span>{{ latest_commit.author_name }} {{latest_commit.epoch | friendly_time }}</span>
10147+ <h6>{{ commit_count }} commits</h6>
10148+ </div>
10149+ <div>
10150+ <span class="right">
10151+ <h6><b>{{refname}}</b></h6>
10152+ </span>
10153+ </div>
10154+ {% if latest_commit.is_verified %}
10155+ <span class="positive">
10156+ {% else %}
10157+ <span class="negative">
10158+ {% endif %}
10159+ {{latest_commit.id | truncate(length=8, end="")}}
10160+ </span>
10161+ </br>
10162+ <a href="/{{collection}}/{{name}}/commit/{{latest_commit.id}}">
10163+ {{latest_commit.summary | truncate(length=60)}}
10164+ </a>
10165+ </header>
10166+ <div class="tree">
10167+ <table>
10168+ <thead>
10169+ <tr>
10170+ <th scope="col">file</th>
10171+ <th class="collapse" scope="col">commit</th>
10172+ <th class="expand-xl" scope="col">size</th>
10173+ <th class="expand-xl" scope="col">mode</th>
10174+ <th scope="col">time</th>
10175+ </tr>
10176+ </thead>
10177+ <tbody>
10178+ {% for item in tree %}
10179+ <tr>
10180+ {% if item.0.submodule %}
10181+ <td>{{ item.0.name }} @ {{ item.0.submodule }}</td>
10182+ {% else %}
10183+ <td>
10184+ <a href="{{item.0.name | make_url(kind=item.0.kind)}}">
10185+ {{item.0.name}}
10186+ {% if item.0.kind == "Submodule" %}
10187+ <span class="tiny">ref</span>
10188+ {% endif %}
10189+ {% if item.0.kind == "Pointer" %}
10190+ <span class="tiny">ptr</span>
10191+ {% endif %}
10192+ </a>
10193+ </td>
10194+ {% endif %}
10195+ <td class="collapse">
10196+ <a href="/{{collection}}/{{name}}/commit/{{item.1.id}}">{{ item.1.summary | truncate(length=60)}}</a></td>
10197+ <td class="expand-xl">{{item.0.size | human_bytes }}</td>
10198+ <td class="expand-xl">{{item.0.mode | filemode }}</td>
10199+ <td> <a href="/{{collection}}/{{name}}/commit/{{item.1.id}}">{{ item.1.epoch | friendly_time }}</a></td>
10200+ </tr>
10201+ {% endfor %}
10202+ </tbody>
10203+ </table>
10204+ </div>
10205+ </article>
10206+ <article class="readme">
10207+ <header> {{ rendered_file_name }} </header>
10208+ <div class="readme">{{ readme | safe }}</div>
10209+ </article>
10210+ </section>
10211+ {% if show_details %}
10212+ <section class="repo-right">
10213+ <article>
10214+ <div class="panel">
10215+ <section class="repo-section">
10216+ <div class="icon-header contrast">
10217+ <h3>Clone</h3>
10218+ </div>
10219+ <div class="clone">
10220+ <div><b>HTTP</b></div>
10221+ <div class="clone-url"><input type="text" value="{{http_clone_url}}" readonly></div>
10222+ </div>
10223+ {% if git_clone_url %}
10224+ <div class="clone">
10225+ <div style="margin-right: 1ch;"><b>SSH</b></div>
10226+ <div class="clone-url"><input type="text" value="{{git_clone_url}}" readonly></div>
10227+ </div>
10228+ {% endif %}
10229+ </section>
10230+ {% if chat_links or email_links %}
10231+ <section class="repo-section">
10232+ <div class="icon-header contrast"><h3>Discussion</h3>
10233+ </div>
10234+ {% if chat_links %}
10235+ <h6> Chat </h6>
10236+ {% for chat in chat_links %}
10237+ <em>{{chat.description}}, status: </em>
10238+ <b class={%- if chat.users_online -%}"positive"{%- else -%}"negative"{%- endif -%}
10239+ data-tooltip="{%- if chat.users_online -%} {{chat.users_online}} Users Online {%- else -%} Offline {%- endif -%}">
10240+ [{%- if chat.users_online -%}{{chat.users_online}}{%- else -%}?{%- endif -%}]
10241+ </b>
10242+ <div class="clone">
10243+ <div>
10244+ <b>{{chat.kind}}</b>
10245+ </div>
10246+ <div class="clone-url"><input type="text" value="{{chat.url}}" readonly></div>
10247+ </div>
10248+ {% endfor %}
10249+ {% endif %}
10250+ {% if email_links %}
10251+ <h6> Mailing Lists </h6>
10252+ {% for email in email_links %}
10253+ <em>{{email.description}}, 100+ threads</em>
10254+ <div class="clone">
10255+ <div>
10256+ <b>mail</b>
10257+ </div>
10258+ <div class="clone-url"><input type="text" value="{{email.url}}" readonly></div>
10259+ </div>
10260+ {% endfor %}
10261+ {% endif %}
10262+ </section>
10263+ {% endif %}
10264+ <section class="repo-section">
10265+ <h3> Subscribe<span class="icon">{{ "feed" | emoji | safe }}</span></h3>
10266+ </br>
10267+ [<a href="{{rss_link_all}}">*</a>,
10268+ <a href="{{rss_link_1d}}">1d</a>,
10269+ <a href="{{rss_link_1w}}">1w</a>,
10270+ <a href="{{rss_link_1m}}">1m</a>]
10271+ </section>
10272+ {% if sites_url %}
10273+ <section class="repo-section">
10274+ <div class="icon-header contrast">
10275+ <h3>Homepage</h3>
10276+ </div>
10277+ <a href={{sites_url}}>{{sites_url}}</a>
10278+ </section>
10279+ {% endif %}
10280+ <section class="repo-section">
10281+ <div class="icon-header contrast"><h3>License</h3>
10282+ </div>
10283+ <i><b>{{ license }}</b></i>
10284+ </section>
10285+ <section class="repo-section">
10286+ <div class="icon-header contrast"><h3><a href="/{{collection}}/{{name}}/authors">Authors</a></h3>
10287+ </div>
10288+ </a>
10289+ <ul class="author-list">
10290+ {% if authors %}
10291+ {% for author in authors %}
10292+ <li> {{ author.0 }}: <i> {{ author.2 }}% </i> </li>
10293+ {% endfor %}
10294+ {% endif %}
10295+ </ul>
10296+ </section>
10297+ <div class="chart">
10298+ <a href="/{{collection}}/{{name}}/chart/activity/{{latest_commit.id}}">
10299+ {{ activity_chart | safe }}
10300+ </a>
10301+ </div>
10302+ <div class="chart">
10303+ <a href="/{{collection}}/{{name}}/chart/languages/{{latest_commit.id}}">
10304+ {{ language_chart | safe }}
10305+ </a>
10306+ </div>
10307+ </div>
10308+ </article>
10309+ </section>
10310+ {% endif %}
10311+ {% if show_details %}
10312+ </div>
10313+ {% endif %}
10314+ {% endblock %}
10315 diff --git a/ayllu/themes/default/templates/rss_summary.html b/ayllu/themes/default/templates/rss_summary.html
10316new file mode 100644
10317index 0000000..ca85e9a
10318--- /dev/null
10319+++ b/ayllu/themes/default/templates/rss_summary.html
10320 @@ -0,0 +1,24 @@
10321+ <center>
10322+ <h1> Summary from {{start_date}} to {{end_date}} </h1>
10323+ <h2>{{n_tags}} tags and {{n_commits}} commits added {% if entries | length > 1 %} across {{n_projects}} projects{%- endif -%} </h2>
10324+ </center>
10325+
10326+ {% for entry in entries %}
10327+ <h2> Updates For {{entry.name}} </h2>
10328+
10329+ {% if entry.tags | length > 0 %}
10330+ </br><h3> <u>New Tags</u> </h3>
10331+ {% endif %}
10332+
10333+ {% if entry.commits | length > 0 %}
10334+ </br><h3> <u>New Commits</u> </h3>
10335+ {% for commit in entry.commits %}
10336+ <article>
10337+ <header>
10338+ <h4><a href="{{origin}}/{{entry.name}}/commit/{{commit.id}}">{{commit.summary}} - {{commit.author_name}}</a></h4>
10339+ </header>
10340+ <pre>{{commit.message}}</pre>
10341+ </article>
10342+ {% endfor %}
10343+ {% endif %}
10344+ {% endfor %}
10345 diff --git a/ayllu/themes/default/templates/tags.html b/ayllu/themes/default/templates/tags.html
10346new file mode 100644
10347index 0000000..2813ddd
10348--- /dev/null
10349+++ b/ayllu/themes/default/templates/tags.html
10350 @@ -0,0 +1,28 @@
10351+ {% import "macros.html" as macros %}
10352+ {% extends "base.html" %}
10353+ {% block content %}
10354+ <section>
10355+ <article>
10356+ <header>
10357+ {{ macros::navigation(items=refnav, title="Tags") }}
10358+ </header>
10359+ {% for tag in tags %}
10360+ <article class="inner">
10361+ <header class="wide">
10362+ <div><h3><a href="/{{collection}}/{{name}}/tree/{{tag.name | urlencode}}">{{ tag.name }}</a></h3></div>
10363+ <div><h5>{{ tag.commit.epoch | friendly_time }}<h5></div>
10364+ </header>
10365+ <pre>{{ tag.summary }}</pre>
10366+ <footer class="wide">
10367+ <div>{{ tag.author_name }}</div>
10368+ <div>
10369+ <a href="/{{collection}}/{{name}}/refs/archive/{{tag.name}}.tar.gz" role="button">{{tag.name}}.tar.gz</a>
10370+ <a href="/{{collection}}/{{name}}/commit/{{tag.commit.id}}"
10371+ role="button">{{tag.commit.id | truncate(length=8, end="")}}</a>
10372+ </div>
10373+ </footer>
10374+ </article>
10375+ {% endfor %}
10376+ </article>
10377+ </section>
10378+ {% endblock %}
10379 diff --git a/ayllu/themes/default/templates/thread.html b/ayllu/themes/default/templates/thread.html
10380new file mode 100644
10381index 0000000..9d75245
10382--- /dev/null
10383+++ b/ayllu/themes/default/templates/thread.html
10384 @@ -0,0 +1,23 @@
10385+ {% extends "base.html" %}
10386+ {% block content %}
10387+ <section class="thread-view">
10388+ <article>
10389+ <header>
10390+ <h1>{{ subject }}</h1></br>
10391+ <h4> Export Thread </h4>
10392+ <p><a href="/mail/export/{{list_id}}/{{thread_id}}">mbox</a></p>
10393+ </header>
10394+ {% for reply in messages %}
10395+ <article>
10396+ <header>
10397+ <b>From: {{ reply.from_address }}</b></br>
10398+ <b>Subject: {{ reply.subject }} </b></br>
10399+ <b><a href="/mail/message/{{list_id}}/{{reply.message_id}}">{{ reply.message_id }}</a></b>
10400+ <span class="right">{{ reply.created_at | format_epoch }}</span>
10401+ </header>
10402+ <pre>{{ reply.text | safe }}</pre>
10403+ </article>
10404+ {% endfor %}
10405+ </section>
10406+ </article>
10407+ {% endblock %}
10408 diff --git a/ayllu/themes/default/templates/threads.html b/ayllu/themes/default/templates/threads.html
10409new file mode 100644
10410index 0000000..1b2c733
10411--- /dev/null
10412+++ b/ayllu/themes/default/templates/threads.html
10413 @@ -0,0 +1,45 @@
10414+ {% import "macros.html" as macros %}
10415+ {% extends "base.html" %}
10416+ {% block content %}
10417+ <section>
10418+ <article>
10419+ <header>
10420+ <h1> {{ list.name }} </h1>
10421+ <span class="right labels">
10422+ {% for topic in list.topics %}
10423+ <span class="feature">{{topic}}</span>
10424+ {% endfor %}
10425+ </span>
10426+ </header>
10427+ <div class="mailing-list-details">
10428+ <h4> {{ list.description }} </h4>
10429+ </br>
10430+ <h4> Subscribe </h4>
10431+ <p> Send an e-mail to <a href="mailto:{{request_address}}?subject=subscribe">{{request_address}}</a> with the following subject: <code>subscribe</code> </p>
10432+ <h4> Unsubscribe </h4>
10433+ <p> Send an e-mail to <a href="mailto:{{ request_address}}?subject=unsubscribe">{{request_address}}</a> with the following subject: <code>unsubscribe</code> </p>
10434+ <h4> Export List </h4>
10435+ <p><a href="/mail/export/{{list.id}}">mbox</a></p>
10436+ </div>
10437+ <h4> Messages </h4>
10438+ <table>
10439+ <thead>
10440+ <th> from </th>
10441+ <th> date </th>
10442+ <th> subject </th>
10443+ <th> replies </th>
10444+ </thead>
10445+ <tbody>
10446+ {% for thread in threads %}
10447+ <tr>
10448+ <td>{{ thread.from }}</a></td>
10449+ <td>{{thread.timestamp | format_epoch }}</td>
10450+ <td><a href="/mail/thread/{{list.id}}/{{thread.message_id}}">{{thread.subject}}</a></td>
10451+ <td>{{thread.n_replies}}</td>
10452+ </tr>
10453+ {% endfor %}
10454+ </tbody>
10455+ </table>
10456+ </article>
10457+ </section>
10458+ {% endblock %}
10459 diff --git a/ayllu/themes/default/templates/user.html b/ayllu/themes/default/templates/user.html
10460new file mode 100644
10461index 0000000..fda2ec3
10462--- /dev/null
10463+++ b/ayllu/themes/default/templates/user.html
10464 @@ -0,0 +1,4 @@
10465+ {% extends "base.html" %}
10466+ {% block content %}
10467+ User Profile
10468+ {% endblock %}
10469 diff --git a/ayllu/themes/default/theme.scss b/ayllu/themes/default/theme.scss
10470new file mode 100644
10471index 0000000..3c4052d
10472--- /dev/null
10473+++ b/ayllu/themes/default/theme.scss
10474 @@ -0,0 +1,83 @@
10475+ // monochrome
10476+
10477+ // Navy-Grey
10478+ $grey-hue: 205;
10479+ $grey-50: hsl($grey-hue, 20%, 94%);
10480+ $grey-100: hsl($grey-hue, 18%, 86%);
10481+ $grey-200: hsl($grey-hue, 16%, 77%);
10482+ $grey-300: hsl($grey-hue, 14%, 68%);
10483+ $grey-400: hsl($grey-hue, 12%, 59%);
10484+ $grey-500: hsl($grey-hue, 10%, 50%);
10485+ $grey-600: hsl($grey-hue, 15%, 41%);
10486+ $grey-700: hsl($grey-hue, 20%, 32%);
10487+ $grey-800: hsl($grey-hue, 25%, 23%);
10488+ $grey-900: hsl($grey-hue, 30%, 15%);
10489+
10490+ // Light Blue
10491+ $primary-hue: $grey-hue;
10492+ $primary-50: hsl($grey-hue, 20%, 94%);
10493+ $primary-100: hsl($grey-hue, 18%, 86%);
10494+ $primary-200: hsl($grey-hue, 16%, 77%);
10495+ $primary-300: hsl($grey-hue, 14%, 68%);
10496+ $primary-400: hsl($grey-hue, 12%, 59%);
10497+ $primary-500: hsl($grey-hue, 10%, 50%);
10498+ $primary-600: hsl($grey-hue, 15%, 41%);
10499+ $primary-700: hsl($grey-hue, 20%, 32%);
10500+ $primary-800: hsl($grey-hue, 25%, 23%);
10501+ $primary-900: hsl($grey-hue, 30%, 15%);
10502+
10503+ // Black & White
10504+ $black: #000;
10505+ $white: #fff;
10506+
10507+ // Amber
10508+ $amber-50: hsl($grey-hue, 20%, 94%);
10509+ $amber-100: hsl($grey-hue, 18%, 86%);
10510+ $amber-200: hsl($grey-hue, 16%, 77%);
10511+ $amber-300: hsl($grey-hue, 14%, 68%);
10512+ $amber-400: hsl($grey-hue, 12%, 59%);
10513+ $amber-500: hsl($grey-hue, 10%, 50%);
10514+ $amber-600: hsl($grey-hue, 15%, 41%);
10515+ $amber-700: hsl($grey-hue, 20%, 32%);
10516+ $amber-800: hsl($grey-hue, 25%, 23%);
10517+ $amber-900: hsl($grey-hue, 30%, 15%);
10518+
10519+ // Green
10520+ $green-50: hsl($grey-hue, 20%, 94%);
10521+ $green-100: hsl($grey-hue, 18%, 86%);
10522+ $green-200: hsl($grey-hue, 16%, 77%);
10523+ $green-300: hsl($grey-hue, 14%, 68%);
10524+ $green-400: hsl($grey-hue, 12%, 59%);
10525+ $green-500: hsl($grey-hue, 10%, 50%);
10526+ $green-600: hsl($grey-hue, 15%, 41%);
10527+ $green-700: hsl($grey-hue, 20%, 32%);
10528+ $green-800: hsl($grey-hue, 25%, 23%);
10529+ $green-900: hsl($grey-hue, 30%, 15%);
10530+
10531+ // Red
10532+ $red-50: hsl($grey-hue, 20%, 94%);
10533+ $red-100: hsl($grey-hue, 18%, 86%);
10534+ $red-200: hsl($grey-hue, 16%, 77%);
10535+ $red-300: hsl($grey-hue, 14%, 68%);
10536+ $red-400: hsl($grey-hue, 12%, 59%);
10537+ $red-500: hsl($grey-hue, 10%, 50%);
10538+ $red-600: hsl($grey-hue, 15%, 41%);
10539+ $red-700: hsl($grey-hue, 20%, 32%);
10540+ $red-800: hsl($grey-hue, 25%, 23%);
10541+ $red-900: hsl($grey-hue, 30%, 15%);
10542+
10543+ $positive: $black;
10544+ $negative: $black;
10545+
10546+ $blame-border: $black;
10547+
10548+ $highlighted: $black;
10549+ $icon-background: $black;
10550+
10551+ $chart-color: $black;
10552+
10553+ // NOTE: colors need to be defined above since they're
10554+ // read as variables in pico.
10555+ @import "@picocss/pico/scss/pico";
10556+ @import "layout.scss";
10557+ @import "base.scss";
10558 diff --git a/ayllu/themes/tokyonight/main.min.css b/ayllu/themes/tokyonight/main.min.css
10559new file mode 100644
10560index 0000000..802790c
10561--- /dev/null
10562+++ b/ayllu/themes/tokyonight/main.min.css
10563 @@ -0,0 +1,32 @@
10564+ span.ts_attribute{color:#4fd6be}span.ts_constant{color:#ff966c}span.ts_function.builtin{color:#82aaff}span.ts_function{color:#c099ff}span.ts_keyword{color:#fca7ea}span.ts_operator{color:#89ddff}span.ts_property{color:#4fd6be}span.ts_punctuation{color:#82aaff}span.ts_punctuation.bracket{color:#828bb8}span.ts_punctuation.delimiter{color:#89ddff}span.ts_string{color:#c3e88d}span.ts_string.special{color:#82aaff}span.ts_tag{color:#82aaff}span.ts_type{color:#65bcff}span.ts_type.builtin{color:#65bcff}span.ts_variable{color:#c8d3f5}span.ts_variable.builtin{color:#ff757f}span.ts_variable.parameter{color:#ffc777}@media (prefers-color-scheme: light){span.ts_attribute{color:#11483e}span.ts_constant{color:#9f2d00}span.ts_function.builtin{color:#003ab5}span.ts_function{color:#8133ff}span.ts_keyword{color:#d007a5}span.ts_operator{color:#0086bc}span.ts_property{color:#11483e}span.ts_punctuation{color:#003ab5}span.ts_punctuation.bracket{color:#c8d3f5}span.ts_punctuation.delimiter{color:#0086bc}span.ts_string{color:#5f8d1c}span.ts_string.special{color:#003ab5}span.ts_tag{color:#003ab5}span.ts_type{color:#005698}span.ts_type.builtin{color:#005698}span.ts_variable{color:#254ecc}span.ts_variable.builtin{color:#a8000c}span.ts_variable.parameter{color:#aa6400}}@media (prefers-color-scheme: dark){span.label{color:#828bb8}}/*!
10565+ * Pico CSS v1.5.10 (https://picocss.com)
10566+ * Copyright 2019-2023 - Licensed under MIT
10567+ */:root{--font-family: system-ui, -apple-system, "Segoe UI", "Roboto", "Ubuntu",
10568+ "Cantarell", "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji",
10569+ "Segoe UI Symbol", "Noto Color Emoji";--line-height: 1.5;--font-weight: 400;--font-size: 16px;--border-radius: 0.25rem;--border-width: 1px;--outline-width: 3px;--spacing: 1rem;--typography-spacing-vertical: 1.5rem;--block-spacing-vertical: calc(var(--spacing) * 2);--block-spacing-horizontal: var(--spacing);--grid-spacing-vertical: 0;--grid-spacing-horizontal: var(--spacing);--form-element-spacing-vertical: 0.75rem;--form-element-spacing-horizontal: 1rem;--nav-element-spacing-vertical: 1rem;--nav-element-spacing-horizontal: 0.5rem;--nav-link-spacing-vertical: 0.5rem;--nav-link-spacing-horizontal: 0.5rem;--form-label-font-weight: var(--font-weight);--transition: 0.2s ease-in-out;--modal-overlay-backdrop-filter: blur(0.25rem)}@media (min-width: 576px){:root{--font-size: 17px}}@media (min-width: 768px){:root{--font-size: 18px}}@media (min-width: 992px){:root{--font-size: 19px}}@media (min-width: 1200px){:root{--font-size: 20px}}@media (min-width: 576px){body>header,body>main,body>footer,section{--block-spacing-vertical: calc(var(--spacing) * 2.5)}}@media (min-width: 768px){body>header,body>main,body>footer,section{--block-spacing-vertical: calc(var(--spacing) * 3)}}@media (min-width: 992px){body>header,body>main,body>footer,section{--block-spacing-vertical: calc(var(--spacing) * 3.5)}}@media (min-width: 1200px){body>header,body>main,body>footer,section{--block-spacing-vertical: calc(var(--spacing) * 4)}}@media (min-width: 576px){article{--block-spacing-horizontal: calc(var(--spacing) * 1.25)}}@media (min-width: 768px){article{--block-spacing-horizontal: calc(var(--spacing) * 1.5)}}@media (min-width: 992px){article{--block-spacing-horizontal: calc(var(--spacing) * 1.75)}}@media (min-width: 1200px){article{--block-spacing-horizontal: calc(var(--spacing) * 2)}}dialog>article{--block-spacing-vertical: calc(var(--spacing) * 2);--block-spacing-horizontal: var(--spacing)}@media (min-width: 576px){dialog>article{--block-spacing-vertical: calc(var(--spacing) * 2.5);--block-spacing-horizontal: calc(var(--spacing) * 1.25)}}@media (min-width: 768px){dialog>article{--block-spacing-vertical: calc(var(--spacing) * 3);--block-spacing-horizontal: calc(var(--spacing) * 1.5)}}a{--text-decoration: none}a.secondary,a.contrast{--text-decoration: underline}small{--font-size: 0.875em}h1,h2,h3,h4,h5,h6{--font-weight: 700}h1{--font-size: 2rem;--typography-spacing-vertical: 3rem}h2{--font-size: 1.75rem;--typography-spacing-vertical: 2.625rem}h3{--font-size: 1.5rem;--typography-spacing-vertical: 2.25rem}h4{--font-size: 1.25rem;--typography-spacing-vertical: 1.874rem}h5{--font-size: 1.125rem;--typography-spacing-vertical: 1.6875rem}[type="checkbox"],[type="radio"]{--border-width: 2px}[type="checkbox"][role="switch"]{--border-width: 3px}thead th,thead td,tfoot th,tfoot td{--border-width: 3px}:not(thead,tfoot)>*>td{--font-size: 0.875em}pre,code,kbd,samp{--font-family: "Menlo", "Consolas", "Roboto Mono", "Ubuntu Monospace",
10570+ "Noto Mono", "Oxygen Mono", "Liberation Mono", monospace,
10571+ "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"}kbd{--font-weight: bolder}[data-theme="light"],:root:not([data-theme="dark"]){--background-color: #fff;--color: #415462;--h1-color: #1b2832;--h2-color: #23333e;--h3-color: #2c3d49;--h4-color: #374956;--h5-color: #415462;--h6-color: #4d606d;--muted-color: #73828c;--muted-border-color: #edf0f3;--primary: #1095c1;--primary-hover: #08769b;--primary-focus: rgba(16,149,193,0.125);--primary-inverse: #fff;--secondary: #596b78;--secondary-hover: #415462;--secondary-focus: rgba(89,107,120,0.125);--secondary-inverse: #fff;--contrast: #1b2832;--contrast-hover: #000;--contrast-focus: rgba(89,107,120,0.125);--contrast-inverse: #fff;--mark-background-color: #fff2ca;--mark-color: #543a25;--ins-color: #388e3c;--del-color: #c62828;--blockquote-border-color: var(--muted-border-color);--blockquote-footer-color: var(--muted-color);--button-box-shadow: 0 0 0 rgba(0, 0, 0, 0);--button-hover-box-shadow: 0 0 0 rgba(0, 0, 0, 0);--form-element-background-color: transparent;--form-element-border-color: #a2afb9;--form-element-color: var(--color);--form-element-placeholder-color: var(--muted-color);--form-element-active-background-color: transparent;--form-element-active-border-color: var(--primary);--form-element-focus-color: var(--primary-focus);--form-element-disabled-background-color: #d5dce2;--form-element-disabled-border-color: #a2afb9;--form-element-disabled-opacity: 0.5;--form-element-invalid-border-color: #c62828;--form-element-invalid-active-border-color: #d32f2f;--form-element-invalid-focus-color: rgba(211,47,47,0.125);--form-element-valid-border-color: #388e3c;--form-element-valid-active-border-color: #43a047;--form-element-valid-focus-color: rgba(67,160,71,0.125);--switch-background-color: #bbc6ce;--switch-color: var(--primary-inverse);--switch-checked-background-color: var(--primary);--range-border-color: #d5dce2;--range-active-border-color: #bbc6ce;--range-thumb-border-color: var(--background-color);--range-thumb-color: var(--secondary);--range-thumb-hover-color: var(--secondary-hover);--range-thumb-active-color: var(--primary);--table-border-color: var(--muted-border-color);--table-row-stripped-background-color: #f6f8f9;--code-background-color: #edf0f3;--code-color: var(--muted-color);--code-kbd-background-color: var(--contrast);--code-kbd-color: var(--contrast-inverse);--code-tag-color: #b34d80;--code-property-color: #3d888f;--code-value-color: #986;--code-comment-color: #a2afb9;--accordion-border-color: var(--muted-border-color);--accordion-close-summary-color: var(--color);--accordion-open-summary-color: var(--muted-color);--card-background-color: var(--background-color);--card-border-color: var(--muted-border-color);--card-box-shadow:
10572+ .0145rem .029rem .174rem rgba(27,40,50,0.01698),
10573+ .0335rem .067rem .402rem rgba(27,40,50,0.024),
10574+ .0625rem .125rem .75rem rgba(27,40,50,0.03),
10575+ .1125rem .225rem 1.35rem rgba(27,40,50,0.036),
10576+ .2085rem .417rem 2.502rem rgba(27,40,50,0.04302),
10577+ .5rem 1rem 6rem rgba(27,40,50,0.06),
10578+ 0 0 0 0.0625rem rgba(27,40,50,0.015);--card-sectionning-background-color: #fafbfc;--dropdown-background-color: #fafbfc;--dropdown-border-color: #e1e6ea;--dropdown-box-shadow: var(--card-box-shadow);--dropdown-color: var(--color);--dropdown-hover-background-color: #edf0f3;--modal-overlay-background-color: rgba(213,220,226,0.7);--progress-background-color: #d5dce2;--progress-color: var(--primary);--loading-spinner-opacity: 0.5;--tooltip-background-color: var(--contrast);--tooltip-color: var(--contrast-inverse);--icon-checkbox: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(255, 255, 255)' stroke-width='4' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='20 6 9 17 4 12'%3E%3C/polyline%3E%3C/svg%3E");--icon-chevron: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(65.28, 84.32, 97.92)' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'%3E%3C/polyline%3E%3C/svg%3E");--icon-chevron-button: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(255, 255, 255)' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'%3E%3C/polyline%3E%3C/svg%3E");--icon-chevron-button-inverse: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(255, 255, 255)' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'%3E%3C/polyline%3E%3C/svg%3E");--icon-close: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(114.75, 129.625, 140.25)' stroke-width='4' stroke-linecap='round' stroke-linejoin='round'%3E%3Cline x1='18' y1='6' x2='6' y2='18'%3E%3C/line%3E%3Cline x1='6' y1='6' x2='18' y2='18'%3E%3C/line%3E%3C/svg%3E");--icon-date: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(65.28, 84.32, 97.92)' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Crect x='3' y='4' width='18' height='18' rx='2' ry='2'%3E%3C/rect%3E%3Cline x1='16' y1='2' x2='16' y2='6'%3E%3C/line%3E%3Cline x1='8' y1='2' x2='8' y2='6'%3E%3C/line%3E%3Cline x1='3' y1='10' x2='21' y2='10'%3E%3C/line%3E%3C/svg%3E");--icon-invalid: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(198, 40, 40)' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='12' cy='12' r='10'%3E%3C/circle%3E%3Cline x1='12' y1='8' x2='12' y2='12'%3E%3C/line%3E%3Cline x1='12' y1='16' x2='12.01' y2='16'%3E%3C/line%3E%3C/svg%3E");--icon-minus: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(255, 255, 255)' stroke-width='4' stroke-linecap='round' stroke-linejoin='round'%3E%3Cline x1='5' y1='12' x2='19' y2='12'%3E%3C/line%3E%3C/svg%3E");--icon-search: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(65.28, 84.32, 97.92)' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='11' cy='11' r='8'%3E%3C/circle%3E%3Cline x1='21' y1='21' x2='16.65' y2='16.65'%3E%3C/line%3E%3C/svg%3E");--icon-time: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(65.28, 84.32, 97.92)' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='12' cy='12' r='10'%3E%3C/circle%3E%3Cpolyline points='12 6 12 12 16 14'%3E%3C/polyline%3E%3C/svg%3E");--icon-valid: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(56, 142, 60)' stroke-width='3' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='20 6 9 17 4 12'%3E%3C/polyline%3E%3C/svg%3E");color-scheme:light}@media only screen and (prefers-color-scheme: dark){:root:not([data-theme]){--background-color: #11191f;--color: #bbc6ce;--h1-color: #edf0f3;--h2-color: #e1e6ea;--h3-color: #d5dce2;--h4-color: #c8d1d8;--h5-color: #bbc6ce;--h6-color: #aebbc3;--muted-color: #73828c;--muted-border-color: #1f2d38;--primary: #1095c1;--primary-hover: #1ab3e6;--primary-focus: rgba(16,149,193,0.25);--primary-inverse: #fff;--secondary: #596b78;--secondary-hover: #73828c;--secondary-focus: rgba(115,130,140,0.25);--secondary-inverse: #fff;--contrast: #edf0f3;--contrast-hover: #fff;--contrast-focus: rgba(115,130,140,0.25);--contrast-inverse: #000;--mark-background-color: #d0c284;--mark-color: #11191f;--ins-color: #388e3c;--del-color: #c62828;--blockquote-border-color: var(--muted-border-color);--blockquote-footer-color: var(--muted-color);--button-box-shadow: 0 0 0 rgba(0, 0, 0, 0);--button-hover-box-shadow: 0 0 0 rgba(0, 0, 0, 0);--form-element-background-color: #11191f;--form-element-border-color: #374956;--form-element-color: var(--color);--form-element-placeholder-color: var(--muted-color);--form-element-active-background-color: var(--form-element-background-color);--form-element-active-border-color: var(--primary);--form-element-focus-color: var(--primary-focus);--form-element-disabled-background-color: #2c3d49;--form-element-disabled-border-color: #415462;--form-element-disabled-opacity: 0.5;--form-element-invalid-border-color: #b71c1c;--form-element-invalid-active-border-color: #c62828;--form-element-invalid-focus-color: rgba(198,40,40,0.25);--form-element-valid-border-color: #2e7d32;--form-element-valid-active-border-color: #388e3c;--form-element-valid-focus-color: rgba(56,142,60,0.25);--switch-background-color: #374956;--switch-color: var(--primary-inverse);--switch-checked-background-color: var(--primary);--range-border-color: #23333e;--range-active-border-color: #2c3d49;--range-thumb-border-color: var(--background-color);--range-thumb-color: var(--secondary);--range-thumb-hover-color: var(--secondary-hover);--range-thumb-active-color: var(--primary);--table-border-color: var(--muted-border-color);--table-row-stripped-background-color: rgba(115,130,140,0.05);--code-background-color: #17232c;--code-color: var(--muted-color);--code-kbd-background-color: var(--contrast);--code-kbd-color: var(--contrast-inverse);--code-tag-color: #a65980;--code-property-color: #599fa6;--code-value-color: #8c8473;--code-comment-color: #4d606d;--accordion-border-color: var(--muted-border-color);--accordion-active-summary-color: var(--primary);--accordion-close-summary-color: var(--color);--accordion-open-summary-color: var(--muted-color);--card-background-color: #141e25;--card-border-color: var(--card-background-color);--card-box-shadow:
10579+ .0145rem .029rem .174rem rgba(0,0,0,0.01698),
10580+ .0335rem .067rem .402rem rgba(0,0,0,0.024),
10581+ .0625rem .125rem .75rem rgba(0,0,0,0.03),
10582+ .1125rem .225rem 1.35rem rgba(0,0,0,0.036),
10583+ .2085rem .417rem 2.502rem rgba(0,0,0,0.04302),
10584+ .5rem 1rem 6rem rgba(0,0,0,0.06),
10585+ 0 0 0 0.0625rem rgba(0,0,0,0.015);--card-sectionning-background-color: #17232c;--dropdown-background-color: #1b2832;--dropdown-border-color: #23333e;--dropdown-box-shadow: var(--card-box-shadow);--dropdown-color: var(--color);--dropdown-hover-background-color: rgba(35,51,62,0.75);--modal-overlay-background-color: rgba(35,51,62,0.8);--progress-background-color: #23333e;--progress-color: var(--primary);--loading-spinner-opacity: 0.5;--tooltip-background-color: var(--contrast);--tooltip-color: var(--contrast-inverse);--icon-checkbox: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(255, 255, 255)' stroke-width='4' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='20 6 9 17 4 12'%3E%3C/polyline%3E%3C/svg%3E");--icon-chevron: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(161.976, 175.304, 184.824)' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'%3E%3C/polyline%3E%3C/svg%3E");--icon-chevron-button: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(255, 255, 255)' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'%3E%3C/polyline%3E%3C/svg%3E");--icon-chevron-button-inverse: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(0, 0, 0)' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'%3E%3C/polyline%3E%3C/svg%3E");--icon-close: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(114.75, 129.625, 140.25)' stroke-width='4' stroke-linecap='round' stroke-linejoin='round'%3E%3Cline x1='18' y1='6' x2='6' y2='18'%3E%3C/line%3E%3Cline x1='6' y1='6' x2='18' y2='18'%3E%3C/line%3E%3C/svg%3E");--icon-date: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(161.976, 175.304, 184.824)' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Crect x='3' y='4' width='18' height='18' rx='2' ry='2'%3E%3C/rect%3E%3Cline x1='16' y1='2' x2='16' y2='6'%3E%3C/line%3E%3Cline x1='8' y1='2' x2='8' y2='6'%3E%3C/line%3E%3Cline x1='3' y1='10' x2='21' y2='10'%3E%3C/line%3E%3C/svg%3E");--icon-invalid: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(183, 28, 28)' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='12' cy='12' r='10'%3E%3C/circle%3E%3Cline x1='12' y1='8' x2='12' y2='12'%3E%3C/line%3E%3Cline x1='12' y1='16' x2='12.01' y2='16'%3E%3C/line%3E%3C/svg%3E");--icon-minus: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(255, 255, 255)' stroke-width='4' stroke-linecap='round' stroke-linejoin='round'%3E%3Cline x1='5' y1='12' x2='19' y2='12'%3E%3C/line%3E%3C/svg%3E");--icon-search: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(161.976, 175.304, 184.824)' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='11' cy='11' r='8'%3E%3C/circle%3E%3Cline x1='21' y1='21' x2='16.65' y2='16.65'%3E%3C/line%3E%3C/svg%3E");--icon-time: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(161.976, 175.304, 184.824)' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='12' cy='12' r='10'%3E%3C/circle%3E%3Cpolyline points='12 6 12 12 16 14'%3E%3C/polyline%3E%3C/svg%3E");--icon-valid: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(46, 125, 50)' stroke-width='3' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='20 6 9 17 4 12'%3E%3C/polyline%3E%3C/svg%3E");color-scheme:dark}}[data-theme="dark"]{--background-color: #11191f;--color: #bbc6ce;--h1-color: #edf0f3;--h2-color: #e1e6ea;--h3-color: #d5dce2;--h4-color: #c8d1d8;--h5-color: #bbc6ce;--h6-color: #aebbc3;--muted-color: #73828c;--muted-border-color: #1f2d38;--primary: #1095c1;--primary-hover: #1ab3e6;--primary-focus: rgba(16,149,193,0.25);--primary-inverse: #fff;--secondary: #596b78;--secondary-hover: #73828c;--secondary-focus: rgba(115,130,140,0.25);--secondary-inverse: #fff;--contrast: #edf0f3;--contrast-hover: #fff;--contrast-focus: rgba(115,130,140,0.25);--contrast-inverse: #000;--mark-background-color: #d0c284;--mark-color: #11191f;--ins-color: #388e3c;--del-color: #c62828;--blockquote-border-color: var(--muted-border-color);--blockquote-footer-color: var(--muted-color);--button-box-shadow: 0 0 0 rgba(0, 0, 0, 0);--button-hover-box-shadow: 0 0 0 rgba(0, 0, 0, 0);--form-element-background-color: #11191f;--form-element-border-color: #374956;--form-element-color: var(--color);--form-element-placeholder-color: var(--muted-color);--form-element-active-background-color: var(--form-element-background-color);--form-element-active-border-color: var(--primary);--form-element-focus-color: var(--primary-focus);--form-element-disabled-background-color: #2c3d49;--form-element-disabled-border-color: #415462;--form-element-disabled-opacity: 0.5;--form-element-invalid-border-color: #b71c1c;--form-element-invalid-active-border-color: #c62828;--form-element-invalid-focus-color: rgba(198,40,40,0.25);--form-element-valid-border-color: #2e7d32;--form-element-valid-active-border-color: #388e3c;--form-element-valid-focus-color: rgba(56,142,60,0.25);--switch-background-color: #374956;--switch-color: var(--primary-inverse);--switch-checked-background-color: var(--primary);--range-border-color: #23333e;--range-active-border-color: #2c3d49;--range-thumb-border-color: var(--background-color);--range-thumb-color: var(--secondary);--range-thumb-hover-color: var(--secondary-hover);--range-thumb-active-color: var(--primary);--table-border-color: var(--muted-border-color);--table-row-stripped-background-color: rgba(115,130,140,0.05);--code-background-color: #17232c;--code-color: var(--muted-color);--code-kbd-background-color: var(--contrast);--code-kbd-color: var(--contrast-inverse);--code-tag-color: #a65980;--code-property-color: #599fa6;--code-value-color: #8c8473;--code-comment-color: #4d606d;--accordion-border-color: var(--muted-border-color);--accordion-active-summary-color: var(--primary);--accordion-close-summary-color: var(--color);--accordion-open-summary-color: var(--muted-color);--card-background-color: #141e25;--card-border-color: var(--card-background-color);--card-box-shadow:
10586+ .0145rem .029rem .174rem rgba(0,0,0,0.01698),
10587+ .0335rem .067rem .402rem rgba(0,0,0,0.024),
10588+ .0625rem .125rem .75rem rgba(0,0,0,0.03),
10589+ .1125rem .225rem 1.35rem rgba(0,0,0,0.036),
10590+ .2085rem .417rem 2.502rem rgba(0,0,0,0.04302),
10591+ .5rem 1rem 6rem rgba(0,0,0,0.06),
10592+ 0 0 0 0.0625rem rgba(0,0,0,0.015);--card-sectionning-background-color: #17232c;--dropdown-background-color: #1b2832;--dropdown-border-color: #23333e;--dropdown-box-shadow: var(--card-box-shadow);--dropdown-color: var(--color);--dropdown-hover-background-color: rgba(35,51,62,0.75);--modal-overlay-background-color: rgba(35,51,62,0.8);--progress-background-color: #23333e;--progress-color: var(--primary);--loading-spinner-opacity: 0.5;--tooltip-background-color: var(--contrast);--tooltip-color: var(--contrast-inverse);--icon-checkbox: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(255, 255, 255)' stroke-width='4' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='20 6 9 17 4 12'%3E%3C/polyline%3E%3C/svg%3E");--icon-chevron: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(161.976, 175.304, 184.824)' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'%3E%3C/polyline%3E%3C/svg%3E");--icon-chevron-button: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(255, 255, 255)' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'%3E%3C/polyline%3E%3C/svg%3E");--icon-chevron-button-inverse: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(0, 0, 0)' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'%3E%3C/polyline%3E%3C/svg%3E");--icon-close: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(114.75, 129.625, 140.25)' stroke-width='4' stroke-linecap='round' stroke-linejoin='round'%3E%3Cline x1='18' y1='6' x2='6' y2='18'%3E%3C/line%3E%3Cline x1='6' y1='6' x2='18' y2='18'%3E%3C/line%3E%3C/svg%3E");--icon-date: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(161.976, 175.304, 184.824)' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Crect x='3' y='4' width='18' height='18' rx='2' ry='2'%3E%3C/rect%3E%3Cline x1='16' y1='2' x2='16' y2='6'%3E%3C/line%3E%3Cline x1='8' y1='2' x2='8' y2='6'%3E%3C/line%3E%3Cline x1='3' y1='10' x2='21' y2='10'%3E%3C/line%3E%3C/svg%3E");--icon-invalid: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(183, 28, 28)' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='12' cy='12' r='10'%3E%3C/circle%3E%3Cline x1='12' y1='8' x2='12' y2='12'%3E%3C/line%3E%3Cline x1='12' y1='16' x2='12.01' y2='16'%3E%3C/line%3E%3C/svg%3E");--icon-minus: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(255, 255, 255)' stroke-width='4' stroke-linecap='round' stroke-linejoin='round'%3E%3Cline x1='5' y1='12' x2='19' y2='12'%3E%3C/line%3E%3C/svg%3E");--icon-search: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(161.976, 175.304, 184.824)' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='11' cy='11' r='8'%3E%3C/circle%3E%3Cline x1='21' y1='21' x2='16.65' y2='16.65'%3E%3C/line%3E%3C/svg%3E");--icon-time: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(161.976, 175.304, 184.824)' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='12' cy='12' r='10'%3E%3C/circle%3E%3Cpolyline points='12 6 12 12 16 14'%3E%3C/polyline%3E%3C/svg%3E");--icon-valid: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(46, 125, 50)' stroke-width='3' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='20 6 9 17 4 12'%3E%3C/polyline%3E%3C/svg%3E");color-scheme:dark}progress,[type="checkbox"],[type="radio"],[type="range"]{accent-color:var(--primary)}*,*::before,*::after{box-sizing:border-box;background-repeat:no-repeat}::before,::after{text-decoration:inherit;vertical-align:inherit}:where(:root){-webkit-tap-highlight-color:transparent;-webkit-text-size-adjust:100%;text-size-adjust:100%;background-color:var(--background-color);color:var(--color);font-weight:var(--font-weight);font-size:var(--font-size);line-height:var(--line-height);font-family:var(--font-family);text-rendering:optimizeLegibility;overflow-wrap:break-word;cursor:default;tab-size:4}main{display:block}body{width:100%;margin:0}body>header,body>main,body>footer{width:100%;margin-right:auto;margin-left:auto;padding:var(--block-spacing-vertical) 0}.container,.container-fluid{width:100%;margin-right:auto;margin-left:auto;padding-right:var(--spacing);padding-left:var(--spacing)}@media (min-width: 576px){.container{max-width:510px;padding-right:0;padding-left:0}}@media (min-width: 768px){.container{max-width:700px}}@media (min-width: 992px){.container{max-width:920px}}@media (min-width: 1200px){.container{max-width:1130px}}section{margin-bottom:var(--block-spacing-vertical)}.grid{grid-column-gap:var(--grid-spacing-horizontal);grid-row-gap:var(--grid-spacing-vertical);display:grid;grid-template-columns:1fr;margin:0}@media (min-width: 992px){.grid{grid-template-columns:repeat(auto-fit, minmax(0%, 1fr))}}.grid>*{min-width:0}figure{display:block;margin:0;padding:0;overflow-x:auto}figure figcaption{padding:calc(var(--spacing) * 0.5) 0;color:var(--muted-color)}b,strong{font-weight:bolder}sub,sup{position:relative;font-size:0.75em;line-height:0;vertical-align:baseline}sub{bottom:-0.25em}sup{top:-0.5em}address,blockquote,dl,figure,form,ol,p,pre,table,ul{margin-top:0;margin-bottom:var(--typography-spacing-vertical);color:var(--color);font-style:normal;font-weight:var(--font-weight);font-size:var(--font-size)}a,[role="link"]{--color: var(--primary);--background-color: transparent;outline:none;background-color:var(--background-color);color:var(--color);text-decoration:var(--text-decoration);transition:background-color var(--transition),color var(--transition),text-decoration var(--transition),box-shadow var(--transition)}a:is([aria-current], :hover, :active, :focus),[role="link"]:is([aria-current], :hover, :active, :focus){--color: var(--primary-hover);--text-decoration: underline}a:focus,[role="link"]:focus{--background-color: var(--primary-focus)}a.secondary,[role="link"].secondary{--color: var(--secondary)}a.secondary:is([aria-current], :hover, :active, :focus),[role="link"].secondary:is([aria-current], :hover, :active, :focus){--color: var(--secondary-hover)}a.secondary:focus,[role="link"].secondary:focus{--background-color: var(--secondary-focus)}a.contrast,[role="link"].contrast{--color: var(--contrast)}a.contrast:is([aria-current], :hover, :active, :focus),[role="link"].contrast:is([aria-current], :hover, :active, :focus){--color: var(--contrast-hover)}a.contrast:focus,[role="link"].contrast:focus{--background-color: var(--contrast-focus)}h1,h2,h3,h4,h5,h6{margin-top:0;margin-bottom:var(--typography-spacing-vertical);color:var(--color);font-weight:var(--font-weight);font-size:var(--font-size);font-family:var(--font-family)}h1{--color: var(--h1-color)}h2{--color: var(--h2-color)}h3{--color: var(--h3-color)}h4{--color: var(--h4-color)}h5{--color: var(--h5-color)}h6{--color: var(--h6-color)}:where(address, blockquote, dl, figure, form, ol, p, pre, table, ul)~:is(h1, h2, h3, h4, h5, h6){margin-top:var(--typography-spacing-vertical)}hgroup,.headings{margin-bottom:var(--typography-spacing-vertical)}hgroup>*,.headings>*{margin-bottom:0}hgroup>*:last-child,.headings>*:last-child{--color: var(--muted-color);--font-weight: unset;font-size:1rem;font-family:unset}p{margin-bottom:var(--typography-spacing-vertical)}small{font-size:var(--font-size)}:where(dl, ol, ul){padding-right:0;padding-left:var(--spacing);padding-inline-start:var(--spacing);padding-inline-end:0}:where(dl, ol, ul) li{margin-bottom:calc(var(--typography-spacing-vertical) * 0.25)}:where(dl, ol, ul) :is(dl, ol, ul){margin:0;margin-top:calc(var(--typography-spacing-vertical) * 0.25)}ul li{list-style:square}mark{padding:0.125rem 0.25rem;background-color:var(--mark-background-color);color:var(--mark-color);vertical-align:baseline}blockquote{display:block;margin:var(--typography-spacing-vertical) 0;padding:var(--spacing);border-right:none;border-left:0.25rem solid var(--blockquote-border-color);border-inline-start:0.25rem solid var(--blockquote-border-color);border-inline-end:none}blockquote footer{margin-top:calc(var(--typography-spacing-vertical) * 0.5);color:var(--blockquote-footer-color)}abbr[title]{border-bottom:1px dotted;text-decoration:none;cursor:help}ins{color:var(--ins-color);text-decoration:none}del{color:var(--del-color)}::selection{background-color:var(--primary-focus)}:where(audio, canvas, iframe, img, svg, video){vertical-align:middle}audio,video{display:inline-block}audio:not([controls]){display:none;height:0}:where(iframe){border-style:none}img{max-width:100%;height:auto;border-style:none}:where(svg:not([fill])){fill:currentColor}svg:not(:root){overflow:hidden}button{margin:0;overflow:visible;font-family:inherit;text-transform:none}button,[type="button"],[type="reset"],[type="submit"]{-webkit-appearance:button}button{display:block;width:100%;margin-bottom:var(--spacing)}[role="button"]{display:inline-block;text-decoration:none}button,input[type="submit"],input[type="button"],input[type="reset"],[role="button"]{--background-color: var(--primary);--border-color: var(--primary);--color: var(--primary-inverse);--box-shadow: var(--button-box-shadow, 0 0 0 rgba(0, 0, 0, 0));padding:var(--form-element-spacing-vertical) var(--form-element-spacing-horizontal);border:var(--border-width) solid var(--border-color);border-radius:var(--border-radius);outline:none;background-color:var(--background-color);box-shadow:var(--box-shadow);color:var(--color);font-weight:var(--font-weight);font-size:1rem;line-height:var(--line-height);text-align:center;cursor:pointer;transition:background-color var(--transition),border-color var(--transition),color var(--transition),box-shadow var(--transition)}button:is([aria-current], :hover, :active, :focus),input[type="submit"]:is([aria-current], :hover, :active, :focus),input[type="button"]:is([aria-current], :hover, :active, :focus),input[type="reset"]:is([aria-current], :hover, :active, :focus),[role="button"]:is([aria-current], :hover, :active, :focus){--background-color: var(--primary-hover);--border-color: var(--primary-hover);--box-shadow: var(--button-hover-box-shadow, 0 0 0 rgba(0, 0, 0, 0));--color: var(--primary-inverse)}button:focus,input[type="submit"]:focus,input[type="button"]:focus,input[type="reset"]:focus,[role="button"]:focus{--box-shadow: var(--button-hover-box-shadow, 0 0 0 rgba(0, 0, 0, 0)),
10593+ 0 0 0 var(--outline-width) var(--primary-focus)}:is(button, input[type="submit"], input[type="button"], [role="button"]).secondary,input[type="reset"]{--background-color: var(--secondary);--border-color: var(--secondary);--color: var(--secondary-inverse);cursor:pointer}:is(button, input[type="submit"], input[type="button"], [role="button"]).secondary:is([aria-current], :hover, :active, :focus),input[type="reset"]:is([aria-current], :hover, :active, :focus){--background-color: var(--secondary-hover);--border-color: var(--secondary-hover);--color: var(--secondary-inverse)}:is(button, input[type="submit"], input[type="button"], [role="button"]).secondary:focus,input[type="reset"]:focus{--box-shadow: var(--button-hover-box-shadow, 0 0 0 rgba(0, 0, 0, 0)),
10594+ 0 0 0 var(--outline-width) var(--secondary-focus)}:is(button, input[type="submit"], input[type="button"], [role="button"]).contrast{--background-color: var(--contrast);--border-color: var(--contrast);--color: var(--contrast-inverse)}:is(button, input[type="submit"], input[type="button"], [role="button"]).contrast:is([aria-current], :hover, :active, :focus){--background-color: var(--contrast-hover);--border-color: var(--contrast-hover);--color: var(--contrast-inverse)}:is(button, input[type="submit"], input[type="button"], [role="button"]).contrast:focus{--box-shadow: var(--button-hover-box-shadow, 0 0 0 rgba(0, 0, 0, 0)),
10595+ 0 0 0 var(--outline-width) var(--contrast-focus)}:is(button, input[type="submit"], input[type="button"], [role="button"]).outline,input[type="reset"].outline{--background-color: transparent;--color: var(--primary)}:is(button, input[type="submit"], input[type="button"], [role="button"]).outline:is([aria-current], :hover, :active, :focus),input[type="reset"].outline:is([aria-current], :hover, :active, :focus){--background-color: transparent;--color: var(--primary-hover)}:is(button, input[type="submit"], input[type="button"], [role="button"]).outline.secondary,input[type="reset"].outline{--color: var(--secondary)}:is(button, input[type="submit"], input[type="button"], [role="button"]).outline.secondary:is([aria-current], :hover, :active, :focus),input[type="reset"].outline:is([aria-current], :hover, :active, :focus){--color: var(--secondary-hover)}:is(button, input[type="submit"], input[type="button"], [role="button"]).outline.contrast{--color: var(--contrast)}:is(button, input[type="submit"], input[type="button"], [role="button"]).outline.contrast:is([aria-current], :hover, :active, :focus){--color: var(--contrast-hover)}:where(button, [type="submit"], [type="button"], [type="reset"], [role="button"])[disabled],:where(fieldset[disabled]) :is(button, [type="submit"], [type="button"], [type="reset"], [role="button"]),a[role="button"]:not([href]){opacity:0.5;pointer-events:none}input,optgroup,select,textarea{margin:0;font-size:1rem;line-height:var(--line-height);font-family:inherit;letter-spacing:inherit}input{overflow:visible}select{text-transform:none}legend{max-width:100%;padding:0;color:inherit;white-space:normal}textarea{overflow:auto}[type="checkbox"],[type="radio"]{padding:0}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type="search"]{-webkit-appearance:textfield;outline-offset:-2px}[type="search"]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}::-moz-focus-inner{padding:0;border-style:none}:-moz-focusring{outline:none}:-moz-ui-invalid{box-shadow:none}::-ms-expand{display:none}[type="file"],[type="range"]{padding:0;border-width:0}input:not([type="checkbox"],[type="radio"],[type="range"]){height:calc( (1rem * var(--line-height)) + (var(--form-element-spacing-vertical) * 2) + (var(--border-width) * 2))}fieldset{margin:0;margin-bottom:var(--spacing);padding:0;border:0}label,fieldset legend{display:block;margin-bottom:calc(var(--spacing) * 0.25);font-weight:var(--form-label-font-weight, var(--font-weight))}input:not([type="checkbox"],[type="radio"]),select,textarea{width:100%}input:not([type="checkbox"],[type="radio"],[type="range"],[type="file"]),select,textarea{appearance:none;padding:var(--form-element-spacing-vertical) var(--form-element-spacing-horizontal)}input,select,textarea{--background-color: var(--form-element-background-color);--border-color: var(--form-element-border-color);--color: var(--form-element-color);--box-shadow: none;border:var(--border-width) solid var(--border-color);border-radius:var(--border-radius);outline:none;background-color:var(--background-color);box-shadow:var(--box-shadow);color:var(--color);font-weight:var(--font-weight);transition:background-color var(--transition),border-color var(--transition),color var(--transition),box-shadow var(--transition)}input:not([type="submit"],[type="button"],[type="reset"],[type="checkbox"],[type="radio"],[readonly]):is(:active, :focus),:where(select, textarea):is(:active, :focus){--background-color: var(--form-element-active-background-color)}input:not([type="submit"],[type="button"],[type="reset"],[role="switch"],[readonly]):is(:active, :focus),:where(select, textarea):is(:active, :focus){--border-color: var(--form-element-active-border-color)}input:not([type="submit"],[type="button"],[type="reset"],[type="range"],[type="file"],[readonly]):focus,select:focus,textarea:focus{--box-shadow: 0 0 0 var(--outline-width) var(--form-element-focus-color)}input:not([type="submit"],[type="button"],[type="reset"])[disabled],select[disabled],textarea[disabled],:where(fieldset[disabled]) :is(input:not([type="submit"], [type="button"], [type="reset"]), select, textarea){--background-color: var(--form-element-disabled-background-color);--border-color: var(--form-element-disabled-border-color);opacity:var(--form-element-disabled-opacity);pointer-events:none}:where(input, select, textarea):not([type="checkbox"],[type="radio"],[type="date"],[type="datetime-local"],[type="month"],[type="time"],[type="week"])[aria-invalid]{padding-right:calc( var(--form-element-spacing-horizontal) + 1.5rem) !important;padding-left:var(--form-element-spacing-horizontal);padding-inline-start:var(--form-element-spacing-horizontal) !important;padding-inline-end:calc( var(--form-element-spacing-horizontal) + 1.5rem) !important;background-position:center right 0.75rem;background-size:1rem auto;background-repeat:no-repeat}:where(input, select, textarea):not([type="checkbox"],[type="radio"],[type="date"],[type="datetime-local"],[type="month"],[type="time"],[type="week"])[aria-invalid="false"]{background-image:var(--icon-valid)}:where(input, select, textarea):not([type="checkbox"],[type="radio"],[type="date"],[type="datetime-local"],[type="month"],[type="time"],[type="week"])[aria-invalid="true"]{background-image:var(--icon-invalid)}:where(input, select, textarea)[aria-invalid="false"]{--border-color: var(--form-element-valid-border-color)}:where(input, select, textarea)[aria-invalid="false"]:is(:active, :focus){--border-color: var(--form-element-valid-active-border-color) !important;--box-shadow: 0 0 0 var(--outline-width) var(--form-element-valid-focus-color) !important}:where(input, select, textarea)[aria-invalid="true"]{--border-color: var(--form-element-invalid-border-color)}:where(input, select, textarea)[aria-invalid="true"]:is(:active, :focus){--border-color: var(--form-element-invalid-active-border-color) !important;--box-shadow: 0 0 0 var(--outline-width) var(--form-element-invalid-focus-color) !important}[dir="rtl"] :where(input, select, textarea):not([type="checkbox"],[type="radio"]):is([aria-invalid], [aria-invalid="true"], [aria-invalid="false"] ){background-position:center left 0.75rem}input::placeholder,input::-webkit-input-placeholder,textarea::placeholder,textarea::-webkit-input-placeholder,select:invalid{color:var(--form-element-placeholder-color);opacity:1}input:not([type="checkbox"],[type="radio"]),select,textarea{margin-bottom:var(--spacing)}select::-ms-expand{border:0;background-color:transparent}select:not([multiple],[size]){padding-right:calc(var(--form-element-spacing-horizontal) + 1.5rem);padding-left:var(--form-element-spacing-horizontal);padding-inline-start:var(--form-element-spacing-horizontal);padding-inline-end:calc(var(--form-element-spacing-horizontal) + 1.5rem);background-image:var(--icon-chevron);background-position:center right 0.75rem;background-size:1rem auto;background-repeat:no-repeat}[dir="rtl"] select:not([multiple],[size]){background-position:center left 0.75rem}:where(input, select, textarea, .grid)+small{display:block;width:100%;margin-top:calc(var(--spacing) * -0.75);margin-bottom:var(--spacing);color:var(--muted-color)}label>:where(input, select, textarea){margin-top:calc(var(--spacing) * 0.25)}[type="checkbox"],[type="radio"]{-webkit-appearance:none;-moz-appearance:none;appearance:none;width:1.25em;height:1.25em;margin-top:-0.125em;margin-right:0.375em;margin-left:0;margin-inline-start:0;margin-inline-end:0.375em;border-width:var(--border-width);font-size:inherit;vertical-align:middle;cursor:pointer}[type="checkbox"]::-ms-check,[type="radio"]::-ms-check{display:none}[type="checkbox"]:checked,[type="checkbox"]:checked:active,[type="checkbox"]:checked:focus,[type="radio"]:checked,[type="radio"]:checked:active,[type="radio"]:checked:focus{--background-color: var(--primary);--border-color: var(--primary);background-image:var(--icon-checkbox);background-position:center;background-size:0.75em auto;background-repeat:no-repeat}[type="checkbox"]~label,[type="radio"]~label{display:inline-block;margin-right:0.375em;margin-bottom:0;cursor:pointer}[type="checkbox"]:indeterminate{--background-color: var(--primary);--border-color: var(--primary);background-image:var(--icon-minus);background-position:center;background-size:0.75em auto;background-repeat:no-repeat}[type="radio"]{border-radius:50%}[type="radio"]:checked,[type="radio"]:checked:active,[type="radio"]:checked:focus{--background-color: var(--primary-inverse);border-width:0.35em;background-image:none}[type="checkbox"][role="switch"]{--background-color: var(--switch-background-color);--border-color: var(--switch-background-color);--color: var(--switch-color);width:2.25em;height:1.25em;border:var(--border-width) solid var(--border-color);border-radius:1.25em;background-color:var(--background-color);line-height:1.25em}[type="checkbox"][role="switch"]:focus{--background-color: var(--switch-background-color);--border-color: var(--switch-background-color)}[type="checkbox"][role="switch"]:checked{--background-color: var(--switch-checked-background-color);--border-color: var(--switch-checked-background-color)}[type="checkbox"][role="switch"]:before{display:block;width:calc(1.25em - (var(--border-width) * 2));height:100%;border-radius:50%;background-color:var(--color);content:"";transition:margin 0.1s ease-in-out}[type="checkbox"][role="switch"]:checked{background-image:none}[type="checkbox"][role="switch"]:checked::before{margin-left:calc(1.125em - var(--border-width));margin-inline-start:calc(1.125em - var(--border-width))}[type="checkbox"][aria-invalid="false"],[type="checkbox"]:checked[aria-invalid="false"],[type="radio"][aria-invalid="false"],[type="radio"]:checked[aria-invalid="false"],[type="checkbox"][role="switch"][aria-invalid="false"],[type="checkbox"][role="switch"]:checked[aria-invalid="false"]{--border-color: var(--form-element-valid-border-color)}[type="checkbox"][aria-invalid="true"],[type="checkbox"]:checked[aria-invalid="true"],[type="radio"][aria-invalid="true"],[type="radio"]:checked[aria-invalid="true"],[type="checkbox"][role="switch"][aria-invalid="true"],[type="checkbox"][role="switch"]:checked[aria-invalid="true"]{--border-color: var(--form-element-invalid-border-color)}[type="color"]::-webkit-color-swatch-wrapper{padding:0}[type="color"]::-moz-focus-inner{padding:0}[type="color"]::-webkit-color-swatch{border:0;border-radius:calc(var(--border-radius) * 0.5)}[type="color"]::-moz-color-swatch{border:0;border-radius:calc(var(--border-radius) * 0.5)}input:not([type="checkbox"],[type="radio"],[type="range"],[type="file"]):is([type="date"], [type="datetime-local"], [type="month"], [type="time"], [type="week"]){--icon-position: 0.75rem;--icon-width: 1rem;padding-right:calc(var(--icon-width) + var(--icon-position));background-image:var(--icon-date);background-position:center right var(--icon-position);background-size:var(--icon-width) auto;background-repeat:no-repeat}input:not([type="checkbox"],[type="radio"],[type="range"],[type="file"])[type="time"]{background-image:var(--icon-time)}[type="date"]::-webkit-calendar-picker-indicator,[type="datetime-local"]::-webkit-calendar-picker-indicator,[type="month"]::-webkit-calendar-picker-indicator,[type="time"]::-webkit-calendar-picker-indicator,[type="week"]::-webkit-calendar-picker-indicator{width:var(--icon-width);margin-right:calc(var(--icon-width) * -1);margin-left:var(--icon-position);opacity:0}[dir="rtl"] :is([type="date"], [type="datetime-local"], [type="month"], [type="time"], [type="week"]){text-align:right}@-moz-document url-prefix(){[type="date"],[type="datetime-local"],[type="month"],[type="time"],[type="week"]{padding-right:var(--form-element-spacing-horizontal) !important;background-image:none !important}}[type="file"]{--color: var(--muted-color);padding:calc(var(--form-element-spacing-vertical) * 0.5) 0;border:0;border-radius:0;background:none}[type="file"]::file-selector-button{--background-color: var(--secondary);--border-color: var(--secondary);--color: var(--secondary-inverse);margin-right:calc(var(--spacing) / 2);margin-left:0;margin-inline-start:0;margin-inline-end:calc(var(--spacing) / 2);padding:calc(var(--form-element-spacing-vertical) * 0.5) calc(var(--form-element-spacing-horizontal) * 0.5);border:var(--border-width) solid var(--border-color);border-radius:var(--border-radius);outline:none;background-color:var(--background-color);box-shadow:var(--box-shadow);color:var(--color);font-weight:var(--font-weight);font-size:1rem;line-height:var(--line-height);text-align:center;cursor:pointer;transition:background-color var(--transition),border-color var(--transition),color var(--transition),box-shadow var(--transition)}[type="file"]::file-selector-button:is(:hover, :active, :focus){--background-color: var(--secondary-hover);--border-color: var(--secondary-hover)}[type="file"]::-webkit-file-upload-button{--background-color: var(--secondary);--border-color: var(--secondary);--color: var(--secondary-inverse);margin-right:calc(var(--spacing) / 2);margin-left:0;margin-inline-start:0;margin-inline-end:calc(var(--spacing) / 2);padding:calc(var(--form-element-spacing-vertical) * 0.5) calc(var(--form-element-spacing-horizontal) * 0.5);border:var(--border-width) solid var(--border-color);border-radius:var(--border-radius);outline:none;background-color:var(--background-color);box-shadow:var(--box-shadow);color:var(--color);font-weight:var(--font-weight);font-size:1rem;line-height:var(--line-height);text-align:center;cursor:pointer;transition:background-color var(--transition),border-color var(--transition),color var(--transition),box-shadow var(--transition)}[type="file"]::-webkit-file-upload-button:is(:hover, :active, :focus){--background-color: var(--secondary-hover);--border-color: var(--secondary-hover)}[type="file"]::-ms-browse{--background-color: var(--secondary);--border-color: var(--secondary);--color: var(--secondary-inverse);margin-right:calc(var(--spacing) / 2);margin-left:0;margin-inline-start:0;margin-inline-end:calc(var(--spacing) / 2);padding:calc(var(--form-element-spacing-vertical) * 0.5) calc(var(--form-element-spacing-horizontal) * 0.5);border:var(--border-width) solid var(--border-color);border-radius:var(--border-radius);outline:none;background-color:var(--background-color);box-shadow:var(--box-shadow);color:var(--color);font-weight:var(--font-weight);font-size:1rem;line-height:var(--line-height);text-align:center;cursor:pointer;transition:background-color var(--transition),border-color var(--transition),color var(--transition),box-shadow var(--transition)}[type="file"]::-ms-browse:is(:hover, :active, :focus){--background-color: var(--secondary-hover);--border-color: var(--secondary-hover)}[type="range"]{-webkit-appearance:none;-moz-appearance:none;appearance:none;width:100%;height:1.25rem;background:none}[type="range"]::-webkit-slider-runnable-track{width:100%;height:.25rem;border-radius:var(--border-radius);background-color:var(--range-border-color);transition:background-color var(--transition),box-shadow var(--transition)}[type="range"]::-moz-range-track{width:100%;height:.25rem;border-radius:var(--border-radius);background-color:var(--range-border-color);transition:background-color var(--transition),box-shadow var(--transition)}[type="range"]::-ms-track{width:100%;height:.25rem;border-radius:var(--border-radius);background-color:var(--range-border-color);transition:background-color var(--transition),box-shadow var(--transition)}[type="range"]::-webkit-slider-thumb{-webkit-appearance:none;width:1.25rem;height:1.25rem;margin-top:-.5rem;border:2px solid var(--range-thumb-border-color);border-radius:50%;background-color:var(--range-thumb-color);cursor:pointer;transition:background-color var(--transition),transform var(--transition)}[type="range"]::-moz-range-thumb{-webkit-appearance:none;width:1.25rem;height:1.25rem;margin-top:-.5rem;border:2px solid var(--range-thumb-border-color);border-radius:50%;background-color:var(--range-thumb-color);cursor:pointer;transition:background-color var(--transition),transform var(--transition)}[type="range"]::-ms-thumb{-webkit-appearance:none;width:1.25rem;height:1.25rem;margin-top:-.5rem;border:2px solid var(--range-thumb-border-color);border-radius:50%;background-color:var(--range-thumb-color);cursor:pointer;transition:background-color var(--transition),transform var(--transition)}[type="range"]:hover,[type="range"]:focus{--range-border-color: var(--range-active-border-color);--range-thumb-color: var(--range-thumb-hover-color)}[type="range"]:active{--range-thumb-color: var(--range-thumb-active-color)}[type="range"]:active::-webkit-slider-thumb{transform:scale(1.25)}[type="range"]:active::-moz-range-thumb{transform:scale(1.25)}[type="range"]:active::-ms-thumb{transform:scale(1.25)}input:not([type="checkbox"],[type="radio"],[type="range"],[type="file"])[type="search"]{padding-inline-start:calc(var(--form-element-spacing-horizontal) + 1.75rem);border-radius:5rem;background-image:var(--icon-search);background-position:center left 1.125rem;background-size:1rem auto;background-repeat:no-repeat}input:not([type="checkbox"],[type="radio"],[type="range"],[type="file"])[type="search"][aria-invalid]{padding-inline-start:calc(var(--form-element-spacing-horizontal) + 1.75rem) !important;background-position:center left 1.125rem, center right 0.75rem}input:not([type="checkbox"],[type="radio"],[type="range"],[type="file"])[type="search"][aria-invalid="false"]{background-image:var(--icon-search),var(--icon-valid)}input:not([type="checkbox"],[type="radio"],[type="range"],[type="file"])[type="search"][aria-invalid="true"]{background-image:var(--icon-search),var(--icon-invalid)}[type="search"]::-webkit-search-cancel-button{-webkit-appearance:none;display:none}[dir="rtl"] :where(input):not([type="checkbox"],[type="radio"],[type="range"],[type="file"])[type="search"]{background-position:center right 1.125rem}[dir="rtl"] :where(input):not([type="checkbox"],[type="radio"],[type="range"],[type="file"])[type="search"][aria-invalid]{background-position:center right 1.125rem, center left 0.75rem}:where(table){width:100%;border-collapse:collapse;border-spacing:0;text-indent:0}th,td{padding:calc(var(--spacing) / 2) var(--spacing);border-bottom:var(--border-width) solid var(--table-border-color);color:var(--color);font-weight:var(--font-weight);font-size:var(--font-size);text-align:left;text-align:start}tfoot th,tfoot td{border-top:var(--border-width) solid var(--table-border-color);border-bottom:0}table[role="grid"] tbody tr:nth-child(odd){background-color:var(--table-row-stripped-background-color)}pre,code,kbd,samp{font-size:0.875em;font-family:var(--font-family)}pre{-ms-overflow-style:scrollbar;overflow:auto}pre,code,kbd{border-radius:var(--border-radius);background:var(--code-background-color);color:var(--code-color);font-weight:var(--font-weight);line-height:initial}code,kbd{display:inline-block;padding:0.375rem 0.5rem}pre{display:block;margin-bottom:var(--spacing);overflow-x:auto}pre>code{display:block;padding:var(--spacing);background:none;font-size:14px;line-height:var(--line-height)}code b{color:var(--code-tag-color);font-weight:var(--font-weight)}code i{color:var(--code-property-color);font-style:normal}code u{color:var(--code-value-color);text-decoration:none}code em{color:var(--code-comment-color);font-style:normal}kbd{background-color:var(--code-kbd-background-color);color:var(--code-kbd-color);vertical-align:baseline}hr{height:0;border:0;border-top:1px solid var(--muted-border-color);color:inherit}[hidden],template{display:none !important}canvas{display:inline-block}details{display:block;margin-bottom:var(--spacing);padding-bottom:var(--spacing);border-bottom:var(--border-width) solid var(--accordion-border-color)}details summary{line-height:1rem;list-style-type:none;cursor:pointer;transition:color var(--transition)}details summary:not([role]){color:var(--accordion-close-summary-color)}details summary::-webkit-details-marker{display:none}details summary::marker{display:none}details summary::-moz-list-bullet{list-style-type:none}details summary::after{display:block;width:1rem;height:1rem;margin-inline-start:calc(var(--spacing, 1rem) * 0.5);float:right;transform:rotate(-90deg);background-image:var(--icon-chevron);background-position:right center;background-size:1rem auto;background-repeat:no-repeat;content:"";transition:transform var(--transition)}details summary:focus{outline:none}details summary:focus:not([role="button"]){color:var(--accordion-active-summary-color)}details summary[role="button"]{width:100%;text-align:left}details summary[role="button"]::after{height:calc(1rem * var(--line-height, 1.5));background-image:var(--icon-chevron-button)}details summary[role="button"]:not(.outline).contrast::after{background-image:var(--icon-chevron-button-inverse)}details[open]>summary{margin-bottom:calc(var(--spacing))}details[open]>summary:not([role]):not(:focus){color:var(--accordion-open-summary-color)}details[open]>summary::after{transform:rotate(0)}[dir="rtl"] details summary{text-align:right}[dir="rtl"] details summary::after{float:left;background-position:left center}article{margin:var(--block-spacing-vertical) 0;padding:var(--block-spacing-vertical) var(--block-spacing-horizontal);border-radius:var(--border-radius);background:var(--card-background-color);box-shadow:var(--card-box-shadow)}article>header,article>footer{margin-right:calc(var(--block-spacing-horizontal) * -1);margin-left:calc(var(--block-spacing-horizontal) * -1);padding:calc(var(--block-spacing-vertical) * 0.66) var(--block-spacing-horizontal);background-color:var(--card-sectionning-background-color)}article>header{margin-top:calc(var(--block-spacing-vertical) * -1);margin-bottom:var(--block-spacing-vertical);border-bottom:var(--border-width) solid var(--card-border-color);border-top-right-radius:var(--border-radius);border-top-left-radius:var(--border-radius)}article>footer{margin-top:var(--block-spacing-vertical);margin-bottom:calc(var(--block-spacing-vertical) * -1);border-top:var(--border-width) solid var(--card-border-color);border-bottom-right-radius:var(--border-radius);border-bottom-left-radius:var(--border-radius)}:root{--scrollbar-width: 0px}dialog{display:flex;z-index:999;position:fixed;top:0;right:0;bottom:0;left:0;align-items:center;justify-content:center;width:inherit;min-width:100%;height:inherit;min-height:100%;padding:var(--spacing);border:0;backdrop-filter:var(--modal-overlay-backdrop-filter);background-color:var(--modal-overlay-background-color);color:var(--color)}dialog article{max-height:calc(100vh - var(--spacing) * 2);overflow:auto}@media (min-width: 576px){dialog article{max-width:510px}}@media (min-width: 768px){dialog article{max-width:700px}}dialog article>header,dialog article>footer{padding:calc(var(--block-spacing-vertical) * 0.5) var(--block-spacing-horizontal)}dialog article>header .close{margin:0;margin-left:var(--spacing);float:right}dialog article>footer{text-align:right}dialog article>footer [role="button"]{margin-bottom:0}dialog article>footer [role="button"]:not(:first-of-type){margin-left:calc(var(--spacing) * 0.5)}dialog article p:last-of-type{margin:0}dialog article .close{display:block;width:1rem;height:1rem;margin-top:calc(var(--block-spacing-vertical) * -0.5);margin-bottom:var(--typography-spacing-vertical);margin-left:auto;background-image:var(--icon-close);background-position:center;background-size:auto 1rem;background-repeat:no-repeat;opacity:0.5;transition:opacity var(--transition)}dialog article .close:is([aria-current], :hover, :active, :focus){opacity:1}dialog:not([open]),dialog[open="false"]{display:none}.modal-is-open{padding-right:var(--scrollbar-width, 0px);overflow:hidden;pointer-events:none;touch-action:none}.modal-is-open dialog{pointer-events:auto}:where(.modal-is-opening, .modal-is-closing) dialog,:where(.modal-is-opening, .modal-is-closing) dialog>article{animation-duration:.2s;animation-timing-function:ease-in-out;animation-fill-mode:both}:where(.modal-is-opening, .modal-is-closing) dialog{animation-duration:.8s;animation-name:modal-overlay}:where(.modal-is-opening, .modal-is-closing) dialog>article{animation-delay:.2s;animation-name:modal}.modal-is-closing dialog,.modal-is-closing dialog>article{animation-delay:0s;animation-direction:reverse}@keyframes modal-overlay{from{backdrop-filter:none;background-color:transparent}}@keyframes modal{from{transform:translateY(-100%);opacity:0}}:where(nav li)::before{float:left;content:"\200B"}nav,nav ul{display:flex}nav{justify-content:space-between}nav ol,nav ul{align-items:center;margin-bottom:0;padding:0;list-style:none}nav ol:first-of-type,nav ul:first-of-type{margin-left:calc(var(--nav-element-spacing-horizontal) * -1)}nav ol:last-of-type,nav ul:last-of-type{margin-right:calc(var(--nav-element-spacing-horizontal) * -1)}nav li{display:inline-block;margin:0;padding:var(--nav-element-spacing-vertical) var(--nav-element-spacing-horizontal)}nav li>*{--spacing: 0}nav :where(a, [role="link"]){display:inline-block;margin:calc(var(--nav-link-spacing-vertical) * -1) calc(var(--nav-link-spacing-horizontal) * -1);padding:var(--nav-link-spacing-vertical) var(--nav-link-spacing-horizontal);border-radius:var(--border-radius);text-decoration:none}nav :where(a, [role="link"]):is([aria-current], :hover, :active, :focus){text-decoration:none}nav[aria-label="breadcrumb"]{align-items:center;justify-content:start}nav[aria-label="breadcrumb"] ul li:not(:first-child){margin-inline-start:var(--nav-link-spacing-horizontal)}nav[aria-label="breadcrumb"] ul li:not(:last-child) ::after{position:absolute;width:calc(var(--nav-link-spacing-horizontal) * 2);margin-inline-start:calc(var(--nav-link-spacing-horizontal) / 2);content:"/";color:var(--muted-color);text-align:center}nav[aria-label="breadcrumb"] a[aria-current]{background-color:transparent;color:inherit;text-decoration:none;pointer-events:none}nav [role="button"]{margin-right:inherit;margin-left:inherit;padding:var(--nav-link-spacing-vertical) var(--nav-link-spacing-horizontal)}aside nav,aside ol,aside ul,aside li{display:block}aside li{padding:calc(var(--nav-element-spacing-vertical) * 0.5) var(--nav-element-spacing-horizontal)}aside li a{display:block}aside li [role="button"]{margin:inherit}[dir="rtl"] nav[aria-label="breadcrumb"] ul li:not(:last-child) ::after{content:"\\"}progress{display:inline-block;vertical-align:baseline}progress{-webkit-appearance:none;-moz-appearance:none;display:inline-block;appearance:none;width:100%;height:0.5rem;margin-bottom:calc(var(--spacing) * 0.5);overflow:hidden;border:0;border-radius:var(--border-radius);background-color:var(--progress-background-color);color:var(--progress-color)}progress::-webkit-progress-bar{border-radius:var(--border-radius);background:none}progress[value]::-webkit-progress-value{background-color:var(--progress-color)}progress::-moz-progress-bar{background-color:var(--progress-color)}@media (prefers-reduced-motion: no-preference){progress:indeterminate{background:var(--progress-background-color) linear-gradient(to right, var(--progress-color) 30%, var(--progress-background-color) 30%) top left/150% 150% no-repeat;animation:progress-indeterminate 1s linear infinite}progress:indeterminate[value]::-webkit-progress-value{background-color:transparent}progress:indeterminate::-moz-progress-bar{background-color:transparent}}@media (prefers-reduced-motion: no-preference){[dir="rtl"] progress:indeterminate{animation-direction:reverse}}@keyframes progress-indeterminate{0%{background-position:200% 0}100%{background-position:-200% 0}}details[role="list"],li[role="list"]{position:relative}details[role="list"] summary+ul,li[role="list"]>ul{display:flex;z-index:99;position:absolute;top:auto;right:0;left:0;flex-direction:column;margin:0;padding:0;border:var(--border-width) solid var(--dropdown-border-color);border-radius:var(--border-radius);border-top-right-radius:0;border-top-left-radius:0;background-color:var(--dropdown-background-color);box-shadow:var(--card-box-shadow);color:var(--dropdown-color);white-space:nowrap}details[role="list"] summary+ul li,li[role="list"]>ul li{width:100%;margin-bottom:0;padding:calc(var(--form-element-spacing-vertical) * 0.5) var(--form-element-spacing-horizontal);list-style:none}details[role="list"] summary+ul li:first-of-type,li[role="list"]>ul li:first-of-type{margin-top:calc(var(--form-element-spacing-vertical) * 0.5)}details[role="list"] summary+ul li:last-of-type,li[role="list"]>ul li:last-of-type{margin-bottom:calc(var(--form-element-spacing-vertical) * 0.5)}details[role="list"] summary+ul li a,li[role="list"]>ul li a{display:block;margin:calc(var(--form-element-spacing-vertical) * -0.5) calc(var(--form-element-spacing-horizontal) * -1);padding:calc(var(--form-element-spacing-vertical) * 0.5) var(--form-element-spacing-horizontal);overflow:hidden;color:var(--dropdown-color);text-decoration:none;text-overflow:ellipsis}details[role="list"] summary+ul li a:hover,li[role="list"]>ul li a:hover{background-color:var(--dropdown-hover-background-color)}details[role="list"] summary::after,li[role="list"]>a::after{display:block;width:1rem;height:calc(1rem * var(--line-height, 1.5));margin-inline-start:0.5rem;float:right;transform:rotate(0deg);background-position:right center;background-size:1rem auto;background-repeat:no-repeat;content:""}details[role="list"]{padding:0;border-bottom:none}details[role="list"] summary{margin-bottom:0}details[role="list"] summary:not([role]){height:calc( 1rem * var(--line-height) + var(--form-element-spacing-vertical) * 2 + var(--border-width) * 2);padding:var(--form-element-spacing-vertical) var(--form-element-spacing-horizontal);border:var(--border-width) solid var(--form-element-border-color);border-radius:var(--border-radius);background-color:var(--form-element-background-color);color:var(--form-element-placeholder-color);line-height:inherit;cursor:pointer;transition:background-color var(--transition),border-color var(--transition),color var(--transition),box-shadow var(--transition)}details[role="list"] summary:not([role]):active,details[role="list"] summary:not([role]):focus{border-color:var(--form-element-active-border-color);background-color:var(--form-element-active-background-color)}details[role="list"] summary:not([role]):focus{box-shadow:0 0 0 var(--outline-width) var(--form-element-focus-color)}details[role="list"][open] summary{border-bottom-right-radius:0;border-bottom-left-radius:0}details[role="list"][open] summary::before{display:block;z-index:1;position:fixed;top:0;right:0;bottom:0;left:0;background:none;content:"";cursor:default}nav details[role="list"] summary,nav li[role="list"] a{display:flex;direction:ltr}nav details[role="list"] summary+ul,nav li[role="list"]>ul{min-width:fit-content;border-radius:var(--border-radius)}nav details[role="list"] summary+ul li a,nav li[role="list"]>ul li a{border-radius:0}nav details[role="list"] summary,nav details[role="list"] summary:not([role]){height:auto;padding:var(--nav-link-spacing-vertical) var(--nav-link-spacing-horizontal)}nav details[role="list"][open] summary{border-radius:var(--border-radius)}nav details[role="list"] summary+ul{margin-top:var(--outline-width);margin-inline-start:0}nav details[role="list"] summary[role="link"]{margin-bottom:calc(var(--nav-link-spacing-vertical) * -1);line-height:var(--line-height)}nav details[role="list"] summary[role="link"]+ul{margin-top:calc(var(--nav-link-spacing-vertical) + var(--outline-width));margin-inline-start:calc(var(--nav-link-spacing-horizontal) * -1)}li[role="list"]:hover>ul,li[role="list"] a:active~ul,li[role="list"] a:focus~ul{display:flex}li[role="list"]>ul{display:none;margin-top:calc(var(--nav-link-spacing-vertical) + var(--outline-width));margin-inline-start:calc( var(--nav-element-spacing-horizontal) - var(--nav-link-spacing-horizontal))}li[role="list"]>a::after{background-image:var(--icon-chevron)}label>details[role="list"]{margin-top:calc(var(--spacing) * .25);margin-bottom:var(--spacing)}[aria-busy="true"]{cursor:progress}[aria-busy="true"]:not(input,select,textarea,html)::before{display:inline-block;width:1em;height:1em;border:0.1875em solid currentColor;border-radius:1em;border-right-color:transparent;content:"";vertical-align:text-bottom;vertical-align:-.125em;animation:spinner 0.75s linear infinite;opacity:var(--loading-spinner-opacity)}[aria-busy="true"]:not(input,select,textarea,html):not(:empty)::before{margin-right:calc(var(--spacing) * 0.5);margin-left:0;margin-inline-start:0;margin-inline-end:calc(var(--spacing) * 0.5)}[aria-busy="true"]:not(input,select,textarea,html):empty{text-align:center}button[aria-busy="true"],input[type="submit"][aria-busy="true"],input[type="button"][aria-busy="true"],input[type="reset"][aria-busy="true"],a[aria-busy="true"]{pointer-events:none}@keyframes spinner{to{transform:rotate(360deg)}}[data-tooltip]{position:relative}[data-tooltip]:not(a,button,input){border-bottom:1px dotted;text-decoration:none;cursor:help}[data-tooltip][data-placement="top"]::before,[data-tooltip][data-placement="top"]::after,[data-tooltip]::before,[data-tooltip]::after{display:block;z-index:99;position:absolute;bottom:100%;left:50%;padding:.25rem .5rem;overflow:hidden;transform:translate(-50%, -0.25rem);border-radius:var(--border-radius);background:var(--tooltip-background-color);content:attr(data-tooltip);color:var(--tooltip-color);font-style:normal;font-weight:var(--font-weight);font-size:.875rem;text-decoration:none;text-overflow:ellipsis;white-space:nowrap;opacity:0;pointer-events:none}[data-tooltip][data-placement="top"]::after,[data-tooltip]::after{padding:0;transform:translate(-50%, 0rem);border-top:.3rem solid;border-right:.3rem solid transparent;border-left:.3rem solid transparent;border-radius:0;background-color:transparent;content:"";color:var(--tooltip-background-color)}[data-tooltip][data-placement="bottom"]::before,[data-tooltip][data-placement="bottom"]::after{top:100%;bottom:auto;transform:translate(-50%, 0.25rem)}[data-tooltip][data-placement="bottom"]:after{transform:translate(-50%, -0.3rem);border:.3rem solid transparent;border-bottom:.3rem solid}[data-tooltip][data-placement="left"]::before,[data-tooltip][data-placement="left"]::after{top:50%;right:100%;bottom:auto;left:auto;transform:translate(-0.25rem, -50%)}[data-tooltip][data-placement="left"]:after{transform:translate(0.3rem, -50%);border:.3rem solid transparent;border-left:.3rem solid}[data-tooltip][data-placement="right"]::before,[data-tooltip][data-placement="right"]::after{top:50%;right:auto;bottom:auto;left:100%;transform:translate(0.25rem, -50%)}[data-tooltip][data-placement="right"]:after{transform:translate(-0.3rem, -50%);border:.3rem solid transparent;border-right:.3rem solid}[data-tooltip]:focus::before,[data-tooltip]:focus::after,[data-tooltip]:hover::before,[data-tooltip]:hover::after{opacity:1}@media (hover: hover) and (pointer: fine){[data-tooltip][data-placement="bottom"]:focus::before,[data-tooltip][data-placement="bottom"]:focus::after,[data-tooltip][data-placement="bottom"]:hover [data-tooltip]:focus::before,[data-tooltip][data-placement="bottom"]:hover [data-tooltip]:focus::after,[data-tooltip]:hover::before,[data-tooltip]:hover::after{animation-duration:.2s;animation-name:tooltip-slide-top}[data-tooltip][data-placement="bottom"]:focus::after,[data-tooltip][data-placement="bottom"]:hover [data-tooltip]:focus::after,[data-tooltip]:hover::after{animation-name:tooltip-caret-slide-top}[data-tooltip][data-placement="bottom"]:focus::before,[data-tooltip][data-placement="bottom"]:focus::after,[data-tooltip][data-placement="bottom"]:hover::before,[data-tooltip][data-placement="bottom"]:hover::after{animation-duration:.2s;animation-name:tooltip-slide-bottom}[data-tooltip][data-placement="bottom"]:focus::after,[data-tooltip][data-placement="bottom"]:hover::after{animation-name:tooltip-caret-slide-bottom}[data-tooltip][data-placement="left"]:focus::before,[data-tooltip][data-placement="left"]:focus::after,[data-tooltip][data-placement="left"]:hover::before,[data-tooltip][data-placement="left"]:hover::after{animation-duration:.2s;animation-name:tooltip-slide-left}[data-tooltip][data-placement="left"]:focus::after,[data-tooltip][data-placement="left"]:hover::after{animation-name:tooltip-caret-slide-left}[data-tooltip][data-placement="right"]:focus::before,[data-tooltip][data-placement="right"]:focus::after,[data-tooltip][data-placement="right"]:hover::before,[data-tooltip][data-placement="right"]:hover::after{animation-duration:.2s;animation-name:tooltip-slide-right}[data-tooltip][data-placement="right"]:focus::after,[data-tooltip][data-placement="right"]:hover::after{animation-name:tooltip-caret-slide-right}}@keyframes tooltip-slide-top{from{transform:translate(-50%, 0.75rem);opacity:0}to{transform:translate(-50%, -0.25rem);opacity:1}}@keyframes tooltip-caret-slide-top{from{opacity:0}50%{transform:translate(-50%, -0.25rem);opacity:0}to{transform:translate(-50%, 0rem);opacity:1}}@keyframes tooltip-slide-bottom{from{transform:translate(-50%, -0.75rem);opacity:0}to{transform:translate(-50%, 0.25rem);opacity:1}}@keyframes tooltip-caret-slide-bottom{from{opacity:0}50%{transform:translate(-50%, -0.5rem);opacity:0}to{transform:translate(-50%, -0.3rem);opacity:1}}@keyframes tooltip-slide-left{from{transform:translate(0.75rem, -50%);opacity:0}to{transform:translate(-0.25rem, -50%);opacity:1}}@keyframes tooltip-caret-slide-left{from{opacity:0}50%{transform:translate(0.05rem, -50%);opacity:0}to{transform:translate(0.3rem, -50%);opacity:1}}@keyframes tooltip-slide-right{from{transform:translate(-0.75rem, -50%);opacity:0}to{transform:translate(0.25rem, -50%);opacity:1}}@keyframes tooltip-caret-slide-right{from{opacity:0}50%{transform:translate(-0.05rem, -50%);opacity:0}to{transform:translate(-0.3rem, -50%);opacity:1}}[aria-controls]{cursor:pointer}[aria-disabled="true"],[disabled]{cursor:not-allowed}[aria-hidden="false"][hidden]{display:initial}[aria-hidden="false"][hidden]:not(:focus){clip:rect(0, 0, 0, 0);position:absolute}a,area,button,input,label,select,summary,textarea,[tabindex]{-ms-touch-action:manipulation}[dir="rtl"]{direction:rtl}@media (prefers-reduced-motion: reduce){*:not([aria-busy="true"]),:not([aria-busy="true"])::before,:not([aria-busy="true"])::after{background-attachment:initial !important;animation-duration:1ms !important;animation-delay:-1ms !important;animation-iteration-count:1 !important;scroll-behavior:auto !important;transition-delay:0s !important;transition-duration:0s !important}}.container,.container-fluid{width:100%;margin-right:auto;margin-left:auto;padding-right:var(--spacing);padding-left:var(--spacing)}@media (min-width: 576px){.container{max-width:510px;padding-right:0;padding-left:0}}@media (min-width: 768px){.container{max-width:700px}}@media (min-width: 992px){.container{max-width:920px}}@media (min-width: 1200px){.container{max-width:1130px}}@media (min-width: 1500px){.container{max-width:1480px}}@media (min-width: 2200px){.container{max-width:2190px}}::-webkit-scrollbar{display:none}:root{--spacing: 0;--font-family: monospace;--font-size: 14px}pre{white-space:pre-wrap;background-color:unset;color:unset;font-size:unset}table{margin-bottom:0px}table{overflow:hidden}table td{padding-left:2px;padding-right:2px}table tbody tr td{overflow:hidden;white-space:nowrap}table th{overflow:hidden;white-space:nowrap;font-weight:bold;text-decoration:underline}header.tree-preamble{font-size:smaller}.wide{display:flex;flex-direction:row;justify-content:space-between;row-gap:10px}.wide div:nth-child(2){margin-left:10px}nav.small a{font-size:small}ul.submenu{font-size:smaller;height:25px}pre{overflow:scroll}[role="button"]{padding:0;margin-top:2px;margin-bottom:2px}button.small{display:initial;width:initial}nav{border-radius:var(--border-radius);margin-left:10px;margin-right:10px}code{padding:0}code.highlighted{width:100%;overflow:scroll}footer.main{padding-top:2em;text-align:center;font-size:smaller}h1,h2,h3,h4,h5,h6{--typography-spacing-vertical: .1em}li.active>a{text-decoration:underline}.logo>svg{width:72px}@media (prefers-color-scheme: dark){.logo{filter:drop-shadow(0 0 4mm #fff)}}footer{height:2em}article{box-shadow:none}article.clone>input{font-size:smaller;height:30px;text-align:center}.blob-preview{text-align:center;border-style:solid;border-color:pink;margin-top:2em;padding:1em}.line-number{user-select:none;padding:0 2px;width:10px}.line{white-space:pre;padding-left:5px}.icon-header>h1,h2,h3,h4,h5{display:inline-block}.icon-header>h1,h2,h3,h4,h5>a{color:none;text-decoration:inherit}.icon-header>svg{height:2em;float:right;margin-top:2px;margin-left:2px}.icon>svg{height:1.5em}.icon-header.contrast>svg{border:2px solid}.emoji{float:right}.emoji>svg{height:30px}header.repo div{display:inline-block}article>header{padding:3px}ul.language-list li{list-style:none}ul.author-list li{list-style:none}span.right{float:right}span.tiny{font-size:.7em;font-weight:bold}span.center{text-align:center}span.tiny-text{font-size:smaller}span.hint{font-weight:bold;text-decoration:underline}span.h1{font-size:x-large;font-weight:bold}span.header-text{font-size:1.2em;font-weight:bold}span.sub-header{font-size:1em;font-weight:bold}span.header{font-size:large}span.timestamp{float:right}span.author{float:right}article.index-listing{margin-top:25px;margin-bottom:15px}article.index-listing>article{padding:3px}article.listing>article{padding:5px}span.labels{font-weight:bold}span.label{border:solid 2px}span.labels{border-radius:5px 5px 5px 5px}section.blame{display:grid;grid-template-columns:1fr 5fr}div.clone{display:flex;height:45px;font-size:smaller}div.clone-url>input{height:2em !important}div.clone>div{padding:5px;line-height:45px}div.clone-url{flex:auto}section.blame article{margin-top:0px !important}article.blame-right{padding:0;border-left:solid 1px}article.blame-left{padding:0;border-right:solid 1px}article.blame-left table tbody tr td{border-left:solid 1px;border-right:solid 1px;text-align:left}.readme ul{padding-right:revert;padding-left:revert;padding-inline-start:revert;padding-inline-end:revert}img.rss-icon-feed{height:4em}div.table{padding:.5em}div.readme{padding:1em}.term-width{word-wrap:break-word;width:600px}footer.tree{font-size:smaller}section.refs-container{display:grid;grid-template-columns:3fr 1fr}article.ref-right{margin-left:1em}@media screen and (max-width: 1000px){section.refs-container{grid-template-columns:1fr}article.ref-right{margin-left:0}}section.controls{text-align:center;border:1px}section.viewer{text-align:center;border:1px}div.repo-container{display:grid;grid-template-columns:3fr 1fr}div.user-box{display:grid;grid-template-columns:1fr 4fr}div.user-box>.name{font-weight:bold;text-decoration:underline}div.commit_message{padding-top:1em}div.user-box>.details{text-align:right}.expand-xl{display:none}div.tree{padding-right:5px;padding-left:5px}article.tree{margin-bottom:5px}section.repo-right{text-align:center}section.repo-right{margin-left:1em}section.repo-right>section{text-align:center}section.repo-right>article{display:block;text-align:center}.spaced>div:first-child{margin-left:2px}.panel{background:var(--card-background-color);padding:.3em}table.commits{width:100%}table.commits>tbody>tr>td.message{word-wrap:break-word;white-space:normal !important}section.thread-view>article{padding-top:1em;padding-bottom:1em}div.chart{text-align:center}@media (max-width: 768px){.collapse{display:none}section.repo-right{margin-top:1em;margin-left:unset}section.viewer>svg{width:400px !important}}@media (max-width: 1200px){section.viewer>svg{width:550px}section.repo-right{margin-top:1em;margin-left:unset}div.tree>table>thead>tr>th:nth-child(5){text-align:right}div.tree>table>tbody>tr>td:nth-child(5){text-align:right}article.repo{grid-template-columns:repeat(2, 1fr)}div.repo-container{grid-template-columns:1fr}}@media (min-width: 1200px){.expand-xl{display:revert}}article.blame-right{border-left:solid #fca7ea 1px !important}article.blame-left{border-right:solid #fca7ea 1px !important}article>header.highlighted{background-color:#fca7ea}article>footer{background-color:unset}.icon-header.contrast>svg{background-color:#fca7ea}article.repo-right{background-color:unset}article.repo-right>article{background-color:unset}article.repo-right>article>header{background-color:unset}.positive{color:#c3e88d}.negative{color:#ff757f}
10596 diff --git a/ayllu/themes/tokyonight/templates/about.html b/ayllu/themes/tokyonight/templates/about.html
10597new file mode 100644
10598index 0000000..e319b72
10599--- /dev/null
10600+++ b/ayllu/themes/tokyonight/templates/about.html
10601 @@ -0,0 +1,9 @@
10602+ {% extends "base.html" %}
10603+ {% block content %}
10604+ <section>
10605+ <article>
10606+ <header></header>
10607+ {{ blurb | safe }}
10608+ </article>
10609+ </section>
10610+ {% endblock %}
10611 diff --git a/ayllu/themes/tokyonight/theme.scss b/ayllu/themes/tokyonight/theme.scss
10612new file mode 100644
10613index 0000000..97551d3
10614--- /dev/null
10615+++ b/ayllu/themes/tokyonight/theme.scss
10616 @@ -0,0 +1,97 @@
10617+ // https://github.com/folke/tokyonight.nvim/blob/main/lua/tokyonight/colors.lua
10618+ $bg_dark: #1e2030;
10619+ $bg: #222436;
10620+ $bg_highlight: #2f334d;
10621+ $terminal_black: #444a73;
10622+ $fg: #c8d3f5;
10623+ $fg_dark: #828bb8;
10624+ $fg_gutter: #3b4261;
10625+ $dark3: #545c7e;
10626+ $comment: #7a88cf;
10627+ $dark5: #737aa2;
10628+ $blue0: #3e68d7;
10629+ $blue: #82aaff;
10630+ $cyan: #86e1fc;
10631+ $blue1: #65bcff;
10632+ $blue2: #0db9d7;
10633+ $blue5: #89ddff;
10634+ $blue6: #b4f9f8;
10635+ $blue7: #394b70;
10636+ $purple: #fca7ea;
10637+ $magenta2: #ff007c;
10638+ $magenta: #c099ff;
10639+ $orange: #ff966c;
10640+ $yellow: #ffc777;
10641+ $green: #c3e88d;
10642+ $green1: #4fd6be;
10643+ $green2: #41a6b5;
10644+ $teal: #4fd6be;
10645+ $red: #ff757f;
10646+ $red1: #c53b53;
10647+
10648+ //
10649+ //
10650+ // tree sitter highlighting
10651+ //
10652+ //
10653+
10654+ span.ts_attribute {color: $teal};
10655+ span.ts_constant {color: $orange};
10656+ span.ts_function.builtin {color: $blue};
10657+ span.ts_function {color: $magenta}
10658+ span.ts_keyword {color: $purple};
10659+ span.ts_operator {color: $blue5};
10660+ span.ts_property {color: $green1};
10661+ span.ts_punctuation {color: $blue};
10662+ span.ts_punctuation.bracket {color: $fg_dark};
10663+ span.ts_punctuation.delimiter {color: $blue5};
10664+ span.ts_string {color: $green};
10665+ span.ts_string.special {color: $blue}
10666+ span.ts_tag {color: $blue};
10667+ span.ts_type {color: $blue1};
10668+ span.ts_type.builtin {color: $blue1};
10669+ span.ts_variable {color: $fg};
10670+ span.ts_variable.builtin {color: $red};
10671+ span.ts_variable.parameter {color: $yellow}
10672+
10673+ @media (prefers-color-scheme: light) {
10674+ span.ts_attribute {color: darken($teal, 40%)};
10675+ span.ts_constant {color: darken($orange, 40%)};
10676+ span.ts_function.builtin {color: darken($blue, 40%)};
10677+ span.ts_function {color: darken($magenta, 20%)}
10678+ span.ts_keyword {color: darken($purple, 40%)};
10679+ span.ts_operator {color: darken($blue5, 40%)};
10680+ span.ts_property {color: darken($green1, 40%)};
10681+ span.ts_punctuation {color: darken($blue, 40%)};
10682+ span.ts_punctuation.bracket {color: $fg};
10683+ span.ts_punctuation.delimiter {color: darken($blue5, 40%)};
10684+ span.ts_string {color: darken($green, 40%)};
10685+ span.ts_string.special {color: darken($blue, 40%)}
10686+ span.ts_tag {color: darken($blue, 40%)};
10687+ span.ts_type {color: darken($blue1, 40%)};
10688+ span.ts_type.builtin {color: darken($blue1, 40%)};
10689+ span.ts_variable {color: darken($fg, 40%)};
10690+ span.ts_variable.builtin {color: darken($red, 40%)};
10691+ span.ts_variable.parameter {color: darken($yellow, 40%)}
10692+ }
10693+
10694+
10695+ @media (prefers-color-scheme: dark) {
10696+ span.label {
10697+ color: $fg_dark;
10698+ }
10699+ }
10700+
10701+ $positive: $green;
10702+ $negative: $red;
10703+
10704+ $blame-border: $purple;
10705+
10706+ $highlighted: $purple;
10707+ $icon-background: $purple;
10708+
10709+ $chart-color: $purple;
10710+
10711+ @import "@picocss/pico/scss/pico";
10712+ @import "../default/layout.scss";
10713+ @import "../default/base.scss";
10714 diff --git a/ayllu/vendor/README.md b/ayllu/vendor/README.md
10715new file mode 100644
10716index 0000000..92a4f84
10717--- /dev/null
10718+++ b/ayllu/vendor/README.md
10719 @@ -0,0 +1,28 @@
10720+ # Language Detection & Syntax Highlighting
10721+
10722+ This directory contains source files used for language detection
10723+ in Ayllu. The `languages.json` file is taken from the Github Linguist project:
10724+ https://github.com/github-linguist/linguist
10725+
10726+ Copyright (c) 2017 GitHub, Inc.
10727+
10728+ Permission is hereby granted, free of charge, to any person
10729+ obtaining a copy of this software and associated documentation
10730+ files (the "Software"), to deal in the Software without
10731+ restriction, including without limitation the rights to use,
10732+ copy, modify, merge, publish, distribute, sublicense, and/or sell
10733+ copies of the Software, and to permit persons to whom the
10734+ Software is furnished to do so, subject to the following
10735+ conditions:
10736+
10737+ The above copyright notice and this permission notice shall be
10738+ included in all copies or substantial portions of the Software.
10739+
10740+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
10741+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
10742+ OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
10743+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
10744+ HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
10745+ WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
10746+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
10747+ OTHER DEALINGS IN THE SOFTWARE.
10748 diff --git a/ayllu/vendor/linguist.json b/ayllu/vendor/linguist.json
10749new file mode 100644
10750index 0000000..46d83e5
10751--- /dev/null
10752+++ b/ayllu/vendor/linguist.json
10753 @@ -0,0 +1,10073 @@
10754+ {
10755+ "1C Enterprise": {
10756+ "type": "programming",
10757+ "color": "#814CCC",
10758+ "extensions": [
10759+ ".bsl",
10760+ ".os"
10761+ ],
10762+ "tm_scope": "source.bsl",
10763+ "ace_mode": "text",
10764+ "language_id": 0
10765+ },
10766+ "2-Dimensional Array": {
10767+ "type": "data",
10768+ "color": "#38761D",
10769+ "extensions": [
10770+ ".2da"
10771+ ],
10772+ "tm_scope": "source.2da",
10773+ "ace_mode": "text",
10774+ "language_id": 387204628
10775+ },
10776+ "4D": {
10777+ "type": "programming",
10778+ "color": "#004289",
10779+ "extensions": [
10780+ ".4dm"
10781+ ],
10782+ "tm_scope": "source.4dm",
10783+ "ace_mode": "text",
10784+ "language_id": 577529595
10785+ },
10786+ "ABAP": {
10787+ "type": "programming",
10788+ "color": "#E8274B",
10789+ "extensions": [
10790+ ".abap"
10791+ ],
10792+ "tm_scope": "source.abap",
10793+ "ace_mode": "abap",
10794+ "language_id": 1
10795+ },
10796+ "ABAP CDS": {
10797+ "type": "programming",
10798+ "color": "#555e25",
10799+ "extensions": [
10800+ ".asddls"
10801+ ],
10802+ "tm_scope": "source.abapcds",
10803+ "language_id": 452681853,
10804+ "ace_mode": "text"
10805+ },
10806+ "ABNF": {
10807+ "type": "data",
10808+ "ace_mode": "text",
10809+ "extensions": [
10810+ ".abnf"
10811+ ],
10812+ "tm_scope": "source.abnf",
10813+ "language_id": 429
10814+ },
10815+ "AGS Script": {
10816+ "type": "programming",
10817+ "color": "#B9D9FF",
10818+ "aliases": [
10819+ "ags"
10820+ ],
10821+ "extensions": [
10822+ ".asc",
10823+ ".ash"
10824+ ],
10825+ "tm_scope": "source.c++",
10826+ "ace_mode": "c_cpp",
10827+ "codemirror_mode": "clike",
10828+ "codemirror_mime_type": "text/x-c++src",
10829+ "language_id": 2
10830+ },
10831+ "AIDL": {
10832+ "type": "programming",
10833+ "color": "#34EB6B",
10834+ "tm_scope": "source.aidl",
10835+ "extensions": [
10836+ ".aidl"
10837+ ],
10838+ "ace_mode": "text",
10839+ "interpreters": [
10840+ "aidl"
10841+ ],
10842+ "language_id": 451700185
10843+ },
10844+ "AL": {
10845+ "type": "programming",
10846+ "color": "#3AA2B5",
10847+ "extensions": [
10848+ ".al"
10849+ ],
10850+ "tm_scope": "source.al",
10851+ "ace_mode": "text",
10852+ "language_id": 658971832
10853+ },
10854+ "AMPL": {
10855+ "type": "programming",
10856+ "color": "#E6EFBB",
10857+ "extensions": [
10858+ ".ampl",
10859+ ".mod"
10860+ ],
10861+ "tm_scope": "source.ampl",
10862+ "ace_mode": "text",
10863+ "language_id": 3
10864+ },
10865+ "ANTLR": {
10866+ "type": "programming",
10867+ "color": "#9DC3FF",
10868+ "extensions": [
10869+ ".g4"
10870+ ],
10871+ "tm_scope": "source.antlr",
10872+ "ace_mode": "text",
10873+ "language_id": 4
10874+ },
10875+ "API Blueprint": {
10876+ "type": "markup",
10877+ "color": "#2ACCA8",
10878+ "ace_mode": "markdown",
10879+ "extensions": [
10880+ ".apib"
10881+ ],
10882+ "tm_scope": "text.html.markdown.source.gfm.apib",
10883+ "language_id": 5
10884+ },
10885+ "APL": {
10886+ "type": "programming",
10887+ "color": "#5A8164",
10888+ "extensions": [
10889+ ".apl",
10890+ ".dyalog"
10891+ ],
10892+ "interpreters": [
10893+ "apl",
10894+ "aplx",
10895+ "dyalog"
10896+ ],
10897+ "tm_scope": "source.apl",
10898+ "ace_mode": "text",
10899+ "codemirror_mode": "apl",
10900+ "codemirror_mime_type": "text/apl",
10901+ "language_id": 6
10902+ },
10903+ "ASL": {
10904+ "type": "programming",
10905+ "ace_mode": "text",
10906+ "extensions": [
10907+ ".asl",
10908+ ".dsl"
10909+ ],
10910+ "tm_scope": "source.asl",
10911+ "language_id": 124996147
10912+ },
10913+ "ASN.1": {
10914+ "type": "data",
10915+ "extensions": [
10916+ ".asn",
10917+ ".asn1"
10918+ ],
10919+ "tm_scope": "source.asn",
10920+ "ace_mode": "text",
10921+ "codemirror_mode": "asn.1",
10922+ "codemirror_mime_type": "text/x-ttcn-asn",
10923+ "language_id": 7
10924+ },
10925+ "ASP.NET": {
10926+ "type": "programming",
10927+ "tm_scope": "text.html.asp",
10928+ "color": "#9400ff",
10929+ "aliases": [
10930+ "aspx",
10931+ "aspx-vb"
10932+ ],
10933+ "extensions": [
10934+ ".asax",
10935+ ".ascx",
10936+ ".ashx",
10937+ ".asmx",
10938+ ".aspx",
10939+ ".axd"
10940+ ],
10941+ "ace_mode": "text",
10942+ "codemirror_mode": "htmlembedded",
10943+ "codemirror_mime_type": "application/x-aspx",
10944+ "language_id": 564186416
10945+ },
10946+ "ATS": {
10947+ "type": "programming",
10948+ "color": "#1ac620",
10949+ "aliases": [
10950+ "ats2"
10951+ ],
10952+ "extensions": [
10953+ ".dats",
10954+ ".hats",
10955+ ".sats"
10956+ ],
10957+ "tm_scope": "source.ats",
10958+ "ace_mode": "ocaml",
10959+ "language_id": 9
10960+ },
10961+ "ActionScript": {
10962+ "type": "programming",
10963+ "tm_scope": "source.actionscript.3",
10964+ "color": "#882B0F",
10965+ "aliases": [
10966+ "actionscript 3",
10967+ "actionscript3",
10968+ "as3"
10969+ ],
10970+ "extensions": [
10971+ ".as"
10972+ ],
10973+ "ace_mode": "actionscript",
10974+ "language_id": 10
10975+ },
10976+ "Ada": {
10977+ "type": "programming",
10978+ "color": "#02f88c",
10979+ "extensions": [
10980+ ".adb",
10981+ ".ada",
10982+ ".ads"
10983+ ],
10984+ "aliases": [
10985+ "ada95",
10986+ "ada2005"
10987+ ],
10988+ "tm_scope": "source.ada",
10989+ "ace_mode": "ada",
10990+ "language_id": 11
10991+ },
10992+ "Adblock Filter List": {
10993+ "type": "data",
10994+ "color": "#800000",
10995+ "ace_mode": "text",
10996+ "extensions": [
10997+ ".txt"
10998+ ],
10999+ "aliases": [
11000+ "ad block filters",
11001+ "ad block",
11002+ "adb",
11003+ "adblock"
11004+ ],
11005+ "tm_scope": "text.adblock",
11006+ "language_id": 884614762
11007+ },
11008+ "Adobe Font Metrics": {
11009+ "type": "data",
11010+ "color": "#fa0f00",
11011+ "tm_scope": "source.afm",
11012+ "extensions": [
11013+ ".afm"
11014+ ],
11015+ "aliases": [
11016+ "acfm",
11017+ "adobe composite font metrics",
11018+ "adobe multiple font metrics",
11019+ "amfm"
11020+ ],
11021+ "ace_mode": "text",
11022+ "language_id": 147198098
11023+ },
11024+ "Agda": {
11025+ "type": "programming",
11026+ "color": "#315665",
11027+ "extensions": [
11028+ ".agda"
11029+ ],
11030+ "tm_scope": "source.agda",
11031+ "ace_mode": "text",
11032+ "language_id": 12
11033+ },
11034+ "Alloy": {
11035+ "type": "programming",
11036+ "color": "#64C800",
11037+ "extensions": [
11038+ ".als"
11039+ ],
11040+ "tm_scope": "source.alloy",
11041+ "ace_mode": "text",
11042+ "language_id": 13
11043+ },
11044+ "Alpine Abuild": {
11045+ "type": "programming",
11046+ "color": "#0D597F",
11047+ "group": "Shell",
11048+ "aliases": [
11049+ "abuild",
11050+ "apkbuild"
11051+ ],
11052+ "filenames": [
11053+ "APKBUILD"
11054+ ],
11055+ "tm_scope": "source.shell",
11056+ "ace_mode": "sh",
11057+ "codemirror_mode": "shell",
11058+ "codemirror_mime_type": "text/x-sh",
11059+ "language_id": 14
11060+ },
11061+ "Altium Designer": {
11062+ "type": "data",
11063+ "color": "#A89663",
11064+ "aliases": [
11065+ "altium"
11066+ ],
11067+ "extensions": [
11068+ ".OutJob",
11069+ ".PcbDoc",
11070+ ".PrjPCB",
11071+ ".SchDoc"
11072+ ],
11073+ "tm_scope": "source.ini",
11074+ "ace_mode": "ini",
11075+ "language_id": 187772328
11076+ },
11077+ "AngelScript": {
11078+ "type": "programming",
11079+ "color": "#C7D7DC",
11080+ "extensions": [
11081+ ".as",
11082+ ".angelscript"
11083+ ],
11084+ "tm_scope": "source.angelscript",
11085+ "ace_mode": "text",
11086+ "codemirror_mode": "clike",
11087+ "codemirror_mime_type": "text/x-c++src",
11088+ "language_id": 389477596
11089+ },
11090+ "Ant Build System": {
11091+ "type": "data",
11092+ "color": "#A9157E",
11093+ "tm_scope": "text.xml.ant",
11094+ "filenames": [
11095+ "ant.xml",
11096+ "build.xml"
11097+ ],
11098+ "ace_mode": "xml",
11099+ "codemirror_mode": "xml",
11100+ "codemirror_mime_type": "application/xml",
11101+ "language_id": 15
11102+ },
11103+ "Antlers": {
11104+ "type": "markup",
11105+ "color": "#ff269e",
11106+ "extensions": [
11107+ ".antlers.html",
11108+ ".antlers.php",
11109+ ".antlers.xml"
11110+ ],
11111+ "tm_scope": "text.html.statamic",
11112+ "ace_mode": "text",
11113+ "language_id": 1067292663
11114+ },
11115+ "ApacheConf": {
11116+ "type": "data",
11117+ "color": "#d12127",
11118+ "aliases": [
11119+ "aconf",
11120+ "apache"
11121+ ],
11122+ "extensions": [
11123+ ".apacheconf",
11124+ ".vhost"
11125+ ],
11126+ "filenames": [
11127+ ".htaccess",
11128+ "apache2.conf",
11129+ "httpd.conf"
11130+ ],
11131+ "tm_scope": "source.apache-config",
11132+ "ace_mode": "apache_conf",
11133+ "language_id": 16
11134+ },
11135+ "Apex": {
11136+ "type": "programming",
11137+ "color": "#1797c0",
11138+ "extensions": [
11139+ ".cls",
11140+ ".trigger"
11141+ ],
11142+ "tm_scope": "source.apex",
11143+ "ace_mode": "java",
11144+ "codemirror_mode": "clike",
11145+ "codemirror_mime_type": "text/x-java",
11146+ "language_id": 17
11147+ },
11148+ "Apollo Guidance Computer": {
11149+ "type": "programming",
11150+ "color": "#0B3D91",
11151+ "group": "Assembly",
11152+ "extensions": [
11153+ ".agc"
11154+ ],
11155+ "tm_scope": "source.agc",
11156+ "ace_mode": "assembly_x86",
11157+ "language_id": 18
11158+ },
11159+ "AppleScript": {
11160+ "type": "programming",
11161+ "aliases": [
11162+ "osascript"
11163+ ],
11164+ "extensions": [
11165+ ".applescript",
11166+ ".scpt"
11167+ ],
11168+ "interpreters": [
11169+ "osascript"
11170+ ],
11171+ "tm_scope": "source.applescript",
11172+ "ace_mode": "applescript",
11173+ "color": "#101F1F",
11174+ "language_id": 19
11175+ },
11176+ "Arc": {
11177+ "type": "programming",
11178+ "color": "#aa2afe",
11179+ "extensions": [
11180+ ".arc"
11181+ ],
11182+ "tm_scope": "none",
11183+ "ace_mode": "text",
11184+ "language_id": 20
11185+ },
11186+ "AsciiDoc": {
11187+ "type": "prose",
11188+ "color": "#73a0c5",
11189+ "ace_mode": "asciidoc",
11190+ "wrap": true,
11191+ "extensions": [
11192+ ".asciidoc",
11193+ ".adoc",
11194+ ".asc"
11195+ ],
11196+ "tm_scope": "text.html.asciidoc",
11197+ "language_id": 22
11198+ },
11199+ "AspectJ": {
11200+ "type": "programming",
11201+ "color": "#a957b0",
11202+ "extensions": [
11203+ ".aj"
11204+ ],
11205+ "tm_scope": "source.aspectj",
11206+ "ace_mode": "text",
11207+ "language_id": 23
11208+ },
11209+ "Assembly": {
11210+ "type": "programming",
11211+ "color": "#6E4C13",
11212+ "aliases": [
11213+ "asm",
11214+ "nasm"
11215+ ],
11216+ "extensions": [
11217+ ".asm",
11218+ ".a51",
11219+ ".i",
11220+ ".inc",
11221+ ".nas",
11222+ ".nasm"
11223+ ],
11224+ "tm_scope": "source.assembly",
11225+ "ace_mode": "assembly_x86",
11226+ "language_id": 24
11227+ },
11228+ "Astro": {
11229+ "type": "markup",
11230+ "color": "#ff5a03",
11231+ "extensions": [
11232+ ".astro"
11233+ ],
11234+ "tm_scope": "source.astro",
11235+ "ace_mode": "html",
11236+ "codemirror_mode": "jsx",
11237+ "codemirror_mime_type": "text/jsx",
11238+ "language_id": 578209015
11239+ },
11240+ "Asymptote": {
11241+ "type": "programming",
11242+ "color": "#ff0000",
11243+ "extensions": [
11244+ ".asy"
11245+ ],
11246+ "interpreters": [
11247+ "asy"
11248+ ],
11249+ "tm_scope": "source.c++",
11250+ "ace_mode": "c_cpp",
11251+ "codemirror_mode": "clike",
11252+ "codemirror_mime_type": "text/x-kotlin",
11253+ "language_id": 591605007
11254+ },
11255+ "Augeas": {
11256+ "type": "programming",
11257+ "color": "#9CC134",
11258+ "extensions": [
11259+ ".aug"
11260+ ],
11261+ "tm_scope": "none",
11262+ "ace_mode": "text",
11263+ "language_id": 25
11264+ },
11265+ "AutoHotkey": {
11266+ "type": "programming",
11267+ "color": "#6594b9",
11268+ "aliases": [
11269+ "ahk"
11270+ ],
11271+ "extensions": [
11272+ ".ahk",
11273+ ".ahkl"
11274+ ],
11275+ "tm_scope": "source.ahk",
11276+ "ace_mode": "autohotkey",
11277+ "language_id": 26
11278+ },
11279+ "AutoIt": {
11280+ "type": "programming",
11281+ "color": "#1C3552",
11282+ "aliases": [
11283+ "au3",
11284+ "AutoIt3",
11285+ "AutoItScript"
11286+ ],
11287+ "extensions": [
11288+ ".au3"
11289+ ],
11290+ "tm_scope": "source.autoit",
11291+ "ace_mode": "autohotkey",
11292+ "language_id": 27
11293+ },
11294+ "Avro IDL": {
11295+ "type": "data",
11296+ "color": "#0040FF",
11297+ "extensions": [
11298+ ".avdl"
11299+ ],
11300+ "tm_scope": "source.avro",
11301+ "ace_mode": "text",
11302+ "language_id": 785497837
11303+ },
11304+ "Awk": {
11305+ "type": "programming",
11306+ "color": "#c30e9b",
11307+ "extensions": [
11308+ ".awk",
11309+ ".auk",
11310+ ".gawk",
11311+ ".mawk",
11312+ ".nawk"
11313+ ],
11314+ "interpreters": [
11315+ "awk",
11316+ "gawk",
11317+ "mawk",
11318+ "nawk"
11319+ ],
11320+ "tm_scope": "source.awk",
11321+ "ace_mode": "text",
11322+ "language_id": 28
11323+ },
11324+ "BASIC": {
11325+ "type": "programming",
11326+ "extensions": [
11327+ ".bas"
11328+ ],
11329+ "tm_scope": "source.basic",
11330+ "ace_mode": "text",
11331+ "color": "#ff0000",
11332+ "language_id": 28923963
11333+ },
11334+ "Ballerina": {
11335+ "type": "programming",
11336+ "extensions": [
11337+ ".bal"
11338+ ],
11339+ "tm_scope": "source.ballerina",
11340+ "ace_mode": "text",
11341+ "color": "#FF5000",
11342+ "language_id": 720859680
11343+ },
11344+ "Batchfile": {
11345+ "type": "programming",
11346+ "aliases": [
11347+ "bat",
11348+ "batch",
11349+ "dosbatch",
11350+ "winbatch"
11351+ ],
11352+ "extensions": [
11353+ ".bat",
11354+ ".cmd"
11355+ ],
11356+ "tm_scope": "source.batchfile",
11357+ "ace_mode": "batchfile",
11358+ "color": "#C1F12E",
11359+ "language_id": 29
11360+ },
11361+ "Beef": {
11362+ "type": "programming",
11363+ "color": "#a52f4e",
11364+ "extensions": [
11365+ ".bf"
11366+ ],
11367+ "tm_scope": "source.cs",
11368+ "ace_mode": "csharp",
11369+ "codemirror_mode": "clike",
11370+ "codemirror_mime_type": "text/x-csharp",
11371+ "language_id": 545626333
11372+ },
11373+ "Befunge": {
11374+ "type": "programming",
11375+ "extensions": [
11376+ ".befunge",
11377+ ".bf"
11378+ ],
11379+ "tm_scope": "source.befunge",
11380+ "ace_mode": "text",
11381+ "language_id": 30
11382+ },
11383+ "Berry": {
11384+ "type": "programming",
11385+ "extensions": [
11386+ ".be"
11387+ ],
11388+ "tm_scope": "source.berry",
11389+ "ace_mode": "text",
11390+ "color": "#15A13C",
11391+ "aliases": [
11392+ "be"
11393+ ],
11394+ "language_id": 121855308
11395+ },
11396+ "BibTeX": {
11397+ "type": "markup",
11398+ "color": "#778899",
11399+ "group": "TeX",
11400+ "extensions": [
11401+ ".bib",
11402+ ".bibtex"
11403+ ],
11404+ "tm_scope": "text.bibtex",
11405+ "ace_mode": "tex",
11406+ "codemirror_mode": "stex",
11407+ "codemirror_mime_type": "text/x-stex",
11408+ "language_id": 982188347
11409+ },
11410+ "Bicep": {
11411+ "type": "programming",
11412+ "color": "#519aba",
11413+ "extensions": [
11414+ ".bicep"
11415+ ],
11416+ "tm_scope": "source.bicep",
11417+ "ace_mode": "text",
11418+ "language_id": 321200902
11419+ },
11420+ "Bikeshed": {
11421+ "type": "markup",
11422+ "color": "#5562ac",
11423+ "extensions": [
11424+ ".bs"
11425+ ],
11426+ "tm_scope": "source.csswg",
11427+ "ace_mode": "html",
11428+ "codemirror_mode": "htmlmixed",
11429+ "codemirror_mime_type": "text/html",
11430+ "language_id": 1055528081
11431+ },
11432+ "Bison": {
11433+ "type": "programming",
11434+ "color": "#6A463F",
11435+ "group": "Yacc",
11436+ "tm_scope": "source.yacc",
11437+ "extensions": [
11438+ ".bison"
11439+ ],
11440+ "ace_mode": "text",
11441+ "language_id": 31
11442+ },
11443+ "BitBake": {
11444+ "type": "programming",
11445+ "color": "#00bce4",
11446+ "tm_scope": "none",
11447+ "extensions": [
11448+ ".bb"
11449+ ],
11450+ "ace_mode": "text",
11451+ "language_id": 32
11452+ },
11453+ "Blade": {
11454+ "type": "markup",
11455+ "color": "#f7523f",
11456+ "extensions": [
11457+ ".blade",
11458+ ".blade.php"
11459+ ],
11460+ "tm_scope": "text.html.php.blade",
11461+ "ace_mode": "text",
11462+ "language_id": 33
11463+ },
11464+ "BlitzBasic": {
11465+ "type": "programming",
11466+ "color": "#00FFAE",
11467+ "aliases": [
11468+ "b3d",
11469+ "blitz3d",
11470+ "blitzplus",
11471+ "bplus"
11472+ ],
11473+ "extensions": [
11474+ ".bb",
11475+ ".decls"
11476+ ],
11477+ "tm_scope": "source.blitzmax",
11478+ "ace_mode": "text",
11479+ "language_id": 34
11480+ },
11481+ "BlitzMax": {
11482+ "type": "programming",
11483+ "color": "#cd6400",
11484+ "extensions": [
11485+ ".bmx"
11486+ ],
11487+ "aliases": [
11488+ "bmax"
11489+ ],
11490+ "tm_scope": "source.blitzmax",
11491+ "ace_mode": "text",
11492+ "language_id": 35
11493+ },
11494+ "Bluespec": {
11495+ "type": "programming",
11496+ "color": "#12223c",
11497+ "extensions": [
11498+ ".bsv"
11499+ ],
11500+ "aliases": [
11501+ "bluespec bsv",
11502+ "bsv"
11503+ ],
11504+ "tm_scope": "source.bsv",
11505+ "ace_mode": "verilog",
11506+ "codemirror_mode": "verilog",
11507+ "codemirror_mime_type": "text/x-systemverilog",
11508+ "language_id": 36
11509+ },
11510+ "Bluespec BH": {
11511+ "type": "programming",
11512+ "group": "Bluespec",
11513+ "color": "#12223c",
11514+ "extensions": [
11515+ ".bs"
11516+ ],
11517+ "aliases": [
11518+ "bh",
11519+ "bluespec classic"
11520+ ],
11521+ "tm_scope": "source.haskell",
11522+ "ace_mode": "haskell",
11523+ "codemirror_mode": "haskell",
11524+ "codemirror_mime_type": "text/x-haskell",
11525+ "language_id": 641580358
11526+ },
11527+ "Boo": {
11528+ "type": "programming",
11529+ "color": "#d4bec1",
11530+ "extensions": [
11531+ ".boo"
11532+ ],
11533+ "ace_mode": "text",
11534+ "tm_scope": "source.boo",
11535+ "language_id": 37
11536+ },
11537+ "Boogie": {
11538+ "type": "programming",
11539+ "color": "#c80fa0",
11540+ "extensions": [
11541+ ".bpl"
11542+ ],
11543+ "interpreters": [
11544+ "boogie"
11545+ ],
11546+ "tm_scope": "source.boogie",
11547+ "ace_mode": "text",
11548+ "language_id": 955017407
11549+ },
11550+ "Brainfuck": {
11551+ "type": "programming",
11552+ "color": "#2F2530",
11553+ "extensions": [
11554+ ".b",
11555+ ".bf"
11556+ ],
11557+ "tm_scope": "source.bf",
11558+ "ace_mode": "text",
11559+ "codemirror_mode": "brainfuck",
11560+ "codemirror_mime_type": "text/x-brainfuck",
11561+ "language_id": 38
11562+ },
11563+ "BrighterScript": {
11564+ "type": "programming",
11565+ "color": "#66AABB",
11566+ "extensions": [
11567+ ".bs"
11568+ ],
11569+ "tm_scope": "source.brs",
11570+ "ace_mode": "text",
11571+ "language_id": 943571030
11572+ },
11573+ "Brightscript": {
11574+ "type": "programming",
11575+ "color": "#662D91",
11576+ "extensions": [
11577+ ".brs"
11578+ ],
11579+ "tm_scope": "source.brs",
11580+ "ace_mode": "text",
11581+ "language_id": 39
11582+ },
11583+ "Browserslist": {
11584+ "type": "data",
11585+ "color": "#ffd539",
11586+ "filenames": [
11587+ ".browserslistrc",
11588+ "browserslist"
11589+ ],
11590+ "tm_scope": "text.browserslist",
11591+ "ace_mode": "text",
11592+ "language_id": 153503348
11593+ },
11594+ "C": {
11595+ "type": "programming",
11596+ "color": "#555555",
11597+ "extensions": [
11598+ ".c",
11599+ ".cats",
11600+ ".h",
11601+ ".idc"
11602+ ],
11603+ "interpreters": [
11604+ "tcc"
11605+ ],
11606+ "tm_scope": "source.c",
11607+ "ace_mode": "c_cpp",
11608+ "codemirror_mode": "clike",
11609+ "codemirror_mime_type": "text/x-csrc",
11610+ "language_id": 41
11611+ },
11612+ "C#": {
11613+ "type": "programming",
11614+ "ace_mode": "csharp",
11615+ "codemirror_mode": "clike",
11616+ "codemirror_mime_type": "text/x-csharp",
11617+ "tm_scope": "source.cs",
11618+ "color": "#178600",
11619+ "aliases": [
11620+ "csharp",
11621+ "cake",
11622+ "cakescript"
11623+ ],
11624+ "extensions": [
11625+ ".cs",
11626+ ".cake",
11627+ ".csx",
11628+ ".linq"
11629+ ],
11630+ "language_id": 42
11631+ },
11632+ "C++": {
11633+ "type": "programming",
11634+ "tm_scope": "source.c++",
11635+ "ace_mode": "c_cpp",
11636+ "codemirror_mode": "clike",
11637+ "codemirror_mime_type": "text/x-c++src",
11638+ "color": "#f34b7d",
11639+ "aliases": [
11640+ "cpp"
11641+ ],
11642+ "extensions": [
11643+ ".cpp",
11644+ ".c++",
11645+ ".cc",
11646+ ".cp",
11647+ ".cppm",
11648+ ".cxx",
11649+ ".h",
11650+ ".h++",
11651+ ".hh",
11652+ ".hpp",
11653+ ".hxx",
11654+ ".inc",
11655+ ".inl",
11656+ ".ino",
11657+ ".ipp",
11658+ ".ixx",
11659+ ".re",
11660+ ".tcc",
11661+ ".tpp",
11662+ ".txx"
11663+ ],
11664+ "language_id": 43
11665+ },
11666+ "C-ObjDump": {
11667+ "type": "data",
11668+ "extensions": [
11669+ ".c-objdump"
11670+ ],
11671+ "tm_scope": "objdump.x86asm",
11672+ "ace_mode": "assembly_x86",
11673+ "language_id": 44
11674+ },
11675+ "C2hs Haskell": {
11676+ "type": "programming",
11677+ "group": "Haskell",
11678+ "aliases": [
11679+ "c2hs"
11680+ ],
11681+ "extensions": [
11682+ ".chs"
11683+ ],
11684+ "tm_scope": "source.haskell",
11685+ "ace_mode": "haskell",
11686+ "codemirror_mode": "haskell",
11687+ "codemirror_mime_type": "text/x-haskell",
11688+ "language_id": 45
11689+ },
11690+ "CAP CDS": {
11691+ "type": "programming",
11692+ "tm_scope": "source.cds",
11693+ "color": "#0092d1",
11694+ "aliases": [
11695+ "cds"
11696+ ],
11697+ "extensions": [
11698+ ".cds"
11699+ ],
11700+ "ace_mode": "text",
11701+ "language_id": 390788699
11702+ },
11703+ "CIL": {
11704+ "type": "data",
11705+ "tm_scope": "source.cil",
11706+ "extensions": [
11707+ ".cil"
11708+ ],
11709+ "ace_mode": "text",
11710+ "language_id": 29176339
11711+ },
11712+ "CLIPS": {
11713+ "type": "programming",
11714+ "color": "#00A300",
11715+ "extensions": [
11716+ ".clp"
11717+ ],
11718+ "tm_scope": "source.clips",
11719+ "ace_mode": "text",
11720+ "language_id": 46
11721+ },
11722+ "CMake": {
11723+ "type": "programming",
11724+ "color": "#DA3434",
11725+ "extensions": [
11726+ ".cmake",
11727+ ".cmake.in"
11728+ ],
11729+ "filenames": [
11730+ "CMakeLists.txt"
11731+ ],
11732+ "tm_scope": "source.cmake",
11733+ "ace_mode": "text",
11734+ "codemirror_mode": "cmake",
11735+ "codemirror_mime_type": "text/x-cmake",
11736+ "language_id": 47
11737+ },
11738+ "COBOL": {
11739+ "type": "programming",
11740+ "extensions": [
11741+ ".cob",
11742+ ".cbl",
11743+ ".ccp",
11744+ ".cobol",
11745+ ".cpy"
11746+ ],
11747+ "tm_scope": "source.cobol",
11748+ "ace_mode": "cobol",
11749+ "codemirror_mode": "cobol",
11750+ "codemirror_mime_type": "text/x-cobol",
11751+ "language_id": 48
11752+ },
11753+ "CODEOWNERS": {
11754+ "type": "data",
11755+ "filenames": [
11756+ "CODEOWNERS"
11757+ ],
11758+ "tm_scope": "text.codeowners",
11759+ "ace_mode": "gitignore",
11760+ "language_id": 321684729
11761+ },
11762+ "COLLADA": {
11763+ "type": "data",
11764+ "color": "#F1A42B",
11765+ "extensions": [
11766+ ".dae"
11767+ ],
11768+ "tm_scope": "text.xml",
11769+ "ace_mode": "xml",
11770+ "codemirror_mode": "xml",
11771+ "codemirror_mime_type": "text/xml",
11772+ "language_id": 49
11773+ },
11774+ "CSON": {
11775+ "type": "data",
11776+ "color": "#244776",
11777+ "tm_scope": "source.coffee",
11778+ "ace_mode": "coffee",
11779+ "codemirror_mode": "coffeescript",
11780+ "codemirror_mime_type": "text/x-coffeescript",
11781+ "extensions": [
11782+ ".cson"
11783+ ],
11784+ "language_id": 424
11785+ },
11786+ "CSS": {
11787+ "type": "markup",
11788+ "tm_scope": "source.css",
11789+ "ace_mode": "css",
11790+ "codemirror_mode": "css",
11791+ "codemirror_mime_type": "text/css",
11792+ "color": "#563d7c",
11793+ "extensions": [
11794+ ".css"
11795+ ],
11796+ "language_id": 50
11797+ },
11798+ "CSV": {
11799+ "type": "data",
11800+ "color": "#237346",
11801+ "ace_mode": "text",
11802+ "tm_scope": "none",
11803+ "extensions": [
11804+ ".csv"
11805+ ],
11806+ "language_id": 51
11807+ },
11808+ "CUE": {
11809+ "type": "programming",
11810+ "extensions": [
11811+ ".cue"
11812+ ],
11813+ "tm_scope": "source.cue",
11814+ "ace_mode": "text",
11815+ "color": "#5886E1",
11816+ "language_id": 356063509
11817+ },
11818+ "CWeb": {
11819+ "type": "programming",
11820+ "color": "#00007a",
11821+ "extensions": [
11822+ ".w"
11823+ ],
11824+ "tm_scope": "none",
11825+ "ace_mode": "text",
11826+ "language_id": 657332628
11827+ },
11828+ "Cabal Config": {
11829+ "type": "data",
11830+ "color": "#483465",
11831+ "aliases": [
11832+ "Cabal"
11833+ ],
11834+ "extensions": [
11835+ ".cabal"
11836+ ],
11837+ "filenames": [
11838+ "cabal.config",
11839+ "cabal.project"
11840+ ],
11841+ "ace_mode": "haskell",
11842+ "codemirror_mode": "haskell",
11843+ "codemirror_mime_type": "text/x-haskell",
11844+ "tm_scope": "source.cabal",
11845+ "language_id": 677095381
11846+ },
11847+ "Cadence": {
11848+ "type": "programming",
11849+ "color": "#00ef8b",
11850+ "ace_mode": "text",
11851+ "tm_scope": "source.cadence",
11852+ "extensions": [
11853+ ".cdc"
11854+ ],
11855+ "language_id": 270184138
11856+ },
11857+ "Cairo": {
11858+ "type": "programming",
11859+ "color": "#ff4a48",
11860+ "ace_mode": "text",
11861+ "tm_scope": "source.cairo",
11862+ "extensions": [
11863+ ".cairo"
11864+ ],
11865+ "language_id": 620599567
11866+ },
11867+ "CameLIGO": {
11868+ "type": "programming",
11869+ "color": "#3be133",
11870+ "extensions": [
11871+ ".mligo"
11872+ ],
11873+ "tm_scope": "source.mligo",
11874+ "ace_mode": "ocaml",
11875+ "codemirror_mode": "mllike",
11876+ "codemirror_mime_type": "text/x-ocaml",
11877+ "group": "LigoLANG",
11878+ "language_id": 829207807
11879+ },
11880+ "Cap'n Proto": {
11881+ "type": "programming",
11882+ "color": "#c42727",
11883+ "tm_scope": "source.capnp",
11884+ "extensions": [
11885+ ".capnp"
11886+ ],
11887+ "ace_mode": "text",
11888+ "language_id": 52
11889+ },
11890+ "CartoCSS": {
11891+ "type": "programming",
11892+ "aliases": [
11893+ "Carto"
11894+ ],
11895+ "extensions": [
11896+ ".mss"
11897+ ],
11898+ "ace_mode": "text",
11899+ "tm_scope": "source.css.mss",
11900+ "language_id": 53
11901+ },
11902+ "Ceylon": {
11903+ "type": "programming",
11904+ "color": "#dfa535",
11905+ "extensions": [
11906+ ".ceylon"
11907+ ],
11908+ "tm_scope": "source.ceylon",
11909+ "ace_mode": "text",
11910+ "language_id": 54
11911+ },
11912+ "Chapel": {
11913+ "type": "programming",
11914+ "color": "#8dc63f",
11915+ "aliases": [
11916+ "chpl"
11917+ ],
11918+ "extensions": [
11919+ ".chpl"
11920+ ],
11921+ "tm_scope": "source.chapel",
11922+ "ace_mode": "text",
11923+ "language_id": 55
11924+ },
11925+ "Charity": {
11926+ "type": "programming",
11927+ "extensions": [
11928+ ".ch"
11929+ ],
11930+ "tm_scope": "none",
11931+ "ace_mode": "text",
11932+ "language_id": 56
11933+ },
11934+ "Checksums": {
11935+ "type": "data",
11936+ "tm_scope": "text.checksums",
11937+ "aliases": [
11938+ "checksum",
11939+ "hash",
11940+ "hashes",
11941+ "sum",
11942+ "sums"
11943+ ],
11944+ "filenames": [
11945+ "MD5SUMS",
11946+ "SHA1SUMS",
11947+ "SHA256SUMS",
11948+ "SHA256SUMS.txt",
11949+ "SHA512SUMS",
11950+ "checksums.txt",
11951+ "cksums",
11952+ "md5sum.txt"
11953+ ],
11954+ "extensions": [
11955+ ".crc32",
11956+ ".md2",
11957+ ".md4",
11958+ ".md5",
11959+ ".sha1",
11960+ ".sha2",
11961+ ".sha224",
11962+ ".sha256",
11963+ ".sha256sum",
11964+ ".sha3",
11965+ ".sha384",
11966+ ".sha512"
11967+ ],
11968+ "ace_mode": "text",
11969+ "language_id": 372063053
11970+ },
11971+ "ChucK": {
11972+ "type": "programming",
11973+ "color": "#3f8000",
11974+ "extensions": [
11975+ ".ck"
11976+ ],
11977+ "tm_scope": "source.java",
11978+ "ace_mode": "java",
11979+ "codemirror_mode": "clike",
11980+ "codemirror_mime_type": "text/x-java",
11981+ "language_id": 57
11982+ },
11983+ "Circom": {
11984+ "type": "programming",
11985+ "ace_mode": "text",
11986+ "extensions": [
11987+ ".circom"
11988+ ],
11989+ "color": "#707575",
11990+ "tm_scope": "source.circom",
11991+ "language_id": 1042332086
11992+ },
11993+ "Cirru": {
11994+ "type": "programming",
11995+ "color": "#ccccff",
11996+ "tm_scope": "source.cirru",
11997+ "ace_mode": "cirru",
11998+ "extensions": [
11999+ ".cirru"
12000+ ],
12001+ "language_id": 58
12002+ },
12003+ "Clarion": {
12004+ "type": "programming",
12005+ "color": "#db901e",
12006+ "ace_mode": "text",
12007+ "extensions": [
12008+ ".clw"
12009+ ],
12010+ "tm_scope": "source.clarion",
12011+ "language_id": 59
12012+ },
12013+ "Clarity": {
12014+ "type": "programming",
12015+ "color": "#5546ff",
12016+ "ace_mode": "lisp",
12017+ "extensions": [
12018+ ".clar"
12019+ ],
12020+ "tm_scope": "source.clar",
12021+ "language_id": 91493841
12022+ },
12023+ "Classic ASP": {
12024+ "type": "programming",
12025+ "color": "#6a40fd",
12026+ "tm_scope": "text.html.asp",
12027+ "aliases": [
12028+ "asp"
12029+ ],
12030+ "extensions": [
12031+ ".asp"
12032+ ],
12033+ "ace_mode": "text",
12034+ "language_id": 8
12035+ },
12036+ "Clean": {
12037+ "type": "programming",
12038+ "color": "#3F85AF",
12039+ "extensions": [
12040+ ".icl",
12041+ ".dcl"
12042+ ],
12043+ "tm_scope": "source.clean",
12044+ "ace_mode": "text",
12045+ "language_id": 60
12046+ },
12047+ "Click": {
12048+ "type": "programming",
12049+ "color": "#E4E6F3",
12050+ "extensions": [
12051+ ".click"
12052+ ],
12053+ "tm_scope": "source.click",
12054+ "ace_mode": "text",
12055+ "language_id": 61
12056+ },
12057+ "Clojure": {
12058+ "type": "programming",
12059+ "tm_scope": "source.clojure",
12060+ "ace_mode": "clojure",
12061+ "codemirror_mode": "clojure",
12062+ "codemirror_mime_type": "text/x-clojure",
12063+ "color": "#db5855",
12064+ "extensions": [
12065+ ".clj",
12066+ ".bb",
12067+ ".boot",
12068+ ".cl2",
12069+ ".cljc",
12070+ ".cljs",
12071+ ".cljs.hl",
12072+ ".cljscm",
12073+ ".cljx",
12074+ ".hic"
12075+ ],
12076+ "filenames": [
12077+ "riemann.config"
12078+ ],
12079+ "interpreters": [
12080+ "bb"
12081+ ],
12082+ "language_id": 62
12083+ },
12084+ "Closure Templates": {
12085+ "type": "markup",
12086+ "color": "#0d948f",
12087+ "ace_mode": "soy_template",
12088+ "codemirror_mode": "soy",
12089+ "codemirror_mime_type": "text/x-soy",
12090+ "aliases": [
12091+ "soy"
12092+ ],
12093+ "extensions": [
12094+ ".soy"
12095+ ],
12096+ "tm_scope": "text.html.soy",
12097+ "language_id": 357046146
12098+ },
12099+ "Cloud Firestore Security Rules": {
12100+ "type": "data",
12101+ "color": "#FFA000",
12102+ "ace_mode": "less",
12103+ "codemirror_mode": "css",
12104+ "codemirror_mime_type": "text/css",
12105+ "tm_scope": "source.firestore",
12106+ "filenames": [
12107+ "firestore.rules"
12108+ ],
12109+ "language_id": 407996372
12110+ },
12111+ "CoNLL-U": {
12112+ "type": "data",
12113+ "extensions": [
12114+ ".conllu",
12115+ ".conll"
12116+ ],
12117+ "tm_scope": "text.conllu",
12118+ "ace_mode": "text",
12119+ "aliases": [
12120+ "CoNLL",
12121+ "CoNLL-X"
12122+ ],
12123+ "language_id": 421026389
12124+ },
12125+ "CodeQL": {
12126+ "type": "programming",
12127+ "color": "#140f46",
12128+ "extensions": [
12129+ ".ql",
12130+ ".qll"
12131+ ],
12132+ "tm_scope": "source.ql",
12133+ "ace_mode": "text",
12134+ "language_id": 424259634,
12135+ "aliases": [
12136+ "ql"
12137+ ]
12138+ },
12139+ "CoffeeScript": {
12140+ "type": "programming",
12141+ "tm_scope": "source.coffee",
12142+ "ace_mode": "coffee",
12143+ "codemirror_mode": "coffeescript",
12144+ "codemirror_mime_type": "text/x-coffeescript",
12145+ "color": "#244776",
12146+ "aliases": [
12147+ "coffee",
12148+ "coffee-script"
12149+ ],
12150+ "extensions": [
12151+ ".coffee",
12152+ "._coffee",
12153+ ".cake",
12154+ ".cjsx",
12155+ ".iced"
12156+ ],
12157+ "filenames": [
12158+ "Cakefile"
12159+ ],
12160+ "interpreters": [
12161+ "coffee"
12162+ ],
12163+ "language_id": 63
12164+ },
12165+ "ColdFusion": {
12166+ "type": "programming",
12167+ "ace_mode": "coldfusion",
12168+ "color": "#ed2cd6",
12169+ "aliases": [
12170+ "cfm",
12171+ "cfml",
12172+ "coldfusion html"
12173+ ],
12174+ "extensions": [
12175+ ".cfm",
12176+ ".cfml"
12177+ ],
12178+ "tm_scope": "text.html.cfm",
12179+ "language_id": 64
12180+ },
12181+ "ColdFusion CFC": {
12182+ "type": "programming",
12183+ "color": "#ed2cd6",
12184+ "group": "ColdFusion",
12185+ "ace_mode": "coldfusion",
12186+ "aliases": [
12187+ "cfc"
12188+ ],
12189+ "extensions": [
12190+ ".cfc"
12191+ ],
12192+ "tm_scope": "source.cfscript",
12193+ "language_id": 65
12194+ },
12195+ "Common Lisp": {
12196+ "type": "programming",
12197+ "tm_scope": "source.lisp",
12198+ "color": "#3fb68b",
12199+ "aliases": [
12200+ "lisp"
12201+ ],
12202+ "extensions": [
12203+ ".lisp",
12204+ ".asd",
12205+ ".cl",
12206+ ".l",
12207+ ".lsp",
12208+ ".ny",
12209+ ".podsl",
12210+ ".sexp"
12211+ ],
12212+ "interpreters": [
12213+ "lisp",
12214+ "sbcl",
12215+ "ccl",
12216+ "clisp",
12217+ "ecl"
12218+ ],
12219+ "ace_mode": "lisp",
12220+ "codemirror_mode": "commonlisp",
12221+ "codemirror_mime_type": "text/x-common-lisp",
12222+ "language_id": 66
12223+ },
12224+ "Common Workflow Language": {
12225+ "aliases": [
12226+ "cwl"
12227+ ],
12228+ "type": "programming",
12229+ "ace_mode": "yaml",
12230+ "codemirror_mode": "yaml",
12231+ "codemirror_mime_type": "text/x-yaml",
12232+ "extensions": [
12233+ ".cwl"
12234+ ],
12235+ "interpreters": [
12236+ "cwl-runner"
12237+ ],
12238+ "color": "#B5314C",
12239+ "tm_scope": "source.cwl",
12240+ "language_id": 988547172
12241+ },
12242+ "Component Pascal": {
12243+ "type": "programming",
12244+ "color": "#B0CE4E",
12245+ "extensions": [
12246+ ".cp",
12247+ ".cps"
12248+ ],
12249+ "tm_scope": "source.pascal",
12250+ "ace_mode": "pascal",
12251+ "codemirror_mode": "pascal",
12252+ "codemirror_mime_type": "text/x-pascal",
12253+ "language_id": 67
12254+ },
12255+ "Cool": {
12256+ "type": "programming",
12257+ "extensions": [
12258+ ".cl"
12259+ ],
12260+ "tm_scope": "source.cool",
12261+ "ace_mode": "text",
12262+ "language_id": 68
12263+ },
12264+ "Coq": {
12265+ "type": "programming",
12266+ "color": "#d0b68c",
12267+ "extensions": [
12268+ ".coq",
12269+ ".v"
12270+ ],
12271+ "tm_scope": "source.coq",
12272+ "ace_mode": "text",
12273+ "language_id": 69
12274+ },
12275+ "Cpp-ObjDump": {
12276+ "type": "data",
12277+ "extensions": [
12278+ ".cppobjdump",
12279+ ".c++-objdump",
12280+ ".c++objdump",
12281+ ".cpp-objdump",
12282+ ".cxx-objdump"
12283+ ],
12284+ "tm_scope": "objdump.x86asm",
12285+ "aliases": [
12286+ "c++-objdump"
12287+ ],
12288+ "ace_mode": "assembly_x86",
12289+ "language_id": 70
12290+ },
12291+ "Creole": {
12292+ "type": "prose",
12293+ "wrap": true,
12294+ "extensions": [
12295+ ".creole"
12296+ ],
12297+ "tm_scope": "text.html.creole",
12298+ "ace_mode": "text",
12299+ "language_id": 71
12300+ },
12301+ "Crystal": {
12302+ "type": "programming",
12303+ "color": "#000100",
12304+ "extensions": [
12305+ ".cr"
12306+ ],
12307+ "ace_mode": "ruby",
12308+ "codemirror_mode": "crystal",
12309+ "codemirror_mime_type": "text/x-crystal",
12310+ "tm_scope": "source.crystal",
12311+ "interpreters": [
12312+ "crystal"
12313+ ],
12314+ "language_id": 72
12315+ },
12316+ "Csound": {
12317+ "type": "programming",
12318+ "color": "#1a1a1a",
12319+ "aliases": [
12320+ "csound-orc"
12321+ ],
12322+ "extensions": [
12323+ ".orc",
12324+ ".udo"
12325+ ],
12326+ "tm_scope": "source.csound",
12327+ "ace_mode": "csound_orchestra",
12328+ "language_id": 73
12329+ },
12330+ "Csound Document": {
12331+ "type": "programming",
12332+ "color": "#1a1a1a",
12333+ "aliases": [
12334+ "csound-csd"
12335+ ],
12336+ "extensions": [
12337+ ".csd"
12338+ ],
12339+ "tm_scope": "source.csound-document",
12340+ "ace_mode": "csound_document",
12341+ "language_id": 74
12342+ },
12343+ "Csound Score": {
12344+ "type": "programming",
12345+ "color": "#1a1a1a",
12346+ "aliases": [
12347+ "csound-sco"
12348+ ],
12349+ "extensions": [
12350+ ".sco"
12351+ ],
12352+ "tm_scope": "source.csound-score",
12353+ "ace_mode": "csound_score",
12354+ "language_id": 75
12355+ },
12356+ "Cuda": {
12357+ "type": "programming",
12358+ "extensions": [
12359+ ".cu",
12360+ ".cuh"
12361+ ],
12362+ "tm_scope": "source.cuda-c++",
12363+ "ace_mode": "c_cpp",
12364+ "codemirror_mode": "clike",
12365+ "codemirror_mime_type": "text/x-c++src",
12366+ "color": "#3A4E3A",
12367+ "language_id": 77
12368+ },
12369+ "Cue Sheet": {
12370+ "type": "data",
12371+ "extensions": [
12372+ ".cue"
12373+ ],
12374+ "tm_scope": "source.cuesheet",
12375+ "ace_mode": "text",
12376+ "language_id": 942714150
12377+ },
12378+ "Curry": {
12379+ "type": "programming",
12380+ "color": "#531242",
12381+ "extensions": [
12382+ ".curry"
12383+ ],
12384+ "tm_scope": "source.curry",
12385+ "ace_mode": "haskell",
12386+ "language_id": 439829048
12387+ },
12388+ "Cycript": {
12389+ "type": "programming",
12390+ "extensions": [
12391+ ".cy"
12392+ ],
12393+ "tm_scope": "source.js",
12394+ "ace_mode": "javascript",
12395+ "codemirror_mode": "javascript",
12396+ "codemirror_mime_type": "text/javascript",
12397+ "language_id": 78
12398+ },
12399+ "Cypher": {
12400+ "type": "programming",
12401+ "color": "#34c0eb",
12402+ "extensions": [
12403+ ".cyp",
12404+ ".cypher"
12405+ ],
12406+ "tm_scope": "source.cypher",
12407+ "ace_mode": "text",
12408+ "language_id": 850806976
12409+ },
12410+ "Cython": {
12411+ "type": "programming",
12412+ "color": "#fedf5b",
12413+ "extensions": [
12414+ ".pyx",
12415+ ".pxd",
12416+ ".pxi"
12417+ ],
12418+ "aliases": [
12419+ "pyrex"
12420+ ],
12421+ "tm_scope": "source.cython",
12422+ "ace_mode": "text",
12423+ "codemirror_mode": "python",
12424+ "codemirror_mime_type": "text/x-cython",
12425+ "language_id": 79
12426+ },
12427+ "D": {
12428+ "type": "programming",
12429+ "color": "#ba595e",
12430+ "aliases": [
12431+ "Dlang"
12432+ ],
12433+ "extensions": [
12434+ ".d",
12435+ ".di"
12436+ ],
12437+ "tm_scope": "source.d",
12438+ "ace_mode": "d",
12439+ "codemirror_mode": "d",
12440+ "codemirror_mime_type": "text/x-d",
12441+ "language_id": 80
12442+ },
12443+ "D-ObjDump": {
12444+ "type": "data",
12445+ "extensions": [
12446+ ".d-objdump"
12447+ ],
12448+ "tm_scope": "objdump.x86asm",
12449+ "ace_mode": "assembly_x86",
12450+ "language_id": 81
12451+ },
12452+ "D2": {
12453+ "type": "markup",
12454+ "color": "#526ee8",
12455+ "extensions": [
12456+ ".d2"
12457+ ],
12458+ "aliases": [
12459+ "d2lang"
12460+ ],
12461+ "tm_scope": "source.d2",
12462+ "ace_mode": "text",
12463+ "language_id": 37531557
12464+ },
12465+ "DIGITAL Command Language": {
12466+ "type": "programming",
12467+ "aliases": [
12468+ "dcl"
12469+ ],
12470+ "extensions": [
12471+ ".com"
12472+ ],
12473+ "tm_scope": "none",
12474+ "ace_mode": "text",
12475+ "language_id": 82
12476+ },
12477+ "DM": {
12478+ "type": "programming",
12479+ "color": "#447265",
12480+ "extensions": [
12481+ ".dm"
12482+ ],
12483+ "aliases": [
12484+ "byond"
12485+ ],
12486+ "tm_scope": "source.dm",
12487+ "ace_mode": "c_cpp",
12488+ "language_id": 83
12489+ },
12490+ "DNS Zone": {
12491+ "type": "data",
12492+ "extensions": [
12493+ ".zone",
12494+ ".arpa"
12495+ ],
12496+ "tm_scope": "text.zone_file",
12497+ "ace_mode": "text",
12498+ "language_id": 84
12499+ },
12500+ "DTrace": {
12501+ "type": "programming",
12502+ "aliases": [
12503+ "dtrace-script"
12504+ ],
12505+ "extensions": [
12506+ ".d"
12507+ ],
12508+ "interpreters": [
12509+ "dtrace"
12510+ ],
12511+ "tm_scope": "source.c",
12512+ "ace_mode": "c_cpp",
12513+ "codemirror_mode": "clike",
12514+ "codemirror_mime_type": "text/x-csrc",
12515+ "language_id": 85
12516+ },
12517+ "Dafny": {
12518+ "type": "programming",
12519+ "color": "#FFEC25",
12520+ "extensions": [
12521+ ".dfy"
12522+ ],
12523+ "interpreters": [
12524+ "dafny"
12525+ ],
12526+ "tm_scope": "text.dfy.dafny",
12527+ "ace_mode": "text",
12528+ "language_id": 969323346
12529+ },
12530+ "Darcs Patch": {
12531+ "type": "data",
12532+ "color": "#8eff23",
12533+ "aliases": [
12534+ "dpatch"
12535+ ],
12536+ "extensions": [
12537+ ".darcspatch",
12538+ ".dpatch"
12539+ ],
12540+ "tm_scope": "none",
12541+ "ace_mode": "text",
12542+ "language_id": 86
12543+ },
12544+ "Dart": {
12545+ "type": "programming",
12546+ "color": "#00B4AB",
12547+ "extensions": [
12548+ ".dart"
12549+ ],
12550+ "interpreters": [
12551+ "dart"
12552+ ],
12553+ "tm_scope": "source.dart",
12554+ "ace_mode": "dart",
12555+ "codemirror_mode": "dart",
12556+ "codemirror_mime_type": "application/dart",
12557+ "language_id": 87
12558+ },
12559+ "DataWeave": {
12560+ "type": "programming",
12561+ "color": "#003a52",
12562+ "extensions": [
12563+ ".dwl"
12564+ ],
12565+ "ace_mode": "text",
12566+ "tm_scope": "source.data-weave",
12567+ "language_id": 974514097
12568+ },
12569+ "Debian Package Control File": {
12570+ "type": "data",
12571+ "color": "#D70751",
12572+ "extensions": [
12573+ ".dsc"
12574+ ],
12575+ "tm_scope": "source.deb-control",
12576+ "ace_mode": "text",
12577+ "language_id": 527438264
12578+ },
12579+ "DenizenScript": {
12580+ "type": "programming",
12581+ "color": "#FBEE96",
12582+ "ace_mode": "yaml",
12583+ "codemirror_mode": "yaml",
12584+ "codemirror_mime_type": "text/x-yaml",
12585+ "extensions": [
12586+ ".dsc"
12587+ ],
12588+ "tm_scope": "source.denizenscript",
12589+ "language_id": 435000929
12590+ },
12591+ "Dhall": {
12592+ "type": "programming",
12593+ "color": "#dfafff",
12594+ "extensions": [
12595+ ".dhall"
12596+ ],
12597+ "tm_scope": "source.haskell",
12598+ "ace_mode": "haskell",
12599+ "codemirror_mode": "haskell",
12600+ "codemirror_mime_type": "text/x-haskell",
12601+ "language_id": 793969321
12602+ },
12603+ "Diff": {
12604+ "type": "data",
12605+ "extensions": [
12606+ ".diff",
12607+ ".patch"
12608+ ],
12609+ "aliases": [
12610+ "udiff"
12611+ ],
12612+ "tm_scope": "source.diff",
12613+ "ace_mode": "diff",
12614+ "codemirror_mode": "diff",
12615+ "codemirror_mime_type": "text/x-diff",
12616+ "language_id": 88
12617+ },
12618+ "DirectX 3D File": {
12619+ "type": "data",
12620+ "color": "#aace60",
12621+ "extensions": [
12622+ ".x"
12623+ ],
12624+ "ace_mode": "text",
12625+ "tm_scope": "none",
12626+ "language_id": 201049282
12627+ },
12628+ "Dockerfile": {
12629+ "type": "programming",
12630+ "aliases": [
12631+ "Containerfile"
12632+ ],
12633+ "color": "#384d54",
12634+ "tm_scope": "source.dockerfile",
12635+ "extensions": [
12636+ ".dockerfile"
12637+ ],
12638+ "filenames": [
12639+ "Containerfile",
12640+ "Dockerfile"
12641+ ],
12642+ "ace_mode": "dockerfile",
12643+ "codemirror_mode": "dockerfile",
12644+ "codemirror_mime_type": "text/x-dockerfile",
12645+ "language_id": 89
12646+ },
12647+ "Dogescript": {
12648+ "type": "programming",
12649+ "color": "#cca760",
12650+ "extensions": [
12651+ ".djs"
12652+ ],
12653+ "tm_scope": "none",
12654+ "ace_mode": "text",
12655+ "language_id": 90
12656+ },
12657+ "Dotenv": {
12658+ "type": "data",
12659+ "color": "#e5d559",
12660+ "extensions": [
12661+ ".env"
12662+ ],
12663+ "filenames": [
12664+ ".env",
12665+ ".env.ci",
12666+ ".env.dev",
12667+ ".env.development",
12668+ ".env.development.local",
12669+ ".env.example",
12670+ ".env.local",
12671+ ".env.prod",
12672+ ".env.production",
12673+ ".env.staging",
12674+ ".env.test",
12675+ ".env.testing"
12676+ ],
12677+ "tm_scope": "source.dotenv",
12678+ "ace_mode": "text",
12679+ "language_id": 111148035
12680+ },
12681+ "Dylan": {
12682+ "type": "programming",
12683+ "color": "#6c616e",
12684+ "extensions": [
12685+ ".dylan",
12686+ ".dyl",
12687+ ".intr",
12688+ ".lid"
12689+ ],
12690+ "tm_scope": "source.dylan",
12691+ "ace_mode": "text",
12692+ "codemirror_mode": "dylan",
12693+ "codemirror_mime_type": "text/x-dylan",
12694+ "language_id": 91
12695+ },
12696+ "E": {
12697+ "type": "programming",
12698+ "color": "#ccce35",
12699+ "extensions": [
12700+ ".e"
12701+ ],
12702+ "interpreters": [
12703+ "rune"
12704+ ],
12705+ "tm_scope": "none",
12706+ "ace_mode": "text",
12707+ "language_id": 92
12708+ },
12709+ "E-mail": {
12710+ "type": "data",
12711+ "aliases": [
12712+ "email",
12713+ "eml",
12714+ "mail",
12715+ "mbox"
12716+ ],
12717+ "extensions": [
12718+ ".eml",
12719+ ".mbox"
12720+ ],
12721+ "tm_scope": "text.eml.basic",
12722+ "ace_mode": "text",
12723+ "codemirror_mode": "mbox",
12724+ "codemirror_mime_type": "application/mbox",
12725+ "language_id": 529653389
12726+ },
12727+ "EBNF": {
12728+ "type": "data",
12729+ "extensions": [
12730+ ".ebnf"
12731+ ],
12732+ "tm_scope": "source.ebnf",
12733+ "ace_mode": "text",
12734+ "codemirror_mode": "ebnf",
12735+ "codemirror_mime_type": "text/x-ebnf",
12736+ "language_id": 430
12737+ },
12738+ "ECL": {
12739+ "type": "programming",
12740+ "color": "#8a1267",
12741+ "extensions": [
12742+ ".ecl",
12743+ ".eclxml"
12744+ ],
12745+ "tm_scope": "source.ecl",
12746+ "ace_mode": "text",
12747+ "codemirror_mode": "ecl",
12748+ "codemirror_mime_type": "text/x-ecl",
12749+ "language_id": 93
12750+ },
12751+ "ECLiPSe": {
12752+ "type": "programming",
12753+ "color": "#001d9d",
12754+ "group": "Prolog",
12755+ "extensions": [
12756+ ".ecl"
12757+ ],
12758+ "tm_scope": "source.prolog.eclipse",
12759+ "ace_mode": "prolog",
12760+ "language_id": 94
12761+ },
12762+ "EJS": {
12763+ "type": "markup",
12764+ "color": "#a91e50",
12765+ "extensions": [
12766+ ".ejs",
12767+ ".ect",
12768+ ".ejs.t",
12769+ ".jst"
12770+ ],
12771+ "tm_scope": "text.html.js",
12772+ "ace_mode": "ejs",
12773+ "language_id": 95
12774+ },
12775+ "EQ": {
12776+ "type": "programming",
12777+ "color": "#a78649",
12778+ "extensions": [
12779+ ".eq"
12780+ ],
12781+ "tm_scope": "source.cs",
12782+ "ace_mode": "csharp",
12783+ "codemirror_mode": "clike",
12784+ "codemirror_mime_type": "text/x-csharp",
12785+ "language_id": 96
12786+ },
12787+ "Eagle": {
12788+ "type": "data",
12789+ "extensions": [
12790+ ".sch",
12791+ ".brd"
12792+ ],
12793+ "tm_scope": "text.xml",
12794+ "ace_mode": "xml",
12795+ "codemirror_mode": "xml",
12796+ "codemirror_mime_type": "text/xml",
12797+ "language_id": 97
12798+ },
12799+ "Earthly": {
12800+ "type": "programming",
12801+ "aliases": [
12802+ "Earthfile"
12803+ ],
12804+ "color": "#2af0ff",
12805+ "tm_scope": "source.earthfile",
12806+ "ace_mode": "text",
12807+ "filenames": [
12808+ "Earthfile"
12809+ ],
12810+ "language_id": 963512632
12811+ },
12812+ "Easybuild": {
12813+ "type": "data",
12814+ "color": "#069406",
12815+ "group": "Python",
12816+ "ace_mode": "python",
12817+ "codemirror_mode": "python",
12818+ "codemirror_mime_type": "text/x-python",
12819+ "tm_scope": "source.python",
12820+ "extensions": [
12821+ ".eb"
12822+ ],
12823+ "language_id": 342840477
12824+ },
12825+ "Ecere Projects": {
12826+ "type": "data",
12827+ "color": "#913960",
12828+ "group": "JavaScript",
12829+ "extensions": [
12830+ ".epj"
12831+ ],
12832+ "tm_scope": "source.json",
12833+ "ace_mode": "json",
12834+ "codemirror_mode": "javascript",
12835+ "codemirror_mime_type": "application/json",
12836+ "language_id": 98
12837+ },
12838+ "Ecmarkup": {
12839+ "type": "markup",
12840+ "color": "#eb8131",
12841+ "group": "HTML",
12842+ "extensions": [
12843+ ".html"
12844+ ],
12845+ "tm_scope": "text.html.ecmarkup",
12846+ "ace_mode": "html",
12847+ "codemirror_mode": "htmlmixed",
12848+ "codemirror_mime_type": "text/html",
12849+ "aliases": [
12850+ "ecmarkdown"
12851+ ],
12852+ "language_id": 844766630
12853+ },
12854+ "EdgeQL": {
12855+ "type": "programming",
12856+ "color": "#31A7FF",
12857+ "aliases": [
12858+ "esdl"
12859+ ],
12860+ "extensions": [
12861+ ".edgeql",
12862+ ".esdl"
12863+ ],
12864+ "ace_mode": "text",
12865+ "tm_scope": "source.edgeql",
12866+ "language_id": 925235833
12867+ },
12868+ "EditorConfig": {
12869+ "type": "data",
12870+ "color": "#fff1f2",
12871+ "group": "INI",
12872+ "extensions": [
12873+ ".editorconfig"
12874+ ],
12875+ "filenames": [
12876+ ".editorconfig"
12877+ ],
12878+ "aliases": [
12879+ "editor-config"
12880+ ],
12881+ "ace_mode": "ini",
12882+ "codemirror_mode": "properties",
12883+ "codemirror_mime_type": "text/x-properties",
12884+ "tm_scope": "source.editorconfig",
12885+ "language_id": 96139566
12886+ },
12887+ "Edje Data Collection": {
12888+ "type": "data",
12889+ "extensions": [
12890+ ".edc"
12891+ ],
12892+ "tm_scope": "source.c++",
12893+ "ace_mode": "c_cpp",
12894+ "codemirror_mode": "clike",
12895+ "codemirror_mime_type": "text/x-c++src",
12896+ "language_id": 342840478
12897+ },
12898+ "Eiffel": {
12899+ "type": "programming",
12900+ "color": "#4d6977",
12901+ "extensions": [
12902+ ".e"
12903+ ],
12904+ "tm_scope": "source.eiffel",
12905+ "ace_mode": "eiffel",
12906+ "codemirror_mode": "eiffel",
12907+ "codemirror_mime_type": "text/x-eiffel",
12908+ "language_id": 99
12909+ },
12910+ "Elixir": {
12911+ "type": "programming",
12912+ "color": "#6e4a7e",
12913+ "extensions": [
12914+ ".ex",
12915+ ".exs"
12916+ ],
12917+ "tm_scope": "source.elixir",
12918+ "ace_mode": "elixir",
12919+ "filenames": [
12920+ "mix.lock"
12921+ ],
12922+ "interpreters": [
12923+ "elixir"
12924+ ],
12925+ "language_id": 100
12926+ },
12927+ "Elm": {
12928+ "type": "programming",
12929+ "color": "#60B5CC",
12930+ "extensions": [
12931+ ".elm"
12932+ ],
12933+ "tm_scope": "source.elm",
12934+ "ace_mode": "elm",
12935+ "codemirror_mode": "elm",
12936+ "codemirror_mime_type": "text/x-elm",
12937+ "language_id": 101
12938+ },
12939+ "Elvish": {
12940+ "type": "programming",
12941+ "ace_mode": "text",
12942+ "extensions": [
12943+ ".elv"
12944+ ],
12945+ "interpreters": [
12946+ "elvish"
12947+ ],
12948+ "tm_scope": "source.elvish",
12949+ "color": "#55BB55",
12950+ "language_id": 570996448
12951+ },
12952+ "Elvish Transcript": {
12953+ "type": "programming",
12954+ "group": "Elvish",
12955+ "ace_mode": "text",
12956+ "tm_scope": "source.elvish-transcript",
12957+ "color": "#55BB55",
12958+ "language_id": 452025714
12959+ },
12960+ "Emacs Lisp": {
12961+ "type": "programming",
12962+ "tm_scope": "source.emacs.lisp",
12963+ "color": "#c065db",
12964+ "aliases": [
12965+ "elisp",
12966+ "emacs"
12967+ ],
12968+ "filenames": [
12969+ ".abbrev_defs",
12970+ ".emacs",
12971+ ".emacs.desktop",
12972+ ".gnus",
12973+ ".spacemacs",
12974+ ".viper",
12975+ "Cask",
12976+ "Project.ede",
12977+ "_emacs",
12978+ "abbrev_defs"
12979+ ],
12980+ "extensions": [
12981+ ".el",
12982+ ".emacs",
12983+ ".emacs.desktop"
12984+ ],
12985+ "ace_mode": "lisp",
12986+ "codemirror_mode": "commonlisp",
12987+ "codemirror_mime_type": "text/x-common-lisp",
12988+ "language_id": 102
12989+ },
12990+ "EmberScript": {
12991+ "type": "programming",
12992+ "color": "#FFF4F3",
12993+ "extensions": [
12994+ ".em",
12995+ ".emberscript"
12996+ ],
12997+ "tm_scope": "source.coffee",
12998+ "ace_mode": "coffee",
12999+ "codemirror_mode": "coffeescript",
13000+ "codemirror_mime_type": "text/x-coffeescript",
13001+ "language_id": 103
13002+ },
13003+ "Erlang": {
13004+ "type": "programming",
13005+ "color": "#B83998",
13006+ "extensions": [
13007+ ".erl",
13008+ ".app",
13009+ ".app.src",
13010+ ".es",
13011+ ".escript",
13012+ ".hrl",
13013+ ".xrl",
13014+ ".yrl"
13015+ ],
13016+ "filenames": [
13017+ "Emakefile",
13018+ "rebar.config",
13019+ "rebar.config.lock",
13020+ "rebar.lock"
13021+ ],
13022+ "tm_scope": "source.erlang",
13023+ "ace_mode": "erlang",
13024+ "codemirror_mode": "erlang",
13025+ "codemirror_mime_type": "text/x-erlang",
13026+ "interpreters": [
13027+ "escript"
13028+ ],
13029+ "language_id": 104
13030+ },
13031+ "Euphoria": {
13032+ "type": "programming",
13033+ "color": "#FF790B",
13034+ "extensions": [
13035+ ".e",
13036+ ".ex"
13037+ ],
13038+ "interpreters": [
13039+ "eui",
13040+ "euiw"
13041+ ],
13042+ "ace_mode": "text",
13043+ "tm_scope": "source.euphoria",
13044+ "language_id": 880693982
13045+ },
13046+ "F#": {
13047+ "type": "programming",
13048+ "color": "#b845fc",
13049+ "aliases": [
13050+ "fsharp"
13051+ ],
13052+ "extensions": [
13053+ ".fs",
13054+ ".fsi",
13055+ ".fsx"
13056+ ],
13057+ "tm_scope": "source.fsharp",
13058+ "ace_mode": "text",
13059+ "codemirror_mode": "mllike",
13060+ "codemirror_mime_type": "text/x-fsharp",
13061+ "language_id": 105
13062+ },
13063+ "F*": {
13064+ "fs_name": "Fstar",
13065+ "type": "programming",
13066+ "color": "#572e30",
13067+ "aliases": [
13068+ "fstar"
13069+ ],
13070+ "extensions": [
13071+ ".fst",
13072+ ".fsti"
13073+ ],
13074+ "tm_scope": "source.fstar",
13075+ "ace_mode": "text",
13076+ "language_id": 336943375
13077+ },
13078+ "FIGlet Font": {
13079+ "type": "data",
13080+ "color": "#FFDDBB",
13081+ "aliases": [
13082+ "FIGfont"
13083+ ],
13084+ "extensions": [
13085+ ".flf"
13086+ ],
13087+ "tm_scope": "source.figfont",
13088+ "ace_mode": "text",
13089+ "language_id": 686129783
13090+ },
13091+ "FLUX": {
13092+ "type": "programming",
13093+ "color": "#88ccff",
13094+ "extensions": [
13095+ ".fx",
13096+ ".flux"
13097+ ],
13098+ "tm_scope": "none",
13099+ "ace_mode": "text",
13100+ "language_id": 106
13101+ },
13102+ "Factor": {
13103+ "type": "programming",
13104+ "color": "#636746",
13105+ "extensions": [
13106+ ".factor"
13107+ ],
13108+ "filenames": [
13109+ ".factor-boot-rc",
13110+ ".factor-rc"
13111+ ],
13112+ "tm_scope": "source.factor",
13113+ "ace_mode": "text",
13114+ "codemirror_mode": "factor",
13115+ "codemirror_mime_type": "text/x-factor",
13116+ "language_id": 108
13117+ },
13118+ "Fancy": {
13119+ "type": "programming",
13120+ "color": "#7b9db4",
13121+ "extensions": [
13122+ ".fy",
13123+ ".fancypack"
13124+ ],
13125+ "filenames": [
13126+ "Fakefile"
13127+ ],
13128+ "tm_scope": "source.fancy",
13129+ "ace_mode": "text",
13130+ "language_id": 109
13131+ },
13132+ "Fantom": {
13133+ "type": "programming",
13134+ "color": "#14253c",
13135+ "extensions": [
13136+ ".fan"
13137+ ],
13138+ "tm_scope": "source.fan",
13139+ "ace_mode": "text",
13140+ "language_id": 110
13141+ },
13142+ "Faust": {
13143+ "type": "programming",
13144+ "color": "#c37240",
13145+ "extensions": [
13146+ ".dsp"
13147+ ],
13148+ "tm_scope": "source.faust",
13149+ "ace_mode": "text",
13150+ "language_id": 622529198
13151+ },
13152+ "Fennel": {
13153+ "type": "programming",
13154+ "tm_scope": "source.fnl",
13155+ "ace_mode": "text",
13156+ "color": "#fff3d7",
13157+ "interpreters": [
13158+ "fennel"
13159+ ],
13160+ "extensions": [
13161+ ".fnl"
13162+ ],
13163+ "language_id": 239946126
13164+ },
13165+ "Filebench WML": {
13166+ "type": "programming",
13167+ "color": "#F6B900",
13168+ "extensions": [
13169+ ".f"
13170+ ],
13171+ "tm_scope": "none",
13172+ "ace_mode": "text",
13173+ "language_id": 111
13174+ },
13175+ "Filterscript": {
13176+ "type": "programming",
13177+ "group": "RenderScript",
13178+ "extensions": [
13179+ ".fs"
13180+ ],
13181+ "tm_scope": "none",
13182+ "ace_mode": "text",
13183+ "language_id": 112
13184+ },
13185+ "Fluent": {
13186+ "type": "programming",
13187+ "color": "#ffcc33",
13188+ "extensions": [
13189+ ".ftl"
13190+ ],
13191+ "tm_scope": "source.ftl",
13192+ "ace_mode": "text",
13193+ "language_id": 206353404
13194+ },
13195+ "Formatted": {
13196+ "type": "data",
13197+ "extensions": [
13198+ ".for",
13199+ ".eam.fs"
13200+ ],
13201+ "tm_scope": "none",
13202+ "ace_mode": "text",
13203+ "language_id": 113
13204+ },
13205+ "Forth": {
13206+ "type": "programming",
13207+ "color": "#341708",
13208+ "extensions": [
13209+ ".fth",
13210+ ".4th",
13211+ ".f",
13212+ ".for",
13213+ ".forth",
13214+ ".fr",
13215+ ".frt",
13216+ ".fs"
13217+ ],
13218+ "tm_scope": "source.forth",
13219+ "ace_mode": "forth",
13220+ "codemirror_mode": "forth",
13221+ "codemirror_mime_type": "text/x-forth",
13222+ "language_id": 114
13223+ },
13224+ "Fortran": {
13225+ "group": "Fortran",
13226+ "type": "programming",
13227+ "color": "#4d41b1",
13228+ "extensions": [
13229+ ".f",
13230+ ".f77",
13231+ ".for",
13232+ ".fpp"
13233+ ],
13234+ "tm_scope": "source.fortran",
13235+ "ace_mode": "text",
13236+ "codemirror_mode": "fortran",
13237+ "codemirror_mime_type": "text/x-fortran",
13238+ "language_id": 107
13239+ },
13240+ "Fortran Free Form": {
13241+ "group": "Fortran",
13242+ "color": "#4d41b1",
13243+ "type": "programming",
13244+ "extensions": [
13245+ ".f90",
13246+ ".f03",
13247+ ".f08",
13248+ ".f95"
13249+ ],
13250+ "tm_scope": "source.fortran.modern",
13251+ "ace_mode": "text",
13252+ "codemirror_mode": "fortran",
13253+ "codemirror_mime_type": "text/x-fortran",
13254+ "language_id": 761352333
13255+ },
13256+ "FreeBasic": {
13257+ "type": "programming",
13258+ "color": "#141AC9",
13259+ "extensions": [
13260+ ".bi",
13261+ ".bas"
13262+ ],
13263+ "tm_scope": "source.vbnet",
13264+ "aliases": [
13265+ "fb"
13266+ ],
13267+ "ace_mode": "text",
13268+ "codemirror_mode": "vb",
13269+ "codemirror_mime_type": "text/x-vb",
13270+ "language_id": 472896659
13271+ },
13272+ "FreeMarker": {
13273+ "type": "programming",
13274+ "color": "#0050b2",
13275+ "aliases": [
13276+ "ftl"
13277+ ],
13278+ "extensions": [
13279+ ".ftl"
13280+ ],
13281+ "tm_scope": "text.html.ftl",
13282+ "ace_mode": "ftl",
13283+ "language_id": 115
13284+ },
13285+ "Frege": {
13286+ "type": "programming",
13287+ "color": "#00cafe",
13288+ "extensions": [
13289+ ".fr"
13290+ ],
13291+ "tm_scope": "source.haskell",
13292+ "ace_mode": "haskell",
13293+ "language_id": 116
13294+ },
13295+ "Futhark": {
13296+ "type": "programming",
13297+ "color": "#5f021f",
13298+ "extensions": [
13299+ ".fut"
13300+ ],
13301+ "tm_scope": "source.futhark",
13302+ "ace_mode": "text",
13303+ "language_id": 97358117
13304+ },
13305+ "G-code": {
13306+ "type": "programming",
13307+ "color": "#D08CF2",
13308+ "extensions": [
13309+ ".g",
13310+ ".cnc",
13311+ ".gco",
13312+ ".gcode"
13313+ ],
13314+ "tm_scope": "source.gcode",
13315+ "ace_mode": "gcode",
13316+ "language_id": 117
13317+ },
13318+ "GAML": {
13319+ "type": "programming",
13320+ "color": "#FFC766",
13321+ "extensions": [
13322+ ".gaml"
13323+ ],
13324+ "tm_scope": "none",
13325+ "ace_mode": "text",
13326+ "language_id": 290345951
13327+ },
13328+ "GAMS": {
13329+ "type": "programming",
13330+ "color": "#f49a22",
13331+ "extensions": [
13332+ ".gms"
13333+ ],
13334+ "tm_scope": "none",
13335+ "ace_mode": "text",
13336+ "language_id": 118
13337+ },
13338+ "GAP": {
13339+ "type": "programming",
13340+ "color": "#0000cc",
13341+ "extensions": [
13342+ ".g",
13343+ ".gap",
13344+ ".gd",
13345+ ".gi",
13346+ ".tst"
13347+ ],
13348+ "tm_scope": "source.gap",
13349+ "ace_mode": "text",
13350+ "language_id": 119
13351+ },
13352+ "GCC Machine Description": {
13353+ "type": "programming",
13354+ "color": "#FFCFAB",
13355+ "extensions": [
13356+ ".md"
13357+ ],
13358+ "tm_scope": "source.lisp",
13359+ "ace_mode": "lisp",
13360+ "codemirror_mode": "commonlisp",
13361+ "codemirror_mime_type": "text/x-common-lisp",
13362+ "language_id": 121
13363+ },
13364+ "GDB": {
13365+ "type": "programming",
13366+ "extensions": [
13367+ ".gdb",
13368+ ".gdbinit"
13369+ ],
13370+ "tm_scope": "source.gdb",
13371+ "ace_mode": "text",
13372+ "language_id": 122
13373+ },
13374+ "GDScript": {
13375+ "type": "programming",
13376+ "color": "#355570",
13377+ "extensions": [
13378+ ".gd"
13379+ ],
13380+ "tm_scope": "source.gdscript",
13381+ "ace_mode": "text",
13382+ "language_id": 123
13383+ },
13384+ "GEDCOM": {
13385+ "type": "data",
13386+ "color": "#003058",
13387+ "ace_mode": "text",
13388+ "extensions": [
13389+ ".ged"
13390+ ],
13391+ "tm_scope": "source.gedcom",
13392+ "language_id": 459577965
13393+ },
13394+ "GLSL": {
13395+ "type": "programming",
13396+ "color": "#5686a5",
13397+ "extensions": [
13398+ ".glsl",
13399+ ".fp",
13400+ ".frag",
13401+ ".frg",
13402+ ".fs",
13403+ ".fsh",
13404+ ".fshader",
13405+ ".geo",
13406+ ".geom",
13407+ ".glslf",
13408+ ".glslv",
13409+ ".gs",
13410+ ".gshader",
13411+ ".rchit",
13412+ ".rmiss",
13413+ ".shader",
13414+ ".tesc",
13415+ ".tese",
13416+ ".vert",
13417+ ".vrx",
13418+ ".vs",
13419+ ".vsh",
13420+ ".vshader"
13421+ ],
13422+ "tm_scope": "source.glsl",
13423+ "ace_mode": "glsl",
13424+ "language_id": 124
13425+ },
13426+ "GN": {
13427+ "type": "data",
13428+ "extensions": [
13429+ ".gn",
13430+ ".gni"
13431+ ],
13432+ "interpreters": [
13433+ "gn"
13434+ ],
13435+ "filenames": [
13436+ ".gn"
13437+ ],
13438+ "tm_scope": "source.gn",
13439+ "ace_mode": "python",
13440+ "codemirror_mode": "python",
13441+ "codemirror_mime_type": "text/x-python",
13442+ "language_id": 302957008
13443+ },
13444+ "GSC": {
13445+ "type": "programming",
13446+ "color": "#FF6800",
13447+ "extensions": [
13448+ ".gsc",
13449+ ".csc",
13450+ ".gsh"
13451+ ],
13452+ "tm_scope": "source.gsc",
13453+ "ace_mode": "c_cpp",
13454+ "codemirror_mode": "clike",
13455+ "codemirror_mime_type": "text/x-csrc",
13456+ "language_id": 257856279
13457+ },
13458+ "Game Maker Language": {
13459+ "type": "programming",
13460+ "color": "#71b417",
13461+ "extensions": [
13462+ ".gml"
13463+ ],
13464+ "tm_scope": "source.c++",
13465+ "ace_mode": "c_cpp",
13466+ "codemirror_mode": "clike",
13467+ "codemirror_mime_type": "text/x-c++src",
13468+ "language_id": 125
13469+ },
13470+ "Gemfile.lock": {
13471+ "type": "data",
13472+ "color": "#701516",
13473+ "searchable": false,
13474+ "tm_scope": "source.gemfile-lock",
13475+ "ace_mode": "text",
13476+ "filenames": [
13477+ "Gemfile.lock"
13478+ ],
13479+ "language_id": 907065713
13480+ },
13481+ "Gemini": {
13482+ "type": "prose",
13483+ "color": "#ff6900",
13484+ "ace_mode": "text",
13485+ "extensions": [
13486+ ".gmi"
13487+ ],
13488+ "aliases": [
13489+ "gemtext"
13490+ ],
13491+ "wrap": true,
13492+ "tm_scope": "source.gemini",
13493+ "language_id": 310828396
13494+ },
13495+ "Genero 4gl": {
13496+ "type": "programming",
13497+ "color": "#63408e",
13498+ "extensions": [
13499+ ".4gl"
13500+ ],
13501+ "tm_scope": "source.genero-4gl",
13502+ "ace_mode": "text",
13503+ "language_id": 986054050
13504+ },
13505+ "Genero per": {
13506+ "type": "markup",
13507+ "color": "#d8df39",
13508+ "extensions": [
13509+ ".per"
13510+ ],
13511+ "tm_scope": "source.genero-per",
13512+ "ace_mode": "text",
13513+ "language_id": 902995658
13514+ },
13515+ "Genie": {
13516+ "type": "programming",
13517+ "ace_mode": "text",
13518+ "extensions": [
13519+ ".gs"
13520+ ],
13521+ "color": "#fb855d",
13522+ "tm_scope": "none",
13523+ "language_id": 792408528
13524+ },
13525+ "Genshi": {
13526+ "type": "programming",
13527+ "color": "#951531",
13528+ "extensions": [
13529+ ".kid"
13530+ ],
13531+ "tm_scope": "text.xml.genshi",
13532+ "aliases": [
13533+ "xml+genshi",
13534+ "xml+kid"
13535+ ],
13536+ "ace_mode": "xml",
13537+ "codemirror_mode": "xml",
13538+ "codemirror_mime_type": "text/xml",
13539+ "language_id": 126
13540+ },
13541+ "Gentoo Ebuild": {
13542+ "type": "programming",
13543+ "color": "#9400ff",
13544+ "group": "Shell",
13545+ "extensions": [
13546+ ".ebuild"
13547+ ],
13548+ "tm_scope": "source.shell",
13549+ "ace_mode": "sh",
13550+ "codemirror_mode": "shell",
13551+ "codemirror_mime_type": "text/x-sh",
13552+ "language_id": 127
13553+ },
13554+ "Gentoo Eclass": {
13555+ "type": "programming",
13556+ "color": "#9400ff",
13557+ "group": "Shell",
13558+ "extensions": [
13559+ ".eclass"
13560+ ],
13561+ "tm_scope": "source.shell",
13562+ "ace_mode": "sh",
13563+ "codemirror_mode": "shell",
13564+ "codemirror_mime_type": "text/x-sh",
13565+ "language_id": 128
13566+ },
13567+ "Gerber Image": {
13568+ "type": "data",
13569+ "color": "#d20b00",
13570+ "aliases": [
13571+ "rs-274x"
13572+ ],
13573+ "extensions": [
13574+ ".gbr",
13575+ ".cmp",
13576+ ".gbl",
13577+ ".gbo",
13578+ ".gbp",
13579+ ".gbs",
13580+ ".gko",
13581+ ".gml",
13582+ ".gpb",
13583+ ".gpt",
13584+ ".gtl",
13585+ ".gto",
13586+ ".gtp",
13587+ ".gts",
13588+ ".ncl",
13589+ ".sol"
13590+ ],
13591+ "interpreters": [
13592+ "gerbv",
13593+ "gerbview"
13594+ ],
13595+ "tm_scope": "source.gerber",
13596+ "ace_mode": "text",
13597+ "language_id": 404627610
13598+ },
13599+ "Gettext Catalog": {
13600+ "type": "prose",
13601+ "aliases": [
13602+ "pot"
13603+ ],
13604+ "extensions": [
13605+ ".po",
13606+ ".pot"
13607+ ],
13608+ "tm_scope": "source.po",
13609+ "ace_mode": "text",
13610+ "language_id": 129
13611+ },
13612+ "Gherkin": {
13613+ "type": "programming",
13614+ "extensions": [
13615+ ".feature",
13616+ ".story"
13617+ ],
13618+ "tm_scope": "text.gherkin.feature",
13619+ "aliases": [
13620+ "cucumber"
13621+ ],
13622+ "ace_mode": "text",
13623+ "color": "#5B2063",
13624+ "language_id": 76
13625+ },
13626+ "Git Attributes": {
13627+ "type": "data",
13628+ "color": "#F44D27",
13629+ "aliases": [
13630+ "gitattributes"
13631+ ],
13632+ "filenames": [
13633+ ".gitattributes"
13634+ ],
13635+ "tm_scope": "source.gitattributes",
13636+ "ace_mode": "gitignore",
13637+ "codemirror_mode": "shell",
13638+ "codemirror_mime_type": "text/x-sh",
13639+ "language_id": 956324166
13640+ },
13641+ "Git Config": {
13642+ "type": "data",
13643+ "color": "#F44D27",
13644+ "group": "INI",
13645+ "aliases": [
13646+ "gitconfig",
13647+ "gitmodules"
13648+ ],
13649+ "extensions": [
13650+ ".gitconfig"
13651+ ],
13652+ "filenames": [
13653+ ".gitconfig",
13654+ ".gitmodules"
13655+ ],
13656+ "ace_mode": "ini",
13657+ "codemirror_mode": "properties",
13658+ "codemirror_mime_type": "text/x-properties",
13659+ "tm_scope": "source.gitconfig",
13660+ "language_id": 807968997
13661+ },
13662+ "Git Revision List": {
13663+ "type": "data",
13664+ "color": "#F44D27",
13665+ "aliases": [
13666+ "Git Blame Ignore Revs"
13667+ ],
13668+ "filenames": [
13669+ ".git-blame-ignore-revs"
13670+ ],
13671+ "tm_scope": "source.git-revlist",
13672+ "ace_mode": "text",
13673+ "language_id": 461881235
13674+ },
13675+ "Gleam": {
13676+ "type": "programming",
13677+ "color": "#ffaff3",
13678+ "ace_mode": "text",
13679+ "extensions": [
13680+ ".gleam"
13681+ ],
13682+ "tm_scope": "source.gleam",
13683+ "language_id": 1054258749
13684+ },
13685+ "Glimmer JS": {
13686+ "type": "programming",
13687+ "extensions": [
13688+ ".gjs"
13689+ ],
13690+ "ace_mode": "javascript",
13691+ "color": "#F5835F",
13692+ "tm_scope": "source.gjs",
13693+ "group": "JavaScript",
13694+ "language_id": 5523150
13695+ },
13696+ "Glyph": {
13697+ "type": "programming",
13698+ "color": "#c1ac7f",
13699+ "extensions": [
13700+ ".glf"
13701+ ],
13702+ "tm_scope": "source.tcl",
13703+ "ace_mode": "tcl",
13704+ "codemirror_mode": "tcl",
13705+ "codemirror_mime_type": "text/x-tcl",
13706+ "language_id": 130
13707+ },
13708+ "Glyph Bitmap Distribution Format": {
13709+ "type": "data",
13710+ "extensions": [
13711+ ".bdf"
13712+ ],
13713+ "tm_scope": "source.bdf",
13714+ "ace_mode": "text",
13715+ "language_id": 997665271
13716+ },
13717+ "Gnuplot": {
13718+ "type": "programming",
13719+ "color": "#f0a9f0",
13720+ "extensions": [
13721+ ".gp",
13722+ ".gnu",
13723+ ".gnuplot",
13724+ ".p",
13725+ ".plot",
13726+ ".plt"
13727+ ],
13728+ "interpreters": [
13729+ "gnuplot"
13730+ ],
13731+ "tm_scope": "source.gnuplot",
13732+ "ace_mode": "text",
13733+ "language_id": 131
13734+ },
13735+ "Go": {
13736+ "type": "programming",
13737+ "color": "#00ADD8",
13738+ "aliases": [
13739+ "golang"
13740+ ],
13741+ "extensions": [
13742+ ".go"
13743+ ],
13744+ "tm_scope": "source.go",
13745+ "ace_mode": "golang",
13746+ "codemirror_mode": "go",
13747+ "codemirror_mime_type": "text/x-go",
13748+ "language_id": 132
13749+ },
13750+ "Go Checksums": {
13751+ "type": "data",
13752+ "color": "#00ADD8",
13753+ "aliases": [
13754+ "go.sum",
13755+ "go sum",
13756+ "go.work.sum",
13757+ "go work sum"
13758+ ],
13759+ "filenames": [
13760+ "go.sum",
13761+ "go.work.sum"
13762+ ],
13763+ "tm_scope": "go.sum",
13764+ "ace_mode": "text",
13765+ "language_id": 1054391671
13766+ },
13767+ "Go Module": {
13768+ "type": "data",
13769+ "color": "#00ADD8",
13770+ "aliases": [
13771+ "go.mod",
13772+ "go mod"
13773+ ],
13774+ "filenames": [
13775+ "go.mod"
13776+ ],
13777+ "tm_scope": "go.mod",
13778+ "ace_mode": "text",
13779+ "language_id": 947461016
13780+ },
13781+ "Go Workspace": {
13782+ "type": "data",
13783+ "color": "#00ADD8",
13784+ "aliases": [
13785+ "go.work",
13786+ "go work"
13787+ ],
13788+ "filenames": [
13789+ "go.work"
13790+ ],
13791+ "tm_scope": "go.mod",
13792+ "ace_mode": "text",
13793+ "language_id": 934546256
13794+ },
13795+ "Godot Resource": {
13796+ "type": "data",
13797+ "color": "#355570",
13798+ "extensions": [
13799+ ".gdnlib",
13800+ ".gdns",
13801+ ".tres",
13802+ ".tscn"
13803+ ],
13804+ "filenames": [
13805+ "project.godot"
13806+ ],
13807+ "tm_scope": "source.gdresource",
13808+ "ace_mode": "text",
13809+ "language_id": 738107771
13810+ },
13811+ "Golo": {
13812+ "type": "programming",
13813+ "color": "#88562A",
13814+ "extensions": [
13815+ ".golo"
13816+ ],
13817+ "tm_scope": "source.golo",
13818+ "ace_mode": "text",
13819+ "language_id": 133
13820+ },
13821+ "Gosu": {
13822+ "type": "programming",
13823+ "color": "#82937f",
13824+ "extensions": [
13825+ ".gs",
13826+ ".gst",
13827+ ".gsx",
13828+ ".vark"
13829+ ],
13830+ "tm_scope": "source.gosu.2",
13831+ "ace_mode": "text",
13832+ "language_id": 134
13833+ },
13834+ "Grace": {
13835+ "type": "programming",
13836+ "color": "#615f8b",
13837+ "extensions": [
13838+ ".grace"
13839+ ],
13840+ "tm_scope": "source.grace",
13841+ "ace_mode": "text",
13842+ "language_id": 135
13843+ },
13844+ "Gradle": {
13845+ "type": "data",
13846+ "color": "#02303a",
13847+ "extensions": [
13848+ ".gradle"
13849+ ],
13850+ "tm_scope": "source.groovy.gradle",
13851+ "ace_mode": "text",
13852+ "language_id": 136
13853+ },
13854+ "Gradle Kotlin DSL": {
13855+ "group": "Gradle",
13856+ "type": "data",
13857+ "color": "#02303a",
13858+ "extensions": [
13859+ ".gradle.kts"
13860+ ],
13861+ "ace_mode": "text",
13862+ "tm_scope": "source.kotlin",
13863+ "language_id": 432600901
13864+ },
13865+ "Grammatical Framework": {
13866+ "type": "programming",
13867+ "aliases": [
13868+ "gf"
13869+ ],
13870+ "extensions": [
13871+ ".gf"
13872+ ],
13873+ "color": "#ff0000",
13874+ "tm_scope": "source.gf",
13875+ "ace_mode": "haskell",
13876+ "codemirror_mode": "haskell",
13877+ "codemirror_mime_type": "text/x-haskell",
13878+ "language_id": 137
13879+ },
13880+ "Graph Modeling Language": {
13881+ "type": "data",
13882+ "extensions": [
13883+ ".gml"
13884+ ],
13885+ "tm_scope": "none",
13886+ "ace_mode": "text",
13887+ "language_id": 138
13888+ },
13889+ "GraphQL": {
13890+ "type": "data",
13891+ "color": "#e10098",
13892+ "extensions": [
13893+ ".graphql",
13894+ ".gql",
13895+ ".graphqls"
13896+ ],
13897+ "tm_scope": "source.graphql",
13898+ "ace_mode": "text",
13899+ "language_id": 139
13900+ },
13901+ "Graphviz (DOT)": {
13902+ "type": "data",
13903+ "color": "#2596be",
13904+ "tm_scope": "source.dot",
13905+ "extensions": [
13906+ ".dot",
13907+ ".gv"
13908+ ],
13909+ "ace_mode": "text",
13910+ "language_id": 140
13911+ },
13912+ "Groovy": {
13913+ "type": "programming",
13914+ "tm_scope": "source.groovy",
13915+ "ace_mode": "groovy",
13916+ "codemirror_mode": "groovy",
13917+ "codemirror_mime_type": "text/x-groovy",
13918+ "color": "#4298b8",
13919+ "extensions": [
13920+ ".groovy",
13921+ ".grt",
13922+ ".gtpl",
13923+ ".gvy"
13924+ ],
13925+ "interpreters": [
13926+ "groovy"
13927+ ],
13928+ "filenames": [
13929+ "Jenkinsfile"
13930+ ],
13931+ "language_id": 142
13932+ },
13933+ "Groovy Server Pages": {
13934+ "type": "programming",
13935+ "color": "#4298b8",
13936+ "group": "Groovy",
13937+ "aliases": [
13938+ "gsp",
13939+ "java server page"
13940+ ],
13941+ "extensions": [
13942+ ".gsp"
13943+ ],
13944+ "tm_scope": "text.html.jsp",
13945+ "ace_mode": "jsp",
13946+ "codemirror_mode": "htmlembedded",
13947+ "codemirror_mime_type": "application/x-jsp",
13948+ "language_id": 143
13949+ },
13950+ "HAProxy": {
13951+ "type": "data",
13952+ "color": "#106da9",
13953+ "extensions": [
13954+ ".cfg"
13955+ ],
13956+ "filenames": [
13957+ "haproxy.cfg"
13958+ ],
13959+ "tm_scope": "source.haproxy-config",
13960+ "ace_mode": "text",
13961+ "language_id": 366607477
13962+ },
13963+ "HCL": {
13964+ "type": "programming",
13965+ "color": "#844FBA",
13966+ "extensions": [
13967+ ".hcl",
13968+ ".nomad",
13969+ ".tf",
13970+ ".tfvars",
13971+ ".workflow"
13972+ ],
13973+ "aliases": [
13974+ "HashiCorp Configuration Language",
13975+ "terraform"
13976+ ],
13977+ "ace_mode": "ruby",
13978+ "codemirror_mode": "ruby",
13979+ "codemirror_mime_type": "text/x-ruby",
13980+ "tm_scope": "source.terraform",
13981+ "language_id": 144
13982+ },
13983+ "HLSL": {
13984+ "type": "programming",
13985+ "color": "#aace60",
13986+ "extensions": [
13987+ ".hlsl",
13988+ ".cginc",
13989+ ".fx",
13990+ ".fxh",
13991+ ".hlsli"
13992+ ],
13993+ "ace_mode": "text",
13994+ "tm_scope": "source.hlsl",
13995+ "language_id": 145
13996+ },
13997+ "HOCON": {
13998+ "type": "data",
13999+ "color": "#9ff8ee",
14000+ "extensions": [
14001+ ".hocon"
14002+ ],
14003+ "filenames": [
14004+ ".scalafix.conf",
14005+ ".scalafmt.conf"
14006+ ],
14007+ "tm_scope": "source.hocon",
14008+ "ace_mode": "text",
14009+ "language_id": 679725279
14010+ },
14011+ "HTML": {
14012+ "type": "markup",
14013+ "tm_scope": "text.html.basic",
14014+ "ace_mode": "html",
14015+ "codemirror_mode": "htmlmixed",
14016+ "codemirror_mime_type": "text/html",
14017+ "color": "#e34c26",
14018+ "aliases": [
14019+ "xhtml"
14020+ ],
14021+ "extensions": [
14022+ ".html",
14023+ ".hta",
14024+ ".htm",
14025+ ".html.hl",
14026+ ".inc",
14027+ ".xht",
14028+ ".xhtml"
14029+ ],
14030+ "language_id": 146
14031+ },
14032+ "HTML+ECR": {
14033+ "type": "markup",
14034+ "color": "#2e1052",
14035+ "tm_scope": "text.html.ecr",
14036+ "group": "HTML",
14037+ "aliases": [
14038+ "ecr"
14039+ ],
14040+ "extensions": [
14041+ ".ecr"
14042+ ],
14043+ "ace_mode": "text",
14044+ "codemirror_mode": "htmlmixed",
14045+ "codemirror_mime_type": "text/html",
14046+ "language_id": 148
14047+ },
14048+ "HTML+EEX": {
14049+ "type": "markup",
14050+ "color": "#6e4a7e",
14051+ "tm_scope": "text.html.elixir",
14052+ "group": "HTML",
14053+ "aliases": [
14054+ "eex",
14055+ "heex",
14056+ "leex"
14057+ ],
14058+ "extensions": [
14059+ ".eex",
14060+ ".html.heex",
14061+ ".html.leex"
14062+ ],
14063+ "ace_mode": "text",
14064+ "codemirror_mode": "htmlmixed",
14065+ "codemirror_mime_type": "text/html",
14066+ "language_id": 149
14067+ },
14068+ "HTML+ERB": {
14069+ "type": "markup",
14070+ "color": "#701516",
14071+ "tm_scope": "text.html.erb",
14072+ "group": "HTML",
14073+ "aliases": [
14074+ "erb",
14075+ "rhtml",
14076+ "html+ruby"
14077+ ],
14078+ "extensions": [
14079+ ".erb",
14080+ ".erb.deface",
14081+ ".rhtml"
14082+ ],
14083+ "ace_mode": "text",
14084+ "codemirror_mode": "htmlembedded",
14085+ "codemirror_mime_type": "application/x-erb",
14086+ "language_id": 150
14087+ },
14088+ "HTML+PHP": {
14089+ "type": "markup",
14090+ "color": "#4f5d95",
14091+ "tm_scope": "text.html.php",
14092+ "group": "HTML",
14093+ "extensions": [
14094+ ".phtml"
14095+ ],
14096+ "ace_mode": "php",
14097+ "codemirror_mode": "php",
14098+ "codemirror_mime_type": "application/x-httpd-php",
14099+ "language_id": 151
14100+ },
14101+ "HTML+Razor": {
14102+ "type": "markup",
14103+ "color": "#512be4",
14104+ "tm_scope": "text.html.cshtml",
14105+ "group": "HTML",
14106+ "aliases": [
14107+ "razor"
14108+ ],
14109+ "extensions": [
14110+ ".cshtml",
14111+ ".razor"
14112+ ],
14113+ "ace_mode": "razor",
14114+ "codemirror_mode": "htmlmixed",
14115+ "codemirror_mime_type": "text/html",
14116+ "language_id": 479039817
14117+ },
14118+ "HTTP": {
14119+ "type": "data",
14120+ "color": "#005C9C",
14121+ "extensions": [
14122+ ".http"
14123+ ],
14124+ "tm_scope": "source.httpspec",
14125+ "ace_mode": "text",
14126+ "codemirror_mode": "http",
14127+ "codemirror_mime_type": "message/http",
14128+ "language_id": 152
14129+ },
14130+ "HXML": {
14131+ "type": "data",
14132+ "color": "#f68712",
14133+ "ace_mode": "text",
14134+ "extensions": [
14135+ ".hxml"
14136+ ],
14137+ "tm_scope": "source.hxml",
14138+ "language_id": 786683730
14139+ },
14140+ "Hack": {
14141+ "type": "programming",
14142+ "ace_mode": "php",
14143+ "codemirror_mode": "php",
14144+ "codemirror_mime_type": "application/x-httpd-php",
14145+ "extensions": [
14146+ ".hack",
14147+ ".hh",
14148+ ".hhi",
14149+ ".php"
14150+ ],
14151+ "tm_scope": "source.hack",
14152+ "color": "#878787",
14153+ "language_id": 153
14154+ },
14155+ "Haml": {
14156+ "type": "markup",
14157+ "color": "#ece2a9",
14158+ "extensions": [
14159+ ".haml",
14160+ ".haml.deface"
14161+ ],
14162+ "tm_scope": "text.haml",
14163+ "ace_mode": "haml",
14164+ "codemirror_mode": "haml",
14165+ "codemirror_mime_type": "text/x-haml",
14166+ "language_id": 154
14167+ },
14168+ "Handlebars": {
14169+ "type": "markup",
14170+ "color": "#f7931e",
14171+ "aliases": [
14172+ "hbs",
14173+ "htmlbars"
14174+ ],
14175+ "extensions": [
14176+ ".handlebars",
14177+ ".hbs"
14178+ ],
14179+ "tm_scope": "text.html.handlebars",
14180+ "ace_mode": "handlebars",
14181+ "language_id": 155
14182+ },
14183+ "Harbour": {
14184+ "type": "programming",
14185+ "color": "#0e60e3",
14186+ "extensions": [
14187+ ".hb"
14188+ ],
14189+ "tm_scope": "source.harbour",
14190+ "ace_mode": "text",
14191+ "language_id": 156
14192+ },
14193+ "Haskell": {
14194+ "type": "programming",
14195+ "color": "#5e5086",
14196+ "extensions": [
14197+ ".hs",
14198+ ".hs-boot",
14199+ ".hsc"
14200+ ],
14201+ "interpreters": [
14202+ "runghc",
14203+ "runhaskell",
14204+ "runhugs"
14205+ ],
14206+ "tm_scope": "source.haskell",
14207+ "ace_mode": "haskell",
14208+ "codemirror_mode": "haskell",
14209+ "codemirror_mime_type": "text/x-haskell",
14210+ "language_id": 157
14211+ },
14212+ "Haxe": {
14213+ "type": "programming",
14214+ "ace_mode": "haxe",
14215+ "codemirror_mode": "haxe",
14216+ "codemirror_mime_type": "text/x-haxe",
14217+ "color": "#df7900",
14218+ "extensions": [
14219+ ".hx",
14220+ ".hxsl"
14221+ ],
14222+ "tm_scope": "source.hx",
14223+ "language_id": 158
14224+ },
14225+ "HiveQL": {
14226+ "type": "programming",
14227+ "extensions": [
14228+ ".q",
14229+ ".hql"
14230+ ],
14231+ "color": "#dce200",
14232+ "tm_scope": "source.hql",
14233+ "ace_mode": "sql",
14234+ "language_id": 931814087
14235+ },
14236+ "HolyC": {
14237+ "type": "programming",
14238+ "color": "#ffefaf",
14239+ "extensions": [
14240+ ".hc"
14241+ ],
14242+ "tm_scope": "source.hc",
14243+ "ace_mode": "c_cpp",
14244+ "codemirror_mode": "clike",
14245+ "codemirror_mime_type": "text/x-csrc",
14246+ "language_id": 928121743
14247+ },
14248+ "Hosts File": {
14249+ "type": "data",
14250+ "color": "#308888",
14251+ "filenames": [
14252+ "HOSTS",
14253+ "hosts"
14254+ ],
14255+ "aliases": [
14256+ "hosts"
14257+ ],
14258+ "tm_scope": "source.hosts",
14259+ "ace_mode": "text",
14260+ "language_id": 231021894
14261+ },
14262+ "Hy": {
14263+ "type": "programming",
14264+ "ace_mode": "text",
14265+ "color": "#7790B2",
14266+ "extensions": [
14267+ ".hy"
14268+ ],
14269+ "interpreters": [
14270+ "hy"
14271+ ],
14272+ "aliases": [
14273+ "hylang"
14274+ ],
14275+ "tm_scope": "source.hy",
14276+ "language_id": 159
14277+ },
14278+ "HyPhy": {
14279+ "type": "programming",
14280+ "ace_mode": "text",
14281+ "extensions": [
14282+ ".bf"
14283+ ],
14284+ "tm_scope": "none",
14285+ "language_id": 160
14286+ },
14287+ "IDL": {
14288+ "type": "programming",
14289+ "color": "#a3522f",
14290+ "extensions": [
14291+ ".pro",
14292+ ".dlm"
14293+ ],
14294+ "tm_scope": "source.idl",
14295+ "ace_mode": "text",
14296+ "codemirror_mode": "idl",
14297+ "codemirror_mime_type": "text/x-idl",
14298+ "language_id": 161
14299+ },
14300+ "IGOR Pro": {
14301+ "type": "programming",
14302+ "color": "#0000cc",
14303+ "extensions": [
14304+ ".ipf"
14305+ ],
14306+ "aliases": [
14307+ "igor",
14308+ "igorpro"
14309+ ],
14310+ "tm_scope": "source.igor",
14311+ "ace_mode": "text",
14312+ "language_id": 162
14313+ },
14314+ "INI": {
14315+ "type": "data",
14316+ "color": "#d1dbe0",
14317+ "extensions": [
14318+ ".ini",
14319+ ".cfg",
14320+ ".cnf",
14321+ ".dof",
14322+ ".lektorproject",
14323+ ".prefs",
14324+ ".pro",
14325+ ".properties",
14326+ ".url"
14327+ ],
14328+ "filenames": [
14329+ ".coveragerc",
14330+ ".flake8",
14331+ ".pylintrc",
14332+ "HOSTS",
14333+ "buildozer.spec",
14334+ "hosts",
14335+ "pylintrc",
14336+ "vlcrc"
14337+ ],
14338+ "tm_scope": "source.ini",
14339+ "aliases": [
14340+ "dosini"
14341+ ],
14342+ "ace_mode": "ini",
14343+ "codemirror_mode": "properties",
14344+ "codemirror_mime_type": "text/x-properties",
14345+ "language_id": 163
14346+ },
14347+ "IRC log": {
14348+ "type": "data",
14349+ "aliases": [
14350+ "irc",
14351+ "irc logs"
14352+ ],
14353+ "extensions": [
14354+ ".irclog",
14355+ ".weechatlog"
14356+ ],
14357+ "tm_scope": "none",
14358+ "ace_mode": "text",
14359+ "codemirror_mode": "mirc",
14360+ "codemirror_mime_type": "text/mirc",
14361+ "language_id": 164
14362+ },
14363+ "Idris": {
14364+ "type": "programming",
14365+ "color": "#b30000",
14366+ "extensions": [
14367+ ".idr",
14368+ ".lidr"
14369+ ],
14370+ "ace_mode": "text",
14371+ "tm_scope": "source.idris",
14372+ "language_id": 165
14373+ },
14374+ "Ignore List": {
14375+ "type": "data",
14376+ "color": "#000000",
14377+ "aliases": [
14378+ "ignore",
14379+ "gitignore",
14380+ "git-ignore"
14381+ ],
14382+ "extensions": [
14383+ ".gitignore"
14384+ ],
14385+ "filenames": [
14386+ ".atomignore",
14387+ ".babelignore",
14388+ ".bzrignore",
14389+ ".coffeelintignore",
14390+ ".cvsignore",
14391+ ".dockerignore",
14392+ ".eleventyignore",
14393+ ".eslintignore",
14394+ ".gitignore",
14395+ ".markdownlintignore",
14396+ ".nodemonignore",
14397+ ".npmignore",
14398+ ".prettierignore",
14399+ ".stylelintignore",
14400+ ".vercelignore",
14401+ ".vscodeignore",
14402+ "gitignore-global",
14403+ "gitignore_global"
14404+ ],
14405+ "ace_mode": "gitignore",
14406+ "tm_scope": "source.gitignore",
14407+ "codemirror_mode": "shell",
14408+ "codemirror_mime_type": "text/x-sh",
14409+ "language_id": 74444240
14410+ },
14411+ "ImageJ Macro": {
14412+ "type": "programming",
14413+ "color": "#99AAFF",
14414+ "aliases": [
14415+ "ijm"
14416+ ],
14417+ "extensions": [
14418+ ".ijm"
14419+ ],
14420+ "ace_mode": "text",
14421+ "tm_scope": "none",
14422+ "language_id": 575143428
14423+ },
14424+ "Imba": {
14425+ "type": "programming",
14426+ "color": "#16cec6",
14427+ "extensions": [
14428+ ".imba"
14429+ ],
14430+ "ace_mode": "text",
14431+ "tm_scope": "source.imba",
14432+ "language_id": 1057618448
14433+ },
14434+ "Inform 7": {
14435+ "type": "programming",
14436+ "wrap": true,
14437+ "extensions": [
14438+ ".ni",
14439+ ".i7x"
14440+ ],
14441+ "tm_scope": "source.inform7",
14442+ "aliases": [
14443+ "i7",
14444+ "inform7"
14445+ ],
14446+ "ace_mode": "text",
14447+ "language_id": 166
14448+ },
14449+ "Ink": {
14450+ "type": "programming",
14451+ "wrap": true,
14452+ "extensions": [
14453+ ".ink"
14454+ ],
14455+ "tm_scope": "source.ink",
14456+ "ace_mode": "text",
14457+ "language_id": 838252715
14458+ },
14459+ "Inno Setup": {
14460+ "type": "programming",
14461+ "color": "#264b99",
14462+ "extensions": [
14463+ ".iss",
14464+ ".isl"
14465+ ],
14466+ "tm_scope": "source.inno",
14467+ "ace_mode": "text",
14468+ "language_id": 167
14469+ },
14470+ "Io": {
14471+ "type": "programming",
14472+ "color": "#a9188d",
14473+ "extensions": [
14474+ ".io"
14475+ ],
14476+ "interpreters": [
14477+ "io"
14478+ ],
14479+ "tm_scope": "source.io",
14480+ "ace_mode": "io",
14481+ "language_id": 168
14482+ },
14483+ "Ioke": {
14484+ "type": "programming",
14485+ "color": "#078193",
14486+ "extensions": [
14487+ ".ik"
14488+ ],
14489+ "interpreters": [
14490+ "ioke"
14491+ ],
14492+ "tm_scope": "source.ioke",
14493+ "ace_mode": "text",
14494+ "language_id": 169
14495+ },
14496+ "Isabelle": {
14497+ "type": "programming",
14498+ "color": "#FEFE00",
14499+ "extensions": [
14500+ ".thy"
14501+ ],
14502+ "tm_scope": "source.isabelle.theory",
14503+ "ace_mode": "text",
14504+ "language_id": 170
14505+ },
14506+ "Isabelle ROOT": {
14507+ "type": "programming",
14508+ "color": "#FEFE00",
14509+ "group": "Isabelle",
14510+ "filenames": [
14511+ "ROOT"
14512+ ],
14513+ "tm_scope": "source.isabelle.root",
14514+ "ace_mode": "text",
14515+ "language_id": 171
14516+ },
14517+ "J": {
14518+ "type": "programming",
14519+ "color": "#9EEDFF",
14520+ "extensions": [
14521+ ".ijs"
14522+ ],
14523+ "interpreters": [
14524+ "jconsole"
14525+ ],
14526+ "tm_scope": "source.j",
14527+ "ace_mode": "text",
14528+ "language_id": 172
14529+ },
14530+ "JAR Manifest": {
14531+ "type": "data",
14532+ "color": "#b07219",
14533+ "filenames": [
14534+ "MANIFEST.MF"
14535+ ],
14536+ "tm_scope": "source.yaml",
14537+ "ace_mode": "text",
14538+ "language_id": 447261135
14539+ },
14540+ "JCL": {
14541+ "type": "programming",
14542+ "color": "#d90e09",
14543+ "extensions": [
14544+ ".jcl"
14545+ ],
14546+ "tm_scope": "source.jcl",
14547+ "ace_mode": "text",
14548+ "language_id": 316620079
14549+ },
14550+ "JFlex": {
14551+ "type": "programming",
14552+ "color": "#DBCA00",
14553+ "group": "Lex",
14554+ "extensions": [
14555+ ".flex",
14556+ ".jflex"
14557+ ],
14558+ "tm_scope": "source.jflex",
14559+ "ace_mode": "text",
14560+ "language_id": 173
14561+ },
14562+ "JSON": {
14563+ "type": "data",
14564+ "color": "#292929",
14565+ "tm_scope": "source.json",
14566+ "ace_mode": "json",
14567+ "codemirror_mode": "javascript",
14568+ "codemirror_mime_type": "application/json",
14569+ "aliases": [
14570+ "geojson",
14571+ "jsonl",
14572+ "topojson"
14573+ ],
14574+ "extensions": [
14575+ ".json",
14576+ ".4DForm",
14577+ ".4DProject",
14578+ ".avsc",
14579+ ".geojson",
14580+ ".gltf",
14581+ ".har",
14582+ ".ice",
14583+ ".JSON-tmLanguage",
14584+ ".jsonl",
14585+ ".mcmeta",
14586+ ".tfstate",
14587+ ".tfstate.backup",
14588+ ".topojson",
14589+ ".webapp",
14590+ ".webmanifest",
14591+ ".yy",
14592+ ".yyp"
14593+ ],
14594+ "filenames": [
14595+ ".all-contributorsrc",
14596+ ".arcconfig",
14597+ ".auto-changelog",
14598+ ".c8rc",
14599+ ".htmlhintrc",
14600+ ".imgbotconfig",
14601+ ".nycrc",
14602+ ".tern-config",
14603+ ".tern-project",
14604+ ".watchmanconfig",
14605+ "Pipfile.lock",
14606+ "composer.lock",
14607+ "deno.lock",
14608+ "flake.lock",
14609+ "mcmod.info"
14610+ ],
14611+ "language_id": 174
14612+ },
14613+ "JSON with Comments": {
14614+ "type": "data",
14615+ "color": "#292929",
14616+ "group": "JSON",
14617+ "tm_scope": "source.js",
14618+ "ace_mode": "javascript",
14619+ "codemirror_mode": "javascript",
14620+ "codemirror_mime_type": "text/javascript",
14621+ "aliases": [
14622+ "jsonc"
14623+ ],
14624+ "extensions": [
14625+ ".jsonc",
14626+ ".code-snippets",
14627+ ".code-workspace",
14628+ ".sublime-build",
14629+ ".sublime-commands",
14630+ ".sublime-completions",
14631+ ".sublime-keymap",
14632+ ".sublime-macro",
14633+ ".sublime-menu",
14634+ ".sublime-mousemap",
14635+ ".sublime-project",
14636+ ".sublime-settings",
14637+ ".sublime-theme",
14638+ ".sublime-workspace",
14639+ ".sublime_metrics",
14640+ ".sublime_session"
14641+ ],
14642+ "filenames": [
14643+ ".babelrc",
14644+ ".devcontainer.json",
14645+ ".eslintrc.json",
14646+ ".jscsrc",
14647+ ".jshintrc",
14648+ ".jslintrc",
14649+ ".swcrc",
14650+ "api-extractor.json",
14651+ "devcontainer.json",
14652+ "jsconfig.json",
14653+ "language-configuration.json",
14654+ "tsconfig.json",
14655+ "tslint.json"
14656+ ],
14657+ "language_id": 423
14658+ },
14659+ "JSON5": {
14660+ "type": "data",
14661+ "color": "#267CB9",
14662+ "extensions": [
14663+ ".json5"
14664+ ],
14665+ "tm_scope": "source.js",
14666+ "ace_mode": "javascript",
14667+ "codemirror_mode": "javascript",
14668+ "codemirror_mime_type": "application/json",
14669+ "language_id": 175
14670+ },
14671+ "JSONLD": {
14672+ "type": "data",
14673+ "color": "#0c479c",
14674+ "extensions": [
14675+ ".jsonld"
14676+ ],
14677+ "tm_scope": "source.js",
14678+ "ace_mode": "javascript",
14679+ "codemirror_mode": "javascript",
14680+ "codemirror_mime_type": "application/json",
14681+ "language_id": 176
14682+ },
14683+ "JSONiq": {
14684+ "color": "#40d47e",
14685+ "type": "programming",
14686+ "ace_mode": "jsoniq",
14687+ "codemirror_mode": "javascript",
14688+ "codemirror_mime_type": "application/json",
14689+ "extensions": [
14690+ ".jq"
14691+ ],
14692+ "tm_scope": "source.jsoniq",
14693+ "language_id": 177
14694+ },
14695+ "Janet": {
14696+ "type": "programming",
14697+ "color": "#0886a5",
14698+ "extensions": [
14699+ ".janet"
14700+ ],
14701+ "tm_scope": "source.janet",
14702+ "ace_mode": "scheme",
14703+ "codemirror_mode": "scheme",
14704+ "codemirror_mime_type": "text/x-scheme",
14705+ "interpreters": [
14706+ "janet"
14707+ ],
14708+ "language_id": 1028705371
14709+ },
14710+ "Jasmin": {
14711+ "type": "programming",
14712+ "color": "#d03600",
14713+ "ace_mode": "java",
14714+ "extensions": [
14715+ ".j"
14716+ ],
14717+ "tm_scope": "source.jasmin",
14718+ "language_id": 180
14719+ },
14720+ "Java": {
14721+ "type": "programming",
14722+ "tm_scope": "source.java",
14723+ "ace_mode": "java",
14724+ "codemirror_mode": "clike",
14725+ "codemirror_mime_type": "text/x-java",
14726+ "color": "#b07219",
14727+ "extensions": [
14728+ ".java",
14729+ ".jav",
14730+ ".jsh"
14731+ ],
14732+ "language_id": 181
14733+ },
14734+ "Java Properties": {
14735+ "type": "data",
14736+ "color": "#2A6277",
14737+ "extensions": [
14738+ ".properties"
14739+ ],
14740+ "tm_scope": "source.java-properties",
14741+ "ace_mode": "properties",
14742+ "codemirror_mode": "properties",
14743+ "codemirror_mime_type": "text/x-properties",
14744+ "language_id": 519377561
14745+ },
14746+ "Java Server Pages": {
14747+ "type": "programming",
14748+ "color": "#2A6277",
14749+ "group": "Java",
14750+ "aliases": [
14751+ "jsp"
14752+ ],
14753+ "extensions": [
14754+ ".jsp",
14755+ ".tag"
14756+ ],
14757+ "tm_scope": "text.html.jsp",
14758+ "ace_mode": "jsp",
14759+ "codemirror_mode": "htmlembedded",
14760+ "codemirror_mime_type": "application/x-jsp",
14761+ "language_id": 182
14762+ },
14763+ "JavaScript": {
14764+ "type": "programming",
14765+ "tm_scope": "source.js",
14766+ "ace_mode": "javascript",
14767+ "codemirror_mode": "javascript",
14768+ "codemirror_mime_type": "text/javascript",
14769+ "color": "#f1e05a",
14770+ "aliases": [
14771+ "js",
14772+ "node"
14773+ ],
14774+ "extensions": [
14775+ ".js",
14776+ "._js",
14777+ ".bones",
14778+ ".cjs",
14779+ ".es",
14780+ ".es6",
14781+ ".frag",
14782+ ".gs",
14783+ ".jake",
14784+ ".javascript",
14785+ ".jsb",
14786+ ".jscad",
14787+ ".jsfl",
14788+ ".jslib",
14789+ ".jsm",
14790+ ".jspre",
14791+ ".jss",
14792+ ".jsx",
14793+ ".mjs",
14794+ ".njs",
14795+ ".pac",
14796+ ".sjs",
14797+ ".ssjs",
14798+ ".xsjs",
14799+ ".xsjslib"
14800+ ],
14801+ "filenames": [
14802+ "Jakefile"
14803+ ],
14804+ "interpreters": [
14805+ "chakra",
14806+ "d8",
14807+ "gjs",
14808+ "js",
14809+ "node",
14810+ "nodejs",
14811+ "qjs",
14812+ "rhino",
14813+ "v8",
14814+ "v8-shell"
14815+ ],
14816+ "language_id": 183
14817+ },
14818+ "JavaScript+ERB": {
14819+ "type": "programming",
14820+ "color": "#f1e05a",
14821+ "tm_scope": "source.js",
14822+ "group": "JavaScript",
14823+ "extensions": [
14824+ ".js.erb"
14825+ ],
14826+ "ace_mode": "javascript",
14827+ "codemirror_mode": "javascript",
14828+ "codemirror_mime_type": "application/javascript",
14829+ "language_id": 914318960
14830+ },
14831+ "Jest Snapshot": {
14832+ "type": "data",
14833+ "color": "#15c213",
14834+ "tm_scope": "source.jest.snap",
14835+ "extensions": [
14836+ ".snap"
14837+ ],
14838+ "ace_mode": "javascript",
14839+ "codemirror_mode": "javascript",
14840+ "codemirror_mime_type": "application/javascript",
14841+ "language_id": 774635084
14842+ },
14843+ "JetBrains MPS": {
14844+ "type": "programming",
14845+ "aliases": [
14846+ "mps"
14847+ ],
14848+ "color": "#21D789",
14849+ "extensions": [
14850+ ".mps",
14851+ ".mpl",
14852+ ".msd"
14853+ ],
14854+ "ace_mode": "xml",
14855+ "codemirror_mode": "xml",
14856+ "codemirror_mime_type": "text/xml",
14857+ "tm_scope": "none",
14858+ "language_id": 465165328
14859+ },
14860+ "Jinja": {
14861+ "type": "markup",
14862+ "color": "#a52a22",
14863+ "aliases": [
14864+ "django",
14865+ "html+django",
14866+ "html+jinja",
14867+ "htmldjango"
14868+ ],
14869+ "extensions": [
14870+ ".jinja",
14871+ ".j2",
14872+ ".jinja2"
14873+ ],
14874+ "tm_scope": "text.html.django",
14875+ "ace_mode": "django",
14876+ "codemirror_mode": "django",
14877+ "codemirror_mime_type": "text/x-django",
14878+ "language_id": 147
14879+ },
14880+ "Jison": {
14881+ "type": "programming",
14882+ "color": "#56b3cb",
14883+ "group": "Yacc",
14884+ "extensions": [
14885+ ".jison"
14886+ ],
14887+ "tm_scope": "source.jison",
14888+ "ace_mode": "text",
14889+ "language_id": 284531423
14890+ },
14891+ "Jison Lex": {
14892+ "type": "programming",
14893+ "color": "#56b3cb",
14894+ "group": "Lex",
14895+ "extensions": [
14896+ ".jisonlex"
14897+ ],
14898+ "tm_scope": "source.jisonlex",
14899+ "ace_mode": "text",
14900+ "language_id": 406395330
14901+ },
14902+ "Jolie": {
14903+ "type": "programming",
14904+ "extensions": [
14905+ ".ol",
14906+ ".iol"
14907+ ],
14908+ "interpreters": [
14909+ "jolie"
14910+ ],
14911+ "color": "#843179",
14912+ "ace_mode": "text",
14913+ "tm_scope": "source.jolie",
14914+ "language_id": 998078858
14915+ },
14916+ "Jsonnet": {
14917+ "color": "#0064bd",
14918+ "type": "programming",
14919+ "ace_mode": "text",
14920+ "extensions": [
14921+ ".jsonnet",
14922+ ".libsonnet"
14923+ ],
14924+ "tm_scope": "source.jsonnet",
14925+ "language_id": 664885656
14926+ },
14927+ "Julia": {
14928+ "type": "programming",
14929+ "extensions": [
14930+ ".jl"
14931+ ],
14932+ "interpreters": [
14933+ "julia"
14934+ ],
14935+ "color": "#a270ba",
14936+ "tm_scope": "source.julia",
14937+ "ace_mode": "julia",
14938+ "codemirror_mode": "julia",
14939+ "codemirror_mime_type": "text/x-julia",
14940+ "language_id": 184
14941+ },
14942+ "Jupyter Notebook": {
14943+ "type": "markup",
14944+ "ace_mode": "json",
14945+ "codemirror_mode": "javascript",
14946+ "codemirror_mime_type": "application/json",
14947+ "tm_scope": "source.json",
14948+ "color": "#DA5B0B",
14949+ "extensions": [
14950+ ".ipynb"
14951+ ],
14952+ "filenames": [
14953+ "Notebook"
14954+ ],
14955+ "aliases": [
14956+ "IPython Notebook"
14957+ ],
14958+ "language_id": 185
14959+ },
14960+ "Just": {
14961+ "type": "programming",
14962+ "aliases": [
14963+ "Justfile"
14964+ ],
14965+ "color": "#384d54",
14966+ "tm_scope": "source.just",
14967+ "filenames": [
14968+ "JUSTFILE",
14969+ "Justfile",
14970+ "justfile"
14971+ ],
14972+ "ace_mode": "text",
14973+ "language_id": 128447695
14974+ },
14975+ "KRL": {
14976+ "type": "programming",
14977+ "color": "#28430A",
14978+ "extensions": [
14979+ ".krl"
14980+ ],
14981+ "tm_scope": "none",
14982+ "ace_mode": "text",
14983+ "language_id": 186
14984+ },
14985+ "Kaitai Struct": {
14986+ "type": "programming",
14987+ "aliases": [
14988+ "ksy"
14989+ ],
14990+ "ace_mode": "yaml",
14991+ "codemirror_mode": "yaml",
14992+ "codemirror_mime_type": "text/x-yaml",
14993+ "color": "#773b37",
14994+ "extensions": [
14995+ ".ksy"
14996+ ],
14997+ "tm_scope": "source.yaml",
14998+ "language_id": 818804755
14999+ },
15000+ "KakouneScript": {
15001+ "type": "programming",
15002+ "color": "#6f8042",
15003+ "tm_scope": "source.kakscript",
15004+ "aliases": [
15005+ "kak",
15006+ "kakscript"
15007+ ],
15008+ "extensions": [
15009+ ".kak"
15010+ ],
15011+ "filenames": [
15012+ "kakrc"
15013+ ],
15014+ "ace_mode": "text",
15015+ "language_id": 603336474
15016+ },
15017+ "KerboScript": {
15018+ "type": "programming",
15019+ "ace_mode": "text",
15020+ "extensions": [
15021+ ".ks"
15022+ ],
15023+ "color": "#41adf0",
15024+ "tm_scope": "source.kerboscript",
15025+ "language_id": 59716426
15026+ },
15027+ "KiCad Layout": {
15028+ "type": "data",
15029+ "color": "#2f4aab",
15030+ "aliases": [
15031+ "pcbnew"
15032+ ],
15033+ "extensions": [
15034+ ".kicad_pcb",
15035+ ".kicad_mod",
15036+ ".kicad_wks"
15037+ ],
15038+ "filenames": [
15039+ "fp-lib-table"
15040+ ],
15041+ "tm_scope": "source.pcb.sexp",
15042+ "ace_mode": "lisp",
15043+ "codemirror_mode": "commonlisp",
15044+ "codemirror_mime_type": "text/x-common-lisp",
15045+ "language_id": 187
15046+ },
15047+ "KiCad Legacy Layout": {
15048+ "type": "data",
15049+ "color": "#2f4aab",
15050+ "extensions": [
15051+ ".brd"
15052+ ],
15053+ "tm_scope": "source.pcb.board",
15054+ "ace_mode": "text",
15055+ "language_id": 140848857
15056+ },
15057+ "KiCad Schematic": {
15058+ "type": "data",
15059+ "color": "#2f4aab",
15060+ "aliases": [
15061+ "eeschema schematic"
15062+ ],
15063+ "extensions": [
15064+ ".kicad_sch",
15065+ ".sch"
15066+ ],
15067+ "tm_scope": "source.pcb.schematic",
15068+ "ace_mode": "text",
15069+ "language_id": 622447435
15070+ },
15071+ "Kickstart": {
15072+ "type": "data",
15073+ "ace_mode": "text",
15074+ "extensions": [
15075+ ".ks"
15076+ ],
15077+ "tm_scope": "source.kickstart",
15078+ "language_id": 692635484
15079+ },
15080+ "Kit": {
15081+ "type": "markup",
15082+ "ace_mode": "html",
15083+ "codemirror_mode": "htmlmixed",
15084+ "codemirror_mime_type": "text/html",
15085+ "extensions": [
15086+ ".kit"
15087+ ],
15088+ "tm_scope": "text.html.basic",
15089+ "language_id": 188
15090+ },
15091+ "Kotlin": {
15092+ "type": "programming",
15093+ "color": "#A97BFF",
15094+ "extensions": [
15095+ ".kt",
15096+ ".ktm",
15097+ ".kts"
15098+ ],
15099+ "tm_scope": "source.kotlin",
15100+ "ace_mode": "text",
15101+ "codemirror_mode": "clike",
15102+ "codemirror_mime_type": "text/x-kotlin",
15103+ "language_id": 189
15104+ },
15105+ "Kusto": {
15106+ "type": "data",
15107+ "extensions": [
15108+ ".csl",
15109+ ".kql"
15110+ ],
15111+ "tm_scope": "source.kusto",
15112+ "ace_mode": "text",
15113+ "language_id": 225697190
15114+ },
15115+ "LFE": {
15116+ "type": "programming",
15117+ "color": "#4C3023",
15118+ "extensions": [
15119+ ".lfe"
15120+ ],
15121+ "tm_scope": "source.lisp",
15122+ "ace_mode": "lisp",
15123+ "codemirror_mode": "commonlisp",
15124+ "codemirror_mime_type": "text/x-common-lisp",
15125+ "language_id": 190
15126+ },
15127+ "LLVM": {
15128+ "type": "programming",
15129+ "extensions": [
15130+ ".ll"
15131+ ],
15132+ "tm_scope": "source.llvm",
15133+ "ace_mode": "text",
15134+ "color": "#185619",
15135+ "language_id": 191
15136+ },
15137+ "LOLCODE": {
15138+ "type": "programming",
15139+ "extensions": [
15140+ ".lol"
15141+ ],
15142+ "color": "#cc9900",
15143+ "tm_scope": "source.lolcode",
15144+ "ace_mode": "text",
15145+ "language_id": 192
15146+ },
15147+ "LSL": {
15148+ "type": "programming",
15149+ "tm_scope": "source.lsl",
15150+ "ace_mode": "lsl",
15151+ "extensions": [
15152+ ".lsl",
15153+ ".lslp"
15154+ ],
15155+ "interpreters": [
15156+ "lsl"
15157+ ],
15158+ "color": "#3d9970",
15159+ "language_id": 193
15160+ },
15161+ "LTspice Symbol": {
15162+ "type": "data",
15163+ "extensions": [
15164+ ".asy"
15165+ ],
15166+ "tm_scope": "source.ltspice.symbol",
15167+ "ace_mode": "text",
15168+ "codemirror_mode": "spreadsheet",
15169+ "codemirror_mime_type": "text/x-spreadsheet",
15170+ "language_id": 1013566805
15171+ },
15172+ "LabVIEW": {
15173+ "type": "programming",
15174+ "color": "#fede06",
15175+ "extensions": [
15176+ ".lvproj",
15177+ ".lvclass",
15178+ ".lvlib"
15179+ ],
15180+ "tm_scope": "text.xml",
15181+ "ace_mode": "xml",
15182+ "codemirror_mode": "xml",
15183+ "codemirror_mime_type": "text/xml",
15184+ "language_id": 194
15185+ },
15186+ "Lark": {
15187+ "type": "data",
15188+ "color": "#2980B9",
15189+ "extensions": [
15190+ ".lark"
15191+ ],
15192+ "tm_scope": "source.lark",
15193+ "ace_mode": "text",
15194+ "codemirror_mode": "ebnf",
15195+ "codemirror_mime_type": "text/x-ebnf",
15196+ "language_id": 758480799
15197+ },
15198+ "Lasso": {
15199+ "type": "programming",
15200+ "color": "#999999",
15201+ "extensions": [
15202+ ".lasso",
15203+ ".las",
15204+ ".lasso8",
15205+ ".lasso9"
15206+ ],
15207+ "tm_scope": "file.lasso",
15208+ "aliases": [
15209+ "lassoscript"
15210+ ],
15211+ "ace_mode": "text",
15212+ "language_id": 195
15213+ },
15214+ "Latte": {
15215+ "type": "markup",
15216+ "color": "#f2a542",
15217+ "extensions": [
15218+ ".latte"
15219+ ],
15220+ "tm_scope": "text.html.smarty",
15221+ "ace_mode": "smarty",
15222+ "codemirror_mode": "smarty",
15223+ "codemirror_mime_type": "text/x-smarty",
15224+ "language_id": 196
15225+ },
15226+ "Lean": {
15227+ "type": "programming",
15228+ "extensions": [
15229+ ".lean",
15230+ ".hlean"
15231+ ],
15232+ "tm_scope": "source.lean",
15233+ "ace_mode": "text",
15234+ "language_id": 197
15235+ },
15236+ "Lean 4": {
15237+ "type": "programming",
15238+ "group": "Lean",
15239+ "extensions": [
15240+ ".lean"
15241+ ],
15242+ "tm_scope": "source.lean4",
15243+ "ace_mode": "text",
15244+ "language_id": 455147478
15245+ },
15246+ "Less": {
15247+ "type": "markup",
15248+ "color": "#1d365d",
15249+ "aliases": [
15250+ "less-css"
15251+ ],
15252+ "extensions": [
15253+ ".less"
15254+ ],
15255+ "tm_scope": "source.css.less",
15256+ "ace_mode": "less",
15257+ "codemirror_mode": "css",
15258+ "codemirror_mime_type": "text/css",
15259+ "language_id": 198
15260+ },
15261+ "Lex": {
15262+ "type": "programming",
15263+ "color": "#DBCA00",
15264+ "aliases": [
15265+ "flex"
15266+ ],
15267+ "extensions": [
15268+ ".l",
15269+ ".lex"
15270+ ],
15271+ "filenames": [
15272+ "Lexer.x",
15273+ "lexer.x"
15274+ ],
15275+ "tm_scope": "source.lex",
15276+ "ace_mode": "text",
15277+ "language_id": 199
15278+ },
15279+ "LigoLANG": {
15280+ "type": "programming",
15281+ "color": "#0e74ff",
15282+ "extensions": [
15283+ ".ligo"
15284+ ],
15285+ "tm_scope": "source.ligo",
15286+ "ace_mode": "pascal",
15287+ "codemirror_mode": "pascal",
15288+ "codemirror_mime_type": "text/x-pascal",
15289+ "group": "LigoLANG",
15290+ "language_id": 1040646257
15291+ },
15292+ "LilyPond": {
15293+ "type": "programming",
15294+ "color": "#9ccc7c",
15295+ "extensions": [
15296+ ".ly",
15297+ ".ily"
15298+ ],
15299+ "tm_scope": "source.lilypond",
15300+ "ace_mode": "text",
15301+ "language_id": 200
15302+ },
15303+ "Limbo": {
15304+ "type": "programming",
15305+ "extensions": [
15306+ ".b",
15307+ ".m"
15308+ ],
15309+ "tm_scope": "none",
15310+ "ace_mode": "text",
15311+ "language_id": 201
15312+ },
15313+ "Linker Script": {
15314+ "type": "data",
15315+ "extensions": [
15316+ ".ld",
15317+ ".lds",
15318+ ".x"
15319+ ],
15320+ "filenames": [
15321+ "ld.script"
15322+ ],
15323+ "tm_scope": "none",
15324+ "ace_mode": "text",
15325+ "language_id": 202
15326+ },
15327+ "Linux Kernel Module": {
15328+ "type": "data",
15329+ "extensions": [
15330+ ".mod"
15331+ ],
15332+ "tm_scope": "none",
15333+ "ace_mode": "text",
15334+ "language_id": 203
15335+ },
15336+ "Liquid": {
15337+ "type": "markup",
15338+ "color": "#67b8de",
15339+ "extensions": [
15340+ ".liquid"
15341+ ],
15342+ "tm_scope": "text.html.liquid",
15343+ "ace_mode": "liquid",
15344+ "language_id": 204
15345+ },
15346+ "Literate Agda": {
15347+ "type": "programming",
15348+ "color": "#315665",
15349+ "group": "Agda",
15350+ "extensions": [
15351+ ".lagda"
15352+ ],
15353+ "tm_scope": "none",
15354+ "ace_mode": "text",
15355+ "language_id": 205
15356+ },
15357+ "Literate CoffeeScript": {
15358+ "type": "programming",
15359+ "color": "#244776",
15360+ "tm_scope": "source.litcoffee",
15361+ "group": "CoffeeScript",
15362+ "ace_mode": "text",
15363+ "wrap": true,
15364+ "aliases": [
15365+ "litcoffee"
15366+ ],
15367+ "extensions": [
15368+ ".litcoffee",
15369+ ".coffee.md"
15370+ ],
15371+ "language_id": 206
15372+ },
15373+ "Literate Haskell": {
15374+ "type": "programming",
15375+ "color": "#5e5086",
15376+ "group": "Haskell",
15377+ "aliases": [
15378+ "lhaskell",
15379+ "lhs"
15380+ ],
15381+ "extensions": [
15382+ ".lhs"
15383+ ],
15384+ "tm_scope": "text.tex.latex.haskell",
15385+ "ace_mode": "text",
15386+ "codemirror_mode": "haskell-literate",
15387+ "codemirror_mime_type": "text/x-literate-haskell",
15388+ "language_id": 207
15389+ },
15390+ "LiveScript": {
15391+ "type": "programming",
15392+ "color": "#499886",
15393+ "aliases": [
15394+ "live-script",
15395+ "ls"
15396+ ],
15397+ "extensions": [
15398+ ".ls",
15399+ "._ls"
15400+ ],
15401+ "filenames": [
15402+ "Slakefile"
15403+ ],
15404+ "tm_scope": "source.livescript",
15405+ "ace_mode": "livescript",
15406+ "codemirror_mode": "livescript",
15407+ "codemirror_mime_type": "text/x-livescript",
15408+ "language_id": 208
15409+ },
15410+ "Logos": {
15411+ "type": "programming",
15412+ "extensions": [
15413+ ".xm",
15414+ ".x",
15415+ ".xi"
15416+ ],
15417+ "ace_mode": "text",
15418+ "tm_scope": "source.logos",
15419+ "language_id": 209
15420+ },
15421+ "Logtalk": {
15422+ "type": "programming",
15423+ "color": "#295b9a",
15424+ "extensions": [
15425+ ".lgt",
15426+ ".logtalk"
15427+ ],
15428+ "tm_scope": "source.logtalk",
15429+ "ace_mode": "text",
15430+ "language_id": 210
15431+ },
15432+ "LookML": {
15433+ "type": "programming",
15434+ "ace_mode": "yaml",
15435+ "codemirror_mode": "yaml",
15436+ "codemirror_mime_type": "text/x-yaml",
15437+ "color": "#652B81",
15438+ "extensions": [
15439+ ".lkml",
15440+ ".lookml"
15441+ ],
15442+ "tm_scope": "source.yaml",
15443+ "language_id": 211
15444+ },
15445+ "LoomScript": {
15446+ "type": "programming",
15447+ "extensions": [
15448+ ".ls"
15449+ ],
15450+ "tm_scope": "source.loomscript",
15451+ "ace_mode": "text",
15452+ "language_id": 212
15453+ },
15454+ "Lua": {
15455+ "type": "programming",
15456+ "tm_scope": "source.lua",
15457+ "ace_mode": "lua",
15458+ "codemirror_mode": "lua",
15459+ "codemirror_mime_type": "text/x-lua",
15460+ "color": "#000080",
15461+ "extensions": [
15462+ ".lua",
15463+ ".fcgi",
15464+ ".nse",
15465+ ".p8",
15466+ ".pd_lua",
15467+ ".rbxs",
15468+ ".rockspec",
15469+ ".wlua"
15470+ ],
15471+ "filenames": [
15472+ ".luacheckrc"
15473+ ],
15474+ "interpreters": [
15475+ "lua"
15476+ ],
15477+ "language_id": 213
15478+ },
15479+ "M": {
15480+ "type": "programming",
15481+ "aliases": [
15482+ "mumps"
15483+ ],
15484+ "extensions": [
15485+ ".mumps",
15486+ ".m"
15487+ ],
15488+ "ace_mode": "text",
15489+ "codemirror_mode": "mumps",
15490+ "codemirror_mime_type": "text/x-mumps",
15491+ "language_id": 214,
15492+ "tm_scope": "none"
15493+ },
15494+ "M4": {
15495+ "type": "programming",
15496+ "extensions": [
15497+ ".m4",
15498+ ".mc"
15499+ ],
15500+ "tm_scope": "source.m4",
15501+ "ace_mode": "text",
15502+ "language_id": 215
15503+ },
15504+ "M4Sugar": {
15505+ "type": "programming",
15506+ "group": "M4",
15507+ "aliases": [
15508+ "autoconf"
15509+ ],
15510+ "extensions": [
15511+ ".m4"
15512+ ],
15513+ "filenames": [
15514+ "configure.ac"
15515+ ],
15516+ "tm_scope": "source.m4",
15517+ "ace_mode": "text",
15518+ "language_id": 216
15519+ },
15520+ "MATLAB": {
15521+ "type": "programming",
15522+ "color": "#e16737",
15523+ "aliases": [
15524+ "octave"
15525+ ],
15526+ "extensions": [
15527+ ".matlab",
15528+ ".m"
15529+ ],
15530+ "tm_scope": "source.matlab",
15531+ "ace_mode": "matlab",
15532+ "codemirror_mode": "octave",
15533+ "codemirror_mime_type": "text/x-octave",
15534+ "language_id": 225
15535+ },
15536+ "MAXScript": {
15537+ "type": "programming",
15538+ "color": "#00a6a6",
15539+ "extensions": [
15540+ ".ms",
15541+ ".mcr"
15542+ ],
15543+ "tm_scope": "source.maxscript",
15544+ "ace_mode": "text",
15545+ "language_id": 217
15546+ },
15547+ "MDX": {
15548+ "type": "markup",
15549+ "color": "#fcb32c",
15550+ "ace_mode": "markdown",
15551+ "codemirror_mode": "gfm",
15552+ "codemirror_mime_type": "text/x-gfm",
15553+ "wrap": true,
15554+ "extensions": [
15555+ ".mdx"
15556+ ],
15557+ "tm_scope": "source.mdx",
15558+ "language_id": 512838272
15559+ },
15560+ "MLIR": {
15561+ "type": "programming",
15562+ "color": "#5EC8DB",
15563+ "extensions": [
15564+ ".mlir"
15565+ ],
15566+ "tm_scope": "source.mlir",
15567+ "ace_mode": "text",
15568+ "language_id": 448253929
15569+ },
15570+ "MQL4": {
15571+ "type": "programming",
15572+ "color": "#62A8D6",
15573+ "extensions": [
15574+ ".mq4",
15575+ ".mqh"
15576+ ],
15577+ "tm_scope": "source.mql5",
15578+ "ace_mode": "c_cpp",
15579+ "language_id": 426
15580+ },
15581+ "MQL5": {
15582+ "type": "programming",
15583+ "color": "#4A76B8",
15584+ "extensions": [
15585+ ".mq5",
15586+ ".mqh"
15587+ ],
15588+ "tm_scope": "source.mql5",
15589+ "ace_mode": "c_cpp",
15590+ "language_id": 427
15591+ },
15592+ "MTML": {
15593+ "type": "markup",
15594+ "color": "#b7e1f4",
15595+ "extensions": [
15596+ ".mtml"
15597+ ],
15598+ "tm_scope": "text.html.basic",
15599+ "ace_mode": "html",
15600+ "codemirror_mode": "htmlmixed",
15601+ "codemirror_mime_type": "text/html",
15602+ "language_id": 218
15603+ },
15604+ "MUF": {
15605+ "type": "programming",
15606+ "group": "Forth",
15607+ "extensions": [
15608+ ".muf",
15609+ ".m"
15610+ ],
15611+ "tm_scope": "none",
15612+ "ace_mode": "forth",
15613+ "codemirror_mode": "forth",
15614+ "codemirror_mime_type": "text/x-forth",
15615+ "language_id": 219
15616+ },
15617+ "Macaulay2": {
15618+ "type": "programming",
15619+ "extensions": [
15620+ ".m2"
15621+ ],
15622+ "aliases": [
15623+ "m2"
15624+ ],
15625+ "interpreters": [
15626+ "M2"
15627+ ],
15628+ "ace_mode": "text",
15629+ "tm_scope": "source.m2",
15630+ "color": "#d8ffff",
15631+ "language_id": 34167825
15632+ },
15633+ "Makefile": {
15634+ "type": "programming",
15635+ "color": "#427819",
15636+ "aliases": [
15637+ "bsdmake",
15638+ "make",
15639+ "mf"
15640+ ],
15641+ "extensions": [
15642+ ".mak",
15643+ ".d",
15644+ ".make",
15645+ ".makefile",
15646+ ".mk",
15647+ ".mkfile"
15648+ ],
15649+ "filenames": [
15650+ "BSDmakefile",
15651+ "GNUmakefile",
15652+ "Kbuild",
15653+ "Makefile",
15654+ "Makefile.am",
15655+ "Makefile.boot",
15656+ "Makefile.frag",
15657+ "Makefile.in",
15658+ "Makefile.inc",
15659+ "Makefile.wat",
15660+ "makefile",
15661+ "makefile.sco",
15662+ "mkfile"
15663+ ],
15664+ "interpreters": [
15665+ "make"
15666+ ],
15667+ "tm_scope": "source.makefile",
15668+ "ace_mode": "makefile",
15669+ "codemirror_mode": "cmake",
15670+ "codemirror_mime_type": "text/x-cmake",
15671+ "language_id": 220
15672+ },
15673+ "Mako": {
15674+ "type": "programming",
15675+ "color": "#7e858d",
15676+ "extensions": [
15677+ ".mako",
15678+ ".mao"
15679+ ],
15680+ "tm_scope": "text.html.mako",
15681+ "ace_mode": "text",
15682+ "language_id": 221
15683+ },
15684+ "Markdown": {
15685+ "type": "prose",
15686+ "color": "#083fa1",
15687+ "aliases": [
15688+ "md",
15689+ "pandoc"
15690+ ],
15691+ "ace_mode": "markdown",
15692+ "codemirror_mode": "gfm",
15693+ "codemirror_mime_type": "text/x-gfm",
15694+ "wrap": true,
15695+ "extensions": [
15696+ ".md",
15697+ ".livemd",
15698+ ".markdown",
15699+ ".mdown",
15700+ ".mdwn",
15701+ ".mkd",
15702+ ".mkdn",
15703+ ".mkdown",
15704+ ".ronn",
15705+ ".scd",
15706+ ".workbook"
15707+ ],
15708+ "filenames": [
15709+ "contents.lr"
15710+ ],
15711+ "tm_scope": "text.md",
15712+ "language_id": 222
15713+ },
15714+ "Marko": {
15715+ "type": "markup",
15716+ "color": "#42bff2",
15717+ "tm_scope": "text.marko",
15718+ "extensions": [
15719+ ".marko"
15720+ ],
15721+ "aliases": [
15722+ "markojs"
15723+ ],
15724+ "ace_mode": "text",
15725+ "codemirror_mode": "htmlmixed",
15726+ "codemirror_mime_type": "text/html",
15727+ "language_id": 932782397
15728+ },
15729+ "Mask": {
15730+ "type": "markup",
15731+ "color": "#f97732",
15732+ "ace_mode": "mask",
15733+ "extensions": [
15734+ ".mask"
15735+ ],
15736+ "tm_scope": "source.mask",
15737+ "language_id": 223
15738+ },
15739+ "Mathematica": {
15740+ "type": "programming",
15741+ "color": "#dd1100",
15742+ "extensions": [
15743+ ".mathematica",
15744+ ".cdf",
15745+ ".m",
15746+ ".ma",
15747+ ".mt",
15748+ ".nb",
15749+ ".nbp",
15750+ ".wl",
15751+ ".wlt"
15752+ ],
15753+ "aliases": [
15754+ "mma",
15755+ "wolfram",
15756+ "wolfram language",
15757+ "wolfram lang",
15758+ "wl"
15759+ ],
15760+ "tm_scope": "source.mathematica",
15761+ "ace_mode": "text",
15762+ "codemirror_mode": "mathematica",
15763+ "codemirror_mime_type": "text/x-mathematica",
15764+ "language_id": 224
15765+ },
15766+ "Maven POM": {
15767+ "type": "data",
15768+ "group": "XML",
15769+ "tm_scope": "text.xml.pom",
15770+ "filenames": [
15771+ "pom.xml"
15772+ ],
15773+ "ace_mode": "xml",
15774+ "codemirror_mode": "xml",
15775+ "codemirror_mime_type": "text/xml",
15776+ "language_id": 226
15777+ },
15778+ "Max": {
15779+ "type": "programming",
15780+ "color": "#c4a79c",
15781+ "aliases": [
15782+ "max/msp",
15783+ "maxmsp"
15784+ ],
15785+ "extensions": [
15786+ ".maxpat",
15787+ ".maxhelp",
15788+ ".maxproj",
15789+ ".mxt",
15790+ ".pat"
15791+ ],
15792+ "tm_scope": "source.json",
15793+ "ace_mode": "json",
15794+ "codemirror_mode": "javascript",
15795+ "codemirror_mime_type": "application/json",
15796+ "language_id": 227
15797+ },
15798+ "Mercury": {
15799+ "type": "programming",
15800+ "color": "#ff2b2b",
15801+ "ace_mode": "prolog",
15802+ "interpreters": [
15803+ "mmi"
15804+ ],
15805+ "extensions": [
15806+ ".m",
15807+ ".moo"
15808+ ],
15809+ "tm_scope": "source.mercury",
15810+ "language_id": 229
15811+ },
15812+ "Mermaid": {
15813+ "type": "markup",
15814+ "color": "#ff3670",
15815+ "aliases": [
15816+ "mermaid example"
15817+ ],
15818+ "extensions": [
15819+ ".mmd",
15820+ ".mermaid"
15821+ ],
15822+ "tm_scope": "source.mermaid",
15823+ "ace_mode": "text",
15824+ "language_id": 385992043
15825+ },
15826+ "Meson": {
15827+ "type": "programming",
15828+ "color": "#007800",
15829+ "filenames": [
15830+ "meson.build",
15831+ "meson_options.txt"
15832+ ],
15833+ "tm_scope": "source.meson",
15834+ "ace_mode": "text",
15835+ "language_id": 799141244
15836+ },
15837+ "Metal": {
15838+ "type": "programming",
15839+ "color": "#8f14e9",
15840+ "extensions": [
15841+ ".metal"
15842+ ],
15843+ "tm_scope": "source.c++",
15844+ "ace_mode": "c_cpp",
15845+ "codemirror_mode": "clike",
15846+ "codemirror_mime_type": "text/x-c++src",
15847+ "language_id": 230
15848+ },
15849+ "Microsoft Developer Studio Project": {
15850+ "type": "data",
15851+ "extensions": [
15852+ ".dsp"
15853+ ],
15854+ "tm_scope": "none",
15855+ "ace_mode": "text",
15856+ "language_id": 800983837
15857+ },
15858+ "Microsoft Visual Studio Solution": {
15859+ "type": "data",
15860+ "extensions": [
15861+ ".sln"
15862+ ],
15863+ "tm_scope": "source.solution",
15864+ "ace_mode": "text",
15865+ "language_id": 849523096
15866+ },
15867+ "MiniD": {
15868+ "type": "programming",
15869+ "extensions": [
15870+ ".minid"
15871+ ],
15872+ "tm_scope": "none",
15873+ "ace_mode": "text",
15874+ "language_id": 231
15875+ },
15876+ "MiniYAML": {
15877+ "type": "data",
15878+ "color": "#ff1111",
15879+ "tm_scope": "source.miniyaml",
15880+ "extensions": [
15881+ ".yaml",
15882+ ".yml"
15883+ ],
15884+ "ace_mode": "yaml",
15885+ "codemirror_mode": "yaml",
15886+ "codemirror_mime_type": "text/x-yaml",
15887+ "language_id": 4896465
15888+ },
15889+ "Mint": {
15890+ "type": "programming",
15891+ "extensions": [
15892+ ".mint"
15893+ ],
15894+ "ace_mode": "text",
15895+ "color": "#02b046",
15896+ "tm_scope": "source.mint",
15897+ "language_id": 968740319
15898+ },
15899+ "Mirah": {
15900+ "type": "programming",
15901+ "color": "#c7a938",
15902+ "extensions": [
15903+ ".druby",
15904+ ".duby",
15905+ ".mirah"
15906+ ],
15907+ "tm_scope": "source.ruby",
15908+ "ace_mode": "ruby",
15909+ "codemirror_mode": "ruby",
15910+ "codemirror_mime_type": "text/x-ruby",
15911+ "language_id": 232
15912+ },
15913+ "Modelica": {
15914+ "type": "programming",
15915+ "color": "#de1d31",
15916+ "extensions": [
15917+ ".mo"
15918+ ],
15919+ "tm_scope": "source.modelica",
15920+ "ace_mode": "text",
15921+ "codemirror_mode": "modelica",
15922+ "codemirror_mime_type": "text/x-modelica",
15923+ "language_id": 233
15924+ },
15925+ "Modula-2": {
15926+ "type": "programming",
15927+ "color": "#10253f",
15928+ "extensions": [
15929+ ".mod"
15930+ ],
15931+ "tm_scope": "source.modula2",
15932+ "ace_mode": "text",
15933+ "language_id": 234
15934+ },
15935+ "Modula-3": {
15936+ "type": "programming",
15937+ "extensions": [
15938+ ".i3",
15939+ ".ig",
15940+ ".m3",
15941+ ".mg"
15942+ ],
15943+ "color": "#223388",
15944+ "ace_mode": "text",
15945+ "tm_scope": "source.modula-3",
15946+ "language_id": 564743864
15947+ },
15948+ "Module Management System": {
15949+ "type": "programming",
15950+ "extensions": [
15951+ ".mms",
15952+ ".mmk"
15953+ ],
15954+ "filenames": [
15955+ "descrip.mmk",
15956+ "descrip.mms"
15957+ ],
15958+ "tm_scope": "none",
15959+ "ace_mode": "text",
15960+ "language_id": 235
15961+ },
15962+ "Monkey": {
15963+ "type": "programming",
15964+ "extensions": [
15965+ ".monkey",
15966+ ".monkey2"
15967+ ],
15968+ "ace_mode": "text",
15969+ "tm_scope": "source.monkey",
15970+ "language_id": 236
15971+ },
15972+ "Monkey C": {
15973+ "type": "programming",
15974+ "color": "#8D6747",
15975+ "extensions": [
15976+ ".mc"
15977+ ],
15978+ "tm_scope": "source.mc",
15979+ "ace_mode": "c_cpp",
15980+ "codemirror_mode": "clike",
15981+ "codemirror_mime_type": "text/x-csrc",
15982+ "language_id": 231751931
15983+ },
15984+ "Moocode": {
15985+ "type": "programming",
15986+ "extensions": [
15987+ ".moo"
15988+ ],
15989+ "tm_scope": "none",
15990+ "ace_mode": "text",
15991+ "language_id": 237
15992+ },
15993+ "MoonScript": {
15994+ "type": "programming",
15995+ "color": "#ff4585",
15996+ "extensions": [
15997+ ".moon"
15998+ ],
15999+ "interpreters": [
16000+ "moon"
16001+ ],
16002+ "tm_scope": "source.moonscript",
16003+ "ace_mode": "text",
16004+ "language_id": 238
16005+ },
16006+ "Motoko": {
16007+ "type": "programming",
16008+ "color": "#fbb03b",
16009+ "extensions": [
16010+ ".mo"
16011+ ],
16012+ "tm_scope": "source.mo",
16013+ "ace_mode": "text",
16014+ "language_id": 202937027
16015+ },
16016+ "Motorola 68K Assembly": {
16017+ "type": "programming",
16018+ "color": "#005daa",
16019+ "group": "Assembly",
16020+ "aliases": [
16021+ "m68k"
16022+ ],
16023+ "extensions": [
16024+ ".asm",
16025+ ".i",
16026+ ".inc",
16027+ ".s",
16028+ ".x68"
16029+ ],
16030+ "tm_scope": "source.m68k",
16031+ "ace_mode": "assembly_x86",
16032+ "language_id": 477582706
16033+ },
16034+ "Move": {
16035+ "type": "programming",
16036+ "color": "#4a137a",
16037+ "extensions": [
16038+ ".move"
16039+ ],
16040+ "tm_scope": "source.move",
16041+ "ace_mode": "text",
16042+ "language_id": 638334599
16043+ },
16044+ "Muse": {
16045+ "type": "prose",
16046+ "extensions": [
16047+ ".muse"
16048+ ],
16049+ "tm_scope": "text.muse",
16050+ "ace_mode": "text",
16051+ "wrap": true,
16052+ "language_id": 474864066,
16053+ "aliases": [
16054+ "amusewiki",
16055+ "emacs muse"
16056+ ]
16057+ },
16058+ "Mustache": {
16059+ "type": "markup",
16060+ "color": "#724b3b",
16061+ "extensions": [
16062+ ".mustache"
16063+ ],
16064+ "tm_scope": "text.html.smarty",
16065+ "ace_mode": "smarty",
16066+ "codemirror_mode": "smarty",
16067+ "codemirror_mime_type": "text/x-smarty",
16068+ "language_id": 638334590
16069+ },
16070+ "Myghty": {
16071+ "type": "programming",
16072+ "extensions": [
16073+ ".myt"
16074+ ],
16075+ "tm_scope": "none",
16076+ "ace_mode": "text",
16077+ "language_id": 239
16078+ },
16079+ "NASL": {
16080+ "type": "programming",
16081+ "extensions": [
16082+ ".nasl",
16083+ ".inc"
16084+ ],
16085+ "tm_scope": "source.nasl",
16086+ "ace_mode": "text",
16087+ "language_id": 171666519
16088+ },
16089+ "NCL": {
16090+ "type": "programming",
16091+ "color": "#28431f",
16092+ "extensions": [
16093+ ".ncl"
16094+ ],
16095+ "tm_scope": "source.ncl",
16096+ "ace_mode": "text",
16097+ "language_id": 240
16098+ },
16099+ "NEON": {
16100+ "type": "data",
16101+ "extensions": [
16102+ ".neon"
16103+ ],
16104+ "tm_scope": "source.neon",
16105+ "ace_mode": "text",
16106+ "aliases": [
16107+ "nette object notation",
16108+ "ne-on"
16109+ ],
16110+ "language_id": 481192983
16111+ },
16112+ "NL": {
16113+ "type": "data",
16114+ "extensions": [
16115+ ".nl"
16116+ ],
16117+ "tm_scope": "none",
16118+ "ace_mode": "text",
16119+ "language_id": 241
16120+ },
16121+ "NPM Config": {
16122+ "type": "data",
16123+ "color": "#cb3837",
16124+ "group": "INI",
16125+ "aliases": [
16126+ "npmrc"
16127+ ],
16128+ "filenames": [
16129+ ".npmrc"
16130+ ],
16131+ "tm_scope": "source.ini.npmrc",
16132+ "ace_mode": "text",
16133+ "language_id": 685022663
16134+ },
16135+ "NSIS": {
16136+ "type": "programming",
16137+ "extensions": [
16138+ ".nsi",
16139+ ".nsh"
16140+ ],
16141+ "tm_scope": "source.nsis",
16142+ "ace_mode": "text",
16143+ "codemirror_mode": "nsis",
16144+ "codemirror_mime_type": "text/x-nsis",
16145+ "language_id": 242
16146+ },
16147+ "NWScript": {
16148+ "type": "programming",
16149+ "color": "#111522",
16150+ "extensions": [
16151+ ".nss"
16152+ ],
16153+ "tm_scope": "source.c.nwscript",
16154+ "ace_mode": "c_cpp",
16155+ "codemirror_mode": "clike",
16156+ "codemirror_mime_type": "text/x-csrc",
16157+ "language_id": 731233819
16158+ },
16159+ "Nasal": {
16160+ "type": "programming",
16161+ "color": "#1d2c4e",
16162+ "extensions": [
16163+ ".nas"
16164+ ],
16165+ "tm_scope": "source.nasal",
16166+ "ace_mode": "nasal",
16167+ "language_id": 178322513
16168+ },
16169+ "Nearley": {
16170+ "type": "programming",
16171+ "ace_mode": "text",
16172+ "color": "#990000",
16173+ "extensions": [
16174+ ".ne",
16175+ ".nearley"
16176+ ],
16177+ "tm_scope": "source.ne",
16178+ "language_id": 521429430
16179+ },
16180+ "Nemerle": {
16181+ "type": "programming",
16182+ "color": "#3d3c6e",
16183+ "extensions": [
16184+ ".n"
16185+ ],
16186+ "tm_scope": "source.nemerle",
16187+ "ace_mode": "text",
16188+ "language_id": 243
16189+ },
16190+ "NetLinx": {
16191+ "type": "programming",
16192+ "color": "#0aa0ff",
16193+ "extensions": [
16194+ ".axs",
16195+ ".axi"
16196+ ],
16197+ "tm_scope": "source.netlinx",
16198+ "ace_mode": "text",
16199+ "language_id": 244
16200+ },
16201+ "NetLinx+ERB": {
16202+ "type": "programming",
16203+ "color": "#747faa",
16204+ "extensions": [
16205+ ".axs.erb",
16206+ ".axi.erb"
16207+ ],
16208+ "tm_scope": "source.netlinx.erb",
16209+ "ace_mode": "text",
16210+ "language_id": 245
16211+ },
16212+ "NetLogo": {
16213+ "type": "programming",
16214+ "color": "#ff6375",
16215+ "extensions": [
16216+ ".nlogo"
16217+ ],
16218+ "tm_scope": "source.lisp",
16219+ "ace_mode": "lisp",
16220+ "codemirror_mode": "commonlisp",
16221+ "codemirror_mime_type": "text/x-common-lisp",
16222+ "language_id": 246
16223+ },
16224+ "NewLisp": {
16225+ "type": "programming",
16226+ "color": "#87AED7",
16227+ "extensions": [
16228+ ".nl",
16229+ ".lisp",
16230+ ".lsp"
16231+ ],
16232+ "interpreters": [
16233+ "newlisp"
16234+ ],
16235+ "tm_scope": "source.lisp",
16236+ "ace_mode": "lisp",
16237+ "codemirror_mode": "commonlisp",
16238+ "codemirror_mime_type": "text/x-common-lisp",
16239+ "language_id": 247
16240+ },
16241+ "Nextflow": {
16242+ "type": "programming",
16243+ "ace_mode": "groovy",
16244+ "tm_scope": "source.nextflow",
16245+ "color": "#3ac486",
16246+ "extensions": [
16247+ ".nf"
16248+ ],
16249+ "filenames": [
16250+ "nextflow.config"
16251+ ],
16252+ "interpreters": [
16253+ "nextflow"
16254+ ],
16255+ "language_id": 506780613
16256+ },
16257+ "Nginx": {
16258+ "type": "data",
16259+ "color": "#009639",
16260+ "extensions": [
16261+ ".nginx",
16262+ ".nginxconf",
16263+ ".vhost"
16264+ ],
16265+ "filenames": [
16266+ "nginx.conf"
16267+ ],
16268+ "tm_scope": "source.nginx",
16269+ "aliases": [
16270+ "nginx configuration file"
16271+ ],
16272+ "ace_mode": "text",
16273+ "codemirror_mode": "nginx",
16274+ "codemirror_mime_type": "text/x-nginx-conf",
16275+ "language_id": 248
16276+ },
16277+ "Nim": {
16278+ "type": "programming",
16279+ "color": "#ffc200",
16280+ "extensions": [
16281+ ".nim",
16282+ ".nim.cfg",
16283+ ".nimble",
16284+ ".nimrod",
16285+ ".nims"
16286+ ],
16287+ "filenames": [
16288+ "nim.cfg"
16289+ ],
16290+ "ace_mode": "text",
16291+ "tm_scope": "source.nim",
16292+ "language_id": 249
16293+ },
16294+ "Ninja": {
16295+ "type": "data",
16296+ "tm_scope": "source.ninja",
16297+ "extensions": [
16298+ ".ninja"
16299+ ],
16300+ "ace_mode": "text",
16301+ "language_id": 250
16302+ },
16303+ "Nit": {
16304+ "type": "programming",
16305+ "color": "#009917",
16306+ "extensions": [
16307+ ".nit"
16308+ ],
16309+ "tm_scope": "source.nit",
16310+ "ace_mode": "text",
16311+ "language_id": 251
16312+ },
16313+ "Nix": {
16314+ "type": "programming",
16315+ "color": "#7e7eff",
16316+ "extensions": [
16317+ ".nix"
16318+ ],
16319+ "aliases": [
16320+ "nixos"
16321+ ],
16322+ "tm_scope": "source.nix",
16323+ "ace_mode": "nix",
16324+ "language_id": 252
16325+ },
16326+ "Nu": {
16327+ "type": "programming",
16328+ "color": "#c9df40",
16329+ "aliases": [
16330+ "nush"
16331+ ],
16332+ "extensions": [
16333+ ".nu"
16334+ ],
16335+ "filenames": [
16336+ "Nukefile"
16337+ ],
16338+ "tm_scope": "source.nu",
16339+ "ace_mode": "scheme",
16340+ "codemirror_mode": "scheme",
16341+ "codemirror_mime_type": "text/x-scheme",
16342+ "interpreters": [
16343+ "nush"
16344+ ],
16345+ "language_id": 253
16346+ },
16347+ "NumPy": {
16348+ "type": "programming",
16349+ "color": "#9C8AF9",
16350+ "group": "Python",
16351+ "extensions": [
16352+ ".numpy",
16353+ ".numpyw",
16354+ ".numsc"
16355+ ],
16356+ "tm_scope": "none",
16357+ "ace_mode": "text",
16358+ "codemirror_mode": "python",
16359+ "codemirror_mime_type": "text/x-python",
16360+ "language_id": 254
16361+ },
16362+ "Nunjucks": {
16363+ "type": "markup",
16364+ "color": "#3d8137",
16365+ "extensions": [
16366+ ".njk"
16367+ ],
16368+ "aliases": [
16369+ "njk"
16370+ ],
16371+ "tm_scope": "text.html.nunjucks",
16372+ "ace_mode": "nunjucks",
16373+ "language_id": 461856962
16374+ },
16375+ "Nushell": {
16376+ "type": "programming",
16377+ "color": "#4E9906",
16378+ "extensions": [
16379+ ".nu"
16380+ ],
16381+ "interpreters": [
16382+ "nu"
16383+ ],
16384+ "aliases": [
16385+ "nu-script",
16386+ "nushell-script"
16387+ ],
16388+ "tm_scope": "source.nushell",
16389+ "ace_mode": "sh",
16390+ "codemirror_mode": "shell",
16391+ "codemirror_mime_type": "text/x-sh",
16392+ "language_id": 446573572
16393+ },
16394+ "OASv2-json": {
16395+ "type": "data",
16396+ "color": "#85ea2d",
16397+ "extensions": [
16398+ ".json"
16399+ ],
16400+ "group": "OpenAPI Specification v2",
16401+ "tm_scope": "source.json",
16402+ "ace_mode": "json",
16403+ "codemirror_mode": "javascript",
16404+ "codemirror_mime_type": "application/json",
16405+ "language_id": 834374816
16406+ },
16407+ "OASv2-yaml": {
16408+ "type": "data",
16409+ "color": "#85ea2d",
16410+ "extensions": [
16411+ ".yaml",
16412+ ".yml"
16413+ ],
16414+ "group": "OpenAPI Specification v2",
16415+ "tm_scope": "source.yaml",
16416+ "ace_mode": "yaml",
16417+ "codemirror_mode": "yaml",
16418+ "codemirror_mime_type": "text/x-yaml",
16419+ "language_id": 105187618
16420+ },
16421+ "OASv3-json": {
16422+ "type": "data",
16423+ "color": "#85ea2d",
16424+ "extensions": [
16425+ ".json"
16426+ ],
16427+ "group": "OpenAPI Specification v3",
16428+ "tm_scope": "source.json",
16429+ "ace_mode": "json",
16430+ "codemirror_mode": "javascript",
16431+ "codemirror_mime_type": "application/json",
16432+ "language_id": 980062566
16433+ },
16434+ "OASv3-yaml": {
16435+ "type": "data",
16436+ "color": "#85ea2d",
16437+ "extensions": [
16438+ ".yaml",
16439+ ".yml"
16440+ ],
16441+ "group": "OpenAPI Specification v3",
16442+ "tm_scope": "source.yaml",
16443+ "ace_mode": "yaml",
16444+ "codemirror_mode": "yaml",
16445+ "codemirror_mime_type": "text/x-yaml",
16446+ "language_id": 51239111
16447+ },
16448+ "OCaml": {
16449+ "type": "programming",
16450+ "ace_mode": "ocaml",
16451+ "codemirror_mode": "mllike",
16452+ "codemirror_mime_type": "text/x-ocaml",
16453+ "color": "#ef7a08",
16454+ "extensions": [
16455+ ".ml",
16456+ ".eliom",
16457+ ".eliomi",
16458+ ".ml4",
16459+ ".mli",
16460+ ".mll",
16461+ ".mly"
16462+ ],
16463+ "interpreters": [
16464+ "ocaml",
16465+ "ocamlrun",
16466+ "ocamlscript"
16467+ ],
16468+ "tm_scope": "source.ocaml",
16469+ "language_id": 255
16470+ },
16471+ "ObjDump": {
16472+ "type": "data",
16473+ "extensions": [
16474+ ".objdump"
16475+ ],
16476+ "tm_scope": "objdump.x86asm",
16477+ "ace_mode": "assembly_x86",
16478+ "language_id": 256
16479+ },
16480+ "Object Data Instance Notation": {
16481+ "type": "data",
16482+ "extensions": [
16483+ ".odin"
16484+ ],
16485+ "tm_scope": "source.odin-ehr",
16486+ "ace_mode": "text",
16487+ "language_id": 985227236
16488+ },
16489+ "ObjectScript": {
16490+ "type": "programming",
16491+ "extensions": [
16492+ ".cls"
16493+ ],
16494+ "language_id": 202735509,
16495+ "tm_scope": "source.objectscript",
16496+ "color": "#424893",
16497+ "ace_mode": "text"
16498+ },
16499+ "Objective-C": {
16500+ "type": "programming",
16501+ "tm_scope": "source.objc",
16502+ "color": "#438eff",
16503+ "aliases": [
16504+ "obj-c",
16505+ "objc",
16506+ "objectivec"
16507+ ],
16508+ "extensions": [
16509+ ".m",
16510+ ".h"
16511+ ],
16512+ "ace_mode": "objectivec",
16513+ "codemirror_mode": "clike",
16514+ "codemirror_mime_type": "text/x-objectivec",
16515+ "language_id": 257
16516+ },
16517+ "Objective-C++": {
16518+ "type": "programming",
16519+ "tm_scope": "source.objc++",
16520+ "color": "#6866fb",
16521+ "aliases": [
16522+ "obj-c++",
16523+ "objc++",
16524+ "objectivec++"
16525+ ],
16526+ "extensions": [
16527+ ".mm"
16528+ ],
16529+ "ace_mode": "objectivec",
16530+ "codemirror_mode": "clike",
16531+ "codemirror_mime_type": "text/x-objectivec",
16532+ "language_id": 258
16533+ },
16534+ "Objective-J": {
16535+ "type": "programming",
16536+ "color": "#ff0c5a",
16537+ "aliases": [
16538+ "obj-j",
16539+ "objectivej",
16540+ "objj"
16541+ ],
16542+ "extensions": [
16543+ ".j",
16544+ ".sj"
16545+ ],
16546+ "tm_scope": "source.js.objj",
16547+ "ace_mode": "text",
16548+ "language_id": 259
16549+ },
16550+ "Odin": {
16551+ "type": "programming",
16552+ "color": "#60AFFE",
16553+ "aliases": [
16554+ "odinlang",
16555+ "odin-lang"
16556+ ],
16557+ "extensions": [
16558+ ".odin"
16559+ ],
16560+ "tm_scope": "source.odin",
16561+ "ace_mode": "text",
16562+ "language_id": 889244082
16563+ },
16564+ "Omgrofl": {
16565+ "type": "programming",
16566+ "extensions": [
16567+ ".omgrofl"
16568+ ],
16569+ "color": "#cabbff",
16570+ "tm_scope": "none",
16571+ "ace_mode": "text",
16572+ "language_id": 260
16573+ },
16574+ "Opa": {
16575+ "type": "programming",
16576+ "extensions": [
16577+ ".opa"
16578+ ],
16579+ "tm_scope": "source.opa",
16580+ "ace_mode": "text",
16581+ "language_id": 261
16582+ },
16583+ "Opal": {
16584+ "type": "programming",
16585+ "color": "#f7ede0",
16586+ "extensions": [
16587+ ".opal"
16588+ ],
16589+ "tm_scope": "source.opal",
16590+ "ace_mode": "text",
16591+ "language_id": 262
16592+ },
16593+ "Open Policy Agent": {
16594+ "type": "programming",
16595+ "color": "#7d9199",
16596+ "ace_mode": "text",
16597+ "extensions": [
16598+ ".rego"
16599+ ],
16600+ "language_id": 840483232,
16601+ "tm_scope": "source.rego"
16602+ },
16603+ "OpenAPI Specification v2": {
16604+ "aliases": [
16605+ "oasv2"
16606+ ],
16607+ "type": "data",
16608+ "color": "#85ea2d",
16609+ "tm_scope": "none",
16610+ "ace_mode": "text",
16611+ "language_id": 848295328
16612+ },
16613+ "OpenAPI Specification v3": {
16614+ "aliases": [
16615+ "oasv3"
16616+ ],
16617+ "type": "data",
16618+ "color": "#85ea2d",
16619+ "tm_scope": "none",
16620+ "ace_mode": "text",
16621+ "language_id": 557959099
16622+ },
16623+ "OpenCL": {
16624+ "type": "programming",
16625+ "color": "#ed2e2d",
16626+ "group": "C",
16627+ "extensions": [
16628+ ".cl",
16629+ ".opencl"
16630+ ],
16631+ "tm_scope": "source.c",
16632+ "ace_mode": "c_cpp",
16633+ "codemirror_mode": "clike",
16634+ "codemirror_mime_type": "text/x-csrc",
16635+ "language_id": 263
16636+ },
16637+ "OpenEdge ABL": {
16638+ "type": "programming",
16639+ "color": "#5ce600",
16640+ "aliases": [
16641+ "progress",
16642+ "openedge",
16643+ "abl"
16644+ ],
16645+ "extensions": [
16646+ ".p",
16647+ ".cls",
16648+ ".w"
16649+ ],
16650+ "tm_scope": "source.abl",
16651+ "ace_mode": "text",
16652+ "language_id": 264
16653+ },
16654+ "OpenQASM": {
16655+ "type": "programming",
16656+ "extensions": [
16657+ ".qasm"
16658+ ],
16659+ "color": "#AA70FF",
16660+ "tm_scope": "source.qasm",
16661+ "ace_mode": "text",
16662+ "language_id": 153739399
16663+ },
16664+ "OpenRC runscript": {
16665+ "type": "programming",
16666+ "group": "Shell",
16667+ "aliases": [
16668+ "openrc"
16669+ ],
16670+ "interpreters": [
16671+ "openrc-run"
16672+ ],
16673+ "tm_scope": "source.shell",
16674+ "ace_mode": "sh",
16675+ "codemirror_mode": "shell",
16676+ "codemirror_mime_type": "text/x-sh",
16677+ "language_id": 265
16678+ },
16679+ "OpenSCAD": {
16680+ "type": "programming",
16681+ "color": "#e5cd45",
16682+ "extensions": [
16683+ ".scad"
16684+ ],
16685+ "tm_scope": "source.scad",
16686+ "ace_mode": "scad",
16687+ "language_id": 266
16688+ },
16689+ "OpenStep Property List": {
16690+ "type": "data",
16691+ "extensions": [
16692+ ".plist",
16693+ ".glyphs"
16694+ ],
16695+ "tm_scope": "source.plist",
16696+ "ace_mode": "text",
16697+ "language_id": 598917541
16698+ },
16699+ "OpenType Feature File": {
16700+ "type": "data",
16701+ "aliases": [
16702+ "AFDKO"
16703+ ],
16704+ "extensions": [
16705+ ".fea"
16706+ ],
16707+ "tm_scope": "source.opentype",
16708+ "ace_mode": "text",
16709+ "language_id": 374317347
16710+ },
16711+ "Option List": {
16712+ "type": "data",
16713+ "color": "#476732",
16714+ "aliases": [
16715+ "opts",
16716+ "ackrc"
16717+ ],
16718+ "filenames": [
16719+ ".ackrc",
16720+ ".rspec",
16721+ ".yardopts",
16722+ "ackrc",
16723+ "mocha.opts"
16724+ ],
16725+ "tm_scope": "source.opts",
16726+ "ace_mode": "sh",
16727+ "codemirror_mode": "shell",
16728+ "codemirror_mime_type": "text/x-sh",
16729+ "language_id": 723589315
16730+ },
16731+ "Org": {
16732+ "type": "prose",
16733+ "color": "#77aa99",
16734+ "wrap": true,
16735+ "extensions": [
16736+ ".org"
16737+ ],
16738+ "tm_scope": "none",
16739+ "ace_mode": "text",
16740+ "language_id": 267
16741+ },
16742+ "Ox": {
16743+ "type": "programming",
16744+ "extensions": [
16745+ ".ox",
16746+ ".oxh",
16747+ ".oxo"
16748+ ],
16749+ "tm_scope": "source.ox",
16750+ "ace_mode": "text",
16751+ "language_id": 268
16752+ },
16753+ "Oxygene": {
16754+ "type": "programming",
16755+ "color": "#cdd0e3",
16756+ "extensions": [
16757+ ".oxygene"
16758+ ],
16759+ "tm_scope": "none",
16760+ "ace_mode": "text",
16761+ "language_id": 269
16762+ },
16763+ "Oz": {
16764+ "type": "programming",
16765+ "color": "#fab738",
16766+ "extensions": [
16767+ ".oz"
16768+ ],
16769+ "tm_scope": "source.oz",
16770+ "ace_mode": "text",
16771+ "codemirror_mode": "oz",
16772+ "codemirror_mime_type": "text/x-oz",
16773+ "language_id": 270
16774+ },
16775+ "P4": {
16776+ "type": "programming",
16777+ "color": "#7055b5",
16778+ "extensions": [
16779+ ".p4"
16780+ ],
16781+ "tm_scope": "source.p4",
16782+ "ace_mode": "text",
16783+ "language_id": 348895984
16784+ },
16785+ "PDDL": {
16786+ "type": "programming",
16787+ "color": "#0d00ff",
16788+ "extensions": [
16789+ ".pddl"
16790+ ],
16791+ "tm_scope": "source.pddl",
16792+ "ace_mode": "text",
16793+ "language_id": 736235603
16794+ },
16795+ "PEG.js": {
16796+ "type": "programming",
16797+ "color": "#234d6b",
16798+ "extensions": [
16799+ ".pegjs"
16800+ ],
16801+ "tm_scope": "source.pegjs",
16802+ "ace_mode": "javascript",
16803+ "codemirror_mode": "javascript",
16804+ "codemirror_mime_type": "text/javascript",
16805+ "language_id": 81442128
16806+ },
16807+ "PHP": {
16808+ "type": "programming",
16809+ "tm_scope": "text.html.php",
16810+ "ace_mode": "php",
16811+ "codemirror_mode": "php",
16812+ "codemirror_mime_type": "application/x-httpd-php",
16813+ "color": "#4F5D95",
16814+ "extensions": [
16815+ ".php",
16816+ ".aw",
16817+ ".ctp",
16818+ ".fcgi",
16819+ ".inc",
16820+ ".php3",
16821+ ".php4",
16822+ ".php5",
16823+ ".phps",
16824+ ".phpt"
16825+ ],
16826+ "filenames": [
16827+ ".php",
16828+ ".php_cs",
16829+ ".php_cs.dist",
16830+ "Phakefile"
16831+ ],
16832+ "interpreters": [
16833+ "php"
16834+ ],
16835+ "aliases": [
16836+ "inc"
16837+ ],
16838+ "language_id": 272
16839+ },
16840+ "PLSQL": {
16841+ "type": "programming",
16842+ "ace_mode": "sql",
16843+ "codemirror_mode": "sql",
16844+ "codemirror_mime_type": "text/x-plsql",
16845+ "tm_scope": "none",
16846+ "color": "#dad8d8",
16847+ "extensions": [
16848+ ".pls",
16849+ ".bdy",
16850+ ".ddl",
16851+ ".fnc",
16852+ ".pck",
16853+ ".pkb",
16854+ ".pks",
16855+ ".plb",
16856+ ".plsql",
16857+ ".prc",
16858+ ".spc",
16859+ ".sql",
16860+ ".tpb",
16861+ ".tps",
16862+ ".trg",
16863+ ".vw"
16864+ ],
16865+ "language_id": 273
16866+ },
16867+ "PLpgSQL": {
16868+ "type": "programming",
16869+ "color": "#336790",
16870+ "ace_mode": "pgsql",
16871+ "codemirror_mode": "sql",
16872+ "codemirror_mime_type": "text/x-sql",
16873+ "tm_scope": "source.sql",
16874+ "extensions": [
16875+ ".pgsql",
16876+ ".sql"
16877+ ],
16878+ "language_id": 274
16879+ },
16880+ "POV-Ray SDL": {
16881+ "type": "programming",
16882+ "color": "#6bac65",
16883+ "aliases": [
16884+ "pov-ray",
16885+ "povray"
16886+ ],
16887+ "extensions": [
16888+ ".pov",
16889+ ".inc"
16890+ ],
16891+ "tm_scope": "source.pov-ray sdl",
16892+ "ace_mode": "text",
16893+ "language_id": 275
16894+ },
16895+ "Pact": {
16896+ "type": "programming",
16897+ "color": "#F7A8B8",
16898+ "ace_mode": "text",
16899+ "tm_scope": "source.pact",
16900+ "extensions": [
16901+ ".pact"
16902+ ],
16903+ "language_id": 756774415
16904+ },
16905+ "Pan": {
16906+ "type": "programming",
16907+ "color": "#cc0000",
16908+ "extensions": [
16909+ ".pan"
16910+ ],
16911+ "tm_scope": "source.pan",
16912+ "ace_mode": "text",
16913+ "language_id": 276
16914+ },
16915+ "Papyrus": {
16916+ "type": "programming",
16917+ "color": "#6600cc",
16918+ "extensions": [
16919+ ".psc"
16920+ ],
16921+ "tm_scope": "source.papyrus.skyrim",
16922+ "ace_mode": "text",
16923+ "language_id": 277
16924+ },
16925+ "Parrot": {
16926+ "type": "programming",
16927+ "color": "#f3ca0a",
16928+ "extensions": [
16929+ ".parrot"
16930+ ],
16931+ "tm_scope": "none",
16932+ "ace_mode": "text",
16933+ "language_id": 278
16934+ },
16935+ "Parrot Assembly": {
16936+ "group": "Parrot",
16937+ "type": "programming",
16938+ "aliases": [
16939+ "pasm"
16940+ ],
16941+ "extensions": [
16942+ ".pasm"
16943+ ],
16944+ "interpreters": [
16945+ "parrot"
16946+ ],
16947+ "tm_scope": "none",
16948+ "ace_mode": "text",
16949+ "language_id": 279
16950+ },
16951+ "Parrot Internal Representation": {
16952+ "group": "Parrot",
16953+ "tm_scope": "source.parrot.pir",
16954+ "type": "programming",
16955+ "aliases": [
16956+ "pir"
16957+ ],
16958+ "extensions": [
16959+ ".pir"
16960+ ],
16961+ "interpreters": [
16962+ "parrot"
16963+ ],
16964+ "ace_mode": "text",
16965+ "language_id": 280
16966+ },
16967+ "Pascal": {
16968+ "type": "programming",
16969+ "color": "#E3F171",
16970+ "aliases": [
16971+ "delphi",
16972+ "objectpascal"
16973+ ],
16974+ "extensions": [
16975+ ".pas",
16976+ ".dfm",
16977+ ".dpr",
16978+ ".inc",
16979+ ".lpr",
16980+ ".pascal",
16981+ ".pp"
16982+ ],
16983+ "interpreters": [
16984+ "instantfpc"
16985+ ],
16986+ "tm_scope": "source.pascal",
16987+ "ace_mode": "pascal",
16988+ "codemirror_mode": "pascal",
16989+ "codemirror_mime_type": "text/x-pascal",
16990+ "language_id": 281
16991+ },
16992+ "Pawn": {
16993+ "type": "programming",
16994+ "color": "#dbb284",
16995+ "extensions": [
16996+ ".pwn",
16997+ ".inc",
16998+ ".sma"
16999+ ],
17000+ "tm_scope": "source.pawn",
17001+ "ace_mode": "text",
17002+ "language_id": 271
17003+ },
17004+ "Pep8": {
17005+ "type": "programming",
17006+ "color": "#C76F5B",
17007+ "extensions": [
17008+ ".pep"
17009+ ],
17010+ "ace_mode": "text",
17011+ "tm_scope": "source.pep8",
17012+ "language_id": 840372442
17013+ },
17014+ "Perl": {
17015+ "type": "programming",
17016+ "tm_scope": "source.perl",
17017+ "ace_mode": "perl",
17018+ "codemirror_mode": "perl",
17019+ "codemirror_mime_type": "text/x-perl",
17020+ "color": "#0298c3",
17021+ "extensions": [
17022+ ".pl",
17023+ ".al",
17024+ ".cgi",
17025+ ".fcgi",
17026+ ".perl",
17027+ ".ph",
17028+ ".plx",
17029+ ".pm",
17030+ ".psgi",
17031+ ".t"
17032+ ],
17033+ "filenames": [
17034+ ".latexmkrc",
17035+ "Makefile.PL",
17036+ "Rexfile",
17037+ "ack",
17038+ "cpanfile",
17039+ "latexmkrc"
17040+ ],
17041+ "interpreters": [
17042+ "cperl",
17043+ "perl"
17044+ ],
17045+ "aliases": [
17046+ "cperl"
17047+ ],
17048+ "language_id": 282
17049+ },
17050+ "Pic": {
17051+ "type": "markup",
17052+ "group": "Roff",
17053+ "tm_scope": "source.pic",
17054+ "extensions": [
17055+ ".pic",
17056+ ".chem"
17057+ ],
17058+ "aliases": [
17059+ "pikchr"
17060+ ],
17061+ "ace_mode": "text",
17062+ "codemirror_mode": "troff",
17063+ "codemirror_mime_type": "text/troff",
17064+ "language_id": 425
17065+ },
17066+ "Pickle": {
17067+ "type": "data",
17068+ "extensions": [
17069+ ".pkl"
17070+ ],
17071+ "tm_scope": "none",
17072+ "ace_mode": "text",
17073+ "language_id": 284
17074+ },
17075+ "PicoLisp": {
17076+ "type": "programming",
17077+ "color": "#6067af",
17078+ "extensions": [
17079+ ".l"
17080+ ],
17081+ "interpreters": [
17082+ "picolisp",
17083+ "pil"
17084+ ],
17085+ "tm_scope": "source.lisp",
17086+ "ace_mode": "lisp",
17087+ "language_id": 285
17088+ },
17089+ "PigLatin": {
17090+ "type": "programming",
17091+ "color": "#fcd7de",
17092+ "extensions": [
17093+ ".pig"
17094+ ],
17095+ "tm_scope": "source.pig_latin",
17096+ "ace_mode": "text",
17097+ "language_id": 286
17098+ },
17099+ "Pike": {
17100+ "type": "programming",
17101+ "color": "#005390",
17102+ "extensions": [
17103+ ".pike",
17104+ ".pmod"
17105+ ],
17106+ "interpreters": [
17107+ "pike"
17108+ ],
17109+ "tm_scope": "source.pike",
17110+ "ace_mode": "text",
17111+ "language_id": 287
17112+ },
17113+ "PlantUML": {
17114+ "type": "data",
17115+ "color": "#fbbd16",
17116+ "extensions": [
17117+ ".puml",
17118+ ".iuml",
17119+ ".plantuml"
17120+ ],
17121+ "tm_scope": "source.wsd",
17122+ "ace_mode": "text",
17123+ "language_id": 833504686
17124+ },
17125+ "Pod": {
17126+ "type": "prose",
17127+ "ace_mode": "perl",
17128+ "codemirror_mode": "perl",
17129+ "codemirror_mime_type": "text/x-perl",
17130+ "wrap": true,
17131+ "extensions": [
17132+ ".pod"
17133+ ],
17134+ "interpreters": [
17135+ "perl"
17136+ ],
17137+ "tm_scope": "none",
17138+ "language_id": 288
17139+ },
17140+ "Pod 6": {
17141+ "type": "prose",
17142+ "ace_mode": "perl",
17143+ "tm_scope": "source.raku",
17144+ "wrap": true,
17145+ "extensions": [
17146+ ".pod",
17147+ ".pod6"
17148+ ],
17149+ "interpreters": [
17150+ "perl6"
17151+ ],
17152+ "language_id": 155357471
17153+ },
17154+ "PogoScript": {
17155+ "type": "programming",
17156+ "color": "#d80074",
17157+ "extensions": [
17158+ ".pogo"
17159+ ],
17160+ "tm_scope": "source.pogoscript",
17161+ "ace_mode": "text",
17162+ "language_id": 289
17163+ },
17164+ "Polar": {
17165+ "type": "programming",
17166+ "color": "#ae81ff",
17167+ "extensions": [
17168+ ".polar"
17169+ ],
17170+ "tm_scope": "source.polar",
17171+ "ace_mode": "text",
17172+ "language_id": 839112914
17173+ },
17174+ "Pony": {
17175+ "type": "programming",
17176+ "extensions": [
17177+ ".pony"
17178+ ],
17179+ "tm_scope": "source.pony",
17180+ "ace_mode": "text",
17181+ "language_id": 290
17182+ },
17183+ "Portugol": {
17184+ "type": "programming",
17185+ "color": "#f8bd00",
17186+ "extensions": [
17187+ ".por"
17188+ ],
17189+ "tm_scope": "source.portugol",
17190+ "ace_mode": "text",
17191+ "language_id": 832391833
17192+ },
17193+ "PostCSS": {
17194+ "type": "markup",
17195+ "color": "#dc3a0c",
17196+ "tm_scope": "source.postcss",
17197+ "group": "CSS",
17198+ "extensions": [
17199+ ".pcss",
17200+ ".postcss"
17201+ ],
17202+ "ace_mode": "text",
17203+ "language_id": 262764437
17204+ },
17205+ "PostScript": {
17206+ "type": "markup",
17207+ "color": "#da291c",
17208+ "extensions": [
17209+ ".ps",
17210+ ".eps",
17211+ ".epsi",
17212+ ".pfa"
17213+ ],
17214+ "tm_scope": "source.postscript",
17215+ "aliases": [
17216+ "postscr"
17217+ ],
17218+ "ace_mode": "text",
17219+ "language_id": 291
17220+ },
17221+ "PowerBuilder": {
17222+ "type": "programming",
17223+ "color": "#8f0f8d",
17224+ "extensions": [
17225+ ".pbt",
17226+ ".sra",
17227+ ".sru",
17228+ ".srw"
17229+ ],
17230+ "tm_scope": "none",
17231+ "ace_mode": "text",
17232+ "language_id": 292
17233+ },
17234+ "PowerShell": {
17235+ "type": "programming",
17236+ "color": "#012456",
17237+ "tm_scope": "source.powershell",
17238+ "ace_mode": "powershell",
17239+ "codemirror_mode": "powershell",
17240+ "codemirror_mime_type": "application/x-powershell",
17241+ "aliases": [
17242+ "posh",
17243+ "pwsh"
17244+ ],
17245+ "extensions": [
17246+ ".ps1",
17247+ ".psd1",
17248+ ".psm1"
17249+ ],
17250+ "interpreters": [
17251+ "pwsh"
17252+ ],
17253+ "language_id": 293
17254+ },
17255+ "Praat": {
17256+ "type": "programming",
17257+ "color": "#c8506d",
17258+ "tm_scope": "source.praat",
17259+ "ace_mode": "praat",
17260+ "extensions": [
17261+ ".praat"
17262+ ],
17263+ "language_id": 106029007
17264+ },
17265+ "Prisma": {
17266+ "type": "data",
17267+ "color": "#0c344b",
17268+ "extensions": [
17269+ ".prisma"
17270+ ],
17271+ "tm_scope": "source.prisma",
17272+ "ace_mode": "text",
17273+ "language_id": 499933428
17274+ },
17275+ "Processing": {
17276+ "type": "programming",
17277+ "color": "#0096D8",
17278+ "extensions": [
17279+ ".pde"
17280+ ],
17281+ "tm_scope": "source.processing",
17282+ "ace_mode": "text",
17283+ "language_id": 294
17284+ },
17285+ "Procfile": {
17286+ "type": "programming",
17287+ "color": "#3B2F63",
17288+ "filenames": [
17289+ "Procfile"
17290+ ],
17291+ "tm_scope": "source.procfile",
17292+ "ace_mode": "batchfile",
17293+ "language_id": 305313959
17294+ },
17295+ "Proguard": {
17296+ "type": "data",
17297+ "extensions": [
17298+ ".pro"
17299+ ],
17300+ "tm_scope": "none",
17301+ "ace_mode": "text",
17302+ "language_id": 716513858
17303+ },
17304+ "Prolog": {
17305+ "type": "programming",
17306+ "color": "#74283c",
17307+ "extensions": [
17308+ ".pl",
17309+ ".plt",
17310+ ".pro",
17311+ ".prolog",
17312+ ".yap"
17313+ ],
17314+ "interpreters": [
17315+ "swipl",
17316+ "yap"
17317+ ],
17318+ "tm_scope": "source.prolog",
17319+ "ace_mode": "prolog",
17320+ "language_id": 295
17321+ },
17322+ "Promela": {
17323+ "type": "programming",
17324+ "color": "#de0000",
17325+ "tm_scope": "source.promela",
17326+ "ace_mode": "text",
17327+ "extensions": [
17328+ ".pml"
17329+ ],
17330+ "language_id": 441858312
17331+ },
17332+ "Propeller Spin": {
17333+ "type": "programming",
17334+ "color": "#7fa2a7",
17335+ "extensions": [
17336+ ".spin"
17337+ ],
17338+ "tm_scope": "source.spin",
17339+ "ace_mode": "text",
17340+ "language_id": 296
17341+ },
17342+ "Protocol Buffer": {
17343+ "type": "data",
17344+ "aliases": [
17345+ "proto",
17346+ "protobuf",
17347+ "Protocol Buffers"
17348+ ],
17349+ "extensions": [
17350+ ".proto"
17351+ ],
17352+ "tm_scope": "source.proto",
17353+ "ace_mode": "protobuf",
17354+ "codemirror_mode": "protobuf",
17355+ "codemirror_mime_type": "text/x-protobuf",
17356+ "language_id": 297
17357+ },
17358+ "Protocol Buffer Text Format": {
17359+ "type": "data",
17360+ "aliases": [
17361+ "text proto",
17362+ "protobuf text format"
17363+ ],
17364+ "extensions": [
17365+ ".textproto",
17366+ ".pbt",
17367+ ".pbtxt"
17368+ ],
17369+ "tm_scope": "source.textproto",
17370+ "ace_mode": "text",
17371+ "language_id": 436568854
17372+ },
17373+ "Public Key": {
17374+ "type": "data",
17375+ "extensions": [
17376+ ".asc",
17377+ ".pub"
17378+ ],
17379+ "tm_scope": "none",
17380+ "ace_mode": "text",
17381+ "codemirror_mode": "asciiarmor",
17382+ "codemirror_mime_type": "application/pgp",
17383+ "language_id": 298
17384+ },
17385+ "Pug": {
17386+ "type": "markup",
17387+ "color": "#a86454",
17388+ "extensions": [
17389+ ".jade",
17390+ ".pug"
17391+ ],
17392+ "tm_scope": "text.jade",
17393+ "ace_mode": "jade",
17394+ "codemirror_mode": "pug",
17395+ "codemirror_mime_type": "text/x-pug",
17396+ "language_id": 179
17397+ },
17398+ "Puppet": {
17399+ "type": "programming",
17400+ "color": "#302B6D",
17401+ "extensions": [
17402+ ".pp"
17403+ ],
17404+ "filenames": [
17405+ "Modulefile"
17406+ ],
17407+ "ace_mode": "text",
17408+ "codemirror_mode": "puppet",
17409+ "codemirror_mime_type": "text/x-puppet",
17410+ "tm_scope": "source.puppet",
17411+ "language_id": 299
17412+ },
17413+ "Pure Data": {
17414+ "type": "data",
17415+ "extensions": [
17416+ ".pd"
17417+ ],
17418+ "tm_scope": "none",
17419+ "ace_mode": "text",
17420+ "language_id": 300
17421+ },
17422+ "PureBasic": {
17423+ "type": "programming",
17424+ "color": "#5a6986",
17425+ "extensions": [
17426+ ".pb",
17427+ ".pbi"
17428+ ],
17429+ "tm_scope": "none",
17430+ "ace_mode": "text",
17431+ "language_id": 301
17432+ },
17433+ "PureScript": {
17434+ "type": "programming",
17435+ "color": "#1D222D",
17436+ "extensions": [
17437+ ".purs"
17438+ ],
17439+ "tm_scope": "source.purescript",
17440+ "ace_mode": "haskell",
17441+ "codemirror_mode": "haskell",
17442+ "codemirror_mime_type": "text/x-haskell",
17443+ "language_id": 302
17444+ },
17445+ "Pyret": {
17446+ "type": "programming",
17447+ "color": "#ee1e10",
17448+ "extensions": [
17449+ ".arr"
17450+ ],
17451+ "ace_mode": "python",
17452+ "tm_scope": "source.arr",
17453+ "language_id": 252961827
17454+ },
17455+ "Python": {
17456+ "type": "programming",
17457+ "tm_scope": "source.python",
17458+ "ace_mode": "python",
17459+ "codemirror_mode": "python",
17460+ "codemirror_mime_type": "text/x-python",
17461+ "color": "#3572A5",
17462+ "extensions": [
17463+ ".py",
17464+ ".cgi",
17465+ ".fcgi",
17466+ ".gyp",
17467+ ".gypi",
17468+ ".lmi",
17469+ ".py3",
17470+ ".pyde",
17471+ ".pyi",
17472+ ".pyp",
17473+ ".pyt",
17474+ ".pyw",
17475+ ".rpy",
17476+ ".spec",
17477+ ".tac",
17478+ ".wsgi",
17479+ ".xpy"
17480+ ],
17481+ "filenames": [
17482+ ".gclient",
17483+ "DEPS",
17484+ "SConscript",
17485+ "SConstruct",
17486+ "wscript"
17487+ ],
17488+ "interpreters": [
17489+ "python",
17490+ "python2",
17491+ "python3",
17492+ "py",
17493+ "pypy",
17494+ "pypy3"
17495+ ],
17496+ "aliases": [
17497+ "python3",
17498+ "rusthon"
17499+ ],
17500+ "language_id": 303
17501+ },
17502+ "Python console": {
17503+ "type": "programming",
17504+ "color": "#3572A5",
17505+ "group": "Python",
17506+ "aliases": [
17507+ "pycon"
17508+ ],
17509+ "tm_scope": "text.python.console",
17510+ "ace_mode": "text",
17511+ "language_id": 428
17512+ },
17513+ "Python traceback": {
17514+ "type": "data",
17515+ "color": "#3572A5",
17516+ "group": "Python",
17517+ "extensions": [
17518+ ".pytb"
17519+ ],
17520+ "tm_scope": "text.python.traceback",
17521+ "ace_mode": "text",
17522+ "language_id": 304
17523+ },
17524+ "Q#": {
17525+ "type": "programming",
17526+ "extensions": [
17527+ ".qs"
17528+ ],
17529+ "aliases": [
17530+ "qsharp"
17531+ ],
17532+ "color": "#fed659",
17533+ "ace_mode": "text",
17534+ "tm_scope": "source.qsharp",
17535+ "language_id": 697448245
17536+ },
17537+ "QML": {
17538+ "type": "programming",
17539+ "color": "#44a51c",
17540+ "extensions": [
17541+ ".qml",
17542+ ".qbs"
17543+ ],
17544+ "tm_scope": "source.qml",
17545+ "ace_mode": "text",
17546+ "language_id": 305
17547+ },
17548+ "QMake": {
17549+ "type": "programming",
17550+ "extensions": [
17551+ ".pro",
17552+ ".pri"
17553+ ],
17554+ "interpreters": [
17555+ "qmake"
17556+ ],
17557+ "tm_scope": "source.qmake",
17558+ "ace_mode": "text",
17559+ "language_id": 306
17560+ },
17561+ "Qt Script": {
17562+ "type": "programming",
17563+ "ace_mode": "javascript",
17564+ "codemirror_mode": "javascript",
17565+ "codemirror_mime_type": "text/javascript",
17566+ "extensions": [
17567+ ".qs"
17568+ ],
17569+ "filenames": [
17570+ "installscript.qs",
17571+ "toolchain_installscript.qs"
17572+ ],
17573+ "color": "#00b841",
17574+ "tm_scope": "source.js",
17575+ "language_id": 558193693
17576+ },
17577+ "Quake": {
17578+ "type": "programming",
17579+ "filenames": [
17580+ "m3makefile",
17581+ "m3overrides"
17582+ ],
17583+ "color": "#882233",
17584+ "ace_mode": "text",
17585+ "tm_scope": "source.quake",
17586+ "language_id": 375265331
17587+ },
17588+ "R": {
17589+ "type": "programming",
17590+ "color": "#198CE7",
17591+ "aliases": [
17592+ "R",
17593+ "Rscript",
17594+ "splus"
17595+ ],
17596+ "extensions": [
17597+ ".r",
17598+ ".rd",
17599+ ".rsx"
17600+ ],
17601+ "filenames": [
17602+ ".Rprofile",
17603+ "expr-dist"
17604+ ],
17605+ "interpreters": [
17606+ "Rscript"
17607+ ],
17608+ "tm_scope": "source.r",
17609+ "ace_mode": "r",
17610+ "codemirror_mode": "r",
17611+ "codemirror_mime_type": "text/x-rsrc",
17612+ "language_id": 307
17613+ },
17614+ "RAML": {
17615+ "type": "markup",
17616+ "ace_mode": "yaml",
17617+ "codemirror_mode": "yaml",
17618+ "codemirror_mime_type": "text/x-yaml",
17619+ "tm_scope": "source.yaml",
17620+ "color": "#77d9fb",
17621+ "extensions": [
17622+ ".raml"
17623+ ],
17624+ "language_id": 308
17625+ },
17626+ "RBS": {
17627+ "type": "data",
17628+ "ace_mode": "ruby",
17629+ "codemirror_mode": "ruby",
17630+ "codemirror_mime_type": "text/x-ruby",
17631+ "extensions": [
17632+ ".rbs"
17633+ ],
17634+ "color": "#701516",
17635+ "tm_scope": "source.rbs",
17636+ "group": "Ruby",
17637+ "language_id": 899227493
17638+ },
17639+ "RDoc": {
17640+ "type": "prose",
17641+ "color": "#701516",
17642+ "ace_mode": "rdoc",
17643+ "wrap": true,
17644+ "extensions": [
17645+ ".rdoc"
17646+ ],
17647+ "tm_scope": "text.rdoc",
17648+ "language_id": 309
17649+ },
17650+ "REALbasic": {
17651+ "type": "programming",
17652+ "extensions": [
17653+ ".rbbas",
17654+ ".rbfrm",
17655+ ".rbmnu",
17656+ ".rbres",
17657+ ".rbtbar",
17658+ ".rbuistate"
17659+ ],
17660+ "tm_scope": "source.vbnet",
17661+ "ace_mode": "text",
17662+ "language_id": 310
17663+ },
17664+ "REXX": {
17665+ "type": "programming",
17666+ "color": "#d90e09",
17667+ "aliases": [
17668+ "arexx"
17669+ ],
17670+ "extensions": [
17671+ ".rexx",
17672+ ".pprx",
17673+ ".rex"
17674+ ],
17675+ "interpreters": [
17676+ "regina",
17677+ "rexx"
17678+ ],
17679+ "tm_scope": "source.rexx",
17680+ "ace_mode": "text",
17681+ "language_id": 311
17682+ },
17683+ "RMarkdown": {
17684+ "type": "prose",
17685+ "color": "#198ce7",
17686+ "wrap": true,
17687+ "ace_mode": "markdown",
17688+ "codemirror_mode": "gfm",
17689+ "codemirror_mime_type": "text/x-gfm",
17690+ "extensions": [
17691+ ".qmd",
17692+ ".rmd"
17693+ ],
17694+ "tm_scope": "text.md",
17695+ "language_id": 313
17696+ },
17697+ "RPC": {
17698+ "type": "programming",
17699+ "aliases": [
17700+ "rpcgen",
17701+ "oncrpc",
17702+ "xdr"
17703+ ],
17704+ "ace_mode": "c_cpp",
17705+ "extensions": [
17706+ ".x"
17707+ ],
17708+ "tm_scope": "source.c",
17709+ "language_id": 1031374237
17710+ },
17711+ "RPGLE": {
17712+ "type": "programming",
17713+ "ace_mode": "text",
17714+ "color": "#2BDE21",
17715+ "aliases": [
17716+ "ile rpg",
17717+ "sqlrpgle"
17718+ ],
17719+ "extensions": [
17720+ ".rpgle",
17721+ ".sqlrpgle"
17722+ ],
17723+ "tm_scope": "source.rpgle",
17724+ "language_id": 609977990
17725+ },
17726+ "RPM Spec": {
17727+ "type": "data",
17728+ "tm_scope": "source.rpm-spec",
17729+ "extensions": [
17730+ ".spec"
17731+ ],
17732+ "aliases": [
17733+ "specfile"
17734+ ],
17735+ "ace_mode": "text",
17736+ "codemirror_mode": "rpm",
17737+ "codemirror_mime_type": "text/x-rpm-spec",
17738+ "language_id": 314
17739+ },
17740+ "RUNOFF": {
17741+ "type": "markup",
17742+ "color": "#665a4e",
17743+ "extensions": [
17744+ ".rnh",
17745+ ".rno"
17746+ ],
17747+ "wrap": true,
17748+ "tm_scope": "text.runoff",
17749+ "ace_mode": "text",
17750+ "language_id": 315
17751+ },
17752+ "Racket": {
17753+ "type": "programming",
17754+ "color": "#3c5caa",
17755+ "extensions": [
17756+ ".rkt",
17757+ ".rktd",
17758+ ".rktl",
17759+ ".scrbl"
17760+ ],
17761+ "interpreters": [
17762+ "racket"
17763+ ],
17764+ "tm_scope": "source.racket",
17765+ "ace_mode": "lisp",
17766+ "language_id": 316
17767+ },
17768+ "Ragel": {
17769+ "type": "programming",
17770+ "color": "#9d5200",
17771+ "extensions": [
17772+ ".rl"
17773+ ],
17774+ "aliases": [
17775+ "ragel-rb",
17776+ "ragel-ruby"
17777+ ],
17778+ "tm_scope": "none",
17779+ "ace_mode": "text",
17780+ "language_id": 317
17781+ },
17782+ "Raku": {
17783+ "type": "programming",
17784+ "color": "#0000fb",
17785+ "extensions": [
17786+ ".6pl",
17787+ ".6pm",
17788+ ".nqp",
17789+ ".p6",
17790+ ".p6l",
17791+ ".p6m",
17792+ ".pl",
17793+ ".pl6",
17794+ ".pm",
17795+ ".pm6",
17796+ ".raku",
17797+ ".rakumod",
17798+ ".t"
17799+ ],
17800+ "interpreters": [
17801+ "perl6",
17802+ "raku",
17803+ "rakudo"
17804+ ],
17805+ "aliases": [
17806+ "perl6",
17807+ "perl-6"
17808+ ],
17809+ "tm_scope": "source.raku",
17810+ "ace_mode": "perl",
17811+ "codemirror_mode": "perl",
17812+ "codemirror_mime_type": "text/x-perl",
17813+ "language_id": 283
17814+ },
17815+ "Rascal": {
17816+ "type": "programming",
17817+ "color": "#fffaa0",
17818+ "extensions": [
17819+ ".rsc"
17820+ ],
17821+ "tm_scope": "source.rascal",
17822+ "ace_mode": "text",
17823+ "language_id": 173616037
17824+ },
17825+ "Raw token data": {
17826+ "type": "data",
17827+ "aliases": [
17828+ "raw"
17829+ ],
17830+ "extensions": [
17831+ ".raw"
17832+ ],
17833+ "tm_scope": "none",
17834+ "ace_mode": "text",
17835+ "language_id": 318
17836+ },
17837+ "ReScript": {
17838+ "type": "programming",
17839+ "color": "#ed5051",
17840+ "ace_mode": "rust",
17841+ "codemirror_mode": "rust",
17842+ "codemirror_mime_type": "text/x-rustsrc",
17843+ "extensions": [
17844+ ".res"
17845+ ],
17846+ "interpreters": [
17847+ "ocaml"
17848+ ],
17849+ "tm_scope": "source.rescript",
17850+ "language_id": 501875647
17851+ },
17852+ "Readline Config": {
17853+ "type": "data",
17854+ "group": "INI",
17855+ "aliases": [
17856+ "inputrc",
17857+ "readline"
17858+ ],
17859+ "filenames": [
17860+ ".inputrc",
17861+ "inputrc"
17862+ ],
17863+ "tm_scope": "source.inputrc",
17864+ "ace_mode": "text",
17865+ "language_id": 538732839
17866+ },
17867+ "Reason": {
17868+ "type": "programming",
17869+ "color": "#ff5847",
17870+ "ace_mode": "rust",
17871+ "codemirror_mode": "rust",
17872+ "codemirror_mime_type": "text/x-rustsrc",
17873+ "extensions": [
17874+ ".re",
17875+ ".rei"
17876+ ],
17877+ "tm_scope": "source.reason",
17878+ "language_id": 869538413
17879+ },
17880+ "ReasonLIGO": {
17881+ "type": "programming",
17882+ "color": "#ff5847",
17883+ "ace_mode": "rust",
17884+ "codemirror_mode": "rust",
17885+ "codemirror_mime_type": "text/x-rustsrc",
17886+ "group": "LigoLANG",
17887+ "extensions": [
17888+ ".religo"
17889+ ],
17890+ "tm_scope": "source.religo",
17891+ "language_id": 319002153
17892+ },
17893+ "Rebol": {
17894+ "type": "programming",
17895+ "color": "#358a5b",
17896+ "extensions": [
17897+ ".reb",
17898+ ".r",
17899+ ".r2",
17900+ ".r3",
17901+ ".rebol"
17902+ ],
17903+ "ace_mode": "text",
17904+ "tm_scope": "source.rebol",
17905+ "language_id": 319
17906+ },
17907+ "Record Jar": {
17908+ "type": "data",
17909+ "filenames": [
17910+ "language-subtag-registry.txt"
17911+ ],
17912+ "tm_scope": "source.record-jar",
17913+ "codemirror_mode": "properties",
17914+ "codemirror_mime_type": "text/x-properties",
17915+ "ace_mode": "text",
17916+ "color": "#0673ba",
17917+ "language_id": 865765202
17918+ },
17919+ "Red": {
17920+ "type": "programming",
17921+ "color": "#f50000",
17922+ "extensions": [
17923+ ".red",
17924+ ".reds"
17925+ ],
17926+ "aliases": [
17927+ "red/system"
17928+ ],
17929+ "tm_scope": "source.red",
17930+ "ace_mode": "text",
17931+ "language_id": 320
17932+ },
17933+ "Redcode": {
17934+ "type": "programming",
17935+ "extensions": [
17936+ ".cw"
17937+ ],
17938+ "tm_scope": "none",
17939+ "ace_mode": "text",
17940+ "language_id": 321
17941+ },
17942+ "Redirect Rules": {
17943+ "type": "data",
17944+ "aliases": [
17945+ "redirects"
17946+ ],
17947+ "filenames": [
17948+ "_redirects"
17949+ ],
17950+ "tm_scope": "source.redirects",
17951+ "ace_mode": "text",
17952+ "language_id": 1020148948
17953+ },
17954+ "Regular Expression": {
17955+ "type": "data",
17956+ "color": "#009a00",
17957+ "extensions": [
17958+ ".regexp",
17959+ ".regex"
17960+ ],
17961+ "aliases": [
17962+ "regexp",
17963+ "regex"
17964+ ],
17965+ "ace_mode": "text",
17966+ "tm_scope": "source.regexp",
17967+ "language_id": 363378884
17968+ },
17969+ "Ren'Py": {
17970+ "type": "programming",
17971+ "aliases": [
17972+ "renpy"
17973+ ],
17974+ "color": "#ff7f7f",
17975+ "extensions": [
17976+ ".rpy"
17977+ ],
17978+ "tm_scope": "source.renpy",
17979+ "ace_mode": "python",
17980+ "language_id": 322
17981+ },
17982+ "RenderScript": {
17983+ "type": "programming",
17984+ "extensions": [
17985+ ".rs",
17986+ ".rsh"
17987+ ],
17988+ "tm_scope": "none",
17989+ "ace_mode": "text",
17990+ "language_id": 323
17991+ },
17992+ "Rez": {
17993+ "type": "programming",
17994+ "extensions": [
17995+ ".r"
17996+ ],
17997+ "tm_scope": "source.rez",
17998+ "ace_mode": "text",
17999+ "color": "#FFDAB3",
18000+ "language_id": 498022874
18001+ },
18002+ "Rich Text Format": {
18003+ "type": "markup",
18004+ "extensions": [
18005+ ".rtf"
18006+ ],
18007+ "tm_scope": "text.rtf",
18008+ "ace_mode": "text",
18009+ "language_id": 51601661
18010+ },
18011+ "Ring": {
18012+ "type": "programming",
18013+ "color": "#2D54CB",
18014+ "extensions": [
18015+ ".ring"
18016+ ],
18017+ "tm_scope": "source.ring",
18018+ "ace_mode": "text",
18019+ "language_id": 431
18020+ },
18021+ "Riot": {
18022+ "type": "markup",
18023+ "color": "#A71E49",
18024+ "ace_mode": "html",
18025+ "extensions": [
18026+ ".riot"
18027+ ],
18028+ "tm_scope": "text.html.riot",
18029+ "language_id": 878396783
18030+ },
18031+ "RobotFramework": {
18032+ "type": "programming",
18033+ "color": "#00c0b5",
18034+ "extensions": [
18035+ ".robot"
18036+ ],
18037+ "tm_scope": "text.robot",
18038+ "ace_mode": "text",
18039+ "language_id": 324
18040+ },
18041+ "Roff": {
18042+ "type": "markup",
18043+ "color": "#ecdebe",
18044+ "extensions": [
18045+ ".roff",
18046+ ".1",
18047+ ".1in",
18048+ ".1m",
18049+ ".1x",
18050+ ".2",
18051+ ".3",
18052+ ".3in",
18053+ ".3m",
18054+ ".3p",
18055+ ".3pm",
18056+ ".3qt",
18057+ ".3x",
18058+ ".4",
18059+ ".5",
18060+ ".6",
18061+ ".7",
18062+ ".8",
18063+ ".9",
18064+ ".l",
18065+ ".man",
18066+ ".mdoc",
18067+ ".me",
18068+ ".ms",
18069+ ".n",
18070+ ".nr",
18071+ ".rno",
18072+ ".tmac"
18073+ ],
18074+ "filenames": [
18075+ "eqnrc",
18076+ "mmn",
18077+ "mmt",
18078+ "troffrc",
18079+ "troffrc-end"
18080+ ],
18081+ "tm_scope": "text.roff",
18082+ "aliases": [
18083+ "groff",
18084+ "man",
18085+ "manpage",
18086+ "man page",
18087+ "man-page",
18088+ "mdoc",
18089+ "nroff",
18090+ "troff"
18091+ ],
18092+ "wrap": true,
18093+ "ace_mode": "text",
18094+ "codemirror_mode": "troff",
18095+ "codemirror_mime_type": "text/troff",
18096+ "language_id": 141
18097+ },
18098+ "Roff Manpage": {
18099+ "type": "markup",
18100+ "color": "#ecdebe",
18101+ "group": "Roff",
18102+ "extensions": [
18103+ ".1",
18104+ ".1in",
18105+ ".1m",
18106+ ".1x",
18107+ ".2",
18108+ ".3",
18109+ ".3in",
18110+ ".3m",
18111+ ".3p",
18112+ ".3pm",
18113+ ".3qt",
18114+ ".3x",
18115+ ".4",
18116+ ".5",
18117+ ".6",
18118+ ".7",
18119+ ".8",
18120+ ".9",
18121+ ".man",
18122+ ".mdoc"
18123+ ],
18124+ "wrap": true,
18125+ "tm_scope": "text.roff",
18126+ "ace_mode": "text",
18127+ "codemirror_mode": "troff",
18128+ "codemirror_mime_type": "text/troff",
18129+ "language_id": 612669833
18130+ },
18131+ "Rouge": {
18132+ "type": "programming",
18133+ "ace_mode": "clojure",
18134+ "codemirror_mode": "clojure",
18135+ "codemirror_mime_type": "text/x-clojure",
18136+ "color": "#cc0088",
18137+ "extensions": [
18138+ ".rg"
18139+ ],
18140+ "tm_scope": "source.clojure",
18141+ "language_id": 325
18142+ },
18143+ "RouterOS Script": {
18144+ "type": "programming",
18145+ "ace_mode": "text",
18146+ "extensions": [
18147+ ".rsc"
18148+ ],
18149+ "interpreters": [
18150+ "RouterOS"
18151+ ],
18152+ "color": "#DE3941",
18153+ "tm_scope": "none",
18154+ "language_id": 592853203
18155+ },
18156+ "Ruby": {
18157+ "type": "programming",
18158+ "tm_scope": "source.ruby",
18159+ "ace_mode": "ruby",
18160+ "codemirror_mode": "ruby",
18161+ "codemirror_mime_type": "text/x-ruby",
18162+ "color": "#701516",
18163+ "aliases": [
18164+ "jruby",
18165+ "macruby",
18166+ "rake",
18167+ "rb",
18168+ "rbx"
18169+ ],
18170+ "extensions": [
18171+ ".rb",
18172+ ".builder",
18173+ ".eye",
18174+ ".fcgi",
18175+ ".gemspec",
18176+ ".god",
18177+ ".jbuilder",
18178+ ".mspec",
18179+ ".pluginspec",
18180+ ".podspec",
18181+ ".prawn",
18182+ ".rabl",
18183+ ".rake",
18184+ ".rbi",
18185+ ".rbuild",
18186+ ".rbw",
18187+ ".rbx",
18188+ ".ru",
18189+ ".ruby",
18190+ ".spec",
18191+ ".thor",
18192+ ".watchr"
18193+ ],
18194+ "interpreters": [
18195+ "ruby",
18196+ "macruby",
18197+ "rake",
18198+ "jruby",
18199+ "rbx"
18200+ ],
18201+ "filenames": [
18202+ ".irbrc",
18203+ ".pryrc",
18204+ ".simplecov",
18205+ "Appraisals",
18206+ "Berksfile",
18207+ "Brewfile",
18208+ "Buildfile",
18209+ "Capfile",
18210+ "Dangerfile",
18211+ "Deliverfile",
18212+ "Fastfile",
18213+ "Gemfile",
18214+ "Guardfile",
18215+ "Jarfile",
18216+ "Mavenfile",
18217+ "Podfile",
18218+ "Puppetfile",
18219+ "Rakefile",
18220+ "Snapfile",
18221+ "Steepfile",
18222+ "Thorfile",
18223+ "Vagrantfile",
18224+ "buildfile"
18225+ ],
18226+ "language_id": 326
18227+ },
18228+ "Rust": {
18229+ "type": "programming",
18230+ "aliases": [
18231+ "rs"
18232+ ],
18233+ "color": "#dea584",
18234+ "extensions": [
18235+ ".rs",
18236+ ".rs.in"
18237+ ],
18238+ "tm_scope": "source.rust",
18239+ "ace_mode": "rust",
18240+ "codemirror_mode": "rust",
18241+ "codemirror_mime_type": "text/x-rustsrc",
18242+ "interpreters": [
18243+ "rust-script"
18244+ ],
18245+ "language_id": 327
18246+ },
18247+ "SAS": {
18248+ "type": "programming",
18249+ "color": "#B34936",
18250+ "extensions": [
18251+ ".sas"
18252+ ],
18253+ "tm_scope": "source.sas",
18254+ "ace_mode": "text",
18255+ "codemirror_mode": "sas",
18256+ "codemirror_mime_type": "text/x-sas",
18257+ "language_id": 328
18258+ },
18259+ "SCSS": {
18260+ "type": "markup",
18261+ "color": "#c6538c",
18262+ "tm_scope": "source.css.scss",
18263+ "ace_mode": "scss",
18264+ "codemirror_mode": "css",
18265+ "codemirror_mime_type": "text/x-scss",
18266+ "extensions": [
18267+ ".scss"
18268+ ],
18269+ "language_id": 329
18270+ },
18271+ "SELinux Policy": {
18272+ "aliases": [
18273+ "SELinux Kernel Policy Language",
18274+ "sepolicy"
18275+ ],
18276+ "type": "data",
18277+ "tm_scope": "source.sepolicy",
18278+ "extensions": [
18279+ ".te"
18280+ ],
18281+ "filenames": [
18282+ "file_contexts",
18283+ "genfs_contexts",
18284+ "initial_sids",
18285+ "port_contexts",
18286+ "security_classes"
18287+ ],
18288+ "ace_mode": "text",
18289+ "language_id": 880010326
18290+ },
18291+ "SMT": {
18292+ "type": "programming",
18293+ "extensions": [
18294+ ".smt2",
18295+ ".smt"
18296+ ],
18297+ "interpreters": [
18298+ "boolector",
18299+ "cvc4",
18300+ "mathsat5",
18301+ "opensmt",
18302+ "smtinterpol",
18303+ "smt-rat",
18304+ "stp",
18305+ "verit",
18306+ "yices2",
18307+ "z3"
18308+ ],
18309+ "tm_scope": "source.smt",
18310+ "ace_mode": "text",
18311+ "language_id": 330
18312+ },
18313+ "SPARQL": {
18314+ "type": "data",
18315+ "color": "#0C4597",
18316+ "tm_scope": "source.sparql",
18317+ "ace_mode": "text",
18318+ "codemirror_mode": "sparql",
18319+ "codemirror_mime_type": "application/sparql-query",
18320+ "extensions": [
18321+ ".sparql",
18322+ ".rq"
18323+ ],
18324+ "language_id": 331
18325+ },
18326+ "SQF": {
18327+ "type": "programming",
18328+ "color": "#3F3F3F",
18329+ "extensions": [
18330+ ".sqf",
18331+ ".hqf"
18332+ ],
18333+ "tm_scope": "source.sqf",
18334+ "ace_mode": "text",
18335+ "language_id": 332
18336+ },
18337+ "SQL": {
18338+ "type": "data",
18339+ "color": "#e38c00",
18340+ "tm_scope": "source.sql",
18341+ "ace_mode": "sql",
18342+ "codemirror_mode": "sql",
18343+ "codemirror_mime_type": "text/x-sql",
18344+ "extensions": [
18345+ ".sql",
18346+ ".cql",
18347+ ".ddl",
18348+ ".inc",
18349+ ".mysql",
18350+ ".prc",
18351+ ".tab",
18352+ ".udf",
18353+ ".viw"
18354+ ],
18355+ "language_id": 333
18356+ },
18357+ "SQLPL": {
18358+ "type": "programming",
18359+ "color": "#e38c00",
18360+ "ace_mode": "sql",
18361+ "codemirror_mode": "sql",
18362+ "codemirror_mime_type": "text/x-sql",
18363+ "tm_scope": "source.sql",
18364+ "extensions": [
18365+ ".sql",
18366+ ".db2"
18367+ ],
18368+ "language_id": 334
18369+ },
18370+ "SRecode Template": {
18371+ "type": "markup",
18372+ "color": "#348a34",
18373+ "tm_scope": "source.lisp",
18374+ "ace_mode": "lisp",
18375+ "codemirror_mode": "commonlisp",
18376+ "codemirror_mime_type": "text/x-common-lisp",
18377+ "extensions": [
18378+ ".srt"
18379+ ],
18380+ "language_id": 335
18381+ },
18382+ "SSH Config": {
18383+ "type": "data",
18384+ "group": "INI",
18385+ "filenames": [
18386+ "ssh-config",
18387+ "ssh_config",
18388+ "sshconfig",
18389+ "sshconfig.snip",
18390+ "sshd-config",
18391+ "sshd_config"
18392+ ],
18393+ "ace_mode": "text",
18394+ "tm_scope": "source.ssh-config",
18395+ "language_id": 554920715
18396+ },
18397+ "STAR": {
18398+ "type": "data",
18399+ "extensions": [
18400+ ".star"
18401+ ],
18402+ "tm_scope": "source.star",
18403+ "ace_mode": "text",
18404+ "language_id": 424510560
18405+ },
18406+ "STL": {
18407+ "type": "data",
18408+ "color": "#373b5e",
18409+ "aliases": [
18410+ "ascii stl",
18411+ "stla"
18412+ ],
18413+ "extensions": [
18414+ ".stl"
18415+ ],
18416+ "tm_scope": "source.stl",
18417+ "ace_mode": "text",
18418+ "language_id": 455361735
18419+ },
18420+ "STON": {
18421+ "type": "data",
18422+ "group": "Smalltalk",
18423+ "extensions": [
18424+ ".ston"
18425+ ],
18426+ "tm_scope": "source.smalltalk",
18427+ "ace_mode": "text",
18428+ "language_id": 336
18429+ },
18430+ "SVG": {
18431+ "type": "data",
18432+ "color": "#ff9900",
18433+ "extensions": [
18434+ ".svg"
18435+ ],
18436+ "tm_scope": "text.xml.svg",
18437+ "ace_mode": "xml",
18438+ "codemirror_mode": "xml",
18439+ "codemirror_mime_type": "text/xml",
18440+ "language_id": 337
18441+ },
18442+ "SWIG": {
18443+ "type": "programming",
18444+ "extensions": [
18445+ ".i"
18446+ ],
18447+ "tm_scope": "source.c++",
18448+ "ace_mode": "c_cpp",
18449+ "codemirror_mode": "clike",
18450+ "codemirror_mime_type": "text/x-c++src",
18451+ "language_id": 1066250075
18452+ },
18453+ "Sage": {
18454+ "type": "programming",
18455+ "extensions": [
18456+ ".sage",
18457+ ".sagews"
18458+ ],
18459+ "tm_scope": "source.python",
18460+ "ace_mode": "python",
18461+ "codemirror_mode": "python",
18462+ "codemirror_mime_type": "text/x-python",
18463+ "language_id": 338
18464+ },
18465+ "SaltStack": {
18466+ "type": "programming",
18467+ "color": "#646464",
18468+ "aliases": [
18469+ "saltstate",
18470+ "salt"
18471+ ],
18472+ "extensions": [
18473+ ".sls"
18474+ ],
18475+ "tm_scope": "source.yaml.salt",
18476+ "ace_mode": "yaml",
18477+ "codemirror_mode": "yaml",
18478+ "codemirror_mime_type": "text/x-yaml",
18479+ "language_id": 339
18480+ },
18481+ "Sass": {
18482+ "type": "markup",
18483+ "color": "#a53b70",
18484+ "tm_scope": "source.sass",
18485+ "extensions": [
18486+ ".sass"
18487+ ],
18488+ "ace_mode": "sass",
18489+ "codemirror_mode": "sass",
18490+ "codemirror_mime_type": "text/x-sass",
18491+ "language_id": 340
18492+ },
18493+ "Scala": {
18494+ "type": "programming",
18495+ "tm_scope": "source.scala",
18496+ "ace_mode": "scala",
18497+ "codemirror_mode": "clike",
18498+ "codemirror_mime_type": "text/x-scala",
18499+ "color": "#c22d40",
18500+ "extensions": [
18501+ ".scala",
18502+ ".kojo",
18503+ ".sbt",
18504+ ".sc"
18505+ ],
18506+ "interpreters": [
18507+ "scala"
18508+ ],
18509+ "language_id": 341
18510+ },
18511+ "Scaml": {
18512+ "type": "markup",
18513+ "color": "#bd181a",
18514+ "extensions": [
18515+ ".scaml"
18516+ ],
18517+ "tm_scope": "source.scaml",
18518+ "ace_mode": "text",
18519+ "language_id": 342
18520+ },
18521+ "Scenic": {
18522+ "type": "programming",
18523+ "color": "#fdc700",
18524+ "extensions": [
18525+ ".scenic"
18526+ ],
18527+ "tm_scope": "source.scenic",
18528+ "ace_mode": "text",
18529+ "interpreters": [
18530+ "scenic"
18531+ ],
18532+ "language_id": 619814037
18533+ },
18534+ "Scheme": {
18535+ "type": "programming",
18536+ "color": "#1e4aec",
18537+ "extensions": [
18538+ ".scm",
18539+ ".sch",
18540+ ".sld",
18541+ ".sls",
18542+ ".sps",
18543+ ".ss"
18544+ ],
18545+ "interpreters": [
18546+ "scheme",
18547+ "guile",
18548+ "bigloo",
18549+ "chicken",
18550+ "csi",
18551+ "gosh",
18552+ "r6rs"
18553+ ],
18554+ "tm_scope": "source.scheme",
18555+ "ace_mode": "scheme",
18556+ "codemirror_mode": "scheme",
18557+ "codemirror_mime_type": "text/x-scheme",
18558+ "language_id": 343
18559+ },
18560+ "Scilab": {
18561+ "type": "programming",
18562+ "color": "#ca0f21",
18563+ "extensions": [
18564+ ".sci",
18565+ ".sce",
18566+ ".tst"
18567+ ],
18568+ "tm_scope": "source.scilab",
18569+ "ace_mode": "text",
18570+ "language_id": 344
18571+ },
18572+ "Self": {
18573+ "type": "programming",
18574+ "color": "#0579aa",
18575+ "extensions": [
18576+ ".self"
18577+ ],
18578+ "tm_scope": "none",
18579+ "ace_mode": "text",
18580+ "language_id": 345
18581+ },
18582+ "ShaderLab": {
18583+ "type": "programming",
18584+ "color": "#222c37",
18585+ "extensions": [
18586+ ".shader"
18587+ ],
18588+ "ace_mode": "text",
18589+ "tm_scope": "source.shaderlab",
18590+ "language_id": 664257356
18591+ },
18592+ "Shell": {
18593+ "type": "programming",
18594+ "color": "#89e051",
18595+ "aliases": [
18596+ "sh",
18597+ "shell-script",
18598+ "bash",
18599+ "zsh"
18600+ ],
18601+ "extensions": [
18602+ ".sh",
18603+ ".bash",
18604+ ".bats",
18605+ ".cgi",
18606+ ".command",
18607+ ".fcgi",
18608+ ".ksh",
18609+ ".sh.in",
18610+ ".tmux",
18611+ ".tool",
18612+ ".trigger",
18613+ ".zsh",
18614+ ".zsh-theme"
18615+ ],
18616+ "filenames": [
18617+ ".bash_aliases",
18618+ ".bash_functions",
18619+ ".bash_history",
18620+ ".bash_logout",
18621+ ".bash_profile",
18622+ ".bashrc",
18623+ ".cshrc",
18624+ ".flaskenv",
18625+ ".kshrc",
18626+ ".login",
18627+ ".profile",
18628+ ".zlogin",
18629+ ".zlogout",
18630+ ".zprofile",
18631+ ".zshenv",
18632+ ".zshrc",
18633+ "9fs",
18634+ "PKGBUILD",
18635+ "bash_aliases",
18636+ "bash_logout",
18637+ "bash_profile",
18638+ "bashrc",
18639+ "cshrc",
18640+ "gradlew",
18641+ "kshrc",
18642+ "login",
18643+ "man",
18644+ "profile",
18645+ "zlogin",
18646+ "zlogout",
18647+ "zprofile",
18648+ "zshenv",
18649+ "zshrc"
18650+ ],
18651+ "interpreters": [
18652+ "ash",
18653+ "bash",
18654+ "dash",
18655+ "ksh",
18656+ "mksh",
18657+ "pdksh",
18658+ "rc",
18659+ "sh",
18660+ "zsh"
18661+ ],
18662+ "tm_scope": "source.shell",
18663+ "ace_mode": "sh",
18664+ "codemirror_mode": "shell",
18665+ "codemirror_mime_type": "text/x-sh",
18666+ "language_id": 346
18667+ },
18668+ "ShellCheck Config": {
18669+ "type": "data",
18670+ "color": "#cecfcb",
18671+ "filenames": [
18672+ ".shellcheckrc"
18673+ ],
18674+ "aliases": [
18675+ "shellcheckrc"
18676+ ],
18677+ "tm_scope": "source.shellcheckrc",
18678+ "ace_mode": "ini",
18679+ "codemirror_mode": "properties",
18680+ "codemirror_mime_type": "text/x-properties",
18681+ "language_id": 687511714
18682+ },
18683+ "ShellSession": {
18684+ "type": "programming",
18685+ "extensions": [
18686+ ".sh-session"
18687+ ],
18688+ "aliases": [
18689+ "bash session",
18690+ "console"
18691+ ],
18692+ "tm_scope": "text.shell-session",
18693+ "ace_mode": "sh",
18694+ "codemirror_mode": "shell",
18695+ "codemirror_mime_type": "text/x-sh",
18696+ "language_id": 347
18697+ },
18698+ "Shen": {
18699+ "type": "programming",
18700+ "color": "#120F14",
18701+ "extensions": [
18702+ ".shen"
18703+ ],
18704+ "tm_scope": "source.shen",
18705+ "ace_mode": "text",
18706+ "language_id": 348
18707+ },
18708+ "Sieve": {
18709+ "type": "programming",
18710+ "tm_scope": "source.sieve",
18711+ "ace_mode": "text",
18712+ "extensions": [
18713+ ".sieve"
18714+ ],
18715+ "codemirror_mode": "sieve",
18716+ "codemirror_mime_type": "application/sieve",
18717+ "language_id": 208976687
18718+ },
18719+ "Simple File Verification": {
18720+ "type": "data",
18721+ "group": "Checksums",
18722+ "color": "#C9BFED",
18723+ "extensions": [
18724+ ".sfv"
18725+ ],
18726+ "aliases": [
18727+ "sfv"
18728+ ],
18729+ "tm_scope": "source.sfv",
18730+ "ace_mode": "ini",
18731+ "codemirror_mode": "properties",
18732+ "codemirror_mime_type": "text/x-properties",
18733+ "language_id": 735623761
18734+ },
18735+ "Singularity": {
18736+ "type": "programming",
18737+ "color": "#64E6AD",
18738+ "tm_scope": "source.singularity",
18739+ "filenames": [
18740+ "Singularity"
18741+ ],
18742+ "ace_mode": "text",
18743+ "language_id": 987024632
18744+ },
18745+ "Slash": {
18746+ "type": "programming",
18747+ "color": "#007eff",
18748+ "extensions": [
18749+ ".sl"
18750+ ],
18751+ "tm_scope": "text.html.slash",
18752+ "ace_mode": "text",
18753+ "language_id": 349
18754+ },
18755+ "Slice": {
18756+ "type": "programming",
18757+ "color": "#003fa2",
18758+ "tm_scope": "source.slice",
18759+ "ace_mode": "text",
18760+ "extensions": [
18761+ ".ice"
18762+ ],
18763+ "language_id": 894641667
18764+ },
18765+ "Slim": {
18766+ "type": "markup",
18767+ "color": "#2b2b2b",
18768+ "extensions": [
18769+ ".slim"
18770+ ],
18771+ "tm_scope": "text.slim",
18772+ "ace_mode": "text",
18773+ "codemirror_mode": "slim",
18774+ "codemirror_mime_type": "text/x-slim",
18775+ "language_id": 350
18776+ },
18777+ "SmPL": {
18778+ "type": "programming",
18779+ "extensions": [
18780+ ".cocci"
18781+ ],
18782+ "aliases": [
18783+ "coccinelle"
18784+ ],
18785+ "ace_mode": "text",
18786+ "tm_scope": "source.smpl",
18787+ "color": "#c94949",
18788+ "language_id": 164123055
18789+ },
18790+ "Smali": {
18791+ "type": "programming",
18792+ "extensions": [
18793+ ".smali"
18794+ ],
18795+ "ace_mode": "text",
18796+ "tm_scope": "source.smali",
18797+ "language_id": 351
18798+ },
18799+ "Smalltalk": {
18800+ "type": "programming",
18801+ "color": "#596706",
18802+ "extensions": [
18803+ ".st",
18804+ ".cs"
18805+ ],
18806+ "aliases": [
18807+ "squeak"
18808+ ],
18809+ "tm_scope": "source.smalltalk",
18810+ "ace_mode": "text",
18811+ "codemirror_mode": "smalltalk",
18812+ "codemirror_mime_type": "text/x-stsrc",
18813+ "language_id": 352
18814+ },
18815+ "Smarty": {
18816+ "type": "programming",
18817+ "color": "#f0c040",
18818+ "extensions": [
18819+ ".tpl"
18820+ ],
18821+ "ace_mode": "smarty",
18822+ "codemirror_mode": "smarty",
18823+ "codemirror_mime_type": "text/x-smarty",
18824+ "tm_scope": "text.html.smarty",
18825+ "language_id": 353
18826+ },
18827+ "Smithy": {
18828+ "type": "programming",
18829+ "ace_mode": "text",
18830+ "codemirror_mode": "clike",
18831+ "codemirror_mime_type": "text/x-csrc",
18832+ "tm_scope": "source.smithy",
18833+ "color": "#c44536",
18834+ "extensions": [
18835+ ".smithy"
18836+ ],
18837+ "language_id": 1027892786
18838+ },
18839+ "Snakemake": {
18840+ "type": "programming",
18841+ "group": "Python",
18842+ "tm_scope": "source.python",
18843+ "ace_mode": "python",
18844+ "codemirror_mode": "python",
18845+ "codemirror_mime_type": "text/x-python",
18846+ "color": "#419179",
18847+ "extensions": [
18848+ ".smk",
18849+ ".snakefile"
18850+ ],
18851+ "filenames": [
18852+ "Snakefile"
18853+ ],
18854+ "aliases": [
18855+ "snakefile"
18856+ ],
18857+ "language_id": 151241392
18858+ },
18859+ "Solidity": {
18860+ "type": "programming",
18861+ "color": "#AA6746",
18862+ "ace_mode": "text",
18863+ "tm_scope": "source.solidity",
18864+ "extensions": [
18865+ ".sol"
18866+ ],
18867+ "language_id": 237469032
18868+ },
18869+ "Soong": {
18870+ "type": "data",
18871+ "tm_scope": "source.bp",
18872+ "ace_mode": "text",
18873+ "filenames": [
18874+ "Android.bp"
18875+ ],
18876+ "language_id": 222900098
18877+ },
18878+ "SourcePawn": {
18879+ "type": "programming",
18880+ "color": "#f69e1d",
18881+ "aliases": [
18882+ "sourcemod"
18883+ ],
18884+ "extensions": [
18885+ ".sp",
18886+ ".inc"
18887+ ],
18888+ "tm_scope": "source.sourcepawn",
18889+ "ace_mode": "text",
18890+ "language_id": 354
18891+ },
18892+ "Spline Font Database": {
18893+ "type": "data",
18894+ "extensions": [
18895+ ".sfd"
18896+ ],
18897+ "tm_scope": "text.sfd",
18898+ "ace_mode": "yaml",
18899+ "language_id": 767169629
18900+ },
18901+ "Squirrel": {
18902+ "type": "programming",
18903+ "color": "#800000",
18904+ "extensions": [
18905+ ".nut"
18906+ ],
18907+ "tm_scope": "source.nut",
18908+ "ace_mode": "c_cpp",
18909+ "codemirror_mode": "clike",
18910+ "codemirror_mime_type": "text/x-c++src",
18911+ "language_id": 355
18912+ },
18913+ "Stan": {
18914+ "type": "programming",
18915+ "color": "#b2011d",
18916+ "extensions": [
18917+ ".stan"
18918+ ],
18919+ "ace_mode": "text",
18920+ "tm_scope": "source.stan",
18921+ "language_id": 356
18922+ },
18923+ "Standard ML": {
18924+ "type": "programming",
18925+ "color": "#dc566d",
18926+ "aliases": [
18927+ "sml"
18928+ ],
18929+ "extensions": [
18930+ ".ml",
18931+ ".fun",
18932+ ".sig",
18933+ ".sml"
18934+ ],
18935+ "tm_scope": "source.ml",
18936+ "ace_mode": "text",
18937+ "codemirror_mode": "mllike",
18938+ "codemirror_mime_type": "text/x-ocaml",
18939+ "language_id": 357
18940+ },
18941+ "Starlark": {
18942+ "type": "programming",
18943+ "tm_scope": "source.python",
18944+ "ace_mode": "python",
18945+ "codemirror_mode": "python",
18946+ "codemirror_mime_type": "text/x-python",
18947+ "color": "#76d275",
18948+ "extensions": [
18949+ ".bzl",
18950+ ".star"
18951+ ],
18952+ "filenames": [
18953+ "BUCK",
18954+ "BUILD",
18955+ "BUILD.bazel",
18956+ "MODULE.bazel",
18957+ "Tiltfile",
18958+ "WORKSPACE",
18959+ "WORKSPACE.bazel"
18960+ ],
18961+ "aliases": [
18962+ "bazel",
18963+ "bzl"
18964+ ],
18965+ "language_id": 960266174
18966+ },
18967+ "Stata": {
18968+ "type": "programming",
18969+ "color": "#1a5f91",
18970+ "extensions": [
18971+ ".do",
18972+ ".ado",
18973+ ".doh",
18974+ ".ihlp",
18975+ ".mata",
18976+ ".matah",
18977+ ".sthlp"
18978+ ],
18979+ "tm_scope": "source.stata",
18980+ "ace_mode": "text",
18981+ "language_id": 358
18982+ },
18983+ "StringTemplate": {
18984+ "type": "markup",
18985+ "color": "#3fb34f",
18986+ "extensions": [
18987+ ".st"
18988+ ],
18989+ "tm_scope": "source.string-template",
18990+ "ace_mode": "html",
18991+ "codemirror_mode": "htmlmixed",
18992+ "codemirror_mime_type": "text/html",
18993+ "language_id": 89855901
18994+ },
18995+ "Stylus": {
18996+ "type": "markup",
18997+ "color": "#ff6347",
18998+ "extensions": [
18999+ ".styl"
19000+ ],
19001+ "tm_scope": "source.stylus",
19002+ "ace_mode": "stylus",
19003+ "language_id": 359
19004+ },
19005+ "SubRip Text": {
19006+ "type": "data",
19007+ "color": "#9e0101",
19008+ "extensions": [
19009+ ".srt"
19010+ ],
19011+ "ace_mode": "text",
19012+ "tm_scope": "text.srt",
19013+ "language_id": 360
19014+ },
19015+ "SugarSS": {
19016+ "type": "markup",
19017+ "color": "#2fcc9f",
19018+ "tm_scope": "source.css.postcss.sugarss",
19019+ "extensions": [
19020+ ".sss"
19021+ ],
19022+ "ace_mode": "text",
19023+ "language_id": 826404698
19024+ },
19025+ "SuperCollider": {
19026+ "type": "programming",
19027+ "color": "#46390b",
19028+ "extensions": [
19029+ ".sc",
19030+ ".scd"
19031+ ],
19032+ "interpreters": [
19033+ "sclang",
19034+ "scsynth"
19035+ ],
19036+ "tm_scope": "source.supercollider",
19037+ "ace_mode": "text",
19038+ "language_id": 361
19039+ },
19040+ "Svelte": {
19041+ "type": "markup",
19042+ "color": "#ff3e00",
19043+ "tm_scope": "source.svelte",
19044+ "ace_mode": "html",
19045+ "codemirror_mode": "htmlmixed",
19046+ "codemirror_mime_type": "text/html",
19047+ "extensions": [
19048+ ".svelte"
19049+ ],
19050+ "language_id": 928734530
19051+ },
19052+ "Sway": {
19053+ "type": "programming",
19054+ "color": "#00F58C",
19055+ "extensions": [
19056+ ".sw"
19057+ ],
19058+ "tm_scope": "source.sway",
19059+ "ace_mode": "rust",
19060+ "codemirror_mode": "rust",
19061+ "codemirror_mime_type": "text/x-rustsrc",
19062+ "language_id": 271471144
19063+ },
19064+ "Sweave": {
19065+ "type": "prose",
19066+ "color": "#198ce7",
19067+ "extensions": [
19068+ ".rnw"
19069+ ],
19070+ "tm_scope": "text.tex.latex.sweave",
19071+ "ace_mode": "tex",
19072+ "language_id": 558779190
19073+ },
19074+ "Swift": {
19075+ "type": "programming",
19076+ "color": "#F05138",
19077+ "extensions": [
19078+ ".swift"
19079+ ],
19080+ "tm_scope": "source.swift",
19081+ "ace_mode": "text",
19082+ "codemirror_mode": "swift",
19083+ "codemirror_mime_type": "text/x-swift",
19084+ "language_id": 362
19085+ },
19086+ "SystemVerilog": {
19087+ "type": "programming",
19088+ "color": "#DAE1C2",
19089+ "extensions": [
19090+ ".sv",
19091+ ".svh",
19092+ ".vh"
19093+ ],
19094+ "tm_scope": "source.systemverilog",
19095+ "ace_mode": "verilog",
19096+ "codemirror_mode": "verilog",
19097+ "codemirror_mime_type": "text/x-systemverilog",
19098+ "language_id": 363
19099+ },
19100+ "TI Program": {
19101+ "type": "programming",
19102+ "ace_mode": "text",
19103+ "color": "#A0AA87",
19104+ "extensions": [
19105+ ".8xp",
19106+ ".8xk",
19107+ ".8xk.txt",
19108+ ".8xp.txt"
19109+ ],
19110+ "language_id": 422,
19111+ "tm_scope": "none"
19112+ },
19113+ "TL-Verilog": {
19114+ "type": "programming",
19115+ "extensions": [
19116+ ".tlv"
19117+ ],
19118+ "tm_scope": "source.tlverilog",
19119+ "ace_mode": "verilog",
19120+ "color": "#C40023",
19121+ "language_id": 118656070
19122+ },
19123+ "TLA": {
19124+ "type": "programming",
19125+ "color": "#4b0079",
19126+ "extensions": [
19127+ ".tla"
19128+ ],
19129+ "tm_scope": "source.tla",
19130+ "ace_mode": "text",
19131+ "language_id": 364
19132+ },
19133+ "TOML": {
19134+ "type": "data",
19135+ "color": "#9c4221",
19136+ "extensions": [
19137+ ".toml"
19138+ ],
19139+ "filenames": [
19140+ "Cargo.lock",
19141+ "Gopkg.lock",
19142+ "Pipfile",
19143+ "pdm.lock",
19144+ "poetry.lock"
19145+ ],
19146+ "tm_scope": "source.toml",
19147+ "ace_mode": "toml",
19148+ "codemirror_mode": "toml",
19149+ "codemirror_mime_type": "text/x-toml",
19150+ "language_id": 365
19151+ },
19152+ "TSQL": {
19153+ "type": "programming",
19154+ "color": "#e38c00",
19155+ "extensions": [
19156+ ".sql"
19157+ ],
19158+ "ace_mode": "sql",
19159+ "tm_scope": "source.tsql",
19160+ "language_id": 918334941
19161+ },
19162+ "TSV": {
19163+ "type": "data",
19164+ "color": "#237346",
19165+ "ace_mode": "text",
19166+ "tm_scope": "source.generic-db",
19167+ "extensions": [
19168+ ".tsv"
19169+ ],
19170+ "language_id": 1035892117
19171+ },
19172+ "TSX": {
19173+ "type": "programming",
19174+ "color": "#3178c6",
19175+ "group": "TypeScript",
19176+ "extensions": [
19177+ ".tsx"
19178+ ],
19179+ "tm_scope": "source.tsx",
19180+ "ace_mode": "javascript",
19181+ "codemirror_mode": "jsx",
19182+ "codemirror_mime_type": "text/jsx",
19183+ "language_id": 94901924
19184+ },
19185+ "TXL": {
19186+ "type": "programming",
19187+ "color": "#0178b8",
19188+ "extensions": [
19189+ ".txl"
19190+ ],
19191+ "tm_scope": "source.txl",
19192+ "ace_mode": "text",
19193+ "language_id": 366
19194+ },
19195+ "Talon": {
19196+ "type": "programming",
19197+ "ace_mode": "text",
19198+ "color": "#333333",
19199+ "extensions": [
19200+ ".talon"
19201+ ],
19202+ "tm_scope": "source.talon",
19203+ "language_id": 959889508
19204+ },
19205+ "Tcl": {
19206+ "type": "programming",
19207+ "color": "#e4cc98",
19208+ "extensions": [
19209+ ".tcl",
19210+ ".adp",
19211+ ".sdc",
19212+ ".tcl.in",
19213+ ".tm",
19214+ ".xdc"
19215+ ],
19216+ "aliases": [
19217+ "sdc",
19218+ "xdc"
19219+ ],
19220+ "filenames": [
19221+ "owh",
19222+ "starfield"
19223+ ],
19224+ "interpreters": [
19225+ "tclsh",
19226+ "wish"
19227+ ],
19228+ "tm_scope": "source.tcl",
19229+ "ace_mode": "tcl",
19230+ "codemirror_mode": "tcl",
19231+ "codemirror_mime_type": "text/x-tcl",
19232+ "language_id": 367
19233+ },
19234+ "Tcsh": {
19235+ "type": "programming",
19236+ "group": "Shell",
19237+ "extensions": [
19238+ ".tcsh",
19239+ ".csh"
19240+ ],
19241+ "interpreters": [
19242+ "tcsh",
19243+ "csh"
19244+ ],
19245+ "tm_scope": "source.shell",
19246+ "ace_mode": "sh",
19247+ "codemirror_mode": "shell",
19248+ "codemirror_mime_type": "text/x-sh",
19249+ "language_id": 368
19250+ },
19251+ "TeX": {
19252+ "type": "markup",
19253+ "color": "#3D6117",
19254+ "ace_mode": "tex",
19255+ "codemirror_mode": "stex",
19256+ "codemirror_mime_type": "text/x-stex",
19257+ "tm_scope": "text.tex.latex",
19258+ "wrap": true,
19259+ "aliases": [
19260+ "latex"
19261+ ],
19262+ "extensions": [
19263+ ".tex",
19264+ ".aux",
19265+ ".bbx",
19266+ ".cbx",
19267+ ".cls",
19268+ ".dtx",
19269+ ".ins",
19270+ ".lbx",
19271+ ".ltx",
19272+ ".mkii",
19273+ ".mkiv",
19274+ ".mkvi",
19275+ ".sty",
19276+ ".toc"
19277+ ],
19278+ "language_id": 369
19279+ },
19280+ "Tea": {
19281+ "type": "markup",
19282+ "extensions": [
19283+ ".tea"
19284+ ],
19285+ "tm_scope": "source.tea",
19286+ "ace_mode": "text",
19287+ "language_id": 370
19288+ },
19289+ "Terra": {
19290+ "type": "programming",
19291+ "extensions": [
19292+ ".t"
19293+ ],
19294+ "color": "#00004c",
19295+ "tm_scope": "source.terra",
19296+ "ace_mode": "lua",
19297+ "codemirror_mode": "lua",
19298+ "codemirror_mime_type": "text/x-lua",
19299+ "interpreters": [
19300+ "lua"
19301+ ],
19302+ "language_id": 371
19303+ },
19304+ "Terraform Template": {
19305+ "type": "markup",
19306+ "extensions": [
19307+ ".tftpl"
19308+ ],
19309+ "color": "#7b42bb",
19310+ "tm_scope": "source.hcl.terraform",
19311+ "ace_mode": "ruby",
19312+ "codemirror_mode": "ruby",
19313+ "codemirror_mime_type": "text/x-ruby",
19314+ "group": "HCL",
19315+ "language_id": 856832701
19316+ },
19317+ "Texinfo": {
19318+ "type": "prose",
19319+ "wrap": true,
19320+ "extensions": [
19321+ ".texinfo",
19322+ ".texi",
19323+ ".txi"
19324+ ],
19325+ "ace_mode": "text",
19326+ "tm_scope": "text.texinfo",
19327+ "interpreters": [
19328+ "makeinfo"
19329+ ],
19330+ "language_id": 988020015
19331+ },
19332+ "Text": {
19333+ "type": "prose",
19334+ "wrap": true,
19335+ "aliases": [
19336+ "fundamental",
19337+ "plain text"
19338+ ],
19339+ "extensions": [
19340+ ".txt",
19341+ ".fr",
19342+ ".nb",
19343+ ".ncl",
19344+ ".no"
19345+ ],
19346+ "filenames": [
19347+ "CITATION",
19348+ "CITATIONS",
19349+ "COPYING",
19350+ "COPYING.regex",
19351+ "COPYRIGHT.regex",
19352+ "FONTLOG",
19353+ "INSTALL",
19354+ "INSTALL.mysql",
19355+ "LICENSE",
19356+ "LICENSE.mysql",
19357+ "NEWS",
19358+ "README.me",
19359+ "README.mysql",
19360+ "README.nss",
19361+ "click.me",
19362+ "delete.me",
19363+ "keep.me",
19364+ "package.mask",
19365+ "package.use.mask",
19366+ "package.use.stable.mask",
19367+ "read.me",
19368+ "readme.1st",
19369+ "test.me",
19370+ "use.mask",
19371+ "use.stable.mask"
19372+ ],
19373+ "tm_scope": "none",
19374+ "ace_mode": "text",
19375+ "language_id": 372
19376+ },
19377+ "TextMate Properties": {
19378+ "type": "data",
19379+ "color": "#df66e4",
19380+ "aliases": [
19381+ "tm-properties"
19382+ ],
19383+ "filenames": [
19384+ ".tm_properties"
19385+ ],
19386+ "ace_mode": "properties",
19387+ "codemirror_mode": "properties",
19388+ "codemirror_mime_type": "text/x-properties",
19389+ "tm_scope": "source.tm-properties",
19390+ "language_id": 981795023
19391+ },
19392+ "Textile": {
19393+ "type": "prose",
19394+ "color": "#ffe7ac",
19395+ "ace_mode": "textile",
19396+ "codemirror_mode": "textile",
19397+ "codemirror_mime_type": "text/x-textile",
19398+ "wrap": true,
19399+ "extensions": [
19400+ ".textile"
19401+ ],
19402+ "tm_scope": "none",
19403+ "language_id": 373
19404+ },
19405+ "Thrift": {
19406+ "type": "programming",
19407+ "color": "#D12127",
19408+ "tm_scope": "source.thrift",
19409+ "extensions": [
19410+ ".thrift"
19411+ ],
19412+ "ace_mode": "text",
19413+ "language_id": 374
19414+ },
19415+ "Toit": {
19416+ "type": "programming",
19417+ "color": "#c2c9fb",
19418+ "extensions": [
19419+ ".toit"
19420+ ],
19421+ "tm_scope": "source.toit",
19422+ "ace_mode": "text",
19423+ "language_id": 356554395
19424+ },
19425+ "Turing": {
19426+ "type": "programming",
19427+ "color": "#cf142b",
19428+ "extensions": [
19429+ ".t",
19430+ ".tu"
19431+ ],
19432+ "tm_scope": "source.turing",
19433+ "ace_mode": "text",
19434+ "language_id": 375
19435+ },
19436+ "Turtle": {
19437+ "type": "data",
19438+ "extensions": [
19439+ ".ttl"
19440+ ],
19441+ "tm_scope": "source.turtle",
19442+ "ace_mode": "text",
19443+ "codemirror_mode": "turtle",
19444+ "codemirror_mime_type": "text/turtle",
19445+ "language_id": 376
19446+ },
19447+ "Twig": {
19448+ "type": "markup",
19449+ "color": "#c1d026",
19450+ "extensions": [
19451+ ".twig"
19452+ ],
19453+ "tm_scope": "text.html.twig",
19454+ "ace_mode": "twig",
19455+ "codemirror_mode": "twig",
19456+ "codemirror_mime_type": "text/x-twig",
19457+ "language_id": 377
19458+ },
19459+ "Type Language": {
19460+ "type": "data",
19461+ "aliases": [
19462+ "tl"
19463+ ],
19464+ "extensions": [
19465+ ".tl"
19466+ ],
19467+ "tm_scope": "source.tl",
19468+ "ace_mode": "text",
19469+ "language_id": 632765617
19470+ },
19471+ "TypeScript": {
19472+ "type": "programming",
19473+ "color": "#3178c6",
19474+ "aliases": [
19475+ "ts"
19476+ ],
19477+ "interpreters": [
19478+ "deno",
19479+ "ts-node"
19480+ ],
19481+ "extensions": [
19482+ ".ts",
19483+ ".cts",
19484+ ".mts"
19485+ ],
19486+ "tm_scope": "source.ts",
19487+ "ace_mode": "typescript",
19488+ "codemirror_mode": "javascript",
19489+ "codemirror_mime_type": "application/typescript",
19490+ "language_id": 378
19491+ },
19492+ "Typst": {
19493+ "type": "programming",
19494+ "color": "#239dad",
19495+ "aliases": [
19496+ "typ"
19497+ ],
19498+ "extensions": [
19499+ ".typ"
19500+ ],
19501+ "tm_scope": "source.typst",
19502+ "ace_mode": "text",
19503+ "language_id": 704730682
19504+ },
19505+ "Unified Parallel C": {
19506+ "type": "programming",
19507+ "color": "#4e3617",
19508+ "group": "C",
19509+ "ace_mode": "c_cpp",
19510+ "codemirror_mode": "clike",
19511+ "codemirror_mime_type": "text/x-csrc",
19512+ "extensions": [
19513+ ".upc"
19514+ ],
19515+ "tm_scope": "source.c",
19516+ "language_id": 379
19517+ },
19518+ "Unity3D Asset": {
19519+ "type": "data",
19520+ "color": "#222c37",
19521+ "ace_mode": "yaml",
19522+ "codemirror_mode": "yaml",
19523+ "codemirror_mime_type": "text/x-yaml",
19524+ "extensions": [
19525+ ".anim",
19526+ ".asset",
19527+ ".mask",
19528+ ".mat",
19529+ ".meta",
19530+ ".prefab",
19531+ ".unity"
19532+ ],
19533+ "tm_scope": "source.yaml",
19534+ "language_id": 380
19535+ },
19536+ "Unix Assembly": {
19537+ "type": "programming",
19538+ "group": "Assembly",
19539+ "extensions": [
19540+ ".s",
19541+ ".ms"
19542+ ],
19543+ "aliases": [
19544+ "gas",
19545+ "gnu asm",
19546+ "unix asm"
19547+ ],
19548+ "tm_scope": "source.x86",
19549+ "ace_mode": "assembly_x86",
19550+ "language_id": 120
19551+ },
19552+ "Uno": {
19553+ "type": "programming",
19554+ "color": "#9933cc",
19555+ "extensions": [
19556+ ".uno"
19557+ ],
19558+ "ace_mode": "csharp",
19559+ "codemirror_mode": "clike",
19560+ "codemirror_mime_type": "text/x-csharp",
19561+ "tm_scope": "source.cs",
19562+ "language_id": 381
19563+ },
19564+ "UnrealScript": {
19565+ "type": "programming",
19566+ "color": "#a54c4d",
19567+ "extensions": [
19568+ ".uc"
19569+ ],
19570+ "tm_scope": "source.java",
19571+ "ace_mode": "java",
19572+ "codemirror_mode": "clike",
19573+ "codemirror_mime_type": "text/x-java",
19574+ "language_id": 382
19575+ },
19576+ "UrWeb": {
19577+ "type": "programming",
19578+ "color": "#ccccee",
19579+ "aliases": [
19580+ "Ur/Web",
19581+ "Ur"
19582+ ],
19583+ "extensions": [
19584+ ".ur",
19585+ ".urs"
19586+ ],
19587+ "tm_scope": "source.ur",
19588+ "ace_mode": "text",
19589+ "language_id": 383
19590+ },
19591+ "V": {
19592+ "type": "programming",
19593+ "color": "#4f87c4",
19594+ "aliases": [
19595+ "vlang"
19596+ ],
19597+ "extensions": [
19598+ ".v"
19599+ ],
19600+ "tm_scope": "source.v",
19601+ "ace_mode": "golang",
19602+ "codemirror_mode": "go",
19603+ "codemirror_mime_type": "text/x-go",
19604+ "language_id": 603371597
19605+ },
19606+ "VBA": {
19607+ "type": "programming",
19608+ "color": "#867db1",
19609+ "extensions": [
19610+ ".bas",
19611+ ".cls",
19612+ ".frm",
19613+ ".vba"
19614+ ],
19615+ "tm_scope": "source.vba",
19616+ "aliases": [
19617+ "visual basic for applications"
19618+ ],
19619+ "ace_mode": "text",
19620+ "codemirror_mode": "vb",
19621+ "codemirror_mime_type": "text/x-vb",
19622+ "language_id": 399230729
19623+ },
19624+ "VBScript": {
19625+ "type": "programming",
19626+ "color": "#15dcdc",
19627+ "extensions": [
19628+ ".vbs"
19629+ ],
19630+ "tm_scope": "source.vbnet",
19631+ "ace_mode": "text",
19632+ "codemirror_mode": "vbscript",
19633+ "codemirror_mime_type": "text/vbscript",
19634+ "language_id": 408016005
19635+ },
19636+ "VCL": {
19637+ "type": "programming",
19638+ "color": "#148AA8",
19639+ "extensions": [
19640+ ".vcl"
19641+ ],
19642+ "tm_scope": "source.varnish.vcl",
19643+ "ace_mode": "text",
19644+ "language_id": 384
19645+ },
19646+ "VHDL": {
19647+ "type": "programming",
19648+ "color": "#adb2cb",
19649+ "extensions": [
19650+ ".vhdl",
19651+ ".vhd",
19652+ ".vhf",
19653+ ".vhi",
19654+ ".vho",
19655+ ".vhs",
19656+ ".vht",
19657+ ".vhw"
19658+ ],
19659+ "tm_scope": "source.vhdl",
19660+ "ace_mode": "vhdl",
19661+ "codemirror_mode": "vhdl",
19662+ "codemirror_mime_type": "text/x-vhdl",
19663+ "language_id": 385
19664+ },
19665+ "Vala": {
19666+ "type": "programming",
19667+ "color": "#a56de2",
19668+ "extensions": [
19669+ ".vala",
19670+ ".vapi"
19671+ ],
19672+ "tm_scope": "source.vala",
19673+ "ace_mode": "vala",
19674+ "language_id": 386
19675+ },
19676+ "Valve Data Format": {
19677+ "type": "data",
19678+ "color": "#f26025",
19679+ "aliases": [
19680+ "keyvalues",
19681+ "vdf"
19682+ ],
19683+ "extensions": [
19684+ ".vdf"
19685+ ],
19686+ "ace_mode": "text",
19687+ "tm_scope": "source.keyvalues",
19688+ "language_id": 544060961
19689+ },
19690+ "Velocity Template Language": {
19691+ "type": "markup",
19692+ "color": "#507cff",
19693+ "aliases": [
19694+ "vtl",
19695+ "velocity"
19696+ ],
19697+ "extensions": [
19698+ ".vtl"
19699+ ],
19700+ "ace_mode": "velocity",
19701+ "tm_scope": "source.velocity",
19702+ "codemirror_mode": "velocity",
19703+ "codemirror_mime_type": "text/velocity",
19704+ "language_id": 292377326
19705+ },
19706+ "Verilog": {
19707+ "type": "programming",
19708+ "color": "#b2b7f8",
19709+ "extensions": [
19710+ ".v",
19711+ ".veo"
19712+ ],
19713+ "tm_scope": "source.verilog",
19714+ "ace_mode": "verilog",
19715+ "codemirror_mode": "verilog",
19716+ "codemirror_mime_type": "text/x-verilog",
19717+ "language_id": 387
19718+ },
19719+ "Vim Help File": {
19720+ "type": "prose",
19721+ "color": "#199f4b",
19722+ "aliases": [
19723+ "help",
19724+ "vimhelp"
19725+ ],
19726+ "extensions": [
19727+ ".txt"
19728+ ],
19729+ "tm_scope": "text.vim-help",
19730+ "ace_mode": "text",
19731+ "language_id": 508563686
19732+ },
19733+ "Vim Script": {
19734+ "type": "programming",
19735+ "color": "#199f4b",
19736+ "tm_scope": "source.viml",
19737+ "aliases": [
19738+ "vim",
19739+ "viml",
19740+ "nvim"
19741+ ],
19742+ "extensions": [
19743+ ".vim",
19744+ ".vba",
19745+ ".vimrc",
19746+ ".vmb"
19747+ ],
19748+ "filenames": [
19749+ ".exrc",
19750+ ".gvimrc",
19751+ ".nvimrc",
19752+ ".vimrc",
19753+ "_vimrc",
19754+ "gvimrc",
19755+ "nvimrc",
19756+ "vimrc"
19757+ ],
19758+ "ace_mode": "text",
19759+ "language_id": 388
19760+ },
19761+ "Vim Snippet": {
19762+ "type": "markup",
19763+ "color": "#199f4b",
19764+ "aliases": [
19765+ "SnipMate",
19766+ "UltiSnip",
19767+ "UltiSnips",
19768+ "NeoSnippet"
19769+ ],
19770+ "extensions": [
19771+ ".snip",
19772+ ".snippet",
19773+ ".snippets"
19774+ ],
19775+ "tm_scope": "source.vim-snippet",
19776+ "ace_mode": "text",
19777+ "language_id": 81265970
19778+ },
19779+ "Visual Basic .NET": {
19780+ "type": "programming",
19781+ "color": "#945db7",
19782+ "extensions": [
19783+ ".vb",
19784+ ".vbhtml"
19785+ ],
19786+ "aliases": [
19787+ "visual basic",
19788+ "vbnet",
19789+ "vb .net",
19790+ "vb.net"
19791+ ],
19792+ "tm_scope": "source.vbnet",
19793+ "ace_mode": "text",
19794+ "codemirror_mode": "vb",
19795+ "codemirror_mime_type": "text/x-vb",
19796+ "language_id": 389
19797+ },
19798+ "Visual Basic 6.0": {
19799+ "type": "programming",
19800+ "color": "#2c6353",
19801+ "extensions": [
19802+ ".bas",
19803+ ".cls",
19804+ ".ctl",
19805+ ".Dsr",
19806+ ".frm"
19807+ ],
19808+ "tm_scope": "source.vbnet",
19809+ "aliases": [
19810+ "vb6",
19811+ "vb 6",
19812+ "visual basic 6",
19813+ "visual basic classic",
19814+ "classic visual basic"
19815+ ],
19816+ "ace_mode": "text",
19817+ "codemirror_mode": "vb",
19818+ "codemirror_mime_type": "text/x-vb",
19819+ "language_id": 679594952
19820+ },
19821+ "Volt": {
19822+ "type": "programming",
19823+ "color": "#1F1F1F",
19824+ "extensions": [
19825+ ".volt"
19826+ ],
19827+ "tm_scope": "source.d",
19828+ "ace_mode": "d",
19829+ "codemirror_mode": "d",
19830+ "codemirror_mime_type": "text/x-d",
19831+ "language_id": 390
19832+ },
19833+ "Vue": {
19834+ "type": "markup",
19835+ "color": "#41b883",
19836+ "extensions": [
19837+ ".vue"
19838+ ],
19839+ "tm_scope": "text.html.vue",
19840+ "ace_mode": "html",
19841+ "language_id": 391
19842+ },
19843+ "Vyper": {
19844+ "type": "programming",
19845+ "extensions": [
19846+ ".vy"
19847+ ],
19848+ "color": "#2980b9",
19849+ "ace_mode": "text",
19850+ "tm_scope": "source.vyper",
19851+ "language_id": 1055641948
19852+ },
19853+ "WDL": {
19854+ "aliases": [
19855+ "Workflow Description Language"
19856+ ],
19857+ "type": "programming",
19858+ "color": "#42f1f4",
19859+ "extensions": [
19860+ ".wdl"
19861+ ],
19862+ "tm_scope": "source.wdl",
19863+ "ace_mode": "text",
19864+ "language_id": 374521672
19865+ },
19866+ "WGSL": {
19867+ "type": "programming",
19868+ "color": "#1a5e9a",
19869+ "extensions": [
19870+ ".wgsl"
19871+ ],
19872+ "tm_scope": "source.wgsl",
19873+ "ace_mode": "text",
19874+ "language_id": 836605993
19875+ },
19876+ "Wavefront Material": {
19877+ "type": "data",
19878+ "extensions": [
19879+ ".mtl"
19880+ ],
19881+ "tm_scope": "source.wavefront.mtl",
19882+ "ace_mode": "text",
19883+ "language_id": 392
19884+ },
19885+ "Wavefront Object": {
19886+ "type": "data",
19887+ "extensions": [
19888+ ".obj"
19889+ ],
19890+ "tm_scope": "source.wavefront.obj",
19891+ "ace_mode": "text",
19892+ "language_id": 393
19893+ },
19894+ "Web Ontology Language": {
19895+ "type": "data",
19896+ "color": "#5b70bd",
19897+ "extensions": [
19898+ ".owl"
19899+ ],
19900+ "tm_scope": "text.xml",
19901+ "ace_mode": "xml",
19902+ "language_id": 394
19903+ },
19904+ "WebAssembly": {
19905+ "type": "programming",
19906+ "color": "#04133b",
19907+ "extensions": [
19908+ ".wast",
19909+ ".wat"
19910+ ],
19911+ "aliases": [
19912+ "wast",
19913+ "wasm"
19914+ ],
19915+ "tm_scope": "source.webassembly",
19916+ "ace_mode": "lisp",
19917+ "codemirror_mode": "commonlisp",
19918+ "codemirror_mime_type": "text/x-common-lisp",
19919+ "language_id": 956556503
19920+ },
19921+ "WebAssembly Interface Type": {
19922+ "type": "data",
19923+ "color": "#6250e7",
19924+ "extensions": [
19925+ ".wit"
19926+ ],
19927+ "aliases": [
19928+ "wit"
19929+ ],
19930+ "ace_mode": "text",
19931+ "tm_scope": "source.wit",
19932+ "codemirror_mode": "webidl",
19933+ "codemirror_mime_type": "text/x-webidl",
19934+ "language_id": 134534086
19935+ },
19936+ "WebIDL": {
19937+ "type": "programming",
19938+ "extensions": [
19939+ ".webidl"
19940+ ],
19941+ "tm_scope": "source.webidl",
19942+ "ace_mode": "text",
19943+ "codemirror_mode": "webidl",
19944+ "codemirror_mime_type": "text/x-webidl",
19945+ "language_id": 395
19946+ },
19947+ "WebVTT": {
19948+ "type": "data",
19949+ "wrap": true,
19950+ "aliases": [
19951+ "vtt"
19952+ ],
19953+ "extensions": [
19954+ ".vtt"
19955+ ],
19956+ "tm_scope": "text.vtt",
19957+ "ace_mode": "text",
19958+ "language_id": 658679714
19959+ },
19960+ "Wget Config": {
19961+ "type": "data",
19962+ "group": "INI",
19963+ "aliases": [
19964+ "wgetrc"
19965+ ],
19966+ "filenames": [
19967+ ".wgetrc"
19968+ ],
19969+ "tm_scope": "source.wgetrc",
19970+ "ace_mode": "text",
19971+ "language_id": 668457123
19972+ },
19973+ "Whiley": {
19974+ "type": "programming",
19975+ "color": "#d5c397",
19976+ "extensions": [
19977+ ".whiley"
19978+ ],
19979+ "tm_scope": "source.whiley",
19980+ "ace_mode": "text",
19981+ "language_id": 888779559
19982+ },
19983+ "Wikitext": {
19984+ "type": "prose",
19985+ "color": "#fc5757",
19986+ "wrap": true,
19987+ "aliases": [
19988+ "mediawiki",
19989+ "wiki"
19990+ ],
19991+ "extensions": [
19992+ ".mediawiki",
19993+ ".wiki",
19994+ ".wikitext"
19995+ ],
19996+ "tm_scope": "text.html.mediawiki",
19997+ "ace_mode": "text",
19998+ "language_id": 228
19999+ },
20000+ "Win32 Message File": {
20001+ "type": "data",
20002+ "extensions": [
20003+ ".mc"
20004+ ],
20005+ "tm_scope": "source.win32-messages",
20006+ "ace_mode": "ini",
20007+ "codemirror_mode": "properties",
20008+ "codemirror_mime_type": "text/x-properties",
20009+ "language_id": 950967261
20010+ },
20011+ "Windows Registry Entries": {
20012+ "type": "data",
20013+ "color": "#52d5ff",
20014+ "extensions": [
20015+ ".reg"
20016+ ],
20017+ "tm_scope": "source.reg",
20018+ "ace_mode": "ini",
20019+ "codemirror_mode": "properties",
20020+ "codemirror_mime_type": "text/x-properties",
20021+ "language_id": 969674868
20022+ },
20023+ "Witcher Script": {
20024+ "type": "programming",
20025+ "color": "#ff0000",
20026+ "extensions": [
20027+ ".ws"
20028+ ],
20029+ "ace_mode": "text",
20030+ "tm_scope": "source.witcherscript",
20031+ "language_id": 686821385
20032+ },
20033+ "Wollok": {
20034+ "type": "programming",
20035+ "color": "#a23738",
20036+ "extensions": [
20037+ ".wlk"
20038+ ],
20039+ "ace_mode": "text",
20040+ "tm_scope": "source.wollok",
20041+ "language_id": 632745969
20042+ },
20043+ "World of Warcraft Addon Data": {
20044+ "type": "data",
20045+ "color": "#f7e43f",
20046+ "extensions": [
20047+ ".toc"
20048+ ],
20049+ "tm_scope": "source.toc",
20050+ "ace_mode": "text",
20051+ "language_id": 396
20052+ },
20053+ "Wren": {
20054+ "type": "programming",
20055+ "color": "#383838",
20056+ "aliases": [
20057+ "wrenlang"
20058+ ],
20059+ "extensions": [
20060+ ".wren"
20061+ ],
20062+ "tm_scope": "source.wren",
20063+ "ace_mode": "text",
20064+ "language_id": 713580619
20065+ },
20066+ "X BitMap": {
20067+ "type": "data",
20068+ "group": "C",
20069+ "aliases": [
20070+ "xbm"
20071+ ],
20072+ "extensions": [
20073+ ".xbm"
20074+ ],
20075+ "ace_mode": "c_cpp",
20076+ "tm_scope": "source.c",
20077+ "codemirror_mode": "clike",
20078+ "codemirror_mime_type": "text/x-csrc",
20079+ "language_id": 782911107
20080+ },
20081+ "X Font Directory Index": {
20082+ "type": "data",
20083+ "filenames": [
20084+ "encodings.dir",
20085+ "fonts.alias",
20086+ "fonts.dir",
20087+ "fonts.scale"
20088+ ],
20089+ "tm_scope": "source.fontdir",
20090+ "ace_mode": "text",
20091+ "language_id": 208700028
20092+ },
20093+ "X PixMap": {
20094+ "type": "data",
20095+ "group": "C",
20096+ "aliases": [
20097+ "xpm"
20098+ ],
20099+ "extensions": [
20100+ ".xpm",
20101+ ".pm"
20102+ ],
20103+ "ace_mode": "c_cpp",
20104+ "tm_scope": "source.c",
20105+ "codemirror_mode": "clike",
20106+ "codemirror_mime_type": "text/x-csrc",
20107+ "language_id": 781846279
20108+ },
20109+ "X10": {
20110+ "type": "programming",
20111+ "aliases": [
20112+ "xten"
20113+ ],
20114+ "ace_mode": "text",
20115+ "extensions": [
20116+ ".x10"
20117+ ],
20118+ "color": "#4B6BEF",
20119+ "tm_scope": "source.x10",
20120+ "language_id": 397
20121+ },
20122+ "XC": {
20123+ "type": "programming",
20124+ "color": "#99DA07",
20125+ "extensions": [
20126+ ".xc"
20127+ ],
20128+ "tm_scope": "source.xc",
20129+ "ace_mode": "c_cpp",
20130+ "codemirror_mode": "clike",
20131+ "codemirror_mime_type": "text/x-csrc",
20132+ "language_id": 398
20133+ },
20134+ "XCompose": {
20135+ "type": "data",
20136+ "filenames": [
20137+ ".XCompose",
20138+ "XCompose",
20139+ "xcompose"
20140+ ],
20141+ "tm_scope": "config.xcompose",
20142+ "ace_mode": "text",
20143+ "language_id": 225167241
20144+ },
20145+ "XML": {
20146+ "type": "data",
20147+ "color": "#0060ac",
20148+ "tm_scope": "text.xml",
20149+ "ace_mode": "xml",
20150+ "codemirror_mode": "xml",
20151+ "codemirror_mime_type": "text/xml",
20152+ "aliases": [
20153+ "rss",
20154+ "xsd",
20155+ "wsdl"
20156+ ],
20157+ "extensions": [
20158+ ".xml",
20159+ ".adml",
20160+ ".admx",
20161+ ".ant",
20162+ ".axaml",
20163+ ".axml",
20164+ ".builds",
20165+ ".ccproj",
20166+ ".ccxml",
20167+ ".clixml",
20168+ ".cproject",
20169+ ".cscfg",
20170+ ".csdef",
20171+ ".csl",
20172+ ".csproj",
20173+ ".ct",
20174+ ".depproj",
20175+ ".dita",
20176+ ".ditamap",
20177+ ".ditaval",
20178+ ".dll.config",
20179+ ".dotsettings",
20180+ ".filters",
20181+ ".fsproj",
20182+ ".fxml",
20183+ ".glade",
20184+ ".gml",
20185+ ".gmx",
20186+ ".grxml",
20187+ ".gst",
20188+ ".hzp",
20189+ ".iml",
20190+ ".ivy",
20191+ ".jelly",
20192+ ".jsproj",
20193+ ".kml",
20194+ ".launch",
20195+ ".mdpolicy",
20196+ ".mjml",
20197+ ".mm",
20198+ ".mod",
20199+ ".mxml",
20200+ ".natvis",
20201+ ".ncl",
20202+ ".ndproj",
20203+ ".nproj",
20204+ ".nuspec",
20205+ ".odd",
20206+ ".osm",
20207+ ".pkgproj",
20208+ ".pluginspec",
20209+ ".proj",
20210+ ".props",
20211+ ".ps1xml",
20212+ ".psc1",
20213+ ".pt",
20214+ ".qhelp",
20215+ ".rdf",
20216+ ".res",
20217+ ".resx",
20218+ ".rs",
20219+ ".rss",
20220+ ".sch",
20221+ ".scxml",
20222+ ".sfproj",
20223+ ".shproj",
20224+ ".srdf",
20225+ ".storyboard",
20226+ ".sublime-snippet",
20227+ ".sw",
20228+ ".targets",
20229+ ".tml",
20230+ ".ts",
20231+ ".tsx",
20232+ ".typ",
20233+ ".ui",
20234+ ".urdf",
20235+ ".ux",
20236+ ".vbproj",
20237+ ".vcxproj",
20238+ ".vsixmanifest",
20239+ ".vssettings",
20240+ ".vstemplate",
20241+ ".vxml",
20242+ ".wixproj",
20243+ ".workflow",
20244+ ".wsdl",
20245+ ".wsf",
20246+ ".wxi",
20247+ ".wxl",
20248+ ".wxs",
20249+ ".x3d",
20250+ ".xacro",
20251+ ".xaml",
20252+ ".xib",
20253+ ".xlf",
20254+ ".xliff",
20255+ ".xmi",
20256+ ".xml.dist",
20257+ ".xmp",
20258+ ".xproj",
20259+ ".xsd",
20260+ ".xspec",
20261+ ".xul",
20262+ ".zcml"
20263+ ],
20264+ "filenames": [
20265+ ".classpath",
20266+ ".cproject",
20267+ ".project",
20268+ "App.config",
20269+ "NuGet.config",
20270+ "Settings.StyleCop",
20271+ "Web.Debug.config",
20272+ "Web.Release.config",
20273+ "Web.config",
20274+ "packages.config"
20275+ ],
20276+ "language_id": 399
20277+ },
20278+ "XML Property List": {
20279+ "type": "data",
20280+ "color": "#0060ac",
20281+ "group": "XML",
20282+ "extensions": [
20283+ ".plist",
20284+ ".stTheme",
20285+ ".tmCommand",
20286+ ".tmLanguage",
20287+ ".tmPreferences",
20288+ ".tmSnippet",
20289+ ".tmTheme"
20290+ ],
20291+ "tm_scope": "text.xml.plist",
20292+ "ace_mode": "xml",
20293+ "codemirror_mode": "xml",
20294+ "codemirror_mime_type": "text/xml",
20295+ "language_id": 75622871
20296+ },
20297+ "XPages": {
20298+ "type": "data",
20299+ "extensions": [
20300+ ".xsp-config",
20301+ ".xsp.metadata"
20302+ ],
20303+ "tm_scope": "text.xml",
20304+ "ace_mode": "xml",
20305+ "codemirror_mode": "xml",
20306+ "codemirror_mime_type": "text/xml",
20307+ "language_id": 400
20308+ },
20309+ "XProc": {
20310+ "type": "programming",
20311+ "extensions": [
20312+ ".xpl",
20313+ ".xproc"
20314+ ],
20315+ "tm_scope": "text.xml",
20316+ "ace_mode": "xml",
20317+ "codemirror_mode": "xml",
20318+ "codemirror_mime_type": "text/xml",
20319+ "language_id": 401
20320+ },
20321+ "XQuery": {
20322+ "type": "programming",
20323+ "color": "#5232e7",
20324+ "extensions": [
20325+ ".xquery",
20326+ ".xq",
20327+ ".xql",
20328+ ".xqm",
20329+ ".xqy"
20330+ ],
20331+ "ace_mode": "xquery",
20332+ "codemirror_mode": "xquery",
20333+ "codemirror_mime_type": "application/xquery",
20334+ "tm_scope": "source.xq",
20335+ "language_id": 402
20336+ },
20337+ "XS": {
20338+ "type": "programming",
20339+ "extensions": [
20340+ ".xs"
20341+ ],
20342+ "tm_scope": "source.c",
20343+ "ace_mode": "c_cpp",
20344+ "codemirror_mode": "clike",
20345+ "codemirror_mime_type": "text/x-csrc",
20346+ "language_id": 403
20347+ },
20348+ "XSLT": {
20349+ "type": "programming",
20350+ "aliases": [
20351+ "xsl"
20352+ ],
20353+ "extensions": [
20354+ ".xslt",
20355+ ".xsl"
20356+ ],
20357+ "tm_scope": "text.xml.xsl",
20358+ "ace_mode": "xml",
20359+ "codemirror_mode": "xml",
20360+ "codemirror_mime_type": "text/xml",
20361+ "color": "#EB8CEB",
20362+ "language_id": 404
20363+ },
20364+ "Xojo": {
20365+ "type": "programming",
20366+ "color": "#81bd41",
20367+ "extensions": [
20368+ ".xojo_code",
20369+ ".xojo_menu",
20370+ ".xojo_report",
20371+ ".xojo_script",
20372+ ".xojo_toolbar",
20373+ ".xojo_window"
20374+ ],
20375+ "tm_scope": "source.xojo",
20376+ "ace_mode": "text",
20377+ "language_id": 405
20378+ },
20379+ "Xonsh": {
20380+ "type": "programming",
20381+ "color": "#285EEF",
20382+ "extensions": [
20383+ ".xsh"
20384+ ],
20385+ "tm_scope": "source.python",
20386+ "ace_mode": "text",
20387+ "codemirror_mode": "python",
20388+ "codemirror_mime_type": "text/x-python",
20389+ "language_id": 614078284
20390+ },
20391+ "Xtend": {
20392+ "type": "programming",
20393+ "color": "#24255d",
20394+ "extensions": [
20395+ ".xtend"
20396+ ],
20397+ "tm_scope": "source.xtend",
20398+ "ace_mode": "text",
20399+ "language_id": 406
20400+ },
20401+ "YAML": {
20402+ "type": "data",
20403+ "color": "#cb171e",
20404+ "tm_scope": "source.yaml",
20405+ "aliases": [
20406+ "yml"
20407+ ],
20408+ "extensions": [
20409+ ".yml",
20410+ ".mir",
20411+ ".reek",
20412+ ".rviz",
20413+ ".sublime-syntax",
20414+ ".syntax",
20415+ ".yaml",
20416+ ".yaml-tmlanguage",
20417+ ".yaml.sed",
20418+ ".yml.mysql"
20419+ ],
20420+ "filenames": [
20421+ ".clang-format",
20422+ ".clang-tidy",
20423+ ".gemrc",
20424+ "CITATION.cff",
20425+ "glide.lock",
20426+ "yarn.lock"
20427+ ],
20428+ "ace_mode": "yaml",
20429+ "codemirror_mode": "yaml",
20430+ "codemirror_mime_type": "text/x-yaml",
20431+ "language_id": 407
20432+ },
20433+ "YANG": {
20434+ "type": "data",
20435+ "extensions": [
20436+ ".yang"
20437+ ],
20438+ "tm_scope": "source.yang",
20439+ "ace_mode": "text",
20440+ "language_id": 408
20441+ },
20442+ "YARA": {
20443+ "type": "programming",
20444+ "color": "#220000",
20445+ "ace_mode": "text",
20446+ "extensions": [
20447+ ".yar",
20448+ ".yara"
20449+ ],
20450+ "tm_scope": "source.yara",
20451+ "language_id": 805122868
20452+ },
20453+ "YASnippet": {
20454+ "type": "markup",
20455+ "aliases": [
20456+ "snippet",
20457+ "yas"
20458+ ],
20459+ "color": "#32AB90",
20460+ "extensions": [
20461+ ".yasnippet"
20462+ ],
20463+ "tm_scope": "source.yasnippet",
20464+ "ace_mode": "text",
20465+ "language_id": 378760102
20466+ },
20467+ "Yacc": {
20468+ "type": "programming",
20469+ "extensions": [
20470+ ".y",
20471+ ".yacc",
20472+ ".yy"
20473+ ],
20474+ "tm_scope": "source.yacc",
20475+ "ace_mode": "text",
20476+ "color": "#4B6C4B",
20477+ "language_id": 409
20478+ },
20479+ "Yul": {
20480+ "type": "programming",
20481+ "color": "#794932",
20482+ "ace_mode": "text",
20483+ "tm_scope": "source.yul",
20484+ "extensions": [
20485+ ".yul"
20486+ ],
20487+ "language_id": 237469033
20488+ },
20489+ "ZAP": {
20490+ "type": "programming",
20491+ "color": "#0d665e",
20492+ "extensions": [
20493+ ".zap",
20494+ ".xzap"
20495+ ],
20496+ "tm_scope": "source.zap",
20497+ "ace_mode": "text",
20498+ "language_id": 952972794
20499+ },
20500+ "ZIL": {
20501+ "type": "programming",
20502+ "color": "#dc75e5",
20503+ "extensions": [
20504+ ".zil",
20505+ ".mud"
20506+ ],
20507+ "tm_scope": "source.zil",
20508+ "ace_mode": "text",
20509+ "language_id": 973483626
20510+ },
20511+ "Zeek": {
20512+ "type": "programming",
20513+ "aliases": [
20514+ "bro"
20515+ ],
20516+ "extensions": [
20517+ ".zeek",
20518+ ".bro"
20519+ ],
20520+ "tm_scope": "source.zeek",
20521+ "ace_mode": "text",
20522+ "language_id": 40
20523+ },
20524+ "ZenScript": {
20525+ "type": "programming",
20526+ "color": "#00BCD1",
20527+ "extensions": [
20528+ ".zs"
20529+ ],
20530+ "tm_scope": "source.zenscript",
20531+ "ace_mode": "text",
20532+ "language_id": 494938890
20533+ },
20534+ "Zephir": {
20535+ "type": "programming",
20536+ "color": "#118f9e",
20537+ "extensions": [
20538+ ".zep"
20539+ ],
20540+ "tm_scope": "source.php.zephir",
20541+ "ace_mode": "php",
20542+ "language_id": 410
20543+ },
20544+ "Zig": {
20545+ "type": "programming",
20546+ "color": "#ec915c",
20547+ "extensions": [
20548+ ".zig"
20549+ ],
20550+ "tm_scope": "source.zig",
20551+ "ace_mode": "text",
20552+ "language_id": 646424281
20553+ },
20554+ "Zimpl": {
20555+ "type": "programming",
20556+ "color": "#d67711",
20557+ "extensions": [
20558+ ".zimpl",
20559+ ".zmpl",
20560+ ".zpl"
20561+ ],
20562+ "tm_scope": "none",
20563+ "ace_mode": "text",
20564+ "language_id": 411
20565+ },
20566+ "cURL Config": {
20567+ "type": "data",
20568+ "group": "INI",
20569+ "aliases": [
20570+ "curlrc"
20571+ ],
20572+ "filenames": [
20573+ ".curlrc",
20574+ "_curlrc"
20575+ ],
20576+ "tm_scope": "source.curlrc",
20577+ "ace_mode": "text",
20578+ "language_id": 992375436
20579+ },
20580+ "desktop": {
20581+ "type": "data",
20582+ "extensions": [
20583+ ".desktop",
20584+ ".desktop.in",
20585+ ".service"
20586+ ],
20587+ "tm_scope": "source.desktop",
20588+ "ace_mode": "text",
20589+ "language_id": 412
20590+ },
20591+ "dircolors": {
20592+ "type": "data",
20593+ "extensions": [
20594+ ".dircolors"
20595+ ],
20596+ "filenames": [
20597+ ".dir_colors",
20598+ ".dircolors",
20599+ "DIR_COLORS",
20600+ "_dir_colors",
20601+ "_dircolors",
20602+ "dir_colors"
20603+ ],
20604+ "tm_scope": "source.dircolors",
20605+ "ace_mode": "text",
20606+ "language_id": 691605112
20607+ },
20608+ "eC": {
20609+ "type": "programming",
20610+ "color": "#913960",
20611+ "extensions": [
20612+ ".ec",
20613+ ".eh"
20614+ ],
20615+ "tm_scope": "source.c.ec",
20616+ "ace_mode": "text",
20617+ "language_id": 413
20618+ },
20619+ "edn": {
20620+ "type": "data",
20621+ "ace_mode": "clojure",
20622+ "codemirror_mode": "clojure",
20623+ "codemirror_mime_type": "text/x-clojure",
20624+ "extensions": [
20625+ ".edn"
20626+ ],
20627+ "tm_scope": "source.clojure",
20628+ "language_id": 414
20629+ },
20630+ "fish": {
20631+ "type": "programming",
20632+ "color": "#4aae47",
20633+ "group": "Shell",
20634+ "interpreters": [
20635+ "fish"
20636+ ],
20637+ "extensions": [
20638+ ".fish"
20639+ ],
20640+ "tm_scope": "source.fish",
20641+ "ace_mode": "text",
20642+ "language_id": 415
20643+ },
20644+ "hoon": {
20645+ "type": "programming",
20646+ "color": "#00b171",
20647+ "tm_scope": "source.hoon",
20648+ "ace_mode": "text",
20649+ "extensions": [
20650+ ".hoon"
20651+ ],
20652+ "language_id": 560883276
20653+ },
20654+ "jq": {
20655+ "color": "#c7254e",
20656+ "ace_mode": "text",
20657+ "type": "programming",
20658+ "extensions": [
20659+ ".jq"
20660+ ],
20661+ "tm_scope": "source.jq",
20662+ "language_id": 905371884
20663+ },
20664+ "kvlang": {
20665+ "type": "markup",
20666+ "ace_mode": "text",
20667+ "extensions": [
20668+ ".kv"
20669+ ],
20670+ "color": "#1da6e0",
20671+ "tm_scope": "source.python.kivy",
20672+ "language_id": 970675279
20673+ },
20674+ "mIRC Script": {
20675+ "type": "programming",
20676+ "color": "#3d57c3",
20677+ "extensions": [
20678+ ".mrc"
20679+ ],
20680+ "tm_scope": "source.msl",
20681+ "ace_mode": "text",
20682+ "language_id": 517654727
20683+ },
20684+ "mcfunction": {
20685+ "type": "programming",
20686+ "color": "#E22837",
20687+ "extensions": [
20688+ ".mcfunction"
20689+ ],
20690+ "tm_scope": "source.mcfunction",
20691+ "ace_mode": "text",
20692+ "language_id": 462488745
20693+ },
20694+ "mupad": {
20695+ "type": "programming",
20696+ "color": "#244963",
20697+ "extensions": [
20698+ ".mu"
20699+ ],
20700+ "tm_scope": "source.mupad",
20701+ "ace_mode": "text",
20702+ "language_id": 416
20703+ },
20704+ "nanorc": {
20705+ "type": "data",
20706+ "color": "#2d004d",
20707+ "group": "INI",
20708+ "extensions": [
20709+ ".nanorc"
20710+ ],
20711+ "filenames": [
20712+ ".nanorc",
20713+ "nanorc"
20714+ ],
20715+ "tm_scope": "source.nanorc",
20716+ "ace_mode": "text",
20717+ "language_id": 775996197
20718+ },
20719+ "nesC": {
20720+ "type": "programming",
20721+ "color": "#94B0C7",
20722+ "extensions": [
20723+ ".nc"
20724+ ],
20725+ "ace_mode": "text",
20726+ "tm_scope": "source.nesc",
20727+ "language_id": 417
20728+ },
20729+ "ooc": {
20730+ "type": "programming",
20731+ "color": "#b0b77e",
20732+ "extensions": [
20733+ ".ooc"
20734+ ],
20735+ "tm_scope": "source.ooc",
20736+ "ace_mode": "text",
20737+ "language_id": 418
20738+ },
20739+ "q": {
20740+ "type": "programming",
20741+ "extensions": [
20742+ ".q"
20743+ ],
20744+ "tm_scope": "source.q",
20745+ "ace_mode": "text",
20746+ "color": "#0040cd",
20747+ "language_id": 970539067
20748+ },
20749+ "reStructuredText": {
20750+ "type": "prose",
20751+ "color": "#141414",
20752+ "wrap": true,
20753+ "aliases": [
20754+ "rst"
20755+ ],
20756+ "extensions": [
20757+ ".rst",
20758+ ".rest",
20759+ ".rest.txt",
20760+ ".rst.txt"
20761+ ],
20762+ "tm_scope": "text.restructuredtext",
20763+ "ace_mode": "text",
20764+ "codemirror_mode": "rst",
20765+ "codemirror_mime_type": "text/x-rst",
20766+ "language_id": 419
20767+ },
20768+ "robots.txt": {
20769+ "type": "data",
20770+ "aliases": [
20771+ "robots",
20772+ "robots txt"
20773+ ],
20774+ "filenames": [
20775+ "robots.txt"
20776+ ],
20777+ "ace_mode": "text",
20778+ "tm_scope": "text.robots-txt",
20779+ "language_id": 674736065
20780+ },
20781+ "sed": {
20782+ "type": "programming",
20783+ "color": "#64b970",
20784+ "extensions": [
20785+ ".sed"
20786+ ],
20787+ "interpreters": [
20788+ "gsed",
20789+ "minised",
20790+ "sed",
20791+ "ssed"
20792+ ],
20793+ "ace_mode": "text",
20794+ "tm_scope": "source.sed",
20795+ "language_id": 847830017
20796+ },
20797+ "wisp": {
20798+ "type": "programming",
20799+ "ace_mode": "clojure",
20800+ "codemirror_mode": "clojure",
20801+ "codemirror_mime_type": "text/x-clojure",
20802+ "color": "#7582D1",
20803+ "extensions": [
20804+ ".wisp"
20805+ ],
20806+ "tm_scope": "source.clojure",
20807+ "language_id": 420
20808+ },
20809+ "xBase": {
20810+ "type": "programming",
20811+ "color": "#403a40",
20812+ "aliases": [
20813+ "advpl",
20814+ "clipper",
20815+ "foxpro"
20816+ ],
20817+ "extensions": [
20818+ ".prg",
20819+ ".ch",
20820+ ".prw"
20821+ ],
20822+ "tm_scope": "source.harbour",
20823+ "ace_mode": "text",
20824+ "language_id": 421
20825+ }
20826+ }
20827 diff --git a/migrations/20230602111216_init.sql b/migrations/20230602111216_init.sql
20828deleted file mode 100644
20829index d3754fe..0000000
20830--- a/migrations/20230602111216_init.sql
20831+++ /dev/null
20832 @@ -1,51 +0,0 @@
20833- CREATE TABLE authors (
20834- id INTEGER PRIMARY KEY NOT NULL,
20835- username TEXT NOT NULL,
20836- email TEXT NOT NULL,
20837- UNIQUE (username, email)
20838- ) STRICT;
20839-
20840- CREATE TABLE languages (
20841- id INTEGER PRIMARY KEY NOT NULL,
20842- git_hash TEXT NOT NULL,
20843- repo_path TEXT NOT NULL,
20844- language TEXT NOT NULL,
20845- loc INTEGER NOT NULL,
20846- UNIQUE (git_hash, repo_path, language)
20847- ) STRICT;
20848-
20849- CREATE TABLE jobs (
20850- id INTEGER PRIMARY KEY,
20851- created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,
20852- kind TEXT NOT NULL,
20853- repo_path TEXT,
20854- runtime INTEGER,
20855- success INTEGER
20856- ) STRICT;
20857-
20858- CREATE TABLE job_tracking (
20859- id INTEGER PRIMARY KEY,
20860- job_id INTEGER REFERENCES jobs(id) ON DELETE CASCADE NOT NULL,
20861- repo_path TEXT NOT NULL,
20862- git_hash TEXT NOT NULL,
20863- kind TEXT NOT NULL,
20864- UNIQUE (repo_path, git_hash, kind)
20865- ) STRICT;
20866-
20867- CREATE TABLE contributions (
20868- id INTEGER PRIMARY KEY,
20869- git_hash TEXT NOT NULL,
20870- repo_path TEXT NOT NULL,
20871- time TEXT NOT NULL,
20872- author_id INTEGER REFERENCES authors(id),
20873- lines_added INTEGER NOT NULL,
20874- lines_removed INTEGER NOT NULL
20875- ) STRICT;
20876-
20877- CREATE TABLE contribution_tally (
20878- id INTEGER PRIMARY KEY,
20879- git_hash TEXT NOT NULL,
20880- repo_path TEXT NOT NULL,
20881- author_id INTEGER REFERENCES authors(id),
20882- total INTEGER NOT NULL
20883- ) STRICT;
20884 diff --git a/queries/authors_list_project.sql b/queries/authors_list_project.sql
20885deleted file mode 100644
20886index 832e3de..0000000
20887--- a/queries/authors_list_project.sql
20888+++ /dev/null
20889 @@ -1,30 +0,0 @@
20890- WITH RECURSIVE max_git_id AS
20891- (SELECT contributions.id AS hash
20892- FROM contributions
20893- WHERE git_hash = ?
20894- AND repo_path = ?
20895- ORDER BY id DESC
20896- LIMIT 1),
20897- total_contributions AS
20898- (SELECT COUNT(*) AS contributions
20899- FROM contributions
20900- WHERE repo_path = ?
20901- AND id <=
20902- (SELECT hash
20903- FROM max_git_id) )
20904- SELECT authors.username,
20905- authors.email,
20906- COUNT(contributions.id) AS "count: i64",
20907- SUM(contributions.lines_added) AS "lines_added: i64",
20908- SUM(contributions.lines_removed) AS "lines_removed: i64",
20909- ROUND((COUNT(contributions.id)/CAST(
20910- (SELECT contributions
20911- FROM total_contributions) AS REAL))*100, 2) AS "percentage: f64"
20912- FROM contributions
20913- LEFT JOIN authors ON (contributions.author_id = authors.id)
20914- WHERE repo_path = ?
20915- AND contributions.id <=
20916- (SELECT hash
20917- FROM max_git_id)
20918- GROUP BY authors.email
20919- ORDER BY "count: i64" DESC
20920 diff --git a/queries/authors_upsert.sql b/queries/authors_upsert.sql
20921deleted file mode 100644
20922index 1c55636..0000000
20923--- a/queries/authors_upsert.sql
20924+++ /dev/null
20925 @@ -1,10 +0,0 @@
20926- INSERT INTO authors
20927- (username, email)
20928- VALUES
20929- (?, ?)
20930- ON CONFLICT
20931- DO UPDATE
20932- SET
20933- username = username,
20934- email = email
20935- RETURNING id
20936 diff --git a/queries/commits_count.sql b/queries/commits_count.sql
20937deleted file mode 100644
20938index 0088bb8..0000000
20939--- a/queries/commits_count.sql
20940+++ /dev/null
20941 @@ -1,9 +0,0 @@
20942- SELECT
20943- COUNT(id) AS count
20944- FROM contributions
20945- WHERE
20946- repo_path = ? AND
20947- id <= (
20948- SELECT id FROM contributions
20949- WHERE git_hash = ? AND repo_path = ?
20950- )
20951 diff --git a/queries/contribution_add.sql b/queries/contribution_add.sql
20952deleted file mode 100644
20953index e1085e3..0000000
20954--- a/queries/contribution_add.sql
20955+++ /dev/null
20956 @@ -1,11 +0,0 @@
20957- WITH previous(total) AS (
20958- SELECT total FROM contribution_tally
20959- WHERE
20960- author_id = ? AND repo_path = ?
20961- ORDER BY id DESC
20962- LIMIT 1
20963- )
20964- INSERT INTO contribution_tally
20965- (git_hash, repo_path, author_id, total)
20966- VALUES
20967- (?, ?, ?, COALESCE((SELECT(total) FROM previous), 0)+1)
20968 diff --git a/queries/contribution_delete.sql b/queries/contribution_delete.sql
20969deleted file mode 100644
20970index 6fdbe5c..0000000
20971--- a/queries/contribution_delete.sql
20972+++ /dev/null
20973 @@ -1,3 +0,0 @@
20974- DELETE FROM contribution_tally
20975- WHERE
20976- repo_path = ?
20977 diff --git a/queries/contributions_add.sql b/queries/contributions_add.sql
20978deleted file mode 100644
20979index 9883d94..0000000
20980--- a/queries/contributions_add.sql
20981+++ /dev/null
20982 @@ -1,4 +0,0 @@
20983- INSERT INTO contributions
20984- (author_id, git_hash, repo_path, time, lines_added, lines_removed)
20985- VALUES
20986- (?, ?, ?, ?, ?, ?)
20987 diff --git a/queries/contributions_bucket.sql b/queries/contributions_bucket.sql
20988deleted file mode 100644
20989index c4e2e55..0000000
20990--- a/queries/contributions_bucket.sql
20991+++ /dev/null
20992 @@ -1,10 +0,0 @@
20993- SELECT
20994- COUNT(id) AS "count!: i64",
20995- time,
20996- SUM(lines_added) AS "added!: i64",
20997- SUM(lines_removed) AS "removed!: i64"
20998- FROM contributions WHERE
20999- repo_path = ? AND
21000- id <= (SELECT id FROM contributions WHERE git_hash = ?) AND
21001- time <= ? AND time >= ?
21002- GROUP BY strftime(?, time)
21003 diff --git a/queries/contributions_delete.sql b/queries/contributions_delete.sql
21004deleted file mode 100644
21005index 0ee5563..0000000
21006--- a/queries/contributions_delete.sql
21007+++ /dev/null
21008 @@ -1,2 +0,0 @@
21009- DELETE FROM contributions
21010- WHERE repo_path = ?
21011 diff --git a/queries/contributions_list.sql b/queries/contributions_list.sql
21012deleted file mode 100644
21013index fae5633..0000000
21014--- a/queries/contributions_list.sql
21015+++ /dev/null
21016 @@ -1,42 +0,0 @@
21017- WITH RECURSIVE
21018- tallys AS (
21019- SELECT
21020- authors.username AS name,
21021- authors.email AS email,
21022- MAX(contribution_tally.total) AS total
21023- FROM contribution_tally
21024- LEFT JOIN authors ON
21025- (authors.id = contribution_tally.author_id)
21026- WHERE
21027- contribution_tally.id < (
21028- SELECT id FROM contributions
21029- WHERE
21030- repo_path = ? AND git_hash = ?
21031- ORDER BY id DESC
21032- LIMIT 1
21033- ) AND
21034- contribution_tally.repo_path = ?
21035- GROUP BY authors.email
21036- ORDER BY contribution_tally.total DESC
21037- LIMIT 5),
21038- raw_commits AS (
21039- SELECT
21040- COUNT(*) as total
21041- FROM contributions
21042- WHERE
21043- contributions.id < (
21044- SELECT id FROM contributions
21045- WHERE
21046- repo_path = ? AND git_hash = ?
21047- ORDER BY id DESC
21048- LIMIT 1
21049- ) AND
21050- contributions.repo_path = ?
21051- )
21052- SELECT
21053- name,
21054- email,
21055- (
21056- CAST(tallys.total AS REAL)/(SELECT total FROM raw_commits)
21057- ) * 100 AS "percentage: i64"
21058- FROM tallys
21059 diff --git a/queries/job_tracking_add.sql b/queries/job_tracking_add.sql
21060deleted file mode 100644
21061index 7e70756..0000000
21062--- a/queries/job_tracking_add.sql
21063+++ /dev/null
21064 @@ -1,4 +0,0 @@
21065- INSERT INTO job_tracking
21066- (repo_path, git_hash, kind, job_id)
21067- VALUES
21068- (?, ?, ?, ?)
21069 diff --git a/queries/job_tracking_delete.sql b/queries/job_tracking_delete.sql
21070deleted file mode 100644
21071index 3aef33d..0000000
21072--- a/queries/job_tracking_delete.sql
21073+++ /dev/null
21074 @@ -1,3 +0,0 @@
21075- DELETE FROM job_tracking
21076- WHERE
21077- repo_path = ?
21078 diff --git a/queries/job_tracking_delete_by_repo.sql b/queries/job_tracking_delete_by_repo.sql
21079deleted file mode 100644
21080index ebd0363..0000000
21081--- a/queries/job_tracking_delete_by_repo.sql
21082+++ /dev/null
21083 @@ -1,3 +0,0 @@
21084- DELETE FROM jobs
21085- WHERE
21086- repo_path = ?
21087 diff --git a/queries/job_tracking_read.sql b/queries/job_tracking_read.sql
21088deleted file mode 100644
21089index 823dfc5..0000000
21090--- a/queries/job_tracking_read.sql
21091+++ /dev/null
21092 @@ -1,8 +0,0 @@
21093- SELECT
21094- git_hash AS "git_hash!"
21095- FROM job_tracking
21096- WHERE
21097- repo_path = ? AND
21098- kind = ?
21099- ORDER BY id DESC
21100- LIMIT 1
21101 diff --git a/queries/jobs_create.sql b/queries/jobs_create.sql
21102deleted file mode 100644
21103index 4a1bea5..0000000
21104--- a/queries/jobs_create.sql
21105+++ /dev/null
21106 @@ -1,5 +0,0 @@
21107- INSERT INTO jobs
21108- (created_at, repo_path, kind)
21109- VALUES
21110- (?, ?, ?)
21111- RETURNING id
21112 diff --git a/queries/jobs_delete.sql b/queries/jobs_delete.sql
21113deleted file mode 100644
21114index c70ba25..0000000
21115--- a/queries/jobs_delete.sql
21116+++ /dev/null
21117 @@ -1,2 +0,0 @@
21118- DELETE FROM jobs
21119- WHERE jobs.id = ?
21120 diff --git a/queries/jobs_list.sql b/queries/jobs_list.sql
21121deleted file mode 100644
21122index 23b3209..0000000
21123--- a/queries/jobs_list.sql
21124+++ /dev/null
21125 @@ -1,15 +0,0 @@
21126- SELECT
21127- jobs.id AS "id!",
21128- created_at AS "created_at: OffsetDateTime",
21129- jobs.repo_path,
21130- jobs.kind,
21131- runtime AS "runtime: u32",
21132- success AS "success: bool",
21133- (
21134- SELECT COUNT(id) FROM job_tracking
21135- WHERE job_tracking.job_id = jobs.id
21136- ) AS "commits!: i32"
21137- FROM jobs
21138- WHERE
21139- jobs.repo_path = ? OR ? IS NULL
21140- ORDER BY DATETIME(created_at) ASC
21141 diff --git a/queries/jobs_update.sql b/queries/jobs_update.sql
21142deleted file mode 100644
21143index ea7e521..0000000
21144--- a/queries/jobs_update.sql
21145+++ /dev/null
21146 @@ -1,6 +0,0 @@
21147- UPDATE jobs
21148- SET
21149- runtime = ?,
21150- success = ?
21151- WHERE
21152- jobs.id = ?
21153 diff --git a/queries/languages_add.sql b/queries/languages_add.sql
21154deleted file mode 100644
21155index 54551b3..0000000
21156--- a/queries/languages_add.sql
21157+++ /dev/null
21158 @@ -1,4 +0,0 @@
21159- INSERT INTO languages
21160- (git_hash, repo_path, language, loc)
21161- VALUES
21162- (?,?,?,?)
21163 diff --git a/queries/languages_delete.sql b/queries/languages_delete.sql
21164deleted file mode 100644
21165index 06c4349..0000000
21166--- a/queries/languages_delete.sql
21167+++ /dev/null
21168 @@ -1,3 +0,0 @@
21169- DELETE FROM languages
21170- WHERE
21171- repo_path = ?
21172 diff --git a/queries/languages_list.sql b/queries/languages_list.sql
21173deleted file mode 100644
21174index 608a719..0000000
21175--- a/queries/languages_list.sql
21176+++ /dev/null
21177 @@ -1,13 +0,0 @@
21178- SELECT
21179- language,
21180- loc,
21181- ROUND(
21182- CAST(loc AS REAL)/CAST(
21183- (SELECT SUM(loc) FROM languages
21184- WHERE repo_path = ? AND git_hash = ?) AS REAL)*100)
21185- AS "percentage: f64"
21186- FROM languages
21187- WHERE
21188- repo_path = ? AND git_hash = ?
21189- ORDER BY "percentage: f64" DESC
21190- LIMIT 5
21191 diff --git a/queries/latest_commit.sql b/queries/latest_commit.sql
21192deleted file mode 100644
21193index bd26eac..0000000
21194--- a/queries/latest_commit.sql
21195+++ /dev/null
21196 @@ -1,7 +0,0 @@
21197- SELECT
21198- git_hash
21199- FROM contributions
21200- WHERE
21201- repo_path = ?
21202- ORDER BY id DESC
21203- LIMIT 1
21204 diff --git a/scripts/bump_linguist_vendor.sh b/scripts/bump_linguist_vendor.sh
21205index 23b2488..5913872 100755
21206--- a/scripts/bump_linguist_vendor.sh
21207+++ b/scripts/bump_linguist_vendor.sh
21208 @@ -2,5 +2,5 @@
21209
21210 LANGUAGE_SOURCE="https://raw.githubusercontent.com/github-linguist/linguist/master/lib/linguist/languages.yml"
21211
21212- mkdir -p vendor
21213- curl "$LANGUAGE_SOURCE" --silent -o - | yq . > vendor/linguist.json
21214+ mkdir -p ayllu/vendor
21215+ curl "$LANGUAGE_SOURCE" --silent -o - | yq . > ayllu/vendor/linguist.json
21216 diff --git a/scripts/compile.sh b/scripts/compile.sh
21217index 088b896..859a4c1 100755
21218--- a/scripts/compile.sh
21219+++ b/scripts/compile.sh
21220 @@ -9,6 +9,8 @@ SCRIPT_FLAGS="-s scripts/compile_stylesheets.sh"
21221
21222 if [ -n "$COMPONENT" ] ; then
21223 PACKAGE_FLAG="--package=${COMPONENT}"
21224+ else
21225+ PACKAGE_FLAG="--package=ayllu"
21226 fi
21227
21228 cargo watch "${IGNORE_FLAGS}" "${SCRIPT_FLAGS}" "${EXEC_FLAGS}" "${PACKAGE_FLAG}"
21229 diff --git a/scripts/compile_stylesheets.sh b/scripts/compile_stylesheets.sh
21230index e176a76..aae7402 100755
21231--- a/scripts/compile_stylesheets.sh
21232+++ b/scripts/compile_stylesheets.sh
21233 @@ -1,6 +1,6 @@
21234 #!/bin/sh
21235
21236- files=$(find themes -maxdepth 2 -name 'theme.scss')
21237+ files=$(find ayllu/themes -maxdepth 2 -name 'theme.scss')
21238
21239 for theme_path in ${files} ; do
21240 sassc --style compressed -I node_modules \
21241 diff --git a/scripts/test.sh b/scripts/test.sh
21242index deef95d..2f3ab60 100755
21243--- a/scripts/test.sh
21244+++ b/scripts/test.sh
21245 @@ -8,6 +8,8 @@ EXEC_FLAGS="test"
21246
21247 if [ -n "$COMPONENT" ] ; then
21248 PACKAGE_FLAG="--package=${COMPONENT}"
21249+ else
21250+ PACKAGE_FLAG="--package=ayllu"
21251 fi
21252
21253 cargo watch "${IGNORE_FLAGS}" "${EXEC_FLAGS}" "${PACKAGE_FLAG}"
21254 diff --git a/scripts/watch.sh b/scripts/watch.sh
21255index cd8cc57..83e939d 100755
21256--- a/scripts/watch.sh
21257+++ b/scripts/watch.sh
21258 @@ -9,6 +9,8 @@ PACKAGE_FLAGS=""
21259
21260 if [ -n "$COMPONENT" ] ; then
21261 PACKAGE_FLAGS="--package=${COMPONENT}"
21262+ else
21263+ PACKAGE_FLAGS="--package=ayllu"
21264 fi
21265
21266 cargo watch "${IGNORE_FLAGS}" -s "scripts/compile_stylesheets.sh && cargo run $PACKAGE_FLAGS -- serve"
21267 diff --git a/scripts/watch_mail.sh b/scripts/watch_mail.sh
21268deleted file mode 100755
21269index 18f6b6d..0000000
21270--- a/scripts/watch_mail.sh
21271+++ /dev/null
21272 @@ -1,3 +0,0 @@
21273- #!/bin/sh
21274-
21275- cd ayllu-mail && cargo watch --watch src --why -x run
21276 diff --git a/scripts/watch_xmpp.sh b/scripts/watch_xmpp.sh
21277deleted file mode 100755
21278index b8f41cb..0000000
21279--- a/scripts/watch_xmpp.sh
21280+++ /dev/null
21281 @@ -1,3 +0,0 @@
21282- #!/bin/sh
21283-
21284- cd ayllu-xmpp && cargo watch --watch src --why -x run
21285 diff --git a/src/config.rs b/src/config.rs
21286deleted file mode 100644
21287index a00c5cf..0000000
21288--- a/src/config.rs
21289+++ /dev/null
21290 @@ -1,383 +0,0 @@
21291- use std::collections::HashMap;
21292- use std::error::Error;
21293- use std::fs::{metadata, read_dir};
21294- use std::num::NonZeroUsize;
21295- use std::thread::available_parallelism;
21296-
21297- use comrak::ComrakOptions;
21298- use url::Url;
21299-
21300- use ayllu_config::{data_dir, runtime_dir, Configurable};
21301-
21302- use serde::{Deserialize, Serialize};
21303-
21304- pub const EXAMPLE_CONFIG: &str = include_str!("../config.example.toml");
21305-
21306- // names that cannot be used in collections because they conflict with certain
21307- // internal urls within the service.
21308- const BANNED_COLLECTION_NAMES: &[&str] = &[
21309- "authors", "about", "api", "browse", "config", "rss", "static", "discuss",
21310- ];
21311-
21312- fn default_bool() -> bool {
21313- false
21314- }
21315-
21316- #[derive(Deserialize, Serialize, Clone, Debug)]
21317- pub struct Sites {
21318- pub enabled: bool,
21319- }
21320-
21321- #[derive(Deserialize, Serialize, Clone, Debug)]
21322- pub struct Highlighting {
21323- pub names: Vec<String>,
21324- }
21325-
21326- #[derive(Deserialize, Serialize, Clone, Debug)]
21327- pub struct Collection {
21328- pub name: String,
21329- pub description: Option<String>,
21330- pub path: String,
21331- pub hidden: Option<bool>,
21332- }
21333-
21334- #[derive(Deserialize, Serialize, Clone, Debug)]
21335- pub struct List {
21336- pub address: String,
21337- pub description: Option<String>,
21338- }
21339-
21340- #[derive(Deserialize, Serialize, Clone, Debug)]
21341- pub struct Web {
21342- #[serde(default = "Web::default_themes_path")]
21343- pub themes_path: String,
21344- #[serde(default = "Web::default_base_theme")]
21345- pub base_theme: String,
21346- #[serde(default = "Web::default_default_theme")]
21347- pub default_theme: String,
21348- #[serde(default = "Web::default_themes")]
21349- pub themes: Vec<String>,
21350- #[serde(default = "Web::default_unsafe_markdown")]
21351- pub unsafe_markdown: bool,
21352- }
21353-
21354- impl Web {
21355- fn default_themes_path() -> String {
21356- String::from("themes")
21357- }
21358-
21359- fn default_base_theme() -> String {
21360- String::from("default")
21361- }
21362-
21363- fn default_themes() -> Vec<String> {
21364- Vec::new()
21365- }
21366-
21367- fn default_default_theme() -> String {
21368- String::from("tokyonight")
21369- }
21370-
21371- fn default_unsafe_markdown() -> bool {
21372- true
21373- }
21374- }
21375-
21376- #[derive(Deserialize, Serialize, Clone, Debug)]
21377- pub struct Database {
21378- #[serde(default = "Database::path_default")]
21379- pub path: String,
21380- #[serde(default = "Database::migrate_default")]
21381- pub migrate: bool,
21382- }
21383-
21384- impl Database {
21385- fn path_default() -> String {
21386- let mut data_path = data_dir();
21387- data_path.push("state.db");
21388- String::from(data_path.to_str().unwrap())
21389- }
21390-
21391- fn migrate_default() -> bool {
21392- true
21393- }
21394- }
21395-
21396- #[derive(Deserialize, Serialize, Clone, Debug)]
21397- pub struct Http {
21398- #[serde(default = "Http::default_address")]
21399- pub address: String,
21400- }
21401-
21402- impl Http {
21403- fn default_address() -> String {
21404- String::from("localhost:8080")
21405- }
21406- }
21407-
21408- #[derive(Deserialize, Serialize, Clone, Debug)]
21409- pub struct TreeSitterParser {
21410- pub language: String,
21411- pub shared_object: String,
21412- pub highlight_query: Option<String>,
21413- pub locals_query: Option<String>,
21414- pub injections_query: Option<String>,
21415- }
21416-
21417- #[derive(Deserialize, Serialize, Clone, Debug)]
21418- pub struct TreeSitter {
21419- #[serde(default = "TreeSitter::default_base_path")]
21420- pub base_path: String,
21421- #[serde(default = "TreeSitter::default_queries_path")]
21422- pub queries_path: String,
21423- pub queries_extras_path: Option<String>,
21424- pub parsers: Option<Vec<TreeSitterParser>>,
21425- #[serde(default = "TreeSitter::default_keywords")]
21426- pub keywords: Vec<String>,
21427- }
21428-
21429- impl TreeSitter {
21430- fn default_base_path() -> String {
21431- String::from("/usr/lib")
21432- }
21433-
21434- fn default_queries_path() -> String {
21435- String::from("/usr/share/tree-sitter/queries")
21436- }
21437-
21438- fn default_keywords() -> Vec<String> {
21439- Vec::new()
21440- }
21441- }
21442-
21443- #[derive(Deserialize, Serialize, Clone, Debug)]
21444- pub struct Lfs {
21445- pub url_template: String,
21446- }
21447-
21448- #[derive(Deserialize, Serialize, Clone, Debug, Default)]
21449- pub struct XmppChannel {
21450- pub jid: String,
21451- pub description: String,
21452- }
21453-
21454- #[derive(Deserialize, Serialize, Clone, Debug, Default)]
21455- pub struct Xmpp {
21456- pub channels: Vec<XmppChannel>,
21457- #[serde(default = "Xmpp::default_socket_path")]
21458- pub socket_path: String,
21459- }
21460-
21461- impl Xmpp {
21462- fn default_socket_path() -> String {
21463- runtime_dir().to_str().unwrap().to_string() + "/ayllu-xmpp.sock"
21464- }
21465- }
21466-
21467- #[derive(Deserialize, Serialize, Clone, Debug, Default)]
21468- pub struct Mail {
21469- #[serde(default = "Mail::default_socket_path")]
21470- pub socket_path: String,
21471- }
21472-
21473- impl Mail {
21474- fn default_socket_path() -> String {
21475- runtime_dir().to_str().unwrap().to_string() + "/ayllu-mail.sock"
21476- }
21477- }
21478-
21479- #[derive(Deserialize, Serialize, Clone, Debug, Default)]
21480- pub struct Link {
21481- pub rel: String,
21482- pub href: Option<String>,
21483- pub template: Option<String>,
21484- pub mime_template: Option<String>,
21485- }
21486-
21487- #[derive(Deserialize, Serialize, Clone, Debug, Default)]
21488- pub struct Author {
21489- pub email: String,
21490- pub links: Vec<Link>,
21491- }
21492-
21493- #[derive(Deserialize, Serialize, Clone, Debug)]
21494- pub struct Language {
21495- pub name: String,
21496- pub extensions: Option<Vec<String>>,
21497- pub color: Option<String>,
21498- pub filenames: Option<Vec<String>>,
21499- }
21500-
21501- #[derive(Deserialize, Serialize, Clone, Debug, Default)]
21502- pub struct Languages {
21503- pub mappings: HashMap<String, String>,
21504- pub extras: Vec<Language>,
21505- }
21506-
21507- #[derive(Deserialize, Serialize, Clone, Debug)]
21508- pub struct Config {
21509- #[serde(default = "Config::default_site_name")]
21510- pub site_name: String,
21511- pub origin: String,
21512- pub domain: Option<String>,
21513- #[serde(default = "Config::default_worker_threads")]
21514- pub worker_threads: NonZeroUsize,
21515- #[serde(default = "Config::default_max_blocking_threads")]
21516- pub max_blocking_threads: NonZeroUsize,
21517- pub sysadmin: Option<String>,
21518- pub blurb: Option<String>,
21519- #[serde(default = "Config::default_robots_txt")]
21520- pub robots: String,
21521- #[serde(default = "default_bool")]
21522- pub subpath_mode: bool,
21523- #[serde(default = "Config::default_log_level")]
21524- pub log_level: String,
21525- pub git_clone_url: Option<String>,
21526- pub default_branch: Option<String>,
21527- pub rss_time_to_live: Option<i64>,
21528- pub web: Web,
21529- pub http: Http,
21530- #[serde(default = "Config::default_jobs_socket_path")]
21531- pub jobs_socket_path: String,
21532- #[serde(default = "Config::default_jobs_n_workers")]
21533- pub jobs_n_workers: NonZeroUsize,
21534- pub database: Database,
21535- #[serde(default = "Vec::new")]
21536- pub collections: Vec<Collection>,
21537- pub sites: Sites,
21538- #[serde(rename = "tree-sitter")]
21539- pub tree_sitter: Option<TreeSitter>,
21540- pub languages: Option<Languages>,
21541- pub lfs: Option<Lfs>,
21542- pub xmpp: Option<Xmpp>,
21543- pub mail: Option<Mail>,
21544- pub authors: Vec<Author>,
21545- }
21546-
21547- impl Configurable for Config {
21548- fn validate(&mut self) -> Result<(), Box<dyn Error>> {
21549- // load themes from the file system when the configuration is loaded, they
21550- // are made available in the configuration page of the UI.
21551- let mut themes: Vec<String> = Vec::new();
21552-
21553- match read_dir(&self.web.themes_path) {
21554- Ok(paths) => {
21555- for dir in paths.into_iter() {
21556- let dir_path = dir?.path();
21557- let filename = dir_path.file_name().unwrap();
21558- let theme_name = filename.to_str().unwrap();
21559- themes.push(theme_name.to_string());
21560- }
21561- }
21562- Err(e) => {
21563- return Err(format!(
21564- "failed to load themes from path: {} ({})",
21565- self.web.themes_path, e
21566- )
21567- .into())
21568- }
21569- }
21570-
21571- let parsed_url = Url::parse(&self.origin)?;
21572- self.domain = parsed_url.domain().map(|domain| domain.to_string());
21573-
21574- self.web.themes = themes;
21575- // verify collection names are all valid
21576- if let Some(collection) = self.collections.iter().find(|collection| {
21577- BANNED_COLLECTION_NAMES
21578- .iter()
21579- .any(|name| *name == collection.name.as_str())
21580- }) {
21581- return Err(format!("{} is a reserved name", collection.name)
21582- .as_str()
21583- .into());
21584- };
21585-
21586- // validate that all collection directories exist
21587- for collection in self.collections.iter() {
21588- if let Err(err) = metadata(collection.path.clone()) {
21589- return Err(
21590- format!("could not load collection {}:\n{}", collection.name, err).into(),
21591- );
21592- }
21593- }
21594- Ok(())
21595- }
21596- }
21597-
21598- impl Config {
21599- fn default_robots_txt() -> String {
21600- let robots = r#"
21601- User-agent: *
21602- Disallow: /*?*
21603- Disallow: /static/*
21604- Disallow: /assets/*
21605- Disallow: /config
21606- Disallow: /rss
21607- Disallow: /*/*/rss/*
21608- Disallow: /*/*/refs/archive/*
21609- Disallow: /*/*/blame/*
21610- Disallow: /*/*/log/*
21611- Disallow: /*/*/tree/*
21612- Disallow: /*/*/chart/*
21613- "#;
21614- String::from(robots.trim_start())
21615- }
21616-
21617- fn default_site_name() -> String {
21618- String::from("🌄 ayllu")
21619- }
21620-
21621- fn default_log_level() -> String {
21622- String::from("info")
21623- }
21624-
21625- fn default_worker_threads() -> NonZeroUsize {
21626- available_parallelism().unwrap()
21627- }
21628-
21629- fn default_max_blocking_threads() -> NonZeroUsize {
21630- NonZeroUsize::new(512).unwrap()
21631- }
21632-
21633- fn default_jobs_socket_path() -> String {
21634- runtime_dir().to_str().unwrap().to_string() + "/ayllu.sock"
21635- }
21636-
21637- fn default_jobs_n_workers() -> NonZeroUsize {
21638- available_parallelism().unwrap()
21639- }
21640-
21641- pub fn to_json(&self) -> String {
21642- serde_json::to_string(self).unwrap()
21643- }
21644-
21645- pub fn markdown_render_options(&self) -> ComrakOptions {
21646- let mut opts = ComrakOptions::default();
21647- opts.extension.description_lists = true;
21648- opts.extension.footnotes = true;
21649- opts.extension.strikethrough = true;
21650- opts.extension.superscript = true;
21651- opts.extension.table = true;
21652- opts.extension.tasklist = true;
21653-
21654- opts.parse.smart = true;
21655- opts.parse.relaxed_tasklist_matching = true;
21656- // allow raw html in the markdown documents
21657- opts.render.unsafe_ = self.web.unsafe_markdown;
21658- // not relevent since we paint ourselves I think
21659- // opts.render.github_pre_lang = false;
21660- opts
21661- }
21662- }
21663-
21664- #[cfg(test)]
21665- mod tests {
21666- use super::*;
21667- use ayllu_config::Reader;
21668-
21669- #[test]
21670- fn test_example_config() {
21671- Reader::<Config>::read(EXAMPLE_CONFIG).unwrap();
21672- }
21673- }
21674 diff --git a/src/database_ext.rs b/src/database_ext.rs
21675deleted file mode 100644
21676index c19e804..0000000
21677--- a/src/database_ext.rs
21678+++ /dev/null
21679 @@ -1,411 +0,0 @@
21680- use async_trait::async_trait;
21681- use futures::TryStreamExt;
21682- use serde::Serialize;
21683- use sqlx::Error;
21684- use time::{format_description, OffsetDateTime};
21685-
21686- use ayllu_database::Wrapper as Database;
21687-
21688- // job related db methods
21689-
21690- pub mod jobs {
21691-
21692- use super::*;
21693-
21694- #[derive(Clone)]
21695- pub struct Job {
21696- pub id: i64,
21697- pub created_at: OffsetDateTime,
21698- pub repo_path: Option<String>,
21699- pub kind: String,
21700- pub runtime: Option<u32>,
21701- pub success: Option<bool>,
21702- pub commits: i32,
21703- }
21704-
21705- #[async_trait]
21706- pub trait JobsExt {
21707- async fn create_job(&self, repo_path: Option<&str>, kind: &str) -> Result<i64, Error>;
21708- async fn update_job(&self, job_id: i64, runtime: i64, success: bool) -> Result<(), Error>;
21709- async fn list_jobs(&self, repo_path: Option<&str>) -> Result<Vec<Job>, Error>;
21710- async fn delete_job(&self, job_id: i64) -> Result<(), Error>;
21711- async fn purge(&self, repo_path: &str) -> Result<(), Error>;
21712-
21713- // used to track job progress
21714-
21715- async fn create_hash(
21716- &self,
21717- repo_path: &str,
21718- git_hash: &str,
21719- kind: &str,
21720- job_id: i64,
21721- ) -> Result<(), Error>;
21722-
21723- async fn read_hash(&self, repo_path: &str, kind: &str) -> Result<Option<String>, Error>;
21724- async fn latest_commit(&self, repo_path: &str) -> Result<Option<String>, Error>;
21725- }
21726-
21727- #[async_trait]
21728- impl JobsExt for Database {
21729- async fn create_job(&self, repo_path: Option<&str>, kind: &str) -> Result<i64, Error> {
21730- let created_at = OffsetDateTime::now_utc();
21731- let ret = sqlx::query_file!("queries/jobs_create.sql", created_at, repo_path, kind,)
21732- .fetch_one(&self.pool)
21733- .await?;
21734- Ok(ret.id)
21735- }
21736-
21737- async fn update_job(&self, job_id: i64, runtime: i64, success: bool) -> Result<(), Error> {
21738- sqlx::query_file!("queries/jobs_update.sql", runtime, success, job_id)
21739- .execute(&self.pool)
21740- .await?;
21741- Ok(())
21742- }
21743-
21744- async fn list_jobs(&self, repo_path: Option<&str>) -> Result<Vec<Job>, Error> {
21745- let jobs = sqlx::query_file_as!(Job, "queries/jobs_list.sql", repo_path, repo_path)
21746- .fetch_all(&self.pool)
21747- .await?;
21748- Ok(jobs)
21749- }
21750-
21751- async fn delete_job(&self, job_id: i64) -> Result<(), Error> {
21752- sqlx::query_file_as!(Job, "queries/jobs_delete.sql", job_id)
21753- .execute(&self.pool)
21754- .await?;
21755- Ok(())
21756- }
21757-
21758- async fn purge(&self, repo_path: &str) -> Result<(), Error> {
21759- sqlx::query_file!("queries/job_tracking_delete_by_repo.sql", repo_path)
21760- .execute(&self.pool)
21761- .await?;
21762- sqlx::query_file!("queries/jobs_delete.sql", repo_path)
21763- .execute(&self.pool)
21764- .await?;
21765- sqlx::query_file!("queries/languages_delete.sql", repo_path)
21766- .execute(&self.pool)
21767- .await?;
21768- sqlx::query_file!("queries/contribution_delete.sql", repo_path)
21769- .execute(&self.pool)
21770- .await?;
21771- sqlx::query_file!("queries/contributions_delete.sql", repo_path)
21772- .execute(&self.pool)
21773- .await?;
21774- Ok(())
21775- }
21776-
21777- async fn create_hash(
21778- &self,
21779- repo_path: &str,
21780- git_hash: &str,
21781- kind: &str,
21782- job_id: i64,
21783- ) -> Result<(), Error> {
21784- sqlx::query_file!(
21785- "queries/job_tracking_add.sql",
21786- repo_path,
21787- git_hash,
21788- kind,
21789- job_id
21790- )
21791- .execute(&self.pool)
21792- .await?;
21793- Ok(())
21794- }
21795-
21796- // lookup the latest hash for the job kind
21797- async fn read_hash(&self, repo_path: &str, kind: &str) -> Result<Option<String>, Error> {
21798- let ret = sqlx::query_file!("queries/job_tracking_read.sql", repo_path, kind,)
21799- .fetch_optional(&self.pool)
21800- .await?;
21801- Ok(ret.map(|record| record.git_hash))
21802- }
21803-
21804- async fn latest_commit(&self, repo_path: &str) -> Result<Option<String>, Error> {
21805- let ret = sqlx::query_file!("queries/latest_commit.sql", repo_path)
21806- .fetch_optional(&self.pool)
21807- .await?;
21808- Ok(ret.map(|record| record.git_hash))
21809- }
21810- }
21811- }
21812-
21813- // author and contributor helpers
21814-
21815- pub mod contributors {
21816- use super::*;
21817-
21818- #[derive(Clone, Serialize)]
21819- pub struct AuthorWithStats {
21820- pub username: String,
21821- pub email: String,
21822- pub count: Option<i64>,
21823- pub lines_added: Option<i64>,
21824- pub lines_removed: Option<i64>,
21825- pub percentage: Option<f64>,
21826- }
21827-
21828- #[async_trait]
21829- pub trait ContributorsExt {
21830- async fn upsert_author(&self, name: &str, email: &str) -> Result<i64, Error>;
21831- async fn authors_list(
21832- &self,
21833- repo_path: &str,
21834- git_hash: &str,
21835- ) -> Result<Vec<AuthorWithStats>, Error>;
21836- async fn contribution_add(
21837- &self,
21838- author_id: i64,
21839- repo_path: &str,
21840- git_hash: &str,
21841- timestamp: OffsetDateTime,
21842- lines_added: i64,
21843- lines_removed: i64,
21844- ) -> Result<(), Error>;
21845- async fn contributors_list(
21846- &self,
21847- repo_path: &str,
21848- git_hash: &str,
21849- ) -> Result<Vec<(String, String, i64)>, Error>;
21850- }
21851-
21852- #[async_trait]
21853- impl ContributorsExt for Database {
21854- async fn upsert_author(&self, name: &str, email: &str) -> Result<i64, Error> {
21855- let ret = sqlx::query_file!("queries/authors_upsert.sql", name, email)
21856- .fetch_one(&self.pool)
21857- .await?;
21858- Ok(ret.id)
21859- }
21860-
21861- async fn authors_list(
21862- &self,
21863- repo_path: &str,
21864- git_hash: &str,
21865- ) -> Result<Vec<AuthorWithStats>, Error> {
21866- sqlx::query_file_as!(
21867- AuthorWithStats,
21868- "queries/authors_list_project.sql",
21869- git_hash,
21870- repo_path,
21871- repo_path,
21872- repo_path,
21873- )
21874- .fetch_all(&self.pool)
21875- .await
21876- }
21877-
21878- async fn contribution_add(
21879- &self,
21880- author_id: i64,
21881- repo_path: &str,
21882- git_hash: &str,
21883- timestamp: OffsetDateTime,
21884- lines_added: i64,
21885- lines_removed: i64,
21886- ) -> Result<(), Error> {
21887- sqlx::query_file!(
21888- "queries/contributions_add.sql",
21889- author_id,
21890- git_hash,
21891- repo_path,
21892- timestamp,
21893- lines_added,
21894- lines_removed
21895- )
21896- .execute(&self.pool)
21897- .await?
21898- .last_insert_rowid();
21899- sqlx::query_file!(
21900- "queries/contribution_add.sql",
21901- author_id,
21902- repo_path,
21903- git_hash,
21904- repo_path,
21905- author_id,
21906- )
21907- .execute(&self.pool)
21908- .await?;
21909-
21910- Ok(())
21911- }
21912-
21913- async fn contributors_list(
21914- &self,
21915- repo_path: &str,
21916- git_hash: &str,
21917- ) -> Result<Vec<(String, String, i64)>, Error> {
21918- let mut ret: Vec<(String, String, i64)> = Vec::new();
21919- let mut rows = sqlx::query_file!(
21920- "queries/contributions_list.sql",
21921- repo_path,
21922- git_hash,
21923- repo_path,
21924- repo_path,
21925- git_hash,
21926- repo_path,
21927- )
21928- .fetch(&self.pool);
21929- while let Some(row) = rows.try_next().await? {
21930- ret.push((row.name, row.email, row.percentage.unwrap()))
21931- }
21932- Ok(ret)
21933- }
21934- }
21935- }
21936-
21937- // forge and repository level statistics
21938-
21939- pub mod stats {
21940-
21941- use super::*;
21942-
21943- #[allow(dead_code)]
21944- pub enum Aggregation {
21945- Day,
21946- Week,
21947- Month,
21948- Year,
21949- }
21950-
21951- #[async_trait]
21952- pub trait StatsExt {
21953- async fn contribution_buckets_for_repo(
21954- &self,
21955- repo_path: &str,
21956- git_hash: &str,
21957- period: Aggregation,
21958- offset: time::Duration,
21959- ) -> Result<Vec<(i64, i64, i64)>, Error>;
21960-
21961- async fn count_commits(&self, repo_path: &str, git_hash: &str) -> Result<i32, Error>;
21962- }
21963-
21964- #[async_trait]
21965- impl StatsExt for Database {
21966- async fn contribution_buckets_for_repo(
21967- &self,
21968- repo_path: &str,
21969- git_hash: &str,
21970- period: Aggregation,
21971- offset: time::Duration,
21972- ) -> Result<Vec<(i64, i64, i64)>, Error> {
21973- let start = OffsetDateTime::now_utc();
21974- let end = OffsetDateTime::now_utc().saturating_sub(offset);
21975- let start_ts = start.unix_timestamp();
21976- let end_ts = end.unix_timestamp();
21977- let seconds = match period {
21978- Aggregation::Day => 86400_i64,
21979- Aggregation::Week => 604800_i64,
21980- Aggregation::Month => 2592000_i64,
21981- Aggregation::Year => 31536000_i64,
21982- };
21983- let n_buckets = (start_ts - end_ts) / seconds;
21984- let mut buckets: Vec<(i64, i64, i64)> = vec![(0, 0, 0); n_buckets as usize];
21985- let strftime = match period {
21986- Aggregation::Day => "%d%m",
21987- Aggregation::Week => "%w",
21988- Aggregation::Month => "%m",
21989- Aggregation::Year => "%y",
21990- };
21991- let rows = sqlx::query_file!(
21992- "queries/contributions_bucket.sql",
21993- repo_path,
21994- git_hash,
21995- start,
21996- end,
21997- strftime
21998- )
21999- .fetch_all(&self.pool)
22000- .await?;
22001- for row in rows.iter() {
22002- let parsed = OffsetDateTime::parse(
22003- row.time.as_str(),
22004- &format_description::well_known::Rfc3339,
22005- )
22006- .unwrap();
22007- let diff = ((start - parsed).as_seconds_f64() / seconds as f64).floor();
22008- buckets[(diff - 1.0).abs() as usize] =
22009- (row.count, row.added, (row.removed - row.removed * 2));
22010- }
22011- // buckets.reverse();
22012- Ok(buckets)
22013- }
22014-
22015- async fn count_commits(&self, repo_path: &str, git_hash: &str) -> Result<i32, Error> {
22016- let result =
22017- sqlx::query_file!("queries/commits_count.sql", repo_path, git_hash, repo_path);
22018- let result = result.fetch_one(&self.pool).await?;
22019- Ok(result.count)
22020- }
22021- }
22022- }
22023-
22024- pub mod langauges {
22025-
22026- use super::*;
22027-
22028- #[async_trait]
22029- pub trait LanguagesExt {
22030- async fn language_create(
22031- &self,
22032- repo_path: &str,
22033- git_hash: &str,
22034- languages: Vec<(&str, i64)>,
22035- ) -> Result<(), Error>;
22036-
22037- async fn languages_list(
22038- &self,
22039- repo_path: &str,
22040- git_hash: &str,
22041- ) -> Result<Vec<(String, u8, u32)>, Error>;
22042- }
22043-
22044- #[async_trait]
22045- impl LanguagesExt for Database {
22046- async fn language_create(
22047- &self,
22048- repo_path: &str,
22049- git_hash: &str,
22050- languages: Vec<(&str, i64)>,
22051- ) -> Result<(), Error> {
22052- for language in languages {
22053- sqlx::query_file!(
22054- "queries/languages_add.sql",
22055- git_hash,
22056- repo_path,
22057- language.0,
22058- language.1,
22059- )
22060- .execute(&self.pool)
22061- .await?;
22062- }
22063- Ok(())
22064- }
22065-
22066- async fn languages_list(
22067- &self,
22068- repo_path: &str,
22069- git_hash: &str,
22070- ) -> Result<Vec<(String, u8, u32)>, Error> {
22071- let mut languages: Vec<(String, u8, u32)> = Vec::new();
22072- let mut rows = sqlx::query_file!(
22073- "queries/languages_list.sql",
22074- repo_path,
22075- git_hash,
22076- repo_path,
22077- git_hash
22078- )
22079- .fetch(&self.pool);
22080- while let Some(row) = rows.try_next().await? {
22081- languages.push((
22082- row.language.clone(),
22083- row.percentage.unwrap_or(0.0) as u8,
22084- row.loc as u32,
22085- ));
22086- }
22087- Ok(languages)
22088- }
22089- }
22090- }
22091 diff --git a/src/job_server/commands.rs b/src/job_server/commands.rs
22092deleted file mode 100644
22093index da7c748..0000000
22094--- a/src/job_server/commands.rs
22095+++ /dev/null
22096 @@ -1,134 +0,0 @@
22097- use std::fs::canonicalize;
22098- use std::io::Write;
22099- use std::path::{Path, PathBuf};
22100-
22101- use anyhow::{format_err, Result};
22102- use tabwriter::TabWriter;
22103- use time::Duration;
22104-
22105- use crate::config::Config;
22106- use crate::time::friendly;
22107- use ayllu_api::jobs_capnp::server::Client;
22108- use ayllu_api::models::{Job, Kind};
22109- use ayllu_git::git_dir;
22110- use ayllu_rpc::Client as RpcHelper;
22111-
22112- fn name(repo_path: Option<String>) -> String {
22113- match repo_path {
22114- Some(repo_path) => {
22115- // return just the $collection/$name part of the path if applicable
22116- let split: Vec<&str> = repo_path.split('/').collect();
22117- if split.len() > 2 {
22118- format!("{}/{}", split[split.len() - 2], split[split.len() - 1])
22119- } else {
22120- repo_path.to_string()
22121- }
22122- }
22123- None => String::new(),
22124- }
22125- }
22126-
22127- fn resolve_path(path: Option<&PathBuf>) -> Result<PathBuf> {
22128- let repo_path = path.map_or_else(
22129- || canonicalize(Path::new(".")),
22130- |path| canonicalize(path.as_path()),
22131- )?;
22132- if !git_dir(repo_path.as_path())? {
22133- Err(format_err!("this path does not look like a git repository"))
22134- } else {
22135- Ok(repo_path)
22136- }
22137- }
22138-
22139- /// Run a job against a specific repository path, if kind is not specified all
22140- /// jobs will be ran.
22141- pub async fn run_one(
22142- config: Config,
22143- repo_path: Option<PathBuf>,
22144- kind: Option<&str>,
22145- _max_depth: Option<usize>,
22146- ) -> Result<()> {
22147- let kinds: Vec<Kind> = kind.map_or(vec![Kind::Cloc, Kind::Contributors], |kind| {
22148- vec![kind.to_string().into()]
22149- });
22150- let repo_path = resolve_path(repo_path.as_ref())?;
22151- RpcHelper::new(&config.jobs_socket_path)
22152- .invoke(move |client: Client| async move {
22153- for kind in kinds {
22154- let mut request = client.submit_request();
22155- // FIXME: set_repo_path must take Path/PathBuf
22156- request
22157- .get()
22158- .set_repo_path(repo_path.display().to_string().as_str().into());
22159- request.get().set_kind(kind.into());
22160- request.send().promise.await?;
22161- }
22162- Ok(())
22163- })
22164- .await?;
22165- Ok(())
22166- }
22167-
22168- pub async fn list(config: Config, repo_path: Option<PathBuf>) -> Result<()> {
22169- let repo_path = repo_path
22170- .map(|repo_path| resolve_path(Some(repo_path).as_ref()))
22171- .transpose()?;
22172- let jobs = RpcHelper::new(&config.jobs_socket_path)
22173- .invoke(move |client: Client| async move {
22174- let mut request = client.list_request();
22175- if let Some(repo_path) = repo_path {
22176- // FIXME: set_repo_path must take Path/PathBuf
22177- request
22178- .get()
22179- .set_repo_path(repo_path.display().to_string().as_str().into());
22180- }
22181- let result = request.send().promise.await?;
22182- let jobs: Vec<Job> = result
22183- .get()?
22184- .get_jobs()?
22185- .iter()
22186- .map(|item| Job {
22187- id: item.get_id(),
22188- repo_path: String::from_utf8(item.get_repo_path().unwrap().0.to_vec()).unwrap(),
22189- created_at: item.get_created_at(),
22190- runtime: item.get_runtime(),
22191- kind: Kind::from(item.get_kind().unwrap()),
22192- commits: item.get_commits(),
22193- })
22194- .collect();
22195- Ok(jobs)
22196- })
22197- .await?;
22198- let mut tw = TabWriter::new(vec![]);
22199- writeln!(&mut tw, "name\ttime\truntime\tkind\tcommits")?;
22200- for job in jobs {
22201- writeln!(
22202- &mut tw,
22203- "{}\t{}\t{}\t{}\t{}",
22204- name(Some(job.repo_path)),
22205- friendly(job.created_at as u64),
22206- Duration::milliseconds(job.runtime),
22207- job.kind,
22208- job.commits,
22209- )?;
22210- }
22211- tw.flush()?;
22212- println!("{}", String::from_utf8(tw.into_inner().unwrap()).unwrap());
22213- Ok(())
22214- }
22215-
22216- pub async fn purge(config: Config, repo_path: Option<PathBuf>) -> Result<()> {
22217- let repo_path = resolve_path(repo_path.as_ref())?;
22218- RpcHelper::new(&config.jobs_socket_path)
22219- .invoke(move |client: Client| async move {
22220- let mut request = client.purge_request();
22221- // FIXME: set_repo_path must take Path/PathBuf
22222- request
22223- .get()
22224- .set_repo_path(repo_path.display().to_string().as_str().into());
22225- request.send().promise.await?;
22226- Ok(())
22227- })
22228- .await?;
22229- Ok(())
22230- }
22231 diff --git a/src/job_server/jobs/cloc.rs b/src/job_server/jobs/cloc.rs
22232deleted file mode 100644
22233index 73a1d64..0000000
22234--- a/src/job_server/jobs/cloc.rs
22235+++ /dev/null
22236 @@ -1,56 +0,0 @@
22237- use std::sync::Arc;
22238- use std::time::{Duration, SystemTime};
22239-
22240- use anyhow::Result;
22241- use tokei::{Config as TokeiConfig, Languages};
22242-
22243- use crate::database_ext::{jobs::JobsExt, langauges::LanguagesExt};
22244- use ayllu_api::models::{Job as JobModel, Kind};
22245- use ayllu_database::Wrapper as Database;
22246- use ayllu_git::Wrapper as Repository;
22247-
22248- pub struct Job {
22249- pub repository: Repository,
22250- pub database: Arc<Database>,
22251- pub model: JobModel,
22252- }
22253-
22254- impl Job {
22255- pub async fn invoke(&self, commits: Vec<String>) -> Result<Duration> {
22256- let start = SystemTime::now();
22257- let repo_path = self.repository.path();
22258- for (i, commit) in commits.iter().enumerate() {
22259- if i % 50 == 0 && i != 0 {
22260- log::info!("processed {}/{} commits", i, commits.len());
22261- }
22262- let mut stats: Vec<(&str, i64)> = Vec::new();
22263- let git_hash = commit.to_string();
22264- self.repository
22265- .with_worktree("/tmp", Some(git_hash.as_str()), |tree_path| {
22266- let config = TokeiConfig::default();
22267- let mut languages = Languages::new();
22268- let paths = &[tree_path];
22269- let exclude = &[];
22270- languages.get_statistics(paths, exclude, &config);
22271- for language in languages {
22272- let name = language.0.name();
22273- let loc = language.1.code;
22274- stats.push((name, loc.try_into().unwrap()));
22275- }
22276- Ok(())
22277- })?;
22278- self.database
22279- .language_create(&repo_path, git_hash.as_str(), stats)
22280- .await?;
22281- self.database
22282- .create_hash(
22283- &repo_path,
22284- git_hash.as_str(),
22285- Kind::Cloc.to_string().as_str(),
22286- self.model.id,
22287- )
22288- .await?;
22289- }
22290- Ok(start.elapsed().unwrap())
22291- }
22292- }
22293 diff --git a/src/job_server/jobs/contributors.rs b/src/job_server/jobs/contributors.rs
22294deleted file mode 100644
22295index cc5e9a4..0000000
22296--- a/src/job_server/jobs/contributors.rs
22297+++ /dev/null
22298 @@ -1,70 +0,0 @@
22299- use std::sync::Arc;
22300- use std::time::{Duration, SystemTime};
22301- use time::OffsetDateTime;
22302-
22303- use anyhow::Result;
22304-
22305- use crate::database_ext::{contributors::ContributorsExt, jobs::JobsExt};
22306- use ayllu_api::models::{Job as JobModel, Kind};
22307- use ayllu_database::Wrapper as Database;
22308- use ayllu_git::Wrapper as Repository;
22309-
22310- #[derive(Default)]
22311- struct Contribution {
22312- name: String,
22313- email: String,
22314- time: i64,
22315- lines_added: usize,
22316- lines_removed: usize,
22317- }
22318-
22319- pub struct Job {
22320- pub repository: Repository,
22321- pub database: Arc<Database>,
22322- pub model: JobModel,
22323- }
22324-
22325- impl Job {
22326- pub async fn invoke(&self, commits: Vec<String>) -> Result<Duration> {
22327- let start = SystemTime::now();
22328- let repo_path = self.repository.path();
22329- for (i, commit) in commits.iter().enumerate() {
22330- if i % 50 == 0 && i != 0 {
22331- log::info!("processed {}/{} commits", i, commits.len());
22332- }
22333- let mut contribution = Contribution::default();
22334- let git_hash = commit.to_string();
22335- let commit = self.repository.commit(Some(git_hash.clone()))?.unwrap();
22336- let stats = self.repository.stats(git_hash.clone().as_str())?;
22337- contribution.name = commit.author_name;
22338- contribution.email = commit.author_email;
22339- contribution.time = commit.epoch;
22340- contribution.lines_added = stats.insertions;
22341- contribution.lines_removed = stats.deletions;
22342- let timestamp = OffsetDateTime::from_unix_timestamp(contribution.time).unwrap();
22343- let author_id = self
22344- .database
22345- .upsert_author(contribution.name.as_str(), contribution.email.as_str())
22346- .await?;
22347- self.database
22348- .contribution_add(
22349- author_id,
22350- &repo_path,
22351- git_hash.as_str(),
22352- timestamp,
22353- contribution.lines_added as i64,
22354- contribution.lines_removed as i64,
22355- )
22356- .await?;
22357- self.database
22358- .create_hash(
22359- &repo_path,
22360- git_hash.as_str(),
22361- Kind::Contributors.to_string().as_str(),
22362- self.model.id,
22363- )
22364- .await?;
22365- }
22366- Ok(start.elapsed().unwrap())
22367- }
22368- }
22369 diff --git a/src/job_server/jobs/mod.rs b/src/job_server/jobs/mod.rs
22370deleted file mode 100644
22371index eb3ca38..0000000
22372--- a/src/job_server/jobs/mod.rs
22373+++ /dev/null
22374 @@ -1,37 +0,0 @@
22375- use std::sync::Arc;
22376- use std::time::Duration;
22377-
22378- use anyhow::Result;
22379-
22380- use ayllu_api::models::{Job, Kind};
22381- use ayllu_database::Wrapper as Database;
22382- use ayllu_git::Wrapper as Repository;
22383-
22384- mod cloc;
22385- mod contributors;
22386-
22387- pub async fn invoke(
22388- commits: Vec<String>,
22389- job: Job,
22390- repository: Repository,
22391- database: Arc<Database>,
22392- ) -> Result<Duration> {
22393- match job.kind {
22394- Kind::Contributors => {
22395- let job = contributors::Job {
22396- repository,
22397- database,
22398- model: job,
22399- };
22400- job.invoke(commits).await
22401- }
22402- Kind::Cloc => {
22403- let job = cloc::Job {
22404- repository,
22405- database,
22406- model: job,
22407- };
22408- job.invoke(commits).await
22409- }
22410- }
22411- }
22412 diff --git a/src/job_server/mod.rs b/src/job_server/mod.rs
22413deleted file mode 100644
22414index 0cceca5..0000000
22415--- a/src/job_server/mod.rs
22416+++ /dev/null
22417 @@ -1,7 +0,0 @@
22418- pub use commands::*;
22419- pub use server::serve;
22420-
22421- mod commands;
22422- mod jobs;
22423- mod runner;
22424- mod server;
22425 diff --git a/src/job_server/runner.rs b/src/job_server/runner.rs
22426deleted file mode 100644
22427index b5a7570..0000000
22428--- a/src/job_server/runner.rs
22429+++ /dev/null
22430 @@ -1,86 +0,0 @@
22431- use std::path::Path;
22432- use std::sync::Arc;
22433-
22434- use anyhow::Result;
22435- use tracing::log;
22436-
22437- use crate::database_ext::jobs::JobsExt;
22438- use crate::job_server::jobs::invoke;
22439- use ayllu_api::models::{Job, Kind};
22440- use ayllu_database::Wrapper as Database;
22441- use ayllu_git::Wrapper as Repository;
22442-
22443- pub struct Runner {
22444- max_depth: Option<usize>,
22445- database: Arc<Database>,
22446- job: Job,
22447- }
22448-
22449- impl Runner {
22450- pub fn new(job: Job, database: Arc<Database>) -> Self {
22451- Runner {
22452- max_depth: None,
22453- database,
22454- job,
22455- }
22456- }
22457-
22458- async fn commits(&self, kind: Kind, repository: &Repository) -> Result<Vec<String>> {
22459- let repo_path = repository.path();
22460- let latest_db_hash = self
22461- .database
22462- .read_hash(&repo_path, kind.to_string().as_str())
22463- .await?;
22464- let latest_git_hash = repository.latest_hash()?;
22465- if latest_git_hash.is_none() {
22466- // empty repository
22467- return Ok(Vec::new());
22468- };
22469- let latest_git_hash = latest_git_hash.unwrap();
22470- let commits: Vec<String> = match latest_db_hash {
22471- Some(db_hash) => {
22472- if latest_git_hash == db_hash {
22473- // nothing to do
22474- return Ok(Vec::new());
22475- }
22476-
22477- repository.commits(Some(db_hash.as_str()), self.max_depth)?
22478- }
22479- None => repository.commits(None, self.max_depth)?,
22480- };
22481- Ok(commits)
22482- }
22483-
22484- pub async fn run(&self) -> Result<()> {
22485- log::info!("running {}", self.job.kind.to_string());
22486- // let name = self.repository.as_ref().map(|r| r.path());
22487- let job_id = self
22488- .database
22489- .create_job(
22490- Some(self.job.repo_path.as_str()),
22491- self.job.kind.to_string().as_str(),
22492- )
22493- .await?;
22494-
22495- let mut job = self.job.clone();
22496- job.id = job_id;
22497-
22498- let repository = Repository::new(Path::new(&job.repo_path))?;
22499- let commits = self.commits(job.kind.clone(), &repository).await?;
22500- log::info!("processing {} commits", commits.len());
22501- if commits.is_empty() {
22502- // nothing to do
22503- log::debug!("deleting unused job");
22504- self.database.delete_job(job_id).await?;
22505- log::debug!("deleted job");
22506- return Ok(());
22507- }
22508-
22509- let runtime = invoke(commits, job, repository, self.database.clone()).await?;
22510-
22511- let duration = runtime.as_millis() as i64;
22512- log::info!("runtime {}ms", duration);
22513- self.database.update_job(job_id, duration, true).await?;
22514- Ok(())
22515- }
22516- }
22517 diff --git a/src/job_server/server.rs b/src/job_server/server.rs
22518deleted file mode 100644
22519index c66afcb..0000000
22520--- a/src/job_server/server.rs
22521+++ /dev/null
22522 @@ -1,112 +0,0 @@
22523- use std::error::Error as StdError;
22524- use std::sync::Arc;
22525-
22526- use capnp::{capability::Promise, Error};
22527- use capnp_rpc::pry;
22528-
22529- use crate::config::Config;
22530- use crate::database_ext::jobs::JobsExt;
22531- use crate::job_server::runner::Runner;
22532- use ayllu_api::jobs_capnp::server::{
22533- Client, ListParams, ListResults, PurgeParams, PurgeResults, Server, SubmitParams, SubmitResults,
22534- };
22535- use ayllu_api::models::{Job, Kind};
22536- use ayllu_database::{Wrapper as Database, Builder};
22537- use ayllu_rpc::Server as CapnpServerHelper;
22538-
22539-
22540- #[derive(Clone)]
22541- pub struct ServerImpl {
22542- db: Arc<Database>,
22543- }
22544-
22545- impl Server for ServerImpl {
22546- fn submit(
22547- &mut self,
22548- params: SubmitParams,
22549- _: SubmitResults,
22550- ) -> ::capnp::capability::Promise<(), ::capnp::Error> {
22551- let repo_path = pry!(pry!(pry!(params.get()).get_repo_path()).to_string());
22552- let kind = match pry!(pry!(params.get()).get_kind()) {
22553- ayllu_api::jobs_capnp::Kind::Contributors => String::from("contributors"),
22554- ayllu_api::jobs_capnp::Kind::Cloc => String::from("cloc"),
22555- };
22556- let db = self.db.clone();
22557- Promise::from_future(async move {
22558- let job = Job {
22559- id: 0,
22560- repo_path: repo_path.clone(),
22561- created_at: 0,
22562- runtime: 0,
22563- kind: Kind::from(kind),
22564- commits: 0,
22565- };
22566- match Runner::new(job, db).run().await {
22567- Ok(()) => Ok(()),
22568- Err(e) => Err(Error {
22569- kind: capnp::ErrorKind::Failed,
22570- extra: e.to_string(),
22571- }),
22572- }
22573- })
22574- }
22575-
22576- fn list(
22577- &mut self,
22578- _: ListParams,
22579- mut result: ListResults,
22580- ) -> ::capnp::capability::Promise<(), ::capnp::Error> {
22581- let db = self.db.clone();
22582- Promise::from_future(async move {
22583- match db.list_jobs(None).await {
22584- Ok(jobs) => {
22585- let mut jobs_result = result.get().init_jobs(jobs.len() as u32);
22586- for (i, job) in jobs.iter().enumerate() {
22587- let mut job_result = jobs_result.reborrow().get(i as u32);
22588- job_result.set_created_at(job.created_at.unix_timestamp());
22589- if job.repo_path.is_some() {
22590- let repo_path = job.repo_path.as_ref().unwrap();
22591- job_result.set_repo_path(repo_path.as_str().into())
22592- }
22593- if job.runtime.is_some() {
22594- job_result.set_runtime(job.runtime.unwrap() as i64)
22595- }
22596- job_result.set_commits(job.commits as i64);
22597- job_result.set_kind(Kind::from(job.kind.clone()).into());
22598- job_result.set_success(job.success.is_some_and(|success| success))
22599- }
22600- Ok(())
22601- }
22602- Err(e) => Err(Error {
22603- kind: capnp::ErrorKind::Failed,
22604- extra: e.to_string(),
22605- }),
22606- }
22607- })
22608- }
22609-
22610- fn purge(
22611- &mut self,
22612- params: PurgeParams,
22613- _: PurgeResults,
22614- ) -> ::capnp::capability::Promise<(), ::capnp::Error> {
22615- let repo_path = pry!(pry!(pry!(params.get()).get_repo_path()).to_string());
22616- let db = self.db.clone();
22617- Promise::from_future(async move {
22618- match db.purge(&repo_path).await {
22619- Ok(_) => Ok(()),
22620- Err(e) => Err(Error {
22621- kind: capnp::ErrorKind::Failed,
22622- extra: e.to_string(),
22623- }),
22624- }
22625- })
22626- }
22627- }
22628-
22629- pub async fn serve(cfg: &Config) -> Result<(), Box<dyn StdError>> {
22630- let db = Builder::default().url(&cfg.database.path).build().await?;
22631- let server = ServerImpl { db: Arc::new(db) };
22632- let runtime = CapnpServerHelper::<Client, ServerImpl>::new(&cfg.jobs_socket_path, server);
22633- runtime.serve().await.map_err(|e| e.into())
22634- }
22635 diff --git a/src/languages.rs b/src/languages.rs
22636deleted file mode 100644
22637index 9b236c8..0000000
22638--- a/src/languages.rs
22639+++ /dev/null
22640 @@ -1,433 +0,0 @@
22641- use std::cmp::{Eq, PartialEq};
22642- use std::collections::HashMap;
22643- use std::hash::Hash;
22644- use std::io::Read;
22645- use std::path::Path;
22646- use std::sync::RwLock;
22647-
22648- use lazy_static::lazy_static;
22649- use serde::{Deserialize, Serialize};
22650-
22651- use crate::config::Language as ConfigLanguage;
22652-
22653- /// Hint is the case-insensitive name of a programming language, .e.g
22654- /// OCaml, ocaml, Rust, rust, RuSt
22655- #[derive(Debug, Clone, Serialize, Deserialize)]
22656- pub struct Hint(pub String);
22657-
22658- impl Eq for Hint {}
22659-
22660- impl PartialEq for Hint {
22661- fn eq(&self, other: &Self) -> bool {
22662- self.0.to_uppercase() == other.0.to_uppercase()
22663- }
22664- }
22665-
22666- impl Hash for Hint {
22667- fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
22668- state.write(self.0.to_uppercase().as_bytes());
22669- }
22670- }
22671-
22672- impl From<String> for Hint {
22673- fn from(value: String) -> Self {
22674- Hint(value)
22675- }
22676- }
22677-
22678- impl From<&String> for Hint {
22679- fn from(value: &String) -> Self {
22680- Hint(value.to_string())
22681- }
22682- }
22683-
22684- impl From<&str> for Hint {
22685- fn from(value: &str) -> Self {
22686- Hint(value.to_string())
22687- }
22688- }
22689-
22690- #[derive(Deserialize, Clone, Debug)]
22691- struct Language {
22692- #[serde(default = "Language::black")]
22693- pub color: String,
22694- pub extensions: Option<Vec<String>>,
22695- pub filenames: Option<Vec<String>>,
22696- pub aliases: Option<Vec<String>>,
22697- #[allow(dead_code)]
22698- #[serde(rename = "codemirror_mime_type")]
22699- pub mime_type: Option<String>,
22700- }
22701-
22702- impl Language {
22703- fn black() -> String {
22704- String::from("#000000")
22705- }
22706- }
22707-
22708- pub struct Languages {
22709- language_to_color: RwLock<HashMap<Hint, String>>,
22710- extension_to_language: RwLock<HashMap<String, Hint>>,
22711- filename_to_language: RwLock<HashMap<String, Hint>>,
22712- alias_to_language: RwLock<HashMap<String, Hint>>,
22713- mappings: RwLock<HashMap<Hint, Hint>>,
22714- }
22715-
22716- impl Languages {
22717- fn new() -> Self {
22718- let linguist_json = include_str!("../vendor/linguist.json");
22719- let by_name: HashMap<String, Language> =
22720- serde_json::from_str(linguist_json).expect("invalid languages file?");
22721- let mut language_to_color: HashMap<Hint, String> = HashMap::new();
22722- let mut extension_to_language: HashMap<String, Hint> = HashMap::new();
22723- let mut filename_to_language: HashMap<String, Hint> = HashMap::new();
22724- let mut alias_to_language: HashMap<String, Hint> = HashMap::new();
22725- for (name, language) in by_name.iter() {
22726- language_to_color.insert(Hint(name.to_string()), language.color.clone());
22727- match &language.extensions {
22728- Some(extensions) => {
22729- for extension in extensions.iter() {
22730- let extension = extension.trim_start_matches('.');
22731- extension_to_language.insert(extension.to_string(), name.into());
22732- }
22733- }
22734- None => {}
22735- };
22736- match &language.filenames {
22737- Some(filenames) => {
22738- for filename in filenames {
22739- filename_to_language.insert(filename.to_string(), name.into());
22740- }
22741- }
22742- None => {}
22743- }
22744- match &language.aliases {
22745- Some(aliases) => {
22746- for alias in aliases {
22747- alias_to_language.insert(alias.to_string(), name.into());
22748- }
22749- }
22750- None => {}
22751- }
22752- }
22753- Languages {
22754- language_to_color: RwLock::new(language_to_color),
22755- extension_to_language: RwLock::new(extension_to_language),
22756- filename_to_language: RwLock::new(filename_to_language),
22757- alias_to_language: RwLock::new(alias_to_language),
22758- mappings: RwLock::new(HashMap::new()),
22759- }
22760- }
22761-
22762- pub fn set_mappings(&self, mappings: HashMap<Hint, Hint>) {
22763- *self.mappings.write().unwrap() = mappings.clone();
22764- }
22765-
22766- pub fn set_languages(&self, languages: Vec<ConfigLanguage>) {
22767- for language in languages {
22768- match &language.color {
22769- Some(color) => {
22770- self.language_to_color
22771- .write()
22772- .unwrap()
22773- .insert(Hint(language.name.clone()), color.to_string());
22774- }
22775- None => {}
22776- };
22777- match &language.extensions {
22778- Some(extensions) => {
22779- for extension in extensions {
22780- let extension = extension.trim_start_matches('.');
22781- let mut extensions = self.extension_to_language.write().unwrap();
22782- extensions.insert(extension.to_string(), language.name.clone().into());
22783- }
22784- }
22785- None => {}
22786- }
22787- match &language.filenames {
22788- Some(filenames) => {
22789- for filename in filenames {
22790- let mut filenames = self.filename_to_language.write().unwrap();
22791- filenames.insert(filename.to_string(), language.name.clone().into());
22792- }
22793- }
22794- None => {}
22795- }
22796- }
22797- }
22798-
22799- pub fn color_from_language(&self, language: &Hint) -> String {
22800- self.language_to_color
22801- .read()
22802- .unwrap()
22803- .get(language)
22804- .cloned()
22805- .unwrap_or(String::from("#000000"))
22806- }
22807-
22808- fn guess_str(&self, code: &str) -> Option<Hint> {
22809- let mut buf = [0; 512];
22810- let mut b = code.as_bytes();
22811- let _ = b.read(&mut buf);
22812- let chunk = String::from_utf8(buf.to_vec()).unwrap();
22813- for line in chunk.split('\n') {
22814- if line.starts_with("#!") {
22815- let mut split = line.split(' ');
22816- let first = split.nth(0).unwrap();
22817- let path = Path::new(first.trim_start_matches("#!"));
22818- if let Some(name) = path.file_name() {
22819- match name.to_str().unwrap() {
22820- "env" => {
22821- // special case for env shebang where the input
22822- // uses an -S arg, see man env.1
22823- // example:
22824- // #!/usr/bin/env -C /tmp -S perl -w -T
22825- if line.contains("-S") || line.contains("--split-line") {
22826- loop {
22827- match split.next() {
22828- Some("-S") | Some("--split-line") => {
22829- return split.next().map(|arg| Hint(arg.to_string()))
22830- }
22831- Some(_) => {} // some other arg
22832- None => return None,
22833- }
22834- }
22835- } else {
22836- match split.nth(0) {
22837- // TODO: Not always correct here, just guessing that the
22838- // executable maps to the language name e.g. /usr/bin/python
22839- Some(program) => return Some(Hint(program.to_string())),
22840- None => return None,
22841- }
22842- }
22843- }
22844- name => return Some(Hint(name.to_string())),
22845- }
22846- }
22847- }
22848- }
22849- None
22850- }
22851-
22852- fn guess_filename(&self, filename: &str) -> Option<Hint> {
22853- match self.filename_to_language.read().unwrap().get(filename) {
22854- Some(hint) => Some(hint.clone()),
22855- None => {
22856- let extension = Path::new(filename)
22857- .extension()
22858- .map(|extension| extension.to_str().unwrap().trim_start_matches('.'));
22859- match extension {
22860- Some(ext) => self.extension_to_language.read().unwrap().get(ext).cloned(),
22861- None => None,
22862- }
22863- }
22864- }
22865- }
22866-
22867- fn guess_alias(&self, alias: &str) -> Option<Hint> {
22868- self.alias_to_language.read().unwrap().get(alias).cloned()
22869- }
22870-
22871- fn resolve(&self, hint: Option<&Hint>) -> Option<Hint> {
22872- match hint {
22873- Some(hint) => match self.mappings.read().unwrap().get(hint) {
22874- Some(other) => Some(other.clone()),
22875- None => Some(hint.clone()),
22876- },
22877- None => None,
22878- }
22879- }
22880-
22881- pub fn guess(&self, code: &str, alias: Option<&str>, filename: Option<&str>) -> Option<Hint> {
22882- let hint = match (alias, filename) {
22883- (Some(alias), Some(filename)) => match self.guess_alias(alias) {
22884- Some(hint) => Some(hint),
22885- None => match self.guess_filename(filename) {
22886- Some(hint) => Some(hint),
22887- None => self.guess_str(code),
22888- },
22889- },
22890- (Some(alias), None) => self.guess_alias(alias),
22891- (None, Some(filename)) => match self.guess_filename(filename) {
22892- Some(hint) => Some(hint),
22893- None => self.guess_str(code),
22894- },
22895- (None, None) => self.guess_str(code),
22896- };
22897- self.resolve(hint.as_ref())
22898- }
22899- }
22900-
22901- lazy_static! {
22902- pub static ref LANGUAGE_TABLE: Languages = Languages::new();
22903- }
22904-
22905- #[cfg(test)]
22906- mod tests {
22907-
22908- use super::*;
22909-
22910- const PYTHON_SCRIPT: &str = r"
22911- #!/usr/bin/env python
22912-
22913- def fuu()
22914- print('bar')
22915-
22916- if __name__ == '__main__'
22917- fuu()
22918- fuu()
22919- ";
22920-
22921- const PYTHON_SCRIPT_2: &str = r"
22922- # some random text here with some comments
22923- # woooo!
22924-
22925- #!/usr/bin/python
22926-
22927- def fuu()
22928- print('bar')
22929-
22930- if __name__ == '__main__'
22931- fuu()
22932- fuu()
22933- ";
22934-
22935- const PERL_SCRIPT: &str = r"
22936- #!/usr/bin/perl
22937-
22938- use strict;
22939- use warnings;
22940-
22941- print('Hello World\n');
22942- ";
22943-
22944- // example from env.1
22945- const PERL_SCRIPT_2: &str = r"
22946- #!/usr/bin/env -S perl -w -T
22947-
22948- use strict;
22949- use warnings;
22950-
22951- print('Hello World\n');
22952- ";
22953-
22954- #[test]
22955- fn test_extension_lookup() {
22956- let table = Languages::new();
22957- let result = table.guess("", None, Some("hello.py"));
22958- assert!(result.is_some_and(|name| name == "Python".into()));
22959- }
22960-
22961- #[test]
22962- fn test_extension_mapping_override() {
22963- let table = Languages::new();
22964- table.set_mappings(HashMap::from_iter(vec![(
22965- Hint("Standard ML".to_string()),
22966- Hint("OCaml".to_string()),
22967- )]));
22968- let result = table.guess("", None, Some("main.ml"));
22969- assert!(result.is_some_and(|name| name == "OCaml".into()));
22970- let result = table.guess("", None, Some("main.mli"));
22971- assert!(result.is_some_and(|name| name == "OCaml".into()));
22972- }
22973-
22974- #[test]
22975- fn test_extension_mapping_override_2() {
22976- let table = Languages::new();
22977- table.set_mappings(HashMap::from_iter(vec![(
22978- Hint("Shell".to_string()),
22979- Hint("Bash".to_string()),
22980- )]));
22981- let result = table.guess("", None, Some(".bashrc"));
22982- assert!(result.is_some_and(|name| name == "Bash".into()));
22983- }
22984-
22985- #[test]
22986- fn test_alias() {
22987- let table = Languages::new();
22988- let result = table.guess("", Some("sh"), None);
22989- assert!(result.is_some_and(|name| name == "Shell".into()))
22990- }
22991-
22992- #[test]
22993- fn test_shebang_python_1() {
22994- let table = Languages::new();
22995- let result = table.guess(PYTHON_SCRIPT, None, None);
22996- assert!(result.is_some_and(|name| name == "Python".into()));
22997- }
22998-
22999- #[test]
23000- fn test_shebang_python_2() {
23001- let table = Languages::new();
23002- let result = table.guess(PYTHON_SCRIPT_2, None, None);
23003- assert!(result.is_some_and(|name| name == "Python".into()));
23004- }
23005-
23006- #[test]
23007- fn test_shebang_perl() {
23008- let table = Languages::new();
23009- let result = table.guess(PERL_SCRIPT, None, None);
23010- assert!(result.is_some_and(|name| name == "Perl".into()));
23011- }
23012-
23013- #[test]
23014- fn test_shebang_perl_2() {
23015- let table = Languages::new();
23016- let result = table.guess(PERL_SCRIPT_2, None, None);
23017- assert!(result.is_some_and(|name| name == "Perl".into()));
23018- }
23019-
23020- #[test]
23021- fn test_color_lookup() {
23022- let table = Languages::new();
23023- let color = table.color_from_language(&"Go".into());
23024- assert!(color == "#00ADD8");
23025- }
23026-
23027- #[test]
23028- fn test_color_lookup_with_override() {
23029- let table = Languages::new();
23030- table.set_languages(vec![ConfigLanguage {
23031- name: "Go".to_string(),
23032- extensions: None,
23033- color: Some("#FF1493".to_string()),
23034- filenames: None,
23035- }]);
23036- let color = table.color_from_language(&"Go".into());
23037- assert!(color == "#FF1493");
23038- }
23039-
23040- #[test]
23041- fn test_custom_language() {
23042- let table = Languages::new();
23043- table.set_languages(vec![ConfigLanguage {
23044- name: "FuuBar".to_string(),
23045- extensions: Some(vec![".fuu".to_string(), ".bar".to_string()]),
23046- color: Some("#FFFFFF".to_string()),
23047- filenames: Some(vec![".fuuscript".to_string()]),
23048- }]);
23049- assert!(table
23050- .guess("", None, Some("main.fuu"))
23051- .is_some_and(|hint| hint == Hint("FuuBar".to_string())));
23052- assert!(table
23053- .guess("", None, Some(".fuuscript"))
23054- .is_some_and(|hint| hint == Hint("FuuBar".to_string())))
23055- }
23056-
23057- #[test]
23058- fn test_filename() {
23059- let table = Languages::new();
23060- assert!(table
23061- .guess("", None, Some(".zshrc"))
23062- .is_some_and(|hint| hint == Hint("Shell".to_string())));
23063- assert!(table
23064- .guess("", None, Some(".profile"))
23065- .is_some_and(|hint| hint == Hint("Shell".to_string())));
23066- assert!(table
23067- .guess("", None, Some("PKGBUILD"))
23068- .is_some_and(|hint| hint == Hint("Shell".to_string())));
23069- }
23070-
23071- #[test]
23072- fn test_mime_lookup() {}
23073- }
23074 diff --git a/src/license.rs b/src/license.rs
23075deleted file mode 100644
23076index 34a6444..0000000
23077--- a/src/license.rs
23078+++ /dev/null
23079 @@ -1,29 +0,0 @@
23080- use std::path::Path;
23081-
23082- use ayllu_git::{Error, Wrapper};
23083-
23084- // TODO: support an array of regexes
23085- pub static LICENSES: &[(&str, &str)] = &[
23086- ("LGPL-2.0", "GNU LIBRARY GENERAL PUBLIC LICENSE"),
23087- ("AGPL-3.0", "GNU AFFERO GENERAL PUBLIC LICENSE"),
23088- ("MIT", "MIT License"),
23089- ("MPL-2.0", "Mozilla Public License Version 2.0"),
23090- ];
23091-
23092- static LICENSE_PATHS: &[&str] = &["LICENSE", "COPYING", "LICENSE.md"];
23093-
23094- // TODO: super naive license detection
23095- // see https://github.com/OpenSourceOrg/licenses
23096- pub fn detect(repository: &Wrapper, commit: Option<String>) -> Result<Option<String>, Error> {
23097- for path in LICENSE_PATHS {
23098- let path = Path::new(path);
23099- if let Some(content) = repository.read_string(path, commit.clone())? {
23100- for license in LICENSES {
23101- if content.contains(license.1) {
23102- return Ok(Some(license.0.to_string()));
23103- }
23104- }
23105- }
23106- }
23107- Ok(None)
23108- }
23109 diff --git a/src/main.rs b/src/main.rs
23110deleted file mode 100644
23111index 0131ad1..0000000
23112--- a/src/main.rs
23113+++ /dev/null
23114 @@ -1,197 +0,0 @@
23115- use std::error::Error;
23116- use std::io::stderr;
23117- use std::path::{Path, PathBuf};
23118- use std::thread::Builder as ThreadBuilder;
23119-
23120- use clap::{Args, Parser, Subcommand};
23121- use tokio::runtime::Builder;
23122-
23123- use ayllu_config::Reader;
23124- use ayllu_database::migrate;
23125-
23126- mod job_server;
23127- mod web2;
23128-
23129- mod config;
23130- mod database_ext;
23131- mod languages;
23132- mod license;
23133- mod time;
23134-
23135- #[derive(Parser, Debug)]
23136- #[clap(version, about, long_about = "hyper performant code forge")]
23137- struct Arguments {
23138- #[clap(short, long, value_name = "FILE")]
23139- config: Option<PathBuf>,
23140- // FIXME: Parse to Level enum directly
23141- /// logging level [ERROR,WARN,INFO,DEBUG,TRACE]
23142- #[clap(short, long)]
23143- level: Option<String>,
23144- #[clap(subcommand)]
23145- subcommand: Command,
23146- }
23147-
23148- #[derive(Args, Debug)]
23149- struct JobArguments {
23150- #[clap(short, long, default_value = "false")]
23151- short: bool,
23152- /// Path to a git repository
23153- path: Option<PathBuf>,
23154- /// Job kind specifier.
23155- #[clap(short, long)]
23156- kind: Option<String>,
23157- }
23158-
23159- #[derive(Subcommand, Debug)]
23160- enum ConfigCommand {
23161- /// Display the current configuration.
23162- Display {
23163- #[clap(long, default_value = "false")]
23164- as_json: bool,
23165- },
23166- /// Generate a sample configuration file.
23167- Generate,
23168- }
23169-
23170- #[derive(Subcommand, Debug)]
23171- enum Command {
23172- /// Configuration options.
23173- #[clap(subcommand)]
23174- Config(ConfigCommand),
23175- /// Perform database migration.
23176- Migrate,
23177- /// Launch the main web server.
23178- Serve,
23179- /// Offline indexing and maintenance.
23180- #[clap(subcommand)]
23181- Jobs(JobsCommand),
23182- }
23183-
23184- #[derive(Parser, Debug)]
23185- enum JobsCommand {
23186- /// View the status of jobs.
23187- List(JobArguments),
23188- /// Run one or more job.
23189- Run {
23190- /// run all jobs across all repositories
23191- #[clap(short, long, default_value = "false")]
23192- all: bool,
23193- #[clap(flatten)]
23194- rest: JobArguments,
23195- },
23196- /// purge data from the database.
23197- Purge(JobArguments),
23198- }
23199-
23200- fn init_config(
23201- path: Option<&Path>,
23202- level: Option<&str>,
23203- ) -> Result<config::Config, ayllu_config::Error> {
23204- let cfg: config::Config = Reader::load(path)?;
23205- tracing_subscriber::fmt()
23206- .compact()
23207- .with_line_number(true)
23208- .with_level(true)
23209- .with_writer(stderr)
23210- // tokei will spam the console unless this is set
23211- .with_env_filter(format!(
23212- "{},tokei::language::language_type=error",
23213- level.unwrap_or(&cfg.log_level)
23214- ))
23215- .init();
23216- log::info!("Logger initialized.");
23217- Ok(cfg)
23218- }
23219-
23220- fn main() -> Result<(), Box<dyn Error>> {
23221- let args: Arguments = Arguments::parse();
23222- match args.subcommand {
23223- Command::Config(subcommand) => match subcommand {
23224- ConfigCommand::Display { as_json: true } => {
23225- println!(
23226- "{}",
23227- init_config(args.config.as_deref(), args.level.as_deref())?.to_json()
23228- );
23229- Ok(())
23230- }
23231- ConfigCommand::Display { as_json: false } => {
23232- println!(
23233- "{:#?}",
23234- init_config(args.config.as_deref(), args.level.as_deref())?
23235- );
23236- Ok(())
23237- }
23238- ConfigCommand::Generate => {
23239- println!("{}", config::EXAMPLE_CONFIG);
23240- Ok(())
23241- }
23242- },
23243- Command::Migrate => {
23244- let cfg = init_config(args.config.as_deref(), args.level.as_deref())?;
23245- let runtime = Builder::new_current_thread().enable_all().build().unwrap();
23246- runtime.block_on(migrate(&cfg.database.path, "./migrations"))?;
23247- Ok(())
23248- }
23249- Command::Serve => {
23250- let cfg = init_config(args.config.as_deref(), args.level.as_deref())?;
23251- // launch the job server in a separate thread
23252- let job_config = cfg.clone();
23253- ThreadBuilder::new()
23254- .name(String::from("ayllu-jobs-runtime"))
23255- .spawn(move || {
23256- let runtime = Builder::new_current_thread().enable_all().build().unwrap();
23257- runtime
23258- .block_on(async {
23259- tokio::task::LocalSet::new()
23260- .run_until(job_server::serve(&job_config))
23261- .await
23262- })
23263- .unwrap();
23264- })
23265- .unwrap();
23266- let runtime = Builder::new_multi_thread()
23267- .worker_threads(cfg.worker_threads.into())
23268- .thread_name("ayllu-web-runtime")
23269- .max_blocking_threads(cfg.max_blocking_threads.into())
23270- .enable_all()
23271- .build()
23272- .unwrap();
23273- runtime.block_on(web2::runtime::serve(&cfg));
23274- Ok(())
23275- }
23276- Command::Jobs(subcommand) => {
23277- let cfg = init_config(args.config.as_deref(), args.level.as_deref())?;
23278- let runtime = Builder::new_current_thread().enable_all().build().unwrap();
23279- match subcommand {
23280- JobsCommand::List(JobArguments { path, .. }) => {
23281- runtime.block_on(async {
23282- tokio::task::LocalSet::new()
23283- .run_until(job_server::list(cfg, path))
23284- .await
23285- })?;
23286- Ok(())
23287- }
23288- JobsCommand::Run {
23289- all: _,
23290- rest: JobArguments { path, short, kind },
23291- } => {
23292- let depth = if short { Some(1) } else { None };
23293- runtime.block_on(async {
23294- tokio::task::LocalSet::new()
23295- .run_until(job_server::run_one(cfg, path, kind.as_deref(), depth))
23296- .await
23297- })?;
23298- Ok(())
23299- }
23300- JobsCommand::Purge(JobArguments { path, .. }) => {
23301- runtime.block_on(async {
23302- tokio::task::LocalSet::new()
23303- .run_until(job_server::purge(cfg, path))
23304- .await
23305- })?;
23306- Ok(())
23307- }
23308- }
23309- }
23310- }
23311- }
23312 diff --git a/src/time.rs b/src/time.rs
23313deleted file mode 100644
23314index f0578dd..0000000
23315--- a/src/time.rs
23316+++ /dev/null
23317 @@ -1,54 +0,0 @@
23318- use std::time::SystemTime;
23319-
23320- // apparently these exist in nightly rust?
23321- const SECOND: u64 = 1;
23322- const MINUTE: u64 = SECOND * 60;
23323- const HOUR: u64 = MINUTE * 60;
23324- const DAY: u64 = HOUR * 24;
23325- const WEEK: u64 = DAY * 7;
23326- const MONTH: u64 = DAY * 30;
23327- const YEAR: u64 = DAY * 356;
23328-
23329- fn pluralize(value: u64) -> String {
23330- match value {
23331- 1 => String::new(),
23332- _ => String::from("s"),
23333- }
23334- }
23335-
23336- // convert number of a seconds into a friendly string, e.g.
23337- // recently, 1s ago, 1m ago, 1h ago, 1d ago, 1mo ago, 1y ago
23338- // FIXME: why is this u64
23339- pub fn friendly(seconds: u64) -> String {
23340- let now = SystemTime::now()
23341- .duration_since(SystemTime::UNIX_EPOCH)
23342- .unwrap();
23343- let seconds = now.as_secs() - seconds;
23344- let message: String;
23345- if seconds == 0 {
23346- message = "recently".to_string();
23347- } else if seconds < MINUTE {
23348- message = format!("{} second{} ago", seconds, pluralize(seconds));
23349- } else if seconds < HOUR {
23350- message = format!(
23351- "{} minute{} ago",
23352- seconds / MINUTE,
23353- pluralize(seconds / MINUTE),
23354- )
23355- } else if seconds < DAY {
23356- message = format!("{} hour{} ago", seconds / HOUR, pluralize(seconds / HOUR),)
23357- } else if seconds < WEEK {
23358- message = format!("{} day{} ago", seconds / DAY, pluralize(seconds / DAY),)
23359- } else if seconds < MONTH {
23360- message = format!("{} week{} ago", seconds / WEEK, pluralize(seconds / WEEK),)
23361- } else if seconds < YEAR {
23362- message = format!(
23363- "{} month{} ago",
23364- seconds / MONTH,
23365- pluralize(seconds / MONTH),
23366- )
23367- } else {
23368- message = format!("{} year{} ago", seconds / YEAR, pluralize(seconds / YEAR),)
23369- }
23370- message
23371- }
23372 diff --git a/src/web2/charts.rs b/src/web2/charts.rs
23373deleted file mode 100644
23374index e331617..0000000
23375--- a/src/web2/charts.rs
23376+++ /dev/null
23377 @@ -1,222 +0,0 @@
23378- use std::i64;
23379- use std::ops::Range;
23380-
23381- use plotters::prelude::*;
23382- use sqlx::Error;
23383-
23384- use crate::languages::{Hint, LANGUAGE_TABLE};
23385-
23386- const ALMOST_BLACK: &str = "#111111";
23387-
23388- const LABEL_STYLE: (FontFamily, f64, FontStyle) = (FontFamily::Monospace, 14.0, FontStyle::Normal);
23389- const TITLE_STYLE: (FontFamily, f64, FontStyle) = (FontFamily::Monospace, 16.0, FontStyle::Bold);
23390-
23391- fn hex_to_rgb(input: &str) -> RGBColor {
23392- let mut hex = input.to_string();
23393- hex = hex.replace('#', "");
23394- if hex.len() != 6 {
23395- panic!("invalid color hex code");
23396- }
23397- let hex = hex.as_str();
23398- let r = &hex[0..2];
23399- let r = i64::from_str_radix(r, 16).unwrap();
23400- let g = &hex[2..4];
23401- let g = i64::from_str_radix(g, 16).unwrap();
23402- let b = &hex[4..6];
23403- let b = i64::from_str_radix(b, 16).unwrap();
23404- RGBColor(r as u8, g as u8, b as u8)
23405- }
23406-
23407- // replace set primary color to currentColor to allow some level of CSS styling
23408- fn filter_black(input: String) -> String {
23409- let output = input.replace(
23410- &format!("fill=\"{}\"", ALMOST_BLACK),
23411- "fill=\"currentColor\"",
23412- );
23413- let output = output.replace(
23414- &format!("stroke=\"{}\"", ALMOST_BLACK),
23415- "stroke=\"currentColor\"",
23416- );
23417- let output = output.replace("stroke=\"#000000\"", "stroke=\"currentColor\"");
23418-
23419- output.replace("fill=\"#000000\"", "fill=\"currentColor\"")
23420- }
23421-
23422- pub struct Renderer {
23423- pub size: (u32, u32),
23424- }
23425-
23426- impl Renderer {
23427- /// create a new chart renderer where the default size is (width, height)
23428- pub fn new(size: (u32, u32)) -> Self {
23429- Renderer { size }
23430- }
23431-
23432- pub fn languages(&self, data: Vec<(String, u8, u32)>) -> Result<String, Error> {
23433- // total LOC for chart title
23434- let loc = data.iter().fold(0, |accm, item| accm + item.2);
23435- let mut output = String::new();
23436- let n_items = match data.len() {
23437- 0 => 0,
23438- n => n - 1,
23439- } as u32;
23440- {
23441- let root = SVGBackend::with_string(&mut output, self.size)
23442- .into_drawing_area()
23443- .titled(
23444- format!("Composition [{} LOC]", loc).as_str(),
23445- TextStyle::from((TITLE_STYLE.0, TITLE_STYLE.1).into_font()).color(&(BLACK)),
23446- )
23447- .unwrap();
23448-
23449- let mut chart = ChartBuilder::on(&root)
23450- .x_label_area_size(35)
23451- .y_label_area_size(50)
23452- .margin(5)
23453- .build_cartesian_2d(0u32..105u32, (0u32..n_items).into_segmented())
23454- .unwrap();
23455-
23456- let names: Vec<String> = data.iter().map(|item| item.0.to_string()).collect();
23457-
23458- let black: RGBAColor = hex_to_rgb(ALMOST_BLACK).into();
23459-
23460- chart
23461- .configure_mesh()
23462- .bold_line_style(ShapeStyle {
23463- color: black,
23464- filled: false,
23465- stroke_width: 1,
23466- })
23467- .x_label_style(
23468- FontDesc::new(LABEL_STYLE.0, LABEL_STYLE.1, LABEL_STYLE.2).with_color(black),
23469- )
23470- .y_label_style(
23471- FontDesc::new(LABEL_STYLE.0, LABEL_STYLE.1, LABEL_STYLE.2).with_color(black),
23472- )
23473- .y_labels(5)
23474- .y_label_formatter(&|v| match *v {
23475- SegmentValue::CenterOf(n) => match names.get(n as usize) {
23476- Some(value) => value.to_string(),
23477- None => String::new(),
23478- },
23479- _ => unreachable!("invalid chart data"),
23480- })
23481- .x_label_formatter(&|v| format!("{}%", v))
23482- .draw()
23483- .unwrap();
23484-
23485- data.iter().enumerate().for_each(|(i, item)| {
23486- let data = [item.1 as u32];
23487- let color =
23488- hex_to_rgb(&LANGUAGE_TABLE.color_from_language(&Hint(item.0.to_string())));
23489- let hist = Histogram::horizontal(&chart)
23490- .style(color.filled())
23491- .data(data.iter().map(|x: &u32| (i as u32, *x)));
23492- chart.draw_series(hist).unwrap();
23493- });
23494- }
23495-
23496- Ok(filter_black(output))
23497- }
23498-
23499- pub fn activity(&self, data: Vec<(i64, i64, i64)>) -> Result<String, Error> {
23500- let lines_added: Vec<i64> = data.iter().map(|item| item.1).collect();
23501- let lines_removed: Vec<i64> = data.iter().map(|item| item.2).collect();
23502- let y_max = match lines_added.iter().max_by(|x, y| x.cmp(y)) {
23503- Some(i) => *i,
23504- None => 90,
23505- };
23506- let y_min = match lines_removed.iter().min_by(|x, y| x.cmp(y)) {
23507- Some(i) => *i,
23508- None => 0,
23509- };
23510- let mut output = String::new();
23511- {
23512- let root = SVGBackend::with_string(&mut output, self.size)
23513- .into_drawing_area()
23514- .titled(
23515- "Recent Activity (90 days)",
23516- TextStyle::from((TITLE_STYLE.0, TITLE_STYLE.1).into_font()).color(&(BLACK)),
23517- )
23518- .unwrap();
23519-
23520- let mut ctx = ChartBuilder::on(&root)
23521- .margin(5)
23522- .set_label_area_size(LabelAreaPosition::Left, 50)
23523- .set_label_area_size(LabelAreaPosition::Bottom, 30)
23524- .build_cartesian_2d(
23525- Range {
23526- start: 0,
23527- end: data.len(),
23528- }
23529- .into_segmented(),
23530- Range {
23531- start: y_min,
23532- end: y_max,
23533- },
23534- )
23535- .unwrap();
23536-
23537- let black = hex_to_rgb(ALMOST_BLACK);
23538-
23539- ctx.configure_mesh()
23540- .bold_line_style(ShapeStyle {
23541- color: black.into(),
23542- filled: false,
23543- stroke_width: 1,
23544- })
23545- .x_label_style(FontDesc::new(LABEL_STYLE.0, LABEL_STYLE.1, LABEL_STYLE.2))
23546- .y_label_style(FontDesc::new(LABEL_STYLE.0, LABEL_STYLE.1, LABEL_STYLE.2))
23547- .draw()
23548- .unwrap();
23549-
23550- ctx.draw_series((0..).zip(lines_added.iter()).map(|(x, y)| {
23551- let x0 = SegmentValue::Exact(x);
23552- let x1 = SegmentValue::Exact(x + 1);
23553- let mut bar = Rectangle::new([(x0, 0), (x1, *y)], GREEN.filled());
23554- bar.set_margin(0, 0, 1, 1);
23555- bar
23556- }))
23557- .unwrap();
23558- ctx.draw_series((0..).zip(lines_removed.iter()).map(|(x, y)| {
23559- let x0 = SegmentValue::Exact(x);
23560- let x1 = SegmentValue::Exact(x + 1);
23561- let mut bar = Rectangle::new([(x0, 0), (x1, *y)], RED.filled());
23562- bar.set_margin(0, 0, 1, 1);
23563- bar
23564- }))
23565- .unwrap();
23566- }
23567-
23568- Ok(filter_black(output))
23569- }
23570- }
23571-
23572- // return a string like ▁▂▃▅▂▇▃▅▂▇▃▂▁▂▁▁▁ to illustrate usage activity
23573- // idea inspired by https://git.zx2c4.com/spark/tree/spark.c
23574- pub fn spark_lines(arr: Vec<i64>, chunks: usize) -> String {
23575- let buckets: Vec<i64> = arr.chunks(chunks).map(|chunk| chunk.iter().sum()).collect();
23576- let n_buckets = buckets.len() as f64;
23577- let mut sorted = buckets.clone();
23578- sorted.sort();
23579- let line = buckets.iter().fold(String::new(), |accm, total| {
23580- let (rank, _) = sorted
23581- .iter()
23582- .enumerate()
23583- .find(|(_, other)| **other == *total)
23584- .unwrap();
23585- let bar = if rank < 1 {
23586- String::from('▁')
23587- } else if (rank as f64) < (n_buckets * 0.20) {
23588- String::from('▂')
23589- } else if (rank as f64) < (n_buckets * 0.40) {
23590- String::from('▃')
23591- } else if (rank as f64) < (n_buckets * 0.80) {
23592- String::from('▅')
23593- } else {
23594- String::from('▇')
23595- };
23596- accm + &bar.to_string()
23597- });
23598- line
23599- }
23600 diff --git a/src/web2/config.rs b/src/web2/config.rs
23601deleted file mode 100644
23602index a8a2a87..0000000
23603--- a/src/web2/config.rs
23604+++ /dev/null
23605 @@ -1,8 +0,0 @@
23606- use serde::{Deserialize, Serialize};
23607-
23608- // Client side configuration via cookies
23609- #[derive(Clone, Deserialize, Serialize)]
23610- pub struct Config {
23611- pub theme: Option<String>,
23612- pub items_per_page: usize,
23613- }
23614 diff --git a/src/web2/error.rs b/src/web2/error.rs
23615deleted file mode 100644
23616index 38d078e..0000000
23617--- a/src/web2/error.rs
23618+++ /dev/null
23619 @@ -1,88 +0,0 @@
23620- use std::error::Error as StdError;
23621- use std::fmt::Display;
23622- use std::io::Error as IoError;
23623-
23624- use axum::{body::Body, response::IntoResponse, response::Response};
23625- use tera::Error as TeraError;
23626-
23627- use ayllu_database::Error as SqlError;
23628- use ayllu_git::Error as GitError;
23629- use ayllu_rpc::Error as RpcError;
23630-
23631- /// Error maps known error types into errors that can be translated into HTTP
23632- /// status codes, e.g. Io::NotFound -> 404
23633- #[derive(Debug, Clone)]
23634- pub enum Error {
23635- Message(String),
23636- NotFound(String),
23637- }
23638-
23639- impl IntoResponse for Error {
23640- fn into_response(self) -> Response {
23641- Response::builder()
23642- .extension(self)
23643- .body(Body::empty())
23644- .unwrap()
23645- }
23646- }
23647-
23648- impl From<Box<dyn StdError>> for Error {
23649- fn from(value: Box<dyn StdError>) -> Self {
23650- Error::Message(value.to_string())
23651- }
23652- }
23653-
23654- impl From<RpcError> for Error {
23655- fn from(value: RpcError) -> Self {
23656- Error::Message(format!("RPC: {:?}", value))
23657- }
23658- }
23659-
23660- impl From<TeraError> for Error {
23661- fn from(value: TeraError) -> Self {
23662- Error::Message(format!("Templating: {:?}", value))
23663- }
23664- }
23665-
23666- impl From<GitError> for Error {
23667- fn from(value: GitError) -> Self {
23668- if value.not_found() {
23669- Error::NotFound(format!("GIT: {:?}", value))
23670- } else {
23671- Error::Message(format!("GIT: {:?}", value))
23672- }
23673- }
23674- }
23675-
23676- impl From<IoError> for Error {
23677- fn from(value: IoError) -> Self {
23678- match value.kind() {
23679- std::io::ErrorKind::NotFound => Error::NotFound(format!("IO: {}", value)),
23680- _ => Error::Message(format!("IO: {:?}", value)),
23681- }
23682- }
23683- }
23684-
23685- impl From<SqlError> for Error {
23686- fn from(value: SqlError) -> Self {
23687- Error::Message(format!("SQL: {:?}", value))
23688- }
23689- }
23690-
23691- impl StdError for Error {
23692- fn source(&self) -> Option<&(dyn StdError + 'static)> {
23693- match self {
23694- Error::Message(_) => None,
23695- Error::NotFound(_) => None,
23696- }
23697- }
23698- }
23699-
23700- impl Display for Error {
23701- fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
23702- match self {
23703- Error::Message(message) => write!(f, "{}", message),
23704- Error::NotFound(message) => write!(f, "{}", message),
23705- }
23706- }
23707- }
23708 diff --git a/src/web2/extractors/config.rs b/src/web2/extractors/config.rs
23709deleted file mode 100644
23710index 9af222e..0000000
23711--- a/src/web2/extractors/config.rs
23712+++ /dev/null
23713 @@ -1,34 +0,0 @@
23714- use axum::{
23715- async_trait,
23716- extract::FromRequestParts,
23717- http::request::Parts,
23718- response::{IntoResponse, Response},
23719- };
23720-
23721- use axum_extra::extract::CookieJar;
23722-
23723- use crate::web2::config::Config;
23724-
23725- pub struct ConfigReader(pub Config);
23726-
23727- #[async_trait]
23728- impl<S> FromRequestParts<S> for ConfigReader
23729- where
23730- S: Send + Sync,
23731- {
23732- type Rejection = Response;
23733-
23734- async fn from_request_parts(parts: &mut Parts, state: &S) -> Result<Self, Self::Rejection> {
23735- let cookies = CookieJar::from_request_parts(parts, state)
23736- .await
23737- .map_err(|err| err.into_response())?;
23738- Ok(ConfigReader(Config {
23739- theme: cookies
23740- .get("theme")
23741- .map(|cookie| cookie.value().to_string()),
23742- items_per_page: cookies.get("items-per-page").map_or(100, |cookie| {
23743- cookie.value().to_string().parse::<usize>().unwrap_or(100)
23744- }),
23745- }))
23746- }
23747- }
23748 diff --git a/src/web2/extractors/mod.rs b/src/web2/extractors/mod.rs
23749deleted file mode 100644
23750index ef68c36..0000000
23751--- a/src/web2/extractors/mod.rs
23752+++ /dev/null
23753 @@ -1 +0,0 @@
23754- pub mod config;
23755 diff --git a/src/web2/highlight.rs b/src/web2/highlight.rs
23756deleted file mode 100644
23757index 462c804..0000000
23758--- a/src/web2/highlight.rs
23759+++ /dev/null
23760 @@ -1,352 +0,0 @@
23761- use std::collections::HashMap;
23762-
23763- use std::fs;
23764- use std::io::{Cursor, Error, Write};
23765- use std::mem;
23766- use std::path::Path;
23767- use std::sync::RwLock;
23768-
23769- use comrak::adapters::SyntaxHighlighterAdapter;
23770- use lazy_static::lazy_static;
23771- use log::debug;
23772- use tera::escape_html;
23773- use tree_sitter::Language;
23774- use tree_sitter_highlight::{HighlightConfiguration, Highlighter as TSHighlighter, HtmlRenderer};
23775-
23776- use crate::config::TreeSitterParser;
23777- use crate::languages::{Hint, LANGUAGE_TABLE};
23778-
23779- lazy_static! {
23780- // global containing all language parsers and syntax highlighter definitions
23781- // this needs to be initialized one time at startup
23782- static ref LANGUAGES: RwLock<HashMap<Hint, Language>> = RwLock::new(HashMap::new());
23783- // highlighter queries
23784- static ref HIGHLIGHTS: RwLock<HashMap<Hint, String>> = RwLock::new(HashMap::new());
23785- // local queries
23786- static ref LOCALS: RwLock<HashMap<Hint, String>> = RwLock::new(HashMap::new());
23787- // injection queries
23788- static ref INJECTIONS: RwLock<HashMap<Hint, String>> = RwLock::new(HashMap::new());
23789- }
23790-
23791- unsafe fn load_language(path: &str, hint: &Hint) -> Language {
23792- let lib = libloading::Library::new(path).unwrap();
23793- let method_name = format!("tree_sitter_{}", hint.0.to_lowercase());
23794- // NOTE: maybe, probably? some security auditing that needs to happen here?
23795- debug!("attempting to load method: {}", method_name);
23796- let func: libloading::Symbol<unsafe extern "C" fn() -> Language> =
23797- lib.get(method_name.as_bytes()).unwrap();
23798- let language = func();
23799- mem::forget(lib);
23800- language
23801- }
23802-
23803- pub struct Loader {
23804- // typically /usr/lib
23805- lib_path: String,
23806- // typically /usr/share/tree-sitter
23807- query_path: String,
23808- dynamic_ok: bool,
23809- extra_queries_path: Option<String>,
23810- extra_parsers: Vec<TreeSitterParser>,
23811- }
23812-
23813- impl Loader {
23814- pub fn new(lib_path: &str, query_path: &str) -> Self {
23815- Loader {
23816- lib_path: lib_path.to_string(),
23817- query_path: query_path.to_string(),
23818- dynamic_ok: false,
23819- extra_queries_path: None,
23820- extra_parsers: Vec::new(),
23821- }
23822- }
23823-
23824- pub fn dynamic(mut self, value: bool) -> Self {
23825- self.dynamic_ok = value;
23826- self
23827- }
23828-
23829- pub fn extra_queries(mut self, path: &str) -> Self {
23830- self.extra_queries_path = Some(path.to_string());
23831- self
23832- }
23833-
23834- pub fn parsers(mut self, parsers: Vec<TreeSitterParser>) -> Self {
23835- self.extra_parsers = parsers.clone();
23836- self
23837- }
23838-
23839- fn try_query_load(&self, path: &str, hint: &Hint) -> Result<(), Error> {
23840- let query_base_path = Path::new(path).join(hint.0.to_string().to_lowercase());
23841- if let Ok(queries) = fs::read_dir(query_base_path) {
23842- for query in queries {
23843- let query = query?;
23844- match query.file_name().into_string().unwrap().as_str() {
23845- "highlights.scm" => {
23846- let highlight_scm = fs::read_to_string(query.path())?;
23847- HIGHLIGHTS
23848- .write()
23849- .unwrap()
23850- .insert(hint.clone(), highlight_scm);
23851- debug!("loaded [{:?}] {}", hint, query.path().display());
23852- }
23853- "locals.scm" => {
23854- let locals_scm = fs::read_to_string(query.path())?;
23855- LOCALS.write().unwrap().insert(hint.clone(), locals_scm);
23856- debug!("loaded [{:?}] {}", hint, query.path().display());
23857- }
23858- "injections.scm" => {
23859- let injections_scm = fs::read_to_string(query.path())?;
23860- INJECTIONS
23861- .write()
23862- .unwrap()
23863- .insert(hint.clone(), injections_scm);
23864- debug!("loaded [{:?}] {}", hint, query.path().display());
23865- }
23866- name => {
23867- debug!("ignoring query file: {}", name)
23868- }
23869- }
23870- }
23871- }
23872- Ok(())
23873- }
23874-
23875- pub fn load(&self) -> Result<(), Error> {
23876- let modules = fs::read_dir(Path::new(&self.lib_path))?;
23877- for module in modules {
23878- let fp = module?;
23879- let file_name = fp.file_name().into_string().unwrap();
23880- if file_name.starts_with("libtree-sitter-") && file_name.ends_with(".so") {
23881- let language_name = file_name.replace("libtree-sitter-", "").replace(".so", "");
23882-
23883- let hint = Hint(language_name);
23884- let language = unsafe { load_language(fp.path().to_str().unwrap(), &hint) };
23885- LANGUAGES.write().unwrap().insert(hint.clone(), language);
23886- debug!(
23887- "loaded tree-sitter shared module: [{:?}] {}",
23888- hint,
23889- fp.path().display()
23890- );
23891- self.try_query_load(self.query_path.as_str(), &hint)?;
23892- // override any base queries with additional queries
23893- self.extra_queries_path
23894- .as_ref()
23895- .map(|path| self.try_query_load(path.as_str(), &hint))
23896- .transpose()?;
23897- }
23898- }
23899- for parser in &self.extra_parsers {
23900- let hint = Hint(parser.language.clone());
23901- let language = unsafe { load_language(&parser.shared_object, &hint) };
23902- LANGUAGES.write().unwrap().insert(hint.clone(), language);
23903- parser
23904- .highlight_query
23905- .as_ref()
23906- .map(|path| {
23907- let highlight_scm = fs::read_to_string(path)?;
23908- HIGHLIGHTS
23909- .write()
23910- .unwrap()
23911- .insert(hint.clone(), highlight_scm);
23912- Ok::<(), Error>(())
23913- })
23914- .transpose()?;
23915- parser
23916- .locals_query
23917- .as_ref()
23918- .map(|path| {
23919- let locals_scm = fs::read_to_string(path)?;
23920- LOCALS.write().unwrap().insert(hint.clone(), locals_scm);
23921- Ok::<(), Error>(())
23922- })
23923- .transpose()?;
23924- parser
23925- .injections_query
23926- .as_ref()
23927- .map(|path| {
23928- let injections_scm = fs::read_to_string(path)?;
23929- INJECTIONS
23930- .write()
23931- .unwrap()
23932- .insert(hint.clone(), injections_scm);
23933- Ok::<(), Error>(())
23934- })
23935- .transpose()?;
23936- }
23937- Ok(())
23938- }
23939- }
23940-
23941- fn render_lines(lines: Vec<&str>, show_line_numbers: bool) -> String {
23942- let buf = Vec::new();
23943- let mut file = Cursor::new(buf);
23944- write!(&mut file, "<table>").unwrap();
23945- for (i, line) in lines.into_iter().enumerate() {
23946- if show_line_numbers {
23947- write!(&mut file, "<tr><td class=line-number>{:?}</td>", i + 1).unwrap();
23948- }
23949- write!(&mut file, "<td class=line>{}</td></tr>", line).unwrap();
23950- }
23951- write!(&mut file, "</table>").unwrap();
23952- let bytes = file.into_inner();
23953- String::from_utf8(bytes).unwrap()
23954- }
23955-
23956- #[derive(Clone, Debug)]
23957- pub struct Highlighter {
23958- names: Vec<String>,
23959- classes: Vec<String>,
23960- }
23961-
23962- impl Highlighter {
23963- pub fn new(names: &[String]) -> Self {
23964- let mut classes: Vec<String> = Vec::new();
23965-
23966- for name in names.iter() {
23967- classes.push(format!("class=\"ts_{}\"", name));
23968- }
23969- Self {
23970- names: names.to_vec(),
23971- classes,
23972- }
23973- }
23974-
23975- pub fn highlight(
23976- &self,
23977- code: &str,
23978- filepath: Option<&str>,
23979- alias: Option<&str>,
23980- hint: Option<Hint>,
23981- show_line_numbers: bool,
23982- ) -> (Option<Hint>, String) {
23983- let buf = Vec::new();
23984- let mut file = Cursor::new(buf);
23985- write!(&mut file, "<table>").unwrap();
23986-
23987- let hint = match hint {
23988- Some(hint) => Some(hint),
23989- None => LANGUAGE_TABLE.guess(code, alias, filepath),
23990- };
23991-
23992- debug!("language hint: {:?}", hint);
23993-
23994- match hint {
23995- Some(hint) => match (
23996- LANGUAGES.read().unwrap().get(&hint),
23997- HIGHLIGHTS.read().unwrap().get(&hint),
23998- ) {
23999- (Some(language), Some(syntax)) => {
24000- debug!("painting syntax for language: {:?}", hint);
24001- let mut highlighter = TSHighlighter::new();
24002- let injections = INJECTIONS
24003- .read()
24004- .unwrap()
24005- .get(&hint)
24006- .cloned()
24007- .unwrap_or_default();
24008- let locals = LOCALS
24009- .read()
24010- .unwrap()
24011- .get(&hint)
24012- .cloned()
24013- .unwrap_or_default();
24014- let mut config =
24015- HighlightConfiguration::new(*language, syntax, &injections, &locals)
24016- .unwrap();
24017-
24018- config.configure(&self.names);
24019-
24020- let code = code.as_bytes();
24021-
24022- let events = highlighter
24023- .highlight(&config, code, None, |_| None)
24024- .unwrap();
24025-
24026- let mut renderer = HtmlRenderer::new();
24027-
24028- renderer
24029- .render(events, code, &move |highlight| {
24030- let ret = match self.classes.get(highlight.0) {
24031- Some(name) => name.as_bytes(),
24032- None => "".as_bytes(),
24033- };
24034- ret
24035- })
24036- .unwrap();
24037-
24038- (
24039- Some(hint.clone()),
24040- render_lines(renderer.lines().collect(), show_line_numbers),
24041- )
24042- }
24043- _ => {
24044- debug!("cannot paint syntax for language: {:?}", hint);
24045- (
24046- None,
24047- render_lines(escape_html(code).lines().collect(), show_line_numbers),
24048- )
24049- }
24050- },
24051- None => {
24052- debug!("cannot determine language");
24053- (
24054- None,
24055- render_lines(code.to_string().lines().collect(), show_line_numbers),
24056- )
24057- }
24058- }
24059- }
24060-
24061- pub fn highlight_vec(
24062- &self,
24063- content: Vec<u8>,
24064- filepath: Option<&str>,
24065- alias: Option<&str>,
24066- hint: Option<Hint>,
24067- show_line_numbers: bool,
24068- ) -> (Option<Hint>, String) {
24069- let content = String::from_utf8(content).unwrap();
24070- self.highlight(content.as_str(), filepath, alias, hint, show_line_numbers)
24071- }
24072- }
24073-
24074- #[derive(Debug, Clone)]
24075- pub struct TreeSitterAdapter {
24076- highlighter: Highlighter,
24077- }
24078-
24079- impl TreeSitterAdapter {
24080- pub fn new(highlighter: Highlighter) -> Self {
24081- TreeSitterAdapter { highlighter }
24082- }
24083- }
24084-
24085- impl SyntaxHighlighterAdapter for TreeSitterAdapter {
24086- fn write_highlighted(
24087- &self,
24088- output: &mut dyn std::io::Write,
24089- lang: Option<&str>,
24090- code: &str,
24091- ) -> std::io::Result<()> {
24092- let (_, highlighted) = self.highlighter.highlight(code, None, lang, None, false);
24093- output.write_all(highlighted.as_bytes()).unwrap();
24094- Ok(())
24095- }
24096-
24097- fn write_pre_tag(
24098- &self,
24099- _: &mut dyn std::io::Write,
24100- _: std::collections::HashMap<String, String>,
24101- ) -> std::io::Result<()> {
24102- Ok(())
24103- }
24104-
24105- fn write_code_tag(
24106- &self,
24107- _: &mut dyn std::io::Write,
24108- _: std::collections::HashMap<String, String>,
24109- ) -> std::io::Result<()> {
24110- Ok(())
24111- }
24112- }
24113 diff --git a/src/web2/middleware/error.rs b/src/web2/middleware/error.rs
24114deleted file mode 100644
24115index 956589e..0000000
24116--- a/src/web2/middleware/error.rs
24117+++ /dev/null
24118 @@ -1,55 +0,0 @@
24119- use std::sync::Arc;
24120-
24121- use axum::{
24122- extract,
24123- http::StatusCode,
24124- middleware::Next,
24125- response::{Html, IntoResponse, Response},
24126- };
24127- use tera::Tera;
24128-
24129- use crate::config::Config;
24130- use crate::web2::error::Error;
24131- use crate::web2::extractors::config::ConfigReader;
24132- use crate::web2::terautil::{Loader, Options};
24133-
24134- pub type State = Arc<(Config, Vec<(String, Tera)>)>;
24135-
24136- pub async fn middleware(
24137- extract::State(state): extract::State<State>,
24138- ConfigReader(client_config): ConfigReader,
24139- req: extract::Request,
24140- next: Next,
24141- ) -> Response {
24142- let response = next.run(req).await;
24143- if let Some(error) = response.extensions().get::<Error>() {
24144- let status_code = match error {
24145- Error::Message(_) => StatusCode::INTERNAL_SERVER_ERROR,
24146- Error::NotFound(_) => StatusCode::NOT_FOUND,
24147- };
24148- // reload the theme since the middleware may not have ran yet
24149- let loader = Loader {
24150- templates: state.1.clone(),
24151- default_theme: state.0.web.default_theme.clone(),
24152- };
24153- let (template, mut ctx) = loader.load(
24154- Options {
24155- origin: state.0.origin.clone(),
24156- site_name: state.0.site_name.clone(),
24157- ..Default::default()
24158- },
24159- client_config.theme,
24160- );
24161- ctx.insert("status_code", status_code.as_str());
24162- ctx.insert("error_message", &error.to_string());
24163- let template_str = if status_code == StatusCode::NOT_FOUND {
24164- log::warn!("Not Found: {}", error.to_string());
24165- template.render("404.html", &ctx).unwrap()
24166- } else {
24167- log::error!("Error: {}", error.to_string());
24168- template.render("5xx.html", &ctx).unwrap()
24169- };
24170- return Html::from(template_str).into_response();
24171- }
24172- response
24173- }
24174 diff --git a/src/web2/middleware/mod.rs b/src/web2/middleware/mod.rs
24175deleted file mode 100644
24176index 59371b3..0000000
24177--- a/src/web2/middleware/mod.rs
24178+++ /dev/null
24179 @@ -1,5 +0,0 @@
24180- pub mod error;
24181- pub mod repository;
24182- pub mod rpc_initiator;
24183- pub mod sites;
24184- pub mod template;
24185 diff --git a/src/web2/middleware/repository.rs b/src/web2/middleware/repository.rs
24186deleted file mode 100644
24187index 3c1c94a..0000000
24188--- a/src/web2/middleware/repository.rs
24189+++ /dev/null
24190 @@ -1,129 +0,0 @@
24191- use std::path::PathBuf;
24192- use std::time::SystemTime;
24193-
24194- use axum::{
24195- extract::{Path, Request, State},
24196- middleware::Next,
24197- response::{IntoResponse, Response},
24198- };
24199-
24200- use serde::Deserialize;
24201-
24202- use crate::config::Config;
24203- use crate::web2::error::Error;
24204- use ayllu_git::{Commit, Config as GitConfig, Wrapper as Repository};
24205-
24206- #[derive(Deserialize)]
24207- pub struct CommonParams {
24208- pub collection: String,
24209- pub name: String,
24210- pub commitish: Option<String>,
24211- pub file_path: Option<String>,
24212- }
24213-
24214- /// Preamble contains repository information useful for all "repository level"
24215- /// pages, basically it's purpose is to reduce boilerplate.
24216- #[derive(Clone)]
24217- pub struct Preamble {
24218- pub start_time: SystemTime,
24219- pub repo_path: PathBuf,
24220- pub is_empty: bool,
24221- pub repo_name: String,
24222- pub collection_name: String,
24223- pub refname: String,
24224- pub config: GitConfig,
24225- pub file_path: Option<PathBuf>,
24226- pub latest_commit: Option<Commit>,
24227- pub latest_commit_id: Option<String>,
24228- }
24229-
24230- impl Preamble {
24231- pub fn new(
24232- system_config: &Config,
24233- collection_name: String,
24234- repo_name: String,
24235- commitish: Option<String>,
24236- file_path: Option<String>,
24237- ) -> Result<Self, Error> {
24238- let start_time = SystemTime::now();
24239- let repo_path = match system_config
24240- .collections
24241- .iter()
24242- .find(|collection| collection.name == collection_name)
24243- {
24244- Some(collection) => PathBuf::from(format!("{}/{}", collection.path, repo_name)),
24245- None => {
24246- return Err(Error::NotFound(String::from("collection not found")));
24247- }
24248- };
24249- let repository = Repository::new(&repo_path)?;
24250- let is_empty = repository.is_empty()?;
24251- let config = repository.config()?;
24252- let (refname, latest_commit) = if is_empty {
24253- (String::new(), None)
24254- } else if let Some(commitish) = commitish {
24255- let commit = repository.resolve_commit(Some(commitish.as_str()))?;
24256- (commitish.to_string(), commit)
24257- } else if let Some(default_branch) = config.default_branch.clone() {
24258- let commit = repository.resolve_commit(Some(default_branch.as_str()))?;
24259- (default_branch.clone(), commit)
24260- } else if let Some(system_default_branch) = &system_config.default_branch {
24261- let commit = repository.resolve_commit(Some(system_default_branch.as_str()))?;
24262- (system_default_branch.clone(), commit)
24263- } else {
24264- let default_branch = repository.default_ref()?;
24265- let commit = repository.resolve_commit(Some(default_branch.as_str()))?;
24266- (default_branch.clone(), commit)
24267- };
24268- Ok(Preamble {
24269- start_time,
24270- repo_path,
24271- is_empty,
24272- repo_name,
24273- collection_name,
24274- refname,
24275- config,
24276- file_path: file_path.map(PathBuf::from),
24277- latest_commit: latest_commit.clone(),
24278- latest_commit_id: latest_commit.map(|commit| commit.id),
24279- })
24280- }
24281-
24282- pub fn repo_path_string(&self) -> String {
24283- self.repo_path.to_str().unwrap().to_string()
24284- }
24285-
24286- pub fn file_path_string(&self) -> String {
24287- self.file_path
24288- .clone()
24289- .map(|fp| fp.to_str().unwrap().to_string())
24290- .unwrap_or_default()
24291- }
24292-
24293- pub fn file_name(&self) -> String {
24294- self.file_path
24295- .clone()
24296- .map_or(String::new(), |fp| fp.to_str().unwrap().to_string())
24297- }
24298- }
24299-
24300- pub async fn middleware(
24301- State(config): State<Config>,
24302- Path(CommonParams {
24303- collection,
24304- name,
24305- commitish,
24306- file_path,
24307- }): Path<CommonParams>,
24308- mut req: Request,
24309- next: Next,
24310- ) -> Response {
24311- let preamble = Preamble::new(&config, collection, name, commitish, file_path);
24312- match preamble {
24313- Ok(preamble) => {
24314- req.extensions_mut().insert(preamble);
24315- }
24316- Err(err) => return err.into_response(),
24317- }
24318- next.run(req).await
24319- }
24320 diff --git a/src/web2/middleware/rpc_initiator.rs b/src/web2/middleware/rpc_initiator.rs
24321deleted file mode 100644
24322index cdc455c..0000000
24323--- a/src/web2/middleware/rpc_initiator.rs
24324+++ /dev/null
24325 @@ -1,121 +0,0 @@
24326- use std::sync::Arc;
24327-
24328- use crate::web2::terautil::{Loader, Options};
24329- use axum::{
24330- body::Body,
24331- extract,
24332- http::{header::CONTENT_TYPE, StatusCode},
24333- middleware::Next,
24334- response::Response,
24335- };
24336- use mime::TEXT_HTML_UTF_8;
24337- use tera::Tera;
24338- use tracing::log;
24339-
24340- use crate::config::Config;
24341- use crate::web2::config::Config as ClientConfig;
24342- use crate::web2::extractors::config::ConfigReader;
24343- use ayllu_rpc::Client;
24344-
24345- pub type State = Arc<(Config, Vec<(String, Tera)>, &'static [Kind])>;
24346-
24347- pub enum Kind {
24348- Mail,
24349- Xmpp,
24350- }
24351-
24352- #[derive(Clone, Default)]
24353- pub struct Initiator {
24354- mail: Option<Client>,
24355- xmpp: Option<Client>,
24356- }
24357-
24358- impl Initiator {
24359- pub fn client(self, kind: Kind) -> Option<Client> {
24360- match kind {
24361- Kind::Mail => self.mail,
24362- Kind::Xmpp => self.xmpp,
24363- }
24364- }
24365- }
24366-
24367- fn plugin_not_enabled(
24368- plugin_name: &str,
24369- cfg: &Config,
24370- client_config: ClientConfig,
24371- templates: &[(String, Tera)],
24372- ) -> Response {
24373- log::warn!("returning error due to missing plugin: {}", plugin_name);
24374- // reload the theme since the middleware may not have ran yet
24375- let loader = Loader {
24376- templates: templates.to_vec(),
24377- default_theme: cfg.web.default_theme.clone(),
24378- };
24379- let (template, mut ctx) = loader.load(
24380- Options {
24381- origin: cfg.origin.clone(),
24382- site_name: cfg.site_name.clone(),
24383- ..Default::default()
24384- },
24385- client_config.theme,
24386- );
24387- ctx.insert("status_code", StatusCode::NOT_IMPLEMENTED.as_str());
24388- ctx.insert(
24389- "error_message",
24390- &format!("Plugin: {} is not enabled", plugin_name),
24391- );
24392- let template_str = template.render("5xx.html", &ctx).unwrap();
24393- Response::builder()
24394- .header(CONTENT_TYPE, TEXT_HTML_UTF_8.as_ref())
24395- .status(StatusCode::NOT_IMPLEMENTED)
24396- .body(Body::new(template_str))
24397- .unwrap()
24398- }
24399-
24400- /// configure an optinoal plugin for use in a handler
24401- pub async fn optional(
24402- extract::State(cfg): extract::State<Arc<Config>>,
24403- mut req: extract::Request,
24404- next: Next,
24405- ) -> Response {
24406- req.extensions_mut().insert(Initiator {
24407- mail: cfg
24408- .mail
24409- .as_ref()
24410- .map(|mail_cfg| Client::new(&mail_cfg.socket_path)),
24411- xmpp: cfg
24412- .xmpp
24413- .as_ref()
24414- .map(|xmpp_cfg| Client::new(&xmpp_cfg.socket_path)),
24415- });
24416- next.run(req).await
24417- }
24418-
24419- /// configure a required plugin for use in a handler, if it is not configured
24420- /// an error page will be returned from the handler
24421- pub async fn required(
24422- extract::State(state): extract::State<State>,
24423- ConfigReader(client_config): ConfigReader,
24424- mut req: extract::Request,
24425- next: Next,
24426- ) -> Response {
24427- let mut initiator = Initiator::default();
24428- for kind in state.2.iter() {
24429- match kind {
24430- Kind::Mail => match &state.0.mail {
24431- Some(mail_cfg) => {
24432- initiator.mail = Some(Client::new(&mail_cfg.socket_path));
24433- }
24434- None => return plugin_not_enabled("ayllu-mail", &state.0, client_config, &state.1),
24435- },
24436- Kind::Xmpp => match &state.0.xmpp {
24437- Some(xmpp_cfg) => {
24438- initiator.xmpp = Some(Client::new(&xmpp_cfg.socket_path));
24439- }
24440- None => return plugin_not_enabled("ayllu-xmpp", &state.0, client_config, &state.1),
24441- },
24442- }
24443- }
24444- req.extensions_mut().insert(initiator);
24445- next.run(req).await
24446- }
24447 diff --git a/src/web2/middleware/sites.rs b/src/web2/middleware/sites.rs
24448deleted file mode 100644
24449index c711298..0000000
24450--- a/src/web2/middleware/sites.rs
24451+++ /dev/null
24452 @@ -1,153 +0,0 @@
24453- use std::fs::read_dir;
24454- use std::path::{Path, PathBuf};
24455-
24456- use axum::{
24457- body::Body,
24458- extract,
24459- http::{header, StatusCode},
24460- middleware::Next,
24461- response::{IntoResponse, Response},
24462- };
24463-
24464- use mime_guess::from_path;
24465-
24466- use crate::config::{Collection, Config};
24467- use crate::web2::error::Error;
24468- use crate::web2::util;
24469- use ayllu_git::Wrapper as Repository;
24470-
24471- pub type State = extract::State<(Config, Vec<(String, (String, String))>)>;
24472- pub type Sites = Vec<(String, (String, String))>;
24473-
24474- // array of all the repositories in each collection
24475- fn repositories(collections: Vec<Collection>) -> Result<Vec<PathBuf>, Error> {
24476- let mut paths: Vec<PathBuf> = Vec::new();
24477- for collection in collections.iter() {
24478- let collection_path = Path::new(&collection.path);
24479- let entries = read_dir(collection_path)?;
24480- for entry in entries {
24481- let entry = entry?;
24482- if entry.file_type()?.is_dir() {
24483- paths.push(entry.path());
24484- }
24485- }
24486- }
24487- Ok(paths)
24488- }
24489-
24490- pub fn sites(cfg: Config) -> Result<Sites, Error> {
24491- let mut sites: Vec<(String, (String, String))> = Vec::new();
24492- for path in repositories(cfg.collections.clone())? {
24493- let repository = Repository::new(path.as_path())?;
24494- let repo_config = repository.config()?;
24495- if repo_config.sites.header.is_some() {
24496- let domain = repo_config.sites.header.unwrap();
24497- match domain.split_once('=') {
24498- Some((key, value)) => {
24499- let repo_path = path.to_str().unwrap().to_string();
24500- sites.push((repo_path.to_string(), (key.to_string(), value.to_string())));
24501- log::info!("serving static site {} -> {}", domain, repo_path);
24502- }
24503- None => panic!("bad key=value header"),
24504- };
24505- }
24506- }
24507- Ok(sites)
24508- }
24509-
24510- pub async fn middleware(
24511- extract::State((cfg, sites)): State,
24512- req: extract::Request,
24513- next: Next,
24514- ) -> Result<Response, Error> {
24515- if !cfg.sites.enabled {
24516- return Ok(next.run(req).await);
24517- }
24518-
24519- let mut repo_path: Option<String> = None;
24520- let headers = req.headers();
24521-
24522- let hostname = headers
24523- .get("host")
24524- .map(|header| header.to_str().unwrap_or(""));
24525-
24526- for (other_repo_path, (key, value)) in sites {
24527- log::debug!("checking site {} {}={}", other_repo_path, key, value);
24528- if let Some(header_value) = headers.get(key.as_str()) {
24529- if header_value.to_str().unwrap() == value {
24530- log::debug!("sites header match: {}={}", key, value);
24531- repo_path = Some(other_repo_path.to_string());
24532- break;
24533- // return self.serve(req, repo_path).await;
24534- }
24535- };
24536- }
24537-
24538- match repo_path {
24539- Some(repo_path) => {
24540- let repository = Repository::new(Path::new(&repo_path))?;
24541- let config = repository.config()?;
24542- let path = util::select_path(req.uri(), Some(0), None).map_or_else(
24543- || PathBuf::from("index.html"),
24544- |path| {
24545- path.strip_prefix("/")
24546- .unwrap_or(path.as_path())
24547- .to_path_buf()
24548- },
24549- );
24550- let blob_path = config
24551- .sites
24552- .content
24553- .map_or(PathBuf::from("public").join(path.clone()), |base| {
24554- PathBuf::from(base).join(path.clone())
24555- });
24556- // assuming path is GET /fuu
24557- // try:
24558- // GET /fuu
24559- // GET /fuu/index.html
24560- // GET /fuu/index.htm
24561- // GET /fuu.html
24562- // GET /fuu.htm
24563- let mut paths: Vec<PathBuf> = vec![
24564- blob_path.clone(),
24565- blob_path.clone().join("index.html"),
24566- blob_path.clone().join("index.htm"),
24567- ];
24568- if blob_path.extension().is_none() {
24569- paths.push(blob_path.clone().with_extension("html"));
24570- paths.push(blob_path.clone().with_extension("htm"));
24571- };
24572- log::debug!("trying paths: {:?}", paths);
24573- for path in paths {
24574- log::debug!("trying path {:?}", path);
24575- if let Some(blob) = repository.blob(path.as_path(), config.sites.branch.clone())? {
24576- log::debug!("got blob from path {:?}", path);
24577- let mime_type = from_path(path.to_str().unwrap()).first_or_octet_stream();
24578- let response = Response::builder()
24579- .header(header::CONTENT_TYPE, mime_type.to_string())
24580- .body(Body::from(blob.content))
24581- .unwrap();
24582- return Ok(response);
24583- };
24584- }
24585-
24586- // special case if running a static site in-front of the forge
24587- // where requests can fall through to the backend.
24588- if hostname.is_some_and(|name| name == cfg.domain.unwrap()) {
24589- let response = next.run(req).await;
24590- Ok(response)
24591- } else {
24592- // TODO: allow custom 404 / error pages on static sites?
24593- Ok((
24594- StatusCode::NOT_FOUND,
24595- format!("cannot find resource: {}", blob_path.to_str().unwrap()),
24596- )
24597- .into_response())
24598- }
24599- }
24600- None => {
24601- let response = next.run(req).await;
24602- Ok(response)
24603- }
24604- }
24605- }
24606 diff --git a/src/web2/middleware/template.rs b/src/web2/middleware/template.rs
24607deleted file mode 100644
24608index 58f93cc..0000000
24609--- a/src/web2/middleware/template.rs
24610+++ /dev/null
24611 @@ -1,47 +0,0 @@
24612- use serde::Deserialize;
24613- use std::sync::Arc;
24614- use tera::{Context as TeraContext, Tera};
24615-
24616- use axum::{extract, middleware::Next, response::Response};
24617-
24618- use crate::config::Config;
24619- use crate::web2::extractors::config::ConfigReader;
24620- use crate::web2::terautil::{Loader, Options};
24621-
24622- pub type Template = (Tera, TeraContext);
24623- pub type State = extract::State<Arc<(Config, Vec<(String, Tera)>)>>;
24624-
24625- #[derive(Deserialize)]
24626- pub struct CommonParams {
24627- pub collection: Option<String>,
24628- pub name: Option<String>,
24629- }
24630-
24631- pub async fn middleware(
24632- extract::State(state): State,
24633- ConfigReader(config): ConfigReader,
24634- extract::Path(CommonParams { collection, name }): extract::Path<CommonParams>,
24635- mut req: extract::Request,
24636- next: Next,
24637- ) -> Response {
24638- let loader = Loader {
24639- templates: state.1.clone(),
24640- default_theme: state.0.web.default_theme.clone(),
24641- };
24642- let (template, ctx) = loader.load(
24643- Options {
24644- origin: state.0.origin.clone(),
24645- collection,
24646- name,
24647- site_name: state.0.site_name.clone(),
24648- url: req.uri().to_string(),
24649- path: req.uri().path().to_string(),
24650- subpath_mode: state.0.subpath_mode,
24651- ..Options::default()
24652- },
24653- config.theme,
24654- );
24655- req.extensions_mut().insert((template, ctx));
24656-
24657- next.run(req).await
24658- }
24659 diff --git a/src/web2/mod.rs b/src/web2/mod.rs
24660deleted file mode 100644
24661index 3d0d739..0000000
24662--- a/src/web2/mod.rs
24663+++ /dev/null
24664 @@ -1,19 +0,0 @@
24665- mod charts;
24666- mod config;
24667- mod error;
24668- mod extractors;
24669- mod highlight;
24670- mod middleware;
24671- mod navigation;
24672- mod routes;
24673- mod server;
24674- mod terautil;
24675- mod util;
24676-
24677- pub mod runtime {
24678- use crate::config::Config;
24679- use crate::web2::server;
24680- pub async fn serve(cfg: &Config) {
24681- server::serve(cfg).await.unwrap();
24682- }
24683- }
24684 diff --git a/src/web2/navigation.rs b/src/web2/navigation.rs
24685deleted file mode 100644
24686index ff3e58e..0000000
24687--- a/src/web2/navigation.rs
24688+++ /dev/null
24689 @@ -1,125 +0,0 @@
24690- pub type Items = Vec<(String, String, bool)>;
24691-
24692- pub fn global(current_page: &str, mail_visible: bool) -> Items {
24693- let mut nav: Items = vec![
24694- (
24695- String::from("about"),
24696- String::from("/about"),
24697- current_page == "about",
24698- ),
24699- (
24700- String::from("config"),
24701- String::from("/config"),
24702- current_page == "config",
24703- ),
24704- ];
24705- if mail_visible {
24706- nav.push((
24707- String::from("mail"),
24708- String::from("/mail"),
24709- current_page == "mail",
24710- ))
24711- }
24712- nav
24713- }
24714-
24715- pub fn primary(current_page: &str, collection: &str, name: &str) -> Items {
24716- vec![
24717- (
24718- String::from("authors"),
24719- format!("/{}/{}/authors", collection, name,),
24720- current_page == "authors",
24721- ),
24722- (
24723- String::from("charts"),
24724- format!("/{}/{}/charts", collection, name),
24725- current_page == "charts",
24726- ),
24727- (
24728- String::from("log"),
24729- format!("/{}/{}/log", collection, name),
24730- current_page == "log",
24731- ),
24732- (
24733- String::from("project"),
24734- format!("/{}/{}", collection, name),
24735- current_page == "project",
24736- ),
24737- (
24738- String::from("refs"),
24739- format!("/{}/{}/refs", collection, name),
24740- current_page == "refs",
24741- ),
24742- ]
24743- }
24744-
24745- pub fn subnav(
24746- current_page: &str,
24747- collection: &str,
24748- name: &str,
24749- file_path: &str,
24750- ref_name: &str,
24751- commit_id: &str,
24752- ) -> Items {
24753- vec![
24754- (
24755- String::from("blob"),
24756- format!("/{}/{}/blob/{}/{}", collection, name, ref_name, file_path,),
24757- current_page == "blob",
24758- ),
24759- (
24760- String::from("blame"),
24761- format!("/{}/{}/blame/{}/{}", collection, name, ref_name, file_path),
24762- current_page == "blame",
24763- ),
24764- (
24765- String::from("log"),
24766- format!("/{}/{}/log/{}/{}", collection, name, ref_name, file_path,),
24767- current_page == "log",
24768- ),
24769- (
24770- String::from("permalink"),
24771- format!(
24772- "/{}/{}/{}/{}/{}",
24773- collection, name, current_page, commit_id, file_path
24774- ),
24775- false,
24776- ),
24777- (
24778- String::from("raw"),
24779- format!("/{}/{}/raw/{}/{}", collection, name, ref_name, file_path),
24780- false,
24781- ),
24782- ]
24783- }
24784-
24785- pub fn chartnav(current_page: &str, collection: &str, name: &str, git_hash: Option<&str>) -> Items {
24786- let commit_path = git_hash.map_or(String::new(), |hash| String::from("/") + hash);
24787- vec![
24788- (
24789- String::from("activity"),
24790- format!("/{}/{}/chart/activity{}", collection, name, commit_path),
24791- current_page == "activity",
24792- ),
24793- (
24794- String::from("languages"),
24795- format!("/{}/{}/chart/languages{}", collection, name, commit_path),
24796- current_page == "languages",
24797- ),
24798- ]
24799- }
24800-
24801- pub fn revnav(current_page: &str, collection: &str, name: &str) -> Items {
24802- vec![
24803- (
24804- String::from("tags"),
24805- format!("/{}/{}/refs/tags", collection, name),
24806- current_page == "tags",
24807- ),
24808- (
24809- String::from("branches"),
24810- format!("/{}/{}/refs/branches", collection, name),
24811- current_page == "branches",
24812- ),
24813- ]
24814- }
24815 diff --git a/src/web2/routes/about.rs b/src/web2/routes/about.rs
24816deleted file mode 100644
24817index 86c3983..0000000
24818--- a/src/web2/routes/about.rs
24819+++ /dev/null
24820 @@ -1,32 +0,0 @@
24821- use comrak::{markdown_to_html_with_plugins, ComrakOptions, ComrakPlugins};
24822-
24823- use axum::{extract::Extension, response::Html};
24824-
24825- use crate::config::Config;
24826- use crate::web2::error::Error;
24827- use crate::web2::highlight::TreeSitterAdapter;
24828- use crate::web2::middleware::template::Template;
24829- use crate::web2::navigation;
24830-
24831- pub async fn serve(
24832- Extension(cfg): Extension<Config>,
24833- Extension(adapter): Extension<TreeSitterAdapter>,
24834- Extension((templates, mut ctx)): Extension<Template>,
24835- ) -> Result<Html<String>, Error> {
24836- ctx.insert("title", "about");
24837- ctx.insert(
24838- "nav_elements",
24839- &navigation::global("about", cfg.mail.is_some()),
24840- );
24841- let options = ComrakOptions::default();
24842- let mut plugins = ComrakPlugins::default();
24843- plugins.render.codefence_syntax_highlighter = Some(&adapter);
24844- let blurb = match &cfg.blurb {
24845- Some(blurb) => blurb.clone(),
24846- None => String::from(""),
24847- };
24848- let blurb = markdown_to_html_with_plugins(&blurb, &options, &plugins);
24849- ctx.insert("blurb", &blurb);
24850- let body = templates.render("about.html", &ctx)?;
24851- Ok(Html(body))
24852- }
24853 diff --git a/src/web2/routes/assets.rs b/src/web2/routes/assets.rs
24854deleted file mode 100644
24855index 976f19b..0000000
24856--- a/src/web2/routes/assets.rs
24857+++ /dev/null
24858 @@ -1,68 +0,0 @@
24859- use std::path::PathBuf;
24860-
24861- use axum::{body::Body, extract::Path, http::header, response::Response, Extension};
24862-
24863- use tokio::fs::File;
24864- use tokio_util::io::ReaderStream;
24865-
24866- use mime;
24867- use mime_guess;
24868-
24869- use crate::config::Config;
24870- use crate::web2::error::Error;
24871- use crate::web2::extractors::config::ConfigReader;
24872-
24873- pub async fn serve_css(
24874- Extension(system_config): Extension<Config>,
24875- ConfigReader(user_config): ConfigReader,
24876- ) -> Result<Response, Error> {
24877- let themes_path = &system_config.web.themes_path.clone();
24878- let mut file_path = PathBuf::from(themes_path);
24879- file_path.push(user_config.theme.unwrap_or(system_config.web.default_theme));
24880- file_path.push("main.min.css");
24881- let file = tokio::fs::File::open(file_path).await?;
24882- let stream = ReaderStream::new(file);
24883- Ok(Response::builder()
24884- .header(header::CONTENT_TYPE, mime::TEXT_CSS.as_ref())
24885- .body(Body::from_stream(stream))
24886- .unwrap())
24887- }
24888-
24889- pub async fn serve_asset(
24890- Extension(system_config): Extension<Config>,
24891- ConfigReader(user_config): ConfigReader,
24892- Path(asset): Path<String>,
24893- ) -> Result<Response, Error> {
24894- let themes_path = &system_config.web.themes_path.clone();
24895- let mut file_path = PathBuf::from(themes_path);
24896- file_path.push(user_config.theme.unwrap_or(system_config.web.default_theme));
24897- file_path.push("assets");
24898- file_path.push(&asset);
24899- let file_path = file_path.to_str().unwrap();
24900- let file = {
24901- match File::open(file_path).await {
24902- Ok(file) => {
24903- log::debug!("loaded asset from: {}", file_path);
24904- file
24905- }
24906- Err(_) => {
24907- let mut fallback_path = PathBuf::from(themes_path);
24908- fallback_path.push(system_config.web.base_theme.clone());
24909- fallback_path.push("assets");
24910- fallback_path.push(&asset);
24911- let fallback_path = fallback_path.to_str().unwrap();
24912- let file = File::open(fallback_path).await?;
24913- log::debug!("loaded asset from: {}", fallback_path);
24914- file
24915- }
24916- }
24917- };
24918- let stream = ReaderStream::new(file);
24919- let mime = mime_guess::from_path(file_path)
24920- .first_or_octet_stream()
24921- .to_string();
24922- Ok(Response::builder()
24923- .header("Content-Type", mime)
24924- .body(Body::from_stream(stream))
24925- .unwrap())
24926- }
24927 diff --git a/src/web2/routes/authors.rs b/src/web2/routes/authors.rs
24928deleted file mode 100644
24929index 2c3a5a4..0000000
24930--- a/src/web2/routes/authors.rs
24931+++ /dev/null
24932 @@ -1,32 +0,0 @@
24933- use std::sync::Arc;
24934-
24935- use axum::{extract::Extension, response::Html};
24936-
24937- use crate::database_ext::contributors::ContributorsExt;
24938- use crate::web2::error::Error;
24939- use crate::web2::middleware::repository::Preamble;
24940- use crate::web2::middleware::template::Template;
24941- use crate::web2::navigation;
24942- use ayllu_database::Wrapper as Database;
24943-
24944- pub async fn serve(
24945- Extension(db): Extension<Arc<Database>>,
24946- Extension(preamble): Extension<Preamble>,
24947- Extension((templates, mut ctx)): Extension<Template>,
24948- ) -> Result<Html<String>, Error> {
24949- // TODO: move to extension
24950- let authors = db
24951- .authors_list(
24952- preamble.repo_path_string().as_str(),
24953- preamble.latest_commit_id.clone().unwrap().as_str(),
24954- )
24955- .await?;
24956- ctx.insert("title", &format!("{} Authors", preamble.repo_name.clone()));
24957- ctx.insert(
24958- "nav_elements",
24959- &navigation::primary("authors", &preamble.collection_name, &preamble.repo_name),
24960- );
24961- ctx.insert("authors", &authors);
24962- let body = templates.render("authors.html", &ctx)?;
24963- Ok(Html(body))
24964- }
24965 diff --git a/src/web2/routes/blame.rs b/src/web2/routes/blame.rs
24966deleted file mode 100644
24967index 4ff451b..0000000
24968--- a/src/web2/routes/blame.rs
24969+++ /dev/null
24970 @@ -1,84 +0,0 @@
24971- use serde::Serialize;
24972-
24973- use axum::{extract::Extension, response::Html};
24974-
24975- use crate::config::Config;
24976- use crate::time as timeutil;
24977- use crate::web2::error::Error;
24978- use crate::web2::highlight::Highlighter;
24979- use crate::web2::middleware::repository::Preamble;
24980- use crate::web2::middleware::template::Template;
24981- use crate::web2::navigation;
24982- use ayllu_git::Wrapper;
24983-
24984- #[derive(Serialize, Default, Clone)]
24985- struct BlameItem {
24986- start: usize,
24987- end: usize,
24988- author_name: String,
24989- author_email: String,
24990- timestamp: String,
24991- commit_id: String,
24992- }
24993-
24994- pub async fn serve(
24995- Extension(_cfg): Extension<Config>,
24996- Extension(preamble): Extension<Preamble>,
24997- Extension(highlighter): Extension<Highlighter>,
24998- Extension((templates, mut ctx)): Extension<Template>,
24999- ) -> Result<Html<String>, Error> {
25000- let repository = Wrapper::new(preamble.repo_path.as_path())?;
25001- let title = format!("blame: {}", preamble.file_name());
25002- ctx.insert("title", &title);
25003- ctx.insert(
25004- "nav_elements",
25005- &navigation::primary("", &preamble.collection_name, &preamble.repo_name),
25006- );
25007- ctx.insert("file_name", &preamble.file_name());
25008- ctx.insert(
25009- "subnav_elements",
25010- &navigation::subnav(
25011- "blame",
25012- &preamble.collection_name,
25013- &preamble.repo_name,
25014- &preamble.file_path_string(),
25015- &preamble.refname,
25016- &preamble.latest_commit_id.clone().unwrap_or(String::new()),
25017- ),
25018- );
25019- ctx.insert("subtitle", "blame");
25020- let file_path = preamble.file_path.clone().unwrap();
25021- if let Some(blob) = repository.blob(file_path.as_path(), preamble.latest_commit_id.clone())? {
25022- if blob.is_binary {
25023- ctx.insert("is_renderable", &false);
25024- } else {
25025- let content = highlighter.highlight_vec(
25026- blob.content,
25027- Some(file_path.to_str().unwrap()),
25028- None,
25029- None,
25030- false,
25031- );
25032- ctx.insert("is_renderable", &true);
25033- ctx.insert("content", &content);
25034- let lines = repository.blame(
25035- preamble.file_path.clone().unwrap().as_path(),
25036- preamble.latest_commit_id.clone(),
25037- )?;
25038- let lines: Vec<BlameItem> = lines
25039- .iter()
25040- .map(|item| BlameItem {
25041- start: item.start,
25042- end: item.end,
25043- author_name: item.author_name.clone(),
25044- author_email: item.author_email.clone(),
25045- timestamp: timeutil::friendly(item.timestamp as u64),
25046- commit_id: item.commit_id.clone(),
25047- })
25048- .collect();
25049- ctx.insert("blame_lines", &lines);
25050- }
25051- }
25052- let body = templates.render("blame.html", &ctx)?;
25053- Ok(Html(body))
25054- }
25055 diff --git a/src/web2/routes/blob.rs b/src/web2/routes/blob.rs
25056deleted file mode 100644
25057index 0f7d68b..0000000
25058--- a/src/web2/routes/blob.rs
25059+++ /dev/null
25060 @@ -1,154 +0,0 @@
25061- use std::time::SystemTime;
25062-
25063- use comrak::{markdown_to_html_with_plugins, ComrakPlugins};
25064-
25065- use axum::{
25066- body::Bytes,
25067- extract::Extension,
25068- http::{header, HeaderValue},
25069- response::{Html, IntoResponse, Redirect, Response},
25070- };
25071-
25072- use crate::config::Config;
25073- use crate::languages::LANGUAGE_TABLE;
25074- use crate::web2::error::Error;
25075- use crate::web2::highlight::{Highlighter, TreeSitterAdapter};
25076- use crate::web2::middleware::repository::Preamble;
25077- use crate::web2::middleware::template::Template;
25078- use crate::web2::navigation;
25079- use crate::web2::util;
25080- use ayllu_git::Wrapper;
25081-
25082- // TODO: considerable clean up is needed here
25083- pub async fn serve(
25084- Extension(cfg): Extension<Config>,
25085- Extension(preamble): Extension<Preamble>,
25086- Extension(highlighter): Extension<Highlighter>,
25087- Extension(adapter): Extension<TreeSitterAdapter>,
25088- Extension((templates, mut ctx)): Extension<Template>,
25089- ) -> Result<Html<String>, Error> {
25090- let repository = Wrapper::new(preamble.repo_path.as_path())?;
25091- let blob = repository.blob(
25092- preamble.file_path.clone().unwrap().as_path(),
25093- preamble.latest_commit_id.clone(),
25094- )?;
25095- if blob.is_none() {
25096- return Err(Error::NotFound(String::from("Not Found")));
25097- }
25098- let blob = blob.unwrap();
25099- ctx.insert("title", &preamble.file_name());
25100- ctx.insert("file_name", &preamble.file_name());
25101- ctx.insert("blob", &blob.drop_content());
25102- ctx.insert("file_size", &blob.size);
25103- ctx.insert("file_mode", &blob.mode);
25104- let mut is_markdown = false;
25105- let mime_type = mime_guess::from_path(preamble.file_path_string()).first_or_octet_stream();
25106- ctx.insert("mime_type", &mime_type.to_string());
25107- ctx.insert("subtitle", "blob");
25108- log::debug!("rendering blob with mime type: {}", mime_type.to_string());
25109- if mime_type.type_() == "image" {
25110- ctx.insert("is_image", &true);
25111- ctx.insert("is_binary", &true);
25112- ctx.insert("content", &None::<()>);
25113- } else if mime_type.type_() == "video" {
25114- ctx.insert("is_video", &true);
25115- ctx.insert("is_binary", &true);
25116- ctx.insert("content", &None::<()>);
25117- } else if mime_type.type_() == "audio" {
25118- ctx.insert("is_audio", &true);
25119- ctx.insert("is_binary", &true);
25120- ctx.insert("content", &None::<()>);
25121- } else if mime_type.to_string().as_str() == "text/markdown" {
25122- let mut plugins = ComrakPlugins::default();
25123- plugins.render.codefence_syntax_highlighter = Some(&adapter);
25124- let content = String::from_utf8(blob.content).unwrap();
25125- let content = markdown_to_html_with_plugins(
25126- content.as_str(),
25127- &cfg.markdown_render_options(),
25128- &plugins,
25129- );
25130- ctx.insert("content", &content);
25131- is_markdown = true;
25132- } else if blob.is_binary {
25133- ctx.insert("content", &None::<()>);
25134- } else {
25135- let content = String::from_utf8(blob.content).unwrap();
25136- let file_path = preamble.file_path_string();
25137- let (hint, content) =
25138- highlighter.highlight(&content, Some(file_path.as_str()), None, None, true);
25139- if let Some(hint) = hint {
25140- ctx.insert("color", &LANGUAGE_TABLE.color_from_language(&hint));
25141- ctx.insert("hint", &hint);
25142- }
25143- ctx.insert("content", &content);
25144- }
25145- ctx.insert(
25146- "nav_elements",
25147- &navigation::primary("blob", &preamble.collection_name, &preamble.repo_name),
25148- );
25149- let subnav = navigation::subnav(
25150- "blob",
25151- &preamble.collection_name,
25152- &preamble.repo_name,
25153- &preamble.file_path_string(),
25154- &preamble.refname,
25155- &preamble.latest_commit_id.clone().unwrap_or(String::new()),
25156- );
25157- ctx.insert("raw_url", &subnav.get(4).unwrap().1);
25158- ctx.insert("subnav_elements", &subnav);
25159- ctx.insert("file_type", &mime_type.to_string());
25160- ctx.insert("is_markdown", &is_markdown);
25161- let end = SystemTime::now();
25162- let duration = end.duration_since(preamble.start_time).unwrap();
25163- ctx.insert("render_time", &duration.as_millis());
25164- let body = templates.render("blob.html", &ctx)?;
25165- Ok(Html(body))
25166- }
25167-
25168- pub async fn serve_raw(
25169- Extension(cfg): Extension<Config>,
25170- Extension(preamble): Extension<Preamble>,
25171- ) -> Result<Response, Error> {
25172- let repository = Wrapper::new(preamble.repo_path.as_path())?;
25173- let blob = repository.blob(
25174- preamble.file_path.clone().unwrap().as_path(),
25175- preamble.latest_commit_id.clone(),
25176- )?;
25177- if blob.is_none() {
25178- return Err(Error::NotFound(format!(
25179- "{} not found",
25180- preamble.file_path_string()
25181- )));
25182- }
25183- let blob = blob.unwrap();
25184- if blob.is_pointer {
25185- let lfs_config = cfg.lfs.clone().unwrap();
25186- let location = util::lfs_url(
25187- lfs_config.url_template.as_str(),
25188- preamble.collection_name.as_str(),
25189- preamble.repo_name.as_str(),
25190- blob.oid.as_str(),
25191- );
25192- log::debug!("redirecting blob to LFS server: {}", location);
25193- Ok(Redirect::permanent(&location).into_response())
25194- } else {
25195- let mime = if blob.is_binary {
25196- mime_guess::from_path(preamble.file_path_string())
25197- .first_or_octet_stream()
25198- .to_string()
25199- } else {
25200- mime_guess::mime::TEXT_PLAIN.to_string()
25201- };
25202- log::debug!(
25203- "serving blob {} with mime {}",
25204- preamble.file_path_string(),
25205- mime.to_string()
25206- );
25207- let mut response = Bytes::from(blob.content.clone()).into_response();
25208- response.headers_mut().insert(
25209- header::CONTENT_TYPE,
25210- HeaderValue::from_str(mime.as_ref()).unwrap(),
25211- );
25212- Ok(response)
25213- }
25214- }
25215 diff --git a/src/web2/routes/builds.rs b/src/web2/routes/builds.rs
25216deleted file mode 100644
25217index ac7fe86..0000000
25218--- a/src/web2/routes/builds.rs
25219+++ /dev/null
25220 @@ -1,91 +0,0 @@
25221- use axum::{
25222- body::Bytes,
25223- extract::Extension,
25224- http::header::{HeaderValue, CONTENT_TYPE},
25225- response::{Html, IntoResponse, Response},
25226- };
25227- use serde::Serialize;
25228-
25229- use crate::config::Config;
25230- use crate::web2::error::Error;
25231-
25232- use crate::web2::middleware::template::Template;
25233-
25234-
25235- #[derive(Debug, Serialize)]
25236- struct Build {
25237- id: String,
25238- timestamp: String,
25239- runtime: f64,
25240- success: bool,
25241- }
25242-
25243- #[derive(Debug, Serialize)]
25244- struct Details {
25245- build: Build,
25246- steps: Vec<(String, String, String)>,
25247- }
25248-
25249- pub async fn serve(
25250- Extension(_cfg): Extension<Config>,
25251- Extension((templates, mut ctx)): Extension<Template>,
25252- ) -> Result<Html<String>, Error> {
25253- ctx.insert("title", "builds");
25254- // TODO
25255- let builds = vec![
25256- Build {
25257- id: String::from("1234"),
25258- timestamp: String::from("1999-01-02"),
25259- runtime: 25.0,
25260- success: true,
25261- },
25262- Build {
25263- id: String::from("4567"),
25264- timestamp: String::from("1999-02-01"),
25265- runtime: 29.0,
25266- success: false,
25267- },
25268- ];
25269- ctx.insert("builds", &builds);
25270- let body = templates.render("builds.html", &ctx)?;
25271- Ok(Html(body))
25272- }
25273-
25274- pub async fn details(
25275- Extension(_cfg): Extension<Config>,
25276- Extension((templates, mut ctx)): Extension<Template>,
25277- ) -> Result<Html<String>, Error> {
25278- ctx.insert("title", "builds");
25279- ctx.insert(
25280- "build",
25281- &Build {
25282- id: String::from("1234"),
25283- timestamp: String::from("1999-01-01"),
25284- runtime: 25.0,
25285- success: true,
25286- },
25287- );
25288- let build_steps: Vec<(String, String, String)> = vec![(
25289- String::from("phase_1"),
25290- String::from("stdout\n\nfuu\nbar\n"),
25291- String::from("stderr\n\n\nerror\n"),
25292- )];
25293- ctx.insert("steps", &build_steps);
25294- let body = templates.render("build.html", &ctx)?;
25295- Ok(Html(body))
25296- }
25297-
25298- pub async fn badge(
25299- Extension(_cfg): Extension<Config>,
25300- Extension((templates, mut ctx)): Extension<Template>,
25301- ) -> Result<Response, Error> {
25302- ctx.insert("key", "build");
25303- ctx.insert("value", "success");
25304- let body = templates.render("badge.svg", &ctx)?;
25305- let mut response = Bytes::from(body).into_response();
25306- response.headers_mut().insert(
25307- CONTENT_TYPE,
25308- HeaderValue::from_str("image/svg+xml").unwrap(),
25309- );
25310- Ok(response)
25311- }
25312 diff --git a/src/web2/routes/chart.rs b/src/web2/routes/chart.rs
25313deleted file mode 100644
25314index 3a60b2b..0000000
25315--- a/src/web2/routes/chart.rs
25316+++ /dev/null
25317 @@ -1,113 +0,0 @@
25318- use std::sync::Arc;
25319-
25320- use axum::{
25321- extract::{Extension, Path},
25322- response::Html,
25323- };
25324- use serde::Deserialize;
25325- use time::Duration;
25326-
25327- use crate::database_ext::{
25328- langauges::LanguagesExt,
25329- stats::{Aggregation, StatsExt},
25330- };
25331- use crate::web2::charts::Renderer;
25332- use crate::web2::error::Error;
25333- use crate::web2::middleware::repository::Preamble;
25334- use crate::web2::middleware::template::Template;
25335- use crate::web2::navigation;
25336- use ayllu_database::Wrapper as Database;
25337-
25338- const MAXIMUM_SIZE: u32 = 2000;
25339- const DEFAULT_HEIGHT: u32 = 450;
25340- const DEFAULT_WIDTH: u32 = 600;
25341-
25342- pub const EMPTY_IMAGE: &str = r#"
25343- <?xml version="1.0" encoding="UTF-8"?><svg xmlns="http://www.w3.org/2000/svg" width="1" height="1"/>
25344- "#;
25345-
25346- #[derive(Deserialize)]
25347- pub struct Params {
25348- pub commit_id: Option<String>,
25349- pub kind: Option<String>,
25350- pub height: Option<u32>,
25351- pub width: Option<u32>,
25352- }
25353-
25354- pub async fn serve(
25355- Path(params): Path<Params>,
25356- Extension(db): Extension<Arc<Database>>,
25357- Extension(preamble): Extension<Preamble>,
25358- Extension((templates, mut ctx)): Extension<Template>,
25359- ) -> Result<Html<String>, Error> {
25360- let git_hash = match params.commit_id.clone() {
25361- Some(git_hash) => Some(git_hash),
25362- None => preamble.latest_commit_id.clone(),
25363- };
25364-
25365- let chart_kind = params.kind.unwrap_or(String::from("activity"));
25366- let width = params.width.map_or(DEFAULT_WIDTH, |width| {
25367- if width > MAXIMUM_SIZE {
25368- DEFAULT_WIDTH
25369- } else {
25370- width
25371- }
25372- });
25373- let height = params.height.map_or(DEFAULT_HEIGHT, |height| {
25374- if height > MAXIMUM_SIZE {
25375- DEFAULT_HEIGHT
25376- } else {
25377- height
25378- }
25379- });
25380-
25381- ctx.insert(
25382- "nav_elements",
25383- &navigation::primary("charts", &preamble.collection_name, &preamble.repo_name),
25384- );
25385- ctx.insert(
25386- "chartnav",
25387- &navigation::chartnav(
25388- &chart_kind,
25389- &preamble.collection_name,
25390- &preamble.repo_name,
25391- params.commit_id.as_deref(),
25392- ),
25393- );
25394- if let Some(git_hash) = git_hash {
25395- // DB based operations
25396- // TODO: move to extension
25397- let chart_builder = Renderer::new((width, height));
25398- let xml = match chart_kind.as_str() {
25399- "activity" => {
25400- ctx.insert("title", "Activity");
25401- ctx.insert("chart_title", "Activity");
25402- let data = db
25403- .contribution_buckets_for_repo(
25404- &preamble.repo_path_string(),
25405- &git_hash,
25406- Aggregation::Day,
25407- 90 * Duration::DAY,
25408- )
25409- .await?;
25410- chart_builder.activity(data)?
25411- }
25412- "languages" => {
25413- ctx.insert("title", "Languages");
25414- ctx.insert("chart_title", "Language Composition");
25415- let data = db
25416- .languages_list(&preamble.repo_path_string(), &git_hash)
25417- .await?;
25418- chart_builder.languages(data)?
25419- }
25420- name => return Err(Error::Message(format!("unknown chart kind: {}", name))),
25421- };
25422- ctx.insert("chart", &xml);
25423- } else {
25424- ctx.insert("title", "empty");
25425- ctx.insert("chart_title", "empty");
25426- ctx.insert("chart", EMPTY_IMAGE);
25427- };
25428- let body = templates.render("chart.html", &ctx)?;
25429- Ok(Html(body))
25430- }
25431 diff --git a/src/web2/routes/commit.rs b/src/web2/routes/commit.rs
25432deleted file mode 100644
25433index 95f730a..0000000
25434--- a/src/web2/routes/commit.rs
25435+++ /dev/null
25436 @@ -1,38 +0,0 @@
25437- use axum::{
25438- extract::{Extension, Path},
25439- response::Html,
25440- };
25441-
25442- use crate::web2::error::Error;
25443- use crate::web2::highlight::Highlighter;
25444- use crate::web2::middleware::repository::Preamble;
25445- use crate::web2::middleware::template::Template;
25446- use crate::web2::navigation;
25447- use ayllu_git::Wrapper;
25448-
25449- pub async fn serve(
25450- Path((_, _, commit_id)): Path<(String, String, String)>,
25451- Extension(preamble): Extension<Preamble>,
25452- Extension(highlighter): Extension<Highlighter>,
25453- Extension((templates, mut ctx)): Extension<Template>,
25454- ) -> Result<Html<String>, Error> {
25455- let repository = Wrapper::new(preamble.repo_path.as_path())?;
25456- let title = format!("commit {}", commit_id);
25457- ctx.insert("title", &title);
25458- ctx.insert(
25459- "nav_elements",
25460- &navigation::primary("", &preamble.collection_name, &preamble.repo_name),
25461- );
25462- ctx.insert("commit_hash", &commit_id);
25463- let commit = repository.commit(Some(commit_id.to_string()))?.unwrap();
25464- let (stats, diff) = repository.diff(&commit_id)?;
25465- ctx.insert("commit", &commit);
25466- ctx.insert("stats", &stats);
25467- ctx.insert("distinct_author", &commit.distinct_author());
25468- ctx.insert("extended_commit_message", &commit.extended_commit_message());
25469- let formatted_diff =
25470- highlighter.highlight(diff.as_str(), None, None, Some("diff".into()), true);
25471- ctx.insert("diff", &formatted_diff);
25472- let body = templates.render("commit.html", &ctx)?;
25473- Ok(Html(body))
25474- }
25475 diff --git a/src/web2/routes/config.rs b/src/web2/routes/config.rs
25476deleted file mode 100644
25477index 85d7184..0000000
25478--- a/src/web2/routes/config.rs
25479+++ /dev/null
25480 @@ -1,47 +0,0 @@
25481- use axum::{
25482- extract::{Extension, Form},
25483- response::{Html, Redirect},
25484- };
25485-
25486- use axum_extra::extract::cookie::{Cookie, CookieJar};
25487-
25488- use crate::config::Config as SystemConfig;
25489- use crate::web2::config::Config;
25490- use crate::web2::error::Error;
25491- use crate::web2::extractors::config::ConfigReader;
25492- use crate::web2::middleware::template::Template;
25493- use crate::web2::navigation;
25494-
25495- pub async fn serve(
25496- Extension(cfg): Extension<SystemConfig>,
25497- ConfigReader(client_config): ConfigReader,
25498- Extension((templates, mut ctx)): Extension<Template>,
25499- ) -> Result<Html<String>, Error> {
25500- ctx.insert("title", "config");
25501- ctx.insert("config", &client_config);
25502- ctx.insert("themes", &cfg.web.themes);
25503- ctx.insert(
25504- "nav_elements",
25505- &navigation::global("config", cfg.mail.is_some()),
25506- );
25507- let body = templates.render("config.html", &ctx)?;
25508- Ok(Html(body))
25509- }
25510-
25511- pub async fn update(
25512- jar: CookieJar,
25513- Extension(cfg): Extension<SystemConfig>,
25514- Form(updates): Form<Config>,
25515- ) -> Result<(CookieJar, Redirect), Error> {
25516- Ok((
25517- jar.add(Cookie::new(
25518- "theme",
25519- updates.theme.unwrap_or(cfg.web.default_theme),
25520- ))
25521- .add(Cookie::new(
25522- "items-per-page",
25523- updates.items_per_page.to_string(),
25524- )),
25525- Redirect::to("/config"),
25526- ))
25527- }
25528 diff --git a/src/web2/routes/finger.rs b/src/web2/routes/finger.rs
25529deleted file mode 100644
25530index ab8c4d1..0000000
25531--- a/src/web2/routes/finger.rs
25532+++ /dev/null
25533 @@ -1,93 +0,0 @@
25534- use axum::{
25535- extract::{Extension, Query},
25536- http::StatusCode,
25537- response::IntoResponse,
25538- response::Json,
25539- response::Response,
25540- };
25541- use serde::Deserialize;
25542- use webfinger::{Link, Prefix, Resolver, ResolverError, Webfinger};
25543-
25544- use std::sync::Arc;
25545-
25546- use crate::config::Config;
25547- // use crate::web2::error::Error;
25548-
25549- #[derive(Clone)]
25550- pub struct CResolver {
25551- pub domain: &'static str,
25552- pub config: Arc<Config>,
25553- }
25554-
25555- #[derive(Deserialize, Clone)]
25556- pub struct FingerParams {
25557- pub resource: String,
25558- }
25559-
25560- pub struct Error(ResolverError);
25561-
25562- impl IntoResponse for Error {
25563- fn into_response(self) -> Response {
25564- let status_code = match self.0 {
25565- ResolverError::InvalidResource => StatusCode::INTERNAL_SERVER_ERROR,
25566- ResolverError::WrongDomain => StatusCode::INTERNAL_SERVER_ERROR,
25567- ResolverError::NotFound => StatusCode::NOT_FOUND,
25568- };
25569- (status_code, format!("resolver: {:?}", self.0)).into_response()
25570- }
25571- }
25572-
25573- impl Resolver<()> for CResolver {
25574- fn instance_domain<'a>(&self) -> &'a str {
25575- self.domain
25576- }
25577-
25578- fn find(
25579- &self,
25580- prefix: Prefix,
25581- acct: String,
25582- _resource_repo: (),
25583- ) -> Result<Webfinger, ResolverError> {
25584- match prefix {
25585- Prefix::Acct => {
25586- // TODO: expand this to use in addition to the static config
25587- // objects also "author repositories" where each author has a
25588- // repo that contains a static json response for this.
25589- let author = self
25590- .config
25591- .authors
25592- .iter()
25593- .find(|author| author.email == acct);
25594- match author {
25595- Some(author) => Ok(Webfinger {
25596- subject: acct.clone(),
25597- aliases: vec![acct.clone()],
25598- links: author
25599- .links
25600- .iter()
25601- .map(|link| Link {
25602- rel: link.rel.clone(),
25603- href: link.href.clone(),
25604- template: link.template.clone(),
25605- mime_type: link.mime_template.clone(),
25606- })
25607- .collect(),
25608- }),
25609- None => Err(ResolverError::NotFound),
25610- }
25611- }
25612- Prefix::Group => Err(ResolverError::InvalidResource),
25613- Prefix::Custom(_) => Err(ResolverError::InvalidResource),
25614- }
25615- }
25616- }
25617-
25618- pub async fn serve(
25619- Extension(resolver): Extension<CResolver>,
25620- Query(params): Query<FingerParams>,
25621- ) -> Result<Json<Webfinger>, Error> {
25622- match resolver.find(webfinger::Prefix::Acct, params.resource, ()) {
25623- Ok(result) => Ok(Json(result)),
25624- Err(e) => Err(Error(e)),
25625- }
25626- }
25627 diff --git a/src/web2/routes/index.rs b/src/web2/routes/index.rs
25628deleted file mode 100644
25629index ca1cf86..0000000
25630--- a/src/web2/routes/index.rs
25631+++ /dev/null
25632 @@ -1,134 +0,0 @@
25633- use std::convert::From;
25634- use std::sync::Arc;
25635-
25636- use serde::Serialize;
25637-
25638- use axum::{
25639- extract::{Extension, Path},
25640- response::Html,
25641- };
25642-
25643- use crate::config::Config;
25644- use crate::database_ext::stats::{Aggregation, StatsExt};
25645- use crate::time as timeutil;
25646- use crate::web2::charts;
25647- use crate::web2::error::Error;
25648- use crate::web2::middleware::template::Template;
25649- use crate::web2::navigation;
25650- use ayllu_database::Wrapper as Database;
25651- use ayllu_git::{Scanner, Wrapper};
25652-
25653- #[derive(Debug, Serialize)]
25654- struct Collection {
25655- name: String,
25656- description: Option<String>,
25657- repositories: Vec<Repository>,
25658- }
25659-
25660- #[derive(Debug, Serialize)]
25661- struct Repository {
25662- name: String,
25663- description: String,
25664- timestamp: Option<u64>,
25665- age: String,
25666- activity: String,
25667- }
25668-
25669- async fn load_repositories(collection_path: &str, db: &Database) -> Result<Vec<Repository>, Error> {
25670- let mut repositories: Vec<Repository> = Vec::new();
25671- let scanner = Scanner::from_path(collection_path)?;
25672- for repo_path in scanner {
25673- let repository = Wrapper::new(repo_path.as_path())?;
25674- let git_config = repository.config()?;
25675- // skip any repository that is marked "hidden"
25676- if git_config.hidden.is_some_and(|hidden| hidden) {
25677- continue;
25678- }
25679- let latest_hash = repository.latest_hash()?;
25680- let name = repo_path.file_name().unwrap();
25681- let name = String::from(name.to_str().unwrap());
25682- let description = git_config
25683- .description
25684- .unwrap_or(String::from("no description present"));
25685- let timestamp = repository.last_modified()?;
25686- let age = timestamp.map_or(String::from("?"), timeutil::friendly);
25687- // TODO move to plugin based system
25688- let db_activity: Vec<(i64, i64, i64)> = match latest_hash {
25689- Some(hash) => {
25690- db.contribution_buckets_for_repo(
25691- repo_path.to_str().unwrap(),
25692- hash.as_str(),
25693- Aggregation::Day,
25694- time::Duration::DAY * 90,
25695- )
25696- .await?
25697- }
25698- None => Vec::new(),
25699- };
25700- let activity = charts::spark_lines(db_activity.iter().map(|item| item.0).collect(), 7);
25701- repositories.push(Repository {
25702- name,
25703- description,
25704- timestamp,
25705- age,
25706- activity,
25707- });
25708- }
25709- repositories.sort_by(|a, b| {
25710- let first = a.timestamp.unwrap_or(0);
25711- let second = b.timestamp.unwrap_or(0);
25712- second.cmp(&first)
25713- });
25714- Ok(repositories)
25715- }
25716-
25717- pub async fn index(
25718- Extension(db): Extension<Arc<Database>>,
25719- Extension(cfg): Extension<Config>,
25720- Extension((templates, mut ctx)): Extension<Template>,
25721- ) -> Result<Html<String>, Error> {
25722- // database::test_cache(config.database.as_str()).await?;
25723- ctx.insert("title", "repository listing");
25724- let mut collections: Vec<Collection> = Vec::new();
25725- for collection in cfg.collections.iter() {
25726- if collection.hidden.is_some_and(|hidden| hidden) {
25727- continue;
25728- };
25729- let repositories = load_repositories(collection.path.as_str(), &db).await?;
25730- collections.push(Collection {
25731- name: collection.name.clone(),
25732- description: collection.description.clone(),
25733- repositories,
25734- });
25735- }
25736- ctx.insert("title", "ayllu");
25737- ctx.insert("collections", &collections);
25738- ctx.insert("nav_elements", &navigation::global("", cfg.mail.is_some()));
25739- let body = templates.render("index.html", &ctx)?;
25740- Ok(Html(body))
25741- }
25742-
25743- pub async fn collection(
25744- Extension(db): Extension<Arc<Database>>,
25745- Extension(cfg): Extension<Config>,
25746- collection: Option<Path<String>>,
25747- Extension((templates, mut ctx)): Extension<Template>,
25748- ) -> Result<Html<String>, Error> {
25749- let entry = cfg.collections.iter().find(|entry| {
25750- collection
25751- .as_ref()
25752- .is_some_and(|collection| entry.name == collection.0)
25753- });
25754- if entry.is_none() {
25755- return Err(Error::NotFound("collection not found".to_string()));
25756- }
25757- let entry = entry.unwrap();
25758- let repositories = load_repositories(entry.path.as_str(), &db).await?;
25759- ctx.insert("title", "ayllu");
25760- ctx.insert("nav_elements", &navigation::global("", cfg.mail.is_some()));
25761- ctx.insert("collection", &entry.clone());
25762- ctx.insert("repositories", &repositories);
25763- ctx.insert("is_hidden", &entry.hidden.is_some_and(|x| x));
25764- let body = templates.render("collection.html", &ctx)?;
25765- Ok(Html(body))
25766- }
25767 diff --git a/src/web2/routes/log.rs b/src/web2/routes/log.rs
25768deleted file mode 100644
25769index 3cdf57d..0000000
25770--- a/src/web2/routes/log.rs
25771+++ /dev/null
25772 @@ -1,75 +0,0 @@
25773- use std::collections::HashMap;
25774-
25775- use axum::{
25776- debug_handler,
25777- extract::{Extension, Query},
25778- response::Html,
25779- };
25780-
25781- use crate::web2::error::Error;
25782- use crate::web2::extractors::config::ConfigReader;
25783- use crate::web2::middleware::repository::Preamble;
25784- use crate::web2::middleware::template::Template;
25785- use crate::web2::navigation;
25786- use ayllu_git::{Selector, Wrapper};
25787-
25788- #[debug_handler]
25789- pub async fn serve(
25790- Extension(preamble): Extension<Preamble>,
25791- Extension((templates, mut ctx)): Extension<Template>,
25792- ConfigReader(config): ConfigReader,
25793- params: Option<Query<HashMap<String, String>>>,
25794- ) -> Result<Html<String>, Error> {
25795- let repository = Wrapper::new(preamble.repo_path.as_path())?;
25796- ctx.insert(
25797- "nav_elements",
25798- &navigation::primary("log", &preamble.collection_name, &preamble.repo_name),
25799- );
25800- ctx.insert("file_view", &false);
25801- match preamble.file_path.clone() {
25802- Some(file_path) => {
25803- let title = format!("log: {}", preamble.file_name());
25804- ctx.insert("title", &title);
25805- ctx.insert("subtitle", &title);
25806- ctx.insert("file_view", &true);
25807- ctx.insert(
25808- "file_name",
25809- &file_path
25810- .file_name()
25811- .map_or(String::new(), |item| item.to_str().unwrap().to_string()),
25812- );
25813- ctx.insert(
25814- "subnav_elements",
25815- &navigation::subnav(
25816- "log",
25817- &preamble.collection_name,
25818- &preamble.repo_name,
25819- &preamble.file_path_string(),
25820- &preamble.refname,
25821- &preamble.latest_commit_id.clone().unwrap_or(String::new()),
25822- ),
25823- );
25824- let commits =
25825- repository.log_path(file_path.as_path(), preamble.latest_commit_id.clone())?;
25826- ctx.insert("commits", &commits);
25827- }
25828- None => {
25829- let title = format!("{} log", preamble.repo_name.clone());
25830- ctx.insert("title", &title);
25831- let commits = repository.log(Selector {
25832- commit: preamble.latest_commit_id.clone(),
25833- username: params
25834- .clone()
25835- .and_then(|params| params.get("username").cloned()),
25836- email: params
25837- .clone()
25838- .and_then(|params| params.get("email").cloned()),
25839- limit: Some(config.items_per_page as i64),
25840- })?;
25841- ctx.insert("commits", &commits);
25842- ctx.insert("has_more", &(commits.len() == config.items_per_page));
25843- }
25844- }
25845- let body = templates.render("log.html", &ctx)?;
25846- Ok(Html(body))
25847- }
25848 diff --git a/src/web2/routes/mail.rs b/src/web2/routes/mail.rs
25849deleted file mode 100644
25850index e6a4977..0000000
25851--- a/src/web2/routes/mail.rs
25852+++ /dev/null
25853 @@ -1,330 +0,0 @@
25854- use axum::{
25855- body::Bytes,
25856- debug_handler,
25857- extract::{Extension, Path},
25858- http::header::CONTENT_TYPE,
25859- response::{Html, IntoResponse, Response},
25860- };
25861- use serde::{Deserialize, Serialize};
25862-
25863- use crate::languages::Hint;
25864- use crate::web2::error::Error;
25865- use crate::web2::highlight::Highlighter;
25866- use crate::web2::middleware::rpc_initiator::{Initiator, Kind as InitiatorKind};
25867- use crate::web2::middleware::template::Template;
25868- use crate::web2::navigation;
25869- use ayllu_api::mail_capnp::server::Client as MailClient;
25870- use ayllu_rpc::Client;
25871-
25872- #[derive(Deserialize)]
25873- pub struct Params {
25874- pub list_id: String,
25875- pub thread_id: Option<String>,
25876- pub message_id: Option<String>,
25877- }
25878-
25879- #[derive(Debug, Serialize, Default)]
25880- struct Thread {
25881- pub id: i64,
25882- pub message_id: String,
25883- pub from: String,
25884- pub subject: String,
25885- pub n_replies: i64,
25886- pub timestamp: i64,
25887- }
25888-
25889- #[derive(Debug, Serialize, Default)]
25890- struct Message {
25891- pub id: String,
25892- pub message_id: String,
25893- pub subject: String,
25894- pub created_at: i64,
25895- pub from_address: String,
25896- pub body: String,
25897- pub text: String,
25898- pub is_patch: bool,
25899- }
25900-
25901- #[derive(Debug, Serialize, Default, PartialEq)]
25902- struct List {
25903- pub id: String,
25904- pub name: String,
25905- pub description: Option<String>,
25906- pub address: String,
25907- pub topics: Vec<String>,
25908- pub request_address: String,
25909- }
25910-
25911- async fn read_list(mail_client: Client, list_id: String) -> Result<Option<List>, Error> {
25912- let list = mail_client
25913- .invoke(move |c: MailClient| async move {
25914- let req = c.lists_request();
25915- let result = req.send().promise.await?;
25916- let rpc_lists = result.get()?.get_lists()?;
25917- for list in rpc_lists.iter() {
25918- let id = list.get_id()?.to_string().unwrap();
25919- let topics: Vec<String> = list
25920- .get_topics()?
25921- .iter()
25922- .map(|topic| topic.unwrap().to_string().unwrap())
25923- .collect();
25924- if list_id == id {
25925- return Ok(Some(List {
25926- id: list_id.clone(),
25927- name: list.get_name()?.to_string().unwrap(),
25928- description: None,
25929- address: list.get_address()?.to_string().unwrap(),
25930- topics,
25931- request_address: list.get_request_address()?.to_string().unwrap(),
25932- }));
25933- }
25934- }
25935- Ok(None)
25936- })
25937- .await?;
25938- Ok(list)
25939- }
25940-
25941- pub async fn lists(
25942- Extension(initiator): Extension<Initiator>,
25943- Extension((templates, mut ctx)): Extension<Template>,
25944- ) -> Result<Html<String>, Error> {
25945- ctx.insert("title", "lists");
25946- ctx.insert("nav_elements", &navigation::global("mail", true));
25947- let mail_client = initiator.client(InitiatorKind::Mail).unwrap();
25948- let lists = mail_client
25949- .invoke(move |c: MailClient| async move {
25950- let mut lists: Vec<List> = Vec::new();
25951- let req = c.lists_request();
25952- let result = req.send().promise.await?;
25953- let rpc_lists = result.get()?.get_lists()?;
25954- for list in rpc_lists.iter() {
25955- let description = if list.has_description() {
25956- Some(list.get_description()?.to_string().unwrap())
25957- } else {
25958- None
25959- };
25960- let topics: Vec<String> = list
25961- .get_topics()?
25962- .iter()
25963- .map(|topic| topic.unwrap().to_string().unwrap())
25964- .collect();
25965- lists.push(List {
25966- id: list.get_id()?.to_string().unwrap(),
25967- name: list.get_name()?.to_string().unwrap(),
25968- description,
25969- address: list.get_name()?.to_string().unwrap(),
25970- topics,
25971- request_address: list.get_request_address()?.to_string().unwrap(),
25972- })
25973- }
25974- Ok(lists)
25975- })
25976- .await?;
25977- ctx.insert("lists", &lists);
25978- // ctx.insert("lists", &cfg.mail.unwrap().lists);
25979- let body = templates.render("lists.html", &ctx)?;
25980- Ok(Html(body))
25981- }
25982-
25983- #[debug_handler]
25984- pub async fn threads(
25985- Path(params): Path<Params>,
25986- // Extension(db): Extension<Arc<Database>>,
25987- Extension(initiator): Extension<Initiator>,
25988- Extension((templates, mut ctx)): Extension<Template>,
25989- ) -> Result<Html<String>, Error> {
25990- let client = initiator.client(InitiatorKind::Mail).unwrap();
25991- let mailing_list = read_list(client.clone(), params.list_id.clone()).await?;
25992- if let Some(mailing_list) = mailing_list {
25993- ctx.insert("title", &format!("list {}", mailing_list.name));
25994- ctx.insert("nav_elements", &navigation::global("mail", true));
25995- ctx.insert("list", &mailing_list);
25996- ctx.insert("title", &format!("list {}", mailing_list.address));
25997- ctx.insert("nav_elements", &navigation::global("mail", true));
25998- ctx.insert("list", &mailing_list);
25999- let mut threads = client
26000- .invoke(move |c: MailClient| async move {
26001- let mut threads: Vec<Thread> = Vec::new();
26002- let mut req = c.list_threads_request();
26003- req.get().set_id(params.list_id.as_str().into());
26004- let result = req.send().promise.await?;
26005- let rpc_threads = result.get()?.get_threads()?;
26006- for thread in rpc_threads.iter() {
26007- let message = thread.get_first()?;
26008- let message_id = message.get_message_id()?.to_string().unwrap();
26009- threads.push(Thread {
26010- id: message.get_id(),
26011- message_id,
26012- from: message.get_from()?.to_string().unwrap(),
26013- subject: message.get_subject()?.to_string().unwrap(),
26014- n_replies: thread.get_n_replies(),
26015- timestamp: thread.get_first().unwrap().get_timestamp(),
26016- })
26017- }
26018- Ok(threads)
26019- })
26020- .await?;
26021- threads.sort_by(|first, second| second.timestamp.cmp(&first.timestamp));
26022- ctx.insert("threads", &threads);
26023- ctx.insert("request_address", &mailing_list.request_address);
26024- let body = templates.render("threads.html", &ctx)?;
26025- Ok(Html(body))
26026- } else {
26027- Err(Error::NotFound(format!(
26028- "no mailing list with id: {} exists",
26029- params.list_id
26030- )))
26031- }
26032- }
26033-
26034- pub async fn thread(
26035- Path(params): Path<Params>,
26036- Extension(initiator): Extension<Initiator>,
26037- Extension(highlighter): Extension<Highlighter>,
26038- Extension((templates, mut ctx)): Extension<Template>,
26039- ) -> Result<Html<String>, Error> {
26040- let client = initiator.client(InitiatorKind::Mail).unwrap();
26041- let mailing_list = read_list(client.clone(), params.list_id.clone()).await?;
26042- if let Some(mailing_list) = mailing_list {
26043- let thread_id = params.thread_id.clone();
26044- let messages = client
26045- .invoke(move |c: MailClient| async move {
26046- let mut messages: Vec<Message> = Vec::new();
26047- let mut req = c.read_thread_request();
26048- req.get().set_id(params.list_id.as_str().into());
26049- req.get().set_message_id(thread_id.unwrap().as_str().into());
26050- let result = req.send().promise.await?;
26051- for message in result.get()?.get_thread()? {
26052- let text = message.get_text().unwrap().to_string().unwrap();
26053- let text = if message.get_is_patch() {
26054- let (_, diff) = highlighter.highlight(
26055- &text,
26056- None,
26057- None,
26058- Some(Hint::from("DIFF")),
26059- false,
26060- );
26061- diff
26062- } else {
26063- text
26064- };
26065- messages.push(Message {
26066- id: message.get_id().to_string(),
26067- subject: message.get_subject()?.to_string().unwrap(),
26068- message_id: message.get_message_id()?.to_string().unwrap(),
26069- created_at: message.get_timestamp(),
26070- from_address: message.get_address()?.to_string().unwrap(),
26071- body: message.get_body()?.to_string().unwrap(),
26072- text,
26073- is_patch: message.get_is_patch(),
26074- })
26075- }
26076- Ok(messages)
26077- })
26078- .await?;
26079- let subject = messages.first().map(|message| message.subject.clone());
26080- ctx.insert("title", &format!("list {}", mailing_list.address));
26081- ctx.insert("nav_elements", &navigation::global("mail", true));
26082- ctx.insert("list", &mailing_list);
26083- ctx.insert("list_id", &mailing_list.id);
26084- ctx.insert("thread_id", &params.thread_id.clone());
26085- ctx.insert("messages", &messages);
26086- ctx.insert("subject", &subject);
26087- let body = templates.render("thread.html", &ctx)?;
26088- Ok(Html(body))
26089- } else {
26090- Err(Error::NotFound(format!(
26091- "no mailing list with id: {} exists",
26092- params.list_id
26093- )))
26094- }
26095- }
26096-
26097- pub async fn message(
26098- Path(params): Path<Params>,
26099- Extension(initiator): Extension<Initiator>,
26100- Extension(highlighter): Extension<Highlighter>,
26101- Extension((templates, mut ctx)): Extension<Template>,
26102- ) -> Result<Html<String>, Error> {
26103- let client = initiator.client(InitiatorKind::Mail).unwrap();
26104- let mailing_list = read_list(client.clone(), params.list_id.clone()).await?;
26105- if let Some(mailing_list) = mailing_list {
26106- let message = client
26107- .invoke(move |c: MailClient| async move {
26108- let mut req = c.read_post_request();
26109- req.get().set_id(params.list_id.as_str().into());
26110- req.get()
26111- .set_message_id(params.message_id.unwrap().as_str().into());
26112- // TODO: detect a missing message and return 404 appropriately
26113- let result = req.send().promise.await?;
26114- let message = result.get()?;
26115- let text = message.get_text().unwrap().to_string().unwrap();
26116- let text = if message.get_is_patch() {
26117- let (_, diff) =
26118- highlighter.highlight(&text, None, None, Some(Hint::from("DIFF")), false);
26119- diff
26120- } else {
26121- text
26122- };
26123- Ok(Message {
26124- id: message.get_id().to_string(),
26125- subject: message.get_subject()?.to_string().unwrap(),
26126- message_id: message.get_message_id()?.to_string().unwrap(),
26127- created_at: message.get_timestamp(),
26128- body: message.get_body()?.to_string().unwrap(),
26129- text,
26130- from_address: message.get_address()?.to_string().unwrap(),
26131- is_patch: message.get_is_patch(),
26132- })
26133- })
26134- .await?;
26135- ctx.insert("title", &format!("list {}", mailing_list.name));
26136- ctx.insert("nav_elements", &navigation::global("mail", true));
26137- ctx.insert("list", &mailing_list);
26138- ctx.insert("list_id", &mailing_list.id.clone());
26139- ctx.insert("thread_id", &params.thread_id);
26140- ctx.insert("message", &message);
26141- let body = templates.render("post.html", &ctx)?;
26142- Ok(Html(body))
26143- } else {
26144- Err(Error::NotFound(format!(
26145- "no mailing list with id: {} exists",
26146- params.list_id
26147- )))
26148- }
26149- }
26150-
26151- pub async fn export(
26152- Path(params): Path<Params>,
26153- Extension(initiator): Extension<Initiator>,
26154- ) -> Result<Response, Error> {
26155- let client = initiator.client(InitiatorKind::Mail).unwrap();
26156- let mailing_list = read_list(client.clone(), params.list_id.clone()).await?;
26157- if let Some(mailing_list) = mailing_list {
26158- let list_id = mailing_list.id.clone();
26159- let thread = client
26160- .invoke(move |c: MailClient| async move {
26161- let mut req = c.export_request();
26162- req.get().set_id(list_id.as_str().into());
26163- if let Some(message_id) = params.message_id {
26164- req.get().set_message_id(message_id.as_str().into());
26165- req.get().set_as_thread(true);
26166- }
26167- let result = req.send().promise.await?;
26168- let post = result.get()?.get_thread()?;
26169- Ok(post.to_vec())
26170- })
26171- .await?;
26172- let mut response = Bytes::from(thread).into_response();
26173- response
26174- .headers_mut()
26175- .insert(CONTENT_TYPE, "application/mbox".parse().unwrap());
26176- Ok(response)
26177- } else {
26178- Err(Error::NotFound(format!(
26179- "no mailing list with id: {} exists",
26180- params.list_id
26181- )))
26182- }
26183- }
26184 diff --git a/src/web2/routes/mod.rs b/src/web2/routes/mod.rs
26185deleted file mode 100644
26186index 98e70a1..0000000
26187--- a/src/web2/routes/mod.rs
26188+++ /dev/null
26189 @@ -1,18 +0,0 @@
26190- pub mod about;
26191- pub mod assets;
26192- pub mod authors;
26193- pub mod blame;
26194- pub mod blob;
26195- pub mod builds;
26196- pub mod chart;
26197- pub mod commit;
26198- pub mod config;
26199- pub mod finger;
26200- pub mod index;
26201- pub mod log;
26202- pub mod mail;
26203- pub mod refs;
26204- pub mod repo;
26205- pub mod robots;
26206- pub mod rss;
26207- pub mod xmpp;
26208 diff --git a/src/web2/routes/refs.rs b/src/web2/routes/refs.rs
26209deleted file mode 100644
26210index 3483ce2..0000000
26211--- a/src/web2/routes/refs.rs
26212+++ /dev/null
26213 @@ -1,106 +0,0 @@
26214- use serde::Serialize;
26215-
26216- use axum::{
26217- body::Body,
26218- extract::{Extension, Path},
26219- http::header::CONTENT_TYPE,
26220- response::{Html, Response},
26221- };
26222- use tokio_util::io::ReaderStream;
26223-
26224- use crate::web2::error::Error;
26225- use crate::web2::middleware::repository::Preamble;
26226- use crate::web2::middleware::template::Template;
26227- use crate::web2::navigation;
26228- use ayllu_git::Wrapper;
26229-
26230- const INVALID_EXTENSION_MESSAGE: &str = "archive requests must have a .tar.gz extension";
26231-
26232- #[derive(Serialize, Default)]
26233- struct Item {
26234- author_name: String,
26235- author_email: String,
26236- name: String,
26237- message: String,
26238- timestamp: String,
26239- short_hash: String,
26240- commit_hash: String,
26241- }
26242-
26243- // TODO: need an individual branch page
26244-
26245- pub async fn branches(
26246- Extension(preamble): Extension<Preamble>,
26247- Extension((templates, mut ctx)): Extension<Template>,
26248- ) -> Result<Html<String>, Error> {
26249- let repository = Wrapper::new(preamble.repo_path.as_path())?;
26250- let branches = repository.branches()?;
26251- ctx.insert("title", &preamble.repo_name);
26252- ctx.insert("branches", &branches);
26253- ctx.insert(
26254- "nav_elements",
26255- &navigation::primary(
26256- "refs",
26257- &preamble.collection_name,
26258- &preamble.repo_name,
26259- ),
26260- );
26261- ctx.insert(
26262- "refnav",
26263- &navigation::revnav(
26264- "branches",
26265- preamble.collection_name.as_str(),
26266- preamble.repo_name.as_str(),
26267- ),
26268- );
26269- let body = templates.render("branches.html", &ctx)?;
26270- Ok(Html(body))
26271- }
26272-
26273- // TODO: need an individual ref page
26274-
26275- pub async fn tags(
26276- Extension(preamble): Extension<Preamble>,
26277- Extension((templates, mut ctx)): Extension<Template>,
26278- ) -> Result<Html<String>, Error> {
26279- let repository = Wrapper::new(preamble.repo_path.as_path())?;
26280- let tags = repository.tags()?;
26281- ctx.insert("title", &preamble.repo_name);
26282- ctx.insert("tags", &tags);
26283- ctx.insert(
26284- "nav_elements",
26285- &navigation::primary(
26286- "refs",
26287- &preamble.collection_name,
26288- &preamble.repo_name,
26289- ),
26290- );
26291- ctx.insert(
26292- "refnav",
26293- &navigation::revnav(
26294- "tags",
26295- preamble.collection_name.as_str(),
26296- preamble.repo_name.as_str(),
26297- ),
26298- );
26299- let body = templates.render("tags.html", &ctx)?;
26300- Ok(Html(body))
26301- }
26302-
26303- pub async fn archive(
26304- Path((_, _, ref_name)): Path<(String, String, String)>,
26305- Extension(preamble): Extension<Preamble>,
26306- ) -> Result<Response, Error> {
26307- if !ref_name.ends_with(".tar.gz") {
26308- return Err(Error::Message(INVALID_EXTENSION_MESSAGE.to_string()));
26309- }
26310- let repository = Wrapper::new(preamble.repo_path.as_path())?;
26311- let ref_name = ref_name.trim_end_matches(".tar.gz");
26312- let mut child_proc = repository.archive(ref_name)?;
26313- let stdout = child_proc.stdout.take().unwrap();
26314- let response = Response::builder()
26315- .header(CONTENT_TYPE, "application/gzip")
26316- .body(Body::from_stream(ReaderStream::new(stdout)))
26317- .unwrap();
26318- Ok(response)
26319- }
26320 diff --git a/src/web2/routes/repo.rs b/src/web2/routes/repo.rs
26321deleted file mode 100644
26322index 31b8779..0000000
26323--- a/src/web2/routes/repo.rs
26324+++ /dev/null
26325 @@ -1,387 +0,0 @@
26326- use std::path::PathBuf;
26327- use std::sync::Arc;
26328-
26329- use axum::{
26330- debug_handler,
26331- extract::{Extension, OriginalUri},
26332- response::Html,
26333- };
26334- use comrak::{markdown_to_html_with_plugins, ComrakPlugins};
26335- use serde::Serialize;
26336- use tera::{to_value, Filter};
26337- use time::Duration;
26338-
26339- use crate::config::Config;
26340- use crate::database_ext::{
26341- contributors::ContributorsExt,
26342- langauges::LanguagesExt,
26343- stats::{Aggregation, StatsExt},
26344- };
26345- use crate::license;
26346- use crate::web2::charts;
26347- use crate::web2::error::Error;
26348- use crate::web2::highlight::TreeSitterAdapter;
26349- use crate::web2::middleware::repository::Preamble;
26350- use crate::web2::middleware::rpc_initiator::{Initiator, Kind as InitiatorKind};
26351- use crate::web2::middleware::template::Template;
26352- use crate::web2::navigation;
26353- use crate::web2::util;
26354- use ayllu_api::xmpp_capnp::server::Client as XmppClient;
26355- use ayllu_database::Wrapper as Database;
26356- use ayllu_git::{ChatKind, Commit, Config as GitConfig, TreeEntry, Wrapper};
26357-
26358- const README_FILES: [&str; 6] = [
26359- "readme.md",
26360- "README.md",
26361- "readme",
26362- "README",
26363- "readme.txt",
26364- "README.txt",
26365- ];
26366-
26367- /// materialized view with data needed to populate the repo page
26368- #[derive(Default)]
26369- struct View {
26370- pub commit_count: i32,
26371- pub authors: Vec<(String, String, i64)>,
26372- pub buckets: Vec<(i64, i64, i64)>,
26373- pub languages: Vec<(String, u8, u32)>,
26374- }
26375-
26376- async fn make_view(
26377- db: &Database,
26378- repo_path: &str,
26379- commit_hash: Option<String>,
26380- ) -> Result<View, Error> {
26381- match commit_hash {
26382- Some(commit_hash) => Ok(View {
26383- commit_count: db.count_commits(repo_path, &commit_hash).await?,
26384- buckets: db
26385- .contribution_buckets_for_repo(
26386- repo_path,
26387- &commit_hash,
26388- Aggregation::Day,
26389- 90 * Duration::DAY,
26390- )
26391- .await?,
26392- authors: db.contributors_list(repo_path, &commit_hash).await?,
26393- languages: db.languages_list(repo_path, &commit_hash).await?,
26394- }),
26395- None => Ok(View::default()),
26396- }
26397- }
26398-
26399- struct ItemBuilder {
26400- base_url: PathBuf,
26401- file_path: Option<PathBuf>,
26402- commitish: String,
26403- }
26404-
26405- impl Filter for ItemBuilder {
26406- fn filter(
26407- &self,
26408- value: &serde_json::Value,
26409- args: &std::collections::HashMap<String, serde_json::Value>,
26410- ) -> tera::Result<serde_json::Value> {
26411- // TODO: should clean this up somehow
26412- let kind = args.get("kind").unwrap().as_str().unwrap();
26413- let kind = kind.to_lowercase();
26414- let kind = if kind == "pointer" || kind == "submodule" {
26415- "blob".to_string()
26416- } else {
26417- kind.clone()
26418- };
26419- let mut url = self.base_url.clone();
26420- url.push(kind.clone());
26421- url.push(self.commitish.clone());
26422- match &self.file_path {
26423- Some(fp) => {
26424- let fp = fp.to_str().unwrap();
26425- let fp = fp.trim_start_matches('/');
26426- url.push(fp)
26427- }
26428- None => {}
26429- }
26430- let value = value.as_str().unwrap();
26431- url.push(value);
26432- let url = url.to_str().unwrap().to_string();
26433- Ok(to_value(url).unwrap())
26434- }
26435- }
26436-
26437- #[derive(Serialize, Clone)]
26438- pub struct ChatLink {
26439- pub kind: String,
26440- pub url: String,
26441- pub description: Option<String>,
26442- pub users_online: Option<i64>,
26443- }
26444-
26445- async fn managed_chats(cfg: &Config, git_cfg: &GitConfig, initiator: Initiator) -> Vec<ChatLink> {
26446- let managed_xmpp_channels: Vec<String> = cfg.xmpp.clone().map_or(Vec::new(), |link| {
26447- link.channels.iter().map(|item| item.jid.clone()).collect()
26448- });
26449- let mut links: Vec<ChatLink> = Vec::new();
26450- if git_cfg.chat.is_some() {
26451- let mut to_lookup: Vec<String> = Vec::new();
26452- for link in git_cfg.chat.clone().unwrap() {
26453- match link.kind {
26454- ChatKind::Xmpp => {
26455- if managed_xmpp_channels.contains(&link.url) {
26456- to_lookup.push(link.url.clone())
26457- } else {
26458- links.push(ChatLink {
26459- kind: format!("{:?}", link.kind),
26460- url: link.url.clone(),
26461- description: None,
26462- users_online: None,
26463- })
26464- };
26465- }
26466- ChatKind::Irc => links.push(ChatLink {
26467- kind: format!("{:?}", link.kind),
26468- url: link.url.clone(),
26469- description: None,
26470- users_online: None,
26471- }),
26472- }
26473- }
26474- let xmpp_client = initiator.client(InitiatorKind::Xmpp);
26475- if let Some(xmpp_client) = xmpp_client {
26476- match xmpp_client
26477- .invoke(move |client: XmppClient| async move {
26478- let mut remote_channels: Vec<ChatLink> = Vec::new();
26479- let mut request = client.stats_request();
26480- let mut channels = request
26481- .get()
26482- .init_channels(managed_xmpp_channels.len() as u32);
26483- for (i, lookup) in to_lookup.iter().enumerate() {
26484- channels.set(i as u32, lookup.as_str().into());
26485- }
26486- let status = request.send().promise.await?;
26487- let results = status.get()?;
26488- let stats = results.get_stats()?;
26489- for stat in stats {
26490- remote_channels.push(ChatLink {
26491- kind: format!("{:?}", ChatKind::Xmpp),
26492- url: stat.get_name()?.to_string().unwrap(),
26493- description: None,
26494- users_online: Some(stat.get_users_online()),
26495- })
26496- }
26497- Ok(remote_channels)
26498- })
26499- .await
26500- {
26501- Ok(remote_links) => {
26502- links.extend(remote_links);
26503- }
26504- Err(err) => {
26505- log::error!(
26506- "failed to resolve discussion group status: {:?}",
26507- err.to_string()
26508- )
26509- }
26510- };
26511- }
26512- }
26513- links
26514- }
26515-
26516- #[derive(Serialize, Clone)]
26517- pub struct EmailLink {
26518- pub url: String,
26519- pub description: Option<String>,
26520- }
26521-
26522- #[debug_handler]
26523- pub async fn serve(
26524- uri: OriginalUri,
26525- Extension(cfg): Extension<Config>,
26526- Extension(initiator): Extension<Initiator>,
26527- Extension(db): Extension<Arc<Database>>,
26528- Extension(preamble): Extension<Preamble>,
26529- Extension(adapter): Extension<TreeSitterAdapter>,
26530- Extension((mut templates, mut ctx)): Extension<Template>,
26531- ) -> Result<Html<String>, Error> {
26532- let repository = Wrapper::new(preamble.repo_path.as_path())?;
26533-
26534- let license = license::detect(&repository, preamble.latest_commit_id.clone())?;
26535-
26536- let tree = repository.tree(
26537- preamble.file_path.clone(),
26538- preamble.latest_commit_id.clone(),
26539- )?;
26540-
26541- let recent_commits = repository.resolve_tree(
26542- preamble.file_path.clone(),
26543- &tree,
26544- preamble.latest_commit_id.clone(),
26545- )?;
26546-
26547- let entries_with_commits: Vec<(&TreeEntry, &Commit)> =
26548- tree.iter().zip(recent_commits.iter()).collect();
26549-
26550- // attempt to render readme if a suitable file is present
26551-
26552- let mut readme: Option<String> = None;
26553-
26554- let mut rendered_file_name: Option<String> = None;
26555-
26556- for path in README_FILES {
26557- let mut readme_path = preamble.file_path.clone().unwrap_or(PathBuf::new());
26558- readme_path.push(path);
26559- if let Some(content) =
26560- repository.read_string(&readme_path, preamble.latest_commit_id.clone())?
26561- {
26562- rendered_file_name = Some(path.to_string());
26563- readme = Some(content);
26564- break;
26565- }
26566- }
26567-
26568- let chat_links = managed_chats(&cfg, &preamble.config, initiator).await;
26569- ctx.insert("chat_links", &chat_links);
26570-
26571- // find any associated mailing lists associated with this repository
26572- /*
26573- let email_links = preamble.config.clone().mail.map(|mail| {
26574- let links: Vec<EmailLink> = mail
26575- .iter()
26576- .map(|address| {
26577- let git_address = address.0.clone();
26578- let description = cfg
26579- .mail
26580- .clone()
26581- .and_then(|mail_cfg| {
26582- mail_cfg
26583- .lists
26584- .iter()
26585- .find(|entry| entry.address == git_address)
26586- .map(|entry| entry.address.clone())
26587- });
26588- EmailLink {
26589- url: address.0.clone(),
26590- description,
26591- }
26592- })
26593- .collect();
26594- links
26595- });
26596-
26597- ctx.insert("email_links", &email_links);
26598- */
26599-
26600- let materialized = make_view(
26601- &db,
26602- &preamble.repo_path_string(),
26603- preamble.latest_commit_id.clone(),
26604- )
26605- .await?;
26606- let chart_builder = charts::Renderer::new((320, 250));
26607-
26608- let mut plugins = ComrakPlugins::default();
26609- plugins.render.codefence_syntax_highlighter = Some(&adapter);
26610-
26611- let readme = readme.map_or(String::new(), |readme| {
26612- markdown_to_html_with_plugins(readme.as_str(), &cfg.markdown_render_options(), &plugins)
26613- });
26614-
26615- // Merge everything together and render
26616- templates.register_filter(
26617- "make_url",
26618- ItemBuilder {
26619- base_url: PathBuf::from(format!(
26620- "/{}/{}",
26621- preamble.collection_name, preamble.repo_name
26622- )),
26623- file_path: preamble.file_path.clone(),
26624- commitish: preamble.refname.clone(),
26625- },
26626- );
26627- ctx.insert("title", &preamble.repo_name);
26628- ctx.insert("sites_url", &preamble.config.homepage);
26629- ctx.insert("readme", &readme);
26630- ctx.insert("file_path", &preamble.file_path_string());
26631- ctx.insert(
26632- "activity_chart",
26633- &chart_builder.activity(materialized.buckets)?,
26634- );
26635- ctx.insert(
26636- "language_chart",
26637- &chart_builder.languages(materialized.languages)?,
26638- );
26639- ctx.insert("tree", &entries_with_commits);
26640- ctx.insert("refname", &preamble.refname.clone());
26641- ctx.insert("show_details", &preamble.file_path.is_none());
26642- ctx.insert(
26643- "nav_elements",
26644- &navigation::primary(
26645- "project",
26646- &preamble.collection_name,
26647- &preamble.repo_name,
26648- ),
26649- );
26650- ctx.insert("commit_count", &materialized.commit_count);
26651- ctx.insert("latest_commit", &preamble.latest_commit);
26652- ctx.insert("authors", &materialized.authors);
26653- ctx.insert("authors_count", &0);
26654- ctx.insert("rendered_file_name", &rendered_file_name);
26655- ctx.insert(
26656- "rss_link_all",
26657- &util::select_path(&uri, Some(0), None).map(|mut path| {
26658- path.push("rss/firehose.xml");
26659- path.to_str().unwrap().to_string()
26660- }),
26661- );
26662- ctx.insert(
26663- "rss_link_1d",
26664- &util::select_path(&uri, Some(0), None).map(|mut path| {
26665- path.push("rss/1d.xml");
26666- path.to_str().unwrap().to_string()
26667- }),
26668- );
26669- ctx.insert(
26670- "rss_link_1w",
26671- &util::select_path(&uri, Some(0), None).map(|mut path| {
26672- path.push("rss/1w.xml");
26673- path.to_str().unwrap().to_string()
26674- }),
26675- );
26676- ctx.insert(
26677- "rss_link_1m",
26678- &util::select_path(&uri, Some(0), None).map(|mut path| {
26679- path.push("rss/1m.xml");
26680- path.to_str().unwrap().to_string()
26681- }),
26682- );
26683- ctx.insert(
26684- "http_clone_url",
26685- &format!(
26686- "{}/{}/{}",
26687- cfg.origin, preamble.collection_name, preamble.repo_name
26688- ),
26689- );
26690- let git_clone_url = match cfg.git_clone_url {
26691- Some(base) => {
26692- let clone_url = format!(
26693- "{}:{}/{}.git",
26694- base, preamble.collection_name, preamble.repo_name,
26695- );
26696- Some(clone_url)
26697- }
26698- None => None,
26699- };
26700- ctx.insert("git_clone_url", &git_clone_url);
26701- match license {
26702- Some(license) => {
26703- ctx.insert("license", &license);
26704- }
26705- None => {
26706- ctx.insert("license", &String::new());
26707- }
26708- };
26709-
26710- let body = templates.render("repo.html", &ctx)?;
26711- Ok(Html(body))
26712- }
26713 diff --git a/src/web2/routes/robots.rs b/src/web2/routes/robots.rs
26714deleted file mode 100644
26715index fe40c5e..0000000
26716--- a/src/web2/routes/robots.rs
26717+++ /dev/null
26718 @@ -1,15 +0,0 @@
26719- use axum::{response::IntoResponse, Extension};
26720-
26721- use crate::config::Config;
26722- use crate::web2::error::Error;
26723-
26724- pub async fn serve(Extension(cfg): Extension<Config>) -> Result<impl IntoResponse, Error> {
26725- let robots_txt = cfg.sysadmin.clone().map_or(cfg.robots.clone(), |sysadmin| {
26726- format!(
26727- "# contact {} with questions\n{}",
26728- sysadmin,
26729- cfg.robots.clone()
26730- )
26731- });
26732- Ok(robots_txt)
26733- }
26734 diff --git a/src/web2/routes/rss.rs b/src/web2/routes/rss.rs
26735deleted file mode 100644
26736index ee16af4..0000000
26737--- a/src/web2/routes/rss.rs
26738+++ /dev/null
26739 @@ -1,751 +0,0 @@
26740- use std::path::PathBuf;
26741-
26742- use axum::{
26743- body::Body,
26744- extract::{Extension, OriginalUri},
26745- http::header::CONTENT_TYPE,
26746- response::Response,
26747- };
26748- use mime::TEXT_XML;
26749- use rss::{Channel, Guid, Item};
26750- use serde::Serialize;
26751- use tera::{Context, Tera};
26752- use time::{format_description::well_known::Rfc2822, macros::time, Duration, OffsetDateTime};
26753-
26754- use crate::config::{Collection, Config};
26755- use crate::web2::error::Error;
26756- use crate::web2::middleware::repository::Preamble;
26757- use crate::web2::middleware::template::Template;
26758- use crate::web2::util;
26759- use ayllu_git::{Commit, Scanner, Tag, Wrapper};
26760-
26761- fn stylesheet_hack(input: String) -> String {
26762- const ORIGINAL: &str = r#"<?xml version="1.0" encoding="utf-8"?>"#;
26763- const REPLACEMENT: &str = r#"<?xml version="1.0" encoding="utf-8"?><?xml-stylesheet href="/static/assets/feed.xsl" type="text/xsl"?>"#;
26764- input.replacen(ORIGINAL, REPLACEMENT, 1)
26765- }
26766-
26767- fn html_commit(summary: String, message: String) -> String {
26768- if summary == message.trim_end() {
26769- message
26770- } else {
26771- format!(
26772- "<pre>{}\n{}</pre>",
26773- summary,
26774- message.replacen(&summary, "", 1)
26775- )
26776- }
26777- }
26778-
26779- /// Timeframe specifies a chunk of time to summarize repository activity for.
26780- #[derive(Clone, Copy)]
26781- enum Timeframe {
26782- Daily,
26783- Weekly,
26784- Monthly,
26785- }
26786-
26787- impl Timeframe {
26788- /// "Clamp" a time period for the given timeframe, for instance the WEEKLY
26789- /// timeframe for the current time of 2021-03-05 13:00:55 UTC will be
26790- /// 2021-02-21 00:00:00 UTC -> 2021-02-28 00:00:00 UTC which is the previous
26791- /// 7 day week from the "current" week.
26792- pub fn clamp(self, current_time: OffsetDateTime) -> (OffsetDateTime, OffsetDateTime) {
26793- match self {
26794- Timeframe::Daily => {
26795- // today at midnight
26796- let end_time = current_time.replace_time(time!(00:00:00));
26797- // 00:00:00 two days ago
26798- let start_time = end_time.saturating_sub(24 * Duration::HOUR);
26799- (start_time, end_time)
26800- }
26801- Timeframe::Weekly => {
26802- // today at midnight
26803- let current_time = current_time.replace_time(time!(00:00:00));
26804- // days to previous sunday
26805- let days_to_sunday = current_time.weekday().number_days_from_sunday();
26806- let end_time = current_time.saturating_sub(days_to_sunday * Duration::DAY);
26807- // one week prior to previous sunday
26808- let start_time = end_time.saturating_sub(7 * Duration::DAY);
26809- (start_time, end_time)
26810- }
26811- Timeframe::Monthly => {
26812- // today at midnight
26813- let current_time = current_time.replace_time(time!(00:00:00));
26814- let days_to_previous_month = current_time.date().day();
26815- let end_time = current_time.saturating_sub(days_to_previous_month * Duration::DAY);
26816- let days_to_previous_month = end_time.date().day();
26817- let start_time =
26818- end_time.saturating_sub((days_to_previous_month - 1) * Duration::DAY);
26819- (start_time, end_time)
26820- }
26821- }
26822- }
26823- }
26824-
26825- #[derive(Serialize)]
26826- struct Data {
26827- name: String,
26828- tags: Vec<Tag>,
26829- commits: Vec<Commit>,
26830- }
26831-
26832- /// Channel builder for RSS feeds for on all repositories hosted on the server.
26833- struct Builder {
26834- templates: Tera,
26835- context: Context,
26836- origin: String,
26837- title: String,
26838- time_to_live: Option<Duration>,
26839- current_time: OffsetDateTime,
26840- }
26841-
26842- impl Builder {
26843- fn scan_repositories(
26844- &self,
26845- collections: Vec<Collection>,
26846- ) -> Result<Vec<(PathBuf, String)>, Error> {
26847- let mut entries: Vec<(PathBuf, String)> = Vec::new();
26848- for collection in collections.iter() {
26849- // NOTE: hide hidden collections from RSS feeds
26850- if collection.hidden.is_some_and(|hidden| hidden) {
26851- continue;
26852- }
26853- for repository_path in Scanner::from_path(&collection.path)? {
26854- let file_name = repository_path.file_name().unwrap();
26855- let name = file_name.to_str().unwrap();
26856- entries.push((
26857- repository_path.clone(),
26858- format!("{}/{}/{}", self.origin, collection.name, name),
26859- ));
26860- }
26861- }
26862- Ok(entries)
26863- }
26864-
26865- fn channel(&self, items: Vec<Item>) -> Channel {
26866- let mut channel = Channel::default();
26867- channel.set_title(self.title.to_string());
26868- channel.set_last_build_date(items.last().map(|item| {
26869- item.pub_date()
26870- .map_or(String::new(), |date| date.to_string())
26871- }));
26872- channel.set_items(items);
26873- if let Some(ttl) = self.time_to_live {
26874- channel.set_ttl(ttl.whole_minutes().to_string());
26875- }
26876- channel
26877- }
26878-
26879- fn firehose(&self, entries: Vec<(PathBuf, String)>, since: Duration) -> Result<Channel, Error> {
26880- let start = self.current_time.saturating_sub(since);
26881- let mut items: Vec<(Item, i64)> = Vec::new();
26882- for entry in entries {
26883- let repository = Wrapper::new(entry.0.as_path())?;
26884- let config = repository.config()?;
26885- if config.hidden.is_some_and(|hidden| hidden) {
26886- continue;
26887- };
26888- for tag in repository.tags_range(Some((
26889- start.unix_timestamp(),
26890- self.current_time.unix_timestamp(),
26891- )))? {
26892- let mut item = rss::Item::default();
26893- // FIXME: need a tag page before we can set a GUID/link
26894- item.set_title(format!("Tag: {}", tag.name));
26895- item.set_description(tag.summary);
26896- item.set_author(tag.author_name);
26897- items.push((item, tag.commit.epoch));
26898- }
26899- for commit in repository.commits_range(
26900- None,
26901- Some((start.unix_timestamp(), self.current_time.unix_timestamp())),
26902- )? {
26903- let mut item = rss::Item::default();
26904- let link = format!("{}/commit/{}", entry.1, commit.id);
26905- item.set_title(format!("Commit: {}", commit.summary));
26906- item.set_author(commit.author_name);
26907- item.set_link(Some(link.clone()));
26908- item.set_guid(Some(Guid {
26909- permalink: true,
26910- value: link,
26911- }));
26912- item.set_description(html_commit(commit.summary, commit.message));
26913- let pub_date = OffsetDateTime::from_unix_timestamp(commit.epoch).unwrap();
26914- item.set_pub_date(pub_date.format(&Rfc2822).unwrap());
26915- items.push((item, commit.epoch))
26916- }
26917- }
26918- // reorder everything by time
26919- items.sort_by(|a, b| a.1.cmp(&b.1));
26920- items.reverse();
26921- Ok(self.channel(items.iter().map(|item| item.0.clone()).collect()))
26922- }
26923-
26924- fn summary(
26925- &self,
26926- entries: Vec<(PathBuf, String)>,
26927- timeframe: Timeframe,
26928- ) -> Result<Channel, Error> {
26929- let (start, end) = timeframe.clamp(self.current_time);
26930- let mut items: Vec<Item> = Vec::new();
26931- let mut all_data: Vec<Data> = Vec::new();
26932- for entry in entries {
26933- let repository = Wrapper::new(entry.0.as_path())?;
26934- let config = repository.config()?;
26935- if config.hidden.is_some_and(|hidden| hidden) {
26936- continue;
26937- };
26938- let tags =
26939- repository.tags_range(Some((start.unix_timestamp(), end.unix_timestamp())))?;
26940- let commits = repository
26941- .commits_range(None, Some((start.unix_timestamp(), end.unix_timestamp())))?;
26942- if !tags.is_empty() || !commits.is_empty() {
26943- all_data.push(Data {
26944- name: repository.name(),
26945- tags,
26946- commits,
26947- });
26948- }
26949- }
26950-
26951- // don't generate a new rss item if there is nothing within it
26952- if all_data.is_empty() {
26953- return Ok(self.channel(vec![]));
26954- }
26955-
26956- let mut ctx = self.context.clone();
26957-
26958- ctx.insert("origin", &self.origin);
26959- ctx.insert("start_date", &start.date().to_string());
26960- ctx.insert("end_date", &end.date().to_string());
26961-
26962- let n_tags = all_data
26963- .iter()
26964- .fold(0, |accm, entries| accm + entries.tags.len());
26965- ctx.insert("n_tags", &n_tags);
26966- let n_commits = all_data
26967- .iter()
26968- .fold(0, |accm, entries| accm + entries.commits.len());
26969- ctx.insert("n_commits", &n_commits);
26970- ctx.insert("n_projects", &all_data.len());
26971- ctx.insert("entries", &all_data);
26972-
26973- let description = self.templates.render("rss_summary.html", &ctx).unwrap();
26974- let mut item = rss::Item::default();
26975- item.set_description(Some(description));
26976- item.set_guid(Some(Guid {
26977- value: format!(
26978- "summary-{}-{}",
26979- start.unix_timestamp(),
26980- end.unix_timestamp()
26981- ),
26982- permalink: false,
26983- }));
26984- items.push(item);
26985- Ok(self.channel(items))
26986- }
26987- }
26988-
26989- pub async fn feed_firehose(
26990- Extension(cfg): Extension<Config>,
26991- Extension((templates, context)): Extension<Template>,
26992- ) -> Result<Response, Error> {
26993- let builder = Builder {
26994- templates,
26995- context,
26996- origin: cfg.origin,
26997- title: String::from("Firehose"),
26998- time_to_live: cfg.rss_time_to_live.map(Duration::seconds),
26999- current_time: OffsetDateTime::now_utc(),
27000- };
27001- let channel = builder.firehose(
27002- builder.scan_repositories(cfg.collections)?,
27003- Duration::days(7),
27004- )?;
27005- let response = Response::builder()
27006- .header(CONTENT_TYPE, TEXT_XML.as_ref())
27007- .body(Body::new(stylesheet_hack(channel.to_string())))
27008- .unwrap();
27009- Ok(response)
27010- }
27011-
27012- pub async fn feed_1d(
27013- Extension(cfg): Extension<Config>,
27014- Extension((templates, context)): Extension<Template>,
27015- ) -> Result<Response, Error> {
27016- let builder = Builder {
27017- templates,
27018- context,
27019- origin: cfg.origin,
27020- title: String::from("Daily Update Summary"),
27021- time_to_live: cfg.rss_time_to_live.map(Duration::seconds),
27022- current_time: OffsetDateTime::now_utc(),
27023- };
27024- let channel = builder.summary(
27025- builder.scan_repositories(cfg.collections)?,
27026- Timeframe::Daily,
27027- )?;
27028- let response = Response::builder()
27029- .header(CONTENT_TYPE, TEXT_XML.as_ref())
27030- .body(Body::new(stylesheet_hack(channel.to_string())))
27031- .unwrap();
27032- Ok(response)
27033- }
27034-
27035- pub async fn feed_1w(
27036- Extension(cfg): Extension<Config>,
27037- Extension((templates, context)): Extension<Template>,
27038- ) -> Result<Response, Error> {
27039- let builder = Builder {
27040- templates,
27041- context,
27042- origin: cfg.origin,
27043- title: String::from("Weekly Update Summary"),
27044- time_to_live: cfg.rss_time_to_live.map(Duration::seconds),
27045- current_time: OffsetDateTime::now_utc(),
27046- };
27047- let channel = builder.summary(
27048- builder.scan_repositories(cfg.collections)?,
27049- Timeframe::Weekly,
27050- )?;
27051- let response = Response::builder()
27052- .header(CONTENT_TYPE, TEXT_XML.as_ref())
27053- .body(Body::new(stylesheet_hack(channel.to_string())))
27054- .unwrap();
27055- Ok(response)
27056- }
27057-
27058- pub async fn feed_1m(
27059- Extension(cfg): Extension<Config>,
27060- Extension((templates, context)): Extension<Template>,
27061- ) -> Result<Response, Error> {
27062- let builder = Builder {
27063- templates,
27064- context,
27065- origin: cfg.origin,
27066- title: String::from("Monthly Update Summary"),
27067- time_to_live: cfg.rss_time_to_live.map(Duration::seconds),
27068- current_time: OffsetDateTime::now_utc(),
27069- };
27070- let channel = builder.summary(
27071- builder.scan_repositories(cfg.collections)?,
27072- Timeframe::Monthly,
27073- )?;
27074- let response = Response::builder()
27075- .header(CONTENT_TYPE, TEXT_XML.as_ref())
27076- .body(Body::new(stylesheet_hack(channel.to_string())))
27077- .unwrap();
27078- Ok(response)
27079- }
27080-
27081- pub async fn feed_repository_firehose(
27082- uri: OriginalUri,
27083- Extension(cfg): Extension<Config>,
27084- Extension(preamble): Extension<Preamble>,
27085- Extension((templates, context)): Extension<Template>,
27086- ) -> Result<Response, Error> {
27087- let project_url = util::project_url(&uri, cfg.origin.as_str());
27088- let builder = Builder {
27089- templates,
27090- context,
27091- origin: cfg.origin,
27092- title: format!(
27093- "Firehose for {}/{}",
27094- preamble.collection_name, preamble.repo_name
27095- ),
27096- time_to_live: cfg.rss_time_to_live.map(Duration::seconds),
27097- current_time: OffsetDateTime::now_utc(),
27098- };
27099- let channel = builder.firehose(vec![(preamble.repo_path, project_url)], Duration::days(7))?;
27100- let response = Response::builder()
27101- .header(CONTENT_TYPE, TEXT_XML.as_ref())
27102- .body(Body::new(stylesheet_hack(channel.to_string())))
27103- .unwrap();
27104- Ok(response)
27105- }
27106-
27107- pub async fn feed_repository_1d(
27108- uri: OriginalUri,
27109- Extension(cfg): Extension<Config>,
27110- Extension(preamble): Extension<Preamble>,
27111- Extension((templates, context)): Extension<Template>,
27112- ) -> Result<Response, Error> {
27113- let project_url = util::project_url(&uri, cfg.origin.as_str());
27114- let builder = Builder {
27115- templates,
27116- context,
27117- origin: cfg.origin,
27118- title: format!(
27119- "Daily Update Summary for {}/{}",
27120- preamble.collection_name, preamble.repo_name
27121- ),
27122- time_to_live: cfg.rss_time_to_live.map(Duration::seconds),
27123- current_time: OffsetDateTime::now_utc(),
27124- };
27125- let channel = builder.summary(vec![(preamble.repo_path, project_url)], Timeframe::Daily)?;
27126- let response = Response::builder()
27127- .header(CONTENT_TYPE, TEXT_XML.as_ref())
27128- .body(Body::new(stylesheet_hack(channel.to_string())))
27129- .unwrap();
27130- Ok(response)
27131- }
27132-
27133- pub async fn feed_repository_1w(
27134- uri: OriginalUri,
27135- Extension(cfg): Extension<Config>,
27136- Extension(preamble): Extension<Preamble>,
27137- Extension((templates, context)): Extension<Template>,
27138- ) -> Result<Response, Error> {
27139- let project_url = util::project_url(&uri, cfg.origin.as_str());
27140- let builder = Builder {
27141- templates,
27142- context,
27143- origin: cfg.origin,
27144- title: format!(
27145- "Weekly Update Summary for {}/{}",
27146- preamble.collection_name, preamble.repo_name
27147- ),
27148- time_to_live: cfg.rss_time_to_live.map(Duration::seconds),
27149- current_time: OffsetDateTime::now_utc(),
27150- };
27151- let channel = builder.summary(vec![(preamble.repo_path, project_url)], Timeframe::Weekly)?;
27152- let response = Response::builder()
27153- .header(CONTENT_TYPE, TEXT_XML.as_ref())
27154- .body(Body::new(stylesheet_hack(channel.to_string())))
27155- .unwrap();
27156- Ok(response)
27157- }
27158-
27159- pub async fn feed_repository_1m(
27160- uri: OriginalUri,
27161- Extension(cfg): Extension<Config>,
27162- Extension(preamble): Extension<Preamble>,
27163- Extension((templates, context)): Extension<Template>,
27164- ) -> Result<Response, Error> {
27165- let project_url = util::project_url(&uri, cfg.origin.as_str());
27166- let builder = Builder {
27167- templates,
27168- context,
27169- origin: cfg.origin,
27170- title: format!(
27171- "Monthly Update Summary for {}/{}",
27172- preamble.collection_name, preamble.repo_name
27173- ),
27174- time_to_live: cfg.rss_time_to_live.map(Duration::seconds),
27175- current_time: OffsetDateTime::now_utc(),
27176- };
27177- let channel = builder.summary(vec![(preamble.repo_path, project_url)], Timeframe::Monthly)?;
27178- let response = Response::builder()
27179- .header(CONTENT_TYPE, TEXT_XML.as_ref())
27180- .body(Body::new(stylesheet_hack(channel.to_string())))
27181- .unwrap();
27182- Ok(response)
27183- }
27184-
27185- #[cfg(test)]
27186- mod tests {
27187-
27188- use super::*;
27189- use tera::Tera;
27190- use time::{format_description::well_known::Rfc2822, macros::datetime, OffsetDateTime};
27191-
27192- use ayllu_git::testing;
27193-
27194- // Thu, 07 Apr 2005 22:13:13 +0200
27195-
27196- /// all tests assume the current time is as below
27197- const CURRENT_TIME: &str = "Tue, 19 Dec 2023 20:55:10 +0000";
27198-
27199- #[test]
27200- fn test_firehose_commits() {
27201- let mut test_repo = testing::Builder::default().with_commands(vec![
27202- // an old commit to be filtered out
27203- format!(
27204- "echo 'content' > file_1.txt && git add file_1.txt && {} git commit -m 'commit 1'",
27205- testing::timestamp_envs("Tue, 14 Dec 2023 20:55:10 +0000")
27206- )
27207- .as_str(),
27208- format!(
27209- "echo 'content' > file_2.txt && git add file_2.txt && {} git commit -m 'commit 2'",
27210- testing::timestamp_envs("Tue, 19 Dec 2023 20:00:00 +0000")
27211- )
27212- .as_str(),
27213- format!(
27214- "echo 'content' > file_3.txt && git add file_3.txt && {} git commit -m 'commit 3'",
27215- testing::timestamp_envs("Tue, 19 Dec 2023 20:01:00 +0000")
27216- )
27217- .as_str(),
27218- ]);
27219- let (name, path) = test_repo.build().expect("failed to init repo");
27220- let templates =
27221- Tera::new("themes/default/templates/*.html").expect("failed to load templates");
27222- let context = Context::new();
27223- let builder = Builder {
27224- templates,
27225- context,
27226- origin: String::from("localhost:8080"),
27227- title: String::from("test"),
27228- time_to_live: Some(Duration::HOUR),
27229- current_time: OffsetDateTime::parse(CURRENT_TIME, &Rfc2822).unwrap(),
27230- };
27231- let channel = builder
27232- .firehose(vec![(path, name)], Duration::days(1))
27233- .expect("failed to build items");
27234- assert!(channel.items.len() == 2);
27235- assert!(channel.items[0]
27236- .title
27237- .as_ref()
27238- .is_some_and(|title| title == "Commit: commit 3"));
27239- assert!(channel.items[1]
27240- .title
27241- .as_ref()
27242- .is_some_and(|title| title == "Commit: commit 2"));
27243- test_repo.cleanup().expect("failed to cleanup repo");
27244- }
27245-
27246- #[test]
27247- fn test_firehose_releases() {
27248- let mut test_repo = testing::Builder::default().with_commands(vec![
27249- format!(
27250- "echo 'content' > file_1.txt && git add file_1.txt && {} git commit -m 'commit 1'",
27251- testing::timestamp_envs("Tue Dec 19 20:00:00 2023 +0000")
27252- )
27253- .as_str(),
27254- format!(
27255- "echo 'content' > file_2.txt && git add file_2.txt && {} git commit -m 'commit 2'",
27256- testing::timestamp_envs("Tue Dec 19 20:01:00 2023 +0000")
27257- )
27258- .as_str(),
27259- // release a new version
27260- format!(
27261- "{} git tag -m 'release version 0.0.1!' v0.0.1",
27262- testing::timestamp_envs("Tue Dec 19 20:02:00 2023 +0000")
27263- )
27264- .as_str(),
27265- ]);
27266- let (name, path) = test_repo.build().expect("failed to init repo");
27267- let templates =
27268- Tera::new("themes/default/templates/*.html").expect("failed to load templates");
27269- let context = Context::new();
27270- let builder = Builder {
27271- templates,
27272- context,
27273- origin: String::from("localhost:8080"),
27274- title: String::from("test"),
27275- time_to_live: Some(Duration::HOUR),
27276- current_time: OffsetDateTime::parse(CURRENT_TIME, &Rfc2822).unwrap(),
27277- };
27278- let channel = builder
27279- .firehose(vec![(path, name)], Duration::days(1))
27280- .expect("failed to build items");
27281- assert!(channel.items.len() == 3);
27282- assert!(channel.items[0]
27283- .title
27284- .as_ref()
27285- .is_some_and(|title| title == "Commit: commit 2"));
27286- assert!(channel.items[1]
27287- .title
27288- .as_ref()
27289- .is_some_and(|title| title == "Tag: v0.0.1"));
27290- assert!(channel.items[1]
27291- .description
27292- .as_ref()
27293- .is_some_and(|description| description == "release version 0.0.1!\n"));
27294- assert!(channel.items[2]
27295- .title
27296- .as_ref()
27297- .is_some_and(|title| title == "Commit: commit 1"));
27298- assert!(channel.items[0].guid.is_some());
27299- // FIXME: assert!(channel.items[1].guid.is_some());
27300- assert!(channel.items[2].guid.is_some());
27301- test_repo.cleanup().expect("failed to cleanup repo");
27302- }
27303-
27304- #[test]
27305- fn test_feed_1d() {
27306- let mut test_repo = testing::Builder::default().with_commands(vec![
27307- // older commit which is filtered
27308- format!(
27309- "echo 'content' > file_1.txt && git add file_1.txt && {} git commit -m 'commit 1'",
27310- testing::timestamp_envs("Tue Dec 16 20:00:00 2023 +0000")
27311- )
27312- .as_str(),
27313- format!(
27314- "echo 'content' > file_2.txt && git add file_2.txt && {} git commit -m 'commit 2'",
27315- testing::timestamp_envs("Tue Dec 18 20:01:00 2023 +0000")
27316- )
27317- .as_str(),
27318- format!(
27319- "echo 'content' > file_3.txt && git add file_3.txt && {} git commit -m 'commit 3'",
27320- testing::timestamp_envs("Tue Dec 18 20:02:00 2023 +0000")
27321- )
27322- .as_str(),
27323- ]);
27324- let (name, path) = test_repo.build().expect("failed to init repo");
27325- let templates =
27326- Tera::new("themes/default/templates/*.html").expect("failed to load templates");
27327- let context = Context::new();
27328- let builder = Builder {
27329- templates,
27330- context,
27331- origin: String::from("localhost:8080"),
27332- title: String::from("test"),
27333- time_to_live: Some(Duration::HOUR),
27334- current_time: OffsetDateTime::parse(CURRENT_TIME, &Rfc2822).unwrap(),
27335- };
27336- let channel = builder
27337- .summary(vec![(path, name)], Timeframe::Daily)
27338- .expect("failed to build items");
27339- assert!(channel.items.len() == 1);
27340- assert!(channel.items[0]
27341- .description
27342- .as_ref()
27343- .is_some_and(|content| content.contains("commit 2") && content.contains("commit 3")));
27344- assert!(channel.items[0].guid.is_some());
27345- assert!(channel.ttl.as_ref().is_some_and(|ttl| ttl == "60"))
27346- }
27347-
27348- #[test]
27349- fn test_feed_1w() {
27350- let mut test_repo = testing::Builder::default().with_commands(vec![
27351- format!(
27352- "echo 'content' > file_1.txt && git add file_1.txt && {} git commit -m 'commit 1'",
27353- testing::timestamp_envs("Tue Dec 15 20:00:00 2023 +0000")
27354- )
27355- .as_str(),
27356- format!(
27357- "echo 'content' > file_2.txt && git add file_2.txt && {} git commit -m 'commit 2'",
27358- testing::timestamp_envs("Tue Dec 16 20:01:00 2023 +0000")
27359- )
27360- .as_str(),
27361- ]);
27362- let (name, path) = test_repo.build().expect("failed to init repo");
27363- let templates =
27364- Tera::new("themes/default/templates/*.html").expect("failed to load templates");
27365- let context = Context::new();
27366- let builder = Builder {
27367- templates,
27368- context,
27369- origin: String::from("localhost:8080"),
27370- title: String::from("test"),
27371- time_to_live: Some(Duration::HOUR),
27372- current_time: OffsetDateTime::parse(CURRENT_TIME, &Rfc2822).unwrap(),
27373- };
27374- let channel = builder
27375- .summary(vec![(path, name)], Timeframe::Weekly)
27376- .expect("failed to build items");
27377- assert!(channel.items.len() == 1);
27378- assert!(channel.items[0]
27379- .description
27380- .as_ref()
27381- .is_some_and(|content| content.contains("commit 1") && content.contains("commit 2")));
27382- assert!(channel.items[0].guid.is_some());
27383- assert!(channel.ttl.as_ref().is_some_and(|ttl| ttl == "60"))
27384- }
27385-
27386- #[test]
27387- fn test_feed_1m() {
27388- let mut test_repo = testing::Builder::default().with_commands(vec![
27389- format!(
27390- "echo 'content' > file_1.txt && git add file_1.txt && {} git commit -m 'commit 1'",
27391- testing::timestamp_envs("Tue Nov 23 00:01:00 2023 +0000")
27392- )
27393- .as_str(),
27394- format!(
27395- "echo 'content' > file_2.txt && git add file_2.txt && {} git commit -m 'commit 2'",
27396- testing::timestamp_envs("Tue Nov 23 00:01:00 2023 +0000")
27397- )
27398- .as_str(),
27399- // a recent commit to be filtered out
27400- format!(
27401- "echo 'content' > file_3.txt && git add file_3.txt && {} git commit -m 'commit 3'",
27402- testing::timestamp_envs("Tue Dec 18 00:01:00 2023 +0000")
27403- )
27404- .as_str(),
27405- ]);
27406- let (name, path) = test_repo.build().expect("failed to init repo");
27407- let templates =
27408- Tera::new("themes/default/templates/*.html").expect("failed to load templates");
27409- let context = Context::new();
27410- let builder = Builder {
27411- templates,
27412- context,
27413- origin: String::from("localhost:8080"),
27414- title: String::from("test"),
27415- time_to_live: Some(Duration::HOUR),
27416- current_time: OffsetDateTime::parse(CURRENT_TIME, &Rfc2822).unwrap(),
27417- };
27418- let channel = builder
27419- .summary(vec![(path, name)], Timeframe::Monthly)
27420- .expect("failed to build items");
27421- assert!(channel.items.len() == 1);
27422- assert!(channel.items[0]
27423- .description
27424- .as_ref()
27425- .is_some_and(|content| content.contains("commit 1") && content.contains("commit 2")));
27426- assert!(channel.items[0].guid.is_some());
27427- assert!(channel.ttl.as_ref().is_some_and(|ttl| ttl == "60"))
27428- }
27429-
27430- #[test]
27431- fn test_feed_1d_no_commits() {
27432- let mut test_repo = testing::Builder::default().with_commands(vec![
27433- // older commit which is filtered
27434- format!(
27435- "echo 'content' > file_1.txt && git add file_1.txt && {} git commit -m 'commit 1'",
27436- testing::timestamp_envs("Tue Dec 16 20:00:00 2023 +0000")
27437- )
27438- .as_str(),
27439- ]);
27440- let (name, path) = test_repo.build().expect("failed to init repo");
27441- let templates =
27442- Tera::new("themes/default/templates/*.html").expect("failed to load templates");
27443- let context = Context::new();
27444- let builder = Builder {
27445- templates,
27446- context,
27447- origin: String::from("localhost:8080"),
27448- title: String::from("test"),
27449- time_to_live: Some(Duration::HOUR),
27450- current_time: OffsetDateTime::parse(CURRENT_TIME, &Rfc2822).unwrap(),
27451- };
27452- let channel = builder
27453- .summary(vec![(path, name)], Timeframe::Daily)
27454- .expect("failed to build items");
27455- assert!(channel.items.is_empty());
27456- assert!(channel.ttl.as_ref().is_some_and(|ttl| ttl == "60"))
27457- }
27458-
27459- #[test]
27460- fn test_timeframe_1d() {
27461- let now = datetime!(2021-03-05 13:00:55 UTC);
27462- let (start, end) = Timeframe::Daily.clamp(now);
27463- assert!(start == datetime!(2021-03-04 00:00:00 UTC));
27464- assert!(end == datetime!(2021-03-05 00:00:00 UTC));
27465- }
27466-
27467- #[test]
27468- fn test_timeframe_1w() {
27469- let now = datetime!(2021-03-05 13:00:55 UTC);
27470- let (start, end) = Timeframe::Weekly.clamp(now);
27471- assert!(start == datetime!(2021-02-21 00:00:00 UTC));
27472- assert!(end == datetime!(2021-02-28 00:00:00 UTC));
27473- let now = datetime!(2021-03-07 21:34:05 UTC);
27474- let (start, end) = Timeframe::Weekly.clamp(now);
27475- assert!(start == datetime!(2021-02-28 00:00:00 UTC));
27476- assert!(end == datetime!(2021-03-07 00:00:00 UTC));
27477- }
27478-
27479- #[test]
27480- fn test_timeframe_1m() {
27481- let now = datetime!(2021-03-05 13:00:55 UTC);
27482- let (start, end) = Timeframe::Monthly.clamp(now);
27483- assert!(start == datetime!(2021-02-01 00:00:00 UTC));
27484- assert!(end == datetime!(2021-02-28 00:00:00 UTC));
27485- let now = datetime!(2021-01-05 13:00:55 UTC);
27486- let (start, end) = Timeframe::Monthly.clamp(now);
27487- assert!(start == datetime!(2020-12-01 00:00:00 UTC));
27488- assert!(end == datetime!(2020-12-31 00:00:00 UTC));
27489- }
27490- }
27491 diff --git a/src/web2/routes/xmpp.rs b/src/web2/routes/xmpp.rs
27492deleted file mode 100644
27493index 749bdce..0000000
27494--- a/src/web2/routes/xmpp.rs
27495+++ /dev/null
27496 @@ -1,101 +0,0 @@
27497- use axum::{
27498- extract::{Extension, Path},
27499- response::Html,
27500- };
27501- use serde::{Deserialize, Serialize};
27502-
27503- use crate::config::Config;
27504- use crate::web2::error::Error;
27505- use crate::web2::extractors::config::ConfigReader;
27506- use crate::web2::middleware::rpc_initiator::{Initiator, Kind as InitiatorKind};
27507- use crate::web2::middleware::template::Template;
27508- use crate::web2::navigation;
27509- use ayllu_api::xmpp_capnp::server::Client as XmppClient;
27510-
27511- #[derive(Debug, Serialize)]
27512- struct ChannelWithStats {
27513- pub name: String,
27514- pub n_users: i64,
27515- pub n_messages: i64,
27516- pub online: bool,
27517- }
27518-
27519- #[derive(Debug, Serialize)]
27520- struct XmppMessage {
27521- pub id: String,
27522- pub nickname: String,
27523- pub timestamp: String,
27524- pub body: String,
27525- }
27526- pub async fn channels(
27527- Extension(_cfg): Extension<Config>,
27528- Extension((templates, mut ctx)): Extension<Template>,
27529- Extension(initiator): Extension<Initiator>,
27530- ) -> Result<Html<String>, Error> {
27531- ctx.insert("title", "Discussions");
27532- ctx.insert("nav_elements", &navigation::global("xmpp", true));
27533- let xmpp_client = initiator.client(InitiatorKind::Xmpp).unwrap();
27534- let channels = xmpp_client
27535- .invoke(move |c: XmppClient| async move {
27536- let mut channels: Vec<ChannelWithStats> = Vec::new();
27537- let req = c.stats_request();
27538- let stats = req.send().promise.await?;
27539- for stat in stats.get()?.get_stats()? {
27540- channels.push(ChannelWithStats {
27541- name: stat.get_name()?.to_string().unwrap(),
27542- n_users: stat.get_users_online(),
27543- n_messages: stat.get_n_messages(),
27544- online: true,
27545- })
27546- }
27547- Ok(channels)
27548- })
27549- .await?;
27550- ctx.insert("channels", &channels);
27551- let body = templates.render("channels.html", &ctx)?;
27552- Ok(Html(body))
27553- }
27554-
27555- #[derive(Deserialize)]
27556- pub struct ChannelParams {
27557- pub channel: String,
27558- pub last_message: Option<String>,
27559- }
27560-
27561- pub async fn channel(
27562- Path(params): Path<ChannelParams>,
27563- Extension(_cfg): Extension<Config>,
27564- ConfigReader(config): ConfigReader,
27565- Extension((templates, mut ctx)): Extension<Template>,
27566- Extension(initiator): Extension<Initiator>,
27567- ) -> Result<Html<String>, Error> {
27568- ctx.insert("title", "lists");
27569- ctx.insert("nav_elements", &navigation::global("xmpp", true));
27570- ctx.insert("channel", &params.channel);
27571- let xmpp_client = initiator.client(InitiatorKind::Xmpp).unwrap();
27572- let messages = xmpp_client
27573- .invoke(move |c: XmppClient| async move {
27574- let mut xmpp_messages: Vec<XmppMessage> = Vec::new();
27575- let mut req = c.messages_request();
27576- req.get().set_channel_name(params.channel.as_str().into());
27577- req.get().set_limit(config.items_per_page as i64);
27578- if let Some(id) = params.last_message {
27579- req.get().set_last_message(id.as_str().into());
27580- }
27581- let messages = req.send().promise.await?;
27582- for message in messages.get()?.get_messages()? {
27583- xmpp_messages.push(XmppMessage {
27584- id: message.get_message_id()?.to_string().unwrap(),
27585- nickname: message.get_nickname()?.to_string().unwrap(),
27586- timestamp: message.get_timestamp()?.to_string().unwrap(),
27587- body: message.get_body()?.to_string().unwrap(),
27588- })
27589- }
27590- Ok(xmpp_messages)
27591- })
27592- .await?;
27593- ctx.insert("messages", &messages);
27594- // ctx.insert("lists", &cfg.mail.unwrap().lists);
27595- let body = templates.render("channel.html", &ctx)?;
27596- Ok(Html(body))
27597- }
27598 diff --git a/src/web2/server.rs b/src/web2/server.rs
27599deleted file mode 100644
27600index 1738afe..0000000
27601--- a/src/web2/server.rs
27602+++ /dev/null
27603 @@ -1,299 +0,0 @@
27604- use std::collections::HashMap;
27605- use std::error::Error;
27606- use std::fs;
27607- use std::net::SocketAddrV4;
27608- use std::sync::Arc;
27609-
27610- use axum::{
27611- body::Body, extract::Request, middleware::from_fn_with_state, routing, Extension, Router,
27612- ServiceExt,
27613- };
27614- use globwalk::glob_builder;
27615- use tera::Tera;
27616- use tokio::net::TcpListener;
27617- use tower::Layer;
27618- use tower_http::{
27619- normalize_path::NormalizePathLayer,
27620- trace::{DefaultOnResponse, TraceLayer},
27621- };
27622- use tracing::{Level, Span};
27623-
27624- use crate::config::Config;
27625- use crate::languages::{Hint, LANGUAGE_TABLE};
27626- use crate::web2::highlight::{Highlighter, Loader, TreeSitterAdapter};
27627- use crate::web2::middleware::error;
27628- use crate::web2::middleware::repository;
27629- use crate::web2::middleware::rpc_initiator;
27630- use crate::web2::middleware::sites;
27631- use crate::web2::middleware::template;
27632- use crate::web2::routes::about;
27633- use crate::web2::routes::assets;
27634- use crate::web2::routes::authors;
27635- use crate::web2::routes::blame;
27636- use crate::web2::routes::blob;
27637- use crate::web2::routes::builds;
27638- use crate::web2::routes::chart;
27639- use crate::web2::routes::commit;
27640- use crate::web2::routes::config;
27641- use crate::web2::routes::finger;
27642- use crate::web2::routes::index;
27643- use crate::web2::routes::log as log_route;
27644- use crate::web2::routes::mail;
27645- use crate::web2::routes::refs;
27646- use crate::web2::routes::repo;
27647- use crate::web2::routes::robots;
27648- use crate::web2::routes::rss;
27649- use crate::web2::routes::xmpp;
27650- use crate::web2::terautil;
27651- use ayllu_database::Builder;
27652-
27653- pub async fn serve(cfg: &Config) -> Result<(), Box<dyn Error>> {
27654- let keywords = match &cfg.tree_sitter {
27655- Some(ts_config) => {
27656- let mut loader = Loader::new(&ts_config.base_path, &ts_config.queries_path);
27657- loader = loader.dynamic(true);
27658- match &ts_config.queries_extras_path {
27659- Some(extras_path) => {
27660- loader = loader.extra_queries(extras_path.as_str());
27661- }
27662- None => {}
27663- }
27664- match &ts_config.parsers {
27665- Some(parsers) => {
27666- loader = loader.parsers(parsers.clone());
27667- }
27668- None => {}
27669- }
27670- loader.load()?;
27671- ts_config.keywords.clone()
27672- }
27673- None => {
27674- log::warn!("tree-sitter is not configured, syntax highlighting will be disabled");
27675- Vec::new()
27676- }
27677- };
27678-
27679- match &cfg.languages {
27680- Some(languages) => {
27681- LANGUAGE_TABLE.set_languages(languages.extras.clone());
27682- LANGUAGE_TABLE.set_mappings(HashMap::from_iter(languages.mappings.iter().map(
27683- |(key, value)| {
27684- let kvs: (Hint, Hint) = (key.into(), value.into());
27685- kvs
27686- },
27687- )))
27688- }
27689- None => {}
27690- }
27691-
27692- let highlighter = Highlighter::new(&keywords);
27693- let adapter = TreeSitterAdapter::new(highlighter.clone());
27694-
27695- let db = Builder::default()
27696- .url(&cfg.database.path)
27697- .log_queries(cfg.log_level == "DEBUG")
27698- .read_only(true) // Web UI is 100% read-only
27699- .build()
27700- .await?;
27701-
27702- // NOTE: files modified on the file system will see their changes
27703- // immediately in the rendered server output however added new files
27704- // will require that the server be restarted.
27705- let templates_path = format!("{}/{}/templates/*", cfg.web.themes_path, cfg.web.base_theme);
27706- // map of all loaded themes at startup, new themes require that the server
27707- // be restarted for now.
27708- let mut templates: Vec<(String, Tera)> = Vec::new();
27709- let mut templates_base = Tera::new(&templates_path)?;
27710- templates_base.register_filter("friendly_time", terautil::FriendlyTime {});
27711- templates_base.register_filter("format_epoch", terautil::FormatEpoch {});
27712- templates_base.register_filter("filemode", terautil::FileMode {});
27713- templates_base.register_filter("human_bytes", terautil::HumanBytes {});
27714- templates_base.register_filter("emoji", terautil::Emojis {});
27715-
27716- for theme in cfg.web.themes.iter().filter(|item| *item != "default") {
27717- let templates_base_path = format!("{}/{}/templates", cfg.web.themes_path, theme);
27718- if fs::metadata(templates_base_path.clone()).is_err() {
27719- // its valid to include a theme without any templates
27720- continue;
27721- }
27722- let templates_path = format!("{}/*.html", templates_base_path);
27723- let mut template_files: Vec<(String, Option<String>)> = Vec::new();
27724- match glob_builder(templates_path).build() {
27725- Ok(globwalker) => {
27726- for entry in globwalker.into_iter() {
27727- let entry = entry?;
27728- let path = String::from(entry.path().to_str().unwrap());
27729- let name = String::from(entry.file_name().to_str().unwrap());
27730- log::info!("loaded template override {} {}", path, name);
27731- template_files.push((path, Some(name)));
27732- }
27733- }
27734- Err(_) => {
27735- continue;
27736- }
27737- }
27738- let mut tera_theme = templates_base.clone();
27739- if !template_files.is_empty() {
27740- log::info!(
27741- "loaded {} template files for theme {}",
27742- template_files.len(),
27743- theme
27744- );
27745- tera_theme.add_template_files(template_files)?;
27746- }
27747- templates.push((theme.clone(), tera_theme));
27748- }
27749- templates.push((String::from("default"), templates_base));
27750-
27751- let site_mapping = if cfg.sites.enabled {
27752- sites::sites(cfg.clone())?
27753- } else {
27754- Vec::new()
27755- };
27756-
27757- let mail_required_plugins: &'static [rpc_initiator::Kind] = &[rpc_initiator::Kind::Mail];
27758- let xmpp_required_plugins: &'static [rpc_initiator::Kind] = &[rpc_initiator::Kind::Xmpp];
27759-
27760- let address: SocketAddrV4 = cfg.http.address.parse()?;
27761- let app = NormalizePathLayer::trim_trailing_slash().layer(
27762- Router::new()
27763- .route("/robots.txt", routing::get(robots::serve))
27764- .nest(
27765- "/",
27766- Router::new()
27767- .route("/", routing::get(index::index))
27768- .route("/browse", routing::get(index::index))
27769- .route("/:collection", routing::get(index::collection))
27770- .route("/rss/firehose.xml", routing::get(rss::feed_firehose))
27771- .route("/rss/1d.xml", routing::get(rss::feed_1d))
27772- .route("/rss/1w.xml", routing::get(rss::feed_1w))
27773- .route("/rss/1m.xml", routing::get(rss::feed_1m))
27774- .route("/about", routing::get(about::serve))
27775- .route("/config", routing::get(config::serve).post(config::update))
27776- .layer(from_fn_with_state(
27777- Arc::new((cfg.clone(), templates.clone())),
27778- template::middleware,
27779- )),
27780- )
27781- .nest(
27782- "/mail",
27783- Router::new()
27784- .route("/", routing::get(mail::lists))
27785- .route("/:list_id", routing::get(mail::threads))
27786- .route("/export/:list_id", routing::get(mail::export))
27787- .route("/export/:list_id/:thread_id", routing::get(mail::export))
27788- .route(
27789- "/export/:list_id/:thread_id/:message_id",
27790- routing::get(mail::export),
27791- )
27792- .route("/thread/:list_id/:thread_id", routing::get(mail::thread))
27793- .route("/message/:list_id/:message_id", routing::get(mail::message))
27794- .layer(from_fn_with_state(
27795- Arc::new((cfg.clone(), templates.clone(), mail_required_plugins)),
27796- rpc_initiator::required,
27797- ))
27798- .layer(from_fn_with_state(
27799- Arc::new((cfg.clone(), templates.clone())),
27800- template::middleware,
27801- )),
27802- )
27803- .nest(
27804- "/xmpp",
27805- Router::new()
27806- .route("/", routing::get(xmpp::channels))
27807- .route("/:channel", routing::get(xmpp::channel))
27808- .route("/:channel/:last_message", routing::get(xmpp::channel))
27809- .layer(from_fn_with_state(
27810- Arc::new((cfg.clone(), templates.clone(), xmpp_required_plugins)),
27811- rpc_initiator::required,
27812- ))
27813- .layer(from_fn_with_state(
27814- Arc::new((cfg.clone(), templates.clone())),
27815- template::middleware,
27816- )),
27817- )
27818- .nest(
27819- "/:collection/:name",
27820- Router::new()
27821- .route("/", routing::get(repo::serve))
27822- .route(
27823- "/rss/firehose.xml",
27824- routing::get(rss::feed_repository_firehose),
27825- )
27826- .route("/rss/1d.xml", routing::get(rss::feed_repository_1d))
27827- .route("/rss/1w.xml", routing::get(rss::feed_repository_1w))
27828- .route("/rss/1m.xml", routing::get(rss::feed_repository_1m))
27829- .route("/commit/:commit_id", routing::get(commit::serve))
27830- .route("/tree/:commitish", routing::get(repo::serve))
27831- .route("/tree/:commitish/*file_path", routing::get(repo::serve))
27832- .route("/log", routing::get(log_route::serve))
27833- .route("/log/:commitish", routing::get(log_route::serve))
27834- .route("/log/:commitish/*file_path", routing::get(log_route::serve))
27835- .route("/charts", routing::get(chart::serve))
27836- .route("/chart/:kind", routing::get(chart::serve))
27837- .route("/chart/:kind/:commit_id", routing::get(chart::serve))
27838- .route(
27839- "/:kind/:commit_id/:width/:height",
27840- routing::get(chart::serve),
27841- )
27842- .route("/authors", routing::get(authors::serve))
27843- .route("/blame/:commitish/*file_path", routing::get(blame::serve))
27844- .route("/blob/:commitish/*file_path", routing::get(blob::serve))
27845- .route("/raw/:commitish/*file_path", routing::get(blob::serve_raw))
27846- .route("/builds", routing::get(builds::serve))
27847- .route("/builds/badge/:commitish", routing::get(builds::badge))
27848- .route("/builds/:commit_id", routing::get(builds::details))
27849- .route(
27850- "/builds/:commit_id/:build_id",
27851- routing::get(builds::details),
27852- )
27853- .route("/refs", routing::get(refs::tags))
27854- .route("/refs/branches", routing::get(refs::branches))
27855- .route("/refs/tags", routing::get(refs::tags))
27856- .route("/refs/archive/:ref_id", routing::get(refs::archive))
27857- .layer(from_fn_with_state(cfg.clone(), repository::middleware))
27858- .layer(from_fn_with_state(
27859- Arc::new(cfg.clone()),
27860- rpc_initiator::optional,
27861- ))
27862- .layer(from_fn_with_state(
27863- Arc::new((cfg.clone(), templates.clone())),
27864- template::middleware,
27865- )),
27866- )
27867- .route(
27868- "/.well-known/webfinger",
27869- routing::get(finger::serve).layer(Extension(finger::CResolver {
27870- domain: "todo",
27871- config: Arc::new(cfg.clone()),
27872- })),
27873- )
27874- .route("/static/main.min.css", routing::get(assets::serve_css))
27875- .route("/static/assets/:asset", routing::get(assets::serve_asset))
27876- .layer(Extension(cfg.clone()))
27877- .layer(Extension(Arc::new(db)))
27878- .layer(Extension(highlighter))
27879- .layer(Extension(adapter))
27880- // error handling
27881- .layer(from_fn_with_state(
27882- Arc::new((cfg.clone(), templates.clone())),
27883- error::middleware,
27884- ))
27885- // git hosted static sites
27886- .layer(from_fn_with_state(
27887- (cfg.clone(), site_mapping),
27888- sites::middleware,
27889- ))
27890- .layer(
27891- TraceLayer::new_for_http()
27892- .on_request(|request: &Request<Body>, _span: &Span| {
27893- tracing::info!("started {} {}", request.method(), request.uri().path())
27894- })
27895- .on_response(DefaultOnResponse::new().level(Level::INFO)),
27896- ),
27897- );
27898- log::info!("listening @ {}", cfg.http.address);
27899- let listener = TcpListener::bind(address).await?;
27900- axum::serve(listener, ServiceExt::<Request>::into_make_service(app)).await?;
27901- Ok(())
27902- }
27903 diff --git a/src/web2/terautil/filters.rs b/src/web2/terautil/filters.rs
27904deleted file mode 100644
27905index 6eef1cc..0000000
27906--- a/src/web2/terautil/filters.rs
27907+++ /dev/null
27908 @@ -1,80 +0,0 @@
27909- use tera::{to_value, Filter, Result, Value};
27910-
27911- use file_mode::Mode;
27912- use time::{format_description::well_known, OffsetDateTime};
27913-
27914- use crate::time as ctime;
27915- use crate::web2::util;
27916-
27917- pub struct FriendlyTime {}
27918-
27919- impl Filter for FriendlyTime {
27920- fn filter(&self, value: &Value, _: &std::collections::HashMap<String, Value>) -> Result<Value> {
27921- let ts = ctime::friendly(value.as_u64().unwrap());
27922- let result = to_value(ts)?;
27923- Ok(result)
27924- }
27925- }
27926-
27927- pub struct FormatEpoch {}
27928-
27929- impl Filter for FormatEpoch {
27930- fn filter(&self, value: &Value, _: &std::collections::HashMap<String, Value>) -> Result<Value> {
27931- let ts = OffsetDateTime::from_unix_timestamp(value.as_i64().unwrap()).unwrap();
27932- let formatted = ts.format(&well_known::Rfc2822).unwrap();
27933- let result = to_value(formatted)?;
27934- Ok(result)
27935- }
27936- }
27937-
27938- pub struct FileMode {}
27939-
27940- impl Filter for FileMode {
27941- fn filter(&self, value: &Value, _: &std::collections::HashMap<String, Value>) -> Result<Value> {
27942- let filemode = value.as_u64().unwrap() as u32;
27943- let mode = Mode::new(filemode, 0o777);
27944- let result = to_value(mode.to_string())?;
27945- Ok(result)
27946- }
27947- }
27948-
27949- pub struct HumanBytes {}
27950-
27951- impl Filter for HumanBytes {
27952- fn filter(&self, value: &Value, _: &std::collections::HashMap<String, Value>) -> Result<Value> {
27953- let bytes = value.as_f64().unwrap();
27954- let result = to_value(util::human_bytes(bytes))?;
27955- Ok(result)
27956- }
27957- }
27958-
27959- pub struct Emojis {}
27960-
27961- impl Filter for Emojis {
27962- fn filter(&self, value: &Value, _: &std::collections::HashMap<String, Value>) -> Result<Value> {
27963- // TODO: load these dynamically with the rest of the theme assets
27964- let name = value.as_str().unwrap();
27965- let svg_content = match name {
27966- "books" => include_str!("../../../themes/default/assets/books.svg"),
27967- "building" => include_str!("../../../themes/default/assets/building.svg"),
27968- "check" => include_str!("../../../themes/default/assets/check.svg"),
27969- "chat" => include_str!("../../../themes/default/assets/chat.svg"),
27970- "code" => include_str!("../../../themes/default/assets/code.svg"),
27971- "feed" => include_str!("../../../themes/default/assets/feed.svg"),
27972- "moon" => include_str!("../../../themes/default/assets/moon.svg"),
27973- "textile-pattern-1" => {
27974- include_str!("../../../themes/default/assets/textile-pattern-1.svg")
27975- }
27976- "textile-pattern-2" => {
27977- include_str!("../../../themes/default/assets/textile-pattern-2.svg")
27978- }
27979- "question" => include_str!("../../../themes/default/assets/question.svg"),
27980- "rss" => include_str!("../../../themes/default/assets/rss.svg"),
27981- "scale" => include_str!("../../../themes/default/assets/scale.svg"),
27982- "xmpp" => include_str!("../../../themes/default/assets/xmpp.svg"),
27983- _ => "",
27984- };
27985- let result = to_value(svg_content)?;
27986- Ok(result)
27987- }
27988- }
27989 diff --git a/src/web2/terautil/loader.rs b/src/web2/terautil/loader.rs
27990deleted file mode 100644
27991index 54d3933..0000000
27992--- a/src/web2/terautil/loader.rs
27993+++ /dev/null
27994 @@ -1,55 +0,0 @@
27995- use serde::{Deserialize, Serialize};
27996- use tera::{Context, Tera};
27997-
27998- const DEFAULT_NAV: &[(&str, &str)] = &[];
27999-
28000- /// top-level theme options available in all pages
28001- #[derive(Serialize, Deserialize, Debug, Default)]
28002- pub struct Options {
28003- pub origin: String,
28004- pub site_name: String,
28005- pub collection: Option<String>,
28006- pub name: Option<String>,
28007- pub url: String,
28008- pub path: String,
28009- pub fluid: bool,
28010- pub subpath_mode: bool,
28011- }
28012-
28013- pub struct Loader {
28014- pub templates: Vec<(String, Tera)>,
28015- pub default_theme: String,
28016- }
28017-
28018- impl Loader {
28019- pub fn load(&self, options: Options, theme_name: Option<String>) -> (Tera, Context) {
28020- let _default_template = || {
28021- self.templates
28022- .iter()
28023- .find(|template| template.0 == self.default_theme)
28024- .unwrap()
28025- .1
28026- .clone()
28027- };
28028- let template = match theme_name {
28029- Some(name) => match self.templates.iter().find(|template| template.0 == name) {
28030- Some(template) => template.1.clone(),
28031- None => _default_template(),
28032- },
28033- None => _default_template(),
28034- };
28035- let mut ctx = Context::new();
28036- ctx.insert("title", "");
28037- ctx.insert("origin", &options.origin);
28038- ctx.insert("collection", &options.collection);
28039- ctx.insert("name", &options.name);
28040- ctx.insert("site_name", &options.site_name.clone());
28041- ctx.insert("nav_elements", DEFAULT_NAV);
28042- // let url = req.uri();
28043- ctx.insert("url", &options.url);
28044- ctx.insert("path", &options.path);
28045- ctx.insert("fluid", &false);
28046- ctx.insert("subpath_mode", &options.subpath_mode);
28047- (template, ctx)
28048- }
28049- }
28050 diff --git a/src/web2/terautil/mod.rs b/src/web2/terautil/mod.rs
28051deleted file mode 100644
28052index b06a8ca..0000000
28053--- a/src/web2/terautil/mod.rs
28054+++ /dev/null
28055 @@ -1,5 +0,0 @@
28056- mod filters;
28057- mod loader;
28058-
28059- pub use filters::*;
28060- pub use loader::{Loader, Options};
28061 diff --git a/src/web2/util.rs b/src/web2/util.rs
28062deleted file mode 100644
28063index 5ad6260..0000000
28064--- a/src/web2/util.rs
28065+++ /dev/null
28066 @@ -1,100 +0,0 @@
28067- use std::path::PathBuf;
28068-
28069- use axum::http::Uri;
28070- use url::Url;
28071-
28072- // select a segment of the path from the given url between start and end.
28073- // e.g.
28074- // http://fuu.bar/baz/qux 0 1 -> Some(/baz)
28075- // http://fuu.bar/baz/qux 0 None -> Some(/baz/qux)
28076- // http://fuu.bar/baz/qux 5 10 -> None
28077- // http://fuu.bar/baz/qux/tree/some/path.txt 2 None -> Some(/some/path.txt)
28078- pub fn select_path(uri: &Uri, start: Option<usize>, end: Option<usize>) -> Option<PathBuf> {
28079- let mut path = PathBuf::from("/");
28080- if let Some(stripped) = uri.path().strip_prefix('/') {
28081- stripped.split('/').enumerate().for_each(|(i, segment)| {
28082- match (start, end) {
28083- (Some(start), Some(end)) => {
28084- if i >= start && i <= end {
28085- path.push(segment);
28086- }
28087- }
28088- (Some(start), None) => {
28089- if i >= start {
28090- path.push(segment);
28091- }
28092- }
28093- (None, Some(end)) => {
28094- if i <= end {
28095- path.push(segment)
28096- }
28097- }
28098- (None, None) => {}
28099- };
28100- })
28101- }
28102- match path.iter().count() {
28103- 0 | 1 => None,
28104- _ => Some(path),
28105- }
28106- }
28107-
28108- pub fn project_url(uri: &Uri, origin: &str) -> String {
28109- let project_path = select_path(uri, Some(0), Some(1));
28110- let project_path = project_path.unwrap_or(PathBuf::from("/"));
28111- let mut url = Url::parse(origin).unwrap();
28112- url.set_path(project_path.to_str().unwrap());
28113- url.to_string()
28114- }
28115-
28116- /// construct an LFS url based on the template
28117- pub fn lfs_url(template: &str, collection: &str, name: &str, oid: &str) -> String {
28118- let url = template.to_string();
28119- let url = url.replace("{collection}", collection);
28120- let url = url.replace("{name}", name);
28121- url.replace("{oid}", oid)
28122- }
28123-
28124- const UNIT: f64 = 1024.0;
28125- const SUFFIX: [&str; 4] = ["B", "KiB", "MiB", "GiB"];
28126-
28127- // return bytes as a human readable string
28128- pub fn human_bytes(size: f64) -> String {
28129- let base = size.log10() / UNIT.log10();
28130- let result = format!("{:.1}", UNIT.powf(base - base.floor()),)
28131- .trim_end_matches(".0")
28132- .to_owned();
28133- [&result, SUFFIX[base.floor() as usize]].join(" ")
28134- }
28135-
28136- #[cfg(test)]
28137- mod tests {
28138- use super::*;
28139- #[test]
28140- fn test_select_path() {
28141- let as_string = |path: Option<PathBuf>| {
28142- let output = path.unwrap().to_str().unwrap().to_string();
28143- output
28144- };
28145-
28146- let input = &Uri::try_from("https://ayllu-forge.org").unwrap();
28147- assert!(select_path(input, None, None,).is_none());
28148- assert!(select_path(input, Some(0), Some(2),).is_none());
28149-
28150- let input = &Uri::try_from("https://ayllu-forge.org/projects/ayllu").unwrap();
28151- assert!(as_string(select_path(input, None, Some(1))) == "/projects/ayllu");
28152-
28153- let input = &Uri::try_from("https://ayllu-forge.org/projects/ayllu/tree/main").unwrap();
28154- assert!(as_string(select_path(input, Some(2), None)) == "/tree/main");
28155-
28156- let input = &Uri::try_from("https://ayllu-forge.org/projects/ayllu/tree/main").unwrap();
28157- assert!(select_path(input, Some(4), None).is_none());
28158-
28159- let input = &Uri::try_from("https://ayllu-forge.org/projects/ayllu/tree/main/").unwrap();
28160- assert!(select_path(input, Some(4), None).is_none());
28161-
28162- let input =
28163- &Uri::try_from("https://ayllu-forge.org/projects/ayllu/tree/main/src/web").unwrap();
28164- assert!(as_string(select_path(input, Some(4), None)) == "/src/web");
28165- }
28166- }
28167 diff --git a/themes/adwaita/templates/.gitkeep b/themes/adwaita/templates/.gitkeep
28168deleted file mode 100644
28169index e69de29..0000000
28170--- a/themes/adwaita/templates/.gitkeep
28171+++ /dev/null
28172 diff --git a/themes/adwaita/theme.scss b/themes/adwaita/theme.scss
28173deleted file mode 100644
28174index 4716ec0..0000000
28175--- a/themes/adwaita/theme.scss
28176+++ /dev/null
28177 @@ -1,171 +0,0 @@
28178- // https://en.wikipedia.org/wiki/Adwaita_(design_language)
28179- // https://developer.gnome.org/hig/reference/palette.html
28180-
28181- $blue1: rgb(153, 193, 241);
28182- $blue2: rgb(98, 160, 234 );
28183- $blue3: rgb(53, 132, 228 );
28184- $blue4: rgb(28, 113, 216 );
28185- $blue5: rgb(26, 95, 180 );
28186- $green1: rgb(143, 240, 164);
28187- $green2: rgb(87, 227 ,137);
28188- $green3: rgb(51, 209 ,122);
28189- $green4: rgb(46, 194 ,126);
28190- $green5: rgb(38, 162 ,105);
28191- $yellow1: rgb(249, 240, 107);
28192- $yellow2: rgb(248, 228, 92);
28193- $yellow3: rgb(246, 211, 45);
28194- $yellow4: rgb(245, 194, 17);
28195- $yellow5: rgb(229, 165, 10);
28196- $orange1: rgb(255, 190, 111);
28197- $orange2: rgb(255, 163, 72);
28198- $orange3: rgb(255, 120, 0);
28199- $orange4: rgb(230, 97, 0);
28200- $orange5: rgb(198, 70, 0);
28201- $red1: rgb(246, 97, 81);
28202- $red2: rgb(237, 51, 59);
28203- $red3: rgb(224, 27, 36);
28204- $red4: rgb(192, 28, 40);
28205- $red5: rgb(165, 29, 45);
28206- $purple1: rgb(220, 138, 221);
28207- $purple2: rgb(192, 97, 203);
28208- $purple3: rgb(145, 65, 172);
28209- $purple4: rgb(129, 61, 156);
28210- $purple5: rgb(97, 53, 131);
28211- $brown1: rgb(205, 171, 143);
28212- $brown2: rgb(181, 131, 90);
28213- $brown3: rgb(152, 106, 68);
28214- $brown4: rgb(134, 94, 60);
28215- $brown5: rgb(99, 69, 44);
28216- $light1: rgb(255, 255, 255);
28217- $light2: rgb(246, 245, 244);
28218- $light3: rgb(222, 221, 218);
28219- $light4: rgb(192, 191, 188);
28220- $light5: rgb(154, 153, 150);
28221- $dark1: rgb(119, 118, 123);
28222- $dark2: rgb(94, 92, 100);
28223- $dark3: rgb(61, 56, 70);
28224- $dark4: rgb(36, 31, 49);
28225- $dark5: rgb(0, 0, 0);
28226-
28227- // pico overrides
28228- $grey-50: $light2; /// 96%
28229- $grey-100: darken($grey-50, 5%);
28230- $grey-200: darken($grey-50, 10%);
28231- $grey-300: darken($grey-50, 20%);
28232- $grey-400: darken($grey-50, 30%);
28233- $grey-500: darken($grey-50, 40%);
28234- $grey-600: darken($grey-50, 50%);
28235- $grey-700: darken($grey-50, 60%);
28236- $grey-800: darken($grey-50, 70%);
28237- $grey-900: darken($grey-50, 80%);
28238-
28239-
28240- // Light Blue
28241- $primary-50: $blue1;
28242- $primary-100: darken($primary-50, 5%);
28243- $primary-200: darken($primary-50, 10%);
28244- $primary-300: darken($primary-50, 20%);
28245- $primary-400: darken($primary-50, 25%);
28246- $primary-500: darken($primary-50, 30%);
28247- $primary-600: darken($primary-50, 35%);
28248- $primary-700: darken($primary-50, 40%);
28249- $primary-800: darken($primary-50, 45%);
28250- $primary-900: darken($primary-50, 50%);
28251-
28252- // Black & White
28253- $black: $dark1;
28254- $white: $light1;
28255-
28256- // Amber
28257- $amber-50: $orange1;
28258- $amber-100: darken($amber-50, 5%);
28259- $amber-200: darken($amber-50, 10%);
28260- $amber-300: darken($amber-50, 15%);
28261- $amber-400: darken($amber-50, 20%);
28262- $amber-500: darken($amber-50, 25%);
28263- $amber-600: darken($amber-50, 30%);
28264- $amber-700: darken($amber-50, 35%);
28265- $amber-800: darken($amber-50, 40%);
28266- $amber-900: darken($amber-50, 45%);
28267-
28268- // Green
28269- $green-50: $green1;
28270- $green-100: darken($green-50, 5%);
28271- $green-200: darken($green-50, 10%);
28272- $green-300: darken($green-50, 15%);
28273- $green-400: darken($green-50, 20%);
28274- $green-500: darken($green-50, 25%);
28275- $green-600: darken($green-50, 30%);
28276- $green-700: darken($green-50, 35%);
28277- $green-800: darken($green-50, 40%);
28278- $green-900: darken($green-50, 45%);
28279-
28280- // Red
28281- $red-50: $red1;
28282- $red-100: darken($red-50, 5%);
28283- $red-200: darken($red-50, 10%);
28284- $red-300: darken($red-50, 15%);
28285- $red-400: darken($red-50, 20%);
28286- $red-500: darken($red-50, 25%);
28287- $red-600: darken($red-50, 30%);
28288- $red-700: darken($red-50, 35%);
28289- $red-800: darken($red-50, 40%);
28290- $red-900: darken($red-50, 45%);
28291-
28292- $primary-500: $blue1;
28293- $primary-600: $blue2;
28294- $primary-700: $blue3;
28295-
28296- //
28297- //
28298- // tree sitter highlighting
28299- // https://github.com/Mofiqul/adwaita.nvim
28300- //
28301- // extra colors for highlighting
28302- $teal1: #93DDC2;
28303- $teal2: #5BC8AF;
28304- $teal3: #33B2A4;
28305- $teal4: #26A1A2;
28306- $teal5: #218787;
28307- $violet2: #7D8AC7;
28308- $violet3: #6362C8;
28309- $violet4: #4E57BA;
28310-
28311- span.ts_attribute {color: $orange4};
28312- span.ts_constant {color: $purple4};
28313- span.ts_function.builtin {color: $blue4};
28314- span.ts_function {color: $blue4};
28315- span.ts_keyword {color: $orange4};
28316- span.ts_operator {color: $purple4};
28317- span.ts_property {color: $blue1};
28318- span.ts_punctuation {color: $blue1};
28319- span.ts_punctuation.bracket {color: $blue1};
28320- span.ts_punctuation.delimiter {color: $blue1};
28321- span.ts_string {color: $teal2};
28322- span.ts_string.special {color: $teal3};
28323- span.ts_tag {color: $teal2};
28324- span.ts_type {color: $teal2};
28325- span.ts_type.builtin {color: $blue1};
28326- span.ts_variable {color: $light4};
28327- span.ts_variable.builtin {color: $light4};
28328- span.ts_variable.parameter {color: $light4};
28329-
28330- @media (prefers-color-scheme: dark) {
28331- span.label {
28332- color: $dark5;
28333- }
28334- }
28335-
28336- $positive: $green1;
28337- $negative: $red1;
28338-
28339- $blame-border: $purple1;
28340-
28341- $highlighted: $purple1;
28342- $icon-background: $purple1;
28343-
28344- $chart-color: $purple1;
28345-
28346- @import "@picocss/pico/scss/pico";
28347- @import "../default/layout.scss";
28348- @import "../default/base.scss";
28349 diff --git a/themes/default/assets/ayllu_logo.svg b/themes/default/assets/ayllu_logo.svg
28350deleted file mode 100644
28351index 28124d3..0000000
28352--- a/themes/default/assets/ayllu_logo.svg
28353+++ /dev/null
28354 @@ -1,90 +0,0 @@
28355- <?xml version="1.0" encoding="UTF-8" standalone="no"?>
28356- <svg xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:cc="http://creativecommons.org/ns#" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:svg="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg" xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" inkscape:version="1.0 (1.0+r73+1)" sodipodi:docname="textil-inca-pattern-3a.svg" id="svg159" version="1.1" viewBox="0 0 902 702">
28357- <metadata id="metadata165">
28358- <rdf:RDF>
28359- <cc:Work rdf:about="">
28360- <dc:format>image/svg+xml</dc:format>
28361- <dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage"/>
28362- </cc:Work>
28363- </rdf:RDF>
28364- </metadata>
28365- <defs id="defs163"/>
28366- <sodipodi:namedview inkscape:current-layer="svg159" inkscape:window-maximized="1" inkscape:window-y="27" inkscape:window-x="67" inkscape:cy="467.33333" inkscape:cx="600.66667" inkscape:zoom="0.75746799" showgrid="false" id="namedview161" inkscape:window-height="1025" inkscape:window-width="1853" inkscape:pageshadow="2" inkscape:pageopacity="0" guidetolerance="10" gridtolerance="10" objecttolerance="10" borderopacity="1" bordercolor="#666666" pagecolor="#ffffff"/>
28367- <path style="fill:#5c451c;fill-opacity:1;fill-rule:evenodd;stroke:#5c451c;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="M 1,1 H 901 V 701 H 1 Z m 0,0" id="path4"/>
28368- <path style="fill:#ffa300;fill-opacity:1;fill-rule:evenodd;stroke:#ffa300;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 641,341 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 z m 0,0" id="path6"/>
28369- <path style="fill:#ffa300;fill-opacity:1;fill-rule:evenodd;stroke:#ffa300;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 121,341 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 z m 0,0" id="path8"/>
28370- <path style="fill:#ffa300;fill-opacity:1;fill-rule:evenodd;stroke:#ffa300;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 401,341 h 20 v -20 h 20 v 20 h 20 v 20 h -20 v 20 h -20 v -20 h -20 z m 0,0" id="path10"/>
28371- <path style="fill:#ffa300;fill-opacity:1;fill-rule:evenodd;stroke:#ffa300;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 441,341 h 20 v -20 h 20 v 20 h 20 v 20 h -20 v 20 h -20 v -20 h -20 z m 0,0" id="path12"/>
28372- <path style="fill:#358794;fill-opacity:1;fill-rule:evenodd;stroke:#358794;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 701,221 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h -40 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 z m 0,0" id="path14"/>
28373- <path style="fill:#358794;fill-opacity:1;fill-rule:evenodd;stroke:#358794;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 701,481 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h -40 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 z m 0,0" id="path16"/>
28374- <path style="fill:#358794;fill-opacity:1;fill-rule:evenodd;stroke:#358794;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 621,361 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 40 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 z m 0,0" id="path18"/>
28375- <path style="fill:#358794;fill-opacity:1;fill-rule:evenodd;stroke:#358794;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 621,341 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -40 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 z m 0,0" id="path20"/>
28376- <path style="fill:#358794;fill-opacity:1;fill-rule:evenodd;stroke:#358794;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 201,221 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 H 81 v 20 H 61 v 20 h 40 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 z m 0,0" id="path22"/>
28377- <path style="fill:#358794;fill-opacity:1;fill-rule:evenodd;stroke:#358794;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="M 201,481 H 181 V 461 H 161 V 441 H 141 V 421 H 121 V 401 H 101 V 381 H 81 V 361 H 61 v -20 h 40 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 z m 0,0" id="path24"/>
28378- <path style="fill:#358794;fill-opacity:1;fill-rule:evenodd;stroke:#358794;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 281,361 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 40 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 z m 0,0" id="path26"/>
28379- <path style="fill:#358794;fill-opacity:1;fill-rule:evenodd;stroke:#358794;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 281,341 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -40 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 z m 0,0" id="path28"/>
28380- <path style="fill:#358794;fill-opacity:1;fill-rule:evenodd;stroke:#358794;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 441,121 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 40 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 z m 0,0" id="path30"/>
28381- <path style="fill:#358794;fill-opacity:1;fill-rule:evenodd;stroke:#358794;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 261,361 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 40 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 z m 0,0" id="path32"/>
28382- <path style="fill:#358794;fill-opacity:1;fill-rule:evenodd;stroke:#358794;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 461,121 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 40 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 z m 0,0" id="path34"/>
28383- <path style="fill:#358794;fill-opacity:1;fill-rule:evenodd;stroke:#358794;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 641,361 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 40 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 z m 0,0" id="path36"/>
28384- <path style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 701,161 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h -40 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 z m 0,0" id="path38"/>
28385- <path style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 701,541 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h -40 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 z m 0,0" id="path40"/>
28386- <path style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 441,61 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 40 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 z m 0,0" id="path42"/>
28387- <path style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 441,641 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -40 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 z m 0,0" id="path44"/>
28388- <path style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 461,61 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 40 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 z m 0,0" id="path46"/>
28389- <path style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 621,421 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 40 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 z m 0,0" id="path48"/>
28390- <path style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 621,281 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -40 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 z m 0,0" id="path50"/>
28391- <path style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 281,281 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -40 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 z m 0,0" id="path52"/>
28392- <path style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 201,161 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 H 81 v 20 H 61 v 20 H 41 v 20 H 21 v 20 H 1 v 20 h 40 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 z m 0,0" id="path54"/>
28393- <path style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="M 201,541 H 181 V 521 H 161 V 501 H 141 V 481 H 121 V 461 H 101 V 441 H 81 V 421 H 61 V 401 H 41 V 381 H 21 V 361 H 1 v -20 h 40 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 z m 0,0" id="path56"/>
28394- <path style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 461,641 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -40 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 z m 0,0" id="path58"/>
28395- <path style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 281,421 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 40 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 z m 0,0" id="path60"/>
28396- <path style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 701,181 h 20 v -80 h 100 v 40 h -40 v -20 h -20 v 40 h 80 V 81 H 701 Z m 0,0" id="path62"/>
28397- <path style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="M 721,181 H 701 V 101 H 601 v 40 h 40 v -20 h 20 v 40 H 581 V 81 h 140 z m 0,0" id="path64"/>
28398- <path style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 701,521 h 20 v 80 h 100 v -40 h -40 v 20 h -20 v -40 h 80 v 80 H 701 Z m 0,0" id="path66"/>
28399- <path style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 721,521 h -20 v 80 H 601 v -40 h 40 v 20 h 20 v -40 h -80 v 80 h 140 z m 0,0" id="path68"/>
28400- <path style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 181,521 h 20 v 80 h 100 v -40 h -40 v 20 h -20 v -40 h 80 v 80 H 181 Z m 0,0" id="path70"/>
28401- <path style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 201,521 h -20 v 80 H 81 v -40 h 40 v 20 h 20 V 541 H 61 v 80 h 140 z m 0,0" id="path72"/>
28402- <path style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 181,181 h 20 v -80 h 100 v 40 h -40 v -20 h -20 v 40 h 80 V 81 H 181 Z m 0,0" id="path74"/>
28403- <path style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="M 201,181 H 181 V 101 H 81 v 40 h 40 v -20 h 20 v 40 H 61 V 81 h 140 z m 0,0" id="path76"/>
28404- <path style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 441,181 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h -40 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 z m 0,0" id="path78"/>
28405- <path style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 461,181 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h 40 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 z m 0,0" id="path80"/>
28406- <path style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 441,521 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h -40 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 z m 0,0" id="path82"/>
28407- <path style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 461,521 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h 40 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 z m 0,0" id="path84"/>
28408- <path style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="M 1,21 H 881 V 61 H 1 Z m 0,0" id="path86"/>
28409- <path style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 1,641 h 880 v 40 H 1 Z m 0,0" id="path88"/>
28410- <path style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 421,281 h 20 v -20 h 20 v 20 h 20 v 20 h -20 v 20 h -20 v -20 h -20 z m 0,0" id="path90"/>
28411- <path style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 681,341 h 20 v -20 h 20 v 20 h 20 v 20 h -20 v 20 h -20 v -20 h -20 z m 0,0" id="path92"/>
28412- <path style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 161,341 h 20 v -20 h 20 v 20 h 20 v 20 h -20 v 20 h -20 v -20 h -20 z m 0,0" id="path94"/>
28413- <path style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 421,401 h 20 v -20 h 20 v 20 h 20 v 20 h -20 v 20 h -20 v -20 h -20 z m 0,0" id="path96"/>
28414- <path style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 401,301 h 20 v 20 h -20 z m 0,0" id="path98"/>
28415- <path style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 381,321 h 20 v 20 h -20 z m 0,0" id="path100"/>
28416- <path style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 361,341 h 20 v 20 h -20 z m 0,0" id="path102"/>
28417- <path style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 381,361 h 20 v 20 h -20 z m 0,0" id="path104"/>
28418- <path style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 401,381 h 20 v 20 h -20 z m 0,0" id="path106"/>
28419- <path style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 501,321 h 20 v 20 h -20 z m 0,0" id="path108"/>
28420- <path style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 521,341 h 20 v 20 h -20 z m 0,0" id="path110"/>
28421- <path style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 501,361 h 20 v 20 h -20 z m 0,0" id="path112"/>
28422- <path style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 481,381 h 20 v 20 h -20 z m 0,0" id="path114"/>
28423- <path style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 421,341 h 20 v 20 h -20 z m 0,0" id="path116"/>
28424- <path style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 461,341 h 20 v 20 h -20 z m 0,0" id="path118"/>
28425- <path style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 881,21 h 20 v 40 h -20 z m 0,0" id="path120"/>
28426- <path style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 881,641 h 20 v 40 h -20 z m 0,0" id="path122"/>
28427- <path style="fill:#de3333;fill-opacity:1;fill-rule:evenodd;stroke:#de3333;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 801,201 h 60 V 81 h 40 v 220 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 z m 0,0" id="path124"/>
28428- <path style="fill:#de3333;fill-opacity:1;fill-rule:evenodd;stroke:#de3333;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="M 101,201 H 41 V 81 H 1 v 220 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 z m 0,0" id="path126"/>
28429- <path style="fill:#de3333;fill-opacity:1;fill-rule:evenodd;stroke:#de3333;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="M 101,501 H 41 V 621 H 1 V 401 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 z m 0,0" id="path128"/>
28430- <path style="fill:#de3333;fill-opacity:1;fill-rule:evenodd;stroke:#de3333;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 801,501 h 60 v 120 h 40 V 401 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 z m 0,0" id="path130"/>
28431- <path style="fill:#de3333;fill-opacity:1;fill-rule:evenodd;stroke:#de3333;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 381,341 h 20 v 20 h -20 z m 0,0" id="path132"/>
28432- <path style="fill:#de3333;fill-opacity:1;fill-rule:evenodd;stroke:#de3333;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 401,321 h 20 v 20 h -20 z m 0,0" id="path134"/>
28433- <path style="fill:#de3333;fill-opacity:1;fill-rule:evenodd;stroke:#de3333;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 421,301 h 20 v 20 h -20 z m 0,0" id="path136"/>
28434- <path style="fill:#de3333;fill-opacity:1;fill-rule:evenodd;stroke:#de3333;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 461,301 h 20 v 20 h -20 z m 0,0" id="path138"/>
28435- <path style="fill:#de3333;fill-opacity:1;fill-rule:evenodd;stroke:#de3333;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 481,321 h 20 v 20 h -20 z m 0,0" id="path140"/>
28436- <path style="fill:#de3333;fill-opacity:1;fill-rule:evenodd;stroke:#de3333;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 501,341 h 20 v 20 h -20 z m 0,0" id="path142"/>
28437- <path style="fill:#de3333;fill-opacity:1;fill-rule:evenodd;stroke:#de3333;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 481,361 h 20 v 20 h -20 z m 0,0" id="path144"/>
28438- <path style="fill:#de3333;fill-opacity:1;fill-rule:evenodd;stroke:#de3333;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 461,381 h 20 v 20 h -20 z m 0,0" id="path146"/>
28439- <path style="fill:#de3333;fill-opacity:1;fill-rule:evenodd;stroke:#de3333;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 441,361 h 20 v 20 h -20 z m 0,0" id="path148"/>
28440- <path style="fill:#de3333;fill-opacity:1;fill-rule:evenodd;stroke:#de3333;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 421,381 h 20 v 20 h -20 z m 0,0" id="path150"/>
28441- <path style="fill:#de3333;fill-opacity:1;fill-rule:evenodd;stroke:#de3333;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 401,361 h 20 v 20 h -20 z m 0,0" id="path152"/>
28442- <path style="fill:#de3333;fill-opacity:1;fill-rule:evenodd;stroke:#de3333;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 881,81 h 20 v 220 h -20 z m 0,0" id="path154"/>
28443- <path style="fill:#de3333;fill-opacity:1;fill-rule:evenodd;stroke:#de3333;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 881,401 h 20 v 220 h -20 z m 0,0" id="path156"/>
28444- </svg>
28445 diff --git a/themes/default/assets/books.svg b/themes/default/assets/books.svg
28446deleted file mode 100644
28447index ba393a5..0000000
28448--- a/themes/default/assets/books.svg
28449+++ /dev/null
28450 @@ -1,16 +0,0 @@
28451- <svg id="emoji" viewBox="0 0 72 72" xmlns="http://www.w3.org/2000/svg">
28452- <g id="color">
28453- <path fill="#fff" d="M61.2167,21.6411,37.911,15.0386a1.0068,1.0068,0,0,0-.5537.0019l-22.5947,6.627a.9484.9484,0,0,0-.1365.0719,5.3829,5.3829,0,0,0-1.8343,9.5426,5.3642,5.3642,0,0,0,.0663,8.6968,5.3776,5.3776,0,0,0,1.4856,9.5022l23.0146,6.7128a1.0034,1.0034,0,0,0,.5547.002l23.3057-6.6514a1.0008,1.0008,0,0,0,.7255-.9619V22.603A1,1,0,0,0,61.2167,21.6411Z"/>
28454- <polygon fill="#92d3f5" points="37.638 15.976 60.944 22.579 37.638 29.231 15.044 22.603 37.638 15.976"/>
28455- <path fill="#61b2e4" d="M15.0823,24.7275,37.0756,31.174a2,2,0,0,0,1.1113.0039l23.2947-6.7653a.613.613,0,0,0,.442-.5794l.0189-1.2508L37.6381,29.2547,15.08,22.6425a4.4,4.4,0,0,0-.4554,8.4813l23.014,6.713,24.2115-6.91.04-1.2423a.6255.6255,0,0,0-.7968-.6215L37.6442,35.7552l-22.4226-6.54a2.4,2.4,0,0,1-.1393-4.4873"/>
28456- <path fill="#d22f27" d="M15.0823,33.4092l21.9933,6.4465a1.9988,1.9988,0,0,0,1.1113.004l23.2947-6.7653a.6131.6131,0,0,0,.442-.5794l.0189-1.2508L37.6381,37.9365,15.08,31.3243a4.4,4.4,0,0,0-.4554,8.4813l23.014,6.713,24.2115-6.91.04-1.2424a.6254.6254,0,0,0-.7968-.6214L37.6442,44.437l-22.4226-6.54a2.4,2.4,0,0,1-.1393-4.4873"/>
28457- <path fill="#5c9e31" d="M15.0823,42.1549l21.9933,6.4465a2,2,0,0,0,1.1113.0039L61.4816,41.84a.613.613,0,0,0,.442-.5794l.0189-1.2507L37.6381,46.6821,15.08,40.07a4.4,4.4,0,0,0-.4554,8.4813l23.014,6.713,24.2115-6.91.04-1.2424a.6254.6254,0,0,0-.7968-.6214L37.6442,53.1827l-22.4226-6.54a2.4,2.4,0,0,1-.1393-4.4873"/>
28458- <polygon fill="#61b2e4" points="44.32 17.794 38.594 16.172 16 22.799 21.81 24.504 44.32 17.794"/>
28459- </g>
28460- <g id="line">
28461- <polygon fill="none" stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" points="37.638 16 60.944 22.603 37.638 29.255 15.044 22.627 37.638 16"/>
28462- <path fill="none" stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15.08,22.6425a4.4,4.4,0,0,0-.4554,8.4813l23.0141,6.713,23.3057-6.6516"/>
28463- <path fill="none" stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15.08,31.2942a4.4,4.4,0,0,0-.4554,8.4813l23.0141,6.713,23.3057-6.6516"/>
28464- <path fill="none" stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15.08,40.04a4.4,4.4,0,0,0-.4554,8.4813l23.0141,6.713,23.3057-6.6516"/>
28465- </g>
28466- </svg>
28467 diff --git a/themes/default/assets/building.svg b/themes/default/assets/building.svg
28468deleted file mode 100644
28469index f57946b..0000000
28470--- a/themes/default/assets/building.svg
28471+++ /dev/null
28472 @@ -1,26 +0,0 @@
28473- <svg id="emoji" viewBox="0 0 72 72" xmlns="http://www.w3.org/2000/svg">
28474- <g id="color">
28475- <rect x="18.54" y="26" width="34.92" height="25.95" fill="#9b9b9a"/>
28476- <rect x="12" y="56" width="48" height="4" fill="#fff" stroke-miterlimit="10"/>
28477- <rect x="14.13" y="22" width="43.74" height="4" fill="#fff" stroke-miterlimit="10"/>
28478- <rect x="15.72" y="52" width="40.55" height="4" fill="#fff" stroke-miterlimit="10"/>
28479- <rect x="18.5" y="26" width="4" height="26" fill="#fff" stroke-miterlimit="10"/>
28480- <rect x="27.35" y="26.15" width="4" height="26" fill="#fff" stroke-miterlimit="10"/>
28481- <rect x="49.52" y="25.95" width="3.941" height="26" fill="#fff" stroke-miterlimit="10"/>
28482- <polygon fill="#fff" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" stroke-width="2" points="36 12 19 21.75 53 21.75"/>
28483- <rect x="40.35" y="26.15" width="4" height="26" fill="#fff" stroke-miterlimit="10"/>
28484- </g>
28485- <g id="hair"/>
28486- <g id="skin"/>
28487- <g id="skin-shadow"/>
28488- <g id="line">
28489- <rect x="11.99" y="55.89" width="48.01" height="3.993" fill="none" stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" stroke-width="2"/>
28490- <rect x="14.18" y="21.76" width="43.65" height="3.999" fill="none" stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" stroke-width="2.001"/>
28491- <rect x="15.27" y="51.76" width="41.47" height="3.993" fill="none" stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" stroke-width="2"/>
28492- <rect x="18.35" y="25.61" width="4.298" height="26.3" fill="none" stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" stroke-width="1.702"/>
28493- <rect x="27.35" y="25.61" width="4.298" height="26.3" fill="none" stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" stroke-width="1.702"/>
28494- <rect x="49.35" y="25.61" width="4.298" height="26.3" fill="none" stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" stroke-width="1.702"/>
28495- <polygon transform="matrix(1.091 0 0 .9982 -3.283 .098)" fill="none" stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" stroke-width="1.916" points="36 12 19 21.75 53 21.75"/>
28496- <rect x="40.35" y="25.61" width="4.298" height="26.3" fill="none" stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" stroke-width="1.702"/>
28497- </g>
28498- </svg>
28499 diff --git a/themes/default/assets/chat.svg b/themes/default/assets/chat.svg
28500deleted file mode 100644
28501index e36c2f6..0000000
28502--- a/themes/default/assets/chat.svg
28503+++ /dev/null
28504 @@ -1,16 +0,0 @@
28505- <svg id="emoji" viewBox="0 0 72 72" xmlns="http://www.w3.org/2000/svg">
28506- <g id="color">
28507- <path fill="#D0CFCE" stroke="none" d="M15.042,14.9756c-2.2091,0-4,1.7909-4,4v11.7841l-0.0019,4.5314c-0.0002,0.2808,0.328,0.4336,0.5428,0.2527 l5.0257-4.389c0.1804-0.1518,0.4086-0.2351,0.6443-0.2351h30.9557c2.2091,0,4-1.7909,4-4v-7.9441c0-2.2091-1.7909-4-4-4H15.042z"/>
28508- <path fill="#9B9B9A" stroke="none" d="M60.5096,60.5444c0.2147,0.1808,0.5427,0.0281,0.5426-0.2526l-0.0018-4.2707h0.0007V44.077 c0-2.2091-1.7909-4-4-4H24.103c-2.2091,0-4,1.7909-4,4v7.9441c0,2.2091,1.7909,4,4,4h30.8579c0.2357,0,0.4639,0.0833,0.6441,0.2351 L60.5096,60.5444z"/>
28509- </g>
28510- <g id="hair"/>
28511- <g id="skin"/>
28512- <g id="skin-shadow"/>
28513- <g id="line">
28514- <path fill="none" stroke="#000000" stroke-miterlimit="10" stroke-width="2" d="M15.042,14.9756c-2.2091,0-4,1.7909-4,4v11.7841 l-0.0019,4.5314c-0.0002,0.2808,0.328,0.4336,0.5428,0.2527l5.0257-4.389c0.1804-0.1518,0.4086-0.2351,0.6443-0.2351h30.9557 c2.2091,0,4-1.7909,4-4v-7.9441c0-2.2091-1.7909-4-4-4H15.042z"/>
28515- <path fill="none" stroke="#000000" stroke-miterlimit="10" stroke-width="2" d="M60.5096,60.5444 c0.2147,0.1808,0.5427,0.0281,0.5426-0.2526l-0.0018-4.2707h0.0007V44.077c0-2.2091-1.7909-4-4-4H24.103c-2.2091,0-4,1.7909-4,4 v7.9441c0,2.2091,1.7909,4,4,4h30.8579c0.2357,0,0.4639,0.0833,0.6441,0.2351L60.5096,60.5444z"/>
28516- <circle cx="31.9965" cy="48.0593" r="2" fill="#000000" stroke="none"/>
28517- <circle cx="40.0081" cy="48.0593" r="2" fill="#000000" stroke="none"/>
28518- <circle cx="48.0198" cy="48.0593" r="2" fill="#000000" stroke="none"/>
28519- </g>
28520- </svg>
28521 diff --git a/themes/default/assets/check.svg b/themes/default/assets/check.svg
28522deleted file mode 100644
28523index 631b6ab..0000000
28524--- a/themes/default/assets/check.svg
28525+++ /dev/null
28526 @@ -1,8 +0,0 @@
28527- <svg id="emoji" viewBox="0 0 72 72" xmlns="http://www.w3.org/2000/svg">
28528- <g id="color">
28529- <path fill="#b1cc33" d="m61.5 23.3-8.013-8.013-25.71 25.71-9.26-9.26-8.013 8.013 17.42 17.44z"/>
28530- </g>
28531- <g id="line">
28532- <path fill="none" stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" stroke-width="2" d="m10.5 39.76 17.42 17.44 33.58-33.89-8.013-8.013-25.71 25.71-9.26-9.26z"/>
28533- </g>
28534- </svg>
28535 diff --git a/themes/default/assets/code.svg b/themes/default/assets/code.svg
28536deleted file mode 100644
28537index f92f207..0000000
28538--- a/themes/default/assets/code.svg
28539+++ /dev/null
28540 @@ -1,17 +0,0 @@
28541- <svg id="emoji" viewBox="0 0 72 72" xmlns="http://www.w3.org/2000/svg">
28542- <g id="color">
28543- <rect x="11" y="16.0833" width="50" height="39.8333" fill="#d0cfce" stroke="none"/>
28544- </g>
28545- <g id="hair"/>
28546- <g id="skin"/>
28547- <g id="skin-shadow"/>
28548- <g id="line">
28549- <rect x="11" y="16.0009" width="50" height="39.9982" fill="none" stroke="#000000" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" stroke-width="2"/>
28550- <polyline fill="none" stroke="#000000" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" stroke-width="2" points="16.3287,16.4792 16.3287,20.8542 11,20.8542 61,20.8542"/>
28551- <line x1="28.8333" x2="21.9062" y1="30.3947" y2="37.3218" fill="none" stroke="#000000" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" stroke-width="2"/>
28552- <line x1="28.8333" x2="21.9062" y1="44.3166" y2="37.3895" fill="none" stroke="#000000" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" stroke-width="2"/>
28553- <line x1="38.1836" x2="32.8086" y1="28.1523" y2="46.25" fill="none" stroke="#000000" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" stroke-width="2"/>
28554- <line x1="42.1588" x2="49.0859" y1="44.2515" y2="37.3244" fill="none" stroke="#000000" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" stroke-width="2"/>
28555- <line x1="42.1588" x2="49.0859" y1="30.3296" y2="37.2567" fill="none" stroke="#000000" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" stroke-width="2"/>
28556- </g>
28557- </svg>
28558 diff --git a/themes/default/assets/favicon.ico b/themes/default/assets/favicon.ico
28559deleted file mode 100644
28560index 613ab78..0000000
28561 Binary files a/themes/default/assets/favicon.ico and /dev/null differ
28562 diff --git a/themes/default/assets/feed.svg b/themes/default/assets/feed.svg
28563deleted file mode 100644
28564index 2fd8aa4..0000000
28565--- a/themes/default/assets/feed.svg
28566+++ /dev/null
28567 @@ -1,14 +0,0 @@
28568- <svg id="emoji" viewBox="0 0 72 72" xmlns="http://www.w3.org/2000/svg">
28569- <g id="color">
28570- <path fill="#F1B31C" d="M59.0349 60H12.9649C12.7092 59.9992 12.4642 59.8973 12.2834 59.7164C12.1026 59.5356 12.0007 59.2906 12 59.0349V12.9649C12.0008 12.7092 12.1027 12.4642 12.2836 12.2834C12.4644 12.1026 12.7094 12.0007 12.9651 12H59.0351C59.2908 12.0008 59.5358 12.1027 59.7166 12.2836C59.8974 12.4644 59.9993 12.7094 60 12.9651V59.0351C59.9992 59.2908 59.8973 59.5358 59.7164 59.7166C59.5356 59.8974 59.2906 59.9993 59.0349 60Z"/>
28571- <circle cx="24.5" cy="47.5" r="4.5" fill="#ffffff"/>
28572- <path fill="#ffffff" fill-rule="evenodd" d="M42 52C42 39.8497 32.1503 30 20 30V37C28.2843 37 35 43.7157 35 52H42Z" clip-rule="evenodd"/>
28573- <path fill="#ffffff" fill-rule="evenodd" d="M52 52C52 34.3269 37.6731 20 20 20V27C33.8071 27 45 38.1929 45 52H52Z" clip-rule="evenodd"/>
28574- </g>
28575- <g id="line">
28576- <path fill="none" stroke="#000000" stroke-linejoin="round" stroke-miterlimit="10" stroke-width="2" d="M59.0349 60H12.9649C12.7092 59.9992 12.4642 59.8973 12.2834 59.7164C12.1026 59.5356 12.0007 59.2906 12 59.0349V12.9649C12.0008 12.7092 12.1027 12.4642 12.2836 12.2834C12.4644 12.1026 12.7094 12.0007 12.9651 12H59.0351C59.2908 12.0008 59.5358 12.1027 59.7166 12.2836C59.8974 12.4644 59.9993 12.7094 60 12.9651V59.0351C59.9992 59.2908 59.8973 59.5358 59.7164 59.7166C59.5356 59.8974 59.2906 59.9993 59.0349 60V60Z"/>
28577- <path fill="none" stroke="#000000" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M29 47.5C29 49.9853 26.9853 52 24.5 52C22.0147 52 20 49.9853 20 47.5C20 45.0147 22.0147 43 24.5 43C26.9853 43 29 45.0147 29 47.5Z"/>
28578- <path fill="none" fill-rule="evenodd" stroke="#000" stroke-linejoin="round" stroke-width="2" d="M41 52C41 40.402 31.598 31 20 31V37C28.2843 37 35 43.7157 35 52H41Z" clip-rule="evenodd"/>
28579- <path fill="none" fill-rule="evenodd" stroke="#000" stroke-linejoin="round" stroke-width="2" d="M52 52C52 34.3269 37.6731 20 20 20V26.0012C34.1062 26.1354 45.5 37.6121 45.5 51.75C45.5 51.8334 45.4996 51.9168 45.4988 52H52Z" clip-rule="evenodd"/>
28580- </g>
28581- </svg>
28582 diff --git a/themes/default/assets/feed.xsl b/themes/default/assets/feed.xsl
28583deleted file mode 100644
28584index 3d1cce4..0000000
28585--- a/themes/default/assets/feed.xsl
28586+++ /dev/null
28587 @@ -1,56 +0,0 @@
28588- <?xml version="1.0" encoding="utf-8"?>
28589- <xsl:stylesheet version="3.0"
28590- xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
28591- xmlns:atom="http://www.w3.org/2005/Atom"
28592- xmlns:dc="http://purl.org/dc/elements/1.1/"
28593- xmlns:itunes="http://www.itunes.com/dtds/podcast-1.0.dtd">
28594- <xsl:output method="html" version="1.0" encoding="UTF-8" indent="yes"/>
28595- <xsl:template match="/">
28596- <html lang="en">
28597- <head>
28598- <title>
28599- <xsl:value-of select="/rss/channel/title"/>
28600- </title>
28601- <link rel="stylesheet" href="/static/main.min.css" />
28602- </head>
28603- <body>
28604- <main class="container">
28605- <article>
28606- <header>
28607- <h1>
28608- <img class="rss-icon-feed" src="/static/assets/feed.svg"/>
28609- <a>
28610- <xsl:attribute name="href"> <xsl:value-of select="/rss/channel/link"/> </xsl:attribute>
28611- <xsl:value-of select="/rss/channel/title"/>
28612- </a>
28613- </h1>
28614- <h2><xsl:value-of select="/rss/channel/description"/></h2>
28615- </header>
28616- <p><strong>This is an <a href="https://en.wikipedia.org/wiki/RSS">RSS feed</a>.</strong></p>
28617- <p>Copy the link from the address bar into your feed reader to receive regular updates.</p>
28618- </article>
28619- <xsl:for-each select="/rss/channel/item">
28620- <article class="entry">
28621- <header>
28622- <h3>
28623- <a>
28624- <xsl:attribute name="href">
28625- <xsl:value-of select="link"/>
28626- </xsl:attribute>
28627- <xsl:value-of select="title"/>
28628- </a>
28629- </h3>
28630- </header>
28631- <xsl:value-of select="description" disable-output-escaping="yes"/>
28632- <footer>
28633- <xsl:value-of select="pubDate"/>
28634- <span class="right"><xsl:value-of select="author" /></span>
28635- </footer>
28636- </article>
28637- </xsl:for-each>
28638- </main>
28639- </body>
28640- </html>
28641- </xsl:template>
28642- </xsl:stylesheet>
28643-
28644 diff --git a/themes/default/assets/logo.svg b/themes/default/assets/logo.svg
28645deleted file mode 100644
28646index 8e43fd6..0000000
28647--- a/themes/default/assets/logo.svg
28648+++ /dev/null
28649 @@ -1,78 +0,0 @@
28650- <?xml version="1.0" encoding="UTF-8" standalone="no"?>
28651- <svg
28652- id="emoji"
28653- viewBox="0 0 234.58891 59.331596"
28654- version="1.1"
28655- sodipodi:docname="ayllu.svg"
28656- width="234.58891"
28657- height="59.331596"
28658- inkscape:export-filename="logo.png"
28659- inkscape:export-xdpi="96"
28660- inkscape:export-ydpi="96"
28661- xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
28662- xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
28663- xmlns="http://www.w3.org/2000/svg"
28664- xmlns:svg="http://www.w3.org/2000/svg">
28665- <defs
28666- id="defs4043">
28667- <rect
28668- x="67.923203"
28669- y="12.228936"
28670- width="188.06631"
28671- height="68.492889"
28672- id="rect7485" />
28673- </defs>
28674- <sodipodi:namedview
28675- id="namedview4041"
28676- pagecolor="#ffffff"
28677- bordercolor="#999999"
28678- borderopacity="1"
28679- inkscape:showpageshadow="0"
28680- inkscape:pageopacity="0"
28681- inkscape:pagecheckerboard="0"
28682- inkscape:deskcolor="#d1d1d1"
28683- showgrid="true">
28684- <inkscape:grid
28685- type="xygrid"
28686- id="grid7481"
28687- originx="0"
28688- originy="0" />
28689- </sodipodi:namedview>
28690- <g
28691- id="color"
28692- transform="translate(-6.3635189,-6.0201046)">
28693- <path
28694- fill="#fcea2b"
28695- stroke="none"
28696- d="m 7.3634,42.4095 c 4.5525,6.1703 11.874,10.1726 20.1303,10.1726 13.8071,0 25,-11.1929 25,-25 0,-8.5226 -4.2646,-16.0492 -10.7763,-20.5621 13.0383,2.8385 22.7812,14.4426 22.7812,28.3317 0,16.0163 -12.9837,29 -29,29 -13.5877,0 -24.9889,-9.3288 -28.1352,-21.9422 z"
28697- id="path4029" />
28698- <path
28699- fill="#f1b31c"
28700- stroke="none"
28701- d="m 45.8373,9.2108 c 8.25,4.25 16.1946,11.8724 16.1946,24.6742 0,15.4494 -12.5242,27.9735 -27.9735,27.9735 -9.2431,0 -19.7524,-4.8353 -24.294,-15.5436 0,0 4.3805,18.6568 25.7189,18.665 C 54.8103,64.9873 63.5252,44.3581 63.5252,44.3581 70.033,12.3815 45.8373,9.2108 45.8373,9.2108 Z"
28702- id="path4031" />
28703- </g>
28704- <g
28705- id="line"
28706- transform="translate(-6.3635189,-6.0201046)">
28707- <path
28708- fill="none"
28709- stroke="#000000"
28710- stroke-linecap="round"
28711- stroke-linejoin="round"
28712- stroke-miterlimit="10"
28713- stroke-width="2"
28714- d="m 7.3634,42.4095 c 4.5525,6.1703 11.874,10.1726 20.1303,10.1726 13.8071,0 25,-11.1929 25,-25 0,-8.5226 -4.2646,-16.0492 -10.7763,-20.5621 13.0383,2.8385 22.7812,14.4426 22.7812,28.3317 0,16.0163 -12.9837,29 -29,29 -13.5877,0 -24.9889,-9.3288 -28.1352,-21.9422 z"
28715- id="path4037" />
28716- </g>
28717- <text
28718- xml:space="preserve"
28719- id="text7483"
28720- style="font-style:normal;font-weight:normal;font-size:40px;line-height:1.25;font-family:sans-serif;white-space:pre;shape-inside:url(#rect7485);fill:#000000;fill-opacity:1;stroke:none"
28721- transform="translate(-3.6949385,-4.8457367)"><tspan
28722- x="67.923828"
28723- y="48.623523"
28724- id="tspan7896"><tspan
28725- style="font-weight:bold;font-family:serif;-inkscape-font-specification:'serif Bold'"
28726- id="tspan7894">ayllu</tspan></tspan></text>
28727- </svg>
28728 diff --git a/themes/default/assets/moon.svg b/themes/default/assets/moon.svg
28729deleted file mode 100644
28730index 51596eb..0000000
28731--- a/themes/default/assets/moon.svg
28732+++ /dev/null
28733 @@ -1,12 +0,0 @@
28734- <svg id="emoji" viewBox="0 0 72 72" xmlns="http://www.w3.org/2000/svg">
28735- <g id="color">
28736- <path fill="#FCEA2B" stroke="none" d="M7.3634,42.4095c4.5525,6.1703,11.874,10.1726,20.1303,10.1726c13.8071,0,25-11.1929,25-25 c0-8.5226-4.2646-16.0492-10.7763-20.5621c13.0383,2.8385,22.7812,14.4426,22.7812,28.3317c0,16.0163-12.9837,29-29,29 C21.9109,64.3517,10.5097,55.0229,7.3634,42.4095z"/>
28737- <path fill="#F1B31C" stroke="none" d="M45.8373,9.2108c8.25,4.25,16.1946,11.8724,16.1946,24.6742c0,15.4494-12.5242,27.9735-27.9735,27.9735 c-9.2431,0-19.7524-4.8353-24.294-15.5436c0,0,4.3805,18.6568,25.7189,18.665c19.327,0.0074,28.0419-20.6218,28.0419-20.6218 C70.033,12.3815,45.8373,9.2108,45.8373,9.2108z"/>
28738- </g>
28739- <g id="hair"/>
28740- <g id="skin"/>
28741- <g id="skin-shadow"/>
28742- <g id="line">
28743- <path fill="none" stroke="#000000" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" stroke-width="2" d="M7.3634,42.4095c4.5525,6.1703,11.874,10.1726,20.1303,10.1726c13.8071,0,25-11.1929,25-25 c0-8.5226-4.2646-16.0492-10.7763-20.5621c13.0383,2.8385,22.7812,14.4426,22.7812,28.3317c0,16.0163-12.9837,29-29,29 C21.9109,64.3517,10.5097,55.0229,7.3634,42.4095z"/>
28744- </g>
28745- </svg>
28746 diff --git a/themes/default/assets/question.svg b/themes/default/assets/question.svg
28747deleted file mode 100644
28748index 9ee75dd..0000000
28749--- a/themes/default/assets/question.svg
28750+++ /dev/null
28751 @@ -1,13 +0,0 @@
28752- <svg id="emoji" viewBox="0 0 72 72" xmlns="http://www.w3.org/2000/svg">
28753- <g id="color">
28754- <path fill="#fff" stroke-miterlimit="10" d="m48.72 20.15c0.3051 9.298-8.021 12.75-10.82 19.21v7.565c0 1.39-1.11 2.5-2.5 2.5-1.38 0-2.5-1.11-2.5-2.5v-9.865c3.493-6.142 10.38-7.469 10.67-17.06 0-5.66-6.543-6.151-7.988-6.175h-0.05c-5.767 1.187-6.135 4.99-7.289 9.549-0.6671 1.521-1.564 2.045-2.9 2.03-1.36-0.2401-2.26-1.54-2.02-2.9 0.5877-2.752 0.758-6.639 2.35-8.502 2.71-3.14 4.819-4.957 9.179-5.167 0.24 0 0.49-0.01 0.73-0.01 0.09 0 0.18 0 0.27 0.01 8.27 0.2401 12.6 2.992 12.87 11.32z"/>
28755- <circle cx="34.95" cy="58.84" r="3" fill="#fff" stroke-miterlimit="10"/>
28756- </g>
28757- <g id="hair"/>
28758- <g id="skin"/>
28759- <g id="skin-shadow"/>
28760- <g id="line">
28761- <circle cx="35.3" cy="58.84" r="2.625" fill="none" stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" stroke-width="1.75"/>
28762- <path d="m35.67 10.07c6.57 0 12.03 3.431 12.03 10.21 0 3.671-0.48 6.526-3.71 9.755-3.956 3.956-7.184 6.346-7.208 8.549-0.0651 5.826-5e-3 8.278-0.0044 8.36a1.476 1.476 0 0 1-1.464 1.487h-0.0115a1.476 1.476 0 0 1-1.476-1.464c-5e-4 -0.0819-0.0607-2.561 0.0044-8.416 0.0358-3.231 3.63-6.127 8.385-10.88 2.178-2.174 2.474-4.716 2.474-7.127 0-3.282-2.965-7.587-8.984-7.587-5.338 0-8.499 3.467-8.499 9.552a1.434 1.434 0 0 1-1.395 1.587 1.552 1.552 0 0 1-1.505-1.759c0-9.882 7.291-12.27 11.36-12.27m0-2c-4.964 0-13.36 3.005-13.36 14.27a3.519 3.519 0 0 0 3.505 3.759 3.418 3.418 0 0 0 3.395-3.587c0-3.445 1.128-7.552 6.499-7.552 4.817 0 6.984 3.267 6.984 5.587 0 2.364-0.3155 4.144-1.886 5.712-0.7515 0.75-1.474 1.454-2.157 2.12-3.933 3.833-6.775 6.602-6.815 10.15-0.055 4.946-0.0218 7.539-5e-3 8.424a3.476 3.476 0 1 0 6.952-0.0248l-0.0012-0.075c-0.0164-0.8595-0.0488-3.384 0.0056-8.248 0.0092-0.8384 2.102-2.822 3.948-4.572 0.8091-0.767 1.726-1.636 2.675-2.585 3.577-3.577 4.296-6.926 4.296-11.17 0-7.301-5.638-12.21-14.03-12.21z"/>
28763- </g>
28764- </svg>
28765 diff --git a/themes/default/assets/rss.svg b/themes/default/assets/rss.svg
28766deleted file mode 100644
28767index b325149..0000000
28768--- a/themes/default/assets/rss.svg
28769+++ /dev/null
28770 @@ -1,18 +0,0 @@
28771- <?xml version="1.0"?>
28772- <!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
28773- <svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="128px" height="128px" id="RSSicon" viewBox="0 0 256 256">
28774- <defs>
28775- <linearGradient x1="0.085" y1="0.085" x2="0.915" y2="0.915" id="RSSg">
28776- <stop offset="0.0" stop-color="#E3702D"/><stop offset="0.1071" stop-color="#EA7D31"/>
28777- <stop offset="0.3503" stop-color="#F69537"/><stop offset="0.5" stop-color="#FB9E3A"/>
28778- <stop offset="0.7016" stop-color="#EA7C31"/><stop offset="0.8866" stop-color="#DE642B"/>
28779- <stop offset="1.0" stop-color="#D95B29"/>
28780- </linearGradient>
28781- </defs>
28782- <rect width="256" height="256" rx="55" ry="55" x="0" y="0" fill="#CC5D15"/>
28783- <rect width="246" height="246" rx="50" ry="50" x="5" y="5" fill="#F49C52"/>
28784- <rect width="236" height="236" rx="47" ry="47" x="10" y="10" fill="url(#RSSg)"/>
28785- <circle cx="68" cy="189" r="24" fill="#FFF"/>
28786- <path d="M160 213h-34a82 82 0 0 0 -82 -82v-34a116 116 0 0 1 116 116z" fill="#FFF"/>
28787- <path d="M184 213A140 140 0 0 0 44 73 V 38a175 175 0 0 1 175 175z" fill="#FFF"/>
28788- </svg>
28789 diff --git a/themes/default/assets/scale.svg b/themes/default/assets/scale.svg
28790deleted file mode 100644
28791index 8ffe8c3..0000000
28792--- a/themes/default/assets/scale.svg
28793+++ /dev/null
28794 @@ -1,22 +0,0 @@
28795- <svg id="emoji" viewBox="0 0 72 72" xmlns="http://www.w3.org/2000/svg">
28796- <g id="color">
28797- <path fill="#9B9B9A" stroke="none" d="M48.0626,62.92c0-3.3137-5.5964-6-12.5-6s-12.5,2.6863-12.5,6H48.0626z"/>
28798- <path fill="#D0CFCE" stroke="none" d="M24.9006,46.9656c0,2.1938-2.1985,3.9723-4.9106,3.9723s-4.9106-1.7785-4.9106-3.9723H24.9006z"/>
28799- <path fill="#D0CFCE" stroke="none" d="M57.9006,46.9248c0,2.1938-2.1985,3.9723-4.9106,3.9723c-2.712,0-4.9106-1.7785-4.9106-3.9723H57.9006z"/>
28800- </g>
28801- <g id="hair"/>
28802- <g id="skin"/>
28803- <g id="skin-shadow"/>
28804- <g id="line">
28805- <path fill="none" stroke="#000000" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" stroke-width="2" d="M15.9996,25.1697c2.6667,0.0001,8.9544-5.3333,20.0002-5.3333S50.6666,23.8365,56,25.1697"/>
28806- <line x1="35.9998" x2="35.9998" y1="23.9064" y2="51.9064" fill="none" stroke="#000000" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" stroke-width="2"/>
28807- <circle cx="35.9998" cy="13.895" r="3" fill="#000000" stroke="none"/>
28808- <path fill="none" stroke="#000000" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" stroke-width="2" d="M48,61.9064c0-3.3137-5.5964-6-12.5-6s-12.5,2.6863-12.5,6"/>
28809- <path fill="none" stroke="#000000" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" stroke-width="2" d="M26,45.9064c0,3.3137-2.6863,6-6,6s-6-2.6863-6-6H26z"/>
28810- <polygon fill="none" stroke="#000000" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" stroke-width="2" points="20,27.9064 15,45.9064 25,45.9064"/>
28811- <line x1="20" x2="20" y1="27.9064" y2="45.9064" fill="none" stroke="#000000" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" stroke-width="2"/>
28812- <path fill="none" stroke="#000000" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" stroke-width="2" d="M59,45.9064c0,3.3137-2.6863,6-6,6s-6-2.6863-6-6H59z"/>
28813- <polygon fill="none" stroke="#000000" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" stroke-width="2" points="53,27.9064 48,45.9064 58,45.9064"/>
28814- <line x1="53" x2="53" y1="27.9064" y2="45.9064" fill="none" stroke="#000000" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" stroke-width="2"/>
28815- </g>
28816- </svg>
28817 diff --git a/themes/default/assets/textile-pattern-1.svg b/themes/default/assets/textile-pattern-1.svg
28818deleted file mode 100644
28819index 28124d3..0000000
28820--- a/themes/default/assets/textile-pattern-1.svg
28821+++ /dev/null
28822 @@ -1,90 +0,0 @@
28823- <?xml version="1.0" encoding="UTF-8" standalone="no"?>
28824- <svg xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:cc="http://creativecommons.org/ns#" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:svg="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg" xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" inkscape:version="1.0 (1.0+r73+1)" sodipodi:docname="textil-inca-pattern-3a.svg" id="svg159" version="1.1" viewBox="0 0 902 702">
28825- <metadata id="metadata165">
28826- <rdf:RDF>
28827- <cc:Work rdf:about="">
28828- <dc:format>image/svg+xml</dc:format>
28829- <dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage"/>
28830- </cc:Work>
28831- </rdf:RDF>
28832- </metadata>
28833- <defs id="defs163"/>
28834- <sodipodi:namedview inkscape:current-layer="svg159" inkscape:window-maximized="1" inkscape:window-y="27" inkscape:window-x="67" inkscape:cy="467.33333" inkscape:cx="600.66667" inkscape:zoom="0.75746799" showgrid="false" id="namedview161" inkscape:window-height="1025" inkscape:window-width="1853" inkscape:pageshadow="2" inkscape:pageopacity="0" guidetolerance="10" gridtolerance="10" objecttolerance="10" borderopacity="1" bordercolor="#666666" pagecolor="#ffffff"/>
28835- <path style="fill:#5c451c;fill-opacity:1;fill-rule:evenodd;stroke:#5c451c;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="M 1,1 H 901 V 701 H 1 Z m 0,0" id="path4"/>
28836- <path style="fill:#ffa300;fill-opacity:1;fill-rule:evenodd;stroke:#ffa300;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 641,341 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 z m 0,0" id="path6"/>
28837- <path style="fill:#ffa300;fill-opacity:1;fill-rule:evenodd;stroke:#ffa300;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 121,341 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 z m 0,0" id="path8"/>
28838- <path style="fill:#ffa300;fill-opacity:1;fill-rule:evenodd;stroke:#ffa300;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 401,341 h 20 v -20 h 20 v 20 h 20 v 20 h -20 v 20 h -20 v -20 h -20 z m 0,0" id="path10"/>
28839- <path style="fill:#ffa300;fill-opacity:1;fill-rule:evenodd;stroke:#ffa300;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 441,341 h 20 v -20 h 20 v 20 h 20 v 20 h -20 v 20 h -20 v -20 h -20 z m 0,0" id="path12"/>
28840- <path style="fill:#358794;fill-opacity:1;fill-rule:evenodd;stroke:#358794;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 701,221 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h -40 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 z m 0,0" id="path14"/>
28841- <path style="fill:#358794;fill-opacity:1;fill-rule:evenodd;stroke:#358794;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 701,481 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h -40 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 z m 0,0" id="path16"/>
28842- <path style="fill:#358794;fill-opacity:1;fill-rule:evenodd;stroke:#358794;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 621,361 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 40 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 z m 0,0" id="path18"/>
28843- <path style="fill:#358794;fill-opacity:1;fill-rule:evenodd;stroke:#358794;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 621,341 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -40 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 z m 0,0" id="path20"/>
28844- <path style="fill:#358794;fill-opacity:1;fill-rule:evenodd;stroke:#358794;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 201,221 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 H 81 v 20 H 61 v 20 h 40 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 z m 0,0" id="path22"/>
28845- <path style="fill:#358794;fill-opacity:1;fill-rule:evenodd;stroke:#358794;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="M 201,481 H 181 V 461 H 161 V 441 H 141 V 421 H 121 V 401 H 101 V 381 H 81 V 361 H 61 v -20 h 40 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 z m 0,0" id="path24"/>
28846- <path style="fill:#358794;fill-opacity:1;fill-rule:evenodd;stroke:#358794;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 281,361 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 40 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 z m 0,0" id="path26"/>
28847- <path style="fill:#358794;fill-opacity:1;fill-rule:evenodd;stroke:#358794;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 281,341 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -40 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 z m 0,0" id="path28"/>
28848- <path style="fill:#358794;fill-opacity:1;fill-rule:evenodd;stroke:#358794;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 441,121 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 40 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 z m 0,0" id="path30"/>
28849- <path style="fill:#358794;fill-opacity:1;fill-rule:evenodd;stroke:#358794;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 261,361 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 40 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 z m 0,0" id="path32"/>
28850- <path style="fill:#358794;fill-opacity:1;fill-rule:evenodd;stroke:#358794;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 461,121 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 40 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 z m 0,0" id="path34"/>
28851- <path style="fill:#358794;fill-opacity:1;fill-rule:evenodd;stroke:#358794;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 641,361 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 40 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 z m 0,0" id="path36"/>
28852- <path style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 701,161 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h -40 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 z m 0,0" id="path38"/>
28853- <path style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 701,541 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h -40 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 z m 0,0" id="path40"/>
28854- <path style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 441,61 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 40 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 z m 0,0" id="path42"/>
28855- <path style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 441,641 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -40 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 z m 0,0" id="path44"/>
28856- <path style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 461,61 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 40 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 z m 0,0" id="path46"/>
28857- <path style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 621,421 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 40 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 z m 0,0" id="path48"/>
28858- <path style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 621,281 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -40 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 z m 0,0" id="path50"/>
28859- <path style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 281,281 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -40 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 z m 0,0" id="path52"/>
28860- <path style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 201,161 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 H 81 v 20 H 61 v 20 H 41 v 20 H 21 v 20 H 1 v 20 h 40 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 z m 0,0" id="path54"/>
28861- <path style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="M 201,541 H 181 V 521 H 161 V 501 H 141 V 481 H 121 V 461 H 101 V 441 H 81 V 421 H 61 V 401 H 41 V 381 H 21 V 361 H 1 v -20 h 40 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 z m 0,0" id="path56"/>
28862- <path style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 461,641 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -40 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 z m 0,0" id="path58"/>
28863- <path style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 281,421 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 40 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 z m 0,0" id="path60"/>
28864- <path style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 701,181 h 20 v -80 h 100 v 40 h -40 v -20 h -20 v 40 h 80 V 81 H 701 Z m 0,0" id="path62"/>
28865- <path style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="M 721,181 H 701 V 101 H 601 v 40 h 40 v -20 h 20 v 40 H 581 V 81 h 140 z m 0,0" id="path64"/>
28866- <path style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 701,521 h 20 v 80 h 100 v -40 h -40 v 20 h -20 v -40 h 80 v 80 H 701 Z m 0,0" id="path66"/>
28867- <path style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 721,521 h -20 v 80 H 601 v -40 h 40 v 20 h 20 v -40 h -80 v 80 h 140 z m 0,0" id="path68"/>
28868- <path style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 181,521 h 20 v 80 h 100 v -40 h -40 v 20 h -20 v -40 h 80 v 80 H 181 Z m 0,0" id="path70"/>
28869- <path style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 201,521 h -20 v 80 H 81 v -40 h 40 v 20 h 20 V 541 H 61 v 80 h 140 z m 0,0" id="path72"/>
28870- <path style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 181,181 h 20 v -80 h 100 v 40 h -40 v -20 h -20 v 40 h 80 V 81 H 181 Z m 0,0" id="path74"/>
28871- <path style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="M 201,181 H 181 V 101 H 81 v 40 h 40 v -20 h 20 v 40 H 61 V 81 h 140 z m 0,0" id="path76"/>
28872- <path style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 441,181 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h -40 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 z m 0,0" id="path78"/>
28873- <path style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 461,181 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h 40 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 z m 0,0" id="path80"/>
28874- <path style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 441,521 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h -40 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 z m 0,0" id="path82"/>
28875- <path style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 461,521 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h 40 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 z m 0,0" id="path84"/>
28876- <path style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="M 1,21 H 881 V 61 H 1 Z m 0,0" id="path86"/>
28877- <path style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 1,641 h 880 v 40 H 1 Z m 0,0" id="path88"/>
28878- <path style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 421,281 h 20 v -20 h 20 v 20 h 20 v 20 h -20 v 20 h -20 v -20 h -20 z m 0,0" id="path90"/>
28879- <path style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 681,341 h 20 v -20 h 20 v 20 h 20 v 20 h -20 v 20 h -20 v -20 h -20 z m 0,0" id="path92"/>
28880- <path style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 161,341 h 20 v -20 h 20 v 20 h 20 v 20 h -20 v 20 h -20 v -20 h -20 z m 0,0" id="path94"/>
28881- <path style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 421,401 h 20 v -20 h 20 v 20 h 20 v 20 h -20 v 20 h -20 v -20 h -20 z m 0,0" id="path96"/>
28882- <path style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 401,301 h 20 v 20 h -20 z m 0,0" id="path98"/>
28883- <path style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 381,321 h 20 v 20 h -20 z m 0,0" id="path100"/>
28884- <path style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 361,341 h 20 v 20 h -20 z m 0,0" id="path102"/>
28885- <path style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 381,361 h 20 v 20 h -20 z m 0,0" id="path104"/>
28886- <path style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 401,381 h 20 v 20 h -20 z m 0,0" id="path106"/>
28887- <path style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 501,321 h 20 v 20 h -20 z m 0,0" id="path108"/>
28888- <path style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 521,341 h 20 v 20 h -20 z m 0,0" id="path110"/>
28889- <path style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 501,361 h 20 v 20 h -20 z m 0,0" id="path112"/>
28890- <path style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 481,381 h 20 v 20 h -20 z m 0,0" id="path114"/>
28891- <path style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 421,341 h 20 v 20 h -20 z m 0,0" id="path116"/>
28892- <path style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 461,341 h 20 v 20 h -20 z m 0,0" id="path118"/>
28893- <path style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 881,21 h 20 v 40 h -20 z m 0,0" id="path120"/>
28894- <path style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 881,641 h 20 v 40 h -20 z m 0,0" id="path122"/>
28895- <path style="fill:#de3333;fill-opacity:1;fill-rule:evenodd;stroke:#de3333;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 801,201 h 60 V 81 h 40 v 220 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 z m 0,0" id="path124"/>
28896- <path style="fill:#de3333;fill-opacity:1;fill-rule:evenodd;stroke:#de3333;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="M 101,201 H 41 V 81 H 1 v 220 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 z m 0,0" id="path126"/>
28897- <path style="fill:#de3333;fill-opacity:1;fill-rule:evenodd;stroke:#de3333;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="M 101,501 H 41 V 621 H 1 V 401 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 z m 0,0" id="path128"/>
28898- <path style="fill:#de3333;fill-opacity:1;fill-rule:evenodd;stroke:#de3333;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 801,501 h 60 v 120 h 40 V 401 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 z m 0,0" id="path130"/>
28899- <path style="fill:#de3333;fill-opacity:1;fill-rule:evenodd;stroke:#de3333;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 381,341 h 20 v 20 h -20 z m 0,0" id="path132"/>
28900- <path style="fill:#de3333;fill-opacity:1;fill-rule:evenodd;stroke:#de3333;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 401,321 h 20 v 20 h -20 z m 0,0" id="path134"/>
28901- <path style="fill:#de3333;fill-opacity:1;fill-rule:evenodd;stroke:#de3333;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 421,301 h 20 v 20 h -20 z m 0,0" id="path136"/>
28902- <path style="fill:#de3333;fill-opacity:1;fill-rule:evenodd;stroke:#de3333;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 461,301 h 20 v 20 h -20 z m 0,0" id="path138"/>
28903- <path style="fill:#de3333;fill-opacity:1;fill-rule:evenodd;stroke:#de3333;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 481,321 h 20 v 20 h -20 z m 0,0" id="path140"/>
28904- <path style="fill:#de3333;fill-opacity:1;fill-rule:evenodd;stroke:#de3333;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 501,341 h 20 v 20 h -20 z m 0,0" id="path142"/>
28905- <path style="fill:#de3333;fill-opacity:1;fill-rule:evenodd;stroke:#de3333;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 481,361 h 20 v 20 h -20 z m 0,0" id="path144"/>
28906- <path style="fill:#de3333;fill-opacity:1;fill-rule:evenodd;stroke:#de3333;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 461,381 h 20 v 20 h -20 z m 0,0" id="path146"/>
28907- <path style="fill:#de3333;fill-opacity:1;fill-rule:evenodd;stroke:#de3333;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 441,361 h 20 v 20 h -20 z m 0,0" id="path148"/>
28908- <path style="fill:#de3333;fill-opacity:1;fill-rule:evenodd;stroke:#de3333;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 421,381 h 20 v 20 h -20 z m 0,0" id="path150"/>
28909- <path style="fill:#de3333;fill-opacity:1;fill-rule:evenodd;stroke:#de3333;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 401,361 h 20 v 20 h -20 z m 0,0" id="path152"/>
28910- <path style="fill:#de3333;fill-opacity:1;fill-rule:evenodd;stroke:#de3333;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 881,81 h 20 v 220 h -20 z m 0,0" id="path154"/>
28911- <path style="fill:#de3333;fill-opacity:1;fill-rule:evenodd;stroke:#de3333;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 881,401 h 20 v 220 h -20 z m 0,0" id="path156"/>
28912- </svg>
28913 diff --git a/themes/default/assets/textile-pattern-2.svg b/themes/default/assets/textile-pattern-2.svg
28914deleted file mode 100644
28915index f442f7a..0000000
28916--- a/themes/default/assets/textile-pattern-2.svg
28917+++ /dev/null
28918 @@ -1,308 +0,0 @@
28919- <?xml version="1.0" encoding="UTF-8" standalone="no"?>
28920- <svg
28921- xmlns:dc="http://purl.org/dc/elements/1.1/"
28922- xmlns:cc="http://creativecommons.org/ns#"
28923- xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
28924- xmlns:svg="http://www.w3.org/2000/svg"
28925- xmlns="http://www.w3.org/2000/svg"
28926- xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
28927- xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
28928- width="1441.0001pt"
28929- height="1441pt"
28930- viewBox="0 0 1441.0001 1441"
28931- version="1.1"
28932- id="svg11"
28933- sodipodi:docname="Manto-funerario-paracas-2.svg"
28934- inkscape:version="1.0 (1.0+r73+1)">
28935- <metadata
28936- id="metadata17">
28937- <rdf:RDF>
28938- <cc:Work
28939- rdf:about="">
28940- <dc:format>image/svg+xml</dc:format>
28941- <dc:type
28942- rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
28943- <dc:title />
28944- </cc:Work>
28945- </rdf:RDF>
28946- </metadata>
28947- <defs
28948- id="defs15" />
28949- <sodipodi:namedview
28950- inkscape:document-rotation="0"
28951- pagecolor="#ffffff"
28952- bordercolor="#666666"
28953- borderopacity="1"
28954- objecttolerance="10"
28955- gridtolerance="10"
28956- guidetolerance="10"
28957- inkscape:pageopacity="0"
28958- inkscape:pageshadow="2"
28959- inkscape:window-width="1853"
28960- inkscape:window-height="1025"
28961- id="namedview13"
28962- showgrid="false"
28963- inkscape:zoom="0.18438366"
28964- inkscape:cx="471.3773"
28965- inkscape:cy="745.57387"
28966- inkscape:window-x="67"
28967- inkscape:window-y="27"
28968- inkscape:window-maximized="1"
28969- inkscape:current-layer="svg11" />
28970- <g
28971- transform="translate(-0.49999149,719.5)"
28972- id="g921">
28973- <path
28974- style="fill:#730033;fill-opacity:1;fill-rule:evenodd;stroke:#730033;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1"
28975- d="M 1,1 H 361 V 361 H 1 Z m 0,0"
28976- id="path4" />
28977- <path
28978- style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:8;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:10;stroke-opacity:1"
28979- d="m 261,281 h -80 v 50 H 101 V 281 H 21 L 61,201 H 21 L 61,121 H 21 L 81,31 V 231 H 261 V 91 l -50,110 h -80 l -30,-70 v -30 l 50,-70 h 10 v 40 h 20 V 31 h 10 l 30,50 20,-50 h 40 l 60,100 h -60 l 60,100 h -60 l 60,100 h -80 z m 0,0"
28980- id="path6" />
28981- <path
28982- style="fill:#730033;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:8;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:10;stroke-opacity:1"
28983- d="m 141,131 20,-20 h 20 l 20,20 -20,40 h -20 z m 0,0"
28984- id="path8" />
28985- </g>
28986- <g
28987- id="g895"
28988- transform="translate(384.03529,784.594)">
28989- <path
28990- id="path4-2"
28991- d="M -383.53528,295.906 H -23.535268 V 655.90601 H -383.53528 Z m 0,0"
28992- style="fill:#e15031;fill-opacity:1;fill-rule:evenodd;stroke:#730033;stroke-width:0.999997;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" />
28993- <path
28994- id="path6-9"
28995- d="m -123.53527,575.90601 h -80 v 50 h -80 v -50 h -80.00001 l 40,-80 h -40 l 40,-80.00001 h -40 l 60,-90 v 200.00001 h 180.00001 V 385.906 l -50,110.00001 h -80.00001 L -283.53527,425.906 v -29.99999 l 50,-70.00001 h 9.99999 v 40 h 20.00001 v -40 h 9.99999 l 30,50 20,-50 h 40.00001 l 60.000002,100 h -60.000002 l 60.000002,100.00001 h -60.000002 l 60.000002,100 h -80.000002 z m 0,0"
28996- style="fill:#ed2031;fill-opacity:1;fill-rule:evenodd;stroke:#730033;stroke-width:8.00002;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:10;stroke-opacity:1" />
28997- <path
28998- id="path8-1"
28999- d="m -243.53528,425.906 20,-20 h 20.00001 l 20,20 -20,40 h -20.00001 z m 0,0"
29000- style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:8.00002;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:10;stroke-opacity:1" />
29001- </g>
29002- <g
29003- id="g944"
29004- transform="translate(-198.02416,1142.587)">
29005- <path
29006- id="path4-27"
29007- d="M 558.52418,-62.086991 H 918.52419 V 297.91302 H 558.52418 Z m 0,0"
29008- style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:0.999997;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" />
29009- <path
29010- id="path6-0"
29011- d="m 818.52419,217.91302 h -80 v 50 h -80 v -50 h -80.00001 l 40,-80 h -40 l 40,-80.000008 h -40 l 60.00001,-90.000003 V 167.91302 h 180 V 27.913013 l -50,110.000007 H 688.52418 L 658.52419,67.913012 v -29.99999 l 50,-70.000013 h 9.99999 V 7.9130143 h 20.00001 V -32.086991 h 9.99999 l 30,50.000006 20,-50.000006 h 40.00001 l 60,100.000003 h -60 l 60,100.000008 h -60 l 60,100 h -80 z m 0,0"
29012- style="fill:#d66236;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:8.00002;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:10;stroke-opacity:1" />
29013- <path
29014- id="path8-9"
29015- d="m 698.52418,67.913012 20,-20 h 20.00001 l 20,20 -20,39.999998 h -20.00001 z m 0,0"
29016- style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:8.00002;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:10;stroke-opacity:1" />
29017- </g>
29018- <g
29019- transform="translate(744.03529,424.59399)"
29020- id="g895-3">
29021- <path
29022- style="fill:#e15031;fill-opacity:1;fill-rule:evenodd;stroke:#730033;stroke-width:0.999997;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1"
29023- d="M -383.53528,295.906 H -23.535268 V 655.90601 H -383.53528 Z m 0,0"
29024- id="path4-2-6" />
29025- <path
29026- style="fill:#ed2031;fill-opacity:1;fill-rule:evenodd;stroke:#730033;stroke-width:8.00002;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:10;stroke-opacity:1"
29027- d="m -123.53527,575.90601 h -80 v 50 h -80 v -50 h -80.00001 l 40,-80 h -40 l 40,-80.00001 h -40 l 60,-90 v 200.00001 h 180.00001 V 385.906 l -50,110.00001 h -80.00001 L -283.53527,425.906 v -29.99999 l 50,-70.00001 h 9.99999 v 40 h 20.00001 v -40 h 9.99999 l 30,50 20,-50 h 40.00001 l 60.000002,100 h -60.000002 l 60.000002,100.00001 h -60.000002 l 60.000002,100 h -80.000002 z m 0,0"
29028- id="path6-9-0" />
29029- <path
29030- style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:8.00002;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:10;stroke-opacity:1"
29031- d="m -243.53528,425.906 20,-20 h 20.00001 l 20,20 -20,40 h -20.00001 z m 0,0"
29032- id="path8-1-6" />
29033- </g>
29034- <g
29035- id="g921-2"
29036- transform="translate(359.50001,359.5)">
29037- <path
29038- id="path4-6"
29039- d="M 1,1 H 361 V 361 H 1 Z m 0,0"
29040- style="fill:#730033;fill-opacity:1;fill-rule:evenodd;stroke:#730033;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" />
29041- <path
29042- id="path6-1"
29043- d="m 261,281 h -80 v 50 H 101 V 281 H 21 L 61,201 H 21 L 61,121 H 21 L 81,31 V 231 H 261 V 91 l -50,110 h -80 l -30,-70 v -30 l 50,-70 h 10 v 40 h 20 V 31 h 10 l 30,50 20,-50 h 40 l 60,100 h -60 l 60,100 h -60 l 60,100 h -80 z m 0,0"
29044- style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:8;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:10;stroke-opacity:1" />
29045- <path
29046- id="path8-8"
29047- d="m 141,131 20,-20 h 20 l 20,20 -20,40 h -20 z m 0,0"
29048- style="fill:#730033;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:8;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:10;stroke-opacity:1" />
29049- </g>
29050- <g
29051- id="g921-9"
29052- transform="translate(719.50001,-0.5)">
29053- <path
29054- id="path4-7"
29055- d="M 1,1 H 361 V 361 H 1 Z m 0,0"
29056- style="fill:#730033;fill-opacity:1;fill-rule:evenodd;stroke:#730033;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" />
29057- <path
29058- id="path6-3"
29059- d="m 261,281 h -80 v 50 H 101 V 281 H 21 L 61,201 H 21 L 61,121 H 21 L 81,31 V 231 H 261 V 91 l -50,110 h -80 l -30,-70 v -30 l 50,-70 h 10 v 40 h 20 V 31 h 10 l 30,50 20,-50 h 40 l 60,100 h -60 l 60,100 h -60 l 60,100 h -80 z m 0,0"
29060- style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:8;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:10;stroke-opacity:1" />
29061- <path
29062- id="path8-6"
29063- d="m 141,131 20,-20 h 20 l 20,20 -20,40 h -20 z m 0,0"
29064- style="fill:#730033;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:8;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:10;stroke-opacity:1" />
29065- </g>
29066- <g
29067- transform="translate(1104.0353,64.594)"
29068- id="g895-1">
29069- <path
29070- style="fill:#e15031;fill-opacity:1;fill-rule:evenodd;stroke:#730033;stroke-width:0.999997;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1"
29071- d="M -383.53528,295.906 H -23.535268 V 655.90601 H -383.53528 Z m 0,0"
29072- id="path4-2-2" />
29073- <path
29074- style="fill:#ed2031;fill-opacity:1;fill-rule:evenodd;stroke:#730033;stroke-width:8.00002;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:10;stroke-opacity:1"
29075- d="m -123.53527,575.90601 h -80 v 50 h -80 v -50 h -80.00001 l 40,-80 h -40 l 40,-80.00001 h -40 l 60,-90 v 200.00001 h 180.00001 V 385.906 l -50,110.00001 h -80.00001 L -283.53527,425.906 v -29.99999 l 50,-70.00001 h 9.99999 v 40 h 20.00001 v -40 h 9.99999 l 30,50 20,-50 h 40.00001 l 60.000002,100 h -60.000002 l 60.000002,100.00001 h -60.000002 l 60.000002,100 h -80.000002 z m 0,0"
29076- id="path6-9-9" />
29077- <path
29078- style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:8.00002;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:10;stroke-opacity:1"
29079- d="m -243.53528,425.906 20,-20 h 20.00001 l 20,20 -20,40 h -20.00001 z m 0,0"
29080- id="path8-1-3" />
29081- </g>
29082- <g
29083- transform="translate(521.97583,422.58699)"
29084- id="g944-1">
29085- <path
29086- style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:0.999997;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1"
29087- d="M 558.52418,-62.086991 H 918.52419 V 297.91302 H 558.52418 Z m 0,0"
29088- id="path4-27-9" />
29089- <path
29090- style="fill:#d66236;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:8.00002;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:10;stroke-opacity:1"
29091- d="m 818.52419,217.91302 h -80 v 50 h -80 v -50 h -80.00001 l 40,-80 h -40 l 40,-80.000008 h -40 l 60.00001,-90.000003 V 167.91302 h 180 V 27.913013 l -50,110.000007 H 688.52418 L 658.52419,67.913012 v -29.99999 l 50,-70.000013 h 9.99999 V 7.9130143 h 20.00001 V -32.086991 h 9.99999 l 30,50.000006 20,-50.000006 h 40.00001 l 60,100.000003 h -60 l 60,100.000008 h -60 l 60,100 h -80 z m 0,0"
29092- id="path6-0-4" />
29093- <path
29094- style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:8.00002;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:10;stroke-opacity:1"
29095- d="m 698.52418,67.913012 20,-20 h 20.00001 l 20,20 -20,39.999998 h -20.00001 z m 0,0"
29096- id="path8-9-7" />
29097- </g>
29098- <g
29099- id="g895-3-8"
29100- transform="translate(1464.0353,-295.406)">
29101- <path
29102- id="path4-2-6-4"
29103- d="M -383.53528,295.906 H -23.535268 V 655.90601 H -383.53528 Z m 0,0"
29104- style="fill:#e15031;fill-opacity:1;fill-rule:evenodd;stroke:#730033;stroke-width:0.999997;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" />
29105- <path
29106- id="path6-9-0-5"
29107- d="m -123.53527,575.90601 h -80 v 50 h -80 v -50 h -80.00001 l 40,-80 h -40 l 40,-80.00001 h -40 l 60,-90 v 200.00001 h 180.00001 V 385.906 l -50,110.00001 h -80.00001 L -283.53527,425.906 v -29.99999 l 50,-70.00001 h 9.99999 v 40 h 20.00001 v -40 h 9.99999 l 30,50 20,-50 h 40.00001 l 60.000002,100 h -60.000002 l 60.000002,100.00001 h -60.000002 l 60.000002,100 h -80.000002 z m 0,0"
29108- style="fill:#ed2031;fill-opacity:1;fill-rule:evenodd;stroke:#730033;stroke-width:8.00002;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:10;stroke-opacity:1" />
29109- <path
29110- id="path8-1-6-0"
29111- d="m -243.53528,425.906 20,-20 h 20.00001 l 20,20 -20,40 h -20.00001 z m 0,0"
29112- style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:8.00002;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:10;stroke-opacity:1" />
29113- </g>
29114- <g
29115- transform="translate(161.97582,782.587)"
29116- id="g944-3">
29117- <path
29118- style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:0.999997;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1"
29119- d="M 558.52418,-62.086991 H 918.52419 V 297.91302 H 558.52418 Z m 0,0"
29120- id="path4-27-6" />
29121- <path
29122- style="fill:#d66236;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:8.00002;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:10;stroke-opacity:1"
29123- d="m 818.52419,217.91302 h -80 v 50 h -80 v -50 h -80.00001 l 40,-80 h -40 l 40,-80.000008 h -40 l 60.00001,-90.000003 V 167.91302 h 180 V 27.913013 l -50,110.000007 H 688.52418 L 658.52419,67.913012 v -29.99999 l 50,-70.000013 h 9.99999 V 7.9130143 h 20.00001 V -32.086991 h 9.99999 l 30,50.000006 20,-50.000006 h 40.00001 l 60,100.000003 h -60 l 60,100.000008 h -60 l 60,100 h -80 z m 0,0"
29124- id="path6-0-1" />
29125- <path
29126- style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:8.00002;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:10;stroke-opacity:1"
29127- d="m 698.52418,67.913012 20,-20 h 20.00001 l 20,20 -20,39.999998 h -20.00001 z m 0,0"
29128- id="path8-9-0" />
29129- </g>
29130- <g
29131- id="g921-6"
29132- transform="translate(719.50003,1079.5)">
29133- <path
29134- id="path4-3"
29135- d="M 1,1 H 361 V 361 H 1 Z m 0,0"
29136- style="fill:#730033;fill-opacity:1;fill-rule:evenodd;stroke:#730033;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" />
29137- <path
29138- id="path6-2"
29139- d="m 261,281 h -80 v 50 H 101 V 281 H 21 L 61,201 H 21 L 61,121 H 21 L 81,31 V 231 H 261 V 91 l -50,110 h -80 l -30,-70 v -30 l 50,-70 h 10 v 40 h 20 V 31 h 10 l 30,50 20,-50 h 40 l 60,100 h -60 l 60,100 h -60 l 60,100 h -80 z m 0,0"
29140- style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:8;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:10;stroke-opacity:1" />
29141- <path
29142- id="path8-0"
29143- d="m 141,131 20,-20 h 20 l 20,20 -20,40 h -20 z m 0,0"
29144- style="fill:#730033;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:8;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:10;stroke-opacity:1" />
29145- </g>
29146- <g
29147- transform="translate(1079.5,719.5)"
29148- id="g921-2-6">
29149- <path
29150- style="fill:#730033;fill-opacity:1;fill-rule:evenodd;stroke:#730033;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1"
29151- d="M 1,1 H 361 V 361 H 1 Z m 0,0"
29152- id="path4-6-1" />
29153- <path
29154- style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:8;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:10;stroke-opacity:1"
29155- d="m 261,281 h -80 v 50 H 101 V 281 H 21 L 61,201 H 21 L 61,121 H 21 L 81,31 V 231 H 261 V 91 l -50,110 h -80 l -30,-70 v -30 l 50,-70 h 10 v 40 h 20 V 31 h 10 l 30,50 20,-50 h 40 l 60,100 h -60 l 60,100 h -60 l 60,100 h -80 z m 0,0"
29156- id="path6-1-5" />
29157- <path
29158- style="fill:#730033;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:8;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:10;stroke-opacity:1"
29159- d="m 141,131 20,-20 h 20 l 20,20 -20,40 h -20 z m 0,0"
29160- id="path8-8-5" />
29161- </g>
29162- <g
29163- transform="translate(-558.02418,422.58698)"
29164- id="g944-4">
29165- <path
29166- style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:0.999997;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1"
29167- d="M 558.52418,-62.086991 H 918.52419 V 297.91302 H 558.52418 Z m 0,0"
29168- id="path4-27-7" />
29169- <path
29170- style="fill:#d66236;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:8.00002;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:10;stroke-opacity:1"
29171- d="m 818.52419,217.91302 h -80 v 50 h -80 v -50 h -80.00001 l 40,-80 h -40 l 40,-80.000008 h -40 l 60.00001,-90.000003 V 167.91302 h 180 V 27.913013 l -50,110.000007 H 688.52418 L 658.52419,67.913012 v -29.99999 l 50,-70.000013 h 9.99999 V 7.9130143 h 20.00001 V -32.086991 h 9.99999 l 30,50.000006 20,-50.000006 h 40.00001 l 60,100.000003 h -60 l 60,100.000008 h -60 l 60,100 h -80 z m 0,0"
29172- id="path6-0-6" />
29173- <path
29174- style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:8.00002;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:10;stroke-opacity:1"
29175- d="m 698.52418,67.913012 20,-20 h 20.00001 l 20,20 -20,39.999998 h -20.00001 z m 0,0"
29176- id="path8-9-5" />
29177- </g>
29178- <g
29179- id="g944-3-6"
29180- transform="translate(-198.02419,62.58699)">
29181- <path
29182- id="path4-27-6-9"
29183- d="M 558.52418,-62.086991 H 918.52419 V 297.91302 H 558.52418 Z m 0,0"
29184- style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:0.999997;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" />
29185- <path
29186- id="path6-0-1-3"
29187- d="m 818.52419,217.91302 h -80 v 50 h -80 v -50 h -80.00001 l 40,-80 h -40 l 40,-80.000008 h -40 l 60.00001,-90.000003 V 167.91302 h 180 V 27.913013 l -50,110.000007 H 688.52418 L 658.52419,67.913012 v -29.99999 l 50,-70.000013 h 9.99999 V 7.9130143 h 20.00001 V -32.086991 h 9.99999 l 30,50.000006 20,-50.000006 h 40.00001 l 60,100.000003 h -60 l 60,100.000008 h -60 l 60,100 h -80 z m 0,0"
29188- style="fill:#d66236;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:8.00002;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:10;stroke-opacity:1" />
29189- <path
29190- id="path8-9-0-7"
29191- d="m 698.52418,67.913012 20,-20 h 20.00001 l 20,20 -20,39.999998 h -20.00001 z m 0,0"
29192- style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:8.00002;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:10;stroke-opacity:1" />
29193- </g>
29194- <g
29195- id="g895-1-4"
29196- transform="translate(384.03528,-295.406)">
29197- <path
29198- id="path4-2-2-5"
29199- d="M -383.53528,295.906 H -23.535268 V 655.90601 H -383.53528 Z m 0,0"
29200- style="fill:#e15031;fill-opacity:1;fill-rule:evenodd;stroke:#730033;stroke-width:0.999997;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" />
29201- <path
29202- id="path6-9-9-2"
29203- d="m -123.53527,575.90601 h -80 v 50 h -80 v -50 h -80.00001 l 40,-80 h -40 l 40,-80.00001 h -40 l 60,-90 v 200.00001 h 180.00001 V 385.906 l -50,110.00001 h -80.00001 L -283.53527,425.906 v -29.99999 l 50,-70.00001 h 9.99999 v 40 h 20.00001 v -40 h 9.99999 l 30,50 20,-50 h 40.00001 l 60.000002,100 h -60.000002 l 60.000002,100.00001 h -60.000002 l 60.000002,100 h -80.000002 z m 0,0"
29204- style="fill:#ed2031;fill-opacity:1;fill-rule:evenodd;stroke:#730033;stroke-width:8.00002;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:10;stroke-opacity:1" />
29205- <path
29206- id="path8-1-3-5"
29207- d="m -243.53528,425.906 20,-20 h 20.00001 l 20,20 -20,40 h -20.00001 z m 0,0"
29208- style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:8.00002;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:10;stroke-opacity:1" />
29209- </g>
29210- <g
29211- id="g895-1-47"
29212- transform="translate(1464.0353,784.594)">
29213- <path
29214- id="path4-2-2-4"
29215- d="M -383.53528,295.906 H -23.535268 V 655.90601 H -383.53528 Z m 0,0"
29216- style="fill:#e15031;fill-opacity:1;fill-rule:evenodd;stroke:#730033;stroke-width:0.999997;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" />
29217- <path
29218- id="path6-9-9-4"
29219- d="m -123.53527,575.90601 h -80 v 50 h -80 v -50 h -80.00001 l 40,-80 h -40 l 40,-80.00001 h -40 l 60,-90 v 200.00001 h 180.00001 V 385.906 l -50,110.00001 h -80.00001 L -283.53527,425.906 v -29.99999 l 50,-70.00001 h 9.99999 v 40 h 20.00001 v -40 h 9.99999 l 30,50 20,-50 h 40.00001 l 60.000002,100 h -60.000002 l 60.000002,100.00001 h -60.000002 l 60.000002,100 h -80.000002 z m 0,0"
29220- style="fill:#ed2031;fill-opacity:1;fill-rule:evenodd;stroke:#730033;stroke-width:8.00002;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:10;stroke-opacity:1" />
29221- <path
29222- id="path8-1-3-3"
29223- d="m -243.53528,425.906 20,-20 h 20.00001 l 20,20 -20,40 h -20.00001 z m 0,0"
29224- style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:8.00002;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:10;stroke-opacity:1" />
29225- </g>
29226- </svg>
29227 diff --git a/themes/default/assets/xmpp.svg b/themes/default/assets/xmpp.svg
29228deleted file mode 100644
29229index 0c904a7..0000000
29230--- a/themes/default/assets/xmpp.svg
29231+++ /dev/null
29232 @@ -1,30 +0,0 @@
29233- <?xml version="1.0" encoding="UTF-8" standalone="no"?>
29234- <!-- Generator: Adobe Illustrator 13.0.2, SVG Export Plug-In . SVG Version: 6.00 Build 14948) -->
29235- <!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
29236- <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" xml:space="preserve" viewBox="0 0 200 200" width="200px" height="200px" x="0px" y="0px" enable-background="new 0 0 200 200">
29237-
29238- <linearGradient id="SVGID_right_" y2="1.279e-13" gradientUnits="userSpaceOnUse" x2="-1073.2" gradientTransform="translate(1196.604,15.368977)" y1="126.85" x1="-1073.2">
29239- <stop stop-color="#1b3967" offset=".011"/>
29240- <stop stop-color="#13b5ea" offset=".467"/>
29241- <stop stop-color="#002b5c" offset=".9945"/>
29242- </linearGradient>
29243-
29244- <linearGradient id="SVGID_left_" y2="1.279e-13" gradientUnits="userSpaceOnUse" x2="-1073.2" gradientTransform="matrix(-1,0,0,1,-994.78801,15.367977)" y1="126.85" x1="-1073.2">
29245- <stop stop-color="#1b3967" offset=".011"/>
29246- <stop stop-color="#13b5ea" offset=".467"/>
29247- <stop stop-color="#002b5c" offset=".9945"/>
29248- </linearGradient>
29249-
29250- <path d="m 151.80512,29.557978 c 0.077,1.313 -1.787,0.968 -1.787,2.293 0,38.551 -46.558,97.366012 -91.687985,108.730012 v 1.639 C 118.28313,136.69999 186.89012,74.419978 188.40012,15.369977 l -36.599,14.189001 z" style="fill:url(#SVGID_right_)"/>
29251- <path d="m 133.67312,34.300978 c 0.076,1.313 0.12,2.63 0.12,3.957 0,38.551 -30.69898,90.497012 -75.826985,101.860012 v 1.639 c 59.044005,-2.79 105.809995,-63.024012 105.809995,-109.200012 0,-2.375 -0.128,-4.729 -0.371,-7.056 l -29.73,8.798 z" style="fill:#e96d1f"/>
29252- <path d="m 163.69112,24.951978 -7.61699,2.722 c 0.041,0.962 0.066,2.254 0.066,3.225 0,41.219 -37.271,98.204012 -87.271995,107.120012 -3.24501,1.088 -7.53801,2.077 -10.932,2.931 v 1.638 C 123.19013,137.02799 169.03613,70.721978 163.69612,24.947978 Z" style="fill:#d9541e"/>
29253-
29254- <path d="m 50.011,29.556978 c -0.077,1.313 1.787,0.968 1.787,2.293 0,38.551 46.558007,97.366012 91.68799,108.730012 v 1.639 C 83.533,136.69899 14.926,74.418978 13.416,15.368977 l 36.599,14.189001 z" style="fill:url(#SVGID_left_)"/>
29255- <path d="m 68.143,34.299978 c -0.076,1.313 -0.12,2.63 -0.12,3.957 0,38.551 30.698995,90.497012 75.82699,101.860012 v 1.639 C 84.806,138.96599 38.04,78.731978 38.04,32.555978 c 0,-2.375 0.128,-4.729 0.371,-7.056 l 29.73,8.798 z" style="fill:#a0ce67"/>
29256- <path d="m 38.125,24.950978 7.617,2.722 c -0.041,0.962 -0.066,2.254 -0.066,3.225 0,41.219 37.271,98.204012 87.27199,107.120012 3.245,1.088 7.538,2.077 10.932,2.931 v 1.638 C 78.626,137.02699 32.78,70.720978 38.12,24.946978 Z" style="fill:#439639"/>
29257-
29258- <path d="m 25.988,172.07799 -13.388,-14.65 h 11.643 l 9.127,10.268 9.129,-10.268 h 11.643 l -13.387,14.646 14.401,14.728 h -12.09 l -9.697,-10.67 -9.693,10.67 H 11.584 l 14.404,-14.73 z"/>
29259- <path d="m 58.508,157.42799 h 13.836 l 10.183,18.905 10.183,-18.905 h 13.83199 v 29.374 h -8.761983 v -21.096 h -0.08 L 85.893,186.80199 H 79.16 l -11.807,-21.096 h -0.082 v 21.096 h -8.764 v -29.37 z"/>
29260- <path d="m 112.66199,157.42799 h 24.546 c 8.559,0 10.628,4.302 10.628,10.063 v 2.516 c 0,4.381 -1.908,9.41 -8.275,9.41 h -17.894 v 7.385 h -9.005 v -29.38 z m 9,14.69 h 13.997 c 2.10901,0 2.92401,-1.377 2.92401,-3.123 v -1.135 c 0,-1.99 -0.976,-3.127 -3.694,-3.127 h -13.227 v 7.38 z"/>
29261- <path d="m 152.72199,157.42799 h 24.546 c 8.561,0 10.63,4.302 10.63,10.063 v 2.516 c 0,4.381 -1.907,9.41 -8.275,9.41 h -17.893 v 7.385 h -9.008 v -29.38 z m 9.01,14.69 h 13.996 c 2.11,0 2.922,-1.377 2.922,-3.123 v -1.135 c 0,-1.99 -0.974,-3.127 -3.693,-3.127 h -13.225 v 7.38 z"/>
29262- </svg>
29263\ No newline at end of file
29264 diff --git a/themes/default/base.scss b/themes/default/base.scss
29265deleted file mode 100644
29266index 633a10c..0000000
29267--- a/themes/default/base.scss
29268+++ /dev/null
29269 @@ -1,558 +0,0 @@
29270- ::-webkit-scrollbar {
29271- display: none;
29272- }
29273-
29274- :root {
29275- --spacing: 0;
29276- --font-family: monospace;
29277- --font-size: 14px;
29278- }
29279-
29280- pre {
29281- white-space: pre-wrap;
29282- background-color: unset;
29283- color: unset;
29284- font-size: unset;
29285- }
29286-
29287- table {
29288- margin-bottom: 0px;
29289- }
29290-
29291- table {
29292- overflow: hidden;
29293- }
29294-
29295- table td {
29296- padding-left: 2px;
29297- padding-right: 2px;
29298- }
29299-
29300- table tbody tr td {
29301- overflow: hidden;
29302- white-space: nowrap;
29303- }
29304-
29305- table th {
29306- overflow: hidden;
29307- white-space: nowrap;
29308- font-weight: bold;
29309- text-decoration: underline;
29310- }
29311-
29312- header.tree-preamble {
29313- font-size: smaller;
29314- }
29315-
29316- .wide {
29317- display: flex;
29318- flex-direction: row;
29319- justify-content: space-between;
29320- row-gap: 10px;
29321- }
29322-
29323- .wide {
29324- div:nth-child(2) {
29325- margin-left: 10px;
29326- }
29327- }
29328-
29329- nav.small a {
29330- font-size: small;
29331- }
29332-
29333- ul.submenu {
29334- font-size: smaller;
29335- height: 25px;
29336- }
29337-
29338- pre {
29339- overflow: scroll;
29340- }
29341-
29342- [role="button"] {
29343- padding: 0;
29344- margin-top: 2px;
29345- margin-bottom: 2px;
29346- }
29347-
29348- button.small {
29349- display: initial;
29350- width: initial;
29351- }
29352-
29353- nav {
29354- border-radius: var(--border-radius);
29355- margin-left: 10px;
29356- margin-right: 10px;
29357- }
29358-
29359- code {
29360- padding: 0;
29361- }
29362-
29363- code.highlighted {
29364- width: 100%;
29365- overflow: scroll;
29366- }
29367-
29368- footer.main {
29369- padding-top: 2em;
29370- text-align: center;
29371- font-size: smaller;
29372- }
29373-
29374- h1, h2, h3, h4, h5, h6 {
29375- --typography-spacing-vertical: .1em;
29376- //margin-top: unset;
29377- //margin-bottom: unset;
29378- }
29379-
29380- li.active > a {
29381- text-decoration: underline;
29382- }
29383-
29384- .logo > svg {
29385- width: 72px;
29386- }
29387-
29388- @media (prefers-color-scheme: dark) {
29389- .logo {
29390- filter: drop-shadow(0 0 4mm #fff);
29391- }
29392- }
29393-
29394- footer {
29395- height: 2em;
29396- }
29397-
29398- article {
29399- box-shadow: none;
29400- }
29401-
29402- article.clone > input {
29403- font-size: smaller;
29404- height: 30px;
29405- text-align: center;
29406- }
29407-
29408- .blob-preview {
29409- text-align: center;
29410- border-style: solid;
29411- border-color: pink;
29412- margin-top: 2em;
29413- padding: 1em;
29414- }
29415-
29416- .line-number {
29417- user-select: none;
29418- // text-align: right;
29419- padding: 0 2px;
29420- width: 10px;
29421- }
29422-
29423- .line {
29424- white-space: pre;
29425- padding-left: 5px;
29426- }
29427-
29428- .icon-header > h1,h2,h3,h4,h5 {
29429- display: inline-block;
29430- }
29431-
29432- .icon-header > h1,h2,h3,h4,h5 > a {
29433- color: none;
29434- text-decoration: inherit;
29435- }
29436-
29437- .icon-header > svg {
29438- height: 2em;
29439- // border: 2px solid;
29440- float: right;
29441- margin-top: 2px;
29442- margin-left: 2px;
29443- }
29444-
29445- .icon > svg {
29446- height: 1.5em;
29447- }
29448-
29449- .icon-header.contrast > svg {
29450- border: 2px solid;
29451- }
29452-
29453- .emoji {
29454- float: right;
29455- }
29456-
29457- .emoji > svg {
29458- height: 30px;
29459- }
29460-
29461- header.repo div {
29462- display: inline-block;
29463- }
29464-
29465- article > header {
29466- padding: 3px;
29467- }
29468-
29469- ul.language-list li {
29470- list-style: none;
29471- }
29472-
29473- ul.author-list li {
29474- list-style: none;
29475- }
29476-
29477- span.right {
29478- float: right;
29479- };
29480-
29481- span.tiny {
29482- font-size: .7em;
29483- font-weight: bold;
29484- }
29485-
29486- span.center {
29487- text-align: center;
29488- }
29489-
29490- span.tiny-text {
29491- font-size: smaller;
29492- }
29493-
29494- span.hint {
29495- font-weight: bold;
29496- text-decoration: underline;
29497- }
29498-
29499- span.h1 {
29500- font-size: x-large;
29501- font-weight: bold;
29502- }
29503-
29504- span.header-text {
29505- font-size: 1.2em;
29506- font-weight: bold;
29507- }
29508-
29509- span.sub-header {
29510- font-size: 1em;
29511- font-weight: bold;
29512- }
29513-
29514- span.header {
29515- font-size: large;
29516- }
29517-
29518- span.timestamp {
29519- float: right;
29520- }
29521-
29522- span.author {
29523- float: right;
29524- }
29525-
29526- article.index-listing {
29527- margin-top: 25px;
29528- margin-bottom: 15px;
29529- }
29530-
29531- article.index-listing > article {
29532- padding: 3px;
29533- }
29534-
29535- article.listing > article {
29536- padding: 5px;
29537- }
29538-
29539- span.labels {
29540- font-weight: bold;
29541- }
29542-
29543- span.label {
29544- border: solid 2px;
29545- }
29546-
29547- span.labels {
29548- border-radius: 5px 5px 5px 5px;
29549- }
29550-
29551- section.blame {
29552- display: grid;
29553- grid-template-columns: 1fr 5fr;
29554- }
29555-
29556- div.clone {
29557- display: flex;
29558- height: 45px;
29559- font-size: smaller;
29560- }
29561-
29562- div.clone-url > input {
29563- height: 2em !important;
29564- }
29565-
29566- div.clone > div {
29567- padding: 5px;
29568- line-height: 45px;
29569- }
29570-
29571- div.clone-url {
29572- flex: auto;
29573- }
29574-
29575- section.blame {
29576- article { margin-top: 0px !important; }
29577- }
29578-
29579- article.blame-right {
29580- padding: 0;
29581- border-left: solid 1px ;
29582- }
29583-
29584- article.blame-left {
29585- padding: 0;
29586- border-right: solid 1px;
29587-
29588- table tbody tr td {
29589- border-left: solid 1px;
29590- border-right: solid 1px;
29591- text-align: left;
29592- }
29593- }
29594-
29595-
29596- .readme {
29597- // fix markdown list rendering
29598- ul {
29599- padding-right: revert;
29600- padding-left: revert;
29601- padding-inline-start: revert;
29602- padding-inline-end: revert;
29603- }
29604- }
29605-
29606- img.rss-icon-feed {
29607- height: 4em;
29608- }
29609-
29610- div.table {
29611- padding: .5em;
29612- }
29613-
29614- div.readme {
29615- padding: 1em;
29616- }
29617-
29618- .term-width {
29619- word-wrap: break-word;
29620- width: 600px;
29621- }
29622-
29623- footer.tree {
29624- font-size: smaller;
29625- }
29626-
29627- // refs
29628-
29629- section.refs-container {
29630- display: grid;
29631- grid-template-columns: 3fr 1fr;
29632- }
29633-
29634- article.ref-right {
29635- margin-left: 1em;
29636- }
29637-
29638-
29639- @media screen and (max-width:1000px) {
29640- section.refs-container {
29641- grid-template-columns: 1fr;
29642- }
29643-
29644- article.ref-right {
29645- margin-left: 0;
29646- }
29647- }
29648-
29649- section.controls {
29650- text-align: center;
29651- border: 1px;
29652- }
29653-
29654- section.viewer {
29655- text-align: center;
29656- border: 1px;
29657- }
29658-
29659- // repo
29660-
29661- div.repo-container {
29662- display: grid;
29663- grid-template-columns: 3fr 1fr;
29664- }
29665-
29666- div.user-box {
29667- display: grid;
29668- grid-template-columns: 1fr 4fr;
29669- }
29670-
29671- div.user-box > .name {
29672- font-weight: bold;
29673- text-decoration: underline;
29674- }
29675-
29676- div.commit_message {
29677- padding-top: 1em;
29678- }
29679-
29680- div.user-box > .details {
29681- text-align: right;
29682- }
29683-
29684- .expand-xl {display: none}
29685-
29686- div.tree > table {
29687- }
29688-
29689- div.tree {
29690- padding-right: 5px;
29691- padding-left: 5px;
29692- }
29693-
29694- article.tree {
29695- margin-bottom: 5px;
29696- }
29697-
29698- section.repo-right {
29699- text-align: center;
29700- }
29701-
29702- section.repo-right {
29703- margin-left: 1em;
29704- }
29705-
29706- section.repo-right > section {
29707- text-align: center;
29708- }
29709-
29710- section.repo-right > article {
29711- display: block;
29712- text-align: center;
29713- }
29714-
29715- .spaced > div:first-child {
29716- margin-left: 2px;
29717- }
29718-
29719- .panel {
29720- background: var(--card-background-color);
29721- padding: .3em;
29722- }
29723-
29724- table.commits {
29725- width: 100%;
29726- }
29727-
29728- table.commits > tbody > tr > td.message {
29729- word-wrap: break-word;
29730- white-space: normal !important;
29731- }
29732-
29733- section.thread-view > article {
29734- padding-top: 1em;
29735- padding-bottom: 1em;
29736- }
29737-
29738- div.chart {
29739- text-align: center;
29740- }
29741-
29742- // breakpoints and adjustments for different screen sizes
29743- @if map-get($breakpoints, "md") {
29744- @media (max-width: map-get($breakpoints, "md")) {
29745- .collapse {display: none}
29746- section.repo-right {
29747- margin-top: 1em;
29748- margin-left: unset;
29749- }
29750- section.viewer > svg {
29751- width: 400px !important;
29752- }
29753- }
29754- }
29755-
29756- @if map-get($breakpoints, "xl") {
29757- @media (max-width: map-get($breakpoints, "xl")) {
29758- section.viewer > svg {
29759- width: 550px;
29760- }
29761- section.repo-right {
29762- margin-top: 1em;
29763- margin-left: unset;
29764- }
29765- div.tree > table > thead > tr > th:nth-child(5) {
29766- text-align: right;
29767- }
29768- div.tree > table > tbody > tr > td:nth-child(5) {
29769- text-align: right;
29770- }
29771- article.repo {
29772- grid-template-columns: repeat(2, 1fr);
29773- }
29774- div.repo-container {
29775- grid-template-columns: 1fr;
29776- }
29777- }
29778- }
29779-
29780-
29781- @if map-get($breakpoints, "xl") {
29782- @media (min-width: map-get($breakpoints, "xl")) {
29783- .expand-xl {display: revert}
29784- }
29785- }
29786-
29787- // code highlighting
29788-
29789- article.blame-right {
29790- border-left: solid $blame-border 1px !important;
29791- }
29792-
29793- article.blame-left {
29794- border-right: solid $blame-border 1px !important;
29795- }
29796-
29797- article > header.highlighted {
29798- background-color: $highlighted;
29799- }
29800-
29801- article > footer {
29802- background-color: unset;
29803- }
29804-
29805- .icon-header.contrast > svg {
29806- background-color: $icon-background;
29807- }
29808-
29809- article.repo-right {
29810- background-color: unset;
29811- }
29812-
29813- article.repo-right > article {
29814- background-color: unset;
29815- }
29816-
29817- article.repo-right > article > header {
29818- background-color: unset;
29819- }
29820-
29821- .positive {
29822- color: $positive;
29823- }
29824-
29825- .negative {
29826- color: $negative;
29827- }
29828 diff --git a/themes/default/layout.scss b/themes/default/layout.scss
29829deleted file mode 100644
29830index de45bde..0000000
29831--- a/themes/default/layout.scss
29832+++ /dev/null
29833 @@ -1,70 +0,0 @@
29834- $breakpoints: (
29835- xs: 0,
29836- sm: 576px,
29837- md: 768px,
29838- lg: 992px,
29839- xl: 1200px,
29840- xxl: 1500px,
29841- xxxl: 2200px
29842- );
29843-
29844- // Viewports
29845- $viewports: (
29846- // 'null' disable the viewport on a breakpoint
29847- sm: 510px,
29848- md: 700px,
29849- lg: 920px,
29850- xl: 1130px,
29851- xxl: 1480px,
29852- xxxl: 2190px
29853- );
29854-
29855-
29856- .container,
29857- .container-fluid {
29858- width: 100%;
29859- margin-right: auto;
29860- margin-left: auto;
29861- padding-right: var(--spacing);
29862- padding-left: var(--spacing);
29863- }
29864-
29865- .container {
29866- @if map-get($breakpoints, "sm") {
29867- @media (min-width: map-get($breakpoints, "sm")) {
29868- max-width: map-get($viewports, "sm");
29869- padding-right: 0;
29870- padding-left: 0;
29871- }
29872- }
29873-
29874- @if map-get($breakpoints, "md") {
29875- @media (min-width: map-get($breakpoints, "md")) {
29876- max-width: map-get($viewports, "md");
29877- }
29878- }
29879-
29880- @if map-get($breakpoints, "lg") {
29881- @media (min-width: map-get($breakpoints, "lg")) {
29882- max-width: map-get($viewports, "lg");
29883- }
29884- }
29885-
29886- @if map-get($breakpoints, "xl") {
29887- @media (min-width: map-get($breakpoints, "xl")) {
29888- max-width: map-get($viewports, "xl");
29889- }
29890- }
29891-
29892- @if map-get($breakpoints, "xxl") {
29893- @media (min-width: map-get($breakpoints, "xxl")) {
29894- max-width: map-get($viewports, "xxl");
29895- }
29896- }
29897-
29898- @if map-get($breakpoints, "xxxl") {
29899- @media (min-width: map-get($breakpoints, "xxxl")) {
29900- max-width: map-get($viewports, "xxxl");
29901- }
29902- }
29903- }
29904 diff --git a/themes/default/templates/404.html b/themes/default/templates/404.html
29905deleted file mode 100644
29906index d8bac44..0000000
29907--- a/themes/default/templates/404.html
29908+++ /dev/null
29909 @@ -1,8 +0,0 @@
29910- {% extends "base.html" %}
29911- {% block content %}
29912- <!-- https://openclipart.org/detail/310184/fair-labyrinth -->
29913- <h1> Not Found ({{status_code}})</h1>
29914- <p> Unable to find the resource you have requested, perhaps try looking somewhere else? </p>
29915- <h4> Error Message: </h4>
29916- <pre>{{ error_message }}</pre>
29917- {% endblock %}
29918 diff --git a/themes/default/templates/5xx.html b/themes/default/templates/5xx.html
29919deleted file mode 100644
29920index e010a62..0000000
29921--- a/themes/default/templates/5xx.html
29922+++ /dev/null
29923 @@ -1,7 +0,0 @@
29924- {% extends "base.html" %}
29925- {% block content %}
29926- <h1> Internal Service Error ({{status_code}})</h1>
29927- <p> Something has gone wrong processing your request, please be patient. </p>
29928- <h4> Error Message: </h4>
29929- <pre>{{ error_message }}</pre>
29930- {% endblock %}
29931 diff --git a/themes/default/templates/about.html b/themes/default/templates/about.html
29932deleted file mode 100644
29933index e319b72..0000000
29934--- a/themes/default/templates/about.html
29935+++ /dev/null
29936 @@ -1,9 +0,0 @@
29937- {% extends "base.html" %}
29938- {% block content %}
29939- <section>
29940- <article>
29941- <header></header>
29942- {{ blurb | safe }}
29943- </article>
29944- </section>
29945- {% endblock %}
29946 diff --git a/themes/default/templates/authors.html b/themes/default/templates/authors.html
29947deleted file mode 100644
29948index 84eb9d4..0000000
29949--- a/themes/default/templates/authors.html
29950+++ /dev/null
29951 @@ -1,21 +0,0 @@
29952- {% extends "base.html" %}
29953- {% block content %}
29954- <section>
29955- <article>
29956- <header><h1>Authors</h1></header>
29957- {% for author in authors %}
29958- <article>
29959- <div class="wide">
29960- <div>
29961- <a href="/{{collection}}/{{name}}/log?username={{author.username | urlencode}}&email={{author.email | urlencode}}">{{author.username}}</a>
29962- </div>
29963- <div>
29964- <span class="positive">+{{author.lines_added}}</span>
29965- <span class="negative">-{{author.lines_removed}}</span>
29966- {{author.percentage}}% ({{author.count}})
29967- </div>
29968- </div>
29969- </article>
29970- {% endfor %}
29971- </article>
29972- {% endblock %}
29973 diff --git a/themes/default/templates/badge.svg b/themes/default/templates/badge.svg
29974deleted file mode 100644
29975index 895794b..0000000
29976--- a/themes/default/templates/badge.svg
29977+++ /dev/null
29978 @@ -1,29 +0,0 @@
29979- <svg xmlns="http://www.w3.org/2000/svg"
29980- xmlns:xlink="http://www.w3.org/1999/xlink" width="108" height="20"
29981- role="img" aria-label="{{key}}: {{value}}">
29982- <title>{{key}}: {{value}}</title>
29983- <linearGradient id="s" x2="0" y2="100%">
29984- <stop offset="0" stop-color="#bbb" stop-opacity=".1" />
29985- <stop offset="1" stop-opacity=".1" />
29986- </linearGradient>
29987- <clipPath id="r">
29988- <rect width="108" height="20" rx="3" fill="#fff" />
29989- </clipPath>
29990- <g clip-path="url(#r)">
29991- <rect width="55" height="20" fill="#555" />
29992- <rect x="55" width="53" height="20" fill="#007ec6" />
29993- <rect width="108" height="20" fill="url(#s)" />
29994- </g>
29995- <g fill="#fff" text-anchor="middle"
29996- font-family="monospace"
29997- text-rendering="geometricPrecision" font-size="110">
29998- <text aria-hidden="true" x="285" y="150" fill="#010101"
29999- fill-opacity=".3" transform="scale(.1)" textLength="450">{{key}}</text>
30000- <text x="285" y="140" transform="scale(.1)" fill="#fff"
30001- textLength="450">{{key}}</text>
30002- <text aria-hidden="true" x="805" y="150" fill="#010101"
30003- fill-opacity=".3" transform="scale(.1)" textLength="430">{{value}}</text>
30004- <text x="805" y="140" transform="scale(.1)" fill="#fff"
30005- textLength="430">{{value}}</text>
30006- </g>
30007- </svg>
30008 diff --git a/themes/default/templates/base.html b/themes/default/templates/base.html
30009deleted file mode 100644
30010index 191259d..0000000
30011--- a/themes/default/templates/base.html
30012+++ /dev/null
30013 @@ -1,32 +0,0 @@
30014- <!DOCTYPE html>
30015- <html lang="en">
30016- <head>
30017- <meta charset="utf-8">
30018- <meta name="viewport" content="width=device-width, initial-scale=1">
30019- <title>
30020- {% block title %}{{ title }}{% endblock %}
30021- </title>
30022- <link rel="stylesheet" href="/static/main.min.css" />
30023- <link href="/static/assets/ayllu_logo.svg"
30024- rel="icon"
30025- type="image/svg+xml" />
30026- {% block head %}{% endblock %}
30027- </head>
30028- <body>
30029- <!-- https://bugzilla.mozilla.org/show_bug.cgi?id=1404468 -->
30030- <script>0</script>
30031- {% block navigation %}
30032- {% include "nav.html" %}
30033- {% endblock %}
30034- {% if fluid %}
30035- <main class="container-fluid">
30036- {% else %}
30037- <main class="container">
30038- {% endif %}
30039- {% block content %}{% endblock %}
30040- </main>
30041- <footer class="main">
30042- {% if render_time %}rendered in {{ render_time }}ms{% endif %}
30043- </footer>
30044- </body>
30045- </html>
30046 diff --git a/themes/default/templates/blame.html b/themes/default/templates/blame.html
30047deleted file mode 100644
30048index 9a0a83b..0000000
30049--- a/themes/default/templates/blame.html
30050+++ /dev/null
30051 @@ -1,33 +0,0 @@
30052- {% import "macros.html" as macros %}
30053- {% extends "base.html" %}
30054- {% block content %}
30055- <article>
30056- <header>
30057- {{ macros::navigation(items=subnav_elements, title="Blame") }}
30058- </header>
30059- <section class="blame">
30060- {% if is_renderable %}
30061- <article class="blame-left">
30062- <pre><table><tbody>
30063- {%- for line in blame_lines -%}
30064- {%- set n_lines = line.end - line.start -%}
30065- <tr>
30066- <td>{{line.author_name}} <a href="/{{collection}}/{{name}}/commit/{{line.commit_id}}">{{line.timestamp}}</a></td>
30067- {%- for _ in range(end=n_lines-1) -%}
30068- <tr><td>&nbsp;</td><td></td></tr>
30069- {%- endfor -%}
30070- {% endfor %}</tbody></table></pre>
30071- </article>
30072- <article class="blame-right">
30073- <pre>{{ content | safe }}</pre>
30074- </article>
30075- {% else %}
30076- <article>
30077- <header>
30078- <h2>This content is not blameable</h2>
30079- </header>
30080- </article>
30081- {% endif %}
30082- </section>
30083- </article>
30084- {% endblock %}
30085 diff --git a/themes/default/templates/blob.html b/themes/default/templates/blob.html
30086deleted file mode 100644
30087index bc4d2e9..0000000
30088--- a/themes/default/templates/blob.html
30089+++ /dev/null
30090 @@ -1,40 +0,0 @@
30091- {% import "macros.html" as macros %}
30092- {% extends "base.html" %}
30093- {% block content %}
30094- <section>
30095- <article>
30096- <header>
30097- {{ macros::navigation(items=subnav_elements, title="Blob") }}
30098- </br>
30099- {%- if hint -%}
30100- <span class="hint" style="color: {{color}}">{{hint}}</span></br>
30101- {%- endif -%}
30102- <span>{{file_name}}</span>
30103- <span class="right"> {{ file_mode | filemode }} {{ file_size | human_bytes}} </span>
30104- </header>
30105- {% if is_binary %}
30106- <div class="blob-preview">
30107- {% if is_image %}
30108- <img src="{{ raw_url }}"></img>
30109- {% elif is_video %}
30110- <video controls>
30111- <source src="{{raw_url}}" type="{{file_type}}" />
30112- <p><a href="{{raw_url}}">raw url</a></p>
30113- </video>
30114- {% elif is_audio %}
30115- <audio src="{{raw_url}}" type="{{file_type}}"></audio>
30116- {% else %}
30117- <center><h3>Cannot render binary content</h3></center>
30118- <a href="{{ raw_url }}">download</a>
30119- {% endif %}
30120- </div>
30121- {% else %}
30122- {% if is_markdown %}
30123- <div class="readme">{{ content | safe }}</div>
30124- {% else %}
30125- <code class="highlighted">{{ content | safe }}</code>
30126- {% endif %}
30127- {% endif %}
30128- </article>
30129- </section>
30130- {% endblock %}
30131 diff --git a/themes/default/templates/branches.html b/themes/default/templates/branches.html
30132deleted file mode 100644
30133index 84ddc1d..0000000
30134--- a/themes/default/templates/branches.html
30135+++ /dev/null
30136 @@ -1,26 +0,0 @@
30137- {% import "macros.html" as macros %}
30138- {% extends "base.html" %}
30139- {% block content %}
30140- <section>
30141- <article>
30142- <header>
30143- {{ macros::navigation(items=refnav, title="Branches") }}
30144- </header>
30145- {% for branch in branches %}
30146- <article class="inner">
30147- <header class="wide">
30148- <div><h3><a href="/{{collection}}/{{name}}/tree/{{branch.name | urlencode }}">{{ branch.name }}</a></h3></div>
30149- <div><h5>{{ branch.commit.epoch | friendly_time }}<h5></div>
30150- </header>
30151- <p>{{branch.commit.message}}</p>
30152- <footer class="wide">
30153- <div>{{ branch.commit.author_name }}</div>
30154- <div>
30155- <a href="/{{collection}}/{{name}}/commit/{{branch.commit.id}}" role="button">{{branch.commit.id | truncate(length=8, end="")}}</a>
30156- </div>
30157- </footer>
30158- </article>
30159- {% endfor %}
30160- </article>
30161- </section>
30162- {% endblock %}
30163 diff --git a/themes/default/templates/build.html b/themes/default/templates/build.html
30164deleted file mode 100644
30165index 2f667cc..0000000
30166--- a/themes/default/templates/build.html
30167+++ /dev/null
30168 @@ -1,29 +0,0 @@
30169- {% extends "base.html" %}
30170- {% block content %}
30171- <section>
30172- <article>
30173- <header>
30174- <h1>Build: {{ build.id }}</h1>
30175- </header>
30176- <table>
30177- <thead>
30178- <th> date </th>
30179- <th> runtime </th>
30180- <th> success </th>
30181- </thead>
30182- <tr>
30183- <td> {{ build.timestamp }} </td>
30184- <td> {{ build.runtime }} </td>
30185- <td> {{ build.success }} </td>
30186- </tr>
30187- </table>
30188- </article>
30189- {% for step in steps %}
30190- <article>
30191- <header> {{ step.0 }} </header>
30192- <code> {{ step.1 }} </code>
30193- <code> {{ step.2 }} </code>
30194- </article>
30195- {% endfor %}
30196- </section>
30197- {% endblock %}
30198 diff --git a/themes/default/templates/builds.html b/themes/default/templates/builds.html
30199deleted file mode 100644
30200index 9e8e383..0000000
30201--- a/themes/default/templates/builds.html
30202+++ /dev/null
30203 @@ -1,26 +0,0 @@
30204- {% extends "base.html" %}
30205- {% block content %}
30206- <section>
30207- <article>
30208- <header>
30209- <h1>Builds</h1>
30210- </header>
30211- <table>
30212- <thead>
30213- <th> id </th>
30214- <th> date </th>
30215- <th> runtime </th>
30216- <th> success </th>
30217- </thead>
30218- {% for build in builds %}
30219- <tr>
30220- <td> <a href="/{{collection}}/{{name}}/builds/{{build.id}}">{{ build.id }}</a></td>
30221- <td> {{ build.timestamp }} </td>
30222- <td> {{ build.runtime }} </td>
30223- <td> {{ build.success }} </td>
30224- </tr>
30225- {% endfor %}
30226- </table>
30227- </article>
30228- </section>
30229- {% endblock %}
30230 diff --git a/themes/default/templates/channel.html b/themes/default/templates/channel.html
30231deleted file mode 100644
30232index 81a29a2..0000000
30233--- a/themes/default/templates/channel.html
30234+++ /dev/null
30235 @@ -1,27 +0,0 @@
30236- {% import "macros.html" as macros %}
30237- {% extends "base.html" %}
30238- {% block content %}
30239- <section>
30240- <article>
30241- <header>
30242- {{ macros::navigation(items=discnav, title=channel) }}
30243- </header>
30244- <table>
30245- <thead>
30246- <th> nick </th>
30247- <th> timestamp </th>
30248- <th> message</th>
30249- </thead>
30250- <tbody>
30251- {% for message in messages %}
30252- <tr>
30253- <td> {{ message.nickname }} </td>
30254- <td> {{ message.timestamp }} </td>
30255- <td>{{ message.body | escape }}</td>
30256- </tr>
30257- {% endfor %}
30258- </tbody>
30259- </table>
30260- </article>
30261- </section>
30262- {% endblock %}
30263 diff --git a/themes/default/templates/channels.html b/themes/default/templates/channels.html
30264deleted file mode 100644
30265index 75e4a03..0000000
30266--- a/themes/default/templates/channels.html
30267+++ /dev/null
30268 @@ -1,27 +0,0 @@
30269- {% import "macros.html" as macros %}
30270- {% extends "base.html" %}
30271- {% block content %}
30272- <section>
30273- <article>
30274- <header>
30275- {{ macros::navigation(items=discnav, title="XMPP Channels") }}
30276- </header>
30277- <table>
30278- <thead>
30279- <th> name </th>
30280- <th> online </th>
30281- <th> messages </th>
30282- </thead>
30283- <tbody>
30284- {% for channel in channels %}
30285- <tr>
30286- <td><a href="/discuss/xmpp/{{channel.name}}">{{ channel.name }}</a></td>
30287- <td>{{ channel.n_users }}</td>
30288- <td>{{ channel.n_messages }}</td>
30289- </tr>
30290- {% endfor %}
30291- </tbody>
30292- </table>
30293- </article>
30294- </section>
30295- {% endblock %}
30296 diff --git a/themes/default/templates/chart.html b/themes/default/templates/chart.html
30297deleted file mode 100644
30298index ed03896..0000000
30299--- a/themes/default/templates/chart.html
30300+++ /dev/null
30301 @@ -1,20 +0,0 @@
30302- {% extends "base.html" %}
30303- {% block content %}
30304- <section>
30305- <article>
30306- <header>
30307- <nav>
30308- <ul><h1>{{ chart_title }}</h1></ul>
30309- <ul>
30310- {% for item in chartnav %}
30311- <li {% if item.2 %} class="active" {% endif %}> <a href="{{ item.1 }}">{{ item.0 }}</a></li>
30312- {% endfor %}
30313- </ul>
30314- </nav>
30315- </header>
30316- <section class="viewer">
30317- {{ chart | safe }}
30318- </section>
30319- </article>
30320- </section>
30321- {% endblock %}
30322 diff --git a/themes/default/templates/collection.html b/themes/default/templates/collection.html
30323deleted file mode 100644
30324index 0b8224b..0000000
30325--- a/themes/default/templates/collection.html
30326+++ /dev/null
30327 @@ -1,23 +0,0 @@
30328- {% extends "base.html" %}
30329- {% block content %}
30330- <section class="index">
30331- <article>
30332- <header>
30333- <span class="h1">{{ collection.name }} </span>
30334- <span class="right">{{ collection.description }}</b> {%- if is_hidden -%} <span class="negative">[hidden]</span> {%- endif -%}</span>
30335- </header>
30336- {% for repo in repositories %}
30337- <article>
30338- <span class="header">
30339- <a href="/{{collection.name}}/{{repo.name}}">{{ repo.name }}</a>
30340- </span>
30341- <span class="right">{{repo.description}}</span>
30342- <footer>
30343- <span><small>{{repo.age}}</small></span>
30344- <span class="right">{{repo.activity}}</span>
30345- </footer>
30346- </article>
30347- {% endfor %}
30348- </article>
30349- </section>
30350- {% endblock %}
30351 diff --git a/themes/default/templates/commit.html b/themes/default/templates/commit.html
30352deleted file mode 100644
30353index b23617e..0000000
30354--- a/themes/default/templates/commit.html
30355+++ /dev/null
30356 @@ -1,61 +0,0 @@
30357- {% extends "base.html" %}
30358- {% block content %}
30359- <article>
30360- <header class="wide">
30361- <h1>Commit</h1>
30362- <span class="right">{{ commit.epoch | friendly_time }}</span>
30363- </header>
30364- <article>
30365- <div class="wide">
30366- <div> <b> Author: </b> </div>
30367- <div>
30368- <a href="/{{collection}}/{{name}}/log?username={{commit.author_name | urlencode}}&email={{commit.author_email | urlencode}}">{{commit.author_name}}</a>
30369- [<a href="mailto://{{commit.author_email}}">{{commit.author_email}}</a>]
30370- </div>
30371- </div>
30372- {% if distinct_author %}
30373- <div class="wide">
30374- <div><b>Committer:</b></div>
30375- <div>
30376- <a href="#">{{commit.committer_name}}</a>
30377- [<a href="mailto://{{commit.committer_email}}">{{commit.committer_email}}</a>]
30378- {{ commit.committer_epoch | format_epoch }}
30379- </div>
30380- </div>
30381- {% endif %}
30382- <div class="wide">
30383- <div><b>Hash:</b></div>
30384- {% if commit.is_verified %}
30385- <span class="positive">
30386- {% else %}
30387- <span class="negative">
30388- {% endif %}
30389- {{commit.id}}
30390- </span>
30391- </div>
30392- <div class="wide">
30393- <div> <b> Time: </b> </div>
30394- <div> {{ commit.author_epoch | format_epoch }} </div>
30395- </div>
30396- <b> Message: </b>
30397- </br>
30398- <i>{{ commit.summary }}</i>
30399- </article>
30400- {% if extended_commit_message %}
30401- <div class="commit_message">
30402- <pre>{{ commit.message }}</pre>
30403- </div>
30404- {% endif %}
30405- <footer>
30406- <span class="positive">+{{stats.insertions}}</span>
30407- <span class="negative">-{{stats.deletions}}</span>
30408- <span>+/-{{stats.files_changed}}</span>
30409- <span class="right">
30410- <a href="/{{collection}}/{{name}}/tree/{{commit_hash}}" role="button">browse</a>
30411- </span>
30412- </footer>
30413- </article>
30414- <article>
30415- <pre>{{diff | safe}}</pre>
30416- </article>
30417- {% endblock %}
30418 diff --git a/themes/default/templates/config.html b/themes/default/templates/config.html
30419deleted file mode 100644
30420index 349356a..0000000
30421--- a/themes/default/templates/config.html
30422+++ /dev/null
30423 @@ -1,25 +0,0 @@
30424- {% extends "base.html" %}
30425- {% block content %}
30426- <div class="container">
30427- <article>
30428- <header><h1>Site Configuration</h1></header>
30429- <form action="/config" method="POST">
30430- <label for="theme"><h5>items-per-page</h5></label>
30431- <select id="items_per_page" name="items_per_page" required>
30432- <option value="50" {%- if config.items_per_page == 50 -%} selected {%- endif -%}>50</option>
30433- <option value="100" {%- if config.items_per_page == 100 -%} selected {%- endif -%}>100</option>
30434- <option value="150" {%- if config.items_per_page == 150 -%} selected {%- endif -%}>150</option>
30435- <option value="200" {%- if config.items_per_page == 200 -%} selected {%- endif -%}>200</option>
30436- </select>
30437- <label for="theme"><h5>theme</h5></label>
30438- <select id="theme" name="theme" required>
30439- {% for theme in themes %}
30440- <option value="{{theme}}" {%- if theme == config.theme -%} selected {%- endif -%}>{{ theme }}</option>
30441- {% endfor %}
30442- </select>
30443- <!-- Button -->
30444- <button type="submit">Submit</button>
30445- </form>
30446- </article>
30447- </div>
30448- {% endblock %}
30449 diff --git a/themes/default/templates/graphql.html b/themes/default/templates/graphql.html
30450deleted file mode 100644
30451index b8d9155..0000000
30452--- a/themes/default/templates/graphql.html
30453+++ /dev/null
30454 @@ -1,6 +0,0 @@
30455- {% extends "base.html" %}
30456- {% block content %}
30457- <p>
30458- Graphql API
30459- </p>
30460- {% endblock %}
30461 diff --git a/themes/default/templates/index.html b/themes/default/templates/index.html
30462deleted file mode 100644
30463index 40ac8a5..0000000
30464--- a/themes/default/templates/index.html
30465+++ /dev/null
30466 @@ -1,37 +0,0 @@
30467- {% extends "base.html" %}
30468- {% block content %}
30469- <section class="index">
30470- <header class="wide"><div><h3>Collections</h3></div>
30471- <div>
30472- <div class="icon-header">
30473- <h3>Subscribe</h3>
30474- {{ "feed" | emoji | safe }}
30475- </div>
30476- [<a href="/rss/firehose.xml">*</a>,
30477- <a href="/rss/1d.xml">1d</a>,
30478- <a href="/rss/1w.xml">1w</a>,
30479- <a href="/rss/1m.xml">1m</a>]
30480- </div>
30481- </header>
30482- {% for collection in collections %}
30483- <article class="index-listing">
30484- <header class="wide">
30485- <div><h5><a href="/{{collection.name}}">{{ collection.name }}</a></h5></div>
30486- <div><b>{{ collection.description }}</b></div>
30487- </header>
30488- {% for repo in collection.repositories %}
30489- <article>
30490- <header class="wide">
30491- <div><a href="/{{collection.name}}/{{repo.name}}">{{ repo.name }}</a></div>
30492- <div>{{repo.description}}</div>
30493- </header>
30494- <div class="wide">
30495- <div>{{repo.age}}</div>
30496- <div>{{repo.activity}}</div>
30497- </footer>
30498- </article>
30499- {% endfor %}
30500- </article>
30501- {% endfor %}
30502- </section>
30503- {% endblock %}
30504 diff --git a/themes/default/templates/lists.html b/themes/default/templates/lists.html
30505deleted file mode 100644
30506index afbcce5..0000000
30507--- a/themes/default/templates/lists.html
30508+++ /dev/null
30509 @@ -1,29 +0,0 @@
30510- {% import "macros.html" as macros %}
30511- {% extends "base.html" %}
30512- {% block content %}
30513- <section>
30514- <article>
30515- <header>
30516- <h1> Mailing Lists </h1>
30517- </header>
30518- <table>
30519- <thead>
30520- <th> id </th>
30521- <th> name </th>
30522- <th> description </th>
30523- <th> address </th>
30524- </thead>
30525- <tbody>
30526- {% for list in lists %}
30527- <tr>
30528- <td><a href="/mail/{{list.id}}">{{ list.id }}</a></td>
30529- <td>{{ list.name }}</td>
30530- <td>{{ list.description }}</td>
30531- <td>{{ list.address }}</td>
30532- </tr>
30533- {% endfor %}
30534- </tbody>
30535- </table>
30536- </article>
30537- </section>
30538- {% endblock %}
30539 diff --git a/themes/default/templates/log.html b/themes/default/templates/log.html
30540deleted file mode 100644
30541index fe97650..0000000
30542--- a/themes/default/templates/log.html
30543+++ /dev/null
30544 @@ -1,61 +0,0 @@
30545- {% import "macros.html" as macros %}
30546- {% extends "base.html" %}
30547- {% block content %}
30548- <section>
30549- <article>
30550- {% if file_view %}
30551- <header>
30552- {{ macros::navigation(items=subnav_elements, title="Log") }}
30553- </header>
30554- {% else %}
30555- <header>
30556- <h1>Log</h1>
30557- </header>
30558- {% endif %}
30559- <table class="commits">
30560- <thead>
30561- <tr>
30562- <th class="collapse">Author</th>
30563- <th class="collapse">Signature</th>
30564- <!--<th class="collapse">Hash</th>-->
30565- <th>Message</th>
30566- <th>Date</th>
30567- </tr>
30568- </thead>
30569- <tbody>
30570- {% for commit in commits %}
30571- <tr>
30572- <td class="collapse"><a href="/{{collection}}/{{name}}/log?username={{commit.author_name | urlencode}}&email={{commit.author_email | urlencode}}">{{commit.author_name}}</a></td>
30573- <!-- <td class="collapse"> 🔒......</td>-->
30574- <td class="collapse">
30575- {% if commit.is_verified %}
30576- <span class="positive">
30577- {% else %}
30578- <span class="negative">
30579- {% endif %}
30580- {{ commit.id | truncate(length=8, end="") }}
30581- </span>
30582- </td>
30583- <td class="message">
30584- <a href="/{{collection}}/{{name}}/commit/{{commit.id}}">
30585- {{ commit.summary | truncate(length=80) }}</a>
30586- {% if commit.is_extended %}
30587- <br/>
30588- <br/>
30589- <pre>{{ commit.message }}</pre>
30590- {% endif %}
30591- </td>
30592- <td> {{ commit.epoch | friendly_time }} </td>
30593- </tr>
30594- {% endfor %}
30595- </tbody>
30596- </table>
30597- {% if has_more %}
30598- <footer class="pagination">
30599- {%- set last_commit = commits | last -%}
30600- <span class="right"><a href="/{{collection}}/{{name}}/log/{{last_commit.id}}"><b>next</b></a></span>
30601- </footer>
30602- {% endif %}
30603- </article>
30604- </section>
30605- {% endblock %}
30606 diff --git a/themes/default/templates/macros.html b/themes/default/templates/macros.html
30607deleted file mode 100644
30608index c636ae9..0000000
30609--- a/themes/default/templates/macros.html
30610+++ /dev/null
30611 @@ -1,10 +0,0 @@
30612- {% macro navigation(items, title="") %}
30613- <nav>
30614- <ul><h1>{{title}}</h1></ul>
30615- <ul>
30616- {% for item in items %}
30617- <li {% if item.2 %} class="active" {% endif %}> <a href="{{ item.1 }}">{{ item.0 }}</a></li>
30618- {% endfor %}
30619- </ul>
30620- </nav>
30621- {% endmacro navigation %}
30622 diff --git a/themes/default/templates/nav.html b/themes/default/templates/nav.html
30623deleted file mode 100644
30624index 2492a79..0000000
30625--- a/themes/default/templates/nav.html
30626+++ /dev/null
30627 @@ -1,16 +0,0 @@
30628- <nav>
30629- <ul>
30630- <li>
30631- <a href="{%- if subpath_mode -%}/browse{%- else -%}/{%- endif -%}">
30632- <div class="logo">{{ "textile-pattern-1" | emoji | safe }}
30633- <h3>&emsp;</h3>
30634- </div>
30635- </a>
30636- </li>
30637- </ul>
30638- <ul>
30639- {% for item in nav_elements %}
30640- <li {% if item.2 %} class="active" {% endif %}> <a href="{{ item.1 }}">{{ item.0 }}</a></li>
30641- {% endfor %}
30642- </ul>
30643- </nav>
30644 diff --git a/themes/default/templates/post.html b/themes/default/templates/post.html
30645deleted file mode 100644
30646index 10a0e41..0000000
30647--- a/themes/default/templates/post.html
30648+++ /dev/null
30649 @@ -1,16 +0,0 @@
30650- {% extends "base.html" %}
30651- {% block content %}
30652- <section class="thread-view">
30653- <article>
30654- <header>
30655- <h1>{{message.message_id}}</h1></br>
30656- <h4> Export Message </h4>
30657- <p> <a href="/mail/export/{{list_id}}/{{thread_id}}/{{message.message_id}}">mbox</a><p>
30658- <b>From: {{ message.from_address }}</b></br>
30659- <b>Subject: {{ message.subject }}</b></br>
30660- <span class="right">{{ message.created_at | format_epoch }}</span>
30661- </header>
30662- <pre>{{ message.text | safe }}</pre>
30663- </article>
30664- </section>
30665- {% endblock %}
30666 diff --git a/themes/default/templates/repo.html b/themes/default/templates/repo.html
30667deleted file mode 100644
30668index 631f670..0000000
30669--- a/themes/default/templates/repo.html
30670+++ /dev/null
30671 @@ -1,178 +0,0 @@
30672- {% extends "base.html" %}
30673- {% block content %}
30674- {% if show_details %}
30675- <div class="repo-container">
30676- {% endif %}
30677- <section class="repo-left">
30678- <article class=tree>
30679- <header class="tree-preamble">
30680- <div class="wide">
30681- <span>{{ latest_commit.author_name }} {{latest_commit.epoch | friendly_time }}</span>
30682- <h6>{{ commit_count }} commits</h6>
30683- </div>
30684- <div>
30685- <span class="right">
30686- <h6><b>{{refname}}</b></h6>
30687- </span>
30688- </div>
30689- {% if latest_commit.is_verified %}
30690- <span class="positive">
30691- {% else %}
30692- <span class="negative">
30693- {% endif %}
30694- {{latest_commit.id | truncate(length=8, end="")}}
30695- </span>
30696- </br>
30697- <a href="/{{collection}}/{{name}}/commit/{{latest_commit.id}}">
30698- {{latest_commit.summary | truncate(length=60)}}
30699- </a>
30700- </header>
30701- <div class="tree">
30702- <table>
30703- <thead>
30704- <tr>
30705- <th scope="col">file</th>
30706- <th class="collapse" scope="col">commit</th>
30707- <th class="expand-xl" scope="col">size</th>
30708- <th class="expand-xl" scope="col">mode</th>
30709- <th scope="col">time</th>
30710- </tr>
30711- </thead>
30712- <tbody>
30713- {% for item in tree %}
30714- <tr>
30715- {% if item.0.submodule %}
30716- <td>{{ item.0.name }} @ {{ item.0.submodule }}</td>
30717- {% else %}
30718- <td>
30719- <a href="{{item.0.name | make_url(kind=item.0.kind)}}">
30720- {{item.0.name}}
30721- {% if item.0.kind == "Submodule" %}
30722- <span class="tiny">ref</span>
30723- {% endif %}
30724- {% if item.0.kind == "Pointer" %}
30725- <span class="tiny">ptr</span>
30726- {% endif %}
30727- </a>
30728- </td>
30729- {% endif %}
30730- <td class="collapse">
30731- <a href="/{{collection}}/{{name}}/commit/{{item.1.id}}">{{ item.1.summary | truncate(length=60)}}</a></td>
30732- <td class="expand-xl">{{item.0.size | human_bytes }}</td>
30733- <td class="expand-xl">{{item.0.mode | filemode }}</td>
30734- <td> <a href="/{{collection}}/{{name}}/commit/{{item.1.id}}">{{ item.1.epoch | friendly_time }}</a></td>
30735- </tr>
30736- {% endfor %}
30737- </tbody>
30738- </table>
30739- </div>
30740- </article>
30741- <article class="readme">
30742- <header> {{ rendered_file_name }} </header>
30743- <div class="readme">{{ readme | safe }}</div>
30744- </article>
30745- </section>
30746- {% if show_details %}
30747- <section class="repo-right">
30748- <article>
30749- <div class="panel">
30750- <section class="repo-section">
30751- <div class="icon-header contrast">
30752- <h3>Clone</h3>
30753- </div>
30754- <div class="clone">
30755- <div><b>HTTP</b></div>
30756- <div class="clone-url"><input type="text" value="{{http_clone_url}}" readonly></div>
30757- </div>
30758- {% if git_clone_url %}
30759- <div class="clone">
30760- <div style="margin-right: 1ch;"><b>SSH</b></div>
30761- <div class="clone-url"><input type="text" value="{{git_clone_url}}" readonly></div>
30762- </div>
30763- {% endif %}
30764- </section>
30765- {% if chat_links or email_links %}
30766- <section class="repo-section">
30767- <div class="icon-header contrast"><h3>Discussion</h3>
30768- </div>
30769- {% if chat_links %}
30770- <h6> Chat </h6>
30771- {% for chat in chat_links %}
30772- <em>{{chat.description}}, status: </em>
30773- <b class={%- if chat.users_online -%}"positive"{%- else -%}"negative"{%- endif -%}
30774- data-tooltip="{%- if chat.users_online -%} {{chat.users_online}} Users Online {%- else -%} Offline {%- endif -%}">
30775- [{%- if chat.users_online -%}{{chat.users_online}}{%- else -%}?{%- endif -%}]
30776- </b>
30777- <div class="clone">
30778- <div>
30779- <b>{{chat.kind}}</b>
30780- </div>
30781- <div class="clone-url"><input type="text" value="{{chat.url}}" readonly></div>
30782- </div>
30783- {% endfor %}
30784- {% endif %}
30785- {% if email_links %}
30786- <h6> Mailing Lists </h6>
30787- {% for email in email_links %}
30788- <em>{{email.description}}, 100+ threads</em>
30789- <div class="clone">
30790- <div>
30791- <b>mail</b>
30792- </div>
30793- <div class="clone-url"><input type="text" value="{{email.url}}" readonly></div>
30794- </div>
30795- {% endfor %}
30796- {% endif %}
30797- </section>
30798- {% endif %}
30799- <section class="repo-section">
30800- <h3> Subscribe<span class="icon">{{ "feed" | emoji | safe }}</span></h3>
30801- </br>
30802- [<a href="{{rss_link_all}}">*</a>,
30803- <a href="{{rss_link_1d}}">1d</a>,
30804- <a href="{{rss_link_1w}}">1w</a>,
30805- <a href="{{rss_link_1m}}">1m</a>]
30806- </section>
30807- {% if sites_url %}
30808- <section class="repo-section">
30809- <div class="icon-header contrast">
30810- <h3>Homepage</h3>
30811- </div>
30812- <a href={{sites_url}}>{{sites_url}}</a>
30813- </section>
30814- {% endif %}
30815- <section class="repo-section">
30816- <div class="icon-header contrast"><h3>License</h3>
30817- </div>
30818- <i><b>{{ license }}</b></i>
30819- </section>
30820- <section class="repo-section">
30821- <div class="icon-header contrast"><h3><a href="/{{collection}}/{{name}}/authors">Authors</a></h3>
30822- </div>
30823- </a>
30824- <ul class="author-list">
30825- {% if authors %}
30826- {% for author in authors %}
30827- <li> {{ author.0 }}: <i> {{ author.2 }}% </i> </li>
30828- {% endfor %}
30829- {% endif %}
30830- </ul>
30831- </section>
30832- <div class="chart">
30833- <a href="/{{collection}}/{{name}}/chart/activity/{{latest_commit.id}}">
30834- {{ activity_chart | safe }}
30835- </a>
30836- </div>
30837- <div class="chart">
30838- <a href="/{{collection}}/{{name}}/chart/languages/{{latest_commit.id}}">
30839- {{ language_chart | safe }}
30840- </a>
30841- </div>
30842- </div>
30843- </article>
30844- </section>
30845- {% endif %}
30846- {% if show_details %}
30847- </div>
30848- {% endif %}
30849- {% endblock %}
30850 diff --git a/themes/default/templates/rss_summary.html b/themes/default/templates/rss_summary.html
30851deleted file mode 100644
30852index ca85e9a..0000000
30853--- a/themes/default/templates/rss_summary.html
30854+++ /dev/null
30855 @@ -1,24 +0,0 @@
30856- <center>
30857- <h1> Summary from {{start_date}} to {{end_date}} </h1>
30858- <h2>{{n_tags}} tags and {{n_commits}} commits added {% if entries | length > 1 %} across {{n_projects}} projects{%- endif -%} </h2>
30859- </center>
30860-
30861- {% for entry in entries %}
30862- <h2> Updates For {{entry.name}} </h2>
30863-
30864- {% if entry.tags | length > 0 %}
30865- </br><h3> <u>New Tags</u> </h3>
30866- {% endif %}
30867-
30868- {% if entry.commits | length > 0 %}
30869- </br><h3> <u>New Commits</u> </h3>
30870- {% for commit in entry.commits %}
30871- <article>
30872- <header>
30873- <h4><a href="{{origin}}/{{entry.name}}/commit/{{commit.id}}">{{commit.summary}} - {{commit.author_name}}</a></h4>
30874- </header>
30875- <pre>{{commit.message}}</pre>
30876- </article>
30877- {% endfor %}
30878- {% endif %}
30879- {% endfor %}
30880 diff --git a/themes/default/templates/tags.html b/themes/default/templates/tags.html
30881deleted file mode 100644
30882index 2813ddd..0000000
30883--- a/themes/default/templates/tags.html
30884+++ /dev/null
30885 @@ -1,28 +0,0 @@
30886- {% import "macros.html" as macros %}
30887- {% extends "base.html" %}
30888- {% block content %}
30889- <section>
30890- <article>
30891- <header>
30892- {{ macros::navigation(items=refnav, title="Tags") }}
30893- </header>
30894- {% for tag in tags %}
30895- <article class="inner">
30896- <header class="wide">
30897- <div><h3><a href="/{{collection}}/{{name}}/tree/{{tag.name | urlencode}}">{{ tag.name }}</a></h3></div>
30898- <div><h5>{{ tag.commit.epoch | friendly_time }}<h5></div>
30899- </header>
30900- <pre>{{ tag.summary }}</pre>
30901- <footer class="wide">
30902- <div>{{ tag.author_name }}</div>
30903- <div>
30904- <a href="/{{collection}}/{{name}}/refs/archive/{{tag.name}}.tar.gz" role="button">{{tag.name}}.tar.gz</a>
30905- <a href="/{{collection}}/{{name}}/commit/{{tag.commit.id}}"
30906- role="button">{{tag.commit.id | truncate(length=8, end="")}}</a>
30907- </div>
30908- </footer>
30909- </article>
30910- {% endfor %}
30911- </article>
30912- </section>
30913- {% endblock %}
30914 diff --git a/themes/default/templates/thread.html b/themes/default/templates/thread.html
30915deleted file mode 100644
30916index 9d75245..0000000
30917--- a/themes/default/templates/thread.html
30918+++ /dev/null
30919 @@ -1,23 +0,0 @@
30920- {% extends "base.html" %}
30921- {% block content %}
30922- <section class="thread-view">
30923- <article>
30924- <header>
30925- <h1>{{ subject }}</h1></br>
30926- <h4> Export Thread </h4>
30927- <p><a href="/mail/export/{{list_id}}/{{thread_id}}">mbox</a></p>
30928- </header>
30929- {% for reply in messages %}
30930- <article>
30931- <header>
30932- <b>From: {{ reply.from_address }}</b></br>
30933- <b>Subject: {{ reply.subject }} </b></br>
30934- <b><a href="/mail/message/{{list_id}}/{{reply.message_id}}">{{ reply.message_id }}</a></b>
30935- <span class="right">{{ reply.created_at | format_epoch }}</span>
30936- </header>
30937- <pre>{{ reply.text | safe }}</pre>
30938- </article>
30939- {% endfor %}
30940- </section>
30941- </article>
30942- {% endblock %}
30943 diff --git a/themes/default/templates/threads.html b/themes/default/templates/threads.html
30944deleted file mode 100644
30945index 1b2c733..0000000
30946--- a/themes/default/templates/threads.html
30947+++ /dev/null
30948 @@ -1,45 +0,0 @@
30949- {% import "macros.html" as macros %}
30950- {% extends "base.html" %}
30951- {% block content %}
30952- <section>
30953- <article>
30954- <header>
30955- <h1> {{ list.name }} </h1>
30956- <span class="right labels">
30957- {% for topic in list.topics %}
30958- <span class="feature">{{topic}}</span>
30959- {% endfor %}
30960- </span>
30961- </header>
30962- <div class="mailing-list-details">
30963- <h4> {{ list.description }} </h4>
30964- </br>
30965- <h4> Subscribe </h4>
30966- <p> Send an e-mail to <a href="mailto:{{request_address}}?subject=subscribe">{{request_address}}</a> with the following subject: <code>subscribe</code> </p>
30967- <h4> Unsubscribe </h4>
30968- <p> Send an e-mail to <a href="mailto:{{ request_address}}?subject=unsubscribe">{{request_address}}</a> with the following subject: <code>unsubscribe</code> </p>
30969- <h4> Export List </h4>
30970- <p><a href="/mail/export/{{list.id}}">mbox</a></p>
30971- </div>
30972- <h4> Messages </h4>
30973- <table>
30974- <thead>
30975- <th> from </th>
30976- <th> date </th>
30977- <th> subject </th>
30978- <th> replies </th>
30979- </thead>
30980- <tbody>
30981- {% for thread in threads %}
30982- <tr>
30983- <td>{{ thread.from }}</a></td>
30984- <td>{{thread.timestamp | format_epoch }}</td>
30985- <td><a href="/mail/thread/{{list.id}}/{{thread.message_id}}">{{thread.subject}}</a></td>
30986- <td>{{thread.n_replies}}</td>
30987- </tr>
30988- {% endfor %}
30989- </tbody>
30990- </table>
30991- </article>
30992- </section>
30993- {% endblock %}
30994 diff --git a/themes/default/templates/user.html b/themes/default/templates/user.html
30995deleted file mode 100644
30996index fda2ec3..0000000
30997--- a/themes/default/templates/user.html
30998+++ /dev/null
30999 @@ -1,4 +0,0 @@
31000- {% extends "base.html" %}
31001- {% block content %}
31002- User Profile
31003- {% endblock %}
31004 diff --git a/themes/default/theme.scss b/themes/default/theme.scss
31005deleted file mode 100644
31006index 3c4052d..0000000
31007--- a/themes/default/theme.scss
31008+++ /dev/null
31009 @@ -1,83 +0,0 @@
31010- // monochrome
31011-
31012- // Navy-Grey
31013- $grey-hue: 205;
31014- $grey-50: hsl($grey-hue, 20%, 94%);
31015- $grey-100: hsl($grey-hue, 18%, 86%);
31016- $grey-200: hsl($grey-hue, 16%, 77%);
31017- $grey-300: hsl($grey-hue, 14%, 68%);
31018- $grey-400: hsl($grey-hue, 12%, 59%);
31019- $grey-500: hsl($grey-hue, 10%, 50%);
31020- $grey-600: hsl($grey-hue, 15%, 41%);
31021- $grey-700: hsl($grey-hue, 20%, 32%);
31022- $grey-800: hsl($grey-hue, 25%, 23%);
31023- $grey-900: hsl($grey-hue, 30%, 15%);
31024-
31025- // Light Blue
31026- $primary-hue: $grey-hue;
31027- $primary-50: hsl($grey-hue, 20%, 94%);
31028- $primary-100: hsl($grey-hue, 18%, 86%);
31029- $primary-200: hsl($grey-hue, 16%, 77%);
31030- $primary-300: hsl($grey-hue, 14%, 68%);
31031- $primary-400: hsl($grey-hue, 12%, 59%);
31032- $primary-500: hsl($grey-hue, 10%, 50%);
31033- $primary-600: hsl($grey-hue, 15%, 41%);
31034- $primary-700: hsl($grey-hue, 20%, 32%);
31035- $primary-800: hsl($grey-hue, 25%, 23%);
31036- $primary-900: hsl($grey-hue, 30%, 15%);
31037-
31038- // Black & White
31039- $black: #000;
31040- $white: #fff;
31041-
31042- // Amber
31043- $amber-50: hsl($grey-hue, 20%, 94%);
31044- $amber-100: hsl($grey-hue, 18%, 86%);
31045- $amber-200: hsl($grey-hue, 16%, 77%);
31046- $amber-300: hsl($grey-hue, 14%, 68%);
31047- $amber-400: hsl($grey-hue, 12%, 59%);
31048- $amber-500: hsl($grey-hue, 10%, 50%);
31049- $amber-600: hsl($grey-hue, 15%, 41%);
31050- $amber-700: hsl($grey-hue, 20%, 32%);
31051- $amber-800: hsl($grey-hue, 25%, 23%);
31052- $amber-900: hsl($grey-hue, 30%, 15%);
31053-
31054- // Green
31055- $green-50: hsl($grey-hue, 20%, 94%);
31056- $green-100: hsl($grey-hue, 18%, 86%);
31057- $green-200: hsl($grey-hue, 16%, 77%);
31058- $green-300: hsl($grey-hue, 14%, 68%);
31059- $green-400: hsl($grey-hue, 12%, 59%);
31060- $green-500: hsl($grey-hue, 10%, 50%);
31061- $green-600: hsl($grey-hue, 15%, 41%);
31062- $green-700: hsl($grey-hue, 20%, 32%);
31063- $green-800: hsl($grey-hue, 25%, 23%);
31064- $green-900: hsl($grey-hue, 30%, 15%);
31065-
31066- // Red
31067- $red-50: hsl($grey-hue, 20%, 94%);
31068- $red-100: hsl($grey-hue, 18%, 86%);
31069- $red-200: hsl($grey-hue, 16%, 77%);
31070- $red-300: hsl($grey-hue, 14%, 68%);
31071- $red-400: hsl($grey-hue, 12%, 59%);
31072- $red-500: hsl($grey-hue, 10%, 50%);
31073- $red-600: hsl($grey-hue, 15%, 41%);
31074- $red-700: hsl($grey-hue, 20%, 32%);
31075- $red-800: hsl($grey-hue, 25%, 23%);
31076- $red-900: hsl($grey-hue, 30%, 15%);
31077-
31078- $positive: $black;
31079- $negative: $black;
31080-
31081- $blame-border: $black;
31082-
31083- $highlighted: $black;
31084- $icon-background: $black;
31085-
31086- $chart-color: $black;
31087-
31088- // NOTE: colors need to be defined above since they're
31089- // read as variables in pico.
31090- @import "@picocss/pico/scss/pico";
31091- @import "layout.scss";
31092- @import "base.scss";
31093 diff --git a/themes/tokyonight/templates/about.html b/themes/tokyonight/templates/about.html
31094deleted file mode 100644
31095index e319b72..0000000
31096--- a/themes/tokyonight/templates/about.html
31097+++ /dev/null
31098 @@ -1,9 +0,0 @@
31099- {% extends "base.html" %}
31100- {% block content %}
31101- <section>
31102- <article>
31103- <header></header>
31104- {{ blurb | safe }}
31105- </article>
31106- </section>
31107- {% endblock %}
31108 diff --git a/themes/tokyonight/theme.scss b/themes/tokyonight/theme.scss
31109deleted file mode 100644
31110index 97551d3..0000000
31111--- a/themes/tokyonight/theme.scss
31112+++ /dev/null
31113 @@ -1,97 +0,0 @@
31114- // https://github.com/folke/tokyonight.nvim/blob/main/lua/tokyonight/colors.lua
31115- $bg_dark: #1e2030;
31116- $bg: #222436;
31117- $bg_highlight: #2f334d;
31118- $terminal_black: #444a73;
31119- $fg: #c8d3f5;
31120- $fg_dark: #828bb8;
31121- $fg_gutter: #3b4261;
31122- $dark3: #545c7e;
31123- $comment: #7a88cf;
31124- $dark5: #737aa2;
31125- $blue0: #3e68d7;
31126- $blue: #82aaff;
31127- $cyan: #86e1fc;
31128- $blue1: #65bcff;
31129- $blue2: #0db9d7;
31130- $blue5: #89ddff;
31131- $blue6: #b4f9f8;
31132- $blue7: #394b70;
31133- $purple: #fca7ea;
31134- $magenta2: #ff007c;
31135- $magenta: #c099ff;
31136- $orange: #ff966c;
31137- $yellow: #ffc777;
31138- $green: #c3e88d;
31139- $green1: #4fd6be;
31140- $green2: #41a6b5;
31141- $teal: #4fd6be;
31142- $red: #ff757f;
31143- $red1: #c53b53;
31144-
31145- //
31146- //
31147- // tree sitter highlighting
31148- //
31149- //
31150-
31151- span.ts_attribute {color: $teal};
31152- span.ts_constant {color: $orange};
31153- span.ts_function.builtin {color: $blue};
31154- span.ts_function {color: $magenta}
31155- span.ts_keyword {color: $purple};
31156- span.ts_operator {color: $blue5};
31157- span.ts_property {color: $green1};
31158- span.ts_punctuation {color: $blue};
31159- span.ts_punctuation.bracket {color: $fg_dark};
31160- span.ts_punctuation.delimiter {color: $blue5};
31161- span.ts_string {color: $green};
31162- span.ts_string.special {color: $blue}
31163- span.ts_tag {color: $blue};
31164- span.ts_type {color: $blue1};
31165- span.ts_type.builtin {color: $blue1};
31166- span.ts_variable {color: $fg};
31167- span.ts_variable.builtin {color: $red};
31168- span.ts_variable.parameter {color: $yellow}
31169-
31170- @media (prefers-color-scheme: light) {
31171- span.ts_attribute {color: darken($teal, 40%)};
31172- span.ts_constant {color: darken($orange, 40%)};
31173- span.ts_function.builtin {color: darken($blue, 40%)};
31174- span.ts_function {color: darken($magenta, 20%)}
31175- span.ts_keyword {color: darken($purple, 40%)};
31176- span.ts_operator {color: darken($blue5, 40%)};
31177- span.ts_property {color: darken($green1, 40%)};
31178- span.ts_punctuation {color: darken($blue, 40%)};
31179- span.ts_punctuation.bracket {color: $fg};
31180- span.ts_punctuation.delimiter {color: darken($blue5, 40%)};
31181- span.ts_string {color: darken($green, 40%)};
31182- span.ts_string.special {color: darken($blue, 40%)}
31183- span.ts_tag {color: darken($blue, 40%)};
31184- span.ts_type {color: darken($blue1, 40%)};
31185- span.ts_type.builtin {color: darken($blue1, 40%)};
31186- span.ts_variable {color: darken($fg, 40%)};
31187- span.ts_variable.builtin {color: darken($red, 40%)};
31188- span.ts_variable.parameter {color: darken($yellow, 40%)}
31189- }
31190-
31191-
31192- @media (prefers-color-scheme: dark) {
31193- span.label {
31194- color: $fg_dark;
31195- }
31196- }
31197-
31198- $positive: $green;
31199- $negative: $red;
31200-
31201- $blame-border: $purple;
31202-
31203- $highlighted: $purple;
31204- $icon-background: $purple;
31205-
31206- $chart-color: $purple;
31207-
31208- @import "@picocss/pico/scss/pico";
31209- @import "../default/layout.scss";
31210- @import "../default/base.scss";
31211 diff --git a/vendor/README.md b/vendor/README.md
31212deleted file mode 100644
31213index 92a4f84..0000000
31214--- a/vendor/README.md
31215+++ /dev/null
31216 @@ -1,28 +0,0 @@
31217- # Language Detection & Syntax Highlighting
31218-
31219- This directory contains source files used for language detection
31220- in Ayllu. The `languages.json` file is taken from the Github Linguist project:
31221- https://github.com/github-linguist/linguist
31222-
31223- Copyright (c) 2017 GitHub, Inc.
31224-
31225- Permission is hereby granted, free of charge, to any person
31226- obtaining a copy of this software and associated documentation
31227- files (the "Software"), to deal in the Software without
31228- restriction, including without limitation the rights to use,
31229- copy, modify, merge, publish, distribute, sublicense, and/or sell
31230- copies of the Software, and to permit persons to whom the
31231- Software is furnished to do so, subject to the following
31232- conditions:
31233-
31234- The above copyright notice and this permission notice shall be
31235- included in all copies or substantial portions of the Software.
31236-
31237- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
31238- EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
31239- OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
31240- NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
31241- HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
31242- WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
31243- FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
31244- OTHER DEALINGS IN THE SOFTWARE.
31245 diff --git a/vendor/linguist.json b/vendor/linguist.json
31246deleted file mode 100644
31247index 46d83e5..0000000
31248--- a/vendor/linguist.json
31249+++ /dev/null
31250 @@ -1,10073 +0,0 @@
31251- {
31252- "1C Enterprise": {
31253- "type": "programming",
31254- "color": "#814CCC",
31255- "extensions": [
31256- ".bsl",
31257- ".os"
31258- ],
31259- "tm_scope": "source.bsl",
31260- "ace_mode": "text",
31261- "language_id": 0
31262- },
31263- "2-Dimensional Array": {
31264- "type": "data",
31265- "color": "#38761D",
31266- "extensions": [
31267- ".2da"
31268- ],
31269- "tm_scope": "source.2da",
31270- "ace_mode": "text",
31271- "language_id": 387204628
31272- },
31273- "4D": {
31274- "type": "programming",
31275- "color": "#004289",
31276- "extensions": [
31277- ".4dm"
31278- ],
31279- "tm_scope": "source.4dm",
31280- "ace_mode": "text",
31281- "language_id": 577529595
31282- },
31283- "ABAP": {
31284- "type": "programming",
31285- "color": "#E8274B",
31286- "extensions": [
31287- ".abap"
31288- ],
31289- "tm_scope": "source.abap",
31290- "ace_mode": "abap",
31291- "language_id": 1
31292- },
31293- "ABAP CDS": {
31294- "type": "programming",
31295- "color": "#555e25",
31296- "extensions": [
31297- ".asddls"
31298- ],
31299- "tm_scope": "source.abapcds",
31300- "language_id": 452681853,
31301- "ace_mode": "text"
31302- },
31303- "ABNF": {
31304- "type": "data",
31305- "ace_mode": "text",
31306- "extensions": [
31307- ".abnf"
31308- ],
31309- "tm_scope": "source.abnf",
31310- "language_id": 429
31311- },
31312- "AGS Script": {
31313- "type": "programming",
31314- "color": "#B9D9FF",
31315- "aliases": [
31316- "ags"
31317- ],
31318- "extensions": [
31319- ".asc",
31320- ".ash"
31321- ],
31322- "tm_scope": "source.c++",
31323- "ace_mode": "c_cpp",
31324- "codemirror_mode": "clike",
31325- "codemirror_mime_type": "text/x-c++src",
31326- "language_id": 2
31327- },
31328- "AIDL": {
31329- "type": "programming",
31330- "color": "#34EB6B",
31331- "tm_scope": "source.aidl",
31332- "extensions": [
31333- ".aidl"
31334- ],
31335- "ace_mode": "text",
31336- "interpreters": [
31337- "aidl"
31338- ],
31339- "language_id": 451700185
31340- },
31341- "AL": {
31342- "type": "programming",
31343- "color": "#3AA2B5",
31344- "extensions": [
31345- ".al"
31346- ],
31347- "tm_scope": "source.al",
31348- "ace_mode": "text",
31349- "language_id": 658971832
31350- },
31351- "AMPL": {
31352- "type": "programming",
31353- "color": "#E6EFBB",
31354- "extensions": [
31355- ".ampl",
31356- ".mod"
31357- ],
31358- "tm_scope": "source.ampl",
31359- "ace_mode": "text",
31360- "language_id": 3
31361- },
31362- "ANTLR": {
31363- "type": "programming",
31364- "color": "#9DC3FF",
31365- "extensions": [
31366- ".g4"
31367- ],
31368- "tm_scope": "source.antlr",
31369- "ace_mode": "text",
31370- "language_id": 4
31371- },
31372- "API Blueprint": {
31373- "type": "markup",
31374- "color": "#2ACCA8",
31375- "ace_mode": "markdown",
31376- "extensions": [
31377- ".apib"
31378- ],
31379- "tm_scope": "text.html.markdown.source.gfm.apib",
31380- "language_id": 5
31381- },
31382- "APL": {
31383- "type": "programming",
31384- "color": "#5A8164",
31385- "extensions": [
31386- ".apl",
31387- ".dyalog"
31388- ],
31389- "interpreters": [
31390- "apl",
31391- "aplx",
31392- "dyalog"
31393- ],
31394- "tm_scope": "source.apl",
31395- "ace_mode": "text",
31396- "codemirror_mode": "apl",
31397- "codemirror_mime_type": "text/apl",
31398- "language_id": 6
31399- },
31400- "ASL": {
31401- "type": "programming",
31402- "ace_mode": "text",
31403- "extensions": [
31404- ".asl",
31405- ".dsl"
31406- ],
31407- "tm_scope": "source.asl",
31408- "language_id": 124996147
31409- },
31410- "ASN.1": {
31411- "type": "data",
31412- "extensions": [
31413- ".asn",
31414- ".asn1"
31415- ],
31416- "tm_scope": "source.asn",
31417- "ace_mode": "text",
31418- "codemirror_mode": "asn.1",
31419- "codemirror_mime_type": "text/x-ttcn-asn",
31420- "language_id": 7
31421- },
31422- "ASP.NET": {
31423- "type": "programming",
31424- "tm_scope": "text.html.asp",
31425- "color": "#9400ff",
31426- "aliases": [
31427- "aspx",
31428- "aspx-vb"
31429- ],
31430- "extensions": [
31431- ".asax",
31432- ".ascx",
31433- ".ashx",
31434- ".asmx",
31435- ".aspx",
31436- ".axd"
31437- ],
31438- "ace_mode": "text",
31439- "codemirror_mode": "htmlembedded",
31440- "codemirror_mime_type": "application/x-aspx",
31441- "language_id": 564186416
31442- },
31443- "ATS": {
31444- "type": "programming",
31445- "color": "#1ac620",
31446- "aliases": [
31447- "ats2"
31448- ],
31449- "extensions": [
31450- ".dats",
31451- ".hats",
31452- ".sats"
31453- ],
31454- "tm_scope": "source.ats",
31455- "ace_mode": "ocaml",
31456- "language_id": 9
31457- },
31458- "ActionScript": {
31459- "type": "programming",
31460- "tm_scope": "source.actionscript.3",
31461- "color": "#882B0F",
31462- "aliases": [
31463- "actionscript 3",
31464- "actionscript3",
31465- "as3"
31466- ],
31467- "extensions": [
31468- ".as"
31469- ],
31470- "ace_mode": "actionscript",
31471- "language_id": 10
31472- },
31473- "Ada": {
31474- "type": "programming",
31475- "color": "#02f88c",
31476- "extensions": [
31477- ".adb",
31478- ".ada",
31479- ".ads"
31480- ],
31481- "aliases": [
31482- "ada95",
31483- "ada2005"
31484- ],
31485- "tm_scope": "source.ada",
31486- "ace_mode": "ada",
31487- "language_id": 11
31488- },
31489- "Adblock Filter List": {
31490- "type": "data",
31491- "color": "#800000",
31492- "ace_mode": "text",
31493- "extensions": [
31494- ".txt"
31495- ],
31496- "aliases": [
31497- "ad block filters",
31498- "ad block",
31499- "adb",
31500- "adblock"
31501- ],
31502- "tm_scope": "text.adblock",
31503- "language_id": 884614762
31504- },
31505- "Adobe Font Metrics": {
31506- "type": "data",
31507- "color": "#fa0f00",
31508- "tm_scope": "source.afm",
31509- "extensions": [
31510- ".afm"
31511- ],
31512- "aliases": [
31513- "acfm",
31514- "adobe composite font metrics",
31515- "adobe multiple font metrics",
31516- "amfm"
31517- ],
31518- "ace_mode": "text",
31519- "language_id": 147198098
31520- },
31521- "Agda": {
31522- "type": "programming",
31523- "color": "#315665",
31524- "extensions": [
31525- ".agda"
31526- ],
31527- "tm_scope": "source.agda",
31528- "ace_mode": "text",
31529- "language_id": 12
31530- },
31531- "Alloy": {
31532- "type": "programming",
31533- "color": "#64C800",
31534- "extensions": [
31535- ".als"
31536- ],
31537- "tm_scope": "source.alloy",
31538- "ace_mode": "text",
31539- "language_id": 13
31540- },
31541- "Alpine Abuild": {
31542- "type": "programming",
31543- "color": "#0D597F",
31544- "group": "Shell",
31545- "aliases": [
31546- "abuild",
31547- "apkbuild"
31548- ],
31549- "filenames": [
31550- "APKBUILD"
31551- ],
31552- "tm_scope": "source.shell",
31553- "ace_mode": "sh",
31554- "codemirror_mode": "shell",
31555- "codemirror_mime_type": "text/x-sh",
31556- "language_id": 14
31557- },
31558- "Altium Designer": {
31559- "type": "data",
31560- "color": "#A89663",
31561- "aliases": [
31562- "altium"
31563- ],
31564- "extensions": [
31565- ".OutJob",
31566- ".PcbDoc",
31567- ".PrjPCB",
31568- ".SchDoc"
31569- ],
31570- "tm_scope": "source.ini",
31571- "ace_mode": "ini",
31572- "language_id": 187772328
31573- },
31574- "AngelScript": {
31575- "type": "programming",
31576- "color": "#C7D7DC",
31577- "extensions": [
31578- ".as",
31579- ".angelscript"
31580- ],
31581- "tm_scope": "source.angelscript",
31582- "ace_mode": "text",
31583- "codemirror_mode": "clike",
31584- "codemirror_mime_type": "text/x-c++src",
31585- "language_id": 389477596
31586- },
31587- "Ant Build System": {
31588- "type": "data",
31589- "color": "#A9157E",
31590- "tm_scope": "text.xml.ant",
31591- "filenames": [
31592- "ant.xml",
31593- "build.xml"
31594- ],
31595- "ace_mode": "xml",
31596- "codemirror_mode": "xml",
31597- "codemirror_mime_type": "application/xml",
31598- "language_id": 15
31599- },
31600- "Antlers": {
31601- "type": "markup",
31602- "color": "#ff269e",
31603- "extensions": [
31604- ".antlers.html",
31605- ".antlers.php",
31606- ".antlers.xml"
31607- ],
31608- "tm_scope": "text.html.statamic",
31609- "ace_mode": "text",
31610- "language_id": 1067292663
31611- },
31612- "ApacheConf": {
31613- "type": "data",
31614- "color": "#d12127",
31615- "aliases": [
31616- "aconf",
31617- "apache"
31618- ],
31619- "extensions": [
31620- ".apacheconf",
31621- ".vhost"
31622- ],
31623- "filenames": [
31624- ".htaccess",
31625- "apache2.conf",
31626- "httpd.conf"
31627- ],
31628- "tm_scope": "source.apache-config",
31629- "ace_mode": "apache_conf",
31630- "language_id": 16
31631- },
31632- "Apex": {
31633- "type": "programming",
31634- "color": "#1797c0",
31635- "extensions": [
31636- ".cls",
31637- ".trigger"
31638- ],
31639- "tm_scope": "source.apex",
31640- "ace_mode": "java",
31641- "codemirror_mode": "clike",
31642- "codemirror_mime_type": "text/x-java",
31643- "language_id": 17
31644- },
31645- "Apollo Guidance Computer": {
31646- "type": "programming",
31647- "color": "#0B3D91",
31648- "group": "Assembly",
31649- "extensions": [
31650- ".agc"
31651- ],
31652- "tm_scope": "source.agc",
31653- "ace_mode": "assembly_x86",
31654- "language_id": 18
31655- },
31656- "AppleScript": {
31657- "type": "programming",
31658- "aliases": [
31659- "osascript"
31660- ],
31661- "extensions": [
31662- ".applescript",
31663- ".scpt"
31664- ],
31665- "interpreters": [
31666- "osascript"
31667- ],
31668- "tm_scope": "source.applescript",
31669- "ace_mode": "applescript",
31670- "color": "#101F1F",
31671- "language_id": 19
31672- },
31673- "Arc": {
31674- "type": "programming",
31675- "color": "#aa2afe",
31676- "extensions": [
31677- ".arc"
31678- ],
31679- "tm_scope": "none",
31680- "ace_mode": "text",
31681- "language_id": 20
31682- },
31683- "AsciiDoc": {
31684- "type": "prose",
31685- "color": "#73a0c5",
31686- "ace_mode": "asciidoc",
31687- "wrap": true,
31688- "extensions": [
31689- ".asciidoc",
31690- ".adoc",
31691- ".asc"
31692- ],
31693- "tm_scope": "text.html.asciidoc",
31694- "language_id": 22
31695- },
31696- "AspectJ": {
31697- "type": "programming",
31698- "color": "#a957b0",
31699- "extensions": [
31700- ".aj"
31701- ],
31702- "tm_scope": "source.aspectj",
31703- "ace_mode": "text",
31704- "language_id": 23
31705- },
31706- "Assembly": {
31707- "type": "programming",
31708- "color": "#6E4C13",
31709- "aliases": [
31710- "asm",
31711- "nasm"
31712- ],
31713- "extensions": [
31714- ".asm",
31715- ".a51",
31716- ".i",
31717- ".inc",
31718- ".nas",
31719- ".nasm"
31720- ],
31721- "tm_scope": "source.assembly",
31722- "ace_mode": "assembly_x86",
31723- "language_id": 24
31724- },
31725- "Astro": {
31726- "type": "markup",
31727- "color": "#ff5a03",
31728- "extensions": [
31729- ".astro"
31730- ],
31731- "tm_scope": "source.astro",
31732- "ace_mode": "html",
31733- "codemirror_mode": "jsx",
31734- "codemirror_mime_type": "text/jsx",
31735- "language_id": 578209015
31736- },
31737- "Asymptote": {
31738- "type": "programming",
31739- "color": "#ff0000",
31740- "extensions": [
31741- ".asy"
31742- ],
31743- "interpreters": [
31744- "asy"
31745- ],
31746- "tm_scope": "source.c++",
31747- "ace_mode": "c_cpp",
31748- "codemirror_mode": "clike",
31749- "codemirror_mime_type": "text/x-kotlin",
31750- "language_id": 591605007
31751- },
31752- "Augeas": {
31753- "type": "programming",
31754- "color": "#9CC134",
31755- "extensions": [
31756- ".aug"
31757- ],
31758- "tm_scope": "none",
31759- "ace_mode": "text",
31760- "language_id": 25
31761- },
31762- "AutoHotkey": {
31763- "type": "programming",
31764- "color": "#6594b9",
31765- "aliases": [
31766- "ahk"
31767- ],
31768- "extensions": [
31769- ".ahk",
31770- ".ahkl"
31771- ],
31772- "tm_scope": "source.ahk",
31773- "ace_mode": "autohotkey",
31774- "language_id": 26
31775- },
31776- "AutoIt": {
31777- "type": "programming",
31778- "color": "#1C3552",
31779- "aliases": [
31780- "au3",
31781- "AutoIt3",
31782- "AutoItScript"
31783- ],
31784- "extensions": [
31785- ".au3"
31786- ],
31787- "tm_scope": "source.autoit",
31788- "ace_mode": "autohotkey",
31789- "language_id": 27
31790- },
31791- "Avro IDL": {
31792- "type": "data",
31793- "color": "#0040FF",
31794- "extensions": [
31795- ".avdl"
31796- ],
31797- "tm_scope": "source.avro",
31798- "ace_mode": "text",
31799- "language_id": 785497837
31800- },
31801- "Awk": {
31802- "type": "programming",
31803- "color": "#c30e9b",
31804- "extensions": [
31805- ".awk",
31806- ".auk",
31807- ".gawk",
31808- ".mawk",
31809- ".nawk"
31810- ],
31811- "interpreters": [
31812- "awk",
31813- "gawk",
31814- "mawk",
31815- "nawk"
31816- ],
31817- "tm_scope": "source.awk",
31818- "ace_mode": "text",
31819- "language_id": 28
31820- },
31821- "BASIC": {
31822- "type": "programming",
31823- "extensions": [
31824- ".bas"
31825- ],
31826- "tm_scope": "source.basic",
31827- "ace_mode": "text",
31828- "color": "#ff0000",
31829- "language_id": 28923963
31830- },
31831- "Ballerina": {
31832- "type": "programming",
31833- "extensions": [
31834- ".bal"
31835- ],
31836- "tm_scope": "source.ballerina",
31837- "ace_mode": "text",
31838- "color": "#FF5000",
31839- "language_id": 720859680
31840- },
31841- "Batchfile": {
31842- "type": "programming",
31843- "aliases": [
31844- "bat",
31845- "batch",
31846- "dosbatch",
31847- "winbatch"
31848- ],
31849- "extensions": [
31850- ".bat",
31851- ".cmd"
31852- ],
31853- "tm_scope": "source.batchfile",
31854- "ace_mode": "batchfile",
31855- "color": "#C1F12E",
31856- "language_id": 29
31857- },
31858- "Beef": {
31859- "type": "programming",
31860- "color": "#a52f4e",
31861- "extensions": [
31862- ".bf"
31863- ],
31864- "tm_scope": "source.cs",
31865- "ace_mode": "csharp",
31866- "codemirror_mode": "clike",
31867- "codemirror_mime_type": "text/x-csharp",
31868- "language_id": 545626333
31869- },
31870- "Befunge": {
31871- "type": "programming",
31872- "extensions": [
31873- ".befunge",
31874- ".bf"
31875- ],
31876- "tm_scope": "source.befunge",
31877- "ace_mode": "text",
31878- "language_id": 30
31879- },
31880- "Berry": {
31881- "type": "programming",
31882- "extensions": [
31883- ".be"
31884- ],
31885- "tm_scope": "source.berry",
31886- "ace_mode": "text",
31887- "color": "#15A13C",
31888- "aliases": [
31889- "be"
31890- ],
31891- "language_id": 121855308
31892- },
31893- "BibTeX": {
31894- "type": "markup",
31895- "color": "#778899",
31896- "group": "TeX",
31897- "extensions": [
31898- ".bib",
31899- ".bibtex"
31900- ],
31901- "tm_scope": "text.bibtex",
31902- "ace_mode": "tex",
31903- "codemirror_mode": "stex",
31904- "codemirror_mime_type": "text/x-stex",
31905- "language_id": 982188347
31906- },
31907- "Bicep": {
31908- "type": "programming",
31909- "color": "#519aba",
31910- "extensions": [
31911- ".bicep"
31912- ],
31913- "tm_scope": "source.bicep",
31914- "ace_mode": "text",
31915- "language_id": 321200902
31916- },
31917- "Bikeshed": {
31918- "type": "markup",
31919- "color": "#5562ac",
31920- "extensions": [
31921- ".bs"
31922- ],
31923- "tm_scope": "source.csswg",
31924- "ace_mode": "html",
31925- "codemirror_mode": "htmlmixed",
31926- "codemirror_mime_type": "text/html",
31927- "language_id": 1055528081
31928- },
31929- "Bison": {
31930- "type": "programming",
31931- "color": "#6A463F",
31932- "group": "Yacc",
31933- "tm_scope": "source.yacc",
31934- "extensions": [
31935- ".bison"
31936- ],
31937- "ace_mode": "text",
31938- "language_id": 31
31939- },
31940- "BitBake": {
31941- "type": "programming",
31942- "color": "#00bce4",
31943- "tm_scope": "none",
31944- "extensions": [
31945- ".bb"
31946- ],
31947- "ace_mode": "text",
31948- "language_id": 32
31949- },
31950- "Blade": {
31951- "type": "markup",
31952- "color": "#f7523f",
31953- "extensions": [
31954- ".blade",
31955- ".blade.php"
31956- ],
31957- "tm_scope": "text.html.php.blade",
31958- "ace_mode": "text",
31959- "language_id": 33
31960- },
31961- "BlitzBasic": {
31962- "type": "programming",
31963- "color": "#00FFAE",
31964- "aliases": [
31965- "b3d",
31966- "blitz3d",
31967- "blitzplus",
31968- "bplus"
31969- ],
31970- "extensions": [
31971- ".bb",
31972- ".decls"
31973- ],
31974- "tm_scope": "source.blitzmax",
31975- "ace_mode": "text",
31976- "language_id": 34
31977- },
31978- "BlitzMax": {
31979- "type": "programming",
31980- "color": "#cd6400",
31981- "extensions": [
31982- ".bmx"
31983- ],
31984- "aliases": [
31985- "bmax"
31986- ],
31987- "tm_scope": "source.blitzmax",
31988- "ace_mode": "text",
31989- "language_id": 35
31990- },
31991- "Bluespec": {
31992- "type": "programming",
31993- "color": "#12223c",
31994- "extensions": [
31995- ".bsv"
31996- ],
31997- "aliases": [
31998- "bluespec bsv",
31999- "bsv"
32000- ],
32001- "tm_scope": "source.bsv",
32002- "ace_mode": "verilog",
32003- "codemirror_mode": "verilog",
32004- "codemirror_mime_type": "text/x-systemverilog",
32005- "language_id": 36
32006- },
32007- "Bluespec BH": {
32008- "type": "programming",
32009- "group": "Bluespec",
32010- "color": "#12223c",
32011- "extensions": [
32012- ".bs"
32013- ],
32014- "aliases": [
32015- "bh",
32016- "bluespec classic"
32017- ],
32018- "tm_scope": "source.haskell",
32019- "ace_mode": "haskell",
32020- "codemirror_mode": "haskell",
32021- "codemirror_mime_type": "text/x-haskell",
32022- "language_id": 641580358
32023- },
32024- "Boo": {
32025- "type": "programming",
32026- "color": "#d4bec1",
32027- "extensions": [
32028- ".boo"
32029- ],
32030- "ace_mode": "text",
32031- "tm_scope": "source.boo",
32032- "language_id": 37
32033- },
32034- "Boogie": {
32035- "type": "programming",
32036- "color": "#c80fa0",
32037- "extensions": [
32038- ".bpl"
32039- ],
32040- "interpreters": [
32041- "boogie"
32042- ],
32043- "tm_scope": "source.boogie",
32044- "ace_mode": "text",
32045- "language_id": 955017407
32046- },
32047- "Brainfuck": {
32048- "type": "programming",
32049- "color": "#2F2530",
32050- "extensions": [
32051- ".b",
32052- ".bf"
32053- ],
32054- "tm_scope": "source.bf",
32055- "ace_mode": "text",
32056- "codemirror_mode": "brainfuck",
32057- "codemirror_mime_type": "text/x-brainfuck",
32058- "language_id": 38
32059- },
32060- "BrighterScript": {
32061- "type": "programming",
32062- "color": "#66AABB",
32063- "extensions": [
32064- ".bs"
32065- ],
32066- "tm_scope": "source.brs",
32067- "ace_mode": "text",
32068- "language_id": 943571030
32069- },
32070- "Brightscript": {
32071- "type": "programming",
32072- "color": "#662D91",
32073- "extensions": [
32074- ".brs"
32075- ],
32076- "tm_scope": "source.brs",
32077- "ace_mode": "text",
32078- "language_id": 39
32079- },
32080- "Browserslist": {
32081- "type": "data",
32082- "color": "#ffd539",
32083- "filenames": [
32084- ".browserslistrc",
32085- "browserslist"
32086- ],
32087- "tm_scope": "text.browserslist",
32088- "ace_mode": "text",
32089- "language_id": 153503348
32090- },
32091- "C": {
32092- "type": "programming",
32093- "color": "#555555",
32094- "extensions": [
32095- ".c",
32096- ".cats",
32097- ".h",
32098- ".idc"
32099- ],
32100- "interpreters": [
32101- "tcc"
32102- ],
32103- "tm_scope": "source.c",
32104- "ace_mode": "c_cpp",
32105- "codemirror_mode": "clike",
32106- "codemirror_mime_type": "text/x-csrc",
32107- "language_id": 41
32108- },
32109- "C#": {
32110- "type": "programming",
32111- "ace_mode": "csharp",
32112- "codemirror_mode": "clike",
32113- "codemirror_mime_type": "text/x-csharp",
32114- "tm_scope": "source.cs",
32115- "color": "#178600",
32116- "aliases": [
32117- "csharp",
32118- "cake",
32119- "cakescript"
32120- ],
32121- "extensions": [
32122- ".cs",
32123- ".cake",
32124- ".csx",
32125- ".linq"
32126- ],
32127- "language_id": 42
32128- },
32129- "C++": {
32130- "type": "programming",
32131- "tm_scope": "source.c++",
32132- "ace_mode": "c_cpp",
32133- "codemirror_mode": "clike",
32134- "codemirror_mime_type": "text/x-c++src",
32135- "color": "#f34b7d",
32136- "aliases": [
32137- "cpp"
32138- ],
32139- "extensions": [
32140- ".cpp",
32141- ".c++",
32142- ".cc",
32143- ".cp",
32144- ".cppm",
32145- ".cxx",
32146- ".h",
32147- ".h++",
32148- ".hh",
32149- ".hpp",
32150- ".hxx",
32151- ".inc",
32152- ".inl",
32153- ".ino",
32154- ".ipp",
32155- ".ixx",
32156- ".re",
32157- ".tcc",
32158- ".tpp",
32159- ".txx"
32160- ],
32161- "language_id": 43
32162- },
32163- "C-ObjDump": {
32164- "type": "data",
32165- "extensions": [
32166- ".c-objdump"
32167- ],
32168- "tm_scope": "objdump.x86asm",
32169- "ace_mode": "assembly_x86",
32170- "language_id": 44
32171- },
32172- "C2hs Haskell": {
32173- "type": "programming",
32174- "group": "Haskell",
32175- "aliases": [
32176- "c2hs"
32177- ],
32178- "extensions": [
32179- ".chs"
32180- ],
32181- "tm_scope": "source.haskell",
32182- "ace_mode": "haskell",
32183- "codemirror_mode": "haskell",
32184- "codemirror_mime_type": "text/x-haskell",
32185- "language_id": 45
32186- },
32187- "CAP CDS": {
32188- "type": "programming",
32189- "tm_scope": "source.cds",
32190- "color": "#0092d1",
32191- "aliases": [
32192- "cds"
32193- ],
32194- "extensions": [
32195- ".cds"
32196- ],
32197- "ace_mode": "text",
32198- "language_id": 390788699
32199- },
32200- "CIL": {
32201- "type": "data",
32202- "tm_scope": "source.cil",
32203- "extensions": [
32204- ".cil"
32205- ],
32206- "ace_mode": "text",
32207- "language_id": 29176339
32208- },
32209- "CLIPS": {
32210- "type": "programming",
32211- "color": "#00A300",
32212- "extensions": [
32213- ".clp"
32214- ],
32215- "tm_scope": "source.clips",
32216- "ace_mode": "text",
32217- "language_id": 46
32218- },
32219- "CMake": {
32220- "type": "programming",
32221- "color": "#DA3434",
32222- "extensions": [
32223- ".cmake",
32224- ".cmake.in"
32225- ],
32226- "filenames": [
32227- "CMakeLists.txt"
32228- ],
32229- "tm_scope": "source.cmake",
32230- "ace_mode": "text",
32231- "codemirror_mode": "cmake",
32232- "codemirror_mime_type": "text/x-cmake",
32233- "language_id": 47
32234- },
32235- "COBOL": {
32236- "type": "programming",
32237- "extensions": [
32238- ".cob",
32239- ".cbl",
32240- ".ccp",
32241- ".cobol",
32242- ".cpy"
32243- ],
32244- "tm_scope": "source.cobol",
32245- "ace_mode": "cobol",
32246- "codemirror_mode": "cobol",
32247- "codemirror_mime_type": "text/x-cobol",
32248- "language_id": 48
32249- },
32250- "CODEOWNERS": {
32251- "type": "data",
32252- "filenames": [
32253- "CODEOWNERS"
32254- ],
32255- "tm_scope": "text.codeowners",
32256- "ace_mode": "gitignore",
32257- "language_id": 321684729
32258- },
32259- "COLLADA": {
32260- "type": "data",
32261- "color": "#F1A42B",
32262- "extensions": [
32263- ".dae"
32264- ],
32265- "tm_scope": "text.xml",
32266- "ace_mode": "xml",
32267- "codemirror_mode": "xml",
32268- "codemirror_mime_type": "text/xml",
32269- "language_id": 49
32270- },
32271- "CSON": {
32272- "type": "data",
32273- "color": "#244776",
32274- "tm_scope": "source.coffee",
32275- "ace_mode": "coffee",
32276- "codemirror_mode": "coffeescript",
32277- "codemirror_mime_type": "text/x-coffeescript",
32278- "extensions": [
32279- ".cson"
32280- ],
32281- "language_id": 424
32282- },
32283- "CSS": {
32284- "type": "markup",
32285- "tm_scope": "source.css",
32286- "ace_mode": "css",
32287- "codemirror_mode": "css",
32288- "codemirror_mime_type": "text/css",
32289- "color": "#563d7c",
32290- "extensions": [
32291- ".css"
32292- ],
32293- "language_id": 50
32294- },
32295- "CSV": {
32296- "type": "data",
32297- "color": "#237346",
32298- "ace_mode": "text",
32299- "tm_scope": "none",
32300- "extensions": [
32301- ".csv"
32302- ],
32303- "language_id": 51
32304- },
32305- "CUE": {
32306- "type": "programming",
32307- "extensions": [
32308- ".cue"
32309- ],
32310- "tm_scope": "source.cue",
32311- "ace_mode": "text",
32312- "color": "#5886E1",
32313- "language_id": 356063509
32314- },
32315- "CWeb": {
32316- "type": "programming",
32317- "color": "#00007a",
32318- "extensions": [
32319- ".w"
32320- ],
32321- "tm_scope": "none",
32322- "ace_mode": "text",
32323- "language_id": 657332628
32324- },
32325- "Cabal Config": {
32326- "type": "data",
32327- "color": "#483465",
32328- "aliases": [
32329- "Cabal"
32330- ],
32331- "extensions": [
32332- ".cabal"
32333- ],
32334- "filenames": [
32335- "cabal.config",
32336- "cabal.project"
32337- ],
32338- "ace_mode": "haskell",
32339- "codemirror_mode": "haskell",
32340- "codemirror_mime_type": "text/x-haskell",
32341- "tm_scope": "source.cabal",
32342- "language_id": 677095381
32343- },
32344- "Cadence": {
32345- "type": "programming",
32346- "color": "#00ef8b",
32347- "ace_mode": "text",
32348- "tm_scope": "source.cadence",
32349- "extensions": [
32350- ".cdc"
32351- ],
32352- "language_id": 270184138
32353- },
32354- "Cairo": {
32355- "type": "programming",
32356- "color": "#ff4a48",
32357- "ace_mode": "text",
32358- "tm_scope": "source.cairo",
32359- "extensions": [
32360- ".cairo"
32361- ],
32362- "language_id": 620599567
32363- },
32364- "CameLIGO": {
32365- "type": "programming",
32366- "color": "#3be133",
32367- "extensions": [
32368- ".mligo"
32369- ],
32370- "tm_scope": "source.mligo",
32371- "ace_mode": "ocaml",
32372- "codemirror_mode": "mllike",
32373- "codemirror_mime_type": "text/x-ocaml",
32374- "group": "LigoLANG",
32375- "language_id": 829207807
32376- },
32377- "Cap'n Proto": {
32378- "type": "programming",
32379- "color": "#c42727",
32380- "tm_scope": "source.capnp",
32381- "extensions": [
32382- ".capnp"
32383- ],
32384- "ace_mode": "text",
32385- "language_id": 52
32386- },
32387- "CartoCSS": {
32388- "type": "programming",
32389- "aliases": [
32390- "Carto"
32391- ],
32392- "extensions": [
32393- ".mss"
32394- ],
32395- "ace_mode": "text",
32396- "tm_scope": "source.css.mss",
32397- "language_id": 53
32398- },
32399- "Ceylon": {
32400- "type": "programming",
32401- "color": "#dfa535",
32402- "extensions": [
32403- ".ceylon"
32404- ],
32405- "tm_scope": "source.ceylon",
32406- "ace_mode": "text",
32407- "language_id": 54
32408- },
32409- "Chapel": {
32410- "type": "programming",
32411- "color": "#8dc63f",
32412- "aliases": [
32413- "chpl"
32414- ],
32415- "extensions": [
32416- ".chpl"
32417- ],
32418- "tm_scope": "source.chapel",
32419- "ace_mode": "text",
32420- "language_id": 55
32421- },
32422- "Charity": {
32423- "type": "programming",
32424- "extensions": [
32425- ".ch"
32426- ],
32427- "tm_scope": "none",
32428- "ace_mode": "text",
32429- "language_id": 56
32430- },
32431- "Checksums": {
32432- "type": "data",
32433- "tm_scope": "text.checksums",
32434- "aliases": [
32435- "checksum",
32436- "hash",
32437- "hashes",
32438- "sum",
32439- "sums"
32440- ],
32441- "filenames": [
32442- "MD5SUMS",
32443- "SHA1SUMS",
32444- "SHA256SUMS",
32445- "SHA256SUMS.txt",
32446- "SHA512SUMS",
32447- "checksums.txt",
32448- "cksums",
32449- "md5sum.txt"
32450- ],
32451- "extensions": [
32452- ".crc32",
32453- ".md2",
32454- ".md4",
32455- ".md5",
32456- ".sha1",
32457- ".sha2",
32458- ".sha224",
32459- ".sha256",
32460- ".sha256sum",
32461- ".sha3",
32462- ".sha384",
32463- ".sha512"
32464- ],
32465- "ace_mode": "text",
32466- "language_id": 372063053
32467- },
32468- "ChucK": {
32469- "type": "programming",
32470- "color": "#3f8000",
32471- "extensions": [
32472- ".ck"
32473- ],
32474- "tm_scope": "source.java",
32475- "ace_mode": "java",
32476- "codemirror_mode": "clike",
32477- "codemirror_mime_type": "text/x-java",
32478- "language_id": 57
32479- },
32480- "Circom": {
32481- "type": "programming",
32482- "ace_mode": "text",
32483- "extensions": [
32484- ".circom"
32485- ],
32486- "color": "#707575",
32487- "tm_scope": "source.circom",
32488- "language_id": 1042332086
32489- },
32490- "Cirru": {
32491- "type": "programming",
32492- "color": "#ccccff",
32493- "tm_scope": "source.cirru",
32494- "ace_mode": "cirru",
32495- "extensions": [
32496- ".cirru"
32497- ],
32498- "language_id": 58
32499- },
32500- "Clarion": {
32501- "type": "programming",
32502- "color": "#db901e",
32503- "ace_mode": "text",
32504- "extensions": [
32505- ".clw"
32506- ],
32507- "tm_scope": "source.clarion",
32508- "language_id": 59
32509- },
32510- "Clarity": {
32511- "type": "programming",
32512- "color": "#5546ff",
32513- "ace_mode": "lisp",
32514- "extensions": [
32515- ".clar"
32516- ],
32517- "tm_scope": "source.clar",
32518- "language_id": 91493841
32519- },
32520- "Classic ASP": {
32521- "type": "programming",
32522- "color": "#6a40fd",
32523- "tm_scope": "text.html.asp",
32524- "aliases": [
32525- "asp"
32526- ],
32527- "extensions": [
32528- ".asp"
32529- ],
32530- "ace_mode": "text",
32531- "language_id": 8
32532- },
32533- "Clean": {
32534- "type": "programming",
32535- "color": "#3F85AF",
32536- "extensions": [
32537- ".icl",
32538- ".dcl"
32539- ],
32540- "tm_scope": "source.clean",
32541- "ace_mode": "text",
32542- "language_id": 60
32543- },
32544- "Click": {
32545- "type": "programming",
32546- "color": "#E4E6F3",
32547- "extensions": [
32548- ".click"
32549- ],
32550- "tm_scope": "source.click",
32551- "ace_mode": "text",
32552- "language_id": 61
32553- },
32554- "Clojure": {
32555- "type": "programming",
32556- "tm_scope": "source.clojure",
32557- "ace_mode": "clojure",
32558- "codemirror_mode": "clojure",
32559- "codemirror_mime_type": "text/x-clojure",
32560- "color": "#db5855",
32561- "extensions": [
32562- ".clj",
32563- ".bb",
32564- ".boot",
32565- ".cl2",
32566- ".cljc",
32567- ".cljs",
32568- ".cljs.hl",
32569- ".cljscm",
32570- ".cljx",
32571- ".hic"
32572- ],
32573- "filenames": [
32574- "riemann.config"
32575- ],
32576- "interpreters": [
32577- "bb"
32578- ],
32579- "language_id": 62
32580- },
32581- "Closure Templates": {
32582- "type": "markup",
32583- "color": "#0d948f",
32584- "ace_mode": "soy_template",
32585- "codemirror_mode": "soy",
32586- "codemirror_mime_type": "text/x-soy",
32587- "aliases": [
32588- "soy"
32589- ],
32590- "extensions": [
32591- ".soy"
32592- ],
32593- "tm_scope": "text.html.soy",
32594- "language_id": 357046146
32595- },
32596- "Cloud Firestore Security Rules": {
32597- "type": "data",
32598- "color": "#FFA000",
32599- "ace_mode": "less",
32600- "codemirror_mode": "css",
32601- "codemirror_mime_type": "text/css",
32602- "tm_scope": "source.firestore",
32603- "filenames": [
32604- "firestore.rules"
32605- ],
32606- "language_id": 407996372
32607- },
32608- "CoNLL-U": {
32609- "type": "data",
32610- "extensions": [
32611- ".conllu",
32612- ".conll"
32613- ],
32614- "tm_scope": "text.conllu",
32615- "ace_mode": "text",
32616- "aliases": [
32617- "CoNLL",
32618- "CoNLL-X"
32619- ],
32620- "language_id": 421026389
32621- },
32622- "CodeQL": {
32623- "type": "programming",
32624- "color": "#140f46",
32625- "extensions": [
32626- ".ql",
32627- ".qll"
32628- ],
32629- "tm_scope": "source.ql",
32630- "ace_mode": "text",
32631- "language_id": 424259634,
32632- "aliases": [
32633- "ql"
32634- ]
32635- },
32636- "CoffeeScript": {
32637- "type": "programming",
32638- "tm_scope": "source.coffee",
32639- "ace_mode": "coffee",
32640- "codemirror_mode": "coffeescript",
32641- "codemirror_mime_type": "text/x-coffeescript",
32642- "color": "#244776",
32643- "aliases": [
32644- "coffee",
32645- "coffee-script"
32646- ],
32647- "extensions": [
32648- ".coffee",
32649- "._coffee",
32650- ".cake",
32651- ".cjsx",
32652- ".iced"
32653- ],
32654- "filenames": [
32655- "Cakefile"
32656- ],
32657- "interpreters": [
32658- "coffee"
32659- ],
32660- "language_id": 63
32661- },
32662- "ColdFusion": {
32663- "type": "programming",
32664- "ace_mode": "coldfusion",
32665- "color": "#ed2cd6",
32666- "aliases": [
32667- "cfm",
32668- "cfml",
32669- "coldfusion html"
32670- ],
32671- "extensions": [
32672- ".cfm",
32673- ".cfml"
32674- ],
32675- "tm_scope": "text.html.cfm",
32676- "language_id": 64
32677- },
32678- "ColdFusion CFC": {
32679- "type": "programming",
32680- "color": "#ed2cd6",
32681- "group": "ColdFusion",
32682- "ace_mode": "coldfusion",
32683- "aliases": [
32684- "cfc"
32685- ],
32686- "extensions": [
32687- ".cfc"
32688- ],
32689- "tm_scope": "source.cfscript",
32690- "language_id": 65
32691- },
32692- "Common Lisp": {
32693- "type": "programming",
32694- "tm_scope": "source.lisp",
32695- "color": "#3fb68b",
32696- "aliases": [
32697- "lisp"
32698- ],
32699- "extensions": [
32700- ".lisp",
32701- ".asd",
32702- ".cl",
32703- ".l",
32704- ".lsp",
32705- ".ny",
32706- ".podsl",
32707- ".sexp"
32708- ],
32709- "interpreters": [
32710- "lisp",
32711- "sbcl",
32712- "ccl",
32713- "clisp",
32714- "ecl"
32715- ],
32716- "ace_mode": "lisp",
32717- "codemirror_mode": "commonlisp",
32718- "codemirror_mime_type": "text/x-common-lisp",
32719- "language_id": 66
32720- },
32721- "Common Workflow Language": {
32722- "aliases": [
32723- "cwl"
32724- ],
32725- "type": "programming",
32726- "ace_mode": "yaml",
32727- "codemirror_mode": "yaml",
32728- "codemirror_mime_type": "text/x-yaml",
32729- "extensions": [
32730- ".cwl"
32731- ],
32732- "interpreters": [
32733- "cwl-runner"
32734- ],
32735- "color": "#B5314C",
32736- "tm_scope": "source.cwl",
32737- "language_id": 988547172
32738- },
32739- "Component Pascal": {
32740- "type": "programming",
32741- "color": "#B0CE4E",
32742- "extensions": [
32743- ".cp",
32744- ".cps"
32745- ],
32746- "tm_scope": "source.pascal",
32747- "ace_mode": "pascal",
32748- "codemirror_mode": "pascal",
32749- "codemirror_mime_type": "text/x-pascal",
32750- "language_id": 67
32751- },
32752- "Cool": {
32753- "type": "programming",
32754- "extensions": [
32755- ".cl"
32756- ],
32757- "tm_scope": "source.cool",
32758- "ace_mode": "text",
32759- "language_id": 68
32760- },
32761- "Coq": {
32762- "type": "programming",
32763- "color": "#d0b68c",
32764- "extensions": [
32765- ".coq",
32766- ".v"
32767- ],
32768- "tm_scope": "source.coq",
32769- "ace_mode": "text",
32770- "language_id": 69
32771- },
32772- "Cpp-ObjDump": {
32773- "type": "data",
32774- "extensions": [
32775- ".cppobjdump",
32776- ".c++-objdump",
32777- ".c++objdump",
32778- ".cpp-objdump",
32779- ".cxx-objdump"
32780- ],
32781- "tm_scope": "objdump.x86asm",
32782- "aliases": [
32783- "c++-objdump"
32784- ],
32785- "ace_mode": "assembly_x86",
32786- "language_id": 70
32787- },
32788- "Creole": {
32789- "type": "prose",
32790- "wrap": true,
32791- "extensions": [
32792- ".creole"
32793- ],
32794- "tm_scope": "text.html.creole",
32795- "ace_mode": "text",
32796- "language_id": 71
32797- },
32798- "Crystal": {
32799- "type": "programming",
32800- "color": "#000100",
32801- "extensions": [
32802- ".cr"
32803- ],
32804- "ace_mode": "ruby",
32805- "codemirror_mode": "crystal",
32806- "codemirror_mime_type": "text/x-crystal",
32807- "tm_scope": "source.crystal",
32808- "interpreters": [
32809- "crystal"
32810- ],
32811- "language_id": 72
32812- },
32813- "Csound": {
32814- "type": "programming",
32815- "color": "#1a1a1a",
32816- "aliases": [
32817- "csound-orc"
32818- ],
32819- "extensions": [
32820- ".orc",
32821- ".udo"
32822- ],
32823- "tm_scope": "source.csound",
32824- "ace_mode": "csound_orchestra",
32825- "language_id": 73
32826- },
32827- "Csound Document": {
32828- "type": "programming",
32829- "color": "#1a1a1a",
32830- "aliases": [
32831- "csound-csd"
32832- ],
32833- "extensions": [
32834- ".csd"
32835- ],
32836- "tm_scope": "source.csound-document",
32837- "ace_mode": "csound_document",
32838- "language_id": 74
32839- },
32840- "Csound Score": {
32841- "type": "programming",
32842- "color": "#1a1a1a",
32843- "aliases": [
32844- "csound-sco"
32845- ],
32846- "extensions": [
32847- ".sco"
32848- ],
32849- "tm_scope": "source.csound-score",
32850- "ace_mode": "csound_score",
32851- "language_id": 75
32852- },
32853- "Cuda": {
32854- "type": "programming",
32855- "extensions": [
32856- ".cu",
32857- ".cuh"
32858- ],
32859- "tm_scope": "source.cuda-c++",
32860- "ace_mode": "c_cpp",
32861- "codemirror_mode": "clike",
32862- "codemirror_mime_type": "text/x-c++src",
32863- "color": "#3A4E3A",
32864- "language_id": 77
32865- },
32866- "Cue Sheet": {
32867- "type": "data",
32868- "extensions": [
32869- ".cue"
32870- ],
32871- "tm_scope": "source.cuesheet",
32872- "ace_mode": "text",
32873- "language_id": 942714150
32874- },
32875- "Curry": {
32876- "type": "programming",
32877- "color": "#531242",
32878- "extensions": [
32879- ".curry"
32880- ],
32881- "tm_scope": "source.curry",
32882- "ace_mode": "haskell",
32883- "language_id": 439829048
32884- },
32885- "Cycript": {
32886- "type": "programming",
32887- "extensions": [
32888- ".cy"
32889- ],
32890- "tm_scope": "source.js",
32891- "ace_mode": "javascript",
32892- "codemirror_mode": "javascript",
32893- "codemirror_mime_type": "text/javascript",
32894- "language_id": 78
32895- },
32896- "Cypher": {
32897- "type": "programming",
32898- "color": "#34c0eb",
32899- "extensions": [
32900- ".cyp",
32901- ".cypher"
32902- ],
32903- "tm_scope": "source.cypher",
32904- "ace_mode": "text",
32905- "language_id": 850806976
32906- },
32907- "Cython": {
32908- "type": "programming",
32909- "color": "#fedf5b",
32910- "extensions": [
32911- ".pyx",
32912- ".pxd",
32913- ".pxi"
32914- ],
32915- "aliases": [
32916- "pyrex"
32917- ],
32918- "tm_scope": "source.cython",
32919- "ace_mode": "text",
32920- "codemirror_mode": "python",
32921- "codemirror_mime_type": "text/x-cython",
32922- "language_id": 79
32923- },
32924- "D": {
32925- "type": "programming",
32926- "color": "#ba595e",
32927- "aliases": [
32928- "Dlang"
32929- ],
32930- "extensions": [
32931- ".d",
32932- ".di"
32933- ],
32934- "tm_scope": "source.d",
32935- "ace_mode": "d",
32936- "codemirror_mode": "d",
32937- "codemirror_mime_type": "text/x-d",
32938- "language_id": 80
32939- },
32940- "D-ObjDump": {
32941- "type": "data",
32942- "extensions": [
32943- ".d-objdump"
32944- ],
32945- "tm_scope": "objdump.x86asm",
32946- "ace_mode": "assembly_x86",
32947- "language_id": 81
32948- },
32949- "D2": {
32950- "type": "markup",
32951- "color": "#526ee8",
32952- "extensions": [
32953- ".d2"
32954- ],
32955- "aliases": [
32956- "d2lang"
32957- ],
32958- "tm_scope": "source.d2",
32959- "ace_mode": "text",
32960- "language_id": 37531557
32961- },
32962- "DIGITAL Command Language": {
32963- "type": "programming",
32964- "aliases": [
32965- "dcl"
32966- ],
32967- "extensions": [
32968- ".com"
32969- ],
32970- "tm_scope": "none",
32971- "ace_mode": "text",
32972- "language_id": 82
32973- },
32974- "DM": {
32975- "type": "programming",
32976- "color": "#447265",
32977- "extensions": [
32978- ".dm"
32979- ],
32980- "aliases": [
32981- "byond"
32982- ],
32983- "tm_scope": "source.dm",
32984- "ace_mode": "c_cpp",
32985- "language_id": 83
32986- },
32987- "DNS Zone": {
32988- "type": "data",
32989- "extensions": [
32990- ".zone",
32991- ".arpa"
32992- ],
32993- "tm_scope": "text.zone_file",
32994- "ace_mode": "text",
32995- "language_id": 84
32996- },
32997- "DTrace": {
32998- "type": "programming",
32999- "aliases": [
33000- "dtrace-script"
33001- ],
33002- "extensions": [
33003- ".d"
33004- ],
33005- "interpreters": [
33006- "dtrace"
33007- ],
33008- "tm_scope": "source.c",
33009- "ace_mode": "c_cpp",
33010- "codemirror_mode": "clike",
33011- "codemirror_mime_type": "text/x-csrc",
33012- "language_id": 85
33013- },
33014- "Dafny": {
33015- "type": "programming",
33016- "color": "#FFEC25",
33017- "extensions": [
33018- ".dfy"
33019- ],
33020- "interpreters": [
33021- "dafny"
33022- ],
33023- "tm_scope": "text.dfy.dafny",
33024- "ace_mode": "text",
33025- "language_id": 969323346
33026- },
33027- "Darcs Patch": {
33028- "type": "data",
33029- "color": "#8eff23",
33030- "aliases": [
33031- "dpatch"
33032- ],
33033- "extensions": [
33034- ".darcspatch",
33035- ".dpatch"
33036- ],
33037- "tm_scope": "none",
33038- "ace_mode": "text",
33039- "language_id": 86
33040- },
33041- "Dart": {
33042- "type": "programming",
33043- "color": "#00B4AB",
33044- "extensions": [
33045- ".dart"
33046- ],
33047- "interpreters": [
33048- "dart"
33049- ],
33050- "tm_scope": "source.dart",
33051- "ace_mode": "dart",
33052- "codemirror_mode": "dart",
33053- "codemirror_mime_type": "application/dart",
33054- "language_id": 87
33055- },
33056- "DataWeave": {
33057- "type": "programming",
33058- "color": "#003a52",
33059- "extensions": [
33060- ".dwl"
33061- ],
33062- "ace_mode": "text",
33063- "tm_scope": "source.data-weave",
33064- "language_id": 974514097
33065- },
33066- "Debian Package Control File": {
33067- "type": "data",
33068- "color": "#D70751",
33069- "extensions": [
33070- ".dsc"
33071- ],
33072- "tm_scope": "source.deb-control",
33073- "ace_mode": "text",
33074- "language_id": 527438264
33075- },
33076- "DenizenScript": {
33077- "type": "programming",
33078- "color": "#FBEE96",
33079- "ace_mode": "yaml",
33080- "codemirror_mode": "yaml",
33081- "codemirror_mime_type": "text/x-yaml",
33082- "extensions": [
33083- ".dsc"
33084- ],
33085- "tm_scope": "source.denizenscript",
33086- "language_id": 435000929
33087- },
33088- "Dhall": {
33089- "type": "programming",
33090- "color": "#dfafff",
33091- "extensions": [
33092- ".dhall"
33093- ],
33094- "tm_scope": "source.haskell",
33095- "ace_mode": "haskell",
33096- "codemirror_mode": "haskell",
33097- "codemirror_mime_type": "text/x-haskell",
33098- "language_id": 793969321
33099- },
33100- "Diff": {
33101- "type": "data",
33102- "extensions": [
33103- ".diff",
33104- ".patch"
33105- ],
33106- "aliases": [
33107- "udiff"
33108- ],
33109- "tm_scope": "source.diff",
33110- "ace_mode": "diff",
33111- "codemirror_mode": "diff",
33112- "codemirror_mime_type": "text/x-diff",
33113- "language_id": 88
33114- },
33115- "DirectX 3D File": {
33116- "type": "data",
33117- "color": "#aace60",
33118- "extensions": [
33119- ".x"
33120- ],
33121- "ace_mode": "text",
33122- "tm_scope": "none",
33123- "language_id": 201049282
33124- },
33125- "Dockerfile": {
33126- "type": "programming",
33127- "aliases": [
33128- "Containerfile"
33129- ],
33130- "color": "#384d54",
33131- "tm_scope": "source.dockerfile",
33132- "extensions": [
33133- ".dockerfile"
33134- ],
33135- "filenames": [
33136- "Containerfile",
33137- "Dockerfile"
33138- ],
33139- "ace_mode": "dockerfile",
33140- "codemirror_mode": "dockerfile",
33141- "codemirror_mime_type": "text/x-dockerfile",
33142- "language_id": 89
33143- },
33144- "Dogescript": {
33145- "type": "programming",
33146- "color": "#cca760",
33147- "extensions": [
33148- ".djs"
33149- ],
33150- "tm_scope": "none",
33151- "ace_mode": "text",
33152- "language_id": 90
33153- },
33154- "Dotenv": {
33155- "type": "data",
33156- "color": "#e5d559",
33157- "extensions": [
33158- ".env"
33159- ],
33160- "filenames": [
33161- ".env",
33162- ".env.ci",
33163- ".env.dev",
33164- ".env.development",
33165- ".env.development.local",
33166- ".env.example",
33167- ".env.local",
33168- ".env.prod",
33169- ".env.production",
33170- ".env.staging",
33171- ".env.test",
33172- ".env.testing"
33173- ],
33174- "tm_scope": "source.dotenv",
33175- "ace_mode": "text",
33176- "language_id": 111148035
33177- },
33178- "Dylan": {
33179- "type": "programming",
33180- "color": "#6c616e",
33181- "extensions": [
33182- ".dylan",
33183- ".dyl",
33184- ".intr",
33185- ".lid"
33186- ],
33187- "tm_scope": "source.dylan",
33188- "ace_mode": "text",
33189- "codemirror_mode": "dylan",
33190- "codemirror_mime_type": "text/x-dylan",
33191- "language_id": 91
33192- },
33193- "E": {
33194- "type": "programming",
33195- "color": "#ccce35",
33196- "extensions": [
33197- ".e"
33198- ],
33199- "interpreters": [
33200- "rune"
33201- ],
33202- "tm_scope": "none",
33203- "ace_mode": "text",
33204- "language_id": 92
33205- },
33206- "E-mail": {
33207- "type": "data",
33208- "aliases": [
33209- "email",
33210- "eml",
33211- "mail",
33212- "mbox"
33213- ],
33214- "extensions": [
33215- ".eml",
33216- ".mbox"
33217- ],
33218- "tm_scope": "text.eml.basic",
33219- "ace_mode": "text",
33220- "codemirror_mode": "mbox",
33221- "codemirror_mime_type": "application/mbox",
33222- "language_id": 529653389
33223- },
33224- "EBNF": {
33225- "type": "data",
33226- "extensions": [
33227- ".ebnf"
33228- ],
33229- "tm_scope": "source.ebnf",
33230- "ace_mode": "text",
33231- "codemirror_mode": "ebnf",
33232- "codemirror_mime_type": "text/x-ebnf",
33233- "language_id": 430
33234- },
33235- "ECL": {
33236- "type": "programming",
33237- "color": "#8a1267",
33238- "extensions": [
33239- ".ecl",
33240- ".eclxml"
33241- ],
33242- "tm_scope": "source.ecl",
33243- "ace_mode": "text",
33244- "codemirror_mode": "ecl",
33245- "codemirror_mime_type": "text/x-ecl",
33246- "language_id": 93
33247- },
33248- "ECLiPSe": {
33249- "type": "programming",
33250- "color": "#001d9d",
33251- "group": "Prolog",
33252- "extensions": [
33253- ".ecl"
33254- ],
33255- "tm_scope": "source.prolog.eclipse",
33256- "ace_mode": "prolog",
33257- "language_id": 94
33258- },
33259- "EJS": {
33260- "type": "markup",
33261- "color": "#a91e50",
33262- "extensions": [
33263- ".ejs",
33264- ".ect",
33265- ".ejs.t",
33266- ".jst"
33267- ],
33268- "tm_scope": "text.html.js",
33269- "ace_mode": "ejs",
33270- "language_id": 95
33271- },
33272- "EQ": {
33273- "type": "programming",
33274- "color": "#a78649",
33275- "extensions": [
33276- ".eq"
33277- ],
33278- "tm_scope": "source.cs",
33279- "ace_mode": "csharp",
33280- "codemirror_mode": "clike",
33281- "codemirror_mime_type": "text/x-csharp",
33282- "language_id": 96
33283- },
33284- "Eagle": {
33285- "type": "data",
33286- "extensions": [
33287- ".sch",
33288- ".brd"
33289- ],
33290- "tm_scope": "text.xml",
33291- "ace_mode": "xml",
33292- "codemirror_mode": "xml",
33293- "codemirror_mime_type": "text/xml",
33294- "language_id": 97
33295- },
33296- "Earthly": {
33297- "type": "programming",
33298- "aliases": [
33299- "Earthfile"
33300- ],
33301- "color": "#2af0ff",
33302- "tm_scope": "source.earthfile",
33303- "ace_mode": "text",
33304- "filenames": [
33305- "Earthfile"
33306- ],
33307- "language_id": 963512632
33308- },
33309- "Easybuild": {
33310- "type": "data",
33311- "color": "#069406",
33312- "group": "Python",
33313- "ace_mode": "python",
33314- "codemirror_mode": "python",
33315- "codemirror_mime_type": "text/x-python",
33316- "tm_scope": "source.python",
33317- "extensions": [
33318- ".eb"
33319- ],
33320- "language_id": 342840477
33321- },
33322- "Ecere Projects": {
33323- "type": "data",
33324- "color": "#913960",
33325- "group": "JavaScript",
33326- "extensions": [
33327- ".epj"
33328- ],
33329- "tm_scope": "source.json",
33330- "ace_mode": "json",
33331- "codemirror_mode": "javascript",
33332- "codemirror_mime_type": "application/json",
33333- "language_id": 98
33334- },
33335- "Ecmarkup": {
33336- "type": "markup",
33337- "color": "#eb8131",
33338- "group": "HTML",
33339- "extensions": [
33340- ".html"
33341- ],
33342- "tm_scope": "text.html.ecmarkup",
33343- "ace_mode": "html",
33344- "codemirror_mode": "htmlmixed",
33345- "codemirror_mime_type": "text/html",
33346- "aliases": [
33347- "ecmarkdown"
33348- ],
33349- "language_id": 844766630
33350- },
33351- "EdgeQL": {
33352- "type": "programming",
33353- "color": "#31A7FF",
33354- "aliases": [
33355- "esdl"
33356- ],
33357- "extensions": [
33358- ".edgeql",
33359- ".esdl"
33360- ],
33361- "ace_mode": "text",
33362- "tm_scope": "source.edgeql",
33363- "language_id": 925235833
33364- },
33365- "EditorConfig": {
33366- "type": "data",
33367- "color": "#fff1f2",
33368- "group": "INI",
33369- "extensions": [
33370- ".editorconfig"
33371- ],
33372- "filenames": [
33373- ".editorconfig"
33374- ],
33375- "aliases": [
33376- "editor-config"
33377- ],
33378- "ace_mode": "ini",
33379- "codemirror_mode": "properties",
33380- "codemirror_mime_type": "text/x-properties",
33381- "tm_scope": "source.editorconfig",
33382- "language_id": 96139566
33383- },
33384- "Edje Data Collection": {
33385- "type": "data",
33386- "extensions": [
33387- ".edc"
33388- ],
33389- "tm_scope": "source.c++",
33390- "ace_mode": "c_cpp",
33391- "codemirror_mode": "clike",
33392- "codemirror_mime_type": "text/x-c++src",
33393- "language_id": 342840478
33394- },
33395- "Eiffel": {
33396- "type": "programming",
33397- "color": "#4d6977",
33398- "extensions": [
33399- ".e"
33400- ],
33401- "tm_scope": "source.eiffel",
33402- "ace_mode": "eiffel",
33403- "codemirror_mode": "eiffel",
33404- "codemirror_mime_type": "text/x-eiffel",
33405- "language_id": 99
33406- },
33407- "Elixir": {
33408- "type": "programming",
33409- "color": "#6e4a7e",
33410- "extensions": [
33411- ".ex",
33412- ".exs"
33413- ],
33414- "tm_scope": "source.elixir",
33415- "ace_mode": "elixir",
33416- "filenames": [
33417- "mix.lock"
33418- ],
33419- "interpreters": [
33420- "elixir"
33421- ],
33422- "language_id": 100
33423- },
33424- "Elm": {
33425- "type": "programming",
33426- "color": "#60B5CC",
33427- "extensions": [
33428- ".elm"
33429- ],
33430- "tm_scope": "source.elm",
33431- "ace_mode": "elm",
33432- "codemirror_mode": "elm",
33433- "codemirror_mime_type": "text/x-elm",
33434- "language_id": 101
33435- },
33436- "Elvish": {
33437- "type": "programming",
33438- "ace_mode": "text",
33439- "extensions": [
33440- ".elv"
33441- ],
33442- "interpreters": [
33443- "elvish"
33444- ],
33445- "tm_scope": "source.elvish",
33446- "color": "#55BB55",
33447- "language_id": 570996448
33448- },
33449- "Elvish Transcript": {
33450- "type": "programming",
33451- "group": "Elvish",
33452- "ace_mode": "text",
33453- "tm_scope": "source.elvish-transcript",
33454- "color": "#55BB55",
33455- "language_id": 452025714
33456- },
33457- "Emacs Lisp": {
33458- "type": "programming",
33459- "tm_scope": "source.emacs.lisp",
33460- "color": "#c065db",
33461- "aliases": [
33462- "elisp",
33463- "emacs"
33464- ],
33465- "filenames": [
33466- ".abbrev_defs",
33467- ".emacs",
33468- ".emacs.desktop",
33469- ".gnus",
33470- ".spacemacs",
33471- ".viper",
33472- "Cask",
33473- "Project.ede",
33474- "_emacs",
33475- "abbrev_defs"
33476- ],
33477- "extensions": [
33478- ".el",
33479- ".emacs",
33480- ".emacs.desktop"
33481- ],
33482- "ace_mode": "lisp",
33483- "codemirror_mode": "commonlisp",
33484- "codemirror_mime_type": "text/x-common-lisp",
33485- "language_id": 102
33486- },
33487- "EmberScript": {
33488- "type": "programming",
33489- "color": "#FFF4F3",
33490- "extensions": [
33491- ".em",
33492- ".emberscript"
33493- ],
33494- "tm_scope": "source.coffee",
33495- "ace_mode": "coffee",
33496- "codemirror_mode": "coffeescript",
33497- "codemirror_mime_type": "text/x-coffeescript",
33498- "language_id": 103
33499- },
33500- "Erlang": {
33501- "type": "programming",
33502- "color": "#B83998",
33503- "extensions": [
33504- ".erl",
33505- ".app",
33506- ".app.src",
33507- ".es",
33508- ".escript",
33509- ".hrl",
33510- ".xrl",
33511- ".yrl"
33512- ],
33513- "filenames": [
33514- "Emakefile",
33515- "rebar.config",
33516- "rebar.config.lock",
33517- "rebar.lock"
33518- ],
33519- "tm_scope": "source.erlang",
33520- "ace_mode": "erlang",
33521- "codemirror_mode": "erlang",
33522- "codemirror_mime_type": "text/x-erlang",
33523- "interpreters": [
33524- "escript"
33525- ],
33526- "language_id": 104
33527- },
33528- "Euphoria": {
33529- "type": "programming",
33530- "color": "#FF790B",
33531- "extensions": [
33532- ".e",
33533- ".ex"
33534- ],
33535- "interpreters": [
33536- "eui",
33537- "euiw"
33538- ],
33539- "ace_mode": "text",
33540- "tm_scope": "source.euphoria",
33541- "language_id": 880693982
33542- },
33543- "F#": {
33544- "type": "programming",
33545- "color": "#b845fc",
33546- "aliases": [
33547- "fsharp"
33548- ],
33549- "extensions": [
33550- ".fs",
33551- ".fsi",
33552- ".fsx"
33553- ],
33554- "tm_scope": "source.fsharp",
33555- "ace_mode": "text",
33556- "codemirror_mode": "mllike",
33557- "codemirror_mime_type": "text/x-fsharp",
33558- "language_id": 105
33559- },
33560- "F*": {
33561- "fs_name": "Fstar",
33562- "type": "programming",
33563- "color": "#572e30",
33564- "aliases": [
33565- "fstar"
33566- ],
33567- "extensions": [
33568- ".fst",
33569- ".fsti"
33570- ],
33571- "tm_scope": "source.fstar",
33572- "ace_mode": "text",
33573- "language_id": 336943375
33574- },
33575- "FIGlet Font": {
33576- "type": "data",
33577- "color": "#FFDDBB",
33578- "aliases": [
33579- "FIGfont"
33580- ],
33581- "extensions": [
33582- ".flf"
33583- ],
33584- "tm_scope": "source.figfont",
33585- "ace_mode": "text",
33586- "language_id": 686129783
33587- },
33588- "FLUX": {
33589- "type": "programming",
33590- "color": "#88ccff",
33591- "extensions": [
33592- ".fx",
33593- ".flux"
33594- ],
33595- "tm_scope": "none",
33596- "ace_mode": "text",
33597- "language_id": 106
33598- },
33599- "Factor": {
33600- "type": "programming",
33601- "color": "#636746",
33602- "extensions": [
33603- ".factor"
33604- ],
33605- "filenames": [
33606- ".factor-boot-rc",
33607- ".factor-rc"
33608- ],
33609- "tm_scope": "source.factor",
33610- "ace_mode": "text",
33611- "codemirror_mode": "factor",
33612- "codemirror_mime_type": "text/x-factor",
33613- "language_id": 108
33614- },
33615- "Fancy": {
33616- "type": "programming",
33617- "color": "#7b9db4",
33618- "extensions": [
33619- ".fy",
33620- ".fancypack"
33621- ],
33622- "filenames": [
33623- "Fakefile"
33624- ],
33625- "tm_scope": "source.fancy",
33626- "ace_mode": "text",
33627- "language_id": 109
33628- },
33629- "Fantom": {
33630- "type": "programming",
33631- "color": "#14253c",
33632- "extensions": [
33633- ".fan"
33634- ],
33635- "tm_scope": "source.fan",
33636- "ace_mode": "text",
33637- "language_id": 110
33638- },
33639- "Faust": {
33640- "type": "programming",
33641- "color": "#c37240",
33642- "extensions": [
33643- ".dsp"
33644- ],
33645- "tm_scope": "source.faust",
33646- "ace_mode": "text",
33647- "language_id": 622529198
33648- },
33649- "Fennel": {
33650- "type": "programming",
33651- "tm_scope": "source.fnl",
33652- "ace_mode": "text",
33653- "color": "#fff3d7",
33654- "interpreters": [
33655- "fennel"
33656- ],
33657- "extensions": [
33658- ".fnl"
33659- ],
33660- "language_id": 239946126
33661- },
33662- "Filebench WML": {
33663- "type": "programming",
33664- "color": "#F6B900",
33665- "extensions": [
33666- ".f"
33667- ],
33668- "tm_scope": "none",
33669- "ace_mode": "text",
33670- "language_id": 111
33671- },
33672- "Filterscript": {
33673- "type": "programming",
33674- "group": "RenderScript",
33675- "extensions": [
33676- ".fs"
33677- ],
33678- "tm_scope": "none",
33679- "ace_mode": "text",
33680- "language_id": 112
33681- },
33682- "Fluent": {
33683- "type": "programming",
33684- "color": "#ffcc33",
33685- "extensions": [
33686- ".ftl"
33687- ],
33688- "tm_scope": "source.ftl",
33689- "ace_mode": "text",
33690- "language_id": 206353404
33691- },
33692- "Formatted": {
33693- "type": "data",
33694- "extensions": [
33695- ".for",
33696- ".eam.fs"
33697- ],
33698- "tm_scope": "none",
33699- "ace_mode": "text",
33700- "language_id": 113
33701- },
33702- "Forth": {
33703- "type": "programming",
33704- "color": "#341708",
33705- "extensions": [
33706- ".fth",
33707- ".4th",
33708- ".f",
33709- ".for",
33710- ".forth",
33711- ".fr",
33712- ".frt",
33713- ".fs"
33714- ],
33715- "tm_scope": "source.forth",
33716- "ace_mode": "forth",
33717- "codemirror_mode": "forth",
33718- "codemirror_mime_type": "text/x-forth",
33719- "language_id": 114
33720- },
33721- "Fortran": {
33722- "group": "Fortran",
33723- "type": "programming",
33724- "color": "#4d41b1",
33725- "extensions": [
33726- ".f",
33727- ".f77",
33728- ".for",
33729- ".fpp"
33730- ],
33731- "tm_scope": "source.fortran",
33732- "ace_mode": "text",
33733- "codemirror_mode": "fortran",
33734- "codemirror_mime_type": "text/x-fortran",
33735- "language_id": 107
33736- },
33737- "Fortran Free Form": {
33738- "group": "Fortran",
33739- "color": "#4d41b1",
33740- "type": "programming",
33741- "extensions": [
33742- ".f90",
33743- ".f03",
33744- ".f08",
33745- ".f95"
33746- ],
33747- "tm_scope": "source.fortran.modern",
33748- "ace_mode": "text",
33749- "codemirror_mode": "fortran",
33750- "codemirror_mime_type": "text/x-fortran",
33751- "language_id": 761352333
33752- },
33753- "FreeBasic": {
33754- "type": "programming",
33755- "color": "#141AC9",
33756- "extensions": [
33757- ".bi",
33758- ".bas"
33759- ],
33760- "tm_scope": "source.vbnet",
33761- "aliases": [
33762- "fb"
33763- ],
33764- "ace_mode": "text",
33765- "codemirror_mode": "vb",
33766- "codemirror_mime_type": "text/x-vb",
33767- "language_id": 472896659
33768- },
33769- "FreeMarker": {
33770- "type": "programming",
33771- "color": "#0050b2",
33772- "aliases": [
33773- "ftl"
33774- ],
33775- "extensions": [
33776- ".ftl"
33777- ],
33778- "tm_scope": "text.html.ftl",
33779- "ace_mode": "ftl",
33780- "language_id": 115
33781- },
33782- "Frege": {
33783- "type": "programming",
33784- "color": "#00cafe",
33785- "extensions": [
33786- ".fr"
33787- ],
33788- "tm_scope": "source.haskell",
33789- "ace_mode": "haskell",
33790- "language_id": 116
33791- },
33792- "Futhark": {
33793- "type": "programming",
33794- "color": "#5f021f",
33795- "extensions": [
33796- ".fut"
33797- ],
33798- "tm_scope": "source.futhark",
33799- "ace_mode": "text",
33800- "language_id": 97358117
33801- },
33802- "G-code": {
33803- "type": "programming",
33804- "color": "#D08CF2",
33805- "extensions": [
33806- ".g",
33807- ".cnc",
33808- ".gco",
33809- ".gcode"
33810- ],
33811- "tm_scope": "source.gcode",
33812- "ace_mode": "gcode",
33813- "language_id": 117
33814- },
33815- "GAML": {
33816- "type": "programming",
33817- "color": "#FFC766",
33818- "extensions": [
33819- ".gaml"
33820- ],
33821- "tm_scope": "none",
33822- "ace_mode": "text",
33823- "language_id": 290345951
33824- },
33825- "GAMS": {
33826- "type": "programming",
33827- "color": "#f49a22",
33828- "extensions": [
33829- ".gms"
33830- ],
33831- "tm_scope": "none",
33832- "ace_mode": "text",
33833- "language_id": 118
33834- },
33835- "GAP": {
33836- "type": "programming",
33837- "color": "#0000cc",
33838- "extensions": [
33839- ".g",
33840- ".gap",
33841- ".gd",
33842- ".gi",
33843- ".tst"
33844- ],
33845- "tm_scope": "source.gap",
33846- "ace_mode": "text",
33847- "language_id": 119
33848- },
33849- "GCC Machine Description": {
33850- "type": "programming",
33851- "color": "#FFCFAB",
33852- "extensions": [
33853- ".md"
33854- ],
33855- "tm_scope": "source.lisp",
33856- "ace_mode": "lisp",
33857- "codemirror_mode": "commonlisp",
33858- "codemirror_mime_type": "text/x-common-lisp",
33859- "language_id": 121
33860- },
33861- "GDB": {
33862- "type": "programming",
33863- "extensions": [
33864- ".gdb",
33865- ".gdbinit"
33866- ],
33867- "tm_scope": "source.gdb",
33868- "ace_mode": "text",
33869- "language_id": 122
33870- },
33871- "GDScript": {
33872- "type": "programming",
33873- "color": "#355570",
33874- "extensions": [
33875- ".gd"
33876- ],
33877- "tm_scope": "source.gdscript",
33878- "ace_mode": "text",
33879- "language_id": 123
33880- },
33881- "GEDCOM": {
33882- "type": "data",
33883- "color": "#003058",
33884- "ace_mode": "text",
33885- "extensions": [
33886- ".ged"
33887- ],
33888- "tm_scope": "source.gedcom",
33889- "language_id": 459577965
33890- },
33891- "GLSL": {
33892- "type": "programming",
33893- "color": "#5686a5",
33894- "extensions": [
33895- ".glsl",
33896- ".fp",
33897- ".frag",
33898- ".frg",
33899- ".fs",
33900- ".fsh",
33901- ".fshader",
33902- ".geo",
33903- ".geom",
33904- ".glslf",
33905- ".glslv",
33906- ".gs",
33907- ".gshader",
33908- ".rchit",
33909- ".rmiss",
33910- ".shader",
33911- ".tesc",
33912- ".tese",
33913- ".vert",
33914- ".vrx",
33915- ".vs",
33916- ".vsh",
33917- ".vshader"
33918- ],
33919- "tm_scope": "source.glsl",
33920- "ace_mode": "glsl",
33921- "language_id": 124
33922- },
33923- "GN": {
33924- "type": "data",
33925- "extensions": [
33926- ".gn",
33927- ".gni"
33928- ],
33929- "interpreters": [
33930- "gn"
33931- ],
33932- "filenames": [
33933- ".gn"
33934- ],
33935- "tm_scope": "source.gn",
33936- "ace_mode": "python",
33937- "codemirror_mode": "python",
33938- "codemirror_mime_type": "text/x-python",
33939- "language_id": 302957008
33940- },
33941- "GSC": {
33942- "type": "programming",
33943- "color": "#FF6800",
33944- "extensions": [
33945- ".gsc",
33946- ".csc",
33947- ".gsh"
33948- ],
33949- "tm_scope": "source.gsc",
33950- "ace_mode": "c_cpp",
33951- "codemirror_mode": "clike",
33952- "codemirror_mime_type": "text/x-csrc",
33953- "language_id": 257856279
33954- },
33955- "Game Maker Language": {
33956- "type": "programming",
33957- "color": "#71b417",
33958- "extensions": [
33959- ".gml"
33960- ],
33961- "tm_scope": "source.c++",
33962- "ace_mode": "c_cpp",
33963- "codemirror_mode": "clike",
33964- "codemirror_mime_type": "text/x-c++src",
33965- "language_id": 125
33966- },
33967- "Gemfile.lock": {
33968- "type": "data",
33969- "color": "#701516",
33970- "searchable": false,
33971- "tm_scope": "source.gemfile-lock",
33972- "ace_mode": "text",
33973- "filenames": [
33974- "Gemfile.lock"
33975- ],
33976- "language_id": 907065713
33977- },
33978- "Gemini": {
33979- "type": "prose",
33980- "color": "#ff6900",
33981- "ace_mode": "text",
33982- "extensions": [
33983- ".gmi"
33984- ],
33985- "aliases": [
33986- "gemtext"
33987- ],
33988- "wrap": true,
33989- "tm_scope": "source.gemini",
33990- "language_id": 310828396
33991- },
33992- "Genero 4gl": {
33993- "type": "programming",
33994- "color": "#63408e",
33995- "extensions": [
33996- ".4gl"
33997- ],
33998- "tm_scope": "source.genero-4gl",
33999- "ace_mode": "text",
34000- "language_id": 986054050
34001- },
34002- "Genero per": {
34003- "type": "markup",
34004- "color": "#d8df39",
34005- "extensions": [
34006- ".per"
34007- ],
34008- "tm_scope": "source.genero-per",
34009- "ace_mode": "text",
34010- "language_id": 902995658
34011- },
34012- "Genie": {
34013- "type": "programming",
34014- "ace_mode": "text",
34015- "extensions": [
34016- ".gs"
34017- ],
34018- "color": "#fb855d",
34019- "tm_scope": "none",
34020- "language_id": 792408528
34021- },
34022- "Genshi": {
34023- "type": "programming",
34024- "color": "#951531",
34025- "extensions": [
34026- ".kid"
34027- ],
34028- "tm_scope": "text.xml.genshi",
34029- "aliases": [
34030- "xml+genshi",
34031- "xml+kid"
34032- ],
34033- "ace_mode": "xml",
34034- "codemirror_mode": "xml",
34035- "codemirror_mime_type": "text/xml",
34036- "language_id": 126
34037- },
34038- "Gentoo Ebuild": {
34039- "type": "programming",
34040- "color": "#9400ff",
34041- "group": "Shell",
34042- "extensions": [
34043- ".ebuild"
34044- ],
34045- "tm_scope": "source.shell",
34046- "ace_mode": "sh",
34047- "codemirror_mode": "shell",
34048- "codemirror_mime_type": "text/x-sh",
34049- "language_id": 127
34050- },
34051- "Gentoo Eclass": {
34052- "type": "programming",
34053- "color": "#9400ff",
34054- "group": "Shell",
34055- "extensions": [
34056- ".eclass"
34057- ],
34058- "tm_scope": "source.shell",
34059- "ace_mode": "sh",
34060- "codemirror_mode": "shell",
34061- "codemirror_mime_type": "text/x-sh",
34062- "language_id": 128
34063- },
34064- "Gerber Image": {
34065- "type": "data",
34066- "color": "#d20b00",
34067- "aliases": [
34068- "rs-274x"
34069- ],
34070- "extensions": [
34071- ".gbr",
34072- ".cmp",
34073- ".gbl",
34074- ".gbo",
34075- ".gbp",
34076- ".gbs",
34077- ".gko",
34078- ".gml",
34079- ".gpb",
34080- ".gpt",
34081- ".gtl",
34082- ".gto",
34083- ".gtp",
34084- ".gts",
34085- ".ncl",
34086- ".sol"
34087- ],
34088- "interpreters": [
34089- "gerbv",
34090- "gerbview"
34091- ],
34092- "tm_scope": "source.gerber",
34093- "ace_mode": "text",
34094- "language_id": 404627610
34095- },
34096- "Gettext Catalog": {
34097- "type": "prose",
34098- "aliases": [
34099- "pot"
34100- ],
34101- "extensions": [
34102- ".po",
34103- ".pot"
34104- ],
34105- "tm_scope": "source.po",
34106- "ace_mode": "text",
34107- "language_id": 129
34108- },
34109- "Gherkin": {
34110- "type": "programming",
34111- "extensions": [
34112- ".feature",
34113- ".story"
34114- ],
34115- "tm_scope": "text.gherkin.feature",
34116- "aliases": [
34117- "cucumber"
34118- ],
34119- "ace_mode": "text",
34120- "color": "#5B2063",
34121- "language_id": 76
34122- },
34123- "Git Attributes": {
34124- "type": "data",
34125- "color": "#F44D27",
34126- "aliases": [
34127- "gitattributes"
34128- ],
34129- "filenames": [
34130- ".gitattributes"
34131- ],
34132- "tm_scope": "source.gitattributes",
34133- "ace_mode": "gitignore",
34134- "codemirror_mode": "shell",
34135- "codemirror_mime_type": "text/x-sh",
34136- "language_id": 956324166
34137- },
34138- "Git Config": {
34139- "type": "data",
34140- "color": "#F44D27",
34141- "group": "INI",
34142- "aliases": [
34143- "gitconfig",
34144- "gitmodules"
34145- ],
34146- "extensions": [
34147- ".gitconfig"
34148- ],
34149- "filenames": [
34150- ".gitconfig",
34151- ".gitmodules"
34152- ],
34153- "ace_mode": "ini",
34154- "codemirror_mode": "properties",
34155- "codemirror_mime_type": "text/x-properties",
34156- "tm_scope": "source.gitconfig",
34157- "language_id": 807968997
34158- },
34159- "Git Revision List": {
34160- "type": "data",
34161- "color": "#F44D27",
34162- "aliases": [
34163- "Git Blame Ignore Revs"
34164- ],
34165- "filenames": [
34166- ".git-blame-ignore-revs"
34167- ],
34168- "tm_scope": "source.git-revlist",
34169- "ace_mode": "text",
34170- "language_id": 461881235
34171- },
34172- "Gleam": {
34173- "type": "programming",
34174- "color": "#ffaff3",
34175- "ace_mode": "text",
34176- "extensions": [
34177- ".gleam"
34178- ],
34179- "tm_scope": "source.gleam",
34180- "language_id": 1054258749
34181- },
34182- "Glimmer JS": {
34183- "type": "programming",
34184- "extensions": [
34185- ".gjs"
34186- ],
34187- "ace_mode": "javascript",
34188- "color": "#F5835F",
34189- "tm_scope": "source.gjs",
34190- "group": "JavaScript",
34191- "language_id": 5523150
34192- },
34193- "Glyph": {
34194- "type": "programming",
34195- "color": "#c1ac7f",
34196- "extensions": [
34197- ".glf"
34198- ],
34199- "tm_scope": "source.tcl",
34200- "ace_mode": "tcl",
34201- "codemirror_mode": "tcl",
34202- "codemirror_mime_type": "text/x-tcl",
34203- "language_id": 130
34204- },
34205- "Glyph Bitmap Distribution Format": {
34206- "type": "data",
34207- "extensions": [
34208- ".bdf"
34209- ],
34210- "tm_scope": "source.bdf",
34211- "ace_mode": "text",
34212- "language_id": 997665271
34213- },
34214- "Gnuplot": {
34215- "type": "programming",
34216- "color": "#f0a9f0",
34217- "extensions": [
34218- ".gp",
34219- ".gnu",
34220- ".gnuplot",
34221- ".p",
34222- ".plot",
34223- ".plt"
34224- ],
34225- "interpreters": [
34226- "gnuplot"
34227- ],
34228- "tm_scope": "source.gnuplot",
34229- "ace_mode": "text",
34230- "language_id": 131
34231- },
34232- "Go": {
34233- "type": "programming",
34234- "color": "#00ADD8",
34235- "aliases": [
34236- "golang"
34237- ],
34238- "extensions": [
34239- ".go"
34240- ],
34241- "tm_scope": "source.go",
34242- "ace_mode": "golang",
34243- "codemirror_mode": "go",
34244- "codemirror_mime_type": "text/x-go",
34245- "language_id": 132
34246- },
34247- "Go Checksums": {
34248- "type": "data",
34249- "color": "#00ADD8",
34250- "aliases": [
34251- "go.sum",
34252- "go sum",
34253- "go.work.sum",
34254- "go work sum"
34255- ],
34256- "filenames": [
34257- "go.sum",
34258- "go.work.sum"
34259- ],
34260- "tm_scope": "go.sum",
34261- "ace_mode": "text",
34262- "language_id": 1054391671
34263- },
34264- "Go Module": {
34265- "type": "data",
34266- "color": "#00ADD8",
34267- "aliases": [
34268- "go.mod",
34269- "go mod"
34270- ],
34271- "filenames": [
34272- "go.mod"
34273- ],
34274- "tm_scope": "go.mod",
34275- "ace_mode": "text",
34276- "language_id": 947461016
34277- },
34278- "Go Workspace": {
34279- "type": "data",
34280- "color": "#00ADD8",
34281- "aliases": [
34282- "go.work",
34283- "go work"
34284- ],
34285- "filenames": [
34286- "go.work"
34287- ],
34288- "tm_scope": "go.mod",
34289- "ace_mode": "text",
34290- "language_id": 934546256
34291- },
34292- "Godot Resource": {
34293- "type": "data",
34294- "color": "#355570",
34295- "extensions": [
34296- ".gdnlib",
34297- ".gdns",
34298- ".tres",
34299- ".tscn"
34300- ],
34301- "filenames": [
34302- "project.godot"
34303- ],
34304- "tm_scope": "source.gdresource",
34305- "ace_mode": "text",
34306- "language_id": 738107771
34307- },
34308- "Golo": {
34309- "type": "programming",
34310- "color": "#88562A",
34311- "extensions": [
34312- ".golo"
34313- ],
34314- "tm_scope": "source.golo",
34315- "ace_mode": "text",
34316- "language_id": 133
34317- },
34318- "Gosu": {
34319- "type": "programming",
34320- "color": "#82937f",
34321- "extensions": [
34322- ".gs",
34323- ".gst",
34324- ".gsx",
34325- ".vark"
34326- ],
34327- "tm_scope": "source.gosu.2",
34328- "ace_mode": "text",
34329- "language_id": 134
34330- },
34331- "Grace": {
34332- "type": "programming",
34333- "color": "#615f8b",
34334- "extensions": [
34335- ".grace"
34336- ],
34337- "tm_scope": "source.grace",
34338- "ace_mode": "text",
34339- "language_id": 135
34340- },
34341- "Gradle": {
34342- "type": "data",
34343- "color": "#02303a",
34344- "extensions": [
34345- ".gradle"
34346- ],
34347- "tm_scope": "source.groovy.gradle",
34348- "ace_mode": "text",
34349- "language_id": 136
34350- },
34351- "Gradle Kotlin DSL": {
34352- "group": "Gradle",
34353- "type": "data",
34354- "color": "#02303a",
34355- "extensions": [
34356- ".gradle.kts"
34357- ],
34358- "ace_mode": "text",
34359- "tm_scope": "source.kotlin",
34360- "language_id": 432600901
34361- },
34362- "Grammatical Framework": {
34363- "type": "programming",
34364- "aliases": [
34365- "gf"
34366- ],
34367- "extensions": [
34368- ".gf"
34369- ],
34370- "color": "#ff0000",
34371- "tm_scope": "source.gf",
34372- "ace_mode": "haskell",
34373- "codemirror_mode": "haskell",
34374- "codemirror_mime_type": "text/x-haskell",
34375- "language_id": 137
34376- },
34377- "Graph Modeling Language": {
34378- "type": "data",
34379- "extensions": [
34380- ".gml"
34381- ],
34382- "tm_scope": "none",
34383- "ace_mode": "text",
34384- "language_id": 138
34385- },
34386- "GraphQL": {
34387- "type": "data",
34388- "color": "#e10098",
34389- "extensions": [
34390- ".graphql",
34391- ".gql",
34392- ".graphqls"
34393- ],
34394- "tm_scope": "source.graphql",
34395- "ace_mode": "text",
34396- "language_id": 139
34397- },
34398- "Graphviz (DOT)": {
34399- "type": "data",
34400- "color": "#2596be",
34401- "tm_scope": "source.dot",
34402- "extensions": [
34403- ".dot",
34404- ".gv"
34405- ],
34406- "ace_mode": "text",
34407- "language_id": 140
34408- },
34409- "Groovy": {
34410- "type": "programming",
34411- "tm_scope": "source.groovy",
34412- "ace_mode": "groovy",
34413- "codemirror_mode": "groovy",
34414- "codemirror_mime_type": "text/x-groovy",
34415- "color": "#4298b8",
34416- "extensions": [
34417- ".groovy",
34418- ".grt",
34419- ".gtpl",
34420- ".gvy"
34421- ],
34422- "interpreters": [
34423- "groovy"
34424- ],
34425- "filenames": [
34426- "Jenkinsfile"
34427- ],
34428- "language_id": 142
34429- },
34430- "Groovy Server Pages": {
34431- "type": "programming",
34432- "color": "#4298b8",
34433- "group": "Groovy",
34434- "aliases": [
34435- "gsp",
34436- "java server page"
34437- ],
34438- "extensions": [
34439- ".gsp"
34440- ],
34441- "tm_scope": "text.html.jsp",
34442- "ace_mode": "jsp",
34443- "codemirror_mode": "htmlembedded",
34444- "codemirror_mime_type": "application/x-jsp",
34445- "language_id": 143
34446- },
34447- "HAProxy": {
34448- "type": "data",
34449- "color": "#106da9",
34450- "extensions": [
34451- ".cfg"
34452- ],
34453- "filenames": [
34454- "haproxy.cfg"
34455- ],
34456- "tm_scope": "source.haproxy-config",
34457- "ace_mode": "text",
34458- "language_id": 366607477
34459- },
34460- "HCL": {
34461- "type": "programming",
34462- "color": "#844FBA",
34463- "extensions": [
34464- ".hcl",
34465- ".nomad",
34466- ".tf",
34467- ".tfvars",
34468- ".workflow"
34469- ],
34470- "aliases": [
34471- "HashiCorp Configuration Language",
34472- "terraform"
34473- ],
34474- "ace_mode": "ruby",
34475- "codemirror_mode": "ruby",
34476- "codemirror_mime_type": "text/x-ruby",
34477- "tm_scope": "source.terraform",
34478- "language_id": 144
34479- },
34480- "HLSL": {
34481- "type": "programming",
34482- "color": "#aace60",
34483- "extensions": [
34484- ".hlsl",
34485- ".cginc",
34486- ".fx",
34487- ".fxh",
34488- ".hlsli"
34489- ],
34490- "ace_mode": "text",
34491- "tm_scope": "source.hlsl",
34492- "language_id": 145
34493- },
34494- "HOCON": {
34495- "type": "data",
34496- "color": "#9ff8ee",
34497- "extensions": [
34498- ".hocon"
34499- ],
34500- "filenames": [
34501- ".scalafix.conf",
34502- ".scalafmt.conf"
34503- ],
34504- "tm_scope": "source.hocon",
34505- "ace_mode": "text",
34506- "language_id": 679725279
34507- },
34508- "HTML": {
34509- "type": "markup",
34510- "tm_scope": "text.html.basic",
34511- "ace_mode": "html",
34512- "codemirror_mode": "htmlmixed",
34513- "codemirror_mime_type": "text/html",
34514- "color": "#e34c26",
34515- "aliases": [
34516- "xhtml"
34517- ],
34518- "extensions": [
34519- ".html",
34520- ".hta",
34521- ".htm",
34522- ".html.hl",
34523- ".inc",
34524- ".xht",
34525- ".xhtml"
34526- ],
34527- "language_id": 146
34528- },
34529- "HTML+ECR": {
34530- "type": "markup",
34531- "color": "#2e1052",
34532- "tm_scope": "text.html.ecr",
34533- "group": "HTML",
34534- "aliases": [
34535- "ecr"
34536- ],
34537- "extensions": [
34538- ".ecr"
34539- ],
34540- "ace_mode": "text",
34541- "codemirror_mode": "htmlmixed",
34542- "codemirror_mime_type": "text/html",
34543- "language_id": 148
34544- },
34545- "HTML+EEX": {
34546- "type": "markup",
34547- "color": "#6e4a7e",
34548- "tm_scope": "text.html.elixir",
34549- "group": "HTML",
34550- "aliases": [
34551- "eex",
34552- "heex",
34553- "leex"
34554- ],
34555- "extensions": [
34556- ".eex",
34557- ".html.heex",
34558- ".html.leex"
34559- ],
34560- "ace_mode": "text",
34561- "codemirror_mode": "htmlmixed",
34562- "codemirror_mime_type": "text/html",
34563- "language_id": 149
34564- },
34565- "HTML+ERB": {
34566- "type": "markup",
34567- "color": "#701516",
34568- "tm_scope": "text.html.erb",
34569- "group": "HTML",
34570- "aliases": [
34571- "erb",
34572- "rhtml",
34573- "html+ruby"
34574- ],
34575- "extensions": [
34576- ".erb",
34577- ".erb.deface",
34578- ".rhtml"
34579- ],
34580- "ace_mode": "text",
34581- "codemirror_mode": "htmlembedded",
34582- "codemirror_mime_type": "application/x-erb",
34583- "language_id": 150
34584- },
34585- "HTML+PHP": {
34586- "type": "markup",
34587- "color": "#4f5d95",
34588- "tm_scope": "text.html.php",
34589- "group": "HTML",
34590- "extensions": [
34591- ".phtml"
34592- ],
34593- "ace_mode": "php",
34594- "codemirror_mode": "php",
34595- "codemirror_mime_type": "application/x-httpd-php",
34596- "language_id": 151
34597- },
34598- "HTML+Razor": {
34599- "type": "markup",
34600- "color": "#512be4",
34601- "tm_scope": "text.html.cshtml",
34602- "group": "HTML",
34603- "aliases": [
34604- "razor"
34605- ],
34606- "extensions": [
34607- ".cshtml",
34608- ".razor"
34609- ],
34610- "ace_mode": "razor",
34611- "codemirror_mode": "htmlmixed",
34612- "codemirror_mime_type": "text/html",
34613- "language_id": 479039817
34614- },
34615- "HTTP": {
34616- "type": "data",
34617- "color": "#005C9C",
34618- "extensions": [
34619- ".http"
34620- ],
34621- "tm_scope": "source.httpspec",
34622- "ace_mode": "text",
34623- "codemirror_mode": "http",
34624- "codemirror_mime_type": "message/http",
34625- "language_id": 152
34626- },
34627- "HXML": {
34628- "type": "data",
34629- "color": "#f68712",
34630- "ace_mode": "text",
34631- "extensions": [
34632- ".hxml"
34633- ],
34634- "tm_scope": "source.hxml",
34635- "language_id": 786683730
34636- },
34637- "Hack": {
34638- "type": "programming",
34639- "ace_mode": "php",
34640- "codemirror_mode": "php",
34641- "codemirror_mime_type": "application/x-httpd-php",
34642- "extensions": [
34643- ".hack",
34644- ".hh",
34645- ".hhi",
34646- ".php"
34647- ],
34648- "tm_scope": "source.hack",
34649- "color": "#878787",
34650- "language_id": 153
34651- },
34652- "Haml": {
34653- "type": "markup",
34654- "color": "#ece2a9",
34655- "extensions": [
34656- ".haml",
34657- ".haml.deface"
34658- ],
34659- "tm_scope": "text.haml",
34660- "ace_mode": "haml",
34661- "codemirror_mode": "haml",
34662- "codemirror_mime_type": "text/x-haml",
34663- "language_id": 154
34664- },
34665- "Handlebars": {
34666- "type": "markup",
34667- "color": "#f7931e",
34668- "aliases": [
34669- "hbs",
34670- "htmlbars"
34671- ],
34672- "extensions": [
34673- ".handlebars",
34674- ".hbs"
34675- ],
34676- "tm_scope": "text.html.handlebars",
34677- "ace_mode": "handlebars",
34678- "language_id": 155
34679- },
34680- "Harbour": {
34681- "type": "programming",
34682- "color": "#0e60e3",
34683- "extensions": [
34684- ".hb"
34685- ],
34686- "tm_scope": "source.harbour",
34687- "ace_mode": "text",
34688- "language_id": 156
34689- },
34690- "Haskell": {
34691- "type": "programming",
34692- "color": "#5e5086",
34693- "extensions": [
34694- ".hs",
34695- ".hs-boot",
34696- ".hsc"
34697- ],
34698- "interpreters": [
34699- "runghc",
34700- "runhaskell",
34701- "runhugs"
34702- ],
34703- "tm_scope": "source.haskell",
34704- "ace_mode": "haskell",
34705- "codemirror_mode": "haskell",
34706- "codemirror_mime_type": "text/x-haskell",
34707- "language_id": 157
34708- },
34709- "Haxe": {
34710- "type": "programming",
34711- "ace_mode": "haxe",
34712- "codemirror_mode": "haxe",
34713- "codemirror_mime_type": "text/x-haxe",
34714- "color": "#df7900",
34715- "extensions": [
34716- ".hx",
34717- ".hxsl"
34718- ],
34719- "tm_scope": "source.hx",
34720- "language_id": 158
34721- },
34722- "HiveQL": {
34723- "type": "programming",
34724- "extensions": [
34725- ".q",
34726- ".hql"
34727- ],
34728- "color": "#dce200",
34729- "tm_scope": "source.hql",
34730- "ace_mode": "sql",
34731- "language_id": 931814087
34732- },
34733- "HolyC": {
34734- "type": "programming",
34735- "color": "#ffefaf",
34736- "extensions": [
34737- ".hc"
34738- ],
34739- "tm_scope": "source.hc",
34740- "ace_mode": "c_cpp",
34741- "codemirror_mode": "clike",
34742- "codemirror_mime_type": "text/x-csrc",
34743- "language_id": 928121743
34744- },
34745- "Hosts File": {
34746- "type": "data",
34747- "color": "#308888",
34748- "filenames": [
34749- "HOSTS",
34750- "hosts"
34751- ],
34752- "aliases": [
34753- "hosts"
34754- ],
34755- "tm_scope": "source.hosts",
34756- "ace_mode": "text",
34757- "language_id": 231021894
34758- },
34759- "Hy": {
34760- "type": "programming",
34761- "ace_mode": "text",
34762- "color": "#7790B2",
34763- "extensions": [
34764- ".hy"
34765- ],
34766- "interpreters": [
34767- "hy"
34768- ],
34769- "aliases": [
34770- "hylang"
34771- ],
34772- "tm_scope": "source.hy",
34773- "language_id": 159
34774- },
34775- "HyPhy": {
34776- "type": "programming",
34777- "ace_mode": "text",
34778- "extensions": [
34779- ".bf"
34780- ],
34781- "tm_scope": "none",
34782- "language_id": 160
34783- },
34784- "IDL": {
34785- "type": "programming",
34786- "color": "#a3522f",
34787- "extensions": [
34788- ".pro",
34789- ".dlm"
34790- ],
34791- "tm_scope": "source.idl",
34792- "ace_mode": "text",
34793- "codemirror_mode": "idl",
34794- "codemirror_mime_type": "text/x-idl",
34795- "language_id": 161
34796- },
34797- "IGOR Pro": {
34798- "type": "programming",
34799- "color": "#0000cc",
34800- "extensions": [
34801- ".ipf"
34802- ],
34803- "aliases": [
34804- "igor",
34805- "igorpro"
34806- ],
34807- "tm_scope": "source.igor",
34808- "ace_mode": "text",
34809- "language_id": 162
34810- },
34811- "INI": {
34812- "type": "data",
34813- "color": "#d1dbe0",
34814- "extensions": [
34815- ".ini",
34816- ".cfg",
34817- ".cnf",
34818- ".dof",
34819- ".lektorproject",
34820- ".prefs",
34821- ".pro",
34822- ".properties",
34823- ".url"
34824- ],
34825- "filenames": [
34826- ".coveragerc",
34827- ".flake8",
34828- ".pylintrc",
34829- "HOSTS",
34830- "buildozer.spec",
34831- "hosts",
34832- "pylintrc",
34833- "vlcrc"
34834- ],
34835- "tm_scope": "source.ini",
34836- "aliases": [
34837- "dosini"
34838- ],
34839- "ace_mode": "ini",
34840- "codemirror_mode": "properties",
34841- "codemirror_mime_type": "text/x-properties",
34842- "language_id": 163
34843- },
34844- "IRC log": {
34845- "type": "data",
34846- "aliases": [
34847- "irc",
34848- "irc logs"
34849- ],
34850- "extensions": [
34851- ".irclog",
34852- ".weechatlog"
34853- ],
34854- "tm_scope": "none",
34855- "ace_mode": "text",
34856- "codemirror_mode": "mirc",
34857- "codemirror_mime_type": "text/mirc",
34858- "language_id": 164
34859- },
34860- "Idris": {
34861- "type": "programming",
34862- "color": "#b30000",
34863- "extensions": [
34864- ".idr",
34865- ".lidr"
34866- ],
34867- "ace_mode": "text",
34868- "tm_scope": "source.idris",
34869- "language_id": 165
34870- },
34871- "Ignore List": {
34872- "type": "data",
34873- "color": "#000000",
34874- "aliases": [
34875- "ignore",
34876- "gitignore",
34877- "git-ignore"
34878- ],
34879- "extensions": [
34880- ".gitignore"
34881- ],
34882- "filenames": [
34883- ".atomignore",
34884- ".babelignore",
34885- ".bzrignore",
34886- ".coffeelintignore",
34887- ".cvsignore",
34888- ".dockerignore",
34889- ".eleventyignore",
34890- ".eslintignore",
34891- ".gitignore",
34892- ".markdownlintignore",
34893- ".nodemonignore",
34894- ".npmignore",
34895- ".prettierignore",
34896- ".stylelintignore",
34897- ".vercelignore",
34898- ".vscodeignore",
34899- "gitignore-global",
34900- "gitignore_global"
34901- ],
34902- "ace_mode": "gitignore",
34903- "tm_scope": "source.gitignore",
34904- "codemirror_mode": "shell",
34905- "codemirror_mime_type": "text/x-sh",
34906- "language_id": 74444240
34907- },
34908- "ImageJ Macro": {
34909- "type": "programming",
34910- "color": "#99AAFF",
34911- "aliases": [
34912- "ijm"
34913- ],
34914- "extensions": [
34915- ".ijm"
34916- ],
34917- "ace_mode": "text",
34918- "tm_scope": "none",
34919- "language_id": 575143428
34920- },
34921- "Imba": {
34922- "type": "programming",
34923- "color": "#16cec6",
34924- "extensions": [
34925- ".imba"
34926- ],
34927- "ace_mode": "text",
34928- "tm_scope": "source.imba",
34929- "language_id": 1057618448
34930- },
34931- "Inform 7": {
34932- "type": "programming",
34933- "wrap": true,
34934- "extensions": [
34935- ".ni",
34936- ".i7x"
34937- ],
34938- "tm_scope": "source.inform7",
34939- "aliases": [
34940- "i7",
34941- "inform7"
34942- ],
34943- "ace_mode": "text",
34944- "language_id": 166
34945- },
34946- "Ink": {
34947- "type": "programming",
34948- "wrap": true,
34949- "extensions": [
34950- ".ink"
34951- ],
34952- "tm_scope": "source.ink",
34953- "ace_mode": "text",
34954- "language_id": 838252715
34955- },
34956- "Inno Setup": {
34957- "type": "programming",
34958- "color": "#264b99",
34959- "extensions": [
34960- ".iss",
34961- ".isl"
34962- ],
34963- "tm_scope": "source.inno",
34964- "ace_mode": "text",
34965- "language_id": 167
34966- },
34967- "Io": {
34968- "type": "programming",
34969- "color": "#a9188d",
34970- "extensions": [
34971- ".io"
34972- ],
34973- "interpreters": [
34974- "io"
34975- ],
34976- "tm_scope": "source.io",
34977- "ace_mode": "io",
34978- "language_id": 168
34979- },
34980- "Ioke": {
34981- "type": "programming",
34982- "color": "#078193",
34983- "extensions": [
34984- ".ik"
34985- ],
34986- "interpreters": [
34987- "ioke"
34988- ],
34989- "tm_scope": "source.ioke",
34990- "ace_mode": "text",
34991- "language_id": 169
34992- },
34993- "Isabelle": {
34994- "type": "programming",
34995- "color": "#FEFE00",
34996- "extensions": [
34997- ".thy"
34998- ],
34999- "tm_scope": "source.isabelle.theory",
35000- "ace_mode": "text",
35001- "language_id": 170
35002- },
35003- "Isabelle ROOT": {
35004- "type": "programming",
35005- "color": "#FEFE00",
35006- "group": "Isabelle",
35007- "filenames": [
35008- "ROOT"
35009- ],
35010- "tm_scope": "source.isabelle.root",
35011- "ace_mode": "text",
35012- "language_id": 171
35013- },
35014- "J": {
35015- "type": "programming",
35016- "color": "#9EEDFF",
35017- "extensions": [
35018- ".ijs"
35019- ],
35020- "interpreters": [
35021- "jconsole"
35022- ],
35023- "tm_scope": "source.j",
35024- "ace_mode": "text",
35025- "language_id": 172
35026- },
35027- "JAR Manifest": {
35028- "type": "data",
35029- "color": "#b07219",
35030- "filenames": [
35031- "MANIFEST.MF"
35032- ],
35033- "tm_scope": "source.yaml",
35034- "ace_mode": "text",
35035- "language_id": 447261135
35036- },
35037- "JCL": {
35038- "type": "programming",
35039- "color": "#d90e09",
35040- "extensions": [
35041- ".jcl"
35042- ],
35043- "tm_scope": "source.jcl",
35044- "ace_mode": "text",
35045- "language_id": 316620079
35046- },
35047- "JFlex": {
35048- "type": "programming",
35049- "color": "#DBCA00",
35050- "group": "Lex",
35051- "extensions": [
35052- ".flex",
35053- ".jflex"
35054- ],
35055- "tm_scope": "source.jflex",
35056- "ace_mode": "text",
35057- "language_id": 173
35058- },
35059- "JSON": {
35060- "type": "data",
35061- "color": "#292929",
35062- "tm_scope": "source.json",
35063- "ace_mode": "json",
35064- "codemirror_mode": "javascript",
35065- "codemirror_mime_type": "application/json",
35066- "aliases": [
35067- "geojson",
35068- "jsonl",
35069- "topojson"
35070- ],
35071- "extensions": [
35072- ".json",
35073- ".4DForm",
35074- ".4DProject",
35075- ".avsc",
35076- ".geojson",
35077- ".gltf",
35078- ".har",
35079- ".ice",
35080- ".JSON-tmLanguage",
35081- ".jsonl",
35082- ".mcmeta",
35083- ".tfstate",
35084- ".tfstate.backup",
35085- ".topojson",
35086- ".webapp",
35087- ".webmanifest",
35088- ".yy",
35089- ".yyp"
35090- ],
35091- "filenames": [
35092- ".all-contributorsrc",
35093- ".arcconfig",
35094- ".auto-changelog",
35095- ".c8rc",
35096- ".htmlhintrc",
35097- ".imgbotconfig",
35098- ".nycrc",
35099- ".tern-config",
35100- ".tern-project",
35101- ".watchmanconfig",
35102- "Pipfile.lock",
35103- "composer.lock",
35104- "deno.lock",
35105- "flake.lock",
35106- "mcmod.info"
35107- ],
35108- "language_id": 174
35109- },
35110- "JSON with Comments": {
35111- "type": "data",
35112- "color": "#292929",
35113- "group": "JSON",
35114- "tm_scope": "source.js",
35115- "ace_mode": "javascript",
35116- "codemirror_mode": "javascript",
35117- "codemirror_mime_type": "text/javascript",
35118- "aliases": [
35119- "jsonc"
35120- ],
35121- "extensions": [
35122- ".jsonc",
35123- ".code-snippets",
35124- ".code-workspace",
35125- ".sublime-build",
35126- ".sublime-commands",
35127- ".sublime-completions",
35128- ".sublime-keymap",
35129- ".sublime-macro",
35130- ".sublime-menu",
35131- ".sublime-mousemap",
35132- ".sublime-project",
35133- ".sublime-settings",
35134- ".sublime-theme",
35135- ".sublime-workspace",
35136- ".sublime_metrics",
35137- ".sublime_session"
35138- ],
35139- "filenames": [
35140- ".babelrc",
35141- ".devcontainer.json",
35142- ".eslintrc.json",
35143- ".jscsrc",
35144- ".jshintrc",
35145- ".jslintrc",
35146- ".swcrc",
35147- "api-extractor.json",
35148- "devcontainer.json",
35149- "jsconfig.json",
35150- "language-configuration.json",
35151- "tsconfig.json",
35152- "tslint.json"
35153- ],
35154- "language_id": 423
35155- },
35156- "JSON5": {
35157- "type": "data",
35158- "color": "#267CB9",
35159- "extensions": [
35160- ".json5"
35161- ],
35162- "tm_scope": "source.js",
35163- "ace_mode": "javascript",
35164- "codemirror_mode": "javascript",
35165- "codemirror_mime_type": "application/json",
35166- "language_id": 175
35167- },
35168- "JSONLD": {
35169- "type": "data",
35170- "color": "#0c479c",
35171- "extensions": [
35172- ".jsonld"
35173- ],
35174- "tm_scope": "source.js",
35175- "ace_mode": "javascript",
35176- "codemirror_mode": "javascript",
35177- "codemirror_mime_type": "application/json",
35178- "language_id": 176
35179- },
35180- "JSONiq": {
35181- "color": "#40d47e",
35182- "type": "programming",
35183- "ace_mode": "jsoniq",
35184- "codemirror_mode": "javascript",
35185- "codemirror_mime_type": "application/json",
35186- "extensions": [
35187- ".jq"
35188- ],
35189- "tm_scope": "source.jsoniq",
35190- "language_id": 177
35191- },
35192- "Janet": {
35193- "type": "programming",
35194- "color": "#0886a5",
35195- "extensions": [
35196- ".janet"
35197- ],
35198- "tm_scope": "source.janet",
35199- "ace_mode": "scheme",
35200- "codemirror_mode": "scheme",
35201- "codemirror_mime_type": "text/x-scheme",
35202- "interpreters": [
35203- "janet"
35204- ],
35205- "language_id": 1028705371
35206- },
35207- "Jasmin": {
35208- "type": "programming",
35209- "color": "#d03600",
35210- "ace_mode": "java",
35211- "extensions": [
35212- ".j"
35213- ],
35214- "tm_scope": "source.jasmin",
35215- "language_id": 180
35216- },
35217- "Java": {
35218- "type": "programming",
35219- "tm_scope": "source.java",
35220- "ace_mode": "java",
35221- "codemirror_mode": "clike",
35222- "codemirror_mime_type": "text/x-java",
35223- "color": "#b07219",
35224- "extensions": [
35225- ".java",
35226- ".jav",
35227- ".jsh"
35228- ],
35229- "language_id": 181
35230- },
35231- "Java Properties": {
35232- "type": "data",
35233- "color": "#2A6277",
35234- "extensions": [
35235- ".properties"
35236- ],
35237- "tm_scope": "source.java-properties",
35238- "ace_mode": "properties",
35239- "codemirror_mode": "properties",
35240- "codemirror_mime_type": "text/x-properties",
35241- "language_id": 519377561
35242- },
35243- "Java Server Pages": {
35244- "type": "programming",
35245- "color": "#2A6277",
35246- "group": "Java",
35247- "aliases": [
35248- "jsp"
35249- ],
35250- "extensions": [
35251- ".jsp",
35252- ".tag"
35253- ],
35254- "tm_scope": "text.html.jsp",
35255- "ace_mode": "jsp",
35256- "codemirror_mode": "htmlembedded",
35257- "codemirror_mime_type": "application/x-jsp",
35258- "language_id": 182
35259- },
35260- "JavaScript": {
35261- "type": "programming",
35262- "tm_scope": "source.js",
35263- "ace_mode": "javascript",
35264- "codemirror_mode": "javascript",
35265- "codemirror_mime_type": "text/javascript",
35266- "color": "#f1e05a",
35267- "aliases": [
35268- "js",
35269- "node"
35270- ],
35271- "extensions": [
35272- ".js",
35273- "._js",
35274- ".bones",
35275- ".cjs",
35276- ".es",
35277- ".es6",
35278- ".frag",
35279- ".gs",
35280- ".jake",
35281- ".javascript",
35282- ".jsb",
35283- ".jscad",
35284- ".jsfl",
35285- ".jslib",
35286- ".jsm",
35287- ".jspre",
35288- ".jss",
35289- ".jsx",
35290- ".mjs",
35291- ".njs",
35292- ".pac",
35293- ".sjs",
35294- ".ssjs",
35295- ".xsjs",
35296- ".xsjslib"
35297- ],
35298- "filenames": [
35299- "Jakefile"
35300- ],
35301- "interpreters": [
35302- "chakra",
35303- "d8",
35304- "gjs",
35305- "js",
35306- "node",
35307- "nodejs",
35308- "qjs",
35309- "rhino",
35310- "v8",
35311- "v8-shell"
35312- ],
35313- "language_id": 183
35314- },
35315- "JavaScript+ERB": {
35316- "type": "programming",
35317- "color": "#f1e05a",
35318- "tm_scope": "source.js",
35319- "group": "JavaScript",
35320- "extensions": [
35321- ".js.erb"
35322- ],
35323- "ace_mode": "javascript",
35324- "codemirror_mode": "javascript",
35325- "codemirror_mime_type": "application/javascript",
35326- "language_id": 914318960
35327- },
35328- "Jest Snapshot": {
35329- "type": "data",
35330- "color": "#15c213",
35331- "tm_scope": "source.jest.snap",
35332- "extensions": [
35333- ".snap"
35334- ],
35335- "ace_mode": "javascript",
35336- "codemirror_mode": "javascript",
35337- "codemirror_mime_type": "application/javascript",
35338- "language_id": 774635084
35339- },
35340- "JetBrains MPS": {
35341- "type": "programming",
35342- "aliases": [
35343- "mps"
35344- ],
35345- "color": "#21D789",
35346- "extensions": [
35347- ".mps",
35348- ".mpl",
35349- ".msd"
35350- ],
35351- "ace_mode": "xml",
35352- "codemirror_mode": "xml",
35353- "codemirror_mime_type": "text/xml",
35354- "tm_scope": "none",
35355- "language_id": 465165328
35356- },
35357- "Jinja": {
35358- "type": "markup",
35359- "color": "#a52a22",
35360- "aliases": [
35361- "django",
35362- "html+django",
35363- "html+jinja",
35364- "htmldjango"
35365- ],
35366- "extensions": [
35367- ".jinja",
35368- ".j2",
35369- ".jinja2"
35370- ],
35371- "tm_scope": "text.html.django",
35372- "ace_mode": "django",
35373- "codemirror_mode": "django",
35374- "codemirror_mime_type": "text/x-django",
35375- "language_id": 147
35376- },
35377- "Jison": {
35378- "type": "programming",
35379- "color": "#56b3cb",
35380- "group": "Yacc",
35381- "extensions": [
35382- ".jison"
35383- ],
35384- "tm_scope": "source.jison",
35385- "ace_mode": "text",
35386- "language_id": 284531423
35387- },
35388- "Jison Lex": {
35389- "type": "programming",
35390- "color": "#56b3cb",
35391- "group": "Lex",
35392- "extensions": [
35393- ".jisonlex"
35394- ],
35395- "tm_scope": "source.jisonlex",
35396- "ace_mode": "text",
35397- "language_id": 406395330
35398- },
35399- "Jolie": {
35400- "type": "programming",
35401- "extensions": [
35402- ".ol",
35403- ".iol"
35404- ],
35405- "interpreters": [
35406- "jolie"
35407- ],
35408- "color": "#843179",
35409- "ace_mode": "text",
35410- "tm_scope": "source.jolie",
35411- "language_id": 998078858
35412- },
35413- "Jsonnet": {
35414- "color": "#0064bd",
35415- "type": "programming",
35416- "ace_mode": "text",
35417- "extensions": [
35418- ".jsonnet",
35419- ".libsonnet"
35420- ],
35421- "tm_scope": "source.jsonnet",
35422- "language_id": 664885656
35423- },
35424- "Julia": {
35425- "type": "programming",
35426- "extensions": [
35427- ".jl"
35428- ],
35429- "interpreters": [
35430- "julia"
35431- ],
35432- "color": "#a270ba",
35433- "tm_scope": "source.julia",
35434- "ace_mode": "julia",
35435- "codemirror_mode": "julia",
35436- "codemirror_mime_type": "text/x-julia",
35437- "language_id": 184
35438- },
35439- "Jupyter Notebook": {
35440- "type": "markup",
35441- "ace_mode": "json",
35442- "codemirror_mode": "javascript",
35443- "codemirror_mime_type": "application/json",
35444- "tm_scope": "source.json",
35445- "color": "#DA5B0B",
35446- "extensions": [
35447- ".ipynb"
35448- ],
35449- "filenames": [
35450- "Notebook"
35451- ],
35452- "aliases": [
35453- "IPython Notebook"
35454- ],
35455- "language_id": 185
35456- },
35457- "Just": {
35458- "type": "programming",
35459- "aliases": [
35460- "Justfile"
35461- ],
35462- "color": "#384d54",
35463- "tm_scope": "source.just",
35464- "filenames": [
35465- "JUSTFILE",
35466- "Justfile",
35467- "justfile"
35468- ],
35469- "ace_mode": "text",
35470- "language_id": 128447695
35471- },
35472- "KRL": {
35473- "type": "programming",
35474- "color": "#28430A",
35475- "extensions": [
35476- ".krl"
35477- ],
35478- "tm_scope": "none",
35479- "ace_mode": "text",
35480- "language_id": 186
35481- },
35482- "Kaitai Struct": {
35483- "type": "programming",
35484- "aliases": [
35485- "ksy"
35486- ],
35487- "ace_mode": "yaml",
35488- "codemirror_mode": "yaml",
35489- "codemirror_mime_type": "text/x-yaml",
35490- "color": "#773b37",
35491- "extensions": [
35492- ".ksy"
35493- ],
35494- "tm_scope": "source.yaml",
35495- "language_id": 818804755
35496- },
35497- "KakouneScript": {
35498- "type": "programming",
35499- "color": "#6f8042",
35500- "tm_scope": "source.kakscript",
35501- "aliases": [
35502- "kak",
35503- "kakscript"
35504- ],
35505- "extensions": [
35506- ".kak"
35507- ],
35508- "filenames": [
35509- "kakrc"
35510- ],
35511- "ace_mode": "text",
35512- "language_id": 603336474
35513- },
35514- "KerboScript": {
35515- "type": "programming",
35516- "ace_mode": "text",
35517- "extensions": [
35518- ".ks"
35519- ],
35520- "color": "#41adf0",
35521- "tm_scope": "source.kerboscript",
35522- "language_id": 59716426
35523- },
35524- "KiCad Layout": {
35525- "type": "data",
35526- "color": "#2f4aab",
35527- "aliases": [
35528- "pcbnew"
35529- ],
35530- "extensions": [
35531- ".kicad_pcb",
35532- ".kicad_mod",
35533- ".kicad_wks"
35534- ],
35535- "filenames": [
35536- "fp-lib-table"
35537- ],
35538- "tm_scope": "source.pcb.sexp",
35539- "ace_mode": "lisp",
35540- "codemirror_mode": "commonlisp",
35541- "codemirror_mime_type": "text/x-common-lisp",
35542- "language_id": 187
35543- },
35544- "KiCad Legacy Layout": {
35545- "type": "data",
35546- "color": "#2f4aab",
35547- "extensions": [
35548- ".brd"
35549- ],
35550- "tm_scope": "source.pcb.board",
35551- "ace_mode": "text",
35552- "language_id": 140848857
35553- },
35554- "KiCad Schematic": {
35555- "type": "data",
35556- "color": "#2f4aab",
35557- "aliases": [
35558- "eeschema schematic"
35559- ],
35560- "extensions": [
35561- ".kicad_sch",
35562- ".sch"
35563- ],
35564- "tm_scope": "source.pcb.schematic",
35565- "ace_mode": "text",
35566- "language_id": 622447435
35567- },
35568- "Kickstart": {
35569- "type": "data",
35570- "ace_mode": "text",
35571- "extensions": [
35572- ".ks"
35573- ],
35574- "tm_scope": "source.kickstart",
35575- "language_id": 692635484
35576- },
35577- "Kit": {
35578- "type": "markup",
35579- "ace_mode": "html",
35580- "codemirror_mode": "htmlmixed",
35581- "codemirror_mime_type": "text/html",
35582- "extensions": [
35583- ".kit"
35584- ],
35585- "tm_scope": "text.html.basic",
35586- "language_id": 188
35587- },
35588- "Kotlin": {
35589- "type": "programming",
35590- "color": "#A97BFF",
35591- "extensions": [
35592- ".kt",
35593- ".ktm",
35594- ".kts"
35595- ],
35596- "tm_scope": "source.kotlin",
35597- "ace_mode": "text",
35598- "codemirror_mode": "clike",
35599- "codemirror_mime_type": "text/x-kotlin",
35600- "language_id": 189
35601- },
35602- "Kusto": {
35603- "type": "data",
35604- "extensions": [
35605- ".csl",
35606- ".kql"
35607- ],
35608- "tm_scope": "source.kusto",
35609- "ace_mode": "text",
35610- "language_id": 225697190
35611- },
35612- "LFE": {
35613- "type": "programming",
35614- "color": "#4C3023",
35615- "extensions": [
35616- ".lfe"
35617- ],
35618- "tm_scope": "source.lisp",
35619- "ace_mode": "lisp",
35620- "codemirror_mode": "commonlisp",
35621- "codemirror_mime_type": "text/x-common-lisp",
35622- "language_id": 190
35623- },
35624- "LLVM": {
35625- "type": "programming",
35626- "extensions": [
35627- ".ll"
35628- ],
35629- "tm_scope": "source.llvm",
35630- "ace_mode": "text",
35631- "color": "#185619",
35632- "language_id": 191
35633- },
35634- "LOLCODE": {
35635- "type": "programming",
35636- "extensions": [
35637- ".lol"
35638- ],
35639- "color": "#cc9900",
35640- "tm_scope": "source.lolcode",
35641- "ace_mode": "text",
35642- "language_id": 192
35643- },
35644- "LSL": {
35645- "type": "programming",
35646- "tm_scope": "source.lsl",
35647- "ace_mode": "lsl",
35648- "extensions": [
35649- ".lsl",
35650- ".lslp"
35651- ],
35652- "interpreters": [
35653- "lsl"
35654- ],
35655- "color": "#3d9970",
35656- "language_id": 193
35657- },
35658- "LTspice Symbol": {
35659- "type": "data",
35660- "extensions": [
35661- ".asy"
35662- ],
35663- "tm_scope": "source.ltspice.symbol",
35664- "ace_mode": "text",
35665- "codemirror_mode": "spreadsheet",
35666- "codemirror_mime_type": "text/x-spreadsheet",
35667- "language_id": 1013566805
35668- },
35669- "LabVIEW": {
35670- "type": "programming",
35671- "color": "#fede06",
35672- "extensions": [
35673- ".lvproj",
35674- ".lvclass",
35675- ".lvlib"
35676- ],
35677- "tm_scope": "text.xml",
35678- "ace_mode": "xml",
35679- "codemirror_mode": "xml",
35680- "codemirror_mime_type": "text/xml",
35681- "language_id": 194
35682- },
35683- "Lark": {
35684- "type": "data",
35685- "color": "#2980B9",
35686- "extensions": [
35687- ".lark"
35688- ],
35689- "tm_scope": "source.lark",
35690- "ace_mode": "text",
35691- "codemirror_mode": "ebnf",
35692- "codemirror_mime_type": "text/x-ebnf",
35693- "language_id": 758480799
35694- },
35695- "Lasso": {
35696- "type": "programming",
35697- "color": "#999999",
35698- "extensions": [
35699- ".lasso",
35700- ".las",
35701- ".lasso8",
35702- ".lasso9"
35703- ],
35704- "tm_scope": "file.lasso",
35705- "aliases": [
35706- "lassoscript"
35707- ],
35708- "ace_mode": "text",
35709- "language_id": 195
35710- },
35711- "Latte": {
35712- "type": "markup",
35713- "color": "#f2a542",
35714- "extensions": [
35715- ".latte"
35716- ],
35717- "tm_scope": "text.html.smarty",
35718- "ace_mode": "smarty",
35719- "codemirror_mode": "smarty",
35720- "codemirror_mime_type": "text/x-smarty",
35721- "language_id": 196
35722- },
35723- "Lean": {
35724- "type": "programming",
35725- "extensions": [
35726- ".lean",
35727- ".hlean"
35728- ],
35729- "tm_scope": "source.lean",
35730- "ace_mode": "text",
35731- "language_id": 197
35732- },
35733- "Lean 4": {
35734- "type": "programming",
35735- "group": "Lean",
35736- "extensions": [
35737- ".lean"
35738- ],
35739- "tm_scope": "source.lean4",
35740- "ace_mode": "text",
35741- "language_id": 455147478
35742- },
35743- "Less": {
35744- "type": "markup",
35745- "color": "#1d365d",
35746- "aliases": [
35747- "less-css"
35748- ],
35749- "extensions": [
35750- ".less"
35751- ],
35752- "tm_scope": "source.css.less",
35753- "ace_mode": "less",
35754- "codemirror_mode": "css",
35755- "codemirror_mime_type": "text/css",
35756- "language_id": 198
35757- },
35758- "Lex": {
35759- "type": "programming",
35760- "color": "#DBCA00",
35761- "aliases": [
35762- "flex"
35763- ],
35764- "extensions": [
35765- ".l",
35766- ".lex"
35767- ],
35768- "filenames": [
35769- "Lexer.x",
35770- "lexer.x"
35771- ],
35772- "tm_scope": "source.lex",
35773- "ace_mode": "text",
35774- "language_id": 199
35775- },
35776- "LigoLANG": {
35777- "type": "programming",
35778- "color": "#0e74ff",
35779- "extensions": [
35780- ".ligo"
35781- ],
35782- "tm_scope": "source.ligo",
35783- "ace_mode": "pascal",
35784- "codemirror_mode": "pascal",
35785- "codemirror_mime_type": "text/x-pascal",
35786- "group": "LigoLANG",
35787- "language_id": 1040646257
35788- },
35789- "LilyPond": {
35790- "type": "programming",
35791- "color": "#9ccc7c",
35792- "extensions": [
35793- ".ly",
35794- ".ily"
35795- ],
35796- "tm_scope": "source.lilypond",
35797- "ace_mode": "text",
35798- "language_id": 200
35799- },
35800- "Limbo": {
35801- "type": "programming",
35802- "extensions": [
35803- ".b",
35804- ".m"
35805- ],
35806- "tm_scope": "none",
35807- "ace_mode": "text",
35808- "language_id": 201
35809- },
35810- "Linker Script": {
35811- "type": "data",
35812- "extensions": [
35813- ".ld",
35814- ".lds",
35815- ".x"
35816- ],
35817- "filenames": [
35818- "ld.script"
35819- ],
35820- "tm_scope": "none",
35821- "ace_mode": "text",
35822- "language_id": 202
35823- },
35824- "Linux Kernel Module": {
35825- "type": "data",
35826- "extensions": [
35827- ".mod"
35828- ],
35829- "tm_scope": "none",
35830- "ace_mode": "text",
35831- "language_id": 203
35832- },
35833- "Liquid": {
35834- "type": "markup",
35835- "color": "#67b8de",
35836- "extensions": [
35837- ".liquid"
35838- ],
35839- "tm_scope": "text.html.liquid",
35840- "ace_mode": "liquid",
35841- "language_id": 204
35842- },
35843- "Literate Agda": {
35844- "type": "programming",
35845- "color": "#315665",
35846- "group": "Agda",
35847- "extensions": [
35848- ".lagda"
35849- ],
35850- "tm_scope": "none",
35851- "ace_mode": "text",
35852- "language_id": 205
35853- },
35854- "Literate CoffeeScript": {
35855- "type": "programming",
35856- "color": "#244776",
35857- "tm_scope": "source.litcoffee",
35858- "group": "CoffeeScript",
35859- "ace_mode": "text",
35860- "wrap": true,
35861- "aliases": [
35862- "litcoffee"
35863- ],
35864- "extensions": [
35865- ".litcoffee",
35866- ".coffee.md"
35867- ],
35868- "language_id": 206
35869- },
35870- "Literate Haskell": {
35871- "type": "programming",
35872- "color": "#5e5086",
35873- "group": "Haskell",
35874- "aliases": [
35875- "lhaskell",
35876- "lhs"
35877- ],
35878- "extensions": [
35879- ".lhs"
35880- ],
35881- "tm_scope": "text.tex.latex.haskell",
35882- "ace_mode": "text",
35883- "codemirror_mode": "haskell-literate",
35884- "codemirror_mime_type": "text/x-literate-haskell",
35885- "language_id": 207
35886- },
35887- "LiveScript": {
35888- "type": "programming",
35889- "color": "#499886",
35890- "aliases": [
35891- "live-script",
35892- "ls"
35893- ],
35894- "extensions": [
35895- ".ls",
35896- "._ls"
35897- ],
35898- "filenames": [
35899- "Slakefile"
35900- ],
35901- "tm_scope": "source.livescript",
35902- "ace_mode": "livescript",
35903- "codemirror_mode": "livescript",
35904- "codemirror_mime_type": "text/x-livescript",
35905- "language_id": 208
35906- },
35907- "Logos": {
35908- "type": "programming",
35909- "extensions": [
35910- ".xm",
35911- ".x",
35912- ".xi"
35913- ],
35914- "ace_mode": "text",
35915- "tm_scope": "source.logos",
35916- "language_id": 209
35917- },
35918- "Logtalk": {
35919- "type": "programming",
35920- "color": "#295b9a",
35921- "extensions": [
35922- ".lgt",
35923- ".logtalk"
35924- ],
35925- "tm_scope": "source.logtalk",
35926- "ace_mode": "text",
35927- "language_id": 210
35928- },
35929- "LookML": {
35930- "type": "programming",
35931- "ace_mode": "yaml",
35932- "codemirror_mode": "yaml",
35933- "codemirror_mime_type": "text/x-yaml",
35934- "color": "#652B81",
35935- "extensions": [
35936- ".lkml",
35937- ".lookml"
35938- ],
35939- "tm_scope": "source.yaml",
35940- "language_id": 211
35941- },
35942- "LoomScript": {
35943- "type": "programming",
35944- "extensions": [
35945- ".ls"
35946- ],
35947- "tm_scope": "source.loomscript",
35948- "ace_mode": "text",
35949- "language_id": 212
35950- },
35951- "Lua": {
35952- "type": "programming",
35953- "tm_scope": "source.lua",
35954- "ace_mode": "lua",
35955- "codemirror_mode": "lua",
35956- "codemirror_mime_type": "text/x-lua",
35957- "color": "#000080",
35958- "extensions": [
35959- ".lua",
35960- ".fcgi",
35961- ".nse",
35962- ".p8",
35963- ".pd_lua",
35964- ".rbxs",
35965- ".rockspec",
35966- ".wlua"
35967- ],
35968- "filenames": [
35969- ".luacheckrc"
35970- ],
35971- "interpreters": [
35972- "lua"
35973- ],
35974- "language_id": 213
35975- },
35976- "M": {
35977- "type": "programming",
35978- "aliases": [
35979- "mumps"
35980- ],
35981- "extensions": [
35982- ".mumps",
35983- ".m"
35984- ],
35985- "ace_mode": "text",
35986- "codemirror_mode": "mumps",
35987- "codemirror_mime_type": "text/x-mumps",
35988- "language_id": 214,
35989- "tm_scope": "none"
35990- },
35991- "M4": {
35992- "type": "programming",
35993- "extensions": [
35994- ".m4",
35995- ".mc"
35996- ],
35997- "tm_scope": "source.m4",
35998- "ace_mode": "text",
35999- "language_id": 215
36000- },
36001- "M4Sugar": {
36002- "type": "programming",
36003- "group": "M4",
36004- "aliases": [
36005- "autoconf"
36006- ],
36007- "extensions": [
36008- ".m4"
36009- ],
36010- "filenames": [
36011- "configure.ac"
36012- ],
36013- "tm_scope": "source.m4",
36014- "ace_mode": "text",
36015- "language_id": 216
36016- },
36017- "MATLAB": {
36018- "type": "programming",
36019- "color": "#e16737",
36020- "aliases": [
36021- "octave"
36022- ],
36023- "extensions": [
36024- ".matlab",
36025- ".m"
36026- ],
36027- "tm_scope": "source.matlab",
36028- "ace_mode": "matlab",
36029- "codemirror_mode": "octave",
36030- "codemirror_mime_type": "text/x-octave",
36031- "language_id": 225
36032- },
36033- "MAXScript": {
36034- "type": "programming",
36035- "color": "#00a6a6",
36036- "extensions": [
36037- ".ms",
36038- ".mcr"
36039- ],
36040- "tm_scope": "source.maxscript",
36041- "ace_mode": "text",
36042- "language_id": 217
36043- },
36044- "MDX": {
36045- "type": "markup",
36046- "color": "#fcb32c",
36047- "ace_mode": "markdown",
36048- "codemirror_mode": "gfm",
36049- "codemirror_mime_type": "text/x-gfm",
36050- "wrap": true,
36051- "extensions": [
36052- ".mdx"
36053- ],
36054- "tm_scope": "source.mdx",
36055- "language_id": 512838272
36056- },
36057- "MLIR": {
36058- "type": "programming",
36059- "color": "#5EC8DB",
36060- "extensions": [
36061- ".mlir"
36062- ],
36063- "tm_scope": "source.mlir",
36064- "ace_mode": "text",
36065- "language_id": 448253929
36066- },
36067- "MQL4": {
36068- "type": "programming",
36069- "color": "#62A8D6",
36070- "extensions": [
36071- ".mq4",
36072- ".mqh"
36073- ],
36074- "tm_scope": "source.mql5",
36075- "ace_mode": "c_cpp",
36076- "language_id": 426
36077- },
36078- "MQL5": {
36079- "type": "programming",
36080- "color": "#4A76B8",
36081- "extensions": [
36082- ".mq5",
36083- ".mqh"
36084- ],
36085- "tm_scope": "source.mql5",
36086- "ace_mode": "c_cpp",
36087- "language_id": 427
36088- },
36089- "MTML": {
36090- "type": "markup",
36091- "color": "#b7e1f4",
36092- "extensions": [
36093- ".mtml"
36094- ],
36095- "tm_scope": "text.html.basic",
36096- "ace_mode": "html",
36097- "codemirror_mode": "htmlmixed",
36098- "codemirror_mime_type": "text/html",
36099- "language_id": 218
36100- },
36101- "MUF": {
36102- "type": "programming",
36103- "group": "Forth",
36104- "extensions": [
36105- ".muf",
36106- ".m"
36107- ],
36108- "tm_scope": "none",
36109- "ace_mode": "forth",
36110- "codemirror_mode": "forth",
36111- "codemirror_mime_type": "text/x-forth",
36112- "language_id": 219
36113- },
36114- "Macaulay2": {
36115- "type": "programming",
36116- "extensions": [
36117- ".m2"
36118- ],
36119- "aliases": [
36120- "m2"
36121- ],
36122- "interpreters": [
36123- "M2"
36124- ],
36125- "ace_mode": "text",
36126- "tm_scope": "source.m2",
36127- "color": "#d8ffff",
36128- "language_id": 34167825
36129- },
36130- "Makefile": {
36131- "type": "programming",
36132- "color": "#427819",
36133- "aliases": [
36134- "bsdmake",
36135- "make",
36136- "mf"
36137- ],
36138- "extensions": [
36139- ".mak",
36140- ".d",
36141- ".make",
36142- ".makefile",
36143- ".mk",
36144- ".mkfile"
36145- ],
36146- "filenames": [
36147- "BSDmakefile",
36148- "GNUmakefile",
36149- "Kbuild",
36150- "Makefile",
36151- "Makefile.am",
36152- "Makefile.boot",
36153- "Makefile.frag",
36154- "Makefile.in",
36155- "Makefile.inc",
36156- "Makefile.wat",
36157- "makefile",
36158- "makefile.sco",
36159- "mkfile"
36160- ],
36161- "interpreters": [
36162- "make"
36163- ],
36164- "tm_scope": "source.makefile",
36165- "ace_mode": "makefile",
36166- "codemirror_mode": "cmake",
36167- "codemirror_mime_type": "text/x-cmake",
36168- "language_id": 220
36169- },
36170- "Mako": {
36171- "type": "programming",
36172- "color": "#7e858d",
36173- "extensions": [
36174- ".mako",
36175- ".mao"
36176- ],
36177- "tm_scope": "text.html.mako",
36178- "ace_mode": "text",
36179- "language_id": 221
36180- },
36181- "Markdown": {
36182- "type": "prose",
36183- "color": "#083fa1",
36184- "aliases": [
36185- "md",
36186- "pandoc"
36187- ],
36188- "ace_mode": "markdown",
36189- "codemirror_mode": "gfm",
36190- "codemirror_mime_type": "text/x-gfm",
36191- "wrap": true,
36192- "extensions": [
36193- ".md",
36194- ".livemd",
36195- ".markdown",
36196- ".mdown",
36197- ".mdwn",
36198- ".mkd",
36199- ".mkdn",
36200- ".mkdown",
36201- ".ronn",
36202- ".scd",
36203- ".workbook"
36204- ],
36205- "filenames": [
36206- "contents.lr"
36207- ],
36208- "tm_scope": "text.md",
36209- "language_id": 222
36210- },
36211- "Marko": {
36212- "type": "markup",
36213- "color": "#42bff2",
36214- "tm_scope": "text.marko",
36215- "extensions": [
36216- ".marko"
36217- ],
36218- "aliases": [
36219- "markojs"
36220- ],
36221- "ace_mode": "text",
36222- "codemirror_mode": "htmlmixed",
36223- "codemirror_mime_type": "text/html",
36224- "language_id": 932782397
36225- },
36226- "Mask": {
36227- "type": "markup",
36228- "color": "#f97732",
36229- "ace_mode": "mask",
36230- "extensions": [
36231- ".mask"
36232- ],
36233- "tm_scope": "source.mask",
36234- "language_id": 223
36235- },
36236- "Mathematica": {
36237- "type": "programming",
36238- "color": "#dd1100",
36239- "extensions": [
36240- ".mathematica",
36241- ".cdf",
36242- ".m",
36243- ".ma",
36244- ".mt",
36245- ".nb",
36246- ".nbp",
36247- ".wl",
36248- ".wlt"
36249- ],
36250- "aliases": [
36251- "mma",
36252- "wolfram",
36253- "wolfram language",
36254- "wolfram lang",
36255- "wl"
36256- ],
36257- "tm_scope": "source.mathematica",
36258- "ace_mode": "text",
36259- "codemirror_mode": "mathematica",
36260- "codemirror_mime_type": "text/x-mathematica",
36261- "language_id": 224
36262- },
36263- "Maven POM": {
36264- "type": "data",
36265- "group": "XML",
36266- "tm_scope": "text.xml.pom",
36267- "filenames": [
36268- "pom.xml"
36269- ],
36270- "ace_mode": "xml",
36271- "codemirror_mode": "xml",
36272- "codemirror_mime_type": "text/xml",
36273- "language_id": 226
36274- },
36275- "Max": {
36276- "type": "programming",
36277- "color": "#c4a79c",
36278- "aliases": [
36279- "max/msp",
36280- "maxmsp"
36281- ],
36282- "extensions": [
36283- ".maxpat",
36284- ".maxhelp",
36285- ".maxproj",
36286- ".mxt",
36287- ".pat"
36288- ],
36289- "tm_scope": "source.json",
36290- "ace_mode": "json",
36291- "codemirror_mode": "javascript",
36292- "codemirror_mime_type": "application/json",
36293- "language_id": 227
36294- },
36295- "Mercury": {
36296- "type": "programming",
36297- "color": "#ff2b2b",
36298- "ace_mode": "prolog",
36299- "interpreters": [
36300- "mmi"
36301- ],
36302- "extensions": [
36303- ".m",
36304- ".moo"
36305- ],
36306- "tm_scope": "source.mercury",
36307- "language_id": 229
36308- },
36309- "Mermaid": {
36310- "type": "markup",
36311- "color": "#ff3670",
36312- "aliases": [
36313- "mermaid example"
36314- ],
36315- "extensions": [
36316- ".mmd",
36317- ".mermaid"
36318- ],
36319- "tm_scope": "source.mermaid",
36320- "ace_mode": "text",
36321- "language_id": 385992043
36322- },
36323- "Meson": {
36324- "type": "programming",
36325- "color": "#007800",
36326- "filenames": [
36327- "meson.build",
36328- "meson_options.txt"
36329- ],
36330- "tm_scope": "source.meson",
36331- "ace_mode": "text",
36332- "language_id": 799141244
36333- },
36334- "Metal": {
36335- "type": "programming",
36336- "color": "#8f14e9",
36337- "extensions": [
36338- ".metal"
36339- ],
36340- "tm_scope": "source.c++",
36341- "ace_mode": "c_cpp",
36342- "codemirror_mode": "clike",
36343- "codemirror_mime_type": "text/x-c++src",
36344- "language_id": 230
36345- },
36346- "Microsoft Developer Studio Project": {
36347- "type": "data",
36348- "extensions": [
36349- ".dsp"
36350- ],
36351- "tm_scope": "none",
36352- "ace_mode": "text",
36353- "language_id": 800983837
36354- },
36355- "Microsoft Visual Studio Solution": {
36356- "type": "data",
36357- "extensions": [
36358- ".sln"
36359- ],
36360- "tm_scope": "source.solution",
36361- "ace_mode": "text",
36362- "language_id": 849523096
36363- },
36364- "MiniD": {
36365- "type": "programming",
36366- "extensions": [
36367- ".minid"
36368- ],
36369- "tm_scope": "none",
36370- "ace_mode": "text",
36371- "language_id": 231
36372- },
36373- "MiniYAML": {
36374- "type": "data",
36375- "color": "#ff1111",
36376- "tm_scope": "source.miniyaml",
36377- "extensions": [
36378- ".yaml",
36379- ".yml"
36380- ],
36381- "ace_mode": "yaml",
36382- "codemirror_mode": "yaml",
36383- "codemirror_mime_type": "text/x-yaml",
36384- "language_id": 4896465
36385- },
36386- "Mint": {
36387- "type": "programming",
36388- "extensions": [
36389- ".mint"
36390- ],
36391- "ace_mode": "text",
36392- "color": "#02b046",
36393- "tm_scope": "source.mint",
36394- "language_id": 968740319
36395- },
36396- "Mirah": {
36397- "type": "programming",
36398- "color": "#c7a938",
36399- "extensions": [
36400- ".druby",
36401- ".duby",
36402- ".mirah"
36403- ],
36404- "tm_scope": "source.ruby",
36405- "ace_mode": "ruby",
36406- "codemirror_mode": "ruby",
36407- "codemirror_mime_type": "text/x-ruby",
36408- "language_id": 232
36409- },
36410- "Modelica": {
36411- "type": "programming",
36412- "color": "#de1d31",
36413- "extensions": [
36414- ".mo"
36415- ],
36416- "tm_scope": "source.modelica",
36417- "ace_mode": "text",
36418- "codemirror_mode": "modelica",
36419- "codemirror_mime_type": "text/x-modelica",
36420- "language_id": 233
36421- },
36422- "Modula-2": {
36423- "type": "programming",
36424- "color": "#10253f",
36425- "extensions": [
36426- ".mod"
36427- ],
36428- "tm_scope": "source.modula2",
36429- "ace_mode": "text",
36430- "language_id": 234
36431- },
36432- "Modula-3": {
36433- "type": "programming",
36434- "extensions": [
36435- ".i3",
36436- ".ig",
36437- ".m3",
36438- ".mg"
36439- ],
36440- "color": "#223388",
36441- "ace_mode": "text",
36442- "tm_scope": "source.modula-3",
36443- "language_id": 564743864
36444- },
36445- "Module Management System": {
36446- "type": "programming",
36447- "extensions": [
36448- ".mms",
36449- ".mmk"
36450- ],
36451- "filenames": [
36452- "descrip.mmk",
36453- "descrip.mms"
36454- ],
36455- "tm_scope": "none",
36456- "ace_mode": "text",
36457- "language_id": 235
36458- },
36459- "Monkey": {
36460- "type": "programming",
36461- "extensions": [
36462- ".monkey",
36463- ".monkey2"
36464- ],
36465- "ace_mode": "text",
36466- "tm_scope": "source.monkey",
36467- "language_id": 236
36468- },
36469- "Monkey C": {
36470- "type": "programming",
36471- "color": "#8D6747",
36472- "extensions": [
36473- ".mc"
36474- ],
36475- "tm_scope": "source.mc",
36476- "ace_mode": "c_cpp",
36477- "codemirror_mode": "clike",
36478- "codemirror_mime_type": "text/x-csrc",
36479- "language_id": 231751931
36480- },
36481- "Moocode": {
36482- "type": "programming",
36483- "extensions": [
36484- ".moo"
36485- ],
36486- "tm_scope": "none",
36487- "ace_mode": "text",
36488- "language_id": 237
36489- },
36490- "MoonScript": {
36491- "type": "programming",
36492- "color": "#ff4585",
36493- "extensions": [
36494- ".moon"
36495- ],
36496- "interpreters": [
36497- "moon"
36498- ],
36499- "tm_scope": "source.moonscript",
36500- "ace_mode": "text",
36501- "language_id": 238
36502- },
36503- "Motoko": {
36504- "type": "programming",
36505- "color": "#fbb03b",
36506- "extensions": [
36507- ".mo"
36508- ],
36509- "tm_scope": "source.mo",
36510- "ace_mode": "text",
36511- "language_id": 202937027
36512- },
36513- "Motorola 68K Assembly": {
36514- "type": "programming",
36515- "color": "#005daa",
36516- "group": "Assembly",
36517- "aliases": [
36518- "m68k"
36519- ],
36520- "extensions": [
36521- ".asm",
36522- ".i",
36523- ".inc",
36524- ".s",
36525- ".x68"
36526- ],
36527- "tm_scope": "source.m68k",
36528- "ace_mode": "assembly_x86",
36529- "language_id": 477582706
36530- },
36531- "Move": {
36532- "type": "programming",
36533- "color": "#4a137a",
36534- "extensions": [
36535- ".move"
36536- ],
36537- "tm_scope": "source.move",
36538- "ace_mode": "text",
36539- "language_id": 638334599
36540- },
36541- "Muse": {
36542- "type": "prose",
36543- "extensions": [
36544- ".muse"
36545- ],
36546- "tm_scope": "text.muse",
36547- "ace_mode": "text",
36548- "wrap": true,
36549- "language_id": 474864066,
36550- "aliases": [
36551- "amusewiki",
36552- "emacs muse"
36553- ]
36554- },
36555- "Mustache": {
36556- "type": "markup",
36557- "color": "#724b3b",
36558- "extensions": [
36559- ".mustache"
36560- ],
36561- "tm_scope": "text.html.smarty",
36562- "ace_mode": "smarty",
36563- "codemirror_mode": "smarty",
36564- "codemirror_mime_type": "text/x-smarty",
36565- "language_id": 638334590
36566- },
36567- "Myghty": {
36568- "type": "programming",
36569- "extensions": [
36570- ".myt"
36571- ],
36572- "tm_scope": "none",
36573- "ace_mode": "text",
36574- "language_id": 239
36575- },
36576- "NASL": {
36577- "type": "programming",
36578- "extensions": [
36579- ".nasl",
36580- ".inc"
36581- ],
36582- "tm_scope": "source.nasl",
36583- "ace_mode": "text",
36584- "language_id": 171666519
36585- },
36586- "NCL": {
36587- "type": "programming",
36588- "color": "#28431f",
36589- "extensions": [
36590- ".ncl"
36591- ],
36592- "tm_scope": "source.ncl",
36593- "ace_mode": "text",
36594- "language_id": 240
36595- },
36596- "NEON": {
36597- "type": "data",
36598- "extensions": [
36599- ".neon"
36600- ],
36601- "tm_scope": "source.neon",
36602- "ace_mode": "text",
36603- "aliases": [
36604- "nette object notation",
36605- "ne-on"
36606- ],
36607- "language_id": 481192983
36608- },
36609- "NL": {
36610- "type": "data",
36611- "extensions": [
36612- ".nl"
36613- ],
36614- "tm_scope": "none",
36615- "ace_mode": "text",
36616- "language_id": 241
36617- },
36618- "NPM Config": {
36619- "type": "data",
36620- "color": "#cb3837",
36621- "group": "INI",
36622- "aliases": [
36623- "npmrc"
36624- ],
36625- "filenames": [
36626- ".npmrc"
36627- ],
36628- "tm_scope": "source.ini.npmrc",
36629- "ace_mode": "text",
36630- "language_id": 685022663
36631- },
36632- "NSIS": {
36633- "type": "programming",
36634- "extensions": [
36635- ".nsi",
36636- ".nsh"
36637- ],
36638- "tm_scope": "source.nsis",
36639- "ace_mode": "text",
36640- "codemirror_mode": "nsis",
36641- "codemirror_mime_type": "text/x-nsis",
36642- "language_id": 242
36643- },
36644- "NWScript": {
36645- "type": "programming",
36646- "color": "#111522",
36647- "extensions": [
36648- ".nss"
36649- ],
36650- "tm_scope": "source.c.nwscript",
36651- "ace_mode": "c_cpp",
36652- "codemirror_mode": "clike",
36653- "codemirror_mime_type": "text/x-csrc",
36654- "language_id": 731233819
36655- },
36656- "Nasal": {
36657- "type": "programming",
36658- "color": "#1d2c4e",
36659- "extensions": [
36660- ".nas"
36661- ],
36662- "tm_scope": "source.nasal",
36663- "ace_mode": "nasal",
36664- "language_id": 178322513
36665- },
36666- "Nearley": {
36667- "type": "programming",
36668- "ace_mode": "text",
36669- "color": "#990000",
36670- "extensions": [
36671- ".ne",
36672- ".nearley"
36673- ],
36674- "tm_scope": "source.ne",
36675- "language_id": 521429430
36676- },
36677- "Nemerle": {
36678- "type": "programming",
36679- "color": "#3d3c6e",
36680- "extensions": [
36681- ".n"
36682- ],
36683- "tm_scope": "source.nemerle",
36684- "ace_mode": "text",
36685- "language_id": 243
36686- },
36687- "NetLinx": {
36688- "type": "programming",
36689- "color": "#0aa0ff",
36690- "extensions": [
36691- ".axs",
36692- ".axi"
36693- ],
36694- "tm_scope": "source.netlinx",
36695- "ace_mode": "text",
36696- "language_id": 244
36697- },
36698- "NetLinx+ERB": {
36699- "type": "programming",
36700- "color": "#747faa",
36701- "extensions": [
36702- ".axs.erb",
36703- ".axi.erb"
36704- ],
36705- "tm_scope": "source.netlinx.erb",
36706- "ace_mode": "text",
36707- "language_id": 245
36708- },
36709- "NetLogo": {
36710- "type": "programming",
36711- "color": "#ff6375",
36712- "extensions": [
36713- ".nlogo"
36714- ],
36715- "tm_scope": "source.lisp",
36716- "ace_mode": "lisp",
36717- "codemirror_mode": "commonlisp",
36718- "codemirror_mime_type": "text/x-common-lisp",
36719- "language_id": 246
36720- },
36721- "NewLisp": {
36722- "type": "programming",
36723- "color": "#87AED7",
36724- "extensions": [
36725- ".nl",
36726- ".lisp",
36727- ".lsp"
36728- ],
36729- "interpreters": [
36730- "newlisp"
36731- ],
36732- "tm_scope": "source.lisp",
36733- "ace_mode": "lisp",
36734- "codemirror_mode": "commonlisp",
36735- "codemirror_mime_type": "text/x-common-lisp",
36736- "language_id": 247
36737- },
36738- "Nextflow": {
36739- "type": "programming",
36740- "ace_mode": "groovy",
36741- "tm_scope": "source.nextflow",
36742- "color": "#3ac486",
36743- "extensions": [
36744- ".nf"
36745- ],
36746- "filenames": [
36747- "nextflow.config"
36748- ],
36749- "interpreters": [
36750- "nextflow"
36751- ],
36752- "language_id": 506780613
36753- },
36754- "Nginx": {
36755- "type": "data",
36756- "color": "#009639",
36757- "extensions": [
36758- ".nginx",
36759- ".nginxconf",
36760- ".vhost"
36761- ],
36762- "filenames": [
36763- "nginx.conf"
36764- ],
36765- "tm_scope": "source.nginx",
36766- "aliases": [
36767- "nginx configuration file"
36768- ],
36769- "ace_mode": "text",
36770- "codemirror_mode": "nginx",
36771- "codemirror_mime_type": "text/x-nginx-conf",
36772- "language_id": 248
36773- },
36774- "Nim": {
36775- "type": "programming",
36776- "color": "#ffc200",
36777- "extensions": [
36778- ".nim",
36779- ".nim.cfg",
36780- ".nimble",
36781- ".nimrod",
36782- ".nims"
36783- ],
36784- "filenames": [
36785- "nim.cfg"
36786- ],
36787- "ace_mode": "text",
36788- "tm_scope": "source.nim",
36789- "language_id": 249
36790- },
36791- "Ninja": {
36792- "type": "data",
36793- "tm_scope": "source.ninja",
36794- "extensions": [
36795- ".ninja"
36796- ],
36797- "ace_mode": "text",
36798- "language_id": 250
36799- },
36800- "Nit": {
36801- "type": "programming",
36802- "color": "#009917",
36803- "extensions": [
36804- ".nit"
36805- ],
36806- "tm_scope": "source.nit",
36807- "ace_mode": "text",
36808- "language_id": 251
36809- },
36810- "Nix": {
36811- "type": "programming",
36812- "color": "#7e7eff",
36813- "extensions": [
36814- ".nix"
36815- ],
36816- "aliases": [
36817- "nixos"
36818- ],
36819- "tm_scope": "source.nix",
36820- "ace_mode": "nix",
36821- "language_id": 252
36822- },
36823- "Nu": {
36824- "type": "programming",
36825- "color": "#c9df40",
36826- "aliases": [
36827- "nush"
36828- ],
36829- "extensions": [
36830- ".nu"
36831- ],
36832- "filenames": [
36833- "Nukefile"
36834- ],
36835- "tm_scope": "source.nu",
36836- "ace_mode": "scheme",
36837- "codemirror_mode": "scheme",
36838- "codemirror_mime_type": "text/x-scheme",
36839- "interpreters": [
36840- "nush"
36841- ],
36842- "language_id": 253
36843- },
36844- "NumPy": {
36845- "type": "programming",
36846- "color": "#9C8AF9",
36847- "group": "Python",
36848- "extensions": [
36849- ".numpy",
36850- ".numpyw",
36851- ".numsc"
36852- ],
36853- "tm_scope": "none",
36854- "ace_mode": "text",
36855- "codemirror_mode": "python",
36856- "codemirror_mime_type": "text/x-python",
36857- "language_id": 254
36858- },
36859- "Nunjucks": {
36860- "type": "markup",
36861- "color": "#3d8137",
36862- "extensions": [
36863- ".njk"
36864- ],
36865- "aliases": [
36866- "njk"
36867- ],
36868- "tm_scope": "text.html.nunjucks",
36869- "ace_mode": "nunjucks",
36870- "language_id": 461856962
36871- },
36872- "Nushell": {
36873- "type": "programming",
36874- "color": "#4E9906",
36875- "extensions": [
36876- ".nu"
36877- ],
36878- "interpreters": [
36879- "nu"
36880- ],
36881- "aliases": [
36882- "nu-script",
36883- "nushell-script"
36884- ],
36885- "tm_scope": "source.nushell",
36886- "ace_mode": "sh",
36887- "codemirror_mode": "shell",
36888- "codemirror_mime_type": "text/x-sh",
36889- "language_id": 446573572
36890- },
36891- "OASv2-json": {
36892- "type": "data",
36893- "color": "#85ea2d",
36894- "extensions": [
36895- ".json"
36896- ],
36897- "group": "OpenAPI Specification v2",
36898- "tm_scope": "source.json",
36899- "ace_mode": "json",
36900- "codemirror_mode": "javascript",
36901- "codemirror_mime_type": "application/json",
36902- "language_id": 834374816
36903- },
36904- "OASv2-yaml": {
36905- "type": "data",
36906- "color": "#85ea2d",
36907- "extensions": [
36908- ".yaml",
36909- ".yml"
36910- ],
36911- "group": "OpenAPI Specification v2",
36912- "tm_scope": "source.yaml",
36913- "ace_mode": "yaml",
36914- "codemirror_mode": "yaml",
36915- "codemirror_mime_type": "text/x-yaml",
36916- "language_id": 105187618
36917- },
36918- "OASv3-json": {
36919- "type": "data",
36920- "color": "#85ea2d",
36921- "extensions": [
36922- ".json"
36923- ],
36924- "group": "OpenAPI Specification v3",
36925- "tm_scope": "source.json",
36926- "ace_mode": "json",
36927- "codemirror_mode": "javascript",
36928- "codemirror_mime_type": "application/json",
36929- "language_id": 980062566
36930- },
36931- "OASv3-yaml": {
36932- "type": "data",
36933- "color": "#85ea2d",
36934- "extensions": [
36935- ".yaml",
36936- ".yml"
36937- ],
36938- "group": "OpenAPI Specification v3",
36939- "tm_scope": "source.yaml",
36940- "ace_mode": "yaml",
36941- "codemirror_mode": "yaml",
36942- "codemirror_mime_type": "text/x-yaml",
36943- "language_id": 51239111
36944- },
36945- "OCaml": {
36946- "type": "programming",
36947- "ace_mode": "ocaml",
36948- "codemirror_mode": "mllike",
36949- "codemirror_mime_type": "text/x-ocaml",
36950- "color": "#ef7a08",
36951- "extensions": [
36952- ".ml",
36953- ".eliom",
36954- ".eliomi",
36955- ".ml4",
36956- ".mli",
36957- ".mll",
36958- ".mly"
36959- ],
36960- "interpreters": [
36961- "ocaml",
36962- "ocamlrun",
36963- "ocamlscript"
36964- ],
36965- "tm_scope": "source.ocaml",
36966- "language_id": 255
36967- },
36968- "ObjDump": {
36969- "type": "data",
36970- "extensions": [
36971- ".objdump"
36972- ],
36973- "tm_scope": "objdump.x86asm",
36974- "ace_mode": "assembly_x86",
36975- "language_id": 256
36976- },
36977- "Object Data Instance Notation": {
36978- "type": "data",
36979- "extensions": [
36980- ".odin"
36981- ],
36982- "tm_scope": "source.odin-ehr",
36983- "ace_mode": "text",
36984- "language_id": 985227236
36985- },
36986- "ObjectScript": {
36987- "type": "programming",
36988- "extensions": [
36989- ".cls"
36990- ],
36991- "language_id": 202735509,
36992- "tm_scope": "source.objectscript",
36993- "color": "#424893",
36994- "ace_mode": "text"
36995- },
36996- "Objective-C": {
36997- "type": "programming",
36998- "tm_scope": "source.objc",
36999- "color": "#438eff",
37000- "aliases": [
37001- "obj-c",
37002- "objc",
37003- "objectivec"
37004- ],
37005- "extensions": [
37006- ".m",
37007- ".h"
37008- ],
37009- "ace_mode": "objectivec",
37010- "codemirror_mode": "clike",
37011- "codemirror_mime_type": "text/x-objectivec",
37012- "language_id": 257
37013- },
37014- "Objective-C++": {
37015- "type": "programming",
37016- "tm_scope": "source.objc++",
37017- "color": "#6866fb",
37018- "aliases": [
37019- "obj-c++",
37020- "objc++",
37021- "objectivec++"
37022- ],
37023- "extensions": [
37024- ".mm"
37025- ],
37026- "ace_mode": "objectivec",
37027- "codemirror_mode": "clike",
37028- "codemirror_mime_type": "text/x-objectivec",
37029- "language_id": 258
37030- },
37031- "Objective-J": {
37032- "type": "programming",
37033- "color": "#ff0c5a",
37034- "aliases": [
37035- "obj-j",
37036- "objectivej",
37037- "objj"
37038- ],
37039- "extensions": [
37040- ".j",
37041- ".sj"
37042- ],
37043- "tm_scope": "source.js.objj",
37044- "ace_mode": "text",
37045- "language_id": 259
37046- },
37047- "Odin": {
37048- "type": "programming",
37049- "color": "#60AFFE",
37050- "aliases": [
37051- "odinlang",
37052- "odin-lang"
37053- ],
37054- "extensions": [
37055- ".odin"
37056- ],
37057- "tm_scope": "source.odin",
37058- "ace_mode": "text",
37059- "language_id": 889244082
37060- },
37061- "Omgrofl": {
37062- "type": "programming",
37063- "extensions": [
37064- ".omgrofl"
37065- ],
37066- "color": "#cabbff",
37067- "tm_scope": "none",
37068- "ace_mode": "text",
37069- "language_id": 260
37070- },
37071- "Opa": {
37072- "type": "programming",
37073- "extensions": [
37074- ".opa"
37075- ],
37076- "tm_scope": "source.opa",
37077- "ace_mode": "text",
37078- "language_id": 261
37079- },
37080- "Opal": {
37081- "type": "programming",
37082- "color": "#f7ede0",
37083- "extensions": [
37084- ".opal"
37085- ],
37086- "tm_scope": "source.opal",
37087- "ace_mode": "text",
37088- "language_id": 262
37089- },
37090- "Open Policy Agent": {
37091- "type": "programming",
37092- "color": "#7d9199",
37093- "ace_mode": "text",
37094- "extensions": [
37095- ".rego"
37096- ],
37097- "language_id": 840483232,
37098- "tm_scope": "source.rego"
37099- },
37100- "OpenAPI Specification v2": {
37101- "aliases": [
37102- "oasv2"
37103- ],
37104- "type": "data",
37105- "color": "#85ea2d",
37106- "tm_scope": "none",
37107- "ace_mode": "text",
37108- "language_id": 848295328
37109- },
37110- "OpenAPI Specification v3": {
37111- "aliases": [
37112- "oasv3"
37113- ],
37114- "type": "data",
37115- "color": "#85ea2d",
37116- "tm_scope": "none",
37117- "ace_mode": "text",
37118- "language_id": 557959099
37119- },
37120- "OpenCL": {
37121- "type": "programming",
37122- "color": "#ed2e2d",
37123- "group": "C",
37124- "extensions": [
37125- ".cl",
37126- ".opencl"
37127- ],
37128- "tm_scope": "source.c",
37129- "ace_mode": "c_cpp",
37130- "codemirror_mode": "clike",
37131- "codemirror_mime_type": "text/x-csrc",
37132- "language_id": 263
37133- },
37134- "OpenEdge ABL": {
37135- "type": "programming",
37136- "color": "#5ce600",
37137- "aliases": [
37138- "progress",
37139- "openedge",
37140- "abl"
37141- ],
37142- "extensions": [
37143- ".p",
37144- ".cls",
37145- ".w"
37146- ],
37147- "tm_scope": "source.abl",
37148- "ace_mode": "text",
37149- "language_id": 264
37150- },
37151- "OpenQASM": {
37152- "type": "programming",
37153- "extensions": [
37154- ".qasm"
37155- ],
37156- "color": "#AA70FF",
37157- "tm_scope": "source.qasm",
37158- "ace_mode": "text",
37159- "language_id": 153739399
37160- },
37161- "OpenRC runscript": {
37162- "type": "programming",
37163- "group": "Shell",
37164- "aliases": [
37165- "openrc"
37166- ],
37167- "interpreters": [
37168- "openrc-run"
37169- ],
37170- "tm_scope": "source.shell",
37171- "ace_mode": "sh",
37172- "codemirror_mode": "shell",
37173- "codemirror_mime_type": "text/x-sh",
37174- "language_id": 265
37175- },
37176- "OpenSCAD": {
37177- "type": "programming",
37178- "color": "#e5cd45",
37179- "extensions": [
37180- ".scad"
37181- ],
37182- "tm_scope": "source.scad",
37183- "ace_mode": "scad",
37184- "language_id": 266
37185- },
37186- "OpenStep Property List": {
37187- "type": "data",
37188- "extensions": [
37189- ".plist",
37190- ".glyphs"
37191- ],
37192- "tm_scope": "source.plist",
37193- "ace_mode": "text",
37194- "language_id": 598917541
37195- },
37196- "OpenType Feature File": {
37197- "type": "data",
37198- "aliases": [
37199- "AFDKO"
37200- ],
37201- "extensions": [
37202- ".fea"
37203- ],
37204- "tm_scope": "source.opentype",
37205- "ace_mode": "text",
37206- "language_id": 374317347
37207- },
37208- "Option List": {
37209- "type": "data",
37210- "color": "#476732",
37211- "aliases": [
37212- "opts",
37213- "ackrc"
37214- ],
37215- "filenames": [
37216- ".ackrc",
37217- ".rspec",
37218- ".yardopts",
37219- "ackrc",
37220- "mocha.opts"
37221- ],
37222- "tm_scope": "source.opts",
37223- "ace_mode": "sh",
37224- "codemirror_mode": "shell",
37225- "codemirror_mime_type": "text/x-sh",
37226- "language_id": 723589315
37227- },
37228- "Org": {
37229- "type": "prose",
37230- "color": "#77aa99",
37231- "wrap": true,
37232- "extensions": [
37233- ".org"
37234- ],
37235- "tm_scope": "none",
37236- "ace_mode": "text",
37237- "language_id": 267
37238- },
37239- "Ox": {
37240- "type": "programming",
37241- "extensions": [
37242- ".ox",
37243- ".oxh",
37244- ".oxo"
37245- ],
37246- "tm_scope": "source.ox",
37247- "ace_mode": "text",
37248- "language_id": 268
37249- },
37250- "Oxygene": {
37251- "type": "programming",
37252- "color": "#cdd0e3",
37253- "extensions": [
37254- ".oxygene"
37255- ],
37256- "tm_scope": "none",
37257- "ace_mode": "text",
37258- "language_id": 269
37259- },
37260- "Oz": {
37261- "type": "programming",
37262- "color": "#fab738",
37263- "extensions": [
37264- ".oz"
37265- ],
37266- "tm_scope": "source.oz",
37267- "ace_mode": "text",
37268- "codemirror_mode": "oz",
37269- "codemirror_mime_type": "text/x-oz",
37270- "language_id": 270
37271- },
37272- "P4": {
37273- "type": "programming",
37274- "color": "#7055b5",
37275- "extensions": [
37276- ".p4"
37277- ],
37278- "tm_scope": "source.p4",
37279- "ace_mode": "text",
37280- "language_id": 348895984
37281- },
37282- "PDDL": {
37283- "type": "programming",
37284- "color": "#0d00ff",
37285- "extensions": [
37286- ".pddl"
37287- ],
37288- "tm_scope": "source.pddl",
37289- "ace_mode": "text",
37290- "language_id": 736235603
37291- },
37292- "PEG.js": {
37293- "type": "programming",
37294- "color": "#234d6b",
37295- "extensions": [
37296- ".pegjs"
37297- ],
37298- "tm_scope": "source.pegjs",
37299- "ace_mode": "javascript",
37300- "codemirror_mode": "javascript",
37301- "codemirror_mime_type": "text/javascript",
37302- "language_id": 81442128
37303- },
37304- "PHP": {
37305- "type": "programming",
37306- "tm_scope": "text.html.php",
37307- "ace_mode": "php",
37308- "codemirror_mode": "php",
37309- "codemirror_mime_type": "application/x-httpd-php",
37310- "color": "#4F5D95",
37311- "extensions": [
37312- ".php",
37313- ".aw",
37314- ".ctp",
37315- ".fcgi",
37316- ".inc",
37317- ".php3",
37318- ".php4",
37319- ".php5",
37320- ".phps",
37321- ".phpt"
37322- ],
37323- "filenames": [
37324- ".php",
37325- ".php_cs",
37326- ".php_cs.dist",
37327- "Phakefile"
37328- ],
37329- "interpreters": [
37330- "php"
37331- ],
37332- "aliases": [
37333- "inc"
37334- ],
37335- "language_id": 272
37336- },
37337- "PLSQL": {
37338- "type": "programming",
37339- "ace_mode": "sql",
37340- "codemirror_mode": "sql",
37341- "codemirror_mime_type": "text/x-plsql",
37342- "tm_scope": "none",
37343- "color": "#dad8d8",
37344- "extensions": [
37345- ".pls",
37346- ".bdy",
37347- ".ddl",
37348- ".fnc",
37349- ".pck",
37350- ".pkb",
37351- ".pks",
37352- ".plb",
37353- ".plsql",
37354- ".prc",
37355- ".spc",
37356- ".sql",
37357- ".tpb",
37358- ".tps",
37359- ".trg",
37360- ".vw"
37361- ],
37362- "language_id": 273
37363- },
37364- "PLpgSQL": {
37365- "type": "programming",
37366- "color": "#336790",
37367- "ace_mode": "pgsql",
37368- "codemirror_mode": "sql",
37369- "codemirror_mime_type": "text/x-sql",
37370- "tm_scope": "source.sql",
37371- "extensions": [
37372- ".pgsql",
37373- ".sql"
37374- ],
37375- "language_id": 274
37376- },
37377- "POV-Ray SDL": {
37378- "type": "programming",
37379- "color": "#6bac65",
37380- "aliases": [
37381- "pov-ray",
37382- "povray"
37383- ],
37384- "extensions": [
37385- ".pov",
37386- ".inc"
37387- ],
37388- "tm_scope": "source.pov-ray sdl",
37389- "ace_mode": "text",
37390- "language_id": 275
37391- },
37392- "Pact": {
37393- "type": "programming",
37394- "color": "#F7A8B8",
37395- "ace_mode": "text",
37396- "tm_scope": "source.pact",
37397- "extensions": [
37398- ".pact"
37399- ],
37400- "language_id": 756774415
37401- },
37402- "Pan": {
37403- "type": "programming",
37404- "color": "#cc0000",
37405- "extensions": [
37406- ".pan"
37407- ],
37408- "tm_scope": "source.pan",
37409- "ace_mode": "text",
37410- "language_id": 276
37411- },
37412- "Papyrus": {
37413- "type": "programming",
37414- "color": "#6600cc",
37415- "extensions": [
37416- ".psc"
37417- ],
37418- "tm_scope": "source.papyrus.skyrim",
37419- "ace_mode": "text",
37420- "language_id": 277
37421- },
37422- "Parrot": {
37423- "type": "programming",
37424- "color": "#f3ca0a",
37425- "extensions": [
37426- ".parrot"
37427- ],
37428- "tm_scope": "none",
37429- "ace_mode": "text",
37430- "language_id": 278
37431- },
37432- "Parrot Assembly": {
37433- "group": "Parrot",
37434- "type": "programming",
37435- "aliases": [
37436- "pasm"
37437- ],
37438- "extensions": [
37439- ".pasm"
37440- ],
37441- "interpreters": [
37442- "parrot"
37443- ],
37444- "tm_scope": "none",
37445- "ace_mode": "text",
37446- "language_id": 279
37447- },
37448- "Parrot Internal Representation": {
37449- "group": "Parrot",
37450- "tm_scope": "source.parrot.pir",
37451- "type": "programming",
37452- "aliases": [
37453- "pir"
37454- ],
37455- "extensions": [
37456- ".pir"
37457- ],
37458- "interpreters": [
37459- "parrot"
37460- ],
37461- "ace_mode": "text",
37462- "language_id": 280
37463- },
37464- "Pascal": {
37465- "type": "programming",
37466- "color": "#E3F171",
37467- "aliases": [
37468- "delphi",
37469- "objectpascal"
37470- ],
37471- "extensions": [
37472- ".pas",
37473- ".dfm",
37474- ".dpr",
37475- ".inc",
37476- ".lpr",
37477- ".pascal",
37478- ".pp"
37479- ],
37480- "interpreters": [
37481- "instantfpc"
37482- ],
37483- "tm_scope": "source.pascal",
37484- "ace_mode": "pascal",
37485- "codemirror_mode": "pascal",
37486- "codemirror_mime_type": "text/x-pascal",
37487- "language_id": 281
37488- },
37489- "Pawn": {
37490- "type": "programming",
37491- "color": "#dbb284",
37492- "extensions": [
37493- ".pwn",
37494- ".inc",
37495- ".sma"
37496- ],
37497- "tm_scope": "source.pawn",
37498- "ace_mode": "text",
37499- "language_id": 271
37500- },
37501- "Pep8": {
37502- "type": "programming",
37503- "color": "#C76F5B",
37504- "extensions": [
37505- ".pep"
37506- ],
37507- "ace_mode": "text",
37508- "tm_scope": "source.pep8",
37509- "language_id": 840372442
37510- },
37511- "Perl": {
37512- "type": "programming",
37513- "tm_scope": "source.perl",
37514- "ace_mode": "perl",
37515- "codemirror_mode": "perl",
37516- "codemirror_mime_type": "text/x-perl",
37517- "color": "#0298c3",
37518- "extensions": [
37519- ".pl",
37520- ".al",
37521- ".cgi",
37522- ".fcgi",
37523- ".perl",
37524- ".ph",
37525- ".plx",
37526- ".pm",
37527- ".psgi",
37528- ".t"
37529- ],
37530- "filenames": [
37531- ".latexmkrc",
37532- "Makefile.PL",
37533- "Rexfile",
37534- "ack",
37535- "cpanfile",
37536- "latexmkrc"
37537- ],
37538- "interpreters": [
37539- "cperl",
37540- "perl"
37541- ],
37542- "aliases": [
37543- "cperl"
37544- ],
37545- "language_id": 282
37546- },
37547- "Pic": {
37548- "type": "markup",
37549- "group": "Roff",
37550- "tm_scope": "source.pic",
37551- "extensions": [
37552- ".pic",
37553- ".chem"
37554- ],
37555- "aliases": [
37556- "pikchr"
37557- ],
37558- "ace_mode": "text",
37559- "codemirror_mode": "troff",
37560- "codemirror_mime_type": "text/troff",
37561- "language_id": 425
37562- },
37563- "Pickle": {
37564- "type": "data",
37565- "extensions": [
37566- ".pkl"
37567- ],
37568- "tm_scope": "none",
37569- "ace_mode": "text",
37570- "language_id": 284
37571- },
37572- "PicoLisp": {
37573- "type": "programming",
37574- "color": "#6067af",
37575- "extensions": [
37576- ".l"
37577- ],
37578- "interpreters": [
37579- "picolisp",
37580- "pil"
37581- ],
37582- "tm_scope": "source.lisp",
37583- "ace_mode": "lisp",
37584- "language_id": 285
37585- },
37586- "PigLatin": {
37587- "type": "programming",
37588- "color": "#fcd7de",
37589- "extensions": [
37590- ".pig"
37591- ],
37592- "tm_scope": "source.pig_latin",
37593- "ace_mode": "text",
37594- "language_id": 286
37595- },
37596- "Pike": {
37597- "type": "programming",
37598- "color": "#005390",
37599- "extensions": [
37600- ".pike",
37601- ".pmod"
37602- ],
37603- "interpreters": [
37604- "pike"
37605- ],
37606- "tm_scope": "source.pike",
37607- "ace_mode": "text",
37608- "language_id": 287
37609- },
37610- "PlantUML": {
37611- "type": "data",
37612- "color": "#fbbd16",
37613- "extensions": [
37614- ".puml",
37615- ".iuml",
37616- ".plantuml"
37617- ],
37618- "tm_scope": "source.wsd",
37619- "ace_mode": "text",
37620- "language_id": 833504686
37621- },
37622- "Pod": {
37623- "type": "prose",
37624- "ace_mode": "perl",
37625- "codemirror_mode": "perl",
37626- "codemirror_mime_type": "text/x-perl",
37627- "wrap": true,
37628- "extensions": [
37629- ".pod"
37630- ],
37631- "interpreters": [
37632- "perl"
37633- ],
37634- "tm_scope": "none",
37635- "language_id": 288
37636- },
37637- "Pod 6": {
37638- "type": "prose",
37639- "ace_mode": "perl",
37640- "tm_scope": "source.raku",
37641- "wrap": true,
37642- "extensions": [
37643- ".pod",
37644- ".pod6"
37645- ],
37646- "interpreters": [
37647- "perl6"
37648- ],
37649- "language_id": 155357471
37650- },
37651- "PogoScript": {
37652- "type": "programming",
37653- "color": "#d80074",
37654- "extensions": [
37655- ".pogo"
37656- ],
37657- "tm_scope": "source.pogoscript",
37658- "ace_mode": "text",
37659- "language_id": 289
37660- },
37661- "Polar": {
37662- "type": "programming",
37663- "color": "#ae81ff",
37664- "extensions": [
37665- ".polar"
37666- ],
37667- "tm_scope": "source.polar",
37668- "ace_mode": "text",
37669- "language_id": 839112914
37670- },
37671- "Pony": {
37672- "type": "programming",
37673- "extensions": [
37674- ".pony"
37675- ],
37676- "tm_scope": "source.pony",
37677- "ace_mode": "text",
37678- "language_id": 290
37679- },
37680- "Portugol": {
37681- "type": "programming",
37682- "color": "#f8bd00",
37683- "extensions": [
37684- ".por"
37685- ],
37686- "tm_scope": "source.portugol",
37687- "ace_mode": "text",
37688- "language_id": 832391833
37689- },
37690- "PostCSS": {
37691- "type": "markup",
37692- "color": "#dc3a0c",
37693- "tm_scope": "source.postcss",
37694- "group": "CSS",
37695- "extensions": [
37696- ".pcss",
37697- ".postcss"
37698- ],
37699- "ace_mode": "text",
37700- "language_id": 262764437
37701- },
37702- "PostScript": {
37703- "type": "markup",
37704- "color": "#da291c",
37705- "extensions": [
37706- ".ps",
37707- ".eps",
37708- ".epsi",
37709- ".pfa"
37710- ],
37711- "tm_scope": "source.postscript",
37712- "aliases": [
37713- "postscr"
37714- ],
37715- "ace_mode": "text",
37716- "language_id": 291
37717- },
37718- "PowerBuilder": {
37719- "type": "programming",
37720- "color": "#8f0f8d",
37721- "extensions": [
37722- ".pbt",
37723- ".sra",
37724- ".sru",
37725- ".srw"
37726- ],
37727- "tm_scope": "none",
37728- "ace_mode": "text",
37729- "language_id": 292
37730- },
37731- "PowerShell": {
37732- "type": "programming",
37733- "color": "#012456",
37734- "tm_scope": "source.powershell",
37735- "ace_mode": "powershell",
37736- "codemirror_mode": "powershell",
37737- "codemirror_mime_type": "application/x-powershell",
37738- "aliases": [
37739- "posh",
37740- "pwsh"
37741- ],
37742- "extensions": [
37743- ".ps1",
37744- ".psd1",
37745- ".psm1"
37746- ],
37747- "interpreters": [
37748- "pwsh"
37749- ],
37750- "language_id": 293
37751- },
37752- "Praat": {
37753- "type": "programming",
37754- "color": "#c8506d",
37755- "tm_scope": "source.praat",
37756- "ace_mode": "praat",
37757- "extensions": [
37758- ".praat"
37759- ],
37760- "language_id": 106029007
37761- },
37762- "Prisma": {
37763- "type": "data",
37764- "color": "#0c344b",
37765- "extensions": [
37766- ".prisma"
37767- ],
37768- "tm_scope": "source.prisma",
37769- "ace_mode": "text",
37770- "language_id": 499933428
37771- },
37772- "Processing": {
37773- "type": "programming",
37774- "color": "#0096D8",
37775- "extensions": [
37776- ".pde"
37777- ],
37778- "tm_scope": "source.processing",
37779- "ace_mode": "text",
37780- "language_id": 294
37781- },
37782- "Procfile": {
37783- "type": "programming",
37784- "color": "#3B2F63",
37785- "filenames": [
37786- "Procfile"
37787- ],
37788- "tm_scope": "source.procfile",
37789- "ace_mode": "batchfile",
37790- "language_id": 305313959
37791- },
37792- "Proguard": {
37793- "type": "data",
37794- "extensions": [
37795- ".pro"
37796- ],
37797- "tm_scope": "none",
37798- "ace_mode": "text",
37799- "language_id": 716513858
37800- },
37801- "Prolog": {
37802- "type": "programming",
37803- "color": "#74283c",
37804- "extensions": [
37805- ".pl",
37806- ".plt",
37807- ".pro",
37808- ".prolog",
37809- ".yap"
37810- ],
37811- "interpreters": [
37812- "swipl",
37813- "yap"
37814- ],
37815- "tm_scope": "source.prolog",
37816- "ace_mode": "prolog",
37817- "language_id": 295
37818- },
37819- "Promela": {
37820- "type": "programming",
37821- "color": "#de0000",
37822- "tm_scope": "source.promela",
37823- "ace_mode": "text",
37824- "extensions": [
37825- ".pml"
37826- ],
37827- "language_id": 441858312
37828- },
37829- "Propeller Spin": {
37830- "type": "programming",
37831- "color": "#7fa2a7",
37832- "extensions": [
37833- ".spin"
37834- ],
37835- "tm_scope": "source.spin",
37836- "ace_mode": "text",
37837- "language_id": 296
37838- },
37839- "Protocol Buffer": {
37840- "type": "data",
37841- "aliases": [
37842- "proto",
37843- "protobuf",
37844- "Protocol Buffers"
37845- ],
37846- "extensions": [
37847- ".proto"
37848- ],
37849- "tm_scope": "source.proto",
37850- "ace_mode": "protobuf",
37851- "codemirror_mode": "protobuf",
37852- "codemirror_mime_type": "text/x-protobuf",
37853- "language_id": 297
37854- },
37855- "Protocol Buffer Text Format": {
37856- "type": "data",
37857- "aliases": [
37858- "text proto",
37859- "protobuf text format"
37860- ],
37861- "extensions": [
37862- ".textproto",
37863- ".pbt",
37864- ".pbtxt"
37865- ],
37866- "tm_scope": "source.textproto",
37867- "ace_mode": "text",
37868- "language_id": 436568854
37869- },
37870- "Public Key": {
37871- "type": "data",
37872- "extensions": [
37873- ".asc",
37874- ".pub"
37875- ],
37876- "tm_scope": "none",
37877- "ace_mode": "text",
37878- "codemirror_mode": "asciiarmor",
37879- "codemirror_mime_type": "application/pgp",
37880- "language_id": 298
37881- },
37882- "Pug": {
37883- "type": "markup",
37884- "color": "#a86454",
37885- "extensions": [
37886- ".jade",
37887- ".pug"
37888- ],
37889- "tm_scope": "text.jade",
37890- "ace_mode": "jade",
37891- "codemirror_mode": "pug",
37892- "codemirror_mime_type": "text/x-pug",
37893- "language_id": 179
37894- },
37895- "Puppet": {
37896- "type": "programming",
37897- "color": "#302B6D",
37898- "extensions": [
37899- ".pp"
37900- ],
37901- "filenames": [
37902- "Modulefile"
37903- ],
37904- "ace_mode": "text",
37905- "codemirror_mode": "puppet",
37906- "codemirror_mime_type": "text/x-puppet",
37907- "tm_scope": "source.puppet",
37908- "language_id": 299
37909- },
37910- "Pure Data": {
37911- "type": "data",
37912- "extensions": [
37913- ".pd"
37914- ],
37915- "tm_scope": "none",
37916- "ace_mode": "text",
37917- "language_id": 300
37918- },
37919- "PureBasic": {
37920- "type": "programming",
37921- "color": "#5a6986",
37922- "extensions": [
37923- ".pb",
37924- ".pbi"
37925- ],
37926- "tm_scope": "none",
37927- "ace_mode": "text",
37928- "language_id": 301
37929- },
37930- "PureScript": {
37931- "type": "programming",
37932- "color": "#1D222D",
37933- "extensions": [
37934- ".purs"
37935- ],
37936- "tm_scope": "source.purescript",
37937- "ace_mode": "haskell",
37938- "codemirror_mode": "haskell",
37939- "codemirror_mime_type": "text/x-haskell",
37940- "language_id": 302
37941- },
37942- "Pyret": {
37943- "type": "programming",
37944- "color": "#ee1e10",
37945- "extensions": [
37946- ".arr"
37947- ],
37948- "ace_mode": "python",
37949- "tm_scope": "source.arr",
37950- "language_id": 252961827
37951- },
37952- "Python": {
37953- "type": "programming",
37954- "tm_scope": "source.python",
37955- "ace_mode": "python",
37956- "codemirror_mode": "python",
37957- "codemirror_mime_type": "text/x-python",
37958- "color": "#3572A5",
37959- "extensions": [
37960- ".py",
37961- ".cgi",
37962- ".fcgi",
37963- ".gyp",
37964- ".gypi",
37965- ".lmi",
37966- ".py3",
37967- ".pyde",
37968- ".pyi",
37969- ".pyp",
37970- ".pyt",
37971- ".pyw",
37972- ".rpy",
37973- ".spec",
37974- ".tac",
37975- ".wsgi",
37976- ".xpy"
37977- ],
37978- "filenames": [
37979- ".gclient",
37980- "DEPS",
37981- "SConscript",
37982- "SConstruct",
37983- "wscript"
37984- ],
37985- "interpreters": [
37986- "python",
37987- "python2",
37988- "python3",
37989- "py",
37990- "pypy",
37991- "pypy3"
37992- ],
37993- "aliases": [
37994- "python3",
37995- "rusthon"
37996- ],
37997- "language_id": 303
37998- },
37999- "Python console": {
38000- "type": "programming",
38001- "color": "#3572A5",
38002- "group": "Python",
38003- "aliases": [
38004- "pycon"
38005- ],
38006- "tm_scope": "text.python.console",
38007- "ace_mode": "text",
38008- "language_id": 428
38009- },
38010- "Python traceback": {
38011- "type": "data",
38012- "color": "#3572A5",
38013- "group": "Python",
38014- "extensions": [
38015- ".pytb"
38016- ],
38017- "tm_scope": "text.python.traceback",
38018- "ace_mode": "text",
38019- "language_id": 304
38020- },
38021- "Q#": {
38022- "type": "programming",
38023- "extensions": [
38024- ".qs"
38025- ],
38026- "aliases": [
38027- "qsharp"
38028- ],
38029- "color": "#fed659",
38030- "ace_mode": "text",
38031- "tm_scope": "source.qsharp",
38032- "language_id": 697448245
38033- },
38034- "QML": {
38035- "type": "programming",
38036- "color": "#44a51c",
38037- "extensions": [
38038- ".qml",
38039- ".qbs"
38040- ],
38041- "tm_scope": "source.qml",
38042- "ace_mode": "text",
38043- "language_id": 305
38044- },
38045- "QMake": {
38046- "type": "programming",
38047- "extensions": [
38048- ".pro",
38049- ".pri"
38050- ],
38051- "interpreters": [
38052- "qmake"
38053- ],
38054- "tm_scope": "source.qmake",
38055- "ace_mode": "text",
38056- "language_id": 306
38057- },
38058- "Qt Script": {
38059- "type": "programming",
38060- "ace_mode": "javascript",
38061- "codemirror_mode": "javascript",
38062- "codemirror_mime_type": "text/javascript",
38063- "extensions": [
38064- ".qs"
38065- ],
38066- "filenames": [
38067- "installscript.qs",
38068- "toolchain_installscript.qs"
38069- ],
38070- "color": "#00b841",
38071- "tm_scope": "source.js",
38072- "language_id": 558193693
38073- },
38074- "Quake": {
38075- "type": "programming",
38076- "filenames": [
38077- "m3makefile",
38078- "m3overrides"
38079- ],
38080- "color": "#882233",
38081- "ace_mode": "text",
38082- "tm_scope": "source.quake",
38083- "language_id": 375265331
38084- },
38085- "R": {
38086- "type": "programming",
38087- "color": "#198CE7",
38088- "aliases": [
38089- "R",
38090- "Rscript",
38091- "splus"
38092- ],
38093- "extensions": [
38094- ".r",
38095- ".rd",
38096- ".rsx"
38097- ],
38098- "filenames": [
38099- ".Rprofile",
38100- "expr-dist"
38101- ],
38102- "interpreters": [
38103- "Rscript"
38104- ],
38105- "tm_scope": "source.r",
38106- "ace_mode": "r",
38107- "codemirror_mode": "r",
38108- "codemirror_mime_type": "text/x-rsrc",
38109- "language_id": 307
38110- },
38111- "RAML": {
38112- "type": "markup",
38113- "ace_mode": "yaml",
38114- "codemirror_mode": "yaml",
38115- "codemirror_mime_type": "text/x-yaml",
38116- "tm_scope": "source.yaml",
38117- "color": "#77d9fb",
38118- "extensions": [
38119- ".raml"
38120- ],
38121- "language_id": 308
38122- },
38123- "RBS": {
38124- "type": "data",
38125- "ace_mode": "ruby",
38126- "codemirror_mode": "ruby",
38127- "codemirror_mime_type": "text/x-ruby",
38128- "extensions": [
38129- ".rbs"
38130- ],
38131- "color": "#701516",
38132- "tm_scope": "source.rbs",
38133- "group": "Ruby",
38134- "language_id": 899227493
38135- },
38136- "RDoc": {
38137- "type": "prose",
38138- "color": "#701516",
38139- "ace_mode": "rdoc",
38140- "wrap": true,
38141- "extensions": [
38142- ".rdoc"
38143- ],
38144- "tm_scope": "text.rdoc",
38145- "language_id": 309
38146- },
38147- "REALbasic": {
38148- "type": "programming",
38149- "extensions": [
38150- ".rbbas",
38151- ".rbfrm",
38152- ".rbmnu",
38153- ".rbres",
38154- ".rbtbar",
38155- ".rbuistate"
38156- ],
38157- "tm_scope": "source.vbnet",
38158- "ace_mode": "text",
38159- "language_id": 310
38160- },
38161- "REXX": {
38162- "type": "programming",
38163- "color": "#d90e09",
38164- "aliases": [
38165- "arexx"
38166- ],
38167- "extensions": [
38168- ".rexx",
38169- ".pprx",
38170- ".rex"
38171- ],
38172- "interpreters": [
38173- "regina",
38174- "rexx"
38175- ],
38176- "tm_scope": "source.rexx",
38177- "ace_mode": "text",
38178- "language_id": 311
38179- },
38180- "RMarkdown": {
38181- "type": "prose",
38182- "color": "#198ce7",
38183- "wrap": true,
38184- "ace_mode": "markdown",
38185- "codemirror_mode": "gfm",
38186- "codemirror_mime_type": "text/x-gfm",
38187- "extensions": [
38188- ".qmd",
38189- ".rmd"
38190- ],
38191- "tm_scope": "text.md",
38192- "language_id": 313
38193- },
38194- "RPC": {
38195- "type": "programming",
38196- "aliases": [
38197- "rpcgen",
38198- "oncrpc",
38199- "xdr"
38200- ],
38201- "ace_mode": "c_cpp",
38202- "extensions": [
38203- ".x"
38204- ],
38205- "tm_scope": "source.c",
38206- "language_id": 1031374237
38207- },
38208- "RPGLE": {
38209- "type": "programming",
38210- "ace_mode": "text",
38211- "color": "#2BDE21",
38212- "aliases": [
38213- "ile rpg",
38214- "sqlrpgle"
38215- ],
38216- "extensions": [
38217- ".rpgle",
38218- ".sqlrpgle"
38219- ],
38220- "tm_scope": "source.rpgle",
38221- "language_id": 609977990
38222- },
38223- "RPM Spec": {
38224- "type": "data",
38225- "tm_scope": "source.rpm-spec",
38226- "extensions": [
38227- ".spec"
38228- ],
38229- "aliases": [
38230- "specfile"
38231- ],
38232- "ace_mode": "text",
38233- "codemirror_mode": "rpm",
38234- "codemirror_mime_type": "text/x-rpm-spec",
38235- "language_id": 314
38236- },
38237- "RUNOFF": {
38238- "type": "markup",
38239- "color": "#665a4e",
38240- "extensions": [
38241- ".rnh",
38242- ".rno"
38243- ],
38244- "wrap": true,
38245- "tm_scope": "text.runoff",
38246- "ace_mode": "text",
38247- "language_id": 315
38248- },
38249- "Racket": {
38250- "type": "programming",
38251- "color": "#3c5caa",
38252- "extensions": [
38253- ".rkt",
38254- ".rktd",
38255- ".rktl",
38256- ".scrbl"
38257- ],
38258- "interpreters": [
38259- "racket"
38260- ],
38261- "tm_scope": "source.racket",
38262- "ace_mode": "lisp",
38263- "language_id": 316
38264- },
38265- "Ragel": {
38266- "type": "programming",
38267- "color": "#9d5200",
38268- "extensions": [
38269- ".rl"
38270- ],
38271- "aliases": [
38272- "ragel-rb",
38273- "ragel-ruby"
38274- ],
38275- "tm_scope": "none",
38276- "ace_mode": "text",
38277- "language_id": 317
38278- },
38279- "Raku": {
38280- "type": "programming",
38281- "color": "#0000fb",
38282- "extensions": [
38283- ".6pl",
38284- ".6pm",
38285- ".nqp",
38286- ".p6",
38287- ".p6l",
38288- ".p6m",
38289- ".pl",
38290- ".pl6",
38291- ".pm",
38292- ".pm6",
38293- ".raku",
38294- ".rakumod",
38295- ".t"
38296- ],
38297- "interpreters": [
38298- "perl6",
38299- "raku",
38300- "rakudo"
38301- ],
38302- "aliases": [
38303- "perl6",
38304- "perl-6"
38305- ],
38306- "tm_scope": "source.raku",
38307- "ace_mode": "perl",
38308- "codemirror_mode": "perl",
38309- "codemirror_mime_type": "text/x-perl",
38310- "language_id": 283
38311- },
38312- "Rascal": {
38313- "type": "programming",
38314- "color": "#fffaa0",
38315- "extensions": [
38316- ".rsc"
38317- ],
38318- "tm_scope": "source.rascal",
38319- "ace_mode": "text",
38320- "language_id": 173616037
38321- },
38322- "Raw token data": {
38323- "type": "data",
38324- "aliases": [
38325- "raw"
38326- ],
38327- "extensions": [
38328- ".raw"
38329- ],
38330- "tm_scope": "none",
38331- "ace_mode": "text",
38332- "language_id": 318
38333- },
38334- "ReScript": {
38335- "type": "programming",
38336- "color": "#ed5051",
38337- "ace_mode": "rust",
38338- "codemirror_mode": "rust",
38339- "codemirror_mime_type": "text/x-rustsrc",
38340- "extensions": [
38341- ".res"
38342- ],
38343- "interpreters": [
38344- "ocaml"
38345- ],
38346- "tm_scope": "source.rescript",
38347- "language_id": 501875647
38348- },
38349- "Readline Config": {
38350- "type": "data",
38351- "group": "INI",
38352- "aliases": [
38353- "inputrc",
38354- "readline"
38355- ],
38356- "filenames": [
38357- ".inputrc",
38358- "inputrc"
38359- ],
38360- "tm_scope": "source.inputrc",
38361- "ace_mode": "text",
38362- "language_id": 538732839
38363- },
38364- "Reason": {
38365- "type": "programming",
38366- "color": "#ff5847",
38367- "ace_mode": "rust",
38368- "codemirror_mode": "rust",
38369- "codemirror_mime_type": "text/x-rustsrc",
38370- "extensions": [
38371- ".re",
38372- ".rei"
38373- ],
38374- "tm_scope": "source.reason",
38375- "language_id": 869538413
38376- },
38377- "ReasonLIGO": {
38378- "type": "programming",
38379- "color": "#ff5847",
38380- "ace_mode": "rust",
38381- "codemirror_mode": "rust",
38382- "codemirror_mime_type": "text/x-rustsrc",
38383- "group": "LigoLANG",
38384- "extensions": [
38385- ".religo"
38386- ],
38387- "tm_scope": "source.religo",
38388- "language_id": 319002153
38389- },
38390- "Rebol": {
38391- "type": "programming",
38392- "color": "#358a5b",
38393- "extensions": [
38394- ".reb",
38395- ".r",
38396- ".r2",
38397- ".r3",
38398- ".rebol"
38399- ],
38400- "ace_mode": "text",
38401- "tm_scope": "source.rebol",
38402- "language_id": 319
38403- },
38404- "Record Jar": {
38405- "type": "data",
38406- "filenames": [
38407- "language-subtag-registry.txt"
38408- ],
38409- "tm_scope": "source.record-jar",
38410- "codemirror_mode": "properties",
38411- "codemirror_mime_type": "text/x-properties",
38412- "ace_mode": "text",
38413- "color": "#0673ba",
38414- "language_id": 865765202
38415- },
38416- "Red": {
38417- "type": "programming",
38418- "color": "#f50000",
38419- "extensions": [
38420- ".red",
38421- ".reds"
38422- ],
38423- "aliases": [
38424- "red/system"
38425- ],
38426- "tm_scope": "source.red",
38427- "ace_mode": "text",
38428- "language_id": 320
38429- },
38430- "Redcode": {
38431- "type": "programming",
38432- "extensions": [
38433- ".cw"
38434- ],
38435- "tm_scope": "none",
38436- "ace_mode": "text",
38437- "language_id": 321
38438- },
38439- "Redirect Rules": {
38440- "type": "data",
38441- "aliases": [
38442- "redirects"
38443- ],
38444- "filenames": [
38445- "_redirects"
38446- ],
38447- "tm_scope": "source.redirects",
38448- "ace_mode": "text",
38449- "language_id": 1020148948
38450- },
38451- "Regular Expression": {
38452- "type": "data",
38453- "color": "#009a00",
38454- "extensions": [
38455- ".regexp",
38456- ".regex"
38457- ],
38458- "aliases": [
38459- "regexp",
38460- "regex"
38461- ],
38462- "ace_mode": "text",
38463- "tm_scope": "source.regexp",
38464- "language_id": 363378884
38465- },
38466- "Ren'Py": {
38467- "type": "programming",
38468- "aliases": [
38469- "renpy"
38470- ],
38471- "color": "#ff7f7f",
38472- "extensions": [
38473- ".rpy"
38474- ],
38475- "tm_scope": "source.renpy",
38476- "ace_mode": "python",
38477- "language_id": 322
38478- },
38479- "RenderScript": {
38480- "type": "programming",
38481- "extensions": [
38482- ".rs",
38483- ".rsh"
38484- ],
38485- "tm_scope": "none",
38486- "ace_mode": "text",
38487- "language_id": 323
38488- },
38489- "Rez": {
38490- "type": "programming",
38491- "extensions": [
38492- ".r"
38493- ],
38494- "tm_scope": "source.rez",
38495- "ace_mode": "text",
38496- "color": "#FFDAB3",
38497- "language_id": 498022874
38498- },
38499- "Rich Text Format": {
38500- "type": "markup",
38501- "extensions": [
38502- ".rtf"
38503- ],
38504- "tm_scope": "text.rtf",
38505- "ace_mode": "text",
38506- "language_id": 51601661
38507- },
38508- "Ring": {
38509- "type": "programming",
38510- "color": "#2D54CB",
38511- "extensions": [
38512- ".ring"
38513- ],
38514- "tm_scope": "source.ring",
38515- "ace_mode": "text",
38516- "language_id": 431
38517- },
38518- "Riot": {
38519- "type": "markup",
38520- "color": "#A71E49",
38521- "ace_mode": "html",
38522- "extensions": [
38523- ".riot"
38524- ],
38525- "tm_scope": "text.html.riot",
38526- "language_id": 878396783
38527- },
38528- "RobotFramework": {
38529- "type": "programming",
38530- "color": "#00c0b5",
38531- "extensions": [
38532- ".robot"
38533- ],
38534- "tm_scope": "text.robot",
38535- "ace_mode": "text",
38536- "language_id": 324
38537- },
38538- "Roff": {
38539- "type": "markup",
38540- "color": "#ecdebe",
38541- "extensions": [
38542- ".roff",
38543- ".1",
38544- ".1in",
38545- ".1m",
38546- ".1x",
38547- ".2",
38548- ".3",
38549- ".3in",
38550- ".3m",
38551- ".3p",
38552- ".3pm",
38553- ".3qt",
38554- ".3x",
38555- ".4",
38556- ".5",
38557- ".6",
38558- ".7",
38559- ".8",
38560- ".9",
38561- ".l",
38562- ".man",
38563- ".mdoc",
38564- ".me",
38565- ".ms",
38566- ".n",
38567- ".nr",
38568- ".rno",
38569- ".tmac"
38570- ],
38571- "filenames": [
38572- "eqnrc",
38573- "mmn",
38574- "mmt",
38575- "troffrc",
38576- "troffrc-end"
38577- ],
38578- "tm_scope": "text.roff",
38579- "aliases": [
38580- "groff",
38581- "man",
38582- "manpage",
38583- "man page",
38584- "man-page",
38585- "mdoc",
38586- "nroff",
38587- "troff"
38588- ],
38589- "wrap": true,
38590- "ace_mode": "text",
38591- "codemirror_mode": "troff",
38592- "codemirror_mime_type": "text/troff",
38593- "language_id": 141
38594- },
38595- "Roff Manpage": {
38596- "type": "markup",
38597- "color": "#ecdebe",
38598- "group": "Roff",
38599- "extensions": [
38600- ".1",
38601- ".1in",
38602- ".1m",
38603- ".1x",
38604- ".2",
38605- ".3",
38606- ".3in",
38607- ".3m",
38608- ".3p",
38609- ".3pm",
38610- ".3qt",
38611- ".3x",
38612- ".4",
38613- ".5",
38614- ".6",
38615- ".7",
38616- ".8",
38617- ".9",
38618- ".man",
38619- ".mdoc"
38620- ],
38621- "wrap": true,
38622- "tm_scope": "text.roff",
38623- "ace_mode": "text",
38624- "codemirror_mode": "troff",
38625- "codemirror_mime_type": "text/troff",
38626- "language_id": 612669833
38627- },
38628- "Rouge": {
38629- "type": "programming",
38630- "ace_mode": "clojure",
38631- "codemirror_mode": "clojure",
38632- "codemirror_mime_type": "text/x-clojure",
38633- "color": "#cc0088",
38634- "extensions": [
38635- ".rg"
38636- ],
38637- "tm_scope": "source.clojure",
38638- "language_id": 325
38639- },
38640- "RouterOS Script": {
38641- "type": "programming",
38642- "ace_mode": "text",
38643- "extensions": [
38644- ".rsc"
38645- ],
38646- "interpreters": [
38647- "RouterOS"
38648- ],
38649- "color": "#DE3941",
38650- "tm_scope": "none",
38651- "language_id": 592853203
38652- },
38653- "Ruby": {
38654- "type": "programming",
38655- "tm_scope": "source.ruby",
38656- "ace_mode": "ruby",
38657- "codemirror_mode": "ruby",
38658- "codemirror_mime_type": "text/x-ruby",
38659- "color": "#701516",
38660- "aliases": [
38661- "jruby",
38662- "macruby",
38663- "rake",
38664- "rb",
38665- "rbx"
38666- ],
38667- "extensions": [
38668- ".rb",
38669- ".builder",
38670- ".eye",
38671- ".fcgi",
38672- ".gemspec",
38673- ".god",
38674- ".jbuilder",
38675- ".mspec",
38676- ".pluginspec",
38677- ".podspec",
38678- ".prawn",
38679- ".rabl",
38680- ".rake",
38681- ".rbi",
38682- ".rbuild",
38683- ".rbw",
38684- ".rbx",
38685- ".ru",
38686- ".ruby",
38687- ".spec",
38688- ".thor",
38689- ".watchr"
38690- ],
38691- "interpreters": [
38692- "ruby",
38693- "macruby",
38694- "rake",
38695- "jruby",
38696- "rbx"
38697- ],
38698- "filenames": [
38699- ".irbrc",
38700- ".pryrc",
38701- ".simplecov",
38702- "Appraisals",
38703- "Berksfile",
38704- "Brewfile",
38705- "Buildfile",
38706- "Capfile",
38707- "Dangerfile",
38708- "Deliverfile",
38709- "Fastfile",
38710- "Gemfile",
38711- "Guardfile",
38712- "Jarfile",
38713- "Mavenfile",
38714- "Podfile",
38715- "Puppetfile",
38716- "Rakefile",
38717- "Snapfile",
38718- "Steepfile",
38719- "Thorfile",
38720- "Vagrantfile",
38721- "buildfile"
38722- ],
38723- "language_id": 326
38724- },
38725- "Rust": {
38726- "type": "programming",
38727- "aliases": [
38728- "rs"
38729- ],
38730- "color": "#dea584",
38731- "extensions": [
38732- ".rs",
38733- ".rs.in"
38734- ],
38735- "tm_scope": "source.rust",
38736- "ace_mode": "rust",
38737- "codemirror_mode": "rust",
38738- "codemirror_mime_type": "text/x-rustsrc",
38739- "interpreters": [
38740- "rust-script"
38741- ],
38742- "language_id": 327
38743- },
38744- "SAS": {
38745- "type": "programming",
38746- "color": "#B34936",
38747- "extensions": [
38748- ".sas"
38749- ],
38750- "tm_scope": "source.sas",
38751- "ace_mode": "text",
38752- "codemirror_mode": "sas",
38753- "codemirror_mime_type": "text/x-sas",
38754- "language_id": 328
38755- },
38756- "SCSS": {
38757- "type": "markup",
38758- "color": "#c6538c",
38759- "tm_scope": "source.css.scss",
38760- "ace_mode": "scss",
38761- "codemirror_mode": "css",
38762- "codemirror_mime_type": "text/x-scss",
38763- "extensions": [
38764- ".scss"
38765- ],
38766- "language_id": 329
38767- },
38768- "SELinux Policy": {
38769- "aliases": [
38770- "SELinux Kernel Policy Language",
38771- "sepolicy"
38772- ],
38773- "type": "data",
38774- "tm_scope": "source.sepolicy",
38775- "extensions": [
38776- ".te"
38777- ],
38778- "filenames": [
38779- "file_contexts",
38780- "genfs_contexts",
38781- "initial_sids",
38782- "port_contexts",
38783- "security_classes"
38784- ],
38785- "ace_mode": "text",
38786- "language_id": 880010326
38787- },
38788- "SMT": {
38789- "type": "programming",
38790- "extensions": [
38791- ".smt2",
38792- ".smt"
38793- ],
38794- "interpreters": [
38795- "boolector",
38796- "cvc4",
38797- "mathsat5",
38798- "opensmt",
38799- "smtinterpol",
38800- "smt-rat",
38801- "stp",
38802- "verit",
38803- "yices2",
38804- "z3"
38805- ],
38806- "tm_scope": "source.smt",
38807- "ace_mode": "text",
38808- "language_id": 330
38809- },
38810- "SPARQL": {
38811- "type": "data",
38812- "color": "#0C4597",
38813- "tm_scope": "source.sparql",
38814- "ace_mode": "text",
38815- "codemirror_mode": "sparql",
38816- "codemirror_mime_type": "application/sparql-query",
38817- "extensions": [
38818- ".sparql",
38819- ".rq"
38820- ],
38821- "language_id": 331
38822- },
38823- "SQF": {
38824- "type": "programming",
38825- "color": "#3F3F3F",
38826- "extensions": [
38827- ".sqf",
38828- ".hqf"
38829- ],
38830- "tm_scope": "source.sqf",
38831- "ace_mode": "text",
38832- "language_id": 332
38833- },
38834- "SQL": {
38835- "type": "data",
38836- "color": "#e38c00",
38837- "tm_scope": "source.sql",
38838- "ace_mode": "sql",
38839- "codemirror_mode": "sql",
38840- "codemirror_mime_type": "text/x-sql",
38841- "extensions": [
38842- ".sql",
38843- ".cql",
38844- ".ddl",
38845- ".inc",
38846- ".mysql",
38847- ".prc",
38848- ".tab",
38849- ".udf",
38850- ".viw"
38851- ],
38852- "language_id": 333
38853- },
38854- "SQLPL": {
38855- "type": "programming",
38856- "color": "#e38c00",
38857- "ace_mode": "sql",
38858- "codemirror_mode": "sql",
38859- "codemirror_mime_type": "text/x-sql",
38860- "tm_scope": "source.sql",
38861- "extensions": [
38862- ".sql",
38863- ".db2"
38864- ],
38865- "language_id": 334
38866- },
38867- "SRecode Template": {
38868- "type": "markup",
38869- "color": "#348a34",
38870- "tm_scope": "source.lisp",
38871- "ace_mode": "lisp",
38872- "codemirror_mode": "commonlisp",
38873- "codemirror_mime_type": "text/x-common-lisp",
38874- "extensions": [
38875- ".srt"
38876- ],
38877- "language_id": 335
38878- },
38879- "SSH Config": {
38880- "type": "data",
38881- "group": "INI",
38882- "filenames": [
38883- "ssh-config",
38884- "ssh_config",
38885- "sshconfig",
38886- "sshconfig.snip",
38887- "sshd-config",
38888- "sshd_config"
38889- ],
38890- "ace_mode": "text",
38891- "tm_scope": "source.ssh-config",
38892- "language_id": 554920715
38893- },
38894- "STAR": {
38895- "type": "data",
38896- "extensions": [
38897- ".star"
38898- ],
38899- "tm_scope": "source.star",
38900- "ace_mode": "text",
38901- "language_id": 424510560
38902- },
38903- "STL": {
38904- "type": "data",
38905- "color": "#373b5e",
38906- "aliases": [
38907- "ascii stl",
38908- "stla"
38909- ],
38910- "extensions": [
38911- ".stl"
38912- ],
38913- "tm_scope": "source.stl",
38914- "ace_mode": "text",
38915- "language_id": 455361735
38916- },
38917- "STON": {
38918- "type": "data",
38919- "group": "Smalltalk",
38920- "extensions": [
38921- ".ston"
38922- ],
38923- "tm_scope": "source.smalltalk",
38924- "ace_mode": "text",
38925- "language_id": 336
38926- },
38927- "SVG": {
38928- "type": "data",
38929- "color": "#ff9900",
38930- "extensions": [
38931- ".svg"
38932- ],
38933- "tm_scope": "text.xml.svg",
38934- "ace_mode": "xml",
38935- "codemirror_mode": "xml",
38936- "codemirror_mime_type": "text/xml",
38937- "language_id": 337
38938- },
38939- "SWIG": {
38940- "type": "programming",
38941- "extensions": [
38942- ".i"
38943- ],
38944- "tm_scope": "source.c++",
38945- "ace_mode": "c_cpp",
38946- "codemirror_mode": "clike",
38947- "codemirror_mime_type": "text/x-c++src",
38948- "language_id": 1066250075
38949- },
38950- "Sage": {
38951- "type": "programming",
38952- "extensions": [
38953- ".sage",
38954- ".sagews"
38955- ],
38956- "tm_scope": "source.python",
38957- "ace_mode": "python",
38958- "codemirror_mode": "python",
38959- "codemirror_mime_type": "text/x-python",
38960- "language_id": 338
38961- },
38962- "SaltStack": {
38963- "type": "programming",
38964- "color": "#646464",
38965- "aliases": [
38966- "saltstate",
38967- "salt"
38968- ],
38969- "extensions": [
38970- ".sls"
38971- ],
38972- "tm_scope": "source.yaml.salt",
38973- "ace_mode": "yaml",
38974- "codemirror_mode": "yaml",
38975- "codemirror_mime_type": "text/x-yaml",
38976- "language_id": 339
38977- },
38978- "Sass": {
38979- "type": "markup",
38980- "color": "#a53b70",
38981- "tm_scope": "source.sass",
38982- "extensions": [
38983- ".sass"
38984- ],
38985- "ace_mode": "sass",
38986- "codemirror_mode": "sass",
38987- "codemirror_mime_type": "text/x-sass",
38988- "language_id": 340
38989- },
38990- "Scala": {
38991- "type": "programming",
38992- "tm_scope": "source.scala",
38993- "ace_mode": "scala",
38994- "codemirror_mode": "clike",
38995- "codemirror_mime_type": "text/x-scala",
38996- "color": "#c22d40",
38997- "extensions": [
38998- ".scala",
38999- ".kojo",
39000- ".sbt",
39001- ".sc"
39002- ],
39003- "interpreters": [
39004- "scala"
39005- ],
39006- "language_id": 341
39007- },
39008- "Scaml": {
39009- "type": "markup",
39010- "color": "#bd181a",
39011- "extensions": [
39012- ".scaml"
39013- ],
39014- "tm_scope": "source.scaml",
39015- "ace_mode": "text",
39016- "language_id": 342
39017- },
39018- "Scenic": {
39019- "type": "programming",
39020- "color": "#fdc700",
39021- "extensions": [
39022- ".scenic"
39023- ],
39024- "tm_scope": "source.scenic",
39025- "ace_mode": "text",
39026- "interpreters": [
39027- "scenic"
39028- ],
39029- "language_id": 619814037
39030- },
39031- "Scheme": {
39032- "type": "programming",
39033- "color": "#1e4aec",
39034- "extensions": [
39035- ".scm",
39036- ".sch",
39037- ".sld",
39038- ".sls",
39039- ".sps",
39040- ".ss"
39041- ],
39042- "interpreters": [
39043- "scheme",
39044- "guile",
39045- "bigloo",
39046- "chicken",
39047- "csi",
39048- "gosh",
39049- "r6rs"
39050- ],
39051- "tm_scope": "source.scheme",
39052- "ace_mode": "scheme",
39053- "codemirror_mode": "scheme",
39054- "codemirror_mime_type": "text/x-scheme",
39055- "language_id": 343
39056- },
39057- "Scilab": {
39058- "type": "programming",
39059- "color": "#ca0f21",
39060- "extensions": [
39061- ".sci",
39062- ".sce",
39063- ".tst"
39064- ],
39065- "tm_scope": "source.scilab",
39066- "ace_mode": "text",
39067- "language_id": 344
39068- },
39069- "Self": {
39070- "type": "programming",
39071- "color": "#0579aa",
39072- "extensions": [
39073- ".self"
39074- ],
39075- "tm_scope": "none",
39076- "ace_mode": "text",
39077- "language_id": 345
39078- },
39079- "ShaderLab": {
39080- "type": "programming",
39081- "color": "#222c37",
39082- "extensions": [
39083- ".shader"
39084- ],
39085- "ace_mode": "text",
39086- "tm_scope": "source.shaderlab",
39087- "language_id": 664257356
39088- },
39089- "Shell": {
39090- "type": "programming",
39091- "color": "#89e051",
39092- "aliases": [
39093- "sh",
39094- "shell-script",
39095- "bash",
39096- "zsh"
39097- ],
39098- "extensions": [
39099- ".sh",
39100- ".bash",
39101- ".bats",
39102- ".cgi",
39103- ".command",
39104- ".fcgi",
39105- ".ksh",
39106- ".sh.in",
39107- ".tmux",
39108- ".tool",
39109- ".trigger",
39110- ".zsh",
39111- ".zsh-theme"
39112- ],
39113- "filenames": [
39114- ".bash_aliases",
39115- ".bash_functions",
39116- ".bash_history",
39117- ".bash_logout",
39118- ".bash_profile",
39119- ".bashrc",
39120- ".cshrc",
39121- ".flaskenv",
39122- ".kshrc",
39123- ".login",
39124- ".profile",
39125- ".zlogin",
39126- ".zlogout",
39127- ".zprofile",
39128- ".zshenv",
39129- ".zshrc",
39130- "9fs",
39131- "PKGBUILD",
39132- "bash_aliases",
39133- "bash_logout",
39134- "bash_profile",
39135- "bashrc",
39136- "cshrc",
39137- "gradlew",
39138- "kshrc",
39139- "login",
39140- "man",
39141- "profile",
39142- "zlogin",
39143- "zlogout",
39144- "zprofile",
39145- "zshenv",
39146- "zshrc"
39147- ],
39148- "interpreters": [
39149- "ash",
39150- "bash",
39151- "dash",
39152- "ksh",
39153- "mksh",
39154- "pdksh",
39155- "rc",
39156- "sh",
39157- "zsh"
39158- ],
39159- "tm_scope": "source.shell",
39160- "ace_mode": "sh",
39161- "codemirror_mode": "shell",
39162- "codemirror_mime_type": "text/x-sh",
39163- "language_id": 346
39164- },
39165- "ShellCheck Config": {
39166- "type": "data",
39167- "color": "#cecfcb",
39168- "filenames": [
39169- ".shellcheckrc"
39170- ],
39171- "aliases": [
39172- "shellcheckrc"
39173- ],
39174- "tm_scope": "source.shellcheckrc",
39175- "ace_mode": "ini",
39176- "codemirror_mode": "properties",
39177- "codemirror_mime_type": "text/x-properties",
39178- "language_id": 687511714
39179- },
39180- "ShellSession": {
39181- "type": "programming",
39182- "extensions": [
39183- ".sh-session"
39184- ],
39185- "aliases": [
39186- "bash session",
39187- "console"
39188- ],
39189- "tm_scope": "text.shell-session",
39190- "ace_mode": "sh",
39191- "codemirror_mode": "shell",
39192- "codemirror_mime_type": "text/x-sh",
39193- "language_id": 347
39194- },
39195- "Shen": {
39196- "type": "programming",
39197- "color": "#120F14",
39198- "extensions": [
39199- ".shen"
39200- ],
39201- "tm_scope": "source.shen",
39202- "ace_mode": "text",
39203- "language_id": 348
39204- },
39205- "Sieve": {
39206- "type": "programming",
39207- "tm_scope": "source.sieve",
39208- "ace_mode": "text",
39209- "extensions": [
39210- ".sieve"
39211- ],
39212- "codemirror_mode": "sieve",
39213- "codemirror_mime_type": "application/sieve",
39214- "language_id": 208976687
39215- },
39216- "Simple File Verification": {
39217- "type": "data",
39218- "group": "Checksums",
39219- "color": "#C9BFED",
39220- "extensions": [
39221- ".sfv"
39222- ],
39223- "aliases": [
39224- "sfv"
39225- ],
39226- "tm_scope": "source.sfv",
39227- "ace_mode": "ini",
39228- "codemirror_mode": "properties",
39229- "codemirror_mime_type": "text/x-properties",
39230- "language_id": 735623761
39231- },
39232- "Singularity": {
39233- "type": "programming",
39234- "color": "#64E6AD",
39235- "tm_scope": "source.singularity",
39236- "filenames": [
39237- "Singularity"
39238- ],
39239- "ace_mode": "text",
39240- "language_id": 987024632
39241- },
39242- "Slash": {
39243- "type": "programming",
39244- "color": "#007eff",
39245- "extensions": [
39246- ".sl"
39247- ],
39248- "tm_scope": "text.html.slash",
39249- "ace_mode": "text",
39250- "language_id": 349
39251- },
39252- "Slice": {
39253- "type": "programming",
39254- "color": "#003fa2",
39255- "tm_scope": "source.slice",
39256- "ace_mode": "text",
39257- "extensions": [
39258- ".ice"
39259- ],
39260- "language_id": 894641667
39261- },
39262- "Slim": {
39263- "type": "markup",
39264- "color": "#2b2b2b",
39265- "extensions": [
39266- ".slim"
39267- ],
39268- "tm_scope": "text.slim",
39269- "ace_mode": "text",
39270- "codemirror_mode": "slim",
39271- "codemirror_mime_type": "text/x-slim",
39272- "language_id": 350
39273- },
39274- "SmPL": {
39275- "type": "programming",
39276- "extensions": [
39277- ".cocci"
39278- ],
39279- "aliases": [
39280- "coccinelle"
39281- ],
39282- "ace_mode": "text",
39283- "tm_scope": "source.smpl",
39284- "color": "#c94949",
39285- "language_id": 164123055
39286- },
39287- "Smali": {
39288- "type": "programming",
39289- "extensions": [
39290- ".smali"
39291- ],
39292- "ace_mode": "text",
39293- "tm_scope": "source.smali",
39294- "language_id": 351
39295- },
39296- "Smalltalk": {
39297- "type": "programming",
39298- "color": "#596706",
39299- "extensions": [
39300- ".st",
39301- ".cs"
39302- ],
39303- "aliases": [
39304- "squeak"
39305- ],
39306- "tm_scope": "source.smalltalk",
39307- "ace_mode": "text",
39308- "codemirror_mode": "smalltalk",
39309- "codemirror_mime_type": "text/x-stsrc",
39310- "language_id": 352
39311- },
39312- "Smarty": {
39313- "type": "programming",
39314- "color": "#f0c040",
39315- "extensions": [
39316- ".tpl"
39317- ],
39318- "ace_mode": "smarty",
39319- "codemirror_mode": "smarty",
39320- "codemirror_mime_type": "text/x-smarty",
39321- "tm_scope": "text.html.smarty",
39322- "language_id": 353
39323- },
39324- "Smithy": {
39325- "type": "programming",
39326- "ace_mode": "text",
39327- "codemirror_mode": "clike",
39328- "codemirror_mime_type": "text/x-csrc",
39329- "tm_scope": "source.smithy",
39330- "color": "#c44536",
39331- "extensions": [
39332- ".smithy"
39333- ],
39334- "language_id": 1027892786
39335- },
39336- "Snakemake": {
39337- "type": "programming",
39338- "group": "Python",
39339- "tm_scope": "source.python",
39340- "ace_mode": "python",
39341- "codemirror_mode": "python",
39342- "codemirror_mime_type": "text/x-python",
39343- "color": "#419179",
39344- "extensions": [
39345- ".smk",
39346- ".snakefile"
39347- ],
39348- "filenames": [
39349- "Snakefile"
39350- ],
39351- "aliases": [
39352- "snakefile"
39353- ],
39354- "language_id": 151241392
39355- },
39356- "Solidity": {
39357- "type": "programming",
39358- "color": "#AA6746",
39359- "ace_mode": "text",
39360- "tm_scope": "source.solidity",
39361- "extensions": [
39362- ".sol"
39363- ],
39364- "language_id": 237469032
39365- },
39366- "Soong": {
39367- "type": "data",
39368- "tm_scope": "source.bp",
39369- "ace_mode": "text",
39370- "filenames": [
39371- "Android.bp"
39372- ],
39373- "language_id": 222900098
39374- },
39375- "SourcePawn": {
39376- "type": "programming",
39377- "color": "#f69e1d",
39378- "aliases": [
39379- "sourcemod"
39380- ],
39381- "extensions": [
39382- ".sp",
39383- ".inc"
39384- ],
39385- "tm_scope": "source.sourcepawn",
39386- "ace_mode": "text",
39387- "language_id": 354
39388- },
39389- "Spline Font Database": {
39390- "type": "data",
39391- "extensions": [
39392- ".sfd"
39393- ],
39394- "tm_scope": "text.sfd",
39395- "ace_mode": "yaml",
39396- "language_id": 767169629
39397- },
39398- "Squirrel": {
39399- "type": "programming",
39400- "color": "#800000",
39401- "extensions": [
39402- ".nut"
39403- ],
39404- "tm_scope": "source.nut",
39405- "ace_mode": "c_cpp",
39406- "codemirror_mode": "clike",
39407- "codemirror_mime_type": "text/x-c++src",
39408- "language_id": 355
39409- },
39410- "Stan": {
39411- "type": "programming",
39412- "color": "#b2011d",
39413- "extensions": [
39414- ".stan"
39415- ],
39416- "ace_mode": "text",
39417- "tm_scope": "source.stan",
39418- "language_id": 356
39419- },
39420- "Standard ML": {
39421- "type": "programming",
39422- "color": "#dc566d",
39423- "aliases": [
39424- "sml"
39425- ],
39426- "extensions": [
39427- ".ml",
39428- ".fun",
39429- ".sig",
39430- ".sml"
39431- ],
39432- "tm_scope": "source.ml",
39433- "ace_mode": "text",
39434- "codemirror_mode": "mllike",
39435- "codemirror_mime_type": "text/x-ocaml",
39436- "language_id": 357
39437- },
39438- "Starlark": {
39439- "type": "programming",
39440- "tm_scope": "source.python",
39441- "ace_mode": "python",
39442- "codemirror_mode": "python",
39443- "codemirror_mime_type": "text/x-python",
39444- "color": "#76d275",
39445- "extensions": [
39446- ".bzl",
39447- ".star"
39448- ],
39449- "filenames": [
39450- "BUCK",
39451- "BUILD",
39452- "BUILD.bazel",
39453- "MODULE.bazel",
39454- "Tiltfile",
39455- "WORKSPACE",
39456- "WORKSPACE.bazel"
39457- ],
39458- "aliases": [
39459- "bazel",
39460- "bzl"
39461- ],
39462- "language_id": 960266174
39463- },
39464- "Stata": {
39465- "type": "programming",
39466- "color": "#1a5f91",
39467- "extensions": [
39468- ".do",
39469- ".ado",
39470- ".doh",
39471- ".ihlp",
39472- ".mata",
39473- ".matah",
39474- ".sthlp"
39475- ],
39476- "tm_scope": "source.stata",
39477- "ace_mode": "text",
39478- "language_id": 358
39479- },
39480- "StringTemplate": {
39481- "type": "markup",
39482- "color": "#3fb34f",
39483- "extensions": [
39484- ".st"
39485- ],
39486- "tm_scope": "source.string-template",
39487- "ace_mode": "html",
39488- "codemirror_mode": "htmlmixed",
39489- "codemirror_mime_type": "text/html",
39490- "language_id": 89855901
39491- },
39492- "Stylus": {
39493- "type": "markup",
39494- "color": "#ff6347",
39495- "extensions": [
39496- ".styl"
39497- ],
39498- "tm_scope": "source.stylus",
39499- "ace_mode": "stylus",
39500- "language_id": 359
39501- },
39502- "SubRip Text": {
39503- "type": "data",
39504- "color": "#9e0101",
39505- "extensions": [
39506- ".srt"
39507- ],
39508- "ace_mode": "text",
39509- "tm_scope": "text.srt",
39510- "language_id": 360
39511- },
39512- "SugarSS": {
39513- "type": "markup",
39514- "color": "#2fcc9f",
39515- "tm_scope": "source.css.postcss.sugarss",
39516- "extensions": [
39517- ".sss"
39518- ],
39519- "ace_mode": "text",
39520- "language_id": 826404698
39521- },
39522- "SuperCollider": {
39523- "type": "programming",
39524- "color": "#46390b",
39525- "extensions": [
39526- ".sc",
39527- ".scd"
39528- ],
39529- "interpreters": [
39530- "sclang",
39531- "scsynth"
39532- ],
39533- "tm_scope": "source.supercollider",
39534- "ace_mode": "text",
39535- "language_id": 361
39536- },
39537- "Svelte": {
39538- "type": "markup",
39539- "color": "#ff3e00",
39540- "tm_scope": "source.svelte",
39541- "ace_mode": "html",
39542- "codemirror_mode": "htmlmixed",
39543- "codemirror_mime_type": "text/html",
39544- "extensions": [
39545- ".svelte"
39546- ],
39547- "language_id": 928734530
39548- },
39549- "Sway": {
39550- "type": "programming",
39551- "color": "#00F58C",
39552- "extensions": [
39553- ".sw"
39554- ],
39555- "tm_scope": "source.sway",
39556- "ace_mode": "rust",
39557- "codemirror_mode": "rust",
39558- "codemirror_mime_type": "text/x-rustsrc",
39559- "language_id": 271471144
39560- },
39561- "Sweave": {
39562- "type": "prose",
39563- "color": "#198ce7",
39564- "extensions": [
39565- ".rnw"
39566- ],
39567- "tm_scope": "text.tex.latex.sweave",
39568- "ace_mode": "tex",
39569- "language_id": 558779190
39570- },
39571- "Swift": {
39572- "type": "programming",
39573- "color": "#F05138",
39574- "extensions": [
39575- ".swift"
39576- ],
39577- "tm_scope": "source.swift",
39578- "ace_mode": "text",
39579- "codemirror_mode": "swift",
39580- "codemirror_mime_type": "text/x-swift",
39581- "language_id": 362
39582- },
39583- "SystemVerilog": {
39584- "type": "programming",
39585- "color": "#DAE1C2",
39586- "extensions": [
39587- ".sv",
39588- ".svh",
39589- ".vh"
39590- ],
39591- "tm_scope": "source.systemverilog",
39592- "ace_mode": "verilog",
39593- "codemirror_mode": "verilog",
39594- "codemirror_mime_type": "text/x-systemverilog",
39595- "language_id": 363
39596- },
39597- "TI Program": {
39598- "type": "programming",
39599- "ace_mode": "text",
39600- "color": "#A0AA87",
39601- "extensions": [
39602- ".8xp",
39603- ".8xk",
39604- ".8xk.txt",
39605- ".8xp.txt"
39606- ],
39607- "language_id": 422,
39608- "tm_scope": "none"
39609- },
39610- "TL-Verilog": {
39611- "type": "programming",
39612- "extensions": [
39613- ".tlv"
39614- ],
39615- "tm_scope": "source.tlverilog",
39616- "ace_mode": "verilog",
39617- "color": "#C40023",
39618- "language_id": 118656070
39619- },
39620- "TLA": {
39621- "type": "programming",
39622- "color": "#4b0079",
39623- "extensions": [
39624- ".tla"
39625- ],
39626- "tm_scope": "source.tla",
39627- "ace_mode": "text",
39628- "language_id": 364
39629- },
39630- "TOML": {
39631- "type": "data",
39632- "color": "#9c4221",
39633- "extensions": [
39634- ".toml"
39635- ],
39636- "filenames": [
39637- "Cargo.lock",
39638- "Gopkg.lock",
39639- "Pipfile",
39640- "pdm.lock",
39641- "poetry.lock"
39642- ],
39643- "tm_scope": "source.toml",
39644- "ace_mode": "toml",
39645- "codemirror_mode": "toml",
39646- "codemirror_mime_type": "text/x-toml",
39647- "language_id": 365
39648- },
39649- "TSQL": {
39650- "type": "programming",
39651- "color": "#e38c00",
39652- "extensions": [
39653- ".sql"
39654- ],
39655- "ace_mode": "sql",
39656- "tm_scope": "source.tsql",
39657- "language_id": 918334941
39658- },
39659- "TSV": {
39660- "type": "data",
39661- "color": "#237346",
39662- "ace_mode": "text",
39663- "tm_scope": "source.generic-db",
39664- "extensions": [
39665- ".tsv"
39666- ],
39667- "language_id": 1035892117
39668- },
39669- "TSX": {
39670- "type": "programming",
39671- "color": "#3178c6",
39672- "group": "TypeScript",
39673- "extensions": [
39674- ".tsx"
39675- ],
39676- "tm_scope": "source.tsx",
39677- "ace_mode": "javascript",
39678- "codemirror_mode": "jsx",
39679- "codemirror_mime_type": "text/jsx",
39680- "language_id": 94901924
39681- },
39682- "TXL": {
39683- "type": "programming",
39684- "color": "#0178b8",
39685- "extensions": [
39686- ".txl"
39687- ],
39688- "tm_scope": "source.txl",
39689- "ace_mode": "text",
39690- "language_id": 366
39691- },
39692- "Talon": {
39693- "type": "programming",
39694- "ace_mode": "text",
39695- "color": "#333333",
39696- "extensions": [
39697- ".talon"
39698- ],
39699- "tm_scope": "source.talon",
39700- "language_id": 959889508
39701- },
39702- "Tcl": {
39703- "type": "programming",
39704- "color": "#e4cc98",
39705- "extensions": [
39706- ".tcl",
39707- ".adp",
39708- ".sdc",
39709- ".tcl.in",
39710- ".tm",
39711- ".xdc"
39712- ],
39713- "aliases": [
39714- "sdc",
39715- "xdc"
39716- ],
39717- "filenames": [
39718- "owh",
39719- "starfield"
39720- ],
39721- "interpreters": [
39722- "tclsh",
39723- "wish"
39724- ],
39725- "tm_scope": "source.tcl",
39726- "ace_mode": "tcl",
39727- "codemirror_mode": "tcl",
39728- "codemirror_mime_type": "text/x-tcl",
39729- "language_id": 367
39730- },
39731- "Tcsh": {
39732- "type": "programming",
39733- "group": "Shell",
39734- "extensions": [
39735- ".tcsh",
39736- ".csh"
39737- ],
39738- "interpreters": [
39739- "tcsh",
39740- "csh"
39741- ],
39742- "tm_scope": "source.shell",
39743- "ace_mode": "sh",
39744- "codemirror_mode": "shell",
39745- "codemirror_mime_type": "text/x-sh",
39746- "language_id": 368
39747- },
39748- "TeX": {
39749- "type": "markup",
39750- "color": "#3D6117",
39751- "ace_mode": "tex",
39752- "codemirror_mode": "stex",
39753- "codemirror_mime_type": "text/x-stex",
39754- "tm_scope": "text.tex.latex",
39755- "wrap": true,
39756- "aliases": [
39757- "latex"
39758- ],
39759- "extensions": [
39760- ".tex",
39761- ".aux",
39762- ".bbx",
39763- ".cbx",
39764- ".cls",
39765- ".dtx",
39766- ".ins",
39767- ".lbx",
39768- ".ltx",
39769- ".mkii",
39770- ".mkiv",
39771- ".mkvi",
39772- ".sty",
39773- ".toc"
39774- ],
39775- "language_id": 369
39776- },
39777- "Tea": {
39778- "type": "markup",
39779- "extensions": [
39780- ".tea"
39781- ],
39782- "tm_scope": "source.tea",
39783- "ace_mode": "text",
39784- "language_id": 370
39785- },
39786- "Terra": {
39787- "type": "programming",
39788- "extensions": [
39789- ".t"
39790- ],
39791- "color": "#00004c",
39792- "tm_scope": "source.terra",
39793- "ace_mode": "lua",
39794- "codemirror_mode": "lua",
39795- "codemirror_mime_type": "text/x-lua",
39796- "interpreters": [
39797- "lua"
39798- ],
39799- "language_id": 371
39800- },
39801- "Terraform Template": {
39802- "type": "markup",
39803- "extensions": [
39804- ".tftpl"
39805- ],
39806- "color": "#7b42bb",
39807- "tm_scope": "source.hcl.terraform",
39808- "ace_mode": "ruby",
39809- "codemirror_mode": "ruby",
39810- "codemirror_mime_type": "text/x-ruby",
39811- "group": "HCL",
39812- "language_id": 856832701
39813- },
39814- "Texinfo": {
39815- "type": "prose",
39816- "wrap": true,
39817- "extensions": [
39818- ".texinfo",
39819- ".texi",
39820- ".txi"
39821- ],
39822- "ace_mode": "text",
39823- "tm_scope": "text.texinfo",
39824- "interpreters": [
39825- "makeinfo"
39826- ],
39827- "language_id": 988020015
39828- },
39829- "Text": {
39830- "type": "prose",
39831- "wrap": true,
39832- "aliases": [
39833- "fundamental",
39834- "plain text"
39835- ],
39836- "extensions": [
39837- ".txt",
39838- ".fr",
39839- ".nb",
39840- ".ncl",
39841- ".no"
39842- ],
39843- "filenames": [
39844- "CITATION",
39845- "CITATIONS",
39846- "COPYING",
39847- "COPYING.regex",
39848- "COPYRIGHT.regex",
39849- "FONTLOG",
39850- "INSTALL",
39851- "INSTALL.mysql",
39852- "LICENSE",
39853- "LICENSE.mysql",
39854- "NEWS",
39855- "README.me",
39856- "README.mysql",
39857- "README.nss",
39858- "click.me",
39859- "delete.me",
39860- "keep.me",
39861- "package.mask",
39862- "package.use.mask",
39863- "package.use.stable.mask",
39864- "read.me",
39865- "readme.1st",
39866- "test.me",
39867- "use.mask",
39868- "use.stable.mask"
39869- ],
39870- "tm_scope": "none",
39871- "ace_mode": "text",
39872- "language_id": 372
39873- },
39874- "TextMate Properties": {
39875- "type": "data",
39876- "color": "#df66e4",
39877- "aliases": [
39878- "tm-properties"
39879- ],
39880- "filenames": [
39881- ".tm_properties"
39882- ],
39883- "ace_mode": "properties",
39884- "codemirror_mode": "properties",
39885- "codemirror_mime_type": "text/x-properties",
39886- "tm_scope": "source.tm-properties",
39887- "language_id": 981795023
39888- },
39889- "Textile": {
39890- "type": "prose",
39891- "color": "#ffe7ac",
39892- "ace_mode": "textile",
39893- "codemirror_mode": "textile",
39894- "codemirror_mime_type": "text/x-textile",
39895- "wrap": true,
39896- "extensions": [
39897- ".textile"
39898- ],
39899- "tm_scope": "none",
39900- "language_id": 373
39901- },
39902- "Thrift": {
39903- "type": "programming",
39904- "color": "#D12127",
39905- "tm_scope": "source.thrift",
39906- "extensions": [
39907- ".thrift"
39908- ],
39909- "ace_mode": "text",
39910- "language_id": 374
39911- },
39912- "Toit": {
39913- "type": "programming",
39914- "color": "#c2c9fb",
39915- "extensions": [
39916- ".toit"
39917- ],
39918- "tm_scope": "source.toit",
39919- "ace_mode": "text",
39920- "language_id": 356554395
39921- },
39922- "Turing": {
39923- "type": "programming",
39924- "color": "#cf142b",
39925- "extensions": [
39926- ".t",
39927- ".tu"
39928- ],
39929- "tm_scope": "source.turing",
39930- "ace_mode": "text",
39931- "language_id": 375
39932- },
39933- "Turtle": {
39934- "type": "data",
39935- "extensions": [
39936- ".ttl"
39937- ],
39938- "tm_scope": "source.turtle",
39939- "ace_mode": "text",
39940- "codemirror_mode": "turtle",
39941- "codemirror_mime_type": "text/turtle",
39942- "language_id": 376
39943- },
39944- "Twig": {
39945- "type": "markup",
39946- "color": "#c1d026",
39947- "extensions": [
39948- ".twig"
39949- ],
39950- "tm_scope": "text.html.twig",
39951- "ace_mode": "twig",
39952- "codemirror_mode": "twig",
39953- "codemirror_mime_type": "text/x-twig",
39954- "language_id": 377
39955- },
39956- "Type Language": {
39957- "type": "data",
39958- "aliases": [
39959- "tl"
39960- ],
39961- "extensions": [
39962- ".tl"
39963- ],
39964- "tm_scope": "source.tl",
39965- "ace_mode": "text",
39966- "language_id": 632765617
39967- },
39968- "TypeScript": {
39969- "type": "programming",
39970- "color": "#3178c6",
39971- "aliases": [
39972- "ts"
39973- ],
39974- "interpreters": [
39975- "deno",
39976- "ts-node"
39977- ],
39978- "extensions": [
39979- ".ts",
39980- ".cts",
39981- ".mts"
39982- ],
39983- "tm_scope": "source.ts",
39984- "ace_mode": "typescript",
39985- "codemirror_mode": "javascript",
39986- "codemirror_mime_type": "application/typescript",
39987- "language_id": 378
39988- },
39989- "Typst": {
39990- "type": "programming",
39991- "color": "#239dad",
39992- "aliases": [
39993- "typ"
39994- ],
39995- "extensions": [
39996- ".typ"
39997- ],
39998- "tm_scope": "source.typst",
39999- "ace_mode": "text",
40000- "language_id": 704730682
40001- },
40002- "Unified Parallel C": {
40003- "type": "programming",
40004- "color": "#4e3617",
40005- "group": "C",
40006- "ace_mode": "c_cpp",
40007- "codemirror_mode": "clike",
40008- "codemirror_mime_type": "text/x-csrc",
40009- "extensions": [
40010- ".upc"
40011- ],
40012- "tm_scope": "source.c",
40013- "language_id": 379
40014- },
40015- "Unity3D Asset": {
40016- "type": "data",
40017- "color": "#222c37",
40018- "ace_mode": "yaml",
40019- "codemirror_mode": "yaml",
40020- "codemirror_mime_type": "text/x-yaml",
40021- "extensions": [
40022- ".anim",
40023- ".asset",
40024- ".mask",
40025- ".mat",
40026- ".meta",
40027- ".prefab",
40028- ".unity"
40029- ],
40030- "tm_scope": "source.yaml",
40031- "language_id": 380
40032- },
40033- "Unix Assembly": {
40034- "type": "programming",
40035- "group": "Assembly",
40036- "extensions": [
40037- ".s",
40038- ".ms"
40039- ],
40040- "aliases": [
40041- "gas",
40042- "gnu asm",
40043- "unix asm"
40044- ],
40045- "tm_scope": "source.x86",
40046- "ace_mode": "assembly_x86",
40047- "language_id": 120
40048- },
40049- "Uno": {
40050- "type": "programming",
40051- "color": "#9933cc",
40052- "extensions": [
40053- ".uno"
40054- ],
40055- "ace_mode": "csharp",
40056- "codemirror_mode": "clike",
40057- "codemirror_mime_type": "text/x-csharp",
40058- "tm_scope": "source.cs",
40059- "language_id": 381
40060- },
40061- "UnrealScript": {
40062- "type": "programming",
40063- "color": "#a54c4d",
40064- "extensions": [
40065- ".uc"
40066- ],
40067- "tm_scope": "source.java",
40068- "ace_mode": "java",
40069- "codemirror_mode": "clike",
40070- "codemirror_mime_type": "text/x-java",
40071- "language_id": 382
40072- },
40073- "UrWeb": {
40074- "type": "programming",
40075- "color": "#ccccee",
40076- "aliases": [
40077- "Ur/Web",
40078- "Ur"
40079- ],
40080- "extensions": [
40081- ".ur",
40082- ".urs"
40083- ],
40084- "tm_scope": "source.ur",
40085- "ace_mode": "text",
40086- "language_id": 383
40087- },
40088- "V": {
40089- "type": "programming",
40090- "color": "#4f87c4",
40091- "aliases": [
40092- "vlang"
40093- ],
40094- "extensions": [
40095- ".v"
40096- ],
40097- "tm_scope": "source.v",
40098- "ace_mode": "golang",
40099- "codemirror_mode": "go",
40100- "codemirror_mime_type": "text/x-go",
40101- "language_id": 603371597
40102- },
40103- "VBA": {
40104- "type": "programming",
40105- "color": "#867db1",
40106- "extensions": [
40107- ".bas",
40108- ".cls",
40109- ".frm",
40110- ".vba"
40111- ],
40112- "tm_scope": "source.vba",
40113- "aliases": [
40114- "visual basic for applications"
40115- ],
40116- "ace_mode": "text",
40117- "codemirror_mode": "vb",
40118- "codemirror_mime_type": "text/x-vb",
40119- "language_id": 399230729
40120- },
40121- "VBScript": {
40122- "type": "programming",
40123- "color": "#15dcdc",
40124- "extensions": [
40125- ".vbs"
40126- ],
40127- "tm_scope": "source.vbnet",
40128- "ace_mode": "text",
40129- "codemirror_mode": "vbscript",
40130- "codemirror_mime_type": "text/vbscript",
40131- "language_id": 408016005
40132- },
40133- "VCL": {
40134- "type": "programming",
40135- "color": "#148AA8",
40136- "extensions": [
40137- ".vcl"
40138- ],
40139- "tm_scope": "source.varnish.vcl",
40140- "ace_mode": "text",
40141- "language_id": 384
40142- },
40143- "VHDL": {
40144- "type": "programming",
40145- "color": "#adb2cb",
40146- "extensions": [
40147- ".vhdl",
40148- ".vhd",
40149- ".vhf",
40150- ".vhi",
40151- ".vho",
40152- ".vhs",
40153- ".vht",
40154- ".vhw"
40155- ],
40156- "tm_scope": "source.vhdl",
40157- "ace_mode": "vhdl",
40158- "codemirror_mode": "vhdl",
40159- "codemirror_mime_type": "text/x-vhdl",
40160- "language_id": 385
40161- },
40162- "Vala": {
40163- "type": "programming",
40164- "color": "#a56de2",
40165- "extensions": [
40166- ".vala",
40167- ".vapi"
40168- ],
40169- "tm_scope": "source.vala",
40170- "ace_mode": "vala",
40171- "language_id": 386
40172- },
40173- "Valve Data Format": {
40174- "type": "data",
40175- "color": "#f26025",
40176- "aliases": [
40177- "keyvalues",
40178- "vdf"
40179- ],
40180- "extensions": [
40181- ".vdf"
40182- ],
40183- "ace_mode": "text",
40184- "tm_scope": "source.keyvalues",
40185- "language_id": 544060961
40186- },
40187- "Velocity Template Language": {
40188- "type": "markup",
40189- "color": "#507cff",
40190- "aliases": [
40191- "vtl",
40192- "velocity"
40193- ],
40194- "extensions": [
40195- ".vtl"
40196- ],
40197- "ace_mode": "velocity",
40198- "tm_scope": "source.velocity",
40199- "codemirror_mode": "velocity",
40200- "codemirror_mime_type": "text/velocity",
40201- "language_id": 292377326
40202- },
40203- "Verilog": {
40204- "type": "programming",
40205- "color": "#b2b7f8",
40206- "extensions": [
40207- ".v",
40208- ".veo"
40209- ],
40210- "tm_scope": "source.verilog",
40211- "ace_mode": "verilog",
40212- "codemirror_mode": "verilog",
40213- "codemirror_mime_type": "text/x-verilog",
40214- "language_id": 387
40215- },
40216- "Vim Help File": {
40217- "type": "prose",
40218- "color": "#199f4b",
40219- "aliases": [
40220- "help",
40221- "vimhelp"
40222- ],
40223- "extensions": [
40224- ".txt"
40225- ],
40226- "tm_scope": "text.vim-help",
40227- "ace_mode": "text",
40228- "language_id": 508563686
40229- },
40230- "Vim Script": {
40231- "type": "programming",
40232- "color": "#199f4b",
40233- "tm_scope": "source.viml",
40234- "aliases": [
40235- "vim",
40236- "viml",
40237- "nvim"
40238- ],
40239- "extensions": [
40240- ".vim",
40241- ".vba",
40242- ".vimrc",
40243- ".vmb"
40244- ],
40245- "filenames": [
40246- ".exrc",
40247- ".gvimrc",
40248- ".nvimrc",
40249- ".vimrc",
40250- "_vimrc",
40251- "gvimrc",
40252- "nvimrc",
40253- "vimrc"
40254- ],
40255- "ace_mode": "text",
40256- "language_id": 388
40257- },
40258- "Vim Snippet": {
40259- "type": "markup",
40260- "color": "#199f4b",
40261- "aliases": [
40262- "SnipMate",
40263- "UltiSnip",
40264- "UltiSnips",
40265- "NeoSnippet"
40266- ],
40267- "extensions": [
40268- ".snip",
40269- ".snippet",
40270- ".snippets"
40271- ],
40272- "tm_scope": "source.vim-snippet",
40273- "ace_mode": "text",
40274- "language_id": 81265970
40275- },
40276- "Visual Basic .NET": {
40277- "type": "programming",
40278- "color": "#945db7",
40279- "extensions": [
40280- ".vb",
40281- ".vbhtml"
40282- ],
40283- "aliases": [
40284- "visual basic",
40285- "vbnet",
40286- "vb .net",
40287- "vb.net"
40288- ],
40289- "tm_scope": "source.vbnet",
40290- "ace_mode": "text",
40291- "codemirror_mode": "vb",
40292- "codemirror_mime_type": "text/x-vb",
40293- "language_id": 389
40294- },
40295- "Visual Basic 6.0": {
40296- "type": "programming",
40297- "color": "#2c6353",
40298- "extensions": [
40299- ".bas",
40300- ".cls",
40301- ".ctl",
40302- ".Dsr",
40303- ".frm"
40304- ],
40305- "tm_scope": "source.vbnet",
40306- "aliases": [
40307- "vb6",
40308- "vb 6",
40309- "visual basic 6",
40310- "visual basic classic",
40311- "classic visual basic"
40312- ],
40313- "ace_mode": "text",
40314- "codemirror_mode": "vb",
40315- "codemirror_mime_type": "text/x-vb",
40316- "language_id": 679594952
40317- },
40318- "Volt": {
40319- "type": "programming",
40320- "color": "#1F1F1F",
40321- "extensions": [
40322- ".volt"
40323- ],
40324- "tm_scope": "source.d",
40325- "ace_mode": "d",
40326- "codemirror_mode": "d",
40327- "codemirror_mime_type": "text/x-d",
40328- "language_id": 390
40329- },
40330- "Vue": {
40331- "type": "markup",
40332- "color": "#41b883",
40333- "extensions": [
40334- ".vue"
40335- ],
40336- "tm_scope": "text.html.vue",
40337- "ace_mode": "html",
40338- "language_id": 391
40339- },
40340- "Vyper": {
40341- "type": "programming",
40342- "extensions": [
40343- ".vy"
40344- ],
40345- "color": "#2980b9",
40346- "ace_mode": "text",
40347- "tm_scope": "source.vyper",
40348- "language_id": 1055641948
40349- },
40350- "WDL": {
40351- "aliases": [
40352- "Workflow Description Language"
40353- ],
40354- "type": "programming",
40355- "color": "#42f1f4",
40356- "extensions": [
40357- ".wdl"
40358- ],
40359- "tm_scope": "source.wdl",
40360- "ace_mode": "text",
40361- "language_id": 374521672
40362- },
40363- "WGSL": {
40364- "type": "programming",
40365- "color": "#1a5e9a",
40366- "extensions": [
40367- ".wgsl"
40368- ],
40369- "tm_scope": "source.wgsl",
40370- "ace_mode": "text",
40371- "language_id": 836605993
40372- },
40373- "Wavefront Material": {
40374- "type": "data",
40375- "extensions": [
40376- ".mtl"
40377- ],
40378- "tm_scope": "source.wavefront.mtl",
40379- "ace_mode": "text",
40380- "language_id": 392
40381- },
40382- "Wavefront Object": {
40383- "type": "data",
40384- "extensions": [
40385- ".obj"
40386- ],
40387- "tm_scope": "source.wavefront.obj",
40388- "ace_mode": "text",
40389- "language_id": 393
40390- },
40391- "Web Ontology Language": {
40392- "type": "data",
40393- "color": "#5b70bd",
40394- "extensions": [
40395- ".owl"
40396- ],
40397- "tm_scope": "text.xml",
40398- "ace_mode": "xml",
40399- "language_id": 394
40400- },
40401- "WebAssembly": {
40402- "type": "programming",
40403- "color": "#04133b",
40404- "extensions": [
40405- ".wast",
40406- ".wat"
40407- ],
40408- "aliases": [
40409- "wast",
40410- "wasm"
40411- ],
40412- "tm_scope": "source.webassembly",
40413- "ace_mode": "lisp",
40414- "codemirror_mode": "commonlisp",
40415- "codemirror_mime_type": "text/x-common-lisp",
40416- "language_id": 956556503
40417- },
40418- "WebAssembly Interface Type": {
40419- "type": "data",
40420- "color": "#6250e7",
40421- "extensions": [
40422- ".wit"
40423- ],
40424- "aliases": [
40425- "wit"
40426- ],
40427- "ace_mode": "text",
40428- "tm_scope": "source.wit",
40429- "codemirror_mode": "webidl",
40430- "codemirror_mime_type": "text/x-webidl",
40431- "language_id": 134534086
40432- },
40433- "WebIDL": {
40434- "type": "programming",
40435- "extensions": [
40436- ".webidl"
40437- ],
40438- "tm_scope": "source.webidl",
40439- "ace_mode": "text",
40440- "codemirror_mode": "webidl",
40441- "codemirror_mime_type": "text/x-webidl",
40442- "language_id": 395
40443- },
40444- "WebVTT": {
40445- "type": "data",
40446- "wrap": true,
40447- "aliases": [
40448- "vtt"
40449- ],
40450- "extensions": [
40451- ".vtt"
40452- ],
40453- "tm_scope": "text.vtt",
40454- "ace_mode": "text",
40455- "language_id": 658679714
40456- },
40457- "Wget Config": {
40458- "type": "data",
40459- "group": "INI",
40460- "aliases": [
40461- "wgetrc"
40462- ],
40463- "filenames": [
40464- ".wgetrc"
40465- ],
40466- "tm_scope": "source.wgetrc",
40467- "ace_mode": "text",
40468- "language_id": 668457123
40469- },
40470- "Whiley": {
40471- "type": "programming",
40472- "color": "#d5c397",
40473- "extensions": [
40474- ".whiley"
40475- ],
40476- "tm_scope": "source.whiley",
40477- "ace_mode": "text",
40478- "language_id": 888779559
40479- },
40480- "Wikitext": {
40481- "type": "prose",
40482- "color": "#fc5757",
40483- "wrap": true,
40484- "aliases": [
40485- "mediawiki",
40486- "wiki"
40487- ],
40488- "extensions": [
40489- ".mediawiki",
40490- ".wiki",
40491- ".wikitext"
40492- ],
40493- "tm_scope": "text.html.mediawiki",
40494- "ace_mode": "text",
40495- "language_id": 228
40496- },
40497- "Win32 Message File": {
40498- "type": "data",
40499- "extensions": [
40500- ".mc"
40501- ],
40502- "tm_scope": "source.win32-messages",
40503- "ace_mode": "ini",
40504- "codemirror_mode": "properties",
40505- "codemirror_mime_type": "text/x-properties",
40506- "language_id": 950967261
40507- },
40508- "Windows Registry Entries": {
40509- "type": "data",
40510- "color": "#52d5ff",
40511- "extensions": [
40512- ".reg"
40513- ],
40514- "tm_scope": "source.reg",
40515- "ace_mode": "ini",
40516- "codemirror_mode": "properties",
40517- "codemirror_mime_type": "text/x-properties",
40518- "language_id": 969674868
40519- },
40520- "Witcher Script": {
40521- "type": "programming",
40522- "color": "#ff0000",
40523- "extensions": [
40524- ".ws"
40525- ],
40526- "ace_mode": "text",
40527- "tm_scope": "source.witcherscript",
40528- "language_id": 686821385
40529- },
40530- "Wollok": {
40531- "type": "programming",
40532- "color": "#a23738",
40533- "extensions": [
40534- ".wlk"
40535- ],
40536- "ace_mode": "text",
40537- "tm_scope": "source.wollok",
40538- "language_id": 632745969
40539- },
40540- "World of Warcraft Addon Data": {
40541- "type": "data",
40542- "color": "#f7e43f",
40543- "extensions": [
40544- ".toc"
40545- ],
40546- "tm_scope": "source.toc",
40547- "ace_mode": "text",
40548- "language_id": 396
40549- },
40550- "Wren": {
40551- "type": "programming",
40552- "color": "#383838",
40553- "aliases": [
40554- "wrenlang"
40555- ],
40556- "extensions": [
40557- ".wren"
40558- ],
40559- "tm_scope": "source.wren",
40560- "ace_mode": "text",
40561- "language_id": 713580619
40562- },
40563- "X BitMap": {
40564- "type": "data",
40565- "group": "C",
40566- "aliases": [
40567- "xbm"
40568- ],
40569- "extensions": [
40570- ".xbm"
40571- ],
40572- "ace_mode": "c_cpp",
40573- "tm_scope": "source.c",
40574- "codemirror_mode": "clike",
40575- "codemirror_mime_type": "text/x-csrc",
40576- "language_id": 782911107
40577- },
40578- "X Font Directory Index": {
40579- "type": "data",
40580- "filenames": [
40581- "encodings.dir",
40582- "fonts.alias",
40583- "fonts.dir",
40584- "fonts.scale"
40585- ],
40586- "tm_scope": "source.fontdir",
40587- "ace_mode": "text",
40588- "language_id": 208700028
40589- },
40590- "X PixMap": {
40591- "type": "data",
40592- "group": "C",
40593- "aliases": [
40594- "xpm"
40595- ],
40596- "extensions": [
40597- ".xpm",
40598- ".pm"
40599- ],
40600- "ace_mode": "c_cpp",
40601- "tm_scope": "source.c",
40602- "codemirror_mode": "clike",
40603- "codemirror_mime_type": "text/x-csrc",
40604- "language_id": 781846279
40605- },
40606- "X10": {
40607- "type": "programming",
40608- "aliases": [
40609- "xten"
40610- ],
40611- "ace_mode": "text",
40612- "extensions": [
40613- ".x10"
40614- ],
40615- "color": "#4B6BEF",
40616- "tm_scope": "source.x10",
40617- "language_id": 397
40618- },
40619- "XC": {
40620- "type": "programming",
40621- "color": "#99DA07",
40622- "extensions": [
40623- ".xc"
40624- ],
40625- "tm_scope": "source.xc",
40626- "ace_mode": "c_cpp",
40627- "codemirror_mode": "clike",
40628- "codemirror_mime_type": "text/x-csrc",
40629- "language_id": 398
40630- },
40631- "XCompose": {
40632- "type": "data",
40633- "filenames": [
40634- ".XCompose",
40635- "XCompose",
40636- "xcompose"
40637- ],
40638- "tm_scope": "config.xcompose",
40639- "ace_mode": "text",
40640- "language_id": 225167241
40641- },
40642- "XML": {
40643- "type": "data",
40644- "color": "#0060ac",
40645- "tm_scope": "text.xml",
40646- "ace_mode": "xml",
40647- "codemirror_mode": "xml",
40648- "codemirror_mime_type": "text/xml",
40649- "aliases": [
40650- "rss",
40651- "xsd",
40652- "wsdl"
40653- ],
40654- "extensions": [
40655- ".xml",
40656- ".adml",
40657- ".admx",
40658- ".ant",
40659- ".axaml",
40660- ".axml",
40661- ".builds",
40662- ".ccproj",
40663- ".ccxml",
40664- ".clixml",
40665- ".cproject",
40666- ".cscfg",
40667- ".csdef",
40668- ".csl",
40669- ".csproj",
40670- ".ct",
40671- ".depproj",
40672- ".dita",
40673- ".ditamap",
40674- ".ditaval",
40675- ".dll.config",
40676- ".dotsettings",
40677- ".filters",
40678- ".fsproj",
40679- ".fxml",
40680- ".glade",
40681- ".gml",
40682- ".gmx",
40683- ".grxml",
40684- ".gst",
40685- ".hzp",
40686- ".iml",
40687- ".ivy",
40688- ".jelly",
40689- ".jsproj",
40690- ".kml",
40691- ".launch",
40692- ".mdpolicy",
40693- ".mjml",
40694- ".mm",
40695- ".mod",
40696- ".mxml",
40697- ".natvis",
40698- ".ncl",
40699- ".ndproj",
40700- ".nproj",
40701- ".nuspec",
40702- ".odd",
40703- ".osm",
40704- ".pkgproj",
40705- ".pluginspec",
40706- ".proj",
40707- ".props",
40708- ".ps1xml",
40709- ".psc1",
40710- ".pt",
40711- ".qhelp",
40712- ".rdf",
40713- ".res",
40714- ".resx",
40715- ".rs",
40716- ".rss",
40717- ".sch",
40718- ".scxml",
40719- ".sfproj",
40720- ".shproj",
40721- ".srdf",
40722- ".storyboard",
40723- ".sublime-snippet",
40724- ".sw",
40725- ".targets",
40726- ".tml",
40727- ".ts",
40728- ".tsx",
40729- ".typ",
40730- ".ui",
40731- ".urdf",
40732- ".ux",
40733- ".vbproj",
40734- ".vcxproj",
40735- ".vsixmanifest",
40736- ".vssettings",
40737- ".vstemplate",
40738- ".vxml",
40739- ".wixproj",
40740- ".workflow",
40741- ".wsdl",
40742- ".wsf",
40743- ".wxi",
40744- ".wxl",
40745- ".wxs",
40746- ".x3d",
40747- ".xacro",
40748- ".xaml",
40749- ".xib",
40750- ".xlf",
40751- ".xliff",
40752- ".xmi",
40753- ".xml.dist",
40754- ".xmp",
40755- ".xproj",
40756- ".xsd",
40757- ".xspec",
40758- ".xul",
40759- ".zcml"
40760- ],
40761- "filenames": [
40762- ".classpath",
40763- ".cproject",
40764- ".project",
40765- "App.config",
40766- "NuGet.config",
40767- "Settings.StyleCop",
40768- "Web.Debug.config",
40769- "Web.Release.config",
40770- "Web.config",
40771- "packages.config"
40772- ],
40773- "language_id": 399
40774- },
40775- "XML Property List": {
40776- "type": "data",
40777- "color": "#0060ac",
40778- "group": "XML",
40779- "extensions": [
40780- ".plist",
40781- ".stTheme",
40782- ".tmCommand",
40783- ".tmLanguage",
40784- ".tmPreferences",
40785- ".tmSnippet",
40786- ".tmTheme"
40787- ],
40788- "tm_scope": "text.xml.plist",
40789- "ace_mode": "xml",
40790- "codemirror_mode": "xml",
40791- "codemirror_mime_type": "text/xml",
40792- "language_id": 75622871
40793- },
40794- "XPages": {
40795- "type": "data",
40796- "extensions": [
40797- ".xsp-config",
40798- ".xsp.metadata"
40799- ],
40800- "tm_scope": "text.xml",
40801- "ace_mode": "xml",
40802- "codemirror_mode": "xml",
40803- "codemirror_mime_type": "text/xml",
40804- "language_id": 400
40805- },
40806- "XProc": {
40807- "type": "programming",
40808- "extensions": [
40809- ".xpl",
40810- ".xproc"
40811- ],
40812- "tm_scope": "text.xml",
40813- "ace_mode": "xml",
40814- "codemirror_mode": "xml",
40815- "codemirror_mime_type": "text/xml",
40816- "language_id": 401
40817- },
40818- "XQuery": {
40819- "type": "programming",
40820- "color": "#5232e7",
40821- "extensions": [
40822- ".xquery",
40823- ".xq",
40824- ".xql",
40825- ".xqm",
40826- ".xqy"
40827- ],
40828- "ace_mode": "xquery",
40829- "codemirror_mode": "xquery",
40830- "codemirror_mime_type": "application/xquery",
40831- "tm_scope": "source.xq",
40832- "language_id": 402
40833- },
40834- "XS": {
40835- "type": "programming",
40836- "extensions": [
40837- ".xs"
40838- ],
40839- "tm_scope": "source.c",
40840- "ace_mode": "c_cpp",
40841- "codemirror_mode": "clike",
40842- "codemirror_mime_type": "text/x-csrc",
40843- "language_id": 403
40844- },
40845- "XSLT": {
40846- "type": "programming",
40847- "aliases": [
40848- "xsl"
40849- ],
40850- "extensions": [
40851- ".xslt",
40852- ".xsl"
40853- ],
40854- "tm_scope": "text.xml.xsl",
40855- "ace_mode": "xml",
40856- "codemirror_mode": "xml",
40857- "codemirror_mime_type": "text/xml",
40858- "color": "#EB8CEB",
40859- "language_id": 404
40860- },
40861- "Xojo": {
40862- "type": "programming",
40863- "color": "#81bd41",
40864- "extensions": [
40865- ".xojo_code",
40866- ".xojo_menu",
40867- ".xojo_report",
40868- ".xojo_script",
40869- ".xojo_toolbar",
40870- ".xojo_window"
40871- ],
40872- "tm_scope": "source.xojo",
40873- "ace_mode": "text",
40874- "language_id": 405
40875- },
40876- "Xonsh": {
40877- "type": "programming",
40878- "color": "#285EEF",
40879- "extensions": [
40880- ".xsh"
40881- ],
40882- "tm_scope": "source.python",
40883- "ace_mode": "text",
40884- "codemirror_mode": "python",
40885- "codemirror_mime_type": "text/x-python",
40886- "language_id": 614078284
40887- },
40888- "Xtend": {
40889- "type": "programming",
40890- "color": "#24255d",
40891- "extensions": [
40892- ".xtend"
40893- ],
40894- "tm_scope": "source.xtend",
40895- "ace_mode": "text",
40896- "language_id": 406
40897- },
40898- "YAML": {
40899- "type": "data",
40900- "color": "#cb171e",
40901- "tm_scope": "source.yaml",
40902- "aliases": [
40903- "yml"
40904- ],
40905- "extensions": [
40906- ".yml",
40907- ".mir",
40908- ".reek",
40909- ".rviz",
40910- ".sublime-syntax",
40911- ".syntax",
40912- ".yaml",
40913- ".yaml-tmlanguage",
40914- ".yaml.sed",
40915- ".yml.mysql"
40916- ],
40917- "filenames": [
40918- ".clang-format",
40919- ".clang-tidy",
40920- ".gemrc",
40921- "CITATION.cff",
40922- "glide.lock",
40923- "yarn.lock"
40924- ],
40925- "ace_mode": "yaml",
40926- "codemirror_mode": "yaml",
40927- "codemirror_mime_type": "text/x-yaml",
40928- "language_id": 407
40929- },
40930- "YANG": {
40931- "type": "data",
40932- "extensions": [
40933- ".yang"
40934- ],
40935- "tm_scope": "source.yang",
40936- "ace_mode": "text",
40937- "language_id": 408
40938- },
40939- "YARA": {
40940- "type": "programming",
40941- "color": "#220000",
40942- "ace_mode": "text",
40943- "extensions": [
40944- ".yar",
40945- ".yara"
40946- ],
40947- "tm_scope": "source.yara",
40948- "language_id": 805122868
40949- },
40950- "YASnippet": {
40951- "type": "markup",
40952- "aliases": [
40953- "snippet",
40954- "yas"
40955- ],
40956- "color": "#32AB90",
40957- "extensions": [
40958- ".yasnippet"
40959- ],
40960- "tm_scope": "source.yasnippet",
40961- "ace_mode": "text",
40962- "language_id": 378760102
40963- },
40964- "Yacc": {
40965- "type": "programming",
40966- "extensions": [
40967- ".y",
40968- ".yacc",
40969- ".yy"
40970- ],
40971- "tm_scope": "source.yacc",
40972- "ace_mode": "text",
40973- "color": "#4B6C4B",
40974- "language_id": 409
40975- },
40976- "Yul": {
40977- "type": "programming",
40978- "color": "#794932",
40979- "ace_mode": "text",
40980- "tm_scope": "source.yul",
40981- "extensions": [
40982- ".yul"
40983- ],
40984- "language_id": 237469033
40985- },
40986- "ZAP": {
40987- "type": "programming",
40988- "color": "#0d665e",
40989- "extensions": [
40990- ".zap",
40991- ".xzap"
40992- ],
40993- "tm_scope": "source.zap",
40994- "ace_mode": "text",
40995- "language_id": 952972794
40996- },
40997- "ZIL": {
40998- "type": "programming",
40999- "color": "#dc75e5",
41000- "extensions": [
41001- ".zil",
41002- ".mud"
41003- ],
41004- "tm_scope": "source.zil",
41005- "ace_mode": "text",
41006- "language_id": 973483626
41007- },
41008- "Zeek": {
41009- "type": "programming",
41010- "aliases": [
41011- "bro"
41012- ],
41013- "extensions": [
41014- ".zeek",
41015- ".bro"
41016- ],
41017- "tm_scope": "source.zeek",
41018- "ace_mode": "text",
41019- "language_id": 40
41020- },
41021- "ZenScript": {
41022- "type": "programming",
41023- "color": "#00BCD1",
41024- "extensions": [
41025- ".zs"
41026- ],
41027- "tm_scope": "source.zenscript",
41028- "ace_mode": "text",
41029- "language_id": 494938890
41030- },
41031- "Zephir": {
41032- "type": "programming",
41033- "color": "#118f9e",
41034- "extensions": [
41035- ".zep"
41036- ],
41037- "tm_scope": "source.php.zephir",
41038- "ace_mode": "php",
41039- "language_id": 410
41040- },
41041- "Zig": {
41042- "type": "programming",
41043- "color": "#ec915c",
41044- "extensions": [
41045- ".zig"
41046- ],
41047- "tm_scope": "source.zig",
41048- "ace_mode": "text",
41049- "language_id": 646424281
41050- },
41051- "Zimpl": {
41052- "type": "programming",
41053- "color": "#d67711",
41054- "extensions": [
41055- ".zimpl",
41056- ".zmpl",
41057- ".zpl"
41058- ],
41059- "tm_scope": "none",
41060- "ace_mode": "text",
41061- "language_id": 411
41062- },
41063- "cURL Config": {
41064- "type": "data",
41065- "group": "INI",
41066- "aliases": [
41067- "curlrc"
41068- ],
41069- "filenames": [
41070- ".curlrc",
41071- "_curlrc"
41072- ],
41073- "tm_scope": "source.curlrc",
41074- "ace_mode": "text",
41075- "language_id": 992375436
41076- },
41077- "desktop": {
41078- "type": "data",
41079- "extensions": [
41080- ".desktop",
41081- ".desktop.in",
41082- ".service"
41083- ],
41084- "tm_scope": "source.desktop",
41085- "ace_mode": "text",
41086- "language_id": 412
41087- },
41088- "dircolors": {
41089- "type": "data",
41090- "extensions": [
41091- ".dircolors"
41092- ],
41093- "filenames": [
41094- ".dir_colors",
41095- ".dircolors",
41096- "DIR_COLORS",
41097- "_dir_colors",
41098- "_dircolors",
41099- "dir_colors"
41100- ],
41101- "tm_scope": "source.dircolors",
41102- "ace_mode": "text",
41103- "language_id": 691605112
41104- },
41105- "eC": {
41106- "type": "programming",
41107- "color": "#913960",
41108- "extensions": [
41109- ".ec",
41110- ".eh"
41111- ],
41112- "tm_scope": "source.c.ec",
41113- "ace_mode": "text",
41114- "language_id": 413
41115- },
41116- "edn": {
41117- "type": "data",
41118- "ace_mode": "clojure",
41119- "codemirror_mode": "clojure",
41120- "codemirror_mime_type": "text/x-clojure",
41121- "extensions": [
41122- ".edn"
41123- ],
41124- "tm_scope": "source.clojure",
41125- "language_id": 414
41126- },
41127- "fish": {
41128- "type": "programming",
41129- "color": "#4aae47",
41130- "group": "Shell",
41131- "interpreters": [
41132- "fish"
41133- ],
41134- "extensions": [
41135- ".fish"
41136- ],
41137- "tm_scope": "source.fish",
41138- "ace_mode": "text",
41139- "language_id": 415
41140- },
41141- "hoon": {
41142- "type": "programming",
41143- "color": "#00b171",
41144- "tm_scope": "source.hoon",
41145- "ace_mode": "text",
41146- "extensions": [
41147- ".hoon"
41148- ],
41149- "language_id": 560883276
41150- },
41151- "jq": {
41152- "color": "#c7254e",
41153- "ace_mode": "text",
41154- "type": "programming",
41155- "extensions": [
41156- ".jq"
41157- ],
41158- "tm_scope": "source.jq",
41159- "language_id": 905371884
41160- },
41161- "kvlang": {
41162- "type": "markup",
41163- "ace_mode": "text",
41164- "extensions": [
41165- ".kv"
41166- ],
41167- "color": "#1da6e0",
41168- "tm_scope": "source.python.kivy",
41169- "language_id": 970675279
41170- },
41171- "mIRC Script": {
41172- "type": "programming",
41173- "color": "#3d57c3",
41174- "extensions": [
41175- ".mrc"
41176- ],
41177- "tm_scope": "source.msl",
41178- "ace_mode": "text",
41179- "language_id": 517654727
41180- },
41181- "mcfunction": {
41182- "type": "programming",
41183- "color": "#E22837",
41184- "extensions": [
41185- ".mcfunction"
41186- ],
41187- "tm_scope": "source.mcfunction",
41188- "ace_mode": "text",
41189- "language_id": 462488745
41190- },
41191- "mupad": {
41192- "type": "programming",
41193- "color": "#244963",
41194- "extensions": [
41195- ".mu"
41196- ],
41197- "tm_scope": "source.mupad",
41198- "ace_mode": "text",
41199- "language_id": 416
41200- },
41201- "nanorc": {
41202- "type": "data",
41203- "color": "#2d004d",
41204- "group": "INI",
41205- "extensions": [
41206- ".nanorc"
41207- ],
41208- "filenames": [
41209- ".nanorc",
41210- "nanorc"
41211- ],
41212- "tm_scope": "source.nanorc",
41213- "ace_mode": "text",
41214- "language_id": 775996197
41215- },
41216- "nesC": {
41217- "type": "programming",
41218- "color": "#94B0C7",
41219- "extensions": [
41220- ".nc"
41221- ],
41222- "ace_mode": "text",
41223- "tm_scope": "source.nesc",
41224- "language_id": 417
41225- },
41226- "ooc": {
41227- "type": "programming",
41228- "color": "#b0b77e",
41229- "extensions": [
41230- ".ooc"
41231- ],
41232- "tm_scope": "source.ooc",
41233- "ace_mode": "text",
41234- "language_id": 418
41235- },
41236- "q": {
41237- "type": "programming",
41238- "extensions": [
41239- ".q"
41240- ],
41241- "tm_scope": "source.q",
41242- "ace_mode": "text",
41243- "color": "#0040cd",
41244- "language_id": 970539067
41245- },
41246- "reStructuredText": {
41247- "type": "prose",
41248- "color": "#141414",
41249- "wrap": true,
41250- "aliases": [
41251- "rst"
41252- ],
41253- "extensions": [
41254- ".rst",
41255- ".rest",
41256- ".rest.txt",
41257- ".rst.txt"
41258- ],
41259- "tm_scope": "text.restructuredtext",
41260- "ace_mode": "text",
41261- "codemirror_mode": "rst",
41262- "codemirror_mime_type": "text/x-rst",
41263- "language_id": 419
41264- },
41265- "robots.txt": {
41266- "type": "data",
41267- "aliases": [
41268- "robots",
41269- "robots txt"
41270- ],
41271- "filenames": [
41272- "robots.txt"
41273- ],
41274- "ace_mode": "text",
41275- "tm_scope": "text.robots-txt",
41276- "language_id": 674736065
41277- },
41278- "sed": {
41279- "type": "programming",
41280- "color": "#64b970",
41281- "extensions": [
41282- ".sed"
41283- ],
41284- "interpreters": [
41285- "gsed",
41286- "minised",
41287- "sed",
41288- "ssed"
41289- ],
41290- "ace_mode": "text",
41291- "tm_scope": "source.sed",
41292- "language_id": 847830017
41293- },
41294- "wisp": {
41295- "type": "programming",
41296- "ace_mode": "clojure",
41297- "codemirror_mode": "clojure",
41298- "codemirror_mime_type": "text/x-clojure",
41299- "color": "#7582D1",
41300- "extensions": [
41301- ".wisp"
41302- ],
41303- "tm_scope": "source.clojure",
41304- "language_id": 420
41305- },
41306- "xBase": {
41307- "type": "programming",
41308- "color": "#403a40",
41309- "aliases": [
41310- "advpl",
41311- "clipper",
41312- "foxpro"
41313- ],
41314- "extensions": [
41315- ".prg",
41316- ".ch",
41317- ".prw"
41318- ],
41319- "tm_scope": "source.harbour",
41320- "ace_mode": "text",
41321- "language_id": 421
41322- }
41323- }