Skip to content

Extension Examples

Boris Buliga edited this page Sep 15, 2024 · 2 revisions

Extension Examples

This document describes a few examples of how Vino can be extended. As you can see, there are numerous ways to implement additional functionality on top of Vino and Vulpea.

Adding Extra Fields to Cellar Entries

To add extra fields to cellar entries, use the vino-entry-create-handle-functions abnormal hook. For example, you can assign an externalId like this:

(add-hook 'vino-entry-create-handle-functions #'my-vino-entry-assign-external-id)

(defun my-vino-entry-assign-external-id (note)
  "Interactively assign extra meta for wine NOTE."
  (let ((external-id (read-string "External ID: ")))
    (unless (string-empty-p external-id)
      (vulpea-utils-with-note note
        (vulpea-buffer-meta-set "externalId" external-id)))))

The definition is straightforward. Just keep in mind that you should not save-buffer unless necessary. Saving the buffer is not particularly slow, but Vulpea will take some time to sync the database, so be mindful of the performance implications.

Adding Extra Fields to Rating Entries

To add extra fields to rating entries, use the vino-rating-create-handle-functions abnormal hook.

Consumed Bottle ID

For example, you can include the consumed bottle ID when writing a rating. As explained in Creating Extensions, the rating abnormal hook comes with extra-data, and Inventory populates this argument with the consumed bottle ID.

(add-hook 'vino-rating-create-handle-functions #'my-vino-rating-assign-extra-meta)

(defun my-vino-rating-assign-extra-meta (rating extra-data)
  "Assign extra meta for RATING note.

EXTRA-DATA contains bottle-id."
  (let* ((wine (vulpea-note-meta-get rating "wine" 'note))
         (bottle-id (assoc-default 'bottle-id extra-data))
         (bottle (vino-inv-get-bottle bottle-id)))
    (vulpea-utils-with-note rating
      (vulpea-buffer-meta-set "bottle" bottle-id 'append))))

Location and Convives

To record the location where the bottle was consumed and the people with whom it was shared:

(add-hook 'vino-rating-create-handle-functions #'my-vino-rating-assign-extra-meta)

(defun my-vino-rating-assign-extra-meta (rating extra-data)
  "Assign extra meta for RATING note.

EXTRA-DATA contains bottle-id."
  (let* ((wine (vulpea-note-meta-get rating "wine" 'note))
         (location (vulpea-select-from "Location"
                                       (vulpea-db-query-by-tags-some '("places" "people" "event"))
                                       :require-match t)))
    (vulpea-utils-with-note rating
      (vulpea-buffer-meta-set "location" location 'append)
      (vulpea-buffer-meta-set "convive"
                              (let ((people (vulpea-db-query-by-tags-every '("people"))))
                                (vulpea-utils-collect-while #'vulpea-select-from nil "Convive" people))
                              'append))))

Managing Finances

To track expenses whenever a bottle is acquired, you can create a custom handler. Here is a rough example (this is similar to what I use in my private configuration):

(add-hook 'vino-inv-acquire-handle-functions #'my-vino-inv-acquire-bottle-handler)

(defun my-vino-inv-acquire-bottle-handler (bottle)
  "Handle wine BOTTLE acquire event."
  (let* ((wine (vino-inv-bottle-wine bottle))
         (price (vino-inv-bottle-price bottle))
         ;; Ensure the price is in UAH (my preferred currency)
         (price (cond
                 ((s-suffix-p brb-currency price) (string-to-number price))
                 ((= 0 (string-to-number price)) 0)
                 (t (read-number (format "Convert %s to UAH: " price))))))
    (unless (= 0 price)
      ;; Record the spending
      (brb-ledger-record-txn
       :date (date-to-time (vino-inv-bottle-purchase-date bottle))
       :comment (concat "[" (vulpea-note-id wine) "]")
       :account-to "spending:wines"
       :account-from "personal:account"
       :amount price))))

Using Bottles Marked in Inventory UI

You can mark bottles in vino-inv-ui using the m key by default. Here is a dummy implementation of a function that creates a spreadsheet for printing custom labels for each acquired bottle. My printer supports templates with data sourced from .xlsx files.

(defun my-vino-inv-ui-print-info ()
  "Save print info for the marked bottles."
  (interactive)
  (let (print-list bottle)
    (save-excursion
      (goto-char (point-min))
      (while (not (eobp))
        (setq cmd (char-after))
        (when (eq cmd ?\*)
          ;; This is the key PKG-DESC.
          (setq bottle (tabulated-list-get-id))
          (push bottle print-list))
        (forward-line)))
    (when (seq-empty-p print-list)
      (user-error "There are no marked bottles."))
    (let* ((data (->> print-list
                      (--map
                       (let ((bottle-id (string-to-number (nth 1 (s-split ":" it)))))
                         (vino-inv-get-bottle bottle-id)))
                      (--sort (< (vino-inv-bottle-id it)
                                 (vino-inv-bottle-id other)))
                      (--map
                       (list (format "#%s" (vino-inv-bottle-id it))
                             (vino-inv-bottle-purchase-date it)
                             (concat "https://barberry.io/wines/" (vulpea-note-id (vino-inv-bottle-wine it)))))))
           (file (make-temp-file "vino-inv-" nil ".csv"))
           (buffer (find-file-noselect file)))
      (with-current-buffer buffer
        (delete-region (point-min) (point-max))
        (insert (string-join '("bottle" "date" "url") ",") "\n")
        (--each data
          (insert (string-join it ",") "\n")))
      (switch-to-buffer buffer)
      (when (y-or-n-p "Convert?")
        (save-buffer)
        (shell-command-to-string
         (format "ssconvert '%s' '%s'"
                 file
                 (expand-file-name
                  "Documents/bottles.xlsx"
                  path-home-dir)))))))