Skip to content

Commit

Permalink
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Initial commit
Browse files Browse the repository at this point in the history
nntrn committed Mar 23, 2024
0 parents commit daad758
Showing 14 changed files with 824 additions and 0 deletions.
50 changes: 50 additions & 0 deletions .github/workflows/docs.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
name: Build Docs

on:
workflow_dispatch:
repository_dispatch:
types: [update_data]
push:
paths:
- "_includes/**"
- "_layouts/**"

permissions:
contents: read
pages: write
id-token: write

concurrency:
group: "pages"
cancel-in-progress: true

jobs:
build:
runs-on: ubuntu-latest
permissions:
contents: read
issues: write
steps:
- name: Checkout
uses: actions/checkout@v4
- run: ./scripts/build-data.sh --remote
- name: Set up Ruby
uses: ruby/setup-ruby@v1
with:
ruby-version: "2.7.1"
bundler-cache: true
- name: Build with Jekyll
run: bundle exec jekyll build
- name: Upload artifact
uses: actions/upload-pages-artifact@v3
# Deployment job
deploy:
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
runs-on: ubuntu-latest
needs: build
steps:
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v4
3 changes: 3 additions & 0 deletions Gemfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
source 'https://rubygems.org'

gem 'jekyll'
21 changes: 21 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# what i'm reading

```sh
./scripts/build-data.sh --out _data2
./scripts/build-data.sh --remote
```

```sh
bundle install
bundle exec jekyll build
bundle exec jekyll serve
```

# trigger update

```
curl -H "Accept: application/vnd.github.everest-preview+json" \
-H "Authorization: token ${GITHUB_TOKEN}" \
--request POST --data '{"event_type": "update_data"}' \
https://api.github.com/repos/nntrn/what-im-reading/dispatches
```
37 changes: 37 additions & 0 deletions _config.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
title: what i'm reading
description: Bookmarks from Apple Books
author: Annie Tran
url: https://nntrn.github.io
baseurl: /what-im-reading
repository: nntrn/what-im-reading
github_username: nntrn
favicon_ico: /assets/favicon.ico

kramdown:
smart_quotes: ["apos", "apos", "quot", "quot"]

compress_html:
clippings: [div, p, ul, td, h1, h2]
endings: all
comments: ["<!--", "-->"]
startings: []
blanklines: true
profile: false

page_gen:
- data: "genre"
template: "genre"
index_files: true
dir: "tag"
- data: "books"
template: "book"
name: "slug"
index_files: false
dir: "tags"

exclude:
- README.md
- Gemfile.lock
- .archive
- annotations.json
- scripts
8 changes: 8 additions & 0 deletions _includes/script.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
function highlightAnnotation() {
Array.from(document.querySelectorAll('.mark')).forEach(e => e.classList.remove('mark'))
if (Number(location.hash.substr(1,))) {
document.querySelector(`.bookmark a[href="${location.hash}"]`).parentElement.parentElement.classList.add('mark')
}
}
highlightAnnotation()
window.onhashchange = highlightAnnotation;
101 changes: 101 additions & 0 deletions _includes/style.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
:root{--px-size:2.5%;--progress-size:13px}
*{box-sizing:border-box}
html{font-size:12px}
.h100,body,html,main{height:100%}
body{font-variant:proportional-width;display:flex;flex-direction:column;gap:.25rem;scrollbar-width:thin;margin:0 0;padding:0 0;margin:auto;font-family:Helvetica Neue,Helvetica,Arial,sans-serif;font-size:inherit;line-height:1.25;counter-reset:reversed(section);text-rendering:optimizeLegibility;overflow-y:hidden}
br{line-height:0!important;font-size:0!important}
date{margin-right:.5rem;text-transform:uppercase}
header{padding:.85rem 0;position:sticky;top:0;z-index:100;background:#fff;border-bottom:1px solid #ccc}
footer{margin-top:1rem}
header h1{padding-left:var(--px-size);padding-right:var(--px-size)}
footer p{border-top:1px solid #aaa;padding-top:1rem;text-align:right}
main{gap:2rem;position:relative;padding-left:var(--px-size);padding-right:var(--px-size);width:100%;scrollbar-width:thin;overflow-y:scroll;margin-bottom:0;display:block;padding:calc(1rem/2) var(--px-size);height:100%;display:flex;flex-direction:column;justify-content:space-between}
a{color:#36c}
a:empty{text-decoration:none;color:inherit}
a:empty:after{content:attr(href)}
a:hover{text-decoration-color:#36c}
a{text-decoration:1.25px underline;text-decoration-skip-ink:none;text-underline-offset:1px;text-rendering:geometricprecision;color:#36c;text-decoration-color:#3366cc61}
aside .container{border-right:1px dotted #222;min-height:75vh}
aside{flex:1;min-width:25vw;position:relative}
aside a,aside u{text-underline-offset:1.25px;text-decoration-skip-ink:none;text-decoration-thickness:1.2px}
aside a:hover{color:#222;text-decoration-color:#aaa}
aside h4 a{font-family:sans-serif;font-size:.9em}
aside strong{font-weight:700;display:block;line-height:1.1}
h1,h2,h4{margin:0 0}
h3{margin:.5rem 0}
h1 a{color:#222}
h1,h2,h3{font-family:times}
h3 a{text-transform:capitalize;text-decoration:none;color:initial;font-size:1.3rem}
hr{border:0;border-bottom:1px solid #aaa;margin:1rem 0}
label span:empty{display:none}
ul.list{padding:0 0rem;list-style:none;padding-right:1rem}
ol,ul{display:flex;flex-direction:column;gap:.5rem;padding:0 2rem;padding-right:1rem}
ul>li{padding:0 .4rem}
ol,ul{padding:0 1.35rem}
ol{padding-left:0}
ol li{list-style:none;padding-left:0}
q:after,q:before{content:""}
q{white-space:pre-wrap}
section{flex:10}
small a{color:inherit;text-decoration:inherit}
small a:hover{opacity:.75}
table{line-height:1.3;border-collapse:collapse}
tr>*{padding-bottom:.5rem;padding-top:.5rem}
td,th{vertical-align:top}
td label{font-size:.9em}
th date{white-space:pre;font-weight:400;color:#8e8e8e}
th{border-right:1.5px dotted #222}
th[data-level="1"]{text-align:right;border:0;font-size:1.4em;white-space:pre;transition:all .6s linear;max-width:min-content}
th[data-level="2"]{text-align:right}
td[colspan]{padding-left:11%}
tr{line-height:1.2}
.right{text-align:right}
mark{background:0 0}
mark strong{color:#fff;background:#222;padding:0 2px}
mark strong{font-size:.9rem}
[colspan] h2{padding-top:1.5rem}
.bookmark .meta{font-size:.9rem;color:#aaa;text-transform:uppercase;font-variant:proportional-nums}
.bookmark a{text-decoration:none}
.bookmark{padding:0 .5rem}
.bold{font-weight:700}
.normal{font-weight:400}
.small{font-size:.8rem}
.col{flex-direction:column}
.content{flex:3}
.content a:hover{text-decoration:underline}
.content a{text-decoration:none}
.content h2{line-height:1}
.content li>a:hover{color:#222}
.content li>a{transition:all 0s;color:#aaa;font-family:sans-serif;text-transform:uppercase;font-variant:proportional-nums}
.content p time{font-weight:700}
.content span{white-space:pre-wrap}
.content ul{gap:1rem}
.flex{display:flex}
.fw-500{font-weight:400!important}
.header a{color:inherit;text-decoration:none;text-transform:capitalize}
.header>*{margin:0 0;padding:0 0}
.gap{gap:var(--gap,.25rem)}
.list>li{padding:0 0}
.mark q{background:#fff0b1;line-height:1.4}
.meta a:hover{text-decoration:none;color:#a7a7a7}
.meta a{color:#000;font-weight:700}
.meta{margin-top:.25rem}
.padded{padding-left:.75rem;padding-right:.75rem}
.chapters{flex:1}
.toc{max-height:min(300px,40vh);position:sticky;top:0;list-style:none;padding:0 0;margin:0 0;z-index:1;width:20px;max-width:20px}
.toc div{padding:0 0;font-size:.9em;position:relative}
.toc a:hover:before{content:attr(title);position:absolute;right:20px;background:#222;padding:4px 4px;color:#fff;z-index:1;font-size:.9em}
.toc a{color:#222;white-space:pre;text-align:center;padding:0 4px;line-height:1}
.toc a:hover{text-decoration:none;font-weight:700}
.toc a:before{font-weight:500}
.site-title a{text-decoration-color:rgba(0,0,0,.1);text-underline-offset:2px;text-decoration-thickness:1.75px;font-family:times;font-weight:700;font-size:.9em;text-decoration-skip-ink:all}
[id]>a:hover:after{content:"#";color:#aaa}
aside ul{display:inline-flex;gap:.1rem;position:relative;position:sticky;top:0}
@media (max-width:750px){
aside .container{min-height:20vh}
main>.flex{display:flex;flex-direction:column-reverse}
:root{--px-size:1rem}
aside ul{margin:1rem .5rem}
ol{padding:0 2.5rem}
ol li{list-style:unset;padding-left:unset}
}
42 changes: 42 additions & 0 deletions _layouts/book.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
---
layout: default
type: book
---
{%- assign pagecreated = page.created | date_to_string: "ordinal", "US" %}
{%- assign pagemodified = page.modified | date_to_string: "ordinal", "US" -%}
{%- assign annotations = site.data.activity | where: 'assetid', page.assetid | sort: "cfi" %}
{%- assign total = annotations.size %}
{%- assign chapters = annotations | group_by: "chapter" %}
<div class="flex gap" style="--gap:2rem">
{%- include postlist.html %}
<article class="content" style="--total: {{total}}">
<h2>{{page.title}}</h2>
<h3>{{page.author}}</h3>
<p>
First annotation on <time>{{pagecreated}}</time>.{%- if pagemodified != pagecreated -%}&nbsp;Last on <time>{{pagemodified}}</time>.{%- endif %}
</p>
<p>{{annotations.size}} bookmark{%- if annotations.size > 1 -%}s{%- endif -%}</p>
<hr>
<section class="flex">
<div class="chapters">
{%- for chapter in chapters %}
{%- assign sortedchapteritems = chapter.items | sort: "cfi" %}
<h3 id="{{chapter.name | slugify: 'latin'}}">{{chapter.name | replace: "#","" |strip}}</h3>
<ul>
{%- for annotation in sortedchapteritems %}
<li id="{{annotation.id}}" class="bookmark">
<q>{{annotation.text}}</q>
<br><span class="meta"><a href="#{{annotation.id}}">#{{annotation.id}}</a>&nbsp;&bull;&nbsp;<time>{{annotation.created|date: '%b %-d %Y %-I:%M%p'}}</time></span>
</li>
{%- endfor %}
</ul>
{%- endfor %}
</div>
<div class="toc">
{%- for chapter in chapters %}
<div style="--length: {{chapter.items|size}}"><a title="{{chapter.name|replace: '#',''|strip}}" href="#{{chapter.name | slugify: 'latin'}}">&mdash;</a></div>
{%- endfor %}
</div>
</section>
</article>
</div>
46 changes: 46 additions & 0 deletions _layouts/default.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no">
<meta charset="UTF-8">
<title>{{page.title|default:site.title}} | @nntrn</title>
<link rel="canonical" href="{{ page.url | absolute_url }}" />
<link rel="icon" href="{{site.favicon_ico|relative_url}}" type="image/x-icon">
<meta name="description" content="{{site.description}}" />
<meta name="author" content="{{site.author}} (@{{site.github_username}})">
<meta name="robots" content="index, follow">
<meta name="mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="default">
<meta property="og:site_name" content="{{ site.title }}">
<meta property="og:type" content="website">
<meta property="og:url" content="{{ page.url | absolute_url }}">
<meta property="og:title" content="{{ page.title | default: site.title }}">
<meta property="og:description" content="{{ page.description | default: site.description }}">
<meta name="twitter:site" content="{{ site.title }}">
<meta name="twitter:card" content="summary">
<meta name="twitter:url" content="{{ site.url | absolute_url }}">
<meta name="twitter:title" content="{{ page.title | default: site.title }}">
<meta name="twitter:description" content="{{ page.description | default: site.description }}">
<style>
{% include style.css %}
</style>
</head>
<body>
<header>
<h1 class="site-title"><a href="{{ '/' | relative_url }}">{{site.title}}</a></h1>
</header>
<main>
{{ content }}
<footer>
<p>Created by <a href="https://github.com/{{site.repository}}">@{{site.github_username}}</a></p>
</footer>
</main>
{%- if page.type == "book" %}
<script>
{% include script.js %}
</script>
{%- endif %}
</body>
</html>
16 changes: 16 additions & 0 deletions _layouts/genre.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
---
layout: default
type: genre
---
{%- assign title = page.tag -%}
{%- assign pagetag = page.tag -%}
{%- assign bookpages = site.data.books | where: 'tags', pagetag -%}
<section>
<h2>#{{pagetag}}</h2>
<ul style="width:90%">
{% for book in bookpages -%}
{% assign bookurl = book.tags | join: "/" | join: book.slug %}
<li><a href="{{ book.slug }}">{{book.title}}</a> by {{book.author}}</li>
{% endfor %}
</ul>
</section>
127 changes: 127 additions & 0 deletions _plugins/datapage.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
# coding: utf-8
# Generate html pages from data in `_data/` and apply layout from `_layouts`
# Adapted from Adolfo Villafiorita and modified by @nntrn (github.com/nntrn)

module Jekyll
module Sanitizer
def sanitize_filename(name)
if(name.is_a? Integer)
return name.to_s
end
return name.tr(
"ÀÁÂÃÄÅàáâãäåĀāĂ㥹ÇçĆćĈĉĊċČčÐðĎďĐđÈÉÊËèéêëĒēĔĕĖėĘęĚěĜĝĞğĠġĢģĤĥĦħÌÍÎÏìíîïĨĩĪīĬĭĮįİıĴĵĶķĸĹĺĻļĽľĿŀŁłÑñŃńŅņŇňʼnŊŋÑñÒÓÔÕÖØòóôõöøŌōŎŏŐőŔŕŖŗŘřŚśŜŝŞşŠšſŢţŤťŦŧÙÚÛÜùúûüŨũŪūŬŭŮůŰűŲųŴŵÝýÿŶŷŸŹźŻżŽž",
"AAAAAAaaaaaaAaAaAaCcCcCcCcCcDdDdDdEEEEeeeeEeEeEeEeEeGgGgGgGgHhHhIIIIiiiiIiIiIiIiIiJjKkkLlLlLlLlLlNnNnNnNnnNnNnOOOOOOooooooOoOoOoRrRrRrSsSsSsSssTtTtTtUUUUuuuuUuUuUuUuUuUuWwYyyYyYZzZzZz"
).downcase.strip.gsub(' ', '-').gsub(/[^\w.-]/, '')
end
end

class DataPage < Page
include Sanitizer

def initialize(site, base, index_files, dir, page_data_prefix, data, name, name_expr, title, title_expr, template, extension, debug)
@site = site
@base = base

if name_expr
record = data
raw_filename = eval(name_expr)
if raw_filename == nil
return
end
else
raw_filename = (index_files ? "index" : data[name])
if raw_filename == nil
return
end
end
if title
raw_title = data[title]
end

filename = sanitize_filename(raw_filename).to_s
@dir = (data[dir] ? data[dir] : dir)
@name = (index_files ? "index" : filename) + "." + extension.to_s

self.process(@name)

if @site.layouts[template].path.end_with? 'html'
@path = @site.layouts[template].path.dup
else
@path = File.join(@site.layouts[template].path, @site.layouts[template].name)
end

base_path = @site.layouts[template].path
base_path.slice! @site.layouts[template].name
self.read_yaml(base_path, @site.layouts[template].name)

if page_data_prefix
self.data[page_data_prefix] = data
else
if data.key?('name')
data['_name'] = data['name']
end
self.data.merge!(data)
end

end
end

class JekyllDatapageGenerator < Generator
safe true

def generate(site)
index_files = site.config['page_gen-dirs'] == true

data = site.config['page_gen']
if data
data.each do |data_spec|
index_files_for_this_data = data_spec['index_files'] != nil ? data_spec['index_files'] : index_files
template = data_spec['template'] || data_spec['data']
name = data_spec['name']
name_expr = data_spec['name_expr']
title = data_spec['title']
title_expr = data_spec['title_expr']
dir = data_spec['dir'] || data_spec['data']
extension = data_spec['extension'] || "html"
page_data_prefix = data_spec['page_data_prefix']
debug = data_spec['debug']

if not site.layouts.key? template
puts "error (datapage-gen). could not find template #{template}. Skipping dataset #{name}."
else
records = nil

data_spec['data'].split('.').each do |level|
if records.nil?
records = site.data[level]
else
records = records[level]
end
end
if (records.kind_of?(Hash))
records = records.values
end

records = records.select { |record| record[data_spec['filter']] } if data_spec['filter']
records = records.select { |record| eval(data_spec['filter_condition']) } if data_spec['filter_condition']

records.uniq.each do |record|
site.pages << DataPage.new(site, site.source, index_files_for_this_data, dir, page_data_prefix, record, name, name_expr, title, title_expr, template, extension, debug)
end
end
end
end
end
end

# module DataPageLinkGenerator
# include Sanitizer
#
# def datapage_url(input, dir)
# extension = @context.registers[:site].config['page_gen-dirs'] ? '.pug' : '.html'
# end
# end

end

# Liquid::Template.register_filter(Jekyll::DataPageLinkGenerator)
Binary file added assets/favicon.ico
Binary file not shown.
36 changes: 36 additions & 0 deletions index.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
---
title: what i'm reading
layout: default
---
{% assign books_by_date = site.data.books | sort: 'created' |reverse | group_by_exp: "item", "item.created | date: '%b %Y'" -%}
<section class="books">
<table style="--gap:.25rem">
{%- for books in books_by_date %}
{%- assign list = books.items | sort: 'created' |reverse %}
<tr class="header">
<th data-level="1"><h2 id="{{books.name| slugify: 'latin'}}">{{books.name| replace: "-", " " |capitalize}}</h2></th>
<td></td>
</tr>
<tr>
<td class="right">started <mark><strong>{{list.size}}</strong>&nbsp;book{% if list.size > 1 %}s{%- endif -%}</mark></td>
<td></td>
</tr>
{%- for book in list %}
{%- assign statcount = site.data.stats.bookmarks_per_month[books.name] %}
<tr data-genre="{{book.tags}}" data-created="{{book.created}}" data-author="{{book.author}}" data-count="{{book.count}}">
<th scope="col" data-level="2" class="padded">
<div><time>{{book.created|date: '%F'}}</time></div><div class="small normal"><a href="{{book.tags}}"></a></div>
</th>
<td class="padded">
<a href="./{{book.tags}}/{{book.slug}}.html"><strong>{{book.title}}</strong></a><br><label>{{book.author}} &bull; {{book.count}}</label>
</td>
</tr>
{%- endfor %}
<tr>
<td></td>
<td>created <mark><strong>{{statcount}}</strong>&nbsp;bookmark{% if statcount > 1 %}s{%- endif -%}</mark></td>
</tr>
<tr><td colspan="2"><br></td></tr>
{%- endfor -%}
</table>
</section>
234 changes: 234 additions & 0 deletions scripts/books.jq
Original file line number Diff line number Diff line change
@@ -0,0 +1,234 @@
def squo: [39]|implode;
def lpad(n): tostring | if (n > length) then ((n - length) * "0") + . else . end;
def squote($text): [squo,$text,squo]|join("");
def dquote($text): "\"\($text)\"";

def unsmart: . | gsub("[“”]";"\"") | gsub("[’‘]";"'");

def unsmart($text): $text | unsmart;

def get_tags($tag): ($tag|split("[\\s]?([^\\w\\s]|for)[\\s]";"x")?|max_by(split(" ")|length)|ascii_downcase|gsub("[\\s]";"-";"x"));

def get_tags: get_tags(.);

def get_author($a):
(($a|split("(\\s)?[;&,]+";"x")|.[0]|gsub("[':]";"")|gsub("[\\.\\s]+";"-")|ascii_downcase)?);

def get_author: get_author(.);

def epublocation($cfi):
$cfi
| gsub("[^0-9]";"-") | gsub("^[-]+";"") | gsub("[-]+$";"";"x") | gsub("[-]{1,}";"-")
| split("-")|.[1:]
| map(select(length < 5)|tonumber);

def remove_citations($text):
$text
| gsub("(?<a>[^0-9\\$,]{3})[0-9]{1,2}(?<b>[^0-9%\\.,]{2})"; .a +.b; "x")
| gsub("(?<a>[^0-9]{3})[0-9]{1,2}([\\s]+)?$"; .a +.b; "xs");

def remove_citations: remove_citations(.);

def split_long_title($text):
$text
| split("\\s?[(:)]\\s?";"x")
| (map(select(length > 0))| [.[0],(.[1:]|map(select(contains("Volume"))))]|flatten|join(" "));

def slugify($text):
([39]|implode) as $squo
| (if ($text|length)>40 then split_long_title($text) else $text end)
| ascii_downcase
| gsub($squo;"";"x")
| gsub("[\\*\"]";"";"x")
| gsub("[^a-zA-Z0-9]+";"-";"x")
| gsub("-$";"";"x");

def get_author_slug($s):
$s
| (if test("&";"x") then split("[,&][\\s]?";"x") else split("; ") end )[0]
| gsub("[.,]";"")
| gsub("[^\\w\\d]+";"-";"x")
| gsub("-$";"";"x")
| ascii_downcase;

def epubchapname($cfi):
$cfi
| [match("(\\[[^\\]]+\\])+";"g")]
| map(
.string
| gsub("[\\]\\[]+";"";"x")
| gsub("(?<a>[a-zA-Z])[0]+(?<d>[0-9])";.a + " " + .d;"x")
| gsub("[xX][0-9]{2,}[^a-zA-Z0-9]+";" ";"x")
| gsub("([x\\.]+)?html";"";"x")
| gsub("[_-]";" ";"x")
| gsub("(?<a>[a-zA-Z])(?<n>[0-9])";.a + " " + .n;"x")
| gsub("(?<a>[a-zA-Z])[0]{1,}(?<n>[1-9])";.a + " " +.n;"x")
| gsub("^[pP][\\s]+";"Part ";"x")
| gsub("^[Ccx]([hapter]+)? ";"Chapter";"xi")
| gsub("^[Ss]([ection ]+)";"Section";"xi")
| gsub("^[iI]([ntroduction]{1,}).*";"Introduction";"x")

|select(length>0)
) | unique[0]
;

def chaptername($location):
$location
| capture("\\[(?<chapter>[^\\]]+)\\]").chapter
| gsub("[0-9]{6,}|margins|\\.?xhtml|epub|ebook|\\.html";"";"xi")
| gsub("[_-]+";" ")
| gsub("[\\s ]$";"";"x")
| gsub("(?<w>[a-zA-Z])(?<d>[0-9])"; .w + " " + .d)
| gsub("^[Ccx]([hapter ]+)([^0-9]+)?(?<c>[0-9])";"## Chapter "+ .c;"xi")
| gsub("^[Ss]([ection ]+)? ";"### Section ";"xi")
| gsub("^[iI][nt][cdinortu]+(?<s>[\\s])?";"## Introduction" + .s;"xi")
| gsub(" [0]+(?<n>[1-9])";" " +.n)
;


def format_text:
split("[\\n\\t]+";"x")
| map(select(test("[a-zA-Z]")) | gsub("^[\\t\\s]+";"";"x"))
| join("\n")
| gsub("[\\s\\t]+$";"";"x")
| gsub("\\s\\n(?<x>[a-xA-Z])"; " "+ .x)
| gsub("[\\n]{2,}";"\n\n";"x")
| gsub("(?<f>[a-z])\\n(?<s>[a-z])";.f + " " + .s;"x")
;


def format_paragraph($text):
$text
| unsmart
| gsub("[\\s\\t]{2,}"; " ";"x")
| gsub("[\\n]+"," \\n";"x")
| gsub("(?<a>[^\\n]{60,72}) "; .a + "\n"; "m")
;

def wrap_text($text;$id):
$text
| gsub("[\\s\\t]{2,}"; " ";"x")
| unsmart
| split("[\\n]+";"x")
| map("\(.) ")
| join("\n")
| gsub("(?<a>[^\\n]{60,72})[\\s](?<b>[a-zA-Z])"; "" + .a + "\n" + .b; "x")
| split("[\\n]+";"x")
| (.[0]|tostring) as $first
| (.[1:]|map(select(test("[^\\s\\t\\n]"))|" \(.)")) as $last
| [ "","* \($first)", $last, " [](#\($id))", "" ]
| flatten(2)
| join("\n");


def markdown_tmpl:
[
"---",
"title: \(if (.title|test(":")) then dquote(.title) else (.title) end)",
"author: \(.author)",
"asset_id: \(.assetid)",
"date: \(.creationDate)",
"modified: \(.modifiedDate)",
"category: \(.tags)",
"tags: [\(.tags)]",
"count: \(.count)",
"---",
"",
"# \(.title)",
"",
"by \(.author)",
"",
([.text]|flatten|join("\n")),
""
]
| join ("\n")
;

def get_chapter:
. | (
if ((.ZFUTUREPROOFING5|length)>0)
then .ZFUTUREPROOFING5
else chaptername(.ZANNOTATIONLOCATION)
end);

def get_chapter($o): $o|get_chapter;

def rechapter:
( if ((.ZFUTUREPROOFING5|length)>0) then "## \(.ZFUTUREPROOFING5)"
elif (.ZANNOTATIONLOCATION|test("[Cc][ hapter]+";"x")) then
"\(get_chapter)"
else ""
end
);

def rechapter($s): $s|rechapter;

def group_by_chapter:
sort_by(.booklocation)
| group_by(.ZPLLOCATIONRANGESTART)
| map([
(group_by(.chapter)
| to_entries
| map(
.key as $k |
.value | (
[.[0].chapter,"",(map(wrap_text(.ZANNOTATIONSELECTEDTEXT;.Z_PK))|join("\n\n")),""]
| map(select(length>0))
| join("\n")
)
)
)
])
| flatten(2)
| join("\n")
;

def bookcontent:
map( select((.ZTITLE) and (.ZANNOTATIONSELECTEDTEXT|length) >10) | . +
{
booklocation: epublocation(.ZANNOTATIONLOCATION),
chapter: rechapter(.)
}
)
| group_by(.ZASSETID)
| map({
assetid: .[0].ZASSETID,
title: (.[0].ZTITLE|gsub("\"";"") | gsub("\\([^0-9]+\\)"; "";"x")),
author: .[0].ZAUTHOR,
creationDate: min_by(.ZANNOTATIONCREATIONDATE).ZANNOTATIONCREATIONDATE,
modifiedDate: max_by(.ZANNOTATIONCREATIONDATE).ZANNOTATIONCREATIONDATE,
tags: get_tags(.[0].ZGENRE),
slug: "\(get_author_slug(.[0].ZSORTAUTHOR))-\(slugify((.[0].ZTITLE|gsub("[^\\w\\s\\d]+";"";"x"))))",
text: group_by_chapter|split("\n"),
count: length
});

def booksplit: bookcontent;

def build:
bookcontent
| map(
@sh "echo \( markdown_tmpl )" + " | cat -s > \(env.OUTDIR//"__test__")/\(.slug).md"
)
| join("\n\n");

def build_eval:
bookcontent
| map(
@sh "echo \( markdown_tmpl )" + " | cat -s > \(env.OUTDIR//"__test__")/\(.slug).md"
)
| join("\n\n");

def create_tag_markdown:
map({
title: .,
content: ([
"---",
"title: \"#\(.)\"",
"tags: \(.)",
"layout: tag",
"---"
] | join("\n"))
})
| map(@sh "echo -e \(.content) >"+ "_tags/\(.title).md")
| join("\n\n");
103 changes: 103 additions & 0 deletions scripts/build-data.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
#!/usr/bin/env bash
set -e

SCRIPT="$(realpath "$0")"
DIR=${SCRIPT%/*}
export OUTDIR=_data
export ANNOTATIONS_FILE=annotations.json

_log() { echo -e "\033[0;${2:-33}m$1\033[0m" 3>&2 2>&1 >&3 3>&-; }

create_book_data() {
_log "Creating books.json..."
cat $ANNOTATIONS_FILE | jq -L $DIR 'include "books";
map( select((.ZTITLE) and (.ZANNOTATIONSELECTEDTEXT|length) >10) |
. + { booklocation: epublocation(.ZANNOTATIONLOCATION), ZTITLE: (.ZTITLE|gsub("\"";"") | gsub("\\([^0-9]+\\)"; "";"x"))})
| group_by(.ZASSETID)
| map({
assetid: .[0].ZASSETID,
title: .[0].ZTITLE,
author: .[0].ZAUTHOR,
created: min_by(.ZANNOTATIONCREATIONDATE).ZANNOTATIONCREATIONDATE,
modified: max_by(.ZANNOTATIONCREATIONDATE).ZANNOTATIONCREATIONDATE,
tags: get_tags(.[0].ZGENRE),
slug: "\(get_author_slug(.[0].ZAUTHOR))-\(slugify(.[0].ZTITLE))",
count: length
})| map(. + {permalink: "\(.tags)/\(.slug)"})'
}

create_genre_data() {
_log "Creating genre.json..."
cat $ANNOTATIONS_FILE | jq -L $DIR 'include "books"; map({tag: get_tags(.ZGENRE)})| unique'
}

create_activity_data() {
_log "Creating activity.json..."
cat $ANNOTATIONS_FILE | jq -L $DIR 'include "books";
map(select((.ZTITLE) and (.ZANNOTATIONSELECTEDTEXT|length) >10))
| sort_by(.ZANNOTATIONCREATIONDATE)
| map({
id: .Z_PK,
assetid: .ZASSETID,
text: (.ZANNOTATIONSELECTEDTEXT|remove_citations|format_text),
created: .ZANNOTATIONCREATIONDATE,
location: .ZANNOTATIONLOCATION,
cfi:(epublocation(.ZANNOTATIONLOCATION)| map(lpad(3))|join("-")),
chapter: (if ((.ZFUTUREPROOFING5|length)>0) then .ZFUTUREPROOFING5 else chaptername(.ZANNOTATIONLOCATION) end),
rangestart: .ZPLLOCATIONRANGESTART
})
| sort_by(.id)'
}

create_word_data() {
cat $ANNOTATIONS_FILE | jq 'map({
Z_PK,
ZASSETID,
words: (.ZANNOTATIONSELECTEDTEXT
| gsub("[.?!] (?<a>[A-Z])"; (.a|ascii_downcase);"x")
| gsub("\([39]|implode)"; "")
| gsub("[^a-zA-Z]+";" ";"x")|split(" ")
| map(select(length >4))|sort|group_by(.)
| map([.[0],length])
| sort_by(.[1])
| map(join("-"))
| reverse)
})'
}

create_stats() {
mkdir -p $OUTDIR/stats
_log "Creating stats for monthly bookmarks"
cat $OUTDIR/activity.json |
jq 'sort_by(.created)
| map(. + {groupby_label: (.created|fromdate|strftime("%b %Y"))})
| group_by(.groupby_label)
| map({key: .[0].groupby_label, value: length})
| from_entries' >$OUTDIR/stats/bookmarks_per_month.json
}

while true; do
case $1 in
-o | --out) OUTDIR="$2" && shift ;;
-r | --remote) FETCH_REMOTE=1 && shift ;;
*.json) ANNOTATIONS_FILE="$1" && shift ;;
esac
shift || break
done

if [[ ! -f $ANNOTATIONS_FILE || $FETCH_REMOTE -eq 1 ]]; then
_log "Fetching remote"
ANNOTATIONS_FILE=/tmp/annotations.json
curl --create-dirs -o $ANNOTATIONS_FILE https://raw.githubusercontent.com/nntrn/bookstand/assets/annotations.json
fi

if [[ -s $ANNOTATIONS_FILE ]]; then
_log "===> Files will be saved to $OUTDIR <===" 36
mkdir -p $OUTDIR
create_book_data >$OUTDIR/books.json
create_genre_data >$OUTDIR/genre.json
create_activity_data >$OUTDIR/activity.json
create_stats
else
_log "annotations is empty"
fi

0 comments on commit daad758

Please sign in to comment.