Refresh player with native block editor preview and local assets

Preserve shortcode rendering, normalize options, bundle the player locally,
and add reproducible packaging and WordPress integration tests.
This commit is contained in:
2026-10-12 17:56:08 +08:00
parent 40e2bf0733
commit f04600c37b
90 changed files with 28835 additions and 2090 deletions
+2
View File
@@ -0,0 +1,2 @@
# Preserve verbatim upstream license texts (some use CRLF or trailing spaces).
licenses/* -whitespace
+3 -8
View File
@@ -1,11 +1,6 @@
# To get started with Dependabot version updates, you'll need to specify which
# package ecosystems to update and where the package manifests are located.
# Please see the documentation for all configuration options:
# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
version: 2 version: 2
updates: updates:
- package-ecosystem: "npm" # See documentation for possible values - package-ecosystem: npm
directory: "/asciiplayer" # Location of package manifests directory: /
schedule: schedule:
interval: "daily" interval: daily
+109
View File
@@ -0,0 +1,109 @@
name: Build and WordPress integration
on:
pull_request:
push:
branches: ['main', 'codex/**']
workflow_dispatch:
permissions:
contents: read
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
php-compatibility:
name: PHP ${{ matrix.php }} syntax and regression tests
runs-on: ubuntu-latest
timeout-minutes: 10
strategy:
fail-fast: false
matrix:
php: ['7.4', '8.3']
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Check syntax and execute PHP regression tests
run: |
docker run --rm -v "$PWD:/app" -w /app "php:${{ matrix.php }}-cli" sh -ec '
find . -type f -name "*.php" -not -path "./node_modules/*" -print0 | xargs -0 -n1 php -l
php tests/php/run.php
'
wordpress:
name: WordPress ${{ matrix.wordpress }} / PHP ${{ matrix.php }}
runs-on: ubuntu-latest
timeout-minutes: 30
strategy:
fail-fast: false
matrix:
include:
- wordpress: 'latest'
php: '8.2'
- wordpress: '6.3'
php: '7.4'
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
with:
node-version: '24'
cache: npm
- run: npm ci
- run: npm run build
- run: npm test
- run: npm run package
- name: Configure clean WordPress version
env:
WP_TEST_VERSION: ${{ matrix.wordpress }}
WP_TEST_PHP: ${{ matrix.php }}
run: |
node --input-type=module <<'JS'
import fs from 'node:fs';
fs.writeFileSync('.wp-env.override.json', JSON.stringify({
core: process.env.WP_TEST_VERSION === 'latest' ? null : `https://wordpress.org/wordpress-${process.env.WP_TEST_VERSION}.zip`,
phpVersion: process.env.WP_TEST_PHP,
}, null, 2));
JS
- name: Start real WordPress and install only the packaged plugin
run: |
npx --no-install wp-env start
npx --no-install wp-env run cli wp plugin install /var/www/html/wp-content/asciiplayer-packages/wp-asciiplayer.zip --activate --force
npx --no-install wp-env run cli wp plugin is-active wp-asciiplayer
npx --no-install wp-env run cli wp core version | tee wordpress-version.txt
npx --no-install wp-env run cli wp plugin list
- name: Install Chromium
run: npx --no-install playwright install --with-deps chromium
- name: Exercise editor iframe, saved blocks, Pages, posts and legacy shortcodes
run: npm run test:e2e
- name: Run Plugin Check (advisory until directory review)
if: always() && matrix.wordpress == 'latest'
continue-on-error: true
run: |
npx --no-install wp-env run cli wp plugin install plugin-check --activate
npx --no-install wp-env run cli wp plugin get plugin-check --field=version | tee plugin-check-version.txt
npx --no-install wp-env run cli wp plugin check wp-asciiplayer --require=/var/www/html/wp-content/plugins/plugin-check/cli.php --format=json | tee plugin-check-report.txt
- name: Record WordPress and PHP logs
if: always()
run: |
npx --no-install wp-env logs --watch=false > wordpress-logs.txt 2>&1 || true
npx --no-install wp-env run cli sh -c 'test ! -f wp-content/debug.log || cat wp-content/debug.log' > wordpress-debug.txt 2>&1 || true
- name: Upload integration evidence and installable ZIP
if: always()
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: wordpress-${{ matrix.wordpress }}-php-${{ matrix.php }}
if-no-files-found: warn
retention-days: 14
path: |
playwright-report/
test-results/
plugin-check-version.txt
plugin-check-report.txt
wordpress-version.txt
wordpress-logs.txt
wordpress-debug.txt
dist/wp-asciiplayer.zip
- name: Stop WordPress
if: always()
run: npx --no-install wp-env stop
+6 -1
View File
@@ -48,4 +48,9 @@ wp-config.php
#/wp-content/themes #/wp-content/themes
.idea/ .idea/
dist/ dist/
/node_modules/
/build/
/playwright-report/
/test-results/
/.wp-env/
+18
View File
@@ -0,0 +1,18 @@
{
"$schema": "https://schemas.wp.org/trunk/wp-env.json",
"core": null,
"phpVersion": "8.2",
"port": 8888,
"plugins": [],
"config": {
"WP_DEBUG": true,
"WP_DEBUG_LOG": true,
"WP_DEBUG_DISPLAY": false,
"SCRIPT_DEBUG": true
},
"mappings": {
"wp-content/uploads/asciiplayer-fixtures": "./tests/fixtures",
"wp-content/asciiplayer-packages": "./dist"
},
"testsEnvironment": false
}
+32 -19
View File
@@ -1,33 +1,46 @@
# wp-asciiplayer # wp-asciiplayer
把 AsciiCast 添加到你的文章! 为 WordPress 提供原生 Asciicast Player 区块,可在编辑器内真实播放终端录屏,
同时兼容原有 `[asciiplayer]` shortcode。
## 快速开始 ## 安装与使用
在文章中添加一个简码,然后输入以下内容: 将构建得到的 `wp-asciiplayer.zip` 上传到「插件 → 安装插件 → 上传插件」并启用。
GitHub 源码 ZIP 需要先构建,不能直接当发行包安装。构建方法见 [SOURCE.md](SOURCE.md)。
要求 WordPress 6.3+、PHP 7.4+;具体测试版本见仓库 CI。
``` 插入「Asciicast Player」区块,粘贴录制文件的 HTTP(S) URL,应用后即可在编辑器内播放、
[asciiplayer]https://asciinema.org/a/28307.cast[/asciiplayer] 暂停和拖动。主题、速度、循环、控制栏、尺寸、起始时间和空闲压缩等选项在区块设置中调整。
``` 保存文章或页面后,前台使用同一播放器。重新打开已有区块时点击「Load preview」加载预览;
编辑预览不会自动播放,前台自动播放设置仍会保留。
简码的内容为 asciicast 文件的 URL。 URL 必须指向录制文件(例如 `.cast`),不能直接使用分享页面地址。跨域文件需要允许
来自你站点的 CORS 请求;HTTPS 页面上的 HTTP 文件可能被浏览器阻止。优先使用同源 HTTPS。
本插件不提供远程文件代理、不扩展上传类型,也不创建额外的录屏管理文章类型。
### 选项 ## 旧文章兼容
AsciiPlayer 简码支持 asciinema-player 所支持的选项,例如设置 `fit` 为 `height` 并关闭控制栏: 原有 shortcode 继续有效,无须批量修改文章:
``` [asciiplayer]https://example.org/demo.cast[/asciiplayer]
[asciiplayer fit="height" controls="false"]https://asciinema.org/a/28307.cast[/asciiplayer] [asciiplayer theme="monokai" speed="1.5" controls="false"]https://example.org/demo.cast[/asciiplayer]
``` [asciiplayer startAt="2" idleTimeLimit="3" style="width: 80%;"]https://example.org/demo.cast[/asciiplayer]
你可以在[这里](https://www.npmjs.com/package/asciinema-player#options)找到所有可用的选项。 布尔值、数值和历史 camelCase 参数会归一化处理;自定义 style 经过 WordPress 安全 CSS 过滤。
区块提供 shortcode 转换能力,转换后请检查设置再保存,不会自动重写旧文章。
经典编辑器继续使用 shortcode,暂不增加 TinyMCE 自定义界面。
> 目前只有 `logger` 选项不受支持,因为这个选项需要传入一个对象。 ## 资源与隐私
#### 样式选项 播放器 JavaScript、CSS、WebAssembly 全部本地附带,不再请求 jsDelivr,
旧 `ap_use_local_resources` 开关不再使用。没有统计追踪或后台远程 API 请求。
为了方便自定义播放器元素,插件额外添加了 `style` 选项,该选项会直接控制 Asciinema-player 容器元素的样式: 你选择的录制文件会由读者浏览器直接请求;外部文件主机可能收到 IP、浏览器请求信息和
依浏览器/站点策略发送的来源信息。应用 URL 或点击加载预览时,编辑器也会发起这类请求。
请使用可信主机,发布前检查录屏是否包含终端里的敏感信息。外部主机适用其自身隐私政策。
``` ## 开发与许可
[asciiplayer style="width: 80%;"]https://asciinema.org/a/28307.cast[/asciiplayer]
``` [构建与测试说明](SOURCE.md) · [第三方许可证](THIRD-PARTY-NOTICES.md)
本插件保持 GPL-3.0-only,播放器及依赖保留各自的兼容许可证。
+51 -22
View File
@@ -1,37 +1,66 @@
# wp-asciiplayer # wp-asciiplayer
Play Ascicast files in the post! An Asciicast Player block for WordPress, with real playback in the editor and
backwards-compatible `[asciiplayer]` shortcodes. [中文说明](README-zh.md)
[中文文档](README-zh.md) ## Install
## Quick start Install the built `wp-asciiplayer.zip` through **Plugins → Add New → Upload
Plugin**, then activate **Asciicast Player**. A GitHub source ZIP must be built
first; it does not contain generated assets. See [SOURCE.md](SOURCE.md).
Add a shortcode to the post and enter the following: Requires WordPress 6.3+ and PHP 7.4+. Compatibility must be checked against your
site and theme; CI tests the versions listed in `.github/workflows/ci.yml`.
``` ## Block editor
[asciiplayer]https://asciinema.org/a/28307.cast[/asciiplayer]
```
The content of the shortcode is the URL of the ascicast file. 1. Insert **Asciicast Player**.
2. Paste an HTTP(S) recording-file URL (for example a `.cast` file), then apply it.
3. Play, pause and seek in the editor. Change theme, speed, looping, controls,
width/fit, start time and idle-time limit from the block settings.
4. Save the post or page; the same locally bundled player is used on the front end.
### Options Existing saved blocks show a **Load preview** action on reopening the editor.
This keeps external recordings from loading until you choose to preview them.
Preview does not autoplay even when front-end autoplay is enabled.
The AsciiPlayer shortcode supports the options supported by Asciinema-player, such as setting `fit` to `height` and closing the control bar: The source must be a recording-file URL, not an asciinema share-page URL.
A recording on another origin must allow CORS requests from your WordPress site.
HTTP recordings on an HTTPS site may be blocked as mixed content. Use a same-site
HTTPS URL when possible. The plugin does not proxy or copy remote files, grant
new upload types, or add a recording-management post type.
``` ## Existing shortcodes
[asciiplayer fit="height" controls="false"]https://asciinema.org/a/28307.cast[/asciiplayer]
```
You can find all available options [here](https://www.npmjs.com/package/asciinema-player#options). Old articles remain supported; no content migration is required:
> Currently, only the `logger` option is not supported because it requires an object to be passed in. [asciiplayer]https://example.org/demo.cast[/asciiplayer]
[asciiplayer theme="monokai" speed="1.5" controls="false"]https://example.org/demo.cast[/asciiplayer]
[asciiplayer startAt="2" idleTimeLimit="3" style="width: 80%;"]https://example.org/demo.cast[/asciiplayer]
#### Style options Boolean and numeric options are typed, and historical camelCase options are
normalized after WordPress lowercases shortcode attribute names. Inline styles
are filtered through WordPress safe-CSS handling; unsafe protocols and markup
are rejected/escaped. Unsupported executable/object options are not accepted.
The Classic Editor continues to support shortcodes; it has no custom visual UI.
The block supports a shortcode-to-block transform for supported options. Review
converted settings before saving; articles are never rewritten automatically.
For the convenience of customizing player elements, ## Local resources and privacy
the plugin has added an additional `style` **option**,
which corresponds to the `style` **attribute** of the player container element:
``` All player JavaScript, CSS, and WebAssembly are included in the plugin. The old
[asciiplayer style="width: 80%;"]https://asciinema.org/a/28307.cast[/asciiplayer] `ap_use_local_resources` switch is no longer used, and jsDelivr is not contacted.
``` No analytics or background remote API calls are added.
A recording URL you choose is fetched directly by the visitor's browser. Its host
can receive the visitor's IP address, browser request metadata and possibly a
referrer, according to browser/site policy. Editor previews have the same effect
when you apply a URL or click **Load preview**. Choose a trusted host and review
recordings for sensitive terminal output before publishing them. The host's own
privacy policy applies; the plugin cannot make an external recording private.
## Development
See [SOURCE.md](SOURCE.md) for locked build and ZIP packaging commands. Source
code is GPL-3.0-only. The player and its dependencies retain their own compatible
licenses; see [THIRD-PARTY-NOTICES.md](THIRD-PARTY-NOTICES.md).
+35
View File
@@ -0,0 +1,35 @@
# Source and reproducible build
This distribution contains the plugin's PHP and JavaScript source, block metadata,
build configuration, scripts, and npm lockfile. The development repository is
https://github.com/LamGC/wp-asciiplayer .
Use Node.js 24 (24.15 or later), npm, and the standard `zip` command:
npm ci
npm run build
npm run package
The installable output is `dist/wp-asciiplayer.zip`. Generated assets are never
loaded from a CDN. `scripts/vendor.mjs` copies the pinned official npm
`[email protected]` distribution, verified by package-lock integrity, into
`build/player.js` and `build/player.css`. WordPress-provided JavaScript packages
are externalized by @wordpress/scripts, not bundled with a second React.
The upstream player's corresponding source and build instructions are at
https://github.com/asciinema/asciinema-player/tree/v3.17.0 . Its JavaScript bundle
includes the upstream terminal WebAssembly implementation. Third-party notices
and applicable license texts are included alongside this file.
Development tests require the full repository (tests are not in the install ZIP):
npm test
npm run test:php
npm run wp-env -- start
npm run test:e2e
PHP tests require a PHP runtime. WordPress integration tests require Docker and
Chromium; the repository CI records the exact WordPress version under test and
installs the release ZIP into the test site before checking it. Refer to the
workflow for setup details. Running a unit test is not a substitute for the
WordPress browser integration suite.
+127
View File
@@ -0,0 +1,127 @@
# Third-party notices
The WordPress plugin's own code is licensed under GPL-3.0-only; see the root
`LICENSE`. The third-party components below retain their respective licenses.
This file records attribution and provenance and does not replace those terms.
## asciinema-player 3.17.0
- Author: Marcin Kulik. The upstream license includes
“Copyright 2011-2021 Marcin Kulik”.
- Upstream source: https://github.com/asciinema/asciinema-player/tree/v3.17.0
- npm package: https://www.npmjs.com/package/asciinema-player/v/3.17.0
- License: Apache-2.0. Full upstream text: `licenses/asciinema-player-LICENSE`.
- The embedded terminal implementation in `src/vt` also provides MIT and
Apache-2.0 texts, preserved as `licenses/vt-js-LICENSE-MIT` and
`licenses/vt-js-LICENSE-APACHE`. Its MIT notice is
“Copyright (c) 2018 Marcin Kulik <m@ku1ik.com>”.
`scripts/vendor.mjs` copies the official npm standalone JavaScript and CSS
byte-for-byte into `build/player.js` and `build/player.css`, respectively.
It does not rebuild or modify the upstream player. The JavaScript includes
the upstream Apache-2.0 license banner and an inline WebAssembly module.
Verified SHA-256 hashes of the original npm files:
- `dist/bundle/asciinema-player.min.js`:
`a13c37632e1b5c49fe9128417b9319a9b5bc64cb457dd5ae52cbba8a3aceb880`
- `dist/bundle/asciinema-player.css`:
`f619fe17597043564f03b2c6918b3daf890ee8b912fb408542fba11afade4fdb`
The upstream v3.17.0 source tree contains the three license files above and
no separate NOTICE file. Recheck this when updating the player.
## JavaScript dependency notices
The following versions are from the upstream player's v3.17.0
`package-lock.json`, rather than this plugin's newer resolved installation
dependencies. Their MIT license texts, including copyright notices, are
preserved under `licenses/`.
| Package | Upstream lock version | License file |
| --- | --- | --- |
| @babel/runtime | 7.27.0 | babel-runtime-LICENSE |
| solid-js | 1.9.6 | solid-js-LICENSE |
| solid-transition-group | 0.2.3 | solid-transition-group-LICENSE |
| @solid-primitives/refs | 1.1.2 | solid-primitives-LICENSE |
| @solid-primitives/transition-group | 1.1.2 | solid-primitives-LICENSE |
| @solid-primitives/utils | 6.3.2 | solid-primitives-LICENSE |
| regenerator-runtime | 0.14.1 | regenerator-runtime-LICENSE |
| seroval | 1.5.0 | seroval-LICENSE |
| seroval-plugins | 1.3.0 | seroval-plugins-LICENSE |
| csstype | 3.1.1 | csstype-LICENSE |
This conservatively covers all non-development dependency entries in that
upstream npm lockfile. It does not assert that every package, type definition,
or function survives the upstream browser build's tree-shaking.
`licenses/upstream-npm-production-dependencies.json` records these entries.
## Rust / WebAssembly dependency notices
The following is the dependency closure of the normal dependencies declared
by `src/vt/Cargo.toml`, using the versions in upstream v3.17.0
`src/vt/Cargo.lock`. It includes compile-time macro dependencies, even where
their own runtime code is not shipped. The original lockfile is preserved as
`licenses/upstream-asciinema-player-Cargo.lock`.
For projects offering MIT as an alternative license, the MIT text is included
here. The upstream projects retain any other licensing options they offer.
| Package | Upstream lock version | Included license text |
| --- | --- | --- |
| allocator-api2 | 0.4.0 | allocator-api2-LICENSE (MIT) |
| avt | 0.16.0 | avt-LICENSE (Apache-2.0) |
| bumpalo | 3.12.0 | bumpalo-LICENSE (MIT) |
| bytemuck | 1.18.0 | bytemuck-LICENSE (MIT) |
| cfg-if | 1.0.0 | cfg-if-LICENSE (MIT) |
| itoa | 1.0.5 | itoa-LICENSE (MIT) |
| js-sys | 0.3.61 | js-sys-LICENSE (MIT) |
| lock_api | 0.4.14 | lock_api-LICENSE (MIT) |
| once_cell | 1.17.1 | once_cell-LICENSE (MIT) |
| proc-macro2 | 1.0.84 | proc-macro2-LICENSE (MIT) |
| quote | 1.0.36 | quote-LICENSE (MIT) |
| rgb | 0.8.50 | rgb-LICENSE (MIT) |
| rustversion | 1.0.22 | rustversion-LICENSE (MIT) |
| ryu | 1.0.12 | ryu-LICENSE (Apache-2.0) |
| scopeguard | 1.2.0 | scopeguard-LICENSE (MIT) |
| serde, serde_derive | 1.0.203 | serde-LICENSE (MIT) |
| serde-wasm-bindgen | 0.6.5 | serde-wasm-bindgen-LICENSE (MIT) |
| serde_json | 1.0.93 | serde_json-LICENSE (MIT) |
| syn | 2.0.66 | syn-LICENSE (MIT) |
| talc | 5.0.3 | talc-LICENSE (MIT) |
| unicode-ident | 1.0.6 | unicode-ident-MIT-LICENSE and unicode-ident-LICENSE (Unicode data terms) |
| unicode-width | 0.1.14 | unicode-width-LICENSE (MIT), unicode-width-COPYRIGHT-LICENSE |
| wasm-bindgen, wasm-bindgen-macro, wasm-bindgen-macro-support, wasm-bindgen-shared | 0.2.106 | wasm-bindgen-LICENSE (MIT) |
The `js-sys` license comes from its corresponding wasm-bindgen 0.2.84
source release. Test-only dependency branches in the upstream Rust lockfile
are not represented as shipped components.
The upstream `flake.nix` pins Rust 1.85.0, which also appears in source-path
strings in the supplied WASM. Rust's MIT and Apache-2.0 texts and copyright
inventory are preserved as `rust-1.85.0-LICENSE-MIT`,
`rust-1.85.0-LICENSE-APACHE`, and `rust-1.85.0-COPYRIGHT`.
Rust 1.85.0's library lockfile selects compiler_builtins 0.1.140; that project's
combined MIT and Apache-2.0 WITH LLVM-exception text is preserved as
`compiler-builtins-LICENSE`, with the referenced upstream compiler-rt
contributor list in `compiler-rt-CREDITS`. These compiler-runtime notices are
included conservatively; they are not a claim that every standard-library
dependency listed in Rust's source distribution is linked into this player.
## Provenance and verification limits
`licenses/SOURCES.json` identifies the source of each preserved text.
These are source-based notices for the official upstream bundle, not an
independently generated binary SBOM or a legal audit. The dependency versions
above are the upstream source lockfile entries; this plugin's build does not
resolve or rebuild that embedded Rust code. The supplied WASM independently
contains paths identifying avt 0.16.0, unicode-width 0.1.14,
serde-wasm-bindgen 0.6.5, and wasm-bindgen 0.2.106. It does not expose a complete
machine-readable inventory of all compiled dependencies.
When changing the player version or rebuilding it from source, review the
new lockfiles, actual linked components, copyright/NOTICE files, compiler
runtime, and any added dependencies; update this inventory and preserved
texts before distribution. Keep this file and the complete `licenses/`
directory in the plugin release ZIP. Source and build instructions are in
`SOURCE.md`.
+27 -93
View File
@@ -1,100 +1,34 @@
<?php <?php
/**
* Plugin Name: Asciicast Player
* Plugin URI: https://github.com/LamGC/wp-asciiplayer
* Description: Add interactive terminal recordings to posts and pages with a block or shortcode.
* Version: 1.1.0
* Requires at least: 6.3
* Requires PHP: 7.4
* Author: LamGC
* Author URI: https://blog.lamgc.moe
* License: GPL-3.0-only
* License URI: https://www.gnu.org/licenses/gpl-3.0.html
* Text Domain: wp-asciiplayer
*
* @package LamGC\Asciiplayer
*/
/* namespace LamGC\Asciiplayer;
Plugin Name: Asciicast Player
Plugin URI: https://github.com/LamGC/wp-asciiplayer
Description: AsciinemaPlayer for WordPress.
Version: 1.0.0
Author: LamGC
Author URI: https://blog.lamgc.moe
License: GNU GENERAL PUBLIC LICENSE Version 3.0
*/
const PLUGIN_VERSION = '1.0.1'; if ( ! defined( 'ABSPATH' ) ) {
exit;
const ASCIIPLAYER_TAG = 'asciiplayer';
const AP_OPTION_USE_LOCAL_RESOURCES = 'ap_use_local_resources';
// 是否使用本地资源, 如果可以的话, 建议使用 CDN.
add_option(AP_OPTION_USE_LOCAL_RESOURCES, false, $autoload = true);
function handle_asciiplayer_code($attr = [], string $content = null): string
{
if (!isset($content)) {
return '';
}
$content = trim($content);
if (!is_array(wp_parse_url($content))) {
return '';
}
$opts = is_assoc($attr) ? $attr : [];
set_default_value($opts, 'preload', true);
$style = null;
if (isset($opts['style']) && is_string($opts['style'])) {
$style = $opts['style'];
}
unset($opts['style']);
$opts_json = urlencode_deep(json_encode($opts));
return "<div
class=\"asciiplayer-container\"
data-ap-src=\"$content\"
data-ap-opts=\"$opts_json\"
data-ap-style=\"$style\"
></div>";
} }
function is_assoc($arr): bool const VERSION = '1.1.0';
{ const PLUGIN_FILE = __FILE__;
if (!is_array($arr)) return false; const SHORTCODE = 'asciiplayer';
if (array() === $arr) return false;
return array_keys($arr) !== range(0, count($arr) - 1);
}
require_once __DIR__ . '/includes/options.php';
require_once __DIR__ . '/includes/render.php';
require_once __DIR__ . '/includes/assets.php';
function set_default_value(array &$array, string $key, $value) add_action( 'init', __NAMESPACE__ . '\\register_plugin' );
{ add_action( 'wp_enqueue_scripts', __NAMESPACE__ . '\\enqueue_content_assets' );
if (!isset($array[$key])) { add_action( 'wp_footer', __NAMESPACE__ . '\\print_late_styles', 5 );
$array[$key] = $value;
}
}
add_shortcode(ASCIIPLAYER_TAG, 'handle_asciiplayer_code');
function load_scripts()
{
if (!is_single()) {
return;
}
global $post;
if (has_shortcode($post->post_content, ASCIIPLAYER_TAG)) {
wp_enqueue_script(
'asciiplayer-adapter-js',
get_adapter_url() . '/dist/bundle.js',
array(),
PLUGIN_VERSION
);
wp_enqueue_style(
'asciiplayer-adapter-css',
get_adapter_url() . '/dist/styles.css',
array(),
PLUGIN_VERSION
);
}
}
add_action('wp_enqueue_scripts', 'load_scripts');
function get_adapter_url(): string
{
$use_local_resources = boolval(get_option(AP_OPTION_USE_LOCAL_RESOURCES, true));
$version = PLUGIN_VERSION;
if ($use_local_resources) {
return plugin_dir_url(__FILE__) . '/asciiplayer';
} else {
return "https://cdn.jsdelivr.net/npm/wp-asciiplayer-adapter@$version";
}
}
-2
View File
@@ -1,2 +0,0 @@
dist/
node_modules/
-16
View File
@@ -1,16 +0,0 @@
# wp-asciiplayer-adapter
This library needs to be used with the WordPress Ascicast Player plugin.
## How to work
The Asciicast Player plugin will generate a div element containing the parameters required for asciicast playback for the asciiplayer short code in the post:
```html
<div class="asciiplayer-container"
data-ap-src="https://asciinema.org/a/28307.cast"
data-ap-opts="%7B%22fit%22%3A%22width%22%2C%22preload%22%3Atrue%7D">
</div>
```
Then, the adapter will search for these div elements and use [asciinema-player](https://www.npmjs.com/package/asciinema-player) to create an Asciicast player within the div elements.
-1792
View File
File diff suppressed because it is too large Load Diff
-27
View File
@@ -1,27 +0,0 @@
{
"name": "wp-asciiplayer-adapter",
"version": "1.0.1",
"description": "asciinema-player for wordpress.",
"main": "dist/bundle.js",
"type": "module",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"bundle": "npx webpack"
},
"keywords": [
"wordpress",
"asciicast",
"asciicast-player"
],
"author": "LamGC",
"license": "GPL-3.0-only",
"dependencies": {
"asciinema-player": "^3.4.0"
},
"devDependencies": {
"css-loader": "^6.8.1",
"mini-css-extract-plugin": "^2.7.6",
"webpack": "^5.85.1",
"webpack-cli": "^5.1.3"
}
}
-52
View File
@@ -1,52 +0,0 @@
import * as AsciinemaPlayer from "asciinema-player";
import "asciinema-player/dist/bundle/asciinema-player.css";
import "../styles/default-player.css";
const defaultOpts = {
preload: true,
fit: "width"
}
document.addEventListener('DOMContentLoaded', () => {
const containers = document.getElementsByClassName("asciiplayer-container");
for (const container of containers) {
const castSource = container.dataset["apSrc"];
const containerStyle = container.dataset["apStyle"];
if (!castSource) {
console.warn(`asciiplayer: container ${container.id} has no data-ap-src attribute, skip.`);
continue;
}
let encodedOptions = container.dataset["apOpts"];
let options = defaultOpts;
if (encodedOptions) {
try {
options = mergeOptions(defaultOpts, JSON.parse(decodeURIComponent(encodedOptions)));
} catch (err) {
console.error("asciiplayer: failed to parse data-ap-options, skip.", err);
continue;
}
}
if (containerStyle) {
container.setAttribute("style", containerStyle);
delete container.dataset["apStyle"];
}
// noinspection JSCheckFunctionSignatures - create 中会判断是否为三个函数.
AsciinemaPlayer.create(castSource, container, options);
}
});
/**
*
* @param defaults {Object}
* @param opts {Object}
*/
function mergeOptions(defaults, opts) {
let result = {};
for (const key of Object.keys(defaults)) {
result[key] = defaults[key];
}
for (const key of Object.keys(opts)) {
result[key] = opts[key];
}
return result;
}
-3
View File
@@ -1,3 +0,0 @@
.asciiplayer-container {
margin: 0 auto;
}
-24
View File
@@ -1,24 +0,0 @@
import path from 'path';
import MiniCssExtractPlugin from 'mini-css-extract-plugin';
export default {
entry: './src/index.js',
mode: "production",
output: {
path: path.resolve('./dist'),
filename: 'bundle.js',
},
module: {
rules: [
{
test: /\.css$/i,
use: [MiniCssExtractPlugin.loader, 'css-loader'],
},
],
},
plugins: [
new MiniCssExtractPlugin({
filename: 'styles.css'
})
]
};
+8
View File
@@ -0,0 +1,8 @@
{
"presets": ["@wordpress/babel-preset-default"],
"plugins": [["@babel/plugin-transform-react-jsx", {
"runtime": "classic",
"pragma": "wp.element.createElement",
"pragmaFrag": "wp.element.Fragment"
}]]
}
+37
View File
@@ -0,0 +1,37 @@
{
"$schema": "https://schemas.wp.org/trunk/block.json",
"apiVersion": 3,
"name": "lamgc/asciiplayer",
"version": "1.1.0",
"title": "Asciicast Player",
"category": "media",
"icon": "editor-code",
"description": "Embed and preview a terminal recording with the locally bundled asciinema player.",
"keywords": ["asciinema", "terminal", "recording"],
"textdomain": "wp-asciiplayer",
"attributes": {
"source": { "type": "string", "default": "" },
"theme": { "type": "string", "default": "asciinema" },
"speed": { "type": "number", "default": 1 },
"loop": { "type": "boolean", "default": false },
"controls": { "type": "string", "default": "auto", "enum": ["auto", "always", "never"] },
"fit": { "type": "string", "default": "width", "enum": ["width", "height", "both", "none"] },
"startAt": { "type": "number", "default": 0 },
"idleTimeLimit": { "type": "number", "default": 0 },
"autoPlay": { "type": "boolean", "default": false },
"preload": { "type": "boolean", "default": true },
"width": { "type": "string", "default": "100%" },
"legacyStyle": { "type": "string", "default": "" },
"cols": { "type": "number" },
"rows": { "type": "number" },
"terminalFontFamily": { "type": "string" },
"terminalFontSize": { "type": "string" },
"terminalLineHeight": { "type": "number" },
"poster": { "type": "string" }
},
"supports": { "html": false, "align": ["wide", "full"] },
"editorScript": "lamgc-asciiplayer-editor",
"viewScript": "lamgc-asciiplayer-view",
"editorStyle": "lamgc-asciiplayer-editor-style",
"style": "lamgc-asciiplayer-style"
}
+114
View File
@@ -0,0 +1,114 @@
<?php
/**
* Locally bundled, shared player assets and block registration.
*
* @package LamGC\Asciiplayer
*/
namespace LamGC\Asciiplayer;
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Register the block, shortcode and all plugin-scoped asset handles.
*
* @return void
*/
function register_plugin() {
$base = plugin_dir_url( PLUGIN_FILE ) . 'build/';
$asset = array(
'dependencies' => array( 'wp-blocks', 'wp-element', 'wp-block-editor', 'wp-components', 'wp-i18n' ),
'version' => VERSION,
);
if ( file_exists( dirname( PLUGIN_FILE ) . '/build/index.asset.php' ) ) {
$asset = require dirname( PLUGIN_FILE ) . '/build/index.asset.php';
}
$view_asset = array( 'dependencies' => array( 'wp-i18n' ), 'version' => VERSION );
if ( file_exists( dirname( PLUGIN_FILE ) . '/build/view.asset.php' ) ) {
$view_asset = require dirname( PLUGIN_FILE ) . '/build/view.asset.php';
}
wp_register_script( 'lamgc-asciiplayer-player', $base . 'player.js', array(), VERSION, true );
wp_register_script(
'lamgc-asciiplayer-view',
$base . 'view.js',
array_unique( array_merge( $view_asset['dependencies'], array( 'lamgc-asciiplayer-player' ) ) ),
$view_asset['version'],
true
);
wp_register_script(
'lamgc-asciiplayer-editor',
$base . 'index.js',
array_unique( array_merge( $asset['dependencies'], array( 'lamgc-asciiplayer-player' ) ) ),
$asset['version'],
true
);
wp_register_style( 'lamgc-asciiplayer-player-style', $base . 'player.css', array(), VERSION );
wp_register_style( 'lamgc-asciiplayer-style', $base . 'style-index.css', array( 'lamgc-asciiplayer-player-style' ), VERSION );
wp_register_style( 'lamgc-asciiplayer-editor-style', $base . 'index.css', array( 'lamgc-asciiplayer-player-style' ), VERSION );
wp_add_inline_script(
'lamgc-asciiplayer-editor',
'window.wpAsciiplayerSettings = ' . wp_json_encode(
array(
'playerScriptUrl' => add_query_arg( 'ver', VERSION, $base . 'player.js' ),
'playerStyleUrl' => add_query_arg( 'ver', VERSION, $base . 'player.css' ),
),
JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_APOS | JSON_HEX_QUOT
) . ';',
'before'
);
wp_set_script_translations( 'lamgc-asciiplayer-view', 'wp-asciiplayer', dirname( PLUGIN_FILE ) . '/languages' );
wp_set_script_translations( 'lamgc-asciiplayer-editor', 'wp-asciiplayer', dirname( PLUGIN_FILE ) . '/languages' );
register_block_type( dirname( PLUGIN_FILE ) . '/build', array( 'render_callback' => __NAMESPACE__ . '\\render_block' ) );
add_shortcode( SHORTCODE, __NAMESPACE__ . '\\render_shortcode' );
}
/**
* Enqueue in the shared renderer as well, for widgets and template calls.
* WordPress deduplicates handles across any number of players on a page.
*
* @return void
*/
function enqueue_player_assets() {
wp_enqueue_script( 'lamgc-asciiplayer-view' );
wp_enqueue_style( 'lamgc-asciiplayer-style' );
}
/**
* Look ahead before the head is printed, including Pages and archive results.
* Render-time enqueueing remains the source of truth for indirect content.
*
* @return void
*/
function enqueue_content_assets() {
global $wp_query;
if ( empty( $wp_query->posts ) || ! is_array( $wp_query->posts ) ) {
return;
}
foreach ( $wp_query->posts as $post ) {
if ( ! is_object( $post ) || ! isset( $post->post_content ) ) {
continue;
}
if ( has_shortcode( $post->post_content, SHORTCODE ) || has_block( 'lamgc/asciiplayer', $post->post_content ) ) {
enqueue_player_assets();
return;
}
}
}
/**
* Print only our late-enqueued CSS, before footer scripts initialize players.
* This covers dynamically rendered shortcodes in widgets and classic themes.
*
* @return void
*/
function print_late_styles() {
if ( wp_style_is( 'lamgc-asciiplayer-style', 'enqueued' ) && ! wp_style_is( 'lamgc-asciiplayer-style', 'done' ) ) {
wp_print_styles( array( 'lamgc-asciiplayer-style' ) );
}
}
+297
View File
@@ -0,0 +1,297 @@
<?php
/**
* Validation shared by the shortcode and dynamic block.
*
* @package LamGC\Asciiplayer
*/
namespace LamGC\Asciiplayer;
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Normalize shortcode's lowercase names, camelCase block names and old aliases.
* Unknown options are intentionally discarded before reaching the player.
*
* @param mixed $attributes Input attributes.
* @return array
*/
function normalize_attributes( $attributes ) {
if ( ! is_array( $attributes ) ) {
return array();
}
$aliases = array(
'source' => 'source',
'src' => 'source',
'autoplay' => 'autoPlay',
'preload' => 'preload',
'loop' => 'loop',
'controls' => 'controls',
'fit' => 'fit',
'theme' => 'theme',
'speed' => 'speed',
'startat' => 'startAt',
'idletimelimit' => 'idleTimeLimit',
'cols' => 'cols',
'rows' => 'rows',
'fontsize' => 'terminalFontSize',
'terminalfontsize' => 'terminalFontSize',
'fontfamily' => 'terminalFontFamily',
'terminalfontfamily' => 'terminalFontFamily',
'lineheight' => 'terminalLineHeight',
'terminallineheight' => 'terminalLineHeight',
'poster' => 'poster',
'width' => 'width',
'style' => 'legacyStyle',
'legacystyle' => 'legacyStyle',
);
$result = array();
foreach ( $attributes as $key => $value ) {
if ( ! is_string( $key ) || ! is_scalar( $value ) ) {
continue;
}
$key = strtolower( str_replace( array( '-', '_' ), '', $key ) );
if ( isset( $aliases[ $key ] ) ) {
$result[ $aliases[ $key ] ] = $value;
}
}
return $result;
}
/**
* Accept HTTP(S) and site-root-relative recordings, never executable schemes.
* No requests are made by PHP, and URL credentials are never emitted.
*
* @param mixed $source Recording URL.
* @return string Sanitized URL, or an empty string for invalid input.
*/
function sanitize_source( $source ) {
if ( ! is_string( $source ) ) {
return '';
}
$source = trim( html_entity_decode( $source, ENT_QUOTES | ENT_HTML5, 'UTF-8' ) );
if ( '' === $source || preg_match( '/[\x00-\x20\x7f<>"\\\\]/', $source ) ) {
return '';
}
$parts = wp_parse_url( $source );
if ( false === $parts || ! is_array( $parts ) || isset( $parts['user'] ) || isset( $parts['pass'] ) ) {
return '';
}
if ( '/' === substr( $source, 0, 1 ) ) {
// Reject protocol-relative URLs, including slash/backslash variants.
if ( '/' === substr( $source, 1, 1 ) || isset( $parts['host'] ) || isset( $parts['scheme'] ) ) {
return '';
}
} elseif ( empty( $parts['host'] ) || empty( $parts['scheme'] ) || ! in_array( strtolower( $parts['scheme'] ), array( 'http', 'https' ), true ) ) {
return '';
}
return esc_url_raw( $source, array( 'http', 'https' ) );
}
/**
* Parse explicit false values rather than casting a nonempty string to true.
*
* @param mixed $value Input.
* @return bool|null Null indicates an unrecognized value.
*/
function parse_boolean( $value ) {
if ( is_bool( $value ) ) {
return $value;
}
if ( ! is_scalar( $value ) ) {
return null;
}
$value = strtolower( trim( (string) $value ) );
if ( in_array( $value, array( '1', 'true', 'yes', 'on' ), true ) ) {
return true;
}
if ( in_array( $value, array( '0', 'false', 'no', 'off' ), true ) ) {
return false;
}
return null;
}
/**
* Validate a finite number, retaining legitimate zero values.
*
* @param mixed $value Input.
* @param float $min Minimum value.
* @param float $max Maximum value.
* @return float|null
*/
function parse_number( $value, $min, $max ) {
if ( is_bool( $value ) || ! is_scalar( $value ) || ! is_numeric( $value ) ) {
return null;
}
$value = (float) $value;
return is_finite( $value ) && $value >= $min && $value <= $max ? $value : null;
}
/**
* Convert the player's supported timecode syntax to seconds.
*
* @param mixed $value Seconds, mm:ss or hh:mm:ss.
* @return float|null
*/
function parse_time( $value ) {
$number = parse_number( $value, 0, PHP_INT_MAX );
if ( null !== $number ) {
return $number;
}
if ( ! is_string( $value ) || ! preg_match( '/^\d+(?::[0-5]?\d){1,2}(?:\.\d+)?$/', trim( $value ) ) ) {
return null;
}
$total = 0.0;
foreach ( explode( ':', trim( $value ) ) as $part ) {
$total = $total * 60 + (float) $part;
}
return is_finite( $total ) && $total <= PHP_INT_MAX ? $total : null;
}
/**
* Return a single safe CSS length for width/font size.
*
* @param mixed $value Input.
* @param bool $font Whether player font-size names are allowed.
* @return string
*/
function sanitize_size( $value, $font = false ) {
if ( ! is_string( $value ) && ! is_numeric( $value ) ) {
return '';
}
$value = strtolower( trim( (string) $value ) );
if ( $font && in_array( $value, array( 'small', 'medium', 'big' ), true ) ) {
return $value;
}
return preg_match( '/^(?:0|(?:\d+(?:\.\d+)?|\.\d+)(?:px|%|em|rem|vw|vh|vmin|vmax|ch))$/', $value ) ? $value : '';
}
/**
* Produce only typed, supported player options; never JavaScript source.
*
* @param array $attributes Normalized attributes.
* @return array
*/
function player_options( array $attributes ) {
$options = array(
'autoPlay' => false,
'preload' => true,
'fit' => 'width',
);
foreach ( array( 'autoPlay', 'preload', 'loop' ) as $name ) {
if ( isset( $attributes[ $name ] ) ) {
$value = parse_boolean( $attributes[ $name ] );
if ( null !== $value ) {
$options[ $name ] = $value;
} elseif ( 'loop' === $name ) {
// Older shortcodes can specify a finite number of replays.
$value = parse_number( $attributes[ $name ], 1, 10000 );
if ( null !== $value && floor( $value ) === $value ) {
$options[ $name ] = (int) $value;
}
}
}
}
if ( isset( $attributes['controls'] ) ) {
$value = strtolower( trim( (string) $attributes['controls'] ) );
if ( 'auto' === $value ) {
$options['controls'] = 'auto';
} elseif ( in_array( $value, array( 'always', 'never' ), true ) ) {
$options['controls'] = 'always' === $value;
} else {
$value = parse_boolean( $attributes['controls'] );
if ( null !== $value ) {
$options['controls'] = $value;
}
}
}
if ( isset( $attributes['fit'] ) ) {
$value = strtolower( trim( (string) $attributes['fit'] ) );
if ( in_array( $value, array( 'width', 'height', 'both', 'none' ), true ) ) {
$options['fit'] = $value;
} elseif ( false === parse_boolean( $attributes['fit'] ) ) {
$options['fit'] = 'none';
}
}
if ( isset( $attributes['theme'] ) && preg_match( '/^(?:auto\/)?[a-zA-Z0-9_-]{1,64}$/', (string) $attributes['theme'] ) ) {
$options['theme'] = (string) $attributes['theme'];
}
foreach ( array( 'speed' => array( 0.01, 100 ), 'idleTimeLimit' => array( 0.001, PHP_INT_MAX ), 'terminalLineHeight' => array( 0.1, 10 ) ) as $name => $range ) {
if ( isset( $attributes[ $name ] ) ) {
$value = parse_number( $attributes[ $name ], $range[0], $range[1] );
if ( null !== $value ) {
$options[ $name ] = $value;
}
}
}
if ( isset( $attributes['startAt'] ) ) {
$value = parse_time( $attributes['startAt'] );
if ( null !== $value ) {
$options['startAt'] = $value;
}
}
foreach ( array( 'cols', 'rows' ) as $name ) {
if ( isset( $attributes[ $name ] ) ) {
$value = parse_number( $attributes[ $name ], 1, 1000 );
if ( null !== $value && floor( $value ) === $value ) {
$options[ $name ] = (int) $value;
}
}
}
if ( isset( $attributes['terminalFontSize'] ) ) {
$value = sanitize_size( $attributes['terminalFontSize'], true );
if ( '' !== $value ) {
$options['terminalFontSize'] = $value;
}
}
if ( isset( $attributes['terminalFontFamily'] ) ) {
$value = trim( (string) $attributes['terminalFontFamily'] );
if ( strlen( $value ) <= 300 && preg_match( '/^[a-zA-Z0-9 _.,\'"-]+$/', $value ) ) {
$options['terminalFontFamily'] = $value;
}
}
if ( isset( $attributes['poster'] ) ) {
$value = (string) $attributes['poster'];
if ( 0 === strpos( $value, 'npt:' ) && null !== parse_time( substr( $value, 4 ) ) ) {
$options['poster'] = $value;
} elseif ( 0 === strpos( $value, 'data:text/plain,' ) && strlen( $value ) <= 32768 ) {
$options['poster'] = $value;
}
}
return $options;
}
/**
* Filter legacy inline style, then apply the dedicated width control if set.
*
* @param array $attributes Normalized attributes.
* @return string
*/
function player_style( array $attributes ) {
$style = isset( $attributes['legacyStyle'] ) ? safecss_filter_attr( (string) $attributes['legacyStyle'] ) : '';
if ( isset( $attributes['width'] ) ) {
$width = sanitize_size( $attributes['width'] );
if ( '' !== $width ) {
$style = rtrim( $style, '; ' ) . ';width:' . $width . ';';
}
}
return ltrim( $style, '; ' );
}
+66
View File
@@ -0,0 +1,66 @@
<?php
/**
* Safe, shared rendering for shortcode and block content.
*
* @package LamGC\Asciiplayer
*/
namespace LamGC\Asciiplayer;
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Render one player. Invalid/missing sources produce no frontend markup.
*
* @param mixed $attributes Player attributes.
* @param string|null $content Optional legacy shortcode source.
* @param bool $block Whether WordPress block wrapper supports apply.
* @return string
*/
function render_player( $attributes = array(), $content = null, $block = false ) {
$attributes = normalize_attributes( $attributes );
$source = sanitize_source( null !== $content ? $content : ( $attributes['source'] ?? '' ) );
if ( '' === $source ) {
return '';
}
$options = wp_json_encode( player_options( $attributes ), JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_APOS | JSON_HEX_QUOT );
if ( false === $options ) {
return '';
}
enqueue_player_assets();
$style = player_style( $attributes );
$wrapper = $block
? get_block_wrapper_attributes( array( 'class' => 'asciiplayer-container', 'style' => $style ) )
: 'class="asciiplayer-container"' . ( '' === $style ? '' : ' style="' . esc_attr( $style ) . '"' );
return sprintf(
'<div %1$s data-ap-src="%2$s" data-ap-options="%3$s"></div>',
$wrapper,
esc_url( $source, array( 'http', 'https' ) ),
esc_attr( $options )
);
}
/**
* Preserve [asciiplayer]URL[/asciiplayer] and its existing options.
*
* @param mixed $attributes Shortcode attributes.
* @param string|null $content Enclosed URL.
* @return string
*/
function render_shortcode( $attributes = array(), $content = null ) {
return null === $content ? '' : render_player( $attributes, $content );
}
/**
* Dynamic block callback. Both authoring paths use exactly the same sanitizer.
*
* @param array $attributes Block attributes.
* @return string
*/
function render_block( $attributes ) {
return render_player( $attributes, null, true );
}
+183
View File
@@ -0,0 +1,183 @@
{
"notice": "License texts are preserved from the specified upstream versions or release commits. Dependency inventories describe upstream source declarations, not an independently generated binary SBOM.",
"sources": [
{
"file": "avt-LICENSE",
"source": "https://raw.githubusercontent.com/asciinema/avt/v0.16.0/LICENSE"
},
{
"file": "rgb-LICENSE",
"source": "https://raw.githubusercontent.com/kornelski/rust-rgb/v0.8.50/LICENSE"
},
{
"file": "unicode-width-LICENSE",
"source": "https://raw.githubusercontent.com/unicode-rs/unicode-width/v0.1.14/LICENSE-MIT"
},
{
"file": "unicode-width-COPYRIGHT-LICENSE",
"source": "https://raw.githubusercontent.com/unicode-rs/unicode-width/v0.1.14/COPYRIGHT"
},
{
"file": "bytemuck-LICENSE",
"source": "https://raw.githubusercontent.com/Lokathor/bytemuck/v1.18.0/LICENSE-MIT"
},
{
"file": "talc-LICENSE",
"source": "https://raw.githubusercontent.com/SFBdragon/talc/9e75a551c30bd82661e366ede044ba4695826966/LICENSE.md"
},
{
"file": "allocator-api2-LICENSE",
"source": "https://raw.githubusercontent.com/zakarumych/allocator-api2/5b163e41f5f6b68969a65dc7afe8a6a9d3d49ccb/LICENSE-MIT"
},
{
"file": "lock_api-LICENSE",
"source": "https://raw.githubusercontent.com/Amanieu/parking_lot/lock_api-v0.4.14/LICENSE-MIT"
},
{
"file": "scopeguard-LICENSE",
"source": "https://raw.githubusercontent.com/bluss/scopeguard/v1.2.0/LICENSE-MIT"
},
{
"file": "serde-LICENSE",
"source": "https://raw.githubusercontent.com/serde-rs/serde/v1.0.203/LICENSE-MIT"
},
{
"file": "serde-wasm-bindgen-LICENSE",
"source": "https://raw.githubusercontent.com/RReverser/serde-wasm-bindgen/v0.6.5/LICENSE"
},
{
"file": "wasm-bindgen-LICENSE",
"source": "https://raw.githubusercontent.com/wasm-bindgen/wasm-bindgen/0.2.106/LICENSE-MIT"
},
{
"file": "cfg-if-LICENSE",
"source": "https://raw.githubusercontent.com/rust-lang/cfg-if/1.0.0/LICENSE-MIT"
},
{
"file": "once_cell-LICENSE",
"source": "https://raw.githubusercontent.com/matklad/once_cell/v1.17.1/LICENSE-MIT"
},
{
"file": "serde_json-LICENSE",
"source": "https://raw.githubusercontent.com/serde-rs/json/v1.0.93/LICENSE-MIT"
},
{
"file": "ryu-LICENSE",
"source": "https://raw.githubusercontent.com/dtolnay/ryu/1.0.12/LICENSE-APACHE"
},
{
"file": "itoa-LICENSE",
"source": "https://raw.githubusercontent.com/dtolnay/itoa/1.0.5/LICENSE-MIT"
},
{
"file": "solid-js-LICENSE",
"source": "https://raw.githubusercontent.com/solidjs/solid/v1.9.6/LICENSE"
},
{
"file": "solid-primitives-LICENSE",
"source": "https://raw.githubusercontent.com/solidjs-community/solid-primitives/@solid-primitives/[email protected]/LICENSE",
"additional_verified_sources": [
"https://raw.githubusercontent.com/solidjs-community/solid-primitives/@solid-primitives/[email protected]/LICENSE",
"https://raw.githubusercontent.com/solidjs-community/solid-primitives/@solid-primitives/[email protected]/LICENSE"
]
},
{
"file": "babel-runtime-LICENSE",
"source": "https://raw.githubusercontent.com/babel/babel/v7.27.0/LICENSE"
},
{
"file": "regenerator-runtime-LICENSE",
"source": "https://raw.githubusercontent.com/facebook/regenerator/v0.14.1/LICENSE"
},
{
"file": "csstype-LICENSE",
"source": "https://raw.githubusercontent.com/frenic/csstype/v3.1.1/LICENSE"
},
{
"file": "seroval-LICENSE",
"source": "https://raw.githubusercontent.com/lxsmnsyc/seroval/1.5.0/LICENSE"
},
{
"file": "seroval-plugins-LICENSE",
"source": "https://raw.githubusercontent.com/lxsmnsyc/seroval/v1.3.0/LICENSE"
},
{
"file": "js-sys-LICENSE",
"source": "https://raw.githubusercontent.com/wasm-bindgen/wasm-bindgen/0.2.84/LICENSE-MIT"
},
{
"file": "rustversion-LICENSE",
"source": "https://raw.githubusercontent.com/dtolnay/rustversion/1.0.22/LICENSE-MIT"
},
{
"file": "proc-macro2-LICENSE",
"source": "https://raw.githubusercontent.com/dtolnay/proc-macro2/1.0.84/LICENSE-MIT"
},
{
"file": "quote-LICENSE",
"source": "https://raw.githubusercontent.com/dtolnay/quote/1.0.36/LICENSE-MIT"
},
{
"file": "syn-LICENSE",
"source": "https://raw.githubusercontent.com/dtolnay/syn/2.0.66/LICENSE-MIT"
},
{
"file": "bumpalo-LICENSE",
"source": "https://raw.githubusercontent.com/fitzgen/bumpalo/3.12.0/LICENSE-MIT"
},
{
"file": "unicode-ident-LICENSE",
"source": "https://raw.githubusercontent.com/dtolnay/unicode-ident/1.0.6/LICENSE-UNICODE"
},
{
"file": "unicode-ident-MIT-LICENSE",
"source": "https://raw.githubusercontent.com/dtolnay/unicode-ident/1.0.6/LICENSE-MIT"
},
{
"file": "asciinema-player-LICENSE",
"source": "https://github.com/asciinema/asciinema-player/blob/v3.17.0/LICENSE"
},
{
"file": "vt-js-LICENSE-APACHE",
"source": "https://github.com/asciinema/asciinema-player/blob/v3.17.0/src/vt/LICENSE_APACHE"
},
{
"file": "vt-js-LICENSE-MIT",
"source": "https://github.com/asciinema/asciinema-player/blob/v3.17.0/src/vt/LICENSE_MIT"
},
{
"file": "rust-1.85.0-LICENSE-MIT",
"source": "https://github.com/rust-lang/rust/blob/1.85.0/LICENSE-MIT"
},
{
"file": "rust-1.85.0-COPYRIGHT",
"source": "https://github.com/rust-lang/rust/blob/1.85.0/COPYRIGHT"
},
{
"file": "compiler-builtins-LICENSE",
"source": "https://raw.githubusercontent.com/rust-lang/compiler-builtins/compiler_builtins-v0.1.140/LICENSE.txt"
},
{
"file": "upstream-asciinema-player-Cargo.lock",
"source": "https://github.com/asciinema/asciinema-player/blob/v3.17.0/src/vt/Cargo.lock"
},
{
"file": "upstream-npm-production-dependencies.json",
"source": "https://github.com/asciinema/asciinema-player/blob/v3.17.0/package-lock.json",
"note": "Non-dev entries extracted; includes root package metadata."
},
{
"file": "solid-transition-group-LICENSE",
"source": "npm:[email protected]/LICENSE",
"note": "Copied byte-for-byte from installed official npm package; exact upstream version."
},
{
"file": "rust-1.85.0-LICENSE-APACHE",
"source": "https://raw.githubusercontent.com/rust-lang/rust/1.85.0/LICENSE-APACHE"
},
{
"file": "compiler-rt-CREDITS",
"source": "https://raw.githubusercontent.com/llvm/llvm-project/main/compiler-rt/CREDITS.TXT"
}
]
}
+23
View File
@@ -0,0 +1,23 @@
Permission is hereby granted, free of charge, to any
person obtaining a copy of this software and associated
documentation files (the "Software"), to deal in the
Software without restriction, including without
limitation the rights to use, copy, modify, merge,
publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software
is furnished to do so, subject to the following
conditions:
The above copyright notice and this permission notice
shall be included in all copies or substantial portions
of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
+201
View File
@@ -0,0 +1,201 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "{}"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright 2011-2021 Marcin Kulik
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
+201
View File
@@ -0,0 +1,201 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
+22
View File
@@ -0,0 +1,22 @@
MIT License
Copyright (c) 2014-present Sebastian McKenzie and other contributors
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+25
View File
@@ -0,0 +1,25 @@
Copyright (c) 2019 Nick Fitzgerald
Permission is hereby granted, free of charge, to any
person obtaining a copy of this software and associated
documentation files (the "Software"), to deal in the
Software without restriction, including without
limitation the rights to use, copy, modify, merge,
publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software
is furnished to do so, subject to the following
conditions:
The above copyright notice and this permission notice
shall be included in all copies or substantial portions
of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
+9
View File
@@ -0,0 +1,9 @@
MIT License
Copyright (c) 2019 Daniel "Lokathor" Gee.
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice (including the next paragraph) shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+25
View File
@@ -0,0 +1,25 @@
Copyright (c) 2014 Alex Crichton
Permission is hereby granted, free of charge, to any
person obtaining a copy of this software and associated
documentation files (the "Software"), to deal in the
Software without restriction, including without
limitation the rights to use, copy, modify, merge,
publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software
is furnished to do so, subject to the following
conditions:
The above copyright notice and this permission notice
shall be included in all copies or substantial portions
of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
+270
View File
@@ -0,0 +1,270 @@
compiler-builtins as a whole is available for use under both the MIT license
and the Apache-2.0 license with the LLVM exception (MIT AND Apache-2.0 WITH
LLVM-exception).
As a contributor, you agree that your code can be used under either the MIT
license, or the Apache-2.0 license, or the Apache-2.0 license with the LLVM
exception.
Text of the relevant licenses is provided below:
------------------------------------------------------------------------------
MIT License
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
------------------------------------------------------------------------------
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
---- LLVM Exceptions to the Apache 2.0 License ----
As an exception, if, as a result of your compiling your source code, portions
of this Software are embedded into an Object form of such source code, you
may redistribute such embedded portions in such Object form without complying
with the conditions of Sections 4(a), 4(b) and 4(d) of the License.
In addition, if you combine or link compiled forms of this Software with
software that is licensed under the GPLv2 ("Combined Software") and if a
court of competent jurisdiction determines that the patent provision (Section
3), the indemnity provision (Section 9) or other Section of the License
conflicts with the conditions of the GPLv2, you may retroactively and
prospectively choose to deem waived or otherwise exclude such Section(s) of
the License, but only in their entirety and only with respect to the Combined
Software.
------------------------------------------------------------------------------
Portions of this software are derived from third-party works licensed under
terms compatible with the above Apache-2.0 WITH LLVM-exception AND MIT
license:
* compiler-builtins is derived from LLVM's compiler-rt (https://llvm.org/).
Work derived from compiler-rt prior to 2019-01-19 is usable under the MIT
license, with the following copyright:
Copyright (c) 2009-2016 by the contributors listed in CREDITS.TXT
The relevant CREDITS.TXT is located at
https://github.com/llvm/llvm-project/blob/main/compiler-rt/CREDITS.TXT.
* Work derived from compiler-rt after 2019-01-19 is usable under the
Apache-2.0 license with the LLVM exception.
* The bundled `math` module is from rust-lang/libm, usable under the MIT
license. See https://github.com/rust-lang/libm for details.
Additionally, some source files may contain comments with specific copyrights
or licenses.
+36
View File
@@ -0,0 +1,36 @@
This file is a partial list of people who have contributed to the LLVM/CompilerRT
project. If you have contributed a patch or made some other contribution to
LLVM/CompilerRT, please submit a patch to this file to add yourself, and it will be
done!
The list is sorted by surname and formatted to allow easy grepping and
beautification by scripts. The fields are: name (N), email (E), web-address
(W), PGP key ID and fingerprint (P), description (D), and snail-mail address
(S).
N: Craig van Vliet
E: [email protected]
W: http://www.auroraux.org
D: Code style and Readability fixes.
N: Edward O'Callaghan
E: [email protected]
W: http://www.auroraux.org
D: CMake'ify Compiler-RT build system
D: Maintain Solaris & AuroraUX ports of Compiler-RT
N: Howard Hinnant
E: [email protected]
D: Architect and primary author of compiler-rt
N: Guan-Hong Liu
E: [email protected]
D: IEEE Quad-precision functions
N: Joerg Sonnenberger
E: [email protected]
D: Maintains NetBSD port.
N: Matt Thomas
E: [email protected]
D: ARM improvements.
+19
View File
@@ -0,0 +1,19 @@
Copyright (c) 2017-2018 Fredrik Nicol
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+23
View File
@@ -0,0 +1,23 @@
Permission is hereby granted, free of charge, to any
person obtaining a copy of this software and associated
documentation files (the "Software"), to deal in the
Software without restriction, including without
limitation the rights to use, copy, modify, merge,
publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software
is furnished to do so, subject to the following
conditions:
The above copyright notice and this permission notice
shall be included in all copies or substantial portions
of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
+25
View File
@@ -0,0 +1,25 @@
Copyright (c) 2014 Alex Crichton
Permission is hereby granted, free of charge, to any
person obtaining a copy of this software and associated
documentation files (the "Software"), to deal in the
Software without restriction, including without
limitation the rights to use, copy, modify, merge,
publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software
is furnished to do so, subject to the following
conditions:
The above copyright notice and this permission notice
shall be included in all copies or substantial portions
of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
+25
View File
@@ -0,0 +1,25 @@
Copyright (c) 2016 The Rust Project Developers
Permission is hereby granted, free of charge, to any
person obtaining a copy of this software and associated
documentation files (the "Software"), to deal in the
Software without restriction, including without
limitation the rights to use, copy, modify, merge,
publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software
is furnished to do so, subject to the following
conditions:
The above copyright notice and this permission notice
shall be included in all copies or substantial portions
of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
+23
View File
@@ -0,0 +1,23 @@
Permission is hereby granted, free of charge, to any
person obtaining a copy of this software and associated
documentation files (the "Software"), to deal in the
Software without restriction, including without
limitation the rights to use, copy, modify, merge,
publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software
is furnished to do so, subject to the following
conditions:
The above copyright notice and this permission notice
shall be included in all copies or substantial portions
of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
+23
View File
@@ -0,0 +1,23 @@
Permission is hereby granted, free of charge, to any
person obtaining a copy of this software and associated
documentation files (the "Software"), to deal in the
Software without restriction, including without
limitation the rights to use, copy, modify, merge,
publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software
is furnished to do so, subject to the following
conditions:
The above copyright notice and this permission notice
shall be included in all copies or substantial portions
of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
+23
View File
@@ -0,0 +1,23 @@
Permission is hereby granted, free of charge, to any
person obtaining a copy of this software and associated
documentation files (the "Software"), to deal in the
Software without restriction, including without
limitation the rights to use, copy, modify, merge,
publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software
is furnished to do so, subject to the following
conditions:
The above copyright notice and this permission notice
shall be included in all copies or substantial portions
of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
+21
View File
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2014-present, Facebook, Inc.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+21
View File
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2019 Kornel
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+384
View File
@@ -0,0 +1,384 @@
Short version for non-lawyers:
The Rust Project is dual-licensed under Apache 2.0 and MIT
terms.
Longer version:
Copyrights in the Rust project are retained by their contributors. No
copyright assignment is required to contribute to the Rust project.
Some files include explicit copyright notices and/or license notices.
For full authorship information, see the version control history or
https://thanks.rust-lang.org
Except as otherwise noted (below and/or in individual files), Rust is
licensed under the Apache License, Version 2.0 <LICENSE-APACHE> or
<http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
<LICENSE-MIT> or <http://opensource.org/licenses/MIT>, at your option.
The Rust Project includes packages written by third parties.
The following third party packages are included, and carry
their own copyright notices and license terms:
* LLVM, located in src/llvm-project, is licensed under the following
terms.
==============================================================================
The LLVM Project is under the Apache License v2.0 with LLVM Exceptions:
==============================================================================
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
---- LLVM Exceptions to the Apache 2.0 License ----
As an exception, if, as a result of your compiling your source code, portions
of this Software are embedded into an Object form of such source code, you
may redistribute such embedded portions in such Object form without complying
with the conditions of Sections 4(a), 4(b) and 4(d) of the License.
In addition, if you combine or link compiled forms of this Software with
software that is licensed under the GPLv2 ("Combined Software") and if a
court of competent jurisdiction determines that the patent provision (Section
3), the indemnity provision (Section 9) or other Section of the License
conflicts with the conditions of the GPLv2, you may retroactively and
prospectively choose to deem waived or otherwise exclude such Section(s) of
the License, but only in their entirety and only with respect to the Combined
Software.
==============================================================================
Software from third parties included in the LLVM Project:
==============================================================================
The LLVM Project contains third party software which is under different license
terms. All such code will be identified clearly using at least one of two
mechanisms:
1) It will be in a separate directory tree with its own `LICENSE.txt` or
`LICENSE` file at the top containing the specific license and restrictions
which apply to that software, or
2) It will contain specific license and restriction terms at the top of every
file.
==============================================================================
Legacy LLVM License (https://llvm.org/docs/DeveloperPolicy.html#legacy):
==============================================================================
University of Illinois/NCSA
Open Source License
Copyright (c) 2003-2019 University of Illinois at Urbana-Champaign.
All rights reserved.
Developed by:
LLVM Team
University of Illinois at Urbana-Champaign
http://llvm.org
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal with
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do
so, subject to the following conditions:
* Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimers.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimers in the
documentation and/or other materials provided with the distribution.
* Neither the names of the LLVM Team, University of Illinois at
Urbana-Champaign, nor the names of its contributors may be used to
endorse or promote products derived from this Software without specific
prior written permission.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
CONTRIBUTORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS WITH THE
SOFTWARE.
* Portions of the FFI code for interacting with the native ABI
is derived from the Clay programming language, which carries
the following license.
Copyright (C) 2008-2010 Tachyon Technologies.
All rights reserved.
Redistribution and use in source and binary forms, with
or without modification, are permitted provided that the
following conditions are met:
1. Redistributions of source code must retain the above
copyright notice, this list of conditions and the
following disclaimer.
2. Redistributions in binary form must reproduce the
above copyright notice, this list of conditions and
the following disclaimer in the documentation and/or
other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR
IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
DEVELOPERS AND CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
OF SUCH DAMAGE.
* Portions of internationalization code use code or data from Unicode, which
carry the following license:
UNICODE LICENSE V3
COPYRIGHT AND PERMISSION NOTICE
Copyright © 1991-2024 Unicode, Inc.
NOTICE TO USER: Carefully read the following legal agreement. BY
DOWNLOADING, INSTALLING, COPYING OR OTHERWISE USING DATA FILES, AND/OR
SOFTWARE, YOU UNEQUIVOCALLY ACCEPT, AND AGREE TO BE BOUND BY, ALL OF THE
TERMS AND CONDITIONS OF THIS AGREEMENT. IF YOU DO NOT AGREE, DO NOT
DOWNLOAD, INSTALL, COPY, DISTRIBUTE OR USE THE DATA FILES OR SOFTWARE.
Permission is hereby granted, free of charge, to any person obtaining a
copy of data files and any associated documentation (the "Data Files") or
software and any associated documentation (the "Software") to deal in the
Data Files or Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, and/or sell
copies of the Data Files or Software, and to permit persons to whom the
Data Files or Software are furnished to do so, provided that either (a)
this copyright and permission notice appear with all copies of the Data
Files or Software, or (b) this copyright and permission notice appear in
associated Documentation.
THE DATA FILES AND SOFTWARE ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF
THIRD PARTY RIGHTS.
IN NO EVENT SHALL THE COPYRIGHT HOLDER OR HOLDERS INCLUDED IN THIS NOTICE
BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL INDIRECT OR CONSEQUENTIAL DAMAGES,
OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THE DATA
FILES OR SOFTWARE.
Except as contained in this notice, the name of a copyright holder shall
not be used in advertising or otherwise to promote the sale, use or other
dealings in these Data Files or Software without prior written
authorization of the copyright holder.
+176
View File
@@ -0,0 +1,176 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
+23
View File
@@ -0,0 +1,23 @@
Permission is hereby granted, free of charge, to any
person obtaining a copy of this software and associated
documentation files (the "Software"), to deal in the
Software without restriction, including without
limitation the rights to use, copy, modify, merge,
publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software
is furnished to do so, subject to the following
conditions:
The above copyright notice and this permission notice
shall be included in all copies or substantial portions
of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
+23
View File
@@ -0,0 +1,23 @@
Permission is hereby granted, free of charge, to any
person obtaining a copy of this software and associated
documentation files (the "Software"), to deal in the
Software without restriction, including without
limitation the rights to use, copy, modify, merge,
publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software
is furnished to do so, subject to the following
conditions:
The above copyright notice and this permission notice
shall be included in all copies or substantial portions
of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
+201
View File
@@ -0,0 +1,201 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
+25
View File
@@ -0,0 +1,25 @@
Copyright (c) 2016-2019 Ulrik Sverdrup "bluss" and scopeguard developers
Permission is hereby granted, free of charge, to any
person obtaining a copy of this software and associated
documentation files (the "Software"), to deal in the
Software without restriction, including without
limitation the rights to use, copy, modify, merge,
publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software
is furnished to do so, subject to the following
conditions:
The above copyright notice and this permission notice
shall be included in all copies or substantial portions
of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
+23
View File
@@ -0,0 +1,23 @@
Permission is hereby granted, free of charge, to any
person obtaining a copy of this software and associated
documentation files (the "Software"), to deal in the
Software without restriction, including without
limitation the rights to use, copy, modify, merge,
publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software
is furnished to do so, subject to the following
conditions:
The above copyright notice and this permission notice
shall be included in all copies or substantial portions
of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
+21
View File
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2019 Cloudflare, Inc.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+23
View File
@@ -0,0 +1,23 @@
Permission is hereby granted, free of charge, to any
person obtaining a copy of this software and associated
documentation files (the "Software"), to deal in the
Software without restriction, including without
limitation the rights to use, copy, modify, merge,
publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software
is furnished to do so, subject to the following
conditions:
The above copyright notice and this permission notice
shall be included in all copies or substantial portions
of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
+21
View File
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2025 Alexis Munsayac
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+21
View File
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2025 Alexis Munsayac
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+21
View File
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2016-2025 Ryan Carniato
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+21
View File
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2021 Solid Core Team
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+21
View File
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2020-2021 Ryan Carniato
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+23
View File
@@ -0,0 +1,23 @@
Permission is hereby granted, free of charge, to any
person obtaining a copy of this software and associated
documentation files (the "Software"), to deal in the
Software without restriction, including without
limitation the rights to use, copy, modify, merge,
publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software
is furnished to do so, subject to the following
conditions:
The above copyright notice and this permission notice
shall be included in all copies or substantial portions
of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
+21
View File
@@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright © 2026 Shaun Beautement
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the “Software”),
to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included
in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS
OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+46
View File
@@ -0,0 +1,46 @@
UNICODE, INC. LICENSE AGREEMENT - DATA FILES AND SOFTWARE
See Terms of Use <https://www.unicode.org/copyright.html>
for definitions of Unicode Inc.’s Data Files and Software.
NOTICE TO USER: Carefully read the following legal agreement.
BY DOWNLOADING, INSTALLING, COPYING OR OTHERWISE USING UNICODE INC.'S
DATA FILES ("DATA FILES"), AND/OR SOFTWARE ("SOFTWARE"),
YOU UNEQUIVOCALLY ACCEPT, AND AGREE TO BE BOUND BY, ALL OF THE
TERMS AND CONDITIONS OF THIS AGREEMENT.
IF YOU DO NOT AGREE, DO NOT DOWNLOAD, INSTALL, COPY, DISTRIBUTE OR USE
THE DATA FILES OR SOFTWARE.
COPYRIGHT AND PERMISSION NOTICE
Copyright © 1991-2022 Unicode, Inc. All rights reserved.
Distributed under the Terms of Use in https://www.unicode.org/copyright.html.
Permission is hereby granted, free of charge, to any person obtaining
a copy of the Unicode data files and any associated documentation
(the "Data Files") or Unicode software and any associated documentation
(the "Software") to deal in the Data Files or Software
without restriction, including without limitation the rights to use,
copy, modify, merge, publish, distribute, and/or sell copies of
the Data Files or Software, and to permit persons to whom the Data Files
or Software are furnished to do so, provided that either
(a) this copyright and permission notice appear with all copies
of the Data Files or Software, or
(b) this copyright and permission notice appear in associated
Documentation.
THE DATA FILES AND SOFTWARE ARE PROVIDED "AS IS", WITHOUT WARRANTY OF
ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT OF THIRD PARTY RIGHTS.
IN NO EVENT SHALL THE COPYRIGHT HOLDER OR HOLDERS INCLUDED IN THIS
NOTICE BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL INDIRECT OR CONSEQUENTIAL
DAMAGES, OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE,
DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THE DATA FILES OR SOFTWARE.
Except as contained in this notice, the name of a copyright holder
shall not be used in advertising or otherwise to promote the sale,
use or other dealings in these Data Files or Software without prior
written authorization of the copyright holder.
+23
View File
@@ -0,0 +1,23 @@
Permission is hereby granted, free of charge, to any
person obtaining a copy of this software and associated
documentation files (the "Software"), to deal in the
Software without restriction, including without
limitation the rights to use, copy, modify, merge,
publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software
is furnished to do so, subject to the following
conditions:
The above copyright notice and this permission notice
shall be included in all copies or substantial portions
of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
+7
View File
@@ -0,0 +1,7 @@
Licensed under the Apache License, Version 2.0
<LICENSE-APACHE or
http://www.apache.org/licenses/LICENSE-2.0> or the MIT
license <LICENSE-MIT or http://opensource.org/licenses/MIT>,
at your option. All files in the project carrying such
notice may not be copied, modified, or distributed except
according to those terms.
+25
View File
@@ -0,0 +1,25 @@
Copyright (c) 2015 The Rust Project Developers
Permission is hereby granted, free of charge, to any
person obtaining a copy of this software and associated
documentation files (the "Software"), to deal in the
Software without restriction, including without
limitation the rights to use, copy, modify, merge,
publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software
is furnished to do so, subject to the following
conditions:
The above copyright notice and this permission notice
shall be included in all copies or substantial portions
of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
+308
View File
@@ -0,0 +1,308 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 4
[[package]]
name = "allocator-api2"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c880a97d28a3681c0267bd29cff89621202715b065127cd445fa0f0fe0aa2880"
[[package]]
name = "avt"
version = "0.16.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "156203bcce48a54533c6a718509c22825c1ebbb606e6b313a6f13e8c6097bd13"
dependencies = [
"rgb",
"unicode-width",
]
[[package]]
name = "bumpalo"
version = "3.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0d261e256854913907f67ed06efbc3338dfe6179796deefc1ff763fc1aee5535"
[[package]]
name = "bytemuck"
version = "1.18.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "94bbb0ad554ad961ddc5da507a12a29b14e4ae5bda06b19f575a3e6079d2e2ae"
[[package]]
name = "cfg-if"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "console_error_panic_hook"
version = "0.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a06aeb73f470f66dcdbf7223caeebb85984942f22f1adb2a088cf9668146bbbc"
dependencies = [
"cfg-if",
"wasm-bindgen",
]
[[package]]
name = "itoa"
version = "1.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fad582f4b9e86b6caa621cabeb0963332d92eea04729ab12892c2533951e6440"
[[package]]
name = "js-sys"
version = "0.3.61"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "445dde2150c55e483f3d8416706b97ec8e8237c307e5b7b4b8dd15e6af2a0730"
dependencies = [
"wasm-bindgen",
]
[[package]]
name = "lock_api"
version = "0.4.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965"
dependencies = [
"scopeguard",
]
[[package]]
name = "once_cell"
version = "1.17.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3"
[[package]]
name = "proc-macro2"
version = "1.0.84"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec96c6a92621310b51366f1e28d05ef11489516e93be030060e5fc12024a49d6"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.36"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7"
dependencies = [
"proc-macro2",
]
[[package]]
name = "rgb"
version = "0.8.50"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "57397d16646700483b67d2dd6511d79318f9d057fdbd21a4066aeac8b41d310a"
dependencies = [
"bytemuck",
]
[[package]]
name = "rustversion"
version = "1.0.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d"
[[package]]
name = "ryu"
version = "1.0.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7b4b9743ed687d4b4bcedf9ff5eaa7398495ae14e61cba0a295704edbc7decde"
[[package]]
name = "scoped-tls"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294"
[[package]]
name = "scopeguard"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
[[package]]
name = "serde"
version = "1.0.203"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7253ab4de971e72fb7be983802300c30b5a7f0c2e56fab8abfc6a214307c0094"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde-wasm-bindgen"
version = "0.6.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8302e169f0eddcc139c70f139d19d6467353af16f9fce27e8c30158036a1e16b"
dependencies = [
"js-sys",
"serde",
"wasm-bindgen",
]
[[package]]
name = "serde_derive"
version = "1.0.203"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "500cbc0ebeb6f46627f50f3f5811ccf6bf00643be300b4c3eabc0ef55dc5b5ba"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "serde_json"
version = "1.0.93"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cad406b69c91885b5107daf2c29572f6c8cdb3c66826821e286c533490c0bc76"
dependencies = [
"itoa",
"ryu",
"serde",
]
[[package]]
name = "syn"
version = "2.0.66"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c42f3f41a2de00b01c0aaad383c5a45241efc8b2d1eda5661812fda5f3cdcff5"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "talc"
version = "5.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3081e3f3ad542dc4cd0ae720f752598f8880fd364dc2d031132e3509f673aa23"
dependencies = [
"allocator-api2",
"lock_api",
]
[[package]]
name = "unicode-ident"
version = "1.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "84a22b9f218b40614adcb3f4ff08b703773ad44fa9423e4e0d346d5db86e4ebc"
[[package]]
name = "unicode-width"
version = "0.1.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af"
[[package]]
name = "vt-js"
version = "0.1.0"
dependencies = [
"avt",
"serde",
"serde-wasm-bindgen",
"talc",
"wasm-bindgen",
"wasm-bindgen-test",
]
[[package]]
name = "wasm-bindgen"
version = "0.2.106"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0d759f433fa64a2d763d1340820e46e111a7a5ab75f993d1852d70b03dbb80fd"
dependencies = [
"cfg-if",
"once_cell",
"rustversion",
"serde",
"serde_json",
"wasm-bindgen-macro",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-futures"
version = "0.4.34"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f219e0d211ba40266969f6dbdd90636da12f75bee4fc9d6c23d1260dadb51454"
dependencies = [
"cfg-if",
"js-sys",
"wasm-bindgen",
"web-sys",
]
[[package]]
name = "wasm-bindgen-macro"
version = "0.2.106"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "48cb0d2638f8baedbc542ed444afc0644a29166f1595371af4fecf8ce1e7eeb3"
dependencies = [
"quote",
"wasm-bindgen-macro-support",
]
[[package]]
name = "wasm-bindgen-macro-support"
version = "0.2.106"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cefb59d5cd5f92d9dcf80e4683949f15ca4b511f4ac0a6e14d4e1ac60c6ecd40"
dependencies = [
"bumpalo",
"proc-macro2",
"quote",
"syn",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-shared"
version = "0.2.106"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cbc538057e648b67f72a982e708d485b2efa771e1ac05fec311f9f63e5800db4"
dependencies = [
"unicode-ident",
]
[[package]]
name = "wasm-bindgen-test"
version = "0.3.34"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6db36fc0f9fb209e88fb3642590ae0205bb5a56216dabd963ba15879fe53a30b"
dependencies = [
"console_error_panic_hook",
"js-sys",
"scoped-tls",
"wasm-bindgen",
"wasm-bindgen-futures",
"wasm-bindgen-test-macro",
]
[[package]]
name = "wasm-bindgen-test-macro"
version = "0.3.34"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0734759ae6b3b1717d661fe4f016efcfb9828f5edb4520c18eaee05af3b43be9"
dependencies = [
"proc-macro2",
"quote",
]
[[package]]
name = "web-sys"
version = "0.3.61"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e33b99f4b23ba3eec1a53ac264e35a755f00e966e0065077d6027c0f575b0b97"
dependencies = [
"js-sys",
"wasm-bindgen",
]
@@ -0,0 +1,137 @@
{
"": {
"name": "asciinema-player",
"version": "3.17.0",
"license": "Apache-2.0",
"dependencies": {
"@babel/runtime": "^7.21.0",
"solid-js": "^1.3.0",
"solid-transition-group": "^0.2.3"
},
"devDependencies": {
"@babel/core": "^7.21.3",
"@babel/plugin-transform-runtime": "^7.21.0",
"@babel/preset-env": "^7.20.2",
"@playwright/test": "^1.56.0",
"@rollup/plugin-babel": "^6.0.4",
"@rollup/plugin-commonjs": "^28.0.5",
"@rollup/plugin-node-resolve": "^16.0.1",
"@rollup/plugin-terser": "^1.0.0",
"@wasm-tool/rollup-plugin-rust": "^3.1.4",
"babel-preset-solid": "^1.6.13",
"eslint": "^7.27.0",
"eslint-config-airbnb-base": "^14.2.1",
"eslint-config-prettier": "^8.3.0",
"eslint-plugin-import": "^2.23.3",
"eslint-plugin-jest": "^24.3.6",
"eslint-plugin-prettier": "^3.4.0",
"less": "^4.1.1",
"prettier": "3.2.5",
"rollup": "^4.43.0"
}
},
"node_modules/@babel/runtime": {
"version": "7.27.0",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.27.0.tgz",
"integrity": "sha512-VtPOkrdPHZsKc/clNqyi9WUA8TINkZ4cGk63UUE3u4pmB2k+ZMQRDuIOagv8UVd6j7k0T3+RRIb7beKTebNbcw==",
"license": "MIT",
"dependencies": {
"regenerator-runtime": "^0.14.0"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@solid-primitives/refs": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/@solid-primitives/refs/-/refs-1.1.2.tgz",
"integrity": "sha512-K7tf2thy7L+YJjdqXspXOg5xvNEOH8tgEWsp0+1mQk3obHBRD6hEjYZk7p7FlJphSZImS35je3UfmWuD7MhDfg==",
"license": "MIT",
"dependencies": {
"@solid-primitives/utils": "^6.3.2"
},
"peerDependencies": {
"solid-js": "^1.6.12"
}
},
"node_modules/@solid-primitives/transition-group": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/@solid-primitives/transition-group/-/transition-group-1.1.2.tgz",
"integrity": "sha512-gnHS0OmcdjeoHN9n7Khu8KNrOlRc8a2weETDt2YT6o1zeW/XtUC6Db3Q9pkMU/9cCKdEmN4b0a/41MKAHRhzWA==",
"license": "MIT",
"peerDependencies": {
"solid-js": "^1.6.12"
}
},
"node_modules/@solid-primitives/utils": {
"version": "6.3.2",
"resolved": "https://registry.npmjs.org/@solid-primitives/utils/-/utils-6.3.2.tgz",
"integrity": "sha512-hZ/M/qr25QOCcwDPOHtGjxTD8w2mNyVAYvcfgwzBHq2RwNqHNdDNsMZYap20+ruRwW4A3Cdkczyoz0TSxLCAPQ==",
"license": "MIT",
"peerDependencies": {
"solid-js": "^1.6.12"
}
},
"node_modules/csstype": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.1.tgz",
"integrity": "sha512-DJR/VvkAvSZW9bTouZue2sSxDwdTN92uHjqeKVm+0dAqdfNykRzQ95tay8aXMBAAPpUiq4Qcug2L7neoRh2Egw=="
},
"node_modules/regenerator-runtime": {
"version": "0.14.1",
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz",
"integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==",
"license": "MIT"
},
"node_modules/seroval": {
"version": "1.5.0",
"resolved": "https://registry.npmjs.org/seroval/-/seroval-1.5.0.tgz",
"integrity": "sha512-OE4cvmJ1uSPrKorFIH9/w/Qwuvi/IMcGbv5RKgcJ/zjA/IohDLU6SVaxFN9FwajbP7nsX0dQqMDes1whk3y+yw==",
"license": "MIT",
"peer": true,
"engines": {
"node": ">=10"
}
},
"node_modules/seroval-plugins": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/seroval-plugins/-/seroval-plugins-1.3.0.tgz",
"integrity": "sha512-FFu/UE3uA8L1vj0CXXZo2Nlh10MtYoOs0G//ptwlQMjfPFSeIVYUNy0zewfV8iM0CrOebAfHEG6J3xA9c+lsaQ==",
"license": "MIT",
"engines": {
"node": ">=10"
},
"peerDependencies": {
"seroval": "^1.0"
}
},
"node_modules/solid-js": {
"version": "1.9.6",
"resolved": "https://registry.npmjs.org/solid-js/-/solid-js-1.9.6.tgz",
"integrity": "sha512-PoasAJvLk60hRtOTe9ulvALOdLjjqxuxcGZRolBQqxOnXrBXHGzqMT4ijNhGsDAYdOgEa8ZYaAE94PSldrFSkA==",
"license": "MIT",
"peer": true,
"dependencies": {
"csstype": "^3.1.0",
"seroval": "^1.1.0",
"seroval-plugins": "^1.1.0"
}
},
"node_modules/solid-transition-group": {
"version": "0.2.3",
"resolved": "https://registry.npmjs.org/solid-transition-group/-/solid-transition-group-0.2.3.tgz",
"integrity": "sha512-iB72c9N5Kz9ykRqIXl0lQohOau4t0dhel9kjwFvx81UZJbVwaChMuBuyhiZmK24b8aKEK0w3uFM96ZxzcyZGdg==",
"license": "MIT",
"dependencies": {
"@solid-primitives/refs": "^1.0.5",
"@solid-primitives/transition-group": "^1.0.2"
},
"engines": {
"node": ">=18.0.0",
"pnpm": ">=8.6.0"
},
"peerDependencies": {
"solid-js": "^1.6.12"
}
}
}
+176
View File
@@ -0,0 +1,176 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
+25
View File
@@ -0,0 +1,25 @@
Copyright (c) 2018 Marcin Kulik <[email protected]>
Permission is hereby granted, free of charge, to any
person obtaining a copy of this software and associated
documentation files (the "Software"), to deal in the
Software without restriction, including without
limitation the rights to use, copy, modify, merge,
publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software
is furnished to do so, subject to the following
conditions:
The above copyright notice and this permission notice
shall be included in all copies or substantial portions
of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
+25
View File
@@ -0,0 +1,25 @@
Copyright (c) 2014 Alex Crichton
Permission is hereby granted, free of charge, to any
person obtaining a copy of this software and associated
documentation files (the "Software"), to deal in the
Software without restriction, including without
limitation the rights to use, copy, modify, merge,
publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software
is furnished to do so, subject to the following
conditions:
The above copyright notice and this permission notice
shall be included in all copies or substantial portions
of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
+23313
View File
File diff suppressed because it is too large Load Diff
+28
View File
@@ -0,0 +1,28 @@
{
"name": "wp-asciiplayer",
"version": "1.1.0",
"private": true,
"description": "Native WordPress asciicast block with real playback previews and compatible shortcodes",
"type": "module",
"license": "GPL-3.0-only",
"engines": {
"node": ">=22.22.2"
},
"scripts": {
"build": "wp-scripts build --config webpack.config.cjs && node scripts/vendor.mjs && node scripts/verify-build.mjs",
"start": "wp-scripts start --config webpack.config.cjs",
"test": "node --test tests/js/*.test.js",
"test:php": "php tests/php/run.php",
"test:e2e": "playwright test",
"wp-env": "wp-env",
"package": "node scripts/package.mjs"
},
"dependencies": {
"asciinema-player": "3.17.0"
},
"devDependencies": {
"@playwright/test": "1.64.0",
"@wordpress/env": "11.17.0",
"@wordpress/scripts": "36.1.0"
}
}
+21
View File
@@ -0,0 +1,21 @@
import { defineConfig, devices } from '@playwright/test';
export default defineConfig({
testDir: './tests/e2e',
fullyParallel: false,
workers: 1,
forbidOnly: Boolean(process.env.CI),
retries: process.env.CI ? 1 : 0,
timeout: 90_000,
expect: { timeout: 20_000 },
reporter: [['list'], ['html', { open: 'never' }]],
outputDir: 'test-results',
use: {
baseURL: process.env.WP_BASE_URL || 'http://localhost:8888',
launchOptions: process.env.PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH ? { executablePath: process.env.PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH } : {},
trace: 'retain-on-failure',
screenshot: 'only-on-failure',
video: 'retain-on-failure',
},
projects: [{ name: 'chromium', use: { ...devices['Desktop Chrome'] } }],
});
+40 -31
View File
@@ -1,59 +1,68 @@
=== Asciicast Player === === Asciicast Player ===
Contributors: lamgc Contributors: lamgc
Tags: asciicast, asciiplayer, asciinema, ascii Tags: asciicast, asciiplayer, asciinema, terminal, block
Requires at least: 5.2 Requires at least: 6.3
Tested up to: 6.2
Stable tag: 1.0.1
Requires PHP: 7.4 Requires PHP: 7.4
Stable tag: 1.1.0
License: GPLv3 License: GPLv3
License URI: https://www.gnu.org/licenses/gpl-3.0.html License URI: https://www.gnu.org/licenses/gpl-3.0.html
Play Asciicast files in WordPress posts. Play terminal recordings with a native block, real editor playback previews, and compatible shortcodes.
== Description == == Description ==
You can find the source code for all versions on [Github](https://github.com/LamGC/wp-asciiplayer). Insert an Asciicast Player block, apply a recording-file URL, and play it directly in the editor. Configure theme, speed, looping, controls, sizing, start time and idle-time limit. Existing [asciiplayer] shortcodes remain supported without rewriting your posts.
This plugin helps you insert Asciicast files into the post, better presenting terminal operations to readers. * Locally bundled asciinema-player, JavaScript, CSS and WebAssembly.
* Native block-editor controls and iframe-aware interactive preview.
* Compatible shortcode rendering in posts and pages.
* Explicit editor preview loading and clear loading/failure feedback.
* No account, analytics, remote proxy, custom post type or extra upload permissions.
## Quick start The source and reproducible build instructions are included in the ZIP and available at https://github.com/LamGC/wp-asciiplayer . Third-party licenses are in THIRD-PARTY-NOTICES.md and licenses/.
Add a shortcode to the post and enter the following: = External recordings and privacy =
``` The plugin itself uses no external runtime service. When you choose an external recording URL, the visitor's browser requests that URL directly. The host may receive IP address, browser request metadata and a referrer according to browser/site policy. Applying a URL or loading a preview in the editor makes the same request. Choose a trusted host and consult its privacy policy. Use a same-site HTTPS recording URL to avoid third-party requests. Review terminal output for sensitive data before publishing.
[asciiplayer]https://asciinema.org/a/28307.cast[/asciiplayer]
```
The content of the shortcode is the URL of the ascicast file. == Installation ==
### Options 1. Upload the built wp-asciiplayer.zip using Plugins → Add New → Upload Plugin.
2. Activate Asciicast Player.
3. Insert an Asciicast Player block or retain your existing shortcode.
The AsciiPlayer shortcode supports the options supported by Asciinema-player, such as setting `fit` to `height` and closing the control bar: A GitHub source ZIP needs npm ci and npm run build before installation. See SOURCE.md for packaging instructions.
``` == Frequently Asked Questions ==
[asciiplayer fit="height" controls="false"]https://asciinema.org/a/28307.cast[/asciiplayer]
```
You can find all available options [here](https://www.npmjs.com/package/asciinema-player#options). = Does it change old articles? =
> Currently, only the `logger` option is not supported because it requires an object to be passed in. No. The [asciiplayer] shortcode remains available. You can explicitly convert supported shortcodes to blocks and review them before saving.
#### Style options = Why does a remote recording fail to load? =
For the convenience of customizing player elements, Use a recording-file URL rather than a share-page URL. The remote server must permit cross-origin (CORS) requests. HTTPS sites may block HTTP recordings. Network errors, invalid formats or missing files can also prevent playback. The plugin does not work around these restrictions through a server proxy.
the plugin has added an additional `style` **option**,
which corresponds to the `style` **attribute** of the player container element:
``` = Are player resources loaded from a CDN? =
[asciiplayer style="width: 80%;"]https://asciinema.org/a/28307.cast[/asciiplayer]
``` No. The JavaScript, CSS and WebAssembly ship with the plugin. The previous ap_use_local_resources setting is no longer used.
= Is the Classic Editor supported? =
Existing shortcodes work in the Classic Editor. The new visual controls and inline playback preview are for the block editor.
== Changelog == == Changelog ==
= 1.1.0 =
* Add a native block with typed controls and interactive, iframe-aware preview.
* Preserve shortcode support and normalize historical parameter names and types.
* Escape output, constrain recording URLs, and filter custom styles.
* Bundle player 3.17.0 locally and add reproducible build/install ZIP tooling.
* Improve asset loading for posts, pages and render-time shortcode use.
* Add unit and real WordPress browser integration tests.
= 1.0.0 = = 1.0.0 =
* Set the version attribute for the resource file in WordPress. * Set the version attribute for resource files and add documentation.
* Added some documents.
= 0.1.0 = = 0.1.0 =
* The first available version. * First available version; asciinema-player 3.4.0.
* Update asciinema-player version to 3.4.0
+38
View File
@@ -0,0 +1,38 @@
import { cp, mkdir, mkdtemp, readdir, readFile, rm, stat, utimes } from 'node:fs/promises';
import { execFileSync } from 'node:child_process';
import path from 'node:path';
import os from 'node:os';
const root = path.resolve(import.meta.dirname, '..');
const required = ['build/block.json', 'build/index.js', 'build/index.asset.php', 'build/view.js', 'build/view.asset.php', 'build/player.js', 'build/player.css'];
for (const file of required) await stat(path.join(root, file));
const pkg = JSON.parse(await readFile(path.join(root, 'package.json'), 'utf8'));
const php = await readFile(path.join(root, 'asciicast-player.php'), 'utf8');
const readme = await readFile(path.join(root, 'readme.txt'), 'utf8');
if (!php.includes(`Version: ${pkg.version}`) || !readme.includes(`Stable tag: ${pkg.version}`)) throw new Error('Version headers disagree.');
const temp = await mkdtemp(path.join(os.tmpdir(), 'wp-asciiplayer-package-'));
const target = path.join(temp, 'wp-asciiplayer');
const inputs = ['asciicast-player.php', 'includes', 'build', 'LICENSE', 'licenses', 'THIRD-PARTY-NOTICES.md', 'README.md', 'README-zh.md', 'readme.txt', 'SOURCE.md', 'src', 'block.json', 'package.json', 'package-lock.json', 'webpack.config.cjs', 'babel.config.json', 'scripts'];
try {
await mkdir(target);
for (const file of inputs) await cp(path.join(root, file), path.join(target, file), { recursive: true });
const files = [];
async function walk(dir) {
for (const entry of (await readdir(dir, { withFileTypes: true })).sort((a,b) => a.name.localeCompare(b.name, 'en'))) {
const full = path.join(dir, entry.name);
if (entry.isDirectory()) await walk(full);
else if (entry.isFile()) {
await utimes(full, new Date('2026-01-01T00:00:00Z'), new Date('2026-01-01T00:00:00Z'));
files.push(path.relative(temp, full));
} else throw new Error(`Unexpected non-regular archive entry: ${full}`);
}
}
await walk(target);
await mkdir(path.join(root, 'dist'), { recursive: true });
const zip = path.join(root, 'dist/wp-asciiplayer.zip');
await rm(zip, { force: true });
execFileSync('zip', ['-X', '-q', zip, ...files], { cwd: temp, env: { ...process.env, TZ: 'UTC' } });
console.log(`Created ${zip} (${files.length} files), version ${pkg.version}.`);
} finally {
await rm(temp, { recursive: true, force: true });
}
+15
View File
@@ -0,0 +1,15 @@
import { copyFile, mkdir, readFile } from 'node:fs/promises';
import path from 'node:path';
const root = path.resolve(import.meta.dirname, '..');
const pkg = JSON.parse(await readFile(path.join(root, 'node_modules/asciinema-player/package.json'), 'utf8'));
if (pkg.version !== '3.17.0') throw new Error(`Unexpected player version: ${pkg.version}`);
await mkdir(path.join(root, 'build'), { recursive: true });
for (const [source, destination] of [
['dist/bundle/asciinema-player.min.js', 'player.js'],
['dist/bundle/asciinema-player.css', 'player.css'],
]) {
await copyFile(path.join(root, 'node_modules/asciinema-player', source), path.join(root, 'build', destination));
}
await copyFile(path.join(root, 'block.json'), path.join(root, 'build/block.json'));
console.log(`Bundled local asciinema-player ${pkg.version}; no CDN is used.`);
+17
View File
@@ -0,0 +1,17 @@
import { readFile } from 'node:fs/promises';
import { createHash } from 'node:crypto';
const hash = (data) => createHash('sha256').update(data).digest('hex');
const asset = await readFile('build/index.asset.php', 'utf8');
if (asset.includes('react-jsx-runtime')) throw new Error('Automatic JSX runtime would break the WordPress 6.3 baseline.');
for (const dependency of ['wp-blocks', 'wp-block-editor', 'wp-element', 'wp-compose']) {
if (!asset.includes(`'${dependency}'`)) throw new Error(`Missing editor dependency: ${dependency}`);
}
for (const [destination, original] of [
['build/player.js', 'node_modules/asciinema-player/dist/bundle/asciinema-player.min.js'],
['build/player.css', 'node_modules/asciinema-player/dist/bundle/asciinema-player.css'],
]) {
if (hash(await readFile(destination)) !== hash(await readFile(original))) throw new Error(`Vendor file mismatch: ${destination}`);
}
const metadata = JSON.parse(await readFile('build/block.json', 'utf8'));
if (metadata.apiVersion !== 3 || metadata.name !== 'lamgc/asciiplayer') throw new Error('Unexpected block metadata.');
console.log('Verified WordPress 6.3-compatible JSX dependencies, block metadata, and exact local vendor files.');
+85
View File
@@ -0,0 +1,85 @@
import { InspectorControls, useBlockProps } from '@wordpress/block-editor';
import { Button, Notice, PanelBody, Placeholder, RangeControl, SelectControl, TextControl, ToggleControl } from '@wordpress/components';
import { useEffect, useState } from '@wordpress/element';
import { __ } from '@wordpress/i18n';
import { finiteNumber, normalizeSource, normalizeWidth } from './options.js';
import Preview from './preview.js';
export default function Edit( { attributes, setAttributes } ) {
const { source, theme, speed, loop, controls, fit, startAt, idleTimeLimit, autoPlay, preload, width } = attributes;
const [ draftSource, setDraftSource ] = useState( source );
const [ approvedSource, setApprovedSource ] = useState( '' );
const [ urlError, setUrlError ] = useState( false );
const [ attempt, setAttempt ] = useState( 0 );
useEffect( () => setDraftSource( source ), [ source ] );
const blockProps = useBlockProps( { className: 'asciiplayer-editor' } );
const applySource = ( event ) => {
event.preventDefault();
const next = normalizeSource( draftSource );
setUrlError( ! next );
if ( ! next ) return;
setAttributes( { source: next } );
setDraftSource( next );
setApprovedSource( next );
setAttempt( ( current ) => current + 1 );
};
const themes = [
{ label: __( 'Asciinema', 'wp-asciiplayer' ), value: 'asciinema' },
{ label: __( 'Recording theme (automatic)', 'wp-asciiplayer' ), value: 'auto/asciinema' },
{ label: 'Dracula', value: 'dracula' }, { label: 'Monokai', value: 'monokai' },
{ label: 'Nord', value: 'nord' }, { label: 'Solarized Dark', value: 'solarized-dark' },
{ label: 'Solarized Light', value: 'solarized-light' }, { label: 'Tango', value: 'tango' },
];
if ( ! themes.some( ( option ) => option.value === theme ) ) themes.push( { label: theme, value: theme } );
const widthOptions = [ '100%', '80%', '60%', '50%' ].map( ( value ) => ( { label: value, value } ) );
if ( ! widthOptions.some( ( option ) => option.value === width ) ) widthOptions.push( { label: width, value: width } );
const fitOptions = [
{ label: __( 'Width', 'wp-asciiplayer' ), value: 'width' },
{ label: __( 'Original size', 'wp-asciiplayer' ), value: 'none' },
];
if ( fit === 'height' || fit === 'both' ) fitOptions.push( { label: fit === 'height' ? __( 'Height (legacy)', 'wp-asciiplayer' ) : __( 'Width and height (legacy)', 'wp-asciiplayer' ), value: fit } );
return (
<>
<InspectorControls>
<PanelBody title={ __( 'Playback', 'wp-asciiplayer' ) }>
<RangeControl label={ __( 'Playback speed', 'wp-asciiplayer' ) } value={ speed } min={ 0.25 } max={ Math.max( 8, speed ) } step={ 0.25 } onChange={ ( value ) => setAttributes( { speed: finiteNumber( value, 1, 0.01, 100 ) } ) } />
<ToggleControl label={ __( 'Loop playback', 'wp-asciiplayer' ) } checked={ loop } onChange={ ( value ) => setAttributes( { loop: value } ) } />
<ToggleControl label={ __( 'Autoplay on published page', 'wp-asciiplayer' ) } help={ __( 'The editor preview always waits for you to press play.', 'wp-asciiplayer' ) } checked={ autoPlay } onChange={ ( value ) => setAttributes( { autoPlay: value } ) } />
<ToggleControl label={ __( 'Preload on published page', 'wp-asciiplayer' ) } help={ __( 'Loads the recording before the visitor presses play. The editor always preloads after you apply the URL.', 'wp-asciiplayer' ) } checked={ preload } onChange={ ( value ) => setAttributes( { preload: value } ) } />
<TextControl label={ __( 'Start at (seconds)', 'wp-asciiplayer' ) } type="number" min="0" step="0.1" value={ startAt } onChange={ ( value ) => setAttributes( { startAt: finiteNumber( value, 0 ) } ) } />
<TextControl label={ __( 'Idle time limit (seconds)', 'wp-asciiplayer' ) } help={ __( 'Shorten long pauses. Use 0 to keep the recording’s default.', 'wp-asciiplayer' ) } type="number" min="0" step="0.1" value={ idleTimeLimit } onChange={ ( value ) => setAttributes( { idleTimeLimit: finiteNumber( value, 0 ) } ) } />
</PanelBody>
<PanelBody title={ __( 'Appearance', 'wp-asciiplayer' ) }>
<SelectControl label={ __( 'Theme', 'wp-asciiplayer' ) } value={ theme } options={ themes } onChange={ ( value ) => setAttributes( { theme: value } ) } />
<SelectControl label={ __( 'Player controls', 'wp-asciiplayer' ) } value={ controls } options={ [
{ label: __( 'Show on interaction', 'wp-asciiplayer' ), value: 'auto' },
{ label: __( 'Always show', 'wp-asciiplayer' ), value: 'always' },
{ label: __( 'Hide', 'wp-asciiplayer' ), value: 'never' },
] } onChange={ ( value ) => setAttributes( { controls: value } ) } />
<SelectControl label={ __( 'Fit', 'wp-asciiplayer' ) } value={ fit } options={ fitOptions } help={ [ 'height', 'both' ].includes( fit ) ? __( 'Height-based fitting requires a fixed container height in your site CSS.', 'wp-asciiplayer' ) : undefined } onChange={ ( value ) => setAttributes( { fit: value } ) } />
<SelectControl label={ __( 'Player width', 'wp-asciiplayer' ) } value={ width } options={ widthOptions } onChange={ ( value ) => setAttributes( { width: value } ) } />
{ attributes.legacyStyle && <p>{ __( 'The custom container style from your shortcode is preserved on the published page.', 'wp-asciiplayer' ) }</p> }
</PanelBody>
</InspectorControls>
<div { ...blockProps }>
<div className="asciiplayer-editor__source">
<form onSubmit={ applySource }>
<TextControl label={ __( 'Recording URL', 'wp-asciiplayer' ) } placeholder="https://example.com/recording.cast" value={ draftSource } onChange={ ( value ) => { setDraftSource( value ); setUrlError( false ); } } autoComplete="off" />
<Button type="submit" variant="primary">{ __( 'Apply URL', 'wp-asciiplayer' ) }</Button>
</form>
<p className="asciiplayer-editor__privacy">{ __( 'Applying a URL loads the recording directly from that server, which receives your browser’s request. Published players contact the same server. Use a direct .cast URL; external servers must allow CORS.', 'wp-asciiplayer' ) }</p>
{ urlError && <Notice status="error" isDismissible={ false }>{ __( 'Enter an HTTP(S) recording URL or a path starting with a single /.', 'wp-asciiplayer' ) }</Notice> }
</div>
{ approvedSource && approvedSource === source ? (
<div style={ { width: normalizeWidth( width ), maxWidth: '100%' } }>
<Preview source={ source } attributes={ attributes } attempt={ attempt } onRetry={ () => setAttempt( ( current ) => current + 1 ) } />
</div>
) : (
<Placeholder icon="editor-code" label={ __( 'Asciicast Player', 'wp-asciiplayer' ) } instructions={ source ? __( 'Load the saved recording to play and adjust it here.', 'wp-asciiplayer' ) : __( 'Paste a terminal recording URL above, then apply it to see an interactive preview.', 'wp-asciiplayer' ) }>
{ source && <Button variant="secondary" onClick={ () => { setApprovedSource( source ); setAttempt( ( current ) => current + 1 ); } }>{ __( 'Load preview', 'wp-asciiplayer' ) }</Button> }
</Placeholder>
) }
</div>
</>
);
}
+50
View File
@@ -0,0 +1,50 @@
.asciiplayer-editor {
border: 1px solid #ddd;
border-radius: 4px;
padding: 16px;
}
.asciiplayer-editor__source form {
display: flex;
align-items: flex-end;
gap: 12px;
}
.asciiplayer-editor__source .components-base-control {
flex: 1;
margin-bottom: 0;
}
.asciiplayer-editor__source .components-base-control__field {
margin-bottom: 0;
}
.asciiplayer-editor__privacy,
.asciiplayer-editor__hint {
font-size: 12px;
line-height: 1.5;
color: #50575e;
}
.asciiplayer-editor__preview {
margin-top: 16px;
}
.asciiplayer-editor__player {
min-width: 0;
text-align: left;
}
.asciiplayer-editor__status {
display: flex;
align-items: center;
gap: 8px;
font-size: 13px;
}
@media (max-width: 480px) {
.asciiplayer-editor__source form {
align-items: stretch;
flex-direction: column;
}
}
+42
View File
@@ -0,0 +1,42 @@
import { createBlock, registerBlockType } from '@wordpress/blocks';
import { next } from '@wordpress/shortcode';
import { decodeEntities } from '@wordpress/html-entities';
import metadata from '../block.json';
import Edit from './edit.js';
import { attributesFromShortcode, canTransformShortcode, normalizeSource } from './options.js';
import './style.css';
import './editor.css';
function matchShortcodeBlock( text = '' ) {
const match = next( 'asciiplayer', text );
return match && text.trim() === match.content.trim() && canTransformShortcode( match.shortcode.attrs ) && normalizeSource( decodeEntities( match.shortcode.content ) ) ? match : null;
}
registerBlockType( metadata.name, {
...metadata,
edit: Edit,
save: () => null,
transforms: {
from: [
{
type: 'shortcode',
tag: 'asciiplayer',
isMatch: canTransformShortcode,
transform: ( { named }, match ) => {
const source = decodeEntities( match.shortcode.content || '' );
if ( ! normalizeSource( source ) ) return createBlock( 'core/shortcode', { text: match.content } );
return createBlock( metadata.name, attributesFromShortcode( named, source ) );
},
},
{
type: 'block',
blocks: [ 'core/shortcode' ],
isMatch: ( { text } ) => Boolean( matchShortcodeBlock( text ) ),
transform: ( { text } ) => {
const match = matchShortcodeBlock( text );
return createBlock( metadata.name, attributesFromShortcode( match.shortcode.attrs.named, decodeEntities( match.shortcode.content ) ) );
},
},
],
},
} );
+139
View File
@@ -0,0 +1,139 @@
/** Shared, side-effect-free validation for the editor and front-end adapter. */
export const DEFAULTS = Object.freeze( {
theme: 'asciinema', speed: 1, loop: false, controls: 'auto', fit: 'width',
startAt: 0, idleTimeLimit: 0, autoPlay: false, preload: true, width: '100%',
} );
export function booleanValue( value, fallback = false ) {
if ( typeof value === 'boolean' ) return value;
if ( value === 1 || /^(true|1|yes|on)$/i.test( String( value ).trim() ) ) return true;
if ( value === 0 || /^(false|0|no|off)$/i.test( String( value ).trim() ) ) return false;
return fallback;
}
export function finiteNumber( value, fallback, min = 0, max = Number.MAX_SAFE_INTEGER ) {
if ( typeof value !== 'number' && typeof value !== 'string' ) return fallback;
if ( typeof value === 'string' && ! /^[+-]?(?:\d+(?:\.\d*)?|\.\d+)(?:e[+-]?\d+)?$/i.test( value.trim() ) ) return fallback;
const number = Number( value );
return Number.isFinite( number ) && number >= min && number <= max ? number : fallback;
}
/** Accept explicit web URLs and site-root-relative URLs; never executable schemes. */
export function normalizeSource( input ) {
if ( typeof input !== 'string' ) return '';
const source = input.trim();
if ( ! source || /[\u0000-\u0020\u007f<>"\\]/.test( source ) ) return '';
if ( /^\/(?!\/)/.test( source ) ) return source;
// URL() repairs missing/extra authority slashes; PHP deliberately does not.
if ( ! /^https?:\/\/[^/]/i.test( source ) ) return '';
try {
const url = new URL( source );
return [ 'http:', 'https:' ].includes( url.protocol ) && ! url.username && ! url.password ? source : '';
} catch {
return '';
}
}
export function normalizeWidth( value ) {
return typeof value === 'string' && /^(?:0|(?:\d+(?:\.\d+)?|\.\d+)(?:px|%|em|rem|vw|vh|vmin|vmax|ch))$/.test( value.trim() )
? value.trim() : DEFAULTS.width;
}
/** A bounded editor frame for old height-fitting shortcodes; never persisted. */
export function previewHeight( attributes ) {
if ( ! [ 'height', 'both' ].includes( attributes.fit ) ) return undefined;
let height = '320px';
for ( const declaration of String( attributes.legacyStyle || '' ).split( ';' ) ) {
const match = declaration.match( /^\s*height\s*:\s*((?:\d+(?:\.\d+)?|\.\d+)(?:px|em|rem|vh|vw|vmin|vmax|ch))\s*$/i );
if ( match ) height = match[ 1 ];
}
return height;
}
export function normalizeControls( value ) {
if ( typeof value === 'string' ) value = value.trim().toLowerCase();
if ( value === 'always' || booleanValue( value, null ) === true ) return true;
if ( value === 'never' || booleanValue( value, null ) === false ) return false;
return 'auto';
}
export function secondsValue( value ) {
if ( typeof value === 'string' && /^\d+(?::[0-5]?\d){1,2}(?:\.\d+)?$/.test( value.trim() ) ) {
return value.trim().split( ':' ).reduce( ( total, part ) => total * 60 + Number( part ), 0 );
}
return finiteNumber( value, 0 );
}
export function normalizePlayerOptions( attributes = {}, { preview = false } = {} ) {
const fit = typeof attributes.fit === 'string' ? attributes.fit.trim().toLowerCase() : attributes.fit;
const fontSize = typeof attributes.terminalFontSize === 'string' ? attributes.terminalFontSize.trim().toLowerCase() : '';
const fontFamily = typeof attributes.terminalFontFamily === 'string' ? attributes.terminalFontFamily.trim() : '';
const options = {
theme: typeof attributes.theme === 'string' && /^(?:auto\/)?[\w-]{1,64}$/.test( attributes.theme ) ? attributes.theme : DEFAULTS.theme,
speed: finiteNumber( attributes.speed, 1, 0.01, 100 ),
loop: booleanValue( attributes.loop ),
controls: normalizeControls( attributes.controls ),
fit: [ 'width', 'height', 'both', 'none' ].includes( fit ) ? fit : booleanValue( fit, null ) === false ? 'none' : 'width',
startAt: secondsValue( attributes.startAt ),
autoPlay: preview ? false : booleanValue( attributes.autoPlay ?? attributes.autoplay ),
preload: preview ? true : booleanValue( attributes.preload, true ),
};
// Shortcodes may intentionally request a finite number of loops.
if ( typeof attributes.loop === 'number' && attributes.loop > 0 && Number.isInteger( attributes.loop ) ) options.loop = attributes.loop;
const idle = finiteNumber( attributes.idleTimeLimit, 0 );
if ( idle > 0 ) options.idleTimeLimit = idle;
for ( const key of [ 'cols', 'rows' ] ) {
const number = finiteNumber( attributes[ key ], 0, 1, 1000 );
if ( Number.isInteger( number ) && number > 0 ) options[ key ] = number;
}
const lineHeight = finiteNumber( attributes.terminalLineHeight, 0, 0.1, 10 );
if ( lineHeight ) options.terminalLineHeight = lineHeight;
if ( fontFamily.length <= 300 && /^[a-zA-Z0-9 _.,'"-]+$/.test( fontFamily ) ) options.terminalFontFamily = fontFamily;
if ( /^(small|medium|big|0|(?:\d+(?:\.\d+)?|\.\d+)(?:px|%|em|rem|vw|vh|vmin|vmax|ch))$/.test( fontSize ) ) options.terminalFontSize = fontSize;
if ( typeof attributes.poster === 'string' && attributes.poster.length <= 32768 && /^(npt:\d+(?::[0-5]?\d){0,2}(?:\.\d+)?|data:text\/plain,[\s\S]*)$/.test( attributes.poster ) ) options.poster = attributes.poster;
return options;
}
const SHORTCODE_KEYS = {
theme: 'theme', speed: 'speed', loop: 'loop', controls: 'controls', fit: 'fit',
startat: 'startAt', idletimelimit: 'idleTimeLimit', autoplay: 'autoPlay', preload: 'preload',
cols: 'cols', rows: 'rows', terminalfontfamily: 'terminalFontFamily',
terminalfontsize: 'terminalFontSize', terminallineheight: 'terminalLineHeight', poster: 'poster', style: 'legacyStyle',
fontsize: 'terminalFontSize', fontfamily: 'terminalFontFamily', lineheight: 'terminalLineHeight', width: 'width',
};
/** Unknown legacy options stay in their original shortcode rather than being lost. */
export function canTransformShortcode( { named = {}, numeric = [] } = {} ) {
return numeric.length === 0 && Object.entries( named ).every( ( [ key, value ] ) => {
const lower = key.toLowerCase();
if ( ! Object.prototype.hasOwnProperty.call( SHORTCODE_KEYS, lower ) ) return false;
if ( lower === 'loop' && booleanValue( value, null ) === null ) return false;
if ( lower === 'style' ) {
const widths = String( value ).matchAll( /(?:^|;)\s*width\s*:\s*([^;]+)/gi );
for ( const match of widths ) if ( normalizeWidth( match[ 1 ] ) !== match[ 1 ].trim() ) return false;
}
return true;
} );
}
export function attributesFromShortcode( named = {}, content = '' ) {
const values = {};
for ( const [ key, value ] of Object.entries( named ) ) {
const target = SHORTCODE_KEYS[ key.toLowerCase() ];
if ( target ) values[ target ] = value;
}
if ( values.fit === 'false' || values.fit === false ) values.fit = 'none';
const normalized = normalizePlayerOptions( values );
const attributes = {
...normalized,
source: normalizeSource( content ),
controls: normalized.controls === true ? 'always' : normalized.controls === false ? 'never' : 'auto',
idleTimeLimit: normalized.idleTimeLimit ?? 0,
};
if ( values.legacyStyle ) {
attributes.legacyStyle = values.legacyStyle;
for ( const match of String( values.legacyStyle ).matchAll( /(?:^|;)\s*width\s*:\s*([^;]+)/gi ) ) attributes.width = normalizeWidth( match[ 1 ] );
}
if ( values.width ) attributes.width = normalizeWidth( values.width );
return attributes;
}
+103
View File
@@ -0,0 +1,103 @@
/**
* asciinema uses realm-global document/HTMLButtonElement internally. Execute its
* bundled script in the canvas document, not in the parent React document.
*/
const pendingDocuments = new WeakMap();
const pendingStyles = new WeakMap();
function findAsset( document, selector, property ) {
return document.querySelector( selector )?.[ property ];
}
export function playerAssets( hostWindow = window ) {
const settings = hostWindow.wpAsciiplayerSettings || {};
return {
script: settings.playerScriptUrl || findAsset( hostWindow.document, '#lamgc-asciiplayer-player-js', 'src' ),
style: settings.playerStyleUrl || findAsset( hostWindow.document, '#lamgc-asciiplayer-player-style-css', 'href' ),
};
}
function ensureStyle( document, url ) {
if ( ! url ) return Promise.reject( new Error( 'The local player stylesheet is unavailable.' ) );
let link = Array.from( document.querySelectorAll( 'link[rel="stylesheet"]' ) ).find( ( item ) => item.href === url && ( item.sheet || ! item.dataset.asciiplayerFailed ) );
if ( link?.sheet ) return Promise.resolve();
if ( pendingStyles.has( document ) ) return pendingStyles.get( document );
const created = ! link;
if ( created ) {
link = document.createElement( 'link' );
link.rel = 'stylesheet';
link.href = url;
link.dataset.asciiplayerAsset = 'style';
}
const promise = new Promise( ( resolve, reject ) => {
let timer;
const cleanup = () => {
document.defaultView.clearTimeout( timer );
link.removeEventListener( 'load', loaded );
link.removeEventListener( 'error', failed );
};
const loaded = () => { cleanup(); resolve(); };
const failed = () => {
cleanup();
link.dataset.asciiplayerFailed = 'true';
if ( created ) link.remove();
reject( new Error( 'The local player stylesheet could not be loaded.' ) );
};
link.addEventListener( 'load', loaded );
link.addEventListener( 'error', failed );
timer = document.defaultView.setTimeout( failed, 20000 );
if ( created ) document.head.appendChild( link );
if ( link.sheet ) loaded();
} );
pendingStyles.set( document, promise );
promise.then( () => pendingStyles.delete( document ), () => pendingStyles.delete( document ) );
return promise;
}
export async function playerForDocument( document, assets ) {
const canvasWindow = document.defaultView;
if ( ! canvasWindow ) return Promise.reject( new Error( 'The editor canvas is unavailable.' ) );
// Player metrics are measured at create(), so CSS must finish first.
await ensureStyle( document, assets.style );
if ( typeof canvasWindow.AsciinemaPlayer?.create === 'function' ) return Promise.resolve( canvasWindow.AsciinemaPlayer );
if ( pendingDocuments.has( document ) ) return pendingDocuments.get( document );
if ( ! assets.script ) return Promise.reject( new Error( 'The local player script is unavailable.' ) );
const pending = new Promise( ( resolve, reject ) => {
let script = Array.from( document.scripts ).find( ( item ) => item.src === assets.script && ! item.dataset.asciiplayerFailed );
const created = ! script;
if ( created ) {
script = document.createElement( 'script' );
script.src = assets.script;
script.async = true;
script.dataset.asciiplayerAsset = 'script';
}
let timer;
const cleanup = () => {
canvasWindow.clearTimeout( timer );
script.removeEventListener( 'load', loaded );
script.removeEventListener( 'error', failed );
};
const failed = () => {
cleanup();
script.dataset.asciiplayerFailed = 'true';
// Only remove a script we own. A retry can then load a fresh copy.
if ( created ) script.remove();
reject( new Error( 'The local player script could not be loaded.' ) );
};
const loaded = () => {
if ( typeof canvasWindow.AsciinemaPlayer?.create !== 'function' ) return failed();
cleanup();
resolve( canvasWindow.AsciinemaPlayer );
};
script.addEventListener( 'load', loaded );
script.addEventListener( 'error', failed );
timer = canvasWindow.setTimeout( failed, 20000 );
if ( created ) document.head.appendChild( script );
// Covers a script that finished between the global check and listener setup.
if ( typeof canvasWindow.AsciinemaPlayer?.create === 'function' ) loaded();
} );
pendingDocuments.set( document, pending );
pending.catch( () => pendingDocuments.delete( document ) );
return pending;
}
+68
View File
@@ -0,0 +1,68 @@
import { Button, Notice, Spinner } from '@wordpress/components';
import { useRefEffect } from '@wordpress/compose';
import { useState } from '@wordpress/element';
import { __ } from '@wordpress/i18n';
import { normalizePlayerOptions, previewHeight } from './options.js';
import { playerAssets, playerForDocument } from './player-bridge.js';
export default function Preview( { source, attributes, attempt, onRetry } ) {
const [ status, setStatus ] = useState( 'loading' );
const [ problem, setProblem ] = useState( '' );
const optionsJSON = JSON.stringify( normalizePlayerOptions( attributes, { preview: true } ) );
const playerRef = useRefEffect( ( container ) => {
let disposed = false;
let player;
let readinessTimer;
let deadline;
const canvasWindow = container.ownerDocument.defaultView;
const request = new canvasWindow.AbortController();
setStatus( 'loading' );
setProblem( '' );
const fail = ( message ) => {
if ( disposed ) return;
canvasWindow.clearInterval( readinessTimer );
canvasWindow.clearTimeout( deadline );
setProblem( message );
setStatus( 'error' );
};
const ready = () => {
if ( disposed ) return;
canvasWindow.clearInterval( readinessTimer );
canvasWindow.clearTimeout( deadline );
setStatus( 'ready' );
};
playerForDocument( container.ownerDocument, playerAssets() ).then( ( api ) => {
if ( disposed ) return;
player = api.create( { url: source, fetchOpts: { signal: request.signal } }, container, JSON.parse( optionsJSON ) );
player.addEventListener( 'error', () => fail( __( 'Unable to load the recording. Check the URL, file format, and the source server’s CORS permissions.', 'wp-asciiplayer' ) ) );
player.addEventListener( 'playing', ready );
// getDuration is public API; a mounted wrapper alone does not mean the
// recording loaded successfully. Works across supported player versions.
const checkReady = () => {
const duration = player.getDuration();
if ( typeof duration === 'number' && Number.isFinite( duration ) ) ready();
};
readinessTimer = canvasWindow.setInterval( checkReady, 150 );
deadline = canvasWindow.setTimeout( () => fail( __( 'The recording is taking too long to load. Check your connection and try again.', 'wp-asciiplayer' ) ), 30000 );
checkReady();
} ).catch( () => fail( __( 'The local player could not start. Try again, or check that the plugin’s built assets are installed.', 'wp-asciiplayer' ) ) );
return () => {
disposed = true;
request.abort();
canvasWindow.clearInterval( readinessTimer );
canvasWindow.clearTimeout( deadline );
if ( player ) player.dispose();
container.replaceChildren();
};
}, [ source, optionsJSON, attempt ] );
return (
<div className="asciiplayer-editor__preview" data-preview-status={ status }>
{ status === 'loading' && <div className="asciiplayer-editor__status" role="status"><Spinner />{ __( 'Loading recording…', 'wp-asciiplayer' ) }</div> }
{ status === 'error' && <Notice status="error" isDismissible={ false }><p>{ problem }</p><Button variant="secondary" onClick={ onRetry }>{ __( 'Retry preview', 'wp-asciiplayer' ) }</Button></Notice> }
<div className="asciiplayer-editor__player" style={ { height: previewHeight( attributes ) } } ref={ playerRef } aria-label={ __( 'Interactive terminal recording preview', 'wp-asciiplayer' ) } />
{ [ 'height', 'both' ].includes( attributes.fit ) && <p className="asciiplayer-editor__hint">{ __( 'Height-based previews use the shortcode’s fixed height when available, or a 320px editor frame. Published height still comes from your container style.', 'wp-asciiplayer' ) }</p> }
<p className="asciiplayer-editor__hint">{ __( 'Use the player controls to play, pause, and seek. Autoplay is disabled only in the editor.', 'wp-asciiplayer' ) }</p>
</div>
);
}
+15
View File
@@ -0,0 +1,15 @@
.wp-block-lamgc-asciiplayer,
.asciiplayer-container {
box-sizing: border-box;
max-width: 100%;
margin-inline: auto;
}
.asciiplayer-container .ap-wrapper {
text-align: left;
}
.asciiplayer-container[data-ap-status="error"] .asciiplayer-error {
padding: 1em;
border: 1px solid #b32d2e;
}
+66
View File
@@ -0,0 +1,66 @@
import { __ } from '@wordpress/i18n';
import { normalizePlayerOptions, normalizeSource } from './options.js';
const instances = new WeakMap();
function showError( container ) {
container.dataset.apStatus = 'error';
if ( container.querySelector( '.asciiplayer-error' ) ) return;
const notice = container.ownerDocument.createElement( 'p' );
notice.className = 'asciiplayer-error';
notice.setAttribute( 'role', 'status' );
notice.textContent = __( 'Unable to load this terminal recording. The file may be unavailable, invalid, or blocked by the source server’s CORS policy.', 'wp-asciiplayer' );
container.appendChild( notice );
}
/** One explicit instance per front-end container; never scan an editor canvas. */
export function mountPlayers( root = document ) {
const document = root.ownerDocument || root;
if ( document.body?.classList.contains( 'wp-admin' ) || document.querySelector( '.editor-styles-wrapper, .block-editor-block-list__layout' ) ) return;
const api = document.defaultView?.AsciinemaPlayer;
if ( typeof api?.create !== 'function' ) return;
root.querySelectorAll( '.asciiplayer-container' ).forEach( ( container ) => {
if ( instances.has( container ) ) return;
const source = normalizeSource( container.dataset.apSrc );
if ( ! source ) return showError( container );
let options;
try {
// New markup uses escaped plain JSON. The legacy adapter used URL-encoded
// JSON; accepting it also lets cached old pages use the new local player.
const serialized = container.dataset.apOptions;
options = serialized ? JSON.parse( serialized ) : JSON.parse( decodeURIComponent( container.dataset.apOpts || '{}' ) );
if ( ! options || Array.isArray( options ) || typeof options !== 'object' ) throw new Error( 'Invalid player options.' );
options = normalizePlayerOptions( options );
} catch {
showError( container );
return;
}
try {
container.dataset.apStatus = 'loading';
const player = api.create( source, container, options );
instances.set( container, player );
player.addEventListener( 'error', () => showError( container ) );
player.addEventListener( 'playing', () => { container.dataset.apStatus = 'ready'; } );
// The upstream player owns loading/error/controls; readiness is confirmed
// by its parsed duration rather than merely by a mounted DOM wrapper.
if ( options.preload || options.autoPlay || options.poster?.startsWith( 'npt:' ) ) {
let ticks = 0;
const timer = document.defaultView.setInterval( () => {
const duration = player.getDuration();
if ( typeof duration === 'number' && Number.isFinite( duration ) ) container.dataset.apStatus = 'ready';
if ( ! container.isConnected || container.dataset.apStatus !== 'loading' || ++ticks > 200 ) document.defaultView.clearInterval( timer );
}, 150 );
} else {
container.dataset.apStatus = 'idle';
}
} catch {
showError( container );
}
} );
}
if ( document.readyState === 'loading' ) {
document.addEventListener( 'DOMContentLoaded', () => mountPlayers(), { once: true } );
} else {
mountPlayers();
}
+67
View File
@@ -0,0 +1,67 @@
# Real WordPress browser integration
These tests run Chromium against WordPress itself. They do not replace WordPress,
PHP rendering, the editor iframe, the media parser, or the player with static mocks.
Recording files are small local fixtures; playback does not depend on a public CDN.
## Run locally (Docker)
Use Node 24 and Docker, then run from the repository root:
```sh
npm ci
npm run build
npm test
npm run package
npm run wp-env -- start
npm run wp-env -- run cli wp plugin install /var/www/html/wp-content/asciiplayer-packages/wp-asciiplayer.zip --activate --force
npx --no-install playwright install chromium
npm run test:e2e
npm run wp-env -- stop
```
The default site is http://localhost:8888 with wp-env's development-only account
`admin` / `password`. Never use these credentials on a public site. `WP_BASE_URL`,
`WP_USERNAME` and `WP_PASSWORD` can override browser-test settings. Set
`PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH` to use an already-installed Chromium binary.
The plugin's source tree is deliberately not mounted as an active plugin in the
committed wp-env configuration. The build ZIP is installed into WordPress through
WP-CLI, so missing package files cause a real integration failure. Fixtures are
mapped under uploads; the package directory is mapped separately for WP-CLI.
## Coverage
- A real iframe editor loads and plays the real local player after applying a URL
- The iframe player actually plays; Space pauses/resumes it and resizing stays inside the canvas
- An inspector speed/theme change rebuilds the player without duplicate instances
- Duplicate, delete, undo, save, reload, and front-end playback preserve the block
- Posts and Pages work, including a mobile-width published Page
- Multiple legacy shortcodes preserve false booleans, hidden controls, and camelCase options
- HTTP 404, malformed asciicast, and an actual no-CORS second origin recover after a valid URL
- Actual terminal output proves successful front-end parsing/playback
- Editor and front-end requests must not load plugin assets from jsDelivr
The preview intentionally requires an Apply URL / Load preview action for each
editing session before contacting the recording server. Tests respect that gate.
## CI evidence and limits
GitHub Actions checks the minimum WordPress 6.3 / PHP 7.4 combination and the latest
stable WordPress / PHP 8.2. It logs the actual core version, installs the ZIP, and
retains screenshots, failure traces/video, WordPress logs, and the package. A
separate job checks PHP 7.4 and 8.3 syntax and focused regression tests.
Latest WordPress also runs Plugin Check and retains its report as an advisory
step. Advisory output is not a passing directory review. Review any findings
before submission, and do not interpret a green browser job as marketplace
approval. Tests do not cover every browser, theme, or third-party plugin.
WordPress Playground is also usable for local smoke tests when Docker is absent,
but its SQLite/PHP-WASM runtime is additional evidence, not a substitute for the
Docker/MySQL matrix above.
References:
- https://developer.wordpress.org/block-editor/reference-guides/packages/packages-env/
- https://developer.wordpress.org/block-editor/reference-guides/block-api/block-api-versions/block-migration-for-iframe-editor-compatibility/
- https://github.com/WordPress/plugin-check/blob/trunk/docs/CLI.md
+261
View File
@@ -0,0 +1,261 @@
import { test, expect } from '@playwright/test';
import { createServer } from 'node:http';
import { readFile } from 'node:fs/promises';
const blockName = 'lamgc/asciiplayer';
const fixturePath = '/wp-content/uploads/asciiplayer-fixtures/hello.cast';
async function logIn(page) {
await page.goto('/wp-login.php');
await page.locator('#user_login').fill(process.env.WP_USERNAME || 'admin');
await page.locator('#user_pass').fill(process.env.WP_PASSWORD || 'password');
await page.locator('#wp-submit').click();
await expect(page).toHaveURL(/\/wp-admin\//);
}
async function openEditor(page, postType = 'post') {
await page.goto(`/wp-admin/post-new.php?post_type=${postType}`);
await page.waitForFunction((name) => Boolean(
window.wp?.blocks?.getBlockType(name) &&
window.wp?.data?.select('core/editor')?.getCurrentPostId()
), blockName);
await page.evaluate(() => {
for (const scope of ['core/edit-post', 'core/editor']) {
window.wp.data.dispatch('core/preferences').set(scope, 'welcomeGuide', false);
}
});
// A block-pattern picker can appear on a new Page in some core releases.
const closeDialog = page.getByRole('dialog').getByRole('button', { name: /close/i });
if (await closeDialog.count()) await closeDialog.first().click();
await expect(page.locator('iframe[name="editor-canvas"]')).toBeVisible();
}
async function insertPlayer(page, attributes = {}) {
return page.evaluate(({ name, attrs }) => {
const block = window.wp.blocks.createBlock(name, attrs);
window.wp.data.dispatch('core/block-editor').insertBlocks(block);
window.wp.data.dispatch('core/block-editor').selectBlock(block.clientId);
return block.clientId;
}, { name: blockName, attrs: attributes });
}
function canvas(page) {
return page.frameLocator('iframe[name="editor-canvas"]');
}
function playerBlock(page, clientId) {
return canvas(page).locator(`[data-block="${clientId}"]`);
}
async function readyPreview(block) {
await expect(block.locator('.asciiplayer-editor__preview')).toHaveAttribute('data-preview-status', 'ready');
await expect(block.locator('.asciiplayer-editor__player .ap-wrapper')).toHaveCount(1);
await expect(block.locator('.ap-wrapper')).toBeVisible();
}
async function loadPreview(block) {
await expect(block).toBeVisible();
const load = block.getByRole('button', { name: 'Load preview', exact: true });
if (await load.count()) await load.click();
await readyPreview(block);
}
async function publish(page, title) {
return page.evaluate(async (postTitle) => {
const editor = window.wp.data.dispatch('core/editor');
editor.editPost({ title: postTitle, status: 'publish' });
await editor.savePost();
const state = window.wp.data.select('core/editor');
if (state.didPostSaveRequestFail()) throw new Error('WordPress could not save the test post.');
return { id: state.getCurrentPostId(), url: state.getPermalink() };
}, title);
}
async function createContent(page, postType, content) {
return page.evaluate(async ({ type, html }) => window.wp.apiFetch({
path: `/wp/v2/${type === 'page' ? 'pages' : 'posts'}`,
method: 'POST',
data: { title: `Asciiplayer integration ${Date.now()}`, status: 'publish', content: html },
}), { type: postType, html: content });
}
async function expectFrontendPlayer(container) {
await expect(container.locator('.ap-wrapper')).toBeVisible();
// This starts actual playback and checks parsed terminal output. Merely
// finding the player's outer DOM would miss fetch/CORS/parser failures.
const start = container.locator('.ap-overlay-start .ap-play-button');
if (await start.isVisible()) await start.click();
await expect(container).toContainText('Hello from wp-asciiplayer');
}
async function screenshot(page, testInfo, name) {
const path = testInfo.outputPath(`${name}.png`);
await page.screenshot({ path, fullPage: true });
await testInfo.attach(name, { path, contentType: 'image/png' });
}
test.beforeEach(async ({ page }) => {
await logIn(page);
});
test('real iframe preview survives edits, duplicate, delete, undo, save and reload', async ({ page }, testInfo) => {
const cdnRequests = [];
const pageErrors = [];
page.on('request', (request) => {
if (new URL(request.url()).hostname.endsWith('jsdelivr.net')) cdnRequests.push(request.url());
});
page.on('pageerror', (error) => pageErrors.push(error.message));
await openEditor(page);
const source = new URL(fixturePath, page.url()).href;
const id = await insertPlayer(page);
const block = playerBlock(page, id);
await block.getByRole('textbox', { name: 'Recording URL', exact: true }).fill(source);
await block.getByRole('button', { name: 'Apply URL', exact: true }).click();
await readyPreview(block);
await block.locator('.ap-overlay-start .ap-play-button').click();
await expect(block.locator('.ap-term-text')).toContainText('Hello from wp-asciiplayer');
const playback = block.locator('.ap-playback-button');
await expect(playback).toHaveAttribute('aria-label', 'Pause');
await block.locator('.ap-term-text').press('Space');
await expect(playback).toHaveAttribute('aria-label', 'Play');
await block.locator('.ap-term-text').press('Space');
await expect(playback).toHaveAttribute('aria-label', 'Pause');
await block.locator('.ap-term-text').press('Space');
await page.setViewportSize({ width: 900, height: 800 });
await expect(block.locator('.ap-wrapper')).toBeVisible();
const canvasWidth = await page.locator('iframe[name="editor-canvas"]').evaluate((element) => element.clientWidth);
await expect.poll(async () => (await block.locator('.ap-wrapper').boundingBox()).width).toBeLessThanOrEqual(canvasWidth);
await page.setViewportSize({ width: 1280, height: 720 });
await page.evaluate(() => window.wp.data.dispatch('core/edit-post').openGeneralSidebar('edit-post/block'));
// Exercise a real inspector input; data-store operations below cover core's
// block lifecycle without depending on changing toolbar/menu copy.
const speed = page.getByRole('spinbutton', { name: 'Playback speed', exact: true });
await speed.fill('1.5');
await speed.press('Tab');
await expect.poll(() => page.evaluate((clientId) =>
window.wp.data.select('core/block-editor').getBlockAttributes(clientId).speed, id
)).toBe(1.5);
await readyPreview(block);
await page.getByRole('combobox', { name: 'Theme', exact: true }).selectOption('solarized-dark');
await readyPreview(block);
await expect(block.locator('.ap-player')).toHaveClass(/asciinema-player-theme-solarized-dark/);
const duplicateId = await page.evaluate(async (clientId) => {
await window.wp.data.dispatch('core/block-editor').duplicateBlocks([clientId]);
return window.wp.data.select('core/block-editor').getBlocks().find((entry) => entry.name === 'lamgc/asciiplayer' && entry.clientId !== clientId).clientId;
}, id);
await loadPreview(playerBlock(page, duplicateId));
await expect(canvas(page).locator('.asciiplayer-editor__player .ap-wrapper')).toHaveCount(2);
await page.evaluate((clientId) => window.wp.data.dispatch('core/block-editor').removeBlocks([clientId]), duplicateId);
await expect(canvas(page).locator('.asciiplayer-editor__player .ap-wrapper')).toHaveCount(1);
await page.evaluate(() => window.wp.data.dispatch('core/editor').undo());
await loadPreview(playerBlock(page, duplicateId));
await expect(canvas(page).locator('.asciiplayer-editor__player .ap-wrapper')).toHaveCount(2);
await page.evaluate((clientId) => window.wp.data.dispatch('core/block-editor').removeBlocks([clientId]), duplicateId);
const saved = await publish(page, 'Inline preview lifecycle integration');
await screenshot(page, testInfo, 'real-wordpress-editor');
await page.reload();
await page.waitForFunction(() => window.wp?.data?.select('core/block-editor')?.getBlocks().length > 0);
const reloaded = await page.evaluate(() => window.wp.data.select('core/block-editor').getBlocks().find((entry) => entry.name === 'lamgc/asciiplayer'));
expect(reloaded.name).toBe(blockName);
expect(reloaded.attributes).toMatchObject({ source, speed: 1.5, theme: 'solarized-dark' });
await loadPreview(playerBlock(page, reloaded.clientId));
await page.goto(saved.url);
const container = page.locator('.asciiplayer-container');
await expect(container).toHaveCount(1);
expect(JSON.parse(await container.getAttribute('data-ap-options'))).toMatchObject({ speed: 1.5, theme: 'solarized-dark' });
await expectFrontendPlayer(container);
await screenshot(page, testInfo, 'real-wordpress-post');
expect(cdnRequests, 'No plugin runtime may be loaded from jsDelivr').toEqual([]);
expect(pageErrors).toEqual([]);
});
test('a published Page plays its block at desktop and mobile widths', async ({ page }, testInfo) => {
await openEditor(page, 'page');
const source = new URL(fixturePath, page.url()).href;
const id = await insertPlayer(page, { source, controls: 'always', preload: true });
await loadPreview(playerBlock(page, id));
const saved = await publish(page, 'Page playback integration');
await page.goto(saved.url);
const container = page.locator('.asciiplayer-container');
await expectFrontendPlayer(container);
await page.setViewportSize({ width: 375, height: 812 });
await expect(container.locator('.ap-wrapper')).toBeVisible();
const bounds = await container.locator('.ap-wrapper').boundingBox();
expect(bounds.width).toBeLessThanOrEqual(375);
await screenshot(page, testInfo, 'real-wordpress-page-mobile');
});
for (const postType of ['post', 'page']) {
test(`legacy shortcodes play multiple recordings on a ${postType}`, async ({ page }) => {
await openEditor(page, postType);
const source = new URL(fixturePath, page.url()).href;
const content = `[asciiplayer controls="false" autoPlay="true" startAt="0.1" idleTimeLimit="1" speed="1.5" loop="false" style="max-width: 640px;"]${source}[/asciiplayer]\n` +
`[asciiplayer controls="true" theme="solarized-dark" preload="true"]${source}[/asciiplayer]`;
const saved = await createContent(page, postType, content);
await page.goto(saved.link);
const containers = page.locator('.asciiplayer-container');
await expect(containers).toHaveCount(2);
const options = JSON.parse(await containers.nth(0).getAttribute('data-ap-options'));
expect(options).toMatchObject({ autoPlay: true, startAt: 0.1, idleTimeLimit: 1, speed: 1.5, loop: false });
expect(options.controls).toBe(false);
const otherOptions = JSON.parse(await containers.nth(1).getAttribute('data-ap-options'));
expect(otherOptions.controls).toBe(true);
await expectFrontendPlayer(containers.nth(0));
await expect(containers.nth(0).locator('.ap-control-bar')).toHaveCount(0);
await expectFrontendPlayer(containers.nth(1));
});
}
for (const filename of ['missing.cast', 'malformed.cast']) {
test(`a ${filename} recording produces a recoverable inline error`, async ({ page }) => {
const pageErrors = [];
page.on('pageerror', (error) => pageErrors.push(error.message));
await openEditor(page);
const source = new URL(`/wp-content/uploads/asciiplayer-fixtures/${filename}`, page.url()).href;
const id = await insertPlayer(page);
const block = playerBlock(page, id);
await block.getByRole('textbox', { name: 'Recording URL', exact: true }).fill(source);
await block.getByRole('button', { name: 'Apply URL', exact: true }).click();
await expect(block.locator('.asciiplayer-editor__preview')).toHaveAttribute('data-preview-status', 'error');
await expect(block).toContainText('Unable to load the recording.');
await expect(block.getByRole('button', { name: 'Retry preview', exact: true })).toBeVisible();
await block.getByRole('textbox', { name: 'Recording URL', exact: true }).fill(new URL(fixturePath, page.url()).href);
await block.getByRole('button', { name: 'Apply URL', exact: true }).click();
await readyPreview(block);
expect(pageErrors).toEqual([]);
});
}
test('a real cross-origin server without CORS permission shows an error and recovers', async ({ page }) => {
const recording = await readFile(new URL('../fixtures/hello.cast', import.meta.url));
const server = createServer((_request, response) => {
response.writeHead(200, { 'Content-Type': 'text/plain' });
response.end(recording);
});
await new Promise((resolve, reject) => {
server.once('error', reject);
server.listen(0, '127.0.0.1', resolve);
});
try {
await openEditor(page);
const id = await insertPlayer(page);
const block = playerBlock(page, id);
const address = server.address();
const crossOriginSource = `http://127.0.0.1:${address.port}/hello.cast`;
await block.getByRole('textbox', { name: 'Recording URL', exact: true }).fill(crossOriginSource);
await block.getByRole('button', { name: 'Apply URL', exact: true }).click();
await expect(block.locator('.asciiplayer-editor__preview')).toHaveAttribute('data-preview-status', 'error');
await expect(block).toContainText('CORS permissions');
await block.getByRole('textbox', { name: 'Recording URL', exact: true }).fill(new URL(fixturePath, page.url()).href);
await block.getByRole('button', { name: 'Apply URL', exact: true }).click();
await readyPreview(block);
} finally {
server.closeAllConnections();
await new Promise((resolve) => server.close(resolve));
}
});
+4
View File
@@ -0,0 +1,4 @@
{"version":2,"width":64,"height":12,"timestamp":1700000000,"env":{"TERM":"xterm-256color","SHELL":"/bin/sh"},"title":"Local integration fixture"}
[0.0,"o","$ printf 'Hello from wp-asciiplayer'\r\n"]
[0.1,"o","Hello from wp-asciiplayer\r\n"]
[12.0,"o","$ "]
+1
View File
@@ -0,0 +1 @@
This is deliberately not an asciicast recording.
+123
View File
@@ -0,0 +1,123 @@
import test from 'node:test';
import assert from 'node:assert/strict';
import { attributesFromShortcode, booleanValue, canTransformShortcode, finiteNumber, normalizeControls, normalizePlayerOptions, normalizeSource, normalizeWidth, previewHeight, secondsValue } from '../../src/options.js';
test( 'source validation permits explicit web URLs and site-relative cast paths', () => {
for ( const value of [ 'https://example.com/recording.cast', 'http://localhost:8888/a.cast?x=1&y=2', '/uploads/hello.cast', "https://example.com/it's.cast" ] ) assert.equal( normalizeSource( ` ${ value } ` ), value );
} );
test( 'source validation rejects executable, credentialed, ambiguous and malformed URLs', () => {
for ( const value of [ '', '//evil.test/cast', '/\\evil.test/cast', 'javascript:alert(1)', 'data:text/plain,cast', 'blob:https://example.com/a', 'file:///tmp/a.cast', 'https://user:[email protected]/a.cast', 'https://example.com/\na.cast', 'https://example.com/<script>', 'relative.cast', {}, null ] ) assert.equal( normalizeSource( value ), '' );
} );
test( 'source validation rejects a missing HTTP authority separator', () => {
assert.equal( normalizeSource( 'https:example.com/a.cast' ), '' );
} );
test( 'source validation rejects a single HTTP authority slash', () => {
assert.equal( normalizeSource( 'https:/example.com/a.cast' ), '' );
} );
test( 'source validation rejects an extra HTTP authority slash', () => {
assert.equal( normalizeSource( 'https:///example.com/a.cast' ), '' );
} );
test( 'string false options never become true through truthiness', () => {
for ( const value of [ false, 0, 'false', '0', 'off', 'no', ' FALSE ' ] ) assert.equal( booleanValue( value, true ), false );
for ( const value of [ true, 1, 'true', '1', 'on', 'yes' ] ) assert.equal( booleanValue( value ), true );
assert.equal( booleanValue( 'wrong', null ), null );
} );
test( 'controls convert editor choices to the actual player API types', () => {
assert.equal( normalizeControls( 'always' ), true );
assert.equal( normalizeControls( 'true' ), true );
assert.equal( normalizeControls( 'never' ), false );
assert.equal( normalizeControls( 'false' ), false );
assert.equal( normalizeControls( 'auto' ), 'auto' );
assert.equal( normalizeControls( 'unexpected' ), 'auto' );
} );
test( 'numeric validation rejects non-finite, empty and boolean values', () => {
for ( const value of [ Infinity, NaN, '', ' ', {}, null, true, '-1', '101', '0x10', '0b10' ] ) assert.equal( finiteNumber( value, 5, 0, 100 ), 5 );
assert.equal( finiteNumber( '0', 5 ), 0 );
assert.equal( finiteNumber( '1.5', 5 ), 1.5 );
} );
test( 'timecodes match the PHP supported syntax and preserve zero', () => {
assert.equal( secondsValue( '1:02:03.5' ), 3723.5 );
assert.equal( secondsValue( '2:03.5' ), 123.5 );
assert.equal( secondsValue( ' 2:03 ' ), 123 );
for ( const value of [ '1:99', '1:90:00', '1:2:3:4', -1, null ] ) assert.equal( secondsValue( value ), 0 );
} );
test( 'preview forces only its own preload and autoplay settings', () => {
const attributes = { autoPlay: true, preload: false, speed: 2, controls: 'always', loop: true };
const saved = normalizePlayerOptions( attributes );
const preview = normalizePlayerOptions( attributes, { preview: true } );
assert.equal( saved.autoPlay, true );
assert.equal( saved.preload, false );
assert.equal( preview.autoPlay, false );
assert.equal( preview.preload, true );
assert.equal( preview.speed, 2 );
assert.equal( preview.controls, true );
assert.equal( attributes.autoPlay, true );
} );
test( 'zero idle limit omits the override and finite legacy loops survive playback', () => {
assert.equal( Object.hasOwn( normalizePlayerOptions( { idleTimeLimit: 0 } ), 'idleTimeLimit' ), false );
assert.equal( normalizePlayerOptions( { idleTimeLimit: 2 } ).idleTimeLimit, 2 );
assert.equal( normalizePlayerOptions( { loop: 3 } ).loop, 3 );
assert.equal( normalizePlayerOptions( { fit: false } ).fit, 'none' );
} );
test( 'optional dimensions, fonts and posters are validated consistently', () => {
const options = normalizePlayerOptions( { cols: 120, rows: 35, terminalLineHeight: 0.1, terminalFontFamily: '"Fira Code", monospace', terminalFontSize: '1.2rem', poster: 'npt:2:03' } );
assert.equal( options.cols, 120 );
assert.equal( options.rows, 35 );
assert.equal( options.terminalLineHeight, 0.1 );
assert.equal( options.terminalFontSize, '1.2rem' );
assert.equal( options.poster, 'npt:2:03' );
const invalid = normalizePlayerOptions( { cols: 1.5, rows: 1001, terminalLineHeight: 11, terminalFontFamily: 'x; color:red', terminalFontSize: 'url(https://evil.test)', poster: 'https://evil.test/poster' } );
for ( const key of [ 'cols', 'rows', 'terminalLineHeight', 'terminalFontFamily', 'terminalFontSize', 'poster' ] ) assert.equal( Object.hasOwn( invalid, key ), false );
} );
test( 'width accepts safe CSS lengths only', () => {
for ( const value of [ '0', '80%', '640px', '.5rem', '80vw', '30ch' ] ) assert.equal( normalizeWidth( value ), value );
for ( const value of [ 'calc(100% - 1px)', '80%;color:red', 'expression(alert(1))', 'auto', null ] ) assert.equal( normalizeWidth( value ), '100%' );
} );
test( 'legacy height fitting gets a usable preview without changing saved styles', () => {
assert.equal( previewHeight( { fit: 'width' } ), undefined );
assert.equal( previewHeight( { fit: 'height' } ), '320px' );
assert.equal( previewHeight( { fit: 'both', legacyStyle: 'width:80%; height:400px;' } ), '400px' );
assert.equal( previewHeight( { fit: 'height', legacyStyle: 'height:expression(alert(1))' } ), '320px' );
} );
test( 'shortcode conversion maps lowercased legacy names and typed booleans', () => {
const attrs = attributesFromShortcode( { controls: 'false', autoplay: 'true', preload: 'false', startat: '1:23', idletimelimit: '2', fontsize: 'small', fit: 'false', style: 'width: 80%; height: 300px;' }, 'https://example.com/test.cast' );
assert.equal( attrs.source, 'https://example.com/test.cast' );
assert.equal( attrs.controls, 'never' );
assert.equal( attrs.autoPlay, true );
assert.equal( attrs.preload, false );
assert.equal( attrs.startAt, 83 );
assert.equal( attrs.idleTimeLimit, 2 );
assert.equal( attrs.terminalFontSize, 'small' );
assert.equal( attrs.fit, 'none' );
assert.equal( attrs.width, '80%' );
assert.equal( attrs.legacyStyle, 'width: 80%; height: 300px;' );
} );
test( 'shortcode conversion normalizes legacy option case and whitespace like PHP', () => {
const attrs = attributesFromShortcode( { fit: ' HEIGHT ', fontsize: ' 15PX ', fontfamily: ' "Fira Code", monospace ' }, '/example.cast' );
assert.equal( attrs.fit, 'height' );
assert.equal( attrs.terminalFontSize, '15px' );
assert.equal( attrs.terminalFontFamily, '"Fira Code", monospace' );
} );
test( 'lossy transforms are refused, so unsupported legacy shortcodes remain untouched', () => {
assert.equal( canTransformShortcode( { named: { loop: '2' } } ), false );
assert.equal( canTransformShortcode( { named: { customOption: 'x' } } ), false );
assert.equal( canTransformShortcode( { named: { style: 'width:calc(100% - 1px)' } } ), false );
assert.equal( canTransformShortcode( { named: {}, numeric: [ 'unexpected' ] } ), false );
assert.equal( canTransformShortcode( { named: { loop: 'true', controls: 'false', style: 'width:80%;height:300px' } } ), true );
} );
+115
View File
@@ -0,0 +1,115 @@
import test from 'node:test';
import assert from 'node:assert/strict';
import { playerForDocument } from '../../src/player-bridge.js';
const assets = { script: 'https://site.test/player.js', style: 'https://site.test/player.css' };
const flush = () => new Promise( ( resolve ) => setImmediate( resolve ) );
function fakeDocument() {
const nodes = [];
const document = {
defaultView: { setTimeout, clearTimeout },
head: { appendChild( node ) { nodes.push( node ); } },
get scripts() { return nodes.filter( ( node ) => node.tag === 'script' ); },
querySelectorAll() { return nodes.filter( ( node ) => node.tag === 'link' ); },
createElement( tag ) {
const events = new Map();
return {
tag, dataset: {},
addEventListener( name, callback ) { events.set( name, callback ); },
removeEventListener( name ) { events.delete( name ); },
dispatch( name ) { events.get( name )?.(); },
remove() { const index = nodes.indexOf( this ); if ( index >= 0 ) nodes.splice( index, 1 ); },
};
},
};
return { document, nodes };
}
test( 'bridge waits for CSS even if the player global already exists', async () => {
const { document, nodes } = fakeDocument();
const api = { create() {} };
document.defaultView.AsciinemaPlayer = api;
let resolved = false;
const pending = playerForDocument( document, assets ).then( ( value ) => { resolved = true; return value; } );
await flush();
assert.equal( resolved, false );
assert.equal( nodes.length, 1 );
nodes[ 0 ].sheet = {};
nodes[ 0 ].dispatch( 'load' );
assert.equal( await pending, api );
assert.equal( nodes.length, 1 );
} );
test( 'parallel previews deduplicate local CSS and script and use the canvas realm', async () => {
const { document, nodes } = fakeDocument();
const first = playerForDocument( document, assets );
const second = playerForDocument( document, assets );
assert.equal( nodes.length, 1 );
nodes[ 0 ].sheet = {};
nodes[ 0 ].dispatch( 'load' );
await flush();
assert.equal( document.scripts.length, 1 );
const canvasAPI = { create() {} };
document.defaultView.AsciinemaPlayer = canvasAPI;
document.scripts[ 0 ].dispatch( 'load' );
assert.equal( await first, canvasAPI );
assert.equal( await second, canvasAPI );
assert.equal( await playerForDocument( document, assets ), canvasAPI );
} );
test( 'failed owned script is removed and retry starts a new request', async () => {
const { document, nodes } = fakeDocument();
const pending = playerForDocument( document, assets );
const rejection = assert.rejects( pending, /script could not be loaded/ );
nodes[ 0 ].sheet = {};
nodes[ 0 ].dispatch( 'load' );
await flush();
document.scripts[ 0 ].dispatch( 'error' );
await rejection;
assert.equal( document.scripts.length, 0 );
const retry = playerForDocument( document, assets );
await flush();
assert.equal( document.scripts.length, 1 );
document.defaultView.AsciinemaPlayer = { create() {} };
document.scripts[ 0 ].dispatch( 'load' );
assert.equal( await retry, document.defaultView.AsciinemaPlayer );
} );
test( 'failed stylesheet is retryable and never starts a script before CSS', async () => {
const { document, nodes } = fakeDocument();
const pending = playerForDocument( document, assets );
const rejection = assert.rejects( pending, /stylesheet could not be loaded/ );
nodes[ 0 ].dispatch( 'error' );
await rejection;
assert.equal( nodes.length, 0 );
const retry = playerForDocument( document, assets );
assert.equal( nodes.length, 1 );
nodes[ 0 ].sheet = {};
nodes[ 0 ].dispatch( 'load' );
await flush();
document.defaultView.AsciinemaPlayer = { create() {} };
document.scripts[ 0 ].dispatch( 'load' );
await retry;
} );
test( 'existing enqueued assets are reused while still loading', async () => {
const { document, nodes } = fakeDocument();
const style = document.createElement( 'link' );
style.href = assets.style;
style.sheet = {};
const script = document.createElement( 'script' );
script.src = assets.script;
document.head.appendChild( style );
document.head.appendChild( script );
const pending = playerForDocument( document, assets );
await flush();
assert.equal( nodes.length, 2 );
document.defaultView.AsciinemaPlayer = { create() {} };
script.dispatch( 'load' );
assert.equal( await pending, document.defaultView.AsciinemaPlayer );
} );
test( 'document with no window fails cleanly', async () => {
await assert.rejects( playerForDocument( { defaultView: null }, assets ), /canvas is unavailable/ );
} );
+107
View File
@@ -0,0 +1,107 @@
<?php
/**
* Minimal WordPress API doubles for focused, dependency-free plugin tests.
* These verify plugin contracts, not the implementation of WordPress sanitizers.
* Real WordPress/browser integration tests cover the latter separately.
*/
define( 'ABSPATH', __DIR__ . '/' );
define( 'PLUGIN_VERSION', 'some-other-plugin' );
$GLOBALS['ap_test'] = array();
function ap_record( $key, $value ) {
$GLOBALS['ap_test'][ $key ][] = $value;
}
function add_action( $hook, $callback, $priority = 10 ) {
ap_record( 'actions', array( $hook, $callback, $priority ) );
}
function add_shortcode( $tag, $callback ) {
ap_record( 'shortcodes', array( $tag, $callback ) );
}
function add_option() {
throw new RuntimeException( 'Plugin must not write options on requests.' );
}
function update_option() {
throw new RuntimeException( 'Plugin must not write options on requests.' );
}
function plugin_dir_url( $file ) {
return 'https://wordpress.test/wp-content/plugins/wp-asciiplayer/';
}
function add_query_arg( $key, $value, $url ) {
return $url . ( false === strpos( $url, '?' ) ? '?' : '&' ) . rawurlencode( $key ) . '=' . rawurlencode( $value );
}
function wp_parse_url( $value ) {
return parse_url( $value );
}
function esc_url_raw( $value, $protocols = null ) {
return $value;
}
function esc_url( $value, $protocols = null ) {
return htmlspecialchars( $value, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8' );
}
function esc_attr( $value ) {
return htmlspecialchars( $value, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8' );
}
function get_block_wrapper_attributes( $attributes ) {
ap_record( 'block_wrappers', $attributes );
$output = array();
foreach ( $attributes as $key => $value ) {
if ( '' !== $value ) {
$output[] = $key . '="' . esc_attr( $value ) . '"';
}
}
return implode( ' ', $output );
}
function wp_json_encode( $value, $flags = 0 ) {
return json_encode( $value, $flags );
}
function safecss_filter_attr( $style ) {
ap_record( 'filtered_css', $style );
$safe = array();
foreach ( explode( ';', $style ) as $declaration ) {
if ( preg_match( '/^\s*(width|height|color)\s*:\s*([a-z0-9.%# -]+)\s*$/i', $declaration ) ) {
$safe[] = trim( $declaration );
}
}
return implode( ';', $safe );
}
function wp_register_script( $handle, $src, $deps, $version, $footer ) {
$GLOBALS['ap_test']['scripts'][ $handle ] = compact( 'src', 'deps', 'version', 'footer' );
}
function wp_register_style( $handle, $src, $deps, $version ) {
$GLOBALS['ap_test']['styles'][ $handle ] = compact( 'src', 'deps', 'version' );
}
function wp_add_inline_script( $handle, $source, $position ) {
ap_record( 'inline_scripts', compact( 'handle', 'source', 'position' ) );
}
function wp_set_script_translations( $handle, $domain, $path ) {
ap_record( 'translations', compact( 'handle', 'domain', 'path' ) );
}
function register_block_type( $path, $args ) {
ap_record( 'blocks', compact( 'path', 'args' ) );
}
function wp_enqueue_script( $handle ) {
$GLOBALS['ap_test']['enqueued_scripts'][ $handle ] = true;
}
function wp_enqueue_style( $handle ) {
$GLOBALS['ap_test']['enqueued_styles'][ $handle ] = true;
}
function wp_style_is( $handle, $status ) {
return ! empty( $GLOBALS['ap_test'][ 'done' === $status ? 'printed_styles' : 'enqueued_styles' ][ $handle ] );
}
function wp_print_styles( $handles ) {
foreach ( $handles as $handle ) {
$GLOBALS['ap_test']['printed_styles'][ $handle ] = true;
ap_record( 'style_print_calls', $handle );
}
}
function has_shortcode( $content, $tag ) {
return false !== strpos( $content, '[' . $tag );
}
function has_block( $name, $content ) {
return false !== strpos( $content, '<!-- wp:' . $name );
}
require dirname( __DIR__, 2 ) . '/asciicast-player.php';
+160
View File
@@ -0,0 +1,160 @@
<?php
/** Run with PHP 7.4+: php tests/php/run.php */
require __DIR__ . '/bootstrap.php';
use function LamGC\Asciiplayer\enqueue_content_assets;
use function LamGC\Asciiplayer\normalize_attributes;
use function LamGC\Asciiplayer\parse_boolean;
use function LamGC\Asciiplayer\parse_number;
use function LamGC\Asciiplayer\parse_time;
use function LamGC\Asciiplayer\player_options;
use function LamGC\Asciiplayer\player_style;
use function LamGC\Asciiplayer\print_late_styles;
use function LamGC\Asciiplayer\register_plugin;
use function LamGC\Asciiplayer\render_block;
use function LamGC\Asciiplayer\render_player;
use function LamGC\Asciiplayer\render_shortcode;
use function LamGC\Asciiplayer\sanitize_source;
$assertions = 0;
function same( $expected, $actual, $message ) {
global $assertions;
++$assertions;
if ( $expected !== $actual ) {
throw new RuntimeException( $message . '\nExpected: ' . var_export( $expected, true ) . '\nActual: ' . var_export( $actual, true ) );
}
}
function contains( $needle, $haystack, $message ) {
same( true, false !== strpos( $haystack, $needle ), $message );
}
function options_from( $html ) {
if ( ! preg_match( '/ data-ap-options="([^"]*)"/', $html, $matches ) ) {
throw new RuntimeException( 'Missing JSON data-ap-options.' );
}
return json_decode( html_entity_decode( $matches[1], ENT_QUOTES, 'UTF-8' ), true );
}
function reset_enqueues() {
$GLOBALS['ap_test']['enqueued_scripts'] = array();
$GLOBALS['ap_test']['enqueued_styles'] = array();
}
try {
// All registration happens in init, without global option writes/collisions.
same( 'some-other-plugin', PLUGIN_VERSION, 'No shared global version constant.' );
same( false, function_exists( 'is_assoc' ), 'No generic global helper functions.' );
same( 3, count( $GLOBALS['ap_test']['actions'] ), 'Register init, enqueue and late-style hooks.' );
foreach ( $GLOBALS['ap_test']['actions'] as $action ) {
same( true, is_callable( $action[1] ), 'Every registered callback is callable.' );
}
register_plugin();
same( 'asciiplayer', $GLOBALS['ap_test']['shortcodes'][0][0], 'Legacy shortcode stays registered.' );
contains( '/build', $GLOBALS['ap_test']['blocks'][0]['path'], 'Block uses local metadata.' );
same( 'LamGC\\Asciiplayer\\render_block', $GLOBALS['ap_test']['blocks'][0]['args']['render_callback'], 'Block is server rendered.' );
foreach ( $GLOBALS['ap_test']['scripts'] as $script ) {
contains( 'https://wordpress.test/wp-content/plugins/wp-asciiplayer/build/', $script['src'], 'Runtime JavaScript is locally bundled.' );
same( true, $script['footer'], 'Scripts load in footer.' );
}
same( true, in_array( 'lamgc-asciiplayer-player', $GLOBALS['ap_test']['scripts']['lamgc-asciiplayer-view']['deps'], true ), 'Frontend adapter depends on local player.' );
same( true, in_array( 'wp-i18n', $GLOBALS['ap_test']['scripts']['lamgc-asciiplayer-view']['deps'], true ), 'Frontend translations dependency registered.' );
same( array( 'lamgc-asciiplayer-player-style' ), $GLOBALS['ap_test']['styles']['lamgc-asciiplayer-style']['deps'], 'Frontend style depends on player stylesheet.' );
contains( 'window.wpAsciiplayerSettings = ', $GLOBALS['ap_test']['inline_scripts'][0]['source'], 'Editor iframe receives trusted runtime URLs.' );
same( 'before', $GLOBALS['ap_test']['inline_scripts'][0]['position'], 'Settings are available before editor boot.' );
contains( 'player.js?ver=1.1.0', $GLOBALS['ap_test']['inline_scripts'][0]['source'], 'Bridge JavaScript cache-buster matches registered asset version.' );
contains( 'player.css?ver=1.1.0', $GLOBALS['ap_test']['inline_scripts'][0]['source'], 'Bridge CSS cache-buster matches registered asset version.' );
// Source validation covers script, HTML, credential and relative-URL attacks.
foreach ( array( '', 'javascript:alert(1)', 'data:text/html,test', 'file:///tmp/a.cast', '//evil.test/a.cast', '/\\evil.test/a.cast', "https://example.test/a\n.cast", 'https://x.test/" onmouseover="alert(1)', 'https://user:[email protected]/a.cast', 'https:///a.cast', 'a.cast', array( 'bad' ) ) as $source ) {
same( '', sanitize_source( $source ), 'Reject unsafe or unsupported source.' );
}
foreach ( array( 'https://example.test/a.cast', 'http://example.test:8080/a.cast?x=1&y=2', '/wp-content/uploads/demo.cast', '/cast/demo.cast?x=1&y=2' ) as $source ) {
same( $source, sanitize_source( $source ), 'Allow HTTP(S) or root-relative source.' );
}
same( 'https://example.test/a.cast?a=1&b=2', sanitize_source( ' https://example.test/a.cast?a=1&amp;b=2 ' ), 'Decode editor entities and trim outer whitespace.' );
foreach ( array( false, 'false', '0', 0, 'off', 'no' ) as $value ) {
same( false, parse_boolean( $value ), 'Explicit false remains false.' );
}
foreach ( array( true, 'true', '1', 1, 'on', 'yes' ) as $value ) {
same( true, parse_boolean( $value ), 'Recognized true values.' );
}
same( null, parse_boolean( 'garbage' ), 'Unrecognized bool is ignored.' );
same( null, parse_number( 'INF', 0, 100 ), 'Infinity is rejected.' );
same( null, parse_number( true, 0, 100 ), 'Boolean is not a numeric option.' );
same( null, parse_number( -1, 0, 100 ), 'Negative time is rejected.' );
same( 0.0, parse_time( '0' ), 'Zero start time is retained.' );
same( 123.0, parse_time( '2:03' ), 'Legacy mm:ss start time.' );
same( 3723.5, parse_time( '1:02:03.5' ), 'Legacy hh:mm:ss fractional time.' );
same( null, parse_time( '1:99' ), 'Invalid time components are rejected.' );
$attrs = normalize_attributes( array( 'autoplay' => 'false', 'startat' => '2:03', 'idle-time-limit' => '2', 'fontSize' => '15px', 'fontfamily' => "'Fira Code', monospace", 'lineheight' => 1.4, 'logger' => 'console', 'unknown' => 'bad', 0 => 'bare', 'rows' => array( 2 ) ) );
same( false, isset( $attrs['unknown'] ), 'Unknown attributes are discarded.' );
same( false, isset( $attrs['logger'] ), 'No executable logger objects.' );
same( false, isset( $attrs['rows'] ), 'Nested option values are discarded.' );
$options = player_options( $attrs );
same( false, $options['autoPlay'], 'Lowercase autoplay normalizes and respects false.' );
same( 123.0, $options['startAt'], 'Lowercase startAt normalizes.' );
same( 2.0, $options['idleTimeLimit'], 'Hyphenated idleTimeLimit normalizes.' );
same( '15px', $options['terminalFontSize'], 'Legacy font aliases map to current player options.' );
same( "'Fira Code', monospace", $options['terminalFontFamily'], 'Font lists survive safely.' );
same( 1.4, $options['terminalLineHeight'], 'Legacy lineHeight maps to current name.' );
$options = player_options( normalize_attributes( array( 'controls' => 'false', 'preload' => 'false', 'loop' => 'false', 'fit' => 'false' ) ) );
same( false, $options['controls'], 'controls=false is a boolean, never a truthy string.' );
same( false, $options['preload'], 'preload=false is retained.' );
same( false, $options['loop'], 'loop=false is retained.' );
same( 'none', $options['fit'], 'Legacy fit=false maps to none.' );
foreach ( array( 'always' => true, 'never' => false, 'auto' => 'auto' ) as $input => $expected ) {
same( $expected, player_options( array( 'controls' => $input ) )['controls'], 'Block control enum converts to player API.' );
}
same( 3, player_options( array( 'loop' => '3' ) )['loop'], 'Finite replay counts stay supported in shortcodes.' );
$options = player_options( array( 'speed' => -1, 'idleTimeLimit' => 0, 'cols' => 4.5, 'rows' => 1001, 'startAt' => -1, 'terminalFontSize' => '12px; color:red', 'terminalFontFamily' => 'url(https://bad.test)', 'theme' => 'x" onclick=bad', 'poster' => 'javascript:alert(1)' ) );
same( array( 'autoPlay' => false, 'preload' => true, 'fit' => 'width' ), $options, 'Invalid options omitted, safe defaults retained.' );
same( 'auto/dracula', player_options( array( 'theme' => 'auto/dracula' ) )['theme'], 'Player theme fallback syntax retained.' );
same( 'npt:1:23', player_options( array( 'poster' => 'npt:1:23' ) )['poster'], 'Safe NPT poster supported.' );
same( 'data:text/plain,<Hello>', player_options( array( 'poster' => 'data:text/plain,<Hello>' ) )['poster'], 'Plain terminal-text posters are supported.' );
reset_enqueues();
same( '', render_shortcode( array() ), 'Unclosed/empty shortcode returns no player.' );
same( '', render_player( array( 'source' => 'javascript:alert(1)' ) ), 'Invalid source emits no HTML.' );
same( array(), $GLOBALS['ap_test']['enqueued_scripts'], 'Invalid source does not enqueue assets.' );
$html = render_shortcode( array( 'controls' => 'false', 'autoPlay' => 'true', 'startAt' => '12', 'idleTimeLimit' => '2' ), 'https://example.test/demo.cast?a=1&b=2' );
contains( 'class="asciiplayer-container"', $html, 'Render adapter class.' );
contains( 'data-ap-src="https://example.test/demo.cast?a=1&amp;b=2"', $html, 'URL is escaped for an HTML attribute.' );
same( false, options_from( $html )['controls'], 'Markup has typed JSON false.' );
same( true, options_from( $html )['autoPlay'], 'CamelCase boolean handled.' );
same( 12, options_from( $html )['startAt'], 'JSON numeric startAt.' );
same( 2, options_from( $html )['idleTimeLimit'], 'JSON numeric idleTimeLimit.' );
same( true, isset( $GLOBALS['ap_test']['enqueued_scripts']['lamgc-asciiplayer-view'] ), 'Every valid render requests the frontend adapter.' );
same( true, isset( $GLOBALS['ap_test']['enqueued_styles']['lamgc-asciiplayer-style'] ), 'Every valid render requests CSS.' );
$html = render_block( array( 'source' => '/demo.cast', 'poster' => 'data:text/plain,"><script>alert(1)</script>', 'legacyStyle' => 'width:80%;color:red;background:url(javascript:alert(1))' ) );
same( false, false !== strpos( $html, '<script>' ), 'Poster cannot escape JSON/HTML attribute.' );
contains( 'style="width:80%;color:red"', $html, 'Legacy style passes through WordPress safe CSS filtering.' );
contains( 'background:url(javascript:alert(1))', end( $GLOBALS['ap_test']['filtered_css'] ), 'The original style is supplied to the WordPress sanitizer.' );
same( 'width:80%;height:200px;width:50%;', player_style( array( 'legacyStyle' => 'width:80%;height:200px', 'width' => '50%' ) ), 'Dedicated width control overrides legacy width.' );
same( '', player_style( array( 'width' => '100%;background:red' ) ), 'Width cannot inject extra declarations.' );
same( render_player( array( 'source' => '/demo.cast', 'speed' => 2 ) ), render_block( array( 'source' => '/demo.cast', 'speed' => 2 ) ), 'Block uses shared renderer.' );
same( 'asciiplayer-container', end( $GLOBALS['ap_test']['block_wrappers'] )['class'], 'Block wrapper classes/layout pass through WordPress block supports.' );
// No is_single gate: query lookahead supports Pages and archive results.
reset_enqueues();
$GLOBALS['wp_query'] = (object) array( 'posts' => array( (object) array( 'post_content' => 'Ordinary content' ), (object) array( 'post_content' => '[asciiplayer]/demo.cast[/asciiplayer]' ) ) );
enqueue_content_assets();
same( true, isset( $GLOBALS['ap_test']['enqueued_scripts']['lamgc-asciiplayer-view'] ), 'Shortcode in any queried post receives assets.' );
reset_enqueues();
$GLOBALS['wp_query']->posts = array( (object) array( 'post_content' => '<!-- wp:lamgc/asciiplayer {"source":"/demo.cast"} /-->' ) );
enqueue_content_assets();
same( true, isset( $GLOBALS['ap_test']['enqueued_scripts']['lamgc-asciiplayer-view'] ), 'Block in query receives assets.' );
reset_enqueues();
$GLOBALS['wp_query']->posts = array( (object) array( 'post_content' => 'No players' ), 12 );
enqueue_content_assets();
same( array(), $GLOBALS['ap_test']['enqueued_scripts'], 'Ordinary content does not enqueue runtime.' );
render_shortcode( array(), '/widget.cast' );
print_late_styles();
print_late_styles();
same( array( 'lamgc-asciiplayer-style' ), $GLOBALS['ap_test']['style_print_calls'], 'Late/widget styles print once in the footer.' );
echo "OK: {$assertions} assertions passed.\n";
} catch ( Throwable $error ) {
fwrite( STDERR, $error->getMessage() . "\n" );
exit( 1 );
}
+7
View File
@@ -0,0 +1,7 @@
const path = require('path');
const config = require('@wordpress/scripts/config/webpack.config');
module.exports = {
...config,
entry: { index: path.resolve(__dirname, 'src/index.js'), view: path.resolve(__dirname, 'src/view.js') },
output: { ...config.output, path: path.resolve(__dirname, 'build') },
};