···11-My Emacs configuration which is, right now, [spacemacs (develop)](https://github.com/syl20bnr/spacemacs/tree/develop).
22-This prohect is used by my [nix-config](https://github.com/kaychaks/nix-config).
11+Doom Configs
+229
doom-config/config.el
···11+;;; $DOOMDIR/config.el -*- lexical-binding: t; -*-
22+33+;; Place your private configuration here! Remember, you do not need to run 'doom
44+;; sync' after modifying this file!
55+66+77+;; Some functionality uses this to identify you, e.g. GPG configuration, email
88+;; clients, file templates and snippets.
99+(setq user-full-name "Kaushik Chakraborty"
1010+ user-mail-address "kaushik.chakraborty3@cognizant.com")
1111+1212+;; Doom exposes five (optional) variables for controlling fonts in Doom. Here
1313+;; are the three important ones:
1414+;;
1515+;; + `doom-font'
1616+;; + `doom-variable-pitch-font'
1717+;; + `doom-big-font' -- used for `doom-big-font-mode'; use this for
1818+;; presentations or streaming.
1919+;;
2020+;; They all accept either a font-spec, font string ("Input Mono-12"), or xlfd
2121+;; font string. You generally only need these two:
2222+(setq doom-font (font-spec :family "JetBrains Mono" :size 16))
2323+2424+;; There are two ways to load a theme. Both assume the theme is installed and
2525+;; available. You can either set `doom-theme' or manually load a theme with the
2626+;; `load-theme' function. This is the default:
2727+(setq doom-theme 'doom-one)
2828+2929+3030+(add-to-list 'default-frame-alist '(inhibit-double-buffering . t))
3131+(add-to-list 'default-frame-alist '(fullscreen . maximized))
3232+3333+3434+(setq evil-split-window-below t
3535+ evil-vsplit-window-right t)
3636+3737+3838+;; If you use `org' and don't want your org files in the default location below,
3939+;; change `org-directory'. It must be set before org loads!
4040+4141+(defun org-todo-age-time (&optional pos)
4242+ (let ((stamp (org-entry-get (or pos (point)) "CREATED" t)))
4343+ (when stamp
4444+ (time-subtract (current-time)
4545+ (org-time-string-to-time
4646+ (org-entry-get (or pos (point)) "CREATED" t))))))
4747+(defun org-todo-age (&optional pos)
4848+ (let ((days (time-to-number-of-days (org-todo-age-time pos))))
4949+ (cond
5050+ ((< days 1) "today")
5151+ ((< days 7) (format "%dd" days))
5252+ ((< days 30) (format "%.1fw" (/ days 7.0)))
5353+ ((< days 358) (format "%.1fM" (/ days 30.0)))
5454+ (t (format "%.1fY" (/ days 365.0))))))
5555+5656+(set-popup-rule! "^\\*Org Agenda" :ignore t)
5757+5858+(after! org
5959+ (setq org-directory "~/developer/src/personal/notes"
6060+ org-default-notes-file (concat org-directory "/inbox.org")
6161+ org-agenda-files (list
6262+ org-directory
6363+ )
6464+ org-todo-keywords '((sequence
6565+ "TODO(t)"
6666+ "RECUR(R)"
6767+ "PROJECT(P)"
6868+ "NOTE(n@)"
6969+ "STARTED(s@/!)"
7070+ "WAITING(w@)"
7171+ "|"
7272+ "DONE(d!)"
7373+ "SOMEDAY(y!)"
7474+ "CANCELLED(c@)"
7575+ "DEFERRED(r@)"
7676+ ))
7777+7878+ org-todo-keyword-faces (quote (
7979+ ("TODO" :foreground "#00BFFF" :weight bold)
8080+ ("RECUR" :foreground "cornflowerblue" :weight bold)
8181+ ("NOTE" :foreground "brown" :weight bold)
8282+ ("STARTED" :foreground "#FF8247" :weight bold)
8383+ ("WAITING" :foreground "#EE6363" :weight bold)
8484+ ("DEFERRED" :foreground "#4876FF" :weight bold)
8585+ ("SOMEDAY" :foreground "#EEDC82" :weight bold)
8686+ ("PROJECT" :foreground "#088e8e" :weight bold)
8787+ ))
8888+ org-todo-repeat-to-state "TODO"
8989+ org-pretty-entities t
9090+ org-use-tag-inheritance nil
9191+9292+ org-agenda-ndays 1
9393+ org-agenda-show-all-dates t
9494+ org-agenda-start-on-weekday nil
9595+ org-agenda-tags-column -100
9696+9797+ org-archive-location "TODO-archive::"
9898+ org-archive-save-context-info (quote (time category itags)))
9999+100100+ (setq org-capture-templates
101101+ '(
102102+ ("a" "Add Task"
103103+ entry
104104+ (file (lambda () (concat org-directory "/inbox.org")))
105105+ "* TODO %?\n:PROPERTIES:\n:ID: %(shell-command-to-string \"uuidgen\"):CREATED: %U\n:END:" :prepend t)
106106+107107+ ("m" "New Micro Blog"
108108+ plain
109109+ (file (lambda ()
110110+ (expand-file-name (concat (format-time-string "%Y%m%d%H%M%S")
111111+ ".md")
112112+ "~/developer/src/personal/blog/micro-posts/")))
113113+ "---\npublished : %<%Y-%m-%d %H:%M:%S%z>\n---\n\n%c%?")
114114+115115+ ("n" "New Note"
116116+ entry
117117+ (file (lambda() (concat org-directory "/notes.org") ))
118118+ "* NOTE %?\n:PROPERTIES:\n:ID: %(shell-command-to-string \"uuidgen\"):CREATED: %U\n:END:" :prepend t)
119119+120120+ ;;;
121121+ ;;; FROM DOOM DEFAULT TEMPLATES
122122+ ;;;
123123+124124+ ;; Will use {project-root}/{todo,notes,changelog}.org, unless a
125125+ ;; {todo,notes,changelog}.org file is found in a parent directory.
126126+ ;; Uses the basename from `+org-capture-todo-file',
127127+ ;; `+org-capture-changelog-file' and `+org-capture-notes-file'.
128128+ ("p" "Templates for projects")
129129+ ("pt" "Project-local todo" entry ; {project-root}/todo.org
130130+ (file+headline +org-capture-project-todo-file "Inbox")
131131+ "* TODO %?\n%i\n%a" :prepend t)
132132+ ("pn" "Project-local notes" entry ; {project-root}/notes.org
133133+ (file+headline +org-capture-project-notes-file "Inbox")
134134+ "* %U %?\n%i\n%a" :prepend t)
135135+ ("pc" "Project-local changelog" entry ; {project-root}/changelog.org
136136+ (file+headline +org-capture-project-changelog-file "Unreleased")
137137+ "* %U %?\n%i\n%a" :prepend t))
138138+ )
139139+140140+ (setq org-agenda-custom-commands
141141+ (quote
142142+ (
143143+ ("P" "All Projects" todo-tree "PROJECT"
144144+ ((org-agenda-overriding-header "All Projects")))
145145+ ("A" "Priority #A tasks\"" agenda ""
146146+ ((org-agenda-span
147147+ (quote day))
148148+ (org-agenda-overriding-header "Today's priority #A tasks: ")
149149+ (org-agenda-skip-function
150150+ (quote
151151+ (org-agenda-skip-entry-if
152152+ (quote notregexp)
153153+ "\\=.*\\[#A\\]")))))
154154+ ("b" "Priority #A and #B tasks" agenda ""
155155+ ((org-agenda-span
156156+ (quote day))
157157+ (org-agenda-overriding-header "Today's priority #A and #B tasks: ")
158158+ (org-agenda-skip-function
159159+ (quote
160160+ (org-agenda-skip-entry-if
161161+ (quote regexp)
162162+ "\\=.*\\[#C\\]")))))
163163+ ("u" "Unscheduled tasks" tags "TODO<>\"\"&TODO<>{DONE\\|CANCELLED\\|DEFERRED\\|SOMEDAY\\|PROJECT\\|NOTE}"
164164+ ((org-agenda-overriding-header "Unscheduled tasks: ")
165165+ (org-agenda-skip-function
166166+ (quote
167167+ (org-agenda-skip-entry-if
168168+ (quote scheduled)
169169+ (quote deadline)
170170+ (quote timestamp)
171171+ )))
172172+ (org-agenda-sorting-strategy
173173+ (quote
174174+ (user-defined-up)))
175175+ (org-agenda-prefix-format "%-11c%5(org-todo-age) ")))
176176+ ("U" "Deferred tasks" tags "TODO=\"DEFERRED\""
177177+ ((org-agenda-overriding-header "Deferred tasks:")
178178+ (org-agenda-sorting-strategy
179179+ (quote
180180+ (user-defined-up)))
181181+ (org-agenda-prefix-format "%-11c%5(org-todo-age) ")))
182182+ ("Y" "Someday tasks" tags "TODO=\"SOMEDAY\""
183183+ ((org-agenda-overriding-header "Someday tasks:")
184184+ (org-agenda-sorting-strategy
185185+ (quote
186186+ (user-defined-up)))
187187+ (org-agenda-prefix-format "%-11c%5(org-todo-age) ")))
188188+ ("S" "Scheduled tasks" tags "TODO<>\"\"&TODO<>{DONE\\|CANCELLED\\|NOTE\\|PROJECT\\|DEFERRED}&STYLE<>\"habit\""
189189+ ((org-agenda-overriding-header "Scheduled tasks: ")
190190+ (org-agenda-skip-function
191191+ (quote
192192+ (org-agenda-skip-entry-if
193193+ (quote notscheduled))))
194194+ (org-agenda-sorting-strategy
195195+ (quote
196196+ (category-up)))))
197197+ ))))
198198+199199+200200+201201+;; This determines the style of line numbers in effect. If set to `nil', line
202202+;; numbers are disabled. For relative line numbers, set this to `relative'.
203203+(setq display-line-numbers-type t)
204204+205205+;; projectile
206206+(setq projectile-project-search-path '("~/developer/src/personal/"
207207+ "~/developer/src/work/"))
208208+209209+;; Haskell
210210+(setq haskell-process-type 'cabal-new-repl
211211+ lsp-haskell-process-path-hie "ghcide"
212212+ lsp-haskell-process-args-hie '())
213213+214214+;; Here are some additional functions/macros that could help you configure Doom:
215215+;;
216216+;; - `load!' for loading external *.el files relative to this one
217217+;; - `use-package' for configuring packages
218218+;; - `after!' for running code after a package has loaded
219219+;; - `add-load-path!' for adding directories to the `load-path', relative to
220220+;; this file. Emacs searches the `load-path' when you load packages with
221221+;; `require' or `use-package'.
222222+;; - `map!' for binding new keys
223223+;;
224224+;; To get information about any of these functions/macros, move the cursor over
225225+;; the highlighted symbol at press 'K' (non-evil users must press 'C-c g k').
226226+;; This will open documentation for it, including demos of how they are used.
227227+;;
228228+;; You can also try 'gd' (or 'C-c g d') to jump to their definition and see how
229229+;; they are implemented.
+180
doom-config/init.el
···11+;;; init.el -*- lexical-binding: t; -*-
22+33+;; This file controls what Doom modules are enabled and what order they load in.
44+;; Remember to run 'doom sync' after modifying it!
55+66+;; NOTE Press 'SPC h d h' (or 'C-h d h' for non-vim users) to access Doom's
77+;; documentation. There you'll find information about all of Doom's modules
88+;; and what flags they support.
99+1010+;; NOTE Move your cursor over a module's name (or its flags) and press 'K' (or
1111+;; 'C-c g k' for non-vim users) to view its documentation. This works on
1212+;; flags as well (those symbols that start with a plus).
1313+;;
1414+;; Alternatively, press 'gd' (or 'C-c g d') on a module to browse its
1515+;; directory (for easy access to its source code).
1616+1717+(doom! :input
1818+ ;;chinese
1919+ ;;japanese
2020+2121+ :completion
2222+ (company +childframe) ; the ultimate code completion backend
2323+ ;;helm ; the *other* search engine for love and life
2424+ ;;ido ; the other *other* search engine...
2525+ (ivy +fuzzy +icons) ; a search engine for love and life
2626+2727+ :ui
2828+ ;;deft ; notational velocity for Emacs
2929+ doom ; what makes DOOM look the way it does
3030+ doom-dashboard ; a nifty splash screen for Emacs
3131+ doom-quit ; DOOM quit-message prompts when you quit Emacs
3232+ ;;fill-column ; a `fill-column' indicator
3333+ hl-todo ; highlight TODO/FIXME/NOTE/DEPRECATED/HACK/REVIEW
3434+ ;;hydra
3535+ ;;indent-guides ; highlighted indent columns
3636+ modeline ; snazzy, Atom-inspired modeline, plus API
3737+ nav-flash ; blink the current line after jumping
3838+ ;;neotree ; a project drawer, like NERDTree for vim
3939+ ophints ; highlight the region an operation acts on
4040+ (popup +all +defaults) ; tame sudden yet inevitable temporary windows
4141+ ;;pretty-code ; replace bits of code with pretty symbols
4242+ ;;tabs ; an tab bar for Emacs
4343+ treemacs ; a project drawer, like neotree but cooler
4444+ ;;unicode ; extended unicode support for various languages
4545+ vc-gutter ; vcs diff in the fringe
4646+ vi-tilde-fringe ; fringe tildes to mark beyond EOB
4747+ window-select ; visually switch windows
4848+ workspaces ; tab emulation, persistence & separate workspaces
4949+ ;;zen ; distraction-free coding or writing
5050+5151+ :editor
5252+ (evil +everywhere); come to the dark side, we have cookies
5353+ file-templates ; auto-snippets for empty files
5454+ fold ; (nigh) universal code folding
5555+ format ; automated prettiness
5656+ ;;god ; run Emacs commands without modifier keys
5757+ ;;lispy ; vim for lisp, for people who don't like vim
5858+ ;;multiple-cursors ; editing in many places at once
5959+ ;;objed ; text object editing for the innocent
6060+ ;;parinfer ; turn lisp into python, sort of
6161+ ;;rotate-text ; cycle region at point between text candidates
6262+ snippets ; my elves. They type so I don't have to
6363+ word-wrap ; soft wrapping with language-aware indent
6464+6565+ :emacs
6666+ dired ; making dired pretty [functional]
6767+ electric ; smarter, keyword-based electric-indent
6868+ ;;ibuffer ; interactive buffer management
6969+ vc ; version-control and Emacs, sitting in a tree
7070+7171+ :term
7272+ eshell ; a consistent, cross-platform shell (WIP)
7373+ ;;shell ; a terminal REPL for Emacs
7474+ term ; terminals in Emacs
7575+ ;;vterm ; another terminals in Emacs
7676+7777+ :checkers
7878+ syntax ; tasing you for every semicolon you forget
7979+ spell ; tasing you for misspelling mispelling
8080+ grammar ; tasing grammar mistake every you make
8181+8282+ :tools
8383+ ;;ansible
8484+ ;;debugger ; FIXME stepping through code, to help you add bugs
8585+ direnv
8686+ docker
8787+ ;;editorconfig ; let someone else argue about tabs vs spaces
8888+ ;;ein ; tame Jupyter notebooks with emacs
8989+ (eval +overlay) ; run code, run (also, repls)
9090+ gist ; interacting with github gists
9191+ (lookup +dictionary +offline) ; navigate your code and its documentation
9292+ (lsp +peek)
9393+ macos ; MacOS-specific commands
9494+ magit ; a git porcelain for Emacs
9595+ make ; run make tasks from Emacs
9696+ ;;pass ; password manager for nerds
9797+ ;;pdf ; pdf enhancements
9898+ ;;prodigy ; FIXME managing external services & code builders
9999+ ;;rgb ; creating color strings
100100+ ;;terraform ; infrastructure as code
101101+ ;;tmux ; an API for interacting with tmux
102102+ ;;upload ; map local to remote projects via ssh/ftp
103103+104104+ :lang
105105+ ;;agda ; types of types of types of types...
106106+ ;;assembly ; assembly for fun or debugging
107107+ cc ; C/C++/Obj-C madness
108108+ ;;clojure ; java with a lisp
109109+ ;;common-lisp ; if you've seen one lisp, you've seen them all
110110+ ;;coq ; proofs-as-programs
111111+ ;;crystal ; ruby at the speed of c
112112+ ;;csharp ; unity, .NET, and mono shenanigans
113113+ data ; config/data formats
114114+ ;;elixir ; erlang done right
115115+ ;;elm ; care for a cup of TEA?
116116+ emacs-lisp ; drown in parentheses
117117+ ;;erlang ; an elegant language for a more civilized age
118118+ ;;ess ; emacs speaks statistics
119119+ ;;faust ; dsp, but you get to keep your soul
120120+ ;;fsharp ; ML stands for Microsoft's Language
121121+ ;;fstar ; (dependent) types and (monadic) effects and Z3
122122+ ;;go ; the hipster dialect
123123+ (haskell +lsp) ; a language that's lazier than I am
124124+ ;;hy ; readability of scheme w/ speed of python
125125+ ;;idris ;
126126+ ;;(java +meghanada) ; the poster child for carpal tunnel syndrome
127127+ javascript ; all(hope(abandon(ye(who(enter(here))))))
128128+ ;;julia ; a better, faster MATLAB
129129+ ;;kotlin ; a better, slicker Java(Script)
130130+ latex ; writing papers in Emacs has never been so fun
131131+ lean
132132+ ;;factor
133133+ ;;ledger ; an accounting system in Emacs
134134+ ;;lua ; one-based indices? one-based indices
135135+ (markdown +grip) ; writing docs for people to ignore
136136+ ;;nim ; python + lisp at the speed of c
137137+ ;;nix ; I hereby declare "nix geht mehr!"
138138+ ;;ocaml ; an objective camel
139139+ (org ; organize your plain life in plain text
140140+ +dragndrop ; drag & drop files/images into org buffers
141141+ +habit
142142+ +protocol
143143+ ;;+hugo ; use Emacs for hugo blogging
144144+ ;;+jupyter ; ipython/jupyter support for babel
145145+ +pandoc ; export-with-pandoc support
146146+ ;;+pomodoro ; be fruitful with the tomato technique
147147+ +present) ; using org-mode for presentations
148148+ ;;perl ; write code no one else can comprehend
149149+ ;;php ; perl's insecure younger brother
150150+ plantuml ; diagrams for confusing people more
151151+ ;;purescript ; javascript, but functional
152152+ ;;(python +lsp) ; beautiful is better than ugly
153153+ ;;qt ; the 'cutest' gui framework ever
154154+ ;;racket ; a DSL for DSLs
155155+ ;;rest ; Emacs as a REST client
156156+ ;;rst ; ReST in peace
157157+ ;;(ruby +rails) ; 1.step {|i| p "Ruby is #{i.even? ? 'love' : 'life'}"}
158158+ ;;rust ; Fe2O3.unwrap().unwrap().unwrap().unwrap()
159159+ ;;scala ; java, but good
160160+ ;;scheme ; a fully conniving family of lisps
161161+ (sh +lsp) ; she sells {ba,z,fi}sh shells on the C xor
162162+ ;;solidity ; do you need a blockchain? No.
163163+ ;;swift ; who asked for emoji variables?
164164+ ;;terra ; Earth and Moon in alignment for performance.
165165+ web ; the tubes
166166+167167+ :email
168168+ ;;(mu4e +gmail)
169169+ ;;notmuch
170170+ ;;(wanderlust +gmail)
171171+172172+ :app
173173+ ;;calendar
174174+ ;;irc ; how neckbeards socialize
175175+ ;;(rss +org) ; emacs as an RSS reader
176176+ ;;twitter ; twitter client https://twitter.com/vnought
177177+178178+ :config
179179+ literate
180180+ (default +bindings +smartparens))
+51
doom-config/packages.el
···11+;; -*- no-byte-compile: t; -*-
22+;;; $DOOMDIR/packages.el
33+44+;; To install a package with Doom you must declare them here, run 'doom sync' on
55+;; the command line, then restart Emacs for the changes to take effect.
66+;; Alternatively, use M-x doom/reload.
77+;;
88+;; WARNING: Disabling core packages listed in ~/.emacs.d/core/packages.el may
99+;; have nasty side-effects and is not recommended.
1010+1111+1212+;; All of Doom's packages are pinned to a specific commit, and updated from
1313+;; release to release. To un-pin all packages and live on the edge, do:
1414+;(unpin! t)
1515+1616+;; ...but to unpin a single package:
1717+;(unpin! pinned-package)
1818+;; Use it to unpin multiple packages
1919+;(unpin! pinned-package another-pinned-package)
2020+2121+2222+;; To install SOME-PACKAGE from MELPA, ELPA or emacsmirror:
2323+;(package! some-package)
2424+2525+;; To install a package directly from a particular repo, you'll need to specify
2626+;; a `:recipe'. You'll find documentation on what `:recipe' accepts here:
2727+;; https://github.com/raxod502/straight.el#the-recipe-format
2828+;(package! another-package
2929+; :recipe (:host github :repo "username/repo"))
3030+3131+;; If the package you are trying to install does not contain a PACKAGENAME.el
3232+;; file, or is located in a subdirectory of the repo, you'll need to specify
3333+;; `:files' in the `:recipe':
3434+;(package! this-package
3535+; :recipe (:host github :repo "username/repo"
3636+; :files ("some-file.el" "src/lisp/*.el")))
3737+3838+;; If you'd like to disable a package included with Doom, for whatever reason,
3939+;; you can do so here with the `:disable' property:
4040+;(package! builtin-package :disable t)
4141+4242+;; You can override the recipe of a built in package without having to specify
4343+;; all the properties for `:recipe'. These will inherit the rest of its recipe
4444+;; from Doom or MELPA/ELPA/Emacsmirror:
4545+;(package! builtin-package :recipe (:nonrecursive t))
4646+;(package! builtin-package-2 :recipe (:repo "myfork/package"))
4747+4848+;; Specify a `:branch' to install a package from a particular branch or tag.
4949+;; This is required for some packages whose default branch isn't 'master' (which
5050+;; our package manager can't deal with; see raxod502/straight.el#279)
5151+;(package! builtin-package :recipe (:branch "develop"))
-21
spacemacs-private/README.md
···11-# Private directory
22-33-The content of this directory is ignored by Git. This is the default place
44-where to store your private configuration layers.
55-66-To create a new configuration layer:
77-88- SPC SPC configuration-layer/create-layer RET
99-1010-Then enter the name of your configuration in the prompt.
1111-1212-A directory named after the created configuration layer will be created here
1313-along with template files within it (packages.el and extensions.el, more info
1414-on the meaning of those files can be found in the [documentation][conf_layers]).
1515-1616-Each created file has further guidance written in them.
1717-1818-Once the configuration is done, restart Emacs to load, install and configure
1919-your layer.
2020-2121-[conf_layers]: https://github.com/syl20bnr/spacemacs/blob/master/doc/DOCUMENTATION.org#extensions-and-packages
-65
spacemacs-private/custom-direnv/packages.el
···11-;;; packages.el --- custom-direnv layer packages file for Spacemacs.
22-;;
33-;; Copyright (c) 2012-2018 Sylvain Benner & Contributors
44-;;
55-;; Author: Kaushik Chakraborty <kaushik@AMB00472.local>
66-;; URL: https://github.com/syl20bnr/spacemacs
77-;;
88-;; This file is not part of GNU Emacs.
99-;;
1010-;;; License: GPLv3
1111-1212-;;; Commentary:
1313-1414-;; See the Spacemacs documentation and FAQs for instructions on how to implement
1515-;; a new layer:
1616-;;
1717-;; SPC h SPC layers RET
1818-;;
1919-;;
2020-;; Briefly, each package to be installed or configured by this layer should be
2121-;; added to `custom-direnv-packages'. Then, for each package PACKAGE:
2222-;;
2323-;; - If PACKAGE is not referenced by any other Spacemacs layer, define a
2424-;; function `custom-direnv/init-PACKAGE' to load and initialize the package.
2525-2626-;; - Otherwise, PACKAGE is already referenced by another Spacemacs layer, so
2727-;; define the functions `custom-direnv/pre-init-PACKAGE' and/or
2828-;; `custom-direnv/post-init-PACKAGE' to customize the package as it is loaded.
2929-3030-;;; Code:
3131-3232-(defconst custom-direnv-packages
3333- '(direnv
3434- flycheck))
3535-3636-(defun custom-direnv/patch-direnv-environment (&rest _args)
3737- (let ((emacs-bin (directory-file-name
3838- (file-name-directory
3939- (executable-find "emacsclient")))))
4040- (setenv "PATH" (concat emacs-bin ":" (getenv "PATH")))
4141- (setq exec-path (cons (file-name-as-directory emacs-bin)
4242- exec-path))))
4343-4444-(defun custom-direnv/init-direnv ()
4545- (use-package direnv
4646- :defer t
4747- :config
4848- (advice-add 'direnv-update-directory-environment
4949- :after #'custom-direnv/patch-direnv-environment)
5050- ))
5151-5252-(defun custom-direnv/post-init-flycheck ()
5353- '(setq flycheck-executable-find
5454- (lambda (cmd)
5555- (add-hook 'post-command-hook #'direnv--maybe-update-environment)
5656- (direnv-update-environment default-directory)
5757- (executable-find cmd))))
5858-5959-(defun custom-direnv/post-init-direnv ()
6060- (direnv-mode)
6161- (add-hook 'git-commit-mode-hook #'custom-direnv/patch-direnv-environment)
6262- )
6363-6464-6565-;;; packages.el ends here
-46
spacemacs-private/custom-lean/README.org
···11-#+TITLE: custom-lean layer
22-33-# The maximum height of the logo should be 200 pixels.
44-[[img/custom-lean.png]]
55-66-# TOC links should be GitHub style anchors.
77-* Table of Contents :TOC_4_gh:noexport:
88-- [[#description][Description]]
99- - [[#features][Features:]]
1010-- [[#install][Install]]
1111-- [[#key-bindings][Key bindings]]
1212-1313-* Description
1414-This layer adds support for [[https://leanprover.github.io/][Lean]] programming language.
1515-1616-** Features:
1717- - syntax highlighting
1818- - autocompletion via ~company-lean~
1919- - searcheable list of options via ~helm-lean~
2020-2121-* Install
2222-To use this configuration layer, add it to your =~/.spacemacs=. You will need to
2323-add =custom-lean= to the existing =dotspacemacs-configuration-layers= list in this
2424-file.
2525-2626-* Key bindings
2727-2828- from [[https://github.com/leanprover/lean-mode][lean-mode]]
2929-3030- | Key | Function |
3131- |--------------------|---------------------------------------------------------------------------------|
3232- | <kbd>M-.</kbd> | jump to definition in source file (`lean-find-definition`) |
3333- | <kbd>M-,</kbd> | jump back to position before <kbd>M-.</kbd> (`xref-pop-marker-stack`) |
3434- | <kbd>C-c C-k</kbd> | shows the keystroke needed to input the symbol under the cursor |
3535- | <kbd>C-c C-x</kbd> | execute lean in stand-alone mode (`lean-std-exe`) |
3636- | <kbd>C-c SPC</kbd> | run a command on the hole at point (`lean-hole`) |
3737- | <kbd>C-c C-d</kbd> | show a searchable list of definitions (`helm-lean-definitions`) |
3838- | <kbd>C-c C-g</kbd> | toggle showing current tactic proof goal (`lean-toggle-show-goal`) |
3939- | <kbd>C-c C-n</kbd> | toggle showing next error in dedicated buffer (`lean-toggle-next-error`) |
4040- | <kbd>C-c C-b</kbd> | toggle showing output in inline boxes (`lean-message-boxes-toggle`) |
4141- | <kbd>C-c C-r</kbd> | restart the lean server (`lean-server-restart`) |
4242- | <kbd>C-c C-s</kbd> | switch to a different Lean version via [elan](https://github.com/Kha/elan) (`lean-server-switch-version`) |
4343- | <kbd>C-c ! n</kbd> | flycheck: go to next error |
4444- | <kbd>C-c ! p</kbd> | flycheck: go to previous error |
4545- | <kbd>C-c ! l</kbd> | flycheck: show list of errors |
4646-
-59
spacemacs-private/custom-lean/packages.el
···11-;;; packages.el --- custom-lean layer packages file for Spacemacs.
22-;;
33-;; Copyright (c) 2012-2018 Sylvain Benner & Contributors
44-;;
55-;; Author: Kaushik Chakraborty <git@kaushikc.org>
66-;; URL: https://kaushikc.org
77-;;
88-;; This file is not part of GNU Emacs.
99-;;
1010-;;; License: GPLv3
1111-1212-;;; Commentary:
1313-1414-;; See the Spacemacs documentation and FAQs for instructions on how to implement
1515-;; a new layer:
1616-;;
1717-;; SPC h SPC layers RET
1818-;;
1919-;;
2020-;; Briefly, each package to be installed or configured by this layer should be
2121-;; added to `custom-lean-packages'. Then, for each package PACKAGE:
2222-;;
2323-;; - If PACKAGE is not referenced by any other Spacemacs layer, define a
2424-;; function `custom-lean/init-PACKAGE' to load and initialize the package.
2525-2626-;; - Otherwise, PACKAGE is already referenced by another Spacemacs layer, so
2727-;; define the functions `custom-lean/pre-init-PACKAGE' and/or
2828-;; `custom-lean/post-init-PACKAGE' to customize the package as it is loaded.
2929-3030-;;; Code:
3131-3232-(defconst custom-lean-packages
3333- '(
3434- lean-mode
3535- (company-lean :requires company)
3636- (helm-lean :requires helm)
3737- ))
3838-3939-(defun custom-lean/init-lean-mode ()
4040- (use-package lean-mode
4141- :defer t))
4242-4343-(defun custom-lean/post-init-company ())
4444-4545-(defun custom-lean/init-company-lean ()
4646- (use-package company-lean
4747- :defer t
4848- :init
4949- (spacemacs|add-company-backends
5050- :backends company-lean
5151- :modes lean-mode)
5252- ))
5353-5454-(defun custom-lean/init-helm-lean ()
5555- (use-package helm-lean
5656- :defer t))
5757-5858-5959-;;; packages.el ends here
-53
spacemacs-private/custom-super-save/packages.el
···11-;;; packages.el --- custom-super-save layer packages file for Spacemacs.
22-;;
33-;; Copyright (c) 2012-2018 Sylvain Benner & Contributors
44-;;
55-;; Author: Kaushik Chakraborty <git@kaushikc.org>
66-;; URL: https://kaushikc.org
77-;;
88-;; This file is not part of GNU Emacs.
99-;;
1010-;;; License: GPLv3
1111-1212-;;; Commentary:
1313-1414-;; See the Spacemacs documentation and FAQs for instructions on how to implement
1515-;; a new layer:
1616-;;
1717-;; SPC h SPC layers RET
1818-;;
1919-;;
2020-;; Briefly, each package to be installed or configured by this layer should be
2121-;; added to `custom-super-save-packages'. Then, for each package PACKAGE:
2222-;;
2323-;; - If PACKAGE is not referenced by any other Spacemacs layer, define a
2424-;; function `custom-super-save/init-PACKAGE' to load and initialize the package.
2525-2626-;; - Otherwise, PACKAGE is already referenced by another Spacemacs layer, so
2727-;; define the functions `custom-super-save/pre-init-PACKAGE' and/or
2828-;; `custom-super-save/post-init-PACKAGE' to customize the package as it is loaded.
2929-3030-;;; Code:
3131-3232-(defconst custom-super-save-packages
3333- '(super-save)
3434- )
3535-(defun custom-super-save/init-super-save ()
3636- (use-package super-save
3737- :defer t
3838- :config
3939- (super-save-mode +1)
4040- (setq auto-save-default t)
4141- (setq super-save-remote-files nil)
4242- (setq super-save-auto-save-when-idle t)
4343- ))
4444-4545-(defun custom-super-save/post-init-super-save ()
4646- (super-save-mode +1)
4747- (setq auto-save-default t)
4848- (setq super-save-remote-files nil)
4949- (setq super-save-auto-save-when-idle t)
5050- )
5151-5252-5353-;;; packages.el ends here
-6
spacemacs-private/local/README.md
···11-# Private directory for local packages
22-33-The content of this directory is ignored by Git.
44-55-This is the place to store the local packages that you define in
66-the `dotspacemacs-additional-packages` variable of your dotfile.
-2123
spacemacs-private/local/beeminder.el
···11-;;; beeminder.el --- Emacs client for Beeminder -*- lexical-binding: t; -*-
22-33-;; Copyright (C) 2015 Marcin 'mbork' Borkowski
44-55-;; Author: Marcin Borkowski <mbork@mbork.pl>
66-;; Keywords: calendar
77-;; Package-Requires: ((request "0.2.0"))
88-99-;; This file is NOT part of GNU Emacs.
1010-1111-;; beeminder.el is free software: you can redistribute it and/or modify
1212-;; it under the terms of the GNU General Public License as published by
1313-;; the Free Software Foundation, either version 3 of the License, or
1414-;; (at your option) any later version.
1515-1616-;; beeminder.el is distributed in the hope that it will be useful,
1717-;; but WITHOUT ANY WARRANTY; without even the implied warranty of
1818-;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1919-;; GNU General Public License for more details.
2020-2121-;; You should have received a copy of the GNU General Public License
2222-;; along with beeminder.el.
2323-;; If not, see <http://www.gnu.org/licenses/>.
2424-2525-;;; Commentary:
2626-;; beeminder.el is an Emacs client for the Beeminder service.
2727-;; Beeminder (taglined "a reminder with a sting") is
2828-;; a motivation/quantified self tool based on behavioral economics
2929-;; principles.
3030-3131-(require 'json)
3232-(require 'request)
3333-(if (>= emacs-major-version 24)
3434- (progn
3535- (require 'cl-lib)
3636- (require 'anaphora)
3737- (defalias 'increasingp '<))
3838- (require 'cl)
3939- (defalias 'cl-reduce 'reduce)
4040- (defalias 'cl-find 'find)
4141- (defalias 'cl-incf 'incf)
4242- (defalias 'cl-decf 'decf)
4343- (defalias 'cl-case 'case)
4444- (defalias 'cl-delete 'delete*)
4545- (defmacro setq-local (var val)
4646- "This is taken from subr.el."
4747- `(set (make-local-variable ',var) ,val))
4848- (defmacro aif (cond then &rest else)
4949- `(let ((it ,cond))
5050- (if it ,then ,@else)))
5151- (defmacro awhen (cond &rest body)
5252- `(aif ,cond
5353- (progn ,@body)))
5454- (defun increasingp (&rest args)
5555- "Return t if ARGS are in increasing order."
5656- (if (cdr args)
5757- (and (< (car args) (cadr args))
5858- (apply #' increasingp (cdr args)))
5959- t)))
6060-(require 'ewoc)
6161-6262-;;; Code:
6363-6464-6565-;; Utilities
6666-(defun trim-leading-whitespace (string)
6767- "Trim tabs and spaces from the beginning of STRING."
6868- (when (string-match "^[ \t]*" string)
6969- (replace-match "" nil nil string)))
7070-7171-(defun beeminder-alist-get (key alist)
7272- "Return the value associated to KEY in ALIST.
7373-This function is needed for Emacsen older than v25."
7474- (cdr (assoc key alist)))
7575-7676-(defun beeminder-set-alist-value (key alist value)
7777- "Set the value corresponding to KEY in ALIST to VALUE.
7878-Note: ALIST should be a symbol. This is morally equivalent to
7979-`(setf (alist-get key (symbol-value alist)) value)',
8080-but works in older Emacsen."
8181- (let ((pair (assoc key (symbol-value alist))))
8282- (if pair
8383- (setcdr pair value)
8484- (set alist (acons key value (symbol-value alist))))))
8585-8686-(defun beeminder-inc-alist-value (key alist increment)
8787- "Increment the value corresponding to KEY in ALIST by INCREMENT.
8888-Throw an error if KEY is not in ALIST."
8989- (let ((pair (assoc key alist)))
9090- (if pair
9191- (incf (cdr pair) increment)
9292- (error "Nothing to increment"))))
9393-9494-(defun beeminder-to-list (sequence)
9595- "Turn SEQUENCE into a list."
9696- (append sequence nil))
9797-9898-9999-;; Settings
100100-101101-(defgroup beeminder nil
102102- "An Emacs client for Beeminder."
103103- :group 'applications)
104104-105105-(defcustom beeminder-username ""
106106- "User name for the Beeminder account."
107107- :type 'string
108108- :group 'beeminder)
109109-110110-(defcustom beeminder-auth-token ""
111111- "Authentication token for Beeminder.
112112-You can retrieve it from the URL
113113-`https://www.beeminder.com/api/v1/auth_token.json'."
114114- :type 'string
115115- :group 'beeminder)
116116-117117-(defcustom beeminder-api-url "https://www.beeminder.com/api/v1/users/"
118118- "The URL for making API calls."
119119- :type 'string
120120- :group 'beeminder)
121121-122122-(defvar beeminder-goals nil
123123- "The list of sexps representing goals.
124124-Updated by `beeminder-get-goals'.")
125125-126126-(defcustom beeminder-default-timeout 30
127127- "Default timeout for HTTP requests sent to beeminder, in seconds."
128128- :type 'number
129129- :group 'beeminder)
130130-131131-132132-;; Beeminder mode
133133-134134-(define-derived-mode beeminder-mode special-mode "Beeminder"
135135- "A major mode for a buffer with Beeminder goal list.")
136136-137137-(defun next-goal (count)
138138- "Move COUNT goals forward in the Beeminder buffer."
139139- (interactive "p")
140140- (ewoc-goto-next beeminder-goals-ewoc
141141- (if (beeminder-before-first-goal-p)
142142- (1- count)
143143- count)))
144144-145145-(defun previous-goal (count)
146146- "Move COUNT goals back in the Beeminder buffer.
147147-If on the first goal, move to (point-min)."
148148- ;; If point is before the place `previous-goal' would move it, move
149149- ;; to (point-min). This jumps to the beginning from any place
150150- ;; before the first node, but won't work when point is on Nth goal
151151- ;; and `count' is greater than N. This doesn't seem a big deal, so
152152- ;; let's just hope nobody notices that.
153153- (interactive "p")
154154- (when (<= (point)
155155- (progn (ewoc-goto-prev beeminder-goals-ewoc count)
156156- (point)))
157157- (goto-char (point-min))))
158158-159159-(define-key beeminder-mode-map (kbd "n") #'next-goal)
160160-(define-key beeminder-mode-map (kbd "p") #'previous-goal)
161161-162162-163163-;; API interface
164164-165165-(defun beeminder-create-api-url (string)
166166- "Prepend Beeminder site address and the username to STRING.
167167-STRING should begin with a slash."
168168- (concat beeminder-api-url beeminder-username string))
169169-170170-(defun beeminder-request-get (request &optional params success-fun error-fun timeout)
171171- "Send a GET REQUEST to beeminder.com, with TIMEOUT.
172172-Add the necessary details (including the username and the auth
173173-token)."
174174- (request (beeminder-create-api-url request)
175175- :parser #'json-read
176176- :params (append params (list (cons "auth_token" beeminder-auth-token)))
177177- :success success-fun
178178- :error error-fun
179179- :timeout (or timeout beeminder-default-timeout)))
180180-181181-(defun beeminder-request-post (request data success-fun error-fun &optional timeout)
182182- "Send a POST REQUEST with given DATA and TIMEOUT to beeminder.com.
183183-Add the username and the auth token."
184184- (request (beeminder-create-api-url request)
185185- :type "POST"
186186- :data (append data
187187- (list (cons "auth_token" beeminder-auth-token)))
188188- :parser #'json-read
189189- :success success-fun
190190- :error error-fun
191191- :timeout (or timeout beeminder-default-timeout)))
192192-193193-(defun beeminder-request-delete (request success-fun error-fun &optional timeout)
194194- "Send a DELETE request to beeminder.com, with TIMEOUT.
195195-Add the necessary details (username and the auth token)."
196196- (request-response-data
197197- (request (concat (beeminder-create-api-url request))
198198- :params (list (cons "auth_token" beeminder-auth-token))
199199- :type "DELETE"
200200- :parser #'json-read
201201- :success success-fun
202202- :error error-fun
203203- :timeout (or timeout beeminder-default-timeout))))
204204-205205-(defun beeminder-request-put (request data success-fun error-fun &optional timeout)
206206- "Send a PUT request to beeminder.com, with TIMEOUT.
207207-Add the necessary details (username and the auth token)."
208208- (request-response-data
209209- (request (beeminder-create-api-url request)
210210- :type "PUT"
211211- :data (append data
212212- (list (cons "auth_token" beeminder-auth-token)))
213213- :parser #'json-read
214214- :success success-fun
215215- :error error-fun
216216- :timeout (or timeout beeminder-default-timeout))))
217217-218218-219219-;; API calls
220220-221221-(defun last-goal-midnight (goal-deadline now) ; TODO: maybe refactor using beeminder-determine-date!
222222- "Return the last \"midnight\" for GOAL-DEADLINE, counting from NOW.
223223-GOAL-DEADLINE is an offset from real midnight in seconds, NOW is
224224-a time value."
225225- (let* ((now-decoded (decode-time now))
226226- (last-real-midnight (encode-time 0 0 0
227227- (cadddr now-decoded)
228228- (nth 4 now-decoded)
229229- (nth 5 now-decoded)
230230- (nth 8 now-decoded)))
231231- (last-midnight (+ goal-deadline
232232- (if (< goal-deadline 0) (* 24 60 60) 0)
233233- (time-to-seconds last-real-midnight))))
234234- (if (> last-midnight
235235- (time-to-seconds now))
236236- (- last-midnight (* 24 60 60))
237237- last-midnight)))
238238-239239-(defun last-user-midnight (now) ; TODO: maybe refactor using beeminder-determine-date!
240240- "Return the last \"midnight\" counting from NOW, as Unix timestamp.
241241-Take `beeminder-when-the-day-ends' into consideration."
242242- (let* ((now-decoded (decode-time now))
243243- (last-real-midnight (encode-time 0 0 0
244244- (cadddr now-decoded)
245245- (nth 4 now-decoded)
246246- (nth 5 now-decoded)
247247- (nth 8 now-decoded)))
248248- (last-midnight (+ (time-to-seconds last-real-midnight)
249249- beeminder-when-the-day-ends)))
250250- (if (> last-midnight (time-to-seconds now))
251251- (- last-midnight (* 24 60 60))
252252- last-midnight)))
253253-254254-(defcustom beeminder-history-length 7
255255- "Number of days from which to load datapoints.")
256256-257257-(defun beeminder-sum-today-value (datapoints start-time stop-time)
258258- "Sum the value for DATAPOINTS between START-TIME and STOP-TIME."
259259- (cl-reduce #'+
260260- (mapcar (lambda (datapoint)
261261- (if (< start-time
262262- (cdr (assoc 'timestamp datapoint))
263263- stop-time)
264264- (cdr (assoc 'value datapoint))
265265- 0))
266266- datapoints)))
267267-268268-(defun beeminder-get-goals ()
269269- "Get all the user's Beeminder goals and put them in the
270270-`beeminder-goals' variable."
271271- (beeminder-log "fetching goals...")
272272- (beeminder-request-get
273273- "/goals.json"
274274- ()
275275- (cl-function (lambda (&key data &allow-other-keys)
276276- (beeminder-log "fetching goals.......")
277277- (let ((goals (beeminder-to-list data))
278278- (now (beeminder-current-time)))
279279- (beeminder-request-get
280280- ".json"
281281- (list
282282- (cons "diff_since"
283283- (number-to-string
284284- (- (last-user-midnight now)
285285- (* beeminder-history-length 24 60 60)))))
286286- (cl-function (lambda (&key data &allow-other-keys)
287287- (let* ((datapoints ; extract datapoints alone
288288- (mapcar
289289- (lambda (goal) ; extract datapoints from a goal from data from API
290290- (cons (beeminder-alist-get 'slug goal)
291291- (beeminder-to-list (beeminder-alist-get 'datapoints goal))))
292292- (cdr (assoc 'goals data))))
293293- (deadlines ; deadlines alone
294294- (mapcar
295295- (lambda (goal)
296296- (cons (cdr (assoc 'slug goal))
297297- (cdr (assoc 'deadline goal))))
298298- goals))
299299- (today-values
300300- (mapcar
301301- (lambda (goal)
302302- (let ((last-midnight (if beeminder-use-goal-midnight-today-values
303303- (last-goal-midnight (cdr (assoc (car goal) deadlines))
304304- now)
305305- (last-user-midnight now))))
306306- (cons (car goal)
307307- (beeminder-sum-today-value (cdr goal) last-midnight (time-to-seconds now)))))
308308- datapoints)))
309309- (cl-flet ((beeminder-join-goal-data (goal)
310310- "Join GOAL data from various sources."
311311- (let ((slug-str (cdr (assoc 'slug goal))))
312312- (append (list (cons 'datapoints
313313- (cdr (assoc slug-str datapoints)))
314314- (cons 'donetoday
315315- (cdr (assoc
316316- (cdr (assoc 'slug goal))
317317- today-values)))
318318- (cons 'history-length beeminder-history-length))
319319- goal))))
320320- (setq beeminder-goals (mapcar #'beeminder-join-goal-data goals))
321321- (mapc #'beeminder-clean-goal goals)
322322- (beeminder-log "fetching goals...done")
323323- (setq beeminder-reloading-in-progress nil)))))
324324- #'beeminder-report-fetching-error))))
325325- #'beeminder-report-fetching-error))
326326-327327-(defun beeminder-clean-goal (goal)
328328- "Remove GOAL from `beeminder-dirty-alist' if needed.
329329-The heuristics for that is simple: if the current curval is
330330-different than the one recorded in that list, remove it."
331331- (let ((slug (intern (cdr (assoc 'slug goal)))))
332332- (if (and (cdr (assoc slug beeminder-dirty-alist))
333333- (/= (cdr (assoc 'curval goal))
334334- (cdr (assoc slug beeminder-dirty-alist))))
335335- (setq beeminder-dirty-alist (assq-delete-all slug beeminder-dirty-alist)))))
336336-337337-(cl-defun beeminder-report-fetching-error (&key error-thrown &allow-other-keys)
338338- "Report ERROR-THROWN when fetching goals."
339339- (beeminder-log (format "fetching goals...error: %s" error-thrown))
340340- (setq beeminder-reloading-in-progress nil))
341341-342342-(defun beeminder-refresh-goal (slug-str)
343343- "Refresh autodata and graph of the goal named SLUG-STR.
344344-Please do not use unless really necessary, since it creates
345345-a considerable server load."
346346- (interactive (list (cdr (assoc 'slug (current-or-read-goal)))))
347347- (beeminder-request-get (concat "/goals/" slug-str "/refresh_graph.json")
348348- nil
349349- (cl-function (lambda (&rest _)
350350- (beeminder-log
351351- (format
352352- "goal %s refreshed"
353353- slug-str))))
354354- (cl-function (lambda (&rest _)
355355- (beeminder-log
356356- (format
357357- "goal %s could not be refreshed"
358358- slug-str))))))
359359-360360-(define-key beeminder-mode-map (kbd "G") #'beeminder-refresh-goal)
361361-362362-363363-;; Submitting datapoints
364364-365365-(defun beeminder-read-string (prompt &optional initial-input history default-value inherit-input-method)
366366- "Replacement for `read-string', showing the default."
367367- (read-string (if default-value
368368- (format "%s (default %s): "
369369- (if (string-match "^\\(.+\\)\\(: \\)$" prompt)
370370- (match-string 1 prompt)
371371- prompt)
372372- default-value)
373373- prompt)
374374- initial-input history default-value inherit-input-method))
375375-376376-(defun beeminder-before-first-goal-p ()
377377- "Return t if the point is before the first goal or if there is
378378-no first goal."
379379- (aif (ewoc-nth beeminder-goals-ewoc 0)
380380- (< (point)
381381- (ewoc-location it))
382382- t))
383383-384384-(defun beeminder-get-slug (goal)
385385- "Return the slug of GOAL."
386386- (intern (cdr (assoc 'slug goal))))
387387-388388-(defun beeminder-slug-to-goal (slug)
389389- "Return the goal corresponding to SLUG."
390390- (cl-find slug beeminder-goals :key #'beeminder-get-slug))
391391-392392-(defun beeminder-slug-to-gnode (slug)
393393- "Return the goal node corresponding to SLUG."
394394- (let ((gnode (ewoc-nth beeminder-goals-ewoc 0)))
395395- (while (and gnode
396396- (not
397397- (eq slug (beeminder-get-slug (ewoc-data gnode)))))
398398- (setq gnode (ewoc-next beeminder-goals-ewoc gnode)))
399399- gnode))
400400-401401-(defvar beeminder-minibuffer-history nil
402402- "History of goal slug-strs entered through minibuffer.")
403403-404404-(defun beeminder-read-slug (&optional default)
405405- "Return a slug read from minibuffer or DEFAULT.
406406-DEFAULT should be a symbol."
407407- (intern (completing-read (if default
408408- (format "Goal slug (default %s): " default)
409409- "Goal slug: ")
410410- (mapcar (lambda (goal)
411411- (symbol-name (beeminder-get-slug goal)))
412412- beeminder-goals)
413413- nil
414414- t
415415- nil
416416- 'beeminder-minibuffer-history
417417- (if default (symbol-name default)))))
418418-419419-(defun current-or-read-goal ()
420420- "Return the goal the point is on.
421421-If the point is before the first goal or in a buffer whose mode
422422-is not `beeminder-mode', use `beeminder-read-slug' to ask for the
423423-goal slug and return that goal instead."
424424- (if (or (not (eq major-mode 'beeminder-mode))
425425- (beeminder-before-first-goal-p))
426426- (let ((default (aif (ewoc-nth beeminder-goals-ewoc 0)
427427- (beeminder-get-slug (ewoc-data it)))))
428428- (beeminder-slug-to-goal
429429- (beeminder-read-slug default)))
430430- (ewoc-data (ewoc-locate beeminder-goals-ewoc))))
431431-432432-(defcustom beeminder-time-format "%FT%T%z"
433433- "Default time format for Beeminder comments.")
434434-435435-(defun beeminder-current-time-string (&optional timestamp)
436436- "Return TIMESTAMP (Unix time) as a string.
437437-Use current time by default. Format is hh:mm:ss tz."
438438- (format-time-string beeminder-time-format
439439- (or timestamp (beeminder-current-time))))
440440-441441-(defvar beeminder-dirty-alist '()
442442- "Alist of slugs and \"curval\" values of changed goals.
443443-A goal is put here by `beeminder-submit-datapoint' and cleared
444444-from the list by `beeminder-get-goals' (if the retrieved data are
445445-actually updated, which can take from a few seconds to even a few
446446-minutes).")
447447-448448-(defgroup beeminder-faces nil
449449- "Faces used be the Beeminder client."
450450- :group 'beeminder)
451451-452452-(defface beeminder-dirty '((t :slant italic :foreground "grey50"))
453453- "Face for displaying \"dirty\" goals, i.e., goals for which
454454-a datapoint was submitted but had ot yet been reloaded."
455455- :group 'beeminder-faces)
456456-457457-(defun ask-for-timestamp (&optional default)
458458- "Ask the user for the timestamp, and return it as Unix time.
459459-If `org-read-date' is present, use that; if not, fall back to
460460-`safe-date-to-time' and augment the result with current time."
461461- (time-to-seconds
462462- (if (fboundp 'org-read-date)
463463- (org-read-date t t nil nil (beeminder-safe-time default))
464464- (let (time)
465465- (while
466466- (progn (setq time (safe-date-to-time (beeminder-read-string "Date+time: ")))
467467- (not
468468- (y-or-n-p
469469- (format-time-string "Time entered: %c. Confirm? " time)))))
470470- time))))
471471-472472-(defun beeminder-default-comment (&optional timestamp)
473473- "Generate the default comment for the given TIMESTAMP."
474474- (concat
475475- "via Emacs at "
476476- (beeminder-current-time-string timestamp)))
477477-478478-(defun beeminder-ask-for-comment (slug-str amount &optional default-comment)
479479- "Ask the user for the comment for the goal named SLUG-STR.
480480-Include AMOUNT in the question, and default to DEFAULT-COMMENT."
481481- (beeminder-read-string
482482- (format "Comment for amount %s for goal %s: " (number-to-human-string amount) slug-str)
483483- nil nil default-comment))
484484-485485-(defcustom beeminder-ask-for-comment t
486486- "Non-nil means ask for comment when a goal is submitted.
487487-This also serves as a confirmation that the user actually wants
488488-to submit data to Beeminder (especially that the question
489489-includes the goal slug and amount), so disabling of this option
490490-is discouraged.")
491491-492492-(defun beeminder-make-goal-dirty (slug)
493493- "Make the goal with SLUG dirty."
494494- (beeminder-set-alist-value slug
495495- 'beeminder-dirty-alist
496496- (cdr (assoc 'curval (beeminder-slug-to-goal slug))))
497497- (aif (beeminder-slug-to-gnode slug)
498498- (ewoc-invalidate beeminder-goals-ewoc it)))
499499-500500-(defun beeminder-clear-dirty-goals ()
501501- "Clear all dirty goals manually.
502502-This may be needed in rare circumstances, namely when
503503-successfully submitting a datapoint of 0."
504504- (interactive)
505505- (setq beeminder-dirty-alist ())
506506- (save-current-goal
507507- (ewoc-refresh beeminder-goals-ewoc)))
508508-509509-(defun beeminder-submit-datapoint (slug-str value &optional comment timestamp id)
510510- "Submit a datapoint to Beeminder goal SLUG-STR with AMOUNT.
511511-Additional data are COMMENT and TIMESTAMP (as Unix time). If
512512-COMMENT is nil, then ask the user for the comment. If TIMESTAMP
513513-is nil, assume now. If PRINT-MESSAGE is non-nil, print suitable
514514-messages in the echo area. If ID is non-nil, use it as requestid.
515515-516516-If called interactively, ask for SLUG-STR (with completion) unless the
517517-point is on a goal node. Then, ask for AMOUNT unless the user
518518-provided a numeric argument, in which case take the argument. Then,
519519-ask for COMMENT, proposing a reasonable default, unless the option
520520-`beeminder-ask-for-comment' is nil. If called with a prefix argument
521521-of \\[universal-argument], ask also for TIMESTAMP. If called with
522522-a prefix argument of `-', use previous day as the TIMESTAMP."
523523- (interactive
524524- (let* ((slug-str (cdr (assoc 'slug (current-or-read-goal))))
525525- (yesterdayp (eq current-prefix-arg '-))
526526- (value (if (numberp current-prefix-arg)
527527- current-prefix-arg
528528- (string-to-number (beeminder-read-string
529529- (format "Datapoint value for %s%s: "
530530- slug-str
531531- (if yesterdayp " (yesterday)" ""))
532532- nil nil "1"))))
533533- (current-timestamp (time-to-seconds (beeminder-current-time))))
534534- (list slug-str
535535- value
536536- (unless beeminder-ask-for-comment
537537- (beeminder-default-comment current-timestamp))
538538- (or (when yesterdayp
539539- (- current-timestamp (* 24 60 60)))
540540- (when (consp current-prefix-arg)
541541- (ask-for-timestamp))
542542- current-timestamp))))
543543- (let ((timestamp (or timestamp (time-to-seconds (beeminder-current-time)))))
544544- (beeminder-log (format "submitting datapoint of %s for goal %s..."
545545- (number-to-human-string value)
546546- slug-str))
547547- (beeminder-request-post (format "/goals/%s/datapoints.json" slug-str)
548548- (list
549549- (cons "value" (format "%f" value))
550550- (cons "comment" (or comment (beeminder-ask-for-comment
551551- slug-str
552552- value
553553- (beeminder-default-comment timestamp))))
554554- (if id (cons "requestid" id))
555555- (cons "timestamp" (format "%s" timestamp)))
556556- (cl-function (lambda (&rest _)
557557- (beeminder-log (format "submitting datapoint of %s for goal %s...done"
558558- (number-to-human-string value)
559559- slug-str))
560560- (let* ((slug (intern slug-str))
561561- (goal (beeminder-slug-to-goal slug)))
562562- (when goal
563563- (beeminder-inc-alist-value 'donetoday goal value)
564564- (beeminder-make-goal-dirty slug)))))
565565- (cl-function (lambda (&rest _)
566566- (beeminder-log
567567- (format "submitting datapoint of %s for goal %s...failed"
568568- (number-to-human-string value)
569569- slug-str)
570570- :error))))))
571571-572572-(define-key beeminder-mode-map (kbd "RET") #'beeminder-submit-datapoint)
573573-574574-575575-;; Sorting EWOC
576576-577577-(defun true (&rest args)
578578- "Always return t (irrespective of ARGS)."
579579- t)
580580-581581-(defun ewoc-sort (ewoc pred)
582582- "Sort EWOC, comparing its nodes using PRED.
583583-Since the author of EWOC didn't really care for sorting, and
584584-neither do I, we just first collect the nodes into a list, sort
585585-it using Elisp's sort, and then recreate the EWOC."
586586- (let ((ewoc-list (ewoc-collect ewoc #'true)))
587587- (ewoc-filter ewoc #'ignore)
588588- (mapcar (lambda (node) (ewoc-enter-last ewoc node))
589589- (sort ewoc-list pred))))
590590-591591-592592-;; Logging
593593-(define-derived-mode beeminder-log-mode special-mode "Beeminder log"
594594- "A major mode for logging beeminder.el actions.")
595595-596596-(defface beeminder-error '((t :foreground "#800" :weight bold))
597597- "Face for displaying error notifications.")
598598-599599-(defface beeminder-warning '((t :foreground "#880" :weight bold))
600600- "Face for displaying warning notifications.")
601601-602602-(defcustom beeminder-notification-expire-time 8
603603- "After that many seconds less important notifications expire.
604604-TODO: not yet implemented."
605605- :type 'integer
606606- :group 'beeminder)
607607-608608-(defvar beeminder-notification-expiration-timer nil
609609- "Timer used to clear notifications after
610610-`beeminder-notification-expire-time'.")
611611-612612-(defun beeminder-log (message &optional level)
613613- "Put MESSAGE into the log and possibly into the notification
614614-area. LEVEL can be `:error', `:warning', `:logonly' or `:nolog'.
615615-Messages without any of these levels expire after
616616-`beeminder-notification-expire-time' seconds."
617617- (let ((level-string (cl-case level
618618- (:error " error")
619619- (:warning " warning")
620620- (t ""))))
621621- (unless (eq level :nolog)
622622- (save-excursion
623623- (setq message (subst-char-in-string ?\n ?\s message t))
624624- (set-buffer (get-buffer-create "*Beeminder log*"))
625625- (beeminder-log-mode)
626626- (goto-char (point-max))
627627- (let ((inhibit-read-only t))
628628- (insert (current-time-string) level-string ": " message "\n"))))
629629- (unless (eq level :logonly)
630630- (message "Beeminder%s: %s"
631631- level-string
632632- message)
633633- (setq beeminder-notification
634634- (cond ((eq level :error)
635635- (propertize message 'face 'beeminder-error))
636636- ((eq level :warning)
637637- (propertize message 'face 'beeminder-warning))
638638- (t message))))
639639- (awhen beeminder-notification-expiration-timer (cancel-timer it))
640640- (unless (memq level '(:error :warning))
641641- (setq beeminder-notification-expiration-timer
642642- (run-at-time beeminder-notification-expire-time nil #'beeminder-clear-notification)))
643643- (when beeminder-goals-ewoc (beeminder-refresh-goals-list))))
644644-645645-(defun beeminder-clear-notification ()
646646- "Clear the notification."
647647- (interactive)
648648- (setq beeminder-notification nil)
649649- (beeminder-refresh-goals-list))
650650-651651-(define-key beeminder-mode-map (kbd "C") #'beeminder-clear-notification)
652652-653653-(defvar beeminder-notification nil
654654- "A message that should appear right below the header in
655655-Beeminder mode.")
656656-657657-(defun beeminder-pop-log ()
658658- "Pop the Beeminder log buffer."
659659- (interactive)
660660- (pop-to-buffer "*Beeminder log*"))
661661-662662-(define-key beeminder-mode-map (kbd "L") #'beeminder-pop-log)
663663-664664-665665-;; Displaying goals
666666-667667-(defvar beeminder-human-time-use-weekday t
668668- "Non-nil means that `beeminder-human-time' uses weekday names.
669669-Otherwise, use number of days from today.")
670670-671671-(defvar beeminder-tomorrow-code "tom"
672672- "The abbreviation for \"tomorrow\".")
673673-674674-(defcustom beeminder-when-the-day-ends (* 6 60 60)
675675- "Number of seconds from midnight when the day is assumed to end.
676676-Times up to this time will be considered to belong to the
677677-previous day. Note: this should be positive, or weird things
678678-might happen."
679679- :type 'integer
680680- :group 'beeminder)
681681-682682-(defun beeminder-plural-ending (number)
683683- "Return \"s\" if NUMBER not equal to one, and \"\" otherwise."
684684- (if (= number 1) "" "s"))
685685-686686-(defun beeminder-safe-time (time)
687687- "Convert TIME to Emacs time format if it is a number."
688688- (if (numberp time)
689689- (seconds-to-time time)
690690- time))
691691-692692-(defun beeminder-time-to-days (time)
693693- "Compute the number of days from 0001-12-31 BC until TIME.
694694-Take into consideration `beeminder-when-the-day-ends'."
695695- (time-to-days
696696- (time-add (beeminder-safe-time time)
697697- (seconds-to-time (- beeminder-when-the-day-ends)))))
698698-699699-(defun beeminder-human-time (time)
700700- "Convert (future) TIME to a human-friendly format.
701701-- For today, the time.
702702-- For tomorrow, the string `beeminder-tomorrow-code' (by default) and
703703- the time.
704704-- For times within a week, abbreviation of the weekday or a plus and
705705- a number of days (depending on `beeminder-human-time-use-weekday')
706706- and the time.
707707-- For later times, iso date without time.
708708-Midnight is treated as belonging to the previous day, not the following one."
709709- (let ((delta (- (beeminder-time-to-days time)
710710- (beeminder-time-to-days (beeminder-current-time)))))
711711- (cond ((zerop delta) (format-time-string " %R" time))
712712- ((= 1 delta) (concat " " beeminder-tomorrow-code
713713- (format-time-string " %R" time)))
714714- ((<= delta 7)
715715- (concat (if beeminder-human-time-use-weekday
716716- (format-time-string " %a"
717717- (time-add time
718718- (seconds-to-time (- beeminder-when-the-day-ends))))
719719- (format " +%d" delta))
720720- " "
721721- (format-time-string "%R" time)))
722722- (t (format-time-string "%Y-%m-%d"
723723- (time-add time
724724- (seconds-to-time (- beeminder-when-the-day-ends))))))))
725725-726726-(defconst beeminder-lanes-to-faces-alist
727727- '((-2 . beeminder-red) (-1 . beeminder-yellow) (1 . beeminder-blue) (2 . beeminder-green))
728728- "Alist mapping the (normalized) value of lane to goal colors.")
729729-730730-(defun beeminder-normalize-lane (lane-number)
731731- "Normalize LANE-NUMBER into the interval -2 .. 2.
732732-This means to return 2 for LANE-NUMBER greater than 2 and -2 for
733733-LANE-NUMBER less than -2."
734734- (min (max lane-number -2) 2))
735735-736736-(defun beeminder-display-string-field (goal field &optional width invisible)
737737- "Return GOAL's FIELD (which should be a symbol) as a string.
738738-Optionally use length WIDTH (padded from the right with spaces).
739739-Make it invisible if INVISIBLE is non-nil."
740740- (let ((text (format (if width
741741- (format "%%-%d.%ds" width width)
742742- "%s")
743743- (cdr (assoc field goal)))))
744744- (if invisible
745745- (propertize text 'invisible invisible)
746746- text)))
747747-748748-(defun beeminder-display-losedate-human (goal)
749749- "Return the losedate field of GOAL in human-friendly format."
750750- (beeminder-human-time (seconds-to-time (1+ (cdr (assoc 'losedate goal))))))
751751-752752-(defun beeminder-get-rate (goal)
753753- "Return the rate of GOAL."
754754- (elt (cdr (assoc 'mathishard goal)) 2))
755755-756756-(defun beeminder-display-rate (goal)
757757- "Return the rate of the GOAL (with units), as a string."
758758- (let ((rate (beeminder-get-rate goal)))
759759- (format (concat
760760- (number-to-human-string rate 4)
761761- "/%s")
762762- (cdr (assoc 'runits goal)))))
763763-764764-(defun beeminder-display-pledge (goal)
765765- "Return the pledge of the GOAL, as a string."
766766- (format "$%.2f" (cdr (assoc 'pledge goal))))
767767-768768-(defcustom beeminder-goal-pp-format
769769- '((beeminder-display-string-field slug 12)
770770- " "
771771- beeminder-display-losedate-human
772772- " "
773773- (beeminder-display-string-field limsum 16)
774774- " "
775775- beeminder-display-rate
776776- " "
777777- beeminder-display-pledge
778778- " "
779779- (beeminder-display-string-field title))
780780- "The format for displaying a Beeminder goal.
781781-It is a list whose elements are either strings, printed verbatim,
782782-either functions, which are then called with one argument (the
783783-goal), or lists, in which case the car of the list is a function
784784-and the cdr the list of arguments it should get after the goal."
785785- :type 'sexp
786786- :group 'beeminder)
787787-788788-(defun beeminder-goal-face (goal)
789789- "Return the face for displaying GOAL."
790790- (if (beeminder-alist-get (intern (cdr (assoc 'slug goal)))
791791- beeminder-dirty-alist)
792792- 'beeminder-dirty
793793- (cdr (assoc (* (cdr (assoc 'yaw goal))
794794- (beeminder-normalize-lane (cdr (assoc 'lane goal))))
795795- beeminder-lanes-to-faces-alist))))
796796-797797-(defun beeminder-goal-representation (goal)
798798- "The string representation of GOAL, with the face applied."
799799- (propertize (mapconcat (lambda (field-specifier)
800800- (cond
801801- ((functionp field-specifier) (funcall field-specifier goal))
802802- ((consp field-specifier) (apply (car field-specifier)
803803- goal
804804- (cdr field-specifier)))
805805- ((stringp field-specifier) field-specifier)))
806806- beeminder-goal-pp-format "")
807807- 'face
808808- (beeminder-goal-face goal)))
809809-810810-(defun beeminder-display-first-goal ()
811811- "Display the first goal in the echo area.
812812-Normally, this should be one of the goals with the nearest
813813-deadline. Caution: if more than one goal has the same deadline,
814814-it is not obvious which one is returned as the first from the
815815-server! You might want to bind this function globally so that
816816-you don't need to enter the Beeminder mode to see the nearest
817817-deadline."
818818- (interactive)
819819- (let* ((goal (car beeminder-goals))
820820- (minutes (/ (- (cdr (assoc 'losedate goal))
821821- (time-to-seconds (beeminder-current-time)))
822822- 60)))
823823- (beeminder-log (format "next goal: %s (%d minute%s left, %s to do)"
824824- (replace-regexp-in-string
825825- " \\{2,\\}"
826826- " "
827827- (beeminder-goal-representation goal))
828828- minutes
829829- (beeminder-plural-ending minutes)
830830- (let ((limsum (cdr (assoc 'limsum goal))))
831831- (string-match "[[:digit:]]+\\(\\.[[:digit:]]*\\)?" limsum)
832832- (match-string 0 limsum))))))
833833-834834-835835-;; Faces for goals
836836-837837-(defface beeminder-green '((t :foreground "#080"))
838838- "Face for displaying Beeminder goals in green."
839839- :group 'beeminder-faces)
840840-841841-(defface beeminder-blue '((t :foreground "#008"))
842842- "Face for displaying Beeminder goals in blue."
843843- :group 'beeminder-faces)
844844-845845-(defface beeminder-yellow '((t :foreground "#880"))
846846- "Face for displaying Beeminder goals in green."
847847- :group 'beeminder-faces)
848848-849849-(defface beeminder-red '((t :foreground "#800"))
850850- "Face for displaying Beeminder goals in red."
851851- :group 'beeminder-faces)
852852-853853-854854-;; Beeminder EWOC
855855-856856-(defvar beeminder-goals-ewoc nil)
857857-858858-(defvar beeminder-short-header nil
859859- "If t, the default header is (extremely) shortened.")
860860-861861-(defun beeminder-toggle-short-header (&optional arg)
862862- "Toggle shortening the header for Beeminder goal list.
863863-If ARG is positive, shorten the header; otherwise, do not."
864864- (interactive "P")
865865- (setq beeminder-short-header
866866- (if (null arg)
867867- (not beeminder-short-header)
868868- (> (prefix-numeric-value arg) 0)))
869869- (save-current-goal
870870- (ewoc-set-hf beeminder-goals-ewoc (beeminder-ewoc-header) "")
871871- (ewoc-refresh beeminder-goals-ewoc)))
872872-873873-(define-key beeminder-mode-map (kbd "=") #'beeminder-toggle-short-header)
874874-875875-(defun beeminder-print-filter (filter)
876876- "Return a printed representation of FILTER.
877877-It should be an element of `beeminder-current-filters'."
878878- (funcall (nth 3 (assoc (car filter) beeminder-filters)) (cdr filter)))
879879-880880-(defun beeminder-ewoc-header ()
881881- "Generate header for the Beeminder EWOC."
882882- (concat (format (if beeminder-short-header
883883- "Beeminder goals user:%s goals:%s/%d"
884884- "Beeminder goals for user %s (%s goals displayed out of %d total)")
885885- beeminder-username
886886- (if beeminder-goals-ewoc
887887- (length (ewoc-collect beeminder-goals-ewoc #'true))
888888- 0)
889889- (length beeminder-goals))
890890- (propertize (concat (format (if beeminder-short-header
891891- " srt:%s"
892892- "\nsorting criterion: %s")
893893- (caddr beeminder-current-sorting-setting))
894894- (if beeminder-short-header
895895- (format " e%s"
896896- (if beeminder-show-everyday "+" "-"))
897897- (format " everyday goals: %s"
898898- (if beeminder-show-everyday
899899- "displayed"
900900- "omitted")))
901901- (format (if beeminder-short-header
902902- " fil:%s"
903903- (format "\nfilter%s: %%s\n"
904904- (beeminder-plural-ending
905905- (length beeminder-current-filters))))
906906- (if beeminder-current-filters
907907- (mapconcat #'beeminder-print-filter
908908- beeminder-current-filters
909909- ", ")
910910- "none")))
911911- 'face 'shadow)
912912- (aif beeminder-notification (concat " " it) "")))
913913-914914-(defun beeminder-create-goals-ewoc ()
915915- "Return a newly created EWOC for Beeminder goals."
916916- (ewoc-create (lambda (goal) (insert (beeminder-goal-representation goal)))
917917- (beeminder-ewoc-header)))
918918-919919-(defun beeminder-populate-ewoc ()
920920- "Populate Beeminder EWOC using the goal list.
921921-In particular, apply filtering and sorting settings. Note: since
922922-only the last sorting criterion is remembered, and sorting is
923923-stable, this might actually change the ordering of goals, which
924924-may have been sorted by another criterion previously."
925925- (ewoc-filter beeminder-goals-ewoc #'ignore)
926926- (mapcar (lambda (goal)
927927- (ewoc-enter-last beeminder-goals-ewoc goal))
928928- beeminder-goals)
929929- (beeminder-apply-filters)
930930- (apply #'beeminder-sort-by-field beeminder-current-sorting-setting)
931931- (ewoc-set-hf beeminder-goals-ewoc (beeminder-ewoc-header) "")
932932- (ewoc-refresh beeminder-goals-ewoc)
933933- (with-current-buffer (ewoc-buffer beeminder-goals-ewoc)
934934- (goto-char (point-min))))
935935-936936-(defun beeminder-list-goals ()
937937- "Switch to a buffer containing the list of Beeminder goals."
938938- (interactive)
939939- (aif (get-buffer "*Beeminder goals*")
940940- (switch-to-buffer it)
941941- (switch-to-buffer "*Beeminder goals*")
942942- (buffer-disable-undo)
943943- (beeminder-mode))
944944- (let ((inhibit-read-only t))
945945- (erase-buffer)
946946- (setq beeminder-goals-ewoc (beeminder-create-goals-ewoc))
947947- (beeminder-populate-ewoc))
948948- (setq truncate-lines t)
949949- (unless beeminder-goals
950950- (beeminder-get-goals)))
951951-952952-953953-;; Current time function
954954-955955-(defalias 'beeminder-current-time 'current-time
956956- "An alias for current-time, useful for testing/debugging.")
957957-958958-959959-;; Sorting
960960-961961-(defcustom beeminder-default-sorting-setting (list 'losedate #'< "losedate")
962962- "Default sorting setting for Beeminder goals.
963963-This is a list whose first element is the field according to
964964-which the sorting should be done, then the predicate, and then
965965-the printed representation of this sorting method (as a string)."
966966- :type 'sexp
967967- :group 'beeminder)
968968-969969-(defvar beeminder-current-sorting-setting beeminder-default-sorting-setting)
970970-971971-(defmacro save-current-goal (&rest body)
972972- "Evaluate BODY and bring the point back to the current goal.
973973-If the Beeminder EWOC disappeared (for some reason), just
974974-evaluate the body."
975975- (declare (indent 0) (debug t))
976976- `(if beeminder-goals-ewoc
977977- (with-current-buffer (ewoc-buffer beeminder-goals-ewoc)
978978- (let* ((current-goal-slug
979979- (if (beeminder-before-first-goal-p)
980980- nil
981981- (beeminder-get-slug (ewoc-data (ewoc-locate beeminder-goals-ewoc)))))
982982- (current-line (unless current-goal-slug (line-number-at-pos))))
983983- ,@body
984984- (cond ((not current-goal-slug)
985985- (goto-char (point-min))
986986- (forward-line (1- current-line)))
987987- (t
988988- (ewoc-goto-node beeminder-goals-ewoc (ewoc-nth beeminder-goals-ewoc 0))
989989- (let ((current-node (ewoc-nth beeminder-goals-ewoc 0)))
990990- (while (and current-node
991991- (not (eq (beeminder-get-slug (ewoc-data current-node))
992992- current-goal-slug)))
993993- (ewoc-goto-next beeminder-goals-ewoc 1)
994994- (setq current-node (ewoc-next beeminder-goals-ewoc current-node)))
995995- (unless current-node (goto-char (point-min))))))))
996996- ,@body))
997997-998998-(defun beeminder-sort-by-field (field predicate info)
999999- "Sort entries in `beeminder-goals-ewoc' by FIELD, using PREDICATE.
10001000-INFO is the printed representation of the sorting criterion."
10011001- (save-current-goal
10021002- (ewoc-sort beeminder-goals-ewoc (lambda (x y)
10031003- (funcall predicate
10041004- (cdr (assoc field x))
10051005- (cdr (assoc field y)))))
10061006- (ewoc-refresh beeminder-goals-ewoc)
10071007- (setq beeminder-current-sorting-setting (list field predicate info))
10081008- (ewoc-set-hf beeminder-goals-ewoc (beeminder-ewoc-header) "")))
10091009-10101010-(defun beeminder-sort-by-losedate ()
10111011- "Sort entries in `beeminder-goals' by losedate."
10121012- (interactive)
10131013- (beeminder-sort-by-field 'losedate #'< "losedate"))
10141014-10151015-(defun beeminder-seconds-to-from-midnight (time)
10161016- "Convert TIME to seconds from midnight.
10171017-If after 6:00, convert to seconds to midnight (with a minus
10181018-sign). The magic time constant 6:00 is the result of Beeminder's way
10191019-of dealing with the \"midnight\" setting."
10201020- (let* ((decoded-time (decode-time time))
10211021- (seconds (+ (car decoded-time)
10221022- (* 60 (cadr decoded-time))
10231023- (* 3600 (caddr decoded-time)))))
10241024- (if (> seconds (* 6 60 60))
10251025- (- seconds (* 24 60 60))
10261026- seconds)))
10271027-10281028-(defun beeminder-earlier-midnight (sec1 sec2 time)
10291029- "Compare SEC1 and SEC2, taking into account the TIME.
10301030-All three parameters are expressed as seconds from midnight, like
10311031-the result of calling `beeminder-seconds-to-from-midnight'. If
10321032-SEC1 < SEC2 < TIME, return t. If TIME < SEC1 < SEC2, return t.
10331033-If SEC2 < TIME < SEC1, return t. In all other cases, return nil.
10341034-This function is useful for sorting goals by their \"midnight\"
10351035-setting, with the goals which are after their \"midnight\" at the
10361036-end."
10371037- (or (increasingp sec1 sec2 time)
10381038- (increasingp time sec1 sec2)
10391039- (increasingp sec2 time sec1)))
10401040-10411041-(defun beeminder-sort-by-midnight ()
10421042- "Sort entries in `beeminder-goals' by their midnight, taking current time into consideration."
10431043- (interactive)
10441044- (beeminder-sort-by-field
10451045- 'deadline
10461046- (lambda (x y)
10471047- (beeminder-earlier-midnight
10481048- x y (beeminder-seconds-to-from-midnight
10491049- (beeminder-current-time))))
10501050- "midnight"))
10511051-10521052-(define-key beeminder-mode-map "l" #'beeminder-sort-by-losedate)
10531053-(define-key beeminder-mode-map "m" #'beeminder-sort-by-midnight)
10541054-10551055-10561056-;; Refreshing view and reloading goals
10571057-10581058-(defun beeminder-refresh-goals-list ()
10591059- "Refresh the goals list."
10601060- (interactive)
10611061- (save-current-goal
10621062- (beeminder-populate-ewoc)))
10631063-10641064-(defvar beeminder-reloading-in-progress nil
10651065- "Non-nil if currently reloading data from the server.")
10661066-10671067-(defun beeminder-clear-reloading-in-progress-flag ()
10681068- "Clear the `beeminder-reloading-in-progress' flag.
10691069-Useful in case of an error."
10701070- (interactive)
10711071- (setq beeminder-reloading-in-progress nil))
10721072-10731073-(defun beeminder-reload-goals-list (&optional force)
10741074- "Reload the goals from the server.
10751075-With a prefix argument, do it even if reloading is in progress
10761076-\(this is useful when the `beeminder-reloading-in-progress'
10771077-variable is somehow messed up)."
10781078- (interactive "P")
10791079- (if (and beeminder-reloading-in-progress
10801080- (not force))
10811081- (beeminder-log "fetching goals already in progress, please wait!")
10821082- (setq beeminder-reloading-in-progress t)
10831083- (beeminder-get-goals)))
10841084-10851085-(define-key beeminder-mode-map (kbd "C-l") #'beeminder-refresh-goals-list)
10861086-(define-key beeminder-mode-map "g" #'beeminder-reload-goals-list)
10871087-10881088-10891089-;; Filtering goals
10901090-10911091-(defvar beeminder-current-filters '()
10921092- "Alist of filters currently in effect.")
10931093-10941094-(defcustom beeminder-saved-filters '()
10951095- "A remembered set of filters for fast retrieval.")
10961096-10971097-(defun beeminder-save-filters ()
10981098- "Save the current filters."
10991099- (interactive)
11001100- (setq beeminder-saved-filters
11011101- (copy-alist beeminder-current-filters))
11021102- (beeminder-log (format "current filter settings %s saved." beeminder-current-filters)))
11031103-11041104-(defun beeminder-retrieve-filters ()
11051105- "Retrieve saved filters."
11061106- (interactive)
11071107- (setq beeminder-current-filters
11081108- (copy-alist beeminder-saved-filters))
11091109- (beeminder-refresh-goals-list)
11101110- (beeminder-log (format "filter settings %s retrieved." beeminder-current-filters)))
11111111-11121112-(defun beeminder-clear-filters ()
11131113- "Clear all filters.
11141114-If there are no saved filters, first save the current filters."
11151115- (interactive)
11161116- (unless beeminder-saved-filters
11171117- (beeminder-save-filters))
11181118- (setq beeminder-current-filters '())
11191119- (beeminder-refresh-goals-list))
11201120-11211121-(define-key beeminder-mode-map "c" #'beeminder-clear-filters)
11221122-11231123-(define-prefix-command 'beeminder-filter-map)
11241124-(define-key beeminder-mode-map "f" #'beeminder-filter-map)
11251125-(define-key beeminder-filter-map "c" #'beeminder-clear-filters)
11261126-(define-key beeminder-filter-map "s" #'beeminder-save-filters)
11271127-(define-key beeminder-filter-map "f" #'beeminder-retrieve-filters)
11281128-(define-key beeminder-filter-map "r" #'beeminder-retrieve-filters)
11291129-11301130-(defcustom beeminder-default-filter-days 3
11311131- "Defalt number of days used for filtering by losedate.
11321132-If the user doesn't specify the number of days for filtering, all
11331133-goals with more than this amount of days left to losedate will be
11341134-filtered out."
11351135- :type 'integer
11361136- :group 'beeminder)
11371137-11381138-(defcustom beeminder-use-goal-midnight-today-values nil
11391139- "If non-nil, compute today's values using the goal's midnight.
11401140-If nil, use the global midnight defined by
11411141-`beeminder-when-the-day-ends'."
11421142- :type 'boolean
11431143- :group 'beeminder)
11441144-11451145-(defcustom beeminder-default-filter-donetoday 100
11461146- "Default percentage of donetoday used for filtering."
11471147- :type 'integer
11481148- :group 'beeminder)
11491149-11501150-(defcustom beeminder-default-filter-urgent-hours 8
11511151- "Default time (in hours) to deadline to consider a goal urgent."
11521152- :type 'integer
11531153- :group 'beeminder)
11541154-11551155-(defcustom beeminder-show-dirty-donetoday t
11561156- "If non-nil, show dirty goals even if they would be normally
11571157- filtered out by the \"donetoday\" filter."
11581158- :type 'boolean
11591159- :group 'beeminder)
11601160-11611161-(defcustom beeminder-show-everyday t
11621162- "If non-nil, show \"everyday goals\" irrespective of the
11631163- \"days\" filter.")
11641164-11651165-(defcustom beeminder-everyday-goals-list
11661166- '()
11671167- "A list of slugs of \"everyday goals\". These are the goals which
11681168-should be done every day, so even when filtering goals with deadline
11691169-after some number of days, they should be shown.")
11701170-11711171-(defun beeminder-toggle-show-everyday (arg)
11721172- "Toggle showing \"everyday goals\" if ARG is zero or nil.
11731173-If ARG is positive, turn it on; if negative, off."
11741174- (interactive "P")
11751175- (let ((narg (prefix-numeric-value arg)))
11761176- (cond ((null arg)
11771177- (setq beeminder-show-everyday (not beeminder-show-everyday)))
11781178- ((> narg 0)
11791179- (setq beeminder-show-everyday t))
11801180- ((< narg 0)
11811181- (setq beeminder-show-everyday nil))))
11821182- (beeminder-refresh-goals-list))
11831183-11841184-(define-key beeminder-mode-map "e" #'beeminder-toggle-show-everyday)
11851185-(define-key beeminder-filter-map "e" #'beeminder-toggle-show-everyday)
11861186-11871187-(defun beeminder-days-p (goal days)
11881188- "Return nil if time to derailment of GOAL > DAYS.
11891189-If DAYS is negative, return nil if time to derailment of GOAL is
11901190-<= -DAYS. If the goal is in `beeminder-everyday-goals-list',
11911191-return t anyway."
11921192- (if (and beeminder-show-everyday
11931193- (memq (beeminder-get-slug goal) beeminder-everyday-goals-list))
11941194- t
11951195- (let ((days-left (- (beeminder-time-to-days (cdr (assoc 'losedate goal)))
11961196- (beeminder-time-to-days (beeminder-current-time)))))
11971197- (if (>= days 0)
11981198- (<= days-left days)
11991199- (>= days-left (- days))))))
12001200-12011201-(defun beeminder-donetoday-p (goal percentage)
12021202- "Return nil if donetoday for GOAL >= PERCENTAGE * day's amount.
12031203-If PERCENTAGE is negative, return nil if donetoday of GOAL is
12041204-less than PERCENTAGE * day's amount. Take the variable
12051205-`beeminder-show-dirty-donetoday' into account."
12061206- (if (and beeminder-show-dirty-donetoday
12071207- (beeminder-alist-get (beeminder-get-slug goal) beeminder-dirty-alist))
12081208- t
12091209- (let* ((rate (beeminder-get-rate goal))
12101210- (daily-rate (/ rate
12111211- (cl-case (intern (cdr (assoc 'runits goal)))
12121212- (y 365.0)
12131213- (m (/ 365.0 12))
12141214- (w 7.0)
12151215- (d 1.0)
12161216- (h (/ 1 24.0)))))
12171217- (100*donetoday (* 100 (cdr (assoc 'donetoday goal))))
12181218- (percentage*daily-rate (* percentage daily-rate)))
12191219- (when (> rate 0)
12201220- (cond ((> percentage 0)
12211221- (< 100*donetoday
12221222- percentage*daily-rate))
12231223- ((zerop percentage)
12241224- (zerop 100*donetoday))
12251225- (t
12261226- (>= 100*donetoday (- percentage*daily-rate))))))))
12271227-12281228-(defun beeminder-calculate-midnight-offset (seconds)
12291229- "Add (* 24 60 60) to SECONDS if negative."
12301230- (if (> seconds 0)
12311231- seconds
12321232- (+ (* 24 60 60) seconds)))
12331233-12341234-(defun beeminder-urgent-p (goal hours)
12351235- "Return nil if time to deadline for GOAL is > HOURS.
12361236-If HOURS is negative or zero, return nil if time to deadline is
12371237-<= -HOURS."
12381238- (let* ((deadline (beeminder-calculate-midnight-offset (beeminder-alist-get 'deadline goal)))
12391239- (now (decode-time (beeminder-current-time)))
12401240- (now-sec (car now))
12411241- (now-min (cadr now))
12421242- (now-hour (caddr now))
12431243- (now-time (+ now-sec (* 60 now-min) (* 3600 now-hour)))
12441244- (time-to-deadline (beeminder-calculate-midnight-offset (- deadline now-time))))
12451245- (if (> hours 0)
12461246- (<= time-to-deadline (* 3600 hours))
12471247- (> time-to-deadline (* -3600 hours)))))
12481248-12491249-(defun beeminder-not-killed-p (goal kill-list)
12501250- "Return nil if GOAL is in the KILL-LIST."
12511251- (not (member (beeminder-get-slug goal) kill-list)))
12521252-12531253-(defvar beeminder-filters `((losedate ,#'beeminder-days-p
12541254- ,beeminder-default-filter-days
12551255- (lambda (days)
12561256- (format (if beeminder-short-header
12571257- "d2d(%s%d)"
12581258- "days to derailment (%s%d)")
12591259- (if (>= days 0) "<=" ">=")
12601260- (abs days))))
12611261- (donetoday ,#'beeminder-donetoday-p
12621262- ,beeminder-default-filter-donetoday
12631263- (lambda (donetoday)
12641264- (format (if beeminder-short-header
12651265- "dt(%s%d%%)"
12661266- "done today (%s%d%%)")
12671267- (cond ((> donetoday 0) "<")
12681268- ((zerop donetoday) "=")
12691269- (t ">="))
12701270- (abs donetoday))))
12711271- (urgent ,#'beeminder-urgent-p
12721272- ,beeminder-default-filter-urgent-hours
12731273- (lambda (hours)
12741274- (format (if beeminder-short-header
12751275- "u(%s%dh)"
12761276- "urgent (%s%d hours to deadline)")
12771277- (if (> hours 0) "<=" ">")
12781278- (abs hours))))
12791279- (killed ,#'beeminder-not-killed-p
12801280- '()
12811281- (lambda (kill-list)
12821282- (format (if beeminder-short-header
12831283- "%dgk"
12841284- "%d goal%s killed")
12851285- (length kill-list)
12861286- (beeminder-plural-ending (length kill-list))))))
12871287-12881288- "List of possible filters. Each element is a list, consisting of:
12891289-- symbol, denoting the filter,
12901290-- predicate (with two arguments - the goal and the parameter),
12911291-- default value for the parameter,
12921292-- formatting function (with one argument - the parameter).")
12931293-12941294-(defun beeminder-kill-goal (gnode)
12951295- "Delete GNODE from `beeminder-goals-ewoc'."
12961296- (interactive (list (beeminder-slug-to-gnode (intern (cdr (assoc 'slug (current-or-read-goal)))))))
12971297- (if gnode
12981298- (let ((inhibit-read-only t)
12991299- (next-goal (or (ewoc-next beeminder-goals-ewoc gnode)
13001300- (ewoc-prev beeminder-goals-ewoc gnode))))
13011301- (ewoc-delete beeminder-goals-ewoc gnode)
13021302- (ewoc-refresh beeminder-goals-ewoc)
13031303- (beeminder-set-alist-value 'killed
13041304- 'beeminder-current-filters
13051305- (cons (beeminder-get-slug (ewoc-data gnode))
13061306- (beeminder-alist-get 'killed beeminder-current-filters)))
13071307- (ewoc-set-hf beeminder-goals-ewoc (beeminder-ewoc-header) "")
13081308- (if next-goal
13091309- (ewoc-goto-node beeminder-goals-ewoc next-goal)
13101310- (goto-char (point-min)))
13111311- (beeminder-log (format "goal %s killed (hidden from view)." (cdr (assoc 'slug (ewoc-data gnode))))))
13121312- (beeminder-log (format "goal %s already killed." (cdr (assoc 'slug (ewoc-data gnode)))))))
13131313-13141314-(define-key beeminder-mode-map (kbd "C-k") #'beeminder-kill-goal)
13151315-(define-key beeminder-filter-map "k" #'beeminder-kill-goal)
13161316-13171317-(defun beeminder-show-kills ()
13181318- "Show all killed goals."
13191319- (interactive)
13201320- (beeminder-log (format "killed goals: %s."
13211321- (aif (beeminder-alist-get 'killed beeminder-current-filters)
13221322- (mapconcat #'symbol-name it ", ")
13231323- "none"))))
13241324-13251325-(defun beeminder-clear-kills ()
13261326- "Unkill all killed goals."
13271327- (interactive)
13281328- (setq beeminder-current-filters
13291329- (assq-delete-all 'killed beeminder-current-filters))
13301330- (beeminder-refresh-goals-list))
13311331-13321332-(defun beeminder-clear-or-show-kills (arg)
13331333- "Unkill all killed goals if ARG is nil.
13341334-With prefix argument, show the list of killed goals."
13351335- (interactive "P")
13361336- (if arg
13371337- (beeminder-show-kills)
13381338- (beeminder-clear-kills)))
13391339-13401340-(define-key beeminder-mode-map (kbd "C-y") #'beeminder-clear-or-show-kills)
13411341-(define-key beeminder-filter-map "y" #'beeminder-clear-or-show-kills)
13421342-13431343-(defun beeminder-apply-filter (filter)
13441344- "Apply FILTER (a dotted pair of symbol and parameter).
13451345-This means deleting some goals from `beeminder-goals-ewoc'."
13461346- (save-current-goal
13471347- (ewoc-filter beeminder-goals-ewoc
13481348- (lambda (goal)
13491349- (funcall (cadr (assoc (car filter) beeminder-filters))
13501350- goal (cdr filter))))))
13511351-13521352-(defun beeminder-apply-filters ()
13531353- "Apply filters from `beeminder-current-filters' in sequence."
13541354- (mapc #'beeminder-apply-filter beeminder-current-filters))
13551355-13561356-(defun beeminder-enable-filter (filter parameter)
13571357- "Enable FILTER (symbol) with PARAMETER (number).
13581358-Disable FILTER if PARAMETER is nil."
13591359- (beeminder-set-alist-value filter 'beeminder-current-filters parameter)
13601360- (setq beeminder-current-filters (rassq-delete-all nil beeminder-current-filters))
13611361- (beeminder-refresh-goals-list))
13621362-13631363-(defun beeminder-filter-parameter (raw-prefix default)
13641364- "Return filter parameter based on RAW-PREFIX and DEFAULT."
13651365- (cond ((eq raw-prefix '-) nil)
13661366- ((null raw-prefix) default)
13671367- (t (prefix-numeric-value raw-prefix))))
13681368-13691369-(defun beeminder-filter-by-losedate (&optional days)
13701370- "Filter out goals with time to losedate greater than DAYS."
13711371- (interactive "P")
13721372- (beeminder-enable-filter 'losedate
13731373- (beeminder-filter-parameter days
13741374- beeminder-default-filter-days)))
13751375-13761376-(defun beeminder-filter-by-donetoday (&optional percentage)
13771377- "Filter out goals with donetoday greater than PERCENTAGE."
13781378- (interactive "P")
13791379- (beeminder-enable-filter 'donetoday
13801380- (beeminder-filter-parameter percentage
13811381- beeminder-default-filter-donetoday)))
13821382-13831383-(defun beeminder-filter-by-urgent (&optional hours)
13841384- "Filter out goals with time to deadline greater than HOURS."
13851385- (interactive "P")
13861386- (beeminder-enable-filter 'urgent
13871387- (beeminder-filter-parameter hours
13881388- beeminder-default-filter-urgent-hours)))
13891389-13901390-(define-key beeminder-mode-map (kbd "d") #'beeminder-filter-by-losedate)
13911391-(define-key beeminder-filter-map (kbd "d") #'beeminder-filter-by-losedate)
13921392-(define-key beeminder-mode-map (kbd "t") #'beeminder-filter-by-donetoday)
13931393-(define-key beeminder-filter-map (kbd "t") #'beeminder-filter-by-donetoday)
13941394-(define-key beeminder-mode-map (kbd "u") #'beeminder-filter-by-urgent)
13951395-(define-key beeminder-filter-map (kbd "u") #'beeminder-filter-by-urgent)
13961396-13971397-13981398-;; Displaying goal details
13991399-14001400-(defcustom beeminder-goal-template-fields-alist
14011401- '((slug . (propertize (symbol-name (beeminder-get-slug goal)) 'face (beeminder-goal-face goal)))
14021402- (limsum . (propertize (cdr (assoc 'limsum goal)) 'face (beeminder-goal-face goal)))
14031403- (backburnerp . (if (string= (cdr (assoc 'burner goal)) "backburner") "(backburner)" ""))
14041404- (username . beeminder-username)
14051405- (dirtyp . (if (assoc (beeminder-get-slug goal) beeminder-dirty-alist)
14061406- (propertize " (goal dirty!)" 'face 'beeminder-dirty) ""))
14071407- (target . (highlight-subtly (number-to-string (elt (cdr (assoc 'mathishard goal)) 1))))
14081408- (goaldate . (highlight-subtly (format-time-string "%x" (seconds-to-time (elt (cdr (assoc 'mathishard goal)) 0)))))
14091409- (rate . (highlight-subtly (number-to-human-string (beeminder-get-rate goal))))
14101410- (runit . (highlight-subtly
14111411- (cl-case (intern (cdr (assoc 'runits goal)))
14121412- (d "day")
14131413- (w "week")
14141414- (m "month")
14151415- (h "hour")
14161416- (y "year"))))
14171417- (curval . (highlight-subtly (number-to-human-string (cdr (assoc 'curval goal)))))
14181418- (autodatap . (aif (cdr (assoc 'autodata goal))
14191419- (concat ", autodata source: "
14201420- (highlight-subtly it))
14211421- ""))
14221422- (goaltype . (highlight-subtly (beeminder-display-goal-type (cdr (assoc 'goal_type goal)))))
14231423- (losedate . (highlight-subtly (trim-leading-whitespace
14241424- (beeminder-display-losedate-human goal))))
14251425- (pledge . (highlight-subtly (beeminder-display-pledge goal)))
14261426- (midnight . (highlight-subtly (beeminder-display-midnight-setting (cdr (assoc 'deadline goal)))))
14271427- (donetoday . (highlight-subtly (number-to-human-string (cdr (assoc 'donetoday goal)))))
14281428- (datapoints . (propertize (beeminder-format-datapoints goal) 'face
14291429- 'shadow))
14301430- (history-length . (highlight-subtly (let ((hl (cdr (assoc 'history-length goal))))
14311431- (if (zerop hl) "all" (format "%s" hl))))))
14321432- "Alist of symbols and corresponding pieces of code to evaluate
14331433-and insert the result in the goal details info.")
14341434-14351435-(defun beeminder-display-time-field (alist field)
14361436- "Return ALIST's (unix-time) FIELD formatted."
14371437- (format-time-string "%x %X" (seconds-to-time (cdr (assoc field alist)))))
14381438-14391439-(defcustom beeminder-datapoint-format
14401440- '((beeminder-display-string-field id 25 t)
14411441- (beeminder-display-time-field timestamp)
14421442- " "
14431443- (beeminder-display-string-field value 8)
14441444- " "
14451445- (beeminder-display-string-field comment))
14461446- "The format for displaying a goal's datapoint.
14471447-The format is identical to that of `beeminder-goal-pp-format'.")
14481448-14491449-(defun beeminder-datapoint-representation (datapoint)
14501450- "The string representation of DATAPOINT."
14511451- ;; TODO: factor out common code of this and `beeminder-goal-pp-format'.
14521452- (mapconcat (lambda (field-specifier)
14531453- (cond
14541454- ((functionp field-specifier) (funcall field-specifier datapoint))
14551455- ((consp field-specifier) (apply (car field-specifier)
14561456- datapoint
14571457- (cdr field-specifier)))
14581458- ((stringp field-specifier) field-specifier)))
14591459- beeminder-datapoint-format ""))
14601460-14611461-(defun beeminder-format-datapoints (goal)
14621462- "Return the printed representation of GOAL's datapoints."
14631463- (mapconcat #'beeminder-datapoint-representation
14641464- (reverse (cdr (assoc 'datapoints goal)))
14651465- "\n"))
14661466-14671467-(defun beeminder-insert-goal-template-with-expansion (template goal)
14681468- "Insert TEMPLATE with information about GOAL.
14691469-If a substring of the form \"#SYMBOL\" is found, and SYMBOL is
14701470-a key in the `beeminder-goal-template-fields-alist' variable,
14711471-\"#SYMBOL\" is replaced with result of evaluating the associated
14721472-value. If there is no such entry, the SYMBOL is looked up in the
14731473-alist representing the current goal, and \"#SYMBOL\" is replaced
14741474-with its printed representation (using `format''s \"%s\"
14751475-specifier). In the latter case, the result is colorized with the
14761476-`subtle-highlight-face'; in the former case, code in
14771477-`beeminder-goal-template-fields-alist' should take care of
14781478-colorization if needed.
14791479-14801480-If a substring of the form \"#SEXP\" is found, and SEXP is not
14811481-a symbol, \"#SEXP\" is replaced with the result of evaluating SEXP.
14821482-Within SEXP, the variable `goal' is bound to the alist holding the
14831483-current's goal properties.
14841484-14851485-Should someone want to insert a literal \"#\" character, the form
14861486-\"#(identity \"#\")\" can be used.
14871487-14881488-Warning: this function uses `eval', so evil code in TEMPLATE or
14891489-`beeminder-goal-template-fields-alist' can do real harm!"
14901490- (save-excursion (insert template))
14911491- (while (search-forward "#" nil t)
14921492- (let ((begin (1- (point)))
14931493- (sexp (read (current-buffer))))
14941494- (delete-region begin (point))
14951495- (insert (format "%s"
14961496- (cond ((symbolp sexp)
14971497- (aif (assoc sexp beeminder-goal-template-fields-alist)
14981498- (eval (cdr it) `((goal . ,goal) t))
14991499- (highlight-subtly (format "%s" (cdr (assoc sexp goal))))))
15001500- (t (eval sexp))))))))
15011501-15021502-(define-derived-mode beeminder-goal-mode special-mode "Beeminder goal"
15031503- "A major mode for a buffer displaying details of a Beeminder goal,
15041504-in particular the history of datapoints.")
15051505-15061506-(defun beeminder-next-datapoint (count)
15071507- "Move forward COUNT datapoints."
15081508- (interactive "p")
15091509- (forward-line 1)
15101510- (re-search-forward "^[0-9a-f]\\{24\\}" nil t count)
15111511- (beginning-of-line))
15121512-15131513-(defun beeminder-previous-datapoint (count)
15141514- "Move forward COUNT datapoints."
15151515- (interactive "p")
15161516- (beginning-of-line)
15171517- (re-search-backward "^[0-9a-f]\\{24\\}" nil t count))
15181518-15191519-(define-key beeminder-goal-mode-map (kbd "n") #'beeminder-next-datapoint)
15201520-(define-key beeminder-goal-mode-map (kbd "p") #'beeminder-previous-datapoint)
15211521-15221522-(defface subtle-highlight '((t :foreground "#004400"))
15231523- "Face for subtly highlighting things.")
15241524-15251525-(defun highlight-subtly (string)
15261526- "Make STRING stand out, but only a little."
15271527- (propertize string 'face 'subtle-highlight))
15281528-15291529-(defun number-to-human-string (number &optional width)
15301530- "Convert NUMBER to a human-friendly form, at least WIDTH characters.
15311531-If NUMBER is greater than 10, use one decimal place. Otherwise,
15321532-use two. Trim any non-significant trailing zeros and the decimal
15331533-point if needed."
15341534- (let ((str (replace-regexp-in-string
15351535- "\\.?0+$" ""
15361536- (format (cond
15371537- ((> number 10) "%.1f")
15381538- (t "%.2f"))
15391539- number))))
15401540- (if width (format (format "%%%ds" width) str) str)))
15411541-15421542-(defun beeminder-display-midnight-setting (seconds)
15431543- "Convert SECONDS to or from midnight to a time string."
15441544- (when (< seconds 0) (setq seconds (+ seconds (* 24 60 60))))
15451545- (let* ((hours (/ seconds 60 60))
15461546- (minutes (/ (- seconds (* hours 60 60)) 60)))
15471547- (format "%d:%02d" hours minutes)))
15481548-15491549-(defun beeminder-display-goal-type (goaltype)
15501550- "Convert GOALTYPE to a printed representation."
15511551- (cl-case (intern goaltype)
15521552- (hustler "do more")
15531553- (biker "odometer")
15541554- (fatloser "weight loss")
15551555- (gainer "gain weight")
15561556- (inboxer "inbox fewer")
15571557- (drinker "do less")
15581558- (custom "custom")))
15591559-15601560-(defcustom beeminder-goal-template
15611561- "Details for Beeminder goal #slug for user #username#backburnerp
15621562-#title#dirtyp
15631563-goal target: #target on #goaldate at rate #rate per #runit (currently at #curval, done today: #donetoday)
15641564-goal type: #goaltype#autodatap
15651565-safe until #losedate (current pledge: #pledge, left to do: #limsum, midnight setting: #midnight)
15661566-15671567-Recent datapoints (#history-length days):
15681568-#datapoints
15691569-"
15701570- "The default template for displaying goal details.
15711571-See the docstring of the function
15721572-`beeminder-insert-goal-template-with-expansion' for the list of
15731573-available keywords.")
15741574-15751575-(defun beeminder-refresh-goal-details ()
15761576- "Refresh goal details, assuming that the respective buffer
15771577-exists and is set up properly."
15781578- (let ((inhibit-read-only t))
15791579- (erase-buffer)
15801580- (beeminder-insert-goal-template-with-expansion
15811581- beeminder-goal-template
15821582- beeminder-detailed-goal))
15831583- (goto-char (point-min)))
15841584-15851585-(defvar beeminder-detailed-goal nil
15861586- "The current goal in the details buffer.")
15871587-15881588-(defun beeminder-display-goal-details (goal)
15891589- "Display details about GOAL in a temporary buffer."
15901590- (interactive (list (current-or-read-goal)))
15911591- (pop-to-buffer "*Beeminder goal details*")
15921592- (remove-images (point-min) (point-max))
15931593- (beeminder-goal-mode)
15941594- (setq-local beeminder-detailed-goal goal)
15951595- (beeminder-refresh-goal-details))
15961596-15971597-(define-key beeminder-mode-map (kbd "TAB") #'beeminder-display-goal-details)
15981598-15991599-(defcustom beeminder-confirm-datapoint-deletion #'y-or-n-p
16001600- "How to ask for confirmation of datapoint deletion.
16011601-If nil, don't ask."
16021602- :type '(choice (const :tag "Ask with yes-or-no-p" yes-or-no-p)
16031603- (const :tag "Ask with y-or-n-p" y-or-n-p)
16041604- (const :tag "Don't ask at all" nil)
16051605- (funtion :tag "Predicate function"))
16061606- :group 'beeminder)
16071607-16081608-(defun beeminder-get-datapoint-id ()
16091609- "Return the id of the datapoint at point."
16101610- (save-excursion
16111611- (beginning-of-line)
16121612- (if (looking-at "[0-9a-f]\\{24\\}")
16131613- (match-string-no-properties 0)
16141614- (error "Not at a datapoint"))))
16151615-16161616-(defun beeminder-delete-datapoint (id)
16171617- "Delete datapoint with id ID.
16181618-If called interactively, take the id from the beginning of the
16191619-line."
16201620- (interactive (list (beeminder-get-datapoint-id)))
16211621- (if (and beeminder-confirm-datapoint-deletion
16221622- (funcall beeminder-confirm-datapoint-deletion "Are you sure you want to delete this datapoint?"))
16231623- (beeminder-request-delete
16241624- (concat "/goals/" (cdr (assoc 'slug beeminder-detailed-goal)) "/datapoints/" id ".json")
16251625- (cl-function (lambda (&rest _)
16261626- (let ((datapoints (cdr (assoc 'datapoints beeminder-detailed-goal))))
16271627- (beeminder-inc-alist-value 'donetoday
16281628- beeminder-detailed-goal
16291629- (- (cdr (assoc 'value (cl-find id datapoints
16301630- :key (lambda (dp)
16311631- (cdr (assoc 'id dp)))
16321632- :test #'string=)))))
16331633- (cl-delete id datapoints :key (lambda (dp) (cdr (assoc 'id dp))) :test #'string=)
16341634- (beeminder-make-goal-dirty (beeminder-get-slug beeminder-detailed-goal))
16351635- (beeminder-refresh-goal-details)
16361636- (beeminder-log (format "datapoint %s succesfully deleted" id)))))
16371637- (cl-function (lambda (&key error-thrown &allow-other-keys)
16381638- (beeminder-log (format "error while deleting datapoint %s for goal %s: %s"
16391639- id
16401640- (cdr (assoc 'slug beeminder-detailed-goal))
16411641- error-thrown)
16421642- :error))))))
16431643-16441644-(define-key beeminder-goal-mode-map (kbd "d") #'beeminder-delete-datapoint)
16451645-16461646-(defun beeminder-get-datapoint (id datapoints)
16471647- "Return datapoint with id ID from DATAPOINTS."
16481648- (cl-find id datapoints
16491649- :key (lambda (dp)
16501650- (cdr (assoc 'id dp)))
16511651- :test #'string=))
16521652-16531653-(defun beeminder-edit-datapoint (id)
16541654- "Edit datapoint with id ID.
16551655-If called interactively, take the id from the beginning of the
16561656-line."
16571657- (interactive (list (beeminder-get-datapoint-id)))
16581658- (let* ((datapoints (reverse (cdr (assoc 'datapoints beeminder-detailed-goal))))
16591659- (datapoint (aif (beeminder-get-datapoint id datapoints)
16601660- it
16611661- (error "%s" "Invalid datapoint id -- beeminder-get-datapoint")))
16621662- (timestamp (ask-for-timestamp (cdr (assoc 'timestamp datapoint))))
16631663- (value (string-to-number
16641664- (let ((default-value (cdr (assoc 'value datapoint))))
16651665- (read-string (format "Value (default %s): " default-value)
16661666- nil nil (number-to-string default-value)))))
16671667- (comment (let ((comment-history
16681668- (mapcar (lambda (dp)
16691669- (cdr (assoc 'comment dp)))
16701670- datapoints)))
16711671- (read-string "Comment: "
16721672- (cdr (assoc 'comment datapoint))
16731673- (cons 'comment-history
16741674- (1+ (position
16751675- id
16761676- datapoints
16771677- :key (lambda (dp)
16781678- (cdr (assoc 'id dp)))
16791679- :test #'string=)))
16801680- nil t))))
16811681- (beeminder-log "updating datapoint...")
16821682- (beeminder-request-put (format "/goals/%s/datapoints/%s.json"
16831683- (beeminder-get-slug beeminder-detailed-goal)
16841684- id)
16851685- (list
16861686- (cons "value" (format "%s" value))
16871687- (cons "comment" comment)
16881688- (cons "timestamp" (format "%s" timestamp)))
16891689- (cl-function (lambda (&rest _) (beeminder-log "updating datapoint...done")))
16901690- (cl-function (lambda (&rest _) (beeminder-log "updating datapoint failed!" :error))))))
16911691-16921692-(define-key beeminder-goal-mode-map (kbd "e") #'beeminder-edit-datapoint)
16931693-16941694-(defun beeminder-display-raw-goal-details ()
16951695- "Display the raw details about GOAL in a temporary buffer.
16961696-The internal representation is an alist."
16971697- (interactive)
16981698- (let ((goal beeminder-detailed-goal))
16991699- (pop-to-buffer "*Beeminder raw goal details*")
17001700- (let ((inhibit-read-only t))
17011701- (erase-buffer)
17021702- (insert (pp-to-string goal))
17031703- (goto-char (point-min))
17041704- (special-mode))))
17051705-17061706-(define-key beeminder-goal-mode-map (kbd ".") #'beeminder-display-raw-goal-details)
17071707-17081708-(defun beeminder-view-in-browser (goal)
17091709- "View GOAL in the web browser."
17101710- (interactive (list (or beeminder-detailed-goal (current-or-read-goal))))
17111711- (browse-url (format "https://beeminder.com/%s/%s" beeminder-username (beeminder-alist-get 'slug goal))))
17121712-17131713-(define-key beeminder-mode-map (kbd "W") #'beeminder-view-in-browser)
17141714-(define-key beeminder-goal-mode-map (kbd "W") #'beeminder-view-in-browser)
17151715-17161716-17171717-;; Downloading more datapoints
17181718-(defun beeminder-download-datapoints (slug-str days)
17191719- "Download datapoints for goal named SLUG-STR from last DAYS.
17201720-If called interactively in the *Beeminder goal details* buffer, use
17211721-current goal; otherwise, ask for the goal. If called without a prefix
17221722-argument, increase the downloaded history by
17231723-`beeminder-history-length' days."
17241724- (interactive (list (if beeminder-detailed-goal
17251725- (cdr (assoc 'slug beeminder-detailed-goal))
17261726- (cdr (assoc 'slug (current-or-read-goal))))
17271727- current-prefix-arg))
17281728- (setq days
17291729- (if days
17301730- (prefix-numeric-value days)
17311731- beeminder-history-length))
17321732- (setq days
17331733- (cond ((> days 0)
17341734- (+ (cdr (assoc 'history-length (beeminder-slug-to-goal (intern slug-str))))
17351735- days))
17361736- ((< days 0)
17371737- (- days))
17381738- (t 0)))
17391739- (beeminder-log (format "fetching datapoints for goal %s..." slug-str))
17401740- (beeminder-request-get (format "/goals/%s.json" slug-str)
17411741- (cons (cons "datapoints" "true")
17421742- (unless (zerop days)
17431743- (cons (cons "diff_since"
17441744- (number-to-string
17451745- (- (last-user-midnight (beeminder-current-time))
17461746- (* days 24 60 60))))
17471747- nil)))
17481748- (cl-function (lambda (&key data &allow-other-keys)
17491749- (beeminder-log (format "fetching datapoints for goal %s...done" slug-str))
17501750- (let* ((gl (beeminder-slug-to-goal (intern slug-str)))
17511751- (dp (assoc 'datapoints gl))
17521752- (hl (assoc 'history-length gl)))
17531753- (setcdr dp (beeminder-to-list (cdr (assoc 'datapoints data))))
17541754- (setcdr hl days)
17551755- (when beeminder-detailed-goal
17561756- (beeminder-refresh-goal-details)))))
17571757- (cl-function (lambda (&key error-thrown &allow-other-keys)
17581758- (beeminder-log (format "fetching datapoints for goal %s...error: %s" slug-str error-thrown))))))
17591759-17601760-(define-key beeminder-goal-mode-map (kbd "m") #'beeminder-download-datapoints)
17611761-17621762-17631763-;; Statistics
17641764-(defun beeminder-determine-date (time day-end)
17651765- "Return date for TIME, taking DAY-END into account.
17661766-TIME is the number of seconds counted from the beginning of Unix
17671767-epoch; DAY-END is the offset from midnight in seconds. The date
17681768-is a string in ISO 8601 basic format (i.e., \"20160417\" for
17691769-April 17, 2016)."
17701770- (format-time-string "%Y%m%d" (time-subtract time day-end)))
17711771-17721772-(defun beeminder-gather-datapoints-by-day (goal)
17731773- "Return an alist of datapoints, collated by date.
17741774-The car of each entry is a string representing the date in ISO
17751775-8601 basic format (i.e., \"20160417\" for April 17, 2016), and
17761776-the cdr is the list of datapoints. If
17771777-`beeminder-use-goal-midnight-today-values' is nil, use the goal's
17781778-\"midnight\" setting to determine the date; otherwise, use
17791779-`beeminder-when-the-day-ends'."
17801780- (let ((day-end (if beeminder-use-goal-midnight-today-values
17811781- beeminder-when-the-day-ends
17821782- (cdr (assoc 'deadline goal))))
17831783- datapoints-by-day)
17841784- (mapc (lambda (datapoint)
17851785- (let ((date (beeminder-determine-date (cdr (assoc 'timestamp datapoint)) day-end)))
17861786- (aif (assoc date datapoints-by-day)
17871787- (push datapoint (cdr it))
17881788- (push (list date datapoint) datapoints-by-day))))
17891789- (cdr (assoc 'datapoints goal)))
17901790- datapoints-by-day))
17911791-17921792-;; (defun beeminder-gather-datapoints-by-day (goal)
17931793-;; "Return an alist of datapoints, collated by date.
17941794-;; The car of each entry is a string representing the date in ISO
17951795-;; 8601 basic format (i.e., \"20160417\" for April 17, 2016), and
17961796-;; the cdr is the list of datapoints. If
17971797-;; `beeminder-use-goal-midnight-today-values' is nil, use the goal's
17981798-;; \"midnight\" setting to determine the date; otherwise, use
17991799-;; `beeminder-when-the-day-ends'."
18001800-;; (let ((day-end (if beeminder-use-goal-midnight-today-values
18011801-;; beeminder-when-the-day-ends
18021802-;; (cdr (assoc 'deadline goal))))
18031803-;; datapoints-by-day)
18041804-;; (mapc (lambda (datapoint)
18051805-;; (let ((date (beeminder-determine-date (cdr (assoc 'timestamp datapoint)) day-end)))
18061806-;; (aif (assoc date datapoints-by-day)
18071807-;; (push datapoint (cdr it))
18081808-;; (push (list date datapoint) datapoints-by-day))))
18091809-;; (cdr (assoc 'datapoints goal)))
18101810-;; datapoints-by-day))
18111811-18121812-(defun beeminder-uniq-mean (list)
18131813- "Return the mean of numbers in LIST after deleting
18141814- duplicates."
18151815- (let ((list list))
18161816- (/ (apply #'+ (delete-dups list))
18171817- (length list)
18181818- 1.0)))
18191819-18201820-(defun beeminder-median (list)
18211821- "Return a median of numbers in LIST.
18221822-If LIST contains an even number of elements n, return
18231823-the (n/2)-th one."
18241824- (let ((list list) median-list)
18251825- (setq list (sort list #'<)
18261826- median-list list)
18271827- (while (cddr list)
18281828- (setq median-list (cdr median-list)
18291829- list (cddr list)))
18301830- (if (cdr list)
18311831- (* 0.5 (+ (car median-list) (cadr median-list)))
18321832- (car median-list))))
18331833-18341834-(defvar beeminder-aggregation-methods
18351835- '(("sum" . (lambda (dps) (apply #'+ dps)))
18361836- ("last" . car) ; this is no mistake - the list is in reverse order!
18371837- ("first" . (lambda (dps) (car (last dps))))
18381838- ("min" . (lambda (dps) (apply #'min dps)))
18391839- ("max" . (lambda (dps) (apply #'max dps)))
18401840- ("truemean" . (lambda (dps) (/ (apply #'+ dps) (length dps) 1.0)))
18411841- ("uniqmean" . beeminder-uniq-mean)
18421842- ("mean" . beeminder-uniq-mean)
18431843- ("median" . beeminder-median)
18441844- ("jolly" . (lambda (dps) (if dps 1 0)))
18451845- ("binary" . (lambda (dps) (if dps 1 0)))
18461846- ("nonzero" . (lambda (dps) (if (cl-some (lambda (dp) (not (= 0 dp))) dps) 1 0)))
18471847- ("triangle" . (lambda (dps) (let ((sum (apply #'+ dps))) (* sum (1+ sum) 0.5))))
18481848- ("square" . (lambda (dps) (let ((sum (apply #'+ dps))) (* sum sum))))
18491849- ("count" . length))
18501850- "An alist mapping aggregation methods to actual functions.")
18511851-18521852-(defun beeminder-aggregate-values (values aggday)
18531853- "Aggregate VALUES (from one day) using the AGGDAY method."
18541854- (funcall (beeminder-alist-get aggday beeminder-aggregation-methods)
18551855- values))
18561856-18571857-(defun beeminder-aggregate-datapoints (datapoints-by-day aggday)
18581858- "Aggregate DATAPOINTS-BY-DAY using the AGGDAY method."
18591859- (mapcar (lambda (day-datapoints)
18601860- (cons (car day-datapoints)
18611861- (beeminder-aggregate-values
18621862- (mapcar (lambda (dp) (beeminder-alist-get 'value dp))
18631863- (cdr day-datapoints))
18641864- aggday)))
18651865- datapoints-by-day))
18661866-18671867-18681868-;; Displaying graphs
18691869-18701870-(defun beeminder-download-graph (slug-str)
18711871- "Download graph for goal SLUG and put it in the tmp directory,
18721872-under the \"beeminder-el\" subdirectory and filename
18731873-\"SLUG.png\". Return the full filename."
18741874- (make-directory (concat temporary-file-directory "beeminder-el") t)
18751875- (let* ((image-file (concat temporary-file-directory "beeminder-el/" slug-str ".png"))
18761876- (inhibit-message t))
18771877- (url-copy-file (beeminder-alist-get 'graph_url (beeminder-slug-to-goal (intern slug-str)))
18781878- image-file
18791879- t)
18801880- image-file))
18811881-18821882-(defun beeminder-insert-graph (image)
18831883- "Insert IMAGE at end-of-buffer. Remove any existing images
18841884-first and position the point and window so that the image can be
18851885-seen next."
18861886- (remove-images (point-min) (point-max))
18871887- (goto-char (point-max))
18881888- (let ((inhibit-read-only t))
18891889- (remove-images (point-min) (point-max))
18901890- (save-excursion
18911891- (insert "\n")
18921892- (put-image image (point-max) "[Graphs are not supported in this Emacs!]")))
18931893- (let* ((size (image-size image))
18941894- (width (ceiling (car size)))
18951895- (height (ceiling (cdr size))))
18961896- (fit-window-to-buffer (selected-window) height height width width))
18971897- (recenter 0))
18981898-18991899-(defun beeminder-display-graph (slug-str)
19001900- "Download the graph of the goal SLUG-STR and display it.
19011901-Switch to the \"details\" buffer first if needed. Do nothing if
19021902-the graph is already displayed."
19031903- (interactive (list (if beeminder-detailed-goal
19041904- (cdr (assoc 'slug beeminder-detailed-goal))
19051905- (cdr (assoc 'slug (current-or-read-goal))))))
19061906- (let* ((image-file (beeminder-download-graph slug-str))
19071907- (image (create-image image-file)))
19081908- (beeminder-display-goal-details (beeminder-slug-to-goal (intern slug-str)))
19091909- (beeminder-insert-graph image)))
19101910-19111911-(define-key beeminder-goal-mode-map (kbd "i") #'beeminder-display-graph)
19121912-(define-key beeminder-mode-map (kbd "i") #'beeminder-display-graph)
19131913-19141914-19151915-;; Org-mode integration
19161916-19171917-(defcustom beeminder-org-inherit-beeminder-properties nil
19181918- "Make beeminder.el use property inheritance.")
19191919-19201920-(defcustom beeminder-org-default-comment "%h at %t"
19211921- "Default format of the comment")
19221922-19231923-(defun beeminder-org-string-substitute (string)
19241924- "Substitute strings for percent-sign codes in STRING.
19251925-Codes are: `%t' - current time, `%h' - current headline, `%p' -
19261926-current path, `%%' - percent sign."
19271927- (let ((time (beeminder-current-time-string))
19281928- (headline (substring-no-properties (org-get-heading t t)))
19291929- (path (mapconcat #'identity (org-get-outline-path t) "/")))
19301930- (format-spec string
19311931- `((?% . "%")
19321932- (?t . ,time)
19331933- (?h . ,headline)
19341934- (?p . ,path)))))
19351935-19361936-(defun beeminder-org-generate-comment ()
19371937- "Given the comment property, generate the comment text. Assume
19381938-that the point is in the right place."
19391939- (let ((comment-prop
19401940- (or (org-entry-get (point)
19411941- "comment"
19421942- beeminder-org-inherit-beeminder-properties)
19431943- beeminder-org-default-comment)))
19441944- (cond
19451945- ((string= comment-prop "time")
19461946- (concat "via Org-mode at " (beeminder-current-time-string)))
19471947- ((string= comment-prop "ask")
19481948- nil)
19491949- ((or (string= comment-prop "headline")
19501950- (null comment-prop))
19511951- (substring-no-properties (org-get-heading t t)))
19521952- ((string= comment-prop "path")
19531953- (mapconcat #'identity (org-get-outline-path t) "/"))
19541954- (t (beeminder-org-string-substitute comment-prop)))))
19551955-19561956-(defun beeminder-org-submit-on-done (state-change)
19571957- "Submit a datapoint when marking an item as DONE.
19581958-This function should be placed in `org-trigger-hook'. It looks
19591959-up the following properties of the headline: the \"beeminder\"
19601960-property (which should be set to \"done\", the \"slug\"
19611961-property (which should be set to the slug of the goal), the
19621962-\"amount\" property (defaults to 1), the \"ask-comment\"
19631963-property (asks for the comment if it is present)."
19641964- (let ((position (plist-get state-change :position)))
19651965- (if (and (string= (downcase (or (org-entry-get position
19661966- "beeminder"
19671967- beeminder-org-inherit-beeminder-properties)
19681968- ""))
19691969- "done")
19701970- (eq (plist-get state-change :type)
19711971- 'todo-state-change)
19721972- (member (plist-get state-change :to)
19731973- org-done-keywords))
19741974- (let* ((slug-str (org-entry-get position
19751975- "slug"
19761976- beeminder-org-inherit-beeminder-properties))
19771977- (amount (aif (org-entry-get position
19781978- "amount"
19791979- beeminder-org-inherit-beeminder-properties)
19801980- (string-to-number it)
19811981- 1))
19821982- (comment (beeminder-org-generate-comment)))
19831983- (beeminder-submit-datapoint slug-str amount comment)))))
19841984-19851985-(defun beeminder-org-submit-clock-at-point ()
19861986- "Submit the data from the clock item at point to Beeminder.
19871987-This is mainly useful if submitting on clocking out (see
19881988-`beeminder-org-submit-on-clock-out' failed for some reason, so
19891989-that the user may want to submit clock items later."
19901990- (interactive)
19911991- (let ((element (org-element-at-point)))
19921992- (if (eq (org-element-type element) 'clock)
19931993- (let ((timestamp (org-element-property :value element))
19941994- (duration (org-element-property :duration element)))
19951995- (when (string-match "\\([[:digit:]]+\\):\\([[:digit:]]\\{2\\}\\)" duration)
19961996- (let* ((minutes (+ (* 60 (string-to-number (match-string 1 duration)))
19971997- (string-to-number (match-string 2 duration))))
19981998- (slug-str (org-entry-get (point)
19991999- "slug"
20002000- beeminder-org-inherit-beeminder-properties))
20012001- (comment (beeminder-org-generate-comment))
20022002- (multiplier (cl-case (intern (or (org-entry-get (point)
20032003- "unit"
20042004- beeminder-org-inherit-beeminder-properties)
20052005- ""))
20062006- ((hour hours)
20072007- (/ 1 60.0))
20082008- ((hail-Mary hail-Marys)
20092009- 3)
20102010- ; 1 hail-Mary ≈ 20 seconds
20112011- (t 1)))
20122012- (year-end (org-element-property :year-end timestamp))
20132013- (month-end (org-element-property :month-end timestamp))
20142014- (day-end (org-element-property :day-end timestamp))
20152015- (hour-end (org-element-property :hour-end timestamp))
20162016- (minute-end (org-element-property :minute-end timestamp))
20172017- (id (format "%04d%02d%02d%02d%02dto%04d%02d%02d%02d%02d"
20182018- (org-element-property :year-start timestamp)
20192019- (org-element-property :month-start timestamp)
20202020- (org-element-property :day-start timestamp)
20212021- (org-element-property :hour-start timestamp)
20222022- (org-element-property :minute-start timestamp)
20232023- year-end
20242024- month-end
20252025- day-end
20262026- hour-end
20272027- minute-end))
20282028- (timestamp (time-to-seconds (encode-time
20292029- 0
20302030- minute-end
20312031- hour-end
20322032- day-end
20332033- month-end
20342034- year-end))))
20352035- (beeminder-submit-datapoint slug-str (* minutes multiplier)
20362036- comment
20372037- timestamp
20382038- id))))
20392039- (beeminder-log "no clock at point!" :nolog))))
20402040-20412041-(defcustom beeminder-org-submit-all-clocks-default-minutes (* 24 60)
20422042- "By default, only the clocks from this many lastminutes will be
20432043-submitted by `beeminder-org-submit-all-clocks'. Does not have to
20442044-be an integer (i.e., value like 0.5 means 30 seconds).")
20452045-20462046-(defun beeminder-org-submit-all-clocks (begin end minutes)
20472047- "Submit all clocks from last MINUTES in the region to Beeminder.
20482048-In interactive use, use region if active and current subtree
20492049-otherwise. Use with caution!"
20502050- (interactive (if (use-region-p)
20512051- (list (region-beginning)
20522052- (region-end)
20532053- current-prefix-arg)
20542054- (list nil nil current-prefix-arg)))
20552055- (unless (numberp minutes)
20562056- (setq minutes beeminder-org-submit-all-clocks-default-minutes))
20572057- (save-excursion
20582058- (save-restriction
20592059- (narrow-to-region
20602060- (or begin (progn (org-back-to-heading t)
20612061- (point)))
20622062- (or end (progn (org-end-of-subtree t t)
20632063- (when (and (org-at-heading-p) ; see org-narrow-to-subtree
20642064- (not (eobp)))
20652065- (backward-char 1))
20662066- (point))))
20672067- (goto-char (point-min))
20682068- (while (re-search-forward "CLOCK: " nil t)
20692069- (let ((eap (org-element-at-point)))
20702070- (when (and (eq (org-element-type eap)
20712071- 'clock)
20722072- (eq (org-element-property :status eap)
20732073- 'closed))
20742074- (let ((ts (org-element-property :value eap)))
20752075- (when (< (time-to-seconds
20762076- (time-subtract (beeminder-current-time)
20772077- (encode-time 0
20782078- (org-element-property :minute-end ts)
20792079- (org-element-property :hour-end ts)
20802080- (org-element-property :day-end ts)
20812081- (org-element-property :month-end ts)
20822082- (org-element-property :year-end ts))))
20832083- (* 60 minutes))
20842084- (beeminder-org-submit-clock-at-point)))))))))
20852085-20862086-(defun beeminder-org-submit-on-clock-out ()
20872087- "Submit the time clocked for this item.
20882088-This function should be placed in `org-clock-out-hook'. It looks
20892089-up the following properties of the headline: the \"beeminder\"
20902090-property (which should be set to \"clock\", the \"slug\"
20912091-property (which should be set to the slug of the goal), the
20922092-\"unit\" property (which may be \"minutes\", which is the
20932093-default, or \"hours\", etc.\"), the \"ask-comment\"
20942094-property (asks for the comment if it is present)."
20952095- (when (and (string= (downcase (or (org-entry-get (point)
20962096- "beeminder"
20972097- beeminder-org-inherit-beeminder-properties)
20982098- ""))
20992099- "clock")
21002100- (not (string-match " LINE REMOVED$" (or (current-message) ""))) ; this is really hackish
21012101- (org-entry-get
21022102- (point) "slug" beeminder-org-inherit-beeminder-properties))
21032103- (beeminder-org-submit-clock-at-point)))
21042104-21052105-(define-minor-mode beeminder-org-integration-mode
21062106- "Toggle a (global) minor mode for Org/Beeminder integration.
21072107-When on, clocking out and marking as DONE for headlines with suitable
21082108-:beeminder: property is submitted automatically."
21092109- :init-value nil
21102110- :global t
21112111- :lighter " B-O"
21122112- (if beeminder-org-integration-mode
21132113- (progn
21142114- (add-hook 'org-trigger-hook #'beeminder-org-submit-on-done)
21152115- (add-hook 'org-clock-out-hook #'beeminder-org-submit-on-clock-out))
21162116- (remove-hook 'org-trigger-hook #'beeminder-org-submit-on-done)
21172117- (remove-hook 'org-clock-out-hook #'beeminder-org-submit-on-clock-out)))
21182118-21192119-21202120-(provide 'beeminder)
21212121-21222122-21232123-;;; beeminder.el ends here
-6
spacemacs-private/snippets/README.md
···11-# Private directory for Yasnippets snippets
22-33-The content of this directory is ignored by Git. This is the default place
44-where to store your private yasnippets.
55-66-This path will be loaded automatically and used whenever Yasnippets loads.
···11-# Private directory for Yatemplate templates
22-33-The content of this directory is ignored by Git. This is the default place
44-where to store your private templates.
55-66-This path will be loaded automatically and used whenever Yatemplate loads.