flickzeug 0.5.1

A fork of diffy: diff, patch, and merge library featuring Myers' algorithm, unified diff format parsing, fuzzy patch application, and three-way merge with conflict detection
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
From 2e4938307ff8e53cffb15874c832b4b547b017eb Mon Sep 17 00:00:00 2001
From: Chris Burr <christopher.burr@cern.ch>
Date: Fri, 8 Aug 2025 06:27:08 +0200
Subject: [PATCH 1/4] feat: migrate from setup.py to pyproject.toml with
 entrypoints
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Replace setup.py with modern pyproject.toml configuration using setuptools-scm
for dynamic versioning. Convert scripts to proper entrypoints in cvmfs.scripts
module.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
---
 cvmfs/scripts/__init__.py                     |  1 +
 cvmfs/scripts/big_catalogs.py                 | 40 +++++++++++++++
 cvmfs/scripts/catdirusage.py                  | 44 ++++++++++++++++
 .../scripts/cvmfs_search.py                   |  6 ++-
 pyproject.toml                                | 50 +++++++++++++++++++
 setup.py                                      | 44 ----------------
 utils/big_catalogs                            | 33 ------------
 utils/catdirusage                             | 37 --------------
 8 files changed, 140 insertions(+), 115 deletions(-)
 create mode 100644 cvmfs/scripts/__init__.py
 create mode 100644 cvmfs/scripts/big_catalogs.py
 create mode 100644 cvmfs/scripts/catdirusage.py
 rename utils/cvmfs_search => cvmfs/scripts/cvmfs_search.py (99%)
 mode change 100755 => 100644
 create mode 100644 pyproject.toml
 delete mode 100644 setup.py
 delete mode 100755 utils/big_catalogs
 delete mode 100755 utils/catdirusage

diff --git a/cvmfs/scripts/__init__.py b/cvmfs/scripts/__init__.py
new file mode 100644
index 0000000..ed1cc7f
--- /dev/null
+++ b/cvmfs/scripts/__init__.py
@@ -0,0 +1 @@
+# Scripts module for cvmfs utilities
\ No newline at end of file
diff --git a/cvmfs/scripts/big_catalogs.py b/cvmfs/scripts/big_catalogs.py
new file mode 100644
index 0000000..3a5eaa4
--- /dev/null
+++ b/cvmfs/scripts/big_catalogs.py
@@ -0,0 +1,40 @@
+#!/usr/bin/env python3
+
+import sys
+import cvmfs
+import os
+
+
+def usage():
+    print("Usage: big_catalogs <local repo name | remote repo url> [BIGNUM [BIGMB]]")
+    print("  Lists all catalogs of the provided CVMFS repository with more than BIGNUM")
+    print("  files in them, default 100000, or more than BIGMB megabytes, default 50.")
+
+
+def main():
+    if len(sys.argv) < 2 or len(sys.argv) > 4:
+        usage()
+        sys.exit(1)
+
+    repo_identifier = sys.argv[1]
+    bignum = 100000
+    bigmb = 50
+    if len(sys.argv) > 2:
+        bignum = int(sys.argv[2])
+    if len(sys.argv) > 3:
+        bigmb = int(sys.argv[3])
+
+    repo = cvmfs.open_repository(repo_identifier)
+    revision = repo.get_current_revision()
+    for clg in revision.catalogs():
+        res = clg.run_sql('SELECT count(*) FROM catalog;')
+        num_entries = res[0][0]
+        uncomp_mb = clg.db_size() / (1024*1024)
+        if (num_entries > bignum) or (uncomp_mb >= bigmb):
+            print(clg.root_prefix, num_entries, 'files',  uncomp_mb, 'MB')
+        del res
+        del clg
+
+
+if __name__ == "__main__":
+    main()
\ No newline at end of file
diff --git a/cvmfs/scripts/catdirusage.py b/cvmfs/scripts/catdirusage.py
new file mode 100644
index 0000000..f5034c9
--- /dev/null
+++ b/cvmfs/scripts/catdirusage.py
@@ -0,0 +1,44 @@
+#!/usr/bin/env python3
+
+import sys
+import cvmfs
+import os
+
+
+def usage():
+    print("Usage: catdirusage <local repo name | remote repo url> topdir")
+    print("  Prints the number of files in the current catalog for each sub-directory")
+    print("  under topdir, sorted smallest to largest.")
+    sys.exit(1)
+
+
+def main():
+    if len(sys.argv) != 3:
+        usage()
+
+    repo_identifier = sys.argv[1]
+    toppath = sys.argv[2]
+
+    repo = cvmfs.open_repository(repo_identifier)
+    revision = repo.get_current_revision()
+    clg = revision.retrieve_catalog_for_path(toppath)
+    counts = {}
+    pathlen = len(toppath)
+    for dirent in clg:
+        file = dirent[0]
+        if len(file) <= pathlen:
+            continue
+        if file[0:pathlen] != toppath:
+            continue
+        slash = file.find('/', pathlen+1)
+        if slash == -1:
+            counts[file] = 0
+        else:
+            counts[file[0:slash]] += 1
+    for dir in sorted(counts, key=counts.get):
+        if counts[dir] > 0:
+            print(str(counts[dir]) + '\t' + dir)
+
+
+if __name__ == "__main__":
+    main()
\ No newline at end of file
diff --git a/utils/cvmfs_search b/cvmfs/scripts/cvmfs_search.py
old mode 100755
new mode 100644
similarity index 99%
rename from utils/cvmfs_search
rename to cvmfs/scripts/cvmfs_search.py
index 4d0c815..2a933d6
--- a/utils/cvmfs_search
+++ b/cvmfs/scripts/cvmfs_search.py
@@ -122,7 +122,7 @@ def search_cvmfs_repo(url, enable_index=True):
     return ret
 
 
-if __name__ == "__main__":
+def main():
     parser = argparse.ArgumentParser(
         description="Search CVMFS repositories for all files matching a given download URL."
     )
@@ -142,3 +142,7 @@ def search_cvmfs_repo(url, enable_index=True):
     names = search_cvmfs_repo(args.url, args.db_index)
     for name in names:
         print(name)
+
+
+if __name__ == "__main__":
+    main()
\ No newline at end of file
diff --git a/pyproject.toml b/pyproject.toml
new file mode 100644
index 0000000..765195d
--- /dev/null
+++ b/pyproject.toml
@@ -0,0 +1,50 @@
+[build-system]
+requires = ["setuptools>=80", "setuptools-scm>=8"]
+build-backend = "setuptools.build_meta"
+
+[project]
+name = "python-cvmfsutils"
+dynamic = ["version"]
+description = "Inspect CernVM-FS repositories"
+readme = "README"
+license = {text = "(c) 2015 CERN - BSD License"}
+authors = [
+    {name = "Rene Meusel", email = "rene.meusel@cern.ch"}
+]
+classifiers = [
+    "Development Status :: 4 - Beta",
+    "Environment :: Console",
+    "Intended Audience :: Developers",
+    "Intended Audience :: System Administrators",
+    "License :: OSI Approved :: BSD License",
+    "Natural Language :: English",
+    "Operating System :: POSIX :: Linux",
+    "Operating System :: MacOS :: MacOS X",
+    "Topic :: Software Development",
+    "Topic :: Software Development :: Libraries :: Python Modules",
+    "Topic :: System :: Filesystems",
+    "Topic :: System :: Networking :: Monitoring",
+    "Topic :: System :: Systems Administration"
+]
+dependencies = [
+    "python-dateutil >= 1.4.1",
+    "requests >= 1.1.0",
+    "M2Crypto >= 0.20.0"
+]
+requires-python = ">=3.6"
+
+[project.urls]
+Homepage = "http://cernvm.cern.ch"
+
+[project.optional-dependencies]
+test = ["xmlrunner"]
+
+[project.scripts]
+big_catalogs = "cvmfs.scripts.big_catalogs:main"
+catdirusage = "cvmfs.scripts.catdirusage:main"
+cvmfs_search = "cvmfs.scripts.cvmfs_search:main"
+
+[tool.setuptools.packages.find]
+exclude = ["*.test*"]
+
+[tool.setuptools_scm]
diff --git a/setup.py b/setup.py
deleted file mode 100644
index e069962..0000000
--- a/setup.py
+++ /dev/null
@@ -1,44 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from setuptools import setup, find_packages
-from os         import path
-
-
-readme_path = path.join(path.dirname(__file__), 'README')
-
-setup(
-  name='python-cvmfsutils',
-  version='0.5.0',
-  url='http://cernvm.cern.ch',
-  author='Rene Meusel',
-  author_email='rene.meusel@cern.ch',
-  license='(c) 2015 CERN - BSD License',
-  description='Inspect CernVM-FS repositories',
-  # read the first paragraph
-  long_description=open(readme_path).read().split("\n\n")[0],
-  classifiers= [
-    'Development Status :: 4 - Beta',
-    'Environment :: Console',
-    'Intended Audience :: Developers',
-    'Intended Audience :: System Administrators',
-    'License :: OSI Approved :: BSD License',
-    'Natural Language :: English',
-    'Operating System :: POSIX :: Linux',
-    'Operating System :: MacOS :: MacOS X',
-    'Topic :: Software Development',
-    'Topic :: Software Development :: Libraries :: Python Modules',
-    'Topic :: System :: Filesystems',
-    'Topic :: System :: Networking :: Monitoring',
-    'Topic :: System :: Systems Administration'
-  ],
-  packages=find_packages(),
-  scripts=['utils/big_catalogs', 'utils/catdirusage', 'utils/cvmfs_search'],
-  zip_safe=False,
-  test_suite='cvmfs.test',
-  tests_require='xmlrunner',
-  install_requires=[ # for pip; don't forget the similar RPM dependencies!
-    'python-dateutil >= 1.4.1',
-    'requests >= 1.1.0',
-    'M2Crypto >= 0.20.0'
-  ]
-)
diff --git a/utils/big_catalogs b/utils/big_catalogs
deleted file mode 100755
index d9aea6e..0000000
--- a/utils/big_catalogs
+++ /dev/null
@@ -1,33 +0,0 @@
-#!/usr/bin/env python3
-
-import sys
-import cvmfs
-import os
-
-def usage():
-    print("Usage: big_catalogs <local repo name | remote repo url> [BIGNUM [BIGMB]]")
-    print("  Lists all catalogs of the provided CVMFS repository with more than BIGNUM")
-    print("  files in them, default 100000, or more than BIGMB megabytes, default 50.")
-
-if len(sys.argv) < 2 or len(sys.argv) > 4:
-    usage();
-    sys.exit(1)
-
-repo_identifier = sys.argv[1]
-bignum = 100000
-bigmb = 50
-if len(sys.argv) > 2:
-  bignum=int(sys.argv[2])
-if len(sys.argv) > 3:
-  bigmb=int(sys.argv[3])
-
-repo = cvmfs.open_repository(repo_identifier)
-revision = repo.get_current_revision()
-for clg in revision.catalogs():
-    res = clg.run_sql('SELECT count(*) FROM catalog;')
-    num_entries = res[0][0]
-    uncomp_mb = clg.db_size() / (1024*1024)
-    if (num_entries > bignum) or (uncomp_mb >= bigmb):
-        print(clg.root_prefix, num_entries, 'files',  uncomp_mb, 'MB')
-    del res
-    del clg
diff --git a/utils/catdirusage b/utils/catdirusage
deleted file mode 100755
index 2679ebe..0000000
--- a/utils/catdirusage
+++ /dev/null
@@ -1,37 +0,0 @@
-#!/usr/bin/env python3
-
-import sys
-import cvmfs
-import os
-
-def usage():
-    print("Usage: catdirusage <local repo name | remote repo url> topdir")
-    print("  Prints the number of files in the current catalog for each sub-directory")
-    print("  under topdir, sorted smallest to largest.")
-    sys.exit(1)
-
-if len(sys.argv) != 3:
-    usage();
-
-repo_identifier = sys.argv[1]
-toppath = sys.argv[2]
-
-repo = cvmfs.open_repository(repo_identifier)
-revision = repo.get_current_revision()
-clg = revision.retrieve_catalog_for_path(toppath)
-counts = {}
-pathlen = len(toppath)
-for dirent in clg:
-    file = dirent[0]
-    if len(file) <= pathlen:
-        continue
-    if file[0:pathlen] != toppath:
-        continue
-    slash = file.find('/', pathlen+1)
-    if slash == -1:
-        counts[file] = 0
-    else:
-        counts[file[0:slash]] += 1
-for dir in sorted(counts, key=counts.get):
-    if counts[dir] > 0:
-        print(str(counts[dir]) + '\t' + dir)

From ef1bd9fb3485c122490058db203b366d48955380 Mon Sep 17 00:00:00 2001
From: Chris Burr <christopher.burr@cern.ch>
Date: Fri, 8 Aug 2025 06:28:45 +0200
Subject: [PATCH 2/4] fix: use raw string for regex to avoid SyntaxWarning
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
---
 cvmfs/__init__.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/cvmfs/__init__.py b/cvmfs/__init__.py
index 6aad449..aa035d6 100644
--- a/cvmfs/__init__.py
+++ b/cvmfs/__init__.py
@@ -36,7 +36,7 @@ def __init__(self, input_string):
 
 
 def __extract_version_string(input_str):
-    match = re.search('.*([0-9][0-9]*\.[0-9][0-9]*\.[0-9][0-9]*).*', input_str)
+    match = re.search(r'.*([0-9][0-9]*\.[0-9][0-9]*\.[0-9][0-9]*).*', input_str)
     if not match or len(match.groups()) != 1:
         raise VersionNotDetected(input_str)
     return match.groups()[0]

From 2b9927f07151e785c55be7763bdef2a982b56b59 Mon Sep 17 00:00:00 2001
From: Chris Burr <christopher.burr@cern.ch>
Date: Fri, 8 Aug 2025 06:35:34 +0200
Subject: [PATCH 3/4] refactor: convert entrypoints to use argparse
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Replace manual sys.argv parsing with argparse for better argument handling
and help messages. All scripts now provide proper --help output, automatic
type validation, and consistent error messages.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
---
 cvmfs/scripts/big_catalogs.py | 46 +++++++++++++++++++++--------------
 cvmfs/scripts/catdirusage.py  | 30 +++++++++++++----------
 cvmfs/scripts/cvmfs_search.py |  2 +-
 3 files changed, 46 insertions(+), 32 deletions(-)

diff --git a/cvmfs/scripts/big_catalogs.py b/cvmfs/scripts/big_catalogs.py
index 3a5eaa4..2893f7f 100644
--- a/cvmfs/scripts/big_catalogs.py
+++ b/cvmfs/scripts/big_catalogs.py
@@ -1,28 +1,38 @@
 #!/usr/bin/env python3
 
-import sys
+import argparse
 import cvmfs
 import os
 
 
-def usage():
-    print("Usage: big_catalogs <local repo name | remote repo url> [BIGNUM [BIGMB]]")
-    print("  Lists all catalogs of the provided CVMFS repository with more than BIGNUM")
-    print("  files in them, default 100000, or more than BIGMB megabytes, default 50.")
-
-
 def main():
-    if len(sys.argv) < 2 or len(sys.argv) > 4:
-        usage()
-        sys.exit(1)
-
-    repo_identifier = sys.argv[1]
-    bignum = 100000
-    bigmb = 50
-    if len(sys.argv) > 2:
-        bignum = int(sys.argv[2])
-    if len(sys.argv) > 3:
-        bigmb = int(sys.argv[3])
+    parser = argparse.ArgumentParser(
+        description="Lists all catalogs of the provided CVMFS repository with more than BIGNUM files in them or more than BIGMB megabytes."
+    )
+    parser.add_argument(
+        "repo_identifier",
+        help="Local repo name or remote repo url"
+    )
+    parser.add_argument(
+        "bignum",
+        type=int,
+        nargs="?",
+        default=100000,
+        help="Minimum number of files in catalog (default: 100000)"
+    )
+    parser.add_argument(
+        "bigmb",
+        type=int,
+        nargs="?",
+        default=50,
+        help="Minimum size in MB (default: 50)"
+    )
+    
+    args = parser.parse_args()
+    
+    repo_identifier = args.repo_identifier
+    bignum = args.bignum
+    bigmb = args.bigmb
 
     repo = cvmfs.open_repository(repo_identifier)
     revision = repo.get_current_revision()
diff --git a/cvmfs/scripts/catdirusage.py b/cvmfs/scripts/catdirusage.py
index f5034c9..e6e83e9 100644
--- a/cvmfs/scripts/catdirusage.py
+++ b/cvmfs/scripts/catdirusage.py
@@ -1,23 +1,27 @@
 #!/usr/bin/env python3
 
-import sys
+import argparse
 import cvmfs
 import os
 
 
-def usage():
-    print("Usage: catdirusage <local repo name | remote repo url> topdir")
-    print("  Prints the number of files in the current catalog for each sub-directory")
-    print("  under topdir, sorted smallest to largest.")
-    sys.exit(1)
-
-
 def main():
-    if len(sys.argv) != 3:
-        usage()
-
-    repo_identifier = sys.argv[1]
-    toppath = sys.argv[2]
+    parser = argparse.ArgumentParser(
+        description="Prints the number of files in the current catalog for each sub-directory under topdir, sorted smallest to largest."
+    )
+    parser.add_argument(
+        "repo_identifier",
+        help="Local repo name or remote repo url"
+    )
+    parser.add_argument(
+        "topdir",
+        help="Top directory to analyze"
+    )
+    
+    args = parser.parse_args()
+    
+    repo_identifier = args.repo_identifier
+    toppath = args.topdir
 
     repo = cvmfs.open_repository(repo_identifier)
     revision = repo.get_current_revision()
diff --git a/cvmfs/scripts/cvmfs_search.py b/cvmfs/scripts/cvmfs_search.py
index 2a933d6..7799ef7 100644
--- a/cvmfs/scripts/cvmfs_search.py
+++ b/cvmfs/scripts/cvmfs_search.py
@@ -126,7 +126,7 @@ def main():
     parser = argparse.ArgumentParser(
         description="Search CVMFS repositories for all files matching a given download URL."
     )
-    parser.add_argument("url", type=str, help="A CVMFS URL to print the paths for.")
+    parser.add_argument("url", help="A CVMFS URL to print the paths for.")
     parser.add_argument(
         "--no-db-index",
         dest="db_index",

From 42877482a21612ddeddc58b34bb618dbf9b3bafe Mon Sep 17 00:00:00 2001
From: Chris Burr <christopher.burr@cern.ch>
Date: Fri, 8 Aug 2025 06:38:38 +0200
Subject: [PATCH 4/4] fix: add missing newlines at end of files

---
 cvmfs/scripts/__init__.py     | 2 +-
 cvmfs/scripts/big_catalogs.py | 2 +-
 cvmfs/scripts/catdirusage.py  | 2 +-
 cvmfs/scripts/cvmfs_search.py | 2 +-
 4 files changed, 4 insertions(+), 4 deletions(-)

diff --git a/cvmfs/scripts/__init__.py b/cvmfs/scripts/__init__.py
index ed1cc7f..2d56b66 100644
--- a/cvmfs/scripts/__init__.py
+++ b/cvmfs/scripts/__init__.py
@@ -1 +1 @@
-# Scripts module for cvmfs utilities
\ No newline at end of file
+# Scripts module for cvmfs utilities
diff --git a/cvmfs/scripts/big_catalogs.py b/cvmfs/scripts/big_catalogs.py
index 2893f7f..4694611 100644
--- a/cvmfs/scripts/big_catalogs.py
+++ b/cvmfs/scripts/big_catalogs.py
@@ -47,4 +47,4 @@ def main():
 
 
 if __name__ == "__main__":
-    main()
\ No newline at end of file
+    main()
diff --git a/cvmfs/scripts/catdirusage.py b/cvmfs/scripts/catdirusage.py
index e6e83e9..bd3c791 100644
--- a/cvmfs/scripts/catdirusage.py
+++ b/cvmfs/scripts/catdirusage.py
@@ -45,4 +45,4 @@ def main():
 
 
 if __name__ == "__main__":
-    main()
\ No newline at end of file
+    main()
diff --git a/cvmfs/scripts/cvmfs_search.py b/cvmfs/scripts/cvmfs_search.py
index 7799ef7..d02f42c 100644
--- a/cvmfs/scripts/cvmfs_search.py
+++ b/cvmfs/scripts/cvmfs_search.py
@@ -145,4 +145,4 @@ def main():
 
 
 if __name__ == "__main__":
-    main()
\ No newline at end of file
+    main()