gitlatexdiff-original 0.2.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,159 @@
1
+ Metadata-Version: 2.4
2
+ Name: gitlatexdiff-original
3
+ Version: 0.2.0
4
+ Summary: Make diff of two versions of a LaTeX document in a Git repo
5
+ Author-email: bjhend <developer@bjhend.de>
6
+ Maintainer-email: bjhend <developer@bjhend.de>
7
+ License-Expression: Apache-2.0
8
+ Project-URL: Repository, https://github.com/bjhend/gitlatexdiff
9
+ Project-URL: Issues, https://github.com/bjhend/gitlatexdiff/issues
10
+ Project-URL: Changelog, https://github.com/bjhend/gitlatexdiff/blob/main/CHANGELOG.md
11
+ Keywords: git,latex,diff,tool
12
+ Classifier: Programming Language :: Python :: 3
13
+ Classifier: Topic :: Text Processing :: Markup :: LaTeX
14
+ Requires-Python: >=3.12
15
+ Description-Content-Type: text/markdown
16
+ Requires-Dist: beartype
17
+ Requires-Dist: mkdocs
18
+ Requires-Dist: mkdocstrings[python]
19
+ Requires-Dist: pymdown-extensions
20
+ Requires-Dist: icecream
21
+
22
+ # Git-LaTeX-Diff Original
23
+
24
+ Make a rendered diff of two versions of a LaTeX document.
25
+
26
+
27
+ ### License
28
+
29
+ See [License](LICENSE.md)
30
+
31
+
32
+ ### Changelog
33
+
34
+ See [Changelog](CHANGELOG.md)
35
+
36
+
37
+ ## Purpose
38
+
39
+ [*latexdiff*](https://www.ctan.org/pkg/latexdiff) is a LaTeX tool to create a diff of two LaTeX documents, which shows deletions and additions as red strike-through text and additions as blue underlined text when compiled to PDF. However, *latexdiff* has some major limitations.
40
+
41
+ To overcome the limitations, this Python script extends *latexdiff* in several ways:
42
+
43
+ * It works with a Git repo such that it compares the current state or a given commit with an earlier commit
44
+ * It resolves `\include` and `\input` commands like LaTeX does
45
+ * It calls `pdflatex` to render the final PDF
46
+
47
+ In addition the `\include` and `\input` resolving itself can be called as standalone script.
48
+
49
+
50
+
51
+ ## Caveats
52
+
53
+ * While `\include` and `\input` are resolved from the respective git revisions, other includes like figures are resolved when compiling the diff. This is done in the new revision. So if the old version includes figures that are missing or renamed in the new revision they will be missing in the diff PDF as well.
54
+ * There is no diff of the bibliography and other generated parts.
55
+ * When using uncommitted changes as new version the rendering has to take place in the documents work directory. This may leave temporary files and have other unexpected side effects. However, if everything is committed or dedicated Git revisions are compared all processing is done in temporary directories that are cleaned up.
56
+
57
+
58
+
59
+ ### Prerequesites
60
+
61
+ * LaTeX must be installed including the tools
62
+ * `pdflatex`
63
+ * `latexdiff`
64
+ * Python3 is available with at least the version set in `pyproject.toml`
65
+
66
+
67
+
68
+ ## Usage
69
+
70
+ The easiest way is to apply the Python tool `uv`, which we describe here. Likely `poetry` will work as well.
71
+
72
+ Change to the directory where you cloned the gitlatexdiff project and call
73
+
74
+ ```bash
75
+ uv sync
76
+ ```
77
+
78
+ to install all dependencies and create a virtual environment.
79
+
80
+ To run the script call
81
+
82
+ ```bash
83
+ uv run gitlatexdiff <options>
84
+ ```
85
+
86
+ To run the script to flatten a LeTeX file standalone call
87
+
88
+ ```bash
89
+ uv run flattenlatex <options>
90
+ ```
91
+
92
+
93
+ #### Tips
94
+
95
+ If the diff sources cannot be compiled check the log file for problems with `\DIF...` commands and see which original LaTeX command caused it. Then you may exclude that command from the diff with for example:
96
+
97
+ `-l 'append-textcmd=hint.*,todo' 'exclude-textcmd=title,.*section,chapter'`
98
+
99
+ Here, LaTeX commands `\title`, `\chapter`, and all ending in `section` are excluded, so diffs in these commands are not marked in the output.
100
+
101
+ Note, that in this case the `'append-textcmd=hint.*,todo'` is the default for option `-l`, which needs to be set explicitely if `-l` is given.
102
+
103
+
104
+ ### Options
105
+
106
+ Call `gitlatexdiff` with option `--help` to get the current list of command line options and their defaults if applicable.
107
+
108
+ #### Mandatory options
109
+
110
+ * `-m`, `--main`: Name of the main LaTeX file whose versions should be compared. It has to reside in the respective Git repository containing the versions to compare. May be given with path if `gitlatexdiff` is called from outside the LaTeX project directory.
111
+
112
+ #### Optional
113
+
114
+ All other command line options are optional.
115
+
116
+ Call `gitlatexdiff --help` to see the defaults of the following options.
117
+
118
+ * `-n`, `--new-rev`: Newer revision to compare with. If not given the current state of the work files is used, which will be the HEAD revision if all files are committed or else the work files.
119
+ * `-o`, `--old-rev`: Older revision to compare with. If not given either the revision before `--new-rev` is used or the HEAD revision if `--new-rev` is also not given and there are uncommitted changes.
120
+ * `--old-main`: Name of the old main LaTeX file which should be compared. Defaults to `--main`.
121
+ * `-d`, `--diff-name`: Name of the final diff file. '`.pdf`' will be appended if necessary. The log file of the last `pdflatex` call will be stored beside this file.
122
+ * `-w`, `--overwrite`: If not given `gitlatexdiff` refuses to overwite an existing diff file.
123
+ * `--num-rounds`: Number of calls to `pdflatex` when compiling the diff.
124
+
125
+ The following options are passed to `latexdiff` or `pdflatex` respectively. For technical reasons values have to be given without leading dashes. Dashes are prepended as required by the respective command.
126
+
127
+ * `-l`, `--latexdiff-options`: Arbitrary number of options passed to `latexdiff` call. Pass without any value to turn off the default.
128
+ * `-p`, `--pdflatex-options`: Arbitrary number of options passed to `pdflatex` call. Pass without any value to turn off the default.
129
+
130
+
131
+ ### `flattenlatex`
132
+
133
+ Python module to recusively resolve `\include` and `\input` commands in a LaTeX document. `gitlatexdiff` uses it on both versions of the input file.
134
+
135
+ Call `uv run flattenlatex --help` to see its options to set input and output file. The input file is mandatory, because included files are drawn from its directory. The output file is optional, if omitted, `stdout` will be used.
136
+
137
+
138
+
139
+ ## Documentation
140
+
141
+ Documentation is created with [MkDocs](https://www.mkdocs.org).
142
+
143
+ The following commands called from the base directory create the documentation:
144
+
145
+ * `uv run mkdocs serve` - Start the docs server.
146
+ * `uv run mkdocs build` - Build static documentation in subfolder `site/`
147
+ * `uv run mkdocs --help` - Print help message and exit.
148
+
149
+
150
+
151
+ ## Contributing
152
+
153
+ Create issues or a pull requests to point out bugs or improvements.
154
+
155
+
156
+ ## A note on the name
157
+
158
+ This project is called *Git-LaTeX-Diff Original* or `gitlatexdiff-original`, because the project name *gitlatexdiff* is already in use on [PyPI](https://pypi.org) for a similar [package](https://pypi.org/project/gitlatexdiff/) that was independently developed. It is called *original* due to the fact that the first publication of this project on GitHub is older.
159
+
@@ -0,0 +1,138 @@
1
+ # Git-LaTeX-Diff Original
2
+
3
+ Make a rendered diff of two versions of a LaTeX document.
4
+
5
+
6
+ ### License
7
+
8
+ See [License](LICENSE.md)
9
+
10
+
11
+ ### Changelog
12
+
13
+ See [Changelog](CHANGELOG.md)
14
+
15
+
16
+ ## Purpose
17
+
18
+ [*latexdiff*](https://www.ctan.org/pkg/latexdiff) is a LaTeX tool to create a diff of two LaTeX documents, which shows deletions and additions as red strike-through text and additions as blue underlined text when compiled to PDF. However, *latexdiff* has some major limitations.
19
+
20
+ To overcome the limitations, this Python script extends *latexdiff* in several ways:
21
+
22
+ * It works with a Git repo such that it compares the current state or a given commit with an earlier commit
23
+ * It resolves `\include` and `\input` commands like LaTeX does
24
+ * It calls `pdflatex` to render the final PDF
25
+
26
+ In addition the `\include` and `\input` resolving itself can be called as standalone script.
27
+
28
+
29
+
30
+ ## Caveats
31
+
32
+ * While `\include` and `\input` are resolved from the respective git revisions, other includes like figures are resolved when compiling the diff. This is done in the new revision. So if the old version includes figures that are missing or renamed in the new revision they will be missing in the diff PDF as well.
33
+ * There is no diff of the bibliography and other generated parts.
34
+ * When using uncommitted changes as new version the rendering has to take place in the documents work directory. This may leave temporary files and have other unexpected side effects. However, if everything is committed or dedicated Git revisions are compared all processing is done in temporary directories that are cleaned up.
35
+
36
+
37
+
38
+ ### Prerequesites
39
+
40
+ * LaTeX must be installed including the tools
41
+ * `pdflatex`
42
+ * `latexdiff`
43
+ * Python3 is available with at least the version set in `pyproject.toml`
44
+
45
+
46
+
47
+ ## Usage
48
+
49
+ The easiest way is to apply the Python tool `uv`, which we describe here. Likely `poetry` will work as well.
50
+
51
+ Change to the directory where you cloned the gitlatexdiff project and call
52
+
53
+ ```bash
54
+ uv sync
55
+ ```
56
+
57
+ to install all dependencies and create a virtual environment.
58
+
59
+ To run the script call
60
+
61
+ ```bash
62
+ uv run gitlatexdiff <options>
63
+ ```
64
+
65
+ To run the script to flatten a LeTeX file standalone call
66
+
67
+ ```bash
68
+ uv run flattenlatex <options>
69
+ ```
70
+
71
+
72
+ #### Tips
73
+
74
+ If the diff sources cannot be compiled check the log file for problems with `\DIF...` commands and see which original LaTeX command caused it. Then you may exclude that command from the diff with for example:
75
+
76
+ `-l 'append-textcmd=hint.*,todo' 'exclude-textcmd=title,.*section,chapter'`
77
+
78
+ Here, LaTeX commands `\title`, `\chapter`, and all ending in `section` are excluded, so diffs in these commands are not marked in the output.
79
+
80
+ Note, that in this case the `'append-textcmd=hint.*,todo'` is the default for option `-l`, which needs to be set explicitely if `-l` is given.
81
+
82
+
83
+ ### Options
84
+
85
+ Call `gitlatexdiff` with option `--help` to get the current list of command line options and their defaults if applicable.
86
+
87
+ #### Mandatory options
88
+
89
+ * `-m`, `--main`: Name of the main LaTeX file whose versions should be compared. It has to reside in the respective Git repository containing the versions to compare. May be given with path if `gitlatexdiff` is called from outside the LaTeX project directory.
90
+
91
+ #### Optional
92
+
93
+ All other command line options are optional.
94
+
95
+ Call `gitlatexdiff --help` to see the defaults of the following options.
96
+
97
+ * `-n`, `--new-rev`: Newer revision to compare with. If not given the current state of the work files is used, which will be the HEAD revision if all files are committed or else the work files.
98
+ * `-o`, `--old-rev`: Older revision to compare with. If not given either the revision before `--new-rev` is used or the HEAD revision if `--new-rev` is also not given and there are uncommitted changes.
99
+ * `--old-main`: Name of the old main LaTeX file which should be compared. Defaults to `--main`.
100
+ * `-d`, `--diff-name`: Name of the final diff file. '`.pdf`' will be appended if necessary. The log file of the last `pdflatex` call will be stored beside this file.
101
+ * `-w`, `--overwrite`: If not given `gitlatexdiff` refuses to overwite an existing diff file.
102
+ * `--num-rounds`: Number of calls to `pdflatex` when compiling the diff.
103
+
104
+ The following options are passed to `latexdiff` or `pdflatex` respectively. For technical reasons values have to be given without leading dashes. Dashes are prepended as required by the respective command.
105
+
106
+ * `-l`, `--latexdiff-options`: Arbitrary number of options passed to `latexdiff` call. Pass without any value to turn off the default.
107
+ * `-p`, `--pdflatex-options`: Arbitrary number of options passed to `pdflatex` call. Pass without any value to turn off the default.
108
+
109
+
110
+ ### `flattenlatex`
111
+
112
+ Python module to recusively resolve `\include` and `\input` commands in a LaTeX document. `gitlatexdiff` uses it on both versions of the input file.
113
+
114
+ Call `uv run flattenlatex --help` to see its options to set input and output file. The input file is mandatory, because included files are drawn from its directory. The output file is optional, if omitted, `stdout` will be used.
115
+
116
+
117
+
118
+ ## Documentation
119
+
120
+ Documentation is created with [MkDocs](https://www.mkdocs.org).
121
+
122
+ The following commands called from the base directory create the documentation:
123
+
124
+ * `uv run mkdocs serve` - Start the docs server.
125
+ * `uv run mkdocs build` - Build static documentation in subfolder `site/`
126
+ * `uv run mkdocs --help` - Print help message and exit.
127
+
128
+
129
+
130
+ ## Contributing
131
+
132
+ Create issues or a pull requests to point out bugs or improvements.
133
+
134
+
135
+ ## A note on the name
136
+
137
+ This project is called *Git-LaTeX-Diff Original* or `gitlatexdiff-original`, because the project name *gitlatexdiff* is already in use on [PyPI](https://pypi.org) for a similar [package](https://pypi.org/project/gitlatexdiff/) that was independently developed. It is called *original* due to the fact that the first publication of this project on GitHub is older.
138
+
@@ -0,0 +1,61 @@
1
+
2
+
3
+ # Copyright 2019,2026 Björn Hendriks
4
+ #
5
+ # Licensed under the Apache License, Version 2.0 (the "License");
6
+ # you may not use this file except in compliance with the License.
7
+ # You may obtain a copy of the License at
8
+ #
9
+ # http://www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing, software
12
+ # distributed under the License is distributed on an "AS IS" BASIS,
13
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ # See the License for the specific language governing permissions and
15
+ # limitations under the License.
16
+
17
+
18
+ [project]
19
+ name = "gitlatexdiff-original"
20
+ version = "0.2.0"
21
+ description = "Make diff of two versions of a LaTeX document in a Git repo"
22
+ readme = "README.md"
23
+ license = "Apache-2.0"
24
+ license-files = [ "LICENSE" ]
25
+ keywords = [ "git", "latex", "diff", "tool" ]
26
+ classifiers = [
27
+ "Programming Language :: Python :: 3",
28
+ "Topic :: Text Processing :: Markup :: LaTeX",
29
+ ]
30
+ authors = [
31
+ {name = "bjhend", email = "developer@bjhend.de"},
32
+ ]
33
+ maintainers = [
34
+ {name = "bjhend", email = "developer@bjhend.de"},
35
+ ]
36
+
37
+ requires-python = ">=3.12"
38
+ dependencies = [
39
+ "beartype",
40
+ "mkdocs",
41
+ "mkdocstrings[python]",
42
+ "pymdown-extensions",
43
+ "icecream",
44
+ ]
45
+
46
+
47
+ [project.urls]
48
+ Repository = "https://github.com/bjhend/gitlatexdiff"
49
+ Issues = "https://github.com/bjhend/gitlatexdiff/issues"
50
+ Changelog = "https://github.com/bjhend/gitlatexdiff/blob/main/CHANGELOG.md"
51
+
52
+
53
+ [project.scripts]
54
+ gitlatexdiff = "gitlatexdiff:main"
55
+ flattenlatex = "gitlatexdiff:flattenCommand"
56
+
57
+
58
+ [build-system]
59
+ requires = ["setuptools>=61.0"]
60
+ build-backend = "setuptools.build_meta"
61
+
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,25 @@
1
+
2
+
3
+ # Copyright 2019,2026 Björn Hendriks
4
+ #
5
+ # Licensed under the Apache License, Version 2.0 (the "License");
6
+ # you may not use this file except in compliance with the License.
7
+ # You may obtain a copy of the License at
8
+ #
9
+ # http://www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing, software
12
+ # distributed under the License is distributed on an "AS IS" BASIS,
13
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ # See the License for the specific language governing permissions and
15
+ # limitations under the License.
16
+
17
+
18
+ # Dynamic type checking with beartype
19
+ from beartype.claw import beartype_this_package
20
+ beartype_this_package()
21
+
22
+
23
+ from .make_diff import main
24
+ from .flatten_latex import flattenCommand
25
+
@@ -0,0 +1,198 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+
4
+ # Recursively replace LaTeX input and include commands by the respective files
5
+ #
6
+ # Can be used either as module providing the parseFile function or directly
7
+ # called with LaTeX code provided on stdin and the result written to stdout.
8
+
9
+
10
+ # Copyright 2019,2026 Björn Hendriks
11
+ #
12
+ # Licensed under the Apache License, Version 2.0 (the "License");
13
+ # you may not use this file except in compliance with the License.
14
+ # You may obtain a copy of the License at
15
+ #
16
+ # http://www.apache.org/licenses/LICENSE-2.0
17
+ #
18
+ # Unless required by applicable law or agreed to in writing, software
19
+ # distributed under the License is distributed on an "AS IS" BASIS,
20
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
21
+ # See the License for the specific language governing permissions and
22
+ # limitations under the License.
23
+
24
+
25
+ import sys
26
+ import os
27
+ import re
28
+ import typing
29
+ import argparse
30
+ import pathlib as pl
31
+ import contextlib
32
+ from icecream import ic
33
+
34
+
35
+
36
+ class _Config():
37
+ """Configuration values"""
38
+
39
+ texExtension = '.tex'
40
+
41
+ # Regular expression template to find LaTeX commands which are not commented out
42
+ # the first part only allows '%' with an odd number of leading backslashes
43
+ _commandPattern = r"^(?P<before>(?:[^%\\]|\\.)*)\\{cmd}{{(?P<filename>.*?)}}(?P<after>.*)"
44
+ inputRe = re.compile(_commandPattern.format(cmd='input'))
45
+ includeRe = re.compile(_commandPattern.format(cmd='include'))
46
+ includeOnlyRe = re.compile(_commandPattern.format(cmd='includeonly'))
47
+
48
+
49
+ def _flattenRecursion(inFile:typing.TextIO, outFile:typing.TextIO,
50
+ includeOnly:list[str]|None=None, isResolveInclude:bool=True) -> None:
51
+ """Copy inFile to outFile recursively inserting inputs and includes
52
+
53
+ According to LaTeX `\\input` is always inserted but `\\include` is more special.
54
+ `\\include` cannot be nested and if `\\includeonly` is given in the preamble
55
+ includes are limited to those files. In addition `\\include` is surrounded by
56
+ `\\clearpage`.
57
+
58
+ Args:
59
+ inFile: readable text file object to parse
60
+ outFile: writable text file object to write result into
61
+ includeOnly: list of filenames to include found in `\\includeonly` command
62
+ isResolveInclude: True if `\\include` should be resolved to avoid nested inclusion
63
+ """
64
+ config = _Config()
65
+
66
+ def getFilename(match:re.Match) -> str:
67
+ """Get filename argument from command match"""
68
+ return match.group('filename').strip()
69
+
70
+ def insertFile(match:re.Match, isInclude:bool) -> None:
71
+ """Open filename in match and insert its content"""
72
+
73
+ if isInclude:
74
+ label = 'include'
75
+ surround = "\\clearpage % inserted due to resolved include\n"
76
+ newIsResolveInclude = False
77
+ else:
78
+ label = 'input'
79
+ surround = ''
80
+ newIsResolveInclude = isResolveInclude
81
+
82
+ fileName = getFilename(match)
83
+ if not fileName.endswith(config.texExtension):
84
+ fileName += config.texExtension
85
+ before = match.group('before')
86
+ after = match.group('after')
87
+
88
+ outFile.write(before)
89
+ outFile.write(f"% ========= begin {label} of {fileName} ==========\n")
90
+ outFile.write(surround)
91
+ with open(fileName) as file:
92
+ _flattenRecursion(file, outFile, includeOnly=includeOnly,
93
+ isResolveInclude=newIsResolveInclude)
94
+ outFile.write(surround)
95
+ outFile.write(f"% ========= end {label} of {fileName} ==========\n")
96
+ outFile.write(after)
97
+
98
+ for line in inFile:
99
+ # Handle \includeonly
100
+ matchIncludeOnly = config.includeOnlyRe.search(line)
101
+ if matchIncludeOnly:
102
+ filenames = getFilename(matchIncludeOnly).split(sep=',')
103
+ includeOnly = [ f.strip() for f in filenames ]
104
+
105
+ # Handle \input
106
+ matchInput = config.inputRe.search(line)
107
+ if matchInput:
108
+ insertFile(match=matchInput, isInclude=False)
109
+ continue
110
+
111
+ # Handle \include
112
+ if isResolveInclude:
113
+ matchInclude = config.includeRe.search(line)
114
+ if matchInclude:
115
+ if (not includeOnly) or (getFilename(matchInclude) in includeOnly):
116
+ insertFile(match=matchInclude, isInclude=True)
117
+ continue
118
+
119
+ # Copy line
120
+ outFile.write(line)
121
+
122
+
123
+ def flatten(inFile:typing.TextIO, outFile:typing.TextIO) -> None:
124
+ """Start recursively resolving inputs/includes
125
+
126
+ Args:
127
+ inFile: An open read text file object to read input LaTeX code from
128
+ OutFile: An open write text file object to write flattened LaTeX code to
129
+ """
130
+ _flattenRecursion(inFile, outFile)
131
+
132
+
133
+ def flattenFiles(inPath:pl.Path, outPath:pl.Path|None=None) -> None:
134
+ """Open files and call flatten() with them
135
+
136
+ Both files can be given as pathlib.Path or string.
137
+
138
+ We require inPath instead of defaulting to stdin, because we need
139
+ its directory to resolve relative input/include paths in the LaTeX code
140
+
141
+ Args:
142
+ inPath: path of input file
143
+ outPath: optional name of output file, if omitted stdout is used instead
144
+
145
+ Raises:
146
+ OSError: if a file cannot be opened.
147
+ """
148
+ cwd = pl.Path.cwd()
149
+ try:
150
+ inPath = pl.Path(inPath)
151
+ assert inPath.is_file()
152
+
153
+ inPathAbs = inPath.absolute()
154
+ if outPath:
155
+ outPath = pl.Path(outPath)
156
+ output:contextlib.AbstractContextManager[typing.TextIO] = contextlib.closing(outPath.absolute().open('w'))
157
+ else:
158
+ output = contextlib.nullcontext(sys.stdout)
159
+
160
+ # Change dir to resolve relative inputs/includes
161
+ os.chdir(inPathAbs.parent)
162
+
163
+ with inPathAbs.open() as inFile:
164
+ with output as outFile:
165
+ flatten(inFile, outFile)
166
+ finally:
167
+ os.chdir(cwd)
168
+
169
+
170
+ def flattenCommand() -> None:
171
+ """Call flatten from command line
172
+
173
+ Parses command line for input and output and calls flattenFiles with them.
174
+
175
+ If output exists an additional command line flag is required to overwrite it.
176
+ """
177
+ parser = argparse.ArgumentParser(description="Flatten a LaTeX file to a single file with all input/include resolved")
178
+
179
+ # See flattenFiles documentation, why --input is required
180
+ parser.add_argument('-i', '--input', required=True, type=pl.Path, help="Main LaTeX file to flatten")
181
+ parser.add_argument('-o', '--output', type=pl.Path, help="Output file, default is stdout")
182
+ parser.add_argument('-w', '--overwrite', action='store_true', help="Silently overwrite existing diff? (default: %(default)s)")
183
+ args = parser.parse_args()
184
+
185
+ if args.output and (not args.overwrite) and args.output.exists():
186
+ print(f"Output {args.output} exists. Delete it or set option --overwrite (-w) to overwrite it.")
187
+ exit(1)
188
+
189
+ try:
190
+ flattenFiles(args.input, args.output)
191
+ except OSError as ex:
192
+ print(f"Cannot open input or output file: {ex}")
193
+ exit(1)
194
+
195
+
196
+ if __name__ == "__main__":
197
+ flattenCommand()
198
+
@@ -0,0 +1,375 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+
4
+ # Make a diff of a LaTeX file to another Git revision with latexdiff
5
+ #
6
+ # Call it with option --help for more info
7
+
8
+ # Copyright 2019,2026 Björn Hendriks
9
+ #
10
+ # Licensed under the Apache License, Version 2.0 (the "License");
11
+ # you may not use this file except in compliance with the License.
12
+ # You may obtain a copy of the License at
13
+ #
14
+ # http://www.apache.org/licenses/LICENSE-2.0
15
+ #
16
+ # Unless required by applicable law or agreed to in writing, software
17
+ # distributed under the License is distributed on an "AS IS" BASIS,
18
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
19
+ # See the License for the specific language governing permissions and
20
+ # limitations under the License.
21
+
22
+
23
+
24
+ import os
25
+ import argparse
26
+ import subprocess
27
+ import tempfile
28
+ import pathlib as pl
29
+ import contextlib
30
+ import typing
31
+ from collections.abc import Generator
32
+ import shutil
33
+ from icecream import ic
34
+ from . import flatten_latex
35
+
36
+
37
+ latexExtension = '.tex'
38
+ pdfExtension = '.pdf'
39
+ logExtension = '.log'
40
+ messagePrefix = "------ "
41
+
42
+ # Command line option defaults
43
+ defaultNumRounds = 3
44
+ defaultDiffName = 'diff'
45
+ defaultLatexdiffOptions = ['append-textcmd=hint.*,todo']
46
+ defaultPdflatexOptions = ['interaction=batchmode']
47
+
48
+
49
+ def callCommand(args:list[str], cwd:pl.Path|None=None) -> str:
50
+ """Call args as shell command and return its stdout (NOT stderr)
51
+
52
+ Args:
53
+ cwd: optional working directory
54
+
55
+ Raises:
56
+ subprocess.CalledProcessError: if the command returns with non-zero
57
+ exit code
58
+
59
+ Returns:
60
+ stdout of the command
61
+ """
62
+ result = subprocess.run(args, cwd=cwd, stdout=subprocess.PIPE, check=True)
63
+ return result.stdout.decode().strip()
64
+
65
+
66
+ class Configuration():
67
+ """Configuration values either from parsing command line or hard coded"""
68
+
69
+ def __init__(self):
70
+ """Get and preprocess command line arguments"""
71
+ args = self._parseArgs()
72
+
73
+ # Options given to latexdiff
74
+ self.latexdiffOptions = self._prependPrefix('--', args.latexdiff_options)
75
+ # Options given to pdflatex
76
+ self.pdflatexOptions = self._prependPrefix('-', args.pdflatex_options)
77
+ # Number of successive calls of pdflatex to achieve the final result
78
+ self.numTexRounds = args.num_rounds
79
+ if self.numTexRounds < 1:
80
+ print(f"--num-rounds set to {self.numTexRounds}, but must be at least 1")
81
+ exit(1)
82
+
83
+ self.mainFileAbs = args.main.resolve().with_suffix(latexExtension)
84
+ self.oldMainFileAbs = args.old_main.resolve().with_suffix(latexExtension) if args.old_main else self.mainFileAbs
85
+ self.diffNameAbs = args.diff_name.resolve().with_suffix(pdfExtension)
86
+ self.logNameAbs = args.diff_name.resolve().with_suffix(logExtension)
87
+
88
+ # Bail out if diffNameAbs exists and no --overwrite given
89
+ if (not args.overwrite) and self.diffNameAbs.exists():
90
+ print(f"Destination file {self.diffNameAbs} exists. Delete it or set --overwrite to overwrite it.")
91
+ exit(1)
92
+
93
+ self.newRevision = args.new_rev
94
+ self.oldRevision = args.old_rev
95
+
96
+ def _prependPrefix(self, prefix:str, options:list[str]) -> list[str]:
97
+ """Prepend prefix to all elements of options tuple
98
+
99
+ Args:
100
+ prefix: string to prepend to all elements of options
101
+ options: iterable of strings to prepend prefix to
102
+
103
+ Returns:
104
+ options with prefix prepended
105
+ """
106
+ return [ prefix + opt for opt in options ]
107
+
108
+ def _parseArgs(self) -> argparse.Namespace:
109
+ """Parse command line arguments
110
+
111
+ Returns:
112
+ result of argparse.ArgumentParser.parse_args()
113
+ """
114
+ parser = argparse.ArgumentParser(description='Make a LaTeX diff for two Git revisions of a LaTeX project')
115
+
116
+ parser.add_argument('-m', '--main', type=pl.Path, required=True, help='Main LaTeX file (required)')
117
+ parser.add_argument('-n', '--new-rev', help='Ref to new revision for diff (default: current state of the repo)')
118
+ parser.add_argument('-o', '--old-rev', help='Ref to old revision for diff (default depends on --new-rev: If --new-rev is given'
119
+ ' one revision before --new-rev. If --new-rev is omitted but everything is committed'
120
+ ' then one before HEAD. Else HEAD itself.)')
121
+ parser.add_argument('--old-main', type=pl.Path, help='Main LaTeX file of old revision (defaults to --main)')
122
+ parser.add_argument('-d', '--diff-name', type=pl.Path, default=defaultDiffName, help='Name for final diff file (default: %(default)s)')
123
+ parser.add_argument('-w', '--overwrite', action='store_true', help='Silently overwrite existing diff (default: %(default)s)')
124
+ parser.add_argument('--num-rounds', type=int, default=defaultNumRounds, help='Number of pdflatexcalls to compile the diff (default: %(default)s)')
125
+ parser.add_argument('-l', '--latexdiff-options', nargs='*', default=defaultLatexdiffOptions,
126
+ help='Options passed to latexdiff without leading dashes (default: %(default)s)')
127
+ parser.add_argument('-p', '--pdflatex-options', nargs='*', default=defaultPdflatexOptions,
128
+ help='Options passed to pdflatex without leading dashes (default: %(default)s)')
129
+
130
+ return parser.parse_args()
131
+
132
+
133
+ class GitRepo():
134
+ """Wrapper for all Git commands
135
+
136
+ We do not apply GitPython or other third party packages to be as portable
137
+ as possible. Instead we call the Git commands directly.
138
+ """
139
+
140
+ def __init__(self, config:Configuration):
141
+ """Init GitRepo with given config
142
+
143
+ Args:
144
+ config: configuration with main file path
145
+ """
146
+ self.repoDir = pl.Path(self._callGit(['rev-parse', '--show-toplevel'], config.mainFileAbs.parent))
147
+
148
+ def getSha1(self, committish:str) -> str:
149
+ """Return SHA1 of committish
150
+
151
+ Args:
152
+ committish: any string that Git recognizes as a commit reference: HEAD,
153
+ branch, tag, or their parents
154
+
155
+ Returns:
156
+ SHA1 of the referenced commit or `None` if it cannot be resolved
157
+ """
158
+
159
+ try:
160
+ return self._callGit(['rev-parse', committish])
161
+ except subprocess.CalledProcessError:
162
+ return None
163
+
164
+ def isDirty(self) -> bool:
165
+ """Check if uncommitted changes or new non-ignored files are present
166
+
167
+ Returns:
168
+ `True` if there are uncommitted files
169
+ """
170
+ try:
171
+ # Returns with non-zero exitcode if a committed file is altered
172
+ self._callGit(['diff-index', '--quiet', 'HEAD', '--'])
173
+ except subprocess.CalledProcessError:
174
+ return True
175
+
176
+ # Returns list of non-ignored untracked files
177
+ untrackedFiles = self._callGit(['ls-files', '--exclude-standard', '--others'])
178
+ return bool(untrackedFiles)
179
+
180
+ @contextlib.contextmanager
181
+ def worktree(self, sha1:str|None=None) -> Generator[pl.Path]:
182
+ """Check out sha1 in a temporary worktree and finally cleanup or return repo dir
183
+
184
+ This is a contextmanager, so call it in a `with` statement.
185
+
186
+ If `sha1` is given check it out in a temporary worktree and remove the
187
+ worktree on exit of the context. If `sha1` is `None` return the repo dir
188
+ itself.
189
+
190
+ Args:
191
+ sha1: SHA1 of the commit to check out in the worktree, if `None` return
192
+ the repo dir itself
193
+
194
+ Returns:
195
+ path to the work files
196
+ """
197
+
198
+ if not sha1:
199
+ yield self.repoDir
200
+ return
201
+
202
+ with tempfile.TemporaryDirectory(prefix='gitlatexdiff_worktree_') as workDir:
203
+ workDirPath = pl.Path(workDir)
204
+ self._callGit(['worktree', 'add', '--force', str(workDirPath), sha1])
205
+ try:
206
+ yield workDirPath
207
+ finally:
208
+ self._callGit(['worktree', 'remove', str(workDirPath)])
209
+
210
+ def _callGit(self, args:list[str], workingDir:pl.Path|None=None) -> str:
211
+ """Call a Git command in the repo or workingDir if given
212
+
213
+ Prepends `git` to the given args and calls callCommand() with them.
214
+
215
+ Args:
216
+ args: command line arguments for the Git command (without `git` itself)
217
+ workingDir: optional dir to execute the command in, if ommitted use
218
+ the repo dir
219
+
220
+ Return:
221
+ stdout of the command
222
+
223
+ Raises:
224
+ see callCommand()
225
+ """
226
+ if workingDir is None:
227
+ workingDir = self.repoDir
228
+ return callCommand(['git'] + args, cwd=workingDir)
229
+
230
+
231
+
232
+ class Diff():
233
+ """Class to create the diff"""
234
+
235
+ def __init__(self, config:Configuration, gitRepo:GitRepo):
236
+ """Init
237
+
238
+ Args:
239
+ config: configuration
240
+ gitRepo: Git repo
241
+ """
242
+
243
+ self.config = config
244
+ self.gitRepo = gitRepo
245
+ self.mainFileRelative = self.config.mainFileAbs.relative_to(self.gitRepo.repoDir)
246
+ self.oldMainFileRelative = self.config.oldMainFileAbs.relative_to(self.gitRepo.repoDir)
247
+
248
+ # Determine new sha1
249
+ if config.newRevision:
250
+ self.newSha1:str|None = self.gitRepo.getSha1(config.newRevision)
251
+ else:
252
+ if self.gitRepo.isDirty():
253
+ self.newSha1 = None
254
+ else:
255
+ self.newSha1 = self.gitRepo.getSha1('HEAD')
256
+
257
+ # Determine old sha1
258
+ if config.oldRevision:
259
+ self.oldSha1 = self.gitRepo.getSha1(config.oldRevision)
260
+ else:
261
+ if self.newSha1:
262
+ # Use one revision before new sha1
263
+ self.oldSha1 = self.gitRepo.getSha1(self.newSha1 + '~')
264
+ else:
265
+ # Compare work files with HEAD revision
266
+ self.oldSha1 = self.gitRepo.getSha1('HEAD')
267
+
268
+ @contextlib.contextmanager
269
+ def _flatFile(self, sha1:str|None, mainFileRelative:pl.Path) -> Generator[pl.Path]:
270
+ """Flatten mainFileRelative in the given sha1 revision and return its path
271
+
272
+ This method is a contextmanager. So it needs to be called in a
273
+ with-statement. On leaving the context the returned file and the worktree
274
+ will be removed.
275
+
276
+ Flatten resolves all include/input commands.
277
+
278
+ If sha1 is not None flattening is done in an exclusive worktree to avoid
279
+ interfering with the repo.
280
+
281
+ Args:
282
+ sha1: revision to check out, if None use the repo itself
283
+ mainFileRelative: relative path to the main file in the repo
284
+
285
+ Returns:
286
+ name of the temporary file
287
+ """
288
+ version = f"version {sha1}" if sha1 else "current version"
289
+ print(f"{messagePrefix}Flattening {mainFileRelative} in {version}")
290
+ with self.gitRepo.worktree(sha1) as workDir:
291
+ mainFileDir = workDir / mainFileRelative.parent
292
+ os.chdir(mainFileDir)
293
+ with tempfile.NamedTemporaryFile(mode='w',
294
+ prefix='flattened_',
295
+ suffix=latexExtension,
296
+ dir=mainFileDir,
297
+ delete_on_close=False) as texFile:
298
+ with (workDir / mainFileRelative).open() as mainFile:
299
+ flatten_latex.flatten(mainFile, typing.cast(typing.TextIO, texFile.file))
300
+ texFile.close()
301
+ yield pl.Path(texFile.name)
302
+
303
+ def makeDiff(self) -> None:
304
+ """Create the diff PDF file in the directory this script was called from"""
305
+
306
+ # Make LaTeX diff
307
+ with (self._flatFile(self.oldSha1, self.oldMainFileRelative) as oldFlatInput,
308
+ self._flatFile(self.newSha1, self.mainFileRelative) as newFlatInput):
309
+ print(f"{messagePrefix}Create diff")
310
+ diffTex = callCommand(['latexdiff'] + self.config.latexdiffOptions
311
+ + [str(oldFlatInput), str(newFlatInput)])
312
+
313
+ # Compile diff in a worktree and move it to its configure final path.
314
+ # Note that workDir is the repo itself if self.newSha1 is None.
315
+ with self.gitRepo.worktree(self.newSha1) as workDir:
316
+ mainFileDir = workDir / self.mainFileRelative.parent
317
+
318
+ # TeX commands need to be executed in their repo to have correct access
319
+ # to all temporary TeX files
320
+ os.chdir(mainFileDir)
321
+
322
+ with tempfile.NamedTemporaryFile(mode='w',
323
+ prefix='diff_',
324
+ suffix=latexExtension,
325
+ dir=mainFileDir,
326
+ delete_on_close=False) as diffTexFile:
327
+ diffTexFile.write(diffTex)
328
+ diffTexFile.close()
329
+ diffTexFilePath = pl.Path(diffTexFile.name)
330
+
331
+ # Call pdflatex sufficiently often on diff
332
+ for i in range(self.config.numTexRounds):
333
+ print(f"{messagePrefix}Compiling diff round {i+1}")
334
+ try:
335
+ callCommand(['pdflatex'] + self.config.pdflatexOptions + [str(diffTexFilePath)], cwd=mainFileDir)
336
+ except subprocess.CalledProcessError as ex:
337
+ # Sometimes a pdfltex call returns an error but still works
338
+ print(f"Warning: pdflatex returned an error, which we ignore: {ex}")
339
+ print(f"See pdflatex log for possible causes: {self.config.logNameAbs}")
340
+
341
+ # Move resulting PDF and log to initial dir
342
+ diffPdfPathname = diffTexFilePath.with_suffix(pdfExtension)
343
+ diffLogPathname = diffTexFilePath.with_suffix(logExtension)
344
+ shutil.move(diffLogPathname, self.config.logNameAbs)
345
+ try:
346
+ shutil.move(diffPdfPathname, self.config.diffNameAbs)
347
+ except FileNotFoundError:
348
+ print(f"No diff created. See log files for possible cause: {self.config.logNameAbs}")
349
+ print("Hint: It may help exclude LaTeX commands with for example \"-l 'exclude-textcmd=title,.*section,chapter'\"")
350
+
351
+ # Remove all temporary pdflatex files
352
+ # This is only relevant if a diff with current dirty work files was made,
353
+ # so it was compiled in the repo itself instead of a temp worktree.
354
+ for path in diffTexFilePath.parent.glob(f'{diffTexFilePath.stem}*'):
355
+ path.unlink()
356
+
357
+ print(f"{messagePrefix}Successfully created diff {self.config.diffNameAbs}")
358
+
359
+
360
+ def main() -> None:
361
+ """Entry point"""
362
+
363
+ try:
364
+ config = Configuration()
365
+ gitRepo = GitRepo(config)
366
+ diff = Diff(config, gitRepo)
367
+ diff.makeDiff()
368
+ except Exception as ex:
369
+ print(f"An error occurred: {ex}")
370
+ exit(1)
371
+
372
+
373
+ if __name__ == "__main__":
374
+ main()
375
+
@@ -0,0 +1,159 @@
1
+ Metadata-Version: 2.4
2
+ Name: gitlatexdiff-original
3
+ Version: 0.2.0
4
+ Summary: Make diff of two versions of a LaTeX document in a Git repo
5
+ Author-email: bjhend <developer@bjhend.de>
6
+ Maintainer-email: bjhend <developer@bjhend.de>
7
+ License-Expression: Apache-2.0
8
+ Project-URL: Repository, https://github.com/bjhend/gitlatexdiff
9
+ Project-URL: Issues, https://github.com/bjhend/gitlatexdiff/issues
10
+ Project-URL: Changelog, https://github.com/bjhend/gitlatexdiff/blob/main/CHANGELOG.md
11
+ Keywords: git,latex,diff,tool
12
+ Classifier: Programming Language :: Python :: 3
13
+ Classifier: Topic :: Text Processing :: Markup :: LaTeX
14
+ Requires-Python: >=3.12
15
+ Description-Content-Type: text/markdown
16
+ Requires-Dist: beartype
17
+ Requires-Dist: mkdocs
18
+ Requires-Dist: mkdocstrings[python]
19
+ Requires-Dist: pymdown-extensions
20
+ Requires-Dist: icecream
21
+
22
+ # Git-LaTeX-Diff Original
23
+
24
+ Make a rendered diff of two versions of a LaTeX document.
25
+
26
+
27
+ ### License
28
+
29
+ See [License](LICENSE.md)
30
+
31
+
32
+ ### Changelog
33
+
34
+ See [Changelog](CHANGELOG.md)
35
+
36
+
37
+ ## Purpose
38
+
39
+ [*latexdiff*](https://www.ctan.org/pkg/latexdiff) is a LaTeX tool to create a diff of two LaTeX documents, which shows deletions and additions as red strike-through text and additions as blue underlined text when compiled to PDF. However, *latexdiff* has some major limitations.
40
+
41
+ To overcome the limitations, this Python script extends *latexdiff* in several ways:
42
+
43
+ * It works with a Git repo such that it compares the current state or a given commit with an earlier commit
44
+ * It resolves `\include` and `\input` commands like LaTeX does
45
+ * It calls `pdflatex` to render the final PDF
46
+
47
+ In addition the `\include` and `\input` resolving itself can be called as standalone script.
48
+
49
+
50
+
51
+ ## Caveats
52
+
53
+ * While `\include` and `\input` are resolved from the respective git revisions, other includes like figures are resolved when compiling the diff. This is done in the new revision. So if the old version includes figures that are missing or renamed in the new revision they will be missing in the diff PDF as well.
54
+ * There is no diff of the bibliography and other generated parts.
55
+ * When using uncommitted changes as new version the rendering has to take place in the documents work directory. This may leave temporary files and have other unexpected side effects. However, if everything is committed or dedicated Git revisions are compared all processing is done in temporary directories that are cleaned up.
56
+
57
+
58
+
59
+ ### Prerequesites
60
+
61
+ * LaTeX must be installed including the tools
62
+ * `pdflatex`
63
+ * `latexdiff`
64
+ * Python3 is available with at least the version set in `pyproject.toml`
65
+
66
+
67
+
68
+ ## Usage
69
+
70
+ The easiest way is to apply the Python tool `uv`, which we describe here. Likely `poetry` will work as well.
71
+
72
+ Change to the directory where you cloned the gitlatexdiff project and call
73
+
74
+ ```bash
75
+ uv sync
76
+ ```
77
+
78
+ to install all dependencies and create a virtual environment.
79
+
80
+ To run the script call
81
+
82
+ ```bash
83
+ uv run gitlatexdiff <options>
84
+ ```
85
+
86
+ To run the script to flatten a LeTeX file standalone call
87
+
88
+ ```bash
89
+ uv run flattenlatex <options>
90
+ ```
91
+
92
+
93
+ #### Tips
94
+
95
+ If the diff sources cannot be compiled check the log file for problems with `\DIF...` commands and see which original LaTeX command caused it. Then you may exclude that command from the diff with for example:
96
+
97
+ `-l 'append-textcmd=hint.*,todo' 'exclude-textcmd=title,.*section,chapter'`
98
+
99
+ Here, LaTeX commands `\title`, `\chapter`, and all ending in `section` are excluded, so diffs in these commands are not marked in the output.
100
+
101
+ Note, that in this case the `'append-textcmd=hint.*,todo'` is the default for option `-l`, which needs to be set explicitely if `-l` is given.
102
+
103
+
104
+ ### Options
105
+
106
+ Call `gitlatexdiff` with option `--help` to get the current list of command line options and their defaults if applicable.
107
+
108
+ #### Mandatory options
109
+
110
+ * `-m`, `--main`: Name of the main LaTeX file whose versions should be compared. It has to reside in the respective Git repository containing the versions to compare. May be given with path if `gitlatexdiff` is called from outside the LaTeX project directory.
111
+
112
+ #### Optional
113
+
114
+ All other command line options are optional.
115
+
116
+ Call `gitlatexdiff --help` to see the defaults of the following options.
117
+
118
+ * `-n`, `--new-rev`: Newer revision to compare with. If not given the current state of the work files is used, which will be the HEAD revision if all files are committed or else the work files.
119
+ * `-o`, `--old-rev`: Older revision to compare with. If not given either the revision before `--new-rev` is used or the HEAD revision if `--new-rev` is also not given and there are uncommitted changes.
120
+ * `--old-main`: Name of the old main LaTeX file which should be compared. Defaults to `--main`.
121
+ * `-d`, `--diff-name`: Name of the final diff file. '`.pdf`' will be appended if necessary. The log file of the last `pdflatex` call will be stored beside this file.
122
+ * `-w`, `--overwrite`: If not given `gitlatexdiff` refuses to overwite an existing diff file.
123
+ * `--num-rounds`: Number of calls to `pdflatex` when compiling the diff.
124
+
125
+ The following options are passed to `latexdiff` or `pdflatex` respectively. For technical reasons values have to be given without leading dashes. Dashes are prepended as required by the respective command.
126
+
127
+ * `-l`, `--latexdiff-options`: Arbitrary number of options passed to `latexdiff` call. Pass without any value to turn off the default.
128
+ * `-p`, `--pdflatex-options`: Arbitrary number of options passed to `pdflatex` call. Pass without any value to turn off the default.
129
+
130
+
131
+ ### `flattenlatex`
132
+
133
+ Python module to recusively resolve `\include` and `\input` commands in a LaTeX document. `gitlatexdiff` uses it on both versions of the input file.
134
+
135
+ Call `uv run flattenlatex --help` to see its options to set input and output file. The input file is mandatory, because included files are drawn from its directory. The output file is optional, if omitted, `stdout` will be used.
136
+
137
+
138
+
139
+ ## Documentation
140
+
141
+ Documentation is created with [MkDocs](https://www.mkdocs.org).
142
+
143
+ The following commands called from the base directory create the documentation:
144
+
145
+ * `uv run mkdocs serve` - Start the docs server.
146
+ * `uv run mkdocs build` - Build static documentation in subfolder `site/`
147
+ * `uv run mkdocs --help` - Print help message and exit.
148
+
149
+
150
+
151
+ ## Contributing
152
+
153
+ Create issues or a pull requests to point out bugs or improvements.
154
+
155
+
156
+ ## A note on the name
157
+
158
+ This project is called *Git-LaTeX-Diff Original* or `gitlatexdiff-original`, because the project name *gitlatexdiff* is already in use on [PyPI](https://pypi.org) for a similar [package](https://pypi.org/project/gitlatexdiff/) that was independently developed. It is called *original* due to the fact that the first publication of this project on GitHub is older.
159
+
@@ -0,0 +1,11 @@
1
+ README.md
2
+ pyproject.toml
3
+ src/gitlatexdiff/__init__.py
4
+ src/gitlatexdiff/flatten_latex.py
5
+ src/gitlatexdiff/make_diff.py
6
+ src/gitlatexdiff_original.egg-info/PKG-INFO
7
+ src/gitlatexdiff_original.egg-info/SOURCES.txt
8
+ src/gitlatexdiff_original.egg-info/dependency_links.txt
9
+ src/gitlatexdiff_original.egg-info/entry_points.txt
10
+ src/gitlatexdiff_original.egg-info/requires.txt
11
+ src/gitlatexdiff_original.egg-info/top_level.txt
@@ -0,0 +1,3 @@
1
+ [console_scripts]
2
+ flattenlatex = gitlatexdiff:flattenCommand
3
+ gitlatexdiff = gitlatexdiff:main
@@ -0,0 +1,5 @@
1
+ beartype
2
+ mkdocs
3
+ mkdocstrings[python]
4
+ pymdown-extensions
5
+ icecream