From ac2bae6089b758a6ee443bc7b8a98ce6c081ecc9 Mon Sep 17 00:00:00 2001 From: Oscar Najera Date: Sat, 19 Oct 2024 20:48:22 +0200 Subject: Grocy frontend --- elisp/grocy.el | 149 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 149 insertions(+) create mode 100644 elisp/grocy.el diff --git a/elisp/grocy.el b/elisp/grocy.el new file mode 100644 index 0000000..90d96a7 --- /dev/null +++ b/elisp/grocy.el @@ -0,0 +1,149 @@ +;;; grocy.el --- Grocy frontend -*- lexical-binding: t; -*- +;; +;; Copyright (C) 2024 Óscar Nájera +;; +;; Author: Óscar Nájera +;; Maintainer: Óscar Nájera +;; Created: October 19, 2024 +;; Modified: October 19, 2024 +;; 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/titan/grocy +;; Package-Requires: ((emacs "27.1")) +;; +;; This file is not part of GNU Emacs. +;; +;;; Commentary: +;; +;; Grocy emacs frontend +;; +;;; Code: + +;; Silence byte-compiler. +(defvar url-http-end-of-headers) + +(defvar grocy-units) +(defvar grocy-locations) + +(defcustom grocy-server "" + "Grocy main domain" + :type 'string + :group 'grocy) + +(defcustom grocy-api-key "" + "API key to authenticate to grocy server" + :type 'string + :group 'grocy) + +(defun grocy--request (endpoint callback &optional method data) + "Do a GET request to api ENDPOINT and call CALLBACK on the resulting buffer. +Set METHOD to other request types and include the alredy encoded DATA." + (let ((url-request-method (or method "GET")) + (url-request-extra-headers + `(("GROCY-API-KEY" . ,grocy-api-key) + ("accept" . "application/json") + ("Content-Type" . "application/json"))) + (url-request-data data)) + (url-retrieve + (concat grocy-server "/api/" endpoint) + callback))) + +(defun grocy-update-units () + (grocy--request + "objects/quantity_units" + (lambda (_status) + (goto-char url-http-end-of-headers) + (let ((result (make-hash-table))) + (mapc + (lambda (unit) + (-let (((&hash "id" "name" "name_plural") unit)) + (puthash id + (cons name name_plural) + result))) + (json-parse-buffer)) + (setq grocy-units result))))) + +(defun grocy-update-locations () + (grocy--request + "objects/locations" + (lambda (_status) + (goto-char url-http-end-of-headers) + (let ((result (make-hash-table))) + (mapc + (lambda (location) + (-let (((&plist :id) location)) + (puthash id (cdddr location) + result))) + (json-parse-buffer :object-type 'plist)) + (setq grocy-locations result))))) + +(defun grocy-stock--row (row) + (-let* (((&alist 'amount 'amount_opened 'product 'best_before_date) row) + ((&alist 'name 'qu_id_stock) product) + (amount + (concat (number-to-string amount) + " " + (funcall + (if (= 1 amount) #'car #'cdr) + (gethash qu_id_stock grocy-units)) + (when (< 0 amount_opened) + (concat " / " + (number-to-string amount_opened) + " open"))))) + (list row (vector best_before_date amount name)))) + +(defun grocy-stock () + "Show the grocery stock." + (interactive) + (grocy--request + "stock" + (lambda (_status) + (goto-char url-http-end-of-headers) + (let ((result (json-parse-buffer :object-type 'alist))) + (with-current-buffer (get-buffer-create "Grocy stock") + (tabulated-list-mode) + (setq tabulated-list-format [("Next due" 12 t) + ("Amount" 20 t) + ("Product" 20 t)] + ;; tabulated-list-padding 2 + tabulated-list-sort-key '("Next due")) + + (thread-last + (cl-map 'list #'grocy-stock--row result) + (setq tabulated-list-entries)) + (tabulated-list-init-header) + (tabulated-list-print) + (display-buffer (current-buffer))))))) + +(defun grocy-products () + (grocy--request + "objects/products" + (lambda (_status) + (goto-char url-http-end-of-headers) + (let ((result (json-parse-buffer))) + (with-current-buffer (get-buffer-create "Grocy products") + (tabulated-list-mode) + (setq tabulated-list-format [("Name" 40 t) + ("Location" 15 t) + ("Unit" 8 t) + ("min stock" 5 t)] + tabulated-list-sort-key '("Name")) + + (thread-last + (cl-map 'list + (lambda (product) + (-let (((&hash "name" "location_id" "qu_id_stock" + "min_stock_amount") product)) + (list product + (vector name + (car (gethash location_id grocy-locations)) + (car (gethash qu_id_stock grocy-units)) + (number-to-string min_stock_amount))))) + result) + (setq tabulated-list-entries)) + (tabulated-list-init-header) + (tabulated-list-print) + (display-buffer (current-buffer))))))) + +(provide 'grocy) +;;; grocy.el ends here -- cgit v1.2.3