about summary refs log tree commit diff
path: root/tools
diff options
context:
space:
mode:
Diffstat (limited to 'tools')
-rw-r--r--tools/blog_cli/README.md41
-rw-r--r--tools/blog_cli/default.nix9
-rw-r--r--tools/blog_cli/main.go209
-rw-r--r--tools/cheddar/src/main.rs51
-rw-r--r--tools/emacs-pkgs/dottime/dottime.el3
-rw-r--r--tools/emacs-pkgs/nix-util/nix-util.el40
-rw-r--r--tools/emacs/config/functions.el29
-rw-r--r--tools/emacs/config/init.el19
-rw-r--r--tools/emacs/config/look-and-feel.el9
-rw-r--r--tools/emacs/default.nix20
10 files changed, 145 insertions, 285 deletions
diff --git a/tools/blog_cli/README.md b/tools/blog_cli/README.md
deleted file mode 100644
index 7afa0fe920..0000000000
--- a/tools/blog_cli/README.md
+++ /dev/null
@@ -1,41 +0,0 @@
-tazblog CLI
-===========
-
-My blog stores its content in DNS, spread out over three types of `TXT` entries:
-
-* `TXT _posts.blog.tazj.in.`: A sorted list of posts, serialised as a JSON list of
-  strings (e.g. `["1486830338", "1476807384"]`)
-
-* `TXT _chunks.$postID.blog.tazj.in`: JSON chunks containing the blog post text
-
-* `TXT _meta.$postID.blog.tazj.in`: JSON blob with blog post metadata
-
-All JSON blobs are base64-encoded.
-
-This CLI tool helps to update those records.
-
-Each blog post data is a series of JSON-encoded structures which follow one of
-these formats:
-
-```
-struct metadata {
-    chunks: int
-    title: string
-    date: date
-}
-```
-
-Where `chunks` describes the number of chunks following this format:
-
-```
-struct chunk {
-    c: int
-    t: string
-}
-```
-
-Writing a blog post to DNS means taking its text and metadata, chunking it up
-and writing the chunks.
-
-Reading a blog post means retrieving all data, reading the metadata and then
-assembling the chunks in order.
diff --git a/tools/blog_cli/default.nix b/tools/blog_cli/default.nix
deleted file mode 100644
index c22e4c949b..0000000000
--- a/tools/blog_cli/default.nix
+++ /dev/null
@@ -1,9 +0,0 @@
-{ pkgs, ... }:
-
-pkgs.buildGo.program {
-  name = "blog_cli";
-  srcs = [ ./main.go ];
-  deps = with pkgs.third_party; [
-    gopkgs."google.golang.org".api.dns.v1.gopkg
-  ];
-} // { meta.enableCI = true; }
diff --git a/tools/blog_cli/main.go b/tools/blog_cli/main.go
deleted file mode 100644
index db64f8378e..0000000000
--- a/tools/blog_cli/main.go
+++ /dev/null
@@ -1,209 +0,0 @@
-// The tazblog CLI implements updating my blog records in DNS, see the
-// README in this folder for details.
-//
-// The post input format is a file with the title on one line,
-// followed by the date on a line, followed by an empty line, followed
-// by the post text.
-package main
-
-import (
-	"context"
-	"encoding/base64"
-	"encoding/json"
-	"flag"
-	"fmt"
-	"io/ioutil"
-	"log"
-	"time"
-
-	"google.golang.org/api/dns/v1"
-)
-
-var (
-	project = flag.String("project", "tazjins-infrastructure", "Target GCP project")
-	zone    = flag.String("zone", "blog-tazj-in", "Target Cloud DNS zone")
-	title   = flag.String("title", "", "Title of the blog post")
-	date    = flag.String("date", "", "Date the post was written on")
-	infile  = flag.String("text", "", "Text file containing the blog post")
-	id      = flag.String("id", "", "Post ID - will be generated if unset")
-)
-
-// Number of runes to include in a single chunk. If any chunks exceed
-// the limit of what can be encoded, the chunk size is reduced and we
-// try again.
-var chunkSize = 200
-
-type day time.Time
-
-func (d day) MarshalJSON() ([]byte, error) {
-	j := (time.Time(d)).Format(`"2006-01-02"`)
-	return []byte(j), nil
-}
-
-type metadata struct {
-	Chunks int    `json:"c"`
-	Title  string `json:"t"`
-	Date   day    `json:"d"`
-}
-
-type chunk struct {
-	Chunk int
-	Text  string
-}
-
-type post struct {
-	ID     string
-	Meta   metadata
-	Chunks []string
-}
-
-func (p *post) writeToDNS() error {
-	var additions []*dns.ResourceRecordSet
-	additions = append(additions, &dns.ResourceRecordSet{
-		Name: fmt.Sprintf("_meta.%s.blog.tazj.in.", p.ID),
-		Type: "TXT",
-		Ttl:  1200,
-		Rrdatas: []string{
-			encodeJSON(p.Meta),
-		},
-	})
-
-	for i, c := range p.Chunks {
-		additions = append(additions, &dns.ResourceRecordSet{
-			Name:    fmt.Sprintf("_%v.%s.blog.tazj.in.", i, p.ID),
-			Type:    "TXT",
-			Ttl:     1200,
-			Rrdatas: []string{c},
-		})
-	}
-
-	ctx := context.Background()
-	dnsSvc, err := dns.NewService(ctx)
-	if err != nil {
-		return err
-	}
-
-	change := dns.Change{
-		Additions: additions,
-	}
-
-	_, err = dnsSvc.Changes.Create(*project, *zone, &change).Do()
-	if err != nil {
-		return err
-	}
-
-	return nil
-}
-
-// Encode given value as JSON and base64-encode it.
-func encodeJSON(v interface{}) string {
-	outer, err := json.Marshal(v)
-	if err != nil {
-		log.Fatalln("Failed to encode JSON", err)
-	}
-
-	return base64.RawStdEncoding.EncodeToString(outer)
-}
-
-// Encode a chunk and check whether it is too large
-func encodeChunk(c chunk) (string, bool) {
-	tooLarge := false
-	s := base64.RawStdEncoding.EncodeToString([]byte(c.Text))
-
-	if len(s) >= 255 {
-		tooLarge = true
-	}
-
-	return s, tooLarge
-}
-
-func createPost(id, title, text string, date day) post {
-	runes := []rune(text)
-	n := 0
-	tooLarge := false
-
-	var chunks []string
-
-	for chunkSize < len(runes) {
-		c, l := encodeChunk(chunk{
-			Chunk: n,
-			Text:  string(runes[0:chunkSize:chunkSize]),
-		})
-
-		tooLarge = tooLarge || l
-		chunks = append(chunks, c)
-		runes = runes[chunkSize:]
-		n++
-	}
-
-	if len(runes) > 0 {
-		c, l := encodeChunk(chunk{
-			Chunk: n,
-			Text:  string(runes),
-		})
-
-		tooLarge = tooLarge || l
-		chunks = append(chunks, c)
-		n++
-	}
-
-	if tooLarge {
-		log.Println("Too large at chunk size", chunkSize)
-		chunkSize -= 5
-		return createPost(id, title, text, date)
-	}
-
-	return post{
-		ID: id,
-		Meta: metadata{
-			Chunks: n,
-			Title:  title,
-			Date:   date,
-		},
-		Chunks: chunks,
-	}
-}
-
-func main() {
-	flag.Parse()
-
-	if *title == "" {
-		log.Fatalln("Post title must be set (-title)")
-	}
-
-	if *infile == "" {
-		log.Fatalln("Post text file must be set (-text)")
-	}
-
-	if *id == "" {
-		log.Fatalln("Post ID must be set (-id)")
-	}
-
-	var postDate day
-	if *date != "" {
-		t, err := time.Parse("2006-01-02", *date)
-		if err != nil {
-			log.Fatalln("Invalid post date", err)
-		}
-
-		postDate = day(t)
-	} else {
-		postDate = day(time.Now())
-	}
-
-	t, err := ioutil.ReadFile(*infile)
-	if err != nil {
-		log.Fatalln("Failed to read post:", err)
-	}
-
-	post := createPost(*id, *title, string(t), postDate)
-
-	log.Println("Writing post to DNS ...")
-	err = post.writeToDNS()
-
-	if err != nil {
-		log.Fatalln("Failed to write post:", err)
-	}
-
-	log.Println("Successfully wrote entries")
-}
diff --git a/tools/cheddar/src/main.rs b/tools/cheddar/src/main.rs
index 0912e29ec0..52e518cd82 100644
--- a/tools/cheddar/src/main.rs
+++ b/tools/cheddar/src/main.rs
@@ -48,17 +48,41 @@ lazy_static! {
 // Emulates the GitHub style (subtle background hue and padding).
 const BLOCK_PRE: &str = "<pre style=\"background-color:#f6f8fa;padding:16px;\">\n";
 
-fn args_extension() -> Option<String> {
-    // The name of the file to be formatted is usually passed in as
-    // the first argument and can be used to determine a syntax set.
-    let args = env::args().collect::<Vec<String>>();
-    if args.len() != 2 {
-        return None
+struct Args {
+    /// Should Cheddar run as an about filter? (i.e. give special
+    /// rendering treatment to Markdown documents)
+    about_filter: bool,
+
+    /// What file extension has been supplied (if any)?
+    extension: Option<String>,
+}
+
+/// Parse the command-line flags passed to cheddar to determine
+/// whether it is running in about-filter mode (`--about-filter`) and
+/// what file extension has been supplied.
+fn parse_args() -> Args {
+    let mut args = Args {
+        about_filter: false,
+        extension: None,
+    };
+
+    for (i, arg) in env::args().enumerate() {
+        if i == 0 {
+            continue;
+        }
+
+        if arg == "--about-filter" {
+            args.about_filter = true;
+            continue;
+        }
+
+        args.extension = Path::new(&arg)
+            .extension()
+            .and_then(OsStr::to_str)
+            .map(|s| s.to_string());
     }
 
-    Path::new(&args[1]).extension()
-        .and_then(OsStr::to_str)
-        .map(|s| s.to_string())
+    return args
 }
 
 fn should_continue(res: &io::Result<usize>) -> bool {
@@ -268,9 +292,10 @@ fn format_code(extension: Option<&str>) {
 }
 
 fn main() {
-    let extension = args_extension();
-    match extension.as_ref().map(String::as_str) {
-        Some("md") => format_markdown(),
-        extension => format_code(extension),
+    let args = parse_args();
+
+    match args.extension.as_ref().map(String::as_str) {
+        Some("md") if args.about_filter => format_markdown(),
+        extension  => format_code(extension),
     }
 }
diff --git a/tools/emacs-pkgs/dottime/dottime.el b/tools/emacs-pkgs/dottime/dottime.el
index 3500b1c9f4..2446f6488f 100644
--- a/tools/emacs-pkgs/dottime/dottime.el
+++ b/tools/emacs-pkgs/dottime/dottime.el
@@ -67,14 +67,13 @@
   ;; This will never display offsets in the chat window, as those are
   ;; always visible in the modeline anyways.
   (when (featurep 'telega)
-    (require 'telega)
     (defun telega-ins--dottime-advice (orig timestamp)
       (let* ((dtime (decode-time timestamp t))
              (current-ts (time-to-seconds (current-time)))
              (ctime (decode-time current-ts))
              (today00 (telega--time-at00 current-ts ctime)))
         (if (> timestamp today00)
-            (telega-ins-fmt "%02d·%02d" (nth 2 dtime) (nth 1 dtime))
+            (telega-ins (format "%02d·%02d" (nth 2 dtime) (nth 1 dtime)))
           (funcall orig timestamp))))
 
     (advice-add 'telega-ins--date :around #'telega-ins--dottime-advice)))
diff --git a/tools/emacs-pkgs/nix-util/nix-util.el b/tools/emacs-pkgs/nix-util/nix-util.el
index 533e7e6f34..4b9dd31a02 100644
--- a/tools/emacs-pkgs/nix-util/nix-util.el
+++ b/tools/emacs-pkgs/nix-util/nix-util.el
@@ -9,11 +9,13 @@
 ;;; Commentary:
 ;;
 ;; This package adds some functionality that I find useful when
-;; working in Nix buffers.
+;; working in Nix buffers or programs installed from Nix.
 
 (require 'json)
 (require 'map)
 
+(defvar nix-depot-path "/home/tazjin/depot")
+
 (defun nix/prefetch-github (owner repo) ; TODO(tazjin): support different branches
   "Fetch the master branch of a GitHub repository and insert the
   call to `fetchFromGitHub' at point."
@@ -38,7 +40,7 @@
                   ("finished\n"
                    (let* ((json-string (with-current-buffer outbuf
                                          (buffer-string)))
-                          (result (json-parse-string json-string)))
+                          (result (json-read-from-string json-string)))
                      (with-current-buffer buffer
                        (goto-char point)
                        (map-let (("rev" rev) ("sha256" sha256)) result
@@ -64,4 +66,38 @@
                   :stderr errbuf
                   :sentinel prefetch-handler)))
 
+(defun nix/sly-from-depot (attribute)
+  "Start a Sly REPL configured with a Lisp matching a derivation
+  from my depot.
+
+  The derivation invokes nix.buildLisp.sbclWith and is built
+  asynchronously. The build output is included in the error
+  thrown on build failures."
+
+  (interactive "sAttribute: ")
+  (lexical-let* ((outbuf (get-buffer-create (format "*depot-out/%s*" attribute)))
+         (errbuf (get-buffer-create (format "*depot-errors/%s*" attribute)))
+         (expression (format "let depot = import <depot> {}; in depot.nix.buildLisp.sbclWith [ depot.%s ]" attribute))
+         ;; TODO(tazjin): use <depot>
+         (command (list "nix-build" "--no-out-link" "-I" (format "depot=%s" nix-depot-path) "-E" expression)))
+
+    (message "Acquiring Lisp for <depot>.%s" attribute)
+    (make-process :name (format "depot-nix-build/%s" attribute)
+                  :buffer outbuf
+                  :stderr errbuf
+                  :command command
+                  :sentinel
+                  (lambda (process event)
+                    (unwind-protect
+                        (pcase event
+                          ("finished\n"
+                           (let* ((outpath (s-trim (with-current-buffer outbuf (buffer-string))))
+                                  (lisp-path (s-concat outpath "/bin/sbcl")))
+                             (message "Acquired Lisp for <depot>.%s at %s" attribute lisp-path)
+                             (sly lisp-path)))
+                          (_ (with-current-buffer errbuf
+                               (error "Failed to build '%s':\n%s" attribute (buffer-string)))))
+                      (kill-buffer outbuf)
+                      (kill-buffer errbuf))))))
+
 (provide 'nix-util)
diff --git a/tools/emacs/config/functions.el b/tools/emacs/config/functions.el
index 2b97130068..1bec8ecd98 100644
--- a/tools/emacs/config/functions.el
+++ b/tools/emacs/config/functions.el
@@ -203,7 +203,7 @@
   (save-excursion
     (move-end-of-line nil)
     (insert (format " %s TODO(%s): %s"
-                    comment-start
+                    (s-trim-right comment-start)
                     (if prefix (read-string "Who needs to do this? ")
                       (getenv "USER"))
                     todo))))
@@ -238,4 +238,31 @@
   (if prefix (text-scale-adjust 0)
     (set-face-attribute 'default nil :height (or to 120))))
 
+(defun notmuch-depot-apply-patch ()
+  "Apply the currently opened notmuch message as a patch on the
+  depot."
+
+  (interactive)
+  ;; The implementation works by letting notmuch render a raw message
+  ;; and capturing it by overriding the `view-buffer' function it
+  ;; calls after rendering.
+  ;;
+  ;; The buffer is then passed to `git-am'.
+  (cl-letf (((symbol-function 'view-buffer)
+             (lambda (buffer &optional exit-action) buffer)))
+    (if-let ((raw-buffer (notmuch-show-view-raw-message)))
+        (progn
+          (with-current-buffer raw-buffer
+            (call-shell-region (point-min) (point-max) "git am -C ~/depot")
+            (message "Patch applied!")
+            (kill-buffer))
+          (magit-status "~/depot"))
+      (warn "notmuch failed to render the raw message buffer"))))
+
+(defun scrot-select ()
+  "Take a screenshot based on a mouse-selection and save it to
+  ~/screenshots."
+  (interactive)
+  (shell-command "scrot '$a_%s.png' -s -e 'mv $f ~/screenshots/'"))
+
 (provide 'functions)
diff --git a/tools/emacs/config/init.el b/tools/emacs/config/init.el
index eac109f985..d705274e93 100644
--- a/tools/emacs/config/init.el
+++ b/tools/emacs/config/init.el
@@ -73,7 +73,9 @@
 (use-package multiple-cursors)
 
 (use-package notmuch
-  :bind (:map global-map ("s-g m" . notmuch)) ;; g m -> gmail
+  :bind (:map global-map
+              ("s-g m" . notmuch)
+              ("s-g M" . counsel-notmuch)) ;; g m -> gmail
   :config
   (setq notmuch-search-oldest-first nil))
 
@@ -147,6 +149,7 @@
                           (local-set-key ">" 'self-insert-command)))))
 
 (use-package f)
+(use-package geiser)
 
 (use-package go-mode
   :bind (:map go-mode-map ("C-c C-r" . recompile))
@@ -157,6 +160,12 @@
 
 (use-package haskell-mode)
 
+(use-package ielm
+  :hook ((inferior-emacs-lisp-mode . (lambda ()
+                                       (paredit-mode)
+                                       (rainbow-delimiters-mode-enable)
+                                       (company-mode)))))
+
 (use-package jq-mode
   :config (add-to-list 'auto-mode-alist '("\\.jq\\'" . jq-mode)))
 
@@ -182,6 +191,14 @@
 (use-package nginx-mode)
 (use-package rust-mode)
 
+(use-package sly
+  :hook ((sly-mrepl-mode . (lambda ()
+                             (paredit-mode)
+                             (rainbow-delimiters-mode-enable)
+                             (company-mode))))
+  :config
+  (setq common-lisp-hyperspec-root "file:///home/tazjin/docs/lisp/"))
+
 (use-package telega
   :bind (:map global-map ("s-t" . telega))
   :config (telega-mode-line-mode 1))
diff --git a/tools/emacs/config/look-and-feel.el b/tools/emacs/config/look-and-feel.el
index 98716dde64..5a4d874f6f 100644
--- a/tools/emacs/config/look-and-feel.el
+++ b/tools/emacs/config/look-and-feel.el
@@ -21,10 +21,9 @@
   (mouse-wheel-mode t)
   (blink-cursor-mode -1))
 
-;; Configure editor fonts
-(let ((font (format "Input Mono-%d" 12)))
-  (setq default-frame-alist `((font-backend . "xft")
-                              (font . ,font)))
+;; Configure Emacs fonts.
+(let ((font (format "JetBrains Mono-%d" 12)))
+  (setq default-frame-alist `((font . ,font)))
   (set-frame-font font t t))
 
 ;; Configure telephone-line
@@ -37,7 +36,7 @@
   load, battery levels on all buffers."
 
   (when (bottom-right-window-p)
-      (telephone-line-raw mode-line-misc-info t)))
+    (telephone-line-raw mode-line-misc-info t)))
 
 (defun telephone-line-setup ()
   (telephone-line-defsegment telephone-line-last-window-segment ()
diff --git a/tools/emacs/default.nix b/tools/emacs/default.nix
index ce515928a2..dda7788b43 100644
--- a/tools/emacs/default.nix
+++ b/tools/emacs/default.nix
@@ -20,6 +20,19 @@ let
   emacsBinPath = lib.makeBinPath [ third_party.telega ];
 
   identity = x: x;
+
+  # EXWM straight from GitHub. As of 2020-02-07, XELB in nixpkgs is
+  # already at a recent enough version and does not need to be
+  # overridden.
+  exwmMaster = exwm.overrideAttrs(_: {
+    src = third_party.fetchFromGitHub {
+      owner = "ch11ng";
+      repo = "exwm";
+      rev = "48db94f48bea1137132345abfe8256cfc6219248";
+      sha256 = "0jj12z6m5kvanq19gds3jpvid2mg8w28bbbq9iycl751y2sj4l1r";
+    };
+  });
+
   tazjinsEmacs = pkgfun: (emacsWithPackages(epkgs: pkgfun(
   # Actual ELPA packages (the enlightened!)
   (with epkgs.elpaPackages; [
@@ -32,6 +45,7 @@ let
 
   # MELPA packages:
   (with epkgs.melpaPackages; [
+    ace-link
     browse-kill-ring
     cargo
     clojure-mode
@@ -43,7 +57,7 @@ let
     elixir-mode
     elm-mode
     erlang
-    exwm
+    geiser
     go-mode
     gruber-darker-theme
     haskell-mode
@@ -56,6 +70,7 @@ let
     ivy-prescient
     jq-mode
     kotlin-mode
+    lispy
     lsp-mode
     magit
     markdown-toc
@@ -71,8 +86,8 @@ let
     racket-mode
     rainbow-delimiters
     refine
-    restclient
     request
+    restclient
     sly
     string-edit
     swiper
@@ -102,6 +117,7 @@ let
   # Custom packages
   (with pkgs.tools.emacs-pkgs; [
     carp-mode
+    exwmMaster
     dottime
     nix-util
     term-switcher