-
Notifications
You must be signed in to change notification settings - Fork 4
Expand file tree
/
Copy pathedit.py
More file actions
121 lines (107 loc) · 3.5 KB
/
Copy pathedit.py
File metadata and controls
121 lines (107 loc) · 3.5 KB
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
#!/usr/bin/python
#
r'''
Convenience functions for editing things.
- Cameron Simpson <cs@cskk.id.au> 02jun2016
'''
from __future__ import print_function, absolute_import
from functools import partial
import json
import os
import os.path
from subprocess import Popen
from tempfile import NamedTemporaryFile
from cs.deco import fmtdoc
from cs.pfx import Pfx
__version__ = '20220429-post'
DISTINFO = {
'keywords': ["python2", "python3"],
'classifiers': [
"Programming Language :: Python",
"Programming Language :: Python :: 2",
"Programming Language :: Python :: 3",
],
'install_requires': ['cs.deco', 'cs.pfx'],
}
# default editor
EDITOR = 'vi'
@fmtdoc
def choose_editor(editor=None, environ=None):
''' Choose an editor,
honouring the `$EDITOR` environment variable.
Parameters:
* `editor`: optional editor,
default from `environ['EDITOR']`
or from `EDITOR` (`{EDITOR!r}`).
* `environ`: optional environment mapping,
default `os.environ`
'''
if editor is None:
if environ is None:
environ = os.environ
editor = environ.get('EDITOR', EDITOR)
return editor
def edit_strings(strs, editor=None, environ=None):
''' Edit an iterable list of `str`, return tuples of changed string pairs.
The editor is chosen by `choose_editor(editor=editor,environ=environ)`.
'''
oldstrs = list(strs)
newstrs = edit(strs, editor, environ)
if len(newstrs) != len(oldstrs):
raise ValueError("%d old strs, %d new strs" % (len(oldstrs), len(newstrs)))
changes = [
old_new for old_new in zip(oldstrs, newstrs) if old_new[0] != old_new[1]
]
return changes
def edit(lines, editor=None, environ=None):
''' Write lines to a temporary file, edit the file, return the new lines.
The editor is chosen by `choose_editor(editor=editor,environ=environ)`.
'''
editor = choose_editor(editor, environ)
with NamedTemporaryFile(mode='w') as T:
for lineno, line in enumerate(lines, 1):
with Pfx("%d: %r", lineno, line):
if '\n' in line:
raise ValueError("newline in line")
T.write(line)
T.write('\n')
T.flush()
P = Popen([editor, T.name])
P.wait()
if P.returncode != 0:
raise RuntimeError("editor fails, aborting")
with open(T.name, 'r') as f:
lines = []
for lineno, line in enumerate(f, 1):
with Pfx("%d: %r", lineno, line):
if not line.endswith('\n'):
raise ValueError("missing newline")
lines.append(line[:-1])
return lines
def edit_obj(o, editor=None, environ=None, to_text=None, from_text=None):
''' Edit the cotents of an object `o`.
Return a new object containing the editing contents.
The default transcription is as JSON.
The editor is chosen by `choose_editor(editor=editor,environ=environ)`.
Parameters:
* `o`: the object whose
* `to_text`: the transcription function of the object to text;
default `json.dumps`
* `from_text`: the transcription function of the object to text;
default `json.loads`
'''
editor = choose_editor(editor, environ)
if to_text is None:
to_text = partial(json.dumps, sort_keys=True, indent=4)
if from_text is None:
from_text = json.loads
with NamedTemporaryFile(mode='w') as T:
T.write(to_text(o))
T.write("\n")
T.flush()
P = Popen([editor, T.name])
P.wait()
if P.returncode != 0:
raise RuntimeError("editor fails, aborting")
with open(T.name, 'r') as f:
return from_text(f.read())