I've applied the following patch adding a GLFileTable class as
discussed here:
https://lists.gnu.org/archive/html/bug-gnulib/2024-04/msg00357.html
For the most part this just changes a dictionary in GLImport to an
actual class. So field initialization is done in GLFileTable.__init__()
and uses are changed like this:
filetable['all'] -> filetable.all_files
filetable['old'] -> filetable.old_files
This is beneficial since the fields can be individually typed. Since
old_files is a list[tuple[str, str]] where the tuple is:
(rewritten-file-name, old-file-name)
# for example, using gl_M4_BASE([glm4])
('glm4/gnulib-cache.m4', 'm4/gnulib-cache.m4')
while all_files only contains 'm4/gnulib-cache.m4'.
I've also used this class in GLTestDir since there are obvious ways to
reduce code duplication there. Specifically the rewrite_filename
stuff in GLImport.prepare() and GLTestDir.execute().
Since I noticed some discussion about bootstrapping, I have focused a
bit more on Python version compatibility. Just so that I have *some*
idea of what will need to be changed if there is ever a need.
+class GLFileTable:
+ '''The GLFileTable class stores file information for the duration of the
+ operation being executed.'''
+
+ all_files: list[str]
+ old_files: list[tuple[str, str]]
+ new_files: list[tuple[str, str]]
+ added_files: list[str]
+ removed_files: list[str]
This syntax was introduced in Python 3.6 which is okay for our
dependency on Python 3.7. For older versions they can be removed or be
placed in comments [1]. I've gone with it since I imagine there will
be bigger fish to fry by lowering the version dependency.
[1] https://peps.python.org/pep-0484/#type-comments
CollinFrom 962397de1dc64f44c5736be14efd9ffb95c93c65 Mon Sep 17 00:00:00 2001
From: Collin Funk <[email protected]>
Date: Wed, 24 Apr 2024 16:04:25 -0700
Subject: [PATCH] gnulib-tool.py: Add a new GLFileTable class.
* pygnulib/GLFileTable.py: New file. Define the GLFileTable class with
five attributes which can be individually typed.
* pygnulib/GLTestDir.py (GLTestDir.execute): Use the GLFileTable class.
* pygnulib/GLImport.py (GLImport.gnulib_comp, GLImport.prepare)
(GLImport.execute): Likewise. Update type hints and doc strings.
---
ChangeLog | 9 ++++
pygnulib/GLFileTable.py | 36 ++++++++++++++
pygnulib/GLImport.py | 102 +++++++++++++++++++---------------------
pygnulib/GLTestDir.py | 18 +++----
4 files changed, 103 insertions(+), 62 deletions(-)
create mode 100644 pygnulib/GLFileTable.py
diff --git a/ChangeLog b/ChangeLog
index 676aaef0be..ee17881d2a 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,12 @@
+2024-04-24 Collin Funk <[email protected]>
+
+ gnulib-tool.py: Add a new GLFileTable class.
+ * pygnulib/GLFileTable.py: New file. Define the GLFileTable class with
+ five attributes which can be individually typed.
+ * pygnulib/GLTestDir.py (GLTestDir.execute): Use the GLFileTable class.
+ * pygnulib/GLImport.py (GLImport.gnulib_comp, GLImport.prepare)
+ (GLImport.execute): Likewise. Update type hints and doc strings.
+
2024-04-24 Paul Eggert <[email protected]>
largefile: port to C++
diff --git a/pygnulib/GLFileTable.py b/pygnulib/GLFileTable.py
new file mode 100644
index 0000000000..6180e0d318
--- /dev/null
+++ b/pygnulib/GLFileTable.py
@@ -0,0 +1,36 @@
+# 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
+
+
+class GLFileTable:
+ '''The GLFileTable class stores file information for the duration of the
+ operation being executed.'''
+
+ all_files: list[str]
+ old_files: list[tuple[str, str]]
+ new_files: list[tuple[str, str]]
+ added_files: list[str]
+ removed_files: list[str]
+
+ def __init__(self, all_files: list[str]) -> None:
+ '''Create a GLFileTable with initialized fields.
+ - all_files, a list of all files being operated on.'''
+ self.all_files = all_files
+ self.old_files = []
+ self.new_files = []
+ self.added_files = []
+ self.removed_files = []
diff --git a/pygnulib/GLImport.py b/pygnulib/GLImport.py
index 0bab7ca150..cf1ebd10ec 100644
--- a/pygnulib/GLImport.py
+++ b/pygnulib/GLImport.py
@@ -46,6 +46,7 @@
from .GLFileSystem import GLFileAssistant
from .GLMakefileTable import GLMakefileTable
from .GLEmiter import GLEmiter
+from .GLFileTable import GLFileTable
#===============================================================================
@@ -588,17 +589,17 @@ def gnulib_cache(self) -> str:
emit += 'gl_VC_FILES([%s])\n' % str(vc_files).lower()
return emit
- def gnulib_comp(self, filetable: dict[str, list[str]], gentests: bool) -> str:
+ def gnulib_comp(self, filetable: GLFileTable, gentests: bool) -> str:
'''Emit the contents of generated $m4base/gnulib-comp.m4 file.
GLConfig: destdir, localpath, tests, sourcebase, m4base, pobase, docbase,
testsbase, conddeps, libtool, macro_prefix, podomain, vc_files.
- filetable is a dictionary with a category used as a key to access
- a list of files.
+ filetable is a GLFileTable containing file information for this
+ import.
gentests is True if a tests Makefile.am is being generated, False
otherwise.'''
- if type(filetable) is not dict:
- raise TypeError(f'filetable should be a dict, not {type(filetable).__name__}')
+ if type(filetable) is not GLFileTable:
+ raise TypeError(f'filetable should be a GLFileTable, not {type(filetable).__name__}')
if type(gentests) is not bool:
raise TypeError(f'gentests should be a bool, not {type(gentests).__name__}')
emit = ''
@@ -655,7 +656,8 @@ def gnulib_comp(self, filetable: dict[str, list[str]], gentests: bool) -> str:
# _AC_LIBOBJ_ALLOCA, invoked from AC_FUNC_ALLOCA.
# All the m4_pushdef/m4_popdef logic in func_emit_initmacro_start/_end
# does not help to avoid this error.
- newfile_set = {x[1] for x in filetable['new']}
+ newfile_set = { pair[1]
+ for pair in filetable.new_files }
if 'lib/alloca.c' in newfile_set:
emit += ' AC_CONFIG_LIBOBJ_DIR([%s])\n' % sourcebase
elif 'tests=lib/alloca.c' in newfile_set:
@@ -726,7 +728,7 @@ def gnulib_comp(self, filetable: dict[str, list[str]], gentests: bool) -> str:
# This macro records the list of files which have been installed by
# gnulib-tool and may be removed by future gnulib-tool invocations.
AC_DEFUN([%s_FILE_LIST], [\n''' % macro_prefix
- emit += ' %s\n' % '\n '.join(filetable['all'])
+ emit += ' %s\n' % '\n '.join(filetable.all_files)
emit += '])\n'
return emit
@@ -802,7 +804,7 @@ def _update_ignorelist_(self, directory: str, ignore: str, files_added: list[str
else: # if self.config['dryrun']
print('Create %s' % srcpath)
- def prepare(self) -> tuple[dict[str, list[str]], dict[str, tuple[re.Pattern, str] | None]]:
+ def prepare(self) -> tuple[GLFileTable, dict[str, tuple[re.Pattern, str] | None]]:
'''Perform preperations before GLImport.execute().
Returns a filetable and the transformers passed to GLFileAssistant().'''
destdir = self.config['destdir']
@@ -963,29 +965,21 @@ def prepare(self) -> tuple[dict[str, list[str]], dict[str, tuple[re.Pattern, str
# representing the files after this gnulib-tool invocation.
# Prepare the filetable.
- filetable = dict()
- filetable['all'] = sorted(set(filelist))
- filetable['old'] = \
- sorted(set(old_table), key=lambda pair: pair[0])
- filetable['new'] = \
- sorted(set(new_table), key=lambda pair: pair[0])
- filetable['added'] = []
- filetable['removed'] = []
+ 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])
# Return the result.
result = tuple([filetable, transformers])
return result
- def execute(self, filetable: dict[str, list[str]], transformers: dict[str, tuple[re.Pattern, str] | None]) -> None:
+ def execute(self, filetable: GLFileTable, transformers: dict[str, tuple[re.Pattern, str] | None]) -> None:
'''Perform operations on the lists of files, which are given in a special
format except filelist argument. Such lists of files can be created using
GLImport.prepare() function.'''
- if type(filetable) is not dict:
- raise TypeError('filetable must be a dict, not %s'
+ if type(filetable) is not GLFileTable:
+ raise TypeError('filetable must be a GLFileTable, not %s'
% type(filetable).__name__)
- for key in ['all', 'old', 'new', 'added', 'removed']:
- if key not in filetable:
- raise KeyError('filetable must contain key %s' % repr(key))
destdir = self.config['destdir']
auxdir = self.config['auxdir']
sourcebase = self.config['sourcebase']
@@ -1004,7 +998,7 @@ def execute(self, filetable: dict[str, list[str]], transformers: dict[str, tuple
# Determine whether to put anything into $testsbase.
testsfiles = [ file
- for file in filetable['all']
+ for file in filetable.all_files
if file.startswith('tests/') or file.startswith('tests=lib/') ]
gentests = len(testsfiles) > 0
@@ -1013,14 +1007,14 @@ def execute(self, filetable: dict[str, list[str]], transformers: dict[str, tuple
if pobase:
dirs.append(pobase)
if [ file
- for file in filetable['all']
+ for file in filetable.all_files
if file.startswith('doc/') ]:
dirs.append(docbase)
if gentests:
dirs.append(testsbase)
dirs.append(auxdir)
dirs += sorted([ os.path.dirname(pair[0])
- for pair in filetable['new'] ])
+ for pair in filetable.new_files ])
dirs = [ os.path.join(destdir, d)
for d in dirs ]
for directory in dirs:
@@ -1037,17 +1031,17 @@ def execute(self, filetable: dict[str, list[str]], transformers: dict[str, tuple
# Create GLFileAssistant instance to process files.
assistant = GLFileAssistant(self.config, transformers)
- # Set of rewritten-file-names from filetable['old'].
+ # Set of rewritten-file-names from filetable.old_files.
old_rewritten_files = { pair[0]
- for pair in filetable['old'] }
- # Set of rewritten-file-names from filetable['new'].
+ for pair in filetable.old_files }
+ # Set of rewritten-file-names from filetable.new_files.
new_rewritten_files = { pair[0]
- for pair in filetable['new'] }
+ for pair in filetable.new_files }
- # Files which are in filetable['old'] and not in filetable['new'].
- # They will be removed and added to filetable['removed'] list.
+ # Files which are in filetable.old_files and not in filetable.new_files.
+ # They will be removed and added to filetable.removed_files list.
pairs = [ pair
- for pair in filetable['old']
+ for pair in filetable.old_files
if pair[0] not in new_rewritten_files ]
pairs = sorted(set(pairs), key=lambda pair: pair[0])
files = sorted(set(pair[0] for pair in pairs))
@@ -1065,13 +1059,13 @@ def execute(self, filetable: dict[str, list[str]], transformers: dict[str, tuple
raise GLError(14, file) from exc
else: # if self.config['dryrun']
print('Remove file %s (backup in %s~)' % (path, path))
- filetable['removed'].append(file)
+ filetable.removed_files.append(file)
- # Files which are in filetable['new'] and not in filetable['old'].
- # They will be added/updated and added to filetable['added'] list.
+ # Files which are in filetable.new_files and not in filetable.old_files.
+ # They will be added/updated and added to filetable.added_files list.
already_present = False
pairs = [ pair
- for pair in filetable['new']
+ for pair in filetable.new_files
if pair[0] not in old_rewritten_files ]
pairs = sorted(set(pairs))
for pair in pairs:
@@ -1081,11 +1075,11 @@ def execute(self, filetable: dict[str, list[str]], transformers: dict[str, tuple
assistant.setRewritten(rewritten)
assistant.add_or_update(already_present)
- # Files which are in filetable['new'] and in filetable['old'].
- # They will be added/updated and added to filetable['added'] list.
+ # Files which are in filetable.new_files and in filetable.old_files.
+ # They will be added/updated and added to filetable.added_files list.
already_present = True
pairs = [ pair
- for pair in filetable['new']
+ for pair in filetable.new_files
if pair[0] in old_rewritten_files ]
pairs = sorted(set(pairs))
for pair in pairs:
@@ -1095,9 +1089,9 @@ def execute(self, filetable: dict[str, list[str]], transformers: dict[str, tuple
assistant.setRewritten(rewritten)
assistant.add_or_update(already_present)
- # Add files which were added to the list of filetable['added'].
- filetable['added'] += assistant.getFiles()
- filetable['added'] = sorted(set(filetable['added']))
+ # Add files which were added to the list of filetable.added_files.
+ filetable.added_files += assistant.getFiles()
+ filetable.added_files = sorted(set(filetable.added_files))
# Default the source makefile name to Makefile.am.
if makefile_name:
@@ -1153,7 +1147,7 @@ def execute(self, filetable: dict[str, list[str]], transformers: dict[str, tuple
print('Creating %s' % filename)
else: # if self.config['dryrun']:
print('Create %s' % filename)
- filetable['added'].append(filename)
+ filetable.added_files.append(filename)
if os.path.isfile(tmpfile):
os.remove(tmpfile)
@@ -1174,7 +1168,7 @@ def execute(self, filetable: dict[str, list[str]], transformers: dict[str, tuple
print('Creating %s' % filename)
else: # if self.config['dryrun']:
print('Create %s' % filename)
- filetable['added'].append(filename)
+ filetable.added_files.append(filename)
if os.path.isfile(tmpfile):
os.remove(tmpfile)
@@ -1182,7 +1176,7 @@ def execute(self, filetable: dict[str, list[str]], transformers: dict[str, tuple
basename = joinpath(pobase, 'POTFILES.in')
tmpfile = assistant.tmpfilename(basename)
with open(tmpfile, mode='w', newline='\n', encoding='utf-8') as file:
- file.write(self.emitter.po_POTFILES_in(filetable['all']))
+ file.write(self.emitter.po_POTFILES_in(filetable.all_files))
basename = joinpath(pobase, 'POTFILES.in')
filename, backup, flag = assistant.super_update(basename, tmpfile)
if flag == 1:
@@ -1195,7 +1189,7 @@ def execute(self, filetable: dict[str, list[str]], transformers: dict[str, tuple
print('Creating %s' % filename)
else: # if self.config['dryrun']:
print('Create %s' % filename)
- filetable['added'].append(filename)
+ filetable.added_files.append(filename)
if os.path.isfile(tmpfile):
os.remove(tmpfile)
@@ -1225,7 +1219,7 @@ def execute(self, filetable: dict[str, list[str]], transformers: dict[str, tuple
print('Updating %s (backup in %s)' % (filename, backup))
elif flag == 2:
print('Creating %s' % filename)
- filetable['added'].append(filename)
+ filetable.added_files.append(filename)
if os.path.isfile(tmpfile):
os.remove(tmpfile)
else: # if not self.config['dryrun']
@@ -1310,7 +1304,7 @@ def execute(self, filetable: dict[str, list[str]], transformers: dict[str, tuple
print('Creating %s' % filename)
else: # if self.config['dryrun']:
print('Create %s' % filename)
- filetable['added'].append(filename)
+ filetable.added_files.append(filename)
if os.path.isfile(tmpfile):
os.remove(tmpfile)
@@ -1334,7 +1328,7 @@ def execute(self, filetable: dict[str, list[str]], transformers: dict[str, tuple
print('Creating %s' % filename)
else: # if self.config['dryrun']:
print('Create %s' % filename)
- filetable['added'].append(filename)
+ filetable.added_files.append(filename)
if os.path.isfile(tmpfile):
os.remove(tmpfile)
@@ -1342,13 +1336,13 @@ def execute(self, filetable: dict[str, list[str]], transformers: dict[str, tuple
# Update the .cvsignore and .gitignore files.
ignorelist = []
# Treat gnulib-comp.m4 like an added file, even if it already existed.
- filetable['added'].append(joinpath(m4base, 'gnulib-comp.m4'))
- filetable['added'] = sorted(set(filetable['added']))
- filetable['removed'] = sorted(set(filetable['removed']))
- for file in filetable['added']:
+ filetable.added_files.append(joinpath(m4base, 'gnulib-comp.m4'))
+ filetable.added_files = sorted(set(filetable.added_files))
+ filetable.removed_files = sorted(set(filetable.removed_files))
+ for file in filetable.added_files:
directory, basename = os.path.split(file)
ignorelist.append(tuple([directory, '|A|', basename]))
- for file in filetable['removed']:
+ for file in filetable.removed_files:
directory, basename = os.path.split(file)
ignorelist.append(tuple([directory, '|R|', basename]))
# Sort ignorelist by directory.
diff --git a/pygnulib/GLTestDir.py b/pygnulib/GLTestDir.py
index b48cf98c35..0ac2f32f69 100644
--- a/pygnulib/GLTestDir.py
+++ b/pygnulib/GLTestDir.py
@@ -51,6 +51,7 @@
from .GLFileSystem import GLFileAssistant
from .GLMakefileTable import GLMakefileTable
from .GLEmiter import GLEmiter
+from .GLFileTable import GLFileTable
def _patch_test_driver() -> None:
@@ -336,23 +337,24 @@ def execute(self) -> None:
# Add files for which the copy in gnulib is newer than the one that
# "automake --add-missing --copy" would provide.
- filelist += ['build-aux/config.guess', 'build-aux/config.sub']
- filelist = sorted(set(filelist))
+ filelist = sorted(set(filelist + ['build-aux/config.guess', 'build-aux/config.sub']))
+
+ # Setup the file table.
+ filetable = GLFileTable(filelist)
# Create directories.
directories = [ joinpath(self.testdir, os.path.dirname(file))
- for file in self.rewrite_files(filelist) ]
+ for file in self.rewrite_files(filetable.all_files) ]
directories = sorted(set(directories))
for directory in directories:
if not os.path.isdir(directory):
os.makedirs(directory)
# Copy files or make symbolic links or hard links.
- filetable = []
- for src in filelist:
+ for src in filetable.all_files:
dest = self.rewrite_files([src])[-1]
- filetable.append(tuple([dest, src]))
- for row in filetable:
+ filetable.new_files.append(tuple([dest, src]))
+ for row in filetable.new_files:
src = row[1]
dest = row[0]
destpath = joinpath(self.testdir, dest)
@@ -395,7 +397,7 @@ def execute(self) -> None:
destfile = joinpath(directory, 'Makefile.am')
emit = '## Process this file with automake to produce Makefile.in.\n\n'
emit += 'EXTRA_DIST =\n'
- for file in filelist:
+ for file in filetable.all_files:
if file.startswith('m4/'):
file = substart('m4/', '', file)
emit += 'EXTRA_DIST += %s\n' % file
--
2.44.0