1

I need to prepare org files like this

* firsth things

...

* compress files

#+begin_src shell
tar cfzv {migration-date}/{client-id}from{start-date}to{finish-date}.tar.gz 1/{soc-id}/{client-id}/
#+end_src

* more things

 ...

And i created this elisp function in porder to replace this strings "{strings}"

(defun fullfill-client-template (start end)
  "Adapt migration template in REGION START END to client."
  (interactive "r")
  (let ((tenant-id (read-from-minibuffer "tenant id: "))
        (soc-id (read-from-minibuffer "soc id: "))
        (client-id (read-from-minibuffer "client id: "))
        (migration-date (read-from-minibuffer "migration date: "))
        (start-date (read-from-minibuffer "start date: "))
        (finish-date (read-from-minibuffer "finish date: ")))
    (replace-string-in-region "{tenant-id}" tenant-id start end)
    (replace-string-in-region "{soc-id}" soc-id start end)
    (replace-string-in-region "{client-id}" client-id start end)
    (replace-string-in-region "{migration-date}" migration-date start end)
    (replace-string-in-region "{start-date}" start-date start end)
    (replace-string-in-region "{finish-date}" finish-date start end)
    (message (format "Migrate client: %s/%s/%s from: %s to %s at %s"
                    tenant-id soc-id client-id start-date finish-date migration-date))))

I get the following error:

Selecting only the section compress files

(error "End after end of buffer")

but when I use edebug the variables start and end are always the same:

    Result: "1"
    
    Result: "2"
    
    Result: "3"
    
    Result: "4"
    
    Result: "5"
    
    Result: "6"
    
    Result: "1"
    
    Result: 23 (#o27, #x17, ?\C-w)
    
    Result: 178 (#o262, #xb2, ?²)
    
    Result: 1 (#o1, #x1, ?\C-a)
    
    Result: "2"
    
    Result: 23 (#o27, #x17, ?\C-w)
    
    Result: 178 (#o262, #xb2, ?²)
    
    Result: 1 (#o1, #x1, ?\C-a)
    
    Result: "3"
    
    Result: 23 (#o27, #x17, ?\C-w)
    
    Result: 178 (#o262, #xb2, ?²)
    
    Result: 2 (#o2, #x2, ?\C-b)
    
    Result: "4"
    
    Result: 23 (#o27, #x17, ?\C-w)
    
    Result: 178 (#o262, #xb2, ?²)
    
    error: "End after end of buffer"
    
    file-truename: End after end of buffer

6
  • 1
    I get no such error: the template variables are replaces by whatever reply I enter at each prompt.
    – NickD
    Oct 13, 2022 at 10:19
  • I fixed the source block to say shell, not elisp, but assuming you are not actually trying to execute the source block, that should not make any difference: running the function on the region should just replace the strings - and IME it does.
    – NickD
    Oct 13, 2022 at 10:23
  • Thanks for the correction, that clarifies the sample, I'm using doom emacs, I will try vith vanilla emacs
    – anquegi
    Oct 13, 2022 at 12:49
  • I tried with vanilla emacs and get same error: replace-string-in-region: End after end of buffer
    – anquegi
    Oct 13, 2022 at 12:56
  • Does your marked region include the end of the buffer before calling fullfill-client-template? Oct 13, 2022 at 13:30

1 Answer 1

1

I think I see the problem. As you are making substitutions, the end of the region (178) does not correspond to a fixed place in the buffer, it moves about depending on the length of the replacement. As long as the buffer end is at point 178 or greater, the replacement succeeds. At some point, the buffer end becomes less than 178 and the replacement barfs.

A workaround is to add a bunch of text at the end of the buffer, so that never happens (which is why I didn't see it in my first attempt).

[This solution is wrong (although it would "work" in the case above, but it is not general enough) - see the EDIT for a correct solution, as suggested in the comment] A better solution is to recalculate the effective end of the region for every replacement to be the lesser of the two values end and the position at the end of the buffer. If the end of the buffer ever becomes less than end, we will only attempt to do the replacement within the restricted region:

  (defun fullfill-client-template (start end)
    "Adapt migration template in REGION START END to client."
    (interactive "r")
    (let ((tenant-id (read-from-minibuffer "tenant id: "))
          (soc-id (read-from-minibuffer "soc id: "))
          (client-id (read-from-minibuffer "client id: "))
          (migration-date (read-from-minibuffer "migration date: "))
          (start-date (read-from-minibuffer "start date: "))
          (finish-date (read-from-minibuffer "finish date: ")))
      (replace-string-in-region "{tenant-id}" tenant-id start (min end (buffer-end 1)))
      (replace-string-in-region "{soc-id}" soc-id start (min end (buffer-end 1)))
      (replace-string-in-region "{client-id}" client-id start (min end (buffer-end 1)))
      (replace-string-in-region "{migration-date}" migration-date start (min end (buffer-end 1)))
      (replace-string-in-region "{start-date}" start-date start (min end (buffer-end 1)))
      (replace-string-in-region "{finish-date}" finish-date start (min end (buffer-end 1)))

      (message (format "Migrate client: %s/%s/%s from: %s to %s at %s"
                      tenant-id soc-id client-id start-date finish-date migration-date))))

An even better way perhaps is to use a marker instead of an absolute position, but I have not gone down that path yet.

EDIT: As a comment points out, the above solution is incorrect. A marker solution could probably be made to work, but as the comment also points out, there is a better solution:

  (defun fullfill-client-template (start end)
    "Adapt migration template in REGION START END to client."
    (interactive "r")
    (let ((tenant-id (read-from-minibuffer "tenant id: "))
          (soc-id (read-from-minibuffer "soc id: "))
          (client-id (read-from-minibuffer "client id: "))
          (migration-date (read-from-minibuffer "migration date: "))
          (start-date (read-from-minibuffer "start date: "))
          (finish-date (read-from-minibuffer "finish date: ")))
      (save-restriction
        (narrow-to-region start end)
        (replace-string-in-region "{tenant-id}" tenant-id (point-min) (point-max))
        (replace-string-in-region "{soc-id}" soc-id (point-min) (point-max))
        (replace-string-in-region "{client-id}" client-id (point-min) (point-max))
        (replace-string-in-region "{migration-date}" migration-date (point-min) (point-max))
        (replace-string-in-region "{start-date}" start-date (point-min) (point-max))
        (replace-string-in-region "{finish-date}" finish-date (point-min) (point-max)))

      (message (format "Migrate client: %s/%s/%s from: %s to %s at %s"
                      tenant-id soc-id client-id start-date finish-date migration-date))))

Thanks to @Gilles 'SO- stop being evil' for the solution.

2
  • 1
    Correct diagnosis but incorrect solution: the end position changes with each replacement, (min end (buffer-end 1)) may be too much or too little at any point depending on the lengths of the strings involved. Rather than use markers, it's easier to use (narrow-to-region start end) at the beginning and then do all the replacements from (point-min) to (point-max), with a save-restriction around the whole thing. Oct 13, 2022 at 15:50
  • Thanks NickD and Gilles'SO-stopbeingevil' this really works, I also was trying the solution suggested by Gilles, Thanks for the analisis and for the well explained and working solution
    – anquegi
    Oct 13, 2022 at 16:25

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.