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: