pyDiffTools 0.1.8__py3-none-any.whl
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.
- pydifftools/__init__.py +11 -0
- pydifftools/check_numbers.py +70 -0
- pydifftools/command_line.py +747 -0
- pydifftools/command_registry.py +65 -0
- pydifftools/comment_functions.py +39 -0
- pydifftools/continuous.py +194 -0
- pydifftools/copy_files.py +75 -0
- pydifftools/diff-doc.js +193 -0
- pydifftools/doc_contents.py +147 -0
- pydifftools/flowchart/__init__.py +15 -0
- pydifftools/flowchart/dot_to_yaml.py +114 -0
- pydifftools/flowchart/graph.py +620 -0
- pydifftools/flowchart/watch_graph.py +168 -0
- pydifftools/html_comments.py +33 -0
- pydifftools/html_uncomments.py +524 -0
- pydifftools/match_spaces.py +235 -0
- pydifftools/notebook/__init__.py +0 -0
- pydifftools/notebook/fast_build.py +1502 -0
- pydifftools/notebook/tex_to_qmd.py +319 -0
- pydifftools/onewordify.py +149 -0
- pydifftools/onewordify_undo.py +54 -0
- pydifftools/outline.py +173 -0
- pydifftools/rearrange_tex.py +188 -0
- pydifftools/searchacro.py +80 -0
- pydifftools/separate_comments.py +73 -0
- pydifftools/split_conflict.py +213 -0
- pydifftools/unseparate_comments.py +69 -0
- pydifftools/update_check.py +31 -0
- pydifftools/wrap_sentences.py +501 -0
- pydifftools/xml2xlsx.vbs +33 -0
- pydifftools-0.1.8.dist-info/METADATA +146 -0
- pydifftools-0.1.8.dist-info/RECORD +36 -0
- pydifftools-0.1.8.dist-info/WHEEL +5 -0
- pydifftools-0.1.8.dist-info/entry_points.txt +2 -0
- pydifftools-0.1.8.dist-info/licenses/LICENSE.md +28 -0
- pydifftools-0.1.8.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,747 @@
|
|
|
1
|
+
# PYTHON_ARGCOMPLETE_OK
|
|
2
|
+
|
|
3
|
+
import argparse
|
|
4
|
+
import sys
|
|
5
|
+
import os
|
|
6
|
+
import gzip
|
|
7
|
+
import time
|
|
8
|
+
import subprocess
|
|
9
|
+
import re
|
|
10
|
+
import nbformat
|
|
11
|
+
import difflib
|
|
12
|
+
import shutil
|
|
13
|
+
from pathlib import Path
|
|
14
|
+
from . import (
|
|
15
|
+
match_spaces,
|
|
16
|
+
split_conflict,
|
|
17
|
+
outline,
|
|
18
|
+
update_check,
|
|
19
|
+
)
|
|
20
|
+
from .continuous import cpb
|
|
21
|
+
from .wrap_sentences import wr as wrap_sentences_wr # registers wrap command
|
|
22
|
+
from .separate_comments import tex_sepcomments
|
|
23
|
+
from .unseparate_comments import tex_unsepcomments
|
|
24
|
+
from .comment_functions import matchingbrackets
|
|
25
|
+
from .copy_files import copy_image_files
|
|
26
|
+
from .searchacro import replace_acros
|
|
27
|
+
from .rearrange_tex import run as rearrange_tex_run
|
|
28
|
+
from .flowchart.watch_graph import wgrph
|
|
29
|
+
from .notebook.tex_to_qmd import tex2qmd
|
|
30
|
+
from .notebook.fast_build import qmdb, qmdinit
|
|
31
|
+
|
|
32
|
+
from .command_registry import _COMMAND_SPECS, register_command
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def printed_exec(cmd):
|
|
36
|
+
print("about to execute:\n", cmd)
|
|
37
|
+
result = os.system(cmd)
|
|
38
|
+
if result != 0:
|
|
39
|
+
raise RuntimeError(
|
|
40
|
+
"os.system failed for command:\n"
|
|
41
|
+
+ cmd
|
|
42
|
+
+ "\n\nTry running the command by itself"
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def errmsg():
|
|
47
|
+
parser = build_parser()
|
|
48
|
+
parser.print_help()
|
|
49
|
+
sys.exit(1)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
_ROOT = os.path.abspath(os.path.dirname(__file__))
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def get_data(path):
|
|
56
|
+
"return vbs and js scripts saved as package data"
|
|
57
|
+
return os.path.join(_ROOT, path)
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def recursive_include_search(directory, basename, does_it_input):
|
|
61
|
+
with open(
|
|
62
|
+
os.path.join(directory, basename + ".tex"), "r", encoding="utf-8"
|
|
63
|
+
) as fp:
|
|
64
|
+
alltxt = fp.read()
|
|
65
|
+
# we're only sensitive to the name of the file, not the directory that it's
|
|
66
|
+
# in
|
|
67
|
+
pattern = re.compile(
|
|
68
|
+
r"\n[^%]*\\(?:input|include)[{]((?:[^}]*/)?" + does_it_input + ")[}]"
|
|
69
|
+
)
|
|
70
|
+
for actual_name in pattern.findall(alltxt):
|
|
71
|
+
print(basename + " directly includes " + does_it_input)
|
|
72
|
+
return True, actual_name
|
|
73
|
+
print(
|
|
74
|
+
"file %s didn't directly include '%s' -- I'm going to look for the"
|
|
75
|
+
" files that it includes" % (basename, does_it_input)
|
|
76
|
+
)
|
|
77
|
+
pattern = re.compile(r"\n[^%]*\\(?:input|include)[{]([^}]+)[}](.*)")
|
|
78
|
+
for inputname, extra in pattern.findall(alltxt):
|
|
79
|
+
if "\\input" in extra or "\\include" in extra:
|
|
80
|
+
raise IOError(
|
|
81
|
+
"Don't put multiple include or input statements on one lien"
|
|
82
|
+
" --> are you trying to make my life difficult!!!??? "
|
|
83
|
+
)
|
|
84
|
+
print("%s includes input file:" % basename, inputname)
|
|
85
|
+
retval, actual_name = recursive_include_search(
|
|
86
|
+
directory, os.path.normpath(inputname), does_it_input
|
|
87
|
+
)
|
|
88
|
+
if retval:
|
|
89
|
+
return True, actual_name
|
|
90
|
+
return False, ""
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def look_for_pdf(directory, origbasename):
|
|
94
|
+
"""look for pdf -- if found return tuple(True, the basename of the pdf, the
|
|
95
|
+
basename of the tex) else return tuple(False, "", "")"""
|
|
96
|
+
found = False
|
|
97
|
+
basename = ""
|
|
98
|
+
actual_name = ""
|
|
99
|
+
for fname in os.listdir(directory):
|
|
100
|
+
if fname[-4:] == ".tex":
|
|
101
|
+
basename = fname[:-4]
|
|
102
|
+
print("found tex file", basename)
|
|
103
|
+
if os.path.exists(os.path.join(directory, basename + ".pdf")):
|
|
104
|
+
print("found matching tex/pdf pair", basename)
|
|
105
|
+
retval, actual_name = recursive_include_search(
|
|
106
|
+
directory, basename, origbasename
|
|
107
|
+
)
|
|
108
|
+
if retval:
|
|
109
|
+
return True, basename, actual_name
|
|
110
|
+
if not found:
|
|
111
|
+
print("but it doesn't seem to include", origbasename)
|
|
112
|
+
print("about to check for other inputs")
|
|
113
|
+
return found, basename, actual_name
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
@register_command(
|
|
117
|
+
"Make a python script from a notebook file, following certain rules."
|
|
118
|
+
)
|
|
119
|
+
def nb2py(arguments):
|
|
120
|
+
assert arguments[0].endswith(".ipynb"), (
|
|
121
|
+
"this is supposed to be called with a .ipynb file argument! (arguments"
|
|
122
|
+
" are %s)"
|
|
123
|
+
% repr(arguments)
|
|
124
|
+
)
|
|
125
|
+
nb = nbformat.read(arguments[0], nbformat.NO_CONVERT)
|
|
126
|
+
last_was_markdown = False
|
|
127
|
+
jupyter_magic_re = re.compile(r"%(.*)")
|
|
128
|
+
code_counter = 1
|
|
129
|
+
with open(
|
|
130
|
+
arguments[0].replace(".ipynb", ".py"), "w", encoding="utf-8"
|
|
131
|
+
) as fpout:
|
|
132
|
+
for j in nb.cells:
|
|
133
|
+
lines = j["source"].split("\n")
|
|
134
|
+
if j["cell_type"] == "markdown":
|
|
135
|
+
for line in lines:
|
|
136
|
+
fpout.write("# " + line + "\n")
|
|
137
|
+
if len(lines[-1]) > 0:
|
|
138
|
+
# print "markdown, last is",repr(j['source'][-1])
|
|
139
|
+
fpout.write("\n")
|
|
140
|
+
last_was_markdown = True
|
|
141
|
+
elif j["cell_type"] == "code":
|
|
142
|
+
# fpout.write("start code\n")
|
|
143
|
+
if not last_was_markdown:
|
|
144
|
+
fpout.write("# In[%d]:\n\n" % code_counter)
|
|
145
|
+
code_counter += 1
|
|
146
|
+
for line in lines:
|
|
147
|
+
m = jupyter_magic_re.match(line)
|
|
148
|
+
if m:
|
|
149
|
+
fpout.write(
|
|
150
|
+
"get_ipython().magic(u'%s')\n" % m.groups()[0]
|
|
151
|
+
)
|
|
152
|
+
else:
|
|
153
|
+
fpout.write(line + "\n")
|
|
154
|
+
if len(lines[-1]) > 0:
|
|
155
|
+
# print "code, last is",repr(j['source'][-1])
|
|
156
|
+
fpout.write("\n")
|
|
157
|
+
last_was_markdown = False
|
|
158
|
+
# fpout.write("end code\n")
|
|
159
|
+
else:
|
|
160
|
+
raise ValueError("Unknown cell type")
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
@register_command(
|
|
164
|
+
"Make a notebook file from a python script, following certain rules."
|
|
165
|
+
)
|
|
166
|
+
def py2nb(arguments):
|
|
167
|
+
jupyter_magic_re = re.compile(
|
|
168
|
+
r"^get_ipython\(\).(?:run_line_)?magic\((?:u?['\"]([^'\"]*)['\"])?"
|
|
169
|
+
+ 6 * r"(?:u?, *['\"]([^'\"]*)['\"])?"
|
|
170
|
+
+ r"\)"
|
|
171
|
+
)
|
|
172
|
+
jupyter_cellmagic_re = re.compile(
|
|
173
|
+
r"^get_ipython\(\).run_cell_magic\((?:u?['\"]([^'\"]*)['\"])?"
|
|
174
|
+
+ 6 * r"(?:u?, *['\"]([^'\"]*)['\"])?"
|
|
175
|
+
+ r"\)\)"
|
|
176
|
+
)
|
|
177
|
+
assert len(arguments) == 1, "py2nb should only be called with one argument"
|
|
178
|
+
assert arguments[0].endswith(".py"), (
|
|
179
|
+
"this is supposed to be called with a .py file argument! (arguments"
|
|
180
|
+
" are %s)"
|
|
181
|
+
% repr(arguments)
|
|
182
|
+
)
|
|
183
|
+
with open(arguments[0], encoding="utf-8") as fpin:
|
|
184
|
+
text = fpin.read()
|
|
185
|
+
text = text.split("\n")
|
|
186
|
+
newtext = []
|
|
187
|
+
in_code_cell = False
|
|
188
|
+
last_line_empty = True
|
|
189
|
+
for thisline in text:
|
|
190
|
+
if thisline.startswith("#"):
|
|
191
|
+
if thisline.startswith("#!") and "python" in thisline:
|
|
192
|
+
pass
|
|
193
|
+
elif thisline.startswith("# coding: utf-8"):
|
|
194
|
+
pass
|
|
195
|
+
elif thisline.startswith("# In["):
|
|
196
|
+
in_code_cell = False
|
|
197
|
+
elif thisline.startswith("# Out["):
|
|
198
|
+
pass
|
|
199
|
+
elif thisline.startswith("# "):
|
|
200
|
+
# this is markdown only if the previous line was empty
|
|
201
|
+
if last_line_empty:
|
|
202
|
+
newtext.append("# <markdowncell>")
|
|
203
|
+
in_code_cell = False
|
|
204
|
+
newtext.append(thisline)
|
|
205
|
+
last_line_empty = False
|
|
206
|
+
elif len(thisline) == 0:
|
|
207
|
+
last_line_empty = True
|
|
208
|
+
newtext.append(thisline)
|
|
209
|
+
else:
|
|
210
|
+
if not in_code_cell:
|
|
211
|
+
newtext.append("# <codecell>")
|
|
212
|
+
in_code_cell = True
|
|
213
|
+
m = jupyter_magic_re.match(thisline)
|
|
214
|
+
if m:
|
|
215
|
+
thisline = "%" + " ".join(
|
|
216
|
+
(j for j in m.groups() if j is not None)
|
|
217
|
+
)
|
|
218
|
+
else:
|
|
219
|
+
m = jupyter_cellmagic_re.match(thisline)
|
|
220
|
+
if m:
|
|
221
|
+
thisline = "%%" + " ".join(
|
|
222
|
+
(j for j in m.groups() if j is not None)
|
|
223
|
+
)
|
|
224
|
+
newtext.append(thisline)
|
|
225
|
+
last_line_empty = False
|
|
226
|
+
text = "\n".join(newtext)
|
|
227
|
+
|
|
228
|
+
text += """
|
|
229
|
+
# <markdowncell>
|
|
230
|
+
# If you can read this, reads_py() is no longer broken!
|
|
231
|
+
|
|
232
|
+
"""
|
|
233
|
+
|
|
234
|
+
nbook = nbformat.v3.reads_py(text)
|
|
235
|
+
|
|
236
|
+
nbook = nbformat.v4.upgrade(nbook) # Upgrade nbformat.v3 to nbformat.v4
|
|
237
|
+
nbook.metadata.update({
|
|
238
|
+
"kernelspec": {
|
|
239
|
+
"name": "Python [Anaconda2]",
|
|
240
|
+
"display_name": "Python [Anaconda2]",
|
|
241
|
+
"language": "python",
|
|
242
|
+
}
|
|
243
|
+
})
|
|
244
|
+
|
|
245
|
+
jsonform = nbformat.v4.writes(nbook) + "\n"
|
|
246
|
+
with open(
|
|
247
|
+
arguments[0].replace(".py", ".ipynb"), "w", encoding="utf-8"
|
|
248
|
+
) as fpout:
|
|
249
|
+
fpout.write(jsonform)
|
|
250
|
+
|
|
251
|
+
|
|
252
|
+
@register_command(
|
|
253
|
+
"use a compiled latex original (first arg) to generate a synctex file for"
|
|
254
|
+
" a scanned document (second arg), e.g. with handwritten markup"
|
|
255
|
+
)
|
|
256
|
+
def gensync(arguments):
|
|
257
|
+
with gzip.open(arguments[0].replace(".pdf", ".synctek.gz")) as fp:
|
|
258
|
+
orig_synctex = fp.read()
|
|
259
|
+
fp.close()
|
|
260
|
+
# since the new synctex is in a new place, I need to tell it
|
|
261
|
+
# how to get back to the original place
|
|
262
|
+
relative_path = os.path.relpath(
|
|
263
|
+
os.path.dir(arguments[0]), os.path.dir(arguments[1])
|
|
264
|
+
)
|
|
265
|
+
base_fname = arguments[0].replace(".pdf", "")
|
|
266
|
+
new_synctex = orig_synctex.replace(base_fname, relative_path + base_fname)
|
|
267
|
+
new_synctex = orig_synctex
|
|
268
|
+
with gzip.open(arguments[1].replace(".pdf", ".synctek.gz")) as fp:
|
|
269
|
+
fp.write(new_synctex)
|
|
270
|
+
fp.write(arguments[1].replace())
|
|
271
|
+
fp.close()
|
|
272
|
+
|
|
273
|
+
|
|
274
|
+
@register_command("rearrange TeX file based on a .rrng plan")
|
|
275
|
+
def rrng(arguments):
|
|
276
|
+
rearrange_tex_run(arguments)
|
|
277
|
+
|
|
278
|
+
|
|
279
|
+
@register_command(
|
|
280
|
+
"git forward search, with arguments",
|
|
281
|
+
"git forward search, with arguments\n\n- file\n- line",
|
|
282
|
+
)
|
|
283
|
+
def gvr(arguments):
|
|
284
|
+
cmd = ["gvim"]
|
|
285
|
+
cmd.append("--remote-wait-silent")
|
|
286
|
+
cmd.append("+" + arguments[1])
|
|
287
|
+
cmd.append(arguments[0])
|
|
288
|
+
subprocess.Popen(" ".join(cmd)) # this is forked
|
|
289
|
+
time.sleep(0.3)
|
|
290
|
+
cmd = ["gvim"]
|
|
291
|
+
cmd.append("--remote-send")
|
|
292
|
+
cmd.append('"zO"')
|
|
293
|
+
time.sleep(0.3)
|
|
294
|
+
cmd = ["gvim"]
|
|
295
|
+
cmd.append("--remote-send")
|
|
296
|
+
cmd.append('":cd %:h\n"')
|
|
297
|
+
subprocess.Popen(" ".join(cmd))
|
|
298
|
+
|
|
299
|
+
|
|
300
|
+
@register_command("match whitespace")
|
|
301
|
+
def wmatch(arguments):
|
|
302
|
+
match_spaces.run(arguments)
|
|
303
|
+
|
|
304
|
+
|
|
305
|
+
@register_command("split conflict")
|
|
306
|
+
def sc(arguments):
|
|
307
|
+
split_conflict.run(arguments)
|
|
308
|
+
|
|
309
|
+
|
|
310
|
+
@register_command("word diff")
|
|
311
|
+
def wd(arguments):
|
|
312
|
+
if arguments[0].find("Temp") > 0:
|
|
313
|
+
# {{{ if it's a temporary file, I need to make a real copy to run
|
|
314
|
+
# pandoc on
|
|
315
|
+
fp = open(arguments[0], encoding="utf-8")
|
|
316
|
+
contents = fp.read()
|
|
317
|
+
fp.close()
|
|
318
|
+
fp = open(
|
|
319
|
+
arguments[1].replace(".md", "_old.md"), "w", encoding="utf-8"
|
|
320
|
+
)
|
|
321
|
+
fp.write(contents)
|
|
322
|
+
fp.close()
|
|
323
|
+
arguments[0] = arguments[1].replace(".md", "_old.md")
|
|
324
|
+
# }}}
|
|
325
|
+
word_files = [x.replace(".md", ".docx") for x in arguments[:2]]
|
|
326
|
+
local_dir = os.path.dirname(arguments[1])
|
|
327
|
+
print("local directory:", local_dir)
|
|
328
|
+
for j in range(2):
|
|
329
|
+
if arguments[0][-5:] == ".docx":
|
|
330
|
+
print(
|
|
331
|
+
"the first argument has a docx extension, so I'm bypassing the"
|
|
332
|
+
" pandoc step"
|
|
333
|
+
)
|
|
334
|
+
else:
|
|
335
|
+
cmd = ["pandoc"]
|
|
336
|
+
cmd += [arguments[j]]
|
|
337
|
+
cmd += ["--csl=edited-pmid-format.csl"]
|
|
338
|
+
cmd += ["--bibliography library_abbrev_utf8.bib"]
|
|
339
|
+
cmd += ["-s --smart"]
|
|
340
|
+
if len(arguments) > 2:
|
|
341
|
+
if arguments[2][-5:] == ".docx":
|
|
342
|
+
cmd += ["--reference-docx=" + arguments[2]]
|
|
343
|
+
else:
|
|
344
|
+
raise RuntimeError(
|
|
345
|
+
"if you pass three arguments to wd, then the third"
|
|
346
|
+
" must be a template for the word document"
|
|
347
|
+
)
|
|
348
|
+
elif os.path.isfile(local_dir + os.path.sep + "template.docx"):
|
|
349
|
+
# by default, use template.docx in the current directory
|
|
350
|
+
cmd += [
|
|
351
|
+
"--reference-docx="
|
|
352
|
+
+ local_dir
|
|
353
|
+
+ os.path.sep
|
|
354
|
+
+ "template.docx"
|
|
355
|
+
]
|
|
356
|
+
cmd += ["-o"]
|
|
357
|
+
cmd += [word_files[j]]
|
|
358
|
+
print("about to run", " ".join(cmd))
|
|
359
|
+
os.system(" ".join(cmd))
|
|
360
|
+
cmd = ["start"]
|
|
361
|
+
cmd += [get_data("diff-doc.js")]
|
|
362
|
+
print("word files are", word_files)
|
|
363
|
+
if word_files[0].find("C:") > -1:
|
|
364
|
+
cmd += [word_files[0]]
|
|
365
|
+
else:
|
|
366
|
+
cmd += [os.getcwd() + os.path.sep + word_files[0]]
|
|
367
|
+
cmd += [os.getcwd() + os.path.sep + word_files[1]]
|
|
368
|
+
print("about to run", " ".join(cmd))
|
|
369
|
+
os.system(" ".join(cmd))
|
|
370
|
+
|
|
371
|
+
|
|
372
|
+
@register_command("Reverse search")
|
|
373
|
+
def rs(arguments):
|
|
374
|
+
cmd = [
|
|
375
|
+
"gvim",
|
|
376
|
+
"--servername",
|
|
377
|
+
"GVIM",
|
|
378
|
+
"--remote",
|
|
379
|
+
f"+{arguments[0]} {arguments[1]}",
|
|
380
|
+
]
|
|
381
|
+
os.system(" ".join(cmd))
|
|
382
|
+
cmd = ["wmctrl", "-a", "GVIM"]
|
|
383
|
+
os.system(" ".join(cmd))
|
|
384
|
+
time.sleep(0.5)
|
|
385
|
+
cmd = ["gvim", "--remote-send", "'<esc>zO:cd %:h'<enter>"]
|
|
386
|
+
os.system(" ".join(cmd))
|
|
387
|
+
|
|
388
|
+
|
|
389
|
+
@register_command(
|
|
390
|
+
"smart latex forward-search",
|
|
391
|
+
"smart latex forward-search\n"
|
|
392
|
+
"currently this works specifically for sumatra pdf located\n"
|
|
393
|
+
'at "C:\\Program Files\\SumatraPDF\\SumatraPDF.exe",\n'
|
|
394
|
+
"but can easily be adapted based on os, etc.\n"
|
|
395
|
+
"Add the following line (or something like it) to your vimrc:\n"
|
|
396
|
+
'map <c-F>s :cd %:h\\|sil !pydifft fs %:p <c-r>=line(".")<cr><cr>\n'
|
|
397
|
+
"it will map Cntrl-F s to a forward search.",
|
|
398
|
+
)
|
|
399
|
+
def fs(arguments):
|
|
400
|
+
texfile, lineno = arguments
|
|
401
|
+
texfile = os.path.normpath(os.path.abspath(texfile))
|
|
402
|
+
directory, texfile = texfile.rsplit(os.path.sep, 1)
|
|
403
|
+
assert texfile[-4:] == ".tex", "needs to be called .tex"
|
|
404
|
+
origbasename = texfile[:-4]
|
|
405
|
+
if os.name == "posix":
|
|
406
|
+
# linux
|
|
407
|
+
cmd = ["zathura --synctex-forward"]
|
|
408
|
+
assert shutil.which("zathura"), (
|
|
409
|
+
"first, install zathura, then set ~/.config/zathura/zathurarc"
|
|
410
|
+
"to include"
|
|
411
|
+
'set synctex-editor-command "pydifft rs %{line} %{input}"'
|
|
412
|
+
)
|
|
413
|
+
else:
|
|
414
|
+
# windows
|
|
415
|
+
cmd = ["start sumatrapdf -reuse-instance"]
|
|
416
|
+
if os.path.exists(os.path.join(directory, origbasename + ".pdf")):
|
|
417
|
+
temp = os.path.join(directory, origbasename + ".pdf")
|
|
418
|
+
cmd.append(f"{lineno}:0:{texfile} {temp}")
|
|
419
|
+
tex_name = origbasename
|
|
420
|
+
else:
|
|
421
|
+
print("no pdf file for this guy, looking for one that has one")
|
|
422
|
+
found, basename, tex_name = look_for_pdf(directory, origbasename)
|
|
423
|
+
if not found:
|
|
424
|
+
while os.path.sep in directory and directory.lower()[-1] != ":":
|
|
425
|
+
build_nb = os.path.join(directory, "build_nb")
|
|
426
|
+
directory, _ = directory.rsplit(os.path.sep, 1)
|
|
427
|
+
if os.path.exists(build_nb):
|
|
428
|
+
print("looking for a build_nb subdirectory")
|
|
429
|
+
found, basename, tex_name = look_for_pdf(
|
|
430
|
+
build_nb, origbasename
|
|
431
|
+
)
|
|
432
|
+
if found:
|
|
433
|
+
directory = build_nb
|
|
434
|
+
break
|
|
435
|
+
print("looking one directory up, in ", directory)
|
|
436
|
+
found, basename, tex_name = look_for_pdf(
|
|
437
|
+
directory, origbasename
|
|
438
|
+
)
|
|
439
|
+
if found:
|
|
440
|
+
break
|
|
441
|
+
if not found:
|
|
442
|
+
raise IOError("This is not the PDF you are looking for!!!")
|
|
443
|
+
print("result:", directory, origbasename, found, basename, tex_name)
|
|
444
|
+
# file has been found, so add to the command
|
|
445
|
+
cmd.append(
|
|
446
|
+
f"{lineno}:0:{tex_name}.tex"
|
|
447
|
+
f" {os.path.join(directory, basename + '.pdf')}"
|
|
448
|
+
)
|
|
449
|
+
if os.name == "posix":
|
|
450
|
+
cmd.append("&")
|
|
451
|
+
else:
|
|
452
|
+
cmd.append("-forward-search")
|
|
453
|
+
cmd.append(tex_name + ".tex")
|
|
454
|
+
cmd.append("%s -fwdsearch-color ff0000" % lineno)
|
|
455
|
+
print("changing to directory", directory)
|
|
456
|
+
os.chdir(directory)
|
|
457
|
+
print("about to execute:\n\t", " ".join(cmd))
|
|
458
|
+
os.system(" ".join(cmd))
|
|
459
|
+
if os.name == "posix":
|
|
460
|
+
cmd = ["wmctrl", "-a", basename + ".pdf"]
|
|
461
|
+
os.system(" ".join(cmd))
|
|
462
|
+
|
|
463
|
+
|
|
464
|
+
@register_command("Convert xml to xlsx")
|
|
465
|
+
def xx(arguments):
|
|
466
|
+
format_codes = {
|
|
467
|
+
"csv": 6,
|
|
468
|
+
"xlsx": 51,
|
|
469
|
+
"xml": 46,
|
|
470
|
+
} # determined by microsoft vbs
|
|
471
|
+
cmd = ["start"]
|
|
472
|
+
cmd += [get_data("xml2xlsx.vbs")]
|
|
473
|
+
first_ext = arguments[0].split(".")[-1]
|
|
474
|
+
second_ext = arguments[1].split(".")[-1]
|
|
475
|
+
for j in arguments[0:2]:
|
|
476
|
+
if j.find("C:") > -1:
|
|
477
|
+
cmd += [j]
|
|
478
|
+
else:
|
|
479
|
+
cmd += [os.getcwd() + os.path.sep + j]
|
|
480
|
+
cmd += [str(format_codes[j]) for j in [first_ext, second_ext]]
|
|
481
|
+
print("about to run", " ".join(cmd))
|
|
482
|
+
os.system(" ".join(cmd))
|
|
483
|
+
|
|
484
|
+
|
|
485
|
+
@register_command("compare files, and rank by how well they compare")
|
|
486
|
+
def cmp(arguments):
|
|
487
|
+
target = arguments[0]
|
|
488
|
+
arguments = arguments[1:]
|
|
489
|
+
with open(target, encoding="utf-8") as fp:
|
|
490
|
+
base_txt = fp.read()
|
|
491
|
+
retval = {}
|
|
492
|
+
for j in arguments:
|
|
493
|
+
with open(j, encoding="utf-8") as fp:
|
|
494
|
+
retval[j] = difflib.SequenceMatcher(
|
|
495
|
+
None, base_txt, fp.read()
|
|
496
|
+
).ratio()
|
|
497
|
+
print(
|
|
498
|
+
"\n".join(
|
|
499
|
+
str(v) + "-->" + str(k)
|
|
500
|
+
for k, v in sorted(
|
|
501
|
+
retval.items(), key=lambda item: item[1], reverse=True
|
|
502
|
+
)
|
|
503
|
+
)
|
|
504
|
+
)
|
|
505
|
+
|
|
506
|
+
|
|
507
|
+
@register_command("tex separate comments")
|
|
508
|
+
def sepc(arguments):
|
|
509
|
+
tex_sepcomments(arguments[0])
|
|
510
|
+
|
|
511
|
+
|
|
512
|
+
@register_command("tex unseparate comments")
|
|
513
|
+
def unsepc(arguments):
|
|
514
|
+
tex_unsepcomments(arguments[0])
|
|
515
|
+
|
|
516
|
+
|
|
517
|
+
@register_command("convert annotated TeX sources to DOCX via pandoc")
|
|
518
|
+
def tex2docx(arguments):
|
|
519
|
+
filename = arguments[0]
|
|
520
|
+
assert filename[-4:] == ".tex"
|
|
521
|
+
basename = filename[:-4]
|
|
522
|
+
with open("%s.tex" % basename, "r", encoding="utf-8") as fp:
|
|
523
|
+
content = fp.read()
|
|
524
|
+
comment_re = re.compile(r"\\pdfcomment([A-Z]+)\b")
|
|
525
|
+
thismatch = comment_re.search(
|
|
526
|
+
content
|
|
527
|
+
) # match doesn't work with newlines, apparently
|
|
528
|
+
while thismatch:
|
|
529
|
+
a = thismatch.start()
|
|
530
|
+
b, c = matchingbrackets(content, a, "{")
|
|
531
|
+
content = (
|
|
532
|
+
content[:a]
|
|
533
|
+
+ content[a + 1 : b]
|
|
534
|
+
+ "("
|
|
535
|
+
+ content[b + 1 : c]
|
|
536
|
+
+ ")"
|
|
537
|
+
+ content[c + 1 :]
|
|
538
|
+
)
|
|
539
|
+
thismatch = comment_re.search(content)
|
|
540
|
+
with open("%s_parencomments.tex" % basename, "w", encoding="utf-8") as fp:
|
|
541
|
+
fp.write(r"\renewcommand{\nts}[1]{\textbf{\textit{#1}}}" + "\n")
|
|
542
|
+
fp.write(content)
|
|
543
|
+
printed_exec(
|
|
544
|
+
"pandoc %s_parencomments.tex -f latex+latex_macros -o %s.md"
|
|
545
|
+
% ((basename,) * 2)
|
|
546
|
+
)
|
|
547
|
+
with open("%s.md" % basename, "r", encoding="utf-8") as fp:
|
|
548
|
+
content = fp.read()
|
|
549
|
+
thisid = 2
|
|
550
|
+
comment_re = re.compile(r"pdfcomment([A-Z]+)\(")
|
|
551
|
+
thismatch = comment_re.search(
|
|
552
|
+
content
|
|
553
|
+
) # match doesn't work with newlines, apparently
|
|
554
|
+
while thismatch:
|
|
555
|
+
a = thismatch.start()
|
|
556
|
+
b, c = matchingbrackets(content, a, "(")
|
|
557
|
+
author = thismatch.groups()[0]
|
|
558
|
+
content = (
|
|
559
|
+
content[:a]
|
|
560
|
+
+ '[%s]{.comment-start id="%d" author="%s"}'
|
|
561
|
+
% (content[b + 1 : c], thisid, author)
|
|
562
|
+
+ '[]{.comment-end id="%d"}' % thisid
|
|
563
|
+
+ content[c + 1 :]
|
|
564
|
+
)
|
|
565
|
+
thisid += 1
|
|
566
|
+
thismatch = comment_re.search(content)
|
|
567
|
+
with open("%s.md" % basename, "w", encoding="utf-8") as fp:
|
|
568
|
+
fp.write(content)
|
|
569
|
+
printed_exec("pandoc %s.md -o %s.docx" % ((basename,) * 2))
|
|
570
|
+
printed_exec("start %s.docx" % (basename))
|
|
571
|
+
|
|
572
|
+
|
|
573
|
+
@register_command("convert DOCX documents back to TeX while cleaning markup")
|
|
574
|
+
def docx2tex(arguments):
|
|
575
|
+
filename = arguments[0]
|
|
576
|
+
assert filename[-5:] == ".docx"
|
|
577
|
+
basename = filename[:-5]
|
|
578
|
+
printed_exec(
|
|
579
|
+
"pandoc %s.docx --track-changes=all -o %s.md" % ((basename,) * 2)
|
|
580
|
+
)
|
|
581
|
+
with open("%s.md" % basename, "r", encoding="utf-8") as fp:
|
|
582
|
+
content = fp.read()
|
|
583
|
+
citation_re = re.compile(r"\\\[\\@")
|
|
584
|
+
thismatch = citation_re.search(
|
|
585
|
+
content
|
|
586
|
+
) # match doesn't work with newlines, apparently
|
|
587
|
+
while thismatch:
|
|
588
|
+
a, b = matchingbrackets(content, thismatch.start(), "[")
|
|
589
|
+
content = (
|
|
590
|
+
content[: a - 1] + content[a:b].replace("\\", "") + content[b:]
|
|
591
|
+
)
|
|
592
|
+
thismatch = citation_re.search(content)
|
|
593
|
+
with open("%s.md" % basename, "w", encoding="utf-8") as fp:
|
|
594
|
+
fp.write(content)
|
|
595
|
+
printed_exec(
|
|
596
|
+
"pandoc %s.md --biblatex -r markdown-auto_identifiers -o %s_reconv.tex"
|
|
597
|
+
% ((basename,) * 2)
|
|
598
|
+
)
|
|
599
|
+
print("about to match spaces:")
|
|
600
|
+
match_spaces.run((basename + ".tex", basename + "_reconv.tex"))
|
|
601
|
+
with open("%s_reconv.tex" % basename, "r", encoding="utf-8") as fp:
|
|
602
|
+
content = fp.read()
|
|
603
|
+
citation_re = re.compile(r"\\autocite\b")
|
|
604
|
+
content = citation_re.sub(r"\\cite", content)
|
|
605
|
+
paragraph_re = re.compile(r"\n\n(\\paragraph{.*)\n\n")
|
|
606
|
+
content = paragraph_re.sub(r"\1", content)
|
|
607
|
+
# {{{ convert \( to dollars
|
|
608
|
+
math_re = re.compile(r"\\\(")
|
|
609
|
+
thismatch = math_re.search(
|
|
610
|
+
content
|
|
611
|
+
) # match doesn't work with newlines, apparently
|
|
612
|
+
while thismatch:
|
|
613
|
+
a = thismatch.start()
|
|
614
|
+
b, c = matchingbrackets(content, a, "(")
|
|
615
|
+
content = (
|
|
616
|
+
content[:a] + "$" + content[a + 1 : b] + "$" + content[c + 1 :]
|
|
617
|
+
)
|
|
618
|
+
thismatch = math_re.search(content)
|
|
619
|
+
# }}}
|
|
620
|
+
with open("%s_reconv.tex" % basename, "w", encoding="utf-8") as fp:
|
|
621
|
+
fp.write(content)
|
|
622
|
+
|
|
623
|
+
|
|
624
|
+
@register_command(
|
|
625
|
+
"make a gzip file suitable for arxiv (currently only test on linux)"
|
|
626
|
+
)
|
|
627
|
+
def arxiv(arguments):
|
|
628
|
+
ROOT_TEX = arguments[0].strip(".tex")
|
|
629
|
+
project_name = Path.cwd().name
|
|
630
|
+
TARGET_DIR = Path(f"../{project_name}_forarxiv/")
|
|
631
|
+
include_suppinfo = False
|
|
632
|
+
copy_image_files(ROOT_TEX, project_name, TARGET_DIR, include_suppinfo)
|
|
633
|
+
os.chdir(Path.cwd().parent)
|
|
634
|
+
output_filename = f"{project_name}_forarxiv.tgz"
|
|
635
|
+
# create tar process
|
|
636
|
+
tar = subprocess.Popen(
|
|
637
|
+
["tar", "cf", "-", TARGET_DIR.name], stdout=subprocess.PIPE
|
|
638
|
+
)
|
|
639
|
+
# create gzip process, using tar's stdout as its stdin
|
|
640
|
+
gzip = subprocess.Popen(
|
|
641
|
+
["gzip", "-9"], stdin=tar.stdout, stdout=subprocess.PIPE
|
|
642
|
+
)
|
|
643
|
+
# close tar's stdout so it doesn't hang around waiting for input
|
|
644
|
+
tar.stdout.close()
|
|
645
|
+
# write gzip's stdout to a file
|
|
646
|
+
with open(output_filename, "wb") as fp:
|
|
647
|
+
shutil.copyfileobj(gzip.stdout, fp)
|
|
648
|
+
gzip.stdout.close()
|
|
649
|
+
|
|
650
|
+
|
|
651
|
+
@register_command(
|
|
652
|
+
"look for the file myacronyms.sty (locally or in texmf) and use it"
|
|
653
|
+
" substitute your acronyms"
|
|
654
|
+
)
|
|
655
|
+
def ac(arguments):
|
|
656
|
+
# Run kpsewhich and capture the output
|
|
657
|
+
kpsewhich_output = subprocess.run(
|
|
658
|
+
["kpsewhich", "myacronyms.sty"], capture_output=True, text=True
|
|
659
|
+
)
|
|
660
|
+
|
|
661
|
+
# Convert the string to a pathlib Path object if the file was found, else
|
|
662
|
+
# assign None
|
|
663
|
+
path = (
|
|
664
|
+
Path(kpsewhich_output.stdout.strip())
|
|
665
|
+
if kpsewhich_output.returncode == 0
|
|
666
|
+
else None
|
|
667
|
+
)
|
|
668
|
+
print("I'm using the acronyms in", path)
|
|
669
|
+
replace_acros(path)
|
|
670
|
+
|
|
671
|
+
|
|
672
|
+
@register_command(
|
|
673
|
+
"Paste mark down as formatted text into email, etc. (Tested on linux)"
|
|
674
|
+
)
|
|
675
|
+
def pmd(arguments):
|
|
676
|
+
# pandoc input.md -t html -o - | xclip -selection clipboard -t text/html
|
|
677
|
+
p1 = subprocess.Popen(
|
|
678
|
+
["pandoc", arguments[0], "-t", "html", "-o", "-"],
|
|
679
|
+
stdout=subprocess.PIPE,
|
|
680
|
+
)
|
|
681
|
+
subprocess.run(
|
|
682
|
+
["xclip", "-selection", "clipboard", "-t", "text/html"],
|
|
683
|
+
stdin=p1.stdout,
|
|
684
|
+
check=True,
|
|
685
|
+
)
|
|
686
|
+
p1.stdout.close()
|
|
687
|
+
p1.wait()
|
|
688
|
+
|
|
689
|
+
|
|
690
|
+
def build_parser():
|
|
691
|
+
parser = argparse.ArgumentParser()
|
|
692
|
+
subparsers = parser.add_subparsers(dest="command")
|
|
693
|
+
subparsers.required = True
|
|
694
|
+
for name, spec in _COMMAND_SPECS.items():
|
|
695
|
+
subparser = subparsers.add_parser(
|
|
696
|
+
name,
|
|
697
|
+
help=spec["help"],
|
|
698
|
+
description=spec["description"],
|
|
699
|
+
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
700
|
+
)
|
|
701
|
+
arguments = []
|
|
702
|
+
if "arguments" in spec:
|
|
703
|
+
arguments = spec["arguments"]
|
|
704
|
+
for argument in arguments:
|
|
705
|
+
flags = argument["flags"]
|
|
706
|
+
kwargs = dict(argument["kwargs"])
|
|
707
|
+
subparser.add_argument(*flags, **kwargs)
|
|
708
|
+
subparser.set_defaults(_handler=spec["handler"])
|
|
709
|
+
return parser
|
|
710
|
+
|
|
711
|
+
|
|
712
|
+
def main(argv=None):
|
|
713
|
+
if argv is None:
|
|
714
|
+
argv = sys.argv[1:]
|
|
715
|
+
# Run the PyPI update check once per UTC day so users see a notice but
|
|
716
|
+
# startup stays fast when offline. The date is stored in the
|
|
717
|
+
# PYDIFFTOOLS_UPDATE_CHECK_LAST_RAN_UTC_DATE environment variable.
|
|
718
|
+
today = time.strftime("%Y-%m-%d", time.gmtime())
|
|
719
|
+
already_checked_today = (
|
|
720
|
+
"PYDIFFTOOLS_UPDATE_CHECK_LAST_RAN_UTC_DATE" in os.environ
|
|
721
|
+
and os.environ["PYDIFFTOOLS_UPDATE_CHECK_LAST_RAN_UTC_DATE"] == today
|
|
722
|
+
)
|
|
723
|
+
if not already_checked_today:
|
|
724
|
+
os.environ["PYDIFFTOOLS_UPDATE_CHECK_LAST_RAN_UTC_DATE"] = today
|
|
725
|
+
current_version, latest_version, is_outdated = (
|
|
726
|
+
update_check.check_update("pyDiffTools")
|
|
727
|
+
)
|
|
728
|
+
if is_outdated and latest_version is not None:
|
|
729
|
+
print(
|
|
730
|
+
"A new pyDiffTools version is available "
|
|
731
|
+
f"(installed {current_version}, latest {latest_version}).",
|
|
732
|
+
file=sys.stderr,
|
|
733
|
+
)
|
|
734
|
+
parser = build_parser()
|
|
735
|
+
if not argv:
|
|
736
|
+
parser.print_help()
|
|
737
|
+
return
|
|
738
|
+
namespace = parser.parse_args(argv)
|
|
739
|
+
handler = namespace._handler
|
|
740
|
+
handler_kwargs = dict(vars(namespace))
|
|
741
|
+
handler_kwargs.pop("_handler", None)
|
|
742
|
+
handler_kwargs.pop("command", None)
|
|
743
|
+
handler(**handler_kwargs)
|
|
744
|
+
|
|
745
|
+
|
|
746
|
+
if __name__ == "__main__":
|
|
747
|
+
main()
|