Skip to content

Commit 0bf9ac2

Browse files
Merge pull request #35 from johannes-mueller/custom-actions
Dynamic custom commands
2 parents 068d3a3 + 3cdee15 commit 0bf9ac2

File tree

4 files changed

+212
-3
lines changed

4 files changed

+212
-3
lines changed

README.md

+7
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,13 @@ the last tested module resp. last tested function are tested. If there are no
130130
last tests, an error message is thrown.
131131

132132

133+
### Custom test or build actions
134+
135+
There are two functions `test-cockpit-add-custom-action` and
136+
`test-cockpit-add-dynamic-custom-action` that allow you to register custom
137+
actions for a project type.
138+
139+
133140
## Dape support
134141

135142
There are stubs to make use of the [Dape](https://github.com/svaante/dape/)

test-cockpit-cask.el

+1-1
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@
4040
(cl-defmethod test-cockpit--engine-current-function-string ((_obj test-cockpit-cask-engine))
4141
"Implement test-cockpit--engine-current-function-string."
4242
(when-let ((fn (buffer-file-name)))
43-
(when (string-suffix-p ".el-test.el" fn)
43+
(when (string-suffix-p "test.el" fn)
4444
(which-function))))
4545

4646

test-cockpit.el

+81-2
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,10 @@ Usually there is one such engine per project that has been
8080
visited during the current session. An engine is an instance of
8181
a derived class of `test-cockpit--engine'.")
8282

83+
84+
(defvar test-cockpit--project-type-custom-actions '()
85+
"Custom actions that can be registered on a project type level.")
86+
8387
(defclass test-cockpit--engine ()
8488
((last-command :initarg :last-command
8589
:initform nil)
@@ -389,6 +393,31 @@ and thus can be repeated using `test-cockpit-repeat-test'."
389393
(oset (test-cockpit--retrieve-engine) last-custom-command compile-command)
390394
(oset (test-cockpit--retrieve-engine) last-command compile-command))
391395

396+
(defun test-cockpit--process-custom-command (command regex replacement)
397+
"Replace REGEX in COMMAND with REPLACEMENT only if the first character of the match is not '%'"
398+
(let ((replace-closure
399+
(lambda (match)
400+
(let ((marker (substring match 0 1))
401+
(old-text (substring match 1)))
402+
(if (equal marker "%")
403+
old-text
404+
(concat marker replacement))))))
405+
(replace-regexp-in-string regex replace-closure command t)))
406+
407+
;;;###autoload
408+
(defun test-cockpit-dynamic-custom-test-command (command)
409+
"Run `compile' command for a custom test command.
410+
The command run is determined by COMMAND where
411+
* %P is replaced with the absolute current procject root path
412+
* %F is replaced with the absolute current buffer file path
413+
* %f is replaced with the current buffer file path relative to project root"
414+
(let* ((case-fold-search nil)
415+
(relative-file-path (substring (buffer-file-name) (length (projectile-project-root))))
416+
(command (test-cockpit--process-custom-command command "\\(.\\)%P" (projectile-project-root)))
417+
(command (test-cockpit--process-custom-command command "\\(.\\)%F" (buffer-file-name)))
418+
(command (test-cockpit--process-custom-command command "\\(.\\)%f" relative-file-path)))
419+
(test-cockpit--run-test command)))
420+
392421
;;;###autoload
393422
(defun test-cockpit-repeat-test (&optional _args)
394423
"Repeat the last test if the current project had last test.
@@ -472,8 +501,51 @@ in order to call the last test action with modified ARGS."
472501
(test-cockpit--launch-dape config)
473502
(user-error "No recent test-action has been performed or no Dape support for backend")))
474503

504+
(defun test-cockpit-add-custom-action (project-type shortcut description action)
505+
"Add a custom ACTION to a test-cockpit of PROJECT-TYPE.
506+
507+
The PROJECT-TYPE must be a registered project type. ACTION can be either a function
508+
or a string. A string is passed as is to the `compile' function.
509+
510+
SHORTCUT is the transient shortcut and DESCRIPTION is the transient description for
511+
the action."
512+
(let ((action (if (stringp action)
513+
`(lambda () (interactive) (test-cockpit--run-test ,action))
514+
action)))
515+
(test-cockpit--add-custom-action-function project-type shortcut description action)))
516+
517+
(defun test-cockpit-add-dynamic-custom-action (project-type shortcut description command-template)
518+
"Add a dynamic custom ACTION to a test-cockpit of PROJECT-TYPE.
519+
520+
The PROJECT-TYPE must be a registered project type. COMMAND-TEMPLATE is a
521+
string that is used to determine the compile command
522+
* %P is replaced with the absolute current procject root path
523+
* %F is replaced with the absolute current buffer file path
524+
* %f is replaced with the current buffer file path relative to project root
525+
526+
SHORTCUT is the transient shortcut and DESCRIPTION is the transient
527+
description for the action."
528+
(test-cockpit--add-custom-action-function project-type shortcut description
529+
`(lambda () (interactive) (test-cockpit-dynamic-custom-test-command ,command-template))))
530+
531+
(defun test-cockpit--add-custom-action-function (project-type shortcut description action)
532+
"Register a custom action consisting of SHORTCUT, DESCRIPTION and ACTION to PROJECT-TYPE."
533+
(let ((action-list (alist-get project-type test-cockpit--project-type-custom-actions))
534+
(action-set `(,shortcut ,description ,action)))
535+
(if action-list
536+
(setcdr (assoc project-type test-cockpit--project-type-custom-actions)
537+
(append action-list `(,action-set)))
538+
(push `(,project-type . (,action-set))
539+
test-cockpit--project-type-custom-actions))))
540+
541+
(defun test-cockpit--custom-actions ()
542+
"Make the transient suffix for the custom actions."
543+
(when-let ((custom-actions
544+
(alist-get (projectile-project-type) test-cockpit--project-type-custom-actions)))
545+
(vconcat ["Custom actions"] (vconcat custom-actions))))
546+
475547
(defun test-cockpit--launch-dape (config)
476-
"Launch the dape debug session and memorize that last test was a dape session."
548+
"Launch the dape debug session with CONFIG and memorize that last test was a dape session."
477549
(dape config)
478550
(oset (test-cockpit--retrieve-engine) last-command 'test-cockpit--last-command-was-dape))
479551

@@ -547,12 +619,13 @@ repetition."
547619
"Get the dape configuration for the last test."
548620
(test-cockpit--engine-dape-last-test-config (test-cockpit--retrieve-engine)))
549621

622+
550623
(transient-define-prefix test-cockpit-prefix ()
551624
"Test the project."
552625
:value 'test-cockpit--last-switches
553626
[])
554627

555-
(defun test-cockpit--main-suffix ()
628+
(defun test-cockpit--test-action-suffix ()
556629
"Setup the main menu common for all projects for testing."
557630
(let ((module-string (or (test-cockpit--current-module-string) (test-cockpit--last-module-string)))
558631
(function-string (or (test-cockpit--current-function-string) (test-cockpit--last-function-string)))
@@ -574,6 +647,12 @@ repetition."
574647
,(if last-cmd
575648
`("r" "repeat" test-cockpit--repeat-interactive-test))))))))
576649

650+
(defun test-cockpit--main-suffix ()
651+
"Setup the main menu common for all projects for testing and actions."
652+
(if-let ((custom-actions-suffix (test-cockpit--custom-actions)))
653+
`[,(test-cockpit--test-action-suffix) ,custom-actions-suffix]
654+
(test-cockpit--test-action-suffix)))
655+
577656
(defun test-cockpit--strip-project-root (path)
578657
"Strip the project root path from a given PATH."
579658
(string-remove-prefix (file-name-as-directory (projectile-project-root)) path))

test/test-cockpit.el-test.el

+123
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@
2424

2525
(defun tc--register-foo-project (test-string)
2626
(setq test-cockpit--project-engines nil)
27+
(setq test-cockpit--project-type-custom-actions
28+
(assoc-delete-all 'foo-project-type test-cockpit--project-type-custom-actions))
2729
(test-cockpit-register-project-type 'foo-project-type 'test-cockpit--foo-engine)
2830
(mocker-let ((projectile-project-type () ((:output 'foo-project-type :min-occur 0)))
2931
(projectile-project-root (&optional _dir) ((:input-matcher (lambda (_) t) :output "foo-project" :min-occur 0))))
@@ -413,6 +415,72 @@
413415
(test-cockpit-repeat-test)))
414416

415417

418+
(ert-deftest test-custom-action-simple ()
419+
(mocker-let ((projectile-project-root (&optional _dir) ((:input-matcher (lambda (_) t) :output "/path/to/project")))
420+
(buffer-file-name () ((:output "/path/to/project/some/file.el")))
421+
(compile (command) ((:input '("custom test command") :output 'success))))
422+
(test-cockpit-dynamic-custom-test-command "custom test command")))
423+
424+
425+
(ert-deftest test-custom-action-repeat ()
426+
(tc--register-foo-project "foo")
427+
(mocker-let (;(projectile-project-type () ((:output 'foo-project-type)))
428+
(projectile-project-root (&optional _dir) ((:input-matcher (lambda (_) t) :output "foo-project")))
429+
(buffer-file-name () ((:output "/path/to/project/some/file.el")))
430+
(compile (command) ((:input '("other custom test action") :output 'success :occur 2))))
431+
(test-cockpit-dynamic-custom-test-command "other custom test action")
432+
(test-cockpit-repeat-test)))
433+
434+
435+
(ert-deftest test-custom-action-replace-project-root ()
436+
(tc--register-foo-project "foo")
437+
(mocker-let ((projectile-project-type () ((:output 'foo-project-type)))
438+
(projectile-project-root (&optional _dir) ((:input-matcher (lambda (_) t) :output "/path/to/project")))
439+
(buffer-file-name () ((:output "/path/to/project/some/file.el")))
440+
(compile (command) ((:input '("command /path/to/project") :output 'success :occur 2))))
441+
(test-cockpit-dynamic-custom-test-command "command %P")
442+
(test-cockpit-repeat-test)))
443+
444+
(ert-deftest test-custom-action-no-replace-project-root ()
445+
(tc--register-foo-project "foo")
446+
(mocker-let ((projectile-project-type () ((:output 'foo-project-type)))
447+
(projectile-project-root (&optional _dir) ((:input-matcher (lambda (_) t) :output "/path/to/project")))
448+
(buffer-file-name () ((:output "/path/to/project/some/file.el")))
449+
(compile (command) ((:input '("command %Project") :output 'success :occur 2))))
450+
(test-cockpit-dynamic-custom-test-command "command %%Project")
451+
(test-cockpit-repeat-test)))
452+
453+
454+
(ert-deftest test-custom-action-replace-absolute-file ()
455+
(tc--register-foo-project "foo")
456+
(mocker-let ((projectile-project-type () ((:output 'foo-project-type)))
457+
(projectile-project-root (&optional _dir) ((:input-matcher (lambda (_) t) :output "/path/to/project/")))
458+
(buffer-file-name () ((:output "/path/to/project/some/file.el")))
459+
(compile (command) ((:input '("command /path/to/project/some/file.el") :output 'success :occur 2))))
460+
(test-cockpit-dynamic-custom-test-command "command %F")
461+
(test-cockpit-repeat-test)))
462+
463+
464+
(ert-deftest test-custom-action-no-replace-absolute-file ()
465+
(tc--register-foo-project "foo")
466+
(mocker-let ((projectile-project-type () ((:output 'foo-project-type)))
467+
(projectile-project-root (&optional _dir) ((:input-matcher (lambda (_) t) :output "/path/to/project/")))
468+
(buffer-file-name () ((:output "/path/to/project/some/file.el")))
469+
(compile (command) ((:input '("command %Foo") :output 'success :occur 2))))
470+
(test-cockpit-dynamic-custom-test-command "command %%Foo")
471+
(test-cockpit-repeat-test)))
472+
473+
474+
(ert-deftest test-custom-action-replace-relative-file ()
475+
(tc--register-foo-project "foo")
476+
(mocker-let ((projectile-project-type () ((:output 'foo-project-type)))
477+
(projectile-project-root (&optional _dir) ((:input-matcher (lambda (_) t) :output "/path/to/project/")))
478+
(buffer-file-name () ((:output "/path/to/project/some/file.el")))
479+
(compile (command) ((:input '("command some/file.el") :output 'success :occur 2))))
480+
(test-cockpit-dynamic-custom-test-command "command %f")
481+
(test-cockpit-repeat-test)))
482+
483+
416484
(ert-deftest test-main-suffix--all-nil ()
417485
(tc--register-foo-project "foo")
418486
(mocker-let ((projectile-project-type () ((:output 'foo-project-type)))
@@ -426,6 +494,60 @@
426494
("c" "custom" test-cockpit-custom-test-command)]))))
427495

428496

497+
(ert-deftest test-main-suffix--one-custom-actions-added ()
498+
(tc--register-foo-project "foo")
499+
(test-cockpit-add-custom-action
500+
'foo-project-type "C" "some custom action" "some_custom_action --foo")
501+
(mocker-let ((projectile-project-type () ((:output 'foo-project-type)))
502+
(test-cockpit--current-module-string () ((:output nil)))
503+
(test-cockpit--current-function-string () ((:output nil)))
504+
(test-cockpit--last-module-string () ((:output nil)))
505+
(test-cockpit--last-function-string () ((:output nil))))
506+
(should (equal (test-cockpit--main-suffix)
507+
[["Run tests"
508+
("p" "project" test-cockpit-test-project)
509+
("c" "custom" test-cockpit-custom-test-command)]
510+
["Custom actions"
511+
("C" "some custom action" (lambda () (interactive) (test-cockpit--run-test "some_custom_action --foo")))]]))))
512+
513+
514+
(ert-deftest test-main-suffix--two-custom-actions-added ()
515+
(tc--register-foo-project "foo")
516+
(test-cockpit-add-custom-action
517+
'foo-project-type "C" "some strange action" "some_strange_action --foo")
518+
(test-cockpit-add-custom-action
519+
'foo-project-type "O" "another custom action" #'some-action-function)
520+
(mocker-let ((projectile-project-type () ((:output 'foo-project-type)))
521+
(test-cockpit--current-module-string () ((:output nil)))
522+
(test-cockpit--current-function-string () ((:output nil)))
523+
(test-cockpit--last-module-string () ((:output nil)))
524+
(test-cockpit--last-function-string () ((:output nil))))
525+
(should (equal (test-cockpit--main-suffix)
526+
[["Run tests"
527+
("p" "project" test-cockpit-test-project)
528+
("c" "custom" test-cockpit-custom-test-command)]
529+
["Custom actions"
530+
("C" "some strange action" (lambda () (interactive) (test-cockpit--run-test "some_strange_action --foo")))
531+
("O" "another custom action" some-action-function)]]))))
532+
533+
534+
(ert-deftest test-main-suffix--dynamic-custom-actions-added ()
535+
(tc--register-foo-project "foo")
536+
(test-cockpit-add-dynamic-custom-action
537+
'foo-project-type "C" "some custom action" "some_custom_action %f --foo")
538+
(mocker-let ((projectile-project-type () ((:output 'foo-project-type)))
539+
(test-cockpit--current-module-string () ((:output nil)))
540+
(test-cockpit--current-function-string () ((:output nil)))
541+
(test-cockpit--last-module-string () ((:output nil)))
542+
(test-cockpit--last-function-string () ((:output nil))))
543+
(should (equal (test-cockpit--main-suffix)
544+
[["Run tests"
545+
("p" "project" test-cockpit-test-project)
546+
("c" "custom" test-cockpit-custom-test-command)]
547+
["Custom actions"
548+
("C" "some custom action" (lambda () (interactive) (test-cockpit-dynamic-custom-test-command "some_custom_action %f --foo")))]]))))
549+
550+
429551
(ert-deftest test-main-suffix--current-module ()
430552
(tc--register-foo-project "foo")
431553
(mocker-let ((projectile-project-root (&optional _dir) ((:input-matcher (lambda (_) t) :output "foo-project")))
@@ -739,6 +861,7 @@
739861
(should (equal default-directory "foo-project")))))
740862
(test-cockpit-custom-test-command))))
741863

864+
742865
(ert-deftest test-set-infix ()
743866
(tc--register-foo-project "foo")
744867
(mocker-let ((projectile-project-type () ((:output 'foo-project-type))))

0 commit comments

Comments
 (0)