biome-multi: basically works

This commit is contained in:
Pavel Korytov 2024-01-04 01:28:36 +03:00
parent 2f96d6de86
commit b351a52c8c
4 changed files with 296 additions and 5 deletions

View file

@ -128,5 +128,42 @@ called with QUERY and the data returned by the API as arguments."
(cl-function (lambda (&key error-thrown response &allow-other-keys) (cl-function (lambda (&key error-thrown response &allow-other-keys)
(biome-api--show-error error-thrown response)))))) (biome-api--show-error error-thrown response))))))
(defun biome-api-get-multiple (queries callback)
"Get data from Open Meteo API.
QUERIES is a list of forms as defined by `biome-query-current'. CALLBACK is
called with QUERIES and the data returned by the API as arguments."
(let (requests)
(seq-map-indexed
(lambda (query idx)
(push
(request (alist-get (alist-get :name query)
biome-api-urls nil nil #'equal)
:type "GET"
:params (biome-api--get-params query)
:parser #'json-read
:success (cl-function
(lambda (&key data &allow-other-keys)
;; I'm not sure why, but `request-response-done-p' for
;; the current request returns nil. I don't
;; know how stable this is, so...
(let ((completed-count
(cl-loop for i from 0
for request in requests
if (or (request-response-done-p request)
(= i (- (length requests) 1 idx)))
sum 1)))
(message "Completed %d/%d requests"
completed-count (length queries))
(when (eq (length queries) completed-count)
(funcall callback (copy-tree queries)
(mapcar #'request-response-data
(reverse requests)))))))
:error
(cl-function (lambda (&key error-thrown response &allow-other-keys)
(biome-api--show-error error-thrown response))))
requests))
queries)))
(provide 'biome-api) (provide 'biome-api)
;;; biome-api.el ends here ;;; biome-api.el ends here

236
biome-multi.el Normal file
View file

@ -0,0 +1,236 @@
;;; biome-multi.el --- Do multiple queries to Open Meteo -*- lexical-binding: t -*-
;; Copyright (C) 2024 Korytov Pavel
;; Author: Korytov Pavel <thexcloud@gmail.com>
;; Maintainer: Korytov Pavel <thexcloud@gmail.com>
;; This file is NOT part of GNU Emacs.
;; 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:
;; TODO
;;; Code:
(require 'biome-query)
(require 'font-lock)
(require 'transient)
(defvar biome-multi-query-current nil
"Current query.
This is a list of forms as defined by `biome-query-current'.")
(defvar biome-multi--callback nil
"Call this with the selected query.")
(defclass biome-multi--transient-report (transient-suffix)
((transient :initform t))
"A class to display the current report for `biome-multi'.")
(cl-defmethod transient-init-value ((_ biome-multi--transient-report))
"A dummy method for `biome-multi--transient-report'."
nil)
(cl-defmethod transient-format ((_ biome-multi--transient-report))
"TODO."
(if (seq-empty-p biome-multi-query-current)
(propertize "Add at least one query" 'face 'error)
(cl-loop for i from 0
for query in biome-multi-query-current
concat (propertize (format "Query #%d: %s\n" i
(alist-get :name query))
'face 'font-lock-keyword-face)
concat (biome-query--format query)
if (not (eq i (1- (length biome-multi-query-current))))
concat "\n")))
(transient-define-infix biome-multi--transient-report-infix ()
:class 'biome-multi--transient-report
:key "~~1")
(defun biome-multi-add-query ()
(interactive)
(biome-query
(lambda (query)
(if (seq-empty-p biome-multi-query-current)
(setq biome-multi-query-current (list (copy-tree query)))
(nconc biome-multi-query-current (list (copy-tree query))))
(biome-multi-query biome-multi--callback))))
(defun biome-multi-reset ()
(interactive)
(setq biome-multi-query-current nil))
(defun biome-multi-edit (idx)
(interactive "nQuery number: ")
(when (or (< idx 0)
(>= idx (length biome-multi-query-current)))
(user-error "Invalid query number"))
(setq biome-query-current (nth idx biome-multi-query-current))
(setq biome-query--callback
(lambda (query)
(setf (nth idx biome-multi-query-current) query)
(biome-multi-query biome-multi--callback)))
(biome-query--section-open (alist-get :name biome-query-current)))
(defun biome-multi-remove (idx)
(interactive "nQuery number: ")
(when (or (< idx 0)
(>= idx (length biome-multi-query-current)))
(user-error "Invalid query number"))
(setq biome-multi-query-current
(cl-loop for query in biome-multi-query-current
for i from 0
unless (eq i idx)
collect query)))
(defun biome-multi-exec ()
(interactive)
(when (seq-empty-p biome-multi-query-current)
(user-error "No queries to execute"))
(funcall biome-multi--callback biome-multi-query-current))
(transient-define-prefix biome-multi-query (callback)
["Open Meteo Multi Query"
(biome-multi--transient-report-infix)]
["Queries"
:class transient-row
("a" "Add query" biome-multi-add-query)
("e" "Edit query" biome-multi-edit :transient t)
("d" "Delete query" biome-multi-remove :transient t)]
["Actions"
:class transient-row
("RET" "Run" biome-multi-exec)
("R" "Reset" biome-multi-reset :transient t)
("q" "Quit" transient-quit-one)]
(interactive (list nil))
(unless callback
(error "Callback is not set. Run M-x `biome-multi' instead"))
(setq biome-multi--callback callback)
(transient-setup 'biome-multi-query))
(defun biome-multi--unique-names-grouped (names-by-group group-names)
(let ((name-occurences (make-hash-table :test #'equal))
(names-mapping (make-hash-table :test #'equal)))
(cl-loop for names in names-by-group
do (cl-loop for name in names
do (puthash name
(1+ (gethash name name-occurences 0))
name-occurences)))
(cl-loop for names in names-by-group
for group-name in group-names
do (cl-loop
for name in names
for occurences = (gethash name name-occurences)
do (puthash (format "%s--%s" group-name name)
(if (= occurences 1)
name
(format "%s_%s" name
(replace-regexp-in-string
(rx space) "_" (downcase group-name))))
names-mapping)))
names-mapping))
(defun biome-multi--unique-names (names)
(let ((name-occurences (make-hash-table :test #'equal))
(added-occurences (make-hash-table :test #'equal)))
(cl-loop for name in names
do (puthash name
(1+ (gethash name name-occurences 0))
name-occurences))
(cl-loop for name in names
for occurences = (gethash name name-occurences)
for added = (gethash name added-occurences)
collect (if (= occurences 1)
name
(format "%s_%d" name
(puthash
name
(1+ (or added 0))
added-occurences))))))
(defun biome-multi--join-results (queries query-names vars-mapping results)
(let ((times (make-hash-table :test #'equal))
(var-values-per-time (make-hash-table :test #'equal)))
(cl-loop for result in results
for query in queries
for query-name in query-names
for group-name = (alist-get :group query)
for vars-field = (intern group-name)
for times-vector = (thread-last
result (alist-get vars-field) (alist-get 'time))
do (cl-loop for time across times-vector
do (puthash time t times))
do (cl-loop for (var-name . values) in (seq-filter
(lambda (v) (not (eq 'time (car v))))
(alist-get vars-field result))
for mapped-var-name =
(gethash (format "%s--%s" query-name var-name) vars-mapping)
for var-values = (make-hash-table :test #'equal)
do (cl-loop for time across times-vector
for value across values
do (puthash time value var-values))
do (puthash mapped-var-name var-values var-values-per-time)))
(let ((times-sorted (seq-sort #'string-lessp (hash-table-keys times))))
`((time . ,(vconcat times-sorted))
,@(cl-loop for var-name being the hash-keys of var-values-per-time
using (hash-values var-values)
collect
(cons (intern var-name)
(vconcat
(cl-loop for time in times-sorted
collect (gethash time var-values)))))))))
(defun biome-multi--merge (queries results)
"Merge QUERIES into one query.
QUERIES is a list of forms as defined by `biome-query-current'."
(let* ((vars-by-group
(cl-loop for query in queries
for group = (alist-get :group query)
collect (alist-get group (alist-get :params query)
nil nil #'string-equal)))
(query-names
(biome-multi--unique-names
(cl-loop for query in queries
collect (alist-get :name query))))
(vars-mapping (biome-multi--unique-names-grouped vars-by-group query-names)))
`(((:name . "Multi Query")
(:group . "multi")
(:params . (("multi" .
,(cl-loop for var-name being the hash-values of vars-mapping
collect var-name)))))
((multi_units
. ,(cons
(cons 'time "iso8601")
(cl-loop
for result in results
for query in queries
for query-name in query-names
for group-name = (alist-get :group query)
for units-field = (intern (format "%s_units" group-name))
append (cl-loop
for (var-name . unit) in (alist-get units-field result)
unless (equal var-name 'time)
collect (cons (intern
(gethash (format "%s--%s" query-name var-name)
vars-mapping))
unit)))))
(multi . ,(biome-multi--join-results queries query-names vars-mapping results))))))
(provide 'biome-multi)
;;; biome-multi.el ends here

View file

@ -175,12 +175,14 @@ KEY is the api key of the variable. VAR-NAMES is the output of
(capitalize (replace-regexp-in-string (capitalize (replace-regexp-in-string
(regexp-quote "_") " " key)))) (regexp-quote "_") " " key))))
(cl-defmethod transient-format ((_obj biome-query--transient-report)) (defun biome-query--format (query)
"Format the `biome-query-current'." "Format QUERY for display.
(let ((group (alist-get :group biome-query-current))
QUERY is a form as defined by `transient-define-prefix'."
(let ((group (alist-get :group query))
(var-names (biome-query--get-var-names-cache)) (var-names (biome-query--get-var-names-cache))
lat lon group-vars line-vars vars) lat lon group-vars line-vars vars)
(dolist (item (alist-get :params biome-query-current)) (dolist (item (alist-get :params query))
(cond (cond
((stringp item) ((stringp item)
(push (biome-query--get-header item var-names) vars)) (push (biome-query--get-header item var-names) vars))
@ -251,6 +253,10 @@ KEY is the api key of the variable. VAR-NAMES is the output of
(when line-vars (when line-vars
(concat (mapconcat #'identity line-vars "\n") "\n"))))) (concat (mapconcat #'identity line-vars "\n") "\n")))))
(cl-defmethod transient-format ((_obj biome-query--transient-report))
"Format the `biome-query-current'."
(biome-query--format biome-query-current))
(transient-define-infix biome-query--transient-report-infix () (transient-define-infix biome-query--transient-report-infix ()
:class 'biome-query--transient-report :class 'biome-query--transient-report
:key "~~1") :key "~~1")
@ -953,7 +959,7 @@ SUFFIXES is a list of suffix definitions."
"Process the query made by `biome-query'." "Process the query made by `biome-query'."
(interactive) (interactive)
(unless biome-query--callback (unless biome-query--callback
(user-error "Biome-query--callback is not set")) (error "Biome-query--callback is not set"))
(funcall biome-query--callback biome-query-current)) (funcall biome-query--callback biome-query-current))
(defun biome-query--generate-preset () (defun biome-query--generate-preset ()

View file

@ -40,6 +40,7 @@
;;; Code: ;;; Code:
(require 'biome-api) (require 'biome-api)
(require 'biome-multi)
(require 'biome-query) (require 'biome-query)
(require 'biome-grid) (require 'biome-grid)
@ -73,6 +74,17 @@ API."
;; previous invocation of `biome' ;; previous invocation of `biome'
(biome-query--section-open (alist-get :name biome-query-current))) (biome-query--section-open (alist-get :name biome-query-current)))
(defun biome-multi ()
"Run multiple queries to Open Meteo and join results."
(interactive)
(biome-multi-query
(lambda (query)
(biome-api-get-multiple
query
(lambda (queries results)
(let ((merged (biome-multi--merge queries results)))
(funcall biome-frontend (nth 0 merged) (nth 1 merged))))))))
(defmacro biome-def-preset (name params) (defmacro biome-def-preset (name params)
"Declare a query preset. "Declare a query preset.