Showing 567 to 570

TOML as default, YAML when you know you need it

Takeaways from: stackoverflow.com/questions/65283208/toml-vs-yaml-vs-strictyaml

In summary, YAML isn't actually sloppy #tech, but it's full of features that most people don't use. For example, you can write a value 1d6 and have that parsed as an object {number: 1, sides: 6}!

When you do have use for stuff like that, you'll know.

However… most uses of YAML/TOML/JSON files just have a program read in the data in a simplistic way to transform into a table or map object, end of story. You're not looking for any sophistications.

In that case, TOML serves better because fewer possibilities in syntax means it's able to give more specific error messages when the file is misformatted.

Created (2 years ago)

Design of this website

Some basic details in About.

Looking at gwern.net/design, I realized I have some opinions on design philosophy too.

URLs shouldn't be too short

  • Look at the link sive.rs/su – what is this monstrosity of a web address? No contextual information. If it had been named https://sive.rs/short-urls, I could recognize it as a page I've read before: "ah yeah, that's that page Sivers wrote about short URLs". With the ultra-short variant, I have to visit it before I can recognize that I've already been there and go back to wherever I was.
    • Sivers would respect my time better if the URL gave enough info for me to avoid clicking it! There's a design criterion.
    • Even qntm.org/destroy is not a link I can recognize on sight. Single-word slugs are usually a bad idea, though gwern.net/design is OK. It strains the limits of my pattern-matcher, but it matches.
      • For that particular page, the top domain gwern.net/... luckily contributes to the meaning, as the page is literally about the design of gwern.net, not about design in general.
      • A year from now, I might not recognize it, but it's night and day compared with qntm.org/destroy, which takes me only an hour to forget. In the time I've been drafting this article I've been largely unaware of just what article that links to – I visit a few times for curiosity, but the association between the generic verb "destroy" and that article fades so quickly. "Qntm destroy what?" It doesn't complete to anything like a sentence.
      • The ideal slug is seen in URLs like www.greaterwrong.com/posts/x4dG4GhpZH2hgz59x/joy-in-the-merely-real, if we ignore for a moment the superfluous bits and bobs like the /posts/x4dG4GhpZH2hgz59x/ and the www. I prefer to handle links such as this. It's many years ago I visited this link, yet even after all that time, it's impossible to mistake. That feature, recognizability, trumps any length aesthetic.
        • Takeaway: slugs should be long and specific, maximizing recognizability.
    • You should have to click links as little as possible (my site is a rat's nest of hyperlinks only as far as it must be). The same theory underlies people like Gwern's efforts to contextualize links and generate preview pop-ups. I say, it helps a lot just having a descriptive URL in the first place.

Permanent page ID🔗

  • My URLs get a unique random ID before the descriptive slug, i.e. domain.com/ID/slug, giving me full freedom to rename the slug part and split and merge pages. I couldn't live without that; almost all my pages have been renamed at least once, and some have been through a dozen renames.
    • When writing the notes for yourself only, it is possible to rely on a mass-renaming toolkit such as what orgrr provides. However, that does not keep alive your blog visitors' bookmarks.
      • To keep them alive, the toolkit would have to keep a record of all renames ever done, that you can translate into URL redirects…
    • My page ID is five alphabetic characters, sans vowels, all lowercase.
      • Example: edstrom.dev/lfqtr/design-of-this-site
      • This implies a 21-character alphabet, or "base-21". Out of that alphabet, there are 4,084,101 ways to compose a five-char string, plenty for one person's homepage (How long page-IDs?).
        • The probability of collision is low but not effectively zero like with a proper UUID, but here's an advantage of a one-person one-machine system – when a collision occurs, I can just renew the ID.
      • Excluding vowels prevents accidentally generating words (and obscenities). It means I can have custom routes such as domain.com/login without worrying that there already exists an autogenerated ID "login".
      • Old mistake: Instead of alphabetic, it used to be base-62 i.e. the ID could include numbers and capital letters. I had links like edstrom.dev/m99K and edstrom.dev/0dSJ.

        Use lowercase alphabetic! It nods to the fact that URLs are a user interface!

        Sometimes I want to send myself a link between devices, but I have to stop and think how to do that. (Do I email myself? Do I use the Share button?) If the URL is short, it's almost always faster and more comfortable to just type it. Right?

        Here base-62 comes into the picture and tramples my lawn, because phone keyboards—

Created (2 years ago)
Updated (2 years ago)

Org-roam fixes

Some mods to #org-roam I've felt necessary.

[2024-08-20 Tue]: My replacement package org-node now contains all of these features!

Skip prompting for a filename

It's enough that you gave a title, shouldn't also have to confirm the filename, right?

(setq org-roam-capture-templates
      '(("d" "default" plain "%?" :if-new
         (file+head "%<%Y%m%d%H%M%S>-${slug}.org"
                    "#+title: ${title}\n#+filetags: :noexport:stub:"))
        :immediate-finish t
        :jump-to-captured t))

Quick stubs🔗

Situation: You're in the middle of writing and you realize you want to link to a node that doesn't yet exist.

I have the following capture template for that. It makes a blank new file without visiting it, so you can pick it on the org-roam-node-insert screen and be on your merry way.

(add-to-list 'org-roam-capture-templates
             '("i" "instantly create this node" plain "%?" :if-new
               (file+head "%<%Y%m%d%H%M%S>-${slug}.org"
                          "#+title: ${title}\n#+filetags: :stub:\n")
               :immediate-finish t)

Performance🔗

Org-roam gets slow as you accumulate thousands of nodes. The following code fixes it. Requires the packages memoize and vulpea.

Vulpea provides faster versions of org-roam commands, but it depends on its own optimized database. The first time you enable vulpea-db-autosync-mode, you need to eval (org-roam-db-sync 'force) so it can populate that database.

;; Make the commands `org-roam-node-find' & `org-roam-node-insert' faster and
;; often instant.

;; Small drawback: after you just created a node, you can't immediately
;; find it as it won't be in the cache.  You must leave Emacs alone for
;; 10 seconds, then it'll enter the cache.

(defun my-vulpea-memo-refresh ()
  (memoize-restore #'vulpea-db-query)
  (memoize         #'vulpea-db-query)
  (vulpea-db-query nil))

(defvar my-vulpea-memo-timer (timer-create))
(defun my-vulpea-memo-schedule-refresh (&rest _)
  "Schedule a re-caching when the user is idle."
  (cancel-timer my-vulpea-memo-timer)
  (setq my-vulpea-memo-timer
        (run-with-idle-timer 10 nil #'my-vulpea-memo-refresh)))

;; Love this lib. Thank you
(use-package vulpea
  :hook ((org-roam-db-autosync-mode . vulpea-db-autosync-enable))
  :bind (([remap org-roam-node-find] . vulpea-find)
         ([remap org-roam-node-insert] . vulpea-insert))
  :config
  (use-package memoize :demand)
  (memoize #'vulpea-db-query)
  (advice-add 'org-roam-db-update-file :after 'my-vulpea-memo-schedule-refresh))

Other speed boosts

;; Don't search for "roam:" links (it slows saving on large files)
(setq org-roam-link-auto-replace nil)

;; Speed up `org-roam-db-sync'
;; NOTE: `setopt' breaks it, use `setq'
;; NOTE: Counterproductive on Windows
(setq org-roam-db-gc-threshold most-positive-fixnum)

If you still find it slow to save large files, maybe you have org-crypt configured. It's that package that encrypts/decrypts subtrees.

Doom Emacs' Org module ships it by default, so here's how to disable it in Doom's packages.el:

(package! org-crypt :disable t)

Custom org-roam-extract-subtree🔗

I encourage you to maintain a personal variant of org-roam-extract-subtree. It's a pleasure to have one that does everything right.

Here's mine. Compare with the original (type M-x find-function org-roam-extract-subtree RET). Differences from the default command:

  1. Actually make a file-level node, i.e. a file with :PROPERTIES: at the top, instead of a plain file with a top-level heading
  2. Skip prompting for a title – just reuse the heading as title
  3. Copy the original file's :CREATED: property if the subtree didn't already have its own (I use this property everywhere because it's not as if you can rely on filesystem ctime)
  4. Copy all the parent tags
    • Only if org-use-tag-inheritance is t
    • If there were no tags anywhere, add a :noexport: tag. That's normally what I want for new nodes (Slipbox workflow).
  5. Add a #+date: for me to fill in later
    • I use this field to indicate the date of "last meaningful update", thus it becomes Last Updated on my blog. Could use file modification time, but it is not meaningful.
  6. Show the resulting file to me, so I can verify it looks right
  7. Leave a link in the original file, where the subtree was
(defun my-org-roam-extract-subtree ()
  "Variant of `org-roam-extract-subtree'.
It skips prompting, and inserts the metadata I want."
  (interactive)
  (save-excursion
    (org-back-to-heading-or-point-min t)
    (when (bobp) (user-error "Already a top-level node"))
    (org-id-get-create)
    (save-buffer)
    (org-roam-db-update-file)
    (let* ((template-info nil)
           (node (org-roam-node-at-point))
           ;; Determine filename based on `org-roam-extract-new-file-path'
           (template (org-roam-format-template
                      (string-trim (org-capture-fill-template
                                    org-roam-extract-new-file-path))
                      (lambda (key default-val)
                        (let ((fn (intern key))
                              (node-fn (intern (concat "org-roam-node-" key))))
                          (cond
                           ((fboundp fn)
                            (funcall fn node))
                           ((fboundp node-fn)
                            (funcall node-fn node))
                           (t (let ((r (read-from-minibuffer (format "%s: " key) default-val)))
                                (plist-put template-info ksym r)
                                r)))))))
           (file-path
            (expand-file-name template org-roam-directory))
           (parent-tags (org-get-tags))
           (parent-creation (save-excursion
                              (goto-char (point-min))
                              (org-entry-get nil "CREATED"))))
      (when (file-exists-p file-path)
        (user-error "%s exists. Aborting" file-path))
      (org-cut-subtree)
      (open-line 1)
      (insert "- " (org-link-make-string
                    (concat "id:" (org-roam-node-id node))
                    (org-roam-node-formatted node)))
      (save-buffer)
      (find-file file-path)
      (org-paste-subtree)
      (while (> (org-current-level) 1)
        (org-promote-subtree))
      (save-buffer)
      (org-roam-promote-entire-buffer)
      (goto-char (point-min))
      (unless (org-entry-get nil "CREATED")
        (org-set-property "CREATED" (or parent-creation
                                        (format-time-string "[%F]"))))
      (org-roam-tag-add (or parent-tags
                            '("noexport")))
      (search-forward "#+title")
      (goto-char (line-beginning-position))
      (if (version<= "29" emacs-version)
          (ensure-empty-lines 0)
        (when (looking-back "\n\n")
          (join-line)))
      (search-forward "#+filetags" nil t)
      (forward-line 1)
      (open-line 2)
      (insert "#+date:")
      (save-buffer))))

Custom org-open-at-point🔗

Before you can start to use roam refs confidently, or even really understand what they are, you need a picture in your head of how they will make your life easier.

Part of my picture was that I should be able to insert a raw URL anywhere and then if I click on it, Emacs will jump to the corresponding roam-node if I happen to have one that refs that URL.

This replacement for org-open-at-point does that.

(define-key global-map [remap org-open-at-point]
            #'my-org-open-at-point-as-maybe-roam-ref)
(defun my-org-open-at-point-as-maybe-roam-ref (&optional arg)
  "Like `org-open-at-point', but prefer to visit any org-roam node
that has the link as a ref.
If already visiting that same node, then follow the link normally."
  (interactive "P")
  (let* ((url (thing-at-point 'url))
         (path (if (derived-mode-p 'org-mode)
                   (org-element-property :path (org-element-context))
                 (replace-regexp-in-string (rx bol (* (not "/"))) "" url)))
         (all-refs (org-roam-db-query
                    [:select [ref id]
                     :from refs
                     :left-join nodes
                     :on (= refs:node-id nodes:id)]))
         (found (when path (assoc path all-refs))))

    (if (and found
             ;; check that the ref does not point to THIS file (if so, better to
             ;; just open the url normally)
             (not (when (derived-mode-p 'org-mode)
                    (equal (cdr found)
                           (or (org-id-get)
                               (progn
                                 (goto-char (point-min))
                                 (org-id-get)))))))
        (org-roam-node-visit (org-roam-node-from-id (cadr found)))
      (if arg
          (org-open-at-point arg)
        (org-open-at-point)))))

Command to auto-rename file based on #+TITLE🔗

In Org-roam, nodes are named by the #+title line – it doesn't care about the name of the files on the filesystem.

However, the filename does influence the exported HTML filenames, which determines your blog post addresses.

With the following command, you can type M-x my-rename-roam-file-by-title whenever you want to update the filename.

(defun my-rename-roam-file-by-title (&optional path)
  "Rename file in current buffer, based on its Org
#+title property.

Can also take a file PATH instead of current buffer."
  (interactive)
  (unless path
    (setq path (buffer-file-name)))
  (unless (equal "org" (file-name-extension path))
    (user-error "File doesn't end in .org: %s" path))
  (let* ((visiting (find-buffer-visiting path))
         (on-window (and visiting (get-buffer-window visiting)))
         (slug
          (with-current-buffer (or visiting (find-file-noselect path))
            (goto-char (point-min))
            (let ((node (org-roam-node-at-point t)))
              (unless (org-roam-node-title node)
                (user-error "Node not yet known to org-roam DB"))
              (org-roam-node-slug node))))
         (new-path (expand-file-name (concat slug ".org")
                                     (file-name-directory path))))
    (if (equal path new-path)
        (message "Filename already correct: %s" path)
      (if (and visiting (buffer-modified-p visiting))
          (message "Unsaved file, letting it be: %s" path)
        (unless (file-writable-p path)
          (error "No permissions to rename file: %s" path))
        (unless (file-writable-p new-path)
          (error "No permissions to write a new file at: %s" new-path))
        ;; Kill buffer before renaming, to be safe
        (when visiting
          (kill-buffer visiting))
        (rename-file path new-path)
        (prog1 (message "File %s renamed to %s"
                        (file-name-nondirectory path)
                        (file-name-nondirectory new-path))
          ;; Visit the file again if you had it open
          (when visiting
            (let ((buf (find-file-noselect new-path)))
              (when on-window
                (set-window-buffer on-window buf)))))))))

Command to rename an image asset and rewrite all links pointing to it

(defun my-rename-roam-asset-and-rewrite-links ()
  (interactive)
  (require 'dash)
  (when (or (equal default-directory org-roam-directory)
            (when (yes-or-no-p "Not in org-roam-directory, go there?")
              (find-file org-roam-directory)
              t))
    (when-let ((bufs (--filter (string-search "*grep*" (buffer-name it))
                               (buffer-list))))
      (when (yes-or-no-p "Some *grep* buffers, kill to be sure this works?")
        (mapc #'kill-buffer bufs)))
    (let* ((filename (file-relative-name
                      (read-file-name "File: ") org-roam-directory))
           (new (read-string "New name: " filename)))
      (mkdir (file-name-directory new) t)
      (unless (file-writable-p new)
        (error "New path wouldn't be writable"))
      (rgrep (regexp-quote filename) "*.org")
      (run-with-timer
       1 nil
       (lambda ()
         (save-window-excursion
           (delete-other-windows)
           (switch-to-buffer (--find (string-search "*grep*" (buffer-name it))
                                     (buffer-list)))
           (wgrep-change-to-wgrep-mode)
           (goto-char (point-min))
           (query-replace filename new)
           (wgrep-finish-edit)
           (when (y-or-n-p "Finished editing links, rename file?")
             (rename-file filename new)
             (message "File renamed from %s to %s" filename new)))))
      (message "Waiting for rgrep to populate buffer..."))))
Created (2 years ago)
Updated (2 years ago)

Do recruiters do anything useful?

Paraphrasing a story someone told on HN: "everyone brags that they hire only the top 1% of developers. Problem is that can't be true, if 100% of the developers here get hired eventually." What's happening is that every time they decide to hire someone, they filter out 99% of what's left on the market and find someone. Then another company filters out 99% of what's left after that, and so on… until all developers have been distributed equally among the companies.

A lot of work for nothing? Why not skip filtering?

Maybe it's that if one company doesn't bother to filter, they will end up with a worse crop over time than the others. (Assuming that filtering is possible, but that's another topic.)

Created (2 years ago)
Showing 567 to 570