;; SPDX-License-Identifier: EUPL-1.2 ;; SPDX-FileCopyrightText: 2025 Uko Kokņevičs (defpackage :ukkoclot/src/tg/method-macros (:documentation "Macros for easy defining TG methods.") (:use :c2cl :iterate) (:import-from :alexandria :make-keyword :with-gensyms) (:import-from :com.dieggsy.f-string :enable-f-strings) (:import-from :serapeum :-> :take) (:import-from :state) (:import-from :str) (:import-from :ukkoclot/src/transport :do-call :http-method) (:export :define-tg-method)) (in-package :ukkoclot/src/tg/method-macros) (eval-when (:compile-toplevel :load-toplevel :execute) (enable-f-strings)) (eval-when (:compile-toplevel :load-toplevel :execute) (defstruct (param (:constructor make-param%)) name type default skip-if-default) (defparameter +unique+ (gensym)) ;; TODO: Fix optional-and-key ! (-> make-param (symbol t &optional t &key (:skip-if-default boolean)) param) (defun make-param (name type ; lint:suppress avoid-optional-and-key &optional (default +unique+) &key (skip-if-default (not (eq default +unique+)))) (let ((default (if (eq default +unique+) `(error ,#f"No value given for {name}") default))) (make-param% :name name :type type :default default :skip-if-default skip-if-default))) ;; TODO: list-of-params, list-of-param-specs (-> parse-param-specs (list) list) (defun parse-param-specs (param-specs) (iter (for param-spec in param-specs) (collect (apply #'make-param param-spec)))) (-> path-from-name (symbol) string) (defun path-from-name (name) (let ((str (str:camel-case name))) (if (str:ends-with-p "%" str :ignore-case nil) (take (- (length str) 1) str) str))) (-> emit-append-to-args (param symbol) list) (defun emit-append-to-args (param args) `(setf ,args (acons ',(param-name param) ,(param-name param) ,args))) (-> emit-arg-type (param) list) (defun emit-arg-type (param) `(,(make-keyword (param-name param)) ,(param-type param))) (-> emit-defun-arg (param) list) (defun emit-defun-arg (param) `(,(param-name param) ,(param-default param))) ;; TODO: list-of-params (-> emit-defun (symbol t list http-method) list) (defun emit-defun (name return-type params method) (with-gensyms (args) `(defun ,name (&key ,@(iter (for param in params) ; lint:suppress lambda-list-invalid (collect (emit-defun-arg param)))) (let (,args) ,@(iter (for param in (reverse params)) (collect (if (param-skip-if-default param) `(unless (equal ,(param-name param) ,(param-default param)) ,(emit-append-to-args param args)) (emit-append-to-args param args)))) (do-call ,method ,(path-from-name name) ',return-type ,args))))) ;; TODO: list-of-params (-> emit-ftype (symbol t list) list) (defun emit-ftype (name return-type params) `(-> ,name (&key ,@(iter (for param in params) (collect (emit-arg-type param)))) ,return-type))) (defmacro define-tg-method ((name type &optional (method :POST)) &body param-specs) (let ((params (parse-param-specs param-specs))) `(progn ,(emit-ftype name type params) ,(emit-defun name type params method))))