From cb7b93f71c2e0456957078af2e07ed30375b23df Mon Sep 17 00:00:00 2001 From: Andreas Gerler Date: Sun, 26 Apr 2026 19:54:06 +0200 Subject: [PATCH] update graphql-mode --- elpa/graphql-mode-20251213.1110/README-elpa | 12 + .../graphql-mode-autoloads.el | 10 +- .../graphql-mode-pkg.el | 6 +- .../graphql-mode.el | 265 ++++++++++++++++-- 4 files changed, 263 insertions(+), 30 deletions(-) create mode 100644 elpa/graphql-mode-20251213.1110/README-elpa rename elpa/{graphql-mode-20240218.1603 => graphql-mode-20251213.1110}/graphql-mode-autoloads.el (75%) rename elpa/{graphql-mode-20240218.1603 => graphql-mode-20251213.1110}/graphql-mode-pkg.el (71%) rename elpa/{graphql-mode-20240218.1603 => graphql-mode-20251213.1110}/graphql-mode.el (63%) diff --git a/elpa/graphql-mode-20251213.1110/README-elpa b/elpa/graphql-mode-20251213.1110/README-elpa new file mode 100644 index 00000000..d14e51ec --- /dev/null +++ b/elpa/graphql-mode-20251213.1110/README-elpa @@ -0,0 +1,12 @@ +This package implements a major mode to edit GraphQL schemas and +query. The basic functionality includes: + + - Syntax highlight + - Automatic indentation + +Additionally, it is able to + - Sending GraphQL queries to an end-point URL + - Uploading files with GraphQL mutations (multipart requests) + +Files with the .graphql and .gql extensions are automatically +opened with this mode. diff --git a/elpa/graphql-mode-20240218.1603/graphql-mode-autoloads.el b/elpa/graphql-mode-20251213.1110/graphql-mode-autoloads.el similarity index 75% rename from elpa/graphql-mode-20240218.1603/graphql-mode-autoloads.el rename to elpa/graphql-mode-20251213.1110/graphql-mode-autoloads.el index e31d1040..c48085ab 100644 --- a/elpa/graphql-mode-20240218.1603/graphql-mode-autoloads.el +++ b/elpa/graphql-mode-20251213.1110/graphql-mode-autoloads.el @@ -11,10 +11,14 @@ ;;; Generated autoloads from graphql-mode.el -(autoload 'graphql-mode "graphql-mode" "\ -A major mode to edit GraphQL schemas. +(autoload 'graphql-mode "graphql-mode" +"A major mode to edit GraphQL schemas. -(fn)" t) +In addition to any hooks its parent mode `prog-mode' might have run, +this mode runs the hook `graphql-mode-hook', as the final or +penultimate step during initialization. + +\\{graphql-mode-map}" t) (add-to-list 'auto-mode-alist '("\\.graphql\\'" . graphql-mode)) (add-to-list 'auto-mode-alist '("\\.gql\\'" . graphql-mode)) (register-definition-prefixes "graphql-mode" '("graphql-")) diff --git a/elpa/graphql-mode-20240218.1603/graphql-mode-pkg.el b/elpa/graphql-mode-20251213.1110/graphql-mode-pkg.el similarity index 71% rename from elpa/graphql-mode-20240218.1603/graphql-mode-pkg.el rename to elpa/graphql-mode-20251213.1110/graphql-mode-pkg.el index 30fc5de3..8edb73be 100644 --- a/elpa/graphql-mode-20240218.1603/graphql-mode-pkg.el +++ b/elpa/graphql-mode-20251213.1110/graphql-mode-pkg.el @@ -1,10 +1,10 @@ ;; -*- no-byte-compile: t; lexical-binding: nil -*- -(define-package "graphql-mode" "20240218.1603" +(define-package "graphql-mode" "20251213.1110" "Major mode for editing GraphQL schemas." '((emacs "25.1")) :url "https://github.com/davazp/graphql-mode" - :commit "2183895ea7932359586c4bb99e8562c4aef3aaf7" - :revdesc "2183895ea793" + :commit "ccec811d0f1cb13e63e330234e408d56ba0f4d8e" + :revdesc "ccec811d0f1c" :keywords '("languages") :authors '(("David Vazquez Pua" . "davazp@gmail.com")) :maintainers '(("David Vazquez Pua" . "davazp@gmail.com"))) diff --git a/elpa/graphql-mode-20240218.1603/graphql-mode.el b/elpa/graphql-mode-20251213.1110/graphql-mode.el similarity index 63% rename from elpa/graphql-mode-20240218.1603/graphql-mode.el rename to elpa/graphql-mode-20251213.1110/graphql-mode.el index 3cd85837..85f5ae35 100644 --- a/elpa/graphql-mode-20240218.1603/graphql-mode.el +++ b/elpa/graphql-mode-20251213.1110/graphql-mode.el @@ -6,8 +6,8 @@ ;; Keywords: languages ;; Package-Requires: ((emacs "25.1")) ;; Homepage: https://github.com/davazp/graphql-mode -;; Package-Version: 20240218.1603 -;; Package-Revision: 2183895ea793 +;; Package-Version: 20251213.1110 +;; Package-Revision: ccec811d0f1c ;; 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 @@ -32,6 +32,7 @@ ;; ;; Additionally, it is able to ;; - Sending GraphQL queries to an end-point URL +;; - Uploading files with GraphQL mutations (multipart requests) ;; ;; Files with the .graphql and .gql extensions are automatically ;; opened with this mode. @@ -77,6 +78,25 @@ :type '(repeat sexp) :group 'graphql) +(defcustom graphql-upload-files nil + "List of files to upload with GraphQL mutation. +Each element should be a cons cell (VAR-NAME . FILE-PATH) where +VAR-NAME is the variable name in the GraphQL query and FILE-PATH +is the path to the file to upload." + :tag "GraphQL" + :type '(repeat (cons (string :tag "Variable name") + (file :tag "File path"))) + :group 'graphql) + +(defcustom graphql-upload-format 'operations + "Format for multipart file upload requests. +- `operations': Use operations/map format (graphql-multipart-request-spec) +- `form-data': Use direct form fields (query/variables/map as separate fields)" + :tag "GraphQL" + :type '(choice (const :tag "Operations format" operations) + (const :tag "Form-data format" form-data)) + :group 'graphql) + (defun graphql-locate-config (dir) "Locate a graphql config starting in DIR." (if-let ((config-dir (locate-dominating-file dir ".graphqlconfig"))) @@ -114,46 +134,224 @@ (push (cons 'variables variables) body)) (json-encode body))) -(defun graphql--query (query &optional operation variables) +(defun graphql--make-multipart-boundary () + "Generate a unique boundary string for multipart form data." + (format "----GraphQLBoundary%s" (md5 (format "%s%s" (current-time) (random))))) + +(defun graphql-encode-multipart-operations (query operation variables upload-files boundary) + "Encode using operations/map format (graphql-multipart-request-spec). + +QUERY is the GraphQL query string. +OPERATION is the operation name. +VARIABLES is the variables alist. +UPLOAD-FILES is a list of (VAR-NAME . FILE-PATH) cons cells. +BOUNDARY is the multipart boundary string. + +Returns the complete multipart body as a unibyte string." + (let* ((operations-obj (list (cons 'query query))) + (map-obj '()) + (parts '()) + (file-index 0)) + + ;; Add operation name if provided + (when (and operation (not (string= operation ""))) + (push (cons 'operationName operation) operations-obj)) + + ;; Add variables + (when variables + (push (cons 'variables variables) operations-obj)) + + ;; Add operations part + (push (format "--%s\r\nContent-Disposition: form-data; name=\"operations\"\r\nContent-Type: application/json\r\n\r\n%s\r\n" + boundary + (json-encode (nreverse operations-obj))) + parts) + + ;; Build the map object + (dolist (upload upload-files) + (let* ((var-name (car upload)) + (field-name (format "%d" file-index))) + ;; Add to map: {"0": ["variables.varName"]} + (push (cons (intern field-name) + (vector (format "variables.%s" var-name))) + map-obj) + (setq file-index (1+ file-index)))) + + ;; Add map part + (push (format "--%s\r\nContent-Disposition: form-data; name=\"map\"\r\nContent-Type: application/json\r\n\r\n%s\r\n" + boundary + (json-encode map-obj)) + parts) + + ;; Add file parts + (setq file-index 0) + (dolist (upload upload-files) + (let* ((file-path (cdr upload)) + (field-name (format "%d" file-index)) + (filename (file-name-nondirectory file-path)) + (file-content (with-temp-buffer + (set-buffer-multibyte nil) + (insert-file-contents-literally file-path) + (buffer-string)))) + (push (format "--%s\r\nContent-Disposition: form-data; name=\"%s\"; filename=\"%s\"\r\nContent-Type: application/octet-stream\r\n\r\n" + boundary + field-name + filename) + parts) + (push file-content parts) + (push "\r\n" parts) + (setq file-index (1+ file-index)))) + + ;; Add final boundary + (push (format "--%s--\r\n" boundary) parts) + + ;; Combine all parts into unibyte string + (apply 'concat (nreverse parts)))) + +(defun graphql-encode-multipart-form-data (query operation variables upload-files boundary) + "Encode using direct form-data fields (query/variables/map). + +QUERY is the GraphQL query string. +OPERATION is the operation name. +VARIABLES is the variables alist. +UPLOAD-FILES is a list of (VAR-NAME . FILE-PATH) cons cells. +BOUNDARY is the multipart boundary string. + +Returns the complete multipart body as a unibyte string. Equivalent +to curl -F \\='query=...' -F \\='variables=...\\=' -F \\='map=...' -F \\='file=@...'." + (let* ((map-obj '()) + (parts '())) + + ;; Add query part + (push (format "--%s\r\nContent-Disposition: form-data; name=\"query\"\r\n\r\n%s\r\n" + boundary + query) + parts) + + ;; Add operation name if provided + (when (and operation (not (string= operation ""))) + (push (format "--%s\r\nContent-Disposition: form-data; name=\"operationName\"\r\n\r\n%s\r\n" + boundary + operation) + parts)) + + ;; Add variables part + (when variables + (push (format "--%s\r\nContent-Disposition: form-data; name=\"variables\"\r\nContent-Type: application/json\r\n\r\n%s\r\n" + boundary + (json-encode variables)) + parts)) + + ;; Build the map object and add files + (dolist (upload upload-files) + (let* ((var-name (car upload)) + (file-path (cdr upload))) + ;; Add to map: {"varName": ["variables.varName"]} + (push (cons (intern var-name) + (vector (format "variables.%s" var-name))) + map-obj) + + ;; Add file part + (let* ((filename (file-name-nondirectory file-path)) + (file-content (with-temp-buffer + (set-buffer-multibyte nil) + (insert-file-contents-literally file-path) + (buffer-string)))) + (push (format "--%s\r\nContent-Disposition: form-data; name=\"%s\"; filename=\"%s\"\r\nContent-Type: application/octet-stream\r\n\r\n" + boundary + var-name + filename) + parts) + (push file-content parts) + (push "\r\n" parts)))) + + ;; Add map part + (push (format "--%s\r\nContent-Disposition: form-data; name=\"map\"\r\nContent-Type: application/json\r\n\r\n%s\r\n" + boundary + (json-encode map-obj)) + parts) + + ;; Add final boundary + (push (format "--%s--\r\n" boundary) parts) + + ;; Combine all parts into unibyte string + (apply 'concat (nreverse parts)))) + +(defun graphql-encode-multipart (query operation variables upload-files boundary) + "Encode GraphQL request as multipart form data for file uploads. + +QUERY is the GraphQL query string. +OPERATION is the operation name. +VARIABLES is the variables alist (files should have nil values). +UPLOAD-FILES is a list of (VAR-NAME . FILE-PATH) cons cells. +BOUNDARY is the multipart boundary string. + +Returns the complete multipart body as a unibyte string. +Uses format specified by `graphql-upload-format'." + (if (eq graphql-upload-format 'form-data) + (graphql-encode-multipart-form-data query operation variables upload-files boundary) + (graphql-encode-multipart-operations query operation variables upload-files boundary))) + +(defun graphql--query (query &optional operation variables upload-files) "Send QUERY to the server and return the response. The query is sent as a HTTP POST request to the URL at `graphql-url'. The query can be any GraphQL definition (query, mutation or subscription). OPERATION is a name for the operation. VARIABLES is the JSON string that specifies the values -of the variables used in the query." +of the variables used in the query. UPLOAD-FILES is a list of +\(VAR-NAME . FILE-PATH) cons cells for file uploads." ;; Note that we need to get the value of graphql-url in the current ;; before before we switch to the temporary one. (let ((url graphql-url)) - (graphql-post-request url query operation variables))) + (graphql-post-request url query operation variables upload-files))) (declare-function request "request") (declare-function request-response-data "request") (declare-function request-response--raw-header "request") -(defun graphql-post-request (url query &optional operation variables) +(defun graphql-post-request (url query &optional operation variables upload-files) "Make post request to graphql server with url and body. URL hostname, path, search parameters, such as operationName and variables QUERY query definition(s) of query, mutation, and/or subscription OPERATION name of the operation if multiple definition is given in QUERY -VARIABLES list of variables for query operation" +VARIABLES list of variables for query operation +UPLOAD-FILES list of (VAR-NAME . FILE-PATH) cons cells for file uploads" (or (require 'request nil t) (error "graphql-post-request needs the request package. \ Please install it and try again.")) - (let* ((body (graphql-encode-json query operation variables)) - (headers (append '(("Content-Type" . "application/json")) graphql-extra-headers))) - (request url - :type "POST" - :data body - :headers headers - :parser 'json-read - :sync t - :complete (lambda (&rest _) - (message "%s" (if (string-equal "" operation) - url - (format "%s?operationName=%s" - url operation))))))) + (if upload-files + ;; Multipart request for file uploads + (let* ((boundary (graphql--make-multipart-boundary)) + (body (graphql-encode-multipart query operation variables upload-files boundary)) + (headers (append `(("Content-Type" . ,(format "multipart/form-data; boundary=%s" boundary))) + graphql-extra-headers))) + (request url + :type "POST" + :data body + :headers headers + :parser 'json-read + :sync t + :complete (lambda (&rest _) + (message "%s" (if (string-equal "" operation) + url + (format "%s?operationName=%s" + url operation)))))) + ;; Regular JSON request + (let* ((body (graphql-encode-json query operation variables)) + (headers (append '(("Content-Type" . "application/json")) graphql-extra-headers))) + (request url + :type "POST" + :data body + :headers headers + :parser 'json-read + :sync t + :complete (lambda (&rest _) + (message "%s" (if (string-equal "" operation) + url + (format "%s?operationName=%s" + url operation)))))))) (defun graphql-beginning-of-query () "Move the point to the beginning of the current query." @@ -226,22 +424,40 @@ Please install it and try again.")) (define-key map (kbd "q") 'quit-window) map)) +(defun graphql-read-upload-files () + "Interactively read files to upload with GraphQL query. +Returns a list of (VAR-NAME . FILE-PATH) cons cells." + (let ((files '()) + (continue t)) + (while continue + (let ((var-name (read-string "Variable name (empty to finish): "))) + (if (string-empty-p var-name) + (setq continue nil) + (let ((file-path (read-file-name (format "File for %s: " var-name)))) + (push (cons var-name file-path) files))))) + (nreverse files))) + (defun graphql-send-query (&optional prompt) "Send the current GraphQL query/mutation/subscription to server. With \\[universal-argument] PROMPT, prompt for -`graphql-url'/`graphql-variables-file'." +`graphql-url'/`graphql-variables-file'/`graphql-upload-files'." (interactive "P") (let* ((url (or (and (not prompt) graphql-url) (read-string "GraphQL URL: " graphql-url))) (var (or (and (not prompt) graphql-variables-file) - (read-file-name "GraphQL Variables: " nil graphql-variables-file)))) + (read-file-name "GraphQL Variables: " nil graphql-variables-file))) + (files (if prompt + (when (y-or-n-p "Upload files? ") + (graphql-read-upload-files)) + graphql-upload-files))) (let ((graphql-url url) - (graphql-variables-file var)) + (graphql-variables-file var) + (graphql-upload-files files)) (let* ((query (buffer-substring-no-properties (point-min) (point-max))) (operation (graphql-current-operation)) (variables (graphql-current-variables var)) - (response (graphql--query query operation variables))) + (response (graphql--query query operation variables files))) (with-current-buffer-window "*GraphQL*" 'display-buffer-pop-up-window nil (erase-buffer) @@ -260,6 +476,7 @@ With \\[universal-argument] PROMPT, prompt for ;; binding). (setq graphql-url url) (setq graphql-variables-file var) + (setq graphql-upload-files files) nil)) (defvar graphql-mode-map -- 2.47.3