dtree-explorer 1.0.0__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.
- dtree_explorer-1.0.0/LICENSE.md +21 -0
- dtree_explorer-1.0.0/PKG-INFO +74 -0
- dtree_explorer-1.0.0/README.md +48 -0
- dtree_explorer-1.0.0/dtree_explorer/__init__.py +3 -0
- dtree_explorer-1.0.0/dtree_explorer/__main__.py +105 -0
- dtree_explorer-1.0.0/dtree_explorer/bridge.py +87 -0
- dtree_explorer-1.0.0/dtree_explorer/explorer.html +1963 -0
- dtree_explorer-1.0.0/dtree_explorer.egg-info/PKG-INFO +74 -0
- dtree_explorer-1.0.0/dtree_explorer.egg-info/SOURCES.txt +14 -0
- dtree_explorer-1.0.0/dtree_explorer.egg-info/dependency_links.txt +1 -0
- dtree_explorer-1.0.0/dtree_explorer.egg-info/entry_points.txt +2 -0
- dtree_explorer-1.0.0/dtree_explorer.egg-info/requires.txt +1 -0
- dtree_explorer-1.0.0/dtree_explorer.egg-info/top_level.txt +1 -0
- dtree_explorer-1.0.0/pyproject.toml +43 -0
- dtree_explorer-1.0.0/setup.cfg +4 -0
- dtree_explorer-1.0.0/tests/test_explorer.py +107 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Enis Olgac
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: dtree-explorer
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: DTree Explorer: encode a directed tree with DTree's two projections (Q1, Q2) and watch it step by step, in the browser
|
|
5
|
+
Author-email: Enis Olgac <enis@digraphs.info>
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/EnisOlgac/DTree-Explorer
|
|
8
|
+
Project-URL: Library, https://github.com/EnisOlgac/DTree
|
|
9
|
+
Project-URL: Online, https://digraphs.blog/dtree-demo/
|
|
10
|
+
Project-URL: Paper, https://doi.org/10.5772/intechopen.1013408
|
|
11
|
+
Keywords: directed tree,tree topology,reachability,visualization,incremental encoding,teaching
|
|
12
|
+
Classifier: Development Status :: 4 - Beta
|
|
13
|
+
Classifier: Intended Audience :: Science/Research
|
|
14
|
+
Classifier: Intended Audience :: Education
|
|
15
|
+
Classifier: Intended Audience :: Developers
|
|
16
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
17
|
+
Classifier: Operating System :: OS Independent
|
|
18
|
+
Classifier: Programming Language :: Python :: 3
|
|
19
|
+
Classifier: Topic :: Scientific/Engineering :: Mathematics
|
|
20
|
+
Classifier: Topic :: Scientific/Engineering :: Visualization
|
|
21
|
+
Requires-Python: >=3.9
|
|
22
|
+
Description-Content-Type: text/markdown
|
|
23
|
+
License-File: LICENSE.md
|
|
24
|
+
Requires-Dist: dtree-encoding>=1.8.0
|
|
25
|
+
Dynamic: license-file
|
|
26
|
+
|
|
27
|
+
# DTree Explorer
|
|
28
|
+
|
|
29
|
+
Encode a directed tree with [DTree](https://github.com/EnisOlgac/DTree)'s two projections Q1 and Q2 and watch it happen, edge by edge. The Explorer draws the tree, places every node in the plane at its rank in Q1 and Q2, lists both projections with the positions the library stores, and answers reachability questions by clicking: *u* reaches *v* exactly when *u* comes before *v* in both lists.
|
|
30
|
+
|
|
31
|
+
Every tree is computed by the published library, [dtree-encoding](https://pypi.org/project/dtree-encoding/); the Explorer only shows what it computes.
|
|
32
|
+
|
|
33
|
+
## Three ways to use it
|
|
34
|
+
|
|
35
|
+
**Online:** [digraphs.blog/dtree-demo](https://digraphs.blog/dtree-demo/), nothing to install.
|
|
36
|
+
|
|
37
|
+
**As a Python program**, offline, with the library installed on your machine:
|
|
38
|
+
|
|
39
|
+
```
|
|
40
|
+
pip install dtree-explorer
|
|
41
|
+
dtree-explorer
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
The command starts a small server on 127.0.0.1 and opens the page in your browser; stop it with Ctrl+C. Options: `--port 8765`, `--no-browser`, `--save explorer.html` (write the page as one file).
|
|
45
|
+
|
|
46
|
+
**As a single file:** download `DTree-Explorer.html` from the [latest release](https://github.com/EnisOlgac/DTree-Explorer/releases/latest) and open it in a browser. It loads Python (Pyodide) from cdn.jsdelivr.net and dtree-encoding from pypi.org, so it needs an internet connection.
|
|
47
|
+
|
|
48
|
+
## Input
|
|
49
|
+
|
|
50
|
+
One line per instruction; lines starting with `#` are comments.
|
|
51
|
+
|
|
52
|
+
| Line | Meaning |
|
|
53
|
+
|---|---|
|
|
54
|
+
| `p: c1; c2` | c1 and c2 become the last children of p |
|
|
55
|
+
| `s > c1; c2` | c1 goes directly after its sibling s, then c2 after c1 |
|
|
56
|
+
| `s < c1; c2` | c1 and c2 go directly before s, in that order |
|
|
57
|
+
|
|
58
|
+
The examples include the trees of the paper: the introductory example, Figure 1, the generalized insertion of Section 4.1 (Case A, before; Case B, after) and Appendix A, whose three variants appear twice: once by the order of the input lines, once with `<` and `>`. Both give the same projections.
|
|
59
|
+
|
|
60
|
+
## Privacy
|
|
61
|
+
|
|
62
|
+
Your tree stays on your computer. The page counts visits only in the copy on digraphs.blog, with Google Analytics and only after you agree; the package and the release file count nothing.
|
|
63
|
+
|
|
64
|
+
## Building
|
|
65
|
+
|
|
66
|
+
Working in this repository needs the library installed: `pip install dtree-encoding`. `src/template.html` is the page, `dtree_explorer/bridge.py` the Python part it runs, `src/build.py` assembles `dtree_explorer/explorer.html` (package and release) and `site/index.html` (digraphs.blog) with the installed dtree-encoding. Tests: `python -m unittest discover -s tests`.
|
|
67
|
+
|
|
68
|
+
## Background
|
|
69
|
+
|
|
70
|
+
E. Olgac, "Topology and Structure of Directed Trees: An Incremental Encoding," IntechOpen, 2025. doi: [10.5772/intechopen.1013408](https://doi.org/10.5772/intechopen.1013408).
|
|
71
|
+
|
|
72
|
+
## License
|
|
73
|
+
|
|
74
|
+
MIT. Design and concept: Enis Olgac. The Explorer was written with Claude (Anthropic).
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# DTree Explorer
|
|
2
|
+
|
|
3
|
+
Encode a directed tree with [DTree](https://github.com/EnisOlgac/DTree)'s two projections Q1 and Q2 and watch it happen, edge by edge. The Explorer draws the tree, places every node in the plane at its rank in Q1 and Q2, lists both projections with the positions the library stores, and answers reachability questions by clicking: *u* reaches *v* exactly when *u* comes before *v* in both lists.
|
|
4
|
+
|
|
5
|
+
Every tree is computed by the published library, [dtree-encoding](https://pypi.org/project/dtree-encoding/); the Explorer only shows what it computes.
|
|
6
|
+
|
|
7
|
+
## Three ways to use it
|
|
8
|
+
|
|
9
|
+
**Online:** [digraphs.blog/dtree-demo](https://digraphs.blog/dtree-demo/), nothing to install.
|
|
10
|
+
|
|
11
|
+
**As a Python program**, offline, with the library installed on your machine:
|
|
12
|
+
|
|
13
|
+
```
|
|
14
|
+
pip install dtree-explorer
|
|
15
|
+
dtree-explorer
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
The command starts a small server on 127.0.0.1 and opens the page in your browser; stop it with Ctrl+C. Options: `--port 8765`, `--no-browser`, `--save explorer.html` (write the page as one file).
|
|
19
|
+
|
|
20
|
+
**As a single file:** download `DTree-Explorer.html` from the [latest release](https://github.com/EnisOlgac/DTree-Explorer/releases/latest) and open it in a browser. It loads Python (Pyodide) from cdn.jsdelivr.net and dtree-encoding from pypi.org, so it needs an internet connection.
|
|
21
|
+
|
|
22
|
+
## Input
|
|
23
|
+
|
|
24
|
+
One line per instruction; lines starting with `#` are comments.
|
|
25
|
+
|
|
26
|
+
| Line | Meaning |
|
|
27
|
+
|---|---|
|
|
28
|
+
| `p: c1; c2` | c1 and c2 become the last children of p |
|
|
29
|
+
| `s > c1; c2` | c1 goes directly after its sibling s, then c2 after c1 |
|
|
30
|
+
| `s < c1; c2` | c1 and c2 go directly before s, in that order |
|
|
31
|
+
|
|
32
|
+
The examples include the trees of the paper: the introductory example, Figure 1, the generalized insertion of Section 4.1 (Case A, before; Case B, after) and Appendix A, whose three variants appear twice: once by the order of the input lines, once with `<` and `>`. Both give the same projections.
|
|
33
|
+
|
|
34
|
+
## Privacy
|
|
35
|
+
|
|
36
|
+
Your tree stays on your computer. The page counts visits only in the copy on digraphs.blog, with Google Analytics and only after you agree; the package and the release file count nothing.
|
|
37
|
+
|
|
38
|
+
## Building
|
|
39
|
+
|
|
40
|
+
Working in this repository needs the library installed: `pip install dtree-encoding`. `src/template.html` is the page, `dtree_explorer/bridge.py` the Python part it runs, `src/build.py` assembles `dtree_explorer/explorer.html` (package and release) and `site/index.html` (digraphs.blog) with the installed dtree-encoding. Tests: `python -m unittest discover -s tests`.
|
|
41
|
+
|
|
42
|
+
## Background
|
|
43
|
+
|
|
44
|
+
E. Olgac, "Topology and Structure of Directed Trees: An Incremental Encoding," IntechOpen, 2025. doi: [10.5772/intechopen.1013408](https://doi.org/10.5772/intechopen.1013408).
|
|
45
|
+
|
|
46
|
+
## License
|
|
47
|
+
|
|
48
|
+
MIT. Design and concept: Enis Olgac. The Explorer was written with Claude (Anthropic).
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
"""Start the DTree Explorer: a local web page that computes with the installed dtree-encoding.
|
|
2
|
+
|
|
3
|
+
dtree-explorer open the Explorer in the browser
|
|
4
|
+
dtree-explorer --port 8765 use a fixed port (default: a free one)
|
|
5
|
+
dtree-explorer --no-browser only start the server and print its address
|
|
6
|
+
dtree-explorer --save FILE write the page as one HTML file and exit
|
|
7
|
+
|
|
8
|
+
The server listens on 127.0.0.1 only and serves the page and one computing endpoint;
|
|
9
|
+
nothing leaves your machine. Stop it with Ctrl+C.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
import argparse
|
|
13
|
+
import json
|
|
14
|
+
import platform
|
|
15
|
+
import sys
|
|
16
|
+
import webbrowser
|
|
17
|
+
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
|
|
18
|
+
from importlib import resources
|
|
19
|
+
|
|
20
|
+
import dtree
|
|
21
|
+
|
|
22
|
+
from . import __version__, bridge
|
|
23
|
+
|
|
24
|
+
MAX_INPUT = 4 * 1024 * 1024 # bytes of tree description per request
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def page() -> bytes:
|
|
28
|
+
return resources.files(__package__).joinpath("explorer.html").read_bytes()
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class Handler(BaseHTTPRequestHandler):
|
|
32
|
+
server_version = "DTreeExplorer/" + __version__
|
|
33
|
+
|
|
34
|
+
def _local_host(self) -> bool:
|
|
35
|
+
host = (self.headers.get("Host") or "").rsplit(":", 1)[0]
|
|
36
|
+
return host in ("127.0.0.1", "localhost") # refuses requests under other host names
|
|
37
|
+
|
|
38
|
+
def _send(self, code, kind, body: bytes):
|
|
39
|
+
self.send_response(code)
|
|
40
|
+
self.send_header("Content-Type", kind)
|
|
41
|
+
self.send_header("Content-Length", str(len(body)))
|
|
42
|
+
self.send_header("Cache-Control", "no-store")
|
|
43
|
+
self.end_headers()
|
|
44
|
+
self.wfile.write(body)
|
|
45
|
+
|
|
46
|
+
def do_GET(self):
|
|
47
|
+
if not self._local_host():
|
|
48
|
+
return self._send(403, "text/plain", b"forbidden")
|
|
49
|
+
path = self.path.split("?", 1)[0]
|
|
50
|
+
if path in ("/", "/index.html"):
|
|
51
|
+
return self._send(200, "text/html; charset=utf-8", page())
|
|
52
|
+
if path == "/api/info":
|
|
53
|
+
info = {"dtree": dtree.__version__, "python": platform.python_version(), "explorer": __version__}
|
|
54
|
+
return self._send(200, "application/json", json.dumps(info).encode())
|
|
55
|
+
self._send(404, "text/plain", b"not found")
|
|
56
|
+
|
|
57
|
+
def do_POST(self):
|
|
58
|
+
if not self._local_host():
|
|
59
|
+
return self._send(403, "text/plain", b"forbidden")
|
|
60
|
+
if self.path.split("?", 1)[0] != "/api/encode":
|
|
61
|
+
return self._send(404, "text/plain", b"not found")
|
|
62
|
+
n = int(self.headers.get("Content-Length") or 0)
|
|
63
|
+
if n > MAX_INPUT:
|
|
64
|
+
return self._send(413, "text/plain", b"description too large")
|
|
65
|
+
text = self.rfile.read(n).decode("utf-8", "replace")
|
|
66
|
+
self._send(200, "application/json", bridge.encode(text).encode())
|
|
67
|
+
|
|
68
|
+
def log_message(self, *args): # quiet
|
|
69
|
+
pass
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def serve(port=0):
|
|
73
|
+
"""Create the server (not yet running); port 0 picks a free port."""
|
|
74
|
+
return ThreadingHTTPServer(("127.0.0.1", port), Handler)
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def main(argv=None):
|
|
78
|
+
ap = argparse.ArgumentParser(prog="dtree-explorer", description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter)
|
|
79
|
+
ap.add_argument("--port", type=int, default=0, help="port on 127.0.0.1 (default: a free one)")
|
|
80
|
+
ap.add_argument("--no-browser", action="store_true", help="do not open the browser")
|
|
81
|
+
ap.add_argument("--save", metavar="FILE", help="write the page as one HTML file and exit")
|
|
82
|
+
ap.add_argument("--version", action="version", version=f"DTree Explorer {__version__}, dtree-encoding {dtree.__version__}")
|
|
83
|
+
args = ap.parse_args(argv)
|
|
84
|
+
if args.save:
|
|
85
|
+
with open(args.save, "wb") as fh:
|
|
86
|
+
fh.write(page())
|
|
87
|
+
print(f"Wrote {args.save}. Open it in a browser; it loads Python (Pyodide) from the internet.")
|
|
88
|
+
return 0
|
|
89
|
+
httpd = serve(args.port)
|
|
90
|
+
url = f"http://127.0.0.1:{httpd.server_address[1]}/"
|
|
91
|
+
print(f"DTree Explorer {__version__} with dtree-encoding {dtree.__version__} at {url}")
|
|
92
|
+
print("Press Ctrl+C to stop.")
|
|
93
|
+
if not args.no_browser:
|
|
94
|
+
webbrowser.open(url)
|
|
95
|
+
try:
|
|
96
|
+
httpd.serve_forever()
|
|
97
|
+
except KeyboardInterrupt:
|
|
98
|
+
print("\nStopped.")
|
|
99
|
+
finally:
|
|
100
|
+
httpd.server_close()
|
|
101
|
+
return 0
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
if __name__ == "__main__":
|
|
105
|
+
sys.exit(main())
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
"""Bridge between the DTree Explorer page and the dtree package.
|
|
2
|
+
|
|
3
|
+
The page runs this module in the browser (Pyodide) with the published dtree-encoding
|
|
4
|
+
package; the same code runs under CPython for the tests and to precompute the example
|
|
5
|
+
that is shown while the Python runtime loads. Everything the page displays about the
|
|
6
|
+
encoding (the order of Q1 and Q2, the positions, the parents) comes from here.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import json
|
|
10
|
+
|
|
11
|
+
import dtree
|
|
12
|
+
from dtree import DTree
|
|
13
|
+
|
|
14
|
+
LIMIT = 300 # insertions recorded step by step; larger trees show the final state only
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def _split(line):
|
|
18
|
+
at = min((i for i in (line.find(":"), line.find(">"), line.find("<")) if i >= 0), default=-1)
|
|
19
|
+
if at < 0:
|
|
20
|
+
return None
|
|
21
|
+
return line[at], line[:at].strip(), [c.strip() for c in line[at + 1:].split(";") if c.strip()]
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def _state(t):
|
|
25
|
+
q1, q2 = t.q1, t.q2
|
|
26
|
+
pos = {v: [str(t.Q1.index(v)), str(t.Q2.index(v))] for v in q1}
|
|
27
|
+
return {"q1": q1, "q2": q2, "pos": pos}
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def encode(text, limit=LIMIT):
|
|
31
|
+
"""Build a tree from the description and return the steps as JSON.
|
|
32
|
+
|
|
33
|
+
Each step is one inserted node: the operation, its line, the new node and its parent,
|
|
34
|
+
and (up to ``limit`` insertions) the full state after it: Q1, Q2 and the positions.
|
|
35
|
+
"""
|
|
36
|
+
t = DTree("E")
|
|
37
|
+
steps, errors, parents = [], [], {}
|
|
38
|
+
recording = True
|
|
39
|
+
for no, raw in enumerate(text.splitlines(), 1):
|
|
40
|
+
line = raw.strip().lstrip("")
|
|
41
|
+
if not line or line.startswith(("#", "--", "**")):
|
|
42
|
+
continue
|
|
43
|
+
if line.startswith("@@"):
|
|
44
|
+
break
|
|
45
|
+
parts = _split(line)
|
|
46
|
+
if parts is None:
|
|
47
|
+
errors.append({"line": no, "text": line, "msg": "expected ':', '>' or '<'"})
|
|
48
|
+
continue
|
|
49
|
+
op, anchor, kids = parts
|
|
50
|
+
for child in kids:
|
|
51
|
+
try:
|
|
52
|
+
if op == ":":
|
|
53
|
+
t.insert(anchor, child)
|
|
54
|
+
shown = f"{anchor}:{child}"
|
|
55
|
+
elif op == ">":
|
|
56
|
+
t.insert_after(anchor, child)
|
|
57
|
+
shown = f"{anchor}>{child}"
|
|
58
|
+
anchor = child
|
|
59
|
+
else:
|
|
60
|
+
t.insert_before(anchor, child)
|
|
61
|
+
shown = f"{anchor}<{child}"
|
|
62
|
+
except (KeyError, ValueError) as e:
|
|
63
|
+
msg = e.args[0] if e.args else str(e)
|
|
64
|
+
errors.append({"line": no, "text": line, "msg": f"{child}: {msg}"})
|
|
65
|
+
continue
|
|
66
|
+
parents[child] = t.get_parent(child)
|
|
67
|
+
step = {"line": no, "op": shown, "new": child}
|
|
68
|
+
if recording and len(steps) < limit:
|
|
69
|
+
step.update(_state(t))
|
|
70
|
+
elif recording:
|
|
71
|
+
recording = False
|
|
72
|
+
for s in steps:
|
|
73
|
+
for k in ("q1", "q2", "pos"):
|
|
74
|
+
s.pop(k, None)
|
|
75
|
+
steps.append(step)
|
|
76
|
+
final = _state(t) if t.root is not None else {"q1": [], "q2": [], "pos": {}}
|
|
77
|
+
return json.dumps({
|
|
78
|
+
"version": dtree.__version__,
|
|
79
|
+
"root": t.root,
|
|
80
|
+
"parents": parents,
|
|
81
|
+
"steps": steps,
|
|
82
|
+
"final": final,
|
|
83
|
+
"stepwise": recording,
|
|
84
|
+
"limit": limit,
|
|
85
|
+
"renumber": [t.Q1.renumber_count, t.Q2.renumber_count],
|
|
86
|
+
"errors": errors,
|
|
87
|
+
})
|