[Checkins] SVN: z3c.recipe.usercrontab/trunk/ - Removed essentially-unused complete environment variable handling.

Reinout van Rees reinout at vanrees.org
Tue Jun 16 08:57:40 EDT 2009


Log message for revision 101076:
  - Removed essentially-unused complete environment variable handling.
  - Adding our entries with descriptive comments now: it includes the buildout
    file and the part name.
  

Changed:
  U   z3c.recipe.usercrontab/trunk/CHANGES.txt
  U   z3c.recipe.usercrontab/trunk/src/z3c/recipe/usercrontab/README.txt
  U   z3c.recipe.usercrontab/trunk/src/z3c/recipe/usercrontab/__init__.py
  U   z3c.recipe.usercrontab/trunk/src/z3c/recipe/usercrontab/tests.py
  U   z3c.recipe.usercrontab/trunk/src/z3c/recipe/usercrontab/usercrontab.py

-=-
Modified: z3c.recipe.usercrontab/trunk/CHANGES.txt
===================================================================
--- z3c.recipe.usercrontab/trunk/CHANGES.txt	2009-06-16 12:27:38 UTC (rev 101075)
+++ z3c.recipe.usercrontab/trunk/CHANGES.txt	2009-06-16 12:57:40 UTC (rev 101076)
@@ -4,9 +4,13 @@
 0.6 (unreleased)
 ----------------
 
-- Nothing changed yet.
+- Removed essentially-unused complete environment variable handling.
+  [reinout]
 
+- Adding our entries with descriptive comments now: it includes the buildout
+  file and the part name.  [reinout]
 
+
 0.5.1 (2009-06-16)
 ------------------
 

Modified: z3c.recipe.usercrontab/trunk/src/z3c/recipe/usercrontab/README.txt
===================================================================
--- z3c.recipe.usercrontab/trunk/src/z3c/recipe/usercrontab/README.txt	2009-06-16 12:27:38 UTC (rev 101075)
+++ z3c.recipe.usercrontab/trunk/src/z3c/recipe/usercrontab/README.txt	2009-06-16 12:57:40 UTC (rev 101076)
@@ -14,131 +14,100 @@
 installing of cronjobs into user crontabs.
 
     >>> from z3c.recipe.usercrontab.usercrontab import UserCrontabManager
-    >>> c = UserCrontabManager()
 
 
-Entry and environment handling
-------------------------------
+Entry handling
+--------------
 
-For these tests, we fake a crontab by filling the list of cron entries
-for this object:
+A user crontab manager manages a user's crontab for one specific buildout
+part.  The part ends up in the identifier.  We'll use 'test' here.
 
-    >>> c.crontab = [ 'MAILTO=""', '@reboot echo "No-one will see this"']
-    >>> print c
-    MAILTO=""
-    @reboot echo "No-one will see this"
+    >>> c = UserCrontabManager(identifier='test')
 
-Now, we're adding a method to it using the official way:
+In these tests, we can fake a crontab by filling the list of cron entries
+manually:
 
-    >>> c.add_entry('@reboot echo "example.com gets spammed!"',
-    ...             MAILTO="example at example.com")
+    >>> c.crontab = ['@reboot echo "hello world"']
+    >>> print c # Handy shortcut
+    @reboot echo "hello world"
 
-The object also has a convenient __repr__, so we can test its output:
+Now, we're adding an entry to it using the official way.  The entry is
+surrounded by markers:
 
+    >>> c.add_entry('@reboot echo "I just got added"')
     >>> print c
-    MAILTO=""
-    @reboot echo "No-one will see this"
-    MAILTO=example at example.com
-    @reboot echo "example.com gets spammed!"
+    @reboot echo "hello world"
+    <BLANKLINE>
+    # Generated by test
+    @reboot echo "I just got added"
+    # END test
+    <BLANKLINE>
 
-Adding another entry with yet another MAILTO line is placed at the end:
+Removing entries also works.  As long as the "Generated by" markers are
+present, it doesn't matter which entry you remove: everything surrounded by
+the markers is zapped:
 
-    >>> c.add_entry('@reboot echo "example.com gets spammed twice!"',
-    ...              MAILTO="twice at example.com")
-    >>> print c
-    MAILTO=""
-    @reboot echo "No-one will see this"
-    MAILTO=example at example.com
-    @reboot echo "example.com gets spammed!"
-    MAILTO=twice at example.com
-    @reboot echo "example.com gets spammed twice!"
-
-When another entry is made with the same MAILTO, the MAILTO clause is
-not repeated again:
-
-    >>> c.add_entry('@reboot echo "twice at example.com gets spammed twice!"',
-    ...             MAILTO="twice at example.com")
-    >>> print c
-    MAILTO=""
-    @reboot echo "No-one will see this"
-    MAILTO=example at example.com
-    @reboot echo "example.com gets spammed!"
-    MAILTO=twice at example.com
-    @reboot echo "twice at example.com gets spammed twice!"
-    @reboot echo "example.com gets spammed twice!"
-
-Removing entries also works, and removes superfluous environment variables:
-
-    >>> c.del_entry('@reboot echo "example.com gets spammed!"') == 1
+    >>> c.del_entry('bla bla') == 1
     True
     >>> print c
-    MAILTO=""
-    @reboot echo "No-one will see this"
-    MAILTO=twice at example.com
-    @reboot echo "twice at example.com gets spammed twice!"
-    @reboot echo "example.com gets spammed twice!"
+    @reboot echo "hello world"
 
-Removing entries does not remove too much:
+Pre-0.6, a WARNING environment variable was used.  An entry (which content
+matters now!) is found there:
 
-    >>> c.del_entry('@reboot echo "twice at example.com gets spammed twice!"') == 1
-    True
+    >>> c.crontab = ['@reboot echo "hello world"',
+    ...              'WARNING="Everything below is added by bla bla',
+    ...              '@reboot echo "old entry 1"',
+    ...              '@reboot echo "old entry 2"']
     >>> print c
-    MAILTO=""
-    @reboot echo "No-one will see this"
-    MAILTO=twice at example.com
-    @reboot echo "example.com gets spammed twice!"
-
-Removing the last entry also removes the dangling MAILTO line:
-
-    >>> c.del_entry('@reboot echo "example.com gets spammed twice!"') == 1
-    True
+    @reboot echo "hello world"
+    WARNING="Everything below is added by bla bla
+    @reboot echo "old entry 1"
+    @reboot echo "old entry 2"
+    >>> c.del_entry('@reboot echo "old entry 1"')
+    1
     >>> print c
-    MAILTO=""
-    @reboot echo "No-one will see this"
+    @reboot echo "hello world"
+    WARNING="Everything below is added by bla bla
+    @reboot echo "old entry 2"
 
-Removing the final entry removes the remaining MAILTO line, leaving us
-with an empty list:
+Removing the last remaining entry under WARNING also removes the WARNING:
 
-    >>> c.del_entry('@reboot echo "No-one will see this"') == 1
-    True
-    >>> len(c.crontab)
-    0
-
-Adding an entry without a MAILTO environment line also doesn't put in
-an empty one:
-
-    >>> c.add_entry('@reboot echo "Someone will see this"')
+    >>> c.del_entry('@reboot echo "old entry 2"')
+    1
     >>> print c
-    @reboot echo "Someone will see this"
+    @reboot echo "hello world"
 
-Adding an entry with an empty MAILTO line adds it at the end, so the
-first entry is not disturbed:
-
-    >>> c.add_entry('@reboot echo "No-one will see this"', MAILTO="")
-    >>> print c
-    @reboot echo "Someone will see this"
-    MAILTO=""
-    @reboot echo "No-one will see this"
-
 Briefly in the 0.5 version, a 'BUILDOUT' environment variable was used for
 grouping items per buildout. Now for some up/downgrade testing.  0.5.1 removes
 the environment variable again. We'll add an entry with such a (now
 deprecated) "grouping environment variable". First the start situation:
 
     >>> c.crontab=[
+    ...     'WARNING="Everything below is added by bla bla',
     ...     'BUILDOUT=my/buildout',
     ...     '@reboot echo nothing happens']
     >>> print c # doctest: +ELLIPSIS +NORMALIZE_WHITESPACE
+    WARNING="Everything below is added by bla bla
     BUILDOUT=my/buildout
     @reboot echo nothing happens
 
-Now we add a similar entry. The BUILDOUT statement is removed:
+Doing anything (adding/removing) zaps BUILDOUT statement:
 
-    >>> c.add_entry('@reboot echo nothing happens')
-    >>> print c # doctest: +REPORT_NDIFF
+    >>> c.del_entry('nonexisting')
+    0
+    >>> print c
+    WARNING="Everything below is added by bla bla
     @reboot echo nothing happens
 
+And just to make sure, deleting that entry empties out the whole file:
 
+    >>> c.del_entry('@reboot echo nothing happens')
+    1
+    >>> print c
+    <BLANKLINE>
+
+
 Read/write crontab methods
 --------------------------
 
@@ -152,7 +121,8 @@
     >>> t.write("#dummy\n")
 
     >>> c = UserCrontabManager(readcrontab="cat %s" % crontestfile,
-    ...                        writecrontab="cat >%s" % crontestfile)
+    ...                        writecrontab="cat >%s" % crontestfile,
+    ...                        identifier='test')
     >>> c.read_crontab()
     >>> a = repr(c)
     >>> c.add_entry('# improbable entry')
@@ -189,16 +159,8 @@
     ... command = echo nothing happens
     ... readcrontab = cat %(crontest)s
     ... writecrontab = cat >%(crontest)s
-    ...
-    ... [bar]
-    ... recipe = z3c.recipe.usercrontab
-    ... times = # @reboot
-    ... command = echo nothing happens
-    ... readcrontab = cat %(crontest)s
-    ... writecrontab = cat >%(crontest)s
     ... ''' % ( { 'crontest': crontestfile } ))
 
-
     >>> import os
     >>> print system(os.path.join('bin', 'buildout'))
     Installing foo.
@@ -213,13 +175,21 @@
 
     >>> '# @reboot\techo nothing happens' in c.crontab
     True
-
-    >>> 'WARNING=The entries below were generated by buildout, do not modify' in c.crontab
-    True
-
+    >>> print c # doctest: +ELLIPSIS +NORMALIZE_WHITESPACE
+    <BLANKLINE>
+    # Generated by .../sample-buildout [foo]
+    # @reboot   echo nothing happens
+    # END .../sample-buildout [foo]
+    <BLANKLINE>
+    
 Uninstall the recipe:
 
-    >>> print system(os.path.join('bin', 'buildout')+' buildout:parts=')
+    >>> write('buildout.cfg',
+    ... '''
+    ... [buildout]
+    ... parts =
+    ... ''' % ( { 'crontest': crontestfile } ))
+    >>> print system(os.path.join('bin', 'buildout'))
     Uninstalling foo.
     Running uninstall recipe.
     <BLANKLINE>
@@ -234,44 +204,127 @@
     >>> a == b
     True
 
-Now, break it by adding the same crontab entry twice:
+A second part installs fine:
 
-    >>> print system(os.path.join('bin', 'buildout')+' "buildout:parts=foo bar"')
+    >>> write('buildout.cfg',
+    ... '''
+    ... [buildout]
+    ... parts = foo bar
+    ...
+    ... [foo]
+    ... recipe = z3c.recipe.usercrontab
+    ... times = # @reboot
+    ... command = echo nothing happens
+    ... readcrontab = cat %(crontest)s
+    ... writecrontab = cat >%(crontest)s
+    ...
+    ... [bar]
+    ... recipe = z3c.recipe.usercrontab
+    ... times = # @reboot
+    ... command = echo something happens
+    ... readcrontab = cat %(crontest)s
+    ... writecrontab = cat >%(crontest)s
+    ... ''' % ( { 'crontest': crontestfile } ))
+    >>> print system(os.path.join('bin', 'buildout'))
     Installing foo.
     Installing bar.
     <BLANKLINE>
+    >>> c.read_crontab()
+    >>> print c # doctest: +ELLIPSIS +NORMALIZE_WHITESPACE
+    <BLANKLINE>
+    # Generated by .../sample-buildout [foo]
+    # @reboot   echo nothing happens
+    # END .../sample-buildout [foo]
+    <BLANKLINE>
+    <BLANKLINE>
+    # Generated by .../sample-buildout [bar]
+    # @reboot   echo something happens
+    # END .../sample-buildout [bar]
+    <BLANKLINE>
 
-    >>> print system(os.path.join('bin', 'buildout')+' buildout:parts=') # doctest: +ELLIPSIS +NORMALIZE_WHITESPACE
+Uninstalling also works fine
+
+    >>> write('buildout.cfg',
+    ... '''
+    ... [buildout]
+    ... parts =
+    ... ''' % ( { 'crontest': crontestfile } ))
+    >>> print system(os.path.join('bin', 'buildout'))
     Uninstalling bar.
     Running uninstall recipe.
-    bar: FATAL ERROR: Found more than one matching crontab-entry during uninstall; please resolve manually.
-    Matched lines: # @reboot echo nothing happens
-    While:
-      Installing.
-      Uninstalling bar.
+    Uninstalling foo.
+    Running uninstall recipe.
     <BLANKLINE>
-    An internal error occured due to a bug in either zc.buildout or in a
-    recipe being used:
-    Traceback (most recent call last):
-    ...
-    RuntimeError: Found more than one matching crontab-entry during uninstall
-    <BLANKLINE>
 
-Manually fix it by removing the offending lines:
 
-    >>> c.read_crontab()
-    >>> c.del_entry("# @reboot\techo nothing happens")
-    2
-    >>> c.write_crontab()
+Safety valves
+-------------
 
-And now we can uninstall again (albeit with some warnings):
+If the section has been removed, nothing can be found by the uninstall.  You
+get warnings that way:
 
-    >>> print system(os.path.join('bin', 'buildout')+' buildout:parts=') # doctest:
-    Uninstalling bar.
-    Running uninstall recipe.
-    bar: WARNING: Did not find a crontab-entry during uninstall; please check manually if everything was removed correctly
+    >>> write('buildout.cfg',
+    ... '''
+    ... [buildout]
+    ... parts = foo
+    ...
+    ... [foo]
+    ... recipe = z3c.recipe.usercrontab
+    ... times = # @reboot
+    ... command = echo nothing happens
+    ... readcrontab = cat %(crontest)s
+    ... writecrontab = cat >%(crontest)s
+    ... ''' % ( { 'crontest': crontestfile } ))
+
+    >>> import os
+    >>> print system(os.path.join('bin', 'buildout'))
+    Installing foo.
+    <BLANKLINE>
+    >>> c.crontab = []
+    >>> c.write_crontab()
+    >>> write('buildout.cfg',
+    ... '''
+    ... [buildout]
+    ... parts =
+    ... ''' % ( { 'crontest': crontestfile } ))
+    >>> print system(os.path.join('bin', 'buildout'))
     Uninstalling foo.
     Running uninstall recipe.
     foo: WARNING: Did not find a crontab-entry during uninstall; please check manually if everything was removed correctly
     <BLANKLINE>
 
+Another test: pre-0.6 config simulation:
+
+    >>> write('buildout.cfg',
+    ... '''
+    ... [buildout]
+    ... parts = foo
+    ...
+    ... [foo]
+    ... recipe = z3c.recipe.usercrontab
+    ... times = # @reboot
+    ... command = echo nothing happens
+    ... readcrontab = cat %(crontest)s
+    ... writecrontab = cat >%(crontest)s
+    ... ''' % ( { 'crontest': crontestfile } ))
+
+    >>> import os
+    >>> print system(os.path.join('bin', 'buildout'))
+    Installing foo.
+    <BLANKLINE>
+    >>> c.crontab = ['WARNING="Everything below is added by bla bla"',
+    ...              'BUILDOUT=/somewhere/out/there',
+    ...              '# @reboot\techo nothing happens']
+    >>> c.write_crontab()
+    >>> write('buildout.cfg',
+    ... '''
+    ... [buildout]
+    ... parts =
+    ... ''' % ( { 'crontest': crontestfile } ))
+    >>> print system(os.path.join('bin', 'buildout'))
+    Uninstalling foo.
+    Running uninstall recipe.
+    <BLANKLINE>
+    >>> c.read_crontab()
+    >>> print c
+    <BLANKLINE>

Modified: z3c.recipe.usercrontab/trunk/src/z3c/recipe/usercrontab/__init__.py
===================================================================
--- z3c.recipe.usercrontab/trunk/src/z3c/recipe/usercrontab/__init__.py	2009-06-16 12:27:38 UTC (rev 101075)
+++ z3c.recipe.usercrontab/trunk/src/z3c/recipe/usercrontab/__init__.py	2009-06-16 12:57:40 UTC (rev 101076)
@@ -20,19 +20,17 @@
         options['entry'] = '%s\t%s' % (options['times'], options['command'])
         readcrontab = self.options.get('readcrontab', None)
         writecrontab = self.options.get('writecrontab', None)
+        self.options['identifier'] = '%s [%s]' % (
+            buildout['buildout']['directory'], name)
+        self.crontab = UserCrontabManager(
+            readcrontab, writecrontab,
+            identifier=self.options['identifier'])
 
-        self.crontab = UserCrontabManager(readcrontab, writecrontab)
-        self.env = {
-            'WARNING':
-            'The entries below were generated by buildout, do not modify',
-            #'BUILDOUT': buildout['buildout']['directory']
-            }
-
     def install(self):
         crontab = self.crontab
 
         crontab.read_crontab()
-        crontab.add_entry(self.options['entry'], **self.env)
+        crontab.add_entry(self.options['entry'])
         crontab.write_crontab()
 
         return ()
@@ -45,7 +43,10 @@
     readcrontab = options.get('readcrontab', None)
     writecrontab = options.get('writecrontab', None)
 
-    crontab = UserCrontabManager(readcrontab, writecrontab)
+    identifier = options.get('identifier', 'NO IDENTIFIER')
+    crontab = UserCrontabManager(
+        readcrontab, writecrontab,
+        identifier=identifier)
     crontab.read_crontab()
     nuked = crontab.del_entry(options['entry'])
     if nuked==0:

Modified: z3c.recipe.usercrontab/trunk/src/z3c/recipe/usercrontab/tests.py
===================================================================
--- z3c.recipe.usercrontab/trunk/src/z3c/recipe/usercrontab/tests.py	2009-06-16 12:27:38 UTC (rev 101075)
+++ z3c.recipe.usercrontab/trunk/src/z3c/recipe/usercrontab/tests.py	2009-06-16 12:57:40 UTC (rev 101076)
@@ -16,16 +16,16 @@
 
 from z3c.recipe.usercrontab import UserCrontabManager
 
-usercrontab = UserCrontabManager()
+usercrontab = UserCrontabManager(identifier='test')
 
 def setUp(test):
     zc.buildout.testing.buildoutSetUp(test)
-    usercrontab.read_crontab()
+    usercrontab.read_crontab() # Store current user's real crontab.
     zc.buildout.testing.install_develop('z3c.recipe.usercrontab', test)
 
 def tearDown(test):
     zc.buildout.testing.buildoutTearDown(test)
-    usercrontab.write_crontab()
+    usercrontab.write_crontab() # Restore current user's real crontab.
 
 def test_suite():
     return unittest.TestSuite(doctest.DocFileSuite('README.txt', setUp=setUp,

Modified: z3c.recipe.usercrontab/trunk/src/z3c/recipe/usercrontab/usercrontab.py
===================================================================
--- z3c.recipe.usercrontab/trunk/src/z3c/recipe/usercrontab/usercrontab.py	2009-06-16 12:27:38 UTC (rev 101075)
+++ z3c.recipe.usercrontab/trunk/src/z3c/recipe/usercrontab/usercrontab.py	2009-06-16 12:57:40 UTC (rev 101076)
@@ -12,6 +12,10 @@
 import re
 
 
+PREPEND = '# Generated by %s'
+APPEND = '# END %s'
+
+
 def escape_string(s):
     """
     Do a smart escape of string s, wrapping it in quotes and escaping
@@ -39,40 +43,6 @@
     return s
 
 
-def dict_pmatch(d1, d2):
-    """
-    Returns true if all keys in d1 are in d2 and all values match
-    """
-    for k, v in d1.iteritems():
-        if not (k in d2 and d2[k]==v):
-            return False
-    return True
-
-
-env_re = re.compile(
-    r'''
-    ^            # Start of line
-    (            # begin first group
-    "[^"]*"      #   something enclosed in double quotes
-    |\'[^\']*\'  #   OR something enclosed in single quotes
-    |[^\s]+      #   OR some non-space-containing string
-    )            # end first group
-
-    \s*=\s*      # "=" surrounded by spaces
-
-    (            # start second group
-    "[^"]*"      #   something enclosed in double quotes
-    |\'[^\']*\'  #   OR something enclosed in single quotes
-    |[^"\']+     #   OR something that does not have quotes
-    )?           # end of *optional* second group
-
-    [^\s]*       # trailing whitespace
-    $            # end of line
-    ''',
-    re.VERBOSE)
-
-
-
 defaultreadcrontab = "crontab -l"
 defaultwritecrontab = "crontab -"
 
@@ -85,9 +55,12 @@
     username = None
     crontab = []
 
-    def __init__(self, readcrontab=None, writecrontab=None):
+    def __init__(self, readcrontab=None, writecrontab=None, identifier=None):
         self.readcrontab = readcrontab or defaultreadcrontab
         self.writecrontab = writecrontab or defaultwritecrontab
+        assert identifier is not None
+        self.append = APPEND % identifier
+        self.prepend = PREPEND % identifier
 
     def read_crontab(self):
         self.crontab = [l.strip("\n") for l in
@@ -102,80 +75,95 @@
     def __repr__(self):
         return "\n".join(self.crontab)
 
-    def add_entry(self, entry, **env):
-        """
-        Add an entry to a crontab, if kw's are set, set environment args
-        to be like that.
-        """
-        cur_env = {}
-        new_crontab = []
-        done = False
+    def find_boundaries(self):
+        # Migration: first zap all old BUILDOUT environment variables from
+        # version 0.5.  A bit rude to do it here, but the feature was only
+        # released for a couple of hours.
+        self.crontab = [line for line in self.crontab
+                        if not line.startswith('BUILDOUT=')]
 
-        for line in self.crontab:
-            match = env_re.match(line)
-            if match:
-                # We have an environment statement ('MAILTO=something')
-                env_key = match.group(1)
-                env_value = match.group(2)
-                if env_key == 'BUILDOUT':
-                    # Feature only available in 0.5, reverted in 0.5.1: we
-                    # omit this environment variable now.
-                    continue
-                cur_env[unescape_string(env_key)] = unescape_string(env_value)
+        start = None
+        end = None
+        old_warning_marker = None
+        for line_number, line in enumerate(self.crontab):
+            if line.strip() == self.prepend:
+                if start is not None:
+                    raise RuntimeError("%s found twice in the same crontab. "
+                                       "Fix by hand." % self.prepend)
+                start = line_number
+            if line.strip() == self.append:
+                if end is not None:
+                    raise RuntimeError("%s found twice in the same crontab. "
+                                       "Fix by hand." % self.append)
+                end = line_number + 1
+                # ^^^ +1 as we want the range boundary and that is behind the
+                # element.
+            if line.startswith('WARNING='):
+                old_warning_marker = line_number
+        return start, end, old_warning_marker
 
-            new_crontab.append(line)
-            if not done and dict_pmatch(env, cur_env):
-                if line != entry:
-                    # Not already added 3 lines above...
-                    new_crontab.append(entry)
-                done = True
+    def add_entry(self, entry):
+        """Add an entry to the crontab.
 
-        if (not done):
-            for (k, v) in env.iteritems():
-                if k not in cur_env or cur_env[k] != v:
-                    if k == 'BUILDOUT':
-                        # empty line for better between-buildout visual separation.
-                        new_crontab.append('')
-                    new_crontab.append('%s=%s' % (escape_string(k),
-                                                  escape_string(v)))
-            new_crontab.append(entry)
+        Find lines enclosed by APPEND/PREPEND, zap and re-add.
 
-        self.crontab = new_crontab
-
-    def del_entry(self, line):
         """
-        Remove an entry from a crontab, dropping useless environment
-        args at the end of the crontab, and which are replaced by
-        something else before an actual crontab entry is found.
+        start, end, old_warning_marker = self.find_boundaries()
+        inject_at = -1 # By default at the end of the file.
+        if old_warning_marker:
+            # At least in front of the old warning marker.
+            inject_at = old_warning_marker
+        if start is not None and end is not None:
+            # But preferably in our existing location.
+            self.crontab[start:end] = []
+            inject_at = start
 
-        (If the same entry occurs multiple times, it is removed several
-         times)
+        to_inject = ['', self.prepend, entry, self.append, '']
+        if inject_at == -1:
+            # [-1:-1] would inject before the last item...
+            self.crontab += to_inject
+        else:
+            self.crontab[inject_at:inject_at] = to_inject
+
+    def del_entry(self, entry):
+        """Remove an entry from a crontab.
+
+        Drop now-useless WARNING environment if it is the last one in the
+        file.
+
         """
-        new_crontab = []
-        fresh_env = {}
-        nuked = True
-        dangling = True
-        num_nuked = 0
-        for l in reversed(self.crontab):
-            m = env_re.match(l)
-            if m:
-                k, v = unescape_string(m.group(1)), unescape_string(m.group(2))
-                if dangling:
-                    continue
-                if nuked is True:
-                    if k in fresh_env:
-                        continue
-                fresh_env[k] = v
-            else:
-                if l==line:
-                    nuked=True
-                    num_nuked = num_nuked + 1
-                    continue
-                else:
-                    if len(l.strip()):
-                        dangling = False
-                        nuked=False
-                        fresh_env = {}
-            new_crontab.append(l)
-        self.crontab = [l for l in reversed(new_crontab)]
-        return num_nuked
+        start, end, old_warning_marker = self.find_boundaries()
+        if start is not None and end is not None:
+            if start > 0:
+                if not self.crontab[start - 1].strip():
+                    # Also strip empty line in front.
+                    start = start - 1
+            if end < len(self.crontab):
+                if not self.crontab[end].strip():
+                    # Also strip empty line after end marker.
+                    # Note: not self.crontab[end + 1] as end is the location
+                    # AFTER the end marker to selected it with [start:end].
+                    end = end + 1
+            if end == len(self.crontab):
+                end = None # Otherwise the last line stays in place
+            self.crontab[start:end] = []
+            return 1 # Number of entries that are removed.
+
+        if old_warning_marker is not None:
+            old = len(self.crontab[old_warning_marker:])
+            self.crontab[old_warning_marker:] = [
+                line for line in self.crontab[old_warning_marker:]
+                 if line != entry]
+            new = len(self.crontab[old_warning_marker:])
+
+            # Cleanup when possible
+            remaining = [line for line in self.crontab[old_warning_marker:]
+                         if line.strip()]
+            if len(remaining) == 1:
+                # Just the WARNING marker, so remove everything.
+                self.crontab[old_warning_marker:] = []
+
+            return old - new
+
+        # Nothing removed.
+        return 0



More information about the Checkins mailing list