v0.1.0-prealpha1

Binaries:
- juniority, an ncurses-based TUI application for viewing your idol
  database.
- juniority-importer, an stdio data importer for modifying your idol
  database.
- idoldb-transform, a data converter script to bootstrap off of
  ikki123-aidoru/idol-database.

Data:
- idol-database.sqlite3, an idol database bootstrapped with data from
  ikki123-aidoru/idol-database.
- idol-database.jun, the juniority import syntax data used to produce
  the above database. An example of juniority import syntax for the
  curious.

Features:
- TUI application provides many views of idol database:
  - Dedicated views for idols, volumes, and studios.
  - Search views for searching over tags for volumes and idols.
- Data importer is complete. It imports data, and can be used to
  deassociate tags and idols from volumes. All database modification is
  done through the data importer.
- idoldb-transform script converts idol-database data into juniority
  import syntax that can be piped into the data importer.
このコミットが含まれているのは:
Jay Eye 2023-03-20 04:16:37 -04:00
コミット 12b9f9cfe7
10個のファイルの変更4459行の追加461行の削除

65
README.md ノーマルファイル
ファイルの表示

@ -0,0 +1,65 @@
# Juniority
Juniority is an idol/movie organizer application. Juniority uses ncurses for a
terminal user interface (TUI) and sqlite3 for data storage. Juniority is written
in GNU Guile.
## Install
### Dependencies
* GNU Guile >=3.0.9
* guile-ncurses >= 3.0
* guile-sqlite3 >= 0.1.3
### GUILE_LOAD_PATH
If you already have a private Guile directory set up, use that. If not, set one up:
``` sh
export PRIVATE_GUILE_LOAD_PATH="$HOME/.local/share/guile/site/3.0"
mkdir -p $PRIVATE_GUILE_LOAD_PATH
ln -sr $JUNIORITY_DIR $PRIVATE_GUILE_LOAD_PATH/juniority
```
Edit your `.bashrc` to include this directory in your `GUILE_LOAD_PATH`:
``` sh
PRIVATE_GUILE_LOAD_PATH="$HOME/.local/share/guile:$HOME/.local/share/guile/site/3.0"
GUILE_LOAD_PATH="$PRIVATE_GUILE_LOAD_PATH:$GUILE_LOAD_PATH"
```
Confirm that Guile is recognizing this change:
``` scheme
scheme@(guile-user)> %load-path
$1 = ( ... )
```
### Binaries
You can symlink the binaries from your personal binary directory:
``` sh
BINDIR="$HOME/.local/bin"
pushd $BINDIR
ln -sr $JUNIORITY_DIR/bin/juniority.scm ./juniority
ln -sr $JUNIORITY_DIR/bin/juniority-import.scm ./juniority-import
```
### Database
Juniority ships with a pre-loaded database thanks to [ikki123-aidoru/idol_database](https://github.com/ikki123-aidoru/idol_database). It is located at `data/idol-database.sqlite3`.
``` sh
PRIVATE_DIR="$HOME/Private" # ecryptfs etc.
mv $JUNIORITY_DIR/data/idol-database.sqlite3 $PRIVATE_DIR/juniority.sqlite3
juniority !!:2
```
## Usage
The binaries display help when invoked without arguments:
- `juniority`
- `juniority-import`

127
bin/idoldb-transform.scm 実行可能ファイル
ファイルの表示

@ -0,0 +1,127 @@
#!/usr/bin/env -S guile -e main -s
coding: utf-8
!#
(use-modules
(ice-9 match)
(ice-9 peg)
(ice-9 rdelim)
(juniority grammar)
)
;; This program transforms an idol_database
;; idol/movie line into a statement in Juniority
;; format.
;; Consequently, it can be piped into
;; `juniority-import'.
;;
;; https://github.com/ikki123-aidoru/idol_database
(define-peg-pattern DASH none "-")
(define-peg-pattern SLASH none "/")
(define-peg-pattern digit body (range #\0 #\9))
(define-peg-pattern year all (+ digit))
(define-peg-pattern month all (+ digit))
(define-peg-pattern day all (+ digit))
(define-peg-pattern dash-date all
(and year DASH month DASH day))
(define-peg-pattern slash-date all
(and day SLASH month SLASH year))
(define-peg-pattern date all
(or dash-date slash-date "N/A"))
(define (main args)
(match args
((bin idols-file movies-file)
(let ((idols-hash (main-idols idols-file)))
(main-movies idols-hash movies-file)))
((bin . other)
(format #t "Usage: ~a path/to/idols.tsv path/to/movies.tsv
This utility is meant to be used in concert with `juniority-import`. E.g.:
~a path/to/idols.tsv path/to/movies.tsv | juniority-import path/to/juniority.db
" bin bin))))
(define (main-idols idols-file)
(let ((idols-hash (make-hash-table))
(idols-port (open-file idols-file "r"))
)
(do ((line (read-line idols-port) (read-line idols-port)))
((eof-object? line))
(match (string-split line #\tab)
;; Ignore header
(("Name" "Japanese Name" "Alias" "DOB" "Doubtful DOB") (noop))
;; Parse data
((en-name "" alias dob _doubtful-dob) (noop)) ;; no
((en-name jp-name alias dob _doubtful-dob)
(let ((date-of-birth
(if (or (string-null? dob)
(string-any
(lambda (ch) (not (or (char-numeric? ch) (eqv? #\- ch))))
dob))
""
(normalize-date dob))))
(format #t "@'~a' '~a'~a\n"
jp-name
en-name
date-of-birth)
(when (not (string-null? alias))
(format #t "@'~a' '~a'~a\n"
alias
en-name
date-of-birth))
(hash-set! idols-hash en-name jp-name)
))
;; Ignore empty lines
(() (noop))
))
idols-hash))
(define (main-movies idols-hash movies-file)
(let ((movies-port (open-file movies-file "r")))
(do ((line (read-line movies-port) (read-line movies-port)))
((eof-object? line))
(match (string-split line #\tab)
;; Ignore headers
(("Title" "Series" "Maker" "Release Date" "Actress") (noop))
;; Parse data
((ident series _maker published idol-en-name)
(let ((idol-jp-name (hash-ref idols-hash idol-en-name))
(date-of-publication
(if (or (string-null? published)
(string-any
(lambda (ch) (not (or (char-numeric? ch) (eqv? #\- ch))))
published))
""
(normalize-date published))))
(if idol-jp-name
(format #t "~a @'~a' ~a\n"
ident
idol-jp-name
date-of-publication)
(noop))))
;; Ignore empty lines
(() (noop))
))))
(define (normalize-date the-date)
(match (peg:tree (match-pattern date the-date))
(('date "N/A") "")
(('date ('dash-date _ _ _))
(format #f " ~a" the-date))
(('date ('slash-date ('day day) ('month month) ('year year)))
(format #f " ~a-~a-~a"
(if (eqv? 2 (string-length year))
(if (< (string->number year) 35)
(string-append "20" year)
(string-append "19" year))
year)
month
day))))

280
bin/juniority-import.scm 実行可能ファイル
ファイルの表示

@ -0,0 +1,280 @@
#!/usr/bin/env -S guile -e main -s
coding: utf-8
!#
(use-modules
(ice-9 exceptions)
(ice-9 match)
(ice-9 format)
(ice-9 peg)
(ice-9 rdelim)
(ice-9 regex)
(juniority database)
(juniority grammar)
(juniority util)
(srfi srfi-1)
(srfi srfi-43)
)
(define (main args)
(match args
((bin db-path) (main-import db-path))
((bin . _)
(format #t "Import:
In order to import your existing records, it must be in the following format:
stmt = volume-stmt | idol-stmt | tag-stmt
volume-stmt = studio-[0-9]+ idol-assoc* tag-assoc* pub?
idol-stmt = idol-tag tag-assoc* dob?
idol-assoc = idol-tag | idol-untag
idol-tag = @'name'
idol-untag = !@'name'
tag-assoc = tag | untag
tag = 'name'
untag = !'name'
dob = date
pub = date
date = nnnn-nn-nn
n = [0-9]
studio = [A-Z]+
Example:
@'高橋ひかる' 'Takahashi' 'Hikaru' 2001-09-22
BSTAR-001 @'高橋ひかる'
BSTAR-019 @'高橋ひかる'
@'蒼井玲奈' 'Aoi' 'Reina' 1996-03-19
ICDV-30223 @'蒼井玲奈' 'thong' 'butt' 'catgirl' 'lollipop' 'popsicle'
...
Notes:
- Idol statements must precede volume statements that reference them.
- Duplicate idol and volume statements are treated as updates.
- (Idol-)Untag expressions remove a previously-associated tag (or idol).
Feed your records to juniority to bootstrap your database:
~a import path/to/db.sqlite3 < path/to/import.jun
Use import mode in the future to add records to your database. For manual entry, combine it with a heredoc for convenience:
~a import path/to/db.sqlite3 << EOF
...
EOF
" bin bin))
))
(define (main-import db-path)
(let ((db (open-db db-path))
)
(migrate-db db)
(do ((line (read-line) (read-line)))
((eof-object? line))
(process-import-line db (peg:tree (match-pattern import-line line)))
)))
(define (process-import-line db line)
(let ()
(match line
(('volume-entry ('volume identifier) 'idols 'tags ('date pub-date))
(import-volume db identifier pub-date (list) (list)))
(('volume-entry ('volume identifier) idols 'tags ('date pub-date))
(import-volume db identifier pub-date (cdr idols) (list)))
(('volume-entry ('volume identifier) 'idols tags ('date pub-date))
(import-volume db identifier pub-date (list) (cdr tags)))
(('volume-entry ('volume identifier) idols tags ('date pub-date))
(import-volume db identifier pub-date (cdr idols) (cdr tags)))
(('volume-entry ('volume identifier) 'idols 'tags)
(import-volume db identifier #nil (list) (list)))
(('volume-entry ('volume identifier) idols 'tags)
(import-volume db identifier #nil (cdr idols) (list)))
(('volume-entry ('volume identifier) 'idols tags)
(import-volume db identifier #nil (list) (cdr tags)))
(('volume-entry ('volume identifier) idols tags)
(import-volume db identifier #nil (cdr idols) (cdr tags)))
(('idol-entry ('idol-tag jp-name) 'tags ('date birth-date))
(import-idol db jp-name birth-date (list)))
(('idol-entry ('idol-tag jp-name) tags ('date birth-date))
(import-idol db jp-name birth-date (cdr tags)))
(('idol-entry ('idol-tag jp-name) 'tags)
(import-idol db jp-name #nil (list)))
(('idol-entry ('idol-tag jp-name) tags)
(import-idol db jp-name #nil (cdr tags)))
(#f (noop))
)))
(define (import-volume db identifier pub-date idols tags)
(let* ((studio-callsign
(first (string-split identifier (lambda (ch) (eq? #\- ch)))))
(volume-id (param-query-one db select-volume-stmt identifier))
(studio-id (param-query-one db select-studio-stmt studio-callsign))
(idol-ids #nil)
(tag-ids #nil)
(existing-volume? #f)
)
(if (vector? volume-id)
(begin
(set! volume-id (vector-ref volume-id 0))
(set! existing-volume? #t)
(log "import-volume: found volume ~a" volume-id))
(begin
(set!
volume-id
(vector-ref
(param-query-one db insert-volume-stmt identifier pub-date)
0))
(log "import-volume: inserted volume ~a" volume-id)))
(if (vector? studio-id)
(begin
(set! studio-id (vector-ref studio-id 0))
(log "import-volume: found studio ~a" studio-id))
(begin
(set!
studio-id
(vector-ref
(param-query-one db insert-studio-stmt studio-callsign)
0))
(log "import-volume: inserted studio ~a" studio-id)))
(set!
idol-ids
(map
(lambda (idol)
(let* ((idol-name (second idol))
(idol-result (param-query-one db select-idol-stmt idol-name))
(idol-id
(if (vector? idol-result)
(begin
(log "import-volume: found idol ~a" idol-name)
(vector-ref idol-result 0))
(raise-exception
(make-exception-with-message
(format #f "import-volume: idol not found ~a" idol-name))))))
(list (first idol) idol-id)))
idols))
(set!
tag-ids
(map
(lambda (tag)
(let* ((tag-name (second tag))
(tag-result (param-query-one db select-tag-stmt tag-name))
(tag-id (if (vector? tag-result)
(begin
(log "import-volume: found tag ~a" tag-name)
(vector-ref tag-result 0))
(begin
(log "import-volume: inserted tag ~a" tag-name)
(vector-ref
(param-query-one db insert-tag-stmt tag-name)
0)))))
(list (first tag) tag-id)))
tags))
(if existing-volume?
(when pub-date
(param-query-one db update-volume-pub-stmt pub-date volume-id)
(log "import-volume: updated volume published date"))
(begin
(param-query-one db assoc-volume-studio-stmt volume-id studio-id)
(log "import-volume: associated studio ~a" studio-id)))
(let ((existing-tags
(param-query-map db
(lambda (ct row) (vector-ref row 0))
select-volume-tag-ids-stmt volume-id))
(existing-idols
(param-query-map db
(lambda (ct row) (vector-ref row 0))
select-volume-idol-ids-stmt volume-id)))
(for-each
(lambda (idol)
(match idol
(('idol-tag idol-id)
(unless (find (lambda (id) (eqv? idol-id id)) existing-idols)
(param-query-one db assoc-volume-idol-stmt volume-id idol-id)
(log "import-volume: associated idol ~a" idol-id)))
(('idol-untag idol-id)
(when (find (lambda (id) (eqv? idol-id id)) existing-idols)
(param-query-one db deassoc-volume-idol-stmt volume-id idol-id)
(log "import-volume: deassociated idol ~a" idol-id)))
(other (log "import-volume: unmatched idol pattern: ~a" other))
)
)
idol-ids)
(for-each
(lambda (tag)
(match tag
(('tag tag-id)
(unless (find (lambda (id) (eqv? tag-id id)) existing-tags)
(param-query-one db assoc-volume-tag-stmt volume-id tag-id)
(log "import-volume: associated tag ~a" tag-id)))
(('untag tag-id)
(when (find (lambda (id) (eqv? tag-id id)) existing-tags)
(param-query-one db deassoc-volume-tag-stmt volume-id tag-id)
(log "import-volume: deassociated tag ~a" tag-id)))
(other (log "import-volume: unmatched tag pattern: ~a" other))))
tag-ids)
)))
(define (import-idol db jp-name birth-date tags)
(let* ((idol-id (param-query-one db select-idol-stmt jp-name))
(tag-ids (list))
(existing-idol? #f)
)
(if (vector? idol-id)
(begin
(set! idol-id (vector-ref idol-id 0))
(set! existing-idol? #t)
(log "import-idol: found idol ~a" idol-id))
(begin
(set!
idol-id
(vector-ref (param-query-one db insert-idol-stmt jp-name birth-date) 0))
(log "import-idol: inserted idol ~a" idol-id)))
(set!
tag-ids
(map
(lambda (tag)
(let* ((tag-name (second tag))
(tag-result (param-query-one db select-tag-stmt tag-name))
(tag-id
(if (vector? tag-result)
(begin
(log "import-idol: found tag ~a" tag-name)
(vector-ref tag-result 0))
(begin
(log "import-idol: inserted tag ~a" tag-name)
(vector-ref
(param-query-one db insert-tag-stmt tag-name)
0)))))
(list (first tag) tag-id)))
tags))
(when (and existing-idol? birth-date)
(param-query-one db update-idol-dob-stmt birth-date idol-id)
(log "import-idol: updated idol birth date"))
(let ((existing-tags
(param-query-map db
(lambda (ct row) (vector-ref row 0))
select-idol-tag-ids-stmt idol-id)))
(for-each
(lambda (tag)
(match tag
(('tag tag-id)
(unless (find (lambda (id) (eqv? tag-id id)) existing-tags)
(param-query-one db assoc-idol-tag-stmt idol-id tag-id)
(log "import-idol: associated tag ~a" tag-id)))
(('untag tag-id)
(when (find (lambda (id) (eqv? tag-id id)) existing-tags)
(param-query-one db deassoc-idol-tag-stmt idol-id tag-id)
(log "import-idol: deassociated tag ~a" tag-id)))
(other (log "import-idol: unmatched tag pattern: ~a" other))))
tag-ids)
)
))

ファイル差分が大きすぎるため省略します 差分を読み込み

2361
data/idol-database.jun ノーマルファイル

ファイル差分が大きすぎるため省略します 差分を読み込み

バイナリ
data/idol-database.sqlite3 ノーマルファイル

バイナリファイルは表示されません。

475
database.scm ノーマルファイル
ファイルの表示

@ -0,0 +1,475 @@
(define-module (juniority database)
#:use-module (ice-9 exceptions)
#:use-module (ice-9 format)
#:use-module (sqlite3)
#:export (
assoc-idol-tag-stmt
assoc-volume-idol-stmt
assoc-volume-studio-stmt
assoc-volume-tag-stmt
count-idols-stmt
count-notes-stmt
count-studios-stmt
count-tags-stmt
count-volumes-stmt
create-tables-sql
deassoc-idol-tag-stmt
deassoc-volume-idol-stmt
deassoc-volume-tag-stmt
insert-idol-stmt
insert-studio-stmt
insert-tag-stmt
insert-volume-stmt
migrate-db
open-db
param-query
param-query-all
param-query-map
param-query-one
search-idols-stmt
search-volumes-stmt
select-all-idols-stmt
select-all-studios-stmt
select-all-tags-stmt
select-all-volumes-stmt
select-idol-notes-stmt
select-idol-stmt
select-idol-tag-ids-stmt
select-idol-tags-stmt
select-idol-volumes-stmt
select-studio-notes-stmt
select-studio-stmt
select-studio-tags-stmt
select-studio-volumes-stmt
select-tag-stmt
select-volume-idol-ids-stmt
select-volume-idols-stmt
select-volume-notes-stmt
select-volume-stmt
select-volume-tag-ids-stmt
select-volume-tags-stmt
update-idol-dob-stmt
update-volume-pub-stmt
))
;;;;;;;;;;;;;;;;;;;;;;;;
;; DATABAS ABASEDA
;; ATABASEDA BASEDATAB
;; TAB ATA ASE ABA
;; ABA AB SED AS
;; BAS BA EDA ASE
;; ASE AS DATABASE
;; SED SE ATABASEDA
;; EDA ED TAB ATA
;; DAT EDA ABA TAB
;; ATABASEDA BASEDATAB
;; TABASED ASEDATAB
;;;;;;;;;;;;;;;;;;;;;;;;
(define (param-query db stmt . args)
;; https://www.sqlite.org/c3ref/stmt.html
;; use `sqlite-prepare' to prepare a statement
(let ((p-stmt (sqlite-prepare db stmt)))
;; use `sqlite-bind-arguments' to bind args to a param query
(apply sqlite-bind-arguments (cons p-stmt args))
;; use `sqlite-step' to run a prepared statement
;; use `sqlite-reset' to commit the implicit transaction
p-stmt))
(define (param-query-all db stmt . rest)
(let ((results (list))
(query (apply param-query (cons* db stmt rest)))
)
(do ((result (sqlite-step query) (sqlite-step query)))
((eq? result #f))
(set! results (cons result results)))
(sqlite-reset query)
(reverse results)))
(define (param-query-map db fn stmt . rest)
(let ((results (list))
(query (apply param-query (cons* db stmt rest)))
)
(do ((result (sqlite-step query) (sqlite-step query))
(count 0 (+ 1 count)))
((eq? result #f))
(set! results (cons (fn count result) results)))
(sqlite-reset query)
(reverse results)))
(define (param-query-one db stmt . args)
;; https://www.sqlite.org/c3ref/stmt.html
;; use `sqlite-prepare' to prepare a statement
(let ((p-stmt (sqlite-prepare db stmt))
(result #nil))
;; use `sqlite-bind-arguments' to bind args to a param query
(apply sqlite-bind-arguments (cons p-stmt args))
;; use `sqlite-step' to run a prepared statement
(set! result (sqlite-step p-stmt))
;; use `sqlite-reset' to commit the implicit transaction
(sqlite-reset p-stmt)
result))
(define (open-db db-path)
(let ((db (sqlite-open db-path
(logior
SQLITE_OPEN_CREATE
SQLITE_OPEN_READWRITE
))))
(unless (sqlite-db? db)
(raise-exception
(make-exception-with-message
(format #t "sqlite: not a database: ~a"
db-path))))
db))
(define (migrate-db db)
(sqlite-exec db create-tables-sql))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; RIES QUER UER
;; ERIESQUE SQUERIES ERI
;; ERI RI SQU SQU RIE
;; RI QU UE IES
;; IESQ UE ER ESQ
;; SQUERIES ER RI SQU
;; RIESQU RI IE QUE
;; UE IE ER ES UER
;; UE UER ESQ IESQ ERI
;; RIESQUER QUERIESQ RIESQUERIE
;; SQUE RIES UE IESQUERIES
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(define assoc-idol-tag-stmt
"INSERT INTO idol_tag (idol_id, tag_id) VALUES (?, ?)")
(define assoc-volume-idol-stmt
"INSERT INTO volume_idol (volume_id, idol_id) VALUES (?, ?)")
(define assoc-volume-studio-stmt
"INSERT INTO volume_studio (volume_id, studio_id) VALUES (?, ?)")
(define assoc-volume-tag-stmt
"INSERT INTO volume_tag (volume_id, tag_id) VALUES (?, ?)")
(define count-idols-stmt "SELECT COUNT(*) FROM idol")
(define count-notes-stmt "SELECT COUNT(*) FROM note")
(define count-studios-stmt "SELECT COUNT(*) FROM studio")
(define count-tags-stmt "SELECT COUNT(*) FROM tag")
(define count-volumes-stmt "SELECT COUNT(*) FROM volume")
(define deassoc-idol-tag-stmt
"DELETE FROM idol_tag WHERE idol_id = ? AND tag_id = ?")
(define deassoc-volume-idol-stmt
"DELETE FROM volume_idol WHERE volume_id = ? AND idol_id = ?")
(define deassoc-volume-tag-stmt
"DELETE FROM volume_tag WHERE volume_id = ? AND tag_id = ?")
(define insert-idol-stmt
"INSERT INTO idol (jp_name, birth_date) VALUES (?, ?) RETURNING id")
(define insert-studio-stmt
"INSERT INTO studio (call_sign) VALUES (?) RETURNING id")
(define insert-tag-stmt
"INSERT INTO tag (name) VALUES (?) RETURNING id")
(define insert-volume-stmt
"INSERT INTO volume (identifier, published) VALUES (?, ?) RETURNING id")
(define search-idols-stmt
"SELECT idol.jp_name, idol.birth_date, idol.rating
FROM idol_tag
JOIN idol ON idol_tag.idol_id = idol.id
JOIN tag ON idol_tag.tag_id = tag.id
WHERE tag.name LIKE '%' || ? || '%'
ORDER BY idol.rating DESC NULLS LAST, idol_tag.id DESC")
(define search-volumes-stmt
"SELECT volume.identifier, volume.published, volume.rating
FROM volume_tag
JOIN volume ON volume_tag.volume_id = volume.id
JOIN tag ON volume_tag.tag_id = tag.id
WHERE tag.name LIKE '%' || ? || '%'
ORDER BY volume.rating DESC NULLS LAST, volume_tag.id DESC")
(define select-all-idols-stmt
"SELECT jp_name, birth_date, rating FROM idol
ORDER BY rating DESC NULLS LAST, birth_date DESC NULLS LAST")
(define select-all-studios-stmt
"SELECT call_sign, full_name, rating FROM studio
ORDER BY call_sign ASC")
(define select-all-tags-stmt
"SELECT name FROM tag
ORDER BY name ASC")
(define select-all-volumes-stmt
"SELECT identifier, published, rating FROM volume
ORDER BY rating DESC NULLS LAST, published DESC NULLS LAST, identifier ASC")
(define select-idol-stmt
"SELECT id, birth_date, rating, jp_name FROM idol WHERE jp_name = ?")
(define select-idol-notes-stmt
"SELECT note.text, note.updated
FROM idol_note JOIN note ON idol_note.note_id = note.id
WHERE idol_note.idol_id = ?
ORDER BY note.created ASC")
(define select-idol-tags-stmt
"SELECT tag.name
FROM idol_tag INNER JOIN tag ON idol_tag.tag_id = tag.id
WHERE idol_tag.idol_id = ?
ORDER BY tag.name ASC")
(define select-idol-tag-ids-stmt
"SELECT tag.id
FROM idol_tag INNER JOIN tag ON idol_tag.tag_id = tag.id
WHERE idol_tag.idol_id = ?
ORDER BY tag.id ASC")
(define select-idol-volumes-stmt
"SELECT volume.identifier, volume.published, volume.rating, volume.title
FROM volume_idol JOIN volume ON volume_idol.volume_id = volume.id
WHERE volume_idol.idol_id = ?
ORDER BY volume.identifier ASC")
(define select-studio-stmt
"SELECT id, call_sign, full_name, rating FROM studio WHERE call_sign = ?")
(define select-studio-notes-stmt
"SELECT note.text, note.updated
FROM studio_note JOIN note ON studio_note.note_id = note.id
WHERE studio_note.studio_id = ?
ORDER BY note.created ASC")
(define select-studio-tags-stmt
"SELECT tag.name
FROM studio_tag JOIN tag ON studio_tag.tag_id = tag.id
WHERE studio_tag.studio_id = ?
ORDER BY tag.name ASC")
(define select-studio-volumes-stmt
"SELECT volume.identifier, volume.published, volume.rating, volume.title
FROM volume_studio JOIN volume ON volume_studio.volume_id = volume.id
WHERE volume_studio.studio_id = ?
ORDER BY volume.identifier ASC")
(define select-tag-stmt
"SELECT id FROM tag WHERE name = ?")
(define select-volume-stmt
"SELECT id, identifier, title, rating, published FROM volume
WHERE identifier = ?")
(define select-volume-notes-stmt
"SELECT note.text, note.updated
FROM volume_note JOIN note ON volume_note.note_id = note.id
WHERE volume_note.volume_id = ?
ORDER BY note.created ASC")
(define select-volume-tags-stmt
"SELECT tag.name
FROM volume_tag JOIN tag ON volume_tag.tag_id = tag.id
WHERE volume_tag.volume_id = ?
ORDER BY tag.name ASC")
(define select-volume-tag-ids-stmt
"SELECT tag.id
FROM volume_tag JOIN tag ON volume_tag.tag_id = tag.id
WHERE volume_tag.volume_id = ?
ORDER BY tag.id ASC")
(define select-volume-idols-stmt
"SELECT idol.jp_name, idol.birth_date, idol.rating
FROM volume_idol JOIN idol ON volume_idol.idol_id = idol.id
WHERE volume_idol.volume_id = ?
ORDER BY idol.birth_date ASC NULLS LAST, rating DESC NULLS LAST")
(define select-volume-idol-ids-stmt
"SELECT idol.id
FROM volume_idol JOIN idol ON volume_idol.idol_id = idol.id
WHERE volume_idol.volume_id = ?
ORDER BY idol.id ASC")
(define update-idol-dob-stmt
"UPDATE idol SET birth_date = ? WHERE id = ?")
(define update-volume-pub-stmt
"UPDATE volume SET published = ? WHERE id = ?")
(define create-tables-sql
"
CREATE TABLE IF NOT EXISTS tag (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name VARCHAR(255) NOT NULL
);
CREATE UNIQUE INDEX IF NOT EXISTS tag_name_unique ON tag (name);
CREATE TABLE IF NOT EXISTS note (
id INTEGER PRIMARY KEY AUTOINCREMENT,
text TEXT NOT NULL,
created INTEGER NOT NULL, -- epoch
updated INTEGER NOT NULL -- epoch
);
CREATE TABLE IF NOT EXISTS studio (
id INTEGER PRIMARY KEY AUTOINCREMENT,
call_sign VARCHAR(255) NOT NULL,
full_name VARCHAR(255),
rating SMALLINT
);
CREATE UNIQUE INDEX IF NOT EXISTS studio_call_sign_unique ON studio (call_sign);
CREATE INDEX IF NOT EXISTS studio_full_name ON studio (full_name);
CREATE TABLE IF NOT EXISTS idol (
id INTEGER PRIMARY KEY AUTOINCREMENT,
jp_name VARCHAR(255),
birth_date VARCHAR(10),
rating SMALLINT
);
CREATE UNIQUE INDEX IF NOT EXISTS idol_jp_name_unique ON idol (jp_name);
CREATE TABLE IF NOT EXISTS volume (
id INTEGER PRIMARY KEY AUTOINCREMENT,
identifier VARCHAR(255) NOT NULL,
title VARCHAR(255),
rating SMALLINT,
published VARCHAR(10) -- YYYY-MM-DD
);
CREATE UNIQUE INDEX IF NOT EXISTS volume_identifier_unique ON volume (identifier);
CREATE INDEX IF NOT EXISTS volume_title ON volume (title);
CREATE INDEX IF NOT EXISTS volume_published ON volume (published);
CREATE TABLE IF NOT EXISTS volume_idol (
id INTEGER PRIMARY KEY AUTOINCREMENT,
volume_id INTEGER,
idol_id INTEGER,
CONSTRAINT fk_volume_idol_volume
FOREIGN KEY (volume_id) REFERENCES volume (id)
ON DELETE RESTRICT
ON UPDATE RESTRICT,
CONSTRAINT fk_volume_idol_idol
FOREIGN KEY (idol_id) REFERENCES idol (id)
ON DELETE RESTRICT
ON UPDATE RESTRICT
);
CREATE INDEX IF NOT EXISTS volume_idol_volume ON volume_idol (volume_id);
CREATE INDEX IF NOT EXISTS volume_idol_idol ON volume_idol (idol_id);
CREATE UNIQUE INDEX IF NOT EXISTS volume_idol_unique ON volume_idol (volume_id, idol_id);
CREATE TABLE IF NOT EXISTS volume_studio (
id INTEGER PRIMARY KEY AUTOINCREMENT,
volume_id INTEGER,
studio_id INTEGER,
CONSTRAINT fk_volume_studio_volume
FOREIGN KEY (volume_id) REFERENCES volume (id)
ON DELETE RESTRICT
ON UPDATE RESTRICT,
CONSTRAINT fk_volume_studio_studio
FOREIGN KEY (studio_id) REFERENCES studio (id)
ON DELETE RESTRICT
ON UPDATE RESTRICT
);
CREATE INDEX IF NOT EXISTS volume_studio_volume ON volume_studio (volume_id);
CREATE INDEX IF NOT EXISTS volume_studio_studio ON volume_studio (studio_id);
CREATE UNIQUE INDEX IF NOT EXISTS volume_studio_unique ON volume_studio (volume_id, studio_id);
CREATE TABLE IF NOT EXISTS volume_tag (
id INTEGER PRIMARY KEY AUTOINCREMENT,
volume_id INTEGER,
tag_id INTEGER,
CONSTRAINT fk_volume_tag_volume
FOREIGN KEY (volume_id) REFERENCES volume (id)
ON DELETE RESTRICT
ON UPDATE RESTRICT,
CONSTRAINT fk_volume_tag_tag
FOREIGN KEY (tag_id) REFERENCES tag (id)
ON DELETE RESTRICT
ON UPDATE RESTRICT
);
CREATE INDEX IF NOT EXISTS volume_tag_volume ON volume_tag (volume_id);
CREATE INDEX IF NOT EXISTS volume_tag_tag ON volume_tag (tag_id);
CREATE UNIQUE INDEX IF NOT EXISTS volume_tag_unique ON volume_tag (volume_id, tag_id);
CREATE TABLE IF NOT EXISTS idol_tag (
id INTEGER PRIMARY KEY AUTOINCREMENT,
idol_id INTEGER,
tag_id INTEGER,
CONSTRAINT fk_idol_tag_idol
FOREIGN KEY (idol_id) REFERENCES idol (id)
ON DELETE RESTRICT
ON UPDATE RESTRICT,
CONSTRAINT fk_idol_tag_tag
FOREIGN KEY (tag_id) REFERENCES tag (id)
ON DELETE RESTRICT
ON UPDATE RESTRICT
);
CREATE INDEX IF NOT EXISTS idol_tag_tag ON idol_tag (tag_id);
CREATE INDEX IF NOT EXISTS idol_tag_idol ON idol_tag (idol_id);
CREATE UNIQUE INDEX IF NOT EXISTS idol_tag_unique ON idol_tag (idol_id, tag_id);
CREATE TABLE IF NOT EXISTS studio_tag (
id INTEGER PRIMARY KEY AUTOINCREMENT,
studio_id INTEGER,
tag_id INTEGER,
CONSTRAINT fk_studio_tag_studio
FOREIGN KEY (studio_id) REFERENCES studio (id)
ON DELETE RESTRICT
ON UPDATE RESTRICT,
CONSTRAINT fk_studio_tag_tag
FOREIGN KEY (tag_id) REFERENCES tag (id)
ON DELETE RESTRICT
ON UPDATE RESTRICT
);
CREATE INDEX IF NOT EXISTS studio_tag_tag ON studio_tag (tag_id);
CREATE INDEX IF NOT EXISTS studio_tag_studio ON studio_tag (studio_id);
CREATE UNIQUE INDEX IF NOT EXISTS studio_tag_unique ON studio_tag (studio_id, tag_id);
CREATE TABLE IF NOT EXISTS volume_note (
id INTEGER PRIMARY KEY AUTOINCREMENT,
volume_id INTEGER,
note_id INTEGER,
CONSTRAINT fk_volume_note_volume
FOREIGN KEY (volume_id) REFERENCES volume (id)
ON DELETE RESTRICT
ON UPDATE RESTRICT,
CONSTRAINT fk_volume_note_note
FOREIGN KEY (note_id) REFERENCES note (id)
ON DELETE RESTRICT
ON UPDATE RESTRICT
);
CREATE INDEX IF NOT EXISTS volume_note_note ON volume_note (note_id);
CREATE INDEX IF NOT EXISTS volume_note_volume ON volume_note (volume_id);
CREATE UNIQUE INDEX IF NOT EXISTS volume_note_unique ON volume_note (volume_id, note_id);
CREATE TABLE IF NOT EXISTS idol_note (
id INTEGER PRIMARY KEY AUTOINCREMENT,
idol_id INTEGER,
note_id INTEGER,
CONSTRAINT fk_idol_note_idol
FOREIGN KEY (idol_id) REFERENCES idol (id)
ON DELETE RESTRICT
ON UPDATE RESTRICT,
CONSTRAINT fk_idol_note_note
FOREIGN KEY (note_id) REFERENCES note (id)
ON DELETE RESTRICT
ON UPDATE RESTRICT
);
CREATE INDEX IF NOT EXISTS idol_note_note ON idol_note (note_id);
CREATE INDEX IF NOT EXISTS idol_note_idol ON idol_note (idol_id);
CREATE UNIQUE INDEX IF NOT EXISTS idol_note_unique ON idol_note (idol_id, note_id);
CREATE TABLE IF NOT EXISTS studio_note (
id INTEGER PRIMARY KEY AUTOINCREMENT,
studio_id INTEGER,
note_id INTEGER,
CONSTRAINT fk_studio_note_studio
FOREIGN KEY (studio_id) REFERENCES studio (id)
ON DELETE RESTRICT
ON UPDATE RESTRICT,
CONSTRAINT fk_studio_note_note
FOREIGN KEY (note_id) REFERENCES note (id)
ON DELETE RESTRICT
ON UPDATE RESTRICT
);
CREATE INDEX IF NOT EXISTS studio_note_note ON studio_note (note_id);
CREATE INDEX IF NOT EXISTS studio_note_studio ON studio_note (studio_id);
CREATE UNIQUE INDEX IF NOT EXISTS studio_note_unique ON studio_note (studio_id, note_id);
CREATE TABLE IF NOT EXISTS tag_note (
id INTEGER PRIMARY KEY AUTOINCREMENT,
tag_id INTEGER,
note_id INTEGER,
CONSTRAINT fk_tag_note_tag
FOREIGN KEY (tag_id) REFERENCES tag (id)
ON DELETE RESTRICT
ON UPDATE RESTRICT,
CONSTRAINT fk_tag_note_note
FOREIGN KEY (note_id) REFERENCES note (id)
ON DELETE RESTRICT
ON UPDATE RESTRICT
);
CREATE INDEX IF NOT EXISTS tag_note_note ON tag_note (note_id);
CREATE INDEX IF NOT EXISTS tag_note_tag ON tag_note (tag_id);
CREATE UNIQUE INDEX IF NOT EXISTS tag_note_unique ON tag_note (tag_id, note_id);
")

91
grammar.scm ノーマルファイル
ファイルの表示

@ -0,0 +1,91 @@
(define-module (juniority grammar)
#:use-module (ice-9 peg)
#:export (import-line
export-idol-stmt
export-volume-stmt
))
;; Whitespace
(define-peg-pattern NL none "\n")
(define-peg-pattern WS none (+ " "))
;; Date
(define-peg-pattern digit body (range #\0 #\9))
(define-peg-pattern date all
(and (+ digit)
"-"
(+ digit)
"-"
(+ digit)))
;; Tags
(define-peg-pattern tag-chars body
(and (* (and peg-any (not-followed-by "'")))
(and peg-any (followed-by "'"))))
(define-peg-pattern tag-body body
(and (ignore "'")
tag-chars
(ignore "'")))
(define-peg-pattern tag all
tag-body)
(define-peg-pattern untag all
(and (ignore "!")
tag-body))
(define-peg-pattern tags all (* (and WS (or tag untag))))
;; Idols
(define-peg-pattern idol-tag all
(and (ignore "@")
tag-body))
(define-peg-pattern idol-untag all
(and (ignore "!")
(ignore "@")
tag-body))
(define-peg-pattern idols all (* (and WS (or idol-tag idol-untag))))
(define-peg-pattern idol-entry all
(and idol-tag
tags
(? (and WS date))))
;; Volumes
(define-peg-pattern caps-alpha body
(range #\A #\Z))
(define-peg-pattern volume all
(and (+ caps-alpha)
"-"
(+ digit)
))
(define-peg-pattern volume-entry all
(and volume
idols
tags
(? (and WS date))
))
;; Entry point
(define-peg-pattern import-line body
(or idol-entry
volume-entry
NL))
;; Export procedures
(define (export-date-expr date)
(if date
(format #f " ~a" date)
""))
(define (export-idol-expr name)
(format #f " @'~a'" name))
(define (export-tag-expr name)
(format #f " '~a'" name))
(define* (export-idol-stmt name tags #:optional born)
(format #f "@'~a'~a~a"
name
(string-concatenate (map export-tag-expr tags))
(export-date-expr born)))
(define* (export-volume-stmt identifier idols tags #:optional published)
(format #f "~a~a~a~a"
identifier
(string-concatenate (map export-idol-expr idols))
(string-concatenate (map export-tag-expr tags))
(export-date-expr published)))

ファイルの表示

@ -6,9 +6,9 @@ CREATE UNIQUE INDEX IF NOT EXISTS tag_name_unique ON tag (name);
CREATE TABLE IF NOT EXISTS note (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
created INTEGER, -- epoch
updated INTEGER -- epoch
text TEXT NOT NULL,
created INTEGER NOT NULL, -- epoch
updated INTEGER NOT NULL -- epoch
);
CREATE TABLE IF NOT EXISTS studio (

10
util.scm ノーマルファイル
ファイルの表示

@ -0,0 +1,10 @@
(define-module (juniority util)
#:export (log
))
(define-syntax log
(syntax-rules ()
((log fmt expr expr1 ...)
(format #t (format #f "~s\n" fmt) expr expr1 ...))
((log expr) (format #t "~a\n" expr))
))