confease 1.0.0__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.
- confease/__init__.py +65 -0
- confease/editors.py +170 -0
- confease/model.py +569 -0
- confease/parsers.py +390 -0
- confease-1.0.0.dist-info/METADATA +199 -0
- confease-1.0.0.dist-info/RECORD +8 -0
- confease-1.0.0.dist-info/WHEEL +4 -0
- confease-1.0.0.dist-info/licenses/LICENSE +21 -0
confease/__init__.py
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
from confease.model import (
|
|
2
|
+
CLI,
|
|
3
|
+
DEF,
|
|
4
|
+
ENV,
|
|
5
|
+
ORIGINS,
|
|
6
|
+
SYS,
|
|
7
|
+
USR,
|
|
8
|
+
Confease,
|
|
9
|
+
Confitem,
|
|
10
|
+
)
|
|
11
|
+
from confease.editors import TextEditor
|
|
12
|
+
from confease.parsers import (
|
|
13
|
+
CFG,
|
|
14
|
+
CONF,
|
|
15
|
+
CONFIG,
|
|
16
|
+
CSV,
|
|
17
|
+
INI,
|
|
18
|
+
JSON,
|
|
19
|
+
PARSER_CLASSES,
|
|
20
|
+
PARSERS,
|
|
21
|
+
TOML,
|
|
22
|
+
XML,
|
|
23
|
+
YAML,
|
|
24
|
+
YML,
|
|
25
|
+
Cfg,
|
|
26
|
+
Csv,
|
|
27
|
+
Ini,
|
|
28
|
+
Json,
|
|
29
|
+
Parser,
|
|
30
|
+
Toml,
|
|
31
|
+
Xml,
|
|
32
|
+
Yaml,
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
__all__ = [
|
|
36
|
+
"CFG",
|
|
37
|
+
"CLI",
|
|
38
|
+
"CONF",
|
|
39
|
+
"CONFIG",
|
|
40
|
+
"CSV",
|
|
41
|
+
"DEF",
|
|
42
|
+
"ENV",
|
|
43
|
+
"INI",
|
|
44
|
+
"JSON",
|
|
45
|
+
"ORIGINS",
|
|
46
|
+
"PARSER_CLASSES",
|
|
47
|
+
"PARSERS",
|
|
48
|
+
"SYS",
|
|
49
|
+
"TextEditor",
|
|
50
|
+
"TOML",
|
|
51
|
+
"USR",
|
|
52
|
+
"XML",
|
|
53
|
+
"YAML",
|
|
54
|
+
"YML",
|
|
55
|
+
"Cfg",
|
|
56
|
+
"Confease",
|
|
57
|
+
"Confitem",
|
|
58
|
+
"Csv",
|
|
59
|
+
"Ini",
|
|
60
|
+
"Json",
|
|
61
|
+
"Parser",
|
|
62
|
+
"Toml",
|
|
63
|
+
"Xml",
|
|
64
|
+
"Yaml",
|
|
65
|
+
]
|
confease/editors.py
ADDED
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
import os
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
import shlex
|
|
4
|
+
import shutil
|
|
5
|
+
import subprocess
|
|
6
|
+
import tempfile
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
__all__ = [
|
|
10
|
+
"TERMINAL_EDITORS",
|
|
11
|
+
"VISUAL_EDITORS",
|
|
12
|
+
"EDITORS",
|
|
13
|
+
"TextEditor",
|
|
14
|
+
]
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
TERMINAL_EDITORS = [["sensible-editor"], ["editor"], ["nano"], ["vim"], ["vi"]]
|
|
18
|
+
VISUAL_WAIT_FLAGS = {
|
|
19
|
+
"code": "--wait",
|
|
20
|
+
"code-insiders": "--wait",
|
|
21
|
+
"codium": "--wait",
|
|
22
|
+
"subl": "--wait",
|
|
23
|
+
"sublime-text": "--wait",
|
|
24
|
+
"sublime_text": "--wait",
|
|
25
|
+
}
|
|
26
|
+
VISUAL_EDITORS = [
|
|
27
|
+
["code", "--wait"],
|
|
28
|
+
["code-insiders", "--wait"],
|
|
29
|
+
["codium", "--wait"],
|
|
30
|
+
["subl", "--wait"],
|
|
31
|
+
["sublime-text", "--wait"],
|
|
32
|
+
["notepadpp.exe"],
|
|
33
|
+
["notepad.exe"],
|
|
34
|
+
]
|
|
35
|
+
EDITORS = [*TERMINAL_EDITORS, *VISUAL_EDITORS]
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class TextEditor:
|
|
39
|
+
"""Small blocking text-editor launcher.
|
|
40
|
+
|
|
41
|
+
``TextEditor`` resolves an explicit editor command, ``EDITOR``/``VISUAL``,
|
|
42
|
+
or a small list of common terminal and visual editors. Known visual editors
|
|
43
|
+
receive a wait flag when needed so callers can safely read the file after
|
|
44
|
+
the process exits.
|
|
45
|
+
"""
|
|
46
|
+
|
|
47
|
+
def __init__(self, editor: str | None = None):
|
|
48
|
+
"""Create an editor launcher.
|
|
49
|
+
|
|
50
|
+
Args:
|
|
51
|
+
editor: Optional shell-style editor command, for example
|
|
52
|
+
``"nano -w"`` or ``"code --wait"``. When omitted, the launcher
|
|
53
|
+
auto-detects an available editor.
|
|
54
|
+
|
|
55
|
+
Raises:
|
|
56
|
+
RuntimeError: If the command string cannot be parsed or is empty.
|
|
57
|
+
"""
|
|
58
|
+
self._editor: list[str] | None = None
|
|
59
|
+
self.editor = editor
|
|
60
|
+
|
|
61
|
+
@property
|
|
62
|
+
def editor(self):
|
|
63
|
+
"""Return the configured editor command, or None for auto-detection."""
|
|
64
|
+
return self._editor
|
|
65
|
+
|
|
66
|
+
@editor.setter
|
|
67
|
+
def editor(self, value: str | None):
|
|
68
|
+
"""Configure an explicit editor command or return to auto-detection."""
|
|
69
|
+
if value is None:
|
|
70
|
+
self._editor = None
|
|
71
|
+
return
|
|
72
|
+
|
|
73
|
+
try:
|
|
74
|
+
command = shlex.split(value)
|
|
75
|
+
except ValueError as exc:
|
|
76
|
+
raise RuntimeError(f"Invalid editor command: {value}") from exc
|
|
77
|
+
if not command:
|
|
78
|
+
raise RuntimeError("Editor command cannot be empty")
|
|
79
|
+
self._editor = command
|
|
80
|
+
|
|
81
|
+
@staticmethod
|
|
82
|
+
def _with_wait_flag(command: list[str]) -> list[str]:
|
|
83
|
+
"""Add blocking flags for known visual editors when omitted."""
|
|
84
|
+
executable = Path(command[0]).name.lower()
|
|
85
|
+
wait_flag = VISUAL_WAIT_FLAGS.get(executable)
|
|
86
|
+
if wait_flag and wait_flag not in command[1:]:
|
|
87
|
+
return [command[0], wait_flag, *command[1:]]
|
|
88
|
+
return command
|
|
89
|
+
|
|
90
|
+
@staticmethod
|
|
91
|
+
def _split_env_editor(value: str) -> list[str]:
|
|
92
|
+
"""Parse an editor command from an environment variable."""
|
|
93
|
+
try:
|
|
94
|
+
return shlex.split(value)
|
|
95
|
+
except ValueError as exc:
|
|
96
|
+
raise RuntimeError(f"Invalid editor command: {value}") from exc
|
|
97
|
+
|
|
98
|
+
def _editor_command(self) -> list[str]:
|
|
99
|
+
"""Return the first available editor command.
|
|
100
|
+
|
|
101
|
+
Raises:
|
|
102
|
+
RuntimeError: If no configured or auto-detected editor is available.
|
|
103
|
+
"""
|
|
104
|
+
if self._editor:
|
|
105
|
+
editors = [self._editor]
|
|
106
|
+
else:
|
|
107
|
+
editors = list(EDITORS)
|
|
108
|
+
env_editor = os.environ.get("EDITOR", "").strip() or os.environ.get("VISUAL", "").strip()
|
|
109
|
+
if env_editor:
|
|
110
|
+
editor_command = self._split_env_editor(env_editor)
|
|
111
|
+
if editor_command:
|
|
112
|
+
editors.insert(0, editor_command)
|
|
113
|
+
|
|
114
|
+
for command in editors:
|
|
115
|
+
if shutil.which(command[0]):
|
|
116
|
+
return self._with_wait_flag(command)
|
|
117
|
+
raise RuntimeError(f"Text editor not found '{self._editor}'" if self._editor else "No text editor found")
|
|
118
|
+
|
|
119
|
+
def open(self, path: str | Path) -> str:
|
|
120
|
+
"""Open the configured editor for an existing or new file path.
|
|
121
|
+
|
|
122
|
+
Args:
|
|
123
|
+
path: File path to open.
|
|
124
|
+
|
|
125
|
+
Returns:
|
|
126
|
+
The expanded file path as a string after the editor exits.
|
|
127
|
+
|
|
128
|
+
Raises:
|
|
129
|
+
ValueError: If ``path`` is ``None``.
|
|
130
|
+
Exception: If the editor cannot be launched or exits unsuccessfully.
|
|
131
|
+
"""
|
|
132
|
+
if path is None:
|
|
133
|
+
raise ValueError("Path not provided")
|
|
134
|
+
try:
|
|
135
|
+
abs_path = Path(path).expanduser()
|
|
136
|
+
with open(abs_path, mode="a+", encoding="utf-8") as message_file:
|
|
137
|
+
subprocess.run([*self._editor_command(), message_file.name], check=True)
|
|
138
|
+
return str(abs_path)
|
|
139
|
+
except OSError as error:
|
|
140
|
+
raise Exception(f"could not open text editor: {error}") from error
|
|
141
|
+
except subprocess.CalledProcessError as error:
|
|
142
|
+
raise Exception(f"text editor process failed: {error}") from error
|
|
143
|
+
|
|
144
|
+
def read(self):
|
|
145
|
+
"""Open a temporary file and return its contents after editing.
|
|
146
|
+
|
|
147
|
+
Returns:
|
|
148
|
+
The text written by the editor.
|
|
149
|
+
|
|
150
|
+
Raises:
|
|
151
|
+
Exception: If the editor cannot be launched or exits unsuccessfully.
|
|
152
|
+
"""
|
|
153
|
+
try:
|
|
154
|
+
with tempfile.NamedTemporaryFile(
|
|
155
|
+
mode="w+",
|
|
156
|
+
encoding="utf-8",
|
|
157
|
+
) as message_file:
|
|
158
|
+
subprocess.run([*self._editor_command(), message_file.name], check=True)
|
|
159
|
+
message_file.seek(0)
|
|
160
|
+
return message_file.read()
|
|
161
|
+
except OSError as error:
|
|
162
|
+
raise Exception(f"could not open text editor: {error}") from error
|
|
163
|
+
except subprocess.CalledProcessError as error:
|
|
164
|
+
raise Exception(f"text editor process failed: {error}") from error
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
def usage():
|
|
168
|
+
editor = TextEditor()
|
|
169
|
+
editor.open("~/.bashrc")
|
|
170
|
+
return editor.read()
|