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;
|
||||
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>
|
||||
</div>
|
||||
</div>
|
||||
<a href="/playground">Playground</a>
|
||||
</div>
|
||||
<div class="spacer"></div>
|
||||
<div class="navigation-block">
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
<a href="/{{page.title}}/docs">Documentation</a>
|
||||
{%- endif %}
|
||||
<a href="https://github.com/uutils/{{page.title}}/releases">Releases</a>
|
||||
{% if page.title == "coreutils" %}<a href="/playground">Try it!</a>{% endif %}
|
||||
</div>
|
||||
{{ page.content | safe }}
|
||||
{%- 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