From 76baf9840010ba181547bbed5d86a29171922054 Mon Sep 17 00:00:00 2001 From: "F. Jason Park" Date: Sun, 26 Mar 2023 19:40:58 -0700 Subject: [PATCH 0/3] *** NOT A PATCH *** *** BLURB HERE *** Daniel Pettersson (1): Fix DCC GET flag parsing in erc-dcc F. Jason Park (2): Add subcommand dispatch facility to erc-cmd-HELP Add subcommand erc-cmd-HELP handler to erc-dcc lisp/erc/erc-dcc.el | 65 ++++++++++++++++++++++++++------- lisp/erc/erc.el | 7 +++- test/lisp/erc/erc-dcc-tests.el | 67 +++++++++++++++++++++++++++------- 3 files changed, 110 insertions(+), 29 deletions(-) Interdiff: diff --git a/lisp/erc/erc-dcc.el b/lisp/erc/erc-dcc.el index d7c685e9413..5406369c62f 100644 --- a/lisp/erc/erc-dcc.el +++ b/lisp/erc/erc-dcc.el @@ -43,7 +43,7 @@ ;; /dcc chat nick - Either accept pending chat offer from nick, or offer ;; DCC chat to nick ;; /dcc close type [nick] - Close DCC connection (SEND/GET/CHAT) with nick -;; /dcc get [-t][-s] nick [file] - Accept DCC offer from nick +;; /dcc get [-t][-s] nick [--] file - Accept DCC offer from nick ;; /dcc list - List all DCC offers/connections ;; /dcc send nick file - Offer DCC SEND to nick @@ -389,12 +389,18 @@ erc-dcc-get-default-directory :type '(choice (const :value nil :tag "Default directory") directory)) ;;;###autoload -(defun erc-cmd-DCC (cmd &rest args) +(defun erc-cmd-DCC (line &rest compat-args) "Parser for /dcc command. This figures out the dcc subcommand and calls the appropriate routine to handle it. The function dispatched should be named \"erc-dcc-do-FOO-command\", where FOO is one of CLOSE, GET, SEND, LIST, CHAT, etc." - (when cmd + (let (cmd args) + ;; Called as library function (i.e., not directly as /dcc) + (if compat-args + (setq cmd line + args compat-args) + (setq args (delete "" (split-string-shell-command line)) + cmd (pop args))) (let ((fn (intern-soft (concat "erc-dcc-do-" (upcase cmd) "-command")))) (if fn (apply fn erc-server-process args) @@ -404,8 +410,17 @@ erc-cmd-DCC (apropos "erc-dcc-do-.*-command") t)))) +(put 'erc-cmd-DCC 'do-not-parse-args t) (autoload 'pcomplete-erc-all-nicks "erc-pcomplete") +(defun erc-dcc--cmd-help (&rest args) + (describe-function + (or (and args (intern-soft (concat "erc-dcc-do-" + (upcase (car args)) "-command"))) + 'erc-cmd-DCC))) + +(put 'erc-cmd-DCC 'erc--cmd-help #'erc-dcc--cmd-help) + ;;;###autoload (defun pcomplete/erc-mode/DCC () "Provide completion for the /DCC command." @@ -430,15 +445,20 @@ pcomplete/erc-mode/DCC (eq (plist-get elt :type) 'GET)) erc-dcc-list))) ('send (pcomplete-erc-all-nicks)))) + (when (equal "get" (downcase (pcomplete-arg 'first 1))) + (pcomplete-opt "-")) (pcomplete-here (pcase (intern (downcase (pcomplete-arg 'first 1))) - ('get (mapcar (lambda (elt) (plist-get elt :file)) + ('get (mapcar (lambda (elt) + (combine-and-quote-strings (list (plist-get elt :file)))) (cl-remove-if-not (lambda (elt) (and (eq (plist-get elt :type) 'GET) (erc-nick-equal-p (erc-extract-nick (plist-get elt :nick)) - (pcomplete-arg 1)))) + (pcase (pcomplete-arg 1) + ("--" (pcomplete-arg 2)) + (v v))))) erc-dcc-list))) ('close (mapcar #'erc-dcc-nick (cl-remove-if-not @@ -505,31 +525,34 @@ erc-dcc-do-CLOSE-command t)) (defun erc-dcc-do-GET-command (proc &rest args) - "Do a DCC GET command. -ARGS are expected to contain: + "Perform a DCC GET command. +Recognize input conforming to the following usage syntax: + + /DCC GET [-t|-s] nick [--] filename + nick The person who is sending the file. - filename The filename to be downloaded. Can be split into multiple arguments - which is then joined by a space. + filename The filename to be downloaded. Can be split into multiple + arguments that are then joined by a space. flags \"-t\" sets `:turbo' see `erc-dcc-list' \"-s\" sets `:secure' see `erc-dcc-list' -ARGS are parsed as follows: - [flag] nick [flag] filename [flag] -PROC is the server process." - (let ((possible-flags '("-s" "-t")) - flags nick elt possible-files filename) - ;; Get flags between get and nick - (while (seq-contains-p possible-flags (car args) 'equal) - (setq flags (cons (pop args) flags))) - (setq nick (or (pop args) "")) - ;; Get flags between nick and filename - (while (seq-contains-p possible-flags (car args) 'equal) - (setq flags (cons (pop args) flags))) - ;; Get flags after filename - (setq args (reverse args)) - (while (seq-contains-p possible-flags (car args) 'equal) - (setq flags (cons (pop args) flags))) - (setq filename (or (mapconcat #'identity (reverse args) " ") "") - elt (erc-dcc-member :nick nick :type 'GET :file filename)) + \"--\" indicates end of options + All of which are optional. + +Expect PROC to be the server process and ARGS to contain +everything after the subcommand \"GET\" in the usage description +above." + ;; Despite the advertised syntax above, we currently respect flags + ;; in these positions: [flag] nick [flag] filename [flag] + (let* ((trailing (and-let* ((trailing (member "--" args))) + (setq args (butlast args (length trailing))) + (cdr trailing))) + (args (seq-group-by (lambda (s) (eq ?- (aref s 0))) args)) + (flags (prog1 (cdr (assq t args)) + (setq args (nconc (cdr (assq nil args)) trailing)))) + (nick (pop args)) + (file (and args (mapconcat #'identity args " "))) + (elt (erc-dcc-member :nick nick :type 'GET :file file)) + (filename (or file (plist-get elt :file) "unknown"))) (if elt (let* ((file (read-file-name (format-prompt "Local filename" diff --git a/lisp/erc/erc.el b/lisp/erc/erc.el index 69bdb5d71b1..60aa26579c5 100644 --- a/lisp/erc/erc.el +++ b/lisp/erc/erc.el @@ -3203,7 +3203,7 @@ erc-cmd-CTCP (erc-send-ctcp-message nick str) t)) -(defun erc-cmd-HELP (&optional func) +(defun erc-cmd-HELP (&optional func &rest rest) "Popup help information. If FUNC contains a valid function or variable, help about that @@ -3236,6 +3236,11 @@ erc-cmd-HELP nil))))) (if sym (cond + ((progn (autoloadp (symbol-function sym)) + (autoload-do-load (symbol-function sym)) + nil)) + ((get sym 'erc--cmd-help) + (apply (get sym 'erc--cmd-help) rest)) ((boundp sym) (describe-variable sym)) ((fboundp sym) (describe-function sym)) (t nil)) diff --git a/test/lisp/erc/erc-dcc-tests.el b/test/lisp/erc/erc-dcc-tests.el index f21463bb5a0..fed86eff2c5 100644 --- a/test/lisp/erc/erc-dcc-tests.el +++ b/test/lisp/erc/erc-dcc-tests.el @@ -100,7 +100,7 @@ erc-dcc-handle-ctcp-send--base (ert-deftest erc-dcc-handle-ctcp-send--turbo () (erc-dcc-tests--dcc-handle-ctcp-send t)) -(defun erc-dcc-tests--erc-dcc-do-GET-command (file) +(defun erc-dcc-tests--erc-dcc-do-GET-command (file &optional sep) (with-temp-buffer (let* ((proc (start-process "fake" (current-buffer) "sleep" "10")) (elt (list :nick "tester!~tester@fake.irc" @@ -134,7 +134,7 @@ erc-dcc-tests--erc-dcc-do-GET-command (ert-info ("No turbo") (should-not (plist-member elt :turbo)) (goto-char erc-input-marker) - (insert "/dcc GET tester " file) + (insert "/dcc GET tester " (or sep "") (prin1-to-string file)) (erc-send-current-line) (should-not (plist-member (car erc-dcc-list) :turbo)) (should (equal (pop calls) (list elt file proc)))) @@ -142,7 +142,7 @@ erc-dcc-tests--erc-dcc-do-GET-command (ert-info ("Arg turbo in pos 2") (should-not (plist-member elt :turbo)) (goto-char erc-input-marker) - (insert "/dcc GET -t tester " file) + (insert "/dcc GET -t tester " (or sep "") (prin1-to-string file)) (erc-send-current-line) (should (eq t (plist-get (car erc-dcc-list) :turbo))) (should (equal (pop calls) (list elt file proc)))) @@ -151,7 +151,7 @@ erc-dcc-tests--erc-dcc-do-GET-command (setq elt (plist-put elt :turbo nil) erc-dcc-list (list elt)) (goto-char erc-input-marker) - (insert "/dcc GET tester -t " file) + (insert "/dcc GET tester -t " (or sep "") (prin1-to-string file)) (erc-send-current-line) (should (eq t (plist-get (car erc-dcc-list) :turbo))) (should (equal (pop calls) (list elt file proc)))) @@ -160,17 +160,18 @@ erc-dcc-tests--erc-dcc-do-GET-command (setq elt (plist-put elt :turbo nil) erc-dcc-list (list elt)) (goto-char erc-input-marker) - (insert "/dcc GET tester " file " -t") + (insert "/dcc GET tester " (prin1-to-string file) " -t" (or sep "")) (erc-send-current-line) - (should (eq t (plist-get (car erc-dcc-list) :turbo))) - (should (equal (pop calls) (list elt file proc)))))))) + (should (eq (if sep nil t) (plist-get (car erc-dcc-list) :turbo))) + (should (equal (pop calls) (if sep nil (list elt file proc))))))))) (ert-deftest erc-dcc-do-GET-command () (erc-dcc-tests--erc-dcc-do-GET-command "foo.bin") (erc-dcc-tests--erc-dcc-do-GET-command "foo - file.bin") - (erc-dcc-tests--erc-dcc-do-GET-command "foo -t file.bin")) + (erc-dcc-tests--erc-dcc-do-GET-command "foo -t file.bin") + (erc-dcc-tests--erc-dcc-do-GET-command "-t" "-- ")) -(defun erc-dcc-tests--pcomplete-common (test-fn) +(defun erc-dcc-tests--pcomplete-common (test-fn &optional file) (with-current-buffer (get-buffer-create "*erc-dcc-do-GET-command*") (let* ((inhibit-message noninteractive) (proc (start-process "fake" (current-buffer) "sleep" "10")) @@ -180,7 +181,7 @@ erc-dcc-tests--pcomplete-common :parent proc :ip "127.0.0.1" :port "9899" - :file "foo.bin" + :file (or file "foo.bin") :size 1405135128)) ;; erc-accidental-paste-threshold-seconds @@ -216,6 +217,20 @@ pcomplete/erc-mode/DCC--get-basic (beginning-of-line) (should (search-forward "/dcc get tester foo.bin" nil t)))))) +(ert-deftest pcomplete/erc-mode/DCC--get-quoted () + (erc-dcc-tests--pcomplete-common + (lambda () + (insert "/dcc get ") + (call-interactively #'completion-at-point) + (save-excursion + (beginning-of-line) + (should (search-forward "/dcc get tester" nil t))) + (call-interactively #'completion-at-point) + (save-excursion + (beginning-of-line) + (should (search-forward "/dcc get tester \"foo bar.bin\"" nil t)))) + "foo bar.bin")) + (ert-deftest pcomplete/erc-mode/DCC--get-1flag () (erc-dcc-tests--pcomplete-common (lambda () @@ -287,4 +302,23 @@ pcomplete/erc-mode/DCC--get-2flags-reverse (beginning-of-line) (should (search-forward "/dcc get -t -s tester foo.bin" nil t)))))) +(ert-deftest pcomplete/erc-mode/DCC--get-sep () + (erc-dcc-tests--pcomplete-common + (lambda () + (insert "/dcc get ") + (call-interactively #'completion-at-point) + (save-excursion + (beginning-of-line) + (should (search-forward "/dcc get tester" nil t))) + (insert "-") + (call-interactively #'completion-at-point) + (save-excursion + (beginning-of-line) + (should (search-forward "/dcc get tester -- " nil t))) + (call-interactively #'completion-at-point) + (save-excursion + (beginning-of-line) + (should (search-forward "/dcc get tester -- -t" nil t)))) + "-t")) + ;;; erc-dcc-tests.el ends here -- 2.39.2