summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorThanos Apollo <[email protected]>2024-01-19 16:21:10 +0200
committerThanos Apollo <[email protected]>2024-01-19 16:21:10 +0200
commit63043374154c6c8e2397e0cc8de00f2c05e049b5 (patch)
treed53d7edecfc304f57b2a78330df583c2ec829c2c
parentfd42525da74189c7c6d1eddd17fa15cfcc643cbd (diff)
parent338bafa7eb8497fbf9ce514ae6fc9531e07ebfce (diff)
Merge branch 'version-0.1.4'0.1.4
- Update gnosis commentary & documentation - Refactor note creation & review Values for note types are not hard coded anymore, making gnosis easier to extend & maintain. - Apply suggestions from emacs-devel with a few adjustments Changes suggested by Philip Kaludercic via the emacs-devel mailing list <https://lists.gnu.org/archive/html/emacs-devel/2024-01/msg00682.html>
-rw-r--r--doc/gnosis.org54
-rw-r--r--gnosis-dev.el7
-rw-r--r--gnosis.el123
3 files changed, 114 insertions, 70 deletions
diff --git a/doc/gnosis.org b/doc/gnosis.org
index 8d23a30..28b9cf9 100644
--- a/doc/gnosis.org
+++ b/doc/gnosis.org
@@ -4,8 +4,8 @@
#+language: en
#+options: ':t toc:nil author:t email:t num:t
#+startup: content
-#+macro: stable-version 0.1.3
-#+macro: release-date 2023-01-18
+#+macro: stable-version 0.1.4
+#+macro: release-date 2023-01-19
#+macro: development-version 0.1.4-dev
#+macro: file @@texinfo:@file{@@$1@@texinfo:}@@
#+macro: space @@texinfo:@: @@
@@ -120,7 +120,7 @@ Every note type has these values in common:
+ ~second-image~ Image displayed /after/ user input
The following sections will cover the important differences you have
-to know when creating notes.
+to know when creating new notes.
* Note Types
** Cloze
@@ -175,8 +175,48 @@ When using the hidden function =gnosis-add-note--y-or-n=, note that the
ANSWER must be either 121 (~y~) or 110 (~n~), as those correspond to the
character values used to represent them.
-* Customization
-** Gnosis Algorithm Initial Interval
+* Customization & Development
+
+To make development and customization easier, gnosis comes with
+=gnosis-dev= module, that should be used to create a custom database for
+testing.
+
+To use =gnosis-dev=, first you have to =(require 'gnosis-dev)= & run =M-x
+gnosis-dev-test=. This will create a new directory 'testing' with a new
+database.
+
+To exit the testing environment, rerun =M-x gnosis-dev-test= and then
+enter =n= (no) at the prompt "Start development env?"
+
+** Creating Custom Note Types
+Creating custom note types for gnosis is a fairly simple thing to do
+
++ First add your NEW-TYPE to =gnosis-note-types=
+
+ #+begin_src emacs-lisp
+ (add-to-list 'gnosis-note-types 'new-type)
+ #+end_src
+
++ Create 2 functions; =gnosis-add-note-TYPE= & =gnosis-add-note--TYPE=
+
+Each note type has a =gnosis-add-note-TYPE= that is used
+interactively & a "hidden function" =gnosis-add-note--TYPE= that handles
+all the logic.
+
+Refer to =gnosis-add-note-basic= & =gnosis-add-note--basic= for a simple
+example of how this is done.
+
++ Create =gnosis-review-TYPE=
+
+This function should handle the review process, displaying it's
+contents and updating the database depending on the result of the
+review (fail/pass). Refer to =gnosis-review-basic= for an example of how
+this should be done.
+
++ Optionally, you might want to create your own custom =gnosis-display= functions
+
+** Customizing Gnosis Algorithm
+*** Gnosis Algorithm Initial Interval
=gnosis-algorithm-interval= is a list of 2 numbers, representing the
first two initial intervals for successful reviews.
@@ -191,7 +231,7 @@ Using the above example, after first successfully reviewing a note,
you will see it again tomorrow, if you successfully review said note
again, the next review will be after 3 days.
-** Gnosis Algorithm Easiness Factor
+*** Gnosis Algorithm Easiness Factor
=gnosis-algorithm-ef= is a list that consists of 3 items.
@@ -216,7 +256,7 @@ Example:
(setq gnosis-algorithm-ef '(0.3 0.3 1.3))
#+end_src
-** Gnosis Algorithm Forgetting Factor
+*** Gnosis Algorithm Forgetting Factor
=gnosis-algorithm-ff= is a floating number below 1.
diff --git a/gnosis-dev.el b/gnosis-dev.el
index 4387ce9..6801290 100644
--- a/gnosis-dev.el
+++ b/gnosis-dev.el
@@ -22,11 +22,10 @@
;; You should have received a copy of the GNU General Public License
;; along with this program. If not, see <https://www.gnu.org/licenses/>.
-;; Random functions to make development/testing of gnosis.el easier
-
;;; Commentary:
-;; Development tools used for gnosis
+;; Development module for gnosis, to make development of gnosis.el
+;; easier by creating a testing environment with random inputs.
;;; Code:
@@ -110,7 +109,7 @@ by the thoracodorsal nerve."
If ask nil, leave development env"
(interactive)
- (let ((ask (y-or-n-p "Start development env?"))
+ (let ((ask (y-or-n-p "Start development env (n for exit)?"))
(testing-dir (concat gnosis-dir "/testing")))
(if ask
(progn
diff --git a/gnosis.el b/gnosis.el
index 7ddeb30..8dc05a5 100644
--- a/gnosis.el
+++ b/gnosis.el
@@ -1,4 +1,4 @@
-;;; gnosis.el --- Spaced Repetition System -*- lexical-binding: t; -*-
+;;; gnosis.el --- Spaced Repetition System For Note Taking & Self Testing -*- lexical-binding: t; -*-
;; Copyright (C) 2023 Thanos Apollo
@@ -24,9 +24,9 @@
;;; Commentary:
-;; Gnosis (γνῶσις), pronounced "noh-sis", meaning knowledge in Greek, is
-;; a Spaced Repetition System <https://en.wikipedia.org/wiki/Spaced_repetition>
-;; for GNU Emacs.
+;; Gnosis is a spaced repetition system for note taking & self testing,
+;; where notes are taken in a Question/Answer/Explanation-like format
+;; & reviewed in spaced intervals.
;;
;; Gnosis does not implement flashcard type review sessions where the
;; user rates his own answer on an arbitrary scale. Instead implements
@@ -34,9 +34,14 @@
;; the MCQ, multiple choice question, even allow for simulating
;; real-life exams.
;;
-;; Unlike other SRS implementations for GNU Emacs, gnosis not rely on
-;; org-mode. Instead utilizes an sqlite database & git, enabling
-;; efficient data management, manipulation and data integrity.
+;; Gnosis can help you better understand and retain the material by
+;; encouraging active engagement. It also provides a clear structure for
+;; your notes & review sessions, making it easier to study.
+;;
+;; Unlike other SRS implementations for GNU Emacs, gnosis does not
+;; rely on org-mode. Instead utilizes an sqlite database & git,
+;; enabling efficient data management, manipulation and data
+;; integrity.
;;; Code:
@@ -45,13 +50,14 @@
(require 'cl-lib)
(require 'gnosis-algorithm)
+(require 'vc)
(defgroup gnosis nil
- "Spaced repetition learning tool."
+ "Spaced Repetition System For Note Taking & Self Testing."
:group 'external
:prefix "gnosis-")
-(defcustom gnosis-dir (concat user-emacs-directory "gnosis")
+(defcustom gnosis-dir (locate-user-emacs-file "gnosis")
"Gnosis directory."
:type 'directory
:group 'gnosis)
@@ -62,13 +68,13 @@
:group 'gnosis)
-(defvar gnosis-images-dir (concat (file-name-as-directory gnosis-dir) "images")
+(defvar gnosis-images-dir (expand-file-name "images" gnosis-dir)
"Gnosis images directory.")
(defconst gnosis-db
(if (not (file-directory-p gnosis-dir))
(gnosis-db-init)
- (emacsql-sqlite (concat (file-name-as-directory gnosis-dir) "gnosis.db")))
+ (emacsql-sqlite (expand-file-name "gnosis.db" gnosis-dir)))
"Gnosis database file.")
(defvar gnosis-testing nil
@@ -77,6 +83,9 @@
(defconst gnosis-db-version 1
"Gnosis database version.")
+(defvar gnosis-note-types '(MCQ Cloze Basic Double y-or-n)
+ "Gnosis available note types.")
+
;;; Faces
(defgroup gnosis-faces nil
@@ -136,8 +145,6 @@
"Face for next review."
:group 'gnosis-face)
-
-
(cl-defun gnosis-select (value table &optional (restrictions '1=1))
"Select VALUE from TABLE, optionally with RESTRICTIONS.
@@ -647,17 +654,14 @@ See `gnosis-add-note--cloze' for more reference."
;;;###autoload
(defun gnosis-add-note (type)
"Create note(s) as TYPE interactively."
- (interactive (list (completing-read "Type: " '(MCQ Cloze Basic Double y-or-n) nil t)))
+ (interactive (list (completing-read "Type: " gnosis-note-types nil t)))
(when gnosis-testing
(unless (y-or-n-p "You are using a testing environment! Continue?")
(error "Aborted")))
- (pcase type
- ("MCQ" (gnosis-add-note-mcq))
- ("Cloze" (gnosis-add-note-cloze))
- ("Basic" (gnosis-add-note-basic))
- ("Double" (gnosis-add-note-double))
- ("y-or-n" (gnosis-add-note-y-or-n))
- (_ (message "No such type."))))
+ (let ((func-name (intern (format "gnosis-add-note-%s" (downcase type)))))
+ (if (fboundp func-name)
+ (funcall func-name)
+ (message "No such type."))))
(defun gnosis-mcq-answer (id)
"Choose the correct answer, from mcq choices for question ID."
@@ -758,9 +762,7 @@ Optionally, add cusotm PROMPT."
(defun gnosis-suspended-p (id)
"Return t if note with ID is suspended."
- (if (= (gnosis-get 'suspend 'review-log `(= id ,id)) 1)
- t
- nil))
+ (= (gnosis-get 'suspend 'review-log `(= id ,id)) 1))
(defun gnosis-get-deck-due-notes (&optional deck-id)
"Return due notes for deck, with value of DECK-ID.
@@ -788,7 +790,7 @@ DATE is a list of the form (year month day)."
(cl-mapcan (lambda (note-id)
(gnosis-get-note-tags note-id))
due-notes)
- :test 'equal)))
+ :test #'equal)))
(cl-defun gnosis-tag-prompt (&key (prompt "Selected tags") (match nil) (due nil))
@@ -816,10 +818,8 @@ Returns a list of unique tags."
"Check if note with value of NOTE-ID for id is due for review.
Check if it's suspended, and if it's due today."
- (if (and (not (gnosis-suspended-p note-id))
- (gnosis-review-is-due-today-p note-id))
- t
- nil))
+ (and (not (gnosis-suspended-p note-id))
+ (gnosis-review-is-due-today-p note-id)))
(defun gnosis-review-is-due-today-p (id)
"Return t if note with ID is due today.
@@ -867,7 +867,7 @@ Returns a list of the form ((yyyy mm dd) ef)."
(cl-mapcan (lambda (note-id)
(gnosis-get-note-tags note-id))
due-notes)
- :test 'equal)))
+ :test #'equal)))
(defun gnosis-review--get-offset (id)
"Return offset for note with value of id ID."
@@ -875,9 +875,9 @@ Returns a list of the form ((yyyy mm dd) ef)."
(gnosis-algorithm-date-diff last-rev)))
(defun gnosis-review-round (num)
- "Round NUM to 1 decimal.
+ "Round NUM to 2 decimals.
-This function is used to round floating point numbers to 1 decimal,
+This function is used to round floating point numbers to 2 decimals,
such as the easiness factor (ef)."
(/ (round (* num 100.00)) 100.00))
@@ -917,8 +917,9 @@ SUCCESS is a binary value, 1 is for successful review."
(answer (nth (- (gnosis-get 'answer 'notes `(= id ,id)) 1) choices))
(user-choice (gnosis-mcq-answer id)))
(if (string= answer user-choice)
- (progn (gnosis-review--update id 1)
- (message "Correct!"))
+ (progn
+ (gnosis-review--update id 1)
+ (message "Correct!"))
(gnosis-review--update id 0)
(message "False"))
(gnosis-display-correct-answer-mcq answer user-choice)
@@ -997,18 +998,17 @@ Used to reveal all clozes left with `gnosis-face-cloze-unanswered' face."
(defun gnosis-review-note (id)
"Start review for note with value of id ID, if note is unsuspended."
- (cond ((gnosis-suspended-p id)
- (message "Note is suspended."))
- (t
- (with-current-buffer (switch-to-buffer (get-buffer-create "*gnosis*"))
- (let ((type (gnosis-get 'type 'notes `(= id ,id))))
- (gnosis-mode)
- (pcase type
- ("mcq" (gnosis-review-mcq id))
- ("basic" (gnosis-review-basic id))
- ("cloze" (gnosis-review-cloze id))
- ("y-or-n" (gnosis-review-y-or-n id))
- (_ (error "Malformed note type"))))))))
+ (when (gnosis-suspended-p id)
+ (message "Suspended note with id: %s" id)
+ (sit-for 0.3)) ;; this should only occur in testing/dev cases
+ (let* ((type (gnosis-get 'type 'notes `(= id ,id)))
+ (func-name (intern (format "gnosis-review-%s" (downcase type)))))
+ (if (fboundp func-name)
+ (progn
+ (with-current-buffer (switch-to-buffer (get-buffer-create "*gnosis*"))
+ (gnosis-mode)
+ (funcall func-name id)))
+ (error "Malformed note type: '%s'" type))))
(defun gnosis-review-commit (note-num)
"Commit review session on git repository.
@@ -1022,9 +1022,9 @@ NOTE-NUM: The number of notes reviewed in the session."
(default-directory gnosis-dir))
(unless git
(error "Git not found, please install git"))
- (unless (file-exists-p (concat (file-name-as-directory gnosis-dir) ".git"))
- (shell-command "git init"))
- (sit-for 0.2) ;; wait for shell command to finish
+ (unless (file-exists-p (expand-file-name ".git" gnosis-dir))
+ (vc-create-repo 'Git))
+ ;; TODO: Redo this using vc
(shell-command (concat git " add " (shell-quote-argument "gnosis.db")))
(shell-command (concat git " commit -m "
(shell-quote-argument (concat (format "Total notes for session: %d " note-num)))))
@@ -1039,13 +1039,18 @@ NOTE-NUM: The number of notes reviewed in the session."
(cl-loop for note in notes
do (gnosis-review-note note)
(setf note-count (1+ note-count))
- (pcase (read-char-choice "Note Action: [n]ext, [s]uspend, [e]dit, [q]uit: " '(?n ?s ?e ?q))
+ (pcase (car (read-multiple-choice
+ "Note actions"
+ '((?n "next")
+ (?s "suspend")
+ (?e "edit")
+ (?q "quit"))))
(?n nil)
(?s (gnosis-suspend-note note))
- (?e (progn (gnosis-edit-note note)
- (recursive-edit)))
- (?q (progn (gnosis-review-commit note-count)
- (cl-return))))
+ (?e (gnosis-edit-note note)
+ (recursive-edit))
+ (?q (gnosis-review-commit note-count)
+ (cl-return)))
finally (gnosis-review-commit note-count))))))
@@ -1119,7 +1124,7 @@ changes."
(extra-notes ,extra-notes)
(image ,image)
(second-image ,second-image))
- do (cond ((equal field 'id)
+ do (cond ((eq field 'id)
(insert (format (concat ":%s " (propertize "%s" 'read-only t) "\n") field value)))
((numberp value)
(insert (format ":%s %s\n" field value)))
@@ -1127,7 +1132,7 @@ changes."
(not (equal value nil)))
(insert (format ":%s '%s\n" field (format "%s" (cl-loop for item in value
collect (format "\"%s\"" item))))))
- ((equal value nil)
+ ((null value)
(insert (format ":%s %s\n" field 'nil)))
(t (insert (format ":%s \"%s\"\n" field value)))))
(delete-char -1) ;; delete extra line
@@ -1149,7 +1154,7 @@ changes."
(define-derived-mode gnosis-edit-mode emacs-lisp-mode "Gnosis EDIT"
"Gnosis Edit Mode."
:interactive t
- :lighter " gnosis-edit-mode"
+ :lighter " Gnosis Edit"
:keymap gnosis-edit-mode-map)
@@ -1175,7 +1180,7 @@ SECOND-IMAGE: Image to display after user-input"
(image . ,image)
(second-image . ,second-image))
when value
- do (cond ((member field '(extra-notes image second-image))
+ do (cond ((memq field '(extra-notes image second-image))
(gnosis-update 'extras `(= ,field ,value) `(= id ,id)))
((listp value)
(gnosis-update 'notes `(= ,field ',value) `(= id ,id)))
@@ -1377,7 +1382,7 @@ review."
;; Make sure gnosis-db is initialized
(setf gnosis-db (emacsql-sqlite (concat (file-name-as-directory gnosis-dir) "gnosis.db"))))
;; Create database tables
- (unless (= (length (emacsql gnosis-db [:select name :from sqlite-master :where (= type table)])) 6)
+ (unless (length= (emacsql gnosis-db [:select name :from sqlite-master :where (= type table)]) 6)
;; Enable foreign keys
(emacsql gnosis-db "PRAGMA foreign_keys = ON")
;; Gnosis version