From daad7586e6fd5ca7d2205f8193eb3998975d2dca Mon Sep 17 00:00:00 2001 From: nntrn <17685332+nntrn@users.noreply.github.com> Date: Sat, 23 Mar 2024 05:18:43 -0500 Subject: [PATCH] Initial commit --- .github/workflows/docs.yml | 50 ++++++++ Gemfile | 3 + README.md | 21 ++++ _config.yml | 37 ++++++ _includes/script.js | 8 ++ _includes/style.css | 101 ++++++++++++++++ _layouts/book.html | 42 +++++++ _layouts/default.html | 46 ++++++++ _layouts/genre.html | 16 +++ _plugins/datapage.rb | 127 ++++++++++++++++++++ assets/favicon.ico | Bin 0 -> 360146 bytes index.md | 36 ++++++ scripts/books.jq | 234 +++++++++++++++++++++++++++++++++++++ scripts/build-data.sh | 103 ++++++++++++++++ 14 files changed, 824 insertions(+) create mode 100644 .github/workflows/docs.yml create mode 100644 Gemfile create mode 100644 README.md create mode 100644 _config.yml create mode 100644 _includes/script.js create mode 100644 _includes/style.css create mode 100644 _layouts/book.html create mode 100644 _layouts/default.html create mode 100644 _layouts/genre.html create mode 100644 _plugins/datapage.rb create mode 100644 assets/favicon.ico create mode 100644 index.md create mode 100644 scripts/books.jq create mode 100755 scripts/build-data.sh diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml new file mode 100644 index 0000000..0dfe503 --- /dev/null +++ b/.github/workflows/docs.yml @@ -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 diff --git a/Gemfile b/Gemfile new file mode 100644 index 0000000..ab38815 --- /dev/null +++ b/Gemfile @@ -0,0 +1,3 @@ +source 'https://rubygems.org' + +gem 'jekyll' \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..d25fb64 --- /dev/null +++ b/README.md @@ -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 +``` \ No newline at end of file diff --git a/_config.yml b/_config.yml new file mode 100644 index 0000000..760bab4 --- /dev/null +++ b/_config.yml @@ -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 diff --git a/_includes/script.js b/_includes/script.js new file mode 100644 index 0000000..d2feaca --- /dev/null +++ b/_includes/script.js @@ -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; \ No newline at end of file diff --git a/_includes/style.css b/_includes/style.css new file mode 100644 index 0000000..a6548d4 --- /dev/null +++ b/_includes/style.css @@ -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} +} \ No newline at end of file diff --git a/_layouts/book.html b/_layouts/book.html new file mode 100644 index 0000000..7d1ebf6 --- /dev/null +++ b/_layouts/book.html @@ -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" %} +
+{%- include postlist.html %} +
+

{{page.title}}

+

{{page.author}}

+

+ First annotation on .{%- if pagemodified != pagecreated -%} Last on .{%- endif %} +

+

{{annotations.size}} bookmark{%- if annotations.size > 1 -%}s{%- endif -%}

+
+
+
+ {%- for chapter in chapters %} + {%- assign sortedchapteritems = chapter.items | sort: "cfi" %} +

{{chapter.name | replace: "#","" |strip}}

+
    + {%- for annotation in sortedchapteritems %} +
  • + {{annotation.text}} +
    #{{annotation.id}} •  +
  • + {%- endfor %} +
+ {%- endfor %} +
+
+ {%- for chapter in chapters %} +
+ {%- endfor %} +
+
+
+
\ No newline at end of file diff --git a/_layouts/default.html b/_layouts/default.html new file mode 100644 index 0000000..2bb0390 --- /dev/null +++ b/_layouts/default.html @@ -0,0 +1,46 @@ + + + + + + + {{page.title|default:site.title}} | @nntrn + + + + + + + + + + + + + + + + + + + + + +
+

{{site.title}}

+
+
+{{ content }} + +
+{%- if page.type == "book" %} + +{%- endif %} + + diff --git a/_layouts/genre.html b/_layouts/genre.html new file mode 100644 index 0000000..e29b7ae --- /dev/null +++ b/_layouts/genre.html @@ -0,0 +1,16 @@ +--- +layout: default +type: genre +--- +{%- assign title = page.tag -%} +{%- assign pagetag = page.tag -%} +{%- assign bookpages = site.data.books | where: 'tags', pagetag -%} +
+

#{{pagetag}}

+ +
\ No newline at end of file diff --git a/_plugins/datapage.rb b/_plugins/datapage.rb new file mode 100644 index 0000000..4196051 --- /dev/null +++ b/_plugins/datapage.rb @@ -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) diff --git a/assets/favicon.ico b/assets/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..afed3b491eae6be5e4ee95b825b0e5383e0fef54 GIT binary patch literal 360146 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 literal 0 HcmV?d00001 diff --git a/index.md b/index.md new file mode 100644 index 0000000..eb8d758 --- /dev/null +++ b/index.md @@ -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'" -%} +
+ +{%- for books in books_by_date %} +{%- assign list = books.items | sort: 'created' |reverse %} + + + + + + + + +{%- for book in list %} +{%- assign statcount = site.data.stats.bookmarks_per_month[books.name] %} + + + + +{%- endfor %} + + + + + +{%- endfor -%} +

{{books.name| replace: "-", " " |capitalize}}

started {{list.size}} book{% if list.size > 1 %}s{%- endif -%}
+
+
+ {{book.title}}
+
created {{statcount}} bookmark{% if statcount > 1 %}s{%- endif -%}

+
diff --git a/scripts/books.jq b/scripts/books.jq new file mode 100644 index 0000000..356eb5d --- /dev/null +++ b/scripts/books.jq @@ -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("(?[^0-9\\$,]{3})[0-9]{1,2}(?[^0-9%\\.,]{2})"; .a +.b; "x") + | gsub("(?[^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-zA-Z])[0]+(?[0-9])";.a + " " + .d;"x") + | gsub("[xX][0-9]{2,}[^a-zA-Z0-9]+";" ";"x") + | gsub("([x\\.]+)?html";"";"x") + | gsub("[_-]";" ";"x") + | gsub("(?[a-zA-Z])(?[0-9])";.a + " " + .n;"x") + | gsub("(?[a-zA-Z])[0]{1,}(?[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 + | gsub("[0-9]{6,}|margins|\\.?xhtml|epub|ebook|\\.html";"";"xi") + | gsub("[_-]+";" ") + | gsub("[\\s ]$";"";"x") + | gsub("(?[a-zA-Z])(?[0-9])"; .w + " " + .d) + | gsub("^[Ccx]([hapter ]+)([^0-9]+)?(?[0-9])";"## Chapter "+ .c;"xi") + | gsub("^[Ss]([ection ]+)? ";"### Section ";"xi") + | gsub("^[iI][nt][cdinortu]+(?[\\s])?";"## Introduction" + .s;"xi") + | gsub(" [0]+(?[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(?[a-xA-Z])"; " "+ .x) + | gsub("[\\n]{2,}";"\n\n";"x") + | gsub("(?[a-z])\\n(?[a-z])";.f + " " + .s;"x") + ; + + +def format_paragraph($text): + $text + | unsmart + | gsub("[\\s\\t]{2,}"; " ";"x") + | gsub("[\\n]+"," \\n";"x") + | gsub("(?[^\\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("(?[^\\n]{60,72})[\\s](?[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"); \ No newline at end of file diff --git a/scripts/build-data.sh b/scripts/build-data.sh new file mode 100755 index 0000000..b52432e --- /dev/null +++ b/scripts/build-data.sh @@ -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-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