this repo has no description
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

doom configs

+461 -2470
+1 -2
README.md
··· 1 - My Emacs configuration which is, right now, [spacemacs (develop)](https://github.com/syl20bnr/spacemacs/tree/develop). 2 - This prohect is used by my [nix-config](https://github.com/kaychaks/nix-config). 1 + Doom Configs
+229
doom-config/config.el
··· 1 + ;;; $DOOMDIR/config.el -*- lexical-binding: t; -*- 2 + 3 + ;; Place your private configuration here! Remember, you do not need to run 'doom 4 + ;; sync' after modifying this file! 5 + 6 + 7 + ;; Some functionality uses this to identify you, e.g. GPG configuration, email 8 + ;; clients, file templates and snippets. 9 + (setq user-full-name "Kaushik Chakraborty" 10 + user-mail-address "kaushik.chakraborty3@cognizant.com") 11 + 12 + ;; Doom exposes five (optional) variables for controlling fonts in Doom. Here 13 + ;; are the three important ones: 14 + ;; 15 + ;; + `doom-font' 16 + ;; + `doom-variable-pitch-font' 17 + ;; + `doom-big-font' -- used for `doom-big-font-mode'; use this for 18 + ;; presentations or streaming. 19 + ;; 20 + ;; They all accept either a font-spec, font string ("Input Mono-12"), or xlfd 21 + ;; font string. You generally only need these two: 22 + (setq doom-font (font-spec :family "JetBrains Mono" :size 16)) 23 + 24 + ;; There are two ways to load a theme. Both assume the theme is installed and 25 + ;; available. You can either set `doom-theme' or manually load a theme with the 26 + ;; `load-theme' function. This is the default: 27 + (setq doom-theme 'doom-one) 28 + 29 + 30 + (add-to-list 'default-frame-alist '(inhibit-double-buffering . t)) 31 + (add-to-list 'default-frame-alist '(fullscreen . maximized)) 32 + 33 + 34 + (setq evil-split-window-below t 35 + evil-vsplit-window-right t) 36 + 37 + 38 + ;; If you use `org' and don't want your org files in the default location below, 39 + ;; change `org-directory'. It must be set before org loads! 40 + 41 + (defun org-todo-age-time (&optional pos) 42 + (let ((stamp (org-entry-get (or pos (point)) "CREATED" t))) 43 + (when stamp 44 + (time-subtract (current-time) 45 + (org-time-string-to-time 46 + (org-entry-get (or pos (point)) "CREATED" t)))))) 47 + (defun org-todo-age (&optional pos) 48 + (let ((days (time-to-number-of-days (org-todo-age-time pos)))) 49 + (cond 50 + ((< days 1) "today") 51 + ((< days 7) (format "%dd" days)) 52 + ((< days 30) (format "%.1fw" (/ days 7.0))) 53 + ((< days 358) (format "%.1fM" (/ days 30.0))) 54 + (t (format "%.1fY" (/ days 365.0)))))) 55 + 56 + (set-popup-rule! "^\\*Org Agenda" :ignore t) 57 + 58 + (after! org 59 + (setq org-directory "~/developer/src/personal/notes" 60 + org-default-notes-file (concat org-directory "/inbox.org") 61 + org-agenda-files (list 62 + org-directory 63 + ) 64 + org-todo-keywords '((sequence 65 + "TODO(t)" 66 + "RECUR(R)" 67 + "PROJECT(P)" 68 + "NOTE(n@)" 69 + "STARTED(s@/!)" 70 + "WAITING(w@)" 71 + "|" 72 + "DONE(d!)" 73 + "SOMEDAY(y!)" 74 + "CANCELLED(c@)" 75 + "DEFERRED(r@)" 76 + )) 77 + 78 + org-todo-keyword-faces (quote ( 79 + ("TODO" :foreground "#00BFFF" :weight bold) 80 + ("RECUR" :foreground "cornflowerblue" :weight bold) 81 + ("NOTE" :foreground "brown" :weight bold) 82 + ("STARTED" :foreground "#FF8247" :weight bold) 83 + ("WAITING" :foreground "#EE6363" :weight bold) 84 + ("DEFERRED" :foreground "#4876FF" :weight bold) 85 + ("SOMEDAY" :foreground "#EEDC82" :weight bold) 86 + ("PROJECT" :foreground "#088e8e" :weight bold) 87 + )) 88 + org-todo-repeat-to-state "TODO" 89 + org-pretty-entities t 90 + org-use-tag-inheritance nil 91 + 92 + org-agenda-ndays 1 93 + org-agenda-show-all-dates t 94 + org-agenda-start-on-weekday nil 95 + org-agenda-tags-column -100 96 + 97 + org-archive-location "TODO-archive::" 98 + org-archive-save-context-info (quote (time category itags))) 99 + 100 + (setq org-capture-templates 101 + '( 102 + ("a" "Add Task" 103 + entry 104 + (file (lambda () (concat org-directory "/inbox.org"))) 105 + "* TODO %?\n:PROPERTIES:\n:ID: %(shell-command-to-string \"uuidgen\"):CREATED: %U\n:END:" :prepend t) 106 + 107 + ("m" "New Micro Blog" 108 + plain 109 + (file (lambda () 110 + (expand-file-name (concat (format-time-string "%Y%m%d%H%M%S") 111 + ".md") 112 + "~/developer/src/personal/blog/micro-posts/"))) 113 + "---\npublished : %<%Y-%m-%d %H:%M:%S%z>\n---\n\n%c%?") 114 + 115 + ("n" "New Note" 116 + entry 117 + (file (lambda() (concat org-directory "/notes.org") )) 118 + "* NOTE %?\n:PROPERTIES:\n:ID: %(shell-command-to-string \"uuidgen\"):CREATED: %U\n:END:" :prepend t) 119 + 120 + ;;; 121 + ;;; FROM DOOM DEFAULT TEMPLATES 122 + ;;; 123 + 124 + ;; Will use {project-root}/{todo,notes,changelog}.org, unless a 125 + ;; {todo,notes,changelog}.org file is found in a parent directory. 126 + ;; Uses the basename from `+org-capture-todo-file', 127 + ;; `+org-capture-changelog-file' and `+org-capture-notes-file'. 128 + ("p" "Templates for projects") 129 + ("pt" "Project-local todo" entry ; {project-root}/todo.org 130 + (file+headline +org-capture-project-todo-file "Inbox") 131 + "* TODO %?\n%i\n%a" :prepend t) 132 + ("pn" "Project-local notes" entry ; {project-root}/notes.org 133 + (file+headline +org-capture-project-notes-file "Inbox") 134 + "* %U %?\n%i\n%a" :prepend t) 135 + ("pc" "Project-local changelog" entry ; {project-root}/changelog.org 136 + (file+headline +org-capture-project-changelog-file "Unreleased") 137 + "* %U %?\n%i\n%a" :prepend t)) 138 + ) 139 + 140 + (setq org-agenda-custom-commands 141 + (quote 142 + ( 143 + ("P" "All Projects" todo-tree "PROJECT" 144 + ((org-agenda-overriding-header "All Projects"))) 145 + ("A" "Priority #A tasks\"" agenda "" 146 + ((org-agenda-span 147 + (quote day)) 148 + (org-agenda-overriding-header "Today's priority #A tasks: ") 149 + (org-agenda-skip-function 150 + (quote 151 + (org-agenda-skip-entry-if 152 + (quote notregexp) 153 + "\\=.*\\[#A\\]"))))) 154 + ("b" "Priority #A and #B tasks" agenda "" 155 + ((org-agenda-span 156 + (quote day)) 157 + (org-agenda-overriding-header "Today's priority #A and #B tasks: ") 158 + (org-agenda-skip-function 159 + (quote 160 + (org-agenda-skip-entry-if 161 + (quote regexp) 162 + "\\=.*\\[#C\\]"))))) 163 + ("u" "Unscheduled tasks" tags "TODO<>\"\"&TODO<>{DONE\\|CANCELLED\\|DEFERRED\\|SOMEDAY\\|PROJECT\\|NOTE}" 164 + ((org-agenda-overriding-header "Unscheduled tasks: ") 165 + (org-agenda-skip-function 166 + (quote 167 + (org-agenda-skip-entry-if 168 + (quote scheduled) 169 + (quote deadline) 170 + (quote timestamp) 171 + ))) 172 + (org-agenda-sorting-strategy 173 + (quote 174 + (user-defined-up))) 175 + (org-agenda-prefix-format "%-11c%5(org-todo-age) "))) 176 + ("U" "Deferred tasks" tags "TODO=\"DEFERRED\"" 177 + ((org-agenda-overriding-header "Deferred tasks:") 178 + (org-agenda-sorting-strategy 179 + (quote 180 + (user-defined-up))) 181 + (org-agenda-prefix-format "%-11c%5(org-todo-age) "))) 182 + ("Y" "Someday tasks" tags "TODO=\"SOMEDAY\"" 183 + ((org-agenda-overriding-header "Someday tasks:") 184 + (org-agenda-sorting-strategy 185 + (quote 186 + (user-defined-up))) 187 + (org-agenda-prefix-format "%-11c%5(org-todo-age) "))) 188 + ("S" "Scheduled tasks" tags "TODO<>\"\"&TODO<>{DONE\\|CANCELLED\\|NOTE\\|PROJECT\\|DEFERRED}&STYLE<>\"habit\"" 189 + ((org-agenda-overriding-header "Scheduled tasks: ") 190 + (org-agenda-skip-function 191 + (quote 192 + (org-agenda-skip-entry-if 193 + (quote notscheduled)))) 194 + (org-agenda-sorting-strategy 195 + (quote 196 + (category-up))))) 197 + )))) 198 + 199 + 200 + 201 + ;; This determines the style of line numbers in effect. If set to `nil', line 202 + ;; numbers are disabled. For relative line numbers, set this to `relative'. 203 + (setq display-line-numbers-type t) 204 + 205 + ;; projectile 206 + (setq projectile-project-search-path '("~/developer/src/personal/" 207 + "~/developer/src/work/")) 208 + 209 + ;; Haskell 210 + (setq haskell-process-type 'cabal-new-repl 211 + lsp-haskell-process-path-hie "ghcide" 212 + lsp-haskell-process-args-hie '()) 213 + 214 + ;; Here are some additional functions/macros that could help you configure Doom: 215 + ;; 216 + ;; - `load!' for loading external *.el files relative to this one 217 + ;; - `use-package' for configuring packages 218 + ;; - `after!' for running code after a package has loaded 219 + ;; - `add-load-path!' for adding directories to the `load-path', relative to 220 + ;; this file. Emacs searches the `load-path' when you load packages with 221 + ;; `require' or `use-package'. 222 + ;; - `map!' for binding new keys 223 + ;; 224 + ;; To get information about any of these functions/macros, move the cursor over 225 + ;; the highlighted symbol at press 'K' (non-evil users must press 'C-c g k'). 226 + ;; This will open documentation for it, including demos of how they are used. 227 + ;; 228 + ;; You can also try 'gd' (or 'C-c g d') to jump to their definition and see how 229 + ;; they are implemented.
+180
doom-config/init.el
··· 1 + ;;; init.el -*- lexical-binding: t; -*- 2 + 3 + ;; This file controls what Doom modules are enabled and what order they load in. 4 + ;; Remember to run 'doom sync' after modifying it! 5 + 6 + ;; NOTE Press 'SPC h d h' (or 'C-h d h' for non-vim users) to access Doom's 7 + ;; documentation. There you'll find information about all of Doom's modules 8 + ;; and what flags they support. 9 + 10 + ;; NOTE Move your cursor over a module's name (or its flags) and press 'K' (or 11 + ;; 'C-c g k' for non-vim users) to view its documentation. This works on 12 + ;; flags as well (those symbols that start with a plus). 13 + ;; 14 + ;; Alternatively, press 'gd' (or 'C-c g d') on a module to browse its 15 + ;; directory (for easy access to its source code). 16 + 17 + (doom! :input 18 + ;;chinese 19 + ;;japanese 20 + 21 + :completion 22 + (company +childframe) ; the ultimate code completion backend 23 + ;;helm ; the *other* search engine for love and life 24 + ;;ido ; the other *other* search engine... 25 + (ivy +fuzzy +icons) ; a search engine for love and life 26 + 27 + :ui 28 + ;;deft ; notational velocity for Emacs 29 + doom ; what makes DOOM look the way it does 30 + doom-dashboard ; a nifty splash screen for Emacs 31 + doom-quit ; DOOM quit-message prompts when you quit Emacs 32 + ;;fill-column ; a `fill-column' indicator 33 + hl-todo ; highlight TODO/FIXME/NOTE/DEPRECATED/HACK/REVIEW 34 + ;;hydra 35 + ;;indent-guides ; highlighted indent columns 36 + modeline ; snazzy, Atom-inspired modeline, plus API 37 + nav-flash ; blink the current line after jumping 38 + ;;neotree ; a project drawer, like NERDTree for vim 39 + ophints ; highlight the region an operation acts on 40 + (popup +all +defaults) ; tame sudden yet inevitable temporary windows 41 + ;;pretty-code ; replace bits of code with pretty symbols 42 + ;;tabs ; an tab bar for Emacs 43 + treemacs ; a project drawer, like neotree but cooler 44 + ;;unicode ; extended unicode support for various languages 45 + vc-gutter ; vcs diff in the fringe 46 + vi-tilde-fringe ; fringe tildes to mark beyond EOB 47 + window-select ; visually switch windows 48 + workspaces ; tab emulation, persistence & separate workspaces 49 + ;;zen ; distraction-free coding or writing 50 + 51 + :editor 52 + (evil +everywhere); come to the dark side, we have cookies 53 + file-templates ; auto-snippets for empty files 54 + fold ; (nigh) universal code folding 55 + format ; automated prettiness 56 + ;;god ; run Emacs commands without modifier keys 57 + ;;lispy ; vim for lisp, for people who don't like vim 58 + ;;multiple-cursors ; editing in many places at once 59 + ;;objed ; text object editing for the innocent 60 + ;;parinfer ; turn lisp into python, sort of 61 + ;;rotate-text ; cycle region at point between text candidates 62 + snippets ; my elves. They type so I don't have to 63 + word-wrap ; soft wrapping with language-aware indent 64 + 65 + :emacs 66 + dired ; making dired pretty [functional] 67 + electric ; smarter, keyword-based electric-indent 68 + ;;ibuffer ; interactive buffer management 69 + vc ; version-control and Emacs, sitting in a tree 70 + 71 + :term 72 + eshell ; a consistent, cross-platform shell (WIP) 73 + ;;shell ; a terminal REPL for Emacs 74 + term ; terminals in Emacs 75 + ;;vterm ; another terminals in Emacs 76 + 77 + :checkers 78 + syntax ; tasing you for every semicolon you forget 79 + spell ; tasing you for misspelling mispelling 80 + grammar ; tasing grammar mistake every you make 81 + 82 + :tools 83 + ;;ansible 84 + ;;debugger ; FIXME stepping through code, to help you add bugs 85 + direnv 86 + docker 87 + ;;editorconfig ; let someone else argue about tabs vs spaces 88 + ;;ein ; tame Jupyter notebooks with emacs 89 + (eval +overlay) ; run code, run (also, repls) 90 + gist ; interacting with github gists 91 + (lookup +dictionary +offline) ; navigate your code and its documentation 92 + (lsp +peek) 93 + macos ; MacOS-specific commands 94 + magit ; a git porcelain for Emacs 95 + make ; run make tasks from Emacs 96 + ;;pass ; password manager for nerds 97 + ;;pdf ; pdf enhancements 98 + ;;prodigy ; FIXME managing external services & code builders 99 + ;;rgb ; creating color strings 100 + ;;terraform ; infrastructure as code 101 + ;;tmux ; an API for interacting with tmux 102 + ;;upload ; map local to remote projects via ssh/ftp 103 + 104 + :lang 105 + ;;agda ; types of types of types of types... 106 + ;;assembly ; assembly for fun or debugging 107 + cc ; C/C++/Obj-C madness 108 + ;;clojure ; java with a lisp 109 + ;;common-lisp ; if you've seen one lisp, you've seen them all 110 + ;;coq ; proofs-as-programs 111 + ;;crystal ; ruby at the speed of c 112 + ;;csharp ; unity, .NET, and mono shenanigans 113 + data ; config/data formats 114 + ;;elixir ; erlang done right 115 + ;;elm ; care for a cup of TEA? 116 + emacs-lisp ; drown in parentheses 117 + ;;erlang ; an elegant language for a more civilized age 118 + ;;ess ; emacs speaks statistics 119 + ;;faust ; dsp, but you get to keep your soul 120 + ;;fsharp ; ML stands for Microsoft's Language 121 + ;;fstar ; (dependent) types and (monadic) effects and Z3 122 + ;;go ; the hipster dialect 123 + (haskell +lsp) ; a language that's lazier than I am 124 + ;;hy ; readability of scheme w/ speed of python 125 + ;;idris ; 126 + ;;(java +meghanada) ; the poster child for carpal tunnel syndrome 127 + javascript ; all(hope(abandon(ye(who(enter(here)))))) 128 + ;;julia ; a better, faster MATLAB 129 + ;;kotlin ; a better, slicker Java(Script) 130 + latex ; writing papers in Emacs has never been so fun 131 + lean 132 + ;;factor 133 + ;;ledger ; an accounting system in Emacs 134 + ;;lua ; one-based indices? one-based indices 135 + (markdown +grip) ; writing docs for people to ignore 136 + ;;nim ; python + lisp at the speed of c 137 + ;;nix ; I hereby declare "nix geht mehr!" 138 + ;;ocaml ; an objective camel 139 + (org ; organize your plain life in plain text 140 + +dragndrop ; drag & drop files/images into org buffers 141 + +habit 142 + +protocol 143 + ;;+hugo ; use Emacs for hugo blogging 144 + ;;+jupyter ; ipython/jupyter support for babel 145 + +pandoc ; export-with-pandoc support 146 + ;;+pomodoro ; be fruitful with the tomato technique 147 + +present) ; using org-mode for presentations 148 + ;;perl ; write code no one else can comprehend 149 + ;;php ; perl's insecure younger brother 150 + plantuml ; diagrams for confusing people more 151 + ;;purescript ; javascript, but functional 152 + ;;(python +lsp) ; beautiful is better than ugly 153 + ;;qt ; the 'cutest' gui framework ever 154 + ;;racket ; a DSL for DSLs 155 + ;;rest ; Emacs as a REST client 156 + ;;rst ; ReST in peace 157 + ;;(ruby +rails) ; 1.step {|i| p "Ruby is #{i.even? ? 'love' : 'life'}"} 158 + ;;rust ; Fe2O3.unwrap().unwrap().unwrap().unwrap() 159 + ;;scala ; java, but good 160 + ;;scheme ; a fully conniving family of lisps 161 + (sh +lsp) ; she sells {ba,z,fi}sh shells on the C xor 162 + ;;solidity ; do you need a blockchain? No. 163 + ;;swift ; who asked for emoji variables? 164 + ;;terra ; Earth and Moon in alignment for performance. 165 + web ; the tubes 166 + 167 + :email 168 + ;;(mu4e +gmail) 169 + ;;notmuch 170 + ;;(wanderlust +gmail) 171 + 172 + :app 173 + ;;calendar 174 + ;;irc ; how neckbeards socialize 175 + ;;(rss +org) ; emacs as an RSS reader 176 + ;;twitter ; twitter client https://twitter.com/vnought 177 + 178 + :config 179 + literate 180 + (default +bindings +smartparens))
+51
doom-config/packages.el
··· 1 + ;; -*- no-byte-compile: t; -*- 2 + ;;; $DOOMDIR/packages.el 3 + 4 + ;; To install a package with Doom you must declare them here, run 'doom sync' on 5 + ;; the command line, then restart Emacs for the changes to take effect. 6 + ;; Alternatively, use M-x doom/reload. 7 + ;; 8 + ;; WARNING: Disabling core packages listed in ~/.emacs.d/core/packages.el may 9 + ;; have nasty side-effects and is not recommended. 10 + 11 + 12 + ;; All of Doom's packages are pinned to a specific commit, and updated from 13 + ;; release to release. To un-pin all packages and live on the edge, do: 14 + ;(unpin! t) 15 + 16 + ;; ...but to unpin a single package: 17 + ;(unpin! pinned-package) 18 + ;; Use it to unpin multiple packages 19 + ;(unpin! pinned-package another-pinned-package) 20 + 21 + 22 + ;; To install SOME-PACKAGE from MELPA, ELPA or emacsmirror: 23 + ;(package! some-package) 24 + 25 + ;; To install a package directly from a particular repo, you'll need to specify 26 + ;; a `:recipe'. You'll find documentation on what `:recipe' accepts here: 27 + ;; https://github.com/raxod502/straight.el#the-recipe-format 28 + ;(package! another-package 29 + ; :recipe (:host github :repo "username/repo")) 30 + 31 + ;; If the package you are trying to install does not contain a PACKAGENAME.el 32 + ;; file, or is located in a subdirectory of the repo, you'll need to specify 33 + ;; `:files' in the `:recipe': 34 + ;(package! this-package 35 + ; :recipe (:host github :repo "username/repo" 36 + ; :files ("some-file.el" "src/lisp/*.el"))) 37 + 38 + ;; If you'd like to disable a package included with Doom, for whatever reason, 39 + ;; you can do so here with the `:disable' property: 40 + ;(package! builtin-package :disable t) 41 + 42 + ;; You can override the recipe of a built in package without having to specify 43 + ;; all the properties for `:recipe'. These will inherit the rest of its recipe 44 + ;; from Doom or MELPA/ELPA/Emacsmirror: 45 + ;(package! builtin-package :recipe (:nonrecursive t)) 46 + ;(package! builtin-package-2 :recipe (:repo "myfork/package")) 47 + 48 + ;; Specify a `:branch' to install a package from a particular branch or tag. 49 + ;; This is required for some packages whose default branch isn't 'master' (which 50 + ;; our package manager can't deal with; see raxod502/straight.el#279) 51 + ;(package! builtin-package :recipe (:branch "develop"))
-21
spacemacs-private/README.md
··· 1 - # Private directory 2 - 3 - The content of this directory is ignored by Git. This is the default place 4 - where to store your private configuration layers. 5 - 6 - To create a new configuration layer: 7 - 8 - SPC SPC configuration-layer/create-layer RET 9 - 10 - Then enter the name of your configuration in the prompt. 11 - 12 - A directory named after the created configuration layer will be created here 13 - along with template files within it (packages.el and extensions.el, more info 14 - on the meaning of those files can be found in the [documentation][conf_layers]). 15 - 16 - Each created file has further guidance written in them. 17 - 18 - Once the configuration is done, restart Emacs to load, install and configure 19 - your layer. 20 - 21 - [conf_layers]: https://github.com/syl20bnr/spacemacs/blob/master/doc/DOCUMENTATION.org#extensions-and-packages
-65
spacemacs-private/custom-direnv/packages.el
··· 1 - ;;; packages.el --- custom-direnv layer packages file for Spacemacs. 2 - ;; 3 - ;; Copyright (c) 2012-2018 Sylvain Benner & Contributors 4 - ;; 5 - ;; Author: Kaushik Chakraborty <kaushik@AMB00472.local> 6 - ;; URL: https://github.com/syl20bnr/spacemacs 7 - ;; 8 - ;; This file is not part of GNU Emacs. 9 - ;; 10 - ;;; License: GPLv3 11 - 12 - ;;; Commentary: 13 - 14 - ;; See the Spacemacs documentation and FAQs for instructions on how to implement 15 - ;; a new layer: 16 - ;; 17 - ;; SPC h SPC layers RET 18 - ;; 19 - ;; 20 - ;; Briefly, each package to be installed or configured by this layer should be 21 - ;; added to `custom-direnv-packages'. Then, for each package PACKAGE: 22 - ;; 23 - ;; - If PACKAGE is not referenced by any other Spacemacs layer, define a 24 - ;; function `custom-direnv/init-PACKAGE' to load and initialize the package. 25 - 26 - ;; - Otherwise, PACKAGE is already referenced by another Spacemacs layer, so 27 - ;; define the functions `custom-direnv/pre-init-PACKAGE' and/or 28 - ;; `custom-direnv/post-init-PACKAGE' to customize the package as it is loaded. 29 - 30 - ;;; Code: 31 - 32 - (defconst custom-direnv-packages 33 - '(direnv 34 - flycheck)) 35 - 36 - (defun custom-direnv/patch-direnv-environment (&rest _args) 37 - (let ((emacs-bin (directory-file-name 38 - (file-name-directory 39 - (executable-find "emacsclient"))))) 40 - (setenv "PATH" (concat emacs-bin ":" (getenv "PATH"))) 41 - (setq exec-path (cons (file-name-as-directory emacs-bin) 42 - exec-path)))) 43 - 44 - (defun custom-direnv/init-direnv () 45 - (use-package direnv 46 - :defer t 47 - :config 48 - (advice-add 'direnv-update-directory-environment 49 - :after #'custom-direnv/patch-direnv-environment) 50 - )) 51 - 52 - (defun custom-direnv/post-init-flycheck () 53 - '(setq flycheck-executable-find 54 - (lambda (cmd) 55 - (add-hook 'post-command-hook #'direnv--maybe-update-environment) 56 - (direnv-update-environment default-directory) 57 - (executable-find cmd)))) 58 - 59 - (defun custom-direnv/post-init-direnv () 60 - (direnv-mode) 61 - (add-hook 'git-commit-mode-hook #'custom-direnv/patch-direnv-environment) 62 - ) 63 - 64 - 65 - ;;; packages.el ends here
-46
spacemacs-private/custom-lean/README.org
··· 1 - #+TITLE: custom-lean layer 2 - 3 - # The maximum height of the logo should be 200 pixels. 4 - [[img/custom-lean.png]] 5 - 6 - # TOC links should be GitHub style anchors. 7 - * Table of Contents :TOC_4_gh:noexport: 8 - - [[#description][Description]] 9 - - [[#features][Features:]] 10 - - [[#install][Install]] 11 - - [[#key-bindings][Key bindings]] 12 - 13 - * Description 14 - This layer adds support for [[https://leanprover.github.io/][Lean]] programming language. 15 - 16 - ** Features: 17 - - syntax highlighting 18 - - autocompletion via ~company-lean~ 19 - - searcheable list of options via ~helm-lean~ 20 - 21 - * Install 22 - To use this configuration layer, add it to your =~/.spacemacs=. You will need to 23 - add =custom-lean= to the existing =dotspacemacs-configuration-layers= list in this 24 - file. 25 - 26 - * Key bindings 27 - 28 - from [[https://github.com/leanprover/lean-mode][lean-mode]] 29 - 30 - | Key | Function | 31 - |--------------------|---------------------------------------------------------------------------------| 32 - | <kbd>M-.</kbd> | jump to definition in source file (`lean-find-definition`) | 33 - | <kbd>M-,</kbd> | jump back to position before <kbd>M-.</kbd> (`xref-pop-marker-stack`) | 34 - | <kbd>C-c C-k</kbd> | shows the keystroke needed to input the symbol under the cursor | 35 - | <kbd>C-c C-x</kbd> | execute lean in stand-alone mode (`lean-std-exe`) | 36 - | <kbd>C-c SPC</kbd> | run a command on the hole at point (`lean-hole`) | 37 - | <kbd>C-c C-d</kbd> | show a searchable list of definitions (`helm-lean-definitions`) | 38 - | <kbd>C-c C-g</kbd> | toggle showing current tactic proof goal (`lean-toggle-show-goal`) | 39 - | <kbd>C-c C-n</kbd> | toggle showing next error in dedicated buffer (`lean-toggle-next-error`) | 40 - | <kbd>C-c C-b</kbd> | toggle showing output in inline boxes (`lean-message-boxes-toggle`) | 41 - | <kbd>C-c C-r</kbd> | restart the lean server (`lean-server-restart`) | 42 - | <kbd>C-c C-s</kbd> | switch to a different Lean version via [elan](https://github.com/Kha/elan) (`lean-server-switch-version`) | 43 - | <kbd>C-c ! n</kbd> | flycheck: go to next error | 44 - | <kbd>C-c ! p</kbd> | flycheck: go to previous error | 45 - | <kbd>C-c ! l</kbd> | flycheck: show list of errors | 46 -
-59
spacemacs-private/custom-lean/packages.el
··· 1 - ;;; packages.el --- custom-lean layer packages file for Spacemacs. 2 - ;; 3 - ;; Copyright (c) 2012-2018 Sylvain Benner & Contributors 4 - ;; 5 - ;; Author: Kaushik Chakraborty <git@kaushikc.org> 6 - ;; URL: https://kaushikc.org 7 - ;; 8 - ;; This file is not part of GNU Emacs. 9 - ;; 10 - ;;; License: GPLv3 11 - 12 - ;;; Commentary: 13 - 14 - ;; See the Spacemacs documentation and FAQs for instructions on how to implement 15 - ;; a new layer: 16 - ;; 17 - ;; SPC h SPC layers RET 18 - ;; 19 - ;; 20 - ;; Briefly, each package to be installed or configured by this layer should be 21 - ;; added to `custom-lean-packages'. Then, for each package PACKAGE: 22 - ;; 23 - ;; - If PACKAGE is not referenced by any other Spacemacs layer, define a 24 - ;; function `custom-lean/init-PACKAGE' to load and initialize the package. 25 - 26 - ;; - Otherwise, PACKAGE is already referenced by another Spacemacs layer, so 27 - ;; define the functions `custom-lean/pre-init-PACKAGE' and/or 28 - ;; `custom-lean/post-init-PACKAGE' to customize the package as it is loaded. 29 - 30 - ;;; Code: 31 - 32 - (defconst custom-lean-packages 33 - '( 34 - lean-mode 35 - (company-lean :requires company) 36 - (helm-lean :requires helm) 37 - )) 38 - 39 - (defun custom-lean/init-lean-mode () 40 - (use-package lean-mode 41 - :defer t)) 42 - 43 - (defun custom-lean/post-init-company ()) 44 - 45 - (defun custom-lean/init-company-lean () 46 - (use-package company-lean 47 - :defer t 48 - :init 49 - (spacemacs|add-company-backends 50 - :backends company-lean 51 - :modes lean-mode) 52 - )) 53 - 54 - (defun custom-lean/init-helm-lean () 55 - (use-package helm-lean 56 - :defer t)) 57 - 58 - 59 - ;;; packages.el ends here
-53
spacemacs-private/custom-super-save/packages.el
··· 1 - ;;; packages.el --- custom-super-save layer packages file for Spacemacs. 2 - ;; 3 - ;; Copyright (c) 2012-2018 Sylvain Benner & Contributors 4 - ;; 5 - ;; Author: Kaushik Chakraborty <git@kaushikc.org> 6 - ;; URL: https://kaushikc.org 7 - ;; 8 - ;; This file is not part of GNU Emacs. 9 - ;; 10 - ;;; License: GPLv3 11 - 12 - ;;; Commentary: 13 - 14 - ;; See the Spacemacs documentation and FAQs for instructions on how to implement 15 - ;; a new layer: 16 - ;; 17 - ;; SPC h SPC layers RET 18 - ;; 19 - ;; 20 - ;; Briefly, each package to be installed or configured by this layer should be 21 - ;; added to `custom-super-save-packages'. Then, for each package PACKAGE: 22 - ;; 23 - ;; - If PACKAGE is not referenced by any other Spacemacs layer, define a 24 - ;; function `custom-super-save/init-PACKAGE' to load and initialize the package. 25 - 26 - ;; - Otherwise, PACKAGE is already referenced by another Spacemacs layer, so 27 - ;; define the functions `custom-super-save/pre-init-PACKAGE' and/or 28 - ;; `custom-super-save/post-init-PACKAGE' to customize the package as it is loaded. 29 - 30 - ;;; Code: 31 - 32 - (defconst custom-super-save-packages 33 - '(super-save) 34 - ) 35 - (defun custom-super-save/init-super-save () 36 - (use-package super-save 37 - :defer t 38 - :config 39 - (super-save-mode +1) 40 - (setq auto-save-default t) 41 - (setq super-save-remote-files nil) 42 - (setq super-save-auto-save-when-idle t) 43 - )) 44 - 45 - (defun custom-super-save/post-init-super-save () 46 - (super-save-mode +1) 47 - (setq auto-save-default t) 48 - (setq super-save-remote-files nil) 49 - (setq super-save-auto-save-when-idle t) 50 - ) 51 - 52 - 53 - ;;; packages.el ends here
-6
spacemacs-private/local/README.md
··· 1 - # Private directory for local packages 2 - 3 - The content of this directory is ignored by Git. 4 - 5 - This is the place to store the local packages that you define in 6 - the `dotspacemacs-additional-packages` variable of your dotfile.
-2123
spacemacs-private/local/beeminder.el
··· 1 - ;;; beeminder.el --- Emacs client for Beeminder -*- lexical-binding: t; -*- 2 - 3 - ;; Copyright (C) 2015 Marcin 'mbork' Borkowski 4 - 5 - ;; Author: Marcin Borkowski <mbork@mbork.pl> 6 - ;; Keywords: calendar 7 - ;; Package-Requires: ((request "0.2.0")) 8 - 9 - ;; This file is NOT part of GNU Emacs. 10 - 11 - ;; beeminder.el is free software: you can redistribute it and/or modify 12 - ;; it under the terms of the GNU General Public License as published by 13 - ;; the Free Software Foundation, either version 3 of the License, or 14 - ;; (at your option) any later version. 15 - 16 - ;; beeminder.el is distributed in the hope that it will be useful, 17 - ;; but WITHOUT ANY WARRANTY; without even the implied warranty of 18 - ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 19 - ;; GNU General Public License for more details. 20 - 21 - ;; You should have received a copy of the GNU General Public License 22 - ;; along with beeminder.el. 23 - ;; If not, see <http://www.gnu.org/licenses/>. 24 - 25 - ;;; Commentary: 26 - ;; beeminder.el is an Emacs client for the Beeminder service. 27 - ;; Beeminder (taglined "a reminder with a sting") is 28 - ;; a motivation/quantified self tool based on behavioral economics 29 - ;; principles. 30 - 31 - (require 'json) 32 - (require 'request) 33 - (if (>= emacs-major-version 24) 34 - (progn 35 - (require 'cl-lib) 36 - (require 'anaphora) 37 - (defalias 'increasingp '<)) 38 - (require 'cl) 39 - (defalias 'cl-reduce 'reduce) 40 - (defalias 'cl-find 'find) 41 - (defalias 'cl-incf 'incf) 42 - (defalias 'cl-decf 'decf) 43 - (defalias 'cl-case 'case) 44 - (defalias 'cl-delete 'delete*) 45 - (defmacro setq-local (var val) 46 - "This is taken from subr.el." 47 - `(set (make-local-variable ',var) ,val)) 48 - (defmacro aif (cond then &rest else) 49 - `(let ((it ,cond)) 50 - (if it ,then ,@else))) 51 - (defmacro awhen (cond &rest body) 52 - `(aif ,cond 53 - (progn ,@body))) 54 - (defun increasingp (&rest args) 55 - "Return t if ARGS are in increasing order." 56 - (if (cdr args) 57 - (and (< (car args) (cadr args)) 58 - (apply #' increasingp (cdr args))) 59 - t))) 60 - (require 'ewoc) 61 - 62 - ;;; Code: 63 - 64 - 65 - ;; Utilities 66 - (defun trim-leading-whitespace (string) 67 - "Trim tabs and spaces from the beginning of STRING." 68 - (when (string-match "^[ \t]*" string) 69 - (replace-match "" nil nil string))) 70 - 71 - (defun beeminder-alist-get (key alist) 72 - "Return the value associated to KEY in ALIST. 73 - This function is needed for Emacsen older than v25." 74 - (cdr (assoc key alist))) 75 - 76 - (defun beeminder-set-alist-value (key alist value) 77 - "Set the value corresponding to KEY in ALIST to VALUE. 78 - Note: ALIST should be a symbol. This is morally equivalent to 79 - `(setf (alist-get key (symbol-value alist)) value)', 80 - but works in older Emacsen." 81 - (let ((pair (assoc key (symbol-value alist)))) 82 - (if pair 83 - (setcdr pair value) 84 - (set alist (acons key value (symbol-value alist)))))) 85 - 86 - (defun beeminder-inc-alist-value (key alist increment) 87 - "Increment the value corresponding to KEY in ALIST by INCREMENT. 88 - Throw an error if KEY is not in ALIST." 89 - (let ((pair (assoc key alist))) 90 - (if pair 91 - (incf (cdr pair) increment) 92 - (error "Nothing to increment")))) 93 - 94 - (defun beeminder-to-list (sequence) 95 - "Turn SEQUENCE into a list." 96 - (append sequence nil)) 97 - 98 - 99 - ;; Settings 100 - 101 - (defgroup beeminder nil 102 - "An Emacs client for Beeminder." 103 - :group 'applications) 104 - 105 - (defcustom beeminder-username "" 106 - "User name for the Beeminder account." 107 - :type 'string 108 - :group 'beeminder) 109 - 110 - (defcustom beeminder-auth-token "" 111 - "Authentication token for Beeminder. 112 - You can retrieve it from the URL 113 - `https://www.beeminder.com/api/v1/auth_token.json'." 114 - :type 'string 115 - :group 'beeminder) 116 - 117 - (defcustom beeminder-api-url "https://www.beeminder.com/api/v1/users/" 118 - "The URL for making API calls." 119 - :type 'string 120 - :group 'beeminder) 121 - 122 - (defvar beeminder-goals nil 123 - "The list of sexps representing goals. 124 - Updated by `beeminder-get-goals'.") 125 - 126 - (defcustom beeminder-default-timeout 30 127 - "Default timeout for HTTP requests sent to beeminder, in seconds." 128 - :type 'number 129 - :group 'beeminder) 130 - 131 - 132 - ;; Beeminder mode 133 - 134 - (define-derived-mode beeminder-mode special-mode "Beeminder" 135 - "A major mode for a buffer with Beeminder goal list.") 136 - 137 - (defun next-goal (count) 138 - "Move COUNT goals forward in the Beeminder buffer." 139 - (interactive "p") 140 - (ewoc-goto-next beeminder-goals-ewoc 141 - (if (beeminder-before-first-goal-p) 142 - (1- count) 143 - count))) 144 - 145 - (defun previous-goal (count) 146 - "Move COUNT goals back in the Beeminder buffer. 147 - If on the first goal, move to (point-min)." 148 - ;; If point is before the place `previous-goal' would move it, move 149 - ;; to (point-min). This jumps to the beginning from any place 150 - ;; before the first node, but won't work when point is on Nth goal 151 - ;; and `count' is greater than N. This doesn't seem a big deal, so 152 - ;; let's just hope nobody notices that. 153 - (interactive "p") 154 - (when (<= (point) 155 - (progn (ewoc-goto-prev beeminder-goals-ewoc count) 156 - (point))) 157 - (goto-char (point-min)))) 158 - 159 - (define-key beeminder-mode-map (kbd "n") #'next-goal) 160 - (define-key beeminder-mode-map (kbd "p") #'previous-goal) 161 - 162 - 163 - ;; API interface 164 - 165 - (defun beeminder-create-api-url (string) 166 - "Prepend Beeminder site address and the username to STRING. 167 - STRING should begin with a slash." 168 - (concat beeminder-api-url beeminder-username string)) 169 - 170 - (defun beeminder-request-get (request &optional params success-fun error-fun timeout) 171 - "Send a GET REQUEST to beeminder.com, with TIMEOUT. 172 - Add the necessary details (including the username and the auth 173 - token)." 174 - (request (beeminder-create-api-url request) 175 - :parser #'json-read 176 - :params (append params (list (cons "auth_token" beeminder-auth-token))) 177 - :success success-fun 178 - :error error-fun 179 - :timeout (or timeout beeminder-default-timeout))) 180 - 181 - (defun beeminder-request-post (request data success-fun error-fun &optional timeout) 182 - "Send a POST REQUEST with given DATA and TIMEOUT to beeminder.com. 183 - Add the username and the auth token." 184 - (request (beeminder-create-api-url request) 185 - :type "POST" 186 - :data (append data 187 - (list (cons "auth_token" beeminder-auth-token))) 188 - :parser #'json-read 189 - :success success-fun 190 - :error error-fun 191 - :timeout (or timeout beeminder-default-timeout))) 192 - 193 - (defun beeminder-request-delete (request success-fun error-fun &optional timeout) 194 - "Send a DELETE request to beeminder.com, with TIMEOUT. 195 - Add the necessary details (username and the auth token)." 196 - (request-response-data 197 - (request (concat (beeminder-create-api-url request)) 198 - :params (list (cons "auth_token" beeminder-auth-token)) 199 - :type "DELETE" 200 - :parser #'json-read 201 - :success success-fun 202 - :error error-fun 203 - :timeout (or timeout beeminder-default-timeout)))) 204 - 205 - (defun beeminder-request-put (request data success-fun error-fun &optional timeout) 206 - "Send a PUT request to beeminder.com, with TIMEOUT. 207 - Add the necessary details (username and the auth token)." 208 - (request-response-data 209 - (request (beeminder-create-api-url request) 210 - :type "PUT" 211 - :data (append data 212 - (list (cons "auth_token" beeminder-auth-token))) 213 - :parser #'json-read 214 - :success success-fun 215 - :error error-fun 216 - :timeout (or timeout beeminder-default-timeout)))) 217 - 218 - 219 - ;; API calls 220 - 221 - (defun last-goal-midnight (goal-deadline now) ; TODO: maybe refactor using beeminder-determine-date! 222 - "Return the last \"midnight\" for GOAL-DEADLINE, counting from NOW. 223 - GOAL-DEADLINE is an offset from real midnight in seconds, NOW is 224 - a time value." 225 - (let* ((now-decoded (decode-time now)) 226 - (last-real-midnight (encode-time 0 0 0 227 - (cadddr now-decoded) 228 - (nth 4 now-decoded) 229 - (nth 5 now-decoded) 230 - (nth 8 now-decoded))) 231 - (last-midnight (+ goal-deadline 232 - (if (< goal-deadline 0) (* 24 60 60) 0) 233 - (time-to-seconds last-real-midnight)))) 234 - (if (> last-midnight 235 - (time-to-seconds now)) 236 - (- last-midnight (* 24 60 60)) 237 - last-midnight))) 238 - 239 - (defun last-user-midnight (now) ; TODO: maybe refactor using beeminder-determine-date! 240 - "Return the last \"midnight\" counting from NOW, as Unix timestamp. 241 - Take `beeminder-when-the-day-ends' into consideration." 242 - (let* ((now-decoded (decode-time now)) 243 - (last-real-midnight (encode-time 0 0 0 244 - (cadddr now-decoded) 245 - (nth 4 now-decoded) 246 - (nth 5 now-decoded) 247 - (nth 8 now-decoded))) 248 - (last-midnight (+ (time-to-seconds last-real-midnight) 249 - beeminder-when-the-day-ends))) 250 - (if (> last-midnight (time-to-seconds now)) 251 - (- last-midnight (* 24 60 60)) 252 - last-midnight))) 253 - 254 - (defcustom beeminder-history-length 7 255 - "Number of days from which to load datapoints.") 256 - 257 - (defun beeminder-sum-today-value (datapoints start-time stop-time) 258 - "Sum the value for DATAPOINTS between START-TIME and STOP-TIME." 259 - (cl-reduce #'+ 260 - (mapcar (lambda (datapoint) 261 - (if (< start-time 262 - (cdr (assoc 'timestamp datapoint)) 263 - stop-time) 264 - (cdr (assoc 'value datapoint)) 265 - 0)) 266 - datapoints))) 267 - 268 - (defun beeminder-get-goals () 269 - "Get all the user's Beeminder goals and put them in the 270 - `beeminder-goals' variable." 271 - (beeminder-log "fetching goals...") 272 - (beeminder-request-get 273 - "/goals.json" 274 - () 275 - (cl-function (lambda (&key data &allow-other-keys) 276 - (beeminder-log "fetching goals.......") 277 - (let ((goals (beeminder-to-list data)) 278 - (now (beeminder-current-time))) 279 - (beeminder-request-get 280 - ".json" 281 - (list 282 - (cons "diff_since" 283 - (number-to-string 284 - (- (last-user-midnight now) 285 - (* beeminder-history-length 24 60 60))))) 286 - (cl-function (lambda (&key data &allow-other-keys) 287 - (let* ((datapoints ; extract datapoints alone 288 - (mapcar 289 - (lambda (goal) ; extract datapoints from a goal from data from API 290 - (cons (beeminder-alist-get 'slug goal) 291 - (beeminder-to-list (beeminder-alist-get 'datapoints goal)))) 292 - (cdr (assoc 'goals data)))) 293 - (deadlines ; deadlines alone 294 - (mapcar 295 - (lambda (goal) 296 - (cons (cdr (assoc 'slug goal)) 297 - (cdr (assoc 'deadline goal)))) 298 - goals)) 299 - (today-values 300 - (mapcar 301 - (lambda (goal) 302 - (let ((last-midnight (if beeminder-use-goal-midnight-today-values 303 - (last-goal-midnight (cdr (assoc (car goal) deadlines)) 304 - now) 305 - (last-user-midnight now)))) 306 - (cons (car goal) 307 - (beeminder-sum-today-value (cdr goal) last-midnight (time-to-seconds now))))) 308 - datapoints))) 309 - (cl-flet ((beeminder-join-goal-data (goal) 310 - "Join GOAL data from various sources." 311 - (let ((slug-str (cdr (assoc 'slug goal)))) 312 - (append (list (cons 'datapoints 313 - (cdr (assoc slug-str datapoints))) 314 - (cons 'donetoday 315 - (cdr (assoc 316 - (cdr (assoc 'slug goal)) 317 - today-values))) 318 - (cons 'history-length beeminder-history-length)) 319 - goal)))) 320 - (setq beeminder-goals (mapcar #'beeminder-join-goal-data goals)) 321 - (mapc #'beeminder-clean-goal goals) 322 - (beeminder-log "fetching goals...done") 323 - (setq beeminder-reloading-in-progress nil))))) 324 - #'beeminder-report-fetching-error)))) 325 - #'beeminder-report-fetching-error)) 326 - 327 - (defun beeminder-clean-goal (goal) 328 - "Remove GOAL from `beeminder-dirty-alist' if needed. 329 - The heuristics for that is simple: if the current curval is 330 - different than the one recorded in that list, remove it." 331 - (let ((slug (intern (cdr (assoc 'slug goal))))) 332 - (if (and (cdr (assoc slug beeminder-dirty-alist)) 333 - (/= (cdr (assoc 'curval goal)) 334 - (cdr (assoc slug beeminder-dirty-alist)))) 335 - (setq beeminder-dirty-alist (assq-delete-all slug beeminder-dirty-alist))))) 336 - 337 - (cl-defun beeminder-report-fetching-error (&key error-thrown &allow-other-keys) 338 - "Report ERROR-THROWN when fetching goals." 339 - (beeminder-log (format "fetching goals...error: %s" error-thrown)) 340 - (setq beeminder-reloading-in-progress nil)) 341 - 342 - (defun beeminder-refresh-goal (slug-str) 343 - "Refresh autodata and graph of the goal named SLUG-STR. 344 - Please do not use unless really necessary, since it creates 345 - a considerable server load." 346 - (interactive (list (cdr (assoc 'slug (current-or-read-goal))))) 347 - (beeminder-request-get (concat "/goals/" slug-str "/refresh_graph.json") 348 - nil 349 - (cl-function (lambda (&rest _) 350 - (beeminder-log 351 - (format 352 - "goal %s refreshed" 353 - slug-str)))) 354 - (cl-function (lambda (&rest _) 355 - (beeminder-log 356 - (format 357 - "goal %s could not be refreshed" 358 - slug-str)))))) 359 - 360 - (define-key beeminder-mode-map (kbd "G") #'beeminder-refresh-goal) 361 - 362 - 363 - ;; Submitting datapoints 364 - 365 - (defun beeminder-read-string (prompt &optional initial-input history default-value inherit-input-method) 366 - "Replacement for `read-string', showing the default." 367 - (read-string (if default-value 368 - (format "%s (default %s): " 369 - (if (string-match "^\\(.+\\)\\(: \\)$" prompt) 370 - (match-string 1 prompt) 371 - prompt) 372 - default-value) 373 - prompt) 374 - initial-input history default-value inherit-input-method)) 375 - 376 - (defun beeminder-before-first-goal-p () 377 - "Return t if the point is before the first goal or if there is 378 - no first goal." 379 - (aif (ewoc-nth beeminder-goals-ewoc 0) 380 - (< (point) 381 - (ewoc-location it)) 382 - t)) 383 - 384 - (defun beeminder-get-slug (goal) 385 - "Return the slug of GOAL." 386 - (intern (cdr (assoc 'slug goal)))) 387 - 388 - (defun beeminder-slug-to-goal (slug) 389 - "Return the goal corresponding to SLUG." 390 - (cl-find slug beeminder-goals :key #'beeminder-get-slug)) 391 - 392 - (defun beeminder-slug-to-gnode (slug) 393 - "Return the goal node corresponding to SLUG." 394 - (let ((gnode (ewoc-nth beeminder-goals-ewoc 0))) 395 - (while (and gnode 396 - (not 397 - (eq slug (beeminder-get-slug (ewoc-data gnode))))) 398 - (setq gnode (ewoc-next beeminder-goals-ewoc gnode))) 399 - gnode)) 400 - 401 - (defvar beeminder-minibuffer-history nil 402 - "History of goal slug-strs entered through minibuffer.") 403 - 404 - (defun beeminder-read-slug (&optional default) 405 - "Return a slug read from minibuffer or DEFAULT. 406 - DEFAULT should be a symbol." 407 - (intern (completing-read (if default 408 - (format "Goal slug (default %s): " default) 409 - "Goal slug: ") 410 - (mapcar (lambda (goal) 411 - (symbol-name (beeminder-get-slug goal))) 412 - beeminder-goals) 413 - nil 414 - t 415 - nil 416 - 'beeminder-minibuffer-history 417 - (if default (symbol-name default))))) 418 - 419 - (defun current-or-read-goal () 420 - "Return the goal the point is on. 421 - If the point is before the first goal or in a buffer whose mode 422 - is not `beeminder-mode', use `beeminder-read-slug' to ask for the 423 - goal slug and return that goal instead." 424 - (if (or (not (eq major-mode 'beeminder-mode)) 425 - (beeminder-before-first-goal-p)) 426 - (let ((default (aif (ewoc-nth beeminder-goals-ewoc 0) 427 - (beeminder-get-slug (ewoc-data it))))) 428 - (beeminder-slug-to-goal 429 - (beeminder-read-slug default))) 430 - (ewoc-data (ewoc-locate beeminder-goals-ewoc)))) 431 - 432 - (defcustom beeminder-time-format "%FT%T%z" 433 - "Default time format for Beeminder comments.") 434 - 435 - (defun beeminder-current-time-string (&optional timestamp) 436 - "Return TIMESTAMP (Unix time) as a string. 437 - Use current time by default. Format is hh:mm:ss tz." 438 - (format-time-string beeminder-time-format 439 - (or timestamp (beeminder-current-time)))) 440 - 441 - (defvar beeminder-dirty-alist '() 442 - "Alist of slugs and \"curval\" values of changed goals. 443 - A goal is put here by `beeminder-submit-datapoint' and cleared 444 - from the list by `beeminder-get-goals' (if the retrieved data are 445 - actually updated, which can take from a few seconds to even a few 446 - minutes).") 447 - 448 - (defgroup beeminder-faces nil 449 - "Faces used be the Beeminder client." 450 - :group 'beeminder) 451 - 452 - (defface beeminder-dirty '((t :slant italic :foreground "grey50")) 453 - "Face for displaying \"dirty\" goals, i.e., goals for which 454 - a datapoint was submitted but had ot yet been reloaded." 455 - :group 'beeminder-faces) 456 - 457 - (defun ask-for-timestamp (&optional default) 458 - "Ask the user for the timestamp, and return it as Unix time. 459 - If `org-read-date' is present, use that; if not, fall back to 460 - `safe-date-to-time' and augment the result with current time." 461 - (time-to-seconds 462 - (if (fboundp 'org-read-date) 463 - (org-read-date t t nil nil (beeminder-safe-time default)) 464 - (let (time) 465 - (while 466 - (progn (setq time (safe-date-to-time (beeminder-read-string "Date+time: "))) 467 - (not 468 - (y-or-n-p 469 - (format-time-string "Time entered: %c. Confirm? " time))))) 470 - time)))) 471 - 472 - (defun beeminder-default-comment (&optional timestamp) 473 - "Generate the default comment for the given TIMESTAMP." 474 - (concat 475 - "via Emacs at " 476 - (beeminder-current-time-string timestamp))) 477 - 478 - (defun beeminder-ask-for-comment (slug-str amount &optional default-comment) 479 - "Ask the user for the comment for the goal named SLUG-STR. 480 - Include AMOUNT in the question, and default to DEFAULT-COMMENT." 481 - (beeminder-read-string 482 - (format "Comment for amount %s for goal %s: " (number-to-human-string amount) slug-str) 483 - nil nil default-comment)) 484 - 485 - (defcustom beeminder-ask-for-comment t 486 - "Non-nil means ask for comment when a goal is submitted. 487 - This also serves as a confirmation that the user actually wants 488 - to submit data to Beeminder (especially that the question 489 - includes the goal slug and amount), so disabling of this option 490 - is discouraged.") 491 - 492 - (defun beeminder-make-goal-dirty (slug) 493 - "Make the goal with SLUG dirty." 494 - (beeminder-set-alist-value slug 495 - 'beeminder-dirty-alist 496 - (cdr (assoc 'curval (beeminder-slug-to-goal slug)))) 497 - (aif (beeminder-slug-to-gnode slug) 498 - (ewoc-invalidate beeminder-goals-ewoc it))) 499 - 500 - (defun beeminder-clear-dirty-goals () 501 - "Clear all dirty goals manually. 502 - This may be needed in rare circumstances, namely when 503 - successfully submitting a datapoint of 0." 504 - (interactive) 505 - (setq beeminder-dirty-alist ()) 506 - (save-current-goal 507 - (ewoc-refresh beeminder-goals-ewoc))) 508 - 509 - (defun beeminder-submit-datapoint (slug-str value &optional comment timestamp id) 510 - "Submit a datapoint to Beeminder goal SLUG-STR with AMOUNT. 511 - Additional data are COMMENT and TIMESTAMP (as Unix time). If 512 - COMMENT is nil, then ask the user for the comment. If TIMESTAMP 513 - is nil, assume now. If PRINT-MESSAGE is non-nil, print suitable 514 - messages in the echo area. If ID is non-nil, use it as requestid. 515 - 516 - If called interactively, ask for SLUG-STR (with completion) unless the 517 - point is on a goal node. Then, ask for AMOUNT unless the user 518 - provided a numeric argument, in which case take the argument. Then, 519 - ask for COMMENT, proposing a reasonable default, unless the option 520 - `beeminder-ask-for-comment' is nil. If called with a prefix argument 521 - of \\[universal-argument], ask also for TIMESTAMP. If called with 522 - a prefix argument of `-', use previous day as the TIMESTAMP." 523 - (interactive 524 - (let* ((slug-str (cdr (assoc 'slug (current-or-read-goal)))) 525 - (yesterdayp (eq current-prefix-arg '-)) 526 - (value (if (numberp current-prefix-arg) 527 - current-prefix-arg 528 - (string-to-number (beeminder-read-string 529 - (format "Datapoint value for %s%s: " 530 - slug-str 531 - (if yesterdayp " (yesterday)" "")) 532 - nil nil "1")))) 533 - (current-timestamp (time-to-seconds (beeminder-current-time)))) 534 - (list slug-str 535 - value 536 - (unless beeminder-ask-for-comment 537 - (beeminder-default-comment current-timestamp)) 538 - (or (when yesterdayp 539 - (- current-timestamp (* 24 60 60))) 540 - (when (consp current-prefix-arg) 541 - (ask-for-timestamp)) 542 - current-timestamp)))) 543 - (let ((timestamp (or timestamp (time-to-seconds (beeminder-current-time))))) 544 - (beeminder-log (format "submitting datapoint of %s for goal %s..." 545 - (number-to-human-string value) 546 - slug-str)) 547 - (beeminder-request-post (format "/goals/%s/datapoints.json" slug-str) 548 - (list 549 - (cons "value" (format "%f" value)) 550 - (cons "comment" (or comment (beeminder-ask-for-comment 551 - slug-str 552 - value 553 - (beeminder-default-comment timestamp)))) 554 - (if id (cons "requestid" id)) 555 - (cons "timestamp" (format "%s" timestamp))) 556 - (cl-function (lambda (&rest _) 557 - (beeminder-log (format "submitting datapoint of %s for goal %s...done" 558 - (number-to-human-string value) 559 - slug-str)) 560 - (let* ((slug (intern slug-str)) 561 - (goal (beeminder-slug-to-goal slug))) 562 - (when goal 563 - (beeminder-inc-alist-value 'donetoday goal value) 564 - (beeminder-make-goal-dirty slug))))) 565 - (cl-function (lambda (&rest _) 566 - (beeminder-log 567 - (format "submitting datapoint of %s for goal %s...failed" 568 - (number-to-human-string value) 569 - slug-str) 570 - :error)))))) 571 - 572 - (define-key beeminder-mode-map (kbd "RET") #'beeminder-submit-datapoint) 573 - 574 - 575 - ;; Sorting EWOC 576 - 577 - (defun true (&rest args) 578 - "Always return t (irrespective of ARGS)." 579 - t) 580 - 581 - (defun ewoc-sort (ewoc pred) 582 - "Sort EWOC, comparing its nodes using PRED. 583 - Since the author of EWOC didn't really care for sorting, and 584 - neither do I, we just first collect the nodes into a list, sort 585 - it using Elisp's sort, and then recreate the EWOC." 586 - (let ((ewoc-list (ewoc-collect ewoc #'true))) 587 - (ewoc-filter ewoc #'ignore) 588 - (mapcar (lambda (node) (ewoc-enter-last ewoc node)) 589 - (sort ewoc-list pred)))) 590 - 591 - 592 - ;; Logging 593 - (define-derived-mode beeminder-log-mode special-mode "Beeminder log" 594 - "A major mode for logging beeminder.el actions.") 595 - 596 - (defface beeminder-error '((t :foreground "#800" :weight bold)) 597 - "Face for displaying error notifications.") 598 - 599 - (defface beeminder-warning '((t :foreground "#880" :weight bold)) 600 - "Face for displaying warning notifications.") 601 - 602 - (defcustom beeminder-notification-expire-time 8 603 - "After that many seconds less important notifications expire. 604 - TODO: not yet implemented." 605 - :type 'integer 606 - :group 'beeminder) 607 - 608 - (defvar beeminder-notification-expiration-timer nil 609 - "Timer used to clear notifications after 610 - `beeminder-notification-expire-time'.") 611 - 612 - (defun beeminder-log (message &optional level) 613 - "Put MESSAGE into the log and possibly into the notification 614 - area. LEVEL can be `:error', `:warning', `:logonly' or `:nolog'. 615 - Messages without any of these levels expire after 616 - `beeminder-notification-expire-time' seconds." 617 - (let ((level-string (cl-case level 618 - (:error " error") 619 - (:warning " warning") 620 - (t "")))) 621 - (unless (eq level :nolog) 622 - (save-excursion 623 - (setq message (subst-char-in-string ?\n ?\s message t)) 624 - (set-buffer (get-buffer-create "*Beeminder log*")) 625 - (beeminder-log-mode) 626 - (goto-char (point-max)) 627 - (let ((inhibit-read-only t)) 628 - (insert (current-time-string) level-string ": " message "\n")))) 629 - (unless (eq level :logonly) 630 - (message "Beeminder%s: %s" 631 - level-string 632 - message) 633 - (setq beeminder-notification 634 - (cond ((eq level :error) 635 - (propertize message 'face 'beeminder-error)) 636 - ((eq level :warning) 637 - (propertize message 'face 'beeminder-warning)) 638 - (t message)))) 639 - (awhen beeminder-notification-expiration-timer (cancel-timer it)) 640 - (unless (memq level '(:error :warning)) 641 - (setq beeminder-notification-expiration-timer 642 - (run-at-time beeminder-notification-expire-time nil #'beeminder-clear-notification))) 643 - (when beeminder-goals-ewoc (beeminder-refresh-goals-list)))) 644 - 645 - (defun beeminder-clear-notification () 646 - "Clear the notification." 647 - (interactive) 648 - (setq beeminder-notification nil) 649 - (beeminder-refresh-goals-list)) 650 - 651 - (define-key beeminder-mode-map (kbd "C") #'beeminder-clear-notification) 652 - 653 - (defvar beeminder-notification nil 654 - "A message that should appear right below the header in 655 - Beeminder mode.") 656 - 657 - (defun beeminder-pop-log () 658 - "Pop the Beeminder log buffer." 659 - (interactive) 660 - (pop-to-buffer "*Beeminder log*")) 661 - 662 - (define-key beeminder-mode-map (kbd "L") #'beeminder-pop-log) 663 - 664 - 665 - ;; Displaying goals 666 - 667 - (defvar beeminder-human-time-use-weekday t 668 - "Non-nil means that `beeminder-human-time' uses weekday names. 669 - Otherwise, use number of days from today.") 670 - 671 - (defvar beeminder-tomorrow-code "tom" 672 - "The abbreviation for \"tomorrow\".") 673 - 674 - (defcustom beeminder-when-the-day-ends (* 6 60 60) 675 - "Number of seconds from midnight when the day is assumed to end. 676 - Times up to this time will be considered to belong to the 677 - previous day. Note: this should be positive, or weird things 678 - might happen." 679 - :type 'integer 680 - :group 'beeminder) 681 - 682 - (defun beeminder-plural-ending (number) 683 - "Return \"s\" if NUMBER not equal to one, and \"\" otherwise." 684 - (if (= number 1) "" "s")) 685 - 686 - (defun beeminder-safe-time (time) 687 - "Convert TIME to Emacs time format if it is a number." 688 - (if (numberp time) 689 - (seconds-to-time time) 690 - time)) 691 - 692 - (defun beeminder-time-to-days (time) 693 - "Compute the number of days from 0001-12-31 BC until TIME. 694 - Take into consideration `beeminder-when-the-day-ends'." 695 - (time-to-days 696 - (time-add (beeminder-safe-time time) 697 - (seconds-to-time (- beeminder-when-the-day-ends))))) 698 - 699 - (defun beeminder-human-time (time) 700 - "Convert (future) TIME to a human-friendly format. 701 - - For today, the time. 702 - - For tomorrow, the string `beeminder-tomorrow-code' (by default) and 703 - the time. 704 - - For times within a week, abbreviation of the weekday or a plus and 705 - a number of days (depending on `beeminder-human-time-use-weekday') 706 - and the time. 707 - - For later times, iso date without time. 708 - Midnight is treated as belonging to the previous day, not the following one." 709 - (let ((delta (- (beeminder-time-to-days time) 710 - (beeminder-time-to-days (beeminder-current-time))))) 711 - (cond ((zerop delta) (format-time-string " %R" time)) 712 - ((= 1 delta) (concat " " beeminder-tomorrow-code 713 - (format-time-string " %R" time))) 714 - ((<= delta 7) 715 - (concat (if beeminder-human-time-use-weekday 716 - (format-time-string " %a" 717 - (time-add time 718 - (seconds-to-time (- beeminder-when-the-day-ends)))) 719 - (format " +%d" delta)) 720 - " " 721 - (format-time-string "%R" time))) 722 - (t (format-time-string "%Y-%m-%d" 723 - (time-add time 724 - (seconds-to-time (- beeminder-when-the-day-ends)))))))) 725 - 726 - (defconst beeminder-lanes-to-faces-alist 727 - '((-2 . beeminder-red) (-1 . beeminder-yellow) (1 . beeminder-blue) (2 . beeminder-green)) 728 - "Alist mapping the (normalized) value of lane to goal colors.") 729 - 730 - (defun beeminder-normalize-lane (lane-number) 731 - "Normalize LANE-NUMBER into the interval -2 .. 2. 732 - This means to return 2 for LANE-NUMBER greater than 2 and -2 for 733 - LANE-NUMBER less than -2." 734 - (min (max lane-number -2) 2)) 735 - 736 - (defun beeminder-display-string-field (goal field &optional width invisible) 737 - "Return GOAL's FIELD (which should be a symbol) as a string. 738 - Optionally use length WIDTH (padded from the right with spaces). 739 - Make it invisible if INVISIBLE is non-nil." 740 - (let ((text (format (if width 741 - (format "%%-%d.%ds" width width) 742 - "%s") 743 - (cdr (assoc field goal))))) 744 - (if invisible 745 - (propertize text 'invisible invisible) 746 - text))) 747 - 748 - (defun beeminder-display-losedate-human (goal) 749 - "Return the losedate field of GOAL in human-friendly format." 750 - (beeminder-human-time (seconds-to-time (1+ (cdr (assoc 'losedate goal)))))) 751 - 752 - (defun beeminder-get-rate (goal) 753 - "Return the rate of GOAL." 754 - (elt (cdr (assoc 'mathishard goal)) 2)) 755 - 756 - (defun beeminder-display-rate (goal) 757 - "Return the rate of the GOAL (with units), as a string." 758 - (let ((rate (beeminder-get-rate goal))) 759 - (format (concat 760 - (number-to-human-string rate 4) 761 - "/%s") 762 - (cdr (assoc 'runits goal))))) 763 - 764 - (defun beeminder-display-pledge (goal) 765 - "Return the pledge of the GOAL, as a string." 766 - (format "$%.2f" (cdr (assoc 'pledge goal)))) 767 - 768 - (defcustom beeminder-goal-pp-format 769 - '((beeminder-display-string-field slug 12) 770 - " " 771 - beeminder-display-losedate-human 772 - " " 773 - (beeminder-display-string-field limsum 16) 774 - " " 775 - beeminder-display-rate 776 - " " 777 - beeminder-display-pledge 778 - " " 779 - (beeminder-display-string-field title)) 780 - "The format for displaying a Beeminder goal. 781 - It is a list whose elements are either strings, printed verbatim, 782 - either functions, which are then called with one argument (the 783 - goal), or lists, in which case the car of the list is a function 784 - and the cdr the list of arguments it should get after the goal." 785 - :type 'sexp 786 - :group 'beeminder) 787 - 788 - (defun beeminder-goal-face (goal) 789 - "Return the face for displaying GOAL." 790 - (if (beeminder-alist-get (intern (cdr (assoc 'slug goal))) 791 - beeminder-dirty-alist) 792 - 'beeminder-dirty 793 - (cdr (assoc (* (cdr (assoc 'yaw goal)) 794 - (beeminder-normalize-lane (cdr (assoc 'lane goal)))) 795 - beeminder-lanes-to-faces-alist)))) 796 - 797 - (defun beeminder-goal-representation (goal) 798 - "The string representation of GOAL, with the face applied." 799 - (propertize (mapconcat (lambda (field-specifier) 800 - (cond 801 - ((functionp field-specifier) (funcall field-specifier goal)) 802 - ((consp field-specifier) (apply (car field-specifier) 803 - goal 804 - (cdr field-specifier))) 805 - ((stringp field-specifier) field-specifier))) 806 - beeminder-goal-pp-format "") 807 - 'face 808 - (beeminder-goal-face goal))) 809 - 810 - (defun beeminder-display-first-goal () 811 - "Display the first goal in the echo area. 812 - Normally, this should be one of the goals with the nearest 813 - deadline. Caution: if more than one goal has the same deadline, 814 - it is not obvious which one is returned as the first from the 815 - server! You might want to bind this function globally so that 816 - you don't need to enter the Beeminder mode to see the nearest 817 - deadline." 818 - (interactive) 819 - (let* ((goal (car beeminder-goals)) 820 - (minutes (/ (- (cdr (assoc 'losedate goal)) 821 - (time-to-seconds (beeminder-current-time))) 822 - 60))) 823 - (beeminder-log (format "next goal: %s (%d minute%s left, %s to do)" 824 - (replace-regexp-in-string 825 - " \\{2,\\}" 826 - " " 827 - (beeminder-goal-representation goal)) 828 - minutes 829 - (beeminder-plural-ending minutes) 830 - (let ((limsum (cdr (assoc 'limsum goal)))) 831 - (string-match "[[:digit:]]+\\(\\.[[:digit:]]*\\)?" limsum) 832 - (match-string 0 limsum)))))) 833 - 834 - 835 - ;; Faces for goals 836 - 837 - (defface beeminder-green '((t :foreground "#080")) 838 - "Face for displaying Beeminder goals in green." 839 - :group 'beeminder-faces) 840 - 841 - (defface beeminder-blue '((t :foreground "#008")) 842 - "Face for displaying Beeminder goals in blue." 843 - :group 'beeminder-faces) 844 - 845 - (defface beeminder-yellow '((t :foreground "#880")) 846 - "Face for displaying Beeminder goals in green." 847 - :group 'beeminder-faces) 848 - 849 - (defface beeminder-red '((t :foreground "#800")) 850 - "Face for displaying Beeminder goals in red." 851 - :group 'beeminder-faces) 852 - 853 - 854 - ;; Beeminder EWOC 855 - 856 - (defvar beeminder-goals-ewoc nil) 857 - 858 - (defvar beeminder-short-header nil 859 - "If t, the default header is (extremely) shortened.") 860 - 861 - (defun beeminder-toggle-short-header (&optional arg) 862 - "Toggle shortening the header for Beeminder goal list. 863 - If ARG is positive, shorten the header; otherwise, do not." 864 - (interactive "P") 865 - (setq beeminder-short-header 866 - (if (null arg) 867 - (not beeminder-short-header) 868 - (> (prefix-numeric-value arg) 0))) 869 - (save-current-goal 870 - (ewoc-set-hf beeminder-goals-ewoc (beeminder-ewoc-header) "") 871 - (ewoc-refresh beeminder-goals-ewoc))) 872 - 873 - (define-key beeminder-mode-map (kbd "=") #'beeminder-toggle-short-header) 874 - 875 - (defun beeminder-print-filter (filter) 876 - "Return a printed representation of FILTER. 877 - It should be an element of `beeminder-current-filters'." 878 - (funcall (nth 3 (assoc (car filter) beeminder-filters)) (cdr filter))) 879 - 880 - (defun beeminder-ewoc-header () 881 - "Generate header for the Beeminder EWOC." 882 - (concat (format (if beeminder-short-header 883 - "Beeminder goals user:%s goals:%s/%d" 884 - "Beeminder goals for user %s (%s goals displayed out of %d total)") 885 - beeminder-username 886 - (if beeminder-goals-ewoc 887 - (length (ewoc-collect beeminder-goals-ewoc #'true)) 888 - 0) 889 - (length beeminder-goals)) 890 - (propertize (concat (format (if beeminder-short-header 891 - " srt:%s" 892 - "\nsorting criterion: %s") 893 - (caddr beeminder-current-sorting-setting)) 894 - (if beeminder-short-header 895 - (format " e%s" 896 - (if beeminder-show-everyday "+" "-")) 897 - (format " everyday goals: %s" 898 - (if beeminder-show-everyday 899 - "displayed" 900 - "omitted"))) 901 - (format (if beeminder-short-header 902 - " fil:%s" 903 - (format "\nfilter%s: %%s\n" 904 - (beeminder-plural-ending 905 - (length beeminder-current-filters)))) 906 - (if beeminder-current-filters 907 - (mapconcat #'beeminder-print-filter 908 - beeminder-current-filters 909 - ", ") 910 - "none"))) 911 - 'face 'shadow) 912 - (aif beeminder-notification (concat " " it) ""))) 913 - 914 - (defun beeminder-create-goals-ewoc () 915 - "Return a newly created EWOC for Beeminder goals." 916 - (ewoc-create (lambda (goal) (insert (beeminder-goal-representation goal))) 917 - (beeminder-ewoc-header))) 918 - 919 - (defun beeminder-populate-ewoc () 920 - "Populate Beeminder EWOC using the goal list. 921 - In particular, apply filtering and sorting settings. Note: since 922 - only the last sorting criterion is remembered, and sorting is 923 - stable, this might actually change the ordering of goals, which 924 - may have been sorted by another criterion previously." 925 - (ewoc-filter beeminder-goals-ewoc #'ignore) 926 - (mapcar (lambda (goal) 927 - (ewoc-enter-last beeminder-goals-ewoc goal)) 928 - beeminder-goals) 929 - (beeminder-apply-filters) 930 - (apply #'beeminder-sort-by-field beeminder-current-sorting-setting) 931 - (ewoc-set-hf beeminder-goals-ewoc (beeminder-ewoc-header) "") 932 - (ewoc-refresh beeminder-goals-ewoc) 933 - (with-current-buffer (ewoc-buffer beeminder-goals-ewoc) 934 - (goto-char (point-min)))) 935 - 936 - (defun beeminder-list-goals () 937 - "Switch to a buffer containing the list of Beeminder goals." 938 - (interactive) 939 - (aif (get-buffer "*Beeminder goals*") 940 - (switch-to-buffer it) 941 - (switch-to-buffer "*Beeminder goals*") 942 - (buffer-disable-undo) 943 - (beeminder-mode)) 944 - (let ((inhibit-read-only t)) 945 - (erase-buffer) 946 - (setq beeminder-goals-ewoc (beeminder-create-goals-ewoc)) 947 - (beeminder-populate-ewoc)) 948 - (setq truncate-lines t) 949 - (unless beeminder-goals 950 - (beeminder-get-goals))) 951 - 952 - 953 - ;; Current time function 954 - 955 - (defalias 'beeminder-current-time 'current-time 956 - "An alias for current-time, useful for testing/debugging.") 957 - 958 - 959 - ;; Sorting 960 - 961 - (defcustom beeminder-default-sorting-setting (list 'losedate #'< "losedate") 962 - "Default sorting setting for Beeminder goals. 963 - This is a list whose first element is the field according to 964 - which the sorting should be done, then the predicate, and then 965 - the printed representation of this sorting method (as a string)." 966 - :type 'sexp 967 - :group 'beeminder) 968 - 969 - (defvar beeminder-current-sorting-setting beeminder-default-sorting-setting) 970 - 971 - (defmacro save-current-goal (&rest body) 972 - "Evaluate BODY and bring the point back to the current goal. 973 - If the Beeminder EWOC disappeared (for some reason), just 974 - evaluate the body." 975 - (declare (indent 0) (debug t)) 976 - `(if beeminder-goals-ewoc 977 - (with-current-buffer (ewoc-buffer beeminder-goals-ewoc) 978 - (let* ((current-goal-slug 979 - (if (beeminder-before-first-goal-p) 980 - nil 981 - (beeminder-get-slug (ewoc-data (ewoc-locate beeminder-goals-ewoc))))) 982 - (current-line (unless current-goal-slug (line-number-at-pos)))) 983 - ,@body 984 - (cond ((not current-goal-slug) 985 - (goto-char (point-min)) 986 - (forward-line (1- current-line))) 987 - (t 988 - (ewoc-goto-node beeminder-goals-ewoc (ewoc-nth beeminder-goals-ewoc 0)) 989 - (let ((current-node (ewoc-nth beeminder-goals-ewoc 0))) 990 - (while (and current-node 991 - (not (eq (beeminder-get-slug (ewoc-data current-node)) 992 - current-goal-slug))) 993 - (ewoc-goto-next beeminder-goals-ewoc 1) 994 - (setq current-node (ewoc-next beeminder-goals-ewoc current-node))) 995 - (unless current-node (goto-char (point-min)))))))) 996 - ,@body)) 997 - 998 - (defun beeminder-sort-by-field (field predicate info) 999 - "Sort entries in `beeminder-goals-ewoc' by FIELD, using PREDICATE. 1000 - INFO is the printed representation of the sorting criterion." 1001 - (save-current-goal 1002 - (ewoc-sort beeminder-goals-ewoc (lambda (x y) 1003 - (funcall predicate 1004 - (cdr (assoc field x)) 1005 - (cdr (assoc field y))))) 1006 - (ewoc-refresh beeminder-goals-ewoc) 1007 - (setq beeminder-current-sorting-setting (list field predicate info)) 1008 - (ewoc-set-hf beeminder-goals-ewoc (beeminder-ewoc-header) ""))) 1009 - 1010 - (defun beeminder-sort-by-losedate () 1011 - "Sort entries in `beeminder-goals' by losedate." 1012 - (interactive) 1013 - (beeminder-sort-by-field 'losedate #'< "losedate")) 1014 - 1015 - (defun beeminder-seconds-to-from-midnight (time) 1016 - "Convert TIME to seconds from midnight. 1017 - If after 6:00, convert to seconds to midnight (with a minus 1018 - sign). The magic time constant 6:00 is the result of Beeminder's way 1019 - of dealing with the \"midnight\" setting." 1020 - (let* ((decoded-time (decode-time time)) 1021 - (seconds (+ (car decoded-time) 1022 - (* 60 (cadr decoded-time)) 1023 - (* 3600 (caddr decoded-time))))) 1024 - (if (> seconds (* 6 60 60)) 1025 - (- seconds (* 24 60 60)) 1026 - seconds))) 1027 - 1028 - (defun beeminder-earlier-midnight (sec1 sec2 time) 1029 - "Compare SEC1 and SEC2, taking into account the TIME. 1030 - All three parameters are expressed as seconds from midnight, like 1031 - the result of calling `beeminder-seconds-to-from-midnight'. If 1032 - SEC1 < SEC2 < TIME, return t. If TIME < SEC1 < SEC2, return t. 1033 - If SEC2 < TIME < SEC1, return t. In all other cases, return nil. 1034 - This function is useful for sorting goals by their \"midnight\" 1035 - setting, with the goals which are after their \"midnight\" at the 1036 - end." 1037 - (or (increasingp sec1 sec2 time) 1038 - (increasingp time sec1 sec2) 1039 - (increasingp sec2 time sec1))) 1040 - 1041 - (defun beeminder-sort-by-midnight () 1042 - "Sort entries in `beeminder-goals' by their midnight, taking current time into consideration." 1043 - (interactive) 1044 - (beeminder-sort-by-field 1045 - 'deadline 1046 - (lambda (x y) 1047 - (beeminder-earlier-midnight 1048 - x y (beeminder-seconds-to-from-midnight 1049 - (beeminder-current-time)))) 1050 - "midnight")) 1051 - 1052 - (define-key beeminder-mode-map "l" #'beeminder-sort-by-losedate) 1053 - (define-key beeminder-mode-map "m" #'beeminder-sort-by-midnight) 1054 - 1055 - 1056 - ;; Refreshing view and reloading goals 1057 - 1058 - (defun beeminder-refresh-goals-list () 1059 - "Refresh the goals list." 1060 - (interactive) 1061 - (save-current-goal 1062 - (beeminder-populate-ewoc))) 1063 - 1064 - (defvar beeminder-reloading-in-progress nil 1065 - "Non-nil if currently reloading data from the server.") 1066 - 1067 - (defun beeminder-clear-reloading-in-progress-flag () 1068 - "Clear the `beeminder-reloading-in-progress' flag. 1069 - Useful in case of an error." 1070 - (interactive) 1071 - (setq beeminder-reloading-in-progress nil)) 1072 - 1073 - (defun beeminder-reload-goals-list (&optional force) 1074 - "Reload the goals from the server. 1075 - With a prefix argument, do it even if reloading is in progress 1076 - \(this is useful when the `beeminder-reloading-in-progress' 1077 - variable is somehow messed up)." 1078 - (interactive "P") 1079 - (if (and beeminder-reloading-in-progress 1080 - (not force)) 1081 - (beeminder-log "fetching goals already in progress, please wait!") 1082 - (setq beeminder-reloading-in-progress t) 1083 - (beeminder-get-goals))) 1084 - 1085 - (define-key beeminder-mode-map (kbd "C-l") #'beeminder-refresh-goals-list) 1086 - (define-key beeminder-mode-map "g" #'beeminder-reload-goals-list) 1087 - 1088 - 1089 - ;; Filtering goals 1090 - 1091 - (defvar beeminder-current-filters '() 1092 - "Alist of filters currently in effect.") 1093 - 1094 - (defcustom beeminder-saved-filters '() 1095 - "A remembered set of filters for fast retrieval.") 1096 - 1097 - (defun beeminder-save-filters () 1098 - "Save the current filters." 1099 - (interactive) 1100 - (setq beeminder-saved-filters 1101 - (copy-alist beeminder-current-filters)) 1102 - (beeminder-log (format "current filter settings %s saved." beeminder-current-filters))) 1103 - 1104 - (defun beeminder-retrieve-filters () 1105 - "Retrieve saved filters." 1106 - (interactive) 1107 - (setq beeminder-current-filters 1108 - (copy-alist beeminder-saved-filters)) 1109 - (beeminder-refresh-goals-list) 1110 - (beeminder-log (format "filter settings %s retrieved." beeminder-current-filters))) 1111 - 1112 - (defun beeminder-clear-filters () 1113 - "Clear all filters. 1114 - If there are no saved filters, first save the current filters." 1115 - (interactive) 1116 - (unless beeminder-saved-filters 1117 - (beeminder-save-filters)) 1118 - (setq beeminder-current-filters '()) 1119 - (beeminder-refresh-goals-list)) 1120 - 1121 - (define-key beeminder-mode-map "c" #'beeminder-clear-filters) 1122 - 1123 - (define-prefix-command 'beeminder-filter-map) 1124 - (define-key beeminder-mode-map "f" #'beeminder-filter-map) 1125 - (define-key beeminder-filter-map "c" #'beeminder-clear-filters) 1126 - (define-key beeminder-filter-map "s" #'beeminder-save-filters) 1127 - (define-key beeminder-filter-map "f" #'beeminder-retrieve-filters) 1128 - (define-key beeminder-filter-map "r" #'beeminder-retrieve-filters) 1129 - 1130 - (defcustom beeminder-default-filter-days 3 1131 - "Defalt number of days used for filtering by losedate. 1132 - If the user doesn't specify the number of days for filtering, all 1133 - goals with more than this amount of days left to losedate will be 1134 - filtered out." 1135 - :type 'integer 1136 - :group 'beeminder) 1137 - 1138 - (defcustom beeminder-use-goal-midnight-today-values nil 1139 - "If non-nil, compute today's values using the goal's midnight. 1140 - If nil, use the global midnight defined by 1141 - `beeminder-when-the-day-ends'." 1142 - :type 'boolean 1143 - :group 'beeminder) 1144 - 1145 - (defcustom beeminder-default-filter-donetoday 100 1146 - "Default percentage of donetoday used for filtering." 1147 - :type 'integer 1148 - :group 'beeminder) 1149 - 1150 - (defcustom beeminder-default-filter-urgent-hours 8 1151 - "Default time (in hours) to deadline to consider a goal urgent." 1152 - :type 'integer 1153 - :group 'beeminder) 1154 - 1155 - (defcustom beeminder-show-dirty-donetoday t 1156 - "If non-nil, show dirty goals even if they would be normally 1157 - filtered out by the \"donetoday\" filter." 1158 - :type 'boolean 1159 - :group 'beeminder) 1160 - 1161 - (defcustom beeminder-show-everyday t 1162 - "If non-nil, show \"everyday goals\" irrespective of the 1163 - \"days\" filter.") 1164 - 1165 - (defcustom beeminder-everyday-goals-list 1166 - '() 1167 - "A list of slugs of \"everyday goals\". These are the goals which 1168 - should be done every day, so even when filtering goals with deadline 1169 - after some number of days, they should be shown.") 1170 - 1171 - (defun beeminder-toggle-show-everyday (arg) 1172 - "Toggle showing \"everyday goals\" if ARG is zero or nil. 1173 - If ARG is positive, turn it on; if negative, off." 1174 - (interactive "P") 1175 - (let ((narg (prefix-numeric-value arg))) 1176 - (cond ((null arg) 1177 - (setq beeminder-show-everyday (not beeminder-show-everyday))) 1178 - ((> narg 0) 1179 - (setq beeminder-show-everyday t)) 1180 - ((< narg 0) 1181 - (setq beeminder-show-everyday nil)))) 1182 - (beeminder-refresh-goals-list)) 1183 - 1184 - (define-key beeminder-mode-map "e" #'beeminder-toggle-show-everyday) 1185 - (define-key beeminder-filter-map "e" #'beeminder-toggle-show-everyday) 1186 - 1187 - (defun beeminder-days-p (goal days) 1188 - "Return nil if time to derailment of GOAL > DAYS. 1189 - If DAYS is negative, return nil if time to derailment of GOAL is 1190 - <= -DAYS. If the goal is in `beeminder-everyday-goals-list', 1191 - return t anyway." 1192 - (if (and beeminder-show-everyday 1193 - (memq (beeminder-get-slug goal) beeminder-everyday-goals-list)) 1194 - t 1195 - (let ((days-left (- (beeminder-time-to-days (cdr (assoc 'losedate goal))) 1196 - (beeminder-time-to-days (beeminder-current-time))))) 1197 - (if (>= days 0) 1198 - (<= days-left days) 1199 - (>= days-left (- days)))))) 1200 - 1201 - (defun beeminder-donetoday-p (goal percentage) 1202 - "Return nil if donetoday for GOAL >= PERCENTAGE * day's amount. 1203 - If PERCENTAGE is negative, return nil if donetoday of GOAL is 1204 - less than PERCENTAGE * day's amount. Take the variable 1205 - `beeminder-show-dirty-donetoday' into account." 1206 - (if (and beeminder-show-dirty-donetoday 1207 - (beeminder-alist-get (beeminder-get-slug goal) beeminder-dirty-alist)) 1208 - t 1209 - (let* ((rate (beeminder-get-rate goal)) 1210 - (daily-rate (/ rate 1211 - (cl-case (intern (cdr (assoc 'runits goal))) 1212 - (y 365.0) 1213 - (m (/ 365.0 12)) 1214 - (w 7.0) 1215 - (d 1.0) 1216 - (h (/ 1 24.0))))) 1217 - (100*donetoday (* 100 (cdr (assoc 'donetoday goal)))) 1218 - (percentage*daily-rate (* percentage daily-rate))) 1219 - (when (> rate 0) 1220 - (cond ((> percentage 0) 1221 - (< 100*donetoday 1222 - percentage*daily-rate)) 1223 - ((zerop percentage) 1224 - (zerop 100*donetoday)) 1225 - (t 1226 - (>= 100*donetoday (- percentage*daily-rate)))))))) 1227 - 1228 - (defun beeminder-calculate-midnight-offset (seconds) 1229 - "Add (* 24 60 60) to SECONDS if negative." 1230 - (if (> seconds 0) 1231 - seconds 1232 - (+ (* 24 60 60) seconds))) 1233 - 1234 - (defun beeminder-urgent-p (goal hours) 1235 - "Return nil if time to deadline for GOAL is > HOURS. 1236 - If HOURS is negative or zero, return nil if time to deadline is 1237 - <= -HOURS." 1238 - (let* ((deadline (beeminder-calculate-midnight-offset (beeminder-alist-get 'deadline goal))) 1239 - (now (decode-time (beeminder-current-time))) 1240 - (now-sec (car now)) 1241 - (now-min (cadr now)) 1242 - (now-hour (caddr now)) 1243 - (now-time (+ now-sec (* 60 now-min) (* 3600 now-hour))) 1244 - (time-to-deadline (beeminder-calculate-midnight-offset (- deadline now-time)))) 1245 - (if (> hours 0) 1246 - (<= time-to-deadline (* 3600 hours)) 1247 - (> time-to-deadline (* -3600 hours))))) 1248 - 1249 - (defun beeminder-not-killed-p (goal kill-list) 1250 - "Return nil if GOAL is in the KILL-LIST." 1251 - (not (member (beeminder-get-slug goal) kill-list))) 1252 - 1253 - (defvar beeminder-filters `((losedate ,#'beeminder-days-p 1254 - ,beeminder-default-filter-days 1255 - (lambda (days) 1256 - (format (if beeminder-short-header 1257 - "d2d(%s%d)" 1258 - "days to derailment (%s%d)") 1259 - (if (>= days 0) "<=" ">=") 1260 - (abs days)))) 1261 - (donetoday ,#'beeminder-donetoday-p 1262 - ,beeminder-default-filter-donetoday 1263 - (lambda (donetoday) 1264 - (format (if beeminder-short-header 1265 - "dt(%s%d%%)" 1266 - "done today (%s%d%%)") 1267 - (cond ((> donetoday 0) "<") 1268 - ((zerop donetoday) "=") 1269 - (t ">=")) 1270 - (abs donetoday)))) 1271 - (urgent ,#'beeminder-urgent-p 1272 - ,beeminder-default-filter-urgent-hours 1273 - (lambda (hours) 1274 - (format (if beeminder-short-header 1275 - "u(%s%dh)" 1276 - "urgent (%s%d hours to deadline)") 1277 - (if (> hours 0) "<=" ">") 1278 - (abs hours)))) 1279 - (killed ,#'beeminder-not-killed-p 1280 - '() 1281 - (lambda (kill-list) 1282 - (format (if beeminder-short-header 1283 - "%dgk" 1284 - "%d goal%s killed") 1285 - (length kill-list) 1286 - (beeminder-plural-ending (length kill-list)))))) 1287 - 1288 - "List of possible filters. Each element is a list, consisting of: 1289 - - symbol, denoting the filter, 1290 - - predicate (with two arguments - the goal and the parameter), 1291 - - default value for the parameter, 1292 - - formatting function (with one argument - the parameter).") 1293 - 1294 - (defun beeminder-kill-goal (gnode) 1295 - "Delete GNODE from `beeminder-goals-ewoc'." 1296 - (interactive (list (beeminder-slug-to-gnode (intern (cdr (assoc 'slug (current-or-read-goal))))))) 1297 - (if gnode 1298 - (let ((inhibit-read-only t) 1299 - (next-goal (or (ewoc-next beeminder-goals-ewoc gnode) 1300 - (ewoc-prev beeminder-goals-ewoc gnode)))) 1301 - (ewoc-delete beeminder-goals-ewoc gnode) 1302 - (ewoc-refresh beeminder-goals-ewoc) 1303 - (beeminder-set-alist-value 'killed 1304 - 'beeminder-current-filters 1305 - (cons (beeminder-get-slug (ewoc-data gnode)) 1306 - (beeminder-alist-get 'killed beeminder-current-filters))) 1307 - (ewoc-set-hf beeminder-goals-ewoc (beeminder-ewoc-header) "") 1308 - (if next-goal 1309 - (ewoc-goto-node beeminder-goals-ewoc next-goal) 1310 - (goto-char (point-min))) 1311 - (beeminder-log (format "goal %s killed (hidden from view)." (cdr (assoc 'slug (ewoc-data gnode)))))) 1312 - (beeminder-log (format "goal %s already killed." (cdr (assoc 'slug (ewoc-data gnode))))))) 1313 - 1314 - (define-key beeminder-mode-map (kbd "C-k") #'beeminder-kill-goal) 1315 - (define-key beeminder-filter-map "k" #'beeminder-kill-goal) 1316 - 1317 - (defun beeminder-show-kills () 1318 - "Show all killed goals." 1319 - (interactive) 1320 - (beeminder-log (format "killed goals: %s." 1321 - (aif (beeminder-alist-get 'killed beeminder-current-filters) 1322 - (mapconcat #'symbol-name it ", ") 1323 - "none")))) 1324 - 1325 - (defun beeminder-clear-kills () 1326 - "Unkill all killed goals." 1327 - (interactive) 1328 - (setq beeminder-current-filters 1329 - (assq-delete-all 'killed beeminder-current-filters)) 1330 - (beeminder-refresh-goals-list)) 1331 - 1332 - (defun beeminder-clear-or-show-kills (arg) 1333 - "Unkill all killed goals if ARG is nil. 1334 - With prefix argument, show the list of killed goals." 1335 - (interactive "P") 1336 - (if arg 1337 - (beeminder-show-kills) 1338 - (beeminder-clear-kills))) 1339 - 1340 - (define-key beeminder-mode-map (kbd "C-y") #'beeminder-clear-or-show-kills) 1341 - (define-key beeminder-filter-map "y" #'beeminder-clear-or-show-kills) 1342 - 1343 - (defun beeminder-apply-filter (filter) 1344 - "Apply FILTER (a dotted pair of symbol and parameter). 1345 - This means deleting some goals from `beeminder-goals-ewoc'." 1346 - (save-current-goal 1347 - (ewoc-filter beeminder-goals-ewoc 1348 - (lambda (goal) 1349 - (funcall (cadr (assoc (car filter) beeminder-filters)) 1350 - goal (cdr filter)))))) 1351 - 1352 - (defun beeminder-apply-filters () 1353 - "Apply filters from `beeminder-current-filters' in sequence." 1354 - (mapc #'beeminder-apply-filter beeminder-current-filters)) 1355 - 1356 - (defun beeminder-enable-filter (filter parameter) 1357 - "Enable FILTER (symbol) with PARAMETER (number). 1358 - Disable FILTER if PARAMETER is nil." 1359 - (beeminder-set-alist-value filter 'beeminder-current-filters parameter) 1360 - (setq beeminder-current-filters (rassq-delete-all nil beeminder-current-filters)) 1361 - (beeminder-refresh-goals-list)) 1362 - 1363 - (defun beeminder-filter-parameter (raw-prefix default) 1364 - "Return filter parameter based on RAW-PREFIX and DEFAULT." 1365 - (cond ((eq raw-prefix '-) nil) 1366 - ((null raw-prefix) default) 1367 - (t (prefix-numeric-value raw-prefix)))) 1368 - 1369 - (defun beeminder-filter-by-losedate (&optional days) 1370 - "Filter out goals with time to losedate greater than DAYS." 1371 - (interactive "P") 1372 - (beeminder-enable-filter 'losedate 1373 - (beeminder-filter-parameter days 1374 - beeminder-default-filter-days))) 1375 - 1376 - (defun beeminder-filter-by-donetoday (&optional percentage) 1377 - "Filter out goals with donetoday greater than PERCENTAGE." 1378 - (interactive "P") 1379 - (beeminder-enable-filter 'donetoday 1380 - (beeminder-filter-parameter percentage 1381 - beeminder-default-filter-donetoday))) 1382 - 1383 - (defun beeminder-filter-by-urgent (&optional hours) 1384 - "Filter out goals with time to deadline greater than HOURS." 1385 - (interactive "P") 1386 - (beeminder-enable-filter 'urgent 1387 - (beeminder-filter-parameter hours 1388 - beeminder-default-filter-urgent-hours))) 1389 - 1390 - (define-key beeminder-mode-map (kbd "d") #'beeminder-filter-by-losedate) 1391 - (define-key beeminder-filter-map (kbd "d") #'beeminder-filter-by-losedate) 1392 - (define-key beeminder-mode-map (kbd "t") #'beeminder-filter-by-donetoday) 1393 - (define-key beeminder-filter-map (kbd "t") #'beeminder-filter-by-donetoday) 1394 - (define-key beeminder-mode-map (kbd "u") #'beeminder-filter-by-urgent) 1395 - (define-key beeminder-filter-map (kbd "u") #'beeminder-filter-by-urgent) 1396 - 1397 - 1398 - ;; Displaying goal details 1399 - 1400 - (defcustom beeminder-goal-template-fields-alist 1401 - '((slug . (propertize (symbol-name (beeminder-get-slug goal)) 'face (beeminder-goal-face goal))) 1402 - (limsum . (propertize (cdr (assoc 'limsum goal)) 'face (beeminder-goal-face goal))) 1403 - (backburnerp . (if (string= (cdr (assoc 'burner goal)) "backburner") "(backburner)" "")) 1404 - (username . beeminder-username) 1405 - (dirtyp . (if (assoc (beeminder-get-slug goal) beeminder-dirty-alist) 1406 - (propertize " (goal dirty!)" 'face 'beeminder-dirty) "")) 1407 - (target . (highlight-subtly (number-to-string (elt (cdr (assoc 'mathishard goal)) 1)))) 1408 - (goaldate . (highlight-subtly (format-time-string "%x" (seconds-to-time (elt (cdr (assoc 'mathishard goal)) 0))))) 1409 - (rate . (highlight-subtly (number-to-human-string (beeminder-get-rate goal)))) 1410 - (runit . (highlight-subtly 1411 - (cl-case (intern (cdr (assoc 'runits goal))) 1412 - (d "day") 1413 - (w "week") 1414 - (m "month") 1415 - (h "hour") 1416 - (y "year")))) 1417 - (curval . (highlight-subtly (number-to-human-string (cdr (assoc 'curval goal))))) 1418 - (autodatap . (aif (cdr (assoc 'autodata goal)) 1419 - (concat ", autodata source: " 1420 - (highlight-subtly it)) 1421 - "")) 1422 - (goaltype . (highlight-subtly (beeminder-display-goal-type (cdr (assoc 'goal_type goal))))) 1423 - (losedate . (highlight-subtly (trim-leading-whitespace 1424 - (beeminder-display-losedate-human goal)))) 1425 - (pledge . (highlight-subtly (beeminder-display-pledge goal))) 1426 - (midnight . (highlight-subtly (beeminder-display-midnight-setting (cdr (assoc 'deadline goal))))) 1427 - (donetoday . (highlight-subtly (number-to-human-string (cdr (assoc 'donetoday goal))))) 1428 - (datapoints . (propertize (beeminder-format-datapoints goal) 'face 1429 - 'shadow)) 1430 - (history-length . (highlight-subtly (let ((hl (cdr (assoc 'history-length goal)))) 1431 - (if (zerop hl) "all" (format "%s" hl)))))) 1432 - "Alist of symbols and corresponding pieces of code to evaluate 1433 - and insert the result in the goal details info.") 1434 - 1435 - (defun beeminder-display-time-field (alist field) 1436 - "Return ALIST's (unix-time) FIELD formatted." 1437 - (format-time-string "%x %X" (seconds-to-time (cdr (assoc field alist))))) 1438 - 1439 - (defcustom beeminder-datapoint-format 1440 - '((beeminder-display-string-field id 25 t) 1441 - (beeminder-display-time-field timestamp) 1442 - " " 1443 - (beeminder-display-string-field value 8) 1444 - " " 1445 - (beeminder-display-string-field comment)) 1446 - "The format for displaying a goal's datapoint. 1447 - The format is identical to that of `beeminder-goal-pp-format'.") 1448 - 1449 - (defun beeminder-datapoint-representation (datapoint) 1450 - "The string representation of DATAPOINT." 1451 - ;; TODO: factor out common code of this and `beeminder-goal-pp-format'. 1452 - (mapconcat (lambda (field-specifier) 1453 - (cond 1454 - ((functionp field-specifier) (funcall field-specifier datapoint)) 1455 - ((consp field-specifier) (apply (car field-specifier) 1456 - datapoint 1457 - (cdr field-specifier))) 1458 - ((stringp field-specifier) field-specifier))) 1459 - beeminder-datapoint-format "")) 1460 - 1461 - (defun beeminder-format-datapoints (goal) 1462 - "Return the printed representation of GOAL's datapoints." 1463 - (mapconcat #'beeminder-datapoint-representation 1464 - (reverse (cdr (assoc 'datapoints goal))) 1465 - "\n")) 1466 - 1467 - (defun beeminder-insert-goal-template-with-expansion (template goal) 1468 - "Insert TEMPLATE with information about GOAL. 1469 - If a substring of the form \"#SYMBOL\" is found, and SYMBOL is 1470 - a key in the `beeminder-goal-template-fields-alist' variable, 1471 - \"#SYMBOL\" is replaced with result of evaluating the associated 1472 - value. If there is no such entry, the SYMBOL is looked up in the 1473 - alist representing the current goal, and \"#SYMBOL\" is replaced 1474 - with its printed representation (using `format''s \"%s\" 1475 - specifier). In the latter case, the result is colorized with the 1476 - `subtle-highlight-face'; in the former case, code in 1477 - `beeminder-goal-template-fields-alist' should take care of 1478 - colorization if needed. 1479 - 1480 - If a substring of the form \"#SEXP\" is found, and SEXP is not 1481 - a symbol, \"#SEXP\" is replaced with the result of evaluating SEXP. 1482 - Within SEXP, the variable `goal' is bound to the alist holding the 1483 - current's goal properties. 1484 - 1485 - Should someone want to insert a literal \"#\" character, the form 1486 - \"#(identity \"#\")\" can be used. 1487 - 1488 - Warning: this function uses `eval', so evil code in TEMPLATE or 1489 - `beeminder-goal-template-fields-alist' can do real harm!" 1490 - (save-excursion (insert template)) 1491 - (while (search-forward "#" nil t) 1492 - (let ((begin (1- (point))) 1493 - (sexp (read (current-buffer)))) 1494 - (delete-region begin (point)) 1495 - (insert (format "%s" 1496 - (cond ((symbolp sexp) 1497 - (aif (assoc sexp beeminder-goal-template-fields-alist) 1498 - (eval (cdr it) `((goal . ,goal) t)) 1499 - (highlight-subtly (format "%s" (cdr (assoc sexp goal)))))) 1500 - (t (eval sexp)))))))) 1501 - 1502 - (define-derived-mode beeminder-goal-mode special-mode "Beeminder goal" 1503 - "A major mode for a buffer displaying details of a Beeminder goal, 1504 - in particular the history of datapoints.") 1505 - 1506 - (defun beeminder-next-datapoint (count) 1507 - "Move forward COUNT datapoints." 1508 - (interactive "p") 1509 - (forward-line 1) 1510 - (re-search-forward "^[0-9a-f]\\{24\\}" nil t count) 1511 - (beginning-of-line)) 1512 - 1513 - (defun beeminder-previous-datapoint (count) 1514 - "Move forward COUNT datapoints." 1515 - (interactive "p") 1516 - (beginning-of-line) 1517 - (re-search-backward "^[0-9a-f]\\{24\\}" nil t count)) 1518 - 1519 - (define-key beeminder-goal-mode-map (kbd "n") #'beeminder-next-datapoint) 1520 - (define-key beeminder-goal-mode-map (kbd "p") #'beeminder-previous-datapoint) 1521 - 1522 - (defface subtle-highlight '((t :foreground "#004400")) 1523 - "Face for subtly highlighting things.") 1524 - 1525 - (defun highlight-subtly (string) 1526 - "Make STRING stand out, but only a little." 1527 - (propertize string 'face 'subtle-highlight)) 1528 - 1529 - (defun number-to-human-string (number &optional width) 1530 - "Convert NUMBER to a human-friendly form, at least WIDTH characters. 1531 - If NUMBER is greater than 10, use one decimal place. Otherwise, 1532 - use two. Trim any non-significant trailing zeros and the decimal 1533 - point if needed." 1534 - (let ((str (replace-regexp-in-string 1535 - "\\.?0+$" "" 1536 - (format (cond 1537 - ((> number 10) "%.1f") 1538 - (t "%.2f")) 1539 - number)))) 1540 - (if width (format (format "%%%ds" width) str) str))) 1541 - 1542 - (defun beeminder-display-midnight-setting (seconds) 1543 - "Convert SECONDS to or from midnight to a time string." 1544 - (when (< seconds 0) (setq seconds (+ seconds (* 24 60 60)))) 1545 - (let* ((hours (/ seconds 60 60)) 1546 - (minutes (/ (- seconds (* hours 60 60)) 60))) 1547 - (format "%d:%02d" hours minutes))) 1548 - 1549 - (defun beeminder-display-goal-type (goaltype) 1550 - "Convert GOALTYPE to a printed representation." 1551 - (cl-case (intern goaltype) 1552 - (hustler "do more") 1553 - (biker "odometer") 1554 - (fatloser "weight loss") 1555 - (gainer "gain weight") 1556 - (inboxer "inbox fewer") 1557 - (drinker "do less") 1558 - (custom "custom"))) 1559 - 1560 - (defcustom beeminder-goal-template 1561 - "Details for Beeminder goal #slug for user #username#backburnerp 1562 - #title#dirtyp 1563 - goal target: #target on #goaldate at rate #rate per #runit (currently at #curval, done today: #donetoday) 1564 - goal type: #goaltype#autodatap 1565 - safe until #losedate (current pledge: #pledge, left to do: #limsum, midnight setting: #midnight) 1566 - 1567 - Recent datapoints (#history-length days): 1568 - #datapoints 1569 - " 1570 - "The default template for displaying goal details. 1571 - See the docstring of the function 1572 - `beeminder-insert-goal-template-with-expansion' for the list of 1573 - available keywords.") 1574 - 1575 - (defun beeminder-refresh-goal-details () 1576 - "Refresh goal details, assuming that the respective buffer 1577 - exists and is set up properly." 1578 - (let ((inhibit-read-only t)) 1579 - (erase-buffer) 1580 - (beeminder-insert-goal-template-with-expansion 1581 - beeminder-goal-template 1582 - beeminder-detailed-goal)) 1583 - (goto-char (point-min))) 1584 - 1585 - (defvar beeminder-detailed-goal nil 1586 - "The current goal in the details buffer.") 1587 - 1588 - (defun beeminder-display-goal-details (goal) 1589 - "Display details about GOAL in a temporary buffer." 1590 - (interactive (list (current-or-read-goal))) 1591 - (pop-to-buffer "*Beeminder goal details*") 1592 - (remove-images (point-min) (point-max)) 1593 - (beeminder-goal-mode) 1594 - (setq-local beeminder-detailed-goal goal) 1595 - (beeminder-refresh-goal-details)) 1596 - 1597 - (define-key beeminder-mode-map (kbd "TAB") #'beeminder-display-goal-details) 1598 - 1599 - (defcustom beeminder-confirm-datapoint-deletion #'y-or-n-p 1600 - "How to ask for confirmation of datapoint deletion. 1601 - If nil, don't ask." 1602 - :type '(choice (const :tag "Ask with yes-or-no-p" yes-or-no-p) 1603 - (const :tag "Ask with y-or-n-p" y-or-n-p) 1604 - (const :tag "Don't ask at all" nil) 1605 - (funtion :tag "Predicate function")) 1606 - :group 'beeminder) 1607 - 1608 - (defun beeminder-get-datapoint-id () 1609 - "Return the id of the datapoint at point." 1610 - (save-excursion 1611 - (beginning-of-line) 1612 - (if (looking-at "[0-9a-f]\\{24\\}") 1613 - (match-string-no-properties 0) 1614 - (error "Not at a datapoint")))) 1615 - 1616 - (defun beeminder-delete-datapoint (id) 1617 - "Delete datapoint with id ID. 1618 - If called interactively, take the id from the beginning of the 1619 - line." 1620 - (interactive (list (beeminder-get-datapoint-id))) 1621 - (if (and beeminder-confirm-datapoint-deletion 1622 - (funcall beeminder-confirm-datapoint-deletion "Are you sure you want to delete this datapoint?")) 1623 - (beeminder-request-delete 1624 - (concat "/goals/" (cdr (assoc 'slug beeminder-detailed-goal)) "/datapoints/" id ".json") 1625 - (cl-function (lambda (&rest _) 1626 - (let ((datapoints (cdr (assoc 'datapoints beeminder-detailed-goal)))) 1627 - (beeminder-inc-alist-value 'donetoday 1628 - beeminder-detailed-goal 1629 - (- (cdr (assoc 'value (cl-find id datapoints 1630 - :key (lambda (dp) 1631 - (cdr (assoc 'id dp))) 1632 - :test #'string=))))) 1633 - (cl-delete id datapoints :key (lambda (dp) (cdr (assoc 'id dp))) :test #'string=) 1634 - (beeminder-make-goal-dirty (beeminder-get-slug beeminder-detailed-goal)) 1635 - (beeminder-refresh-goal-details) 1636 - (beeminder-log (format "datapoint %s succesfully deleted" id))))) 1637 - (cl-function (lambda (&key error-thrown &allow-other-keys) 1638 - (beeminder-log (format "error while deleting datapoint %s for goal %s: %s" 1639 - id 1640 - (cdr (assoc 'slug beeminder-detailed-goal)) 1641 - error-thrown) 1642 - :error)))))) 1643 - 1644 - (define-key beeminder-goal-mode-map (kbd "d") #'beeminder-delete-datapoint) 1645 - 1646 - (defun beeminder-get-datapoint (id datapoints) 1647 - "Return datapoint with id ID from DATAPOINTS." 1648 - (cl-find id datapoints 1649 - :key (lambda (dp) 1650 - (cdr (assoc 'id dp))) 1651 - :test #'string=)) 1652 - 1653 - (defun beeminder-edit-datapoint (id) 1654 - "Edit datapoint with id ID. 1655 - If called interactively, take the id from the beginning of the 1656 - line." 1657 - (interactive (list (beeminder-get-datapoint-id))) 1658 - (let* ((datapoints (reverse (cdr (assoc 'datapoints beeminder-detailed-goal)))) 1659 - (datapoint (aif (beeminder-get-datapoint id datapoints) 1660 - it 1661 - (error "%s" "Invalid datapoint id -- beeminder-get-datapoint"))) 1662 - (timestamp (ask-for-timestamp (cdr (assoc 'timestamp datapoint)))) 1663 - (value (string-to-number 1664 - (let ((default-value (cdr (assoc 'value datapoint)))) 1665 - (read-string (format "Value (default %s): " default-value) 1666 - nil nil (number-to-string default-value))))) 1667 - (comment (let ((comment-history 1668 - (mapcar (lambda (dp) 1669 - (cdr (assoc 'comment dp))) 1670 - datapoints))) 1671 - (read-string "Comment: " 1672 - (cdr (assoc 'comment datapoint)) 1673 - (cons 'comment-history 1674 - (1+ (position 1675 - id 1676 - datapoints 1677 - :key (lambda (dp) 1678 - (cdr (assoc 'id dp))) 1679 - :test #'string=))) 1680 - nil t)))) 1681 - (beeminder-log "updating datapoint...") 1682 - (beeminder-request-put (format "/goals/%s/datapoints/%s.json" 1683 - (beeminder-get-slug beeminder-detailed-goal) 1684 - id) 1685 - (list 1686 - (cons "value" (format "%s" value)) 1687 - (cons "comment" comment) 1688 - (cons "timestamp" (format "%s" timestamp))) 1689 - (cl-function (lambda (&rest _) (beeminder-log "updating datapoint...done"))) 1690 - (cl-function (lambda (&rest _) (beeminder-log "updating datapoint failed!" :error)))))) 1691 - 1692 - (define-key beeminder-goal-mode-map (kbd "e") #'beeminder-edit-datapoint) 1693 - 1694 - (defun beeminder-display-raw-goal-details () 1695 - "Display the raw details about GOAL in a temporary buffer. 1696 - The internal representation is an alist." 1697 - (interactive) 1698 - (let ((goal beeminder-detailed-goal)) 1699 - (pop-to-buffer "*Beeminder raw goal details*") 1700 - (let ((inhibit-read-only t)) 1701 - (erase-buffer) 1702 - (insert (pp-to-string goal)) 1703 - (goto-char (point-min)) 1704 - (special-mode)))) 1705 - 1706 - (define-key beeminder-goal-mode-map (kbd ".") #'beeminder-display-raw-goal-details) 1707 - 1708 - (defun beeminder-view-in-browser (goal) 1709 - "View GOAL in the web browser." 1710 - (interactive (list (or beeminder-detailed-goal (current-or-read-goal)))) 1711 - (browse-url (format "https://beeminder.com/%s/%s" beeminder-username (beeminder-alist-get 'slug goal)))) 1712 - 1713 - (define-key beeminder-mode-map (kbd "W") #'beeminder-view-in-browser) 1714 - (define-key beeminder-goal-mode-map (kbd "W") #'beeminder-view-in-browser) 1715 - 1716 - 1717 - ;; Downloading more datapoints 1718 - (defun beeminder-download-datapoints (slug-str days) 1719 - "Download datapoints for goal named SLUG-STR from last DAYS. 1720 - If called interactively in the *Beeminder goal details* buffer, use 1721 - current goal; otherwise, ask for the goal. If called without a prefix 1722 - argument, increase the downloaded history by 1723 - `beeminder-history-length' days." 1724 - (interactive (list (if beeminder-detailed-goal 1725 - (cdr (assoc 'slug beeminder-detailed-goal)) 1726 - (cdr (assoc 'slug (current-or-read-goal)))) 1727 - current-prefix-arg)) 1728 - (setq days 1729 - (if days 1730 - (prefix-numeric-value days) 1731 - beeminder-history-length)) 1732 - (setq days 1733 - (cond ((> days 0) 1734 - (+ (cdr (assoc 'history-length (beeminder-slug-to-goal (intern slug-str)))) 1735 - days)) 1736 - ((< days 0) 1737 - (- days)) 1738 - (t 0))) 1739 - (beeminder-log (format "fetching datapoints for goal %s..." slug-str)) 1740 - (beeminder-request-get (format "/goals/%s.json" slug-str) 1741 - (cons (cons "datapoints" "true") 1742 - (unless (zerop days) 1743 - (cons (cons "diff_since" 1744 - (number-to-string 1745 - (- (last-user-midnight (beeminder-current-time)) 1746 - (* days 24 60 60)))) 1747 - nil))) 1748 - (cl-function (lambda (&key data &allow-other-keys) 1749 - (beeminder-log (format "fetching datapoints for goal %s...done" slug-str)) 1750 - (let* ((gl (beeminder-slug-to-goal (intern slug-str))) 1751 - (dp (assoc 'datapoints gl)) 1752 - (hl (assoc 'history-length gl))) 1753 - (setcdr dp (beeminder-to-list (cdr (assoc 'datapoints data)))) 1754 - (setcdr hl days) 1755 - (when beeminder-detailed-goal 1756 - (beeminder-refresh-goal-details))))) 1757 - (cl-function (lambda (&key error-thrown &allow-other-keys) 1758 - (beeminder-log (format "fetching datapoints for goal %s...error: %s" slug-str error-thrown)))))) 1759 - 1760 - (define-key beeminder-goal-mode-map (kbd "m") #'beeminder-download-datapoints) 1761 - 1762 - 1763 - ;; Statistics 1764 - (defun beeminder-determine-date (time day-end) 1765 - "Return date for TIME, taking DAY-END into account. 1766 - TIME is the number of seconds counted from the beginning of Unix 1767 - epoch; DAY-END is the offset from midnight in seconds. The date 1768 - is a string in ISO 8601 basic format (i.e., \"20160417\" for 1769 - April 17, 2016)." 1770 - (format-time-string "%Y%m%d" (time-subtract time day-end))) 1771 - 1772 - (defun beeminder-gather-datapoints-by-day (goal) 1773 - "Return an alist of datapoints, collated by date. 1774 - The car of each entry is a string representing the date in ISO 1775 - 8601 basic format (i.e., \"20160417\" for April 17, 2016), and 1776 - the cdr is the list of datapoints. If 1777 - `beeminder-use-goal-midnight-today-values' is nil, use the goal's 1778 - \"midnight\" setting to determine the date; otherwise, use 1779 - `beeminder-when-the-day-ends'." 1780 - (let ((day-end (if beeminder-use-goal-midnight-today-values 1781 - beeminder-when-the-day-ends 1782 - (cdr (assoc 'deadline goal)))) 1783 - datapoints-by-day) 1784 - (mapc (lambda (datapoint) 1785 - (let ((date (beeminder-determine-date (cdr (assoc 'timestamp datapoint)) day-end))) 1786 - (aif (assoc date datapoints-by-day) 1787 - (push datapoint (cdr it)) 1788 - (push (list date datapoint) datapoints-by-day)))) 1789 - (cdr (assoc 'datapoints goal))) 1790 - datapoints-by-day)) 1791 - 1792 - ;; (defun beeminder-gather-datapoints-by-day (goal) 1793 - ;; "Return an alist of datapoints, collated by date. 1794 - ;; The car of each entry is a string representing the date in ISO 1795 - ;; 8601 basic format (i.e., \"20160417\" for April 17, 2016), and 1796 - ;; the cdr is the list of datapoints. If 1797 - ;; `beeminder-use-goal-midnight-today-values' is nil, use the goal's 1798 - ;; \"midnight\" setting to determine the date; otherwise, use 1799 - ;; `beeminder-when-the-day-ends'." 1800 - ;; (let ((day-end (if beeminder-use-goal-midnight-today-values 1801 - ;; beeminder-when-the-day-ends 1802 - ;; (cdr (assoc 'deadline goal)))) 1803 - ;; datapoints-by-day) 1804 - ;; (mapc (lambda (datapoint) 1805 - ;; (let ((date (beeminder-determine-date (cdr (assoc 'timestamp datapoint)) day-end))) 1806 - ;; (aif (assoc date datapoints-by-day) 1807 - ;; (push datapoint (cdr it)) 1808 - ;; (push (list date datapoint) datapoints-by-day)))) 1809 - ;; (cdr (assoc 'datapoints goal))) 1810 - ;; datapoints-by-day)) 1811 - 1812 - (defun beeminder-uniq-mean (list) 1813 - "Return the mean of numbers in LIST after deleting 1814 - duplicates." 1815 - (let ((list list)) 1816 - (/ (apply #'+ (delete-dups list)) 1817 - (length list) 1818 - 1.0))) 1819 - 1820 - (defun beeminder-median (list) 1821 - "Return a median of numbers in LIST. 1822 - If LIST contains an even number of elements n, return 1823 - the (n/2)-th one." 1824 - (let ((list list) median-list) 1825 - (setq list (sort list #'<) 1826 - median-list list) 1827 - (while (cddr list) 1828 - (setq median-list (cdr median-list) 1829 - list (cddr list))) 1830 - (if (cdr list) 1831 - (* 0.5 (+ (car median-list) (cadr median-list))) 1832 - (car median-list)))) 1833 - 1834 - (defvar beeminder-aggregation-methods 1835 - '(("sum" . (lambda (dps) (apply #'+ dps))) 1836 - ("last" . car) ; this is no mistake - the list is in reverse order! 1837 - ("first" . (lambda (dps) (car (last dps)))) 1838 - ("min" . (lambda (dps) (apply #'min dps))) 1839 - ("max" . (lambda (dps) (apply #'max dps))) 1840 - ("truemean" . (lambda (dps) (/ (apply #'+ dps) (length dps) 1.0))) 1841 - ("uniqmean" . beeminder-uniq-mean) 1842 - ("mean" . beeminder-uniq-mean) 1843 - ("median" . beeminder-median) 1844 - ("jolly" . (lambda (dps) (if dps 1 0))) 1845 - ("binary" . (lambda (dps) (if dps 1 0))) 1846 - ("nonzero" . (lambda (dps) (if (cl-some (lambda (dp) (not (= 0 dp))) dps) 1 0))) 1847 - ("triangle" . (lambda (dps) (let ((sum (apply #'+ dps))) (* sum (1+ sum) 0.5)))) 1848 - ("square" . (lambda (dps) (let ((sum (apply #'+ dps))) (* sum sum)))) 1849 - ("count" . length)) 1850 - "An alist mapping aggregation methods to actual functions.") 1851 - 1852 - (defun beeminder-aggregate-values (values aggday) 1853 - "Aggregate VALUES (from one day) using the AGGDAY method." 1854 - (funcall (beeminder-alist-get aggday beeminder-aggregation-methods) 1855 - values)) 1856 - 1857 - (defun beeminder-aggregate-datapoints (datapoints-by-day aggday) 1858 - "Aggregate DATAPOINTS-BY-DAY using the AGGDAY method." 1859 - (mapcar (lambda (day-datapoints) 1860 - (cons (car day-datapoints) 1861 - (beeminder-aggregate-values 1862 - (mapcar (lambda (dp) (beeminder-alist-get 'value dp)) 1863 - (cdr day-datapoints)) 1864 - aggday))) 1865 - datapoints-by-day)) 1866 - 1867 - 1868 - ;; Displaying graphs 1869 - 1870 - (defun beeminder-download-graph (slug-str) 1871 - "Download graph for goal SLUG and put it in the tmp directory, 1872 - under the \"beeminder-el\" subdirectory and filename 1873 - \"SLUG.png\". Return the full filename." 1874 - (make-directory (concat temporary-file-directory "beeminder-el") t) 1875 - (let* ((image-file (concat temporary-file-directory "beeminder-el/" slug-str ".png")) 1876 - (inhibit-message t)) 1877 - (url-copy-file (beeminder-alist-get 'graph_url (beeminder-slug-to-goal (intern slug-str))) 1878 - image-file 1879 - t) 1880 - image-file)) 1881 - 1882 - (defun beeminder-insert-graph (image) 1883 - "Insert IMAGE at end-of-buffer. Remove any existing images 1884 - first and position the point and window so that the image can be 1885 - seen next." 1886 - (remove-images (point-min) (point-max)) 1887 - (goto-char (point-max)) 1888 - (let ((inhibit-read-only t)) 1889 - (remove-images (point-min) (point-max)) 1890 - (save-excursion 1891 - (insert "\n") 1892 - (put-image image (point-max) "[Graphs are not supported in this Emacs!]"))) 1893 - (let* ((size (image-size image)) 1894 - (width (ceiling (car size))) 1895 - (height (ceiling (cdr size)))) 1896 - (fit-window-to-buffer (selected-window) height height width width)) 1897 - (recenter 0)) 1898 - 1899 - (defun beeminder-display-graph (slug-str) 1900 - "Download the graph of the goal SLUG-STR and display it. 1901 - Switch to the \"details\" buffer first if needed. Do nothing if 1902 - the graph is already displayed." 1903 - (interactive (list (if beeminder-detailed-goal 1904 - (cdr (assoc 'slug beeminder-detailed-goal)) 1905 - (cdr (assoc 'slug (current-or-read-goal)))))) 1906 - (let* ((image-file (beeminder-download-graph slug-str)) 1907 - (image (create-image image-file))) 1908 - (beeminder-display-goal-details (beeminder-slug-to-goal (intern slug-str))) 1909 - (beeminder-insert-graph image))) 1910 - 1911 - (define-key beeminder-goal-mode-map (kbd "i") #'beeminder-display-graph) 1912 - (define-key beeminder-mode-map (kbd "i") #'beeminder-display-graph) 1913 - 1914 - 1915 - ;; Org-mode integration 1916 - 1917 - (defcustom beeminder-org-inherit-beeminder-properties nil 1918 - "Make beeminder.el use property inheritance.") 1919 - 1920 - (defcustom beeminder-org-default-comment "%h at %t" 1921 - "Default format of the comment") 1922 - 1923 - (defun beeminder-org-string-substitute (string) 1924 - "Substitute strings for percent-sign codes in STRING. 1925 - Codes are: `%t' - current time, `%h' - current headline, `%p' - 1926 - current path, `%%' - percent sign." 1927 - (let ((time (beeminder-current-time-string)) 1928 - (headline (substring-no-properties (org-get-heading t t))) 1929 - (path (mapconcat #'identity (org-get-outline-path t) "/"))) 1930 - (format-spec string 1931 - `((?% . "%") 1932 - (?t . ,time) 1933 - (?h . ,headline) 1934 - (?p . ,path))))) 1935 - 1936 - (defun beeminder-org-generate-comment () 1937 - "Given the comment property, generate the comment text. Assume 1938 - that the point is in the right place." 1939 - (let ((comment-prop 1940 - (or (org-entry-get (point) 1941 - "comment" 1942 - beeminder-org-inherit-beeminder-properties) 1943 - beeminder-org-default-comment))) 1944 - (cond 1945 - ((string= comment-prop "time") 1946 - (concat "via Org-mode at " (beeminder-current-time-string))) 1947 - ((string= comment-prop "ask") 1948 - nil) 1949 - ((or (string= comment-prop "headline") 1950 - (null comment-prop)) 1951 - (substring-no-properties (org-get-heading t t))) 1952 - ((string= comment-prop "path") 1953 - (mapconcat #'identity (org-get-outline-path t) "/")) 1954 - (t (beeminder-org-string-substitute comment-prop))))) 1955 - 1956 - (defun beeminder-org-submit-on-done (state-change) 1957 - "Submit a datapoint when marking an item as DONE. 1958 - This function should be placed in `org-trigger-hook'. It looks 1959 - up the following properties of the headline: the \"beeminder\" 1960 - property (which should be set to \"done\", the \"slug\" 1961 - property (which should be set to the slug of the goal), the 1962 - \"amount\" property (defaults to 1), the \"ask-comment\" 1963 - property (asks for the comment if it is present)." 1964 - (let ((position (plist-get state-change :position))) 1965 - (if (and (string= (downcase (or (org-entry-get position 1966 - "beeminder" 1967 - beeminder-org-inherit-beeminder-properties) 1968 - "")) 1969 - "done") 1970 - (eq (plist-get state-change :type) 1971 - 'todo-state-change) 1972 - (member (plist-get state-change :to) 1973 - org-done-keywords)) 1974 - (let* ((slug-str (org-entry-get position 1975 - "slug" 1976 - beeminder-org-inherit-beeminder-properties)) 1977 - (amount (aif (org-entry-get position 1978 - "amount" 1979 - beeminder-org-inherit-beeminder-properties) 1980 - (string-to-number it) 1981 - 1)) 1982 - (comment (beeminder-org-generate-comment))) 1983 - (beeminder-submit-datapoint slug-str amount comment))))) 1984 - 1985 - (defun beeminder-org-submit-clock-at-point () 1986 - "Submit the data from the clock item at point to Beeminder. 1987 - This is mainly useful if submitting on clocking out (see 1988 - `beeminder-org-submit-on-clock-out' failed for some reason, so 1989 - that the user may want to submit clock items later." 1990 - (interactive) 1991 - (let ((element (org-element-at-point))) 1992 - (if (eq (org-element-type element) 'clock) 1993 - (let ((timestamp (org-element-property :value element)) 1994 - (duration (org-element-property :duration element))) 1995 - (when (string-match "\\([[:digit:]]+\\):\\([[:digit:]]\\{2\\}\\)" duration) 1996 - (let* ((minutes (+ (* 60 (string-to-number (match-string 1 duration))) 1997 - (string-to-number (match-string 2 duration)))) 1998 - (slug-str (org-entry-get (point) 1999 - "slug" 2000 - beeminder-org-inherit-beeminder-properties)) 2001 - (comment (beeminder-org-generate-comment)) 2002 - (multiplier (cl-case (intern (or (org-entry-get (point) 2003 - "unit" 2004 - beeminder-org-inherit-beeminder-properties) 2005 - "")) 2006 - ((hour hours) 2007 - (/ 1 60.0)) 2008 - ((hail-Mary hail-Marys) 2009 - 3) 2010 - ; 1 hail-Mary ≈ 20 seconds 2011 - (t 1))) 2012 - (year-end (org-element-property :year-end timestamp)) 2013 - (month-end (org-element-property :month-end timestamp)) 2014 - (day-end (org-element-property :day-end timestamp)) 2015 - (hour-end (org-element-property :hour-end timestamp)) 2016 - (minute-end (org-element-property :minute-end timestamp)) 2017 - (id (format "%04d%02d%02d%02d%02dto%04d%02d%02d%02d%02d" 2018 - (org-element-property :year-start timestamp) 2019 - (org-element-property :month-start timestamp) 2020 - (org-element-property :day-start timestamp) 2021 - (org-element-property :hour-start timestamp) 2022 - (org-element-property :minute-start timestamp) 2023 - year-end 2024 - month-end 2025 - day-end 2026 - hour-end 2027 - minute-end)) 2028 - (timestamp (time-to-seconds (encode-time 2029 - 0 2030 - minute-end 2031 - hour-end 2032 - day-end 2033 - month-end 2034 - year-end)))) 2035 - (beeminder-submit-datapoint slug-str (* minutes multiplier) 2036 - comment 2037 - timestamp 2038 - id)))) 2039 - (beeminder-log "no clock at point!" :nolog)))) 2040 - 2041 - (defcustom beeminder-org-submit-all-clocks-default-minutes (* 24 60) 2042 - "By default, only the clocks from this many lastminutes will be 2043 - submitted by `beeminder-org-submit-all-clocks'. Does not have to 2044 - be an integer (i.e., value like 0.5 means 30 seconds).") 2045 - 2046 - (defun beeminder-org-submit-all-clocks (begin end minutes) 2047 - "Submit all clocks from last MINUTES in the region to Beeminder. 2048 - In interactive use, use region if active and current subtree 2049 - otherwise. Use with caution!" 2050 - (interactive (if (use-region-p) 2051 - (list (region-beginning) 2052 - (region-end) 2053 - current-prefix-arg) 2054 - (list nil nil current-prefix-arg))) 2055 - (unless (numberp minutes) 2056 - (setq minutes beeminder-org-submit-all-clocks-default-minutes)) 2057 - (save-excursion 2058 - (save-restriction 2059 - (narrow-to-region 2060 - (or begin (progn (org-back-to-heading t) 2061 - (point))) 2062 - (or end (progn (org-end-of-subtree t t) 2063 - (when (and (org-at-heading-p) ; see org-narrow-to-subtree 2064 - (not (eobp))) 2065 - (backward-char 1)) 2066 - (point)))) 2067 - (goto-char (point-min)) 2068 - (while (re-search-forward "CLOCK: " nil t) 2069 - (let ((eap (org-element-at-point))) 2070 - (when (and (eq (org-element-type eap) 2071 - 'clock) 2072 - (eq (org-element-property :status eap) 2073 - 'closed)) 2074 - (let ((ts (org-element-property :value eap))) 2075 - (when (< (time-to-seconds 2076 - (time-subtract (beeminder-current-time) 2077 - (encode-time 0 2078 - (org-element-property :minute-end ts) 2079 - (org-element-property :hour-end ts) 2080 - (org-element-property :day-end ts) 2081 - (org-element-property :month-end ts) 2082 - (org-element-property :year-end ts)))) 2083 - (* 60 minutes)) 2084 - (beeminder-org-submit-clock-at-point))))))))) 2085 - 2086 - (defun beeminder-org-submit-on-clock-out () 2087 - "Submit the time clocked for this item. 2088 - This function should be placed in `org-clock-out-hook'. It looks 2089 - up the following properties of the headline: the \"beeminder\" 2090 - property (which should be set to \"clock\", the \"slug\" 2091 - property (which should be set to the slug of the goal), the 2092 - \"unit\" property (which may be \"minutes\", which is the 2093 - default, or \"hours\", etc.\"), the \"ask-comment\" 2094 - property (asks for the comment if it is present)." 2095 - (when (and (string= (downcase (or (org-entry-get (point) 2096 - "beeminder" 2097 - beeminder-org-inherit-beeminder-properties) 2098 - "")) 2099 - "clock") 2100 - (not (string-match " LINE REMOVED$" (or (current-message) ""))) ; this is really hackish 2101 - (org-entry-get 2102 - (point) "slug" beeminder-org-inherit-beeminder-properties)) 2103 - (beeminder-org-submit-clock-at-point))) 2104 - 2105 - (define-minor-mode beeminder-org-integration-mode 2106 - "Toggle a (global) minor mode for Org/Beeminder integration. 2107 - When on, clocking out and marking as DONE for headlines with suitable 2108 - :beeminder: property is submitted automatically." 2109 - :init-value nil 2110 - :global t 2111 - :lighter " B-O" 2112 - (if beeminder-org-integration-mode 2113 - (progn 2114 - (add-hook 'org-trigger-hook #'beeminder-org-submit-on-done) 2115 - (add-hook 'org-clock-out-hook #'beeminder-org-submit-on-clock-out)) 2116 - (remove-hook 'org-trigger-hook #'beeminder-org-submit-on-done) 2117 - (remove-hook 'org-clock-out-hook #'beeminder-org-submit-on-clock-out))) 2118 - 2119 - 2120 - (provide 'beeminder) 2121 - 2122 - 2123 - ;;; beeminder.el ends here
-6
spacemacs-private/snippets/README.md
··· 1 - # Private directory for Yasnippets snippets 2 - 3 - The content of this directory is ignored by Git. This is the default place 4 - where to store your private yasnippets. 5 - 6 - This path will be loaded automatically and used whenever Yasnippets loads.
-17
spacemacs-private/snippets/haskell-mode/haddock-module-desc
··· 1 - # -*- mode: snippet -*- 2 - # name: haskell haddock module description 3 - # key: had.m 4 - # expand-env: ((yas-indent-line 'fixed)) 5 - # -- 6 - --------------------------------- 7 - -- | 8 - -- Module : `(file-name-sans-extension (buffer-name))` 9 - -- Copyright : (C) Kaushik Chakraborty, `(format-time-string "%Y")` 10 - -- License : Apache v2 (see the file LICENSE) 11 - -- Maintainer : Kaushik Chakraborty <git@kaushikc.org> 12 - -- Stability : experimental 13 - --${1: 14 - -- ${2:description} 15 - --} 16 - --------------------------------- 17 - $0
-12
spacemacs-private/snippets/markdown-mode/blog-header
··· 1 - # -*- mode: snippet -*- 2 - # name: blog metadata header 3 - # key: bh 4 - # -- 5 - --- 6 - title : $1 7 - published : ${2:`(format-time-string "%Y-%m-%d")`} 8 - tags : $3 9 - link : $4 10 - --- 11 - 12 - $0
-5
spacemacs-private/snippets/markdown-mode/blog-sports-tags
··· 1 - # -*- mode: snippet -*- 2 - # name: blog-sports-tags-world-cup 3 - # key: btswc 4 - # -- 5 - sports, football, world-cup, wc-${1:`(format-time-string "%Y")`}
-9
spacemacs-private/snippets/markdown-mode/micro-blog-header
··· 1 - # -*- mode: snippet -*- 2 - # name: micro blog header 3 - # key: mbh 4 - # -- 5 - --- 6 - published : `(format-time-string "%Y-%m-%d %H:%M:%S%z")` 7 - --- 8 - 9 - $0
-40
spacemacs-private/spacemacs.env
··· 1 - ]1337;RemoteHost=kaushik@AMB00472]1337;CurrentDir=/Users/kaushik]1337;ShellIntegrationVersion=5;shell=zshPWD=/Users/kaushik 2 - LC_ALL=en_US.UTF-8 3 - LANG=en_US.UTF-8 4 - LSCOLORS=Gxfxcxdxbxegedabagacad 5 - LESS=-R 6 - PAGER=less 7 - NIX_PATH=/nix/var/nix/profiles/per-user/root/channels 8 - NIX_SSL_CERT_FILE=/nix/var/nix/profiles/default/etc/ssl/certs/ca-bundle.crt 9 - NIX_PROFILES=/nix/var/nix/profiles/default /Users/kaushik/.nix-profile 10 - NIX_USER_PROFILE_DIR=/nix/var/nix/profiles/per-user/kaushik 11 - NIX_REMOTE=daemon 12 - PERL_LWP_SSL_VERIFY_HOSTNAME=0 13 - NO_PROXY=localhost,127.0.0.1,10.154.194.1,10.154.194.2,10.154.194.3,10.154.194.4,10.154.194.5,10.154.194.6,10.154.194.7,10.154.194.8,10.154.194.9,10.154.194.10,10.154.194.11,10.154.194.12,10.154.194.13,10.154.194.14,10.154.194.15,10.154.194.16,10.154.194.17,10.154.194.18,10.154.194.19,10.154.194.20,10.154.194.21,10.154.194.22,10.154.194.23,10.154.194.24,10.154.194.25,10.154.194.26,10.154.194.27,10.154.194.28,10.154.194.29,10.154.194.30,10.154.194.31,10.154.194.32,10.154.194.33,10.154.194.34,10.154.194.35,10.154.194.36,10.154.194.37,10.154.194.38,10.154.194.39,10.154.194.40,10.154.194.41,10.154.194.42,10.154.194.43,10.154.194.44,10.154.194.45,10.154.194.46,10.154.194.47,10.154.194.48,10.154.194.49,10.154.194.50,10.154.194.51,10.154.194.52,10.154.194.53,10.154.194.54,10.154.194.55,10.154.194.56,10.154.194.57,10.154.194.58,10.154.194.59,10.154.194.60,10.154.194.61,10.154.194.62,10.154.194.63,10.154.194.64,10.154.194.65,10.154.194.66,10.154.194.67,10.154.194.68,10.154.194.69,10.154.194.70,10.154.194.71,10.154.194.72,10.154.194.73,10.154.194.74,10.154.194.75,10.154.194.76,10.154.194.77,10.154.194.78,10.154.194.79,10.154.194.80,10.154.194.81,10.154.194.82,10.154.194.83,10.154.194.84,10.154.194.85,10.154.194.86,10.154.194.87,10.154.194.88,10.154.194.89,10.154.194.90,10.154.194.91,10.154.194.92,10.154.194.93,10.154.194.94,10.154.194.95,10.154.194.96,10.154.194.97,10.154.194.98,10.154.194.99,10.154.194.100,10.154.194.101,10.154.194.102,10.154.194.103,10.154.194.104,10.154.194.105,10.154.194.106,10.154.194.107,10.154.194.108,10.154.194.109,10.154.194.110,10.154.194.111,10.154.194.112,10.154.194.113,10.154.194.114,10.154.194.115,10.154.194.116,10.154.194.117,10.154.194.118,10.154.194.119,10.154.194.120,10.154.194.121,10.154.194.122,10.154.194.123,10.154.194.124,10.154.194.125,10.154.194.126,10.154.194.127,10.154.194.128,10.154.194.129,10.154.194.130,10.154.194.131,10.154.194.132,10.154.194.133,10.154.194.134,10.154.194.135,10.154.194.136,10.154.194.137,10.154.194.138,10.154.194.139,10.154.194.140,10.154.194.141,10.154.194.142,10.154.194.143,10.154.194.144,10.154.194.145,10.154.194.146,10.154.194.147,10.154.194.148,10.154.194.149,10.154.194.150,10.154.194.151,10.154.194.152,10.154.194.153,10.154.194.154,10.154.194.155,10.154.194.156,10.154.194.157,10.154.194.158,10.154.194.159,10.154.194.160,10.154.194.161,10.154.194.162,10.154.194.163,10.154.194.164,10.154.194.165,10.154.194.166,10.154.194.167,10.154.194.168,10.154.194.169,10.154.194.170,10.154.194.171,10.154.194.172,10.154.194.173,10.154.194.174,10.154.194.175,10.154.194.176,10.154.194.177,10.154.194.178,10.154.194.179,10.154.194.180,10.154.194.181,10.154.194.182,10.154.194.183,10.154.194.184,10.154.194.185,10.154.194.186,10.154.194.187,10.154.194.188,10.154.194.189,10.154.194.190,10.154.194.191,10.154.194.192,10.154.194.193,10.154.194.194,10.154.194.195,10.154.194.196,10.154.194.197,10.154.194.198,10.154.194.199,10.154.194.200,10.154.194.201,10.154.194.202,10.154.194.203,10.154.194.204,10.154.194.205,10.154.194.206,10.154.194.207,10.154.194.208,10.154.194.209,10.154.194.210,10.154.194.211,10.154.194.212,10.154.194.213,10.154.194.214,10.154.194.215,10.154.194.216,10.154.194.217,10.154.194.218,10.154.194.219,10.154.194.220,10.154.194.221,10.154.194.222,10.154.194.223,10.154.194.224,10.154.194.225,10.154.194.226,10.154.194.227,10.154.194.228,10.154.194.229,10.154.194.230,10.154.194.231,10.154.194.232,10.154.194.233,10.154.194.234,10.154.194.235,10.154.194.236,10.154.194.237,10.154.194.238,10.154.194.239,10.154.194.240,10.154.194.241,10.154.194.242,10.154.194.243,10.154.194.244,10.154.194.245,10.154.194.246,10.154.194.247,10.154.194.248,10.154.194.249,10.154.194.250,10.154.194.251,10.154.194.252,10.154.194.253,10.154.194.254,10.154.194.255,10.154.198.1,10.154.198.2,10.154.198.3,10.154.198.4,10.154.198.5,10.154.198.6,10.154.198.7,10.154.198.8,10.154.198.9,10.154.198.10,10.154.198.11,10.154.198.12,10.154.198.13,10.154.198.14,10.154.198.15,10.154.198.16,10.154.198.17,10.154.198.18,10.154.198.19,10.154.198.20,10.154.198.21,10.154.198.22,10.154.198.23,10.154.198.24,10.154.198.25,10.154.198.26,10.154.198.27,10.154.198.28,10.154.198.29,10.154.198.30,10.154.198.31,10.154.198.32,10.154.198.33,10.154.198.34,10.154.198.35,10.154.198.36,10.154.198.37,10.154.198.38,10.154.198.39,10.154.198.40,10.154.198.41,10.154.198.42,10.154.198.43,10.154.198.44,10.154.198.45,10.154.198.46,10.154.198.47,10.154.198.48,10.154.198.49,10.154.198.50,10.154.198.51,10.154.198.52,10.154.198.53,10.154.198.54,10.154.198.55,10.154.198.56,10.154.198.57,10.154.198.58,10.154.198.59,10.154.198.60,10.154.198.61,10.154.198.62,10.154.198.63,10.154.198.64,10.154.198.65,10.154.198.66,10.154.198.67,10.154.198.68,10.154.198.69,10.154.198.70,10.154.198.71,10.154.198.72,10.154.198.73,10.154.198.74,10.154.198.75,10.154.198.76,10.154.198.77,10.154.198.78,10.154.198.79,10.154.198.80,10.154.198.81,10.154.198.82,10.154.198.83,10.154.198.84,10.154.198.85,10.154.198.86,10.154.198.87,10.154.198.88,10.154.198.89,10.154.198.90,10.154.198.91,10.154.198.92,10.154.198.93,10.154.198.94,10.154.198.95,10.154.198.96,10.154.198.97,10.154.198.98,10.154.198.99,10.154.198.100,10.154.198.101,10.154.198.102,10.154.198.103,10.154.198.104,10.154.198.105,10.154.198.106,10.154.198.107,10.154.198.108,10.154.198.109,10.154.198.110,10.154.198.111,10.154.198.112,10.154.198.113,10.154.198.114,10.154.198.115,10.154.198.116,10.154.198.117,10.154.198.118,10.154.198.119,10.154.198.120,10.154.198.121,10.154.198.122,10.154.198.123,10.154.198.124,10.154.198.125,10.154.198.126,10.154.198.127,10.154.198.128,10.154.198.129,10.154.198.130,10.154.198.131,10.154.198.132,10.154.198.133,10.154.198.134,10.154.198.135,10.154.198.136,10.154.198.137,10.154.198.138,10.154.198.139,10.154.198.140,10.154.198.141,10.154.198.142,10.154.198.143,10.154.198.144,10.154.198.145,10.154.198.146,10.154.198.147,10.154.198.148,10.154.198.149,10.154.198.150,10.154.198.151,10.154.198.152,10.154.198.153,10.154.198.154,10.154.198.155,10.154.198.156,10.154.198.157,10.154.198.158,10.154.198.159,10.154.198.160,10.154.198.161,10.154.198.162,10.154.198.163,10.154.198.164,10.154.198.165,10.154.198.166,10.154.198.167,10.154.198.168,10.154.198.169,10.154.198.170,10.154.198.171,10.154.198.172,10.154.198.173,10.154.198.174,10.154.198.175,10.154.198.176,10.154.198.177,10.154.198.178,10.154.198.179,10.154.198.180,10.154.198.181,10.154.198.182,10.154.198.183,10.154.198.184,10.154.198.185,10.154.198.186,10.154.198.187,10.154.198.188,10.154.198.189,10.154.198.190,10.154.198.191,10.154.198.192,10.154.198.193,10.154.198.194,10.154.198.195,10.154.198.196,10.154.198.197,10.154.198.198,10.154.198.199,10.154.198.200,10.154.198.201,10.154.198.202,10.154.198.203,10.154.198.204,10.154.198.205,10.154.198.206,10.154.198.207,10.154.198.208,10.154.198.209,10.154.198.210,10.154.198.211,10.154.198.212,10.154.198.213,10.154.198.214,10.154.198.215,10.154.198.216,10.154.198.217,10.154.198.218,10.154.198.219,10.154.198.220,10.154.198.221,10.154.198.222,10.154.198.223,10.154.198.224,10.154.198.225,10.154.198.226,10.154.198.227,10.154.198.228,10.154.198.229,10.154.198.230,10.154.198.231,10.154.198.232,10.154.198.233,10.154.198.234,10.154.198.235,10.154.198.236,10.154.198.237,10.154.198.238,10.154.198.239,10.154.198.240,10.154.198.241,10.154.198.242,10.154.198.243,10.154.198.244,10.154.198.245,10.154.198.246,10.154.198.247,10.154.198.248,10.154.198.249,10.154.198.250,10.154.198.251,10.154.198.252,10.154.198.253,10.154.198.254,10.154.198.255 14 - AWS_PROFILE=kube-aws-admin 15 - COURSIER_CACHE=/Users/kaushik/Developer/repo/sbt/coursier/ 16 - ZSH=/Users/kaushik/.oh-my-zsh 17 - OLDPWD=/Users/kaushik 18 - SHLVL=1 19 - ITERM_SESSION_ID=w2t1p0:A0182136-1AD9-4C15-AB89-38DE7ECB78D6 20 - __CF_USER_TEXT_ENCODING=0x1F5:0x0:0x0 21 - LOGNAME=kaushik 22 - XPC_SERVICE_NAME=0 23 - USER=kaushik 24 - TMPDIR=/var/folders/4m/8lbd4pzn2z73b5zjzbbpc0tc0000gn/T/ 25 - HOME=/Users/kaushik 26 - TERM=xterm-256color 27 - COLORTERM=truecolor 28 - DISPLAY=/private/tmp/com.apple.launchd.p4S0G7dI7F/org.macosforge.xquartz:0 29 - PATH=/Users/kaushik/.local/bin/luna-studio:/Users/kaushik/bin:/Users/kaushik/.local/bin:/usr/local/bin:/Users/kaushik/.nix-profile/bin:/Users/kaushik/.nix-profile/lib/kde4/libexec:/nix/var/nix/profiles/default/bin:/nix/var/nix/profiles/default:/nix/var/nix/profiles/default/lib/kde4/libexec:/Users/kaushik/Library/Python/2.7/bin:/opt/local/bin:/Users/kaushik/bin:/Users/kaushik/.local/bin:/usr/local/bin:/Users/kaushik/.local/bin/luna-studio:/Users/kaushik/bin:/Users/kaushik/.local/bin:/usr/local/bin:/Users/kaushik/.nix-profile/bin:/Users/kaushik/.nix-profile/lib/kde4/libexec:/nix/var/nix/profiles/default/bin:/nix/var/nix/profiles/default:/nix/var/nix/profiles/default/lib/kde4/libexec:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:/Library/TeX/texbin:/usr/local/MacGPG2/bin:/opt/X11/bin:/Users/kaushik/Library/Python/2.7/bin:/opt/local/bin:/Users/kaushik/bin:/Users/kaushik/.local/bin:/Applications/Emacs.app/Contents/MacOS/bin-x86_64-10_9:/Applications/Emacs.app/Contents/MacOS/libexec-x86_64-10_9 30 - TERM_PROGRAM=iTerm.app 31 - TERM_PROGRAM_VERSION=3.1.6 32 - LC_CTYPE=UTF-8 33 - SHELL=/bin/zsh 34 - XPC_FLAGS=0x0 35 - ITERM_PROFILE=Default 36 - COLORFGBG=11;15 37 - Apple_PubSub_Socket_Render=/private/tmp/com.apple.launchd.L8tyLG5nky/Render 38 - SSH_AUTH_SOCK=/private/tmp/com.apple.launchd.ThFPgG6osR/Listeners 39 - TERM_SESSION_ID=w2t1p0:A0182136-1AD9-4C15-AB89-38DE7ECB78D6 40 - _=/usr/bin/env
-6
spacemacs-private/templates/README.md
··· 1 - # Private directory for Yatemplate templates 2 - 3 - The content of this directory is ignored by Git. This is the default place 4 - where to store your private templates. 5 - 6 - This path will be loaded automatically and used whenever Yatemplate loads.