dotemacs

My Emacs configuration
git clone git://git.entf.net/dotemacs
Log | Files | Refs | LICENSE

restclient.el (32358B)


      1 ;;; restclient.el --- An interactive HTTP client for Emacs
      2 ;;
      3 ;; Public domain.
      4 
      5 ;; Author: Pavel Kurnosov <pashky@gmail.com>
      6 ;; Maintainer: Pavel Kurnosov <pashky@gmail.com>
      7 ;; Created: 01 Apr 2012
      8 ;; Keywords: http
      9 ;; Package-Version: 20220101.1239
     10 ;; Package-Commit: 9e2cfa86529133eba6c9ef53794be182f15e4c21
     11 
     12 ;; This file is not part of GNU Emacs.
     13 ;; This file is public domain software. Do what you want.
     14 
     15 ;;; Commentary:
     16 ;;
     17 ;; This is a tool to manually explore and test HTTP REST
     18 ;; webservices.  Runs queries from a plain-text query sheet, displays
     19 ;; results as a pretty-printed XML, JSON and even images.
     20 
     21 ;;; Code:
     22 ;;
     23 (require 'url)
     24 (require 'json)
     25 (require 'outline)
     26 (eval-when-compile (require 'subr-x))
     27 (eval-when-compile
     28   (if (version< emacs-version "26")
     29       (require 'cl)
     30     (require 'cl-lib)))
     31 
     32 (defgroup restclient nil
     33   "An interactive HTTP client for Emacs."
     34   :group 'tools)
     35 
     36 (defcustom restclient-log-request t
     37   "Log restclient requests to *Messages*."
     38   :group 'restclient
     39   :type 'boolean)
     40 
     41 (defcustom restclient-same-buffer-response t
     42   "Re-use same buffer for responses or create a new one each time."
     43   :group 'restclient
     44   :type 'boolean)
     45 
     46 (defcustom restclient-same-buffer-response-name "*HTTP Response*"
     47   "Name for response buffer."
     48   :group 'restclient
     49   :type 'string)
     50 
     51 (defcustom restclient-info-buffer-name "*Restclient Info*"
     52   "Name for info buffer."
     53   :group 'restclient
     54   :type 'string)
     55 
     56 (defcustom restclient-inhibit-cookies nil
     57   "Inhibit restclient from sending cookies implicitly."
     58   :group 'restclient
     59   :type 'boolean)
     60 
     61 (defcustom restclient-content-type-modes '(("text/xml" . xml-mode)
     62                                            ("text/plain" . text-mode)
     63                                            ("application/xml" . xml-mode)
     64                                            ("application/json" . js-mode)
     65                                            ("image/png" . image-mode)
     66                                            ("image/jpeg" . image-mode)
     67                                            ("image/jpg" . image-mode)
     68                                            ("image/gif" . image-mode)
     69                                            ("text/html" . html-mode))
     70   "An association list mapping content types to buffer modes"
     71   :group 'restclient
     72   :type '(alist :key-type string :value-type symbol))
     73 
     74 (defcustom restclient-response-body-only nil
     75   "When parsing response, only return its body."
     76   :group 'restclient
     77   :type 'boolean)
     78 
     79 (defgroup restclient-faces nil
     80   "Faces used in Restclient Mode"
     81   :group 'restclient
     82   :group 'faces)
     83 
     84 (defface restclient-variable-name-face
     85   '((t (:inherit font-lock-preprocessor-face)))
     86   "Face for variable name."
     87   :group 'restclient-faces)
     88 
     89 (defface restclient-variable-string-face
     90   '((t (:inherit font-lock-string-face)))
     91   "Face for variable value (string)."
     92   :group 'restclient-faces)
     93 
     94 (defface restclient-variable-elisp-face
     95   '((t (:inherit font-lock-function-name-face)))
     96   "Face for variable value (Emacs lisp)."
     97   :group 'restclient-faces)
     98 
     99 (defface restclient-variable-multiline-face
    100   '((t (:inherit font-lock-doc-face)))
    101   "Face for multi-line variable value marker."
    102   :group 'restclient-faces)
    103 
    104 (defface restclient-variable-usage-face
    105   '((t (:inherit restclient-variable-name-face)))
    106   "Face for variable usage (only used when headers/body is represented as a single variable, not highlighted when variable appears in the middle of other text)."
    107   :group 'restclient-faces)
    108 
    109 (defface restclient-method-face
    110   '((t (:inherit font-lock-keyword-face)))
    111   "Face for HTTP method."
    112   :group 'restclient-faces)
    113 
    114 (defface restclient-url-face
    115   '((t (:inherit font-lock-function-name-face)))
    116   "Face for variable value (Emacs lisp)."
    117   :group 'restclient-faces)
    118 
    119 (defface restclient-file-upload-face
    120   '((t (:inherit restclient-variable-multiline-face)))
    121   "Face for highlighting upload file paths."
    122   :group 'restclient-faces)
    123 
    124 (defface restclient-header-name-face
    125   '((t (:inherit font-lock-variable-name-face)))
    126   "Face for HTTP header name."
    127   :group 'restclient-faces)
    128 
    129 (defface restclient-header-value-face
    130   '((t (:inherit font-lock-string-face)))
    131   "Face for HTTP header value."
    132   :group 'restclient-faces)
    133 
    134 (defface restclient-request-hook-face
    135   '((t (:inherit font-lock-preprocessor-face)))
    136   "Face for single request hook indicator."
    137   :group 'restclient-faces)
    138 
    139 (defface restclient-request-hook-name-face
    140   '((t (:inherit font-lock-function-name-face)))
    141   "Face for single request hook type names."
    142   :group 'restclient-faces)
    143 
    144 (defface restclient-request-hook-args-face
    145   '((t (:inherit font-lock-string-face)))
    146   "Face for single request hook type arguments."
    147   :group 'restclient-faces)
    148 
    149 
    150 (defvar restclient-within-call nil)
    151 
    152 (defvar restclient-request-time-start nil)
    153 (defvar restclient-request-time-end nil)
    154 
    155 (defvar restclient-var-overrides nil
    156   "An alist of vars that will override any set in the file,
    157   also where dynamic vars set on callbacks are stored.")
    158 
    159 (defvar restclient-result-handlers '()
    160   "A registry of available completion hooks.
    161    Stored as an alist of name -> (hook-creation-func . description)")
    162 
    163 (defvar restclient-curr-request-functions nil
    164   "A list of functions to run once when the next request is loaded")
    165 
    166 (defvar restclient-response-loaded-hook nil
    167   "Hook run after response buffer is formatted.")
    168 
    169 (defvar restclient-http-do-hook nil
    170   "Hook to run before making request.")
    171 
    172 (defvar restclient-response-received-hook nil
    173   "Hook run after data is loaded into response buffer.")
    174 
    175 (defcustom restclient-vars-max-passes 10
    176   "Maximum number of recursive variable references. This is to prevent hanging if two variables reference each other directly or indirectly."
    177   :group 'restclient
    178   :type 'integer)
    179 
    180 (defconst restclient-comment-separator "#")
    181 (defconst restclient-comment-start-regexp (concat "^" restclient-comment-separator))
    182 (defconst restclient-comment-not-regexp (concat "^[^" restclient-comment-separator "]"))
    183 (defconst restclient-empty-line-regexp "^\\s-*$")
    184 
    185 (defconst restclient-method-url-regexp
    186   "^\\(GET\\|POST\\|DELETE\\|PUT\\|HEAD\\|OPTIONS\\|PATCH\\) \\(.*\\)$")
    187 
    188 (defconst restclient-header-regexp
    189   "^\\([^](),/:;@[\\{}= \t]+\\): \\(.*\\)$")
    190 
    191 (defconst restclient-use-var-regexp
    192   "^\\(:[^: \n]+\\)$")
    193 
    194 (defconst restclient-var-regexp
    195   (concat "^\\(:[^:= ]+\\)[ \t]*\\(:?\\)=[ \t]*\\(<<[ \t]*\n\\(\\(.*\n\\)*?\\)" restclient-comment-separator "\\|\\([^<].*\\)$\\)"))
    196 
    197 (defconst restclient-svar-regexp
    198   "^\\(:[^:= ]+\\)[ \t]*=[ \t]*\\(.+?\\)$")
    199 
    200 (defconst restclient-evar-regexp
    201   "^\\(:[^: ]+\\)[ \t]*:=[ \t]*\\(.+?\\)$")
    202 
    203 (defconst restclient-mvar-regexp
    204   "^\\(:[^: ]+\\)[ \t]*:?=[ \t]*\\(<<\\)[ \t]*$")
    205 
    206 (defconst restclient-file-regexp
    207   "^<[ \t]*\\([^<>\n\r]+\\)[ \t]*$")
    208 
    209 (defconst restclient-content-type-regexp
    210   "^Content-[Tt]ype: \\(\\w+\\)/\\(?:[^\\+\r\n]*\\+\\)*\\([^;\r\n]+\\)")
    211 
    212 (defconst restclient-response-hook-regexp
    213   "^\\(->\\) \\([^[:space:]]+\\) +\\(.*\\)$")
    214 
    215 ;; The following disables the interactive request for user name and
    216 ;; password should an API call encounter a permission-denied response.
    217 ;; This API is meant to be usable without constant asking for username
    218 ;; and password.
    219 (defadvice url-http-handle-authentication (around restclient-fix)
    220   (if restclient-within-call
    221       (setq ad-return-value t)
    222     ad-do-it))
    223 (ad-activate 'url-http-handle-authentication)
    224 
    225 (defadvice url-cache-extract (around restclient-fix-2)
    226   (unless restclient-within-call
    227     ad-do-it))
    228 (ad-activate 'url-cache-extract)
    229 
    230 (defadvice url-http-user-agent-string (around restclient-fix-3)
    231   (if restclient-within-call
    232       (setq ad-return-value nil)
    233     ad-do-it))
    234 (ad-activate 'url-http-user-agent-string)
    235 
    236 (defun restclient-http-do (method url headers entity &rest handle-args)
    237   "Send ENTITY and HEADERS to URL as a METHOD request."
    238   (if restclient-log-request
    239       (message "HTTP %s %s Headers:[%s] Body:[%s]" method url headers entity))
    240   (let ((url-request-method (encode-coding-string method 'us-ascii))
    241         (url-request-extra-headers '())
    242         (url-request-data (encode-coding-string entity 'utf-8))
    243         (url-mime-charset-string (url-mime-charset-string))
    244         (url-mime-language-string nil)
    245         (url-mime-encoding-string nil)
    246         (url-mime-accept-string nil)
    247         (url-personal-mail-address nil))
    248 
    249     (dolist (header headers)
    250       (let* ((mapped (assoc-string (downcase (car header))
    251                                    '(("from" . url-personal-mail-address)
    252                                      ("accept-encoding" . url-mime-encoding-string)
    253                                      ("accept-charset" . url-mime-charset-string)
    254                                      ("accept-language" . url-mime-language-string)
    255                                      ("accept" . url-mime-accept-string)))))
    256 
    257         (if mapped
    258             (set (cdr mapped) (encode-coding-string (cdr header) 'us-ascii))
    259           (let* ((hkey (encode-coding-string (car header) 'us-ascii))
    260                  (hvalue (encode-coding-string (cdr header) 'us-ascii)))
    261             (setq url-request-extra-headers (cons (cons hkey hvalue) url-request-extra-headers))))))
    262 
    263     (setq restclient-within-call t)
    264     (setq restclient-request-time-start (current-time))
    265     (run-hooks 'restclient-http-do-hook)
    266     (url-retrieve url 'restclient-http-handle-response
    267                   (append (list method url (if restclient-same-buffer-response
    268                                                restclient-same-buffer-response-name
    269                                              (format "*HTTP %s %s*" method url))) handle-args) nil restclient-inhibit-cookies)))
    270 
    271 (defun restclient-prettify-response (method url)
    272   (save-excursion
    273     (let ((start (point)) (guessed-mode) (end-of-headers))
    274       (while (and (not (looking-at restclient-empty-line-regexp))
    275                   (eq (progn
    276                         (when (looking-at restclient-content-type-regexp)
    277                           (setq guessed-mode
    278                                 (cdr (assoc-string (concat
    279                                                     (match-string-no-properties 1)
    280                                                     "/"
    281                                                     (match-string-no-properties 2))
    282                                                    restclient-content-type-modes
    283                                                    t))))
    284                         (forward-line)) 0)))
    285       (setq end-of-headers (point))
    286       (while (and (looking-at restclient-empty-line-regexp)
    287                   (eq (forward-line) 0)))
    288       (unless guessed-mode
    289         (setq guessed-mode
    290               (or (assoc-default nil
    291                                  ;; magic mode matches
    292                                  '(("<\\?xml " . xml-mode)
    293                                    ("{\\s-*\"" . js-mode))
    294                                  (lambda (re _dummy)
    295                                    (looking-at re))) 'js-mode)))
    296       (let ((headers (buffer-substring-no-properties start end-of-headers)))
    297         (when guessed-mode
    298           (delete-region start (point))
    299           (unless (eq guessed-mode 'image-mode)
    300             (apply guessed-mode '())
    301             (if (fboundp 'font-lock-flush)
    302                 (font-lock-flush)
    303               (with-no-warnings
    304                 (font-lock-fontify-buffer))))
    305 
    306           (cond
    307            ((eq guessed-mode 'xml-mode)
    308             (goto-char (point-min))
    309             (while (search-forward-regexp "\>[ \\t]*\<" nil t)
    310               (backward-char) (insert "\n"))
    311             (indent-region (point-min) (point-max)))
    312 
    313            ((eq guessed-mode 'image-mode)
    314             (let* ((img (buffer-string)))
    315               (delete-region (point-min) (point-max))
    316               (fundamental-mode)
    317               (insert-image (create-image img nil t))))
    318 
    319            ((eq guessed-mode 'js-mode)
    320             (let ((json-special-chars (remq (assoc ?/ json-special-chars) json-special-chars))
    321 		  ;; Emacs 27 json.el uses `replace-buffer-contents' for
    322 		  ;; pretty-printing which is great because it keeps point and
    323 		  ;; markers intact but can be very slow with huge minimalized
    324 		  ;; JSON.  We don't need that here.
    325 		  (json-pretty-print-max-secs 0))
    326               (ignore-errors (json-pretty-print-buffer)))
    327             (restclient-prettify-json-unicode)))
    328 
    329           (goto-char (point-max))
    330           (or (eq (point) (point-min)) (insert "\n"))
    331 	  (unless restclient-response-body-only
    332             (let ((hstart (point)))
    333               (insert method " " url "\n" headers)
    334               (insert (format "Request duration: %fs\n" (float-time (time-subtract restclient-request-time-end restclient-request-time-start))))
    335               (unless (member guessed-mode '(image-mode text-mode))
    336 		(comment-region hstart (point))))))))))
    337 
    338 (defun restclient-prettify-json-unicode ()
    339   (save-excursion
    340     (goto-char (point-min))
    341     (while (re-search-forward "\\\\[Uu]\\([0-9a-fA-F]\\{4\\}\\)" nil t)
    342       (replace-match (char-to-string (decode-char 'ucs (string-to-number (match-string 1) 16))) t nil))))
    343 
    344 (defun restclient-http-handle-response (status method url bufname raw stay-in-window)
    345   "Switch to the buffer returned by `url-retreive'.
    346 The buffer contains the raw HTTP response sent by the server."
    347   (setq restclient-within-call nil)
    348   (setq restclient-request-time-end (current-time))
    349   (if (= (point-min) (point-max))
    350       (signal (car (plist-get status :error)) (cdr (plist-get status :error)))
    351     (when (buffer-live-p (current-buffer))
    352       (with-current-buffer (restclient-decode-response
    353                             (current-buffer)
    354                             bufname
    355                             restclient-same-buffer-response)
    356         (run-hooks 'restclient-response-received-hook)
    357         (unless raw
    358           (restclient-prettify-response method url))
    359         (buffer-enable-undo)
    360 	(restclient-response-mode)
    361         (run-hooks 'restclient-response-loaded-hook)
    362         (if stay-in-window
    363             (display-buffer (current-buffer) t)
    364           (switch-to-buffer-other-window (current-buffer)))))))
    365 
    366 (defun restclient-decode-response (raw-http-response-buffer target-buffer-name same-name)
    367   "Decode the HTTP response using the charset (encoding) specified in the Content-Type header. If no charset is specified, default to UTF-8."
    368   (let* ((charset-regexp "^Content-Type.*charset=\\([-A-Za-z0-9]+\\)")
    369          (image? (save-excursion
    370                    (search-forward-regexp "^Content-Type.*[Ii]mage" nil t)))
    371          (encoding (if (save-excursion
    372                          (search-forward-regexp charset-regexp nil t))
    373                        (intern (downcase (match-string 1)))
    374                      'utf-8)))
    375     (if image?
    376         ;; Dont' attempt to decode. Instead, just switch to the raw HTTP response buffer and
    377         ;; rename it to target-buffer-name.
    378         (with-current-buffer raw-http-response-buffer
    379           ;; We have to kill the target buffer if it exists, or `rename-buffer'
    380           ;; will raise an error.
    381           (when (get-buffer target-buffer-name)
    382             (kill-buffer target-buffer-name))
    383           (rename-buffer target-buffer-name)
    384           raw-http-response-buffer)
    385       ;; Else, switch to the new, empty buffer that will contain the decoded HTTP
    386       ;; response. Set its encoding, copy the content from the unencoded
    387       ;; HTTP response buffer and decode.
    388       (let ((decoded-http-response-buffer
    389              (get-buffer-create
    390               (if same-name target-buffer-name (generate-new-buffer-name target-buffer-name)))))
    391         (with-current-buffer decoded-http-response-buffer
    392           (setq buffer-file-coding-system encoding)
    393           (save-excursion
    394             (erase-buffer)
    395             (insert-buffer-substring raw-http-response-buffer))
    396           (kill-buffer raw-http-response-buffer)
    397           (condition-case nil
    398               (decode-coding-region (point-min) (point-max) encoding)
    399             (error
    400              (message (concat "Error when trying to decode http response with encoding: "
    401                               (symbol-name encoding)))))
    402           decoded-http-response-buffer)))))
    403 
    404 (defun restclient-current-min ()
    405   (save-excursion
    406     (beginning-of-line)
    407     (if (looking-at restclient-comment-start-regexp)
    408         (if (re-search-forward restclient-comment-not-regexp (point-max) t)
    409             (point-at-bol) (point-max))
    410       (if (re-search-backward restclient-comment-start-regexp (point-min) t)
    411           (point-at-bol 2)
    412         (point-min)))))
    413 
    414 (defun restclient-current-max ()
    415   (save-excursion
    416     (if (re-search-forward restclient-comment-start-regexp (point-max) t)
    417         (max (- (point-at-bol) 1) 1)
    418       (progn (goto-char (point-max))
    419              (if (looking-at "^$") (- (point) 1) (point))))))
    420 
    421 (defun restclient-replace-all-in-string (replacements string)
    422   (if replacements
    423       (let ((current string)
    424             (pass restclient-vars-max-passes)
    425             (continue t))
    426         (while (and continue (> pass 0))
    427           (setq pass (- pass 1))
    428           (setq current (replace-regexp-in-string (regexp-opt (mapcar 'car replacements))
    429                                                   (lambda (key)
    430                                                     (setq continue t)
    431                                                     (cdr (assoc key replacements)))
    432                                                   current t t)))
    433         current)
    434     string))
    435 
    436 (defun restclient-replace-all-in-header (replacements header)
    437   (cons (car header)
    438         (restclient-replace-all-in-string replacements (cdr header))))
    439 
    440 (defun restclient-chop (text)
    441   (if text (replace-regexp-in-string "\n$" "" text) nil))
    442 
    443 (defun restclient-find-vars-before-point ()
    444   (let ((vars nil)
    445         (bound (point)))
    446     (save-excursion
    447       (goto-char (point-min))
    448       (while (search-forward-regexp restclient-var-regexp bound t)
    449         (let ((name (match-string-no-properties 1))
    450               (should-eval (> (length (match-string 2)) 0))
    451               (value (or (restclient-chop (match-string-no-properties 4)) (match-string-no-properties 3))))
    452           (setq vars (cons (cons name (if should-eval (restclient-eval-var value) value)) vars))))
    453       (append restclient-var-overrides vars))))
    454 
    455 (defun restclient-eval-var (string)
    456   (with-output-to-string (princ (eval (read string)))))
    457 
    458 (defun restclient-make-header (&optional string)
    459   (cons (match-string-no-properties 1 string)
    460         (match-string-no-properties 2 string)))
    461 
    462 (defun restclient-parse-headers (string)
    463   (let ((start 0)
    464         (headers '()))
    465     (while (string-match restclient-header-regexp string start)
    466       (setq headers (cons (restclient-make-header string) headers)
    467             start (match-end 0)))
    468     headers))
    469 
    470 (defun restclient-read-file (path)
    471   (with-temp-buffer
    472     (insert-file-contents path)
    473     (buffer-string)))
    474 
    475 (defun restclient-parse-body (entity vars)
    476   (if (= 0 (or (string-match restclient-file-regexp entity) 1))
    477       (restclient-read-file (match-string 1 entity))
    478     (restclient-replace-all-in-string vars entity)))
    479 
    480 (defun restclient-parse-hook (cb-type args-offset args)
    481   (if-let ((handler (assoc cb-type restclient-result-handlers)))
    482       (funcall (cadr handler) args args-offset)
    483     `(lambda ()
    484        (message "Unknown restclient hook type %s" ,cb-type))))
    485 
    486 (defun restclient-register-result-func (name creation-func description)
    487   (let ((new-cell (cons name (cons creation-func description))))
    488     (setq restclient-result-handlers (cons new-cell restclient-result-handlers))))
    489 
    490 (defun restclient-remove-var (var-name)
    491   (setq restclient-var-overrides (assoc-delete-all var-name restclient-var-overrides)))
    492 
    493 (defun restclient-set-var (var-name value)
    494   (restclient-remove-var var-name)
    495   (setq restclient-var-overrides (cons (cons var-name value) restclient-var-overrides)))
    496 
    497 (defun restclient-get-var-at-point (var-name buffer-name buffer-pos)
    498   (message (format "getting var %s form %s at %s" var-name buffer-name buffer-pos))
    499   (let* ((vars-at-point  (save-excursion
    500 			   (switch-to-buffer buffer-name)
    501 			   (goto-char buffer-pos)
    502 			   ;; if we're called from a restclient buffer we need to lookup vars before the current hook or evar
    503 			   ;; outside a restclient buffer only globals are available so moving the point wont matter
    504 			   (re-search-backward "^:\\|->" (point-min) t)
    505 			   (restclient-find-vars-before-point))))
    506     (restclient-replace-all-in-string vars-at-point (cdr (assoc var-name vars-at-point)))))
    507 
    508 (defmacro restclient-get-var (var-name)
    509   (lexical-let ((buf-name (buffer-name (current-buffer)))
    510 		(buf-point (point)))
    511     `(restclient-get-var-at-point ,var-name ,buf-name ,buf-point)))
    512 
    513 (defun restclient-single-request-function ()
    514   (dolist (f restclient-curr-request-functions)
    515     (ignore-errors
    516       (funcall f)))  
    517   (setq restclient-curr-request-functions nil)
    518   (remove-hook 'restclient-response-loaded-hook 'restclient-single-request-function))
    519 
    520 
    521 (defun restclient-http-parse-current-and-do (func &rest args)
    522   (save-excursion
    523     (goto-char (restclient-current-min))
    524     (when (re-search-forward restclient-method-url-regexp (point-max) t)
    525       (let ((method (match-string-no-properties 1))
    526             (url (string-trim (match-string-no-properties 2)))
    527             (vars (restclient-find-vars-before-point))
    528             (headers '()))
    529         (forward-line)
    530         (while (cond
    531 		((looking-at restclient-response-hook-regexp)
    532 		 (when-let (hook-function (restclient-parse-hook (match-string-no-properties 2)
    533 								 (match-end 2)
    534 								 (match-string-no-properties 3)))
    535 		   (push hook-function restclient-curr-request-functions)))
    536                 ((and (looking-at restclient-header-regexp) (not (looking-at restclient-empty-line-regexp)))
    537                  (setq headers (cons (restclient-replace-all-in-header vars (restclient-make-header)) headers)))
    538                 ((looking-at restclient-use-var-regexp)
    539                  (setq headers (append headers (restclient-parse-headers (restclient-replace-all-in-string vars (match-string 1)))))))
    540           (forward-line))
    541         (when (looking-at restclient-empty-line-regexp)
    542           (forward-line))
    543 	(when restclient-curr-request-functions
    544 	  (add-hook 'restclient-response-loaded-hook 'restclient-single-request-function))
    545         (let* ((cmax (restclient-current-max))
    546                (entity (restclient-parse-body (buffer-substring (min (point) cmax) cmax) vars))
    547                (url (restclient-replace-all-in-string vars url)))
    548           (apply func method url headers entity args))))))
    549 
    550 (defun restclient-copy-curl-command ()
    551   "Formats the request as a curl command and copies the command to the clipboard."
    552   (interactive)
    553   (restclient-http-parse-current-and-do
    554    '(lambda (method url headers entity)
    555       (let ((header-args
    556              (apply 'append
    557                     (mapcar (lambda (header)
    558                               (list "-H" (format "%s: %s" (car header) (cdr header))))
    559                             headers))))
    560         (kill-new (concat "curl "
    561                           (mapconcat 'shell-quote-argument
    562                                      (append '("-i")
    563                                              header-args
    564                                              (list (concat "-X" method))
    565                                              (list url)
    566                                              (when (> (string-width entity) 0)
    567                                                (list "-d" entity)))
    568                                      " "))))
    569       (message "curl command copied to clipboard."))))
    570 
    571 
    572 (defun restclient-elisp-result-function (args offset)
    573   (goto-char offset)
    574   (lexical-let ((form (macroexpand-all (read (current-buffer)))))
    575     (lambda ()
    576       (eval form))))
    577 
    578 (restclient-register-result-func
    579  "run-hook" #'restclient-elisp-result-function
    580  "Call the provided (possibly multi-line) elisp when the result
    581   buffer is formatted. Equivalent to a restclient-response-loaded-hook
    582   that only runs for this request.
    583   eg. -> on-response (message \"my hook called\")" )
    584 
    585 ;;;###autoload
    586 (defun restclient-http-send-current (&optional raw stay-in-window)
    587   "Sends current request.
    588 Optional argument RAW don't reformat response if t.
    589 Optional argument STAY-IN-WINDOW do not move focus to response buffer if t."
    590   (interactive)
    591   (restclient-http-parse-current-and-do 'restclient-http-do raw stay-in-window))
    592 
    593 ;;;###autoload
    594 (defun restclient-http-send-current-raw ()
    595   "Sends current request and get raw result (no reformatting or syntax highlight of XML, JSON or images)."
    596   (interactive)
    597   (restclient-http-send-current t))
    598 
    599 ;;;###autoload
    600 (defun restclient-http-send-current-stay-in-window ()
    601   "Send current request and keep focus in request window."
    602   (interactive)
    603   (restclient-http-send-current nil t))
    604 
    605 (defun restclient-jump-next ()
    606   "Jump to next request in buffer."
    607   (interactive)
    608   (let ((last-min nil))
    609     (while (not (eq last-min (goto-char (restclient-current-min))))
    610       (goto-char (restclient-current-min))
    611       (setq last-min (point))))
    612   (goto-char (+ (restclient-current-max) 1))
    613   (goto-char (restclient-current-min)))
    614 
    615 (defun restclient-jump-prev ()
    616   "Jump to previous request in buffer."
    617   (interactive)
    618   (let* ((current-min (restclient-current-min))
    619          (end-of-entity
    620           (save-excursion
    621             (progn (goto-char (restclient-current-min))
    622                    (while (and (or (looking-at "^\s*\\(#.*\\)?$")
    623                                    (eq (point) current-min))
    624                                (not (eq (point) (point-min))))
    625                      (forward-line -1)
    626                      (beginning-of-line))
    627                    (point)))))
    628     (unless (eq (point-min) end-of-entity)
    629       (goto-char end-of-entity)
    630       (goto-char (restclient-current-min)))))
    631 
    632 (defun restclient-mark-current ()
    633   "Mark current request."
    634   (interactive)
    635   (goto-char (restclient-current-min))
    636   (set-mark-command nil)
    637   (goto-char (restclient-current-max))
    638   (backward-char 1)
    639   (setq deactivate-mark nil))
    640 
    641 (defun restclient-show-info ()  
    642   ;; restclient-info-buffer-name
    643   (interactive)
    644   (let ((vars-at-point (restclient-find-vars-before-point)))
    645     (cl-labels ((non-overidden-vars-at-point ()
    646 					     (seq-filter (lambda (v)
    647 							   (null (assoc (car v) restclient-var-overrides)))
    648 							 vars-at-point))
    649 		(sanitize-value-cell (var-value)
    650 		     (replace-regexp-in-string "\n" "|\n| |"
    651 			       (replace-regexp-in-string "\|" "\\\\vert{}"
    652 					 (restclient-replace-all-in-string vars-at-point var-value))))
    653 		(var-row (var-name var-value)
    654 			 (insert "|" var-name "|" (sanitize-value-cell var-value) "|\n"))
    655 		(var-table (table-name)
    656 			   (insert (format "* %s \n|--|\n|Name|Value|\n|---|\n" table-name)))
    657 		(var-table-footer ()
    658 				  (insert "|--|\n\n")))
    659       
    660       (with-current-buffer (get-buffer-create restclient-info-buffer-name)
    661 	;; insert our info
    662 	(erase-buffer)
    663 
    664 	(insert "\Restclient Info\ \n\n")
    665        
    666 	(var-table "Dynamic Variables")
    667 	(dolist (dv restclient-var-overrides)
    668 	  (var-row (car dv) (cdr dv)))
    669 	(var-table-footer)
    670 
    671 	;;    (insert ":Info:\n Dynamic vars defined by request hooks or with calls to restclient-set-var\n:END:")
    672 
    673 	(var-table "Vars at current position")
    674 	(dolist (dv (non-overidden-vars-at-point))
    675 	  (var-row (car dv) (cdr dv)))
    676 	(var-table-footer)
    677 
    678 
    679 	;; registered callbacks
    680 	(var-table "Registered request hook types")
    681 	(dolist (handler-name (delete-dups (mapcar 'car restclient-result-handlers)))
    682 	       (var-row handler-name (cddr (assoc handler-name restclient-result-handlers))))
    683     	(var-table-footer)
    684 
    685 	(insert "\n\n'q' to exit\n")
    686 	(org-mode)
    687 	(org-toggle-pretty-entities)
    688 	(org-table-iterate-buffer-tables)
    689 	(outline-show-all)
    690 	(restclient-response-mode)
    691 	(goto-char (point-min))))
    692     (switch-to-buffer-other-window restclient-info-buffer-name)))
    693 
    694 (defun restclient-narrow-to-current ()
    695   "Narrow to region of current request"
    696   (interactive)
    697   (narrow-to-region (restclient-current-min) (restclient-current-max)))
    698 
    699 (defun restclient-toggle-body-visibility ()
    700   (interactive)
    701   ;; If we are not on the HTTP call line, don't do anything
    702   (let ((at-header (save-excursion
    703                      (beginning-of-line)
    704                      (looking-at restclient-method-url-regexp))))
    705     (when at-header
    706       (save-excursion
    707         (end-of-line)
    708         ;; If the overlays at this point have 'invisible set, toggling
    709         ;; must make the region visible. Else it must hide the region
    710         
    711         ;; This part of code is from org-hide-block-toggle method of
    712         ;; Org mode
    713         (let ((overlays (overlays-at (point))))
    714           (if (memq t (mapcar
    715                        (lambda (o)
    716                          (eq (overlay-get o 'invisible) 'outline))
    717                        overlays))
    718               (outline-flag-region (point) (restclient-current-max) nil)
    719             (outline-flag-region (point) (restclient-current-max) t)))) t)))
    720 
    721 (defun restclient-toggle-body-visibility-or-indent ()
    722   (interactive)
    723   (unless (restclient-toggle-body-visibility)
    724     (indent-for-tab-command)))
    725 
    726 (defconst restclient-mode-keywords
    727   (list (list restclient-method-url-regexp '(1 'restclient-method-face) '(2 'restclient-url-face))
    728         (list restclient-svar-regexp '(1 'restclient-variable-name-face) '(2 'restclient-variable-string-face))
    729         (list restclient-evar-regexp '(1 'restclient-variable-name-face) '(2 'restclient-variable-elisp-face t))
    730         (list restclient-mvar-regexp '(1 'restclient-variable-name-face) '(2 'restclient-variable-multiline-face t))
    731         (list restclient-use-var-regexp '(1 'restclient-variable-usage-face))
    732         (list restclient-file-regexp '(0 'restclient-file-upload-face))
    733         (list restclient-header-regexp '(1 'restclient-header-name-face t) '(2 'restclient-header-value-face t))
    734 	(list restclient-response-hook-regexp '(1 ' restclient-request-hook-face t)
    735 	      '(2 'restclient-request-hook-name-face t)
    736 	      '(3 'restclient-request-hook-args-face t))))
    737 
    738 (defconst restclient-mode-syntax-table
    739   (let ((table (make-syntax-table)))
    740     (modify-syntax-entry ?\# "<" table)
    741     (modify-syntax-entry ?\n ">#" table)
    742     table))
    743 
    744 (defvar restclient-mode-map
    745   (let ((map (make-sparse-keymap)))
    746     (define-key map (kbd "C-c C-c") 'restclient-http-send-current)
    747     (define-key map (kbd "C-c C-r") 'restclient-http-send-current-raw)
    748     (define-key map (kbd "C-c C-v") 'restclient-http-send-current-stay-in-window)
    749     (define-key map (kbd "C-c C-n") 'restclient-jump-next)
    750     (define-key map (kbd "C-c C-p") 'restclient-jump-prev)
    751     (define-key map (kbd "C-c C-.") 'restclient-mark-current)
    752     (define-key map (kbd "C-c C-u") 'restclient-copy-curl-command)
    753     (define-key map (kbd "C-c n n") 'restclient-narrow-to-current)
    754     (define-key map (kbd "C-c C-i") 'restclient-show-info)   
    755     map)
    756   "Keymap for restclient-mode.")
    757 
    758 (define-minor-mode restclient-outline-mode
    759   "Minor mode to allow show/hide of request bodies by TAB."
    760       :init-value nil
    761       :lighter nil
    762       :keymap '(("\t" . restclient-toggle-body-visibility-or-indent)
    763                 ("\C-c\C-a" . restclient-toggle-body-visibility-or-indent))
    764       :group 'restclient)
    765 
    766 (define-minor-mode restclient-response-mode
    767   "Minor mode to allow additional keybindings in restclient response buffer."
    768   :init-value nil
    769   :lighter nil
    770   :keymap '(("q" . (lambda ()
    771 		     (interactive)
    772 		     (quit-window (get-buffer-window (current-buffer))))))
    773   :group 'restclient)
    774 
    775 ;;;###autoload
    776 (define-derived-mode restclient-mode fundamental-mode "REST Client"
    777   "Turn on restclient mode."
    778   (set (make-local-variable 'comment-start) "# ")
    779   (set (make-local-variable 'comment-start-skip) "# *")
    780   (set (make-local-variable 'comment-column) 48)
    781 
    782   (set (make-local-variable 'font-lock-defaults) '(restclient-mode-keywords))
    783   ;; We use outline-mode's method outline-flag-region to hide/show the
    784   ;; body. As a part of it, it sets 'invisibility text property to
    785   ;; 'outline. To get ellipsis, we need 'outline to be in
    786   ;; buffer-invisibility-spec
    787   (add-to-invisibility-spec '(outline . t)))
    788 
    789 (add-hook 'restclient-mode-hook 'restclient-outline-mode)
    790 
    791 (provide 'restclient)
    792 
    793 (eval-after-load 'helm
    794   '(ignore-errors (require 'restclient-helm)))
    795 
    796 (eval-after-load 'jq-mode
    797   '(ignore-errors (require 'restclient-jq)))
    798 
    799 ;;; restclient.el ends here