skip to Main Content

I have a vim script to aid in Python code refactoring; depending on the file, it inserts multiple equal import lines, e.g. many occurrences of from Products.CMFCore.utils import getToolByName if many lines are changed to use getToolByName.

To clean up, I have several lines like the following near the end of my script:

normal gg
/^(from AccessControl import Unauthorized>)?/
.+1,$s,^from AccessControl import Unauthorizedn,,e
normal gg
/^(from Products.CMFCore.utils import getToolByName>)?/
.+1,$s,^from Products.CMFCore.utils import getToolByNamen,,e

The idea is:

  • For each possibly inserted import line,
  • go to the top of the file,
  • seek the first line which contains this import (well, starts with it, and perhaps contains more),
  • and delete all further lines (.+1,$) which match exactly the inserted import.

To avoid errors (which would hurt a lot, especially when using the script via :argdo), I made the search optional (?), since the e flag works for the :s command only. If that search doesn’t find a true match, the following :.+1,$ s command shouldn’t remove anything either, since it is more rescrictive.

(The remaining inserted line needs to be moved manually)

However, because of that ?, the substitution does not only remove the following occurences (.+1) but all – including the first one I’d like to keep.

How would I fix this?

A sed-based solution would help me as well.

Versions:
Same problem with vim v7.4 (CentOS Linux) and gvim 8.1 on Windows.

Thank you!

Edit:
Now that I have a workaround, I’m still interested in a "proper" solution which doesn’t need temporary changes, and thus makes vim consider the file unchanged if everything was fine already.

2

Answers


  1. Chosen as BEST ANSWER

    This will probably not be the best solution, but here is the workaround I found:

    Above the first normal gg line, I added code to add two lines to the end of the file:

    normal GoELEPHANT IN CAIRO
    normal yyp
    

    Then I changed my /.../ lines to unconditionally but alternatively find those additional lines, e.g.:

    normal gg
    /^(from AccessControl import Unauthorized>|ELEPHANT IN CAIRO)/
    .+1;$ s/^from AccessControl import Unauthorizedn//e
    

    Finally I remove those lines:

    g/^ELEPHANT IN CAIRO$/d
    

  2. As you’ve noticed, the problem happens because your search for from AccessControl import Unauthorized is using a ?, which actually makes it match every line and not the first line with the actual import.

    You could work around this issue by using the pattern that matches the exact line you would like to match, but using :silent! to prevent any errors from breaking your sequence.

    This would work:

    normal gg
    silent! /^from AccessControl import Unauthorized>/
    .+1,$s,^from AccessControl import Unauthorizedn,,e
    

    But a much better solution is to use a range that finds the pattern and starts on the next line at the same time. You can also use the :d command to delete a line, and :g//d to delete all lines (in the range) matching a specific regular expression. Furthermore, if you use the same pattern twice, then using simply // on your :g command will match the same pattern again.

    Putting it all together:

    :0/^from AccessControl import Unauthorized>/+1,$g//d
    

    So this starts on line 0 (before the beginning of the file, so it also matches on the very first line of the file), then finds the first occurrence of the pattern, and then starting on the line that follows, looks for all other matches of that same pattern all the way to the end of the file, and deletes the lines that match.

    Same as earlier, if you don’t want to see an error if there’s no match of the pattern (or if there’s a single match, in which case the :g// will fail), you can use :silent! to suppress it again.

    :silent! 0/^from AccessControl import Unauthorized>/+1,$g//d
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search