dprojectstools 0.0.26__tar.gz → 0.0.28__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.
Files changed (59) hide show
  1. {dprojectstools-0.0.26/src/dprojectstools.egg-info → dprojectstools-0.0.28}/PKG-INFO +4 -1
  2. {dprojectstools-0.0.26 → dprojectstools-0.0.28}/README.md +3 -1
  3. {dprojectstools-0.0.26 → dprojectstools-0.0.28}/pyproject.toml +3 -2
  4. {dprojectstools-0.0.26 → dprojectstools-0.0.28}/src/dprojectstools/backups/restic.py +3 -0
  5. {dprojectstools-0.0.26 → dprojectstools-0.0.28}/src/dprojectstools/console/read_password.py +1 -1
  6. dprojectstools-0.0.28/src/dprojectstools/xeditor/hightlight_env.py +112 -0
  7. dprojectstools-0.0.28/src/dprojectstools/xeditor/hightlight_md.py +191 -0
  8. dprojectstools-0.0.28/src/dprojectstools/xeditor/hightlight_none.py +3 -0
  9. {dprojectstools-0.0.26 → dprojectstools-0.0.28}/src/dprojectstools/xeditor/xeditor.py +61 -36
  10. {dprojectstools-0.0.26 → dprojectstools-0.0.28/src/dprojectstools.egg-info}/PKG-INFO +4 -1
  11. {dprojectstools-0.0.26 → dprojectstools-0.0.28}/src/dprojectstools.egg-info/SOURCES.txt +3 -0
  12. {dprojectstools-0.0.26 → dprojectstools-0.0.28}/src/dprojectstools.egg-info/requires.txt +1 -0
  13. {dprojectstools-0.0.26 → dprojectstools-0.0.28}/LICENSE +0 -0
  14. {dprojectstools-0.0.26 → dprojectstools-0.0.28}/setup.cfg +0 -0
  15. {dprojectstools-0.0.26 → dprojectstools-0.0.28}/src/dprojectstools/__init__.py +0 -0
  16. {dprojectstools-0.0.26 → dprojectstools-0.0.28}/src/dprojectstools/backups/__init__.py +0 -0
  17. {dprojectstools-0.0.26 → dprojectstools-0.0.28}/src/dprojectstools/clipboard/__init__.py +0 -0
  18. {dprojectstools-0.0.26 → dprojectstools-0.0.28}/src/dprojectstools/clipboard/clipboard.py +0 -0
  19. {dprojectstools-0.0.26 → dprojectstools-0.0.28}/src/dprojectstools/commands/__init__.py +0 -0
  20. {dprojectstools-0.0.26 → dprojectstools-0.0.28}/src/dprojectstools/commands/commands.py +0 -0
  21. {dprojectstools-0.0.26 → dprojectstools-0.0.28}/src/dprojectstools/commands/p.py +0 -0
  22. {dprojectstools-0.0.26 → dprojectstools-0.0.28}/src/dprojectstools/config/__init__.py +0 -0
  23. {dprojectstools-0.0.26 → dprojectstools-0.0.28}/src/dprojectstools/config/config.py +0 -0
  24. {dprojectstools-0.0.26 → dprojectstools-0.0.28}/src/dprojectstools/console/__init__.py +0 -0
  25. {dprojectstools-0.0.26 → dprojectstools-0.0.28}/src/dprojectstools/console/keys.py +0 -0
  26. {dprojectstools-0.0.26 → dprojectstools-0.0.28}/src/dprojectstools/console/readKey.py +0 -0
  27. {dprojectstools-0.0.26 → dprojectstools-0.0.28}/src/dprojectstools/console/sequences.py +0 -0
  28. {dprojectstools-0.0.26 → dprojectstools-0.0.28}/src/dprojectstools/crypto/__init__.py +0 -0
  29. {dprojectstools-0.0.26 → dprojectstools-0.0.28}/src/dprojectstools/crypto/aes.py +0 -0
  30. {dprojectstools-0.0.26 → dprojectstools-0.0.28}/src/dprojectstools/db/__main__.py +0 -0
  31. {dprojectstools-0.0.26 → dprojectstools-0.0.28}/src/dprojectstools/db/db_inspector.py +0 -0
  32. {dprojectstools-0.0.26 → dprojectstools-0.0.28}/src/dprojectstools/db/db_schema.py +0 -0
  33. {dprojectstools-0.0.26 → dprojectstools-0.0.28}/src/dprojectstools/db/generator_cs.py +0 -0
  34. {dprojectstools-0.0.26 → dprojectstools-0.0.28}/src/dprojectstools/db/generator_sql.py +0 -0
  35. {dprojectstools-0.0.26 → dprojectstools-0.0.28}/src/dprojectstools/db/generator_vb.py +0 -0
  36. {dprojectstools-0.0.26 → dprojectstools-0.0.28}/src/dprojectstools/db/generator_xml.py +0 -0
  37. {dprojectstools-0.0.26 → dprojectstools-0.0.28}/src/dprojectstools/docker/__init__.py +0 -0
  38. {dprojectstools-0.0.26 → dprojectstools-0.0.28}/src/dprojectstools/docker/docker.py +0 -0
  39. {dprojectstools-0.0.26 → dprojectstools-0.0.28}/src/dprojectstools/git/__init__.py +0 -0
  40. {dprojectstools-0.0.26 → dprojectstools-0.0.28}/src/dprojectstools/git/git.py +0 -0
  41. {dprojectstools-0.0.26 → dprojectstools-0.0.28}/src/dprojectstools/i18n/__init__.py +0 -0
  42. {dprojectstools-0.0.26 → dprojectstools-0.0.28}/src/dprojectstools/i18n/translator.py +0 -0
  43. {dprojectstools-0.0.26 → dprojectstools-0.0.28}/src/dprojectstools/i18n/xgettext.py +0 -0
  44. {dprojectstools-0.0.26 → dprojectstools-0.0.28}/src/dprojectstools/images/__init__.py +0 -0
  45. {dprojectstools-0.0.26 → dprojectstools-0.0.28}/src/dprojectstools/images/fonticons.py +0 -0
  46. {dprojectstools-0.0.26 → dprojectstools-0.0.28}/src/dprojectstools/math/bc.py +0 -0
  47. {dprojectstools-0.0.26 → dprojectstools-0.0.28}/src/dprojectstools/secrets/__init__.py +0 -0
  48. {dprojectstools-0.0.26 → dprojectstools-0.0.28}/src/dprojectstools/secrets/__main__.py +0 -0
  49. {dprojectstools-0.0.26 → dprojectstools-0.0.28}/src/dprojectstools/secrets/secrets.py +0 -0
  50. {dprojectstools-0.0.26 → dprojectstools-0.0.28}/src/dprojectstools/utils/__init__.py +0 -0
  51. {dprojectstools-0.0.26 → dprojectstools-0.0.28}/src/dprojectstools/utils/env.py +0 -0
  52. {dprojectstools-0.0.26 → dprojectstools-0.0.28}/src/dprojectstools/utils/process.py +0 -0
  53. {dprojectstools-0.0.26 → dprojectstools-0.0.28}/src/dprojectstools/utils/versions.py +0 -0
  54. {dprojectstools-0.0.26 → dprojectstools-0.0.28}/src/dprojectstools/utils/xml.py +0 -0
  55. {dprojectstools-0.0.26 → dprojectstools-0.0.28}/src/dprojectstools/xeditor/__init__.py +0 -0
  56. {dprojectstools-0.0.26 → dprojectstools-0.0.28}/src/dprojectstools/xeditor/__main__.py +0 -0
  57. {dprojectstools-0.0.26 → dprojectstools-0.0.28}/src/dprojectstools.egg-info/dependency_links.txt +0 -0
  58. {dprojectstools-0.0.26 → dprojectstools-0.0.28}/src/dprojectstools.egg-info/entry_points.txt +0 -0
  59. {dprojectstools-0.0.26 → dprojectstools-0.0.28}/src/dprojectstools.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dprojectstools
3
- Version: 0.0.26
3
+ Version: 0.0.28
4
4
  Summary: A set of development tools
5
5
  Author-email: Marc Delos <marcdp@dprojects.com>
6
6
  Project-URL: Homepage, https://github.com/marcdp/dprojectstools
@@ -23,6 +23,7 @@ Requires-Dist: sqlalchemy>=2.0.38
23
23
  Requires-Dist: websocat>=1.13.0
24
24
  Requires-Dist: json5>=0.13.0
25
25
  Requires-Dist: PyYAML>=6.0.3
26
+ Requires-Dist: pyodbc>=5.2.0
26
27
  Dynamic: license-file
27
28
 
28
29
 
@@ -83,3 +84,5 @@ cd dprojectstools
83
84
  MIT License
84
85
 
85
86
  See the LICENSE file for details.
87
+
88
+
@@ -55,4 +55,6 @@ cd dprojectstools
55
55
 
56
56
  MIT License
57
57
 
58
- See the LICENSE file for details.
58
+ See the LICENSE file for details.
59
+
60
+
@@ -10,7 +10,7 @@ where = ["src"]
10
10
 
11
11
  [project]
12
12
  name = "dprojectstools"
13
- version = "0.0.26"
13
+ version = "0.0.28"
14
14
  authors = [
15
15
  { name="Marc Delos", email="marcdp@dprojects.com" },
16
16
  ]
@@ -26,7 +26,8 @@ dependencies = [
26
26
  "sqlalchemy>=2.0.38",
27
27
  "websocat>=1.13.0",
28
28
  "json5>=0.13.0",
29
- "PyYAML>=6.0.3"
29
+ "PyYAML>=6.0.3",
30
+ "pyodbc>=5.2.0"
30
31
  ]
31
32
  description = "A set of development tools"
32
33
  readme = "README.md"
@@ -81,6 +81,7 @@ class Restic:
81
81
  iexclude: Annotated[list[str], "Include"] = None,
82
82
  verify = False,
83
83
  delete = False,
84
+ verbose = False,
84
85
  no_same_owner = False,
85
86
  no_same_permissions = False
86
87
  ):
@@ -101,6 +102,8 @@ class Restic:
101
102
  command += " --verify"
102
103
  if delete:
103
104
  command += " --delete"
105
+ if verbose:
106
+ command += " --verbose"
104
107
  if iexclude != None:
105
108
  for iexclude in iexclude:
106
109
  command += " --iexclude " + iexclude
@@ -1,6 +1,6 @@
1
1
  import sys
2
2
 
3
- def read_password(prompt: str = "", mask: str = "*") -> str:
3
+ def read_password(prompt: str = "Enter password: ", mask: str = "*") -> str:
4
4
  print(prompt, end="", flush=True)
5
5
  chars = []
6
6
 
@@ -0,0 +1,112 @@
1
+ import re
2
+ from dataclasses import dataclass
3
+ from typing import List, Optional
4
+ from dprojectstools.console import Sequences
5
+
6
+ # ---- Token model ----
7
+ @dataclass(frozen=True)
8
+ class Token:
9
+ kind: str # "indent" | "comment" | "key" | "eq" | "value" | "enc" | "ws" | "other"
10
+ text: str
11
+
12
+ # ---- Regexes (simple, not quote-aware) ----
13
+
14
+ _RX_COMMENT_LINE = re.compile(r"^\s*#.*$")
15
+ _RX_ASSIGN = re.compile(r"^(\s*)([A-Za-z_][A-Za-z0-9_]*)(\s*)=(\s*)(.*)$")
16
+ _RX_ENC = re.compile(r"\benc(?::v\d+)?:") # enc: or enc:v1: etc.
17
+
18
+ def tokenize_env_line(line: str) -> List[Token]:
19
+ """
20
+ Mini-tokenizer for .env lines.
21
+ Goals:
22
+ - Identify KEY=VALUE assignments
23
+ - Identify full-line comments
24
+ - Highlight the first enc marker within the value
25
+ Non-goals:
26
+ - Perfect handling of quoted values / escaped # / export prefix
27
+ - Multiline values
28
+
29
+ Returns list[Token].
30
+ """
31
+ # Preserve newline as part of the last token if present
32
+ nl = ""
33
+ if line.endswith("\n"):
34
+ line, nl = line[:-1], "\n"
35
+
36
+ # Full-line comment
37
+ if _RX_COMMENT_LINE.match(line):
38
+ return [Token("comment", line + nl)]
39
+
40
+ m = _RX_ASSIGN.match(line)
41
+ if not m:
42
+ # Not an assignment; treat as "other"
43
+ return [Token("other", line + nl)]
44
+
45
+ indent, key, pre_eq_ws, post_eq_ws, rhs = m.groups()
46
+
47
+ tokens: List[Token] = []
48
+ if indent:
49
+ tokens.append(Token("indent", indent))
50
+ tokens.append(Token("key", key))
51
+ if pre_eq_ws:
52
+ tokens.append(Token("ws", pre_eq_ws))
53
+ tokens.append(Token("eq", "="))
54
+ if post_eq_ws:
55
+ tokens.append(Token("ws", post_eq_ws))
56
+
57
+ # Best-effort inline comment: split on first " #" or "\t#"
58
+ comment_idx: Optional[int] = None
59
+ for pat in (" #", "\t#"):
60
+ idx = rhs.find(pat)
61
+ if idx != -1 and (comment_idx is None or idx < comment_idx):
62
+ comment_idx = idx + 1 # keep the leading whitespace before '#'
63
+
64
+ if comment_idx is None:
65
+ value_part = rhs
66
+ comment_part = ""
67
+ else:
68
+ value_part = rhs[:comment_idx]
69
+ comment_part = rhs[comment_idx:]
70
+
71
+ # Tokenize value_part with enc marker (first occurrence only)
72
+ m_enc = _RX_ENC.search(value_part)
73
+ if not m_enc:
74
+ tokens.append(Token("value", value_part))
75
+ else:
76
+ a, b = m_enc.span()
77
+ if a > 0:
78
+ tokens.append(Token("value", value_part[:a]))
79
+ tokens.append(Token("enc", value_part[a:b]))
80
+ if b < len(value_part):
81
+ tokens.append(Token("value", value_part[b:]))
82
+
83
+ if comment_part:
84
+ tokens.append(Token("comment", comment_part))
85
+
86
+ if nl:
87
+ # Attach newline to the last token so rendering preserves lines
88
+ last = tokens[-1]
89
+ tokens[-1] = Token(last.kind, last.text + nl)
90
+
91
+ return tokens
92
+
93
+
94
+ def tokenize_env(text: str) -> List[List[Token]]:
95
+ """Tokenize a whole .env file into tokens per line."""
96
+ return [tokenize_env_line(line) for line in text.splitlines(keepends=True)]
97
+
98
+ def hightlight_env(line: str) -> str:
99
+ result = []
100
+ for token in tokenize_env_line(line):
101
+ # "indent" | "comment" | "key" | "eq" | "value" | "enc" | "ws" | "other"
102
+ if token.kind == "comment":
103
+ result.append(Sequences.FG_BRIGHT_GREEN + token.text + Sequences.RESET)
104
+ elif token.kind == "key":
105
+ result.append(Sequences.FG_CYAN + token.text + Sequences.RESET)
106
+ elif token.kind == "enc":
107
+ result.append(Sequences.FG_BRIGHT_BLUE + token.text)
108
+ elif token.kind == "value":
109
+ result.append(token.text + Sequences.RESET)
110
+ else:
111
+ result.append(token.text)
112
+ return "".join(result)
@@ -0,0 +1,191 @@
1
+ import re
2
+ from dataclasses import dataclass
3
+ from typing import List, Optional
4
+ from dprojectstools.console import Sequences
5
+
6
+ # ---- Token model ----
7
+ @dataclass(frozen=True)
8
+ class Token:
9
+ kind: str # "text" | "heading" | "frontmatter_delim" | "frontmatter_key" | "fence" | "code" | "enc" | "comment"
10
+ text: str
11
+
12
+ # ---- Regexes ----
13
+ _RX_HEADING = re.compile(r"^(\s{0,3})(#{1,6})(\s+)(.*)$")
14
+ _RX_FENCE = re.compile(r"^(\s{0,3})(```+)(\s*)(.*)$") # ```lang
15
+ _RX_FM_DELIM = re.compile(r"^\s*---\s*$") # YAML frontmatter delimiter
16
+ _RX_FM_XVAULT = re.compile(r"^(\s*)(_xvault)(\s*:\s*)(.*)$")
17
+ _RX_ENC = re.compile(r"\benc(?::v\d+)?:") # enc: or enc:v1: etc.
18
+
19
+ def _split_enc(text: str, base_kind: str = "text") -> List[Token]:
20
+ """Split a string into tokens highlighting all enc markers."""
21
+ out: List[Token] = []
22
+ last = 0
23
+ for m in _RX_ENC.finditer(text):
24
+ a, b = m.span()
25
+ if a > last:
26
+ out.append(Token(base_kind, text[last:a]))
27
+ out.append(Token("enc", text[a:b]))
28
+ last = b
29
+ if last < len(text):
30
+ out.append(Token(base_kind, text[last:]))
31
+ return out
32
+
33
+ @dataclass
34
+ class MdState:
35
+ in_frontmatter: bool = False
36
+ frontmatter_possible: bool = True # only at top of file until first non-empty non-frontmatter line
37
+ in_fence: bool = False
38
+ fence_lang: str = ""
39
+
40
+ def tokenize_md_line(line: str, state: MdState) -> List[Token]:
41
+ """
42
+ Mini-tokenizer for Markdown lines with state.
43
+ - Detects YAML frontmatter only at the top of file.
44
+ - Detects fenced blocks ```lang (multi-line).
45
+ - Highlights headings and enc markers.
46
+ - Highlights inline code spans `...` (simple: not nested).
47
+ """
48
+ # Preserve newline as part of last token (same style as your env tokenizer)
49
+ nl = ""
50
+ if line.endswith("\n"):
51
+ line, nl = line[:-1], "\n"
52
+
53
+ tokens: List[Token] = []
54
+
55
+ # --- FRONTMATTER detection (only at top) ---
56
+ if state.frontmatter_possible:
57
+ if not state.in_frontmatter:
58
+ if _RX_FM_DELIM.match(line):
59
+ state.in_frontmatter = True
60
+ tokens.append(Token("frontmatter_delim", line + nl))
61
+ return tokens
62
+ # If first meaningful line is not '---', stop considering frontmatter
63
+ if line.strip() != "":
64
+ state.frontmatter_possible = False
65
+ else:
66
+ # we are inside frontmatter
67
+ if _RX_FM_DELIM.match(line):
68
+ state.in_frontmatter = False
69
+ state.frontmatter_possible = False
70
+ tokens.append(Token("frontmatter_delim", line + nl))
71
+ return tokens
72
+
73
+ # highlight _xvault: line specially
74
+ mfm = _RX_FM_XVAULT.match(line)
75
+ if mfm:
76
+ indent, key, sep, rest = mfm.groups()
77
+ if indent:
78
+ tokens.append(Token("text", indent))
79
+ tokens.append(Token("frontmatter_key", key))
80
+ tokens.append(Token("text", sep))
81
+ # highlight enc markers inside value part
82
+ tokens.extend(_split_enc(rest, base_kind="text"))
83
+ if nl:
84
+ last = tokens[-1]
85
+ tokens[-1] = Token(last.kind, last.text + nl)
86
+ return tokens
87
+
88
+ # otherwise treat as frontmatter text (optionally highlight enc)
89
+ tokens.extend(_split_enc(line, base_kind="text"))
90
+ if nl:
91
+ last = tokens[-1]
92
+ tokens[-1] = Token(last.kind, last.text + nl)
93
+ return tokens
94
+
95
+ # --- FENCE detection (toggle in_fence) ---
96
+ mf = _RX_FENCE.match(line)
97
+ if mf:
98
+ indent, ticks, sp, lang = mf.groups()
99
+ # Toggle fence on/off
100
+ if not state.in_fence:
101
+ state.in_fence = True
102
+ state.fence_lang = lang.strip()
103
+ else:
104
+ state.in_fence = False
105
+ state.fence_lang = ""
106
+
107
+ # Tokenize fence line
108
+ if indent:
109
+ tokens.append(Token("text", indent))
110
+ tokens.append(Token("fence", ticks))
111
+ tokens.append(Token("text", sp + lang))
112
+ if nl:
113
+ last = tokens[-1]
114
+ tokens[-1] = Token(last.kind, last.text + nl)
115
+ return tokens
116
+
117
+ # --- Inside fenced block ---
118
+ if state.in_fence:
119
+ # For xvault blocks, highlight enc markers aggressively
120
+ if state.fence_lang.startswith("xvault"):
121
+ tokens.extend(_split_enc(line, base_kind="code"))
122
+ else:
123
+ tokens.append(Token("code", line))
124
+ if nl:
125
+ last = tokens[-1]
126
+ tokens[-1] = Token(last.kind, last.text + nl)
127
+ return tokens
128
+
129
+ # --- Headings ---
130
+ mh = _RX_HEADING.match(line)
131
+ if mh:
132
+ indent, hashes, sp, rest = mh.groups()
133
+ if indent:
134
+ tokens.append(Token("text", indent))
135
+ tokens.append(Token("heading", hashes))
136
+ tokens.append(Token("text", sp))
137
+ # highlight enc markers in heading text if any (rare but fine)
138
+ tokens.extend(_split_enc(rest, base_kind="heading"))
139
+ if nl:
140
+ last = tokens[-1]
141
+ tokens[-1] = Token(last.kind, last.text + nl)
142
+ return tokens
143
+
144
+ # --- Inline code spans: split by backticks (simple) ---
145
+ # This is not a full Markdown inline parser; it's good enough for highlighting secrets in `...`.
146
+ parts = line.split("`")
147
+ if len(parts) == 1:
148
+ tokens.extend(_split_enc(line, base_kind="text"))
149
+ else:
150
+ for i, part in enumerate(parts):
151
+ if i % 2 == 0:
152
+ tokens.extend(_split_enc(part, base_kind="text"))
153
+ else:
154
+ # inside code span
155
+ tokens.append(Token("code", "`"))
156
+ tokens.extend(_split_enc(part, base_kind="code"))
157
+ tokens.append(Token("code", "`"))
158
+
159
+ if nl:
160
+ last = tokens[-1]
161
+ tokens[-1] = Token(last.kind, last.text + nl)
162
+
163
+ return tokens
164
+
165
+
166
+ def tokenize_md(text: str) -> List[List[Token]]:
167
+ """Tokenize a whole markdown text into tokens per line."""
168
+ state = MdState()
169
+ out: List[List[Token]] = []
170
+ for line in text.splitlines(keepends=True):
171
+ out.append(tokenize_md_line(line, state))
172
+ return out
173
+
174
+
175
+
176
+ def hightlight_md(line: str) -> str:
177
+ result = []
178
+ state = MdState()
179
+ for token in tokenize_md_line(line, state):
180
+ # "text" | "heading" | "frontmatter_delim" | "frontmatter_key" | "fence" | "code" | "enc" | "comment"
181
+ if token.kind == "comment":
182
+ result.append(Sequences.FG_BRIGHT_GREEN + token.text + Sequences.RESET)
183
+ elif token.kind == "heading":
184
+ result.append(Sequences.FG_CYAN + token.text + Sequences.RESET)
185
+ elif token.kind == "code":
186
+ result.append(Sequences.FG_RED + token.text + Sequences.RESET)
187
+ elif token.kind == "enc":
188
+ result.append(Sequences.FG_BRIGHT_BLUE + token.text + Sequences.RESET)
189
+ else:
190
+ result.append(token.text)
191
+ return "".join(result)
@@ -0,0 +1,3 @@
1
+
2
+ def hightlight_none(line: str) -> str:
3
+ return line
@@ -1,3 +1,4 @@
1
+ import re
1
2
  from ..console import Sequences, Keys, readKey
2
3
  from ..crypto import aes_decrypt, aes_encrypt
3
4
  from ..clipboard import copy
@@ -8,12 +9,17 @@ import json
8
9
  import json5
9
10
  import yaml
10
11
 
11
- # read correctly ESCAPE sequence key in linux
12
+
13
+ from .hightlight_none import hightlight_none
14
+ from .hightlight_env import hightlight_env
15
+ from .hightlight_md import hightlight_md
16
+
17
+
12
18
  # undo + redo
13
19
 
14
20
  # constants
15
21
  TAB_SPACES = 4
16
-
22
+
17
23
  # class
18
24
  class XEditor:
19
25
 
@@ -71,7 +77,7 @@ class XEditor:
71
77
  try:
72
78
  self.load(filename)
73
79
  except ValueError as e:
74
- print("error: invalid password")
80
+ print("error: " + str(e))
75
81
  return False
76
82
  # dump
77
83
  if dump:
@@ -301,7 +307,7 @@ class XEditor:
301
307
  line = line[:self._cursor_x] + key + line[self._cursor_x:]
302
308
  self._lines[self._cursor_y] = line
303
309
  self._setCursor(self._cursor_x + 1, self._cursor_y)
304
- self._printHeader(flush = False)
310
+ self._printFooter(flush = False)
305
311
  self._printLine(self._cursor_y, flush = False)
306
312
  self._printCursor()
307
313
  else:
@@ -309,7 +315,7 @@ class XEditor:
309
315
  line = line[:self._cursor_x] + key + line[self._cursor_x + 1:]
310
316
  self._lines[self._cursor_y] = line
311
317
  self._setCursor(self._cursor_x + 1, self._cursor_y)
312
- self._printHeader(flush = False)
318
+ self._printFooter(flush = False)
313
319
  self._printLine(self._cursor_y)
314
320
 
315
321
  def enter(self):
@@ -324,7 +330,7 @@ class XEditor:
324
330
  self._lines[self._cursor_y] = line_before
325
331
  self._lines.insert(self._cursor_y + 1, line_after)
326
332
  self._setCursor(0, self._cursor_y + 1)
327
- self._printHeader(flush = False)
333
+ self._printFooter(flush = False)
328
334
  self._printLines(flush = False)
329
335
  self._printCursor()
330
336
 
@@ -364,8 +370,8 @@ class XEditor:
364
370
  self._select_x = 0
365
371
  self._select_y = y1
366
372
  # print
367
- self._printHeader(flush = False)
368
373
  self._printLines(flush = False)
374
+ self._printFooter(flush = False)
369
375
  self._printCursor()
370
376
 
371
377
  def untab(self):
@@ -380,8 +386,8 @@ class XEditor:
380
386
  self._setCursor(self._cursor_x - TAB_SPACES, self._cursor_y)
381
387
  if self._select_x != None:
382
388
  self._select_x -= TAB_SPACES
383
- self._printHeader(flush = False)
384
389
  self._printLines(flush = False)
390
+ self._printFooter(flush = False)
385
391
  self._printCursor()
386
392
  else:
387
393
  # multime lines selected (unindent as a block)
@@ -402,8 +408,8 @@ class XEditor:
402
408
  line = line[len(spaces):]
403
409
  self._lines[y] = line
404
410
  # print
405
- self._printHeader(flush = False)
406
411
  self._printLines(flush = False)
412
+ self._printFooter(flush = False)
407
413
  self._printCursor()
408
414
 
409
415
  def backspace(self):
@@ -419,15 +425,15 @@ class XEditor:
419
425
  self._lines[self._cursor_y - 1] = line
420
426
  self._lines.pop(self._cursor_y)
421
427
  self._setCursor(len(line_prev), self._cursor_y - 1)
422
- self._printHeader(flush = False)
423
428
  self._printLines(flush = False)
429
+ self._printFooter(flush = False)
424
430
  self._printCursor()
425
431
  else:
426
432
  self._dirty = True
427
433
  line = line[:self._cursor_x - 1] + line[self._cursor_x:]
428
434
  self._lines[self._cursor_y] = line
429
435
  self._setCursor(self._cursor_x - 1, self._cursor_y)
430
- self._printHeader(flush = False)
436
+ self._printFooter(flush = False)
431
437
  self._printLine(self._cursor_y)
432
438
 
433
439
  def delete(self):
@@ -441,20 +447,20 @@ class XEditor:
441
447
  line += self._lines[self._cursor_y + 1]
442
448
  self._lines[self._cursor_y] = line
443
449
  self._lines.pop(self._cursor_y + 1)
444
- self._printHeader(flush = False)
445
450
  self._printLines(flush = False)
451
+ self._printFooter(flush = False)
446
452
  self._printCursor()
447
453
  else:
448
454
  self._dirty = True
449
455
  line = line[:self._cursor_x] + line[self._cursor_x + 1:]
450
456
  self._lines[self._cursor_y] = line
451
- self._printHeader(flush = False)
457
+ self._printFooter(flush = False)
452
458
  self._printLine(self._cursor_y)
453
459
 
454
460
  def insert(self):
455
461
  # insert
456
462
  self._insert = not self._insert
457
- self._printHeader(flush = False)
463
+ self._printFooter(flush = False)
458
464
  self._printCursor()
459
465
 
460
466
  def escape(self):
@@ -491,8 +497,8 @@ class XEditor:
491
497
  self._lines[y] = line
492
498
  # print
493
499
  self._setCursor(x, y)
494
- self._printHeader(flush = False)
495
500
  self._printLines(flush = False)
501
+ self._printFooter(flush = False)
496
502
  self._printCursor()
497
503
 
498
504
  def getSelectedText(self):
@@ -576,8 +582,8 @@ class XEditor:
576
582
  self._select_x = 0
577
583
  self._select_y = 0
578
584
  self._setCursor(len(self._lines[-1]), len(self._lines) -1 )
579
- self._printHeader(flush = False)
580
585
  self._printLines(flush = False)
586
+ self._printFooter(flush = False)
581
587
  self._printCursor()
582
588
 
583
589
  def help(self):
@@ -600,7 +606,7 @@ class XEditor:
600
606
  self._lines = [""]
601
607
  self._newline = os.linesep
602
608
  if os.path.exists(filename):
603
- with open(filename,"r") as file:
609
+ with open(filename, "r", encoding="utf-8") as file:
604
610
  text = file.read()
605
611
  # decrypt
606
612
  if self._password != None:
@@ -628,7 +634,7 @@ class XEditor:
628
634
  if self._filename == None:
629
635
  self._result = self._newline.join(self._lines)
630
636
  else:
631
- with open(self._filename,"w") as file:
637
+ with open(self._filename,"w", encoding="utf-8") as file:
632
638
  file.write(text)
633
639
  # print
634
640
  self._dirty = False
@@ -699,7 +705,7 @@ class XEditor:
699
705
  if self._cursor_y >= self._offset_y + self._rows - 1:
700
706
  self._setOffset(self._offset_x, self._cursor_y - self._rows + 1 + 1)
701
707
 
702
- self._printHeader(flush = False)
708
+ self._printFooter(flush = False)
703
709
  self._printCursor()
704
710
 
705
711
  def autodetect_newline(self, text: str) -> str:
@@ -715,11 +721,11 @@ class XEditor:
715
721
  self._stdout.flush()
716
722
 
717
723
  def _printAll(self):
718
- self._printHeader(flush = False)
719
724
  self._printLines(flush = False)
725
+ self._printFooter(flush = False)
720
726
  self._printCursor()
721
727
 
722
- def _printHeader(self, flush:bool = True, message: str = None):
728
+ def _printFooter(self, flush:bool = True, message: str = None):
723
729
  filename = ""
724
730
  if self._filename != None:
725
731
  filename = self._filename
@@ -736,7 +742,8 @@ class XEditor:
736
742
 
737
743
  # print
738
744
  self._stdout.write(Sequences.CURSOR_HIDE)
739
- self._stdout.write(Sequences.SET_CURSOR_POSITION_X_Y.format(1,1))
745
+
746
+ self._stdout.write(Sequences.SET_CURSOR_POSITION_X_Y.format(1, self._rows))
740
747
  self._stdout.write(Sequences.BG_WHITE + Sequences.FG_BLACK)
741
748
  self._stdout.write(header)
742
749
  self._stdout.write(Sequences.RESET)
@@ -745,7 +752,7 @@ class XEditor:
745
752
  self._stdout.flush()
746
753
 
747
754
  def _printCursor(self, flush = True):
748
- self._stdout.write(Sequences.SET_CURSOR_POSITION_X_Y.format(1 + self._cursor_x - self._offset_x, 1 + 1 + self._cursor_y - self._offset_y))
755
+ self._stdout.write(Sequences.SET_CURSOR_POSITION_X_Y.format(1 + self._cursor_x - self._offset_x, 1 + self._cursor_y - self._offset_y))
749
756
  if flush:
750
757
  self._stdout.flush()
751
758
 
@@ -761,7 +768,7 @@ class XEditor:
761
768
  line_len = len(line)
762
769
 
763
770
  self._stdout.write(Sequences.CURSOR_HIDE)
764
- self._stdout.write(Sequences.SET_CURSOR_POSITION_X_Y.format(1, 1 + 1 + self._cursor_y - self._offset_y))
771
+ self._stdout.write(Sequences.SET_CURSOR_POSITION_X_Y.format(1, 1 + self._cursor_y - self._offset_y))
765
772
 
766
773
  # selected
767
774
  if self._select_x != None:
@@ -771,14 +778,18 @@ class XEditor:
771
778
  line += " " * (self._cols - line_len)
772
779
  if self._cursor_y == self._rows - 1 - 1:
773
780
  line = line[:line_len - 1]
774
-
781
+
782
+ # highlight
783
+ line = self._hightlight(line)
784
+
785
+ # print
775
786
  self._stdout.write(line)
776
787
  self._stdout.write(Sequences.CURSOR_SHOW)
777
788
  self._printCursor(flush = flush)
778
789
 
779
790
  def _printLines(self, flush = True):
780
791
  self._stdout.write(Sequences.CURSOR_HIDE)
781
- self._stdout.write(Sequences.SET_CURSOR_POSITION_X_Y.format(1,2))
792
+ self._stdout.write(Sequences.SET_CURSOR_POSITION_X_Y.format(1,1))
782
793
  self._stdout.write(Sequences.RESET)
783
794
  for y in range(self._rows - 1):
784
795
  line = None
@@ -811,8 +822,13 @@ class XEditor:
811
822
  # write
812
823
  if y == self._rows - 1 - 1:
813
824
  line = line[:len(line) - 1]
825
+
826
+ # hightlight
827
+ line = self._hightlight(line)
828
+
814
829
  # write line
815
830
  self._stdout.write(line)
831
+
816
832
  self._printCursor(flush = False)
817
833
  self._stdout.write(Sequences.CURSOR_SHOW)
818
834
  if flush:
@@ -820,10 +836,10 @@ class XEditor:
820
836
 
821
837
  def _question(self, message: str) -> str:
822
838
  self._stdout.write(Sequences.BG_WHITE + Sequences.FG_BLACK)
823
- self._stdout.write(Sequences.SET_CURSOR_POSITION_X_Y.format(1, 1))
839
+ self._stdout.write(Sequences.SET_CURSOR_POSITION_X_Y.format(1, self._rows))
824
840
  self._stdout.write(" " * self._cols)
825
841
 
826
- self._stdout.write(Sequences.SET_CURSOR_POSITION_X_Y.format(1, 1))
842
+ self._stdout.write(Sequences.SET_CURSOR_POSITION_X_Y.format(1, self._rows))
827
843
  self._stdout.write(Sequences.BG_WHITE + Sequences.FG_BLACK)
828
844
  result = None
829
845
  try:
@@ -836,19 +852,21 @@ class XEditor:
836
852
 
837
853
  def _questionYesNo(self, message: str) -> str:
838
854
  self._stdout.write(Sequences.BG_WHITE + Sequences.FG_BLACK)
839
- self._stdout.write(Sequences.SET_CURSOR_POSITION_X_Y.format(1, 1))
840
- self._stdout.write(" " * self._cols)
841
- self._stdout.write(Sequences.SET_CURSOR_POSITION_X_Y.format(1, 1))
855
+ self._stdout.write(Sequences.SET_CURSOR_POSITION_X_Y.format(1, self._rows))
856
+ self._stdout.write(" " * (self._cols - 1))
857
+ self._stdout.write(Sequences.SET_CURSOR_POSITION_X_Y.format(1, self._rows))
842
858
  self._stdout.write(Sequences.BG_WHITE + Sequences.FG_BLACK)
843
859
  result = None
844
860
  print(" " + message + " ", end='', flush=True)
845
861
  key = ""
846
862
  while True:
847
863
  key = readKey()
848
- print(key)
849
864
  if key == Keys.CTRL_C:
850
865
  result = None
851
866
  break
867
+ if key == Keys.ESCAPE:
868
+ result = None
869
+ break
852
870
  key = key.lower()
853
871
  if key == "y" or key == "n":
854
872
  result = key
@@ -858,7 +876,7 @@ class XEditor:
858
876
  return result
859
877
 
860
878
  def _show_error(self, message: str) -> str:
861
- self._printHeader(message = message )
879
+ self._printFooter(message = message )
862
880
  self._printCursor()
863
881
 
864
882
  def _colorizeLine(self, line, index):
@@ -956,6 +974,13 @@ class XEditor:
956
974
  def _loop(self):
957
975
  # clear
958
976
  self._clear()
977
+ # highlighter
978
+ if self._format == "env":
979
+ self._hightlight = hightlight_env
980
+ elif self._format == "md":
981
+ self._hightlight = hightlight_md
982
+ else:
983
+ self._hightlight = hightlight_none
959
984
  # set cursor style
960
985
  self._stdout.write(Sequences.CURSOR_SHAPE_BLINKING_BLOCK)
961
986
  # prepare
@@ -964,8 +989,8 @@ class XEditor:
964
989
  # loop
965
990
  while not self._stop:
966
991
  # read key
967
- key = readKey()
968
-
992
+ key = readKey()
993
+
969
994
  # check for size changed
970
995
  current_size = os.get_terminal_size()
971
996
  if self._rows != current_size.lines or self._cols != current_size.columns:
@@ -987,6 +1012,7 @@ class XEditor:
987
1012
  self.undo()
988
1013
  elif key == Keys.CTRL_Y:
989
1014
  self.redo()
1015
+
990
1016
  # arrows
991
1017
  elif key == Keys.RIGHT_ARROW:
992
1018
  self.right()
@@ -1037,7 +1063,6 @@ class XEditor:
1037
1063
  self.home(True)
1038
1064
  elif key == Keys.SHIFT_END_ARROW:
1039
1065
  self.end(True)
1040
-
1041
1066
  elif key == Keys.CTRL_SHIFT_RIGHT_ARROW:
1042
1067
  self.rightWord(True)
1043
1068
  elif key == Keys.CTRL_SHIFT_LEFT_ARROW:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dprojectstools
3
- Version: 0.0.26
3
+ Version: 0.0.28
4
4
  Summary: A set of development tools
5
5
  Author-email: Marc Delos <marcdp@dprojects.com>
6
6
  Project-URL: Homepage, https://github.com/marcdp/dprojectstools
@@ -23,6 +23,7 @@ Requires-Dist: sqlalchemy>=2.0.38
23
23
  Requires-Dist: websocat>=1.13.0
24
24
  Requires-Dist: json5>=0.13.0
25
25
  Requires-Dist: PyYAML>=6.0.3
26
+ Requires-Dist: pyodbc>=5.2.0
26
27
  Dynamic: license-file
27
28
 
28
29
 
@@ -83,3 +84,5 @@ cd dprojectstools
83
84
  MIT License
84
85
 
85
86
  See the LICENSE file for details.
87
+
88
+
@@ -51,4 +51,7 @@ src/dprojectstools/utils/versions.py
51
51
  src/dprojectstools/utils/xml.py
52
52
  src/dprojectstools/xeditor/__init__.py
53
53
  src/dprojectstools/xeditor/__main__.py
54
+ src/dprojectstools/xeditor/hightlight_env.py
55
+ src/dprojectstools/xeditor/hightlight_md.py
56
+ src/dprojectstools/xeditor/hightlight_none.py
54
57
  src/dprojectstools/xeditor/xeditor.py
@@ -10,3 +10,4 @@ sqlalchemy>=2.0.38
10
10
  websocat>=1.13.0
11
11
  json5>=0.13.0
12
12
  PyYAML>=6.0.3
13
+ pyodbc>=5.2.0
File without changes