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.
@@ -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,3 @@
1
+ """DTree Explorer: encode a directed tree with DTree's projections Q1 and Q2 and watch it, step by step."""
2
+
3
+ __version__ = "1.0.0"
@@ -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
+ })