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.
- {dprojectstools-0.0.26/src/dprojectstools.egg-info → dprojectstools-0.0.28}/PKG-INFO +4 -1
- {dprojectstools-0.0.26 → dprojectstools-0.0.28}/README.md +3 -1
- {dprojectstools-0.0.26 → dprojectstools-0.0.28}/pyproject.toml +3 -2
- {dprojectstools-0.0.26 → dprojectstools-0.0.28}/src/dprojectstools/backups/restic.py +3 -0
- {dprojectstools-0.0.26 → dprojectstools-0.0.28}/src/dprojectstools/console/read_password.py +1 -1
- dprojectstools-0.0.28/src/dprojectstools/xeditor/hightlight_env.py +112 -0
- dprojectstools-0.0.28/src/dprojectstools/xeditor/hightlight_md.py +191 -0
- dprojectstools-0.0.28/src/dprojectstools/xeditor/hightlight_none.py +3 -0
- {dprojectstools-0.0.26 → dprojectstools-0.0.28}/src/dprojectstools/xeditor/xeditor.py +61 -36
- {dprojectstools-0.0.26 → dprojectstools-0.0.28/src/dprojectstools.egg-info}/PKG-INFO +4 -1
- {dprojectstools-0.0.26 → dprojectstools-0.0.28}/src/dprojectstools.egg-info/SOURCES.txt +3 -0
- {dprojectstools-0.0.26 → dprojectstools-0.0.28}/src/dprojectstools.egg-info/requires.txt +1 -0
- {dprojectstools-0.0.26 → dprojectstools-0.0.28}/LICENSE +0 -0
- {dprojectstools-0.0.26 → dprojectstools-0.0.28}/setup.cfg +0 -0
- {dprojectstools-0.0.26 → dprojectstools-0.0.28}/src/dprojectstools/__init__.py +0 -0
- {dprojectstools-0.0.26 → dprojectstools-0.0.28}/src/dprojectstools/backups/__init__.py +0 -0
- {dprojectstools-0.0.26 → dprojectstools-0.0.28}/src/dprojectstools/clipboard/__init__.py +0 -0
- {dprojectstools-0.0.26 → dprojectstools-0.0.28}/src/dprojectstools/clipboard/clipboard.py +0 -0
- {dprojectstools-0.0.26 → dprojectstools-0.0.28}/src/dprojectstools/commands/__init__.py +0 -0
- {dprojectstools-0.0.26 → dprojectstools-0.0.28}/src/dprojectstools/commands/commands.py +0 -0
- {dprojectstools-0.0.26 → dprojectstools-0.0.28}/src/dprojectstools/commands/p.py +0 -0
- {dprojectstools-0.0.26 → dprojectstools-0.0.28}/src/dprojectstools/config/__init__.py +0 -0
- {dprojectstools-0.0.26 → dprojectstools-0.0.28}/src/dprojectstools/config/config.py +0 -0
- {dprojectstools-0.0.26 → dprojectstools-0.0.28}/src/dprojectstools/console/__init__.py +0 -0
- {dprojectstools-0.0.26 → dprojectstools-0.0.28}/src/dprojectstools/console/keys.py +0 -0
- {dprojectstools-0.0.26 → dprojectstools-0.0.28}/src/dprojectstools/console/readKey.py +0 -0
- {dprojectstools-0.0.26 → dprojectstools-0.0.28}/src/dprojectstools/console/sequences.py +0 -0
- {dprojectstools-0.0.26 → dprojectstools-0.0.28}/src/dprojectstools/crypto/__init__.py +0 -0
- {dprojectstools-0.0.26 → dprojectstools-0.0.28}/src/dprojectstools/crypto/aes.py +0 -0
- {dprojectstools-0.0.26 → dprojectstools-0.0.28}/src/dprojectstools/db/__main__.py +0 -0
- {dprojectstools-0.0.26 → dprojectstools-0.0.28}/src/dprojectstools/db/db_inspector.py +0 -0
- {dprojectstools-0.0.26 → dprojectstools-0.0.28}/src/dprojectstools/db/db_schema.py +0 -0
- {dprojectstools-0.0.26 → dprojectstools-0.0.28}/src/dprojectstools/db/generator_cs.py +0 -0
- {dprojectstools-0.0.26 → dprojectstools-0.0.28}/src/dprojectstools/db/generator_sql.py +0 -0
- {dprojectstools-0.0.26 → dprojectstools-0.0.28}/src/dprojectstools/db/generator_vb.py +0 -0
- {dprojectstools-0.0.26 → dprojectstools-0.0.28}/src/dprojectstools/db/generator_xml.py +0 -0
- {dprojectstools-0.0.26 → dprojectstools-0.0.28}/src/dprojectstools/docker/__init__.py +0 -0
- {dprojectstools-0.0.26 → dprojectstools-0.0.28}/src/dprojectstools/docker/docker.py +0 -0
- {dprojectstools-0.0.26 → dprojectstools-0.0.28}/src/dprojectstools/git/__init__.py +0 -0
- {dprojectstools-0.0.26 → dprojectstools-0.0.28}/src/dprojectstools/git/git.py +0 -0
- {dprojectstools-0.0.26 → dprojectstools-0.0.28}/src/dprojectstools/i18n/__init__.py +0 -0
- {dprojectstools-0.0.26 → dprojectstools-0.0.28}/src/dprojectstools/i18n/translator.py +0 -0
- {dprojectstools-0.0.26 → dprojectstools-0.0.28}/src/dprojectstools/i18n/xgettext.py +0 -0
- {dprojectstools-0.0.26 → dprojectstools-0.0.28}/src/dprojectstools/images/__init__.py +0 -0
- {dprojectstools-0.0.26 → dprojectstools-0.0.28}/src/dprojectstools/images/fonticons.py +0 -0
- {dprojectstools-0.0.26 → dprojectstools-0.0.28}/src/dprojectstools/math/bc.py +0 -0
- {dprojectstools-0.0.26 → dprojectstools-0.0.28}/src/dprojectstools/secrets/__init__.py +0 -0
- {dprojectstools-0.0.26 → dprojectstools-0.0.28}/src/dprojectstools/secrets/__main__.py +0 -0
- {dprojectstools-0.0.26 → dprojectstools-0.0.28}/src/dprojectstools/secrets/secrets.py +0 -0
- {dprojectstools-0.0.26 → dprojectstools-0.0.28}/src/dprojectstools/utils/__init__.py +0 -0
- {dprojectstools-0.0.26 → dprojectstools-0.0.28}/src/dprojectstools/utils/env.py +0 -0
- {dprojectstools-0.0.26 → dprojectstools-0.0.28}/src/dprojectstools/utils/process.py +0 -0
- {dprojectstools-0.0.26 → dprojectstools-0.0.28}/src/dprojectstools/utils/versions.py +0 -0
- {dprojectstools-0.0.26 → dprojectstools-0.0.28}/src/dprojectstools/utils/xml.py +0 -0
- {dprojectstools-0.0.26 → dprojectstools-0.0.28}/src/dprojectstools/xeditor/__init__.py +0 -0
- {dprojectstools-0.0.26 → dprojectstools-0.0.28}/src/dprojectstools/xeditor/__main__.py +0 -0
- {dprojectstools-0.0.26 → dprojectstools-0.0.28}/src/dprojectstools.egg-info/dependency_links.txt +0 -0
- {dprojectstools-0.0.26 → dprojectstools-0.0.28}/src/dprojectstools.egg-info/entry_points.txt +0 -0
- {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.
|
|
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
|
+
|
|
@@ -10,7 +10,7 @@ where = ["src"]
|
|
|
10
10
|
|
|
11
11
|
[project]
|
|
12
12
|
name = "dprojectstools"
|
|
13
|
-
version = "0.0.
|
|
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
|
|
@@ -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)
|
|
@@ -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
|
-
|
|
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:
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
|
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
|
-
|
|
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 +
|
|
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 +
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
840
|
-
self._stdout.write(" " * self._cols)
|
|
841
|
-
self._stdout.write(Sequences.SET_CURSOR_POSITION_X_Y.format(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.
|
|
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.
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{dprojectstools-0.0.26 → dprojectstools-0.0.28}/src/dprojectstools.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
{dprojectstools-0.0.26 → dprojectstools-0.0.28}/src/dprojectstools.egg-info/entry_points.txt
RENAMED
|
File without changes
|
|
File without changes
|