Vim Tips Wiki
(Move categories to tip template)
(Add Related plugins section.)
 
(4 intermediate revisions by 2 users not shown)
Line 3: Line 3:
 
|previous=1520
 
|previous=1520
 
|next=1522
 
|next=1522
|created=October 9, 2007
+
|created=2007
 
|complexity=advanced
 
|complexity=advanced
 
|author=Fritzophrenic
 
|author=Fritzophrenic
 
|version=7.0
 
|version=7.0
  +
|subpage=/200712
 
|category1=Advanced Regex
 
|category1=Advanced Regex
 
|category2=Automated Text Insertion
 
|category2=Automated Text Insertion
Line 24: Line 25:
   
 
An explanation of the command string follows:
 
An explanation of the command string follows:
*'''<tt><nowiki>g#</nowiki></tt>''' - search and replace only on a line matching copyright notice patten. '#' is used rather than the customary '/' to avoid confusion with all the '\' characters.
+
*'''<code>g#</code>''' - search and replace only on a line matching copyright notice patten. '#' is used rather than the customary '/' to avoid confusion with all the '\' characters.
:*'''<tt><nowiki>\\cCOPYRIGHT </nowiki></tt>''' - match on text COPYRIGHT followed by a space (case-insensitive)
+
:*'''<code>\\cCOPYRIGHT </code>''' - match on text COPYRIGHT followed by a space (case-insensitive)
:*'''<tt><nowiki>\\(".strftime("%Y")."\\)\\@!</nowiki></tt>''' - only match when the entire match does not consist of the current year (i.e. don't update a notice containing only the current year, like "Copyright 2007").
+
:*'''<code>\\(".strftime("%Y")."\\)\\@!</code>''' - only match when the entire match does not consist of the current year (i.e. don't update a notice containing only the current year, like "Copyright 2007").
:*'''<tt><nowiki>[0-9]\\{4\\}</nowiki></tt>''' - matches a 4-digit number for the first year in the notice
+
:*'''<code>[0-9]\\{4\\}</code>''' - matches a 4-digit number for the first year in the notice
:*'''<tt><nowiki>\\(-".strftime("%Y")."\\)\\@!</nowiki></tt>''' - don't match on up-to-date notices
+
:*'''<code>\\(-".strftime("%Y")."\\)\\@!</code>''' - don't match on up-to-date notices
*'''<tt><nowiki>#s#</nowiki></tt>''' - search and replace the outdated year
+
*'''<code>#s#</code>''' - search and replace the outdated year
:*'''<tt><nowiki>\\([0-9]\\{4\\}\\)</nowiki></tt>''' - match a 4-digit year and give it a backreference
+
:*'''<code>\\([0-9]\\{4\\}\\)</code>''' - match a 4-digit year and give it a backreference
:*'''<tt><nowiki>\\(-[0-9]\\{4\\}\\)\\?</nowiki></tt>''' - optionally match an ending year, e.g. the "-2006" in "copyright 2000-2006".
+
:*'''<code>\\(-[0-9]\\{4\\}\\)\\?</code>''' - optionally match an ending year, e.g. the "-2006" in "copyright 2000-2006".
:*'''<tt><nowiki>#\\1-".strftime("%Y")</nowiki></tt>''' - replace the found text with the first year in the copyright, a hyphen, and the current year
+
:*'''<code>#\\1-".strftime("%Y")</code>''' - replace the found text with the first year in the copyright, a hyphen, and the current year
   
 
This script will "jump" to the copyright notice whenever it gets updated. If this is not desired, you can put in a command before the g#s# to create a mark and a command after it to jump back to the mark.
 
This script will "jump" to the copyright notice whenever it gets updated. If this is not desired, you can put in a command before the g#s# to create a mark and a command after it to jump back to the mark.
Line 49: Line 50:
 
<pre>
 
<pre>
 
\ exe '%s:'.
 
\ exe '%s:'.
\ '\cCOPYRIGHT\s*\%((c)\|&amp;copy;\|&copy;\)\?\s*'.
+
\ '\cCOPYRIGHT\s*\%((c)\|&copy;\|&amp;copy;\)\?\s*'.
 
\ '\%([0-9]\{4}\(-[0-9]\{4\}\)\?,\s*\)*\zs'.
 
\ '\%([0-9]\{4}\(-[0-9]\{4\}\)\?,\s*\)*\zs'.
 
\ '\('.
 
\ '\('.
Line 62: Line 63:
 
\ '&, '.strftime("%Y").':e' |
 
\ '&, '.strftime("%Y").':e' |
 
\ exe '%s:'.
 
\ exe '%s:'.
\ '\cCOPYRIGHT\s*\%((c)\|&amp;copy;\|&copy;\)\?\s*'.
+
\ '\cCOPYRIGHT\s*\%((c)\|&copy;\|&amp;copy;\)\?\s*'.
 
\ '\%([0-9]\{4}\%(-[0-9]\{4\}\)\?,\s*\)*\zs'.
 
\ '\%([0-9]\{4}\%(-[0-9]\{4\}\)\?,\s*\)*\zs'.
 
\ '\%('.strftime("%Y").'\)\@!\([0-9]\{4\}\)'.
 
\ '\%('.strftime("%Y").'\)\@!\([0-9]\{4\}\)'.
Line 74: Line 75:
 
===First Pass (needs a comma)===
 
===First Pass (needs a comma)===
 
Summary: Replace all lines with a copyright notice, that do NOT end in the previous or current year, with a comma and the current year.
 
Summary: Replace all lines with a copyright notice, that do NOT end in the previous or current year, with a comma and the current year.
*'''<tt><nowiki>%s:</nowiki></tt>''' - start a "replace in all lines" search, using ':' rather than the customary '/' for clarity.
+
*'''<code>%s:</code>''' - start a "replace in all lines" search, using ':' rather than the customary '/' for clarity.
*'''<tt><nowiki>\cCOPYRIGHT\s*\%((c)\|&amp;copy;\|&copy;\)\?\s*</nowiki></tt>''' - find lines containing the copyright flag (copyright {optional symbol}) as in the simple method.
+
*'''<code>\cCOPYRIGHT\s*\%((c)\|&copy;\|&amp;copy;\)\?\s*</code>''' - find lines containing the copyright flag (copyright {optional symbol}) as in the simple method.
*'''<tt><nowiki>\%({below}\)*</nowiki></tt>''' - match any number of year ranges followed by commas, but DO NOT use them for backreferences.
+
*'''<code>\%({below}\)*</code>''' - match any number of year ranges followed by commas, but DO NOT use them for backreferences.
:*'''<tt><nowiki>[0-9]\{4}\(-[0-9]\{4\}\)\?,\s*</nowiki></tt>''' - match a year or year range, followed by a comma and whitespace.
+
:*'''<code>[0-9]\{4}\(-[0-9]\{4\}\)\?,\s*</code>''' - match a year or year range, followed by a comma and whitespace.
*'''<tt><nowiki>\zs</nowiki></tt>''' - Place the "start of match" at this point. This means that any matched text previous to this point (i.e. the copyright flag, and any year-ranges followed by commas) will NOT be replaced with the replacement text.
+
*'''<code>\zs</code>''' - Place the "start of match" at this point. This means that any matched text previous to this point (i.e. the copyright flag, and any year-ranges followed by commas) will NOT be replaced with the replacement text.
*'''<tt><nowiki>\({below}\&{below}\)</nowiki></tt>''' - The parentheses group two "concats" separated by the \&. All concats must match at the same place for the pattern to match. When it matches, it matches the final concat.
+
*'''<code>\({below}\&{below}\)</code>''' - The parentheses group two "concats" separated by the \&. All concats must match at the same place for the pattern to match. When it matches, it matches the final concat.
:*'''<tt><nowiki>\%('.strftime("%Y").'\)\@![0-9]\{4\}</nowiki></tt>''' - match any year that is not the current year.
+
:*'''<code>\%('.strftime("%Y").'\)\@![0-9]\{4\}</code>''' - match any year that is not the current year.
:*'''<tt><nowiki>\%(-'.strftime("%Y").'\)\@!\%(-[0-9]\{4\}\)\?</nowiki></tt>''' - optionally match any year range ending ("-{year}") to complete the first concat
+
:*'''<code>\%(-'.strftime("%Y").'\)\@!\%(-[0-9]\{4\}\)\?</code>''' - optionally match any year range ending ("-{year}") to complete the first concat
:*'''<tt><nowiki>\%([0-9]\{4\}-\)\?</nowiki></tt>''' - optionally match a year range beginning ("-{year}")
+
:*'''<code>\%([0-9]\{4\}-\)\?</code>''' - optionally match a year range beginning ("-{year}")
:*'''<tt><nowiki>\%('.(strftime("%Y")-1).'\)\@!\%([0-9]\)\{4\}</nowiki></tt>''' - match any year except for the previous year (note, current year already known NOT to match) to complete the final concat
+
:*'''<code>\%('.(strftime("%Y")-1).'\)\@!\%([0-9]\)\{4\}</code>''' - match any year except for the previous year (note, current year already known NOT to match) to complete the final concat
*'''<tt><nowiki>\ze</nowiki></tt>''' - place the end of the match at this point, so that any text following this point is NOT affected by the replacement.
+
*'''<code>\ze</code>''' - place the end of the match at this point, so that any text following this point is NOT affected by the replacement.
*'''<tt><nowiki>\%(\%([0-9]\{4\}\)\@!.\)*$</nowiki></tt>''' - match any text that does not contain a year, until the end of the line. This ensures that we captured only the very last year in the line with our final concat, above.
+
*'''<code>\%(\%([0-9]\{4\}\)\@!.\)*$</code>''' - match any text that does not contain a year, until the end of the line. This ensures that we captured only the very last year in the line with our final concat, above.
*'''<tt><nowiki>:&, '.strftime("%Y")</nowiki></tt>''' - replace with the entire match (i.e. the last concat above) followed by a comma and the current year. Recall that the match start and end were defined carefully so that only the desired text is replaced.
+
*'''<code>:&, '.strftime("%Y")</code>''' - replace with the entire match (i.e. the last concat above) followed by a comma and the current year. Recall that the match start and end were defined carefully so that only the desired text is replaced.
*'''<tt><nowiki>:e</nowiki></tt>''' - suppress error when no match is found so you don't see error messages when your copyright is up-to-date, and to allow the mapping to continue on to the second pass...
+
*'''<code>:e</code>''' - suppress error when no match is found so you don't see error messages when your copyright is up-to-date, and to allow the mapping to continue on to the second pass...
   
 
===Second Pass (can use a hyphen)===
 
===Second Pass (can use a hyphen)===
 
Summary: Replace all remaining lines with a copyright notice, that do NOT end in the current year (i.e. they end in the previous year), with a hyphen and the current year.
 
Summary: Replace all remaining lines with a copyright notice, that do NOT end in the current year (i.e. they end in the previous year), with a hyphen and the current year.
*'''<tt><nowiki>%s:</nowiki></tt>''' - start a "replace in all lines" search, using ':' rather than the customary '/' for clarity.
+
*'''<code>%s:</code>''' - start a "replace in all lines" search, using ':' rather than the customary '/' for clarity.
*'''<tt><nowiki>\cCOPYRIGHT\s*\%((c)\|&amp;copy;\|&copy;\)\?\s*</nowiki></tt>''' - find lines containing the copyright flag (copyright {optional symbol}) as in the simple method.
+
*'''<code>\cCOPYRIGHT\s*\%((c)\|&copy;\|&amp;copy;\)\?\s*</code>''' - find lines containing the copyright flag (copyright {optional symbol}) as in the simple method.
*'''<tt><nowiki>\%({below}\)*</nowiki></tt>''' - match any number of year ranges followed by commas, but DO NOT use them for backreferences.
+
*'''<code>\%({below}\)*</code>''' - match any number of year ranges followed by commas, but DO NOT use them for backreferences.
:*'''<tt><nowiki>[0-9]\{4}\(-[0-9]\{4\}\)\?,\s*</nowiki></tt>''' - match a year or year range, followed by a comma and whitespace.
+
:*'''<code>[0-9]\{4}\(-[0-9]\{4\}\)\?,\s*</code>''' - match a year or year range, followed by a comma and whitespace.
*'''<tt><nowiki>\zs</nowiki></tt>''' - Place the "start of match" at this point. This means that any matched text previous to this point (i.e. the copyright flag, and any year-ranges followed by commas) will NOT be replaced with the replacement text.
+
*'''<code>\zs</code>''' - Place the "start of match" at this point. This means that any matched text previous to this point (i.e. the copyright flag, and any year-ranges followed by commas) will NOT be replaced with the replacement text.
*'''<tt><nowiki>\%('.strftime("%Y").'\)\@!\([0-9]\{4\}\)</nowiki></tt>''' - match any year except for the current year, and place it in the first backreference (note the use of \%(\) in every previous grouping)
+
*'''<code>\%('.strftime("%Y").'\)\@!\([0-9]\{4\}\)</code>''' - match any year except for the current year, and place it in the first backreference (note the use of \%(\) in every previous grouping)
*'''<tt><nowiki>\%(-'.strftime("%Y").'\)\@!\%(-[0-9]\{4\}\)\?</nowiki></tt>''' - optionally match a year range ending that does NOT include the current year.
+
*'''<code>\%(-'.strftime("%Y").'\)\@!\%(-[0-9]\{4\}\)\?</code>''' - optionally match a year range ending that does NOT include the current year.
*'''<tt><nowiki>\ze</nowiki></tt>''' - place the end of the match at this point, so that any text following this point is NOT affected by the replacement.
+
*'''<code>\ze</code>''' - place the end of the match at this point, so that any text following this point is NOT affected by the replacement.
*'''<tt><nowiki>\%(\%([0-9]\{4\}\)\@!.\)*$</nowiki></tt>''' - match any text that does not contain a year, until the end of the line. This ensures that we captured only the very last year in the line.
+
*'''<code>\%(\%([0-9]\{4\}\)\@!.\)*$</code>''' - match any text that does not contain a year, until the end of the line. This ensures that we captured only the very last year in the line.
*'''<tt><nowiki>\1-'.strftime("%Y")</nowiki></tt>''' - replace with the first backreference (i.e. the first year in the final year range of the line), followed by a hyphen and the current year.
+
*'''<code>\1-'.strftime("%Y")</code>''' - replace with the first backreference (i.e. the first year in the final year range of the line), followed by a hyphen and the current year.
*'''<tt><nowiki>:e</nowiki></tt>''' - suppress error when no match is found so you don't see error messages whenever your copyright notice is up-to-date.
+
*'''<code>:e</code>''' - suppress error when no match is found so you don't see error messages whenever your copyright notice is up-to-date.
   
 
===References===
 
===References===
Line 119: Line 120:
 
==See also==
 
==See also==
 
*[[VimTip97|Tip 97 Insert current date or time]]
 
*[[VimTip97|Tip 97 Insert current date or time]]
  +
  +
==Related plugins==
  +
* {{script|id=680|text=ferallastchange.vim}} is an old plugin that searches for a prefix and then updates the timestamp in a certain format.
  +
* {{script|id=371|text=timstamp.vim}} takes pattern and replacement parts like this plugin (the replacement part also supports some custom tokens e.g. for hostname), and is functionally very close.
  +
* {{script|id=923|text=timestamp.vim}} is based on timstamp.vim. It allows only one pattern and feeds the replacement part to strftime(), making it less general.
  +
* {{script|id=3578|text=lastmod.vim}} is a short and simple plugin for replacing one pattern with a timestamp in the first N lines.
  +
* {{script|id=4654|text=AutoAdapt}} automatically adapts information like "last modified" timestamps and copyright notices, or applies any other substitution before each file save.
   
 
==Comments==
 
==Comments==
 
 
----
 

Latest revision as of 06:52, 12 July 2013

Tip 1521 Printable Monobook Previous Next

created 2007 · complexity advanced · author Fritzophrenic · version 7.0


Especially when editing source code, there is often a copyright notice embedded in the file. Insert one of the following in a vimrc file to automatically update this copyright notice in ALL FILES when writing them.

Simple Copyright Notices[]

" Automatically update copyright notice with current year
autocmd BufWritePre *
  \ if &modified |
  \   exe "g#\\cCOPYRIGHT \\(".strftime("%Y")."\\)\\@![0-9]\\{4\\}\\(-".strftime("%Y")."\\)\\@!#s#\\([0-9]\\{4\\}\\)\\(-[0-9]\\{4\\}\\)\\?#\\1-".strftime("%Y") |
  \ endif

This replaces yyyy or yyyy-yyyy with yyyy-<current year> on any line in the file containing an outdated "COPYRIGHT yyyy" notice (case-insensitive). It only does this if the file has been modified.

An explanation of the command string follows:

  • g# - search and replace only on a line matching copyright notice patten. '#' is used rather than the customary '/' to avoid confusion with all the '\' characters.
  • \\cCOPYRIGHT - match on text COPYRIGHT followed by a space (case-insensitive)
  • \\(".strftime("%Y")."\\)\\@! - only match when the entire match does not consist of the current year (i.e. don't update a notice containing only the current year, like "Copyright 2007").
  • [0-9]\\{4\\} - matches a 4-digit number for the first year in the notice
  • \\(-".strftime("%Y")."\\)\\@! - don't match on up-to-date notices
  • #s# - search and replace the outdated year
  • \\([0-9]\\{4\\}\\) - match a 4-digit year and give it a backreference
  • \\(-[0-9]\\{4\\}\\)\\? - optionally match an ending year, e.g. the "-2006" in "copyright 2000-2006".
  • #\\1-".strftime("%Y") - replace the found text with the first year in the copyright, a hyphen, and the current year

This script will "jump" to the copyright notice whenever it gets updated. If this is not desired, you can put in a command before the g#s# to create a mark and a command after it to jump back to the mark.

References[]

More Complex Notices[]

The above works great for simple copyrights with just a range of years, but if you need more precise/correct ones (that skip years something was not worked on, e.g. "copyright 2004, 2006-2008") it will fail miserably. Here's what to replace the guts with, instead of the g#s## command above:

          \   exe '%s:'.
          \       '\cCOPYRIGHT\s*\%((c)\|©\|&copy;\)\?\s*'.
          \         '\%([0-9]\{4}\(-[0-9]\{4\}\)\?,\s*\)*\zs'.
          \         '\('.
          \           '\%('.strftime("%Y").'\)\@![0-9]\{4\}'.
          \           '\%(-'.strftime("%Y").'\)\@!\%(-[0-9]\{4\}\)\?'.
          \         '\&'.
          \           '\%([0-9]\{4\}-\)\?'.
          \           '\%('.(strftime("%Y")-1).'\)\@!'.
          \           '\%([0-9]\)\{4\}'.
          \         '\)'.
          \         '\ze\%(\%([0-9]\{4\}\)\@!.\)*$:'.
          \       '&, '.strftime("%Y").':e' |
          \   exe '%s:'.
          \       '\cCOPYRIGHT\s*\%((c)\|©\|&copy;\)\?\s*'.
          \         '\%([0-9]\{4}\%(-[0-9]\{4\}\)\?,\s*\)*\zs'.
          \           '\%('.strftime("%Y").'\)\@!\([0-9]\{4\}\)'.
          \           '\%(-'.strftime("%Y").'\)\@!\%(-[0-9]\{4\}\)\?'.
          \         '\ze\%(\%([0-9]\{4\}\)\@!.\)*$:'.
          \       '\1-'.strftime("%Y").':e' |

This makes two passes, one to update years that it needs a comma for, the next to update years it can use a hyphen for.

First Pass (needs a comma)[]

Summary: Replace all lines with a copyright notice, that do NOT end in the previous or current year, with a comma and the current year.

  • %s: - start a "replace in all lines" search, using ':' rather than the customary '/' for clarity.
  • \cCOPYRIGHT\s*\%((c)\|©\|&copy;\)\?\s* - find lines containing the copyright flag (copyright {optional symbol}) as in the simple method.
  • \%({below}\)* - match any number of year ranges followed by commas, but DO NOT use them for backreferences.
  • [0-9]\{4}\(-[0-9]\{4\}\)\?,\s* - match a year or year range, followed by a comma and whitespace.
  • \zs - Place the "start of match" at this point. This means that any matched text previous to this point (i.e. the copyright flag, and any year-ranges followed by commas) will NOT be replaced with the replacement text.
  • \({below}\&{below}\) - The parentheses group two "concats" separated by the \&. All concats must match at the same place for the pattern to match. When it matches, it matches the final concat.
  • \%('.strftime("%Y").'\)\@![0-9]\{4\} - match any year that is not the current year.
  • \%(-'.strftime("%Y").'\)\@!\%(-[0-9]\{4\}\)\? - optionally match any year range ending ("-{year}") to complete the first concat
  • \%([0-9]\{4\}-\)\? - optionally match a year range beginning ("-{year}")
  • \%('.(strftime("%Y")-1).'\)\@!\%([0-9]\)\{4\} - match any year except for the previous year (note, current year already known NOT to match) to complete the final concat
  • \ze - place the end of the match at this point, so that any text following this point is NOT affected by the replacement.
  • \%(\%([0-9]\{4\}\)\@!.\)*$ - match any text that does not contain a year, until the end of the line. This ensures that we captured only the very last year in the line with our final concat, above.
  • :&, '.strftime("%Y") - replace with the entire match (i.e. the last concat above) followed by a comma and the current year. Recall that the match start and end were defined carefully so that only the desired text is replaced.
  • :e - suppress error when no match is found so you don't see error messages when your copyright is up-to-date, and to allow the mapping to continue on to the second pass...

Second Pass (can use a hyphen)[]

Summary: Replace all remaining lines with a copyright notice, that do NOT end in the current year (i.e. they end in the previous year), with a hyphen and the current year.

  • %s: - start a "replace in all lines" search, using ':' rather than the customary '/' for clarity.
  • \cCOPYRIGHT\s*\%((c)\|©\|&copy;\)\?\s* - find lines containing the copyright flag (copyright {optional symbol}) as in the simple method.
  • \%({below}\)* - match any number of year ranges followed by commas, but DO NOT use them for backreferences.
  • [0-9]\{4}\(-[0-9]\{4\}\)\?,\s* - match a year or year range, followed by a comma and whitespace.
  • \zs - Place the "start of match" at this point. This means that any matched text previous to this point (i.e. the copyright flag, and any year-ranges followed by commas) will NOT be replaced with the replacement text.
  • \%('.strftime("%Y").'\)\@!\([0-9]\{4\}\) - match any year except for the current year, and place it in the first backreference (note the use of \%(\) in every previous grouping)
  • \%(-'.strftime("%Y").'\)\@!\%(-[0-9]\{4\}\)\? - optionally match a year range ending that does NOT include the current year.
  • \ze - place the end of the match at this point, so that any text following this point is NOT affected by the replacement.
  • \%(\%([0-9]\{4\}\)\@!.\)*$ - match any text that does not contain a year, until the end of the line. This ensures that we captured only the very last year in the line.
  • \1-'.strftime("%Y") - replace with the first backreference (i.e. the first year in the final year range of the line), followed by a hyphen and the current year.
  • :e - suppress error when no match is found so you don't see error messages whenever your copyright notice is up-to-date.

References[]

Take-Away Lessons[]

  • Use the 'g' command or \zs and \ze atoms to carefully limit your 's' commands to certain lines, and to simplify your replacement text for 's' commands.
  • Use \%(\) for grouping whenever you don't need the group for a back-reference to simplify things and provide some speedup.
  • Zero-width matches such as \@! are very powerful - familiarize yourself with them.
  • Complicated regular expressions can be made far less complex by splitting them into sections and working on each section independently.
  • Familiarize yourself with the definition of a pattern (:help /pattern) and its constituent components (like branches - :help /branch). Using these components fully can simplify your patterns or even make possible things that are not possible otherwise.

See also[]

Related plugins[]

  • ferallastchange.vim is an old plugin that searches for a prefix and then updates the timestamp in a certain format.
  • timstamp.vim takes pattern and replacement parts like this plugin (the replacement part also supports some custom tokens e.g. for hostname), and is functionally very close.
  • timestamp.vim is based on timstamp.vim. It allows only one pattern and feeds the replacement part to strftime(), making it less general.
  • lastmod.vim is a short and simple plugin for replacing one pattern with a timestamp in the first N lines.
  • AutoAdapt automatically adapts information like "last modified" timestamps and copyright notices, or applies any other substitution before each file save.

Comments[]