aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorJoris2023-09-16 18:28:16 +0200
committerJoris2023-09-16 18:31:24 +0200
commit06f045e90bb57c36738e58ee6830e2a2391bc6a3 (patch)
treea5b65b048c103b4ea0e5d3c0fa5e115cbaf3cf5f /src
parent230f2e0623d22af6e68466884bd5dea5dcb846dc (diff)
Migrate CLI to python
Diffstat (limited to 'src')
-rw-r--r--src/library/__init__.py0
-rw-r--r--src/library/command.py21
-rw-r--r--src/main.py56
-rw-r--r--src/view/__init__.py0
-rw-r--r--src/view/client/book.ts (renamed from src/book.ts)0
-rw-r--r--src/view/client/lib/functions.ts (renamed from src/lib/functions.ts)0
-rw-r--r--src/view/client/lib/i18n.ts (renamed from src/lib/i18n.ts)0
-rw-r--r--src/view/client/lib/rx.ts (renamed from src/lib/rx.ts)0
-rw-r--r--src/view/client/lib/search.ts (renamed from src/lib/search.ts)0
-rw-r--r--src/view/client/main.ts (renamed from src/main.ts)0
-rw-r--r--src/view/client/view/books.ts (renamed from src/view/books.ts)1
-rw-r--r--src/view/client/view/components/modal.ts (renamed from src/view/components/modal.ts)0
-rw-r--r--src/view/client/view/filters.ts (renamed from src/view/filters.ts)0
-rw-r--r--src/view/command.py17
-rw-r--r--src/view/public/index.html7
-rw-r--r--src/view/public/main.css235
-rw-r--r--src/view/tsconfig.json13
17 files changed, 350 insertions, 0 deletions
diff --git a/src/library/__init__.py b/src/library/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/src/library/__init__.py
diff --git a/src/library/command.py b/src/library/command.py
new file mode 100644
index 0000000..6b8577e
--- /dev/null
+++ b/src/library/command.py
@@ -0,0 +1,21 @@
+import glob
+import json
+import os
+import tomllib
+
+def run(book_library):
+ print(get(book_library))
+
+def get(book_library):
+ metadatas = []
+
+ for path in glob.glob(f'{book_library}/**/metadata.toml', recursive=True):
+ with open(path, 'rb') as f:
+ directory = os.path.dirname(os.path.realpath(path))
+ metadata = tomllib.load(f)
+ for p in glob.glob(f'{directory}/cover.*'):
+ metadata['cover'] = p
+ break
+ metadatas.append(metadata)
+
+ return json.dumps(metadatas)
diff --git a/src/main.py b/src/main.py
new file mode 100644
index 0000000..618cc5a
--- /dev/null
+++ b/src/main.py
@@ -0,0 +1,56 @@
+# Manage book library.
+#
+# Required dependencies:
+#
+# - python >= 3.11
+# - requests
+# - pillow
+# - ebook-convert CLI (from calibre)
+
+import sys
+import os
+
+import library.command
+import view.command
+
+def print_help(title='Manage book library'):
+ print(f"""{title}
+
+- Print library metadata as json:
+
+ $ python {sys.argv[0]} library
+
+- View books in web page:
+
+ $ python {sys.argv[0]} view browser-cmd
+
+Environment variables:
+
+ BOOK_LIBRARY: path to book library.""")
+
+def get_book_library():
+ path = os.getenv('BOOK_LIBRARY')
+ if path is None or not os.path.isdir(path):
+ print_help(title='BOOK_LIBRARY environment variable is required.')
+ exit(1)
+ else:
+ return path
+
+def main():
+ match sys.argv:
+ case [ _, 'library' ]:
+ book_library = get_book_library()
+ library.command.run(book_library)
+ case [ _, 'view', browser_cmd ]:
+ book_library = get_book_library()
+ view.command.run(book_library, browser_cmd)
+ case [ _, '--help' ]:
+ print_help()
+ case [ _, '-h' ]:
+ print_help()
+ case _:
+ print_help('Command not found.')
+ exit(1)
+
+if __name__ == "__main__":
+ main()
diff --git a/src/view/__init__.py b/src/view/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/src/view/__init__.py
diff --git a/src/book.ts b/src/view/client/book.ts
index 680cc11..680cc11 100644
--- a/src/book.ts
+++ b/src/view/client/book.ts
diff --git a/src/lib/functions.ts b/src/view/client/lib/functions.ts
index 21fdad9..21fdad9 100644
--- a/src/lib/functions.ts
+++ b/src/view/client/lib/functions.ts
diff --git a/src/lib/i18n.ts b/src/view/client/lib/i18n.ts
index 3716367..3716367 100644
--- a/src/lib/i18n.ts
+++ b/src/view/client/lib/i18n.ts
diff --git a/src/lib/rx.ts b/src/view/client/lib/rx.ts
index bf01b6d..bf01b6d 100644
--- a/src/lib/rx.ts
+++ b/src/view/client/lib/rx.ts
diff --git a/src/lib/search.ts b/src/view/client/lib/search.ts
index 026cb94..026cb94 100644
--- a/src/lib/search.ts
+++ b/src/view/client/lib/search.ts
diff --git a/src/main.ts b/src/view/client/main.ts
index 5885871..5885871 100644
--- a/src/main.ts
+++ b/src/view/client/main.ts
diff --git a/src/view/books.ts b/src/view/client/view/books.ts
index 5ec019a..aba55c1 100644
--- a/src/view/books.ts
+++ b/src/view/client/view/books.ts
@@ -66,6 +66,7 @@ function viewBook({ book, onSelect }: ViewBookParams): Html {
{ className: 'g-Book' },
h('img',
{ src: book.cover,
+ alt: book.title,
className: 'g-Book__Image',
onclick: () => onSelect(book)
}
diff --git a/src/view/components/modal.ts b/src/view/client/view/components/modal.ts
index 5e845e1..5e845e1 100644
--- a/src/view/components/modal.ts
+++ b/src/view/client/view/components/modal.ts
diff --git a/src/view/filters.ts b/src/view/client/view/filters.ts
index efe4115..efe4115 100644
--- a/src/view/filters.ts
+++ b/src/view/client/view/filters.ts
diff --git a/src/view/command.py b/src/view/command.py
new file mode 100644
index 0000000..e50027f
--- /dev/null
+++ b/src/view/command.py
@@ -0,0 +1,17 @@
+import os
+import shutil
+import subprocess
+import tempfile
+import time
+
+import library.command
+
+def run(book_library, browser_cmd):
+ tmp_dir = tempfile.mkdtemp()
+ directory = os.path.dirname(os.path.realpath(__file__))
+ shutil.copytree(f'{directory}/public', tmp_dir, dirs_exist_ok=True)
+ with open(f'{tmp_dir}/books.js', 'w') as f:
+ json = library.command.get(book_library)
+ f.write(f'const bookLibrary = {json}')
+ browser_cmd = f'{browser_cmd} {tmp_dir}/index.html'
+ subprocess.run(browser_cmd.split(' '))
diff --git a/src/view/public/index.html b/src/view/public/index.html
new file mode 100644
index 0000000..ce4d568
--- /dev/null
+++ b/src/view/public/index.html
@@ -0,0 +1,7 @@
+<!DOCTYPE html>
+<meta charset="UTF-8">
+<link rel="stylesheet" href="main.css">
+<title>Bibliothèque</title>
+<body></body>
+<script src="books.js"></script>
+<script src="main.js"></script>
diff --git a/src/view/public/main.css b/src/view/public/main.css
new file mode 100644
index 0000000..f361cbe
--- /dev/null
+++ b/src/view/public/main.css
@@ -0,0 +1,235 @@
+/* Variables */
+
+:root {
+ --color-focus: #888833;
+
+ --font-size-dog: 1.5rem;
+
+ --spacing-mouse: 0.25rem;
+ --spacing-cat: 0.5rem;
+ --spacing-dog: 1rem;
+ --spacing-horse: 2rem;
+
+ --width-close-button: 3rem;
+ --width-book: 13rem;
+}
+
+/* Top level */
+
+html {
+ height: 100%;
+}
+
+body {
+ margin: 0;
+ display: flex;
+ height: 100%;
+ font-family: sans-serif;
+}
+
+dl {
+ margin: 0;
+}
+
+dd {
+ margin-left: 0;
+}
+
+p {
+ margin: 0;
+}
+
+/* Modal */
+
+.g-Modal {
+ width: 100vw;
+ height: 100vh;
+ position: fixed;
+ top: 0;
+ left: 0;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ background-color: rgba(0, 0, 0, 0.5);
+ z-index: 1;
+}
+
+.g-Modal__Content {
+ position: relative;
+ background-color: white;
+ width: 50%;
+ border-radius: 0.2rem;
+ border: 1px solid #EEE;
+}
+
+.g-Modal__Header {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ padding: var(--spacing-dog) var(--spacing-horse);
+ border-bottom: 1px solid #EEE;
+}
+
+.g-Modal__Body {
+ padding: var(--spacing-horse);
+ max-height: 50vh;
+ overflow-y: scroll;
+}
+
+.g-Modal__Close {
+ cursor: pointer;
+ font-weight: bold;
+ border-radius: 50%;
+ border: 1px solid #EEE;
+ background-color: transparent;
+ width: var(--width-close-button);
+ height: var(--width-close-button);
+ font-size: 1.7rem;
+ flex-shrink: 0;
+}
+
+.g-Modal__Close:hover, .g-Modal__Close:focus {
+ background-color: #EEE;
+}
+
+/* Filters */
+
+aside {
+ background-color: #333;
+ color: white;
+ width: 200px;
+ overflow-y: auto;
+}
+
+ul {
+ margin: 0;
+ padding: 0;
+ list-style-type: none;
+}
+
+.g-FilterTitle {
+ padding: 0.5rem 1rem;
+ background-color: #555;
+ border-left: 8px solid transparent;
+ font-weight: bold;
+}
+
+.g-Filter--Unselected {
+ border-left: 8px solid #555;
+}
+
+.g-Filter--Selected {
+ border-left: 8px solid #883333;
+}
+
+.g-Filter button {
+ border: none;
+ background-color: transparent;
+ color: inherit;
+ cursor: pointer;
+ padding: 0.5rem 1rem;
+ width: 100%;
+ text-align: left;
+}
+
+.g-Filter button:hover {
+ background-color: var(--color-focus);
+}
+
+/* Books */
+
+main {
+ width: 100%;
+ padding: 1rem;
+ overflow-y: auto;
+}
+
+header {
+ display: flex;
+ font-size: 120%;
+ margin-bottom: 1rem;
+}
+
+.g-Search {
+ margin-right: 1rem;
+}
+
+.g-Books {
+ display: grid;
+ grid-template-columns: repeat(auto-fit, minmax(var(--width-book), 1fr));
+ grid-gap: 1rem;
+}
+
+.g-Book {
+ align-self: center;
+ border: 1px solid #DDDDDD;
+ padding: 0;
+ width: fit-content;
+}
+
+.g-Book:hover {
+ cursor: pointer;
+ outline: none;
+}
+
+.g-Book:hover .g-Book__Image {
+ transform: scale(105%);
+ opacity: 0.9;
+}
+
+.g-Book__Image {
+ display: flex;
+ width: var(--width-book);
+ transition: all 0.2s ease-in-out;
+}
+
+/* Book detail */
+
+.g-BookDetail {
+ display: flex;
+ align-items: flex-start;
+}
+
+.g-BookDetail img {
+ width: var(--width-book);
+ margin-right: var(--spacing-horse);
+ border: 1px solid #EEE;
+}
+
+.g-BookDetail__Title {
+ font-size: var(--font-size-dog);
+ margin-bottom: var(--spacing-mouse);
+}
+
+.g-BookDetail__Subtitle {
+ font-style: italic;
+ margin-bottom: var(--spacing-mouse);
+}
+
+.g-BookDetail dl {
+ display: flex;
+ flex-direction: column;
+ gap: var(--spacing-cat);
+ margin-bottom: var(--spacing-dog);
+}
+
+.g-BookDetail dt {
+ display: inline;
+ text-decoration: underline;
+}
+
+.g-BookDetail dd {
+ display: inline;
+}
+
+.g-BookDetail p {
+ font-family: serif;
+ text-align: justify;
+ line-height: 1.4rem;
+ font-size: 1.1rem;
+ text-indent: 1.5rem;
+}
+
+.g-BookDetail p:not(:last-child) {
+ margin-bottom: var(--spacing-dog);
+}
diff --git a/src/view/tsconfig.json b/src/view/tsconfig.json
new file mode 100644
index 0000000..1e07c37
--- /dev/null
+++ b/src/view/tsconfig.json
@@ -0,0 +1,13 @@
+{
+ "compilerOptions": {
+ "module": "amd",
+ "target": "es2020",
+ "baseUrl": "client",
+ "outFile": "public/main.js",
+ "noImplicitAny": true,
+ "strictNullChecks": true,
+ "removeComments": true,
+ "preserveConstEnums": true
+ },
+ "include": ["client/**/*"]
+}