Switching to one.el
This seems to be a really nice balance of control and getting me to write some more elisp.
This commit is contained in:
@@ -14,4 +14,3 @@
|
||||
* [[file:multi-room-audio.org][Multi-room audio setup]]
|
||||
* [[file:vi-everywhere.org][vi-editing everywhere]]
|
||||
* [[file:aws-metric-filters.org][AWS Cloudwatch Metric Filters]]
|
||||
|
||||
|
||||
440
one.org
Normal file
440
one.org
Normal file
@@ -0,0 +1,440 @@
|
||||
* circumlocuting
|
||||
:PROPERTIES:
|
||||
:ONE: wfot-default-home-list-pages
|
||||
:CUSTOM_ID: /
|
||||
:END:
|
||||
|
||||
Hello,
|
||||
|
||||
Here's what I've been thinking about
|
||||
|
||||
* The problem with large companies
|
||||
:PROPERTIES:
|
||||
:ONE: wfot-default
|
||||
:CUSTOM_ID: /blog/large-companies/
|
||||
:END:
|
||||
|
||||
Organizing people is a difficult problem which only gets more difficult as youmore people need to be organized.
|
||||
|
||||
The larger a company is the more of its internal structures, rules, policies, history, etc are devoted _just_ to organizing people.
|
||||
|
||||
For me, realizing this was like the first time you hear a flourescent light buzzing in an otherwise quiet room.
|
||||
|
||||
Reasonable people can differ on this point, but for my own sake I'd much rather avoid all the people-organizing baggage that comes with large companies.
|
||||
|
||||
I don't have a hard-and-fast rule about the size of a place I want to work but the larger a place is then generally the more reason I need to want to be there.
|
||||
|
||||
Of course, this is all kind of theoretical at this point, as [[https://flipstone.com][Flipstone]] is my forever home.
|
||||
|
||||
#+BEGIN_SRC haskell
|
||||
|
||||
doThings :: String -> T.Text -> NEL.NonEmpty Char -> BS.ByteString -> IO ()
|
||||
doThings = error "WTF is this"
|
||||
|
||||
#+END_SRC
|
||||
|
||||
* Simple CSS frameworks - 2024-09-30
|
||||
:PROPERTIES:
|
||||
:ONE: wfot-default
|
||||
:CUSTOM_ID: /blog/simple-css-frameworks/
|
||||
:END:
|
||||
|
||||
I really like simple drop-in CSS resets like the one I use for this site.
|
||||
|
||||
At the time of writing, I'm using [[https://picocss.com/][Pico]] but I also considered [[https://yegor256.github.io/tacit/][tacit]]
|
||||
|
||||
The idea is that they provide nice default styling of HTML elements out of the box without the need to reference any specific classes.
|
||||
|
||||
The idea works well for sites that are much more content than layout - like this one.
|
||||
|
||||
Using tacit is a matter of incluing this link tag in the page's HEAD element:
|
||||
|
||||
#+BEGIN_SRC html
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@picocss/pico@2/css/pico.min.css">
|
||||
#+END_SRC
|
||||
* Let people fail - 2024-09-25
|
||||
:PROPERTIES:
|
||||
:ONE: wfot-default
|
||||
:CUSTOM_ID: /blog/let-people-fail/
|
||||
:END:
|
||||
|
||||
** How (and why) to let people fail
|
||||
Warning: This, like most things, will involve a fair bit of projection.
|
||||
|
||||
Effective and enjoyable collaboration with other people requires mutual trust.
|
||||
|
||||
I believe that for someone to feel trusted by another person then they need the space to fail.
|
||||
|
||||
I _think_ this is obvious when considering what not having the space to fail looks like.
|
||||
|
||||
Not having the space to fail means your collaborator is doing one of two things:
|
||||
|
||||
1. Directing every action you take a.k.a. micromanaging
|
||||
2. Coming behind you and redoing all of your work
|
||||
|
||||
Both of these are attempts by the other person to minimize risk (or simply cases where they're failing to manage their own anxieties).
|
||||
|
||||
These actions are counter productive to fostering trust and should be avoided unless failure is too costly.
|
||||
|
||||
I'm _not_ saying all collaboration _requires_ building trust. There are times when you simply can't afford failure or mistakes.
|
||||
|
||||
What I am saying is that people frequently misjudge the value in deliberately giving others the space to fail for the sake of fostering trust.
|
||||
|
||||
Building trust is important and we should do it deliberately.
|
||||
* TODO Just what is it you do here?
|
||||
:PROPERTIES:
|
||||
:ONE: wfot-default
|
||||
:CUSTOM_ID: /blog/job-description/
|
||||
:END:
|
||||
|
||||
I've never liked working at [[#/blog/large-companies/][large companies]]. Mostly because I think
|
||||
they complicate things, but some things are more complicated at small
|
||||
companies.
|
||||
|
||||
* TODO Managing Expectations
|
||||
:PROPERTIES:
|
||||
:ONE: wfot-default
|
||||
:CUSTOM_ID: /blog/managing-expectations/
|
||||
:END:
|
||||
|
||||
I'll figure this out one day. Until then I'll just keep saying yes and burning myself out making everyone happy.
|
||||
|
||||
* HTTPS @ Homelab
|
||||
:PROPERTIES:
|
||||
:ONE: wfot-default
|
||||
:CUSTOM_ID: /blog/large-companies/
|
||||
:END:
|
||||
** HTTPS @ Home
|
||||
I run a lot of services at home.
|
||||
|
||||
This includes, but isn't limited to
|
||||
|
||||
- [[https://archivebox.io/][ArchiveBox]]
|
||||
- [[https://github.com/dani-garcia/vaultwarden][VaultWarden]]
|
||||
- [[https://github.com/navidrome/navidrome][Navidrome]]
|
||||
- [[https://plex.tv][Plex]]
|
||||
- [[https://github.com/LibrePhotos/librephotos][LibrePhotos]]
|
||||
- This blog
|
||||
|
||||
and a lot more.
|
||||
|
||||
Pretty much anything that's served up over HTTP is always nice if not
|
||||
necessary to have behind TLS.
|
||||
|
||||
[[https://letsencrypt.org/][LetsEncrypt]] long ago brought free certs to
|
||||
the masses and there are a lot of tools for automating that nowadays.
|
||||
|
||||
My preferred approach for getting all the unnecessary nonsense I
|
||||
self-host at home behind TLS is [[https://caddyserver.com][Caddy]].
|
||||
|
||||
I have a super straight forward setup, generally:
|
||||
|
||||
- Run Caddy in a docker container
|
||||
- Create a wildcard CNAME record in my DNS pointing at my home's
|
||||
(effectively) static IP
|
||||
- Add an entry in my Caddyfile for each services I'm running at home on
|
||||
its own subdomain
|
||||
- If it's a service then I add it with a =reverse_proxy= block
|
||||
- If it's a static site (like this) then there's a block for
|
||||
- If it's something I want only accessible on my home network then I put
|
||||
a block like
|
||||
|
||||
#+BEGIN_EXAMPLE
|
||||
@local_network {
|
||||
path *
|
||||
remote_ip
|
||||
}
|
||||
#+END_EXAMPLE
|
||||
|
||||
in the directive. And voila.
|
||||
|
||||
Then tell Caddy to reload the config and I'm done.
|
||||
* Multi-room audio setup
|
||||
:PROPERTIES:
|
||||
:ONE: wfot-default
|
||||
:CUSTOM_ID: /blog/multi-room-audio/
|
||||
:END:
|
||||
** My multiroom audio setup
|
||||
|
||||
I've put my home audio solution together out of the following
|
||||
components.
|
||||
|
||||
- [[https://github.com/badaix/snapcast][Snapcast]]
|
||||
|
||||
- [[https://www.musicpd.org/][MPD]]
|
||||
|
||||
- [[https://github.com/librespot-org/librespot][Librespot]]
|
||||
|
||||
- [[https://github.com/mikebrady/shairport-sync][Shairport-sync]]
|
||||
|
||||
- A mini-PC in my closet running the above software
|
||||
|
||||
- Two Raspberry Pi 4s
|
||||
|
||||
- Four Raspberry Pi Zero Ws
|
||||
|
||||
- Some desktop speakers and some Bluetooth speakers (wired to the Pis)
|
||||
|
||||
Each of the Raspberry Pis is in a room or porch attached to a speaker.
|
||||
|
||||
Snapcast lets me take an audio source and synchronize it across multiple
|
||||
clients. Each of the Raspberry Pis are running a =snapclient= instance
|
||||
and play whatever the =snapserver= instance tells them to.
|
||||
|
||||
Snapcast is setup to send whichever of the streams (MPD, Spotify,
|
||||
Shairport-sync/AirPlay) is playing audio to each of the clients that are
|
||||
connected to it.
|
||||
|
||||
This lets me or anyone else on my WiFi network play directly on one or
|
||||
more of the speakers - each named for the room that they're in using
|
||||
either Spotify, AirPlay, picking from my own music collection or by
|
||||
pointing at a URL (like to a podcast episode).
|
||||
|
||||
This works out great and we've used it at home for the past year.
|
||||
|
||||
I'd like to get the podcast experience to a more seamless place but it's
|
||||
pretty OK right now using AirMusic on my phone to play audio to the
|
||||
speakers over AirPlay.
|
||||
|
||||
* vi-editing everywhere
|
||||
:PROPERTIES:
|
||||
:ONE: wfot-default
|
||||
:CUSTOM_ID: /blog/vi-everywhere/
|
||||
:END:
|
||||
|
||||
** vi modal editing in most places
|
||||
For my sake, I prefer to have Vim bindings in as many places as
|
||||
possible.
|
||||
|
||||
Most shells can be configured to use Vim bindings by putting =set -o vi=
|
||||
somewhere in your shell startup script.
|
||||
|
||||
If you're using ZSH then you'll probably want an additional binding to
|
||||
restore CTRL-R reverse history search.
|
||||
|
||||
=bindkey '^R' history-incremental-search-backward=
|
||||
|
||||
For CLI tools that use the =readline= library then you can configure its
|
||||
input mode using a =.inputrc= file in your =$HOME= directory.
|
||||
|
||||
This affects REPLs like =ghci= and tools like =psql=.
|
||||
|
||||
#+begin_src txt
|
||||
set editing-mode vi
|
||||
$if mode=vi
|
||||
|
||||
set keymap vi-command
|
||||
# these are for vi-command mode
|
||||
Control-l: clear-screen
|
||||
|
||||
set keymap vi-insert
|
||||
# these are for vi-insert mode
|
||||
Control-l: clear-screen
|
||||
$endif
|
||||
#+end_src
|
||||
* AWS Cloudwatch Metric Filters
|
||||
:PROPERTIES:
|
||||
:ONE: wfot-default
|
||||
:CUSTOM_ID: /blog/large-companies/
|
||||
:END:
|
||||
|
||||
** Structed and passively collected metrics via AWS CloudWatch
|
||||
|
||||
AWS is a vast and sprawling set of services. It can be hard to find the
|
||||
hidden gems like this one so I wanted to point this one out.
|
||||
|
||||
Structured metrics are very helpful to monitoring the health and
|
||||
function of an software system.
|
||||
|
||||
- Do you want to know how long a particular transaction typically takes?
|
||||
- How fast your database queries are?
|
||||
- How long external APIs take to respond?
|
||||
- Fire an alert when a particular function on the site happens too many
|
||||
times? Or too few times?
|
||||
|
||||
...plus a million other things specific to whatever system you're
|
||||
working on.
|
||||
|
||||
There are a lot of great tools for doing this and one that you might not
|
||||
know about is AWS CloudWatch Metric Filters. If you're already on AWS
|
||||
then you should consider these because it requires only that your
|
||||
application logs to CloudWatch.
|
||||
|
||||
If you're on ECS then the [[https://docs.aws.amazon.com/AmazonECS/latest/developerguide/using_awslogs.html][awslogs]] log driver for Docker gets you that
|
||||
nearly for free. By "free" I mean that your application itself can
|
||||
have /zero/ dependencies on AWS services and not require any AWS
|
||||
credentials or libraries to start pumping out metrics that you can
|
||||
visualize, alert on and record over time.
|
||||
|
||||
The [[https://docs.aws.amazon.com/AmazonCloudWatch/latest/logs/MonitoringLogData.html][AWS docs]] themselves offer the canonical reference for configuring
|
||||
these so I won't go into detail here.
|
||||
|
||||
However, the gist is that for a log filter you define the following
|
||||
properties
|
||||
|
||||
- A filter pattern for extracting a discrete metric value out of a log
|
||||
entry
|
||||
- A metric name to store the value in
|
||||
- An optional dimension for sub-classifying the value
|
||||
- And finally a log group to extract the metric values from
|
||||
|
||||
After that you just run the application and as the logs roll in the
|
||||
metric values get pumped out. Then you can [[https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/Create-alarm-on-metric-math-expression.html][define alarms for alerting]]
|
||||
on them, [[https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/CloudWatch_Dashboards.html][graph them]], [[https://docs.aws.amazon.com/autoscaling/ec2/userguide/as-scaling-simple-step.html#policy-creating-alarm-console][define autoscaling rules]] from them and more.
|
||||
|
||||
To conclude - AWS is big and hairy. While there are benefits to staying
|
||||
platform agnostic, some AWS services don't require much or any coupling
|
||||
of your application code to take advantage of. Cloudwatch Metrics is one
|
||||
of those services and you can get a lot of value out of it with not much
|
||||
effort.
|
||||
|
||||
|
||||
* The default page
|
||||
:PROPERTIES:
|
||||
:ONE: wfot-default
|
||||
:CUSTOM_ID: /blog/default/
|
||||
:END:
|
||||
|
||||
This page is rendered with the default render function ~one-default~
|
||||
specified in ~ONE~ org property.
|
||||
|
||||
** Do you want a table of content?
|
||||
|
||||
As you can see, ~one-default~ doesn't add a table of content (TOC). If
|
||||
you want a default render function that adds the TOC to the page you can
|
||||
use the render function ~one-default-with-toc~ presented in [[#/blog/one-default-with-toc/][The default
|
||||
page with a TOC]].
|
||||
|
||||
** Headline foo
|
||||
*** Headline bar
|
||||
|
||||
Some content.
|
||||
|
||||
*** Headline baz
|
||||
|
||||
#+BEGIN_SRC bash :results verbatim
|
||||
tree
|
||||
#+END_SRC
|
||||
|
||||
#+RESULTS:
|
||||
#+begin_example
|
||||
.
|
||||
├── assets
|
||||
│ └── one.css
|
||||
├── one.org
|
||||
└── public
|
||||
├── blog
|
||||
│ ├── default
|
||||
│ │ └── index.html
|
||||
│ ├── default-home-list-pages
|
||||
│ │ └── index.html
|
||||
│ ├── one-default-doc
|
||||
│ │ └── index.html
|
||||
│ ├── one-default-with-sidebar
|
||||
│ │ └── index.html
|
||||
│ └── one-default-with-toc
|
||||
│ └── index.html
|
||||
├── index.html
|
||||
└── one.css
|
||||
|
||||
8 directories, 9 files
|
||||
#+end_example
|
||||
|
||||
* The default page with a sidebar
|
||||
:PROPERTIES:
|
||||
:ONE: wfot-default-with-sidebar
|
||||
:CUSTOM_ID: /blog/one-default-with-sidebar/
|
||||
:END:
|
||||
|
||||
This page is rendered with the render function ~one-default-with-sidebar~
|
||||
specified in the org property ~ONE~.
|
||||
|
||||
** Do you want a sidebar and a TOC?
|
||||
|
||||
Perhaps you want a sidebar listing all the pages on your website and a
|
||||
table of content, as many modern documentation sites do. If so, you
|
||||
can use the default render function ~one-default-doc~ presented in [[#/blog/one-default-doc/][The
|
||||
default page with TOC and sidebar]].
|
||||
|
||||
** Headline foo
|
||||
*** Headline bar
|
||||
|
||||
Some content.
|
||||
|
||||
*** Headline baz
|
||||
|
||||
#+BEGIN_SRC bash :results verbatim
|
||||
tree
|
||||
#+END_SRC
|
||||
|
||||
#+RESULTS:
|
||||
#+begin_example
|
||||
.
|
||||
├── assets
|
||||
│ └── one.css
|
||||
├── one.org
|
||||
└── public
|
||||
├── blog
|
||||
│ ├── default
|
||||
│ │ └── index.html
|
||||
│ ├── default-home-list-pages
|
||||
│ │ └── index.html
|
||||
│ ├── one-default-doc
|
||||
│ │ └── index.html
|
||||
│ ├── one-default-with-sidebar
|
||||
│ │ └── index.html
|
||||
│ └── one-default-with-toc
|
||||
│ └── index.html
|
||||
├── index.html
|
||||
└── one.css
|
||||
|
||||
8 directories, 9 files
|
||||
#+end_example
|
||||
|
||||
* The default page with TOC and sidebar
|
||||
:PROPERTIES:
|
||||
:ONE: wfot-default-doc
|
||||
:CUSTOM_ID: /blog/one-default-doc/
|
||||
:END:
|
||||
|
||||
This page is rendered with the function ~one-default-doc~ specified
|
||||
in the org property ~ONE~.
|
||||
|
||||
** Do you want to know more about one.el?
|
||||
|
||||
Check the documentation at https://one.tonyaldon.com.
|
||||
|
||||
** Headline foo
|
||||
*** Headline bar
|
||||
|
||||
Some content.
|
||||
|
||||
*** Headline baz
|
||||
|
||||
#+BEGIN_SRC bash :results verbatim
|
||||
tree
|
||||
#+END_SRC
|
||||
|
||||
#+RESULTS:
|
||||
#+begin_example
|
||||
.
|
||||
├── assets
|
||||
│ └── one.css
|
||||
├── one.org
|
||||
└── public
|
||||
├── blog
|
||||
│ ├── default
|
||||
│ │ └── index.html
|
||||
│ ├── default-home-list-pages
|
||||
│ │ └── index.html
|
||||
│ ├── one-default-doc
|
||||
│ │ └── index.html
|
||||
│ ├── one-default-with-sidebar
|
||||
│ │ └── index.html
|
||||
│ └── one-default-with-toc
|
||||
│ └── index.html
|
||||
├── index.html
|
||||
└── one.css
|
||||
|
||||
8 directories, 9 files
|
||||
#+end_example
|
||||
160
onerc.el
Normal file
160
onerc.el
Normal file
@@ -0,0 +1,160 @@
|
||||
(setq wfot-css "https://cdn.jsdelivr.net/npm/holiday.css@0.11.2")
|
||||
|
||||
(defun wfot-default (page-tree pages _global)
|
||||
"willfullyobtuse default render function
|
||||
|
||||
See `one-is-page', `one-render-pages' and `one-default-css'."
|
||||
(let* ((title (org-element-property :raw-value page-tree))
|
||||
(path (org-element-property :CUSTOM_ID page-tree))
|
||||
(content (org-export-data-with-backend
|
||||
(org-element-contents page-tree)
|
||||
'one-ox nil))
|
||||
(website-name (one-default-website-name pages))
|
||||
(nav (one-default-nav path pages)))
|
||||
(jack-html
|
||||
"<!DOCTYPE html>"
|
||||
`(:html
|
||||
(:head
|
||||
(:meta (@ :name "viewport" :content "width=device-width,initial-scale=1"))
|
||||
(:link (@ :rel "stylesheet" :href wfot-css))
|
||||
(:title ,title))
|
||||
(:body
|
||||
(:div.header (:a (@ :href "/") ,website-name))
|
||||
(:div.content
|
||||
(:div.title
|
||||
,(if (not (string= path "/"))
|
||||
`(:div.title (:h1 ,title))
|
||||
'(:div.title-empty)))
|
||||
,content
|
||||
,nav))))))
|
||||
|
||||
(defun wfot-default-home-list-pages (page-tree pages _global)
|
||||
"Default render function to use in the home page that lists pages.
|
||||
|
||||
See `one-is-page' for the meaning of PAGE-TREE and PAGES.
|
||||
|
||||
Also see `one-render-pages' and `one-default-css'."
|
||||
(let* ((title (org-element-property :raw-value page-tree))
|
||||
(content (org-export-data-with-backend
|
||||
(org-element-contents page-tree)
|
||||
'one-ox nil))
|
||||
(website-name (one-default-website-name pages))
|
||||
;; All pages but the home pages
|
||||
(pages-list (one-default-pages pages "/.+")))
|
||||
(jack-html
|
||||
"<!DOCTYPE html>"
|
||||
`(:html
|
||||
(:head
|
||||
(:meta (@ :name "viewport" :content "width=device-width,initial-scale=1"))
|
||||
(:link (@ :rel "stylesheet" :type "text/css" :href wfot-css))
|
||||
(:title ,title))
|
||||
(:body
|
||||
(:div.header (:a (@ :href "/") ,website-name))
|
||||
(:div.content
|
||||
(:div/home-list-pages ,content)
|
||||
(:div/pages (:ul ,(reverse pages-list)))))))))
|
||||
|
||||
(defun wfot-default-home (page-tree pages _global)
|
||||
"Default render function to use in the home page.
|
||||
|
||||
See `one-is-page' for the meaning of PAGE-TREE and PAGES.
|
||||
|
||||
Also see `one-render-pages' and `one-default-css'."
|
||||
(let* ((title (org-element-property :raw-value page-tree))
|
||||
(content (org-export-data-with-backend
|
||||
(org-element-contents page-tree)
|
||||
'one-ox nil))
|
||||
(website-name (one-default-website-name pages)))
|
||||
(jack-html
|
||||
"<!DOCTYPE html>"
|
||||
`(:html
|
||||
(:head
|
||||
(:meta (@ :name "viewport" :content "width=device-width,initial-scale=1"))
|
||||
(:link (@ :rel "stylesheet" :type "text/css" :href wfot-css))
|
||||
(:title ,title))
|
||||
(:body
|
||||
(:div.header ,website-name)
|
||||
(:div.content
|
||||
(:div/home ,content)))))))
|
||||
|
||||
(defun wfot-default-with-sidebar (page-tree pages global)
|
||||
"Default render function with a sidebar listing PAGES.
|
||||
|
||||
See `one-is-page' for the meaning of PAGE-TREE and GLOBAL.
|
||||
|
||||
Also see `one-default-sidebar', `one-render-pages' and `one-default-css'."
|
||||
(wfot-default-sidebar page-tree pages global))
|
||||
|
||||
(defun wfot-default-sidebar (page-tree pages _global &optional with-toc)
|
||||
"Return a HTML string with PAGES listed in a sidebar.
|
||||
|
||||
The arguments PAGE-TREE, PAGES and _GLOBAL are the same as
|
||||
render functions take (See `one-is-page').
|
||||
|
||||
When WITH-TOC is non-nil, add the table of content of PAGE-TREE
|
||||
in the HTML string.
|
||||
|
||||
This function is meant to be used by `one-default-with-sidebar'
|
||||
and `one-default-doc' render functions.
|
||||
|
||||
See `one-render-pages', `one-default-css' and `one-default-pages'."
|
||||
(let* ((title (org-element-property :raw-value page-tree))
|
||||
(path (org-element-property :CUSTOM_ID page-tree))
|
||||
(content (org-export-data-with-backend
|
||||
(org-element-contents page-tree)
|
||||
'one-ox nil))
|
||||
(website-name (one-default-website-name pages))
|
||||
(pages-list (one-default-pages pages))
|
||||
(headlines (cdr (one-default-list-headlines page-tree)))
|
||||
(toc (one-default-toc-component headlines))
|
||||
(nav (one-default-nav path pages)))
|
||||
(jack-html
|
||||
"<!DOCTYPE html>"
|
||||
`(:html
|
||||
(:head
|
||||
(:meta (@ :name "viewport" :content "width=device-width,initial-scale=1"))
|
||||
(:link (@ :rel "stylesheet" :type "text/css" :href wfot-css))
|
||||
(:title ,title))
|
||||
(:body
|
||||
;; sidebar-left and sidebar-main are for small devices
|
||||
(:div/sidebar-left (@ :onclick "followSidebarLink()")
|
||||
(:div (:div "Pages"))
|
||||
,pages-list)
|
||||
(:div/sidebar-main)
|
||||
(:div/sidebar-header
|
||||
(:svg/hamburger (@ :viewBox "0 0 24 24" :onclick "sidebarShow()")
|
||||
(:path (@ :d "M21,6H3V5h18V6z M21,11H3v1h18V11z M21,17H3v1h18V17z")))
|
||||
(:a (@ :href "/") ,website-name))
|
||||
(:div/sidebar-content
|
||||
(:div/sidebar ,pages-list)
|
||||
(:article
|
||||
,(if (not (string= path "/"))
|
||||
`(:div.title (:h1 ,title))
|
||||
'(:div.title-empty))
|
||||
,(when with-toc toc)
|
||||
,content
|
||||
,nav)))
|
||||
(:script "
|
||||
function sidebarShow() {
|
||||
if (window.innerWidth < 481)
|
||||
document.getElementById('sidebar-left').style.width = '75vw';
|
||||
else {
|
||||
document.getElementById('sidebar-left').style.width = 'min(300px, 34vw)';
|
||||
}
|
||||
document.getElementById('sidebar-main').setAttribute('onclick', 'sidebarHide()');
|
||||
document.getElementById('sidebar-main').style.display = 'block';
|
||||
}
|
||||
function sidebarHide() {
|
||||
document.getElementById('sidebar-left').style.width = '0';
|
||||
document.getElementById('sidebar-main').style.display = 'none';
|
||||
}
|
||||
")))))
|
||||
|
||||
|
||||
(defun wfot-default-doc (page-tree pages global)
|
||||
"Default render function with a sidebar listing PAGES and the table of content.
|
||||
|
||||
See `one-is-page' for the meaning of PAGE-TREE and GLOBAL.
|
||||
|
||||
Also see `one-default-sidebar', `one-render-pages' and `one-default-css'."
|
||||
(wfot-default-sidebar page-tree pages global 'with-toc))
|
||||
@@ -8,8 +8,8 @@
|
||||
#+html_head_simple: <link rel="stylesheet" href="https://cdn.simplecss.org/simple.min.css">
|
||||
#+html_head_holiday: <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/holiday.css@0.11.2" />
|
||||
#+html_head_mvp: <link rel="stylesheet" href="https://unpkg.com/mvp.css">
|
||||
#+html_head_pico: <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@picocss/pico@2/css/pico.min.css">
|
||||
#+html_head: <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@picocss/pico@2/css/pico.classless.amber.min.css">
|
||||
#+html_head: <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@picocss/pico@2/css/pico.classless.min.css" />
|
||||
#+html_head_pico_amber: <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@picocss/pico@2/css/pico.classless.amber.min.css">
|
||||
#+html_head_tacit: <link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/yegor256/tacit@gh-pages/tacit-css-1.8.1.min.css"/>
|
||||
#+html_container: main
|
||||
#+html_content_class: container
|
||||
|
||||
@@ -4,8 +4,6 @@
|
||||
#+subtitle:
|
||||
:END:
|
||||
** vi modal editing in most places
|
||||
#+HTML: <section>
|
||||
#+HTML: <article>
|
||||
For my sake, I prefer to have Vim bindings in as many places as
|
||||
possible.
|
||||
|
||||
@@ -21,8 +19,6 @@ For CLI tools that use the =readline= library then you can configure its
|
||||
input mode using a =.inputrc= file in your =$HOME= directory.
|
||||
|
||||
This affects REPLs like =ghci= and tools like =psql=.
|
||||
#+HTML: </article>
|
||||
#+HTML: </section>
|
||||
|
||||
#+begin_src txt
|
||||
set editing-mode vi
|
||||
@@ -37,87 +33,3 @@ set keymap vi-insert
|
||||
Control-l: clear-screen
|
||||
$endif
|
||||
#+end_src
|
||||
|
||||
|
||||
#+BEGIN_SRC haskell
|
||||
module Data.Validation.Aeson where
|
||||
|
||||
import Control.Monad.Identity
|
||||
|
||||
import Data.Aeson
|
||||
import qualified Data.Aeson.Key as Key
|
||||
import qualified Data.Aeson.KeyMap as KeyMap
|
||||
import qualified Data.ByteString as BS
|
||||
import qualified Data.ByteString.Lazy as LazyBS
|
||||
import qualified Data.Map.Strict as Map
|
||||
import qualified Data.Set as Set
|
||||
import qualified Data.Text as Text
|
||||
import qualified Data.Vector as Vec
|
||||
|
||||
import Data.Validation.Types
|
||||
|
||||
decodeValidJSON :: Validator Value a -> LazyBS.ByteString -> ValidationResult a
|
||||
decodeValidJSON validator input =
|
||||
runIdentity (decodeValidJSONT (liftV validator) input)
|
||||
|
||||
decodeValidJSONStrict :: Validator Value a -> BS.ByteString -> ValidationResult a
|
||||
decodeValidJSONStrict validator input =
|
||||
runIdentity (decodeValidJSONStrictT (liftV validator) input)
|
||||
|
||||
decodeValidJSONT ::
|
||||
Applicative m =>
|
||||
ValidatorT Value m a ->
|
||||
LazyBS.ByteString ->
|
||||
m (ValidationResult a)
|
||||
decodeValidJSONT validator input =
|
||||
case eitherDecode input of
|
||||
Left err -> pure $ Invalid (errMessage $ Text.pack err)
|
||||
Right value -> runValidatorT validator (value :: Value)
|
||||
|
||||
decodeValidJSONStrictT ::
|
||||
Applicative m =>
|
||||
ValidatorT Value m a ->
|
||||
BS.ByteString ->
|
||||
m (ValidationResult a)
|
||||
decodeValidJSONStrictT validator input =
|
||||
case eitherDecodeStrict input of
|
||||
Left err -> pure $ Invalid (errMessage $ Text.pack err)
|
||||
Right value -> runValidatorT validator (value :: Value)
|
||||
|
||||
instance Validatable Value where
|
||||
inputText (String text) = Just text
|
||||
inputText _ = Nothing
|
||||
|
||||
inputNull Null = IsNull
|
||||
inputNull _ = NotNull
|
||||
|
||||
inputBool (Bool True) = Just True
|
||||
inputBool (Bool False) = Just False
|
||||
inputBool _ = Nothing
|
||||
|
||||
arrayItems (Array items) = Just items
|
||||
arrayItems _ = Nothing
|
||||
|
||||
scientificNumber (Number sci) = Just sci
|
||||
scientificNumber _ = Nothing
|
||||
|
||||
lookupChild attrName (Object hmap) =
|
||||
LookupResult $
|
||||
KeyMap.lookup (Key.fromText attrName) hmap
|
||||
lookupChild _ _ = InvalidLookup
|
||||
|
||||
instance ToJSON Errors where
|
||||
toJSON (Messages set) =
|
||||
Array
|
||||
. Vec.fromList
|
||||
. map toJSON
|
||||
. Set.toList
|
||||
$ set
|
||||
toJSON (Group attrs) =
|
||||
Object
|
||||
. KeyMap.fromList
|
||||
. Map.toList
|
||||
. Map.mapKeys Key.fromText
|
||||
. Map.map toJSON
|
||||
$ attrs
|
||||
#+END_SRC
|
||||
|
||||
Reference in New Issue
Block a user