@@ -151,6 +151,18 @@ loaded. You can toggle the `use-package' integration later using
151151 " Non-nil means to validate patches when byte-compiling."
152152 :type 'boolean )
153153
154+ (defcustom el-patch-use-advice nil
155+ " Non-nil causes el-patch to use Emacs' advice system for patching functions.
156+ This can be set globally or bound dynamically around a patch.
157+
158+ An advice is used if the patched function has the same name and
159+ the same number of arguments as the original.
160+
161+ An advice takes precedence over subsequent non-advice patches.
162+ You may need to un-advice or un-patch a function to apply a new
163+ patch."
164+ :type 'boolean )
165+
154166; ;;; Internal variables
155167
156168(defvar el-patch-variant nil
@@ -222,6 +234,13 @@ This function lives halfway between `copy-sequence' and
222234 (cons (car tree) (el-patch--copy-semitree (cdr tree)))
223235 tree))
224236
237+ (defun el-patch--advice-name (name variant-name )
238+ " Return advice name for a given NAME and VARIANT-NAME."
239+ (intern
240+ (format " %S @%s @el-patch--advice"
241+ name
242+ (if variant-name (format " %S " el-patch-variant) " " ))))
243+
225244(defun el-patch--resolve (form new &optional table )
226245 " Resolve a patch FORM.
227246Return a list of forms to be spliced into the surrounding
@@ -536,38 +555,82 @@ PATCH-DEFINITION is an unquoted list starting with `defun',
536555 (let ((definition (el-patch--resolve-definition patch-definition t )))
537556 ; ; Then we parse out the definition type and symbol name.
538557 (cl-destructuring-bind (type name . body) definition
539- (let ((register-patch
540- `(let ((table (or (bound-and-true-p el-patch--patches)
541- (make-hash-table :test #'eq ))))
542- (setq el-patch--patches table)
543- (setq table
544- (puthash ', name
545- (gethash
546- ', name table
547- (make-hash-table :test #'eq ))
548- table))
549- (setq table
550- (puthash ', type
551- (gethash
552- ', type table
553- (make-hash-table :test #'eq ))
554- table))
555- (puthash el-patch-variant ', patch-definition table))))
558+ (let* ((advise (and el-patch-use-advice
559+ ; ; Only advice functions
560+ (let* ((props (alist-get type
561+ el-patch-deftype-alist))
562+ (classifier (plist-get props :classify )))
563+ (and classifier
564+ (equal
565+ (caar (funcall classifier definition))
566+ 'function )))
567+ ; ; Patches must have the same name and
568+ ; ; same number of arguments
569+ (let ((orig-def (el-patch--resolve-definition
570+ (cl-subseq patch-definition 0 3 )
571+ nil )))
572+ ; ; Same name and same argument count
573+ (and (equal name (nth 1 orig-def))
574+ (equal (length (nth 2 definition))
575+ (length (nth 2 orig-def)))))
576+ 'advice ))
577+ (register-patch
578+ `(let ((table (or (bound-and-true-p el-patch--patches)
579+ (make-hash-table :test #'eq ))))
580+ (setq el-patch--patches table)
581+ (setq table
582+ (puthash ', name
583+ (gethash
584+ ', name table
585+ (make-hash-table :test #'eq ))
586+ table))
587+ (setq table
588+ (puthash ', type
589+ (gethash
590+ ', type table
591+ (make-hash-table :test #'equal ))
592+ table))
593+ (puthash (cons ,(when advise `(quote , advise ))
594+ el-patch-variant)
595+ ', patch-definition table))))
556596 ; ; If we need to validate the patch, then we also need to
557597 ; ; register it at compile-time, not just at runtime.
558598 (when (and el-patch-validate-during-compile byte-compile-current-file)
559599 (eval register-patch t )
560- (el-patch-validate name type 'nomsg nil el-patch-variant))
600+ (el-patch-validate name type 'nomsg nil
601+ (cons advise el-patch-variant)))
602+ ; ; Check that `el-patch-variant' is not a cons or a string
603+ (when (or (consp el-patch-variant)
604+ (stringp el-patch-variant))
605+ (error " `el-patch-variant' cannot be a string or a cons" ))
561606 `(progn
562607 ; ; Register the patch in our hash. We want to do this right
563608 ; ; away so that if there is an error then at least the user
564609 ; ; can undo the patch (as long as it is not too terribly
565610 ; ; wrong).
566611 , register-patch
567612 ; ; Now we actually overwrite the current definition.
568- (el-patch--stealthy-eval
569- , definition
570- " This function was patched by `el-patch' ." ))))))
613+ ,(if advise
614+ ; ; Use advice system
615+ (let ((advice-name (el-patch--advice-name name
616+ el-patch-variant)))
617+ `(progn
618+ (el-patch--stealthy-eval
619+ ,(append
620+ (list (car definition) ; ; Same type
621+ advice-name) ; ; Different name
622+ ; ; Rest is the same
623+ (cddr definition))
624+ ,(format
625+ ; ; The new line before the name is to avoid
626+ ; ; long doc strings
627+ " This advice was defined by `el-patch' for\n `%S' ."
628+ name))
629+ (advice-add (quote , name )
630+ :override (quote , advice-name ))))
631+ `(el-patch--stealthy-eval
632+ , definition
633+ " This definition was patched by `el-patch' ." )))))))
571634
572635; ;;;; Removing patches
573636
@@ -579,10 +642,16 @@ patched. NAME, TYPE, and VARIANT are as returned by
579642`el-patch-get' ."
580643 (interactive (el-patch--select-patch))
581644 (if-let ((patch-definition (el-patch-get name type variant)))
582- (eval `(el-patch--stealthy-eval
583- ,(el-patch--resolve-definition
584- patch-definition nil )
585- " This function was patched and then unpatched by `el-patch' ." ))
645+ (if (car variant)
646+ ; ; an advice, remove it
647+ (advice-remove name
648+ (el-patch--advice-name name (cdr variant)))
649+ ; ; Otherwise just re-evaluate original definition
650+ (eval
651+ `(el-patch--stealthy-eval
652+ ,(el-patch--resolve-definition
653+ patch-definition nil )
654+ " This function was patched and then unpatched by `el-patch' ." )))
586655 (error " There is no patch for %S %S " type name)))
587656
588657; ;;; Defining patch types
@@ -933,9 +1002,9 @@ See `el-patch-validate'."
9331002 (let ((type-hash (gethash type patch-hash)))
9341003 (dolist (variant (hash-table-keys type-hash))
9351004 (setq patch-count (1+ patch-count))
936- (let (( el-patch-variant variant))
937- ( unless (el-patch-validate name type 'nomsg )
938- (setq warning-count (1+ warning-count) ))))))))
1005+ (unless ( el-patch-validate name type 'nomsg nil
1006+ variant )
1007+ (setq warning-count (1+ warning-count))))))))
9391008 (cond
9401009 ((zerop patch-count)
9411010 (user-error " No patches defined" ))
@@ -1019,17 +1088,27 @@ nil; see `el-patch-variant')."
10191088 nil
10201089 'require-match )))))
10211090 (type-hash (gethash type patch-hash))
1022- (options (mapcar #'symbol-name
1023- (hash-table-keys type-hash)))
1024- (variant (intern
1025- (pcase (length options)
1026- (0 (error " Internal `el-patch' error " ))
1027- (1 (car options))
1028- (_ (completing-read
1029- " Which variant? "
1030- options
1031- nil
1032- 'require-match ))))))
1091+ (options (hash-table-keys type-hash))
1092+ (variant (pcase (length options)
1093+ (0 (error " Internal `el-patch' error " ))
1094+ (1 (car options))
1095+ (_ (let ((completing-options
1096+ (mapcar (lambda (x )
1097+ (cons (format " %s%S "
1098+ (or (and (car x)
1099+ " Advice: " )
1100+ " " )
1101+ (cdr x))
1102+ x))
1103+ (hash-table-keys type-hash))))
1104+ (alist-get
1105+ (completing-read
1106+ " Which variant? "
1107+ completing-options
1108+ nil
1109+ 'require-match )
1110+ completing-options
1111+ nil nil 'equal ))))))
10331112 (list name type variant)))
10341113
10351114(defun el-patch--ediff-forms (name1 form1 name2 form2 )
0 commit comments