humanlang 3.0.8__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- human/__init__.py +175 -0
- human/cmd_map.py +78 -0
- human/decompiler.py +889 -0
- human/reader/trees.js +1270 -0
- human/reader/web.html +317 -0
- human/shapes/README.md +8 -0
- human/shapes/rail.md +44 -0
- human/shapes/skeleton.md +28 -0
- human/skills/decompile/SKILL.md +180 -0
- human/skills/human/SKILL.md +96 -0
- human/skills/shapes/SKILL.md +30 -0
- humanlang-3.0.8.dist-info/METADATA +48 -0
- humanlang-3.0.8.dist-info/RECORD +18 -0
- humanlang-3.0.8.dist-info/WHEEL +5 -0
- humanlang-3.0.8.dist-info/entry_points.txt +2 -0
- humanlang-3.0.8.dist-info/licenses/LICENSE-APACHE +202 -0
- humanlang-3.0.8.dist-info/licenses/LICENSE-MIT +21 -0
- humanlang-3.0.8.dist-info/top_level.txt +1 -0
human/__init__.py
ADDED
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
import argparse
|
|
2
|
+
import fnmatch
|
|
3
|
+
import json
|
|
4
|
+
import os
|
|
5
|
+
import shutil
|
|
6
|
+
import sys
|
|
7
|
+
import tempfile
|
|
8
|
+
from functools import partial
|
|
9
|
+
from http.server import SimpleHTTPRequestHandler, ThreadingHTTPServer
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
|
|
12
|
+
from . import cmd_map, decompiler
|
|
13
|
+
|
|
14
|
+
PKG = Path(__file__).parent
|
|
15
|
+
|
|
16
|
+
IGNORE_DIRS = {".git", "__pycache__", "node_modules", ".venv", "venv",
|
|
17
|
+
"build", "dist", ".idea", ".vscode"}
|
|
18
|
+
SUFFIXES = {".py", ".js", ".ts", ".tsx", ".jsx", ".html", ".htm", ".css", ".md",
|
|
19
|
+
".toml", ".json", ".yaml", ".yml", ".sh", ".rs", ".go", ".c", ".h",
|
|
20
|
+
".cpp", ".java", ".rb", ".sql"}
|
|
21
|
+
|
|
22
|
+
DESTINATIONS = {
|
|
23
|
+
"claude": Path.home() / ".claude" / "skills",
|
|
24
|
+
"droid": Path.home() / ".factory" / "skills",
|
|
25
|
+
"shared": Path.home() / ".agents" / "skills",
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def scan_files(root):
|
|
30
|
+
out = []
|
|
31
|
+
for dirpath, dirnames, filenames in os.walk(root):
|
|
32
|
+
d = Path(dirpath)
|
|
33
|
+
if d != root and (d / "human" / "human.json").is_file():
|
|
34
|
+
dirnames[:] = []
|
|
35
|
+
continue
|
|
36
|
+
dirnames[:] = sorted(x for x in dirnames
|
|
37
|
+
if not x.startswith(".") and x not in IGNORE_DIRS
|
|
38
|
+
and not (d == root and x == "human"))
|
|
39
|
+
for f in sorted(filenames):
|
|
40
|
+
if f.startswith(".") or f == "abstraction.txt":
|
|
41
|
+
continue
|
|
42
|
+
p = d / f
|
|
43
|
+
if p.suffix.lower() in SUFFIXES:
|
|
44
|
+
out.append(p.relative_to(root).as_posix())
|
|
45
|
+
return out
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def is_ignored(rel, patterns):
|
|
49
|
+
for p in patterns:
|
|
50
|
+
q = (p["path"] if isinstance(p, dict) else p).rstrip("/")
|
|
51
|
+
if rel == q or rel.startswith(q + "/") or fnmatch.fnmatch(rel, q):
|
|
52
|
+
return True
|
|
53
|
+
return False
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def cmd_init(a):
|
|
57
|
+
root = Path(a.folder).resolve()
|
|
58
|
+
root.mkdir(parents=True, exist_ok=True)
|
|
59
|
+
h = root / "human"
|
|
60
|
+
h.mkdir(exist_ok=True)
|
|
61
|
+
for name in ("web.html", "trees.js"):
|
|
62
|
+
shutil.copy(PKG / "reader" / name, h / name)
|
|
63
|
+
map_path = h / "human.json"
|
|
64
|
+
if map_path.exists():
|
|
65
|
+
data = json.loads(map_path.read_text())
|
|
66
|
+
decompiler.guard_structure(data, map_path)
|
|
67
|
+
else:
|
|
68
|
+
data = {"code_file": root.name, "explanations": [],
|
|
69
|
+
"not_covered": {"code_lines": [], "blank_lines": []}}
|
|
70
|
+
data["code_file"] = root.name
|
|
71
|
+
patterns = data.get("ignore", [])
|
|
72
|
+
found = scan_files(root)
|
|
73
|
+
data["ignore"] = patterns
|
|
74
|
+
data["files"] = [f for f in found if not is_ignored(f, patterns)]
|
|
75
|
+
data.pop("ignored", None)
|
|
76
|
+
map_path.write_text(json.dumps(data, indent=2) + "\n")
|
|
77
|
+
hidden = len(found) - len(data["files"])
|
|
78
|
+
tail = f", {hidden} ignored" if hidden else ""
|
|
79
|
+
print(f"project {root.name}: {len(data['files'])} files{tail}")
|
|
80
|
+
print(f"wrote {map_path}")
|
|
81
|
+
print(f"read it with: human serve (from {root})")
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
class FreshHandler(SimpleHTTPRequestHandler):
|
|
85
|
+
def end_headers(self):
|
|
86
|
+
self.send_header("Cache-Control", "no-cache")
|
|
87
|
+
super().end_headers()
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def cmd_serve(a):
|
|
91
|
+
root = decompiler.find_root(Path(a.folder).resolve())
|
|
92
|
+
handler = partial(FreshHandler, directory=str(root))
|
|
93
|
+
srv = ThreadingHTTPServer(("0.0.0.0", a.port), handler)
|
|
94
|
+
print(f"serving {root}")
|
|
95
|
+
print(f"http://localhost:{a.port}/human/web.html")
|
|
96
|
+
try:
|
|
97
|
+
srv.serve_forever()
|
|
98
|
+
except KeyboardInterrupt:
|
|
99
|
+
pass
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def cmd_skills(a):
|
|
103
|
+
dests = list(DESTINATIONS) if a.dest == "all" else [a.dest]
|
|
104
|
+
skills = sorted(p.name for p in (PKG / "skills").iterdir() if p.is_dir())
|
|
105
|
+
for dest in dests:
|
|
106
|
+
base = DESTINATIONS[dest]
|
|
107
|
+
base.mkdir(parents=True, exist_ok=True)
|
|
108
|
+
for name in skills:
|
|
109
|
+
dst = base / name
|
|
110
|
+
if dst.exists():
|
|
111
|
+
shutil.rmtree(dst)
|
|
112
|
+
shutil.copytree(PKG / "skills" / name, dst)
|
|
113
|
+
shutil.copytree(PKG / "shapes", dst / "shapes", dirs_exist_ok=True)
|
|
114
|
+
print(f"installed {name} -> {dst}")
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def cmd_map_h(a):
|
|
118
|
+
if not a.verbatim:
|
|
119
|
+
cmd_map.cmd_map(a)
|
|
120
|
+
return
|
|
121
|
+
want = Path(a.verbatim).read_text().strip()
|
|
122
|
+
text = decompiler.read_text_arg(a)
|
|
123
|
+
got = decompiler.ANCHOR_RE.sub(lambda m: m.group(1), text)
|
|
124
|
+
if got != want:
|
|
125
|
+
sys.exit("with the pins stripped the text is not the abstraction word for word; "
|
|
126
|
+
"pins may go in, the words may not change")
|
|
127
|
+
fd, tmp = tempfile.mkstemp(suffix=".txt")
|
|
128
|
+
try:
|
|
129
|
+
os.write(fd, text.encode())
|
|
130
|
+
os.close(fd)
|
|
131
|
+
a.text = tmp
|
|
132
|
+
cmd_map.cmd_map(a)
|
|
133
|
+
finally:
|
|
134
|
+
os.unlink(tmp)
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
def main():
|
|
138
|
+
ap = argparse.ArgumentParser(prog="human")
|
|
139
|
+
sub = ap.add_subparsers(dest="cmd", required=True)
|
|
140
|
+
i = sub.add_parser("init")
|
|
141
|
+
i.add_argument("folder", nargs="?", default=".")
|
|
142
|
+
v = sub.add_parser("serve")
|
|
143
|
+
v.add_argument("folder", nargs="?", default=".")
|
|
144
|
+
v.add_argument("--port", type=int, default=8010)
|
|
145
|
+
k = sub.add_parser("skills")
|
|
146
|
+
k.add_argument("--dest", choices=list(DESTINATIONS) + ["all"], default="claude")
|
|
147
|
+
m = sub.add_parser("map")
|
|
148
|
+
m.add_argument("code_file")
|
|
149
|
+
m.add_argument("--block")
|
|
150
|
+
m.add_argument("--text")
|
|
151
|
+
m.add_argument("--verbatim")
|
|
152
|
+
r = sub.add_parser("retext")
|
|
153
|
+
r.add_argument("code_file")
|
|
154
|
+
r.add_argument("id", type=int)
|
|
155
|
+
r.add_argument("--text")
|
|
156
|
+
u = sub.add_parser("undo")
|
|
157
|
+
u.add_argument("code_file")
|
|
158
|
+
s = sub.add_parser("show")
|
|
159
|
+
s.add_argument("code_file")
|
|
160
|
+
l = sub.add_parser("lines")
|
|
161
|
+
l.add_argument("code_file")
|
|
162
|
+
y = sub.add_parser("sync")
|
|
163
|
+
y.add_argument("code_file")
|
|
164
|
+
y.add_argument("--old")
|
|
165
|
+
y.add_argument("--stale", type=int)
|
|
166
|
+
y.add_argument("--tries", type=int, default=4)
|
|
167
|
+
a = ap.parse_args()
|
|
168
|
+
{"init": cmd_init, "serve": cmd_serve, "skills": cmd_skills, "map": cmd_map_h,
|
|
169
|
+
"retext": decompiler.cmd_retext, "undo": decompiler.cmd_undo,
|
|
170
|
+
"show": decompiler.cmd_show, "lines": decompiler.cmd_lines,
|
|
171
|
+
"sync": decompiler.cmd_sync}[a.cmd](a)
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
if __name__ == "__main__":
|
|
175
|
+
main()
|
human/cmd_map.py
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import sys
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
from . import decompiler
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def load_map(map_path, name):
|
|
9
|
+
if map_path.exists():
|
|
10
|
+
data = json.loads(map_path.read_text())
|
|
11
|
+
decompiler.guard_structure(data, map_path)
|
|
12
|
+
return data
|
|
13
|
+
return {"code_file": name, "explanations": [], "not_covered": {"code_lines": [], "blank_lines": []}}
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def entry_span(name, code_name, code_path, spans, n):
|
|
17
|
+
if name == code_name:
|
|
18
|
+
return [[1, n]]
|
|
19
|
+
if name in spans:
|
|
20
|
+
return [list(spans[name])]
|
|
21
|
+
sys.exit(f"{name!r} is not a block of {code_name}")
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def next_id(data):
|
|
25
|
+
return max((e["id"] for e in data["explanations"]), default=0) + 1
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def map_project(a, root):
|
|
29
|
+
if a.block:
|
|
30
|
+
sys.exit("a project map has no blocks of its own; drop --block")
|
|
31
|
+
map_path = root / "human" / "human.json"
|
|
32
|
+
data = load_map(map_path, root.name)
|
|
33
|
+
text = decompiler.read_text_arg(a)
|
|
34
|
+
eid = next_id(data)
|
|
35
|
+
try:
|
|
36
|
+
anchors = decompiler.build_anchors(text, data, {}, eid, root)
|
|
37
|
+
decompiler.project_pins(anchors)
|
|
38
|
+
except AssertionError as e:
|
|
39
|
+
sys.exit(str(e))
|
|
40
|
+
record = {"id": eid, "block": root.name, "block_lines": [],
|
|
41
|
+
"text": text, "anchors": anchors}
|
|
42
|
+
data["explanations"].append(record)
|
|
43
|
+
map_path.write_text(json.dumps(data, indent=2) + "\n")
|
|
44
|
+
files = sorted({x["file"] for x in anchors})
|
|
45
|
+
print(f"entry {eid}: {root.name}, {len(anchors)} pins into {len(files)} files ({', '.join(files)})")
|
|
46
|
+
print(f"wrote {map_path}")
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def cmd_map(a):
|
|
50
|
+
code_path = Path(a.code_file).resolve()
|
|
51
|
+
root = decompiler.find_root(code_path)
|
|
52
|
+
if code_path.is_dir():
|
|
53
|
+
map_project(a, root)
|
|
54
|
+
return
|
|
55
|
+
code_name = decompiler.rel_name(code_path, root)
|
|
56
|
+
lines = code_path.read_text().splitlines()
|
|
57
|
+
spans = decompiler.block_spans(code_path, lines)
|
|
58
|
+
map_path = decompiler.map_path_of(code_path, root)
|
|
59
|
+
data = load_map(map_path, code_name)
|
|
60
|
+
text = decompiler.read_text_arg(a)
|
|
61
|
+
n = len(lines)
|
|
62
|
+
block = (a.block or code_name).strip()
|
|
63
|
+
block_lines = entry_span(block, code_name, code_path, spans, n)
|
|
64
|
+
eid = next_id(data)
|
|
65
|
+
try:
|
|
66
|
+
anchors = decompiler.build_anchors(text, data, spans, eid, root)
|
|
67
|
+
decompiler.check_cycle(data, eid, anchors)
|
|
68
|
+
except AssertionError as e:
|
|
69
|
+
sys.exit(str(e))
|
|
70
|
+
record = {"id": eid, "block": block, "block_lines": block_lines,
|
|
71
|
+
"text": text, "anchors": anchors}
|
|
72
|
+
data["explanations"].append(record)
|
|
73
|
+
missing, blank = decompiler.recompute(data, lines)
|
|
74
|
+
map_path.write_text(json.dumps(data, indent=2) + "\n")
|
|
75
|
+
decompiler.register_file(root, code_name)
|
|
76
|
+
print(f"entry {eid}: {block}, lines {decompiler.fmt(decompiler.expand(block_lines))}, {decompiler.anchor_counts(anchors)}")
|
|
77
|
+
decompiler.print_coverage(missing, blank, lines)
|
|
78
|
+
print(f"wrote {map_path}")
|