visset 0.1.0__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.
- visset/__init__.py +118 -0
- visset/__main__.py +3 -0
- visset/_assets/player.js +204 -0
- visset/_version.py +1 -0
- visset/cli.py +165 -0
- visset/export/__init__.py +1 -0
- visset/export/html.py +90 -0
- visset/py.typed +0 -0
- visset/tracer/__init__.py +6 -0
- visset/tracer/captions.py +210 -0
- visset/tracer/engine.py +546 -0
- visset/tracer/live.py +74 -0
- visset/tracer/positions.py +394 -0
- visset/tracer/request.py +88 -0
- visset/tracer/run.py +401 -0
- visset/tracer/snapshot.py +258 -0
- visset/tracer/tokens.py +87 -0
- visset/tracer/window.py +86 -0
- visset-0.1.0.dist-info/METADATA +50 -0
- visset-0.1.0.dist-info/RECORD +23 -0
- visset-0.1.0.dist-info/WHEEL +4 -0
- visset-0.1.0.dist-info/entry_points.txt +2 -0
- visset-0.1.0.dist-info/licenses/LICENSE +21 -0
visset/__init__.py
ADDED
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
"""visset: step-by-step visual explanations of code.
|
|
2
|
+
|
|
3
|
+
import visset
|
|
4
|
+
visset.show(binary_search, [1, 3, 5, 7], 5) # a live call, like plt.show()
|
|
5
|
+
visset.show("total = 0\\nfor x in xs:\\n total += x", given={"xs": [4, 5]})
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
from collections.abc import Callable
|
|
11
|
+
from typing import TYPE_CHECKING, Any
|
|
12
|
+
|
|
13
|
+
from ._version import __version__
|
|
14
|
+
|
|
15
|
+
if TYPE_CHECKING:
|
|
16
|
+
from .tracer.request import Limits
|
|
17
|
+
|
|
18
|
+
__all__ = ["__version__", "show", "to_html", "trace", "trace_call", "trace_source"]
|
|
19
|
+
|
|
20
|
+
Trace = dict[str, Any]
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def trace(target: str | Callable[..., Any], /, *args: Any, **kwargs: Any) -> Trace:
|
|
24
|
+
"""Trace a function call or a piece of source code.
|
|
25
|
+
|
|
26
|
+
- `trace(fn, *args, **kwargs)` calls `fn` and records only that call.
|
|
27
|
+
(Use `trace_call` for options such as `lines=` or `limits=`.)
|
|
28
|
+
- `trace("source", **options)` runs source text; see `trace_source` for the options.
|
|
29
|
+
"""
|
|
30
|
+
if callable(target):
|
|
31
|
+
return trace_call(target, args, kwargs)
|
|
32
|
+
if args:
|
|
33
|
+
raise TypeError("trace(source, ...) takes options as keywords; see trace_source()")
|
|
34
|
+
return trace_source(target, **kwargs)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def trace_call(
|
|
38
|
+
fn: Callable[..., Any],
|
|
39
|
+
args: tuple[Any, ...] = (),
|
|
40
|
+
kwargs: dict[str, Any] | None = None,
|
|
41
|
+
*,
|
|
42
|
+
lines: tuple[int, int] | None = None,
|
|
43
|
+
limits: Limits | None = None,
|
|
44
|
+
title: str | None = None,
|
|
45
|
+
) -> Trace:
|
|
46
|
+
"""Trace one call of `fn` in this process. Shows only `fn`'s source (or `lines` of its file).
|
|
47
|
+
|
|
48
|
+
Arguments can be any objects. Runs without the sandbox, so use it on code you trust to
|
|
49
|
+
finish; the step and recursion limits still apply.
|
|
50
|
+
"""
|
|
51
|
+
from .tracer.live import trace_call as _trace_call
|
|
52
|
+
|
|
53
|
+
return _trace_call(fn, args, kwargs, lines=lines, limits=limits, title=title)
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def trace_source(
|
|
57
|
+
source: str,
|
|
58
|
+
*,
|
|
59
|
+
path: str = "snippet.py",
|
|
60
|
+
function: str | None = None,
|
|
61
|
+
args: str | tuple[Any, ...] = "",
|
|
62
|
+
given: str | dict[str, Any] | None = None,
|
|
63
|
+
lines: tuple[int, int] | None = None,
|
|
64
|
+
stdin: str = "",
|
|
65
|
+
limits: Limits | None = None,
|
|
66
|
+
sandbox: bool = True,
|
|
67
|
+
title: str | None = None,
|
|
68
|
+
) -> Trace:
|
|
69
|
+
"""Run source text and record it.
|
|
70
|
+
|
|
71
|
+
- `given`: inputs for a snippet, e.g. `{"arr": [1, 3, 5], "target": 3}` (literals).
|
|
72
|
+
- `function` + `args`: run the module untraced, then trace only that call.
|
|
73
|
+
- `lines`: show only these lines (1-based, inclusive), e.g. part of a function.
|
|
74
|
+
- `sandbox=False`: run in this process (faster start, no time/memory protection).
|
|
75
|
+
"""
|
|
76
|
+
import textwrap
|
|
77
|
+
|
|
78
|
+
from .tracer import Limits, TraceRequest, execute, run_sandboxed
|
|
79
|
+
|
|
80
|
+
if isinstance(args, tuple):
|
|
81
|
+
args = ", ".join(repr(a) for a in args)
|
|
82
|
+
if isinstance(given, dict):
|
|
83
|
+
given = ", ".join(f"{k}={v!r}" for k, v in given.items())
|
|
84
|
+
req = TraceRequest(
|
|
85
|
+
source=textwrap.dedent(source), # a pasted fragment of a function is indented
|
|
86
|
+
path=path,
|
|
87
|
+
function=function,
|
|
88
|
+
args=args,
|
|
89
|
+
given=given or "",
|
|
90
|
+
lines=lines,
|
|
91
|
+
stdin=stdin,
|
|
92
|
+
limits=limits or Limits(),
|
|
93
|
+
title=title,
|
|
94
|
+
)
|
|
95
|
+
result, _status = run_sandboxed(req) if sandbox else execute(req)
|
|
96
|
+
return result
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def to_html(trace: Trace, path: str | None = None) -> str:
|
|
100
|
+
"""Render a trace as one self-contained HTML file. Returns the HTML; writes it if `path`."""
|
|
101
|
+
from .export.html import render_html
|
|
102
|
+
|
|
103
|
+
html = render_html(trace)
|
|
104
|
+
if path is not None:
|
|
105
|
+
with open(path, "w", encoding="utf-8") as f:
|
|
106
|
+
f.write(html)
|
|
107
|
+
return html
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def show(target: Trace | str | Callable[..., Any], /, *args: Any, **kwargs: Any) -> str:
|
|
111
|
+
"""Open a walkthrough in the browser, like `plt.show()`. Returns the HTML file's path.
|
|
112
|
+
|
|
113
|
+
Accepts a trace, or the same arguments as `trace()`: `show(fn, *args)` / `show("code")`.
|
|
114
|
+
"""
|
|
115
|
+
from .export.html import open_in_browser
|
|
116
|
+
|
|
117
|
+
data = target if isinstance(target, dict) else trace(target, *args, **kwargs)
|
|
118
|
+
return open_in_browser(data)
|
visset/__main__.py
ADDED
visset/_assets/player.js
ADDED
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
"use strict";(()=>{var Ze=Object.defineProperty;var Qe=(a,s,t)=>s in a?Ze(a,s,{enumerable:!0,configurable:!0,writable:!0,value:t}):a[s]=t;var k=(a,s,t)=>Qe(a,typeof s!="symbol"?s+"":s,t);function ae(a,s,t){if(a&&a.length){let[e,n]=s,r=Math.PI/180*t,i=Math.cos(r),o=Math.sin(r);for(let l of a){let[c,h]=l;l[0]=(c-e)*i-(h-n)*o+e,l[1]=(c-e)*o+(h-n)*i+n}}}function Ue(a,s){return a[0]===s[0]&&a[1]===s[1]}function Je(a,s,t,e=1){let n=t,r=Math.max(s,.1),i=a[0]&&a[0][0]&&typeof a[0][0]=="number"?[a]:a,o=[0,0];if(n)for(let c of i)ae(c,o,n);let l=(function(c,h,m){let d=[];for(let b of c){let x=[...b];Ue(x[0],x[x.length-1])||x.push([x[0][0],x[0][1]]),x.length>2&&d.push(x)}let u=[];h=Math.max(h,.1);let f=[];for(let b of d)for(let x=0;x<b.length-1;x++){let w=b[x],S=b[x+1];if(w[1]!==S[1]){let M=Math.min(w[1],S[1]);f.push({ymin:M,ymax:Math.max(w[1],S[1]),x:M===w[1]?w[0]:S[0],islope:(S[0]-w[0])/(S[1]-w[1])})}}if(f.sort(((b,x)=>b.ymin<x.ymin?-1:b.ymin>x.ymin?1:b.x<x.x?-1:b.x>x.x?1:b.ymax===x.ymax?0:(b.ymax-x.ymax)/Math.abs(b.ymax-x.ymax))),!f.length)return u;let p=[],g=f[0].ymin,v=0;for(;p.length||f.length;){if(f.length){let b=-1;for(let x=0;x<f.length&&!(f[x].ymin>g);x++)b=x;f.splice(0,b+1).forEach((x=>{p.push({s:g,edge:x})}))}if(p=p.filter((b=>!(b.edge.ymax<=g))),p.sort(((b,x)=>b.edge.x===x.edge.x?0:(b.edge.x-x.edge.x)/Math.abs(b.edge.x-x.edge.x))),(m!==1||v%h==0)&&p.length>1)for(let b=0;b<p.length;b+=2){let x=b+1;if(x>=p.length)break;let w=p[b].edge,S=p[x].edge;u.push([[Math.round(w.x),g],[Math.round(S.x),g]])}g+=m,p.forEach((b=>{b.edge.x=b.edge.x+m*b.edge.islope})),v++}return u})(i,r,e);if(n){for(let c of i)ae(c,o,-n);(function(c,h,m){let d=[];c.forEach((u=>d.push(...u))),ae(d,h,m)})(l,o,-n)}return l}function q(a,s){var t;let e=s.hachureAngle+90,n=s.hachureGap;n<0&&(n=4*s.strokeWidth),n=Math.round(Math.max(n,.1));let r=1;return s.roughness>=1&&(((t=s.randomizer)===null||t===void 0?void 0:t.next())||Math.random())>.7&&(r=n),Je(a,n,e,r||1)}var F=class{constructor(s){this.helper=s}fillPolygons(s,t){return this._fillPolygons(s,t)}_fillPolygons(s,t){let e=q(s,t);return{type:"fillSketch",ops:this.renderLines(e,t)}}renderLines(s,t){let e=[];for(let n of s)e.push(...this.helper.doubleLineOps(n[0][0],n[0][1],n[1][0],n[1][1],t));return e}};function ne(a){let s=a[0],t=a[1];return Math.sqrt(Math.pow(s[0]-t[0],2)+Math.pow(s[1]-t[1],2))}var pe=class extends F{fillPolygons(s,t){let e=t.hachureGap;e<0&&(e=4*t.strokeWidth),e=Math.max(e,.1);let n=q(s,Object.assign({},t,{hachureGap:e})),r=Math.PI/180*t.hachureAngle,i=[],o=.5*e*Math.cos(r),l=.5*e*Math.sin(r);for(let[c,h]of n)ne([c,h])&&i.push([[c[0]-o,c[1]+l],[...h]],[[c[0]+o,c[1]-l],[...h]]);return{type:"fillSketch",ops:this.renderLines(i,t)}}},ue=class extends F{fillPolygons(s,t){let e=this._fillPolygons(s,t),n=Object.assign({},t,{hachureAngle:t.hachureAngle+90}),r=this._fillPolygons(s,n);return e.ops=e.ops.concat(r.ops),e}},de=class{constructor(s){this.helper=s}fillPolygons(s,t){let e=q(s,t=Object.assign({},t,{hachureAngle:0}));return this.dotsOnLines(e,t)}dotsOnLines(s,t){let e=[],n=t.hachureGap;n<0&&(n=4*t.strokeWidth),n=Math.max(n,.1);let r=t.fillWeight;r<0&&(r=t.strokeWidth/2);let i=n/4;for(let o of s){let l=ne(o),c=l/n,h=Math.ceil(c)-1,m=l-h*n,d=(o[0][0]+o[1][0])/2-n/4,u=Math.min(o[0][1],o[1][1]);for(let f=0;f<h;f++){let p=u+m+f*n,g=d-i+2*Math.random()*i,v=p-i+2*Math.random()*i,b=this.helper.ellipse(g,v,r,r,t);e.push(...b.ops)}}return{type:"fillSketch",ops:e}}},fe=class{constructor(s){this.helper=s}fillPolygons(s,t){let e=q(s,t);return{type:"fillSketch",ops:this.dashedLine(e,t)}}dashedLine(s,t){let e=t.dashOffset<0?t.hachureGap<0?4*t.strokeWidth:t.hachureGap:t.dashOffset,n=t.dashGap<0?t.hachureGap<0?4*t.strokeWidth:t.hachureGap:t.dashGap,r=[];return s.forEach((i=>{let o=ne(i),l=Math.floor(o/(e+n)),c=(o+n-l*(e+n))/2,h=i[0],m=i[1];h[0]>m[0]&&(h=i[1],m=i[0]);let d=Math.atan((m[1]-h[1])/(m[0]-h[0]));for(let u=0;u<l;u++){let f=u*(e+n),p=f+e,g=[h[0]+f*Math.cos(d)+c*Math.cos(d),h[1]+f*Math.sin(d)+c*Math.sin(d)],v=[h[0]+p*Math.cos(d)+c*Math.cos(d),h[1]+p*Math.sin(d)+c*Math.sin(d)];r.push(...this.helper.doubleLineOps(g[0],g[1],v[0],v[1],t))}})),r}},me=class{constructor(s){this.helper=s}fillPolygons(s,t){let e=t.hachureGap<0?4*t.strokeWidth:t.hachureGap,n=t.zigzagOffset<0?e:t.zigzagOffset,r=q(s,t=Object.assign({},t,{hachureGap:e+n}));return{type:"fillSketch",ops:this.zigzagLines(r,n,t)}}zigzagLines(s,t,e){let n=[];return s.forEach((r=>{let i=ne(r),o=Math.round(i/(2*t)),l=r[0],c=r[1];l[0]>c[0]&&(l=r[1],c=r[0]);let h=Math.atan((c[1]-l[1])/(c[0]-l[0]));for(let m=0;m<o;m++){let d=2*m*t,u=2*(m+1)*t,f=Math.sqrt(2*Math.pow(t,2)),p=[l[0]+d*Math.cos(h),l[1]+d*Math.sin(h)],g=[l[0]+u*Math.cos(h),l[1]+u*Math.sin(h)],v=[p[0]+f*Math.cos(h+Math.PI/4),p[1]+f*Math.sin(h+Math.PI/4)];n.push(...this.helper.doubleLineOps(p[0],p[1],v[0],v[1],e),...this.helper.doubleLineOps(v[0],v[1],g[0],g[1],e))}})),n}},T={},ge=class{constructor(s){this.seed=s}next(){return this.seed?(2**31-1&(this.seed=Math.imul(48271,this.seed)))/2**31:Math.random()}},Xe=0,ie=1,Ee=2,Q={A:7,a:7,C:6,c:6,H:1,h:1,L:2,l:2,M:2,m:2,Q:4,q:4,S:4,s:4,T:2,t:2,V:1,v:1,Z:0,z:0};function le(a,s){return a.type===s}function Me(a){let s=[],t=(function(i){let o=new Array;for(;i!=="";)if(i.match(/^([ \t\r\n,]+)/))i=i.substr(RegExp.$1.length);else if(i.match(/^([aAcChHlLmMqQsStTvVzZ])/))o[o.length]={type:Xe,text:RegExp.$1},i=i.substr(RegExp.$1.length);else{if(!i.match(/^(([-+]?[0-9]+(\.[0-9]*)?|[-+]?\.[0-9]+)([eE][-+]?[0-9]+)?)/))return[];o[o.length]={type:ie,text:`${parseFloat(RegExp.$1)}`},i=i.substr(RegExp.$1.length)}return o[o.length]={type:Ee,text:""},o})(a),e="BOD",n=0,r=t[n];for(;!le(r,Ee);){let i=0,o=[];if(e==="BOD"){if(r.text!=="M"&&r.text!=="m")return Me("M0,0"+a);n++,i=Q[r.text],e=r.text}else le(r,ie)?i=Q[e]:(n++,i=Q[r.text],e=r.text);if(!(n+i<t.length))throw new Error("Path data ended short");for(let l=n;l<n+i;l++){let c=t[l];if(!le(c,ie))throw new Error("Param not a number: "+e+","+c.text);o[o.length]=+c.text}if(typeof Q[e]!="number")throw new Error("Bad segment: "+e);{let l={key:e,data:o};s.push(l),n+=i,r=t[n],e==="M"&&(e="L"),e==="m"&&(e="l")}}return s}function He(a){let s=0,t=0,e=0,n=0,r=[];for(let{key:i,data:o}of a)switch(i){case"M":r.push({key:"M",data:[...o]}),[s,t]=o,[e,n]=o;break;case"m":s+=o[0],t+=o[1],r.push({key:"M",data:[s,t]}),e=s,n=t;break;case"L":r.push({key:"L",data:[...o]}),[s,t]=o;break;case"l":s+=o[0],t+=o[1],r.push({key:"L",data:[s,t]});break;case"C":r.push({key:"C",data:[...o]}),s=o[4],t=o[5];break;case"c":{let l=o.map(((c,h)=>h%2?c+t:c+s));r.push({key:"C",data:l}),s=l[4],t=l[5];break}case"Q":r.push({key:"Q",data:[...o]}),s=o[2],t=o[3];break;case"q":{let l=o.map(((c,h)=>h%2?c+t:c+s));r.push({key:"Q",data:l}),s=l[2],t=l[3];break}case"A":r.push({key:"A",data:[...o]}),s=o[5],t=o[6];break;case"a":s+=o[5],t+=o[6],r.push({key:"A",data:[o[0],o[1],o[2],o[3],o[4],s,t]});break;case"H":r.push({key:"H",data:[...o]}),s=o[0];break;case"h":s+=o[0],r.push({key:"H",data:[s]});break;case"V":r.push({key:"V",data:[...o]}),t=o[0];break;case"v":t+=o[0],r.push({key:"V",data:[t]});break;case"S":r.push({key:"S",data:[...o]}),s=o[2],t=o[3];break;case"s":{let l=o.map(((c,h)=>h%2?c+t:c+s));r.push({key:"S",data:l}),s=l[2],t=l[3];break}case"T":r.push({key:"T",data:[...o]}),s=o[0],t=o[1];break;case"t":s+=o[0],t+=o[1],r.push({key:"T",data:[s,t]});break;case"Z":case"z":r.push({key:"Z",data:[]}),s=e,t=n}return r}function Ne(a){let s=[],t="",e=0,n=0,r=0,i=0,o=0,l=0;for(let{key:c,data:h}of a){switch(c){case"M":s.push({key:"M",data:[...h]}),[e,n]=h,[r,i]=h;break;case"C":s.push({key:"C",data:[...h]}),e=h[4],n=h[5],o=h[2],l=h[3];break;case"L":s.push({key:"L",data:[...h]}),[e,n]=h;break;case"H":e=h[0],s.push({key:"L",data:[e,n]});break;case"V":n=h[0],s.push({key:"L",data:[e,n]});break;case"S":{let m=0,d=0;t==="C"||t==="S"?(m=e+(e-o),d=n+(n-l)):(m=e,d=n),s.push({key:"C",data:[m,d,...h]}),o=h[0],l=h[1],e=h[2],n=h[3];break}case"T":{let[m,d]=h,u=0,f=0;t==="Q"||t==="T"?(u=e+(e-o),f=n+(n-l)):(u=e,f=n);let p=e+2*(u-e)/3,g=n+2*(f-n)/3,v=m+2*(u-m)/3,b=d+2*(f-d)/3;s.push({key:"C",data:[p,g,v,b,m,d]}),o=u,l=f,e=m,n=d;break}case"Q":{let[m,d,u,f]=h,p=e+2*(m-e)/3,g=n+2*(d-n)/3,v=u+2*(m-u)/3,b=f+2*(d-f)/3;s.push({key:"C",data:[p,g,v,b,u,f]}),o=m,l=d,e=u,n=f;break}case"A":{let m=Math.abs(h[0]),d=Math.abs(h[1]),u=h[2],f=h[3],p=h[4],g=h[5],v=h[6];m===0||d===0?(s.push({key:"C",data:[e,n,g,v,g,v]}),e=g,n=v):(e!==g||n!==v)&&(De(e,n,g,v,m,d,u,f,p).forEach((function(b){s.push({key:"C",data:b})})),e=g,n=v);break}case"Z":s.push({key:"Z",data:[]}),e=r,n=i}t=c}return s}function _(a,s,t){return[a*Math.cos(t)-s*Math.sin(t),a*Math.sin(t)+s*Math.cos(t)]}function De(a,s,t,e,n,r,i,o,l,c){let h=(m=i,Math.PI*m/180);var m;let d=[],u=0,f=0,p=0,g=0;if(c)[u,f,p,g]=c;else{[a,s]=_(a,s,-h),[t,e]=_(t,e,-h);let $=(a-t)/2,E=(s-e)/2,A=$*$/(n*n)+E*E/(r*r);A>1&&(A=Math.sqrt(A),n*=A,r*=A);let z=n*n,I=r*r,Ge=z*I-z*E*E-I*$*$,Ke=z*E*E+I*$*$,Se=(o===l?-1:1)*Math.sqrt(Math.abs(Ge/Ke));p=Se*n*E/r+(a+t)/2,g=Se*-r*$/n+(s+e)/2,u=Math.asin(parseFloat(((s-g)/r).toFixed(9))),f=Math.asin(parseFloat(((e-g)/r).toFixed(9))),a<p&&(u=Math.PI-u),t<p&&(f=Math.PI-f),u<0&&(u=2*Math.PI+u),f<0&&(f=2*Math.PI+f),l&&u>f&&(u-=2*Math.PI),!l&&f>u&&(f-=2*Math.PI)}let v=f-u;if(Math.abs(v)>120*Math.PI/180){let $=f,E=t,A=e;f=l&&f>u?u+120*Math.PI/180*1:u+120*Math.PI/180*-1,d=De(t=p+n*Math.cos(f),e=g+r*Math.sin(f),E,A,n,r,i,0,l,[f,$,p,g])}v=f-u;let b=Math.cos(u),x=Math.sin(u),w=Math.cos(f),S=Math.sin(f),M=Math.tan(v/4),P=4/3*n*M,O=4/3*r*M,Z=[a,s],C=[a+P*x,s-O*b],N=[t+P*S,e-O*w],we=[t,e];if(C[0]=2*Z[0]-C[0],C[1]=2*Z[1]-C[1],c)return[C,N,we].concat(d);{d=[C,N,we].concat(d);let $=[];for(let E=0;E<d.length;E+=3){let A=_(d[E][0],d[E][1],h),z=_(d[E+1][0],d[E+1][1],h),I=_(d[E+2][0],d[E+2][1],h);$.push([A[0],A[1],z[0],z[1],I[0],I[1]])}return $}}var Ye={randOffset:function(a,s){return y(a,s)},randOffsetWithRange:function(a,s,t){return ee(a,s,t)},ellipse:function(a,s,t,e,n){let r=ze(t,e,n);return be(a,s,n,r).opset},doubleLineOps:function(a,s,t,e,n){return H(a,s,t,e,n,!0)}};function Ve(a,s,t,e,n){return{type:"path",ops:H(a,s,t,e,n)}}function X(a,s,t){let e=(a||[]).length;if(e>2){let n=[];for(let r=0;r<e-1;r++)n.push(...H(a[r][0],a[r][1],a[r+1][0],a[r+1][1],t));return s&&n.push(...H(a[e-1][0],a[e-1][1],a[0][0],a[0][1],t)),{type:"path",ops:n}}return e===2?Ve(a[0][0],a[0][1],a[1][0],a[1][1],t):{type:"path",ops:[]}}function et(a,s,t,e,n){return(function(r,i){return X(r,!0,i)})([[a,s],[a+t,s],[a+t,s+e],[a,s+e]],n)}function Te(a,s){if(a.length){let t=typeof a[0][0]=="number"?[a]:a,e=U(t[0],1*(1+.2*s.roughness),s),n=s.disableMultiStroke?[]:U(t[0],1.5*(1+.22*s.roughness),$e(s));for(let r=1;r<t.length;r++){let i=t[r];if(i.length){let o=U(i,1*(1+.2*s.roughness),s),l=s.disableMultiStroke?[]:U(i,1.5*(1+.22*s.roughness),$e(s));for(let c of o)c.op!=="move"&&e.push(c);for(let c of l)c.op!=="move"&&n.push(c)}}return{type:"path",ops:e.concat(n)}}return{type:"path",ops:[]}}function ze(a,s,t){let e=Math.sqrt(2*Math.PI*Math.sqrt((Math.pow(a/2,2)+Math.pow(s/2,2))/2)),n=Math.ceil(Math.max(t.curveStepCount,t.curveStepCount/Math.sqrt(200)*e)),r=2*Math.PI/n,i=Math.abs(a/2),o=Math.abs(s/2),l=1-t.curveFitting;return i+=y(i*l,t),o+=y(o*l,t),{increment:r,rx:i,ry:o}}function be(a,s,t,e){let[n,r]=Ce(e.increment,a,s,e.rx,e.ry,1,e.increment*ee(.1,ee(.4,1,t),t),t),i=te(n,null,t);if(!t.disableMultiStroke&&t.roughness!==0){let[o]=Ce(e.increment,a,s,e.rx,e.ry,1.5,0,t),l=te(o,null,t);i=i.concat(l)}return{estimatedPoints:r,opset:{type:"path",ops:i}}}function Le(a,s,t,e,n,r,i,o,l){let c=a,h=s,m=Math.abs(t/2),d=Math.abs(e/2);m+=y(.01*m,l),d+=y(.01*d,l);let u=n,f=r;for(;u<0;)u+=2*Math.PI,f+=2*Math.PI;f-u>2*Math.PI&&(u=0,f=2*Math.PI);let p=2*Math.PI/l.curveStepCount,g=Math.min(p/2,(f-u)/2),v=Oe(g,c,h,m,d,u,f,1,l);if(!l.disableMultiStroke){let b=Oe(g,c,h,m,d,u,f,1.5,l);v.push(...b)}return i&&(o?v.push(...H(c,h,c+m*Math.cos(u),h+d*Math.sin(u),l),...H(c,h,c+m*Math.cos(f),h+d*Math.sin(f),l)):v.push({op:"lineTo",data:[c,h]},{op:"lineTo",data:[c+m*Math.cos(u),h+d*Math.sin(u)]})),{type:"path",ops:v}}function Pe(a,s){let t=Ne(He(Me(a))),e=[],n=[0,0],r=[0,0];for(let{key:i,data:o}of t)switch(i){case"M":r=[o[0],o[1]],n=[o[0],o[1]];break;case"L":e.push(...H(r[0],r[1],o[0],o[1],s)),r=[o[0],o[1]];break;case"C":{let[l,c,h,m,d,u]=o;e.push(...tt(l,c,h,m,d,u,r,s)),r=[d,u];break}case"Z":e.push(...H(r[0],r[1],n[0],n[1],s)),r=[n[0],n[1]]}return{type:"path",ops:e}}function ce(a,s){let t=[];for(let e of a)if(e.length){let n=s.maxRandomnessOffset||0,r=e.length;if(r>2){t.push({op:"move",data:[e[0][0]+y(n,s),e[0][1]+y(n,s)]});for(let i=1;i<r;i++)t.push({op:"lineTo",data:[e[i][0]+y(n,s),e[i][1]+y(n,s)]})}}return{type:"fillPath",ops:t}}function R(a,s){return(function(t,e){let n=t.fillStyle||"hachure";if(!T[n])switch(n){case"zigzag":T[n]||(T[n]=new pe(e));break;case"cross-hatch":T[n]||(T[n]=new ue(e));break;case"dots":T[n]||(T[n]=new de(e));break;case"dashed":T[n]||(T[n]=new fe(e));break;case"zigzag-line":T[n]||(T[n]=new me(e));break;default:n="hachure",T[n]||(T[n]=new F(e))}return T[n]})(s,Ye).fillPolygons(a,s)}function $e(a){let s=Object.assign({},a);return s.randomizer=void 0,a.seed&&(s.seed=a.seed+1),s}function Ie(a){return a.randomizer||(a.randomizer=new ge(a.seed||0)),a.randomizer.next()}function ee(a,s,t,e=1){return t.roughness*e*(Ie(t)*(s-a)+a)}function y(a,s,t=1){return ee(-a,a,s,t)}function H(a,s,t,e,n,r=!1){let i=r?n.disableMultiStrokeFill:n.disableMultiStroke,o=ve(a,s,t,e,n,!0,!1);if(i)return o;let l=ve(a,s,t,e,n,!0,!0);return o.concat(l)}function ve(a,s,t,e,n,r,i){let o=Math.pow(a-t,2)+Math.pow(s-e,2),l=Math.sqrt(o),c=1;c=l<200?1:l>500?.4:-.0016668*l+1.233334;let h=n.maxRandomnessOffset||0;h*h*100>o&&(h=l/10);let m=h/2,d=.2+.2*Ie(n),u=n.bowing*n.maxRandomnessOffset*(e-s)/200,f=n.bowing*n.maxRandomnessOffset*(a-t)/200;u=y(u,n,c),f=y(f,n,c);let p=[],g=()=>y(m,n,c),v=()=>y(h,n,c),b=n.preserveVertices;return r&&(i?p.push({op:"move",data:[a+(b?0:g()),s+(b?0:g())]}):p.push({op:"move",data:[a+(b?0:y(h,n,c)),s+(b?0:y(h,n,c))]})),i?p.push({op:"bcurveTo",data:[u+a+(t-a)*d+g(),f+s+(e-s)*d+g(),u+a+2*(t-a)*d+g(),f+s+2*(e-s)*d+g(),t+(b?0:g()),e+(b?0:g())]}):p.push({op:"bcurveTo",data:[u+a+(t-a)*d+v(),f+s+(e-s)*d+v(),u+a+2*(t-a)*d+v(),f+s+2*(e-s)*d+v(),t+(b?0:v()),e+(b?0:v())]}),p}function U(a,s,t){if(!a.length)return[];let e=[];e.push([a[0][0]+y(s,t),a[0][1]+y(s,t)]),e.push([a[0][0]+y(s,t),a[0][1]+y(s,t)]);for(let n=1;n<a.length;n++)e.push([a[n][0]+y(s,t),a[n][1]+y(s,t)]),n===a.length-1&&e.push([a[n][0]+y(s,t),a[n][1]+y(s,t)]);return te(e,null,t)}function te(a,s,t){let e=a.length,n=[];if(e>3){let r=[],i=1-t.curveTightness;n.push({op:"move",data:[a[1][0],a[1][1]]});for(let o=1;o+2<e;o++){let l=a[o];r[0]=[l[0],l[1]],r[1]=[l[0]+(i*a[o+1][0]-i*a[o-1][0])/6,l[1]+(i*a[o+1][1]-i*a[o-1][1])/6],r[2]=[a[o+1][0]+(i*a[o][0]-i*a[o+2][0])/6,a[o+1][1]+(i*a[o][1]-i*a[o+2][1])/6],r[3]=[a[o+1][0],a[o+1][1]],n.push({op:"bcurveTo",data:[r[1][0],r[1][1],r[2][0],r[2][1],r[3][0],r[3][1]]})}if(s&&s.length===2){let o=t.maxRandomnessOffset;n.push({op:"lineTo",data:[s[0]+y(o,t),s[1]+y(o,t)]})}}else e===3?(n.push({op:"move",data:[a[1][0],a[1][1]]}),n.push({op:"bcurveTo",data:[a[1][0],a[1][1],a[2][0],a[2][1],a[2][0],a[2][1]]})):e===2&&n.push(...ve(a[0][0],a[0][1],a[1][0],a[1][1],t,!0,!0));return n}function Ce(a,s,t,e,n,r,i,o){let l=[],c=[];if(o.roughness===0){a/=4,c.push([s+e*Math.cos(-a),t+n*Math.sin(-a)]);for(let h=0;h<=2*Math.PI;h+=a){let m=[s+e*Math.cos(h),t+n*Math.sin(h)];l.push(m),c.push(m)}c.push([s+e*Math.cos(0),t+n*Math.sin(0)]),c.push([s+e*Math.cos(a),t+n*Math.sin(a)])}else{let h=y(.5,o)-Math.PI/2;c.push([y(r,o)+s+.9*e*Math.cos(h-a),y(r,o)+t+.9*n*Math.sin(h-a)]);let m=2*Math.PI+h-.01;for(let d=h;d<m;d+=a){let u=[y(r,o)+s+e*Math.cos(d),y(r,o)+t+n*Math.sin(d)];l.push(u),c.push(u)}c.push([y(r,o)+s+e*Math.cos(h+2*Math.PI+.5*i),y(r,o)+t+n*Math.sin(h+2*Math.PI+.5*i)]),c.push([y(r,o)+s+.98*e*Math.cos(h+i),y(r,o)+t+.98*n*Math.sin(h+i)]),c.push([y(r,o)+s+.9*e*Math.cos(h+.5*i),y(r,o)+t+.9*n*Math.sin(h+.5*i)])}return[c,l]}function Oe(a,s,t,e,n,r,i,o,l){let c=r+y(.1,l),h=[];h.push([y(o,l)+s+.9*e*Math.cos(c-a),y(o,l)+t+.9*n*Math.sin(c-a)]);for(let m=c;m<=i;m+=a)h.push([y(o,l)+s+e*Math.cos(m),y(o,l)+t+n*Math.sin(m)]);return h.push([s+e*Math.cos(i),t+n*Math.sin(i)]),h.push([s+e*Math.cos(i),t+n*Math.sin(i)]),te(h,null,l)}function tt(a,s,t,e,n,r,i,o){let l=[],c=[o.maxRandomnessOffset||1,(o.maxRandomnessOffset||1)+.3],h=[0,0],m=o.disableMultiStroke?1:2,d=o.preserveVertices;for(let u=0;u<m;u++)u===0?l.push({op:"move",data:[i[0],i[1]]}):l.push({op:"move",data:[i[0]+(d?0:y(c[0],o)),i[1]+(d?0:y(c[0],o))]}),h=d?[n,r]:[n+y(c[u],o),r+y(c[u],o)],l.push({op:"bcurveTo",data:[a+y(c[u],o),s+y(c[u],o),t+y(c[u],o),e+y(c[u],o),h[0],h[1]]});return l}function B(a){return[...a]}function Ae(a,s=0){let t=a.length;if(t<3)throw new Error("A curve must have at least three points.");let e=[];if(t===3)e.push(B(a[0]),B(a[1]),B(a[2]),B(a[2]));else{let n=[];n.push(a[0],a[0]);for(let o=1;o<a.length;o++)n.push(a[o]),o===a.length-1&&n.push(a[o]);let r=[],i=1-s;e.push(B(n[0]));for(let o=1;o+2<n.length;o++){let l=n[o];r[0]=[l[0],l[1]],r[1]=[l[0]+(i*n[o+1][0]-i*n[o-1][0])/6,l[1]+(i*n[o+1][1]-i*n[o-1][1])/6],r[2]=[n[o+1][0]+(i*n[o][0]-i*n[o+2][0])/6,n[o+1][1]+(i*n[o][1]-i*n[o+2][1])/6],r[3]=[n[o+1][0],n[o+1][1]],e.push(r[1],r[2],r[3])}}return e}function Y(a,s){return Math.pow(a[0]-s[0],2)+Math.pow(a[1]-s[1],2)}function st(a,s,t){let e=Y(s,t);if(e===0)return Y(a,s);let n=((a[0]-s[0])*(t[0]-s[0])+(a[1]-s[1])*(t[1]-s[1]))/e;return n=Math.max(0,Math.min(1,n)),Y(a,D(s,t,n))}function D(a,s,t){return[a[0]+(s[0]-a[0])*t,a[1]+(s[1]-a[1])*t]}function xe(a,s,t,e){let n=e||[];if((function(o,l){let c=o[l+0],h=o[l+1],m=o[l+2],d=o[l+3],u=3*h[0]-2*c[0]-d[0];u*=u;let f=3*h[1]-2*c[1]-d[1];f*=f;let p=3*m[0]-2*d[0]-c[0];p*=p;let g=3*m[1]-2*d[1]-c[1];return g*=g,u<p&&(u=p),f<g&&(f=g),u+f})(a,s)<t){let o=a[s+0];n.length?(r=n[n.length-1],i=o,Math.sqrt(Y(r,i))>1&&n.push(o)):n.push(o),n.push(a[s+3])}else{let l=a[s+0],c=a[s+1],h=a[s+2],m=a[s+3],d=D(l,c,.5),u=D(c,h,.5),f=D(h,m,.5),p=D(d,u,.5),g=D(u,f,.5),v=D(p,g,.5);xe([l,d,p,v],0,t,n),xe([v,g,f,m],0,t,n)}var r,i;return n}function nt(a,s){return se(a,0,a.length,s)}function se(a,s,t,e,n){let r=n||[],i=a[s],o=a[t-1],l=0,c=1;for(let h=s+1;h<t-1;++h){let m=st(a[h],i,o);m>l&&(l=m,c=h)}return Math.sqrt(l)>e?(se(a,s,c+1,e,r),se(a,c,t,e,r)):(r.length||r.push(i),r.push(o)),r}function he(a,s=.15,t){let e=[],n=(a.length-1)/3;for(let r=0;r<n;r++)xe(a,3*r,s,e);return t&&t>0?se(e,0,e.length,t):e}var L="none",W=class{constructor(s){this.defaultOptions={maxRandomnessOffset:2,roughness:1,bowing:1,stroke:"#000",strokeWidth:1,curveTightness:0,curveFitting:.95,curveStepCount:9,fillStyle:"hachure",fillWeight:-1,hachureAngle:-41,hachureGap:-1,dashOffset:-1,dashGap:-1,zigzagOffset:-1,seed:0,disableMultiStroke:!1,disableMultiStrokeFill:!1,preserveVertices:!1,fillShapeRoughnessGain:.8},this.config=s||{},this.config.options&&(this.defaultOptions=this._o(this.config.options))}static newSeed(){return Math.floor(Math.random()*2**31)}_o(s){return s?Object.assign({},this.defaultOptions,s):this.defaultOptions}_d(s,t,e){return{shape:s,sets:t||[],options:e||this.defaultOptions}}line(s,t,e,n,r){let i=this._o(r);return this._d("line",[Ve(s,t,e,n,i)],i)}rectangle(s,t,e,n,r){let i=this._o(r),o=[],l=et(s,t,e,n,i);if(i.fill){let c=[[s,t],[s+e,t],[s+e,t+n],[s,t+n]];i.fillStyle==="solid"?o.push(ce([c],i)):o.push(R([c],i))}return i.stroke!==L&&o.push(l),this._d("rectangle",o,i)}ellipse(s,t,e,n,r){let i=this._o(r),o=[],l=ze(e,n,i),c=be(s,t,i,l);if(i.fill)if(i.fillStyle==="solid"){let h=be(s,t,i,l).opset;h.type="fillPath",o.push(h)}else o.push(R([c.estimatedPoints],i));return i.stroke!==L&&o.push(c.opset),this._d("ellipse",o,i)}circle(s,t,e,n){let r=this.ellipse(s,t,e,e,n);return r.shape="circle",r}linearPath(s,t){let e=this._o(t);return this._d("linearPath",[X(s,!1,e)],e)}arc(s,t,e,n,r,i,o=!1,l){let c=this._o(l),h=[],m=Le(s,t,e,n,r,i,o,!0,c);if(o&&c.fill)if(c.fillStyle==="solid"){let d=Object.assign({},c);d.disableMultiStroke=!0;let u=Le(s,t,e,n,r,i,!0,!1,d);u.type="fillPath",h.push(u)}else h.push((function(d,u,f,p,g,v,b){let x=d,w=u,S=Math.abs(f/2),M=Math.abs(p/2);S+=y(.01*S,b),M+=y(.01*M,b);let P=g,O=v;for(;P<0;)P+=2*Math.PI,O+=2*Math.PI;O-P>2*Math.PI&&(P=0,O=2*Math.PI);let Z=(O-P)/b.curveStepCount,C=[];for(let N=P;N<=O;N+=Z)C.push([x+S*Math.cos(N),w+M*Math.sin(N)]);return C.push([x+S*Math.cos(O),w+M*Math.sin(O)]),C.push([x,w]),R([C],b)})(s,t,e,n,r,i,c));return c.stroke!==L&&h.push(m),this._d("arc",h,c)}curve(s,t){let e=this._o(t),n=[],r=Te(s,e);if(e.fill&&e.fill!==L)if(e.fillStyle==="solid"){let i=Te(s,Object.assign(Object.assign({},e),{disableMultiStroke:!0,roughness:e.roughness?e.roughness+e.fillShapeRoughnessGain:0}));n.push({type:"fillPath",ops:this._mergedShape(i.ops)})}else{let i=[],o=s;if(o.length){let l=typeof o[0][0]=="number"?[o]:o;for(let c of l)c.length<3?i.push(...c):c.length===3?i.push(...he(Ae([c[0],c[0],c[1],c[2]]),10,(1+e.roughness)/2)):i.push(...he(Ae(c),10,(1+e.roughness)/2))}i.length&&n.push(R([i],e))}return e.stroke!==L&&n.push(r),this._d("curve",n,e)}polygon(s,t){let e=this._o(t),n=[],r=X(s,!0,e);return e.fill&&(e.fillStyle==="solid"?n.push(ce([s],e)):n.push(R([s],e))),e.stroke!==L&&n.push(r),this._d("polygon",n,e)}path(s,t){let e=this._o(t),n=[];if(!s)return this._d("path",n,e);s=(s||"").replace(/\n/g," ").replace(/(-\s)/g,"-").replace("/(ss)/g"," ");let r=e.fill&&e.fill!=="transparent"&&e.fill!==L,i=e.stroke!==L,o=!!(e.simplification&&e.simplification<1),l=(function(h,m,d){let u=Ne(He(Me(h))),f=[],p=[],g=[0,0],v=[],b=()=>{v.length>=4&&p.push(...he(v,m)),v=[]},x=()=>{b(),p.length&&(f.push(p),p=[])};for(let{key:S,data:M}of u)switch(S){case"M":x(),g=[M[0],M[1]],p.push(g);break;case"L":b(),p.push([M[0],M[1]]);break;case"C":if(!v.length){let P=p.length?p[p.length-1]:g;v.push([P[0],P[1]])}v.push([M[0],M[1]]),v.push([M[2],M[3]]),v.push([M[4],M[5]]);break;case"Z":b(),p.push([g[0],g[1]])}if(x(),!d)return f;let w=[];for(let S of f){let M=nt(S,d);M.length&&w.push(M)}return w})(s,1,o?4-4*(e.simplification||1):(1+e.roughness)/2),c=Pe(s,e);if(r)if(e.fillStyle==="solid")if(l.length===1){let h=Pe(s,Object.assign(Object.assign({},e),{disableMultiStroke:!0,roughness:e.roughness?e.roughness+e.fillShapeRoughnessGain:0}));n.push({type:"fillPath",ops:this._mergedShape(h.ops)})}else n.push(ce(l,e));else n.push(R(l,e));return i&&(o?l.forEach((h=>{n.push(X(h,!1,e))})):n.push(c)),this._d("path",n,e)}opsToPath(s,t){let e="";for(let n of s.ops){let r=typeof t=="number"&&t>=0?n.data.map((i=>+i.toFixed(t))):n.data;switch(n.op){case"move":e+=`M${r[0]} ${r[1]} `;break;case"bcurveTo":e+=`C${r[0]} ${r[1]}, ${r[2]} ${r[3]}, ${r[4]} ${r[5]} `;break;case"lineTo":e+=`L${r[0]} ${r[1]} `}}return e.trim()}toPaths(s){let t=s.sets||[],e=s.options||this.defaultOptions,n=[];for(let r of t){let i=null;switch(r.type){case"path":i={d:this.opsToPath(r),stroke:e.stroke,strokeWidth:e.strokeWidth,fill:L};break;case"fillPath":i={d:this.opsToPath(r),stroke:L,strokeWidth:0,fill:e.fill||L};break;case"fillSketch":i=this.fillSketch(r,e)}i&&n.push(i)}return n}fillSketch(s,t){let e=t.fillWeight;return e<0&&(e=t.strokeWidth/2),{d:this.opsToPath(s),stroke:t.fill||L,strokeWidth:e,fill:L}}_mergedShape(s){return s.filter(((t,e)=>e===0||t.op!=="move"))}},ye=class{constructor(s,t){this.canvas=s,this.ctx=this.canvas.getContext("2d"),this.gen=new W(t)}draw(s){let t=s.sets||[],e=s.options||this.getDefaultOptions(),n=this.ctx,r=s.options.fixedDecimalPlaceDigits;for(let i of t)switch(i.type){case"path":n.save(),n.strokeStyle=e.stroke==="none"?"transparent":e.stroke,n.lineWidth=e.strokeWidth,e.strokeLineDash&&n.setLineDash(e.strokeLineDash),e.strokeLineDashOffset&&(n.lineDashOffset=e.strokeLineDashOffset),this._drawToContext(n,i,r),n.restore();break;case"fillPath":{n.save(),n.fillStyle=e.fill||"";let o=s.shape==="curve"||s.shape==="polygon"||s.shape==="path"?"evenodd":"nonzero";this._drawToContext(n,i,r,o),n.restore();break}case"fillSketch":this.fillSketch(n,i,e)}}fillSketch(s,t,e){let n=e.fillWeight;n<0&&(n=e.strokeWidth/2),s.save(),e.fillLineDash&&s.setLineDash(e.fillLineDash),e.fillLineDashOffset&&(s.lineDashOffset=e.fillLineDashOffset),s.strokeStyle=e.fill||"",s.lineWidth=n,this._drawToContext(s,t,e.fixedDecimalPlaceDigits),s.restore()}_drawToContext(s,t,e,n="nonzero"){s.beginPath();for(let r of t.ops){let i=typeof e=="number"&&e>=0?r.data.map((o=>+o.toFixed(e))):r.data;switch(r.op){case"move":s.moveTo(i[0],i[1]);break;case"bcurveTo":s.bezierCurveTo(i[0],i[1],i[2],i[3],i[4],i[5]);break;case"lineTo":s.lineTo(i[0],i[1])}}t.type==="fillPath"?s.fill(n):s.stroke()}get generator(){return this.gen}getDefaultOptions(){return this.gen.defaultOptions}line(s,t,e,n,r){let i=this.gen.line(s,t,e,n,r);return this.draw(i),i}rectangle(s,t,e,n,r){let i=this.gen.rectangle(s,t,e,n,r);return this.draw(i),i}ellipse(s,t,e,n,r){let i=this.gen.ellipse(s,t,e,n,r);return this.draw(i),i}circle(s,t,e,n){let r=this.gen.circle(s,t,e,n);return this.draw(r),r}linearPath(s,t){let e=this.gen.linearPath(s,t);return this.draw(e),e}polygon(s,t){let e=this.gen.polygon(s,t);return this.draw(e),e}arc(s,t,e,n,r,i,o=!1,l){let c=this.gen.arc(s,t,e,n,r,i,o,l);return this.draw(c),c}curve(s,t){let e=this.gen.curve(s,t);return this.draw(e),e}path(s,t){let e=this.gen.path(s,t);return this.draw(e),e}},J="http://www.w3.org/2000/svg",ke=class{constructor(s,t){this.svg=s,this.gen=new W(t)}draw(s){let t=s.sets||[],e=s.options||this.getDefaultOptions(),n=this.svg.ownerDocument||window.document,r=n.createElementNS(J,"g"),i=s.options.fixedDecimalPlaceDigits;for(let o of t){let l=null;switch(o.type){case"path":l=n.createElementNS(J,"path"),l.setAttribute("d",this.opsToPath(o,i)),l.setAttribute("stroke",e.stroke),l.setAttribute("stroke-width",e.strokeWidth+""),l.setAttribute("fill","none"),e.strokeLineDash&&l.setAttribute("stroke-dasharray",e.strokeLineDash.join(" ").trim()),e.strokeLineDashOffset&&l.setAttribute("stroke-dashoffset",`${e.strokeLineDashOffset}`);break;case"fillPath":l=n.createElementNS(J,"path"),l.setAttribute("d",this.opsToPath(o,i)),l.setAttribute("stroke","none"),l.setAttribute("stroke-width","0"),l.setAttribute("fill",e.fill||""),s.shape!=="curve"&&s.shape!=="polygon"||l.setAttribute("fill-rule","evenodd");break;case"fillSketch":l=this.fillSketch(n,o,e)}l&&r.appendChild(l)}return r}fillSketch(s,t,e){let n=e.fillWeight;n<0&&(n=e.strokeWidth/2);let r=s.createElementNS(J,"path");return r.setAttribute("d",this.opsToPath(t,e.fixedDecimalPlaceDigits)),r.setAttribute("stroke",e.fill||""),r.setAttribute("stroke-width",n+""),r.setAttribute("fill","none"),e.fillLineDash&&r.setAttribute("stroke-dasharray",e.fillLineDash.join(" ").trim()),e.fillLineDashOffset&&r.setAttribute("stroke-dashoffset",`${e.fillLineDashOffset}`),r}get generator(){return this.gen}getDefaultOptions(){return this.gen.defaultOptions}opsToPath(s,t){return this.gen.opsToPath(s,t)}line(s,t,e,n,r){let i=this.gen.line(s,t,e,n,r);return this.draw(i)}rectangle(s,t,e,n,r){let i=this.gen.rectangle(s,t,e,n,r);return this.draw(i)}ellipse(s,t,e,n,r){let i=this.gen.ellipse(s,t,e,n,r);return this.draw(i)}circle(s,t,e,n){let r=this.gen.circle(s,t,e,n);return this.draw(r)}linearPath(s,t){let e=this.gen.linearPath(s,t);return this.draw(e)}polygon(s,t){let e=this.gen.polygon(s,t);return this.draw(e)}arc(s,t,e,n,r,i,o=!1,l){let c=this.gen.arc(s,t,e,n,r,i,o,l);return this.draw(c)}curve(s,t){let e=this.gen.curve(s,t);return this.draw(e)}path(s,t){let e=this.gen.path(s,t);return this.draw(e)}},Re={canvas:(a,s)=>new ye(a,s),svg:(a,s)=>new ke(a,s),generator:a=>new W(a),newSeed:()=>W.newSeed()};var _e="http://www.w3.org/2000/svg";function Be(a,s,t,e){if(!t.length)return;let n=Re.svg(a),r={seed:e.seed,stroke:"currentColor",strokeWidth:1.7,roughness:1.1,bowing:1.2},i=document.createElementNS(_e,"g");i.setAttribute("class",`mark mark-${s} tone-${e.tone}`);let o=t[0],l=t[t.length-1],c=Math.min(...t.map(p=>p.x)),h=Math.max(...t.map(p=>p.x+p.w)),m=o.y,d=l.y+l.h;switch(t.length>1&&s==="circle"?"bracket":s){case"circle":{let p=Math.min(14,6+o.w*.08);i.append(n.ellipse(o.x+o.w/2,o.y+o.h/2,o.w+p*2,o.h+12,{...r,roughness:.9,curveStepCount:14}));break}case"underline":for(let p of t){let g=p.y+p.h-1;i.append(n.line(p.x-1,g,p.x+p.w+1,g+.5,{...r,roughness:.8}))}break;case"strike":for(let p of t){let g=p.y+p.h/2;i.append(n.line(p.x-2,g,p.x+p.w+2,g,r))}break;case"box":i.append(n.rectangle(c-5,m-2,h-c+10,d-m+4,r));break;case"bracket":{let p=Math.max(2,e.gutter);if(i.append(n.linearPath([[p+7,m+2],[p,m+2],[p,d-2],[p+7,d-2]],{...r,roughness:.7})),s==="circle"||s==="bracket")for(let g of t)i.prepend(We(g));break}case"highlight":for(let p of t)i.append(We(p));break;case"arrow":{let p=o.y+o.h/2,g=o.x-6,v=Math.max(2,g-34);i.append(n.line(v,p,g,p,r)),i.append(n.linearPath([[g-7,p-5],[g,p],[g-7,p+5]],r));break}}a.append(i),e.animate&&rt(i)}function We(a){let s=document.createElementNS(_e,"rect");return s.setAttribute("x",String(a.x-3)),s.setAttribute("y",String(a.y+1)),s.setAttribute("width",String(a.w+6)),s.setAttribute("height",String(a.h-2)),s.setAttribute("rx","4"),s.setAttribute("class","mark-fill"),s}function rt(a){let s=Array.from(a.querySelectorAll("path")),t=320/Math.max(1,Math.min(s.length,2));s.forEach((e,n)=>{let r=e.getTotalLength?.()??0;r&&(e.style.strokeDasharray=`${r} ${r}`,e.style.strokeDashoffset=String(r),e.animate([{strokeDashoffset:r},{strokeDashoffset:0}],{duration:t,delay:40+n*t*.55,easing:"cubic-bezier(.3,.7,.4,1)",fill:"forwards"}))});for(let e of a.querySelectorAll(".mark-fill"))e.animate([{opacity:0},{opacity:1}],{duration:220,fill:"both"})}var ot="http://www.w3.org/2000/svg",re=class{constructor(s){k(this,"el");k(this,"content");k(this,"svg");k(this,"rows",[]);k(this,"segments",[]);k(this,"lines");k(this,"marks",[]);k(this,"seed",1);k(this,"activeRows",[]);k(this,"scrollTarget");k(this,"scrollTimer");this.lines=s.text.replace(/\n$/,"").split(`
|
|
2
|
+
`),this.el=document.createElement("div"),this.el.className="code-scroll",this.el.tabIndex=0,this.el.setAttribute("role","region"),this.el.setAttribute("aria-label",`Source code${s.path?`: ${s.path}`:""}`),this.content=document.createElement("div"),this.content.className="code",this.svg=document.createElementNS(ot,"svg"),this.svg.setAttribute("class","marks"),this.svg.setAttribute("aria-hidden","true"),this.renderLines(s),this.content.append(this.svg),this.el.append(this.content),new ResizeObserver(()=>this.redraw(!1)).observe(this.content)}renderLines(s){let t=new Map;for(let[n,r,i,o]of s.tokens??[]){let l=t.get(n)??[];l.push([r,i,o]),t.set(n,l)}let e=document.createDocumentFragment();this.lines.forEach((n,r)=>{let i=r+1,o=document.createElement("div");o.className="line";let l=document.createElement("span");l.className="ln",l.textContent=String(i+(s.firstLine??1)-1);let c=document.createElement("span");c.className="src";let h=Array.from(n),m=(t.get(i)??[]).sort((p,g)=>p[0]-g[0]),d=[],u=0,f=(p,g,v)=>{if(g<=p)return;let b=h.slice(p,g).join(""),x=document.createTextNode(b);if(v){let w=document.createElement("span");w.className=`tok-${v}`,w.append(x),c.append(w)}else c.append(x);d.push({node:x,cpStart:p,text:b})};for(let[p,g,v]of m)p<u||(f(u,p),f(p,Math.min(g,h.length),v),u=Math.min(g,h.length));f(u,h.length),h.length||c.append(document.createTextNode("\u200B")),o.append(l,c),e.append(o),this.rows.push(o),this.segments.push(d)}),this.content.append(e)}setFocus(s,t,e,n=[]){this.marks=s,this.seed=t;for(let r of this.activeRows)r.classList.remove("active","caller");this.activeRows=[];for(let r of n)this.flag(r,"caller");for(let r of s)for(let i=r.start[0];i<=r.end[0];i++)this.flag(i,"active");this.redraw(e),this.reveal()}flag(s,t){let e=this.rows[s-1];e&&(e.classList.add(t),this.activeRows.push(e))}redraw(s){if(this.svg.replaceChildren(),!this.content.isConnected)return;let t=(this.rows[0]?.querySelector(".src")?.offsetLeft??40)-12;this.marks.forEach((e,n)=>{Be(this.svg,e.mark,this.boxes(e),{seed:this.seed*31+n+1,animate:s,tone:e.tone??"accent",gutter:t})})}boxes(s){let t=this.content.getBoundingClientRect(),e=[];for(let n=s.start[0];n<=s.end[0];n++){let r=this.lines[n-1]??"",i=Array.from(r).length,o=i-Array.from(r.trimStart()).length,l=n===s.start[0]?s.start[1]:o,c=n===s.end[0]?s.end[1]:i,h=this.measure(n,l,Math.max(l,c));h&&e.push({x:h.left-t.left,y:h.top-t.top,w:h.width,h:h.height})}return e}measure(s,t,e){let n=this.segments[s-1],r=this.rows[s-1];if(!n||!r)return null;let i=r.querySelector(".src");if(!n.length||t===e){let p=i.getBoundingClientRect();return new DOMRect(p.left,p.top,Math.max(8,p.width),p.height)}let o=document.createRange(),l=p=>{for(let v of n){let b=Array.from(v.text).length;if(p<=v.cpStart+b)return[v.node,Array.from(v.text).slice(0,p-v.cpStart).join("").length]}let g=n[n.length-1];return[g.node,g.text.length]},[c,h]=l(t),[m,d]=l(e);o.setStart(c,h),o.setEnd(m,d);let u=o.getBoundingClientRect(),f=r.getBoundingClientRect();return new DOMRect(u.left,f.top,u.width,f.height)}reveal(){let s=this.marks[0];if(!s)return;let t=this.rows[s.start[0]-1],e=this.rows[s.end[0]-1]??t;if(!t||!e)return;let n=t.offsetTop,r=e.offsetTop+e.offsetHeight,i=this.el.clientHeight,o=this.scrollTarget??this.el.scrollTop,l=24;if(n>=o+l&&r<=o+i-l)return;let c=this.el.scrollHeight-i,h=Math.max(0,Math.min(c,n-(i-(r-n))/2)),m=matchMedia("(prefers-reduced-motion: reduce)").matches;this.scrollTarget=h,this.el.scrollTo({top:h,behavior:m?"auto":"smooth"}),clearTimeout(this.scrollTimer),this.scrollTimer=window.setTimeout(()=>{this.scrollTarget=void 0},600)}};var at="visset.trace/0.",j=class{constructor(s){k(this,"trace");k(this,"length");k(this,"outEnds",[]);k(this,"allOut");k(this,"versionMaps",new Map);k(this,"changeCache",new Map);if(!s||typeof s.schema!="string"||!s.schema.startsWith(at))throw new Error(`visset: unsupported trace (schema ${String(s?.schema)})`);this.trace=s,this.length=s.steps.length;let t="";for(let e of s.steps)t+=e.stdout??"",this.outEnds.push(t.length);this.allOut=t}step(s){let t=this.trace.steps[s];if(!t)throw new RangeError(`step ${s} out of range`);return t}output(s){let t=this.outEnds[s]??0,e=s>0?this.outEnds[s-1]??0:0;return{text:this.allOut.slice(0,t),freshFrom:e}}resolve(s,t){if(!("ref"in t))return;let e=this.versions(s).get(t.ref);return e?this.trace.objects[e]:void 0}versions(s){let t=this.versionMaps.get(s);if(!t){t=new Map;for(let e of this.step(s).heap)t.set(e.slice(0,e.lastIndexOf("@")),e);this.versionMaps.set(s,t)}return t}format(s,t,e=80){let n=this.fmt(s,t,0,new Set);return n.length>e?`${n.slice(0,e-1)}\u2026`:n}fmt(s,t,e,n){if("p"in t)return t.p;let r=this.resolve(s,t);if(!r)return"\u2026";if(n.has(t.ref))return r.shape==="sequence"?"[\u2026]":"{\u2026}";if(e>=3&&["sequence","mapping","set","record"].includes(r.shape))return"\u2026";n.add(t.ref);let o=h=>this.fmt(s,h,e+1,n),l=r.more?`, \u2026 ${r.more} more`:"",c;switch(r.shape){case"sequence":{let h=(r.items??[]).map(o).join(", ")+l;c=r.type==="tuple"?`(${h}${r.items?.length===1?",":""})`:`[${h}]`;break}case"set":c=(r.items?.length??0)===0?`${r.type}()`:`{${(r.items??[]).map(o).join(", ")}${l}}`;break;case"mapping":c=`{${(r.entries??[]).map(([h,m])=>`${o(h)}: ${o(m)}`).join(", ")}${l}}`;break;case"record":c=`${r.type}(${(r.fields??[]).map(([h,m])=>`${h}=${o(m)}`).join(", ")})`;break;case"function":c=`\u0192 ${r.repr??r.type}`;break;case"class":c=`class ${r.repr??r.type}`;break;default:c=r.repr??r.type}return n.delete(t.ref),c}changes(s){let t=this.changeCache.get(s);if(t)return t;let e=this.step(s),n;return e.changes?n=new Set(e.changes):s===0?n=new Set(e.stack.flatMap(r=>r.locals.map(([i])=>`${r.id}.${i}`))):n=this.diff(s-1,s),this.changeCache.set(s,n),n}diff(s,t){let e=new Set,n=new Map(this.step(s).stack.map(r=>[r.id,r]));for(let r of this.step(t).stack){let i=n.get(r.id);i||e.add(r.id);let o=new Map(i?.locals??[]);for(let[l,c]of r.locals){let h=o.get(l);(!h||!it(h,c)||this.signature(s,c)!==this.signature(t,c))&&e.add(`${r.id}.${l}`)}(r.returnValue||r.yieldValue)&&e.add(`${r.id}:return`)}return e}signature(s,t){if(!("ref"in t))return"";let e=this.versions(s),n=[],r=[t.ref],i=new Set;for(;r.length&&i.size<200;){let o=r.pop();if(i.has(o))continue;i.add(o);let l=e.get(o);n.push(l??o);let c=l?this.trace.objects[l]:void 0;if(c)for(let h of lt(c))r.push(h)}return n.sort().join("|")}};function it(a,s){return"ref"in a||"ref"in s?"ref"in a&&"ref"in s&&a.ref===s.ref:a.p===s.p&&a.t===s.t}function lt(a){let s=[],t=e=>{"ref"in e&&s.push(e.ref)};for(let e of a.items??[])t(e);for(let[e,n]of a.entries??[])t(e),t(n);for(let[,e]of a.fields??[])t(e);return s}function Fe(a){let s=[],t=/`([^`]+)`/g,e=0;for(let n=t.exec(a);n;n=t.exec(a))n.index>e&&s.push({code:!1,text:a.slice(e,n.index)}),s.push({code:!0,text:n[1]??""}),e=n.index+n[0].length;return e<a.length&&s.push({code:!1,text:a.slice(e)}),s}var oe=class{constructor(){k(this,"el");k(this,"framesEl");k(this,"outputEl");k(this,"frameEls",new Map);this.el=document.createElement("div"),this.el.className="state";let s=document.createElement("div");s.className="pane-head",s.textContent="Frames",this.framesEl=document.createElement("div"),this.framesEl.className="frames",this.framesEl.setAttribute("role","list"),this.framesEl.setAttribute("aria-label","Call stack");let t=document.createElement("div");t.className="console";let e=document.createElement("div");e.className="pane-head",e.textContent="Output",this.outputEl=document.createElement("pre"),this.outputEl.setAttribute("aria-label","Program output"),t.append(e,this.outputEl),this.el.append(s,this.framesEl,t)}render(s,t,e){let n=s.step(t),r=s.changes(t),i=new Set;n.stack.forEach((l,c)=>{i.add(l.id);let h=this.frameEl(l,r.has(l.id)&&e);h.classList.toggle("top",c===n.stack.length-1),this.fillFrame(h,l,s,t,r,e),this.framesEl.children[c]!==h&&this.framesEl.insertBefore(h,this.framesEl.children[c]??null)});for(let[l,c]of this.frameEls)i.has(l)||(c.remove(),this.frameEls.delete(l));n.stack.length?this.framesEl.querySelector(":scope > .empty")?.remove():(this.framesEl.innerHTML='<p class="empty">No frames: the program has not started.</p>',this.frameEls.clear());let o=this.framesEl.lastElementChild;o&&o.offsetTop+o.offsetHeight>this.framesEl.scrollTop+this.framesEl.clientHeight&&(this.framesEl.scrollTop=o.offsetTop+o.offsetHeight-this.framesEl.clientHeight),this.renderOutput(s,t)}frameEl(s,t){let e=this.frameEls.get(s.id);return e||(e=document.createElement("section"),e.className="frame",e.setAttribute("role","listitem"),e.innerHTML='<header class="frame-head"><span class="frame-name"></span><span class="frame-ret"></span></header><div class="vars"></div>',t&&e.classList.add("entering"),this.frameEls.set(s.id,e)),e}fillFrame(s,t,e,n,r,i){s.querySelector(".frame-name").textContent=t.name,s.setAttribute("aria-label",`Frame ${t.name}`);let o=s.querySelector(".frame-ret"),l=t.returnValue??t.yieldValue;o.textContent=l?`${t.returnValue?"returns":"yields"} ${e.format(n,l,40)}`:"",o.classList.toggle("changed",!!l);let c=s.querySelector(".vars");if(!t.locals.length){c.innerHTML='<span class="empty">no variables yet</span>';return}c.querySelector(":scope > .empty")?.remove();let h=new Map;for(let u of c.querySelectorAll(".var-name"))h.set(u.dataset.name,[u,u.nextElementSibling]);let m=new Set,d=c.firstElementChild;for(let[u,f]of t.locals){m.add(u);let p=h.get(u);if(!p){let b=document.createElement("span");b.className="var-name",b.dataset.name=u,b.textContent=u;let x=document.createElement("span");x.className="var-value",p=[b,x]}let[g,v]=p;d!==g&&c.insertBefore(g,d),c.insertBefore(v,g.nextSibling),d=v.nextElementSibling,this.setValue(v,f,e,n,r.has(`${t.id}.${u}`),i)}for(let[u,[f,p]]of h)m.has(u)||(f.remove(),p.remove())}setValue(s,t,e,n,r,i){s.textContent=e.format(n,t),s.dataset.type="p"in t?t.t:e.resolve(n,t)?.shape??"ref",s.title="p"in t?t.t:e.resolve(n,t)?.type??"",s.classList.remove("changed","pulse"),r&&(s.classList.add("changed"),i&&(s.offsetWidth,s.classList.add("pulse")))}renderOutput(s,t){let{text:e,freshFrom:n}=s.output(t);if(this.outputEl.replaceChildren(),!e){let r=document.createElement("span");r.className="empty",r.textContent="Nothing printed yet.",this.outputEl.append(r);return}if(this.outputEl.append(document.createTextNode(e.slice(0,n))),n<e.length){let r=document.createElement("mark");r.className="fresh",r.textContent=e.slice(n),this.outputEl.append(r)}this.outputEl.scrollTop=this.outputEl.scrollHeight}};var je=`
|
|
3
|
+
:host {
|
|
4
|
+
--font-ui: -apple-system, BlinkMacSystemFont, "SF Pro Text", "Inter", "Segoe UI", Roboto, system-ui, sans-serif;
|
|
5
|
+
--font-mono: ui-monospace, "SF Mono", "JetBrains Mono", "Cascadia Code", Menlo, Consolas, monospace;
|
|
6
|
+
--accent: #5e5ce6;
|
|
7
|
+
--accent-soft: rgba(94, 92, 230, 0.11);
|
|
8
|
+
--accent-strong: rgba(94, 92, 230, 0.28);
|
|
9
|
+
--on-accent: #fff;
|
|
10
|
+
--error: #d70015;
|
|
11
|
+
--error-soft: rgba(215, 0, 21, 0.09);
|
|
12
|
+
--surface: #ffffff;
|
|
13
|
+
--panel: #fbfbfd;
|
|
14
|
+
--frame: #ffffff;
|
|
15
|
+
--text: #1d1d1f;
|
|
16
|
+
--muted: #6e6e73;
|
|
17
|
+
--faint: #a1a1a6;
|
|
18
|
+
--line: rgba(0, 0, 0, 0.08);
|
|
19
|
+
--active-line: rgba(94, 92, 230, 0.06);
|
|
20
|
+
--caller-line: rgba(0, 0, 0, 0.03);
|
|
21
|
+
--code-bg: rgba(0, 0, 0, 0.05);
|
|
22
|
+
--track: rgba(0, 0, 0, 0.1);
|
|
23
|
+
--shadow: 0 1px 2px rgba(0, 0, 0, 0.04), 0 12px 40px rgba(0, 0, 0, 0.07);
|
|
24
|
+
--syn-keyword: #9b2393;
|
|
25
|
+
--syn-string: #1d7a4f;
|
|
26
|
+
--syn-number: #8a5100;
|
|
27
|
+
--syn-comment: #6e6e73;
|
|
28
|
+
--syn-builtin: #2c5d8f;
|
|
29
|
+
--syn-def: #1d1d1f;
|
|
30
|
+
display: block;
|
|
31
|
+
height: 600px;
|
|
32
|
+
min-height: 420px;
|
|
33
|
+
container-type: inline-size;
|
|
34
|
+
color-scheme: light;
|
|
35
|
+
-webkit-font-smoothing: antialiased;
|
|
36
|
+
}
|
|
37
|
+
:host([hidden]) { display: none; }
|
|
38
|
+
${qe(':host([theme="dark"])')}
|
|
39
|
+
@media (prefers-color-scheme: dark) {
|
|
40
|
+
${qe(':host(:not([theme="light"]))')}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
* { box-sizing: border-box; }
|
|
44
|
+
.player {
|
|
45
|
+
height: 100%;
|
|
46
|
+
display: grid;
|
|
47
|
+
grid-template-rows: auto minmax(0, 1fr) auto auto;
|
|
48
|
+
background: var(--surface);
|
|
49
|
+
color: var(--text);
|
|
50
|
+
font: 14px/1.45 var(--font-ui);
|
|
51
|
+
border: 1px solid var(--line);
|
|
52
|
+
border-radius: 18px;
|
|
53
|
+
box-shadow: var(--shadow);
|
|
54
|
+
overflow: hidden;
|
|
55
|
+
}
|
|
56
|
+
.player:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
|
|
57
|
+
|
|
58
|
+
/* header */
|
|
59
|
+
.bar { display: flex; align-items: baseline; gap: 12px; padding: 16px 22px 12px; min-width: 0; }
|
|
60
|
+
.title { font-weight: 600; font-size: 15px; letter-spacing: -0.01em; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
|
61
|
+
.meta { margin-left: auto; font-size: 12px; color: var(--muted); white-space: nowrap; }
|
|
62
|
+
|
|
63
|
+
/* panes */
|
|
64
|
+
.panes { display: grid; grid-template-columns: minmax(0, 1.3fr) minmax(0, 1fr); gap: 12px; padding: 0 14px; min-height: 0; }
|
|
65
|
+
.pane { background: var(--panel); border: 1px solid var(--line); border-radius: 12px; min-height: 0; display: flex; flex-direction: column; overflow: hidden; }
|
|
66
|
+
.pane-head { flex: none; font-size: 11px; font-weight: 600; letter-spacing: 0.06em; text-transform: uppercase; color: var(--muted); padding: 11px 14px 5px; }
|
|
67
|
+
|
|
68
|
+
/* code */
|
|
69
|
+
.code-scroll { flex: 1; min-height: 0; overflow: auto; overscroll-behavior: contain; }
|
|
70
|
+
.code-scroll:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; border-radius: 0 0 12px 12px; }
|
|
71
|
+
.code { position: relative; min-width: max-content; padding: 4px 0 28px; font: 13.5px/1.8 var(--font-mono); font-variant-ligatures: none; }
|
|
72
|
+
.line { display: flex; padding-right: 32px; transition: background-color 0.25s ease; }
|
|
73
|
+
.ln { flex: none; width: 3.4em; padding-right: 1.3em; text-align: right; color: var(--faint); user-select: none; }
|
|
74
|
+
.src { white-space: pre; }
|
|
75
|
+
.line.caller { background: var(--caller-line); }
|
|
76
|
+
.line.active { background: var(--active-line); }
|
|
77
|
+
.line.active .ln { color: var(--text); }
|
|
78
|
+
.marks { position: absolute; inset: 0; width: 100%; height: 100%; overflow: visible; pointer-events: none; color: var(--accent); }
|
|
79
|
+
.marks .tone-error { color: var(--error); }
|
|
80
|
+
.mark-fill { fill: var(--accent-soft); stroke: none; }
|
|
81
|
+
.tone-error .mark-fill { fill: var(--error-soft); }
|
|
82
|
+
.tok-keyword { color: var(--syn-keyword); }
|
|
83
|
+
.tok-constant { color: var(--syn-keyword); }
|
|
84
|
+
.tok-string { color: var(--syn-string); }
|
|
85
|
+
.tok-number { color: var(--syn-number); }
|
|
86
|
+
.tok-comment { color: var(--syn-comment); font-style: italic; }
|
|
87
|
+
.tok-builtin { color: var(--syn-builtin); }
|
|
88
|
+
.tok-definition { color: var(--syn-def); font-weight: 600; }
|
|
89
|
+
|
|
90
|
+
/* state */
|
|
91
|
+
.state { display: flex; flex-direction: column; height: 100%; min-height: 0; }
|
|
92
|
+
.frames { position: relative; flex: 1 1 auto; min-height: 0; overflow: auto; overscroll-behavior: contain; display: flex; flex-direction: column; gap: 8px; padding: 2px 12px 12px; }
|
|
93
|
+
.frame { flex: none; background: var(--frame); border: 1px solid var(--line); border-radius: 10px; padding: 8px 12px 9px; transition: border-color 0.25s ease, box-shadow 0.25s ease; }
|
|
94
|
+
.frame.top { border-color: var(--accent-strong); box-shadow: inset 3px 0 0 var(--accent); }
|
|
95
|
+
.frame.entering { animation: vs-enter 0.32s cubic-bezier(.2,.8,.3,1); }
|
|
96
|
+
.frame-head { display: flex; align-items: baseline; gap: 10px; margin-bottom: 4px; font-size: 12.5px; }
|
|
97
|
+
.frame-name { font-weight: 600; font-family: var(--font-mono); }
|
|
98
|
+
.frame-ret { margin-left: auto; font: 12px var(--font-mono); color: var(--accent); }
|
|
99
|
+
.vars { display: grid; grid-template-columns: max-content minmax(0, 1fr); column-gap: 16px; row-gap: 2px; font: 13px/1.6 var(--font-mono); }
|
|
100
|
+
.var-name { color: var(--muted); }
|
|
101
|
+
.var-value { min-width: 0; overflow-wrap: anywhere; border-radius: 5px; margin: 0 -5px; padding: 0 5px; transition: background-color 0.4s ease; }
|
|
102
|
+
.var-value[data-type="str"] { color: var(--syn-string); }
|
|
103
|
+
.var-value.changed { background: var(--accent-soft); }
|
|
104
|
+
.var-value.pulse { animation: vs-pulse 0.9s ease-out; }
|
|
105
|
+
.empty { color: var(--faint); font: 12.5px var(--font-ui); }
|
|
106
|
+
.console { flex: 0 0 auto; max-height: 36%; display: flex; flex-direction: column; border-top: 1px solid var(--line); }
|
|
107
|
+
.console pre { margin: 0; padding: 0 14px 12px; min-height: 2.4em; overflow: auto; font: 12.5px/1.6 var(--font-mono); white-space: pre-wrap; overflow-wrap: anywhere; }
|
|
108
|
+
.console mark.fresh { background: var(--accent-soft); color: inherit; border-radius: 3px; }
|
|
109
|
+
|
|
110
|
+
/* caption */
|
|
111
|
+
.caption-row { padding: 16px 22px 4px; min-height: 0; }
|
|
112
|
+
.caption { margin: 0; min-height: 2.9em; font-size: 15.5px; line-height: 1.45; letter-spacing: -0.005em; text-wrap: pretty; }
|
|
113
|
+
.caption.enter { animation: vs-fade 0.22s ease-out; }
|
|
114
|
+
.caption code { font: 0.88em var(--font-mono); background: var(--code-bg); padding: 1px 5px; border-radius: 5px; }
|
|
115
|
+
.caption.error code { color: var(--error); background: var(--error-soft); }
|
|
116
|
+
.notice { margin: 8px 0 0; padding: 7px 11px; font-size: 12.5px; color: var(--muted); background: var(--code-bg); border-radius: 8px; }
|
|
117
|
+
.notice.error { color: var(--error); background: var(--error-soft); }
|
|
118
|
+
|
|
119
|
+
/* controls */
|
|
120
|
+
.controls { display: flex; align-items: center; gap: 6px; padding: 8px 16px 16px; }
|
|
121
|
+
button, select { font: inherit; color: inherit; }
|
|
122
|
+
.btn { display: inline-grid; place-items: center; width: 34px; height: 34px; padding: 0; border: 0; border-radius: 50%; background: transparent; color: var(--text); cursor: pointer; transition: background-color 0.15s ease, transform 0.1s ease; }
|
|
123
|
+
.btn:hover { background: var(--code-bg); }
|
|
124
|
+
.btn:active { transform: scale(0.94); }
|
|
125
|
+
.btn:disabled { color: var(--faint); cursor: default; background: transparent; transform: none; }
|
|
126
|
+
.btn svg { width: 16px; height: 16px; fill: currentColor; }
|
|
127
|
+
.btn.play { width: 40px; height: 40px; background: var(--accent); color: var(--on-accent); }
|
|
128
|
+
.btn.play:hover { filter: brightness(1.08); background: var(--accent); }
|
|
129
|
+
.btn:focus-visible, .speed:focus-visible, .slider:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
|
|
130
|
+
.slider { flex: 1; min-width: 60px; margin: 0 8px; height: 22px; background: transparent; appearance: none; -webkit-appearance: none; cursor: pointer; --pct: 0%; }
|
|
131
|
+
.slider::-webkit-slider-runnable-track { height: 4px; border-radius: 2px; background: linear-gradient(to right, var(--accent) var(--pct), var(--track) var(--pct)); }
|
|
132
|
+
.slider::-moz-range-track { height: 4px; border-radius: 2px; background: linear-gradient(to right, var(--accent) var(--pct), var(--track) var(--pct)); }
|
|
133
|
+
.slider::-webkit-slider-thumb { -webkit-appearance: none; width: 16px; height: 16px; margin-top: -6px; border-radius: 50%; background: #fff; border: 0; box-shadow: 0 0 0 0.5px rgba(0,0,0,0.12), 0 1px 4px rgba(0,0,0,0.25); }
|
|
134
|
+
.slider::-moz-range-thumb { width: 16px; height: 16px; border-radius: 50%; background: #fff; border: 0; box-shadow: 0 0 0 0.5px rgba(0,0,0,0.12), 0 1px 4px rgba(0,0,0,0.25); }
|
|
135
|
+
.counter { min-width: 6.5em; text-align: right; font-size: 12.5px; color: var(--muted); font-variant-numeric: tabular-nums; }
|
|
136
|
+
.speed { height: 28px; padding: 0 6px; border: 1px solid var(--line); border-radius: 8px; background: var(--surface); font-size: 12.5px; color: var(--muted); cursor: pointer; }
|
|
137
|
+
|
|
138
|
+
.sr-only { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap; }
|
|
139
|
+
|
|
140
|
+
@container (max-width: 720px) {
|
|
141
|
+
.panes { grid-template-columns: minmax(0, 1fr); grid-template-rows: minmax(0, 1.25fr) minmax(0, 1fr); }
|
|
142
|
+
.bar { padding: 14px 16px 10px; }
|
|
143
|
+
.caption-row { padding: 12px 16px 2px; }
|
|
144
|
+
.caption { font-size: 15px; }
|
|
145
|
+
.controls { padding: 6px 10px 12px; }
|
|
146
|
+
.counter { min-width: 0; }
|
|
147
|
+
.speed { display: none; }
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
@keyframes vs-pulse { 0% { background: var(--accent-strong); } 100% { background: var(--accent-soft); } }
|
|
151
|
+
@keyframes vs-enter { from { opacity: 0; transform: translateY(-6px); } to { opacity: 1; transform: none; } }
|
|
152
|
+
@keyframes vs-fade { from { opacity: 0.2; transform: translateY(2px); } to { opacity: 1; transform: none; } }
|
|
153
|
+
|
|
154
|
+
@media (prefers-reduced-motion: reduce) {
|
|
155
|
+
*, *::before, *::after { animation: none !important; transition: none !important; }
|
|
156
|
+
}
|
|
157
|
+
`;function qe(a){return`${a} {
|
|
158
|
+
--accent: #8b88ff;
|
|
159
|
+
--accent-soft: rgba(139, 136, 255, 0.16);
|
|
160
|
+
--accent-strong: rgba(139, 136, 255, 0.36);
|
|
161
|
+
--on-accent: #0b0b14;
|
|
162
|
+
--error: #ff6961;
|
|
163
|
+
--error-soft: rgba(255, 105, 97, 0.14);
|
|
164
|
+
--surface: #161618;
|
|
165
|
+
--panel: #1c1c1e;
|
|
166
|
+
--frame: #232326;
|
|
167
|
+
--text: #f5f5f7;
|
|
168
|
+
--muted: #a1a1a6;
|
|
169
|
+
--faint: #6e6e73;
|
|
170
|
+
--line: rgba(255, 255, 255, 0.09);
|
|
171
|
+
--active-line: rgba(139, 136, 255, 0.1);
|
|
172
|
+
--caller-line: rgba(255, 255, 255, 0.035);
|
|
173
|
+
--code-bg: rgba(255, 255, 255, 0.08);
|
|
174
|
+
--track: rgba(255, 255, 255, 0.16);
|
|
175
|
+
--shadow: 0 1px 2px rgba(0, 0, 0, 0.3), 0 12px 40px rgba(0, 0, 0, 0.4);
|
|
176
|
+
--syn-keyword: #ff7ab2;
|
|
177
|
+
--syn-string: #7ed4a0;
|
|
178
|
+
--syn-number: #e5b567;
|
|
179
|
+
--syn-comment: #8e8e93;
|
|
180
|
+
--syn-builtin: #7cc0f5;
|
|
181
|
+
--syn-def: #f5f5f7;
|
|
182
|
+
color-scheme: dark;
|
|
183
|
+
}`}var V=[.5,1,1.5,2,3],ct=1300,G={prev:'<svg viewBox="0 0 16 16" aria-hidden="true"><path d="M10.3 2.3a1 1 0 0 1 0 1.4L6 8l4.3 4.3a1 1 0 1 1-1.4 1.4l-5-5a1 1 0 0 1 0-1.4l5-5a1 1 0 0 1 1.4 0Z"/></svg>',next:'<svg viewBox="0 0 16 16" aria-hidden="true"><path d="M5.7 2.3a1 1 0 0 0 0 1.4L10 8l-4.3 4.3a1 1 0 1 0 1.4 1.4l5-5a1 1 0 0 0 0-1.4l-5-5a1 1 0 0 0-1.4 0Z"/></svg>',play:'<svg viewBox="0 0 16 16" aria-hidden="true"><path d="M5 2.9v10.2a.9.9 0 0 0 1.4.76l7.6-5.1a.9.9 0 0 0 0-1.5L6.4 2.1A.9.9 0 0 0 5 2.9Z"/></svg>',pause:'<svg viewBox="0 0 16 16" aria-hidden="true"><rect x="3.5" y="2.5" width="3.2" height="11" rx="1"/><rect x="9.3" y="2.5" width="3.2" height="11" rx="1"/></svg>'},ht=`
|
|
184
|
+
<div class="player" part="player">
|
|
185
|
+
<header class="bar"><span class="title"></span><span class="meta"></span></header>
|
|
186
|
+
<div class="panes">
|
|
187
|
+
<section class="pane code-pane" aria-label="Code"><div class="pane-head file"></div></section>
|
|
188
|
+
<section class="pane state-pane" aria-label="Program state"></section>
|
|
189
|
+
</div>
|
|
190
|
+
<div class="caption-row">
|
|
191
|
+
<p class="caption" aria-live="polite"></p>
|
|
192
|
+
<p class="notice" hidden></p>
|
|
193
|
+
</div>
|
|
194
|
+
<nav class="controls" aria-label="Walkthrough controls">
|
|
195
|
+
<button class="btn prev" type="button" aria-label="Previous step (Left arrow)" title="Previous (\u2190)">${G.prev}</button>
|
|
196
|
+
<button class="btn play" type="button" aria-label="Play (Space)" title="Play (Space)">${G.play}</button>
|
|
197
|
+
<button class="btn next" type="button" aria-label="Next step (Right arrow)" title="Next (\u2192)">${G.next}</button>
|
|
198
|
+
<input class="slider" type="range" min="0" max="0" value="0" step="1" aria-label="Step">
|
|
199
|
+
<span class="counter" aria-hidden="true"></span>
|
|
200
|
+
<select class="speed" aria-label="Playback speed" title="Speed ([ and ])">
|
|
201
|
+
${V.map(a=>`<option value="${a}"${a===1?" selected":""}>${a}\xD7</option>`).join("")}
|
|
202
|
+
</select>
|
|
203
|
+
</nav>
|
|
204
|
+
</div>`,K=class extends HTMLElement{constructor(){super();k(this,"model",null);k(this,"idx",0);k(this,"timer");k(this,"speed",1);k(this,"codeView",null);k(this,"stateView",new oe);k(this,"root");k(this,"els");k(this,"onWindowKey",t=>this.handleKey(t));this.root=this.attachShadow({mode:"open"}),this.root.innerHTML=`<style>${je}</style>${ht}`;let t=e=>this.root.querySelector(e);this.els={title:t(".title"),meta:t(".meta"),file:t(".file"),codePane:t(".code-pane"),caption:t(".caption"),notice:t(".notice"),prev:t(".prev"),next:t(".next"),play:t(".play"),slider:t(".slider"),counter:t(".counter"),speed:t(".speed")},t(".state-pane").append(this.stateView.el),this.els.prev.addEventListener("click",()=>this.prev()),this.els.next.addEventListener("click",()=>this.next()),this.els.play.addEventListener("click",()=>this.toggle()),this.els.slider.addEventListener("input",()=>{this.pause(),this.goTo(Number(this.els.slider.value))}),this.els.speed.addEventListener("change",()=>this.setSpeed(Number(this.els.speed.value))),this.addEventListener("keydown",e=>{this.hasAttribute("global-keys")||this.handleKey(e)})}connectedCallback(){if(this.hasAttribute("tabindex")||(this.tabIndex=0),this.hasAttribute("global-keys")&&window.addEventListener("keydown",this.onWindowKey),!this.model){let t=this.querySelector('script[type="application/json"]');t?.textContent?this.trace=JSON.parse(t.textContent):this.getAttribute("src")&&this.load(this.getAttribute("src"))}}disconnectedCallback(){this.pause(),window.removeEventListener("keydown",this.onWindowKey)}attributeChangedCallback(t,e,n){e!==n&&(t==="src"&&n&&this.isConnected&&this.load(n),t==="step"&&n!==null&&this.goTo(Number(n)),t==="speed"&&n!==null&&this.setSpeed(Number(n)))}get trace(){return this.model?.trace??null}set trace(t){if(this.pause(),!!t){try{this.model=new j(t)}catch(e){this.showError(String(e instanceof Error?e.message:e));return}this.mount()}}get index(){return this.idx}get length(){return this.model?.length??0}goTo(t,e=!0){let n=this.model;if(!n?.length||Number.isNaN(t))return;let r=Math.max(0,Math.min(n.length-1,Math.trunc(t))),i=r!==this.idx;this.idx=r,this.render(e&&i&&!pt()),i&&this.dispatchEvent(new CustomEvent("stepchange",{detail:{index:r,step:n.step(r)}}))}next(){this.goTo(this.idx+1)}prev(){this.pause(),this.goTo(this.idx-1)}play(){!this.model||this.timer!==void 0||(this.idx>=this.model.length-1&&this.goTo(0),this.timer=window.setInterval(()=>{!this.model||this.idx>=this.model.length-1?this.pause():this.next()},ct/this.speed),this.syncPlayButton())}pause(){this.timer!==void 0&&window.clearInterval(this.timer),this.timer=void 0,this.syncPlayButton()}toggle(){this.timer===void 0?this.play():this.pause()}setSpeed(t){if(!Number.isFinite(t)||t<=0)return;this.speed=t;let e=V.includes(t)?String(t):"";e&&(this.els.speed.value=e),this.timer!==void 0&&(this.pause(),this.play())}async load(t){try{let e=await fetch(t);if(!e.ok)throw new Error(`HTTP ${e.status}`);this.trace=await e.json()}catch(e){this.showError(`Could not load ${t}: ${e instanceof Error?e.message:e}`)}}mount(){let t=this.model,{meta:e,sources:n}=t.trace,r=n[0];this.els.title.textContent=e.title??r.path??"Walkthrough";let i=e.language?e.language[0].toUpperCase()+e.language.slice(1):"";this.els.meta.textContent=[i,`${t.length.toLocaleString()} step${t.length===1?"":"s"}`].filter(Boolean).join(" \xB7 ");let o=this.els.title.textContent;this.els.file.textContent=r.path&&r.path!==o?r.path:"Code",this.codeView?.el.remove(),this.codeView=new re(r),this.els.codePane.append(this.codeView.el),this.els.slider.max=String(Math.max(0,t.length-1)),this.idx=Math.max(0,Math.min(t.length-1,Number(this.getAttribute("step")??0)||0));let l=Number(this.getAttribute("speed"));l>0&&this.setSpeed(l),requestAnimationFrame(()=>this.render(!1))}render(t){let e=this.model;if(!e?.length){this.els.caption.textContent="This trace has no steps.";return}let n=this.idx,r=e.step(n),i=r.stack.slice(0,-1).map(c=>c.line??0).filter(c=>c>0);this.codeView?.setFocus(r.focus,n+1,t,i),this.stateView.render(e,n,t);let o=this.els.caption;o.replaceChildren(...Fe(r.caption??"").map(c=>{if(!c.code)return document.createTextNode(c.text);let h=document.createElement("code");return h.textContent=c.text,h})),o.classList.toggle("error",r.kind==="exception"),t&&(o.classList.remove("enter"),o.offsetWidth,o.classList.add("enter")),this.renderNotice(n===e.length-1);let l=e.length;this.els.slider.value=String(n),this.els.slider.style.setProperty("--pct",l>1?`${n/(l-1)*100}%`:"100%"),this.els.slider.setAttribute("aria-valuetext",`Step ${n+1} of ${l}: ${r.caption??""}`),this.els.counter.textContent=`${(n+1).toLocaleString()} / ${l.toLocaleString()}`,this.els.prev.disabled=n===0,this.els.next.disabled=n===l-1}renderNotice(t){let{truncated:e,error:n}=this.model.trace.meta,r=this.els.notice,i="";if(t&&n){let o=n.line?` on line ${n.line}`:"";i=`The program stopped with ${n.type}${o}: ${n.message}`}else if(t&&e){let o=e.limit??0;i=e.reason==="maxSteps"?`Stopped after ${o.toLocaleString()} steps: the program may loop forever.`:e.reason==="timeout"?`Stopped after ${o/1e3}s: the program took too long.`:e.reason==="maxStack"?`Stopped at ${o} nested calls: the recursion may never end.`:"Stopped early."}r.textContent=i,r.hidden=!i,r.classList.toggle("error",!!(t&&n))}showError(t){this.els.caption.textContent=t,this.els.caption.classList.add("error")}syncPlayButton(){let t=this.timer!==void 0;this.els.play.innerHTML=t?G.pause:G.play,this.els.play.setAttribute("aria-label",t?"Pause (Space)":"Play (Space)"),this.els.play.title=t?"Pause (Space)":"Play (Space)"}handleKey(t){if(t.defaultPrevented||t.altKey||t.ctrlKey||t.metaKey)return;let e=t.composedPath()[0];if(e instanceof HTMLElement&&e.closest("input:not(.slider), select, textarea, [contenteditable]"))return;let n=this.model;if(!n)return;let i={ArrowRight:()=>{this.pause(),this.next()},ArrowLeft:()=>this.prev()," ":()=>this.toggle(),Home:()=>{this.pause(),this.goTo(0)},End:()=>{this.pause(),this.goTo(n.length-1)},"[":()=>this.setSpeed(V[Math.max(0,V.indexOf(this.speed)-1)]??1),"]":()=>this.setSpeed(V[Math.min(V.length-1,V.indexOf(this.speed)+1)]??1)}[t.key];i&&(t.preventDefault(),i())}};k(K,"observedAttributes",["src","step","speed"]);function pt(){return matchMedia("(prefers-reduced-motion: reduce)").matches}typeof customElements<"u"&&!customElements.get("visset-player")&&customElements.define("visset-player",K);})();
|
visset/_version.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.1.0"
|
visset/cli.py
ADDED
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
"""`visset` command line. Imports are lazy so `visset --help` starts instantly."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import argparse
|
|
6
|
+
import json
|
|
7
|
+
import sys
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
from typing import Any
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def _add_trace_args(p: argparse.ArgumentParser) -> None:
|
|
13
|
+
p.add_argument("file", help="Python file to trace")
|
|
14
|
+
p.add_argument(
|
|
15
|
+
"--fn",
|
|
16
|
+
metavar="NAME",
|
|
17
|
+
help="trace one call of this function (or Class.method); shows only its source",
|
|
18
|
+
)
|
|
19
|
+
p.add_argument(
|
|
20
|
+
"--lines",
|
|
21
|
+
metavar="A-B",
|
|
22
|
+
help="show only these lines, e.g. 4-9 (part of a function); the program still runs",
|
|
23
|
+
)
|
|
24
|
+
p.add_argument(
|
|
25
|
+
"--given",
|
|
26
|
+
default="",
|
|
27
|
+
metavar="INPUTS",
|
|
28
|
+
help='inputs for a snippet, e.g. "arr=[1, 3, 5], target=3"',
|
|
29
|
+
)
|
|
30
|
+
p.add_argument(
|
|
31
|
+
"--args",
|
|
32
|
+
default="",
|
|
33
|
+
metavar="ARGS",
|
|
34
|
+
help='arguments for --fn as Python literals, e.g. "[1, 3, 5], 3"',
|
|
35
|
+
)
|
|
36
|
+
p.add_argument(
|
|
37
|
+
"--stdin", default=None, metavar="TEXT", help="text the program reads via input()"
|
|
38
|
+
)
|
|
39
|
+
p.add_argument("--max-steps", type=int, default=10_000, metavar="N")
|
|
40
|
+
p.add_argument("--timeout", type=float, default=5.0, metavar="SECONDS")
|
|
41
|
+
p.add_argument(
|
|
42
|
+
"--no-sandbox",
|
|
43
|
+
action="store_true",
|
|
44
|
+
help="run in this process: faster start, no time/memory protection",
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def _parser() -> argparse.ArgumentParser:
|
|
49
|
+
from . import __version__
|
|
50
|
+
|
|
51
|
+
parser = argparse.ArgumentParser(
|
|
52
|
+
prog="visset",
|
|
53
|
+
description="Step-by-step visual explanations of code.",
|
|
54
|
+
)
|
|
55
|
+
parser.add_argument("--version", action="version", version=f"visset {__version__}")
|
|
56
|
+
sub = parser.add_subparsers(dest="command", required=True, metavar="COMMAND")
|
|
57
|
+
|
|
58
|
+
run = sub.add_parser("run", help="trace a file and open the walkthrough (or export with -o)")
|
|
59
|
+
_add_trace_args(run)
|
|
60
|
+
run.add_argument(
|
|
61
|
+
"-o", "--output", metavar="FILE.html", help="write HTML here instead of opening"
|
|
62
|
+
)
|
|
63
|
+
run.add_argument("--open", action="store_true", help="open in the browser even with -o")
|
|
64
|
+
|
|
65
|
+
trace = sub.add_parser("trace", help="trace a file and write the raw JSON trace")
|
|
66
|
+
_add_trace_args(trace)
|
|
67
|
+
trace.add_argument("-o", "--output", metavar="FILE.json", help="default: stdout")
|
|
68
|
+
|
|
69
|
+
render = sub.add_parser("render", help="render a JSON trace to HTML")
|
|
70
|
+
render.add_argument("trace", help="trace .json file")
|
|
71
|
+
render.add_argument("-o", "--output", metavar="FILE.html")
|
|
72
|
+
render.add_argument("--open", action="store_true")
|
|
73
|
+
return parser
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def _trace(ns: argparse.Namespace) -> tuple[dict[str, Any], int]:
|
|
77
|
+
from .tracer import Limits, TraceRequest, execute, run_sandboxed
|
|
78
|
+
|
|
79
|
+
path = Path(ns.file)
|
|
80
|
+
try:
|
|
81
|
+
source = path.read_text(encoding="utf-8")
|
|
82
|
+
except OSError as e:
|
|
83
|
+
raise SystemExit(f"visset: cannot read {path}: {e.strerror}") from e
|
|
84
|
+
from .tracer.window import WindowError, parse_lines
|
|
85
|
+
|
|
86
|
+
try:
|
|
87
|
+
lines = parse_lines(ns.lines) if ns.lines else None
|
|
88
|
+
except WindowError as e:
|
|
89
|
+
raise SystemExit(f"visset: {e}") from e
|
|
90
|
+
req = TraceRequest(
|
|
91
|
+
source=source,
|
|
92
|
+
path=str(path.resolve()),
|
|
93
|
+
function=ns.fn,
|
|
94
|
+
args=ns.args,
|
|
95
|
+
given=ns.given,
|
|
96
|
+
lines=lines,
|
|
97
|
+
stdin=ns.stdin or "",
|
|
98
|
+
limits=Limits(max_steps=ns.max_steps, timeout_ms=int(ns.timeout * 1000)),
|
|
99
|
+
)
|
|
100
|
+
return execute(req) if ns.no_sandbox else run_sandboxed(req)
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def _report(trace: dict[str, Any]) -> None:
|
|
104
|
+
meta = trace["meta"]
|
|
105
|
+
n = len(trace["steps"])
|
|
106
|
+
err = sys.stderr
|
|
107
|
+
first = trace["sources"][0].get("firstLine")
|
|
108
|
+
if n == 0 and first:
|
|
109
|
+
err.write("note: no code ran inside the selected lines.\n")
|
|
110
|
+
if meta.get("error"):
|
|
111
|
+
e = meta["error"]
|
|
112
|
+
where = f" on line {e['line']}" if e.get("line") else ""
|
|
113
|
+
err.write(f"note: the program raised {e['type']}{where}; the walkthrough ends there.\n")
|
|
114
|
+
if meta.get("truncated"):
|
|
115
|
+
t = meta["truncated"]
|
|
116
|
+
why = {
|
|
117
|
+
"maxSteps": f"after {t['limit']:,} steps",
|
|
118
|
+
"timeout": f"after {t['limit'] / 1000:g}s",
|
|
119
|
+
"maxStack": f"at {t['limit']} nested calls",
|
|
120
|
+
}.get(t["reason"], t["reason"])
|
|
121
|
+
err.write(f"note: tracing stopped {why}; the walkthrough shows what ran until then.\n")
|
|
122
|
+
err.write(f"{n} step{'s' if n != 1 else ''} recorded.\n")
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def main(argv: list[str] | None = None) -> int:
|
|
126
|
+
ns = _parser().parse_args(argv)
|
|
127
|
+
from .tracer import TraceError
|
|
128
|
+
|
|
129
|
+
try:
|
|
130
|
+
if ns.command == "render":
|
|
131
|
+
trace = json.loads(Path(ns.trace).read_text(encoding="utf-8"))
|
|
132
|
+
return _emit_html(trace, ns.output, ns.open)
|
|
133
|
+
trace, _status = _trace(ns)
|
|
134
|
+
except TraceError as e:
|
|
135
|
+
sys.stderr.write(f"visset: {e}\n")
|
|
136
|
+
return 1
|
|
137
|
+
_report(trace)
|
|
138
|
+
if ns.command == "trace":
|
|
139
|
+
text = json.dumps(trace, ensure_ascii=False, indent=None, separators=(",", ":"))
|
|
140
|
+
if ns.output:
|
|
141
|
+
Path(ns.output).write_text(text, encoding="utf-8")
|
|
142
|
+
else:
|
|
143
|
+
sys.stdout.write(text + "\n")
|
|
144
|
+
return 0
|
|
145
|
+
return _emit_html(trace, ns.output, ns.open)
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
def _emit_html(trace: dict[str, Any], output: str | None, force_open: bool) -> int:
|
|
149
|
+
from .export.html import PlayerMissingError, open_in_browser, render_html
|
|
150
|
+
|
|
151
|
+
try:
|
|
152
|
+
if output and not force_open:
|
|
153
|
+
Path(output).write_text(render_html(trace), encoding="utf-8")
|
|
154
|
+
sys.stderr.write(f"wrote {output}\n")
|
|
155
|
+
else:
|
|
156
|
+
path = open_in_browser(trace, output)
|
|
157
|
+
sys.stderr.write(f"opened {path}\n")
|
|
158
|
+
except PlayerMissingError as e:
|
|
159
|
+
sys.stderr.write(f"visset: {e}\n")
|
|
160
|
+
return 1
|
|
161
|
+
return 0
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
if __name__ == "__main__":
|
|
165
|
+
raise SystemExit(main())
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Exporters: turn a trace into something you can open or share."""
|