# AUTOGENERATED FILE! PLEASE DON'T EDIT HERE. EDIT THE SOURCE NOTEBOOKS INSTEAD
"""
This is for support for transpilation of clis to JS
"""
__all__ = ["JsFunc", "toJsFunc"]
from k1lib.cli.init import BaseCli, fastF; import k1lib.cli.init as init
import k1lib.cli as cli; import k1lib, time, json, base64, math, re
settings = k1lib.settings.cli
settings.add("kjs", k1lib.Settings(), "cli.kjs settings")
kjsSett = settings.kjs
kjsSett.add("jsF", {}, "All ._jsF() (cli to JS) transpile functions, looks like Dict[type -> _jsF(meta, **kwargs) transpile func]")
kjsSett.add("jsonF", {}, "All ._jsonF() JSON-serialization functions, looks like Dict[type -> _jsonF(obj) func]. See utils.deref.json() docs")
kjsSett.add("pyF", {}, "(future feature) All ._pyF() (cli to Python) transpile functions, looks like Dict[type -> _pyF (meta, **kwargs) transpile func]")
kjsSett.add("cppF", {}, "(future feature) All ._cppF() (cli to C++) transpile functions, looks like Dict[type -> _cppF (meta, **kwargs) transpile func]")
kjsSett.add("javaF", {}, "(future feature) All ._javaF() (cli to Java) transpile functions, looks like Dict[type -> _javaF(meta, **kwargs) transpile func]")
kjsSett.add("sqlF", {}, "(future feature) All ._sqlF() (cli to SQL) transpile functions, looks like Dict[type -> _sqlF (meta, **kwargs) transpile func]")
kjsSett.jsF[str] = lambda meta: (f"", "String")
kjsSett.jsF[int] = lambda meta: (f"", "parseInt")
kjsSett.jsF[float] = lambda meta: (f"", "parseFloat")
kjsSett.jsF[set] = lambda meta: (f"", "new Set")
kjsSett.jsF[list] = lambda meta: (f"", "Array.from")
kjsSett.jsF[tuple] = lambda meta: (f"", "Array.from")
kjsSett.jsF[abs] = lambda meta: (f"", "Math.abs")
kjsSett.jsF[json.loads] = lambda meta: (f"", "JSON.parse")
kjsSett.jsF[json.dumps] = lambda meta: (f"", "JSON.stringify")
kjsSett.jsF[base64.b64decode] = lambda meta: (f"", "atob")
kjsSett.jsF[base64.b64encode] = lambda meta: (f"", "btoa")
def _jst_round(meta, ndigits=None): # _jst_round
if ndigits is None: return "", f"Math.round" # _jst_round
else: # _jst_round
fIdx = init._jsFAuto(); dataIdx = init._jsDAuto() # _jst_round
return f"{fIdx} = ({dataIdx}) => Math.round(({dataIdx})*Math.pow(10, {ndigits})+Number.EPSILON)/Math.pow(10, {ndigits})", fIdx # _jst_round
kjsSett.jsF[round] = _jst_round # _jst_round
import math; kjsSett.jsF[math.floor] = lambda meta: (f"", "Math.floor") # _jst_round
import html # _jst_round
def _jsF_html_escape(meta): # _jsF_html_escape
fIdx = init._jsFAuto(); dataIdx = init._jsDAuto() # _jsF_html_escape
return f"{fIdx} = ({dataIdx}) => {dataIdx}.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/\"/g, '"').replace(/'/g, ''');", fIdx # _jsF_html_escape
kjsSett.jsF[html.escape] = _jsF_html_escape # _jsF_html_escape
def _jsonF_range(obj): # _jsonF_range
if obj.step != 1: return "[" + ", ".join([f"{e}" for e in obj]) + "]" # _jsonF_range
end = f".slice({obj.start})" if obj.start != 0 else "" # _jsonF_range
return f"[...Array({obj.stop}).keys()]{end}" # _jsonF_range
kjsSett.jsonF[range] = _jsonF_range # _jsonF_range
def v(x): return x if isinstance(x, str) else json.dumps(x) # v
def vs(xs): return [x if isinstance(x, str) else json.dumps(x) for x in xs] # vs
class vOld(BaseCli): # vOld
def __init__(self, v:str): # vOld
"""Specifies a variable should be taken on the js side""" # vOld
super().__init__(); self.v = v # vOld
def __ror__(self, it): return NotImplemented # vOld
def __str__(self): return f"\ue157{self.v}\ue239" # for injection into grep, and other clis that wants to extract this var out of a completely normal string # vOld
def __repr__(self): return f"<kjs.v v={self.v}>" # vOld
@staticmethod # vOld
def getValue(value): # vOld
"""If it's an instance of :class:`v`, then return its internal value, else
return the same object passed in. Useful for streamlining building out other clis""" # vOld
return value.v if isinstance(value, v) else value # vOld
class Arg: # Arg
def __init__(self, arg): # Arg
if isinstance(arg, str): # Arg
name = arg; type_ = str; default = "" # Arg
elif isinstance(arg, (tuple, list)): # Arg
if len(arg) == 3: # Arg
name, type_, default = arg # Arg
elif len(arg) == 2: # Arg
name, type_ = arg # Arg
if type_ == str: default = "" # Arg
if type_ == int: default = 1 # Arg
if type_ == float: default = 1.0 # Arg
if type_ == bool: default = True # Arg
if isinstance(type_, (range, k1lib.serve.slider)): default = round((type_.start + type_.stop)/2) # surprisingly no bug here where .step != 1, because <input type="range"> covered us # Arg
if isinstance(type_, list): default = type_[0] # Arg
if isinstance(type_, k1lib.serve.date): default = time.time() | cli.toIso() # Arg
else: raise Exception(f"Argument has to specify 3 values: (name:str, type, default value), or 2 values: (name:str, type)") # Arg
else: raise Exception(f"Can't parse argument `{arg}`. It must be either the argument name alone, or a tuple of (name:str, type, default value)") # Arg
self.idx = cli.init._jsDAuto(); self.name = name; self.type_ = type_; self.default = default # Arg
def html(self): # Arg
idx = self.idx; name = self.name; t = self.type_; default = self.default # Arg
if t == str: return f"<input type='text' value='{default}' placeholder='{name}' id='{idx}' style='padding: 4px 4px'>" # Arg
if t == int or t == float: return f"<input type='number' value='{default}' placeholder='{name}' id='{idx}' style='padding: 4px 4px'>" # Arg
if t == bool: return f"<input type='checkbox' {'checked' if default else ''} id='{idx}' style='padding: 4px 4px'>" # Arg
if isinstance(t, (range, k1lib.serve.slider)): return f"<div style='display: flex; flex-direction: row; align-items: center'><input type='range' id='{idx}' min='{t.start}' max='{t.stop}' value='{default}' step='{t.step}' /><span id='{idx}_preview' style='margin-left: 8px'></span></div>" # Arg
if isinstance(t, list): # Arg
if default not in t: raise Exception(f"Dropdown with options {t} does not have the default option {default}") # Arg
s = "".join([f"<option value='{i}' {'selected' if e == default else ''}>{e}</option>" for i, e in enumerate(t)]) # Arg
return f"<select id='{idx}'>{s}</select>" # Arg
if isinstance(t, k1lib.serve.date): # Arg
value = f"value='{default}'" if default else ""; minDate = f"min='{t.minDate}'" if t.minDate else ""; maxDate = f"max='{t.maxDate}'" if t.maxDate else "" # Arg
return f'<input id="{idx}" type="datetime-local" {value} {minDate} {maxDate} />' # Arg
raise Exception(f"Don't know type {t} on .html()") # Arg
def value(self): # grab value of interface # Arg
idx = self.idx; name = self.name; t = self.type_; default = self.default # Arg
q = f"document.querySelector('#{self.idx}')" # Arg
if t == str: return f"{q}.value" # Arg
if t == int: return f"parseInt({q}.value || 0)" # Arg
if t == float: return f"parseFloat({q}.value || 0.0)" # Arg
if t == bool: return f"{q}.checked" # Arg
if isinstance(t, range): return f"parseInt({q}.value)" # Arg
if isinstance(t, k1lib.serve.slider): return f"parseFloat({q}.value)" # Arg
if isinstance(t, list): return f"{json.dumps(t)}[{q}.value]" # Arg
if isinstance(t, k1lib.serve.date): return f"{q}.value" # Arg
raise Exception(f"Don't know type {t} on .value()") # Arg
def preview(self): # Arg
idx = self.idx; name = self.name; t = self.type_; default = self.default # Arg
if isinstance(t, range): return f"document.querySelector('#{self.idx}_preview').innerHTML = parseInt(document.querySelector('#{self.idx}').value);" # Arg
if isinstance(t, k1lib.serve.slider): # Arg
delta = 10**(-math.floor(math.log10(t.step))) # figuring out the correct order of magnitude to round to # Arg
return f"document.querySelector('#{self.idx}_preview').innerHTML = Math.round(document.querySelector('#{self.idx}').value*{delta})/{delta};" # Arg
return "" # Arg
def onchange(self, cb:str): # Arg
idx = self.idx; name = self.name; t = self.type_; default = self.default # Arg
q = f"document.querySelector('#{self.idx}')" # Arg
if t == str or t == int or t == float: return f"{q}.oninput = {cb};" # Arg
if t == bool: return f"{q}.onclick = {cb};" # Arg
if isinstance(t, (range, k1lib.serve.slider, list, k1lib.serve.date)): return f"{q}.oninput = {cb};" # Arg
raise Exception(f"Don't know type {t} on .onchange()") # Arg
class _JsFuncInterface: # _JsFuncInterface
def __init__(self, jsFunc, mode, args, debounce, delay): # _JsFuncInterface
self.jsFunc = jsFunc; self.mode = mode; self.args = args; self.debounce = debounce; self.delay = delay # _JsFuncInterface
if delay and delay < debounce: raise Exception(f"delay value ({delay}) is lower than debounce value ({debounce}), which doesn't make sense. Please raise delay, or lower the debounce value") # _JsFuncInterface
def _repr_html_(self): # _JsFuncInterface
pre = init._jsFAuto(); mode = self.mode; args = self.args; debounce = self.debounce*1000 # _JsFuncInterface
postprocess = "" # _JsFuncInterface
if mode == "html": # _JsFuncInterface
res = "val"; postprocess = f"""
containerDiv = document.createElement('div'); containerDiv.innerHTML = val;
for (const scriptElem of containerDiv.querySelectorAll('script')) eval(scriptElem.textContent);""" # _JsFuncInterface
elif mode == "pre": res = "`<pre>${val}</pre>`" # _JsFuncInterface
elif mode == "json": res = "`<pre>${JSON.stringify(val)}</pre>`" # _JsFuncInterface
elif mode == "jsone": res = "`<pre>${JSON.stringify(val, null, 2)}</pre>`" # _JsFuncInterface
elif mode == "table": res = """ '<table>' + val.map((v) => "<tr>" + v.map((e) => `<td>${e}</td>`).join("") + "</tr>").join("") + '</table>' """ # _JsFuncInterface
else: raise Exception(f"Unknown interface output mode {mode}") # _JsFuncInterface
inps = "".join([f"<div>{a.name}</div><div>{a.html()}</div>" for a in args]) # _JsFuncInterface
inps = f"""<div style='display: grid; grid-template-columns: 1fr 3fr; width: 400px'>{inps}</div>""" # _JsFuncInterface
onchanges = "".join([a.onchange(f"{pre}_reload") for a in args]) # _JsFuncInterface
values = ", ".join([a.value() for a in args]) # _JsFuncInterface
preview = "\n".join([a.preview() for a in args]) # _JsFuncInterface
delayCode = f"(async () => {{ while (true) {{ await new Promise(r => setTimeout(r, {self.delay*1000})); await {pre}_reload_core(); }} }})();" if self.delay else "" # _JsFuncInterface
return f"""\
<!-- k1lib.JsFuncInterface -->
<style>
.{pre}_btn:hover {{
}}
</style>
{inps}
<pre id="{pre}_error" style="color: red"></pre>
<div id="{pre}_loading" style="margin-top: 4px"> </div>
<div id="{pre}_result"></div>
<script src="https://k1js.com/dist/amd/latest.js"></script>
<script>
{pre}_loadF = () => {{
{self.jsFunc.fn}
window.{self.jsFunc.fIdx} = {self.jsFunc.fIdx};
{pre}_error = document.querySelector("#{pre}_error");
{pre}_resultDiv = document.querySelector("#{pre}_result");
{pre}_loadingDiv = document.querySelector("#{pre}_loading");
const runningCoreHandle = [null];
const {pre}_reload_core = async (e) => {{
if (runningCoreHandle[0]) {{ runningCoreHandle[0].cancel = true; }}
const handle = {{"cancel": false}}; runningCoreHandle[0] = handle;
try {{
{pre}_error.innerHTML = "";
{pre}_loadingDiv.innerHTML = "(loading...)";
const val = await {self.jsFunc.fIdx}({values});
if (!handle.cancel) {{
{pre}_loadingDiv.innerHTML = " ";
{pre}_resultDiv.innerHTML = {res};
{postprocess}
}}
}} catch (e) {{ if (!handle.cancel) {pre}_error.innerHTML = e.stack; }}
runningCoreHandle[0] = null;
}};
let {pre}_reload_timeout = null;
const {pre}_reload = async (e) => {{
{preview}
if ({debounce} <= 0) return {pre}_reload_core();
else {{
clearTimeout({pre}_reload_timeout);
{pre}_reload_timeout = setTimeout({pre}_reload_core, {debounce})
}}
}}
{onchanges}
{delayCode}
{pre}_reload(null);
}};
// trying multiple loading schemes, because in a live jupyter nb, it's using umd, while in an exported jupyter nb, it's using amd. The madness of JS
(async () => {{
try {{
require(['k1js'], function(k1js) {{ (async () => {{ window.k1js = await k1js; {pre}_loadF(); }})(); }});
}} catch (e) {{}}
}})();
(async () => {{
try {{
const res = await (await fetch("https://k1js.com/dist/umd/latest.js")).text();
eval(res); window.k1js = await k1js; {pre}_loadF();
}} catch (e) {{}}
}})();
</script>""" # _JsFuncInterface
[docs]class JsFunc: # JsFunc
[docs] def __init__(self, fn:str, fIdx:str, args, _async): # JsFunc
"""Represents a generated function from :class:`toJsFunc` .
More docs are available at that class.
This is not supposed to be instantiated by the end user. Please
use :class:`toJsFunc` instead. It's here for documentation purposes only""" # JsFunc
self.fn = fn; self.fIdx = fIdx; self.args = args; self._async = _async # JsFunc
def __repr__(self): # JsFunc
fn = self.fn.split('\n') | cli.head(300).all() | cli.join('\n') # JsFunc
return f"""<JsFunc {self.fIdx}>\n\nGenerated JS function:\n\n{fn}""" # JsFunc
def _repr_html_(self): # JsFunc
fn = self.fn.split('\n') | cli.join('\n') # JsFunc
header = html.escape(f"<JsFunc {self.fIdx}>") + "<br>Generated JS function:" # JsFunc
try: return f"""{header}<br><br>{k1lib.fmt.js(fn)}""" # JsFunc
except: return f"<pre style='overflow-x: auto'>{html.escape(repr(self))}</pre>" # JsFunc
[docs] def interface(self, mode="html", debounce:float=0.03, delay:float=None): # JsFunc
"""Creates an interface.
Different output modes:
- html: expected the js function to return html and will display it as-is
- pre: wraps the function result inside a "pre" tag, which makes html honor white spaces and whatnot
- json: json formats the function result in a compact way on 1 line
- jsone: "json expanded", same as above, but in an expanded way on multiple lines
- table: expects function result to be a table, then displays the table nicely
:param mode: see above
:param debounce: if specified, will wait for that many seconds until the user hasn't modified the input UIs.
If function is not async (meaning it should executes quite fast), then this param will be ignored""" # JsFunc
return _JsFuncInterface(self, mode, self.args, debounce if self._async else 0, delay) # JsFunc
def moveOut(code:str): # move "k1_moveOutStart - k1_moveOutEnd" blocks to the top # moveOut
pattern = r'//k1_moveOutStart(.*?)//k1_moveOutEnd'; matches = re.findall(pattern, code, re.DOTALL); heads = [] # moveOut
def process_match(match): heads.append(match.group(1).strip()); return '' # moveOut
res = re.sub(pattern, process_match, code, flags=re.DOTALL); pre = '\n'.join(heads); return f"//k1_moveOutStart\n{pre}\n//k1_moveOutEnd\n{res}" # moveOut
[docs]class toJsFunc(BaseCli): # toJsFunc
[docs] def __init__(self, *args, delay:float=None): # toJsFunc
"""Capture cli operations and transpiles all of them to JS, to quickly
build functionalities in JS. Example::
# creates an instance of class JsFunc that contains the transpiled js code
jsf = range(10) | deref() | (toJsFunc("term") | head(kjs.v("term")) & head(6))
# displays a search interface to jupyter notebook
jsf.interface("json")
So a little more background on how this works. With clis, it can "capture" other
clis to the right of it if it desires. So, ``toJsFunc`` will capture the 2 ``head``s
and transpile them to js. So Python data never really gets passed through those later
heads. Read more about this at :class:`~k1lib.cli.init.BaseCli` and on the main
cli page at https://k1lib.com/latest/cli/index.html
The arguments passed in, namely "term" here, specifies what arguments should the JS
function have. Then you can reference those JS arguments downstream inside any cli,
like ``head(kjs.v("term"))``.
The input ``range(10) | deref()`` actually gets converted into json and injected as
data directly within the function, so it's pretty sweet. It does require the input
to be comprised of relatively simple data structures, with no arbitrary objects.
This is a relatively experimental/novel feature. The transpilation mechanism can
do a lot, but if it sees that something is too complicated, then it will raise an
error and complain, instead of failing silently.
When creating an interface, you can supply them with multiple different types of
UI:
.. pyexec:: html True
arg1 = "extraText"
arg2 = ("on", bool)
arg3 = ("someNum", int)
arg4 = ("n", range(0, 10, 2))
arg5 = ("character", ["reimu", "marisa", "cirno"])
jsF = list(range(100)) | (toJsFunc(arg1, arg2, arg3, arg4, arg5) | head("n") & head("on*10") | aS("[*x, character, someNum**2, len(extraText)*2]"))
jsF.interface("json")
""" # toJsFunc
super().__init__(capture=True) # toJsFunc
self.args = [Arg(a) for a in args] # toJsFunc
[docs] def __ror__(self, it) -> JsFunc: # toJsFunc
header, fn, _async = k1lib.kast.asyncGuard(self.capturedSerial._jsF(("root",))) # used to be (kast, self.args) # toJsFunc
header = "\n".join([f" {e}" for e in header.split("\n")]); fIdx = init._jsFAuto(); dataIdx = init._jsDAuto() # toJsFunc
return JsFunc(moveOut(f"""
{dataIdx} = {it | cli.deref.js()};
async function {fIdx}({', '.join([a.name for a in self.args])}) {{
{header}
return {'await ' if _async else ''}{fn}({dataIdx});
}}"""), fIdx, self.args, _async) # toJsFunc
def _jsF(self, meta): # toJsFunc
"""Hidden feature, cause this is getting way too complicated and this idea has not been fully fleshed out yet
.. admonition:: Nested toJsFunc()
Surprisingly and mind bendingly, this can transpile itself, but the use case is rather different.
Here's a dead simple example::
jsF = 3 | (toJsFunc() | op()+2 | (toJsFunc("args", "are", "ignored", "here") | aS("x*2")))
""" # toJsFunc
fIdx = cli.init._jsFAuto(); dataIdx = cli.init._jsDAuto(); argIdx = cli.init._jsDAuto(); pre = cli.init._jsDAuto(); # toJsFunc
h1,f1,a1 = k1lib.kast.asyncGuard(self.capturedSerial._jsF(meta)) # toJsFunc
return f"""//k1_moveOutStart\nlet {dataIdx} = null;\n{h1}\n//k1_moveOutEnd
{fIdx} = ({argIdx}) => {{ // returns html string that will run the function on load
{dataIdx} = {argIdx};
return unescape(`<div id='{pre}_div'></div>
%3Cscript%3E
setTimeout({'async ' if a1 else ''}() => {{
console.log("executed");
document.querySelector('#{pre}_div').innerHTML = {'await ' if a1 else ''}{f1}({dataIdx})
}}, 100);
%3C/script%3E`);
}};""", fIdx # toJsFunc