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,147 @@
|
|
|
1
|
+
from collections import OrderedDict
|
|
2
|
+
from fuzzywuzzy import process
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class doc_contents_class(object):
|
|
6
|
+
def __init__(self, format_type="latex"):
|
|
7
|
+
self.contents = OrderedDict()
|
|
8
|
+
self.contents["header"] = ""
|
|
9
|
+
self.types = {}
|
|
10
|
+
self.types["header"] = "header"
|
|
11
|
+
self._reordering_started = False
|
|
12
|
+
self._aliases = {}
|
|
13
|
+
self._processed_titles = []
|
|
14
|
+
self.set_format(format_type)
|
|
15
|
+
|
|
16
|
+
def set_format(self, format_type):
|
|
17
|
+
if format_type == "markdown":
|
|
18
|
+
# markdown levels go deeper, so include subparagraph mapping
|
|
19
|
+
self.level_numbers = {
|
|
20
|
+
"section": 1,
|
|
21
|
+
"subsection": 2,
|
|
22
|
+
"subsubsection": 3,
|
|
23
|
+
"paragraph": 4,
|
|
24
|
+
"subparagraph": 5,
|
|
25
|
+
}
|
|
26
|
+
else:
|
|
27
|
+
# default to latex behavior
|
|
28
|
+
self.level_numbers = {
|
|
29
|
+
"section": 1,
|
|
30
|
+
"subsection": 2,
|
|
31
|
+
"subsubsection": 3,
|
|
32
|
+
"paragraph": 4,
|
|
33
|
+
"subparagraph": 5,
|
|
34
|
+
}
|
|
35
|
+
# map indentation back to section type for outline parsing
|
|
36
|
+
self.inv_prefix = {
|
|
37
|
+
(level - 1) * "\t": section
|
|
38
|
+
for section, level in self.level_numbers.items()
|
|
39
|
+
}
|
|
40
|
+
self.format_type = format_type
|
|
41
|
+
|
|
42
|
+
def start_sec(self, thistype, thistitle):
|
|
43
|
+
assert thistitle not in self.contents.keys(), (
|
|
44
|
+
"more than one section with the name:\n" + thistitle
|
|
45
|
+
)
|
|
46
|
+
self.contents[thistitle] = ""
|
|
47
|
+
self.types[thistitle] = thistype
|
|
48
|
+
print("added", thistitle)
|
|
49
|
+
|
|
50
|
+
def __setstate__(self, d):
|
|
51
|
+
"set the info from a pickle"
|
|
52
|
+
self.contents = d["contents"]
|
|
53
|
+
self.types = d["types"]
|
|
54
|
+
self._aliases = {} # doesn't exist, but still needed
|
|
55
|
+
self._reordering_started = False
|
|
56
|
+
self._processed_titles = []
|
|
57
|
+
if "format_type" in d:
|
|
58
|
+
self.set_format(d["format_type"])
|
|
59
|
+
else:
|
|
60
|
+
self.set_format("latex")
|
|
61
|
+
return
|
|
62
|
+
|
|
63
|
+
def __getstate__(self):
|
|
64
|
+
"return info for a pickle"
|
|
65
|
+
return {
|
|
66
|
+
"contents": self.contents,
|
|
67
|
+
"types": self.types,
|
|
68
|
+
"format_type": self.format_type,
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
def __iadd__(self, value):
|
|
72
|
+
self.contents[next(reversed(self.contents))] += value
|
|
73
|
+
return self
|
|
74
|
+
|
|
75
|
+
def __str__(self):
|
|
76
|
+
if len(self._processed_titles) > 0:
|
|
77
|
+
raise ValueError(
|
|
78
|
+
"the following section"
|
|
79
|
+
" titles were not utilized -- this program is"
|
|
80
|
+
" for reordering, not dropping!:\n"
|
|
81
|
+
+ str(self._processed_titles)
|
|
82
|
+
)
|
|
83
|
+
retval = ""
|
|
84
|
+
for j in self.contents.keys():
|
|
85
|
+
if self.types[j] != "header":
|
|
86
|
+
new_name = j
|
|
87
|
+
if j in self._aliases.keys():
|
|
88
|
+
new_name = self._aliases[j]
|
|
89
|
+
if self.format_type == "markdown":
|
|
90
|
+
retval += "#" * self.level_numbers[self.types[j]]
|
|
91
|
+
retval += f" {new_name}\n\n"
|
|
92
|
+
else:
|
|
93
|
+
retval += f"\\{self.types[j]}{{{new_name}}}"
|
|
94
|
+
retval += f"{self.contents[j]}"
|
|
95
|
+
return retval
|
|
96
|
+
|
|
97
|
+
@property
|
|
98
|
+
def outline(self):
|
|
99
|
+
retval = []
|
|
100
|
+
for j in self.contents.keys():
|
|
101
|
+
if self.types[j] != "header":
|
|
102
|
+
indent = (self.level_numbers[self.types[j]] - 1) * "\t"
|
|
103
|
+
thistitle = (indent + "\t").join(j.split("\n"))
|
|
104
|
+
retval.append(indent + "*\t" + thistitle)
|
|
105
|
+
self._reordering_started = False
|
|
106
|
+
return "\n".join(retval)
|
|
107
|
+
|
|
108
|
+
def outline_in_order(self, thisline):
|
|
109
|
+
if not self._reordering_started:
|
|
110
|
+
self._processed_titles = [
|
|
111
|
+
j for j in self.contents.keys() if self.types[j] != "header"
|
|
112
|
+
]
|
|
113
|
+
self._reordering_started = True
|
|
114
|
+
ilevel = 0
|
|
115
|
+
spacelevel = 0
|
|
116
|
+
hitmarker = False
|
|
117
|
+
for j, thischar in enumerate(thisline):
|
|
118
|
+
if not hitmarker:
|
|
119
|
+
if thischar == " ":
|
|
120
|
+
spacelevel += 1
|
|
121
|
+
if spacelevel == 4 or thischar == "\t":
|
|
122
|
+
ilevel += 1
|
|
123
|
+
spacelevel = 0
|
|
124
|
+
elif thischar == "*":
|
|
125
|
+
hitmarker = True
|
|
126
|
+
else:
|
|
127
|
+
assert thischar in [" ", "\t"]
|
|
128
|
+
title = thisline[j + 1 :]
|
|
129
|
+
break
|
|
130
|
+
if not hitmarker:
|
|
131
|
+
raise ValueError("somehow, there wasn't a * marker!")
|
|
132
|
+
if title not in self.contents.keys():
|
|
133
|
+
best_match, match_quality = process.extractOne(
|
|
134
|
+
title, self.contents.keys()
|
|
135
|
+
)
|
|
136
|
+
yesorno = input(
|
|
137
|
+
f"didn't find\n\t{title}\nin keys, maybe you"
|
|
138
|
+
f" want\n\t{best_match}\nsay y or n"
|
|
139
|
+
)
|
|
140
|
+
if yesorno == "y":
|
|
141
|
+
self._aliases[best_match] = title # will be replaced later
|
|
142
|
+
title = best_match
|
|
143
|
+
else:
|
|
144
|
+
raise ValueError("problem with replacement")
|
|
145
|
+
self.contents.move_to_end(title)
|
|
146
|
+
self._processed_titles.remove(title)
|
|
147
|
+
self.types[title] = self.inv_prefix[ilevel * "\t"]
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"""Flowchart helpers for dot/yaml conversion and watching.
|
|
2
|
+
|
|
3
|
+
Heavy dependencies (like PyYAML) are imported lazily so unrelated CLI commands
|
|
4
|
+
can start up even when optional packages are absent.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
__all__ = [
|
|
8
|
+
"IndentDumper",
|
|
9
|
+
"load_graph_yaml",
|
|
10
|
+
"write_dot_from_yaml",
|
|
11
|
+
"dot_to_yaml",
|
|
12
|
+
"watch_graph",
|
|
13
|
+
]
|
|
14
|
+
|
|
15
|
+
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import sys
|
|
2
|
+
import re
|
|
3
|
+
import yaml
|
|
4
|
+
import pydot
|
|
5
|
+
|
|
6
|
+
from .graph import IndentDumper
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def _label_to_text(label: str) -> str:
|
|
10
|
+
if label is None:
|
|
11
|
+
return None
|
|
12
|
+
stripped = label.strip()
|
|
13
|
+
if stripped.startswith('<') and stripped.endswith('>'):
|
|
14
|
+
stripped = stripped[1:-1]
|
|
15
|
+
stripped = stripped.replace('<br align="left"/>', '\n')
|
|
16
|
+
stripped = stripped.replace('<br/>', '\n')
|
|
17
|
+
lines = [l.strip() for l in stripped.splitlines()]
|
|
18
|
+
out_lines = []
|
|
19
|
+
current: list[str] = []
|
|
20
|
+
|
|
21
|
+
def flush():
|
|
22
|
+
nonlocal current
|
|
23
|
+
if current:
|
|
24
|
+
out_lines.append(' '.join(current))
|
|
25
|
+
current = []
|
|
26
|
+
|
|
27
|
+
for s in lines:
|
|
28
|
+
if not s:
|
|
29
|
+
continue
|
|
30
|
+
if s.startswith('<font'):
|
|
31
|
+
flush()
|
|
32
|
+
if out_lines and out_lines[-1] != '':
|
|
33
|
+
out_lines.append('')
|
|
34
|
+
out_lines.append(s)
|
|
35
|
+
elif s.startswith('</font'):
|
|
36
|
+
flush()
|
|
37
|
+
out_lines.append(s)
|
|
38
|
+
elif s.startswith('•'):
|
|
39
|
+
flush()
|
|
40
|
+
current = ['* ' + s[1:].lstrip()]
|
|
41
|
+
elif re.match(r'\d+[.)]\s', s):
|
|
42
|
+
flush()
|
|
43
|
+
current = [s]
|
|
44
|
+
else:
|
|
45
|
+
if current:
|
|
46
|
+
current.append(s)
|
|
47
|
+
else:
|
|
48
|
+
current = [s]
|
|
49
|
+
flush()
|
|
50
|
+
return '\n'.join(out_lines)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def dot_to_yaml(dot_path, yaml_path):
|
|
54
|
+
graphs = pydot.graph_from_dot_file(dot_path)
|
|
55
|
+
if not graphs:
|
|
56
|
+
raise ValueError("No graph found in dot file")
|
|
57
|
+
graph = graphs[0]
|
|
58
|
+
nodes = {}
|
|
59
|
+
for node in graph.get_nodes():
|
|
60
|
+
name = node.get_name().strip('"')
|
|
61
|
+
if name in ('graph', 'node', 'edge'):
|
|
62
|
+
continue
|
|
63
|
+
label = node.get('label')
|
|
64
|
+
if label is not None:
|
|
65
|
+
label = label.strip('"')
|
|
66
|
+
label = _label_to_text(label)
|
|
67
|
+
nodes[name] = {
|
|
68
|
+
'text': label,
|
|
69
|
+
'children': [],
|
|
70
|
+
'parents': [],
|
|
71
|
+
}
|
|
72
|
+
styles = {}
|
|
73
|
+
for sub in graph.get_subgraphs():
|
|
74
|
+
sub_name = sub.get_name().strip('"')
|
|
75
|
+
attrs = {}
|
|
76
|
+
if sub.get_node_defaults():
|
|
77
|
+
attrs['node'] = sub.get_node_defaults()
|
|
78
|
+
styles[sub_name] = {'attrs': attrs}
|
|
79
|
+
for n in sub.get_nodes():
|
|
80
|
+
nname = n.get_name().strip('"')
|
|
81
|
+
if nname == 'node':
|
|
82
|
+
continue
|
|
83
|
+
if nname not in nodes:
|
|
84
|
+
label = n.get('label')
|
|
85
|
+
if label is not None:
|
|
86
|
+
label = label.strip('"')
|
|
87
|
+
label = _label_to_text(label)
|
|
88
|
+
nodes[nname] = {'text': label, 'children': [], 'parents': []}
|
|
89
|
+
nodes[nname]['style'] = sub_name
|
|
90
|
+
for edge in graph.get_edges():
|
|
91
|
+
src = edge.get_source().strip('"')
|
|
92
|
+
dst = edge.get_destination().strip('"')
|
|
93
|
+
nodes.setdefault(src, {'text': None, 'children': [], 'parents': []})
|
|
94
|
+
nodes.setdefault(dst, {'text': None, 'children': [], 'parents': []})
|
|
95
|
+
nodes[src]['children'].append(dst)
|
|
96
|
+
nodes[dst]['parents'].append(src)
|
|
97
|
+
data = {'styles': styles, 'nodes': nodes}
|
|
98
|
+
with open(yaml_path, 'w') as f:
|
|
99
|
+
yaml.dump(
|
|
100
|
+
data,
|
|
101
|
+
f,
|
|
102
|
+
Dumper=IndentDumper,
|
|
103
|
+
default_flow_style=False,
|
|
104
|
+
sort_keys=True,
|
|
105
|
+
allow_unicode=True,
|
|
106
|
+
indent=2,
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
if __name__ == '__main__':
|
|
111
|
+
if len(sys.argv) != 3:
|
|
112
|
+
print('Usage: dot_to_yaml.py <input.dot> <output.yaml>')
|
|
113
|
+
sys.exit(1)
|
|
114
|
+
dot_to_yaml(sys.argv[1], sys.argv[2])
|