Hello community,

here is the log from the commit of package crudini for openSUSE:Factory checked 
in at 2014-10-11 19:26:43
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/crudini (Old)
 and      /work/SRC/openSUSE:Factory/.crudini.new (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "crudini"

Changes:
--------
--- /work/SRC/openSUSE:Factory/crudini/crudini.changes  2013-10-01 
08:09:45.000000000 +0200
+++ /work/SRC/openSUSE:Factory/.crudini.new/crudini.changes     2014-10-11 
19:28:34.000000000 +0200
@@ -1,0 +2,18 @@
+Fri Oct 10 19:21:16 UTC 2014 - dmuel...@suse.com
+
+- update to 0.4:
+  * add --format=lines to support line by line processing
+  * doc: tweak readme to mention --format=lines
+  * Declare encoding to avoid fatal error
+  * fix duplicate DEFAULT section header being output
+  * ensure edited ini file contents are always complete
+  * split out --options from synopsis
+  * send --help to stdout
+  * provide alternative --rewrite file editing option
+  * provide --output option to allow redirecting output
+  * use only the base 'crudini' name in --help
+  * ensure writes to the edited ini are never lost
+  * add a new --list option to update a list of values
+  * honor case when merging new parameters
+
+-------------------------------------------------------------------

Old:
----
  crudini-0.3.tar.gz

New:
----
  crudini-0.4.tar.gz

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Other differences:
------------------
++++++ crudini.spec ++++++
--- /var/tmp/diff_new_pack.QWuJC2/_old  2014-10-11 19:28:35.000000000 +0200
+++ /var/tmp/diff_new_pack.QWuJC2/_new  2014-10-11 19:28:35.000000000 +0200
@@ -1,7 +1,7 @@
 #
 # spec file for package crudini
 #
-# Copyright (c) 2013 SUSE LINUX Products GmbH, Nuernberg, Germany.
+# Copyright (c) 2014 SUSE LINUX Products GmbH, Nuernberg, Germany.
 #
 # All modifications and additions to the file contributed by third parties
 # remain the property of their copyright owners, unless otherwise agreed
@@ -17,7 +17,7 @@
 
 
 Name:           crudini
-Version:        0.3
+Version:        0.4
 Release:        0
 Summary:        CRUD for .ini files
 License:        GPL-2.0

++++++ crudini-0.3.tar.gz -> crudini-0.4.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/crudini-0.3/EXAMPLES new/crudini-0.4/EXAMPLES
--- old/crudini-0.3/EXAMPLES    1970-01-01 01:00:00.000000000 +0100
+++ new/crudini-0.4/EXAMPLES    2014-09-05 19:17:30.000000000 +0200
@@ -0,0 +1,35 @@
+Examples:
+
+# Add/Update a var
+  crudini --set config_file section parameter value
+
+# Update an existing var
+  crudini --set --existing config_file section parameter value
+
+# Delete a var
+  crudini --del config_file section parameter
+
+# Delete a section
+  crudini --del config_file section
+
+# output a value
+  crudini --get config_file section parameter
+
+# output a global value not in a section
+  crudini --get config_file '' parameter
+
+# output a section
+  crudini --get config_file section
+
+# output a section, parseable by shell
+  eval $(crudini --get --format=sh config_file section)
+
+# update an ini file from shell variable(s)
+  echo name="$name" | crudini --merge config_file section
+
+# merge an ini file from another ini
+  crudini --merge config_file < another.ini
+
+# compare two ini files using standard UNIX text processing
+  diff <(crudini --get --format=lines file1.ini|sort) \
+       <(crudini --get --format=lines file2.ini|sort)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/crudini-0.3/Makefile new/crudini-0.4/Makefile
--- old/crudini-0.3/Makefile    1970-01-01 01:00:00.000000000 +0100
+++ new/crudini-0.4/Makefile    2014-09-05 19:17:30.000000000 +0200
@@ -0,0 +1,12 @@
+name = crudini
+version = 0.4
+
+all:
+       help2man -n "manipulate ini files" -o crudini.1 -N ./crudini-help
+       ./crudini-help --help > README
+
+dist: all
+       mkdir ${name}-${version}
+       { git ls-files; echo crudini.1; } | xargs cp -a --parents 
--target=${name}-${version}
+       tar -czf ${name}-${version}.tar.gz ${name}-${version}
+       rm -Rf ${name}-${version}
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/crudini-0.3/README new/crudini-0.4/README
--- old/crudini-0.3/README      2013-03-08 14:48:41.000000000 +0100
+++ new/crudini-0.4/README      2014-09-05 19:17:30.000000000 +0200
@@ -1,40 +1,55 @@
-A utility for manipulating ini files
-
-
-crudini --set [--existing] config_file section [param] [value]
-        --get [--format=sh|ini] config_file [section] [param]
-        --del [--existing] config_file section [param]
-        --merge [--existing] config_file [section]
+crudini - A utility for manipulating ini files
 
+Usage: crudini --set [OPTION]...   config_file section   [param] [value]
+  or:  crudini --get [OPTION]...   config_file [section] [param]
+  or:  crudini --del [OPTION]...   config_file section   [param] [value]
+  or:  crudini --merge [OPTION]... config_file [section]
+
+Options:
+
+  --existing       For --set, --del and --merge fail if the
+                     section or param is not present
+  --format=FMT     For --get, select the output FMT.
+                     Formats are sh,ini,lines
+  --inplace        Lock and write files in place.
+                     This is not atomic but has less restrictions
+                     than the default replacement method.
+  --list           For --set and --del, update a list (set) of values
+  --list-sep=STR   Delimit list values with "STR" instead of " ,"
+  --output=FILE    Write output to FILE instead. '-' means stdout
 
 Examples:
 
 # Add/Update a var
-crudini --set config_file section parameter value
+  crudini --set config_file section parameter value
 
 # Update an existing var
-crudini --set --existing config_file section parameter value
+  crudini --set --existing config_file section parameter value
 
 # Delete a var
-crudini --del config_file section parameter
+  crudini --del config_file section parameter
 
 # Delete a section
-crudini --del config_file section
+  crudini --del config_file section
 
 # output a value
-crudini --get config_file section parameter
+  crudini --get config_file section parameter
 
 # output a global value not in a section
-crudini --get config_file '' parameter
+  crudini --get config_file '' parameter
 
 # output a section
-crudini --get config_file section
+  crudini --get config_file section
 
 # output a section, parseable by shell
-eval $(crudini --get --format=sh config_file section)
+  eval $(crudini --get --format=sh config_file section)
 
 # update an ini file from shell variable(s)
-echo name="$name" | crudini --merge config_file section
+  echo name="$name" | crudini --merge config_file section
 
 # merge an ini file from another ini
-crudini --merge config_file < another.ini
+  crudini --merge config_file < another.ini
+
+# compare two ini files using standard UNIX text processing
+  diff <(crudini --get --format=lines file1.ini|sort) \
+       <(crudini --get --format=lines file2.ini|sort)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/crudini-0.3/TODO new/crudini-0.4/TODO
--- old/crudini-0.3/TODO        2013-03-08 14:48:41.000000000 +0100
+++ new/crudini-0.4/TODO        2014-09-05 19:17:30.000000000 +0200
@@ -5,4 +5,17 @@
 
 support multiple files passed to --merge
 
-possibly support --format=sh|json with --mergea
+possibly support --format=sh|json with --merge
+
+possibly support multiple duplicate names per section
+to support MultiStrOpt in openstack config files file example.
+This could be interfaced using the --list=multiname option.
+Also have --list autodetect multiline lists as used by yum like:
+  name = val1, val2
+         val3
+I.E. split on a combo of [\n,]
+
+possibly support --lower to output normalised case for
+--get and --sort
+
+support python3
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/crudini-0.3/crudini new/crudini-0.4/crudini
--- old/crudini-0.3/crudini     2013-03-08 14:48:41.000000000 +0100
+++ new/crudini-0.4/crudini     2014-09-05 19:17:30.000000000 +0200
@@ -1,19 +1,34 @@
 #!/usr/bin/python
+# -*- coding: utf-8 -*-
 # vim:fileencoding=utf8
 #
-# Copyright (C) 2013, Pádraig Brady <p...@draigbrady.com>
+# Copyright (C) 2014, Pádraig Brady <p...@draigbrady.com>
 #
 # This program is free software; you can redistribute it and/or modify it
 # under the terms of the GPLv2, the GNU General Public License version 2, as
 # published by the Free Software Foundation. http://gnu.org/licenses/gpl.html
 
+import os
 import sys
+import errno
+import contextlib
 import ConfigParser
 import getopt
 import iniparse
 import pipes
+import shutil
 import string
+import tempfile
 from cStringIO import StringIO
+import time
+
+# The following exits cleanly on Ctrl-C,
+# while treating other exceptions as before.
+def cli_exception(type, value, tb):
+    if not issubclass(type, KeyboardInterrupt):
+        sys.__excepthook__(type, value, tb)
+if sys.stdin.isatty():
+    sys.excepthook=cli_exception
 
 try:
     iniparse.DEFAULTSECT
@@ -21,12 +36,28 @@
     iniparse.DEFAULTSECT = 'DEFAULT'
 
 def usage(exitval=0):
-    cmd = sys.argv[0]
-    sys.stderr.write(
-      cmd + " --set [--existing] config_file section [param] [value]\n" +
-      cmd + " --get [--format=sh|ini] config_file [section] [param]\n" +
-      cmd + " --del [--existing] config_file section [param]\n" +
-      cmd + " --merge [--existing] config_file [section]\n"
+    cmd = os.path.basename(sys.argv[0])
+    output = sys.stderr if exitval else sys.stdout
+    output.write(
+      "A utility for manipulating ini files\n"
+      "\n" +
+      "Usage: " + cmd + " --set [OPTION]...   config_file section   [param] 
[value]\n" +
+      "  or:  " + cmd + " --get [OPTION]...   config_file [section] [param]\n" 
+
+      "  or:  " + cmd + " --del [OPTION]...   config_file section   [param] 
[value]\n" +
+      "  or:  " + cmd + " --merge [OPTION]... config_file [section]\n" +
+      "\n"
+      "Options:\n"
+      "\n"
+      "  --existing       For --set, --del and --merge fail if the\n"
+      "                     section or param is not present\n"
+      "  --format=FMT     For --get, select the output FMT.\n"
+      "                     Formats are sh,ini,lines\n"
+      "  --inplace        Lock and write files in place.\n"
+      "                     This is not atomic but has less restrictions\n"
+      "                     than the default replacement method.\n"
+      "  --list           For --set and --del, update a list (set) of values\n"
+      "  --list-sep=STR   Delimit list values with \"STR\" instead of \" ,\"\n"
+      "  --output=FILE    Write output to FILE instead. '-' means stdout\n"
     )
     sys.exit(exitval)
 
@@ -49,8 +80,20 @@
     else:
         print section
 
-def print_name_value(name, value):
-    if fmt == 'sh':
+def print_name_value(name, value, section=None):
+    if fmt == 'lines':
+        # Both unambiguous and easily parseable by shell. Caveat is
+        # that sections and values with spaces are awkward to split in shell
+        if section:
+            line = '[ %s ]' % section
+            if name:
+                line += ' '
+        if name:
+            line += '%s' % name
+        if value:
+            line += ' = %s' % value.replace('\n','\\n')
+        print line
+    elif fmt == 'sh':
         # Note we provide validation of the output indentifiers
         # as it's dangerous to leave validation to shell.
         # consider for example doing eval on this in shell:
@@ -64,24 +107,26 @@
     else:
         print name or value
 
-mode = fmt = update = cfgfile = section = param = value = None
+mode = fmt = update = inplace = cfgfile = output = section = param = \
+value = vlist = listsep = None
 
 def parse_options():
     try:
-        long_options = ['set', 'del', 'get', 'merge', 'existing', 'format=',
-                        'help', 'version']
+        long_options = ['set', 'del', 'get', 'list', 'list-sep=', 'merge',
+                        'existing', 'format=', 'output=', 'inplace', 'help', 
'version']
         opts, args = getopt.getopt(sys.argv[1:], '', long_options)
     except getopt.GetoptError, e:
         error(str(e))
         usage(1)
 
-    global mode, fmt, update, cfgfile, section, param, value
+    global mode, fmt, update, inplace, cfgfile, output, section, param, \
+           value, vlist, listsep
 
     for o, a in opts:
         if o in ('--help',):
             usage(0)
         elif o in ('--version',):
-            print 'crudini 0.3'
+            print 'crudini 0.4'
             sys.exit(0)
         elif o in ('--set', '--del', '--get', '--merge'):
             if mode:
@@ -90,11 +135,19 @@
             mode = o
         elif o in ('--format',):
             fmt = a
-            if fmt not in ('sh','ini'):
+            if fmt not in ('sh','ini','lines'):
                 error('--format not recognized: %s' % fmt)
                 usage(1)
         elif o in ('--existing',):
             update = True
+        elif o in ('--inplace',):
+            inplace = True
+        elif o in ('--list',):
+            vlist = "set" #TODO support combos of list, sorted, ...
+        elif o in ('--list-sep',):
+            listsep = a
+        elif o in ('--output',):
+            output = a
 
     if not mode:
         error('One of --set|--del|--get|--merge must be specified')
@@ -108,15 +161,19 @@
     except IndexError:
         pass
 
+    if not output:
+        output = cfgfile
+
     if cfgfile is None:
         usage(1)
     if section is None and mode in ('--del', '--set'):
         usage(1)
-    if param is not None and mode in ('--merge'):
-        usage(1)
-    if value is not None and mode not in ('--set'):
-        error('A value should not be specified with %s' % mode)
+    if param is not None and mode in ('--merge',):
         usage(1)
+    if value is not None and mode not in ('--set',):
+        if not (mode == '--del' and vlist):
+            error('A value should not be specified with %s' % mode)
+            usage(1)
 
     if mode == '--merge' and fmt == 'sh':
         # I'm not sure how useful is is to support this.
@@ -148,44 +205,211 @@
         else:
             return self.fp.readline()
 
+    def seek(self, *args):
+        self.fp.seek(*args)
+
 stdin = ""
 
-def _parse_file(filename, add_default=False):
-    # Note we use RawConfigParser rather than SafeConfigParser
-    # to avoid unwanted variable interpolation.
-    # Note iniparse doesn't currently support allow_no_value=True.
+def has_default_section(locked_file):
+    try:
+        if locked_file is None:
+            fp = StringIO(stdin)
+        else:
+            fp = locked_file.fp
+
+        for line in fp:
+            if line.startswith('[%s]' % iniparse.DEFAULTSECT):
+                return True
+
+        return False
+
+    except IOError as e:
+        error(str(e))
+        sys.exit(1)
+    finally:
+        fp.seek(0)
+
+def delete_if_exists(path):
+    """Delete a file, but ignore file not found error.
+    """
+    try:
+        os.unlink(path)
+    except EnvironmentError as e:
+        if e.errno != errno.ENOENT:
+            print str(e)
+            raise
+
+class FileLock(object):
+    """Advisory file based locking.  This should be reasonably cross platform
+       and also work over distributed file systems."""
+    def __init__(self, exclusive=False, separated=False):
+        # In inplace mode, the process must be careful to not close this fp
+        # until finished, nor open and close another fp associated with the 
file.
+        self.fp = None
+        self.locked = False
+        self.die = delete_if_exists # reference so available at teardown
+
+        # Note we can't combine these methods to provide separated locks
+        # which are immune to stale file deadlock, as once the separated
+        # file is unlinked or renamed, you introduce a race with 3 or more 
users
+        # if there is an associated fcntl lock.
+
+        if separated:
+            import signal
+            def cleanup(signum, frame):
+                sys.exit(1)
+            if hasattr(signal, "SIGTERM"):
+                signal.signal(signal.SIGTERM, cleanup)
+
+            def lock(self):
+                while True:
+                    try:
+                        os.open(self.lockpath, os.O_EXCL | os.O_CREAT, 0)
+                    except EnvironmentError as e:
+                        if e.errno == errno.EEXIST:
+                            time.sleep(1)
+                        else:
+                            raise
+                    else:
+                        self.locked = True
+                        break
+
+            def unlock(self):
+                if self.locked: # Don't clobber other locks on ctrl-c etc.
+                    self.die (self.lockpath)
+                self.locked = False
+
+        elif os.name == 'nt':
+            import msvcrt
+            def lock(self):
+                msvcrt.locking(self.fp, msvcrt.LK_LOCK, 1)
+                self.locked = True
+
+            def unlock(self):
+                if self.locked:
+                    msvcrt.locking(self.fp, msvcrt.LK_UNLCK, 1)
+                self.locked = False
+
+        else:
+            import fcntl
+            def lock(self):
+                fcntl.lockf(self.fp, fcntl.LOCK_EX if exclusive else 
fcntl.LOCK_SH)
+                self.locked = True
+
+            def unlock(self):
+                if self.locked:
+                    fcntl.lockf(self.fp, fcntl.LOCK_UN)
+                self.locked = False
+
+        FileLock.lock = lock
+        FileLock.unlock = unlock
+
+
+class LockedFile(FileLock):
+    """Open a file with advisory locking.  This provides the Isolation
+       property of ACID, to avoid missing writes.  In addition this provides AC
+       properties of ACID if crudini is the only logic accessing the ini file.
+       This should work on most platforms and distributed file systems.
+
+       Caveats in --inplace mode:
+        - File must be writeable
+        - File should be generally non readable to avoid read lock DoS.
+       Caveats in replace mode:
+        - Possibility of stale lock files left on crash leading to deadlock.
+        - Less responsive when there is contention."""
+
+    def __init__(self, filename, operation, inplace):
+
+        self.filename = filename
+        self.operation = operation
+
+        FileLock.__init__(self, operation != "--get", not inplace)
+
+        if inplace:
+            self.lockpath = filename
+        else:
+            self.lockpath = os.path.join(os.path.dirname(filename),
+                                         '.' + os.path.basename(filename) + 
'.crudini.lck')
+
+        if inplace:
+            open_mode = 'r'
+            if operation != "--get":
+                open_mode += '+'
+
+        try:
+            if inplace:
+                self.fp = open(self.lockpath, open_mode)
+                # In general readers are protected by file_replace()
+                # but using read lock here gives AC of the ACID propserties
+                # when only accessing the file through crudini even with 
file_rewrite().
+                self.lock()
+            else:
+                self.lock()
+                self.fp = open(self.filename)
+        except EnvironmentError as e:
+            error(str(e))
+            sys.exit(1)
+
+    def __del__(self):
+        # explicit close so closed in correct order
+        # if taking lock multiple times
+        self.unlock()
+        if self.fp:
+            self.fp.close()
+
+
+locked_file = None
+
+# Note we use RawConfigParser rather than SafeConfigParser
+# to avoid unwanted variable interpolation.
+# Note iniparse doesn't currently support allow_no_value=True.
+class CrudiniConfigParser(iniparse.RawConfigParser):
+    def __init__(self, preserve_case=False):
+        iniparse.RawConfigParser.__init__(self)
+        if preserve_case:
+            self.optionxform = str
+
+def _parse_file(filename, add_default=False, preserve_case=False):
     try:
         if filename == '-':
             fp = StringIO(stdin)
         else:
-            fp = open(filename)
+            global locked_file
+            fp = locked_file.fp
         if add_default:
             fp = add_default_section(fp)
-        conf = iniparse.RawConfigParser()
+        conf = CrudiniConfigParser(preserve_case=preserve_case)
         conf.readfp(fp)
         return conf
-    except IOError as e:
+    except EnvironmentError as e:
         error(str(e))
         sys.exit(1)
+    finally:
+        fp.seek(0) # in case we need to reparse
 
 
-def parse_file(filename):
+def parse_file(filename, preserve_case=False):
     global added_default_section
     added_default_section = False
 
+    global locked_file
+    if filename != '-':
+        locked_file = LockedFile (filename, mode, inplace)
+
     try:
-        conf = _parse_file(filename)
+        conf = _parse_file(filename, preserve_case=preserve_case)
 
         if not conf.items(iniparse.DEFAULTSECT):
-            # reparse with inserted [DEFAULT] to be able to add global opts 
etc.
-            # XXX: We don't distinguish the edge case where
-            # there is just [DEFAULT] in a file with no name=values.
-            # In that case a redundant [DEFAULT] will be output.
-            conf = _parse_file(filename, add_default=True)
-            added_default_section = True
+            # Check if there is just [DEFAULT] in a file with no
+            # name=values to avoid adding a duplicate section.
+            if not has_default_section(locked_file):
+                # reparse with inserted [DEFAULT] to be able to add global 
opts etc.
+                conf = _parse_file(filename, add_default=True,
+                                   preserve_case=preserve_case)
+                added_default_section = True
 
     except ConfigParser.MissingSectionHeaderError:
-        conf = _parse_file(filename, add_default=True)
+        conf = _parse_file(filename, add_default=True, 
preserve_case=preserve_case)
         added_default_section = True
     except ConfigParser.ParsingError as e:
         error(str(e))
@@ -197,7 +421,7 @@
 
 if mode == '--merge':
     stdin = sys.stdin.read() # read all upfront so that we can reparse if 
needed
-    mconf = parse_file('-')
+    mconf = parse_file('-', preserve_case=True)
 
 madded_default_section = added_default_section
 conf = parse_file(cfgfile)
@@ -206,20 +430,61 @@
    and not madded_default_section and mconf.items(iniparse.DEFAULTSECT):
     added_default_section = madded_default_section
 
+# TODO item should be items and split also
+# especially in merge mode
+def update_list(curr_val, item, mode, sep):
+    curr_items = []
+    use_space = True
+    if curr_val:
+        if sep is None:
+            use_space = ' ' in curr_val or ',' not in curr_val
+            curr_items = [v.strip() for v in curr_val.split(",")]
+        else:
+            curr_items = curr_val.split(sep)
+
+    if mode == "--set":
+        if item not in curr_items:
+            curr_items.append(item)
+    elif mode == "--del":
+        try:
+            curr_items.remove(item)
+        except ValueError:
+            pass
+
+    if sep is None:
+        sep = ","
+        if use_space:
+            sep += " "
+
+    return sep.join(curr_items)
+
 def set_name_value(section, param, value):
+    curr_val = None
+
     if update:
         if param is None:
             _sec = section == iniparse.DEFAULTSECT or conf.has_section(section)
             if not _sec:
                 raise ConfigParser.NoSectionError(section)
         else:
-           _val = conf.get(section, param)
+            curr_val = conf.get(section, param)
     elif section != iniparse.DEFAULTSECT and not conf.has_section(section):
-        conf.add_section(section)
+        if mode == "--del":
+            raise ConfigParser.NoSectionError(section)
+        else:
+            conf.add_section(section)
 
     if param is not None:
+        if not update:
+            try:
+                curr_val = conf.get(section, param)
+            except ConfigParser.NoOptionError:
+                if mode == "--del":
+                    return
         if value is None:
             value = ''
+        if vlist:
+            value = update_list(curr_val, value, mode, listsep)
         conf.set(section, param, value)
 
 try:
@@ -240,7 +505,7 @@
                     ignore_errs = (ConfigParser.NoOptionError,)
                     if section is not None:
                         msection = section
-                    else:
+                    elif not update:
                         ignore_errs += (ConfigParser.NoSectionError,)
                     try:
                         set_param = True
@@ -261,7 +526,9 @@
         elif value is None:
             if not conf.remove_option(section, param) and update:
                 raise ConfigParser.NoOptionError(section, param)
-    elif mode == '--get':
+        else: # remove item from list
+            set_name_value(section, param, value)
+    elif mode == '--get' and fmt != 'lines':
         if section is None:
             if conf.defaults():
                 print_section_header(iniparse.DEFAULTSECT)
@@ -290,25 +557,125 @@
             else:
                 name = None
             print_name_value(name, val)
-except ConfigParser.NoSectionError:
-    error('Section not found: %s' % section)
+    elif mode == '--get' and fmt == 'lines':
+        if section is None:
+            sections = conf.sections()
+            if conf.defaults():
+                sections.insert(0, iniparse.DEFAULTSECT)
+        else:
+            sections = (section,)
+        if param is not None:
+            val = conf.get(section, param)
+            print_name_value(param, val, section)
+        else:
+            for section in sections:
+                if section == iniparse.DEFAULTSECT:
+                    defaults_to_strip = {}
+                else:
+                    defaults_to_strip = conf.defaults()
+                items = False
+                for item in conf.items(section):
+                    # XXX: Note this strips an item from section
+                    # if matching value also in default (global) section.
+                    if defaults_to_strip.get(item[0]) != item[1]:
+                        val = item[1]
+                        print_name_value(item[0], val, section)
+                        items = True
+                if not items:
+                    print_name_value(None, None, section)
+
+except ConfigParser.NoSectionError as e:
+    error('Section not found: %s' % e.section)
     sys.exit(1)
 except ConfigParser.NoOptionError:
     error('Parameter not found: %s' % param)
     sys.exit(1)
 
+@contextlib.contextmanager
+def remove_file_on_error(path):
+    """Protect code that wants to operate on PATH atomically.
+    Any exception will cause PATH to be removed.
+    """
+    try:
+        yield
+    except Exception:
+        t, v, tb = sys.exc_info()
+        delete_if_exists(path)
+        raise t, v, tb
+
+def file_replace(name, data):
+    """Replace file as atomically as possible,
+    fulfilling and AC properties of ACID.
+    This is essentially using method 9 from:
+    http://www.pixelbeat.org/docs/unix_file_replacement.html
+
+    Caveats:
+     - Changes ownership of the file being edited
+       by non root users (due to POSIX interface limitations).
+     - Loses any extended attributes of the original file
+       (due to the simplicity of this implementation).
+     - Existing hardlinks will be separated from the
+       newly replaced file.
+     - Ignores the write permissions of the original file.
+     - Requires write permission on the directory as well as the file.
+     - With python2 on windows we don't fulfill the A ACID property.
+
+    To avoid the above caveats see the --inplace option.
+    """
+    (f, tmp) = tempfile.mkstemp(".tmp", prefix=name+".", dir=".")
+
+    with remove_file_on_error(tmp):
+        shutil.copystat(name, tmp)
+
+        if hasattr(os, 'fchown') and os.geteuid() == 0:
+            st = os.stat(name)
+            os.fchown(f, st.st_uid, st.st_gid)
+
+        os.write(f, data)
+        os.close(f)
+
+        if hasattr(os,'replace'): # >= python 3.3
+            os.replace(tmp, name) # atomic even on windos
+        elif os.name == 'posix':
+            os.rename(tmp, name) # atomic on POSIX
+        else:
+            backup = tmp+'.backup'
+            os.rename(name, backup)
+            os.rename(tmp, name)
+            delete_if_exists(backup)
+
+def file_rewrite(name, data):
+    """Rewrite file inplace avoiding the caveats
+    noted in file_replace().
+
+    Caveats:
+     - Not Atomic as readers may see incomplete data for a while.
+     - Not Consistent as multiple writers may overlap.
+     - Less Durable as exisiting data truncated before I/O completes.
+     - Requires write access to file rather than write access to dir.
+    """
+    with open(name, 'w') as f:
+        f.write(data)
+
 if mode != '--get':
-    with open(cfgfile, 'w') as f:
-        # XXX: Ideally we should just do conf.write(f) here,
-        # but to avoid iniparse issues, we massage the data a little here
-        str_data = str(conf.data)
-        if len(str_data) and str_data[-1] != '\n':
-            str_data += '\n'
-
-        if (
-            (added_default_section and not (section_explicit_default and mode 
in ('--set', '--merge')))
-            or (mode == '--del' and section == iniparse.DEFAULTSECT and param 
is None)
-           ):
-            str_data = str_data.replace('[%s]\n' % iniparse.DEFAULTSECT, '', 1)
+    # XXX: Ideally we should just do conf.write(f) here,
+    # but to avoid iniparse issues, we massage the data a little here
+    str_data = str(conf.data)
+    if len(str_data) and str_data[-1] != '\n':
+        str_data += '\n'
+
+    if (
+        (added_default_section and not (section_explicit_default and mode in 
('--set', '--merge')))
+        or (mode == '--del' and section == iniparse.DEFAULTSECT and param is 
None)
+        ):
+        str_data = str_data.replace('[%s]\n' % iniparse.DEFAULTSECT, '', 1)
 
-        f.write(str_data)
+    try:
+        if output == '-':
+            sys.stdout.write(str_data)
+        else:
+            file_edit = file_rewrite if inplace else file_replace
+            file_edit(output, str_data)
+    except EnvironmentError as e:
+        error(str(e))
+        sys.exit(1)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/crudini-0.3/crudini-help new/crudini-0.4/crudini-help
--- old/crudini-0.3/crudini-help        1970-01-01 01:00:00.000000000 +0100
+++ new/crudini-0.4/crudini-help        2014-09-05 19:17:30.000000000 +0200
@@ -0,0 +1,13 @@
+#!/bin/sh
+
+# crudini --help generator for help2man and README
+
+if [ "$1" = '--help' ]; then
+  printf '%s' 'crudini - '
+  ./crudini --help
+  echo
+  cat EXAMPLES
+elif [ "$1" = '--version' ]; then
+  ./crudini --version
+fi
+
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/crudini-0.3/example.ini new/crudini-0.4/example.ini
--- old/crudini-0.3/example.ini 2013-03-08 14:48:41.000000000 +0100
+++ new/crudini-0.4/example.ini 2014-09-05 19:17:30.000000000 +0200
@@ -31,8 +31,14 @@
 [section1]
 combine=sections
 
+[empty section]
+
 [non-sh-compat]
 space name=val
 útf8name=val
 1num=val
 ls;name=val
+
+[list]
+list1 = v1, v2
+list2 = v1,v2
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/crudini-0.3/setup.py new/crudini-0.4/setup.py
--- old/crudini-0.3/setup.py    1970-01-01 01:00:00.000000000 +0100
+++ new/crudini-0.4/setup.py    2014-09-05 19:17:30.000000000 +0200
@@ -0,0 +1,28 @@
+# -*- coding: utf-8 -*-
+
+import os
+from setuptools import setup
+
+def read(fname):
+    return open(os.path.join(os.path.dirname(__file__), fname)).read()
+
+setup(
+    name = "crudini",
+    version = "0.4",
+    author = "Pádraig Brady",
+    author_email = "p...@draigbrady.com",
+    description = ("A utility for manipulating ini files"),
+    license = "GPLv2",
+    keywords = "ini config edit",
+    url = "http://github.com/pixelb/crudini";,
+    long_description=read('README'),
+    classifiers=[
+        "Development Status :: 5 - Production/Stable",
+        "Topic :: Utilities",
+        "Topic :: System :: Systems Administration",
+        "License :: OSI Approved :: GNU General Public License v2 (GPLv2)",
+        "Programming Language :: Python :: 2",
+    ],
+    install_requires = ['iniparse'],
+    scripts=["crudini"]
+)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/crudini-0.3/tests/example.lines 
new/crudini-0.4/tests/example.lines
--- old/crudini-0.3/tests/example.lines 1970-01-01 01:00:00.000000000 +0100
+++ new/crudini-0.4/tests/example.lines 2014-09-05 19:17:30.000000000 +0200
@@ -0,0 +1,28 @@
+[ DEFAULT ] global = supported
+[ section1 ] dup1 = val1
+[ section1 ] dup2 = val2
+[ section1 ] nospace = val
+[ section1 ] multiline = with\nleading\nspace
+[ section1 ] nmultiline = not supported with\
+[ section1 ] comment_after1 = val
+[ section1 ] comment_after2 = val;not a comment
+[ section1 ] comment_after3 = val #not a comment
+[ section1 ] escaped_not_processed = test \nescape
+[ section1 ] colon = val
+[ section1 ] double_quotes = "not removed"
+[ section1 ] single_quotes = 'not removed'
+[ section1 ] spaces_stripped = val
+[ section1 ] internal_not_stripped = v  al
+[ section1 ] notempty1 = ;comment=val
+[ section1 ] empty
+[ section1 ] python_interpolate = %(dup1)s/blah
+[ section1 ] interpolate2 = ${dup1}/blah
+[ section1 ] caps = not significant
+[ section1 ] combine = sections
+[ empty section ]
+[ non-sh-compat ] space name = val
+[ non-sh-compat ] útf8name = val
+[ non-sh-compat ] 1num = val
+[ non-sh-compat ] ls;name = val
+[ list ] list1 = v1, v2
+[ list ] list2 = v1,v2
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/crudini-0.3/tests/section1.lines 
new/crudini-0.4/tests/section1.lines
--- old/crudini-0.3/tests/section1.lines        1970-01-01 01:00:00.000000000 
+0100
+++ new/crudini-0.4/tests/section1.lines        2014-09-05 19:17:30.000000000 
+0200
@@ -0,0 +1,20 @@
+[ section1 ] dup1 = val1
+[ section1 ] dup2 = val2
+[ section1 ] nospace = val
+[ section1 ] multiline = with\nleading\nspace
+[ section1 ] nmultiline = not supported with\
+[ section1 ] comment_after1 = val
+[ section1 ] comment_after2 = val;not a comment
+[ section1 ] comment_after3 = val #not a comment
+[ section1 ] escaped_not_processed = test \nescape
+[ section1 ] colon = val
+[ section1 ] double_quotes = "not removed"
+[ section1 ] single_quotes = 'not removed'
+[ section1 ] spaces_stripped = val
+[ section1 ] internal_not_stripped = v  al
+[ section1 ] notempty1 = ;comment=val
+[ section1 ] empty
+[ section1 ] python_interpolate = %(dup1)s/blah
+[ section1 ] interpolate2 = ${dup1}/blah
+[ section1 ] caps = not significant
+[ section1 ] combine = sections
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/crudini-0.3/tests/test.sh 
new/crudini-0.4/tests/test.sh
--- old/crudini-0.3/tests/test.sh       2013-03-08 14:48:41.000000000 +0100
+++ new/crudini-0.4/tests/test.sh       2014-09-05 19:17:30.000000000 +0200
@@ -1,4 +1,4 @@
-#!/bin/sh
+#!/bin/bash
 
 trap "exit 130" INT
 cleanup() { rm -f test.ini good.ini example.ini; exit; }
@@ -8,17 +8,16 @@
 
 test=0
 
-fail() { test=$(($test+1)); echo "Test $test FAIL"; exit 1; }
-ok() { test=$(($test+1)); echo "Test $test OK"; }
+fail() { test=$(($test+1)); echo "Test $test FAIL (line ${BASH_LINENO[-2]})"; 
exit 1; }
+ok() { test=$(($test+1)); echo "Test $test OK (line ${BASH_LINENO[-2]})"; }
 
 cp ../example.ini .
 
 # invalid params ----------------------------------------
 
-# 1
 :> test.ini
 crudini 2>/dev/null && fail
-crudini --met test.init 2>/dev/null && fail # bad mode
+crudini --met test.ini 2>/dev/null && fail # bad mode
 crudini --set 2>/dev/null && fail # no file
 crudini --set test.ini  2>/dev/null && fail # no section
 crudini --get 2>/dev/null && fail # no file
@@ -34,96 +33,100 @@
 
 # --set -------------------------------------------------
 
-# 2
 :> test.ini
 crudini --set test.ini '' name val
 printf '%s\n' 'name = val' > good.ini
 diff -u test.ini good.ini && ok || fail
 
-# 3
 :> test.ini
 crudini --set test.ini DEFAULT name val
 printf '%s\n' '[DEFAULT]' 'name = val' > good.ini
 diff -u test.ini good.ini && ok || fail
 
-# 4
 # Note blank line inserted at start
 :> test.ini
 crudini --set test.ini nonDEFAULT name val
 printf '%s\n' '' '[nonDEFAULT]' 'name = val' > good.ini
 diff -u test.ini good.ini && ok || fail
 
-# 5
 printf '%s\n' 'global=val' > test.ini
 crudini --set test.ini '' global valnew
 printf '%s\n' 'global=valnew' > good.ini
 diff -u test.ini good.ini && ok || fail
 
-# 6
 printf '%s\n' 'global=val' > test.ini
 crudini --set test.ini DEFAULT global valnew
 printf '%s\n' '[DEFAULT]' 'global=valnew' > good.ini
 diff -u test.ini good.ini && ok || fail
 
-# 7
 printf '%s\n' '[DEFAULT]' 'global=val' > test.ini
 crudini --set test.ini DEFAULT global valnew
 printf '%s\n' '[DEFAULT]' 'global=valnew' > good.ini
 diff -u test.ini good.ini && ok || fail
 
-# 8
 printf '%s\n' 'global=val' '' '[nonDEFAULT]' 'name=val' > test.ini
 crudini --set test.ini '' global valnew
 printf '%s\n' 'global=valnew' '' '[nonDEFAULT]' 'name=val' > good.ini
 diff -u test.ini good.ini && ok || fail
 
-# 9
+# do these --sets which test [DEFAULT] handling also with --inplace
+for mode in '' '--inplace'; do
 # Add '[DEFAULT]' if explicitly specified
-printf '%s\n' 'global=val' '' '[nonDEFAULT]' 'name=val' > test.ini
-crudini --set test.ini DEFAULT global valnew
-printf '%s\n' '[DEFAULT]' 'global=valnew' '' '[nonDEFAULT]' 'name=val' > 
good.ini
-diff -u test.ini good.ini && ok || fail
+  printf '%s\n' 'global=val' '' '[nonDEFAULT]' 'name=val' > test.ini
+  crudini $mode --set test.ini DEFAULT global valnew
+  printf '%s\n' '[DEFAULT]' 'global=valnew' '' '[nonDEFAULT]' 'name=val' > 
good.ini
+  diff -u test.ini good.ini && ok || fail
 
-# 10
-printf '%s\n' '[nonDEFAULT1]' 'name=val' '[nonDEFAULT2]' 'name=val' > test.ini
-crudini --set test.ini DEFAULT global val
-printf '%s\n' '[DEFAULT]' 'global = val' '[nonDEFAULT1]' 'name=val' 
'[nonDEFAULT2]' 'name=val' > good.ini
-diff -u test.ini good.ini && ok || fail
+  printf '%s\n' '[nonDEFAULT1]' 'name=val' '[nonDEFAULT2]' 'name=val' > 
test.ini
+  crudini $mode --set test.ini DEFAULT global val
+  printf '%s\n' '[DEFAULT]' 'global = val' '[nonDEFAULT1]' 'name=val' 
'[nonDEFAULT2]' 'name=val' > good.ini
+  diff -u test.ini good.ini && ok || fail
 
-# 11
-printf '%s\n' '[nonDEFAULT1]' 'name=val' '[nonDEFAULT2]' 'name=val' > test.ini
-crudini --set test.ini '' global val
-printf '%s\n' 'global = val' '[nonDEFAULT1]' 'name=val' '[nonDEFAULT2]' 
'name=val' > good.ini
-diff -u test.ini good.ini && ok || fail
+  printf '%s\n' '[nonDEFAULT1]' 'name=val' '[nonDEFAULT2]' 'name=val' > 
test.ini
+  crudini $mode --set test.ini '' global val
+  printf '%s\n' 'global = val' '[nonDEFAULT1]' 'name=val' '[nonDEFAULT2]' 
'name=val' > good.ini
+  diff -u test.ini good.ini && ok || fail
 
-# 12 XXX: Extraneous [DEFAULT] output in this edge case
-printf '%s\n' '[DEFAULT]' > test.ini
-crudini --set test.ini DEFAULT global val
-printf '%s\n' '[DEFAULT]' '[DEFAULT]' 'global = val' > good.ini
-diff -u test.ini good.ini && ok || fail
+  # Ensure '[DEFAULT]' is not duplicated
+  printf '%s\n' '[DEFAULT]' > test.ini
+  crudini $mode --set test.ini DEFAULT global val
+  printf '%s\n' '[DEFAULT]' 'global = val' > good.ini
+  diff -u test.ini good.ini && ok || fail
+
+  # Ensure '[DEFAULT]' is not duplicated when trailing space is present
+  printf '%s\n' '[DEFAULT]  ' > test.ini
+  crudini $mode --set test.ini DEFAULT global val
+  printf '%s\n' '[DEFAULT]  ' 'global = val' > good.ini
+  diff -u test.ini good.ini && ok || fail
 
-# 13 Maintain colon separation
-crudini --set example.ini section1 colon val
-grep -q '^colon:val' example.ini && ok || fail
+  # Ensure '[DEFAULT]' is not duplicated when a trailing comment is present
+  printf '%s\n' '[DEFAULT] #comment' > test.ini
+  crudini $mode --set test.ini DEFAULT global val
+  printf '%s\n' '[DEFAULT] #comment' 'global = val' > good.ini
+  diff -u test.ini good.ini && ok || fail
 
-# 14 Maintain space separation
-crudini --set example.ini section1 nospace val
-grep -q '^nospace=val' example.ini && ok || fail
+  # Maintain colon separation
+  crudini $mode --set example.ini section1 colon val
+  grep -q '^colon:val' example.ini && ok || fail
+
+  # Maintain space separation
+  crudini $mode --set example.ini section1 nospace val
+  grep -q '^nospace=val' example.ini && ok || fail
+done
 
-# 15 value is optional
+# value is optional
 :> test.ini
 crudini --set test.ini '' name
 printf '%s\n' 'name = ' > good.ini
 diff -u test.ini good.ini && ok || fail
 
-# 16
 # value is optional
 printf '%s\n' 'name=val' > test.ini
 crudini --set test.ini '' name
 printf '%s\n' 'name=' > good.ini
 diff -u test.ini good.ini && ok || fail
 
-# 17 --existing
+# --existing
 :> test.ini
 crudini --set test.ini '' gname val
 crudini --set --existing test.ini '' gname val2
@@ -134,20 +137,20 @@
 printf '%s\n' 'gname = val2' '' '' '[section1]' 'name = val2' > good.ini
 diff -u test.ini good.ini && ok || fail
 
-# 18 missing
+# missing
 crudini --set missing.ini '' name val 2>/dev/null && fail || ok
 
 # --get -------------------------------------------------
 
-# 19 basic get
+# basic get
 test "$(crudini --get example.ini section1 cAps)" = 'not significant' && ok || 
fail
 
-# 20 get sections
+# get sections
 crudini --get example.ini > test.ini
-printf '%s\n' DEFAULT section1 non-sh-compat > good.ini
+printf '%s\n' DEFAULT section1 'empty section' non-sh-compat list > good.ini
 diff -u test.ini good.ini && ok || fail
 
-# 21 get implicit default section
+# get implicit default section
 crudini --get example.ini '' > test.ini
 printf '%s\n' 'global' > good.ini
 diff -u test.ini good.ini || fail
@@ -156,7 +159,7 @@
 diff -u test.ini good.ini || fail
 ok
 
-# 22 get explicit default section
+# get explicit default section
 crudini --get example.ini DEFAULT > test.ini
 printf '%s\n' 'global' > good.ini
 diff -u test.ini good.ini || fail
@@ -165,22 +168,22 @@
 diff -u test.ini good.ini || fail
 ok
 
-# 23 get section1 in ini format
+# get section1 in ini format
 crudini --format=ini --get example.ini section1 > test.ini
 diff -u test.ini section1.ini && ok || fail
 
-# 24 get section1 in sh format
+# get section1 in sh format
 crudini --format=sh --get example.ini section1 > test.ini
 diff -u test.ini section1.sh && ok || fail
 
-# 24 empty DEFAULT is not printed
+# empty DEFAULT is not printed
 printf '%s\n' '[DEFAULT]' '#comment' '[section1]' > test.ini
 test "$(crudini --get test.ini)" = 'section1' || fail
 printf '%s\n' '#comment' '[section1]' > test.ini
 test "$(crudini --get test.ini)" = 'section1' || fail
 ok
 
-# 26 missing bits
+# missing bits
 :> test.ini
 crudini --get missing.ini 2>/dev/null && fail
 test "$(crudini --get test.ini)" = '' || fail
@@ -190,113 +193,122 @@
 
 # --merge -----------------------------------------------
 
-# 27 XXX: An empty default section isn't merged
+# XXX: An empty default section isn't merged
 :> test.ini
 printf '%s\n' '[DEFAULT]' '#comment' '[section1]' |
 crudini --merge test.ini || fail
 printf '%s\n' '' '[section1]' > good.ini
 diff -u test.ini good.ini && ok || fail
 
-# 28
 :> test.ini
 printf '%s\n' '[DEFAULT]' 'name=val' '[section1]' |
 crudini --merge test.ini || fail
 printf '%s\n' '[DEFAULT]' 'name = val' '' '[section1]' > good.ini
 diff -u test.ini good.ini && ok || fail
 
-# 29
 :> test.ini
 printf '%s\n' 'name=val' |
 crudini --merge test.ini || fail
 printf '%s\n' 'name = val' > good.ini
 diff -u test.ini good.ini && ok || fail
 
-# 30
 printf '%s\n' 'name=val1' > test.ini
 printf '%s\n' 'name = val2' |
 crudini --merge test.ini || fail
 printf '%s\n' 'name=val2' > good.ini
 diff -u test.ini good.ini && ok || fail
 
-# 31
 printf '%s\n' '[DEFAULT]' 'name=val1' > test.ini
 printf '%s\n' 'name=val2' |
 crudini --merge test.ini || fail
 printf '%s\n' '[DEFAULT]' 'name=val2' > good.ini
 diff -u test.ini good.ini && ok || fail
 
-# 32
 printf '%s\n' 'name = val1' > test.ini
 printf '%s\n' 'name=val2' |
 crudini --merge test.ini '' || fail
 printf '%s\n' 'name = val2' > good.ini
 diff -u test.ini good.ini && ok || fail
 
-# 33
 printf '%s\n' '[DEFAULT]' 'name=val1' > test.ini
 printf '%s\n' '[DEFAULT]' 'name=val2' |
 crudini --merge test.ini || fail
 printf '%s\n' '[DEFAULT]' 'name=val2' > good.ini
 diff -u test.ini good.ini && ok || fail
 
-# 34
 printf '%s\n' '[DEFAULT]' 'name=val1' > test.ini
 printf '%s\n' '[DEFAULT]' 'name=val2' |
 crudini --merge test.ini '' || fail
 printf '%s\n' '[DEFAULT]' 'name=val2' > good.ini
 diff -u test.ini good.ini && ok || fail
 
-# 35
 printf '%s\n' '[DEFAULT]' 'name=val1' > test.ini
 printf '%s\n' 'name=val2' |
 crudini --merge test.ini '' || fail
 printf '%s\n' '[DEFAULT]' 'name=val2' > good.ini
 diff -u test.ini good.ini && ok || fail
 
-# 36
 printf '%s\n' 'name=val1' > test.ini
 printf '%s\n' 'name=val2' |
 crudini --merge test.ini DEFAULT || fail
 printf '%s\n' '[DEFAULT]' 'name=val2' > good.ini
 diff -u test.ini good.ini && ok || fail
 
-# 37
 printf '%s\n' 'name=val1' > test.ini
 printf '%s\n' 'name=val2' |
 crudini --merge test.ini new || fail
 printf '%s\n' 'name=val1' '' '' '[new]' 'name = val2' > good.ini
 diff -u test.ini good.ini && ok || fail
 
-# 38
 printf '%s\n' 'name=val1' > test.ini
 printf '%s\n' 'name=val2' |
 crudini --merge --existing test.ini new 2>/dev/null && fail || ok
 
-# 39
 printf '%s\n' 'name=val1' > test.ini
 printf '%s\n' 'name2=val2' |
 crudini --merge --existing test.ini || fail
 printf '%s\n' 'name=val1' > good.ini
 diff -u test.ini good.ini && ok || fail
 
-# 40
 printf '%s\n' 'name=val1' '[section1]' 'name=val2' > test.ini
 printf '%s\n' 'name=val1a' '[section1]' 'name=val2a' |
 crudini --merge --existing test.ini || fail
 printf '%s\n' 'name=val1a' '[section1]' 'name=val2a' > good.ini
 diff -u test.ini good.ini && ok || fail
 
-# 41 All input sections merged to a specific section
+# All input sections merged to a specific section
 printf '%s\n' 'name=val1' '[section1]' 'name=val2' > test.ini
 printf '%s\n' 'name=val2a' '[section2]' 'name2=val' |
 crudini --merge test.ini 'section1' || fail
 printf '%s\n' 'name=val1' '[section1]' 'name=val2a' 'name2 = val' > good.ini
 diff -u test.ini good.ini && ok || fail
 
+# Maintain case for existing parameters
+printf '%s\n' '[section]' 'name=val' > test.ini
+printf '%s\n' '[section]' 'Name=val' |
+crudini --merge test.ini || fail
+printf '%s\n' '[section]' 'name=val'> good.ini
+diff -u test.ini good.ini && ok || fail
+
+# Honor case for new parameters (spacing not currently honored)
+printf '%s\n' '[section]' 'name1=val' > test.ini
+printf '%s\n' '[section]' 'Name2=val' |
+crudini --merge test.ini || fail
+printf '%s\n' '[section]' 'name1=val' 'Name2 = val' > good.ini
+diff -u test.ini good.ini && ok || fail
+
+# Note iniparse currently matches sections case insensitively
+printf '%s\n' '[section1]' 'name=val1' > test.ini
+printf '%s\n' '[Section1]' 'name=val2' |
+crudini --merge --existing 2>/dev/null test.ini && fail || ok
+printf '%s\n' '[Section1]' 'name=val2' |
+crudini --merge test.ini || fail
+printf '%s\n' '[section1]' 'name=val1' '' '' '[Section1]' 'name = val2' > 
good.ini
+diff -u test.ini good.ini && ok || fail
+
 # --del -------------------------------------------------
 
 for sec in '' '[DEFAULT]'; do
-# 42 46
   printf '%s\n' $sec 'name = val' > test.ini
   crudini --del test.ini '' noname || fail
   crudini --del --existing test.ini '' noname 2>/dev/null && fail
@@ -305,7 +317,6 @@
   [ "$sec" ] && printf '%s\n' $sec > good.ini
   diff -u test.ini good.ini && ok || fail
 
-# 43 47
   printf '%s\n' $sec 'name = val' > test.ini
   crudini --del test.ini 'DEFAULT' noname || fail
   crudini --del --existing test.ini 'DEFAULT' noname 2>/dev/null && fail
@@ -314,7 +325,6 @@
   [ "$sec" ] && printf '%s\n' $sec > good.ini
   diff -u test.ini good.ini && ok || fail
 
-# 44 48
   printf '%s\n' $sec 'name = val' > test.ini
   crudini --del test.ini nosect || fail
   crudini --del --existing test.ini nosect 2>/dev/null && fail
@@ -322,7 +332,6 @@
   :> good.ini
   diff -u test.ini good.ini && ok || fail
 
-# 45 49
   printf '%s\n' $sec 'name = val' > test.ini
   crudini --del test.ini nosect || fail
   crudini --del --existing test.ini nosect 2>/dev/null && fail
@@ -330,3 +339,54 @@
   :> good.ini
   diff -u test.ini good.ini && ok || fail
 done
+
+# --get-lines --------------------------------------------
+
+crudini --get --format=lines example.ini section1 > test.ini || fail
+diff -u test.ini section1.lines && ok || fail
+
+crudini --get --format=lines example.ini > test.ini || fail
+diff -u test.ini example.lines && ok || fail
+
+# --list -------------------------------------------------
+
+# Add new item to list
+crudini --list --set example.ini list list1 v3 || fail
+test "$(crudini --get example.ini list list1)" = 'v1, v2, v3' && ok || fail
+
+# Ensure item in list
+crudini --list --set example.ini list list1 v3 || fail
+test "$(crudini --get example.ini list list1)" = 'v1, v2, v3' && ok || fail
+
+# Delete item from list
+crudini --list --del example.ini list list1 v3 || fail
+test "$(crudini --get example.ini list list1)" = 'v1, v2' && ok || fail
+
+# Delete non existing item from list
+for existing in '' '--existing'; do
+  crudini $existing --list --del example.ini list list1 v3 || fail
+  test "$(crudini --get example.ini list list1)" = 'v1, v2' && ok || fail
+done
+
+# Add new item to list without spacing
+#  auto
+crudini --list --set example.ini list list2 v3 || fail
+test "$(crudini --get example.ini list list2)" = 'v1,v2,v3' && ok || fail
+crudini --set example.ini list list2 'v1,v2' || fail
+#  explicit
+crudini --list --list-sep=, --set example.ini list list2 v3 || fail
+test "$(crudini --get example.ini list list2)" = 'v1,v2,v3' && ok || fail
+
+
+# Delete item from list without spacing
+#  auto
+crudini --list --del example.ini list list2 v3 || fail
+test "$(crudini --get example.ini list list2)" = 'v1,v2' && ok || fail
+crudini --set example.ini list list2 'v1,v2,v3' || fail
+#  explicit
+crudini --list --list-sep=, --del example.ini list list2 v3 || fail
+test "$(crudini --get example.ini list list2)" = 'v1,v2' && ok || fail
+
+# Delete honoring --existing
+crudini --list --existing --del example.ini nolist list1 v3 2>/dev/null && 
fail || ok
+crudini --list --existing --del example.ini list nolist1 v3 2>/dev/null && 
fail || ok
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/crudini-0.3/tox.ini new/crudini-0.4/tox.ini
--- old/crudini-0.3/tox.ini     1970-01-01 01:00:00.000000000 +0100
+++ new/crudini-0.4/tox.ini     2014-09-05 19:17:30.000000000 +0200
@@ -0,0 +1,6 @@
+[tox]
+envlist = py26,py27
+
+[testenv]
+deps=iniparse
+commands=/bin/bash -c 'cd tests && ./test.sh'

-- 
To unsubscribe, e-mail: opensuse-commit+unsubscr...@opensuse.org
For additional commands, e-mail: opensuse-commit+h...@opensuse.org

Reply via email to