Hi Bruno,

On 4/25/24 1:18 PM, Bruno Haible wrote:
> Yep, that's the right way to do it. Maybe file_name should come first
> (by the usual heuristic that the argument that shows most variation
> comes first)?

Sure, that is fine with me. I think the other way is based on how I
like to write C. Usually I like to place structures (or pointers to
them) first. The best example I can think of off the top of my head
is:

    int fprintf (FILE *stream, const char *format, ...);

vs.

   int fputs (const char *str, FILE *stream);

I always feel awkward using 'fputs' because of that. But that is a
different language so I can adapt. :)

How does this patch look? I'll wait for you to review it before
pushing any changes.

I ran all tests with Python 3.7 and they pass. I don't think
gnulib-tool --copy-file is tested there though. I tried to use gettext
since I remember autogen.sh does it a few times but I see the
following failure:

$ cd gettext
$ env GNULIB_TOOL_IMPL=sh+py sh -x ./autogen.sh
[...]
Copying file gnulib-lib/exitfail.c
1 out of 1 hunk FAILED -- saving rejects to file /tmp/glDMpS4d/exitfail.h.rej
/home/collin/.local/src/gnulib/gnulib-tool.sh: *** patch file 
gnulib-local/lib/exitfail.h.diff didn't apply cleanly
/home/collin/.local/src/gnulib/gnulib-tool.sh: *** Stop.
+ exit 1

I assume that is unrelated to these changes and will be an easy fix
for you. When I remove the diff everything passes with
GNULIB_TOOL_IMPL=sh+py.

Collin
From 95f83aabfcd2cd0624ae85357f7f40f34bb1a01b Mon Sep 17 00:00:00 2001
From: Collin Funk <collin.fu...@gmail.com>
Date: Thu, 25 Apr 2024 15:30:29 -0700
Subject: [PATCH] gnulib-tool.py: Reduce code duplication in file name
 transformations.

* pygnulib/functions.py: New file for shared functions between modules.
Add a function based on functions removed from GLImport and GLTestDir.
Accepts a single file name instead of a list.
* pygnulib/GLImport.py (GLImport.prepare): Use the new function.
(GLImport.rewrite_new_files, GLImport.rewrite_old_files): Remove
functions.
* pygnulib/GLTestDir.py (GLTestDir.execute): Use the new function.
(GLTestDir.rewrite_files): Remove functions.
* pygnulib/main.py (main): Remove unused function import. Use the new
function.
---
 ChangeLog             | 14 +++++++
 pygnulib/GLImport.py  | 97 +++++--------------------------------------
 pygnulib/GLTestDir.py | 55 +++++-------------------
 pygnulib/functions.py | 52 +++++++++++++++++++++++
 pygnulib/main.py      | 17 +-------
 5 files changed, 88 insertions(+), 147 deletions(-)
 create mode 100644 pygnulib/functions.py

diff --git a/ChangeLog b/ChangeLog
index abefd3bae2..276258b885 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,17 @@
+2024-04-25  Collin Funk  <collin.fu...@gmail.com>
+
+	gnulib-tool.py: Reduce code duplication in file name transformations.
+	* pygnulib/functions.py: New file for shared functions between modules.
+	Add a function based on functions removed from GLImport and GLTestDir.
+	Accepts a single file name instead of a list.
+	* pygnulib/GLImport.py (GLImport.prepare): Use the new function.
+	(GLImport.rewrite_new_files, GLImport.rewrite_old_files): Remove
+	functions.
+	* pygnulib/GLTestDir.py (GLTestDir.execute): Use the new function.
+	(GLTestDir.rewrite_files): Remove functions.
+	* pygnulib/main.py (main): Remove unused function import. Use the new
+	function.
+
 2024-04-25  Bruno Haible  <br...@clisp.org>
 
 	doc: Remove documentation of IRIX as supported platform.
diff --git a/pygnulib/GLImport.py b/pygnulib/GLImport.py
index cf1ebd10ec..47c0e83555 100644
--- a/pygnulib/GLImport.py
+++ b/pygnulib/GLImport.py
@@ -38,6 +38,7 @@
     relativize,
     rmtree,
 )
+from .functions import rewrite_file_name
 from .GLError import GLError
 from .GLConfig import GLConfig
 from .GLModuleSystem import GLModuleTable
@@ -303,80 +304,6 @@ def __repr__(self) -> str:
         result = '<pygnulib.GLImport %s>' % hex(id(self))
         return result
 
-    def rewrite_old_files(self, files: list[str]) -> list[str]:
-        '''Replace auxdir, docbase, sourcebase, m4base and testsbase from default
-        to their version from cached config.'''
-        if type(files) is not list:
-            raise TypeError('files argument must has list type, not %s'
-                            % type(files).__name__)
-        for file in files:
-            if type(file) is not str:
-                raise TypeError('each file must be a string instance')
-        files = sorted(set(files))
-        files = [ '%s%s' % (file, os.path.sep)
-                  for file in files ]
-        auxdir = self.cache['auxdir']
-        docbase = self.cache['docbase']
-        sourcebase = self.cache['sourcebase']
-        m4base = self.cache['m4base']
-        testsbase = self.cache['testsbase']
-        result = []
-        for file in files:
-            if file.startswith('build-aux/'):
-                path = substart('build-aux/', '%s/' % auxdir, file)
-            elif file.startswith('doc/'):
-                path = substart('doc/', '%s/' % docbase, file)
-            elif file.startswith('lib/'):
-                path = substart('lib/', '%s/' % sourcebase, file)
-            elif file.startswith('m4/'):
-                path = substart('m4/', '%s/' % m4base, file)
-            elif file.startswith('tests/'):
-                path = substart('tests/', '%s/' % testsbase, file)
-            elif file.startswith('tests=lib/'):
-                path = substart('tests=lib/', '%s/' % testsbase, file)
-            elif file.startswith('top/'):
-                path = substart('top/', '', file)
-            else:  # file is not a special file
-                path = file
-            result.append(os.path.normpath(path))
-        return sorted(set(result))
-
-    def rewrite_new_files(self, files: list[str]) -> list[str]:
-        '''Replace auxdir, docbase, sourcebase, m4base and testsbase from
-        default to their version from config.'''
-        if type(files) is not list:
-            raise TypeError('files argument must has list type, not %s'
-                            % type(files).__name__)
-        for file in files:
-            if type(file) is not str:
-                raise TypeError('each file must be a string instance')
-        files = sorted(set(files))
-        auxdir = self.config['auxdir']
-        docbase = self.config['docbase']
-        sourcebase = self.config['sourcebase']
-        m4base = self.config['m4base']
-        testsbase = self.config['testsbase']
-        result = []
-        for file in files:
-            if file.startswith('build-aux/'):
-                path = substart('build-aux/', '%s/' % auxdir, file)
-            elif file.startswith('doc/'):
-                path = substart('doc/', '%s/' % docbase, file)
-            elif file.startswith('lib/'):
-                path = substart('lib/', '%s/' % sourcebase, file)
-            elif file.startswith('m4/'):
-                path = substart('m4/', '%s/' % m4base, file)
-            elif file.startswith('tests/'):
-                path = substart('tests/', '%s/' % testsbase, file)
-            elif file.startswith('tests=lib/'):
-                path = substart('tests=lib/', '%s/' % testsbase, file)
-            elif file.startswith('top/'):
-                path = substart('top/', '', file)
-            else:  # file is not a special file
-                path = file
-            result.append(os.path.normpath(path))
-        return sorted(set(result))
-
     def actioncmd(self) -> str:
         '''Return command-line invocation comment.'''
         modules = self.config.getModules()
@@ -943,31 +870,27 @@ def prepare(self) -> tuple[GLFileTable, dict[str, tuple[re.Pattern, str] | None]
         # old_files is the list of files according to the last gnulib-tool invocation.
         # new_files is the list of files after this gnulib-tool invocation.
 
-        # Construct tables and transformers.
+        # Construct the transformers.
         transformers = dict()
         transformers['lib'] = sed_transform_lib_file
         transformers['aux'] = sed_transform_build_aux_file
         transformers['main'] = sed_transform_main_lib_file
         transformers['tests'] = sed_transform_testsrelated_lib_file
-        old_table = []
-        new_table = []
-        for src in old_files:
-            dest = self.rewrite_old_files([src])[-1]
-            old_table.append(tuple([dest, src]))
-        for src in new_files:
-            dest = self.rewrite_new_files([src])[-1]
-            new_table.append(tuple([dest, src]))
-        old_table = sorted(set(old_table))
-        new_table = sorted(set(new_table))
+
         # old_table is a table with two columns: (rewritten-file-name original-file-name),
         # representing the files according to the last gnulib-tool invocation.
+        old_table = { (rewrite_file_name(file_name, self.cache, True), file_name)
+                      for file_name in old_files }
+
         # new_table is a table with two columns: (rewritten-file-name original-file-name),
         # representing the files after this gnulib-tool invocation.
+        new_table = { (rewrite_file_name(file_name, self.config, True), file_name)
+                      for file_name in new_files }
 
         # Prepare the filetable.
         filetable = GLFileTable(sorted(set(filelist)))
-        filetable.old_files = sorted(set(old_table), key=lambda pair: pair[0])
-        filetable.new_files = sorted(set(new_table), key=lambda pair: pair[0])
+        filetable.old_files = sorted(old_table, key=lambda pair: pair[0])
+        filetable.new_files = sorted(new_table, key=lambda pair: pair[0])
 
         # Return the result.
         result = tuple([filetable, transformers])
diff --git a/pygnulib/GLTestDir.py b/pygnulib/GLTestDir.py
index 0ac2f32f69..8534e19254 100644
--- a/pygnulib/GLTestDir.py
+++ b/pygnulib/GLTestDir.py
@@ -42,6 +42,7 @@
     relinverse,
     rmtree,
 )
+from .functions import rewrite_file_name
 from .enums import CopyAction
 from .GLError import GLError
 from .GLConfig import GLConfig
@@ -122,42 +123,6 @@ def __init__(self, config: GLConfig, testdir: str) -> None:
         self.config.resetWitnessCMacro()
         self.config.resetVCFiles()
 
-    def rewrite_files(self, files: list[str]) -> list[str]:
-        '''Replace auxdir, docbase, sourcebase, m4base and testsbase from
-        default to their version from config.'''
-        if type(files) is not list:
-            raise TypeError('files argument must have list type, not %s'
-                            % type(files).__name__)
-        for file in files:
-            if type(file) is not str:
-                raise TypeError('each file must be a string instance')
-        files = sorted(set(files))
-        auxdir = self.config['auxdir']
-        docbase = self.config['docbase']
-        sourcebase = self.config['sourcebase']
-        m4base = self.config['m4base']
-        testsbase = self.config['testsbase']
-        result = []
-        for file in files:
-            if file.startswith('build-aux/'):
-                path = substart('build-aux/', '%s/' % auxdir, file)
-            elif file.startswith('doc/'):
-                path = substart('doc/', '%s/' % docbase, file)
-            elif file.startswith('lib/'):
-                path = substart('lib/', '%s/' % sourcebase, file)
-            elif file.startswith('m4/'):
-                path = substart('m4/', '%s/' % m4base, file)
-            elif file.startswith('tests/'):
-                path = substart('tests/', '%s/' % testsbase, file)
-            elif file.startswith('tests=lib/'):
-                path = substart('tests=lib/', '%s/' % testsbase, file)
-            elif file.startswith('top/'):
-                path = substart('top/', '', file)
-            else:  # file is not a special file
-                path = file
-            result.append(os.path.normpath(path))
-        return sorted(set(result))
-
     def execute(self) -> None:
         '''Create a scratch package with the given modules.'''
         auxdir = self.config['auxdir']
@@ -339,24 +304,24 @@ def execute(self) -> None:
         # "automake --add-missing --copy" would provide.
         filelist = sorted(set(filelist + ['build-aux/config.guess', 'build-aux/config.sub']))
 
+        # new_table is a table with two columns: (rewritten-file-name original-file-name),
+        # representing the files after this gnulib-tool invocation.
+        new_table = { (rewrite_file_name(file_name, self.config, True), file_name)
+                      for file_name in filelist }
+
         # Setup the file table.
         filetable = GLFileTable(filelist)
+        filetable.new_files = sorted(new_table, key=lambda pair: pair[0])
 
         # Create directories.
-        directories = [ joinpath(self.testdir, os.path.dirname(file))
-                        for file in self.rewrite_files(filetable.all_files) ]
-        directories = sorted(set(directories))
+        directories = sorted({ joinpath(self.testdir, os.path.dirname(pair[0]))
+                               for pair in filetable.new_files })
         for directory in directories:
             if not os.path.isdir(directory):
                 os.makedirs(directory)
 
         # Copy files or make symbolic links or hard links.
-        for src in filetable.all_files:
-            dest = self.rewrite_files([src])[-1]
-            filetable.new_files.append(tuple([dest, src]))
-        for row in filetable.new_files:
-            src = row[1]
-            dest = row[0]
+        for (dest, src) in filetable.new_files:
             destpath = joinpath(self.testdir, dest)
             if src.startswith('tests=lib/'):
                 src = substart('tests=lib/', 'lib/', src)
diff --git a/pygnulib/functions.py b/pygnulib/functions.py
new file mode 100644
index 0000000000..9459d9c1f8
--- /dev/null
+++ b/pygnulib/functions.py
@@ -0,0 +1,52 @@
+# Copyright (C) 2002-2024 Free Software Foundation, Inc.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+from __future__ import annotations
+
+import os.path
+from .constants import substart
+from .GLConfig import GLConfig
+
+
+def rewrite_file_name(file_name: str, config: GLConfig, also_tests_lib: bool = False) -> str:
+    '''Replace auxdir, docbase, sourcebase, m4base and testsbase from their
+    default to the ones specified in a GLConfig object.
+    - file_name, the file name to transform,
+    - config, the GLConfig storing directory names,
+    - also_tests_lib, True if 'tests=lib/' should be replaced.'''
+    if type(file_name) is not str:
+        raise TypeError(f'file_name must be a str type, not {type(file_name).__name__}')
+    auxdir = config['auxdir']
+    docbase = config['docbase']
+    sourcebase = config['sourcebase']
+    m4base = config['m4base']
+    testsbase = config['testsbase']
+    if file_name.startswith('build-aux/'):
+        result = substart('build-aux/', '%s/' % auxdir, file_name)
+    elif file_name.startswith('doc/'):
+        result = substart('doc/', '%s/' % docbase, file_name)
+    elif file_name.startswith('lib/'):
+        result = substart('lib/', '%s/' % sourcebase, file_name)
+    elif file_name.startswith('m4/'):
+        result = substart('m4/', '%s/' % m4base, file_name)
+    elif file_name.startswith('tests/'):
+        result = substart('tests/', '%s/' % testsbase, file_name)
+    elif also_tests_lib and file_name.startswith('tests=lib/'):
+        result = substart('tests=lib/', '%s/' % testsbase, file_name)
+    elif file_name.startswith('top/'):
+        result = substart('top/', '', file_name)
+    else:  # file is not a special file
+        result = file_name
+    return os.path.normpath(result)
diff --git a/pygnulib/main.py b/pygnulib/main.py
index ea781f5a97..3230c45a31 100644
--- a/pygnulib/main.py
+++ b/pygnulib/main.py
@@ -96,12 +96,12 @@
     lines_to_multiline,
     ensure_writable,
     copyfile,
-    substart,
     force_output,
     init_DIRS,
     rmtree,
     DEFAULT_AUTOCONF_MINVERSION,
 )
+from pygnulib.functions import rewrite_file_name
 from pygnulib.enums import CopyAction
 from pygnulib.GLConfig import GLConfig
 from pygnulib.GLError import GLError
@@ -1309,20 +1309,7 @@ def main() -> None:
         lookedup, flag = filesystem.lookup(srcpath)
         if os.path.isdir(dest):
             destdir = dest
-            if srcpath.startswith('build-aux/'):
-                destpath = substart('build-aux/', '%s/' % auxdir, srcpath)
-            elif srcpath.startswith('doc/'):
-                destpath = substart('doc/', '%s/' % docbase, srcpath)
-            elif srcpath.startswith('lib/'):
-                destpath = substart('lib/', '%s/' % sourcebase, srcpath)
-            elif srcpath.startswith('m4/'):
-                destpath = substart('m4/', '%s/' % m4base, srcpath)
-            elif srcpath.startswith('tests/'):
-                destpath = substart('tests/', '%s/' % testsbase, srcpath)
-            elif srcpath.startswith('top/'):
-                destpath = substart('top/', '', srcpath)
-            else:  # either case
-                destpath = srcpath
+            destpath = rewrite_file_name(srcpath, config)
         else:  # if not os.path.isdir(dest)
             destdir = os.path.dirname(dest)
             destpath = os.path.basename(dest)
-- 
2.44.0

Reply via email to