想要培养一些好的习惯,想要不那么费力的变得更自律。
读了一些关于时间管理和习惯培养的书籍和文章,我意识到“仪式感”和“记录反思”的重要性。事实上,以自己喜欢的方式做记录是件很开心的事情,我已经连续80天写晨间日记和晚间总结。现在, 写日志已经变成了习惯。但我希望培养更多好的习惯,用我自己喜欢的方式(在emacs中),所以有了gk-habit这个demo。
2 思路
gk-habit使用emacsql-sqlite创建数据库,有两个表 "habit" 和 "record"。前者记录习惯及其相关参数,后者记录习惯打卡的数据。
habit表
- create-time 习惯创建的时间
- name 习惯名称
- frequency-type 习惯打卡频率类型,包括“每天”、“每周几重复”,“一周几次”,“一月几次”
- frequency-param 频率类型的参数,每天重复则为nil、每周几重复则用数字表示(134表示每周一三四)、一周或一月的打卡的次数。
- period 习惯发生的时间时间段
- remind-time 提醒打卡的时间
- remind-string 提醒打卡时的文本
- status 习惯的状态,active或archive,archive表示不再跟踪该习惯
- archive-time 归档的时间
record表
- create-time 记录创建的时间
- habit 记录的习惯
- status 习惯完成状态(DONE完成,MISS未完成)
- comment 未完成的原因
3 代码
;;; habit record - gk-habit (require 'cl-lib) (require 'emacsql-sqlite) (defconst gkh-buffer "*Gk-Habit*") (defvar gkh-db (emacsql-sqlite3 "~/.emacs.d/gk-habit/habit.db")) (defvar gkh-file "~/.emacs.d/gk-habit/habit.org") (defvar gkh-record-status '("DONE" "MISS")) (defvar gkh-frequency-type '("everyday" "repeat" "by-week" "by-month")) (defvar gkh-period '("get-up" "morning" "noon" "afternoon" "evening" "before-sleep")) (defvar gkh-table-habit-column '("create-time" "name" "frequency" "frequency-param" "peroid" "remind-time" "remind-string" "status" "archive-time")) (defvar gkh-table-record-column '("create-time" "habit" "status" "comment")) (defun gkh-db-create-tables () (interactive) "Create database tables of gk-habit." (emacsql gkh-db [:create-table habit ([(create-time string :primary-key :not-null) (name string :not-null :unique) (frequency-type string :not-null) (frequency-param) (period string :not-null) (remind-time) (remind-string) (status string :not-null) (archive-time string)])]) (emacsql gkh-db [:create-table record ([(create-time string :primary-key :not-null) (habit string :not-null) (status string :not-null) (comment string)] (:foreign-key [habit] :references habit [name] :on-delete :cascade))])) (defun gkh-db-drop-tables () "Drop database tables of gk-habit." (interactive) (let* ((tables (emacsql gkh-db [:select name :from sqlite_master :where (= type 'table)])) (table (completing-read "Choose a table: " tables nil t))) (emacsql gkh-db `[:drop-table ,(intern table)]))) (defun gkh--frequency-params (frequency-type) "Get the habit frequency parameters" (let (param) (cond ((string= frequency-type "everyday") (setq param nil)) ((string= frequency-type "repeat") (setq param (completing-read "repeated day (exam. \"135\" means habit repeat on Monday, Wensday and Friday in every week.): " nil))) ((string= frequency-type "by-week") (setq param (completing-read "how many times a week: " nil))) ((string= frequency-type "by-month") (setq param (completing-read "how many times a month: " nil)))) param)) (defun gkh-init () "Gk-habit initialize, create database and org tables." (interactive) (ignore-errors (gkh-db-create-tables)) (gkh-org-table-draw)) (defun gkh-new () "Add a new habit" (interactive) (cl-block 'return (let* ((create-time (format-time-string "%Y-%m-%d %T")) (habit (let ((temp (completing-read "name of habit: " nil)) (habits (mapcar 'car (emacsql gkh-db [:select name :from habit :where (= status "Active")])))) (if (member temp habits) (cl-return-from 'return (message "the habit '%s' already exist!" temp)) temp))) (frequency-type (completing-read "frequency of habit: " gkh-frequency-type nil t)) (frequency-param (gkh--frequency-params frequency-type)) (period (completing-read "period of habit: " gkh-period nil t)) (remind-time (let ((temp (completing-read "remind this habit at: " nil))) (if (string= "" temp) nil temp))) (remind-string (let ((temp (completing-read "habit remind sentence: " nil))) (if (string= "" temp) nil temp)))) (emacsql gkh-db `[:insert :into habit :values ([,create-time ,habit ,frequency-type ,frequency-param ,period ,remind-time ,remind-string "Active" nil])]) (gkh-org-table-draw) (message "Habit '%s' is added!" habit)))) (defun gkh-record () "Insert a habit redord in table." (interactive) (let* ((create-time (format-time-string "%Y-%m-%d %T")) (habit (completing-read "Choose a habit: " (emacsql gkh-db [:select [name] :from habit :where (= status "Active")]))) (status (completing-read "Is the habit done?" gkh-record-status nil t)) (comment (when (string= "MISS" status) (completing-read "Reason why missed: " nil)))) (emacsql gkh-db `[:insert-into record :values ([,create-time ,habit ,status ,comment])]) (gkh-org-table-draw) (message "Habit '%s' is %s, record on %s, %s" habit status create-time comment))) (defun gkh-archive () "Archive a habit" (interactive) (let* ((habits (emacsql gkh-db [:select name :from habit :where (= status "Active")])) (habit (completing-read "Choose a habit: " habits nil t))) (emacsql gkh-db `[:update habit :set [(= status "Archive") (= archive-time ,(format-time-string "%Y-%m-%d %T"))] :where (= name ,habit)]) (gkh-org-table-draw) (message "habit %s has been archived!" habit))) (defun gkh-org-table-draw () "Draw gk-habit database in org table." (interactive) (let* ((table-alist '(("habit" . gkh-table-habit-column) ("record" . gkh-table-record-column)))) (with-temp-file gkh-file (goto-char (point-min)) (dotimes (i (length table-alist)) (let* ((headline (car (nth i table-alist))) (column-list (eval (cdr (nth i table-alist)))) (column-num (length column-list))) (insert (concat "* " headline " table\n")) (org-table-create (concat (format "%s" column-num) "x2")) (dotimes (j column-num) (org-table-next-field) (insert (nth j column-list))) (let ((items (emacsql gkh-db `[:select * :from ,(intern headline)]))) (dotimes (m (length items)) (dotimes (n column-num) (org-table-next-field) (insert (format "%s" (nth n (nth m items))))))) (org-table-align) (forward-line 2) (end-of-line) (newline 2)))))) (defun gkh-org-table-display () "Display gk-habit org table in a bottom buffer." (interactive) (gkh-org-table-draw) (if (string= (buffer-name) gkh-buffer) (message "Already in the Gk Habit buffer.")) (select-window (or (get-buffer-window gkh-buffer) (selected-window))) (with-current-buffer (get-buffer-create gkh-buffer) (org-mode) (read-only-mode -1) (erase-buffer) (insert (file-contents gkh-file)) (valign-mode) (goto-char (point-min)) (read-only-mode 1)) (view-buffer gkh-buffer 'kill-buffer)) (provide 'gk-habit)
4 计划
- 设置习惯打卡时间提醒
- 使用matplotlib库绘制习惯打卡统计图
发布于 2020-08-04