Introduce makel.el to make future MELPA packaging easier
This commit is contained in:
parent
1afcb6f37a
commit
d277e955b9
6 changed files with 294 additions and 47 deletions
15
.github/workflows/test.yml
vendored
15
.github/workflows/test.yml
vendored
|
@ -17,12 +17,27 @@ jobs:
|
|||
- 26.3
|
||||
- snapshot
|
||||
steps:
|
||||
|
||||
- name: Set up Emacs
|
||||
uses: purcell/setup-emacs@v1.0
|
||||
with:
|
||||
version: ${{ matrix.emacs_version }}
|
||||
|
||||
- uses: actions/checkout@v1
|
||||
|
||||
- name: Install taskwarrior package
|
||||
run: sudo apt-get install -y taskwarrior
|
||||
|
||||
- name: Create taskwarrior config
|
||||
run: echo -e 'data.location=~/.task\nverbose=no' > ~/.taskrc
|
||||
|
||||
- name: Install elisp dependencies
|
||||
run: make ci-dependencies
|
||||
|
||||
# TODO: Replace this with `make check` when packaging for MELPA
|
||||
|
||||
- name: Run checkdoc
|
||||
run: make lint-checkdoc
|
||||
|
||||
- name: Run ERT test suite
|
||||
run: make test
|
||||
|
|
3
.gitignore
vendored
3
.gitignore
vendored
|
@ -1,3 +1,2 @@
|
|||
.task
|
||||
dash.el
|
||||
transient.el
|
||||
makel.el
|
31
Makefile
31
Makefile
|
@ -1,13 +1,30 @@
|
|||
ELPA_DEPENDENCIES=package-lint transient dash
|
||||
|
||||
ELPA_ARCHIVES=melpa-stable gnu
|
||||
|
||||
TEST_ERT_FILES = $(wildcard test/*.el)
|
||||
LINT_CHECKDOC_FILES = "taskwarrior.el" ${TEST_ERT_FILES}
|
||||
LINT_PACKAGE_LINT_FILES = ${LINT_CHECKDOC_FILES}
|
||||
LINT_COMPILE_FILES = ${LINT_CHECKDOC_FILES}
|
||||
|
||||
makel.mk: # Download makel
|
||||
@if [ -f ../makel/makel.mk ]; then \
|
||||
ln -s ../makel/makel.mk .; \
|
||||
else \
|
||||
curl \
|
||||
--fail --silent --show-error --insecure --location \
|
||||
--retry 9 --retry-delay 9 \
|
||||
-O https://gitlab.petton.fr/DamienCassou/makel/raw/v0.5.3/makel.mk; \
|
||||
fi
|
||||
|
||||
.PHONY: test-data
|
||||
test-data: ## Generate some example tasks
|
||||
for n in $$(seq 10); do task add "Example task $${n}"; done
|
||||
|
||||
.PHONY: test
|
||||
test: fetch-deps ## Run ERT test suite
|
||||
emacs -batch -l dash.el -l transient.el -l taskwarrior.el -l taskwarrior-test.el -f ert-run-tests-batch-and-exit
|
||||
# .PHONY: fetch-deps
|
||||
# fetch-deps: ## Fetch required dependencies
|
||||
# curl -fsSkL --retry 9 --retry-delay 9 -O "https://raw.githubusercontent.com/magit/transient/master/lisp/transient.el"
|
||||
# curl -fsSkL --retry 9 --retry-delay 9 -O "https://raw.githubusercontent.com/magnars/dash.el/master/dash.el"
|
||||
|
||||
.PHONY: fetch-deps
|
||||
fetch-deps: ## Fetch required dependencies
|
||||
curl -fsSkL --retry 9 --retry-delay 9 -O "https://raw.githubusercontent.com/magit/transient/master/lisp/transient.el"
|
||||
curl -fsSkL --retry 9 --retry-delay 9 -O "https://raw.githubusercontent.com/magnars/dash.el/master/dash.el"
|
||||
# Include makel.mk if present
|
||||
-include makel.mk
|
||||
|
|
154
makel.mk
Normal file
154
makel.mk
Normal file
|
@ -0,0 +1,154 @@
|
|||
MAKEL_VERSION=0.5.3
|
||||
|
||||
MAKEL_LOAD_PATH=-L . $(patsubst %,-L ../%,$(ELPA_DEPENDENCIES))
|
||||
|
||||
MAKEL_SET_ARCHIVES0=${ELPA_ARCHIVES}
|
||||
MAKEL_SET_ARCHIVES1=$(patsubst gnu,(cons \"gnu\" \"https://elpa.gnu.org/packages/\"),${MAKEL_SET_ARCHIVES0})
|
||||
MAKEL_SET_ARCHIVES2=$(patsubst melpa,(cons \"melpa\" \"https://melpa.org/packages/\"),${MAKEL_SET_ARCHIVES1})
|
||||
MAKEL_SET_ARCHIVES3=$(patsubst melpa-stable,(cons \"melpa-stable\" \"https://stable.melpa.org/packages/\"),${MAKEL_SET_ARCHIVES2})
|
||||
MAKEL_SET_ARCHIVES4=$(patsubst org,(cons \"org\" \"https://orgmode.org/elpa/\"),${MAKEL_SET_ARCHIVES3})
|
||||
MAKEL_SET_ARCHIVES=(setq package-archives (list ${MAKEL_SET_ARCHIVES4}))
|
||||
|
||||
EMACSBIN?=emacs
|
||||
BATCH=$(EMACSBIN) -Q --batch $(MAKEL_LOAD_PATH) \
|
||||
--eval "(setq load-prefer-newer t)" \
|
||||
--eval "(require 'package)" \
|
||||
--eval "${MAKEL_SET_ARCHIVES}" \
|
||||
--eval "(setq enable-dir-local-variables nil)" \
|
||||
--funcall package-initialize
|
||||
|
||||
CURL = curl --fail --silent --show-error --insecure \
|
||||
--location --retry 9 --retry-delay 9 \
|
||||
--remote-name-all
|
||||
|
||||
# Definition of a utility function `split_with_commas`.
|
||||
# Argument 1: a space-separated list of filenames
|
||||
# Return: a comma+space-separated list of filenames
|
||||
comma:=,
|
||||
empty:=
|
||||
space:=$(empty) $(empty)
|
||||
split_with_commas=$(subst ${space},${comma}${space},$(1))
|
||||
|
||||
|
||||
.PHONY: debug install-elpa-dependencies download-non-elpa-dependencies ci-dependencies check test test-ert test-buttercup lint lint-checkdoc lint-package-lint lint-compile
|
||||
|
||||
makel-version:
|
||||
@echo "makel v${MAKEL_VERSION}"
|
||||
|
||||
debug:
|
||||
@echo "MAKEL_LOAD_PATH=${MAKEL_LOAD_PATH}"
|
||||
@echo "MAKEL_SET_ARCHIVES=${MAKEL_SET_ARCHIVES}"
|
||||
@${BATCH} --eval "(message \"%S\" package-archives)"
|
||||
|
||||
install-elpa-dependencies:
|
||||
@if [ -n "${ELPA_DEPENDENCIES}" ]; then \
|
||||
echo "# Install ELPA dependencies: $(call split_with_commas,${ELPA_DEPENDENCIES})…"; \
|
||||
output=$$(mktemp --tmpdir "makel-ci-dependencies-XXXXX"); \
|
||||
$(BATCH) \
|
||||
--funcall package-refresh-contents \
|
||||
${patsubst %,--eval "(package-install (quote %))",${ELPA_DEPENDENCIES}} \
|
||||
> $${output} 2>&1 || ( cat $${output} && exit 1 ); \
|
||||
fi
|
||||
|
||||
download-non-elpa-dependencies:
|
||||
@if [ -n "${DOWNLOAD_DEPENDENCIES}" ]; then \
|
||||
echo "# Download non-ELPA dependencies: $(call split_with_commas,${DOWNLOAD_DEPENDENCIES})…"; \
|
||||
$(CURL) $(patsubst %,"%",${DOWNLOAD_DEPENDENCIES}); \
|
||||
fi
|
||||
|
||||
ci-dependencies: install-elpa-dependencies download-non-elpa-dependencies
|
||||
|
||||
check: test lint
|
||||
|
||||
####################################
|
||||
# Tests
|
||||
####################################
|
||||
|
||||
test: test-ert test-buttercup
|
||||
|
||||
####################################
|
||||
# Tests - ERT
|
||||
####################################
|
||||
|
||||
MAKEL_TEST_ERT_FILES0=$(filter-out %-autoloads.el,${TEST_ERT_FILES})
|
||||
MAKEL_TEST_ERT_FILES=$(patsubst %,(load-file \"%\"),${MAKEL_TEST_ERT_FILES0})
|
||||
|
||||
test-ert:
|
||||
# Run ert tests from $(call split_with_commas,${MAKEL_TEST_ERT_FILES0})…
|
||||
@output=$$(mktemp --tmpdir "makel-test-ert-XXXXX"); \
|
||||
${BATCH} \
|
||||
$(if ${TEST_ERT_OPTIONS},${TEST_ERT_OPTIONS}) \
|
||||
--eval "(progn ${MAKEL_TEST_ERT_FILES} (ert-run-tests-batch-and-exit))" \
|
||||
> $${output} 2>&1 || ( cat $${output} && exit 1 )
|
||||
|
||||
####################################
|
||||
# Tests - Buttercup
|
||||
####################################
|
||||
|
||||
test-buttercup:
|
||||
@if [ -n "${TEST_BUTTERCUP_OPTIONS}" ]; then \
|
||||
echo "# Run buttercup tests on $(call split_with_commas,${TEST_BUTTERCUP_OPTIONS})"; \
|
||||
output=$$(mktemp --tmpdir "makel-test-buttercup-XXXXX"); \
|
||||
${BATCH} \
|
||||
--eval "(require 'buttercup)" \
|
||||
-f buttercup-run-discover ${TEST_BUTTERCUP_OPTIONS} \
|
||||
> $${output} 2>&1 || ( cat $${output} && exit 1 ); \
|
||||
fi;
|
||||
|
||||
####################################
|
||||
# Lint
|
||||
####################################
|
||||
|
||||
lint: lint-checkdoc lint-package-lint lint-compile
|
||||
|
||||
####################################
|
||||
# Lint - Checkdoc
|
||||
####################################
|
||||
|
||||
MAKEL_LINT_CHECKDOC_FILES0=$(filter-out %-autoloads.el,${LINT_CHECKDOC_FILES})
|
||||
MAKEL_LINT_CHECKDOC_FILES=$(patsubst %,\"%\",${MAKEL_LINT_CHECKDOC_FILES0})
|
||||
|
||||
# This rule has to work around the fact that checkdoc doesn't throw
|
||||
# errors, it always succeeds. We thus have to check if checkdoc
|
||||
# printed anything to decide the exit status of the rule.
|
||||
lint-checkdoc:
|
||||
# Run checkdoc on $(call split_with_commas,${MAKEL_LINT_CHECKDOC_FILES0})…
|
||||
@output=$$(mktemp --tmpdir "makel-lint-checkdoc-XXXXX"); \
|
||||
${BATCH} \
|
||||
$(if ${LINT_CHECKDOC_OPTIONS},${LINT_CHECKDOC_OPTIONS}) \
|
||||
--eval "(mapcar #'checkdoc-file (list ${MAKEL_LINT_CHECKDOC_FILES}))" \
|
||||
> $${output} 2>&1; \
|
||||
if [ "$$(stat --printf='%s' $${output})" -eq 0 ]; then \
|
||||
exit 0; \
|
||||
else \
|
||||
cat $${output}; \
|
||||
exit 1; \
|
||||
fi
|
||||
|
||||
####################################
|
||||
# Lint - Package-lint
|
||||
####################################
|
||||
|
||||
MAKEL_LINT_PACKAGE_LINT_FILES=$(filter-out %-autoloads.el,${LINT_PACKAGE_LINT_FILES})
|
||||
|
||||
lint-package-lint:
|
||||
# Run package-lint on $(call split_with_commas,${MAKEL_LINT_PACKAGE_LINT_FILES})…
|
||||
@${BATCH} \
|
||||
--eval "(require 'package-lint)" \
|
||||
$(if ${LINT_PACKAGE_LINT_OPTIONS},${LINT_PACKAGE_LINT_OPTIONS}) \
|
||||
--funcall package-lint-batch-and-exit \
|
||||
${MAKEL_LINT_PACKAGE_LINT_FILES}
|
||||
|
||||
####################################
|
||||
# Lint - Compilation
|
||||
####################################
|
||||
|
||||
MAKEL_LINT_COMPILE_FILES=$(filter-out %-autoloads.el,${LINT_COMPILE_FILES})
|
||||
|
||||
lint-compile:
|
||||
# Run byte compilation on $(call split_with_commas,${MAKEL_LINT_COMPILE_FILES})…
|
||||
@${BATCH} \
|
||||
--eval "(setq byte-compile-error-on-warn t)" \
|
||||
$(if ${LINT_COMPILE_OPTIONS},${LINT_COMPILE_OPTIONS}) \
|
||||
--funcall batch-byte-compile \
|
||||
${MAKEL_LINT_COMPILE_FILES}
|
105
taskwarrior.el
105
taskwarrior.el
|
@ -1,5 +1,29 @@
|
|||
;; Frontend for taskwarrior
|
||||
;;
|
||||
;;; taskwarrior.el --- An interactive taskwarrior interface -*- lexical-binding: t; -*-
|
||||
|
||||
;; Copyright (C) 2019-2020 Patrick Winter
|
||||
|
||||
;; Author: Patrick Winter <patrickwinter@posteo.ch>
|
||||
;; Keywords: Tools
|
||||
;; Url: https://gitlab/winpat/taskwarrior.el
|
||||
;; Package-requires: ((emacs "26.3"))
|
||||
;; Version: 0.0.1
|
||||
|
||||
;; This program is free software; you can redistribute it and/or modify
|
||||
;; it under the terms of the GNU General Public License as published by
|
||||
;; the Free Software Foundation, either version 3 of the License, or
|
||||
;; (at your option) any later version.
|
||||
|
||||
;; This program is distributed in the hope that it will be useful,
|
||||
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
;; GNU General Public License for more details.
|
||||
|
||||
;; You should have received a copy of the GNU General Public License
|
||||
;; along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
;;; Commentary:
|
||||
|
||||
;; The following todo exist:
|
||||
;; TODO: Implement modeline indicator for deadline and entries
|
||||
;; TODO: Update buffer if command modifies the state
|
||||
;; TODO: Extract "1-1000" id filter into variable
|
||||
|
@ -7,6 +31,8 @@
|
|||
;; (and why alist-get does not work with strings)
|
||||
;; TODO: Restore position after taskwarrior-update-buffer is called
|
||||
|
||||
;;; Code:
|
||||
|
||||
(require 'json)
|
||||
(require 'dash)
|
||||
(require 'transient)
|
||||
|
@ -72,7 +98,7 @@
|
|||
(" M " . taskwarrior-priority-medium-face)
|
||||
(" H " . taskwarrior-priority-high-face)))
|
||||
|
||||
(defvar taskwarrior-mode-map nil "Keymap for `taskwarrior-mode'")
|
||||
(defvar taskwarrior-mode-map nil "Keymap for `taskwarrior-mode'.")
|
||||
(progn
|
||||
(setq taskwarrior-mode-map (make-sparse-keymap))
|
||||
(define-key taskwarrior-mode-map (kbd "a") 'taskwarrior-add)
|
||||
|
@ -96,6 +122,7 @@
|
|||
(define-key taskwarrior-mode-map (kbd "P") 'taskwarrior-edit-project))
|
||||
|
||||
(defun taskwarrior-load-profile (profile)
|
||||
"Load a predefined taskwarrior PROFILE."
|
||||
(interactive
|
||||
(list (completing-read "Profile: " (-map 'car taskwarrior-profile-alist))))
|
||||
(let ((filter (cdr (assoc-string profile taskwarrior-profile-alist))))
|
||||
|
@ -105,6 +132,7 @@
|
|||
(taskwarrior-update-buffer))))
|
||||
|
||||
(defun taskwarrior-unmark-task ()
|
||||
"Unmark task."
|
||||
(interactive)
|
||||
(let ((id (taskwarrior-id-at-point)))
|
||||
(if (local-variable-p 'taskwarrior-marks)
|
||||
|
@ -119,6 +147,7 @@
|
|||
(next-line)))
|
||||
|
||||
(defun taskwarrior-mark-task ()
|
||||
"Mark current task."
|
||||
(interactive)
|
||||
(let ((id (taskwarrior-id-at-point)))
|
||||
(if (local-variable-p 'taskwarrior-marks)
|
||||
|
@ -134,32 +163,38 @@
|
|||
(next-line))))
|
||||
|
||||
(defun taskwarrior-open-annotation ()
|
||||
"Open annotation on task."
|
||||
(interactive)
|
||||
(let* ((id (taskwarrior-id-at-point))
|
||||
(task (taskwarrior-export-task id))
|
||||
(annotations (-map (lambda (x) (alist-get 'description x)) (vector-to-list (alist-get 'annotations task))))
|
||||
(annotations (-map (lambda (x) (alist-get 'description x)) (taskwarrior-vector-to-list (alist-get 'annotations task))))
|
||||
(choice (completing-read "Tags: " annotations)))
|
||||
(org-open-link-from-string choice)))
|
||||
|
||||
(defun taskwarrior-info ()
|
||||
"Display detailed information about task."
|
||||
(interactive)
|
||||
(let* ((id (taskwarrior-id-at-point))
|
||||
(buf (get-buffer-create "*taskwarrior info*")))
|
||||
(progn
|
||||
(switch-to-buffer-other-window buf)
|
||||
(insert (taskwarrior--shell-command "info" "" id)))))
|
||||
(erase-buffer)
|
||||
(insert (taskwarrior--shell-command "info" id)))))
|
||||
|
||||
(defun taskwarrior--parse-created-task-id (output)
|
||||
"Extract task id from shell OUTPUT of `task add`."
|
||||
(when (string-match "^.*Created task \\([0-9]+\\)\\.*$" output)
|
||||
(message (match-string 1 output))))
|
||||
|
||||
(defun taskwarrior--parse-org-link (link)
|
||||
"Extract 'org-mode' link from LINK."
|
||||
(string-match org-bracket-link-regexp link)
|
||||
(list
|
||||
(match-string 1 link)
|
||||
(match-string 3 link)))
|
||||
|
||||
(defun taskwarrior-capture (arg)
|
||||
"Capture a taskwarrior task with content ARG."
|
||||
(interactive "P")
|
||||
(let* ((link (car (cdr (taskwarrior--parse-org-link (org-store-link arg)))))
|
||||
(description (read-from-minibuffer "Description: "))
|
||||
|
@ -168,17 +203,20 @@
|
|||
(shell-command-to-string (format "task %s annotate %s" id link))))
|
||||
|
||||
(defun taskwarrior-id-at-point ()
|
||||
"Get id of task at point."
|
||||
(let ((line (thing-at-point 'line t)))
|
||||
(string-match "^ [0-9]*" line)
|
||||
(string-trim-left (match-string 0 line))))
|
||||
|
||||
(defun taskwarrior-reset-filter ()
|
||||
"Reset the currently set filter."
|
||||
(interactive)
|
||||
(progn
|
||||
(setq-local taskwarrior-active-filter nil)
|
||||
(taskwarrior-update-buffer)))
|
||||
|
||||
(defun taskwarrior-set-filter ()
|
||||
"Set or edit the current filter."
|
||||
(interactive)
|
||||
(let ((new-filter (read-from-minibuffer "Filter: " taskwarrior-active-filter)))
|
||||
(progn
|
||||
|
@ -186,6 +224,7 @@
|
|||
(taskwarrior-update-buffer))))
|
||||
|
||||
(defun taskwarrior--shell-command (command &optional filter modifications miscellaneous confirm)
|
||||
"Run a taskwarrior COMMAND with specified FILTER MODIFICATIONS MISCELLANEOUS CONFIRM."
|
||||
(let* ((confirmation (if confirm (concat "echo " confirm " |") ""))
|
||||
(cmd (format "%s task %s %s %s %s"
|
||||
(or confirmation "")
|
||||
|
@ -197,18 +236,19 @@
|
|||
(message cmd)
|
||||
(shell-command-to-string cmd))))
|
||||
|
||||
(defun vector-to-list (vector)
|
||||
"Convert a vector to a list"
|
||||
(defun taskwarrior-vector-to-list (vector)
|
||||
"Convert a VECTOR to a list."
|
||||
(append vector nil))
|
||||
|
||||
(defun taskwarrior--concat-tag-list (tags)
|
||||
"Concat a list of TAGS in to readable format."
|
||||
(mapconcat
|
||||
(function (lambda (x) (format "+%s" x)))
|
||||
(vector-to-list tags)
|
||||
(taskwarrior-vector-to-list tags)
|
||||
" "))
|
||||
|
||||
(defun taskwarrior-export (&optional filter)
|
||||
"Turn task export into the tabulated list entry form"
|
||||
"Turn task export into the tabulated list entry form filted by FILTER."
|
||||
(let ((filter (concat filter " id.not:0")))
|
||||
(mapcar
|
||||
(lambda (entry)
|
||||
|
@ -220,11 +260,12 @@
|
|||
(tags (or (taskwarrior--concat-tag-list (alist-get 'tags entry)) ""))
|
||||
(description (format "%s" (alist-get 'description entry))))
|
||||
`(,id [,id ,urgency ,priority ,annotations ,project ,tags ,description])))
|
||||
(vector-to-list
|
||||
(taskwarrior-vector-to-list
|
||||
(json-read-from-string
|
||||
(taskwarrior--shell-command "export" filter))))))
|
||||
|
||||
(defun taskwarrior-update-buffer ()
|
||||
"Update the taskwarrior buffer."
|
||||
(interactive)
|
||||
(progn
|
||||
(setq tabulated-list-entries
|
||||
|
@ -234,15 +275,16 @@
|
|||
(tabulated-list-print t)))
|
||||
|
||||
(defun taskwarrior-export-task (id)
|
||||
(let ((task (vector-to-list
|
||||
"Export task with ID."
|
||||
(let ((task (taskwarrior-vector-to-list
|
||||
(json-read-from-string
|
||||
(taskwarrior--shell-command "export" (concat "id:" id))))))
|
||||
(if (< (length task) 1)
|
||||
(error "Seems like two task have the same id.")
|
||||
(error "Seems like two task have the same id")
|
||||
(car task))))
|
||||
|
||||
(defun taskwarrior--change-attribute (attribute)
|
||||
"Change an attribute of a task"
|
||||
"Change an ATTRIBUTE of a task."
|
||||
(let* ((prefix (concat attribute ":"))
|
||||
(id (taskwarrior-id-at-point))
|
||||
(task (taskwarrior-export-task id))
|
||||
|
@ -252,11 +294,12 @@
|
|||
(taskwarrior--mutable-shell-command "modify" id (concat prefix quoted-value))))
|
||||
|
||||
(defun taskwarrior-edit-tags ()
|
||||
"Edit tags on task."
|
||||
(interactive)
|
||||
(let* ((id (taskwarrior-id-at-point))
|
||||
(task (taskwarrior-export-task id))
|
||||
(options (split-string (shell-command-to-string "task _tags") "\n"))
|
||||
(old (vector-to-list (alist-get 'tags task)))
|
||||
(old (taskwarrior-vector-to-list (alist-get 'tags task)))
|
||||
(current-tags (mapconcat 'identity old " "))
|
||||
(new (split-string (completing-read "Tags: " options nil nil current-tags) " "))
|
||||
(added-tags (mapconcat
|
||||
|
@ -268,7 +311,7 @@
|
|||
(taskwarrior--mutable-shell-command "modify" id (concat added-tags " " removed-tags))))
|
||||
|
||||
(defun taskwarrior-edit-project ()
|
||||
"Change the project of a task"
|
||||
"Change the project of a task."
|
||||
(interactive)
|
||||
(let* ((id (taskwarrior-id-at-point))
|
||||
(task (taskwarrior-export-task id))
|
||||
|
@ -278,48 +321,55 @@
|
|||
(taskwarrior--mutable-shell-command "modify" id (concat "project:" new))))
|
||||
|
||||
(defun taskwarrior-change-description ()
|
||||
"Change the description of a task"
|
||||
"Change the description of a task."
|
||||
(interactive)
|
||||
(taskwarrior--change-attribute "description"))
|
||||
|
||||
(defun taskwarrior-edit-priority ()
|
||||
"Change the priority of a task"
|
||||
"Change the priority of a task."
|
||||
(interactive)
|
||||
(let* ((id (taskwarrior-id-at-point))
|
||||
(options '("" "H" "M" "L"))
|
||||
(new (completing-read "Priority: " options)))
|
||||
(taskwarrior--mutable-shell-command "modify" id (concat "priority:" new))))
|
||||
|
||||
|
||||
|
||||
(defun taskwarrior-add (description)
|
||||
"Add new task with DESCRIPTION."
|
||||
(interactive "sDescription: ")
|
||||
(progn
|
||||
(taskwarrior--add description)
|
||||
(taskwarrior--revert-buffer)))
|
||||
|
||||
(defun taskwarrior--add (description)
|
||||
"Add new task with DESCRIPTION."
|
||||
(let ((output (taskwarrior--shell-command "add" "" description)))
|
||||
(when (string-match "Created task \\([[:digit:]]+\\)." output)
|
||||
(match-string 1 output))))
|
||||
|
||||
(defun taskwarrior-mark-p ()
|
||||
"Whether there are any marked tasks"
|
||||
"Whether there are any marked tasks."
|
||||
(and
|
||||
(boundp 'taskwarrior-marks)
|
||||
(> (length taskwarrior-marks) 0)))
|
||||
|
||||
(defun taskwarrior-delete ()
|
||||
"Delete task at point."
|
||||
(interactive)
|
||||
(taskwarrior-multi-action 'taskwarrior--delete "Delete?"))
|
||||
|
||||
(defun taskwarrior--delete (id)
|
||||
"Delete task with id."
|
||||
"Delete task with ID."
|
||||
(taskwarrior--mutable-shell-command "delete" id "" "" "yes"))
|
||||
|
||||
(defun taskwarrior-done ()
|
||||
"Mark task at point as done."
|
||||
(interactive)
|
||||
(taskwarrior-multi-action 'taskwarrior--done "Done?"))
|
||||
|
||||
(defun taskwarrior-multi-action (action confirmation-text)
|
||||
"Run a ACTION after processing the CONFIRMATION-TEXT."
|
||||
(when (yes-or-no-p confirmation-text)
|
||||
(if (taskwarrior-mark-p)
|
||||
(dolist (id taskwarrior-marks)
|
||||
|
@ -328,11 +378,11 @@
|
|||
(funcall action id)))))
|
||||
|
||||
(defun taskwarrior--done (id)
|
||||
"Mark task as done."
|
||||
"Mark task with ID as done."
|
||||
(taskwarrior--mutable-shell-command "done" id))
|
||||
|
||||
(defun taskwarrior-annotate (annotation)
|
||||
"Delete current task."
|
||||
"Add ANNOTATION to task at point."
|
||||
(interactive "sAnnotation: ")
|
||||
(let ((id (taskwarrior-id-at-point)))
|
||||
(taskwarrior--mutable-shell-command "annotate" id annotation)))
|
||||
|
@ -344,13 +394,14 @@
|
|||
(goto-line line-number)))
|
||||
|
||||
(defun taskwarrior--mutable-shell-command (command &optional filter modifications misc confirm)
|
||||
"Run shell command and restore taskwarrior buffer."
|
||||
"Run shell COMMAND with FILTER MODIFICATIONS MISC and CONFIRM."
|
||||
(let ((line-number (line-number-at-pos)))
|
||||
(taskwarrior--shell-command command filter modifications misc confirm)
|
||||
(taskwarrior-update-buffer)
|
||||
(goto-line line-number)))
|
||||
|
||||
(defun taskwarrior--urgency-predicate (A B)
|
||||
"Compare urgency of task A to task B."
|
||||
(let ((a (aref (cadr A) 1))
|
||||
(b (aref (cadr B) 1)))
|
||||
(>
|
||||
|
@ -377,8 +428,7 @@
|
|||
;;; Externally visible functions
|
||||
;;;###autoload
|
||||
(defun taskwarrior ()
|
||||
"Open the taskwarrior buffer. If one already exists, bring it to
|
||||
the front and focus it. Otherwise, create one and load the data."
|
||||
"Open the taskwarrior buffer. If one already exists, bring it to the front and focus it. Otherwise, create one and load the data."
|
||||
(interactive)
|
||||
(let* ((buf (get-buffer-create taskwarrior-buffer-name)))
|
||||
(progn
|
||||
|
@ -389,18 +439,22 @@ the front and focus it. Otherwise, create one and load the data."
|
|||
(hl-line-mode))))
|
||||
|
||||
(defun taskwarrior-set-due ()
|
||||
"Set due date on task."
|
||||
(interactive)
|
||||
(taskwarrior--change-attribute "due"))
|
||||
|
||||
(defun taskwarrior-set-scheduled ()
|
||||
"Set schedule date on task at point."
|
||||
(interactive)
|
||||
(taskwarrior--change-attribute "scheduled"))
|
||||
|
||||
(defun taskwarrior-set-wait ()
|
||||
"Set wait date on task at point."
|
||||
(interactive)
|
||||
(taskwarrior--change-attribute "wait"))
|
||||
|
||||
(defun taskwarrior-set-untl ()
|
||||
"Set until date on task at point."
|
||||
(interactive)
|
||||
(taskwarrior--change-attribute "until"))
|
||||
|
||||
|
@ -411,3 +465,6 @@ the front and focus it. Otherwise, create one and load the data."
|
|||
("s" "scheduled" taskwarrior-set-scheduled)
|
||||
("w" "wait" taskwarrior-set-wait)
|
||||
("u" "until" taskwarrior-set-untl)]])
|
||||
|
||||
(provide 'taskwarrior)
|
||||
;;; taskwarrior.el ends here
|
||||
|
|
|
@ -1,17 +1,22 @@
|
|||
;;; taskwarrior-test.el --- taskwarrior unit tests
|
||||
;;; taskwarrior-test.el --- Taskwarrior test suite -*- lexical-binding: t; -*-
|
||||
|
||||
;; Copyright (C) 2019-2020 Patrick Winter
|
||||
;;
|
||||
;; License: GPLv3
|
||||
|
||||
;;; Commentary:
|
||||
|
||||
;; Run through make target `m̀ake test`
|
||||
|
||||
;;; Code:
|
||||
|
||||
(require 'ert)
|
||||
(require 'taskwarrior)
|
||||
|
||||
(ert-deftest taskwarrior-add-task-test ()
|
||||
(ert-deftest taskwarrior-add-task ()
|
||||
"Ensure that special characters such as quotes and parens are properly escaped when adding new tasks"
|
||||
(let* ((task-id (taskwarrior--add "project:ert +emacs \"Write test suite for taskwarrior.el (using ERT)\""))
|
||||
(task (taskwarrior-export-task task-id)))
|
||||
(should (string= (alist-get 'project task) "ert"))
|
||||
(should (string= (aref (alist-get 'tags task) 0) "emacs"))
|
||||
(should (string= (alist-get 'description task) "Write test suite for taskwarrior.el (using ERT)"))))
|
||||
|
||||
(provide 'taskwarrior-test)
|
||||
;;; taskwarrior-test.el ends here
|
Loading…
Reference in a new issue