You've already forked uutils.github.io
mirror of
https://github.com/uutils/uutils.github.io.git
synced 2026-06-10 16:12:28 -07:00
Add interactive WASM playground for uutils coreutils
A browser-based terminal that runs real Rust coreutils compiled to WebAssembly (wasm32-wasip1) using @bjorn3/browser_wasi_shim. Features: - xterm.js terminal with command history and line editing - Pipe support (cmd1 | cmd2 | cmd3), file redirects (>) - Persistent virtual filesystem across commands - cd/pwd builtins with virtual working directory - Locale support (locale builtin + selector) - UTF-8/multibyte correct output (raw ConsoleStdout, fixed args_sizes_get) - Click-to-run example buttons - Inline wasm_example shortcode for blog posts
This commit is contained in:
@@ -0,0 +1,90 @@
|
|||||||
|
+++
|
||||||
|
title = "Playground"
|
||||||
|
template = "page.html"
|
||||||
|
+++
|
||||||
|
|
||||||
|
<p>Try <a href="https://github.com/uutils/coreutils">uutils coreutils</a> directly in your browser! This interactive terminal runs Rust coreutils via WebAssembly — no installation needed.</p>
|
||||||
|
|
||||||
|
<!-- Language selector hidden until l10n is embedded in the WASM binary
|
||||||
|
<div class="playground-toolbar">
|
||||||
|
<label for="locale-select">Language:</label>
|
||||||
|
<select id="locale-select" onchange="setLocale(this.value)">
|
||||||
|
<option value="en-US">English</option>
|
||||||
|
<option value="fr-FR">French</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
-->
|
||||||
|
|
||||||
|
<div id="wasm-playground"></div>
|
||||||
|
|
||||||
|
<script src="/js/wasm-terminal.js" defer></script>
|
||||||
|
<script defer>
|
||||||
|
document.addEventListener("DOMContentLoaded", function() {
|
||||||
|
initPlayground("wasm-playground");
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<noscript>
|
||||||
|
<p>JavaScript is required to run the interactive playground.</p>
|
||||||
|
</noscript>
|
||||||
|
|
||||||
|
## Try it out
|
||||||
|
|
||||||
|
Click an example to run it in the terminal:
|
||||||
|
|
||||||
|
<div class="playground-examples">
|
||||||
|
<button class="playground-example">seq 1 20 | factor</button>
|
||||||
|
<button class="playground-example">sort fruits.txt | uniq -c | sort -rn</button>
|
||||||
|
<button class="playground-example">cut -d, -f2 csv.txt | tail -4 | sort -n</button>
|
||||||
|
<button class="playground-example">echo 'rust coreutils in the browser' | tr a-z A-Z</button>
|
||||||
|
<button class="playground-example">echo 'こんにちは世界' | base64 | base64 -d</button>
|
||||||
|
<button class="playground-example">echo 'Hello, world!' | sha256sum</button>
|
||||||
|
<button class="playground-example">shuf -n3 names.txt | nl</button>
|
||||||
|
<button class="playground-example">seq 100 | shuf -n5 | sort -n</button>
|
||||||
|
<button class="playground-example">paste -d: names.txt numbers.txt | head -5</button>
|
||||||
|
<button class="playground-example">echo 'مرحبا بالعالم' | wc -c -w</button>
|
||||||
|
<button class="playground-example">seq 1 15 | fmt -w 40</button>
|
||||||
|
<button class="playground-example">echo '🍎,🍌,🍒,🥝' | cut -d🍌 -f2</button>
|
||||||
|
<button class="playground-example">printf '🍒 cherry\n🍎 apple\n🍌 banana\n' | sort -k2</button>
|
||||||
|
<button class="playground-example">date</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
document.querySelectorAll('.playground-example').forEach(function(btn) {
|
||||||
|
btn.addEventListener('click', function() {
|
||||||
|
var cmd = btn.textContent;
|
||||||
|
document.getElementById('wasm-playground').scrollIntoView({ behavior: 'smooth' });
|
||||||
|
if (window.runInTerminal) {
|
||||||
|
window.runInTerminal(cmd);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
## Available commands
|
||||||
|
|
||||||
|
The following commands run as **real Rust coreutils compiled to WebAssembly**:
|
||||||
|
|
||||||
|
`arch` `b2sum` `base32` `base64` `basenc` `basename` `cat` `cksum`
|
||||||
|
`comm` `cp` `csplit` `cut` `date` `dir` `dircolors` `dirname`
|
||||||
|
`echo` `expand` `expr` `factor` `false` `fmt` `fold` `head`
|
||||||
|
`join` `link` `ln` `ls` `md5sum` `mkdir` `mv` `nl` `nproc` `numfmt`
|
||||||
|
`od` `paste` `pathchk` `printenv` `printf` `pr` `ptx` `pwd`
|
||||||
|
`readlink` `realpath` `rm` `rmdir`
|
||||||
|
`seq` `sort` `split` `tail` `touch` `tr` `tsort`
|
||||||
|
`sha1sum` `sha224sum` `sha256sum` `sha384sum` `sha512sum`
|
||||||
|
`shred` `shuf` `sleep` `sum` `tee` `true` `truncate`
|
||||||
|
`uname` `unexpand` `uniq` `unlink` `vdir` `wc`
|
||||||
|
|
||||||
|
The following are **shell builtins** implemented in JavaScript:
|
||||||
|
|
||||||
|
- `help` — list available commands and examples
|
||||||
|
- `clear` — clear the terminal screen
|
||||||
|
|
||||||
|
Some commands are not yet available in the WASM build because they
|
||||||
|
depend on platform-specific syscalls not fully supported by
|
||||||
|
WebAssembly/WASI.<br />
|
||||||
|
`chcon` and `runcon` are excluded as they require
|
||||||
|
SELinux, which is not relevant in a browser environment.<br />
|
||||||
|
We are actively working on adding more commands as we improve WASI
|
||||||
|
compatibility in uutils coreutils.
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
/**
|
||||||
|
* Inline "Run" button handler for uutils code examples.
|
||||||
|
* Lightweight alternative to the full terminal playground.
|
||||||
|
*/
|
||||||
|
|
||||||
|
(function () {
|
||||||
|
let runtimeLoaded = false;
|
||||||
|
|
||||||
|
async function ensureRuntime() {
|
||||||
|
if (runtimeLoaded) return;
|
||||||
|
// Load the terminal runtime which provides uutilsExecute
|
||||||
|
const script = document.createElement("script");
|
||||||
|
script.src = "/js/wasm-terminal.js";
|
||||||
|
await new Promise((resolve, reject) => {
|
||||||
|
script.onload = resolve;
|
||||||
|
script.onerror = reject;
|
||||||
|
document.head.appendChild(script);
|
||||||
|
});
|
||||||
|
runtimeLoaded = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function runExample(button) {
|
||||||
|
const container = button.closest(".wasm-example");
|
||||||
|
if (!container) return;
|
||||||
|
|
||||||
|
const command = container.dataset.command;
|
||||||
|
const outputEl = container.querySelector(".wasm-example-output");
|
||||||
|
const preEl = outputEl.querySelector("pre");
|
||||||
|
|
||||||
|
// Show loading state
|
||||||
|
button.disabled = true;
|
||||||
|
button.textContent = "Running...";
|
||||||
|
outputEl.style.display = "block";
|
||||||
|
preEl.textContent = "Loading...";
|
||||||
|
|
||||||
|
try {
|
||||||
|
await ensureRuntime();
|
||||||
|
const output = await window.uutilsExecute(command);
|
||||||
|
preEl.textContent = output || "(no output)";
|
||||||
|
} catch (e) {
|
||||||
|
preEl.textContent = "Error: " + e.message;
|
||||||
|
}
|
||||||
|
|
||||||
|
button.disabled = false;
|
||||||
|
button.textContent = "Run";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Expose globally
|
||||||
|
window.runWasmExample = runExample;
|
||||||
|
})();
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -519,3 +519,130 @@ main img {
|
|||||||
background-color: #c04828;
|
background-color: #c04828;
|
||||||
border-color: #c04828;
|
border-color: #c04828;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* WASM PLAYGROUND */
|
||||||
|
.playground-toolbar {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.5em;
|
||||||
|
margin: 1em 0 0;
|
||||||
|
font-size: 0.9em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.playground-toolbar select {
|
||||||
|
padding: 0.3em 0.5em;
|
||||||
|
border-radius: 0.3em;
|
||||||
|
border: 1px solid var(--header-border-color);
|
||||||
|
background: var(--highlight-bg-color);
|
||||||
|
color: var(--highlight-fg-color);
|
||||||
|
font-size: 1em;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
#wasm-playground {
|
||||||
|
width: 100%;
|
||||||
|
min-height: 480px;
|
||||||
|
border-radius: 0.5em;
|
||||||
|
overflow: hidden;
|
||||||
|
margin: 1.5em 0;
|
||||||
|
border: 1px solid var(--header-border-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
#wasm-playground .wasm-loading {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
height: 480px;
|
||||||
|
background: #1e1e2e;
|
||||||
|
color: #cdd6f4;
|
||||||
|
font-family: monospace;
|
||||||
|
font-size: 1.1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* INLINE WASM EXAMPLES */
|
||||||
|
.wasm-example {
|
||||||
|
margin: 1em 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wasm-example-code {
|
||||||
|
display: flex;
|
||||||
|
align-items: stretch;
|
||||||
|
gap: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wasm-example-code pre {
|
||||||
|
flex: 1;
|
||||||
|
padding: 0.8em 1em;
|
||||||
|
background: var(--highlight-bg-color);
|
||||||
|
color: var(--highlight-fg-color);
|
||||||
|
border-radius: 0.3em 0 0 0.3em;
|
||||||
|
font-family: "Fira Code", monospace;
|
||||||
|
font-size: 0.9em;
|
||||||
|
overflow-x: auto;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wasm-run-btn {
|
||||||
|
padding: 0.8em 1.5em;
|
||||||
|
background: var(--accent-color);
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
border-radius: 0 0.3em 0.3em 0;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 0.9em;
|
||||||
|
font-weight: 600;
|
||||||
|
transition: background-color 0.2s;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wasm-run-btn:hover {
|
||||||
|
background: #d45530;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wasm-run-btn:disabled {
|
||||||
|
opacity: 0.6;
|
||||||
|
cursor: wait;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wasm-example-output {
|
||||||
|
margin-top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wasm-example-output pre {
|
||||||
|
padding: 0.8em 1em;
|
||||||
|
background: #1e1e2e;
|
||||||
|
color: #cdd6f4;
|
||||||
|
border-radius: 0 0 0.3em 0.3em;
|
||||||
|
font-family: "Fira Code", monospace;
|
||||||
|
font-size: 0.9em;
|
||||||
|
overflow-x: auto;
|
||||||
|
max-height: 300px;
|
||||||
|
overflow-y: auto;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* PLAYGROUND CLICKABLE EXAMPLES */
|
||||||
|
.playground-examples {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 0.5em;
|
||||||
|
margin: 1em 0 2em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.playground-example {
|
||||||
|
padding: 0.4em 0.8em;
|
||||||
|
background: var(--highlight-bg-color);
|
||||||
|
color: var(--highlight-fg-color);
|
||||||
|
border: 1px solid var(--header-border-color);
|
||||||
|
border-radius: 0.3em;
|
||||||
|
font-family: "Fira Code", monospace;
|
||||||
|
font-size: 0.85em;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: border-color 0.2s, background-color 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.playground-example:hover {
|
||||||
|
border-color: var(--accent-color);
|
||||||
|
background: var(--accent-color);
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|||||||
@@ -43,6 +43,7 @@
|
|||||||
<a href="/bsdutils">Bsdutils</a>
|
<a href="/bsdutils">Bsdutils</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<a href="/playground">Playground</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="spacer"></div>
|
<div class="spacer"></div>
|
||||||
<div class="navigation-block">
|
<div class="navigation-block">
|
||||||
|
|||||||
@@ -18,6 +18,7 @@
|
|||||||
<a href="/{{page.title}}/docs">Documentation</a>
|
<a href="/{{page.title}}/docs">Documentation</a>
|
||||||
{%- endif %}
|
{%- endif %}
|
||||||
<a href="https://github.com/uutils/{{page.title}}/releases">Releases</a>
|
<a href="https://github.com/uutils/{{page.title}}/releases">Releases</a>
|
||||||
|
{% if page.title == "coreutils" %}<a href="/playground">Try it!</a>{% endif %}
|
||||||
</div>
|
</div>
|
||||||
{{ page.content | safe }}
|
{{ page.content | safe }}
|
||||||
{%- endblock main -%}
|
{%- endblock main -%}
|
||||||
|
|||||||
@@ -0,0 +1,10 @@
|
|||||||
|
<div class="wasm-example" data-command="{{ command }}">
|
||||||
|
<div class="wasm-example-code">
|
||||||
|
<pre><code>$ {{ command }}</code></pre>
|
||||||
|
<button class="wasm-run-btn" onclick="runWasmExample(this)">Run</button>
|
||||||
|
</div>
|
||||||
|
<div class="wasm-example-output" style="display:none">
|
||||||
|
<pre></pre>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<script>if(!document.querySelector('script[src="/js/wasm-example.js"]')){var s=document.createElement("script");s.src="/js/wasm-example.js";s.defer=true;document.head.appendChild(s);}</script>
|
||||||
Reference in New Issue
Block a user