Collin Funk wrote:
> > FWIW '\n'.join([]) is '', so perhaps it isn't necessary to check
> > len(cleansed).
> 
> Oops, you are correct. Thanks, that should make the code look nicer. I
> think I originally had '\n'.join(cleansed) + '\n', where the goal was
> to remove the + '\n' but I removed it. Good catch.

This is the second time we have to deal with this unlucky '\n'.join(...)
idiom.

It is not good if, each time we deal with a list of lines, we have to give
extra thought to the case of 0 lines. This is only a recipe for inconsistencies.

Instead, I'm applying this patch, eliminating all uses of this unlucky
idiom.


2024-03-17  Bruno Haible  <br...@clisp.org>

        gnulib-tool.py: Handle empty lists of lines consistently.
        * pygnulib/constants.py (lines_to_multiline): New function.
        (nlremove): Remove unused function.
        * pygnulib/GLEmiter.py (_eliminate_NMD): Use lines_to_multiline instead
        of the '\n'.join idiom.
        (GLEmiter.autoconfSnippet, GLEmiter.autoconfSnippets,
        GLEmiter.lib_Makefile_am): Likewise.
        * pygnulib/GLImport.py (GLImport._update_ignorelist_, GLImport.execute):
        Likewise.
        * pygnulib/GLModuleSystem.py (GLModule.getDependenciesRecursively,
        GLModule.getLinkDirectiveRecursively, GLModuleTable.remove_if_blocks):
        Likewise.
        * pygnulib/GLTestDir.py (GLTestDir.execute): Likewise.
        * pygnulib/main.py (main): Likewise.

diff --git a/pygnulib/GLEmiter.py b/pygnulib/GLEmiter.py
index a988f9f51f..97a5b5f62e 100644
--- a/pygnulib/GLEmiter.py
+++ b/pygnulib/GLEmiter.py
@@ -49,6 +49,7 @@ UTILS = constants.UTILS
 TESTS = constants.TESTS
 joinpath = constants.joinpath
 relinverse = constants.relinverse
+lines_to_multiline = constants.lines_to_multiline
 isfile = os.path.isfile
 normpath = os.path.normpath
 
@@ -104,10 +105,7 @@ def _eliminate_NMD(snippet: str, automake_subdir: bool) -> 
str:
         line = _eliminate_NMD_from_line(line, automake_subdir)
         if line != None:
             result.append(line)
-    if len(result) > 0:
-        return '\n'.join(result) + '\n'
-    else:
-        return ''
+    return lines_to_multiline(result)
 
 
 
#===============================================================================
@@ -230,7 +228,7 @@ class GLEmiter(object):
             lines = [ line
                       for line in snippet.split('\n')
                       if line.strip() ]
-            snippet = '%s\n' % '\n'.join(lines)
+            snippet = lines_to_multiline(lines)
             pattern = re.compile('^(.*)$', re.M)
             snippet = pattern.sub('%s\\1' % indentation, snippet)
             if disable_libtool:
@@ -258,7 +256,7 @@ class GLEmiter(object):
         lines = [ line
                   for line in emit.split('\n')
                   if line.strip() ]
-        emit = '%s\n' % '\n'.join(lines)
+        emit = lines_to_multiline(lines)
         return emit
 
     def autoconfSnippets(self, modules, referenceable_modules, moduletable,
@@ -441,7 +439,7 @@ class GLEmiter(object):
         lines = [ line
                   for line in emit.split('\n')
                   if line.strip() ]
-        emit = '%s\n' % '\n'.join(lines)
+        emit = lines_to_multiline(lines)
         return emit
 
     def preEarlyMacros(self, require, indentation, modules):
@@ -908,9 +906,8 @@ AC_DEFUN([%V1%_LIBSOURCES], [
                             capture_output=True)
             if result.returncode == 0:
                 # sort -u
-                emit += '\n'.join(sorted(list(set(x.strip() for x in
-                                                  
result.stdout.decode(encoding='utf-8').splitlines()))))
-                emit += '\n'
+                emit += lines_to_multiline(sorted(list(set(x.strip()
+                                                           for x in 
result.stdout.decode(encoding='utf-8').splitlines()))))
             else:
                 emit += '== gnulib-tool GNU Make output failed as follows ==\n'
                 emit += ['# stderr: ' + x + '\n' for x in
diff --git a/pygnulib/GLImport.py b/pygnulib/GLImport.py
index c06f0f9f6b..5ab314af36 100644
--- a/pygnulib/GLImport.py
+++ b/pygnulib/GLImport.py
@@ -51,6 +51,7 @@ cleaner = constants.cleaner
 copyfile = constants.copyfile
 copyfile2 = constants.copyfile2
 movefile = constants.movefile
+lines_to_multiline = constants.lines_to_multiline
 isabs = os.path.isabs
 isdir = os.path.isdir
 isfile = os.path.isfile
@@ -803,7 +804,7 @@ AC_DEFUN([%s_FILE_LIST], [\n''' % macro_prefix
                 dirs_ignore = [ line
                                 for line in dirs_ignore
                                 if line.strip() ]
-                srcdata = '\n'.join(sorted(set(dirs_ignore))).strip()
+                srcdata = lines_to_multiline(sorted(set(dirs_ignore)))
                 dirs_ignore += [ d
                                  for d in dirs_added
                                  if d not in dirs_ignore ]
@@ -813,7 +814,7 @@ AC_DEFUN([%s_FILE_LIST], [\n''' % macro_prefix
                 dirs_ignore = [ '%s%s' % (anchor, d)
                                 for d in dirs_ignore ]
                 dirs_ignore = sorted(set(dirs_ignore))
-                destdata = '\n'.join(sorted(set(dirs_ignore))).strip()
+                destdata = lines_to_multiline(sorted(set(dirs_ignore)))
                 if srcdata != destdata:
                     if not self.config['dryrun']:
                         print('Updating %s (backup in %s)' % (srcpath, 
backupname))
@@ -833,8 +834,7 @@ AC_DEFUN([%s_FILE_LIST], [\n''' % macro_prefix
                     if ignore == '.cvsignore':
                         dirs_added = ['.deps', '.dirstamp'] + dirs_added
                     with codecs.open(joinpath(destdir, srcpath), 'wb', 
'UTF-8') as file:
-                        file.write('\n'.join(dirs_added))
-                        file.write('\n')
+                        file.write(lines_to_multiline(dirs_added))
                 else:  # if self.config['dryrun']
                     print('Create %s' % srcpath)
 
@@ -1297,7 +1297,7 @@ AC_DEFUN([%s_FILE_LIST], [\n''' % macro_prefix
                 data = '# Set of available languages.\n'
                 files = [ constants.subend('.po', '', file)
                           for file in os.listdir(joinpath(destdir, pobase)) ]
-                data += '\n'.join(files)
+                data += lines_to_multiline(files)
                 with codecs.open(tmpfile, 'wb', 'UTF-8') as file:
                     file.write(data)
                 filename, backup, flag = self.assistant.super_update(basename, 
tmpfile)
diff --git a/pygnulib/GLModuleSystem.py b/pygnulib/GLModuleSystem.py
index 55b479e096..13d03d3af8 100644
--- a/pygnulib/GLModuleSystem.py
+++ b/pygnulib/GLModuleSystem.py
@@ -44,6 +44,7 @@ ENCS = constants.ENCS
 TESTS = constants.TESTS
 joinpath = constants.joinpath
 subend = constants.subend
+lines_to_multiline = constants.lines_to_multiline
 isdir = os.path.isdir
 isfile = os.path.isfile
 filter_filelist = constants.filter_filelist
@@ -352,12 +353,9 @@ class GLModule(object):
             # Remove handledmodules from inmodules.
             inmodules = inmodules.difference(handledmodules)
 
-        if len(outmodules) > 0:
-            module_names = sorted([ str(module)
-                                    for module in outmodules ])
-            return '\n'.join(module_names) + '\n'
-        else:
-            return ''
+        module_names = sorted([ str(module)
+                                for module in outmodules ])
+        return lines_to_multiline(module_names)
 
     def getLinkDirectiveRecursively(self) -> str:
         '''Return a list of the link directives of this module separated
@@ -386,17 +384,14 @@ class GLModule(object):
             # Remove handledmodules from inmodules.
             inmodules = inmodules.difference(handledmodules)
 
-        if len(outmodules) > 0:
-            # Remove whitespace from sections.
-            link_sections = [ module.getLink().strip()
-                              for module in outmodules ]
-            # Sort the link directives.
-            directives = sorted([ line
-                                  for section in link_sections
-                                  for line in section.splitlines() ])
-            return '\n'.join(directives) + '\n'
-        else:
-            return ''
+        # Remove whitespace from sections.
+        link_sections = [ module.getLink().strip()
+                          for module in outmodules ]
+        # Sort the link directives.
+        directives = sorted([ line
+                              for section in link_sections
+                              for line in section.splitlines() ])
+        return lines_to_multiline(directives)
 
     def getShellFunc(self):
         '''GLModule.getShellFunc() -> str
@@ -1064,10 +1059,7 @@ class GLModuleTable(object):
                 cleansed.append(line[5:])
             elif depth == 0:
                 cleansed.append(line)
-        if len(cleansed) > 0:
-            return '\n'.join(cleansed)
-        else:
-            return ''
+        return lines_to_multiline(cleansed)
 
     def add_dummy(self, modules):
         '''GLModuleTable.add_dummy(modules) -> list
diff --git a/pygnulib/GLTestDir.py b/pygnulib/GLTestDir.py
index 156af057b5..e33319b575 100644
--- a/pygnulib/GLTestDir.py
+++ b/pygnulib/GLTestDir.py
@@ -54,6 +54,7 @@ relinverse = constants.relinverse
 copyfile = constants.copyfile
 ensure_writable = constants.ensure_writable
 movefile = constants.movefile
+lines_to_multiline = constants.lines_to_multiline
 isdir = os.path.isdir
 isfile = os.path.isfile
 normpath = os.path.normpath
@@ -461,7 +462,7 @@ class GLTestDir(object):
                         lines = [ line
                                   for line in snippet.split('\n')
                                   if line.strip() ]
-                        snippet = '\n'.join(lines)
+                        snippet = lines_to_multiline(lines)
                         pattern = re.compile('AC_REQUIRE\\(\\[([^()]*)\\]\\)', 
re.M)
                         snippet = pattern.sub('\\1', snippet)
                         snippet = snippet.strip()
@@ -469,7 +470,7 @@ class GLTestDir(object):
                 snippets = [ snippet
                              for snippet in snippets
                              if snippet.strip()]
-                emit += '%s\n' % '\n'.join(snippets)
+                emit += lines_to_multiline(snippets)
                 if libtool:
                     emit += 'LT_INIT([win32-dll])\n'
                     emit += 'LT_LANG([C++])\n'
@@ -578,7 +579,7 @@ class GLTestDir(object):
                 lines = [ line
                           for line in snippet.split('\n')
                           if line.strip() ]
-                snippet = '\n'.join(lines)
+                snippet = lines_to_multiline(lines)
                 pattern = re.compile('AC_REQUIRE\\(\\[([^()]*)\\]\\)', re.M)
                 snippet = pattern.sub('\\1', snippet)
                 snippet = snippet.strip()
@@ -586,7 +587,7 @@ class GLTestDir(object):
         snippets = [ snippet
                      for snippet in snippets
                      if snippet.strip() ]
-        emit += '%s\n' % '\n'.join(snippets)
+        emit += lines_to_multiline(snippets)
         if libtool:
             emit += 'LT_INIT([win32-dll])\n'
             emit += 'LT_LANG([C++])\n'
diff --git a/pygnulib/constants.py b/pygnulib/constants.py
index 047ecf6e6f..b7434155d8 100644
--- a/pygnulib/constants.py
+++ b/pygnulib/constants.py
@@ -465,6 +465,17 @@ def filter_filelist(separator, filelist,
     return result
 
 
+def lines_to_multiline(lines):
+    '''lines_to_multiline(List[str]) -> str
+
+    Combine the lines to a single string, terminating each line with a newline
+    character.'''
+    if len(lines) > 0:
+        return '\n'.join(lines) + '\n'
+    else:
+        return ''
+
+
 def substart(orig, repl, data):
     '''Replaces the start portion of a string.
 
@@ -496,18 +507,6 @@ def nlconvert(text):
     return text
 
 
-def nlremove(text):
-    '''Remove empty lines from the source text.'''
-    text = nlconvert(text)
-    text = text.replace('\r\n', '\n')
-    lines = [ line
-              for line in text.split('\n')
-              if line != '' ]
-    text = '\n'.join(lines)
-    text = nlconvert(text)
-    return text
-
-
 def remove_trailing_slashes(text):
     '''Remove trailing slashes from a file name, except when the file name
     consists only of slashes.'''
diff --git a/pygnulib/main.py b/pygnulib/main.py
index cf137ab616..e4499400c6 100644
--- a/pygnulib/main.py
+++ b/pygnulib/main.py
@@ -81,6 +81,7 @@ TESTS = constants.TESTS
 joinpath = constants.joinpath
 copyfile = constants.copyfile
 ensure_writable = constants.ensure_writable
+lines_to_multiline = constants.lines_to_multiline
 isabs = os.path.isabs
 isdir = os.path.isdir
 isfile = os.path.isfile
@@ -814,9 +815,9 @@ def main():
     if mode == 'list':
         modulesystem = classes.GLModuleSystem(config)
         listing = modulesystem.list()
-        result = '\n'.join(listing)
+        result = lines_to_multiline(listing)
         os.rmdir(config['tempdir'])
-        print(result)
+        print(result, end='')
 
     elif mode == 'find':
         modulesystem = classes.GLModuleSystem(config)
@@ -1124,7 +1125,7 @@ def main():
             module = modulesystem.find(name)
             if module:
                 files = module.getFiles()
-                print('\n'.join(files))
+                print(lines_to_multiline(files), end='')
 
     elif mode == 'extract-dependencies':
         if avoids:




Reply via email to