Initial commit

This commit is contained in:
~lucidiot 2023-02-24 15:11:05 +01:00
commit 42b004c664
4 changed files with 452 additions and 0 deletions

15
.gitignore vendored Normal file
View File

@ -0,0 +1,15 @@
.vscode/*
[._]*.s[a-v][a-z]
[._]*.sw[a-p]
[._]s[a-rt-v][a-z]
[._]ss[a-gi-z]
[._]sw[a-p]
Session.vim
Sessionx.vim
.netrwhist
*~
tags
[._]*.un~
*.sqlite

85
ics.jq Normal file
View File

@ -0,0 +1,85 @@
# RFC 5545 (iCalendar) parser
# ~lucidiot, 2023
# iCalendar (.ics) files contain a series of components delimited by BEGIN:<type> and END:<type>.
# Each component can have properties. Each property has a name and a value.
# Each property may have some optional key/value parameters:
# THING;PARAM1=VALUE1;PARAM2=VALUE2:CONTENT
# Parameters may have a list of values instead of just one parameter value:
# THING;PARAM=VAL1,VAL2,VAL3,"VAL4,WITH,COMMAS":CONTENT
# We do parse the parameter syntax here, but they will in the end only be stored as a single string.
# Remove any final newlines as this would appear to us as an empty line
rtrimstr("\n")
| rtrimstr("\r")
# Lines are supposed to end after 75 characters. Adding a space at the beginning of the next line
# means that the next line is really just part of the previous line, so we remove those extra
# line breaks to merge every line.
| gsub("\r?\n "; "")
# Iterate on each line.
| reduce split("\n")[] as $item (
# Initial state of the parser
{
# Placeholder for the root component of this file.
# The _type will be filled in with the type specified in BEGIN:<type>.
# "_" is not an allowed character in property names, so we can use it for our own purposes.
"root": {"_type": null},
# Path within this state where the parser is currently inserting new properties.
# This is used to keep track of where we are in the hierarchy when parsing nested components.
"current_path": ["root"]
};
. as $state
| (
$item
# Parse a whole line as { name: "...", param: "..." (or null), value: "..." }
| capture("^(?'name'[a-zA-Z0-9-]+)(?:;(?'params'[a-zA-Z0-9-]+=(?:\"[^[:cntrl:]\"]*\"|[^[:cntrl:]\",;:]*)(?:,(?:\"[^[:cntrl:]\"]*\"|[^[:cntrl:]\",;:]*))*(?:;[a-zA-Z0-9-]+=(?:\"[^[:cntrl:]\"]*\"|[^[:cntrl:]\",;:]*)(?:,(?:\"[^[:cntrl:]\"]*\"|[^[:cntrl:]\",;:]*))*)*))?:(?'value'[^[:cntrl:]]*)\r?$")
) as {$name, $params, $value}
# Property names should be case-insensitive, we will use lowercase everywhere
| ($name | ascii_downcase) as $name
| $state
| if .current_path[0] != "root" then
# If we get any line after an `END:` that was meant for the root component,
# the current_path will be set to []. We should not allow parsing anything else.
error("Unexpected end of root component")
elif getpath([.current_path[], "_type"]) == null then
# When the type was not yet filled in, we are expecting a BEGIN for the root component.
if $name == "begin" then
setpath([.current_path[], "_type"]; $value)
else
error("Expected BEGIN, got \($name)")
end
elif $name == "begin" then
# This BEGIN: declares a nested component.
# When we are somewhere other than the root component, we will never get a `null` type
# because we can set it as soon as we get here; so we know that the above branch only
# runs for the root component and we are always working with nested components.
# We will nest components under an array called `_components`.
# We therefore add to our paths the `_components` key, and then the index of this new
# component. The length of the array matches the last index of the array + 1, so this
# will append our new component at the end of the list.
.current_path += ["_components", ((getpath(.current_path)._components // []) | length)]
# Add the new component now at our new path with the type from the BEGIN:<type>.
| setpath(.current_path; {"_type": $value})
elif $name == "end" then
# Handle an END by checking that its type matches the type we are working with now,
# and going back up in the structure by removing the array index and the `_components` from the path.
if $value == getpath([.current_path[], "_type"]) then
.current_path |= .[:-2]
else
error("Unexpected end of \($value) component while in a \(getpath([.current_path[], "_type"])) component")
end
else
# This is not any special case, so we will just set a property.
# Since some properties could have multiple values for the same set of parameters
# and multiple sets of parameters for the same name, we structure the output like this:
# { "name": {"parameters": ["value1", "value2"] } }
# When there are no parameters, we will use the "" key.
setpath(
[.current_path[], $name, ($params // "")];
(getpath([.current_path[], $name, ($params // "")]) // []) + [$value]
)
end
)
# Return the parsed calendar from our parser's state.
| .root

292
init.sql Normal file
View File

@ -0,0 +1,292 @@
PRAGMA foreign_keys = OFF;
BEGIN TRANSACTION;
INSERT INTO library (name) VALUES
('Abbaye-les-Bains'),
('Alliance'),
('Arlequin'),
('Bibliothèque d''étude et du patrimoine'),
('Kateb Yacine'),
('Bibliothèque municipale internationale'),
('Centre-Ville'),
('Eaux-Claires'),
('Fonds commun'),
('Jardin de Ville'),
('Saint-Bruno'),
('Tesseire-Malherbe'),
('Archives municipales'),
('Musée de Grenoble');
INSERT INTO weekdays (dow, name) VALUES
(0, 'Dimanche'),
(1, 'Lundi'),
(2, 'Mardi'),
(3, 'Mercredi'),
(4, 'Jeudi'),
(5, 'Vendredi'),
(6, 'Samedi');
INSERT INTO schedule (library, dow, holidays, start, end) VALUES
('Bibliothèque d''étude et du patrimoine', 2, FALSE, '10:00:00', '19:00:00'),
('Bibliothèque d''étude et du patrimoine', 3, FALSE, '10:00:00', '19:00:00'),
('Bibliothèque d''étude et du patrimoine', 4, FALSE, '13:00:00', '19:00:00'),
('Bibliothèque d''étude et du patrimoine', 5, FALSE, '10:00:00', '19:00:00'),
('Bibliothèque d''étude et du patrimoine', 6, FALSE, '10:00:00', '18:00:00'),
('Bibliothèque d''étude et du patrimoine', 2, TRUE, '13:00:00', '18:00:00'),
('Bibliothèque d''étude et du patrimoine', 3, TRUE, '13:00:00', '18:00:00'),
('Bibliothèque d''étude et du patrimoine', 4, TRUE, '13:00:00', '18:00:00'),
('Bibliothèque d''étude et du patrimoine', 5, TRUE, '13:00:00', '18:00:00'),
('Bibliothèque d''étude et du patrimoine', 6, TRUE, '13:00:00', '18:00:00'),
('Bibliothèque municipale internationale', 2, FALSE, '17:00:00', '19:00:00'),
('Bibliothèque municipale internationale', 3, FALSE, '13:00:00', '18:00:00'),
('Bibliothèque municipale internationale', 4, FALSE, '17:00:00', '19:00:00'),
('Bibliothèque municipale internationale', 5, FALSE, '17:00:00', '19:00:00'),
('Bibliothèque municipale internationale', 6, NULL, '10:00:00', '13:00:00'),
('Bibliothèque municipale internationale', 6, FALSE, '14:00:00', '17:00:00'),
('Bibliothèque municipale internationale', 2, TRUE, '14:00:00', '18:00:00'),
('Bibliothèque municipale internationale', 3, TRUE, '14:00:00', '18:00:00'),
('Bibliothèque municipale internationale', 4, TRUE, '14:00:00', '18:00:00'),
('Bibliothèque municipale internationale', 5, TRUE, '14:00:00', '18:00:00'),
('Abbaye-les-Bains', 2, NULL, '13:00:00', '18:30:00'),
('Abbaye-les-Bains', 3, NULL, '10:00:00', '13:00:00'),
('Abbaye-les-Bains', 3, NULL, '14:00:00', '18:00:00'),
('Abbaye-les-Bains', 4, NULL, '09:00:00', '12:00:00'),
('Abbaye-les-Bains', 5, NULL, '13:00:00', '18:30:00'),
('Abbaye-les-Bains', 6, NULL, '10:00:00', '13:00:00'),
('Abbaye-les-Bains', 6, NULL, '14:00:00', '18:00:00'),
('Archives municipales', 1, NULL, '13:00:00', '17:00:00'),
('Archives municipales', 2, NULL, '13:00:00', '17:00:00'),
('Archives municipales', 3, NULL, '13:00:00', '17:00:00'),
('Archives municipales', 4, NULL, '09:00:00', '12:30:00'),
('Archives municipales', 5, NULL, '09:00:00', '12:30:00'),
('Musée de Grenoble', 1, NULL, '14:00:00', '18:00:00'),
('Musée de Grenoble', 3, NULL, '14:00:00', '18:00:00'),
('Musée de Grenoble', 4, NULL, '14:00:00', '18:00:00'),
('Musée de Grenoble', 5, NULL, '14:00:00', '18:00:00'),
('Kateb Yacine', 2, NULL, '11:00:00', '18:30:00'),
('Kateb Yacine', 3, NULL, '11:00:00', '18:30:00'),
('Kateb Yacine', 4, NULL, '11:00:00', '18:30:00'),
('Kateb Yacine', 5, NULL, '11:00:00', '18:30:00'),
('Kateb Yacine', 6, NULL, '11:00:00', '18:30:00'),
('Centre-Ville', 2, NULL, '11:00:00', '18:30:00'),
('Centre-Ville', 3, NULL, '11:00:00', '18:30:00'),
('Centre-Ville', 4, NULL, '11:00:00', '18:30:00'),
('Centre-Ville', 5, NULL, '11:00:00', '18:30:00'),
('Centre-Ville', 6, NULL, '11:00:00', '18:30:00'),
('Alliance', 2, NULL, '14:00:00', '19:00:00'),
('Alliance', 3, NULL, '10:00:00', '13:00:00'),
('Alliance', 3, NULL, '14:00:00', '18:00:00'),
('Alliance', 6, NULL, '10:00:00', '13:00:00'),
('Alliance', 6, NULL, '14:00:00', '18:00:00'),
('Arlequin', 2, NULL, '13:00:00', '18:00:00'),
('Arlequin', 3, NULL, '10:00:00', '13:00:00'),
('Arlequin', 3, NULL, '14:00:00', '18:00:00'),
('Arlequin', 5, NULL, '13:00:00', '18:00:00'),
('Arlequin', 4, NULL, '09:00:00', '12:00:00'),
('Arlequin', 6, NULL, '10:00:00', '13:00:00'),
('Arlequin', 6, NULL, '14:00:00', '18:00:00'),
('Eaux-Claires', 2, NULL, '13:00:00', '18:30:00'),
('Eaux-Claires', 3, NULL, '10:00:00', '13:00:00'),
('Eaux-Claires', 3, NULL, '14:00:00', '18:00:00'),
('Eaux-Claires', 4, NULL, '09:00:00', '12:00:00'),
('Eaux-Claires', 5, NULL, '13:00:00', '18:30:00'),
('Eaux-Claires', 6, NULL, '10:00:00', '13:00:00'),
('Eaux-Claires', 6, NULL, '14:00:00', '18:00:00'),
('Jardin de Ville', 2, NULL, '16:00:00', '18:00:00'),
('Jardin de Ville', 3, NULL, '10:00:00', '13:00:00'),
('Jardin de Ville', 3, NULL, '14:00:00', '18:00:00'),
('Jardin de Ville', 5, NULL, '16:00:00', '18:00:00'),
('Jardin de Ville', 6, NULL, '10:00:00', '13:00:00'),
('Jardin de Ville', 6, NULL, '14:00:00', '18:00:00'),
('Tesseire-Malherbe', 2, NULL, '13:00:00', '18:30:00'),
('Tesseire-Malherbe', 3, NULL, '10:00:00', '13:00:00'),
('Tesseire-Malherbe', 3, NULL, '14:00:00', '18:00:00'),
('Tesseire-Malherbe', 4, NULL, '09:00:00', '12:00:00'),
('Tesseire-Malherbe', 5, NULL, '13:00:00', '18:30:00'),
('Tesseire-Malherbe', 6, NULL, '10:00:00', '13:00:00'),
('Tesseire-Malherbe', 6, NULL, '14:00:00', '18:00:00'),
('Saint-Bruno', 2, NULL, '13:00:00', '18:30:00'),
('Saint-Bruno', 3, NULL, '10:00:00', '13:00:00'),
('Saint-Bruno', 3, NULL, '14:00:00', '18:00:00'),
('Saint-Bruno', 4, NULL, '09:00:00', '12:00:00'),
('Saint-Bruno', 5, NULL, '13:00:00', '18:30:00'),
('Saint-Bruno', 6, NULL, '10:00:00', '13:00:00'),
('Saint-Bruno', 6, NULL, '14:00:00', '18:00:00');
INSERT INTO holidays (start, end, closed) VALUES
('2017-10-20', '2017-11-05', FALSE),
('2017-12-22', '2018-01-07', FALSE),
('2018-01-01', '2018-01-01', TRUE),
('2018-02-09', '2018-02-25', FALSE),
('2018-04-02', '2018-04-02', TRUE),
('2018-04-06', '2018-04-22', FALSE),
('2018-05-01', '2018-05-01', TRUE),
('2018-05-08', '2018-05-08', TRUE),
('2018-05-10', '2018-05-10', TRUE),
('2018-05-21', '2018-05-21', TRUE),
('2018-07-06', '2018-09-02', FALSE),
('2018-07-14', '2018-07-14', TRUE),
('2018-08-15', '2018-08-15', TRUE),
('2018-10-19', '2018-11-04', FALSE),
('2018-11-01', '2018-11-01', TRUE),
('2018-11-11', '2018-11-11', TRUE),
('2018-12-21', '2019-01-06', FALSE),
('2018-12-25', '2018-12-25', TRUE),
('2019-01-01', '2019-01-01', TRUE),
('2019-02-15', '2019-03-03', FALSE),
('2019-04-12', '2019-04-28', FALSE),
('2019-04-22', '2019-04-22', TRUE),
('2019-05-01', '2019-05-01', TRUE),
('2019-05-08', '2019-05-08', TRUE),
('2019-05-28', '2019-06-02', FALSE),
('2019-05-30', '2019-05-30', TRUE),
('2019-06-10', '2019-06-10', TRUE),
('2019-07-05', '2019-09-01', FALSE),
('2019-07-14', '2019-07-14', TRUE),
('2019-08-15', '2019-08-15', TRUE),
('2019-10-18', '2019-11-03', FALSE),
('2019-11-01', '2019-11-01', TRUE),
('2019-11-11', '2019-11-11', TRUE),
('2019-12-20', '2020-01-05', FALSE),
('2019-12-25', '2019-12-25', TRUE),
('2020-01-01', '2020-01-01', TRUE),
('2020-02-21', '2020-03-08', FALSE),
('2020-04-13', '2020-04-13', TRUE),
('2020-04-17', '2020-05-03', FALSE),
('2020-05-01', '2020-05-01', TRUE),
('2020-05-08', '2020-05-08', TRUE),
('2020-05-19', '2020-05-24', FALSE),
('2020-05-21', '2020-05-21', TRUE),
('2020-06-01', '2020-06-01', TRUE),
('2020-07-03', '2020-08-31', FALSE),
('2020-07-14', '2020-07-14', TRUE),
('2020-08-15', '2020-08-15', TRUE),
('2020-10-16', '2020-11-01', FALSE),
('2020-11-01', '2020-11-01', TRUE),
('2020-11-11', '2020-11-11', TRUE),
('2020-12-18', '2021-01-03', FALSE),
('2020-12-25', '2020-12-25', TRUE),
('2021-01-01', '2021-01-01', TRUE),
('2021-02-05', '2021-02-21', FALSE),
('2021-04-05', '2021-04-05', TRUE),
('2021-04-09', '2021-04-25', FALSE),
('2021-05-01', '2021-05-01', TRUE),
('2021-05-08', '2021-05-08', TRUE),
('2021-05-12', '2021-05-16', FALSE),
('2021-05-13', '2021-05-13', TRUE),
('2021-05-24', '2021-05-24', TRUE),
('2021-07-05', '2021-09-01', FALSE),
('2021-07-14', '2021-07-14', TRUE),
('2021-08-15', '2021-08-15', TRUE),
('2021-10-22', '2021-11-07', FALSE),
('2021-11-01', '2021-11-01', TRUE),
('2021-11-11', '2021-11-11', TRUE),
('2021-12-17', '2022-01-02', FALSE),
('2021-12-25', '2021-12-25', TRUE),
('2022-01-01', '2022-01-01', TRUE),
('2022-02-11', '2022-02-27', FALSE),
('2022-04-15', '2022-05-01', FALSE),
('2022-04-18', '2022-04-18', TRUE),
('2022-05-01', '2022-05-01', TRUE),
('2022-05-08', '2022-05-08', TRUE),
('2022-05-25', '2022-05-27', FALSE),
('2022-05-26', '2022-05-26', TRUE),
('2022-06-06', '2022-06-06', TRUE),
('2022-07-06', '2022-08-31', FALSE),
('2022-07-14', '2022-07-14', TRUE),
('2022-08-15', '2022-08-15', TRUE),
('2022-10-21', '2022-11-06', FALSE),
('2022-11-01', '2022-11-01', TRUE),
('2022-11-11', '2022-11-11', TRUE),
('2022-12-16', '2023-01-02', FALSE),
('2022-12-25', '2022-12-25', TRUE),
('2023-01-01', '2023-01-01', TRUE),
('2023-02-03', '2023-02-19', FALSE),
('2023-04-07', '2023-04-23', FALSE),
('2023-04-10', '2023-04-10', TRUE),
('2023-05-01', '2023-05-01', TRUE),
('2023-05-08', '2023-05-08', TRUE),
('2023-05-17', '2023-05-21', FALSE),
('2023-05-18', '2023-05-18', TRUE),
('2023-05-29', '2023-05-29', TRUE),
('2023-07-07', '2023-09-03', FALSE),
('2023-07-14', '2023-07-14', TRUE),
('2023-08-15', '2023-08-15', TRUE),
('2023-10-20', '2023-11-05', FALSE),
('2023-11-01', '2023-11-01', TRUE),
('2023-11-11', '2023-11-11', TRUE),
('2023-12-22', '2024-01-07', FALSE),
('2023-12-25', '2023-12-25', TRUE),
('2024-01-01', '2024-01-01', TRUE),
('2024-02-16', '2024-03-03', FALSE),
('2024-04-01', '2024-04-01', TRUE),
('2024-04-12', '2024-04-28', FALSE),
('2024-05-01', '2024-05-01', TRUE),
('2024-05-08', '2024-05-08', TRUE),
('2024-05-09', '2024-05-09', TRUE),
('2024-05-09', '2024-05-10', FALSE),
('2024-05-20', '2024-05-20', TRUE),
('2024-07-05', '2024-09-01', FALSE),
('2024-07-14', '2024-07-14', TRUE),
('2024-08-15', '2024-08-15', TRUE),
('2024-10-18', '2024-11-03', FALSE),
('2024-11-01', '2024-11-01', TRUE),
('2024-11-11', '2024-11-11', TRUE),
('2024-12-20', '2025-01-05', FALSE),
('2024-12-25', '2024-12-25', TRUE),
('2025-01-01', '2025-01-01', TRUE),
('2025-02-21', '2025-03-09', FALSE),
('2025-04-18', '2025-05-04', FALSE),
('2025-04-21', '2025-04-21', TRUE),
('2025-05-01', '2025-05-01', TRUE),
('2025-05-08', '2025-05-08', TRUE),
('2025-05-29', '2025-05-29', TRUE),
('2025-05-29', '2025-05-30', FALSE),
('2025-06-09', '2025-06-09', TRUE),
('2025-07-04', '2025-08-31', FALSE),
('2025-07-14', '2025-07-14', TRUE),
('2025-08-15', '2025-08-15', TRUE),
('2025-10-17', '2025-11-02', FALSE),
('2025-11-01', '2025-11-01', TRUE),
('2025-11-11', '2025-11-11', TRUE),
('2025-12-19', '2026-01-04', FALSE),
('2025-12-25', '2025-12-25', TRUE),
('2026-01-01', '2026-01-01', TRUE),
('2026-02-06', '2026-02-22', FALSE),
('2026-04-03', '2026-04-19', FALSE),
('2026-04-06', '2026-04-06', TRUE),
('2026-05-01', '2026-05-01', TRUE),
('2026-05-08', '2026-05-08', TRUE),
('2026-05-14', '2026-05-14', TRUE),
('2026-05-14', '2026-05-15', FALSE),
('2026-05-25', '2026-05-25', TRUE),
('2026-07-03', '2026-07-03', FALSE),
('2026-07-14', '2026-07-14', TRUE),
('2026-08-15', '2026-08-15', TRUE),
('2026-11-01', '2026-11-01', TRUE),
('2026-11-11', '2026-11-11', TRUE),
('2026-12-25', '2026-12-25', TRUE),
('2027-01-01', '2027-01-01', TRUE),
('2027-03-29', '2027-03-29', TRUE),
('2027-05-01', '2027-05-01', TRUE),
('2027-05-06', '2027-05-06', TRUE),
('2027-05-08', '2027-05-08', TRUE),
('2027-05-17', '2027-05-17', TRUE),
('2027-07-14', '2027-07-14', TRUE),
('2027-08-15', '2027-08-15', TRUE),
('2027-11-01', '2027-11-01', TRUE),
('2027-11-11', '2027-11-11', TRUE),
('2027-12-25', '2027-12-25', TRUE),
('2028-01-01', '2028-01-01', TRUE),
('2028-04-17', '2028-04-17', TRUE),
('2028-05-01', '2028-05-01', TRUE),
('2028-05-08', '2028-05-08', TRUE),
('2028-05-25', '2028-05-25', TRUE),
('2028-06-05', '2028-06-05', TRUE),
('2028-07-14', '2028-07-14', TRUE),
('2028-08-15', '2028-08-15', TRUE),
('2028-11-01', '2028-11-01', TRUE),
('2028-11-11', '2028-11-11', TRUE),
('2028-12-25', '2028-12-25', TRUE);
COMMIT;

60
schema.sql Normal file
View File

@ -0,0 +1,60 @@
BEGIN TRANSACTION;
CREATE TABLE library (
name TEXT PRIMARY KEY NOT NULL
);
CREATE TABLE work (
ark TEXT NOT NULL PRIMARY KEY
CHECK (ark LIKE 'ark:/%'),
author TEXT NOT NULL,
title TEXT NOT NULL
);
CREATE TABLE book (
ark TEXT NOT NULL
REFERENCES work (ark) ON UPDATE CASCADE ON DELETE CASCADE,
library TEXT NOT NULL
REFERENCES library (name) ON UPDATE CASCADE ON DELETE CASCADE,
location TEXT NOT NULL,
dewey TEXT NOT NULL,
borrowable INTEGER NOT NULL
DEFAULT TRUE
CHECK (borrowable IS TRUE OR borrowable IS FALSE),
PRIMARY KEY (ark, library)
);
CREATE TABLE weekdays (
dow INTEGER NOT NULL PRIMARY KEY
CHECK (dow BETWEEN 0 AND 6),
name TEXT NOT NULL
);
CREATE TABLE schedule (
library TEXT NOT NULL
REFERENCES library (name) ON UPDATE CASCADE ON DELETE CASCADE,
dow INTEGER NOT NULL
REFERENCES weekdays (dow) ON UPDATE CASCADE ON DELETE RESTRICT,
holidays INTEGER
CHECK (holidays IS NULL OR holidays IS TRUE OR holidays IS FALSE),
start TEXT NOT NULL
CHECK (start GLOB '[01][0-9]:[0-5][0-9]:[0-5][0-9]' OR start GLOB '2[0-3]:[0-5][0-9]:[0-5][0-9]'),
end TEXT NOT NULL
CHECK (end GLOB '[01][0-9]:[0-5][0-9]:[0-5][0-9]' OR end GLOB '2[0-3]:[0-5][0-9]:[0-5][0-9]'),
UNIQUE (library, dow, start, end),
CONSTRAINT start_before_end CHECK (julianday(start) < julianday(end))
);
CREATE TABLE holidays (
start TEXT NOT NULL
CHECK (start GLOB '[0-9][0-9][0-9][0-9]-[01][0-9]-[0-3][0-9]'),
end TEXT NOT NULL
CHECK (end GLOB '[0-9][0-9][0-9][0-9]-[01][0-9]-[0-3][0-9]'),
closed INTEGER NOT NULL
DEFAULT FALSE
CHECK (closed IS FALSE OR closed IS TRUE),
PRIMARY KEY (start, end),
CONSTRAINT start_before_end CHECK (julianday(start) <= julianday(end))
);
COMMIT;