Control: tags 936346 + patch
Control: tags 936346 + pending

Dear maintainer,

I've prepared an NMU for crudini (versioned as 0.9.3-0.1) and uploaded 
it to DELAYED/15. Please feel free to tell me if I should cancel it.

cu
Adrian
diff -Nru crudini-0.7/crudini crudini-0.9.3/crudini
--- crudini-0.7/crudini	2015-06-14 03:27:16.000000000 +0300
+++ crudini-0.9.3/crudini	2019-08-30 14:26:49.000000000 +0300
@@ -1,16 +1,16 @@
-#!/usr/bin/python
+#!/usr/bin/env python
 # -*- coding: utf-8 -*-
 # vim:fileencoding=utf8
 #
-# Copyright (C) 2013-2015, Pádraig Brady <p...@draigbrady.com>
+# Copyright © 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
+from __future__ import print_function
 
 import atexit
-from cStringIO import StringIO
-import ConfigParser
+import sys
 import contextlib
 import errno
 import getopt
@@ -20,13 +20,19 @@
 import pipes
 import shutil
 import string
-import sys
 import tempfile
 
+if sys.version_info[0] >= 3:
+    from io import StringIO
+    import configparser
+else:
+    from cStringIO import StringIO
+    import ConfigParser as configparser
+
 
 def error(message=None):
     if message:
-        sys.stderr.write(message+'\n')
+        sys.stderr.write(message + '\n')
 
 
 def delete_if_exists(path):
@@ -36,15 +42,33 @@
         os.unlink(path)
     except EnvironmentError as e:
         if e.errno != errno.ENOENT:
-            print str(e)
+            print(str(e))
             raise
 
 
+# TODO: support configurable options for various ini variants.
+# For now just support parameters without '=' specified
+class CrudiniInputFilter():
+    def __init__(self, fp):
+        self.fp = fp
+        self.crudini_no_arg = False
+
+    def readline(self):
+        line = self.fp.readline()
+        # XXX: This doesn't handle ;inline comments.
+        # Really should be done within inparse.
+        if (line and line[0] not in '[ \t#;\n\r' and
+           '=' not in line and ':' not in line):
+            self.crudini_no_arg = True
+            line = line[:-1] + ' = crudini_no_arg\n'
+        return line
+
+
 # XXX: should be done in iniparse.  Used to
 # add support for ini files without a section
-class AddDefaultSection():
+class AddDefaultSection(CrudiniInputFilter):
     def __init__(self, fp):
-        self.fp = fp
+        CrudiniInputFilter.__init__(self, fp)
         self.first = True
 
     def readline(self):
@@ -52,7 +76,7 @@
             self.first = False
             return '[%s]' % iniparse.DEFAULTSECT
         else:
-            return self.fp.readline()
+            return CrudiniInputFilter.readline(self)
 
 
 class FileLock(object):
@@ -104,7 +128,6 @@
         - 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, create):
@@ -180,6 +203,9 @@
 class CrudiniConfigParser(iniparse.RawConfigParser):
     def __init__(self, preserve_case=False):
         iniparse.RawConfigParser.__init__(self)
+        # Without the following we can't have params starting with "rem"!
+        # We ignore lines starting with '%' which mercurial uses to include
+        iniparse.change_comment_syntax('%;#', allow_rem=False)
         if preserve_case:
             self.optionxform = str
 
@@ -193,7 +219,7 @@
         :param section: str
         """
 
-        print section
+        print(section)
 
     def name_value(self, name, value, section=None):
         """Print parameter.
@@ -203,17 +229,21 @@
         :param section: str (default 'None')
         """
 
-        print name or value
+        if value == 'crudini_no_arg':
+            value = ''
+        print(name or value)
 
 
 class PrintIni(Print):
     """Use for ini output format."""
 
     def section_header(self, section):
-        print "[%s]" % section
+        print("[%s]" % section)
 
     def name_value(self, name, value, section=None):
-        print name, '=', value.replace('\n', '\n ')
+        if value == 'crudini_no_arg':
+            value = ''
+        print(name, '=', value.replace('\n', '\n '))
 
 
 class PrintLines(Print):
@@ -228,9 +258,11 @@
                 line += ' '
         if name:
             line += '%s' % name
+        if value == 'crudini_no_arg':
+            value = ''
         if value:
             line += ' = %s' % value.replace('\n', '\\n')
-        print line
+        print(line)
 
 
 class PrintSh(Print):
@@ -261,6 +293,8 @@
         if not PrintSh._valid_sh_identifier(name):
             error('Invalid sh identifier: %s' % name)
             sys.exit(1)
+        if value == 'crudini_no_arg':
+            value = ''
         sys.stdout.write("%s=%s\n" % (name, pipes.quote(value)))
 
 
@@ -293,7 +327,7 @@
         except Exception:
             t, v, tb = sys.exc_info()
             delete_if_exists(path)
-            raise t, v, tb
+            raise t(v).with_traceback(tb)
 
     @staticmethod
     def file_replace(name, data):
@@ -315,7 +349,7 @@
 
         To avoid the above caveats see the --inplace option.
         """
-        (f, tmp) = tempfile.mkstemp(".tmp", prefix=name+".", dir=".")
+        (f, tmp) = tempfile.mkstemp(".tmp", prefix=name + ".", dir=".")
 
         with Crudini.remove_file_on_error(tmp):
             shutil.copystat(name, tmp)
@@ -324,8 +358,17 @@
                 st = os.stat(name)
                 os.fchown(f, st.st_uid, st.st_gid)
 
-            os.write(f, data)
-            os.fsync(f)  # See http://stackoverflow.com/q/7433057/4421
+            if sys.version_info[0] >= 3:
+                os.write(f, bytearray(data, 'utf-8'))
+            else:
+                os.write(f, data)
+            # We assume the existing file is persisted,
+            # so sync here to ensure new data is persisted
+            # before referencing it.  Otherwise the metadata could
+            # be written first, referencing the new data, which
+            # would be nothing if a crash occured before the
+            # data was allocated/persisted.
+            os.fsync(f)
             os.close(f)
 
             if hasattr(os, 'replace'):  # >= python 3.3
@@ -333,12 +376,16 @@
             elif os.name == 'posix':
                 os.rename(tmp, name)  # atomic on POSIX
             else:
-                backup = tmp+'.backup'
+                backup = tmp + '.backup'
                 os.rename(name, backup)
                 os.rename(tmp, name)
                 delete_if_exists(backup)
 
-            # Sync out the new directory entry also
+            # Sync out the new directory entry to provide
+            # better durability that the new inode is referenced
+            # rather than continuing to reference the old inode.
+            # This also provides verification in exit status that
+            # this update completes.
             O_DIRECTORY = os.O_DIRECTORY if hasattr(os, 'O_DIRECTORY') else 0
             dirfd = os.open(os.path.dirname(name) or '.', O_DIRECTORY)
             os.fsync(dirfd)
@@ -411,7 +458,7 @@
 
   --existing[=WHAT]  For --set, --del and --merge, fail if item is missing,
                        where WHAT is 'file', 'section', or 'param', or if
-                       not specified; all specifed items.
+                       not specified; all specified items.
   --format=FMT       For --get, select the output FMT.
                        Formats are sh,ini,lines
   --inplace          Lock and write files in place.
@@ -421,6 +468,8 @@
   --list-sep=STR     Delimit list values with \"STR\" instead of \" ,\"
   --output=FILE      Write output to FILE instead. '-' means stdout
   --verbose          Indicate on stderr if changes were made
+  --help             Write this help to stdout
+  --version          Write version to stdout
 """ % (cmd, cmd, cmd, cmd)
         )
         sys.exit(exitval)
@@ -452,7 +501,7 @@
                 'version'
             ]
             opts, args = getopt.getopt(sys.argv[1:], '', long_options)
-        except getopt.GetoptError, e:
+        except getopt.GetoptError as e:
             error(str(e))
             self.usage(1)
 
@@ -460,7 +509,7 @@
             if o in ('--help',):
                 self.usage(0)
             elif o in ('--version',):
-                print 'crudini 0.7'
+                print('crudini 0.9.3')
                 sys.exit(0)
             elif o in ('--verbose',):
                 self.verbose = True
@@ -548,8 +597,11 @@
         return False
 
     def _chksum(self, data):
-        h = hashlib.md5()
-        h.update(data)
+        h = hashlib.sha256()
+        if sys.version_info[0] >= 3:
+            h.update(bytearray(data, 'utf-8'))
+        else:
+            h.update(data)
         return h.digest()
 
     def _parse_file(self, filename, add_default=False, preserve_case=False):
@@ -576,9 +628,12 @@
             fp = StringIO(self.data)
             if add_default:
                 fp = AddDefaultSection(fp)
+            else:
+                fp = CrudiniInputFilter(fp)
 
             conf = CrudiniConfigParser(preserve_case=preserve_case)
             conf.readfp(fp)
+            self.crudini_no_arg = fp.crudini_no_arg
             return conf
         except EnvironmentError as e:
             error(str(e))
@@ -608,14 +663,14 @@
                     )
                     self.added_default_section = True
 
-        except ConfigParser.MissingSectionHeaderError:
+        except configparser.MissingSectionHeaderError:
             conf = self._parse_file(
                 filename,
                 add_default=True,
                 preserve_case=preserve_case
             )
             self.added_default_section = True
-        except ConfigParser.ParsingError as e:
+        except configparser.ParsingError as e:
             error(str(e))
             sys.exit(1)
 
@@ -631,14 +686,14 @@
                     section == iniparse.DEFAULTSECT or
                     self.conf.has_section(section)
                 ):
-                    raise ConfigParser.NoSectionError(section)
+                    raise configparser.NoSectionError(section)
             else:
                 try:
                     curr_val = self.conf.get(section, param)
-                except ConfigParser.NoSectionError:
+                except configparser.NoSectionError:
                     if self.update == 'section':
                         raise
-                except ConfigParser.NoOptionError:
+                except configparser.NoOptionError:
                     if self.update == 'param':
                         raise
         elif (section != iniparse.DEFAULTSECT and
@@ -656,11 +711,11 @@
             if self.update not in ('param', 'section'):
                 try:
                     curr_val = self.conf.get(section, param)
-                except ConfigParser.NoOptionError:
+                except configparser.NoOptionError:
                     if self.mode == "--del":
                         return
             if value is None:
-                value = ''
+                value = 'crudini_no_arg' if self.crudini_no_arg else ''
             if self.vlist:
                 value = self.update_list(
                     curr_val,
@@ -689,11 +744,11 @@
                 # XXX: Note this doesn't update an item in section
                 # if matching value also in default (global) section.
                 if defaults_to_strip.get(item[0]) != item[1]:
-                    ignore_errs = (ConfigParser.NoOptionError,)
+                    ignore_errs = (configparser.NoOptionError,)
                     if self.section is not None:
                         msection = self.section
                     elif self.update not in ('param', 'section'):
-                        ignore_errs += (ConfigParser.NoSectionError,)
+                        ignore_errs += (configparser.NoSectionError,)
                     try:
                         set_param = True
                         self.set_name_value(msection, item[0], item[1])
@@ -713,13 +768,13 @@
             else:
                 if not self.conf.remove_section(self.section) \
                    and self.update in ('param', 'section'):
-                    raise ConfigParser.NoSectionError(self.section)
+                    raise configparser.NoSectionError(self.section)
         elif self.value is None:
             try:
                 if not self.conf.remove_option(self.section, self.param) \
                    and self.update == 'param':
-                    raise ConfigParser.NoOptionError(self.section, self.param)
-            except ConfigParser.NoSectionError:
+                    raise configparser.NoOptionError(self.section, self.param)
+            except configparser.NoSectionError:
                 if self.update in ('param', 'section'):
                     raise
         else:  # remove item from list
@@ -803,21 +858,25 @@
         self.madded_default_section = self.added_default_section
 
         try:
-            self.conf = self.parse_file(self.cfgfile)
-        except ConfigParser.ParsingError as e:
-            error('Error parsing %s: %s' % (self.cfgfile, e.message))
-            sys.exit(1)
+            if self.mode == '--get' and self.param is None:
+                # Maintain case when outputting params.
+                # Note sections are handled case sensitively
+                # even if optionxform is not set.
+                preserve_case = True
+            else:
+                preserve_case = False
+            self.conf = self.parse_file(self.cfgfile,
+                                        preserve_case=preserve_case)
 
-        # Take the [DEFAULT] header from the input if present
-        if (
-            self.mode == '--merge' and
-            self.update not in ('param', 'section') and
-            not self.madded_default_section and
-            self.mconf.items(iniparse.DEFAULTSECT)
-        ):
-            self.added_default_section = self.madded_default_section
+            # Take the [DEFAULT] header from the input if present
+            if (
+                self.mode == '--merge' and
+                self.update not in ('param', 'section') and
+                not self.madded_default_section and
+                self.mconf.items(iniparse.DEFAULTSECT)
+            ):
+                self.added_default_section = self.madded_default_section
 
-        try:
             if self.mode == '--set':
                 self.command_set()
             elif self.mode == '--merge':
@@ -826,45 +885,46 @@
                 self.command_del()
             elif self.mode == '--get':
                 self.command_get()
-        except ConfigParser.NoSectionError as e:
-            error('Section not found: %s' % e.section)
-            sys.exit(1)
-        except ConfigParser.NoOptionError:
-            error('Parameter not found: %s' % self.param)
-            sys.exit(1)
-
-        if self.mode != '--get':
-            # 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(self.conf.data)
-            if len(str_data) and str_data[-1] != '\n':
-                str_data += '\n'
 
-            if (
-                (
-                    self.added_default_section and
-                    not (
-                        self.section_explicit_default and
-                        self.mode in ('--set', '--merge')
+            if self.mode != '--get':
+                # 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(self.conf.data)
+                if len(str_data) and str_data[-1] != '\n':
+                    str_data += '\n'
+
+                if (
+                    (
+                        self.added_default_section and
+                        not (
+                            self.section_explicit_default and
+                            self.mode in ('--set', '--merge')
+                        )
+                    ) or
+                    (
+                        self.mode == '--del' and
+                        self.section == iniparse.DEFAULTSECT and
+                        self.param is None
                     )
-                ) or
-                (
-                    self.mode == '--del' and
-                    self.section == iniparse.DEFAULTSECT and
-                    self.param is None
-                )
-            ):
-                # See note at add_section() call above detailing
-                # where this extra \n comes from that we handle
-                # here for the edge case of new files.
-                default_sect = '[%s]\n' % iniparse.DEFAULTSECT
-                if not self.newline_at_start and \
-                   str_data.startswith(default_sect + '\n'):
-                    str_data = str_data[len(default_sect) + 1:]
-                else:
-                    str_data = str_data.replace(default_sect, '', 1)
+                ):
+                    # See note at add_section() call above detailing
+                    # where this extra \n comes from that we handle
+                    # here for the edge case of new files.
+                    default_sect = '[%s]\n' % iniparse.DEFAULTSECT
+                    if not self.newline_at_start and \
+                       str_data.startswith(default_sect + '\n'):
+                        str_data = str_data[len(default_sect) + 1:]
+                    else:
+                        str_data = str_data.replace(default_sect, '', 1)
+
+                if self.crudini_no_arg:
+                    # This is the main case
+                    str_data = str_data.replace(' = crudini_no_arg', '')
+                    # Handle setting empty values for existing param= format
+                    str_data = str_data.replace('=crudini_no_arg', '=')
+                    # Handle setting empty values for existing colon: format
+                    str_data = str_data.replace(':crudini_no_arg', ':')
 
-            try:
                 changed = self.chksum != self._chksum(str_data)
 
                 if self.output == '-':
@@ -873,26 +933,47 @@
                     if self.inplace:
                         self.file_rewrite(self.output, str_data)
                     else:
-                        self.file_replace(self.output, str_data)
+                        self.file_replace(os.path.realpath(self.output),
+                                          str_data)
 
                 if self.verbose:
                     def quote_val(val):
                         return pipes.quote(val).replace('\n', '\\n')
                     what = ' '.join(map(quote_val,
-                                        filter(bool,
-                                               [self.mode, self.cfgfile,
-                                                self.section, self.param,
-                                                self.value])))
+                                        list(filter(bool,
+                                                    [self.mode, self.cfgfile,
+                                                     self.section, self.param,
+                                                     self.value]))))
                     sys.stderr.write('%s: %s\n' %
                                      (('unchanged', 'changed')[changed], what))
-            except EnvironmentError as e:
+
+            # Finish writing now to consistently handle errors here
+            # (and while excepthook is set)
+            sys.stdout.flush()
+        except configparser.ParsingError as e:
+            error('Error parsing %s: %s' % (self.cfgfile, e.message))
+            sys.exit(1)
+        except configparser.NoSectionError as e:
+            error('Section not found: %s' % e.section)
+            sys.exit(1)
+        except configparser.NoOptionError:
+            error('Parameter not found: %s' % self.param)
+            sys.exit(1)
+        except EnvironmentError as e:
+            # Handle EPIPE as python 2 doesn't catch SIGPIPE
+            if e.errno != errno.EPIPE:
                 error(str(e))
                 sys.exit(1)
+            # Python3 fix for exception on exit:
+            # https://docs.python.org/3/library/signal.html#note-on-sigpipe
+            nullf = os.open(os.devnull, os.O_WRONLY)
+            os.dup2(nullf, sys.stdout.fileno())
 
 
 def main():
     crudini = Crudini()
     return crudini.run()
 
+
 if __name__ == "__main__":
     sys.exit(main())
diff -Nru crudini-0.7/crudini.1 crudini-0.9.3/crudini.1
--- crudini-0.7/crudini.1	2015-06-14 03:31:34.000000000 +0300
+++ crudini-0.9.3/crudini.1	2019-08-30 14:33:19.000000000 +0300
@@ -1,5 +1,5 @@
-.\" DO NOT MODIFY THIS FILE!  It was generated by help2man 1.46.6.
-.TH CRUDINI "1" "June 2015" "crudini 0.7" "User Commands"
+.\" DO NOT MODIFY THIS FILE!  It was generated by help2man 1.47.4.
+.TH CRUDINI "1" "August 2019" "crudini 0.9.3" "User Commands"
 .SH NAME
 crudini \- manipulate ini files
 .SH SYNOPSIS
@@ -21,7 +21,7 @@
 \fB\-\-existing\fR[=\fI\,WHAT\/\fR]
 For \fB\-\-set\fR, \fB\-\-del\fR and \fB\-\-merge\fR, fail if item is missing,
 where WHAT is 'file', 'section', or 'param', or if
-not specified; all specifed items.
+not specified; all specified items.
 .TP
 \fB\-\-format\fR=\fI\,FMT\/\fR
 For \fB\-\-get\fR, select the output FMT.
@@ -43,6 +43,12 @@
 .TP
 \fB\-\-verbose\fR
 Indicate on stderr if changes were made
+.TP
+\fB\-\-help\fR
+Write this help to stdout
+.TP
+\fB\-\-version\fR
+Write version to stdout
 .SH EXAMPLES
 # Add/Update a var
 .IP
diff -Nru crudini-0.7/debian/changelog crudini-0.9.3/debian/changelog
--- crudini-0.7/debian/changelog	2015-07-12 03:55:06.000000000 +0300
+++ crudini-0.9.3/debian/changelog	2019-12-20 16:37:02.000000000 +0200
@@ -1,3 +1,11 @@
+crudini (0.9.3-0.1) unstable; urgency=medium
+
+  * Non-maintainer upload.
+  * New upstream release.
+  * Use Python 3. (Closes: #936346)
+
+ -- Adrian Bunk <b...@debian.org>  Fri, 20 Dec 2019 16:37:02 +0200
+
 crudini (0.7-1) unstable; urgency=medium
 
   * New upstream version
diff -Nru crudini-0.7/debian/control crudini-0.9.3/debian/control
--- crudini-0.7/debian/control	2015-07-12 03:54:48.000000000 +0300
+++ crudini-0.9.3/debian/control	2019-12-20 16:37:02.000000000 +0200
@@ -4,11 +4,11 @@
 Maintainer: Python Applications Packaging Team <python-apps-t...@lists.alioth.debian.org>
 Uploaders: Zev Benjamin <z...@mit.edu>, Luke Faraone <lfara...@debian.org>
 Build-Depends: debhelper (>= 8.0.0),
- python,
+ python3,
  dh-python,
 # Needed to generate manpage
  help2man,
- python-iniparse
+ python3-iniparse
 Standards-Version: 3.9.6
 Homepage: http://www.pixelbeat.org/programs/crudini/
 Vcs-Svn: svn://anonscm.debian.org/python-apps/packages/crudini/trunk/
@@ -16,7 +16,7 @@
 
 Package: crudini
 Architecture: any
-Depends: ${python:Depends}, ${misc:Depends}, python-iniparse
+Depends: ${python3:Depends}, ${misc:Depends}, python3-iniparse
 Description: utility for manipulating ini files
  crudini is a utility to simplify reading and updating ini files
  from shell scripts, so named as it provides CRUD functionality.  It
diff -Nru crudini-0.7/debian/patches/01-typofix.diff crudini-0.9.3/debian/patches/01-typofix.diff
--- crudini-0.7/debian/patches/01-typofix.diff	2015-07-12 03:51:33.000000000 +0300
+++ crudini-0.9.3/debian/patches/01-typofix.diff	1970-01-01 02:00:00.000000000 +0200
@@ -1,16 +0,0 @@
-Description: Fix a typo in the documentation.
-Author: Luke Faraone <lfara...@debian.org>
-Origin: vendor
-Forwarded: https://github.com/pixelb/crudini/pull/25
-
---- crudini-0.7.orig/crudini
-+++ crudini-0.7/crudini
-@@ -411,7 +411,7 @@ Options:
- 
-   --existing[=WHAT]  For --set, --del and --merge, fail if item is missing,
-                        where WHAT is 'file', 'section', or 'param', or if
--                       not specified; all specifed items.
-+                       not specified; all specified items.
-   --format=FMT       For --get, select the output FMT.
-                        Formats are sh,ini,lines
-   --inplace          Lock and write files in place.
diff -Nru crudini-0.7/debian/patches/python3.patch crudini-0.9.3/debian/patches/python3.patch
--- crudini-0.7/debian/patches/python3.patch	1970-01-01 02:00:00.000000000 +0200
+++ crudini-0.9.3/debian/patches/python3.patch	2019-12-20 16:37:02.000000000 +0200
@@ -0,0 +1,12 @@
+Description: crudini: Use Python 3 instead of Python 2
+Author: Adrian Bunk <b...@debian.org>
+Bug-Debian: https://bugs.debian.org/936346
+
+--- crudini-0.9.3.orig/crudini
++++ crudini-0.9.3/crudini
+@@ -1,4 +1,4 @@
+-#!/usr/bin/env python
++#!/usr/bin/env python3
+ # -*- coding: utf-8 -*-
+ # vim:fileencoding=utf8
+ #
diff -Nru crudini-0.7/debian/patches/series crudini-0.9.3/debian/patches/series
--- crudini-0.7/debian/patches/series	2015-07-12 03:50:33.000000000 +0300
+++ crudini-0.9.3/debian/patches/series	2019-12-20 16:37:02.000000000 +0200
@@ -1 +1 @@
-01-typofix.diff
+python3.patch
diff -Nru crudini-0.7/debian/rules crudini-0.9.3/debian/rules
--- crudini-0.7/debian/rules	2013-12-04 20:56:34.000000000 +0200
+++ crudini-0.9.3/debian/rules	2019-12-20 16:37:02.000000000 +0200
@@ -10,4 +10,4 @@
 #export DH_VERBOSE=1
 
 %:
-	dh $@ --with python2
+	dh $@ --with python3
diff -Nru crudini-0.7/Makefile crudini-0.9.3/Makefile
--- crudini-0.7/Makefile	2015-06-14 03:27:03.000000000 +0300
+++ crudini-0.9.3/Makefile	2019-08-30 14:26:28.000000000 +0300
@@ -1,5 +1,5 @@
 name = crudini
-version = 0.7
+version = 0.9.3
 
 all:
 	help2man -n "manipulate ini files" -o crudini.1 -N ./crudini-help
diff -Nru crudini-0.7/NEWS crudini-0.9.3/NEWS
--- crudini-0.7/NEWS	2015-06-14 03:27:37.000000000 +0300
+++ crudini-0.9.3/NEWS	2019-08-30 14:27:03.000000000 +0300
@@ -1,5 +1,49 @@
 crudini NEWS                                    -*- outline -*-
 
+* Noteworthy changes in release 0.9.3 (2019-08-30)
+
+** Bug fixes
+
+  Reading ini files with windows line endings is again supported.
+  Regression added in v0.9.
+
+** Improvements
+
+  python 3 support.
+
+
+* Noteworthy changes in release 0.9 (2016-12-13)
+
+** Bug fixes
+
+  Write errors to stdout are diagnosed correctly and consistently.
+
+  Replacing symlinks now replaces the target rather than the symlink itself.
+
+** Changes in behavior
+
+  The case of parameters is maintained with --get.
+
+** Improvements
+
+  Single token parameters (without equals) are now supported,
+  which are used in mysql config for example.
+
+
+* Noteworthy changes in release 0.8 (2016-11-23)
+
+** Bug fixes
+
+  crudini now handles parameters starting with "rem".
+  Previously an entry such as "remote = 1" would be ignored.
+
+** New features
+
+  Support mercurial config files by treating lines starting
+  with '%' as comments, thus ignoring mercurial '%include'
+  and '%unset' directives.
+
+
 * Noteworthy changes in release 0.7 (2015-06-14)
 
 ** Bug fixes
diff -Nru crudini-0.7/noequals.ini crudini-0.9.3/noequals.ini
--- crudini-0.7/noequals.ini	1970-01-01 02:00:00.000000000 +0200
+++ crudini-0.9.3/noequals.ini	2016-12-13 15:21:50.000000000 +0200
@@ -0,0 +1,19 @@
+# Differences from mysql.conf
+# #comments can't be at middle of line
+# single/double quotes are not stripped
+# leading spaces on line are not ignored
+
+!include directives treated as parameters
+
+[noequals]
+param1
+param2=
+param3 =
+colon1:
+colon2 :
+space param
+never #comment
+not ;comment
+multiline=val
+ spaceval
+	tabval
diff -Nru crudini-0.7/README crudini-0.9.3/README
--- crudini-0.7/README	2015-06-14 03:31:35.000000000 +0300
+++ crudini-0.9.3/README	2019-08-30 14:33:19.000000000 +0300
@@ -9,7 +9,7 @@
 
   --existing[=WHAT]  For --set, --del and --merge, fail if item is missing,
                        where WHAT is 'file', 'section', or 'param', or if
-                       not specified; all specifed items.
+                       not specified; all specified items.
   --format=FMT       For --get, select the output FMT.
                        Formats are sh,ini,lines
   --inplace          Lock and write files in place.
@@ -19,6 +19,8 @@
   --list-sep=STR     Delimit list values with "STR" instead of " ,"
   --output=FILE      Write output to FILE instead. '-' means stdout
   --verbose          Indicate on stderr if changes were made
+  --help             Write this help to stdout
+  --version          Write version to stdout
 
 Examples:
 
diff -Nru crudini-0.7/setup.py crudini-0.9.3/setup.py
--- crudini-0.7/setup.py	2015-06-14 03:26:59.000000000 +0300
+++ crudini-0.9.3/setup.py	2019-08-30 14:26:35.000000000 +0300
@@ -7,9 +7,10 @@
 def read(fname):
     return open(os.path.join(os.path.dirname(__file__), fname)).read()
 
+
 setup(
     name="crudini",
-    version="0.7",
+    version="0.9.3",
     author="Pádraig Brady",
     author_email="p...@draigbrady.com",
     description=("A utility for manipulating ini files"),
@@ -24,6 +25,6 @@
         "License :: OSI Approved :: GNU General Public License v2 (GPLv2)",
         "Programming Language :: Python :: 2",
     ],
-    install_requires=['iniparse'],
+    install_requires=['iniparse>=0.3.2'],
     scripts=["crudini"]
 )
diff -Nru crudini-0.7/tests/example.lines crudini-0.9.3/tests/example.lines
--- crudini-0.7/tests/example.lines	2014-09-05 18:46:40.000000000 +0300
+++ crudini-0.9.3/tests/example.lines	2016-12-10 17:52:57.000000000 +0200
@@ -17,7 +17,7 @@
 [ section1 ] empty
 [ section1 ] python_interpolate = %(dup1)s/blah
 [ section1 ] interpolate2 = ${dup1}/blah
-[ section1 ] caps = not significant
+[ section1 ] Caps = not significant
 [ section1 ] combine = sections
 [ empty section ]
 [ non-sh-compat ] space name = val
diff -Nru crudini-0.7/tests/section1.ini crudini-0.9.3/tests/section1.ini
--- crudini-0.7/tests/section1.ini	2013-01-19 19:00:45.000000000 +0200
+++ crudini-0.9.3/tests/section1.ini	2016-12-10 17:51:43.000000000 +0200
@@ -19,5 +19,5 @@
 empty = 
 python_interpolate = %(dup1)s/blah
 interpolate2 = ${dup1}/blah
-caps = not significant
+Caps = not significant
 combine = sections
diff -Nru crudini-0.7/tests/section1.lines crudini-0.9.3/tests/section1.lines
--- crudini-0.7/tests/section1.lines	2013-05-15 20:19:55.000000000 +0300
+++ crudini-0.9.3/tests/section1.lines	2016-12-10 17:52:30.000000000 +0200
@@ -16,5 +16,5 @@
 [ section1 ] empty
 [ section1 ] python_interpolate = %(dup1)s/blah
 [ section1 ] interpolate2 = ${dup1}/blah
-[ section1 ] caps = not significant
+[ section1 ] Caps = not significant
 [ section1 ] combine = sections
diff -Nru crudini-0.7/tests/section1.sh crudini-0.9.3/tests/section1.sh
--- crudini-0.7/tests/section1.sh	2013-01-19 19:01:04.000000000 +0200
+++ crudini-0.9.3/tests/section1.sh	2016-12-10 17:52:03.000000000 +0200
@@ -18,5 +18,5 @@
 empty=''
 python_interpolate='%(dup1)s/blah'
 interpolate2='${dup1}/blah'
-caps='not significant'
+Caps='not significant'
 combine=sections
diff -Nru crudini-0.7/tests/test.sh crudini-0.9.3/tests/test.sh
--- crudini-0.7/tests/test.sh	2015-02-06 20:10:24.000000000 +0200
+++ crudini-0.9.3/tests/test.sh	2019-01-01 17:06:31.000000000 +0200
@@ -1,7 +1,7 @@
 #!/bin/bash
 
 trap "exit 130" INT
-cleanup() { rm -f test.ini good.ini example.ini; exit; }
+cleanup() { rm -f err noequals*.ini test.ini ltest.ini good.ini example.ini; exit; }
 trap cleanup EXIT
 
 export PATH=..:$PATH
@@ -217,6 +217,13 @@
 test "$(crudini --get test.ini)" = 'section1' || fail
 ok
 
+# Ensure we handle comments correctly
+printf '%s\n' '[DEFAULT]' '#c1' ';c2' '%inc1' > test.ini
+test "$(crudini --get test.ini)" = '' || fail
+printf '%s\n' '[section1]' 'remote=1' > test.ini
+test "$(crudini --get test.ini 'section1')" = 'remote' || fail
+ok
+
 # missing bits
 :> test.ini
 crudini --get missing.ini 2>/dev/null && fail
@@ -465,3 +472,34 @@
 crudini --set file.conf '' option 2 || fail
 diff -u good.conf file.conf && ok || fail
 rm file.conf good.conf
+
+# ensure errors diagnosed correctly
+crudini --get example.ini 2>err | :
+! test -s err && ok || fail  #EPIPE ignored
+if test -e /dev/full; then
+crudini --get example.ini 2>err >/dev/full
+grep -q 'No space left' err && ok || fail
+fi
+
+# ensure symlinks handled correctly in file replace mode
+printf '%s\n' '[section]' 'param = value' > test.ini
+ln -s test.ini ltest.ini
+crudini --set ltest.ini section param newvalue || fail
+test "$(crudini --get test.ini section param)" = 'newvalue' && ok || fail
+crudini --output=ltest.ini --set ltest.ini section param newvalue2 || fail
+test "$(crudini --get test.ini section param)" = 'newvalue2' && ok || fail
+
+# Test single token parameters (without equals)
+cp ../noequals.ini .
+crudini --get noequals.ini >/dev/null && ok || fail
+cp noequals.ini noequals_new.ini
+printf '%s\n' 'new' 'new_equals = ' >> noequals_new.ini
+for param in param{1..3} colon{1..2} new; do
+ crudini --set noequals.ini noequals $param || fail
+done
+crudini --set noequals.ini noequals new_equals '' || fail
+diff -u noequals.ini noequals_new.ini && ok || fail
+
+# Test can read windows format files
+printf '%s\r\n' '' 'param = value' > test.ini
+crudini --get test.ini DEFAULT param > /dev/null || fail
diff -Nru crudini-0.7/TODO crudini-0.9.3/TODO
--- crudini-0.7/TODO	2015-02-06 15:26:13.000000000 +0200
+++ crudini-0.9.3/TODO	2016-12-13 14:27:00.000000000 +0200
@@ -1,5 +1,3 @@
-support variables without =
-
 support --set,--merge of #commented name=value
 with operation controlled with
   --with-comment=always
diff -Nru crudini-0.7/tox.ini crudini-0.9.3/tox.ini
--- crudini-0.7/tox.ini	2014-11-28 16:14:35.000000000 +0200
+++ crudini-0.9.3/tox.ini	2016-11-26 13:16:39.000000000 +0200
@@ -2,8 +2,8 @@
 envlist = py26,py27,pep8
 
 [testenv]
-deps=iniparse
-commands=/bin/bash -c 'cd tests && ./test.sh'
+deps = iniparse>=0.3.2
+commands = /bin/bash -c 'cd tests && ./test.sh'
 
 [testenv:pep8]
 deps = flake8

Reply via email to