Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Allow custom, per-card, cloze context value #97

Open
wants to merge 5 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion org-fc-cache.el
Original file line number Diff line number Diff line change
Expand Up @@ -166,7 +166,7 @@ This is especially relevant w.r.t a card's due date / suspension state before re
(org-fc-review-with-current-item cur
(if (org-fc-suspended-entry-p)
(error "Trying to review a suspended card"))
(let* ((position (plist-get cur :position))
(let* ((position (oref cur pos))
(review-data (org-fc-review-data-get))
(row (assoc position review-data #'string=))
(due (parse-iso8601-time-string (nth 4 row))))
Expand Down
115 changes: 115 additions & 0 deletions org-fc-card.el
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
;;; org-fc-card.el --- Representation of a card. -*- lexical-binding: t; -*-

;; Copyright (C) 2020 Leon Rische

;; Author: Leon Rische <emacs@leonrische.me>
;; Url: https://www.leonrische.me/pages/org_flashcards.html
;; Package-requires: ((emacs "26.3") (org "9.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:
;;
;; A single card.
;;
;;; Code:

(require 'eieio)
(require 'org-fc-position)

(defclass org-fc-card ()
((created
:initform nil
:initarg :created
:type list
:custom (repeat integer)
:documentation "The time at which this card was created.")
(filetitle
:initform ""
:initarg :filetitle
:type string
:documentation "The title of the file which contains this card.")
(tags
:initform nil
:initarg :tags
:type list
:custom (repeat string)
:documentation "The tags on this card.")
(id
:initform ""
:initarg :id
:type string
:documentation "The org-id for this card.")
(inherited-tags
:initform ""
:initarg :inherited-tags
:type string
:documentation "Inherited tags in the form :taga:tagb:."
)
(local-tags
:initform ""
:initarg :local-tags
:type string
:documentation "Tags on the card itself in the form :taga:tagb:.")
(path
:initform ""
:initarg :path
:type string
:documentation "Full path to the file which contains this card.")
(positions
:initform nil
:initarg :positions
:type list
:documentation "The `org-fc-position's for this card.")
(suspended
:initform nil
:initarg :suspended
:type boolean
:documentation "t when the card is suspended; nil otherwise.")
(title
:initform nil
:initarg :title
:type (or null string))
(type
:initform nil
:initarg :type
:type symbol
:documentation "The org-fc-* card type (e.g. double or cloze).")))

(cl-defmethod org-fc-card--to-positions ((card org-fc-card card))
"Return list of `org-fc-position' extracted from CARD."
(mapcar
(lambda (pos)
(let ((box (plist-get pos :box))
(ease (plist-get pos :ease))
(interval (plist-get pos :interval))
(pos-pos (plist-get pos :position))
(due (plist-get pos :due)))
(org-fc-position
:box box
:card card
:due due
:ease ease
:interval interval
:pos pos-pos)))
(oref card positions)))

(defun org-fc-cards--to-positions (cards)
"Convert a list of CARDS (`org-fc-card's) to a list of `org-fc-positions's."
(cl-loop for card in cards
append (org-fc-card--to-positions card)))

(provide 'org-fc-card)
;;; org-fc-card.el ends here
98 changes: 29 additions & 69 deletions org-fc-core.el
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,14 @@
;;; Code:

(require 'outline)
(require 'dash)

(require 'org-id)
(require 'org-indent)
(require 'org-element)

(require 'org-fc-card)

(require 'subr-x)

;;; Customization
Expand Down Expand Up @@ -82,11 +85,6 @@ Used to generate absolute paths to the awk scripts.")
:type 'string
:group 'org-fc)

(defcustom org-fc-type-cloze-max-hole-property "FC_CLOZE_MAX"
"Name of the property to use for storing the max hole index."
:type 'string
:group 'org-fc)

(defcustom org-fc-suspended-tag "suspended"
"Tag for marking suspended cards."
:type 'string
Expand Down Expand Up @@ -180,14 +178,6 @@ This is expected to be called on an card entry heading."
Used to determine if a card uses the compact style."
(not (null (org-fc-back-heading-position))))

(defun org-fc-sorted-random (n)
"Generate a list of N sorted random numbers."
(sort (cl-loop for i below n collect (cl-random 1.0)) #'>))

(defun org-fc-zip (as bs)
"Zip two lists AS and BS."
(cl-loop for a in as for b in bs collect (cons a b)))

;; File-scoped variant of `org-id-goto'
(defun org-fc-id-goto (id file)
"Go to the heading with ID in FILE."
Expand Down Expand Up @@ -583,65 +573,35 @@ use `(and (type double) (tag \"math\"))'."

(funcall org-fc-index-function paths filter)))

(defun org-fc-index-flatten-card (card)
"Flatten CARD into a list of positions.
Relevant data from the card is included in each position
element."
(defun org-fc-shuffle (items)
"Return a shuffled version of ITEMS."
(let* ((randoms (cl-loop for _ below (length items)
collect (cl-random 1.0)))
(zipped (-zip randoms items))
(sorted-zipped (sort zipped
(lambda (a b)
(> (car a) (car b))))))
(mapcar
#'cdr
sorted-zipped)))

(defun org-fc-index--to-cards (index)
"Convert an index to a list of `org-fc-card'."
(mapcar
(lambda (pos)
(list
(lambda (card)
(org-fc-card
:created (plist-get card :created)
:filetitle (plist-get card :filetitle)
:tags (plist-get card :tags)
:path (plist-get card :path)
:id (plist-get card :id)
:type (plist-get card :type)
:due (plist-get pos :due)
:position (plist-get pos :position)))
(plist-get card :positions)))

(defun org-fc-index-filter-due (index)
"Filter INDEX to include only unsuspended due positions.
Cards with no positions are removed from the index."
(let (res (now (current-time)))
(dolist (card index)
(unless (plist-get card :suspended)
(let ((due
(cl-remove-if-not
(lambda (pos)
(time-less-p (plist-get pos :due) now))
(plist-get card :positions))))
(unless (null due)
(plist-put
card :positions
(if (or (not org-fc-bury-siblings)
(member (plist-get card :cloze-type) '(single enumeration)))
due (list (car due))))
(push card res)))))
res))

(defun org-fc-index-positions (index)
"Return all positions in INDEX."
(mapcan (lambda (card) (org-fc-index-flatten-card card)) index))

(defun org-fc-index-shuffled-positions (index)
"Return all positions in INDEX in random order.
Positions are shuffled in a way that preserves the order of the
positions for each card."
;; 1. assign each position a random number
;; 2. flatten the list
;; 3. sort by the random number
;; 4. remove the random numbers from the result
(let ((positions
(mapcan
(lambda (card)
(let ((pos (org-fc-index-flatten-card card)))
(org-fc-zip
(org-fc-sorted-random (length pos))
pos)))
index)))
(mapcar
#'cdr
(sort positions (lambda (a b) (> (car a) (car b)))))))
:inherited-tags (plist-get card :inherited-tags)
:local-tags (plist-get card :local-tags)
:path (plist-get card :path)
:positions (plist-get card :positions)
:suspended (plist-get card :suspended)
:tags (plist-get card :tags)
:title (plist-get card :title)
:type (plist-get card :type)))
index))

;;; Demo Mode

Expand Down
80 changes: 80 additions & 0 deletions org-fc-position.el
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
;;; org-fc-position.el --- Representation of a position -*- lexical-binding: t; -*-
;;
;; Copyright (C) 2022 Cash Weaver
;;
;; Author: Cash Weaver <cashbweaver@gmail.com>
;; Maintainer: Cash Weaver <cashbweaver@gmail.com>
;; Created: September 26, 2022
;; Modified: September 26, 2022
;; Version: 0.0.1
;; Keywords: abbrev bib c calendar comm convenience data docs emulations extensions faces files frames games hardware help hypermedia i18n internal languages lisp local maint mail matching mouse multimedia news outlines processes terminals tex tools unix vc wp
;; Homepage: https://github.com/cashweaver/org-fc-position
;; Package-Requires: ((emacs "24.3"))
;;
;; This file is not part of GNU Emacs.
;;
;;; Commentary:
;;
;; A position is a single "view" of a card.
;;
;;; Code:

(require 'eieio)
(require 'dash)

(defclass org-fc-position ()
((box
:initform 0
:initarg :box
:type (integer -1 *)
:documentation "The positions's current Leitner system box.")
(card
:initform nil
:initarg :card
:documentation "The `org-fc-card' which contains this position.")
(due
:initform nil
:initarg :due
:type list
:custom (repeat integer)
:documentation "The time at which this position is due.")
(ease
:initform 0
:initarg :ease
:type (or float integer)
:documentation "The ease factor.")
(interval
:initform 0
:initarg :interval
:type (or float integer)
:documentation "The interval, in days.")
(pos
:initform ""
:initarg :pos
:type (or integer string)
:documentation "The name of the position (e.g. \"front\" for double/normal or \"0\" for cloze)."))
"Represents a single position.")

(cl-defmethod org-fc-position--is-due ((pos org-fc-position))
"Return t if POS is due; else nil."
(time-less-p (oref pos due) (current-time)))

(defun org-fc-positions--filter-due (positions)
"Filter POSITIONS to include only enabled and due positions."
(let ((due-enabled-positions (cl-loop for pos in positions
when (and (not (oref (oref pos card) suspended))
(org-fc-position--is-due pos))
collect pos)))
(if org-fc-bury-siblings
(org-fc-positions--one-per-card due-enabled-positions)
due-enabled-positions)))

(defun org-fc-positions--one-per-card (positions)
"Return filtered list of POSITIONS; only one position per card."
(let ((-compare-fn (lambda (a b)
(equal (oref (oref a card) id)
(oref (oref b card) id)))))
(-distinct positions)))

(provide 'org-fc-position)
;;; org-fc-position.el ends here