Compare commits

..

14 Commits

Author SHA1 Message Date
548bdcb6a4 feat(vn): simple vn 2024-01-06 00:57:08 +00:00
e2a452914e feat(books): show completed books 2024-01-01 18:44:55 +00:00
797ab70d71 build: fix clean rule 2024-01-01 18:18:36 +00:00
b8c3974807 build: update make target 2024-01-01 18:18:36 +00:00
b67863f972 feat(garden): generate html files 2024-01-01 18:18:36 +00:00
9b39147c87 feat(books): start collection 2024-01-01 18:18:36 +00:00
bffbb0f371 feat(rss): generate unformatted file titles 2024-01-01 18:18:36 +00:00
db3eb3221c ci(garden): add rule file requirements 2024-01-01 18:18:36 +00:00
0d4bdebac9 ci(garden): build garden site files 2024-01-01 18:18:36 +00:00
ea39f8a5a8 feat: add navigation 2024-01-01 18:18:36 +00:00
ecbc5a24c4 build: add make targets 2024-01-01 18:18:36 +00:00
d0e08324e8 feat(garden): generate rss 2024-01-01 18:18:36 +00:00
371758485e feat(garden): rss youtube subscriptions
Describe how to get RSS feeds for YouTube channels
2024-01-01 18:18:36 +00:00
d10a3f19c3 update blog 2024-01-01 16:15:42 +00:00
87 changed files with 448 additions and 123 deletions

2
blog

@ -1 +1 @@
Subproject commit 6470541904da6d0a5d9dceeca6959370c4937759
Subproject commit 5a1de185c30b8a1ef3400fe161fcc8f12fbacaa0

@ -15,7 +15,8 @@ BLOG_BUILD_DIR = $(BLOG_BASE_DIR)out/html/
BLOG_OUT_DIR = $(OUT_DIR)/blog
BLOG_INDEX = $(BLOG_BUILD_DIR)/index.html
VN = src/vn/
GARDEN_BASE_DIR = $(SRC_DIR)/garden/
GARDEN_BUILD_DIR = $(GARDEN_BASE_DIR)html
PAGES = $(shell find $(ROOT_DIR) -wholename "$(ROOT_DIR)*.html")
STYLES = $(shell find $(ROOT_DIR) -wholename "$(ROOT_DIR)*.css")
@ -28,10 +29,8 @@ HTML_TARGETS = $(PAGES:$(ROOT_DIR)/%.html=$(OUT_DIR)/%.html)
CSS_TARGETS = $(STYLES:$(ROOT_DIR)/%.css=$(OUT_DIR)/%.css)
PNG_TARGETS = $(IMG_DIR)/%.png=$(OUT_DIR)/%.png
all: html blog vn | $(OUT_DIR)
vn: $(VN) | $(OUT_DIR)
cp -r $(VN) $(OUT_DIR)/vn
all: html blog garden | $(OUT_DIR)
cp -r src/garden/html $(OUT_DIR)/garden
html: $(HTML_TARGETS) $(CSS_TARGETS) | $(OUT_DIR)
cp $(IMG_DIR)/*.png $(OUT_DIR)/
@ -67,8 +66,12 @@ blog: $(HTML_INCLUDES) $(CSS_TARGETS)
cp $(CSS_TARGETS) `dirname $$page` ; \
done
garden:
make --directory $(GARDEN_BASE_DIR) site
clean:
make --directory $(GARDEN_BASE_DIR) clean
make --directory $(BLOG_BASE_DIR) clean
rm -rf $(OUT_DIR)
.PHONY: blog vn
.PHONY: blog garden

1
src/garden/.gitignore vendored Normal file

@ -0,0 +1 @@
*.html

22
src/garden/Makefile Normal file

@ -0,0 +1,22 @@
py = feed.py books.py
md = rss.md book-collecting.md gardens.md
html = $(md:%.md=%.html)
site: Makefile $(md) $(py)
mkdir html
python md2html.py $(md)
cp $(html) $(py) Makefile html
clean-html:
rm -r html
.PHONY: clean-html
feed:
python feed.py `pwd`
rss: feed
clean: clean-html
.PHONY: feed clean

@ -0,0 +1,106 @@
how do you define a book collection?
my book collection is the set of all books.
i prefer physical books to e-readers.
unfortunately i have quite a few these days.
i want to read them all eventually!
i also tend to live in quite small places
and i want to be able to move city easily!
so here's my system for organising my physical book collection.
i want to:
* read books i already have
* read as many different books as possible
* minimise physical storage requirements
* keep track of books i've read
* gather books i don't already have
constraints
* i don't know for sure what book i will want to read next
for every book in the world
* i either have or have not read it
* i have access to it or i don't
so i sort my book collection with 4 categories
*-------------------*-----------------------*
| | |
| unread | read |
| have | have |
| | |
| 37º2 le matin | L'Homme des Jeux |
| | |
*-------------------*-----------------------*
| | |
| unread | read |
| haven't | haven't |
| | |
| Das Kapital | Frankisstein |
| | |
*-------------------*-----------------------*
i can then begin to optimise my collection.
* i do not have this book, and i have read it.
* i have this book, but i have not read it.
* i have this book, and i have read it.
* i do not have this book, but i have not read it.
the books i am most interested in having nearby are unread ones, as i would like to read as many different books as possible.
books i have already read i don't need nearby anymore.
i might pass it on, or store it somewhere with less of a premium on space.
i could also attempt to track where it is!
based on my requirements and my categories, i create four lists for the books
* ready
* all done
* read and gone
* hunted
that looks like a decent start to the system, so i suppose now i'll start collecting!
so i'll use markdown lists in the format
```
* [x] author - title # reading
* [ ] author - title # nearby
```
when collecting music i use artist - year - name
however, publication year is an extra step that will slow data entry, so won't use this to start with - i have a lot of books
i realised i had a gpt-4 sub and that it could look at pictures now so i gave it a go
i fed it some photos and some formatting preferences and i got out perfect markdown lists
```
- [ ] Doctorow, Cory - Walkaway
- [ ] Ferreira, Pedro G. - The Perfect Theory
- [ ] Hadfield, Chris - An Astronaut's Guide to Life on Earth
- [ ] Heinlein, Robert A. - Beyond This Horizon
```
books are lovely are great to look at, but the mishmash of fonts and presentation are a nightmare for indexing.
now we have some good and lovely metadata :)
this is an imperfect method, as the only way i can check it is still by combing through the physical books manually
but it does let me target my combing after identifying problems in the index
and in the meantime gives us a bunch of data to play with
markdown lists also allow me to mark some items in a list
this is looks flexible, so i think in my 'ready' list i will mark which book(s) i am currently reading
```
- [ ] Doctorow, Cory - Walkaway
- [ ] Ferreira, Pedro G. - The Perfect Theory
- [ ] Hadfield, Chris - An Astronaut's Guide to Life on Earth
- [ ] Heinlein, Robert A. - Beyond This Horizon
```
the other lists i will leave unmarked for now, until i think of something to do with them.
as for doing things with them, i wrote a [python script](#) which processes the data in the now-populated all-done and ready lists to yield some interesting (?) and fun (?) results?
[example output](#)

118
src/garden/books.py Normal file

@ -0,0 +1,118 @@
import sys
import os
import re
def print_usage():
print(f"usage: python {sys.argv[0]} DIR")
print(f"")
print(f"\tDIR\tdirectory containing markdown lists in files.")
if len(sys.argv) != 2:
print_usage()
exit(1)
base_path = os.path.abspath(sys.argv[1])
ready_list_name = "ready.md"
completed_lists = ["2023.md", "2022.md", "202x.md"]
def get_path(list_name : str) -> str:
return os.path.join(base_path, list_name)
def get_matches(list_name : str) -> list[re.Match]:
# Matches a markdown list item
entry_pattern = re.compile(r"^[*-] \[([ *x])\] (.+) - (.+)")
matches = []
with open(get_path(list_name)) as f:
matches = [entry_pattern.match(l) for l in f.readlines()]
return [m for m in matches if m is not None]
class Book:
def __init__(self, match : re.Pattern):
self.mark = match.group(1) != " "
self.author = match.group(2)
self.title = match.group(3)
def is_metadata_complete(self):
if not self.title or not self.author:
return False
if self.title == "???" or self.author == "???":
return False
return True
@staticmethod
def get_list(list_name : str, filter_partial_metadata = True) -> []:
books = [Book(m) for m in get_matches(list_name)]
if filter_partial_metadata:
books = [b for b in books if b.is_metadata_complete()]
return books
def print_title(title : str, books : list[str]):
print(f"# {title} ({len(books)})\n")
def print_section(title : str, books : list[str]):
print_title(title, books)
longest_title = max([len(b.title) for b in books])
title_column_width = longest_title + 2
for book in books:
row = [book.title, book.author]
format_str = "- {: <" + str(title_column_width) + "} {: <20}"
print(format_str.format(*row))
print()
def print_in_progress():
books = [b for b in Book.get_list(ready_list_name, False) if b.mark]
print_section("in progress", books)
def print_completed():
completed_books = []
summaries = []
for completed_list in completed_lists:
books = [b for b in Book.get_list(completed_list)]
year = completed_list.split(".")[0]
completed_books += books
summaries.append(f"{year} - {len(books)}")
print_title("completed", completed_books)
for s in summaries:
print(s)
print()
def print_available():
books = []
for completed_list in completed_lists:
books += [b for b in Book.get_list(completed_list) if b.mark]
print_section("available", books)
def print_partial_metadata():
books = Book.get_list(ready_list_name, False)
for completed_list in completed_lists:
books += Book.get_list(completed_list, False)
books = [b for b in books if not b.is_metadata_complete()]
print_section("metadata incomplete", books)
print_in_progress()
print_completed()
print_available()
print_partial_metadata()

63
src/garden/feed.py Normal file

@ -0,0 +1,63 @@
#!/usr/bin/env python3
import markdown
import pathlib
import sys
import re
import glob
import os
def print_usage():
print("\nusage: python feed.py ROOT\n")
print("\n")
print("\t\ROOT\tbase folder")
# check args for at most one file paths
if len(sys.argv) > 2:
print_usage()
sys.exit(1)
base_folder = sys.argv[1] if len(sys.argv) == 2 else os.getcwd()
print(base_folder)
def get_paths() -> [str]:
return [x for x in glob.glob(f"{base_folder}/*.md")]
def get_text(path):
with open(path) as f:
return f.read()
#def to_html(md : str) -> str:
# return markdown.markdown(md, extensions=["fenced_code"])
def get_title(md):
m = re.compile(r"^# (.+)\n").match(md)
if m is not None:
return m.groups(1)[0]
# truncated first line of file for auto-title
return md.splitlines()[0][0:30]
def get_entry(path):
return get_title(get_text(path))
def get_entries() -> [str]:
entries = [get_entry(p) for p in get_paths()]
return "\n\n".join(entries)
def get_header() -> str:
return """<?xml version="1.0" encoding="utf-8" ?>
<rss xmlns:atom="http://www.w3.org/2005/Atom" version="2.0">
<channel>
<title></title>
<link>https://ktyl.dev/garden</link>
<description></description>
<atom:link href="https://ktyl.dev/garden/feed.xml" rel="self" type="application/rss+xml"/>
"""
def get_footer() -> str:
return "\n</channel></rss>"
print(get_header())
print(get_entries())
print(get_footer())

15
src/garden/feed.xml Normal file

@ -0,0 +1,15 @@
python feed.py `pwd`
<?xml version="1.0" encoding="utf-8" ?>
<rss xmlns:atom="http://www.w3.org/2005/Atom" version="2.0">
<channel>
<title></title>
<link>https://ktyl.dev/garden</link>
<description></description>
<atom:link href="https://ktyl.dev/garden/feed.xml" rel="self" type="application/rss+xml"/>
Digital Gardens
RSS
</channel></rss>

5
src/garden/gardens.md Normal file

@ -0,0 +1,5 @@
# Digital Gardens
* Obsidian
* Notion
* Git

32
src/garden/md2html.py Normal file

@ -0,0 +1,32 @@
#!/usr/bin/env python
import sys
import markdown
def print_usage():
print(f"usage: python {sys.argv[0]} PATHS")
print("")
print("\t\PATHS\tpaths of input markdown files")
if len(sys.argv) < 2:
print_usage()
sys.exit(1)
paths = sys.argv[1:]
bad_paths = [p for p in paths if not p.endswith(".md")]
if len(bad_paths) != 0:
for p in bad_paths:
print(f"Not a markdown file: {p}")
exit(1)
def write_html(src : str):
with open(src) as md:
dest = src.replace(".md", ".html")
with open(dest, "w") as html:
print(f"{src} -> {dest}")
html.write(markdown.markdown(md.read()))
for p in paths:
write_html(p)

35
src/garden/rss.md Normal file

@ -0,0 +1,35 @@
# RSS
Really Simple Syndication (RSS) is an ancient file format for communicating updates over the Web, and my personal favourite.
* [It's Time for an RSS Revival](https://www.wired.com/story/rss-readers-feedly-inoreader-old-reader/)
### Stubs:
* my curated feeds
* reader applications
## Technical Simplicity
From a technical perspective, like [Gemini](gemini://gemini.circumlunar.space) [\(HTTP\)](https://gemini.circumlunar.space/) it's grokkable with a couple good reads of the specification.
I wrote [a simple generator](https://ktyl.dev/blog/2022/6/3/rss.md)] for my blog posts, and am now working on [a simpler one](./feed.py) for the garden.
To start with, I'd like to generate it as simply as possible, from just a directory structure, but I can already see I'd like to do more with it.
I work predominantly in [Git](https://git-scm.com/) repositories, which, like a directory, or a garden, is tree-based.
My blog's feed has a chronological hierarchy, but at present it's based on manually creating and naming folders.
It's also very much a _published_ format, rather than a living one.
This is silly!
Instead, I'd like to structure my feed around a Git repository, as I think it'd be a much better descriptor of activity.
## Managing YouTube Subscriptions
There are many reasons to avoid the YouTube homepage, such as recommended videos or the accursed Shorts.
YouTube publishes channel-specific RSS feeds, making it totally possible to circumvent its subscription system entirely - including having to make an account - and keep track of channels one enjoys with RSS feeds instead.
Get a channel's ID:
1. Go to the channel's page
2. Go to the About section
3. Share icon under Stats block
4. Copy channel ID
The channel's feed is available at `https://www.youtube.com/feeds/videos.xml?channel_id=CHANNEL_ID_HERE`.

@ -2,4 +2,5 @@
<li><a href="/about.html">about</a></li>
<li><a href="/blog.html">blog</a></li>
<li><a href="/gallery.html">gallery</a></li>
<li><a href="/garden/index.html">garden</a></li>
</ul>

@ -3,22 +3,14 @@
<link rel="stylesheet" href="styles.css" />
</head>
<html>
<body onload="advance()">
<div class="column">
<body>
<div class=img-container>
<img id="portrait" src="img/frog.png"></img>
<div class="centre">
<img id="portrait" src="img/stargazer.png"></img>
<div id="text-box"></div>
<a id="button" href="#" onclick="advance()">start</a>
</div>
<div class="text-box">
<p id="text-box-content">SET_DIALOGUE_TEXT</p>
</div>
<div id="button" onclick="advance()">
<a id="button-text" class="centre" href="#">SET_BUTTON_TEXT</a>
</div>
</div>
</body>
</html>
<script src="main.js"></script>

Binary file not shown.

Before

(image error) Size: 235 KiB

Binary file not shown.

Before

(image error) Size: 169 KiB

Binary file not shown.

Before

(image error) Size: 94 KiB

Binary file not shown.

Before

(image error) Size: 251 KiB

Binary file not shown.

Before

(image error) Size: 220 KiB

Binary file not shown.

Before

(image error) Size: 106 KiB

Binary file not shown.

Before

(image error) Size: 155 KiB

Binary file not shown.

Before

(image error) Size: 135 KiB

Binary file not shown.

Before

(image error) Size: 174 KiB

Binary file not shown.

Before

(image error) Size: 432 KiB

Binary file not shown.

Before

(image error) Size: 60 KiB

Binary file not shown.

Before

(image error) Size: 164 KiB

Binary file not shown.

Before

(image error) Size: 79 KiB

Binary file not shown.

Before

(image error) Size: 255 KiB

Binary file not shown.

Before

(image error) Size: 98 KiB

Binary file not shown.

Before

(image error) Size: 133 KiB

Binary file not shown.

Before

(image error) Size: 198 KiB

Binary file not shown.

Before

(image error) Size: 53 KiB

Binary file not shown.

Before

(image error) Size: 237 KiB

Binary file not shown.

Before

(image error) Size: 134 KiB

Binary file not shown.

Before

(image error) Size: 149 KiB

Binary file not shown.

Before

(image error) Size: 66 KiB

Binary file not shown.

Before

(image error) Size: 132 KiB

Binary file not shown.

Before

(image error) Size: 120 KiB

Binary file not shown.

Before

(image error) Size: 146 KiB

Binary file not shown.

Before

(image error) Size: 374 KiB

Binary file not shown.

Before

(image error) Size: 139 KiB

Binary file not shown.

Before

(image error) Size: 138 KiB

Binary file not shown.

Before

(image error) Size: 229 KiB

Binary file not shown.

Before

(image error) Size: 163 KiB

Binary file not shown.

Before

(image error) Size: 173 KiB

Binary file not shown.

Before

(image error) Size: 135 KiB

Binary file not shown.

Before

(image error) Size: 90 KiB

Binary file not shown.

Before

(image error) Size: 190 KiB

Binary file not shown.

Before

(image error) Size: 170 KiB

Binary file not shown.

Before

(image error) Size: 134 KiB

Binary file not shown.

Before

(image error) Size: 273 KiB

Binary file not shown.

Before

(image error) Size: 151 KiB

Binary file not shown.

Before

(image error) Size: 473 KiB

Binary file not shown.

Before

(image error) Size: 142 KiB

Binary file not shown.

Before

(image error) Size: 151 KiB

Binary file not shown.

Before

(image error) Size: 196 KiB

Binary file not shown.

Before

(image error) Size: 136 KiB

Binary file not shown.

Before

(image error) Size: 69 KiB

Binary file not shown.

Before

(image error) Size: 99 KiB

Binary file not shown.

Before

(image error) Size: 284 KiB

Binary file not shown.

Before

(image error) Size: 288 KiB

Binary file not shown.

Before

(image error) Size: 186 KiB

Binary file not shown.

Before

(image error) Size: 127 KiB

Binary file not shown.

Before

(image error) Size: 119 KiB

Binary file not shown.

Before

(image error) Size: 482 KiB

Binary file not shown.

Before

(image error) Size: 96 KiB

Binary file not shown.

Before

(image error) Size: 112 KiB

Binary file not shown.

Before

(image error) Size: 348 KiB

Binary file not shown.

Before

(image error) Size: 275 KiB

Binary file not shown.

Before

(image error) Size: 242 KiB

Binary file not shown.

Before

(image error) Size: 137 KiB

Binary file not shown.

Before

(image error) Size: 209 KiB

Binary file not shown.

Before

(image error) Size: 114 KiB

Binary file not shown.

Before

(image error) Size: 79 KiB

Binary file not shown.

Before

(image error) Size: 226 KiB

Binary file not shown.

Before

(image error) Size: 220 KiB

Binary file not shown.

Before

(image error) Size: 164 KiB

Binary file not shown.

Before

(image error) Size: 110 KiB

Binary file not shown.

Before

(image error) Size: 102 KiB

Binary file not shown.

Before

(image error) Size: 114 KiB

Binary file not shown.

Before

(image error) Size: 236 KiB

Binary file not shown.

Before

(image error) Size: 242 KiB

Binary file not shown.

Before

(image error) Size: 290 KiB

Binary file not shown.

Before

(image error) Size: 410 KiB

Binary file not shown.

Before

(image error) Size: 139 KiB

Binary file not shown.

Before

(image error) Size: 177 KiB

@ -1,34 +1,32 @@
let idx = 0
let frames = [
"press start to begin!", "img/kt/stargazer.png", "start",
"hello! you're just in time!", "img/yay.png", null,
"this was starting to get ridiculous.", "img/you-shouldnt-be-doing-that.png", null,
"there are books EVERYWWHERE", "img/scared.png", null,
"and more keep coming!", "img/oshit.png", null,
"we've got to do something about it before the deadline!", "img/determined.png", null,
"hello! you're just in time!", "img/kt/yay.png", null,
"this was starting to get ridiculous.", "img/kt/you-shouldnt-be-doing-that.png", null,
"there are books EVERYWWHERE", "img/kt/scared.png", null,
"and more keep coming!", "img/kt/oshit.png", null,
"we've got to do something about them at some point, why not now?", "img/kt/determined.png", null,
"hey, could you sort the books for me?", "img/books-messy.png", "stack books by size",
"hey, could you sort the books for me?", "img/books-messy.png", "stack by size",
"yes, i can you've finished STACKING them,", "img/books-size.png", null,
"but that's hardly FINISHED, is it?", "img/books-size.png", null,
"i mean sure, if you want to be a pedant about it they're sort of ordered by size,", "img/nonplussed.png", null,
"but they're still just piled up in the same place, what's that supposed to change?", "img/coffee.png", null,
"yes, i can you've finished STACKING them,", "img/books-size.png", null,
"but that's hardly FINISHED, is it?", "img/books-size.png", null,
"i mean sure, if you want to be a pedant about it they're sort of ordered by size,", "img/kt/nonplussed.png", null,
"but they're still just piled up in the same place, what's that supposed to change?", "img/kt/coffee.png", null,
"why don't you try again?", "img/books-size.png", "push stack over",
"", "img/books-messy.png", "order books by colour",
"", "img/books-colour.png", null,
"why don't you try again?", "img/books-size.png", "push stack over",
"", "img/books-messy.png", "order books by colour",
"", "img/books-colour.png", null,
"no, colours won't work either.", "img/kt/dreamer0.png", null,
"why?", "img/kt/blast.png", null,
"simple, i don't do colours.", "img/kt/grin.png", null,
"it's nothing personal, they're just not for me, you know?", "img/kt/glad-you-asked.png", "restart"
"no, colours won't work either.", "img/dreamer0.png", null,
"why?", "img/blast.png", null,
"simple, i don't do colours.", "img/grin.png", null,
"it's nothing personal, they're just not for me, you know?", "img/glad-you-asked.png", "restart"
];
function setText(text) {
document.getElementById('text-box-content').innerHTML = text;
document.getElementById('text-box').innerHTML = text;
}
function setImage(path) {
@ -37,11 +35,12 @@ function setImage(path) {
function setButtonText(text) {
console.log(text);
if (text == null) {
if (text == null)
{
text = "next";
}
document.getElementById('button-text').innerHTML = text;
document.getElementById('button').innerHTML = text;
}
function advance() {

@ -1,87 +1,20 @@
body
{
padding: 0;
margin: 0;
width: 100%;
height: 100%;
font-family: sans-serif;
color: white;
background-color: black;
}
.column {
margin: 0 auto;
left: 50%;
width: 80%;
/*transform: translateX(50%);*/
height: 100%;
}
/* make column full-width for small viewports */
@media only screen and (max-width: 600px) {
.column {
left: 0;
width: 100%;
}
}
.img-container {
position: relative;
height: 60%;
width: 100%;
}
img {
object-fit: contain;
width: 100%;
height: 100%;
max-height: 70vh;
width: auto;
}
.centre {
margin: auto;
padding: 0;
width: 50%;
}
#button {
position: relative;
margin: 0;
width: 100%;
height: 15%;
}
a {
color: white;
}
#button a {
position: absolute;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
font-size: 4em;
text-align: center;
text-decoration: none;
}
#button:hover a {
color: grey;
}
.text-box {
position: relative;
width: 100%;
height: 25%;
}
.text-box p {
margin: 0;
width: 80%;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
text-align: center;
word-wrap: wrap;
font-size: 2em;
}
#text-box {
width 100%;
height: 4em;
}