Switch branches/tags
Nothing to show
Find file Copy path
Fetching contributors…
Cannot retrieve contributors at this time
279 lines (266 sloc) 11.2 KB
(defun org-fast-task-reclock (&optional no-clock)
"Quickly make a new task and clock into it."
(interactive "P")
(let ((file (ido-completing-read "Org-file: " (reverse org-agenda-files))))
(find-file file)
(let ((headings (list)))
(goto-char (point-min))
(while (search-forward-regexp "^\\*" nil t)
(destructuring-bind (_ _ status _ title _) (org-heading-components)
(when (not status)
(push title headings))
(let ((heading (ido-completing-read "Project: " headings))
(found nil))
(goto-char (point-min))
(while (and (not found)
(search-forward-regexp "^\\*" nil t))
(destructuring-bind (_ _ status _ title _) (org-heading-components)
(when (string= heading title)
(setq found t)
(org-metaright 1)
(org-todo "TODO")
(insert (read-from-minibuffer "Task title: "))
(unless no-clock (org-clock-in)))
(define-key org-mode-map (kbd "C-#") 'org-begin-template)
(defun org-begin-template ()
"Make a template at point."
(if (org-at-table-p)
(call-interactively 'org-table-rotate-recalc-marks)
(let* ((choices '(("s" . "SRC")
("e" . "EXAMPLE")
("q" . "QUOTE")
("v" . "VERSE")
("c" . "CENTER")
("l" . "LaTeX")
("h" . "HTML")
("a" . "ASCII")))
(concat (propertize "Template type: " 'face 'minibuffer-prompt)
(mapconcat (lambda (choice)
(concat (propertize (car choice) 'face 'font-lock-type-face)
": "
(cdr choice)))
", ")))))))
(let ((result (assoc key choices)))
(when result
(let ((choice (cdr result)))
(let ((start (region-beginning))
(end (region-end)))
(goto-char end)
(insert "\n#+END_" choice)
(goto-char start)
(insert "#+BEGIN_" choice "\n")))
(insert "#+BEGIN_" choice "\n")
(save-excursion (insert "\n#+END_" choice))))))))))
(define-key org-mode-map (kbd "C-c C-e") 'org-focus-estimate)
(define-key org-mode-map (kbd "C-c C-s") 'org-focus-schedule)
(define-key org-mode-map (kbd "C-c C-x C-i") 'org-clock-in)
(define-key org-mode-map (kbd "C-c C-x C-b") 'org-focus-bump)
(setq org-clock-clocked-in-display nil)
(defun org-focus-contract-start ()
(goto-char (point-min))
(when (search-forward-regexp "#\\+CONTRACT_START: \\(.+\\)$" nil t 1)
(org-focus-parse-time (match-string 1)))))
(defun org-focus-contract-hours ()
(goto-char (point-min))
(when (search-forward-regexp "#\\+CONTRACT_HOURS: \\([0-9]+\\)$" nil t 1)
(string-to-number (match-string 1)))))
(defun org-toggl-client-id ()
(goto-char (point-min))
(when (search-forward-regexp "#\\+TOGGL_CLIENT_ID: \\([0-9]+\\)$" nil t 1)
(string-to-number (match-string 1)))))
(defun org-toggl-project-id ()
(goto-char (point-min))
(when (search-forward-regexp "#\\+TOGGL_PROJECT_ID: \\([0-9]+\\)$" nil t 1)
(string-to-number (match-string 1)))))
(defun org-focus-timesheet ()
"Generate a work timesheet since DATE."
(let* ((contract-start (org-focus-contract-start))
"Since date: "
(if contract-start
(format-time-string "%Y-%m-%d" contract-start)
(format-time-string "%Y-%m-01")))))
(hours-ordered (org-focus-contract-hours))
(items (org-focus-buffer-items))
(buffer-name (replace-in-string (buffer-name) ".org" ""))
(histogram (make-hash-table :test 'equal))
(hours-used 0.0))
(mapc (lambda (item)
(when (plist-get item :clocks)
(mapc (lambda (clock)
(let* ((date (plist-get clock :date))
(hours (plist-get clock :hours))
(key (concat (format-time-string "%Y-%m-%d" date)
(plist-get item :title))))
(when (and (> hours 0.0)
(org-focus-day< start-date date))
(setq hours-used (+ hours-used hours))
(puthash key
(+ (gethash key histogram 0)
(plist-get item :clocks))))
(let ((buffer
(format "*Timesheet from %s*"
(format-time-string "%Y-%m-%d" start-date)))))
(with-current-buffer buffer
(let ((inhibit-read-only t))
(maphash (lambda (key hours)
(let* ((parts (split-string key "\t"))
(date (nth 0 parts))
(title (nth 1 parts)))
(insert (format "%s\t%.1f\t\t%s\n"
(replace-regexp-in-string "[0-9]+-[0-9]+-" "" date)
(sort-lines nil
(goto-char (point-min))
(insert (format "Reporting start: %s\n"
(format-time-string "%Y-%m-%d" start-date))
(format "Hours used: %.2f\n" hours-used)
(format "Client: %s\n" buffer-name)
(if contract-start
(format "Contract start: %s\n"
(format-time-string "%Y-%m-%d" contract-start))
(if (and contract-start
(org-focus-day= contract-start start-date)
(format "Hours left: %.2f\n" (- hours-ordered hours-used))
(switch-to-buffer-other-window buffer))))
(defun org-focus-toggl ()
"Generate an import file for toggl."
(let* ((contract-start (org-focus-contract-start))
"Since date: "
(if contract-start
(format-time-string "%Y-%m-%d" contract-start)
(format-time-string "%Y-%m-01")))))
(items (org-focus-buffer-items))
(client (read-from-minibuffer "Client: "))
(project (read-from-minibuffer "Project: "))
(format "*Toggle from %s*"
(format-time-string "%Y-%m-%d" start-date)))))
(with-current-buffer buffer
(let ((inhibit-read-only t)
(lasthash nil))
(lambda (item)
(lambda (clock)
(let* ((hash (make-hash-table)))
(when (and (> (plist-get clock :hours) 0.0)
(org-focus-day< start-date (plist-get clock :date)))
(puthash "Email" "" hash)
(puthash "Client" client hash)
(puthash "Description" (plist-get item :title) hash)
(puthash "Start date" (format-time-string "%F" (plist-get clock :date)) hash)
(puthash "Start time" (format-time-string "%T" (plist-get clock :date)) hash)
(puthash "Duration" (concat (plist-get clock :hours-stamp) ":00") hash)
(puthash "Billable" "Y" hash)
(mapc (lambda (key)
(insert (gethash key hash) "\t"))
(hash-table-keys hash))
(insert "\n")
(setq lasthash hash))))
(plist-get item :clocks)))
(goto-char (point-min))
(insert (mapconcat #'identity (hash-table-keys lasthash) "\t")
(switch-to-buffer-other-window buffer)
(define-key org-focus-mode-map (kbd "t") 'org-focus-toggl)
(defvar org-focus-toggl-regex
"[ ]+\\([^ ]+\\)[ ]+\\([0-9]+:[0-9]+\\)[ ]+/[ ]+[0-9:]+[ ]+[A-Z]+[ ]+\\(.+\\)")
(defvar org-focus-projects
(defun org-focus-toggl (dry-run)
(interactive "P")
(let ((this-time (get-text-property (point) 'time))
(total-hours 0))
(when this-time
(forward-line 1)
(goto-char (line-beginning-position))
(while (looking-at org-focus-toggl-regex)
(let* ((fname (match-string-no-properties 1))
(hours (match-string-no-properties 2))
(description (match-string-no-properties 3))
(pid (cdr (assoc fname org-focus-projects))))
(setq total-hours
(+ total-hours
(org-focus-roundup-half-hours (org-focus-parse-hours hours))))
(if (not dry-run)
(progn (toggl-submit
(org-focus-trim-string description)
(* (org-focus-roundup-half-hours (org-focus-parse-hours hours)) 60 60))
(sit-for 1.5))
"Clocking: %s => %f hours (%s)"
(org-focus-trim-string description)
(org-focus-roundup-half-hours (org-focus-parse-hours hours))
(org-focus-format-hours (org-focus-roundup-half-hours (org-focus-parse-hours hours)))))
(forward-line 1)))
(message "%s hours toggl'd!" (org-focus-format-hours total-hours)))))
(defun org-focus-trim-string (string)
"Remove white spaces in beginning and ending of STRING.
White space here is any of: space, tab, emacs newline (line feed, ASCII 10)."
(replace-regexp-in-string "\\`[ \t\n]*" "" (replace-regexp-in-string "[ \t\n]*\\'" "" string)))
(defun org-focus-roundup-half-hours (h)
"Tracked time is rounded up to the nearest 15 minutes.
After 40 minutes, rounded up to the nearest hour.
Allowed minutes: 0 15 30 60
(let* ((mins (- h (floor h))))
(if (= 0 mins)
(if (> mins 0.25)
(if (> mins 0.5)
(if (> mins 0.6)
(+ 1 (floor h))
(if (> mins 0.75)
(+ 1 (floor h))
(+ 0.75 (floor h))))
(+ 0.5 (floor h)))
(+ 0.25 (floor h))))))
(define-key org-mode-map (kbd "C-c C-x C-i") 'org-multiclock-in)
(define-key org-mode-map (kbd "C-c C-x C-o") 'org-multiclock-out)